Exemplo n.º 1
0
        /// <summary>
        /// Constructs a cluster proxy from a cluster login.
        /// </summary>
        /// <param name="kubeContext">The cluster context.</param>
        /// <param name="nodeProxyCreator">
        /// The optional application supplied function that creates a node proxy
        /// given the node name, public address or FQDN, private address, and
        /// the node definition.
        /// </param>
        /// <param name="appendToLog">Optionally have logs appended to an existing log file rather than creating a new one.</param>
        /// <param name="defaultRunOptions">
        /// Optionally specifies the <see cref="RunOptions"/> to be assigned to the
        /// <see cref="SshProxy{TMetadata}.DefaultRunOptions"/> property for the
        /// nodes managed by the cluster proxy.  This defaults to <see cref="RunOptions.None"/>.
        /// </param>
        /// <remarks>
        /// The <paramref name="nodeProxyCreator"/> function will be called for each node in
        /// the cluster definition giving the application the chance to create the management
        /// proxy using the node's SSH credentials and also to specify logging.  A default
        /// creator that doesn't initialize SSH credentials and logging is used if <c>null</c>
        /// is passed.
        /// </remarks>
        public ClusterProxy(
            KubeConfigContext kubeContext,
            NodeProxyCreator nodeProxyCreator = null,
            bool appendToLog             = false,
            RunOptions defaultRunOptions = RunOptions.None)

            : this(kubeContext.Extension.ClusterDefinition, nodeProxyCreator, appendToLog : appendToLog, defaultRunOptions : defaultRunOptions)
        {
            Covenant.Requires <ArgumentNullException>(kubeContext != null);

            this.KubeContext = kubeContext;
        }
Exemplo n.º 2
0
 /// <summary>
 /// Clears all cached items.
 /// </summary>
 private static void ClearCachedItems()
 {
     cachedConfig             = null;
     cachedContext            = null;
     cachedHeadendClient      = null;
     cachedNeonKubeUserFolder = null;
     cachedKubeUserFolder     = null;
     cachedRunFolder          = null;
     cachedLogFolder          = null;
     cachedClustersFolder     = null;
     cachedPasswordsFolder    = null;
     cachedCacheFolder        = null;
     cachedDesktopFolder      = null;
     cachedClientConfig       = null;
     cachedClusterCertificate = null;
     cachedProgramFolder      = null;
     cachedPwshPath           = null;
 }
Exemplo n.º 3
0
        /// <summary>
        /// Sets the current Kubernetes config context.
        /// </summary>
        /// <param name="contextName">The context name of <c>null</c> to clear the current context.</param>
        /// <exception cref="ArgumentException">Thrown if the context specified doesnt exist.</exception>
        public static void SetCurrentContext(KubeContextName contextName)
        {
            if (contextName == null)
            {
                cachedContext         = null;
                Config.CurrentContext = null;
            }
            else
            {
                var newContext = Config.GetContext(contextName);

                if (newContext == null)
                {
                    throw new ArgumentException($"Kubernetes [context={contextName}] does not exist.");
                }

                cachedContext         = newContext;
                Config.CurrentContext = (string)contextName;
            }

            cachedClusterCertificate = null;

            Config.Save();
        }
Exemplo n.º 4
0
 /// <summary>
 /// This is used for special situations for setting up a cluster to
 /// set an uninitialized Kubernetes config context as the current
 /// <see cref="CurrentContext"/>.
 /// </summary>
 /// <param name="context">The context being set or <c>null</c> to reset.</param>
 public static void InitContext(KubeConfigContext context = null)
 {
     cachedContext = context;
 }
Exemplo n.º 5
0
        /// <summary>
        /// Constructs the <see cref="ISetupController"/> to be used for setting up a cluster.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <param name="maxParallel">
        /// Optionally specifies the maximum number of node operations to be performed in parallel.
        /// This <b>defaults to 500</b> which is effectively infinite.
        /// </param>
        /// <param name="unredacted">
        /// Optionally indicates that sensitive information <b>won't be redacted</b> from the setup logs
        /// (typically used when debugging).
        /// </param>
        /// <param name="debugMode">Optionally indicates that the cluster will be prepared in debug mode.</param>
        /// <param name="uploadCharts">
        /// <para>
        /// Optionally specifies that the current Helm charts should be uploaded to replace the charts in the base image.
        /// </para>
        /// <note>
        /// This will be treated as <c>true</c> when <paramref name="debugMode"/> is passed as <c>true</c>.
        /// </note>
        /// </param>
        /// <param name = "clusterspace" > Optionally specifies the clusterspace for the operation.</param>
        /// <param name="neonCloudHeadendUri">Optionally overrides the neonCLOUD headend service URI.  This defaults to <see cref="KubeConst.NeonCloudHeadendUri"/>.</param>
        /// <param name="disableConsoleOutput">
        /// Optionally disables status output to the console.  This is typically
        /// enabled for non-console applications.
        /// </param>
        /// <returns>The <see cref="ISetupController"/>.</returns>
        /// <exception cref="NeonKubeException">Thrown when there's a problem.</exception>
        public static ISetupController CreateClusterSetupController(
            ClusterDefinition clusterDefinition,
            int maxParallel            = 500,
            bool unredacted            = false,
            bool debugMode             = false,
            bool uploadCharts          = false,
            string clusterspace        = null,
            string neonCloudHeadendUri = null,
            bool disableConsoleOutput  = false)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));
            Covenant.Requires <ArgumentException>(maxParallel > 0, nameof(maxParallel));

            neonCloudHeadendUri ??= KubeConst.NeonCloudHeadendUri;

            clusterDefinition.Validate();

            // Determine where the log files should go.

            var logFolder = KubeHelper.LogFolder;

            // Ensure that the [prepare-ok] file in the log folder exists, indicating that
            // the last prepare operation succeeded.

            var prepareOkPath = Path.Combine(logFolder, "prepare-ok");

            if (!File.Exists(prepareOkPath))
            {
                throw new NeonKubeException($"Cannot locate the [{prepareOkPath}] file.  Cluster prepare must have failed.");
            }

            // Clear the log folder except for the [prepare-ok] file.

            if (Directory.Exists(logFolder))
            {
                foreach (var file in Directory.GetFiles(logFolder, "*", SearchOption.TopDirectoryOnly))
                {
                    if (Path.GetFileName(file) != "prepare-ok")
                    {
                        NeonHelper.DeleteFile(file);
                    }
                }
            }
            else
            {
                throw new DirectoryNotFoundException(logFolder);
            }

            // Reload the any KubeConfig file to ensure we're up-to-date.

            KubeHelper.LoadConfig();

            // Do some quick checks to ensure that component versions look reasonable.

            //var kubernetesVersion = new Version(KubeVersions.Kubernetes);
            //var crioVersion       = new Version(KubeVersions.Crio);

            //if (crioVersion.Major != kubernetesVersion.Major || crioVersion.Minor != kubernetesVersion.Minor)
            //{
            //    throw new NeonKubeException($"[{nameof(KubeConst)}.{nameof(KubeVersions.Crio)}={KubeVersions.Crio}] major and minor versions don't match [{nameof(KubeConst)}.{nameof(KubeVersions.Kubernetes)}={KubeVersions.Kubernetes}].");
            //}

            // Initialize the cluster proxy.

            var contextName = KubeContextName.Parse($"root@{clusterDefinition.Name}");
            var kubeContext = new KubeConfigContext(contextName);

            KubeHelper.InitContext(kubeContext);

            ClusterProxy cluster = null;

            cluster = new ClusterProxy(
                hostingManagerFactory:  new HostingManagerFactory(() => HostingLoader.Initialize()),
                operation:              ClusterProxy.Operation.Setup,
                clusterDefinition:      clusterDefinition,
                nodeProxyCreator:       (nodeName, nodeAddress) =>
            {
                var logStream      = new FileStream(Path.Combine(logFolder, $"{nodeName}.log"), FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
                var logWriter      = new StreamWriter(logStream);
                var context        = KubeHelper.CurrentContext;
                var sshCredentials = context.Extension.SshCredentials ?? SshCredentials.FromUserPassword(KubeConst.SysAdminUser, KubeConst.SysAdminPassword);

                return(new NodeSshProxy <NodeDefinition>(nodeName, nodeAddress, sshCredentials, logWriter: logWriter));
            });

            if (unredacted)
            {
                cluster.SecureRunOptions = RunOptions.None;
            }

            // Configure the setup controller.

            var controller = new SetupController <NodeDefinition>($"Setup [{cluster.Definition.Name}] cluster", cluster.Nodes, KubeHelper.LogFolder, disableConsoleOutput: disableConsoleOutput)
            {
                MaxParallel     = maxParallel,
                LogBeginMarker  = "# CLUSTER-BEGIN-SETUP #########################################################",
                LogEndMarker    = "# CLUSTER-END-SETUP-SUCCESS ###################################################",
                LogFailedMarker = "# CLUSTER-END-SETUP-FAILED ####################################################"
            };

            // Load the cluster login information if it exists and when it indicates that
            // setup is still pending, we'll use that information (especially the generated
            // secure SSH password).
            //
            // Otherwise, we'll write (or overwrite) the context file with a fresh context.

            var clusterLoginPath = KubeHelper.GetClusterLoginPath((KubeContextName)$"{KubeConst.RootUser}@{clusterDefinition.Name}");
            var clusterLogin     = ClusterLogin.Load(clusterLoginPath);

            if (clusterLogin == null || !clusterLogin.SetupDetails.SetupPending)
            {
                clusterLogin = new ClusterLogin(clusterLoginPath)
                {
                    ClusterDefinition = clusterDefinition,
                    SshUsername       = KubeConst.SysAdminUser,
                    SetupDetails      = new KubeSetupDetails()
                    {
                        SetupPending = true
                    }
                };

                clusterLogin.Save();
            }

            // Update the cluster node SSH credentials to use the secure password.

            var sshCredentials = SshCredentials.FromUserPassword(KubeConst.SysAdminUser, clusterLogin.SshPassword);

            foreach (var node in cluster.Nodes)
            {
                node.UpdateCredentials(sshCredentials);
            }

            // Configure the setup controller state.

            controller.Add(KubeSetupProperty.Preparing, false);
            controller.Add(KubeSetupProperty.ReleaseMode, KubeHelper.IsRelease);
            controller.Add(KubeSetupProperty.DebugMode, debugMode);
            controller.Add(KubeSetupProperty.MaintainerMode, !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NC_ROOT")));
            controller.Add(KubeSetupProperty.ClusterProxy, cluster);
            controller.Add(KubeSetupProperty.ClusterLogin, clusterLogin);
            controller.Add(KubeSetupProperty.HostingManager, cluster.HostingManager);
            controller.Add(KubeSetupProperty.HostingEnvironment, cluster.HostingManager.HostingEnvironment);
            controller.Add(KubeSetupProperty.ClusterspaceFolder, clusterspace);
            controller.Add(KubeSetupProperty.NeonCloudHeadendUri, neonCloudHeadendUri);
            controller.Add(KubeSetupProperty.Redact, !unredacted);

            // Configure the setup steps.

            controller.AddGlobalStep("resource requirements", KubeSetup.CalculateResourceRequirements);

            cluster.HostingManager.AddSetupSteps(controller);

            controller.AddWaitUntilOnlineStep("connect nodes");
            controller.AddNodeStep("check node OS", (controller, node) => node.VerifyNodeOS());

            controller.AddNodeStep("check image version",
                                   (controller, node) =>
            {
                // Ensure that the node image version matches the current neonKUBE (build) version.

                var imageVersion = node.ImageVersion;

                if (imageVersion == null)
                {
                    throw new Exception($"Node image is not stamped with the image version file: {KubeConst.ImageVersionPath}");
                }

                if (imageVersion != SemanticVersion.Parse(KubeVersions.NeonKube))
                {
                    throw new Exception($"Node image version [{imageVersion}] does not match the neonKUBE version [{KubeVersions.NeonKube}] implemented by the current build.");
                }
            });

            controller.AddNodeStep("disable cloud-init", (controller, node) => node.SudoCommand("touch /etc/cloud/cloud-init.disabled"));
            controller.AddNodeStep("node basics", (controller, node) => node.BaseInitialize(controller, upgradeLinux: false));  // $todo(jefflill): We don't support Linux distribution upgrades yet.
            controller.AddNodeStep("root certificates", (controller, node) => node.UpdateRootCertificates());
            controller.AddNodeStep("setup ntp", (controller, node) => node.SetupConfigureNtp(controller));
            controller.AddNodeStep("cluster metadata", ConfigureMetadataAsync);

            // Perform common configuration for the bootstrap node first.
            // We need to do this so the the package cache will be running
            // when the remaining nodes are configured.

            var configureControlPlaneStepLabel = cluster.Definition.ControlNodes.Count() > 1 ? "setup first control-plane node" : "setup control-plane node";

            controller.AddNodeStep(configureControlPlaneStepLabel,
                                   (controller, node) =>
            {
                node.SetupNode(controller, KubeSetup.ClusterManifest);
            },
                                   (controller, node) => node == cluster.FirstControlNode);

            // Perform common configuration for the remaining nodes (if any).

            if (cluster.Definition.Nodes.Count() > 1)
            {
                controller.AddNodeStep("setup other nodes",
                                       (controller, node) =>
                {
                    node.SetupNode(controller, KubeSetup.ClusterManifest);
                    node.InvokeIdempotent("setup/setup-node-restart", () => node.Reboot(wait: true));
                },
                                       (controller, node) => node != cluster.FirstControlNode);
            }

            if (debugMode)
            {
                controller.AddNodeStep("load images", (controller, node) => node.NodeLoadImagesAsync(controller, downloadParallel: 5, loadParallel: 3));
            }

            controller.AddNodeStep("install helm",
                                   (controller, node) =>
            {
                node.NodeInstallHelm(controller);
            });

            controller.AddNodeStep("install kustomize",
                                   (controller, node) =>
            {
                node.NodeInstallKustomize(controller);
            });

            if (uploadCharts || debugMode)
            {
                controller.AddNodeStep("upload helm charts",
                                       (controller, node) =>
                {
                    cluster.FirstControlNode.SudoCommand($"rm -rf {KubeNodeFolder.Helm}/*");
                    cluster.FirstControlNode.NodeInstallHelmArchive(controller);

                    var zipPath = LinuxPath.Combine(KubeNodeFolder.Helm, "charts.zip");

                    cluster.FirstControlNode.SudoCommand($"unzip {zipPath} -d {KubeNodeFolder.Helm}");
                    cluster.FirstControlNode.SudoCommand($"rm -f {zipPath}");
                },
                                       (controller, node) => node == cluster.FirstControlNode);
            }

            //-----------------------------------------------------------------
            // Cluster setup.

            controller.AddGlobalStep("setup cluster", controller => KubeSetup.SetupClusterAsync(controller));
            controller.AddGlobalStep("persist state",
                                     controller =>
            {
                // Indicate that setup is complete.

                clusterLogin.ClusterDefinition.ClearSetupState();
                clusterLogin.SetupDetails.SetupPending = false;
                clusterLogin.Save();
            });

            //-----------------------------------------------------------------
            // Verify the cluster.

            controller.AddNodeStep("check control-plane nodes",
                                   (controller, node) =>
            {
                KubeDiagnostics.CheckControlNode(node, cluster.Definition);
            },
                                   (controller, node) => node.Metadata.IsControlPane);

            if (cluster.Workers.Count() > 0)
            {
                controller.AddNodeStep("check workers",
                                       (controller, node) =>
                {
                    KubeDiagnostics.CheckWorker(node, cluster.Definition);
                },
                                       (controller, node) => node.Metadata.IsWorker);
            }

            cluster.HostingManager.AddPostSetupSteps(controller);

            // We need to dispose this after the setup controller runs.

            controller.AddDisposable(cluster);

            return(controller);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Removes a neonKUBE related kubecontext if it exists.
        /// </summary>
        /// <param name="context">The context to be removed.</param>
        /// <param name="noSave">Optionally prevent context save after the change.</param>
        public void RemoveContext(KubeConfigContext context, bool noSave = false)
        {
            Covenant.Requires <ArgumentNullException>(context != null, nameof(context));

            for (int i = 0; i < Contexts.Count; i++)
            {
                if (Contexts[i].Name == context.Name)
                {
                    Contexts.RemoveAt(i);
                    break;
                }
            }

            // Remove the referenced cluster and user if they're not
            // referenced by another context (to prevent orphans).

            for (int i = 0; i < Clusters.Count; i++)
            {
                if (Clusters[i].Name == context.Properties.Cluster)
                {
                    Clusters.RemoveAt(i);
                    break;
                }
            }

            for (int i = 0; i < Users.Count; i++)
            {
                if (Users[i].Name == context.Properties.User)
                {
                    Users.RemoveAt(i);
                    break;
                }
            }

            // Clear the current context if the removed context was the current one.

            if (CurrentContext == context.Name)
            {
                CurrentContext = null;
            }

            // Persist as required.

            if (!noSave)
            {
                Save();

                // We need to remove the extension file too (if one exists).

                var extensionPath = Path.Combine(KubeHelper.LoginsFolder, $"{context.Name}.login.yaml");

                try
                {
                    File.Delete(extensionPath);
                }
                catch (IOException)
                {
                    // Intentially ignoring this.
                }
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Adds or updates a kubecontext.
        /// </summary>
        /// <param name="context">The new context.</param>
        /// <param name="cluster">The context cluster information.</param>
        /// <param name="user">The context user information.</param>
        /// <param name="noSave">Optionally prevent context save after the change.</param>
        public void SetContext(KubeConfigContext context, KubeConfigCluster cluster, KubeConfigUser user, bool noSave = false)
        {
            Covenant.Requires <ArgumentNullException>(context != null, nameof(context));
            Covenant.Requires <ArgumentNullException>(cluster != null, nameof(cluster));
            Covenant.Requires <ArgumentNullException>(user != null, nameof(user));
            Covenant.Requires <ArgumentNullException>(context.Properties.Cluster == cluster.Name, nameof(context));
            Covenant.Requires <ArgumentNullException>(context.Properties.User == user.Name, nameof(context));

            var updated = false;

            for (int i = 0; i < Contexts.Count; i++)
            {
                if (Contexts[i].Name == context.Name)
                {
                    Contexts[i] = context;
                    updated     = true;
                    break;
                }
            }

            if (!updated)
            {
                Contexts.Add(context);
            }

            // We also need to add or update the referenced cluster and user properties.

            updated = false;

            for (int i = 0; i < Clusters.Count; i++)
            {
                if (Clusters[i].Name == context.Properties.Cluster)
                {
                    Clusters[i] = cluster;
                    updated     = true;
                    break;
                }
            }

            if (!updated)
            {
                Clusters.Add(cluster);
            }

            updated = false;

            for (int i = 0; i < Users.Count; i++)
            {
                if (Users[i].Name == context.Properties.User)
                {
                    Users[i] = user;
                    updated  = true;
                    break;
                }
            }

            if (!updated)
            {
                Users.Add(user);
            }

            // Persist as required.

            if (!noSave)
            {
                Save();
            }
        }