/// <summary> /// Updates a service or container start script on a hive node with a new image. /// </summary> /// <param name="node">The target hive node.</param> /// <param name="scriptName">The script name (without the <b>.sh</b>).</param> /// <param name="image">The fully qualified image name.</param> private static void UpdateStartScript(SshProxy <NodeDefinition> node, string scriptName, string image) { var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{scriptName}.sh"); node.Status = $"edit: {scriptPath}"; if (node.FileExists(scriptPath)) { var curScript = node.DownloadText(scriptPath); var sbNewScript = new StringBuilder(); // Scan for the generated code section and then replace the first // line that looks like: // // TARGET_IMAGE=OLD-IMAGE // // with the new image and then upload the change. using (var reader = new StringReader(curScript)) { var inGenerated = false; var wasEdited = false; foreach (var line in reader.Lines()) { if (wasEdited) { sbNewScript.AppendLine(line); continue; } if (!inGenerated && line.StartsWith(ServiceHelper.ParamSectionMarker)) { inGenerated = true; } if (line.StartsWith("TARGET_IMAGE=")) { sbNewScript.AppendLine($"TARGET_IMAGE={image}"); wasEdited = true; } else { sbNewScript.AppendLine(line); } } } node.UploadText(scriptPath, sbNewScript.ToString(), permissions: "740"); } node.Status = string.Empty; }
/// <summary> /// Update the Elasticsearch container launch scripts to enable automatic /// memory settings based on any cgroup limits. /// </summary> /// <param name="node">The target node.</param> private void UpdateElasticsearch(SshProxy <NodeDefinition> node) { // This method is called for all cluster nodes, even those // that aren't currently hosting Elasticsearch, so we can // update any scripts that may have been orphaned (for // consistency). // // The update consists of replacing the script line that // sets the [ES_JAVA_OPTS] environment variable with: // // --env ES_JAVA_OPTS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \ // // To ensure that this feature is enabled in favor of the // old hacked memory level settings. var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, "neon-log-esdata.sh"); node.InvokeIdempotentAction(GetIdempotentTag("neon-log-esdata"), () => { if (node.FileExists(scriptPath)) { node.Status = $"edit: {scriptPath}"; var orgScript = node.DownloadText(scriptPath); var newScript = new StringBuilder(); foreach (var line in new StringReader(orgScript).Lines()) { if (line.Contains("ES_JAVA_OPTS=")) { newScript.AppendLine(" --env \"ES_JAVA_OPTS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap\" \\"); } else { newScript.AppendLine(line); } } node.UploadText(scriptPath, newScript.ToString(), permissions: ""); node.Status = string.Empty; } }); }
/// <summary> /// Initializes a near virgin server with the basic capabilities required /// for a cluster host node. /// </summary> /// <param name="node">The target cluster node.</param> /// <param name="clusterDefinition">The cluster definition.</param> /// <param name="kubeSetupInfo">Kubernetes setup details.</param> /// <param name="shutdown">Optionally shuts down the node.</param> public static void PrepareNode(SshProxy <NodeDefinition> node, ClusterDefinition clusterDefinition, KubeSetupInfo kubeSetupInfo, bool shutdown = false) { Covenant.Requires <ArgumentNullException>(node != null); Covenant.Requires <ArgumentNullException>(clusterDefinition != null); Covenant.Requires <ArgumentNullException>(kubeSetupInfo != null); if (node.FileExists($"{KubeHostFolders.State}/setup/prepared")) { return; // Already prepared } //----------------------------------------------------------------- // Ensure that the cluster host folders exist. node.CreateHostFolders(); //----------------------------------------------------------------- // Package manager configuration. if (!clusterDefinition.NodeOptions.AllowPackageManagerIPv6) { // Restrict the [apt] package manager to using IPv4 to communicate // with the package mirrors, since IPv6 often doesn't work. node.UploadText("/etc/apt/apt.conf.d/99-force-ipv4-transport", "Acquire::ForceIPv4 \"true\";"); node.SudoCommand("chmod 644 /etc/apt/apt.conf.d/99-force-ipv4-transport"); } // Configure [apt] to retry. node.UploadText("/etc/apt/apt.conf.d/99-retries", $"APT::Acquire::Retries \"{clusterDefinition.NodeOptions.PackageManagerRetries}\";"); node.SudoCommand("chmod 644 /etc/apt/apt.conf.d/99-retries"); //----------------------------------------------------------------- // Other configuration. ConfigureOpenSSH(node, TimeSpan.Zero); node.UploadConfigFiles(clusterDefinition, kubeSetupInfo); node.UploadResources(clusterDefinition, kubeSetupInfo); if (clusterDefinition != null) { ConfigureEnvironmentVariables(node, clusterDefinition); } node.SudoCommand("safe-apt-get update"); node.InvokeIdempotentAction("setup/prep-node", () => { node.Status = "preparing"; node.SudoCommand("setup-prep.sh"); node.Reboot(wait: true); }); // We need to upload the cluster configuration and initialize drives attached // to the node. We're going to assume that these are not already initialized. // $todo(jeff.lill): // // We may need an option that allows an operator to pre-build a hardware // based drive array or something. I'm going to defer this to later and // concentrate on commodity hardware and cloud deployments for now. CommonSteps.ConfigureEnvironmentVariables(node, clusterDefinition); node.Status = "setup: disk"; node.SudoCommand("setup-disk.sh"); // Clear any DHCP leases to be super sure that cloned node // VMs will obtain fresh IP addresses. node.Status = "clear: DHCP leases"; node.SudoCommand("rm -f /var/lib/dhcp/*"); // Indicate that the node has been fully prepared. node.SudoCommand($"touch {KubeHostFolders.State}/setup/prepared"); // Shutdown the node if requested. if (shutdown) { node.Status = "shutdown"; node.SudoCommand("shutdown 0", RunOptions.Defaults | RunOptions.Shutdown); } }