selfhosting.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package selfhosting
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "time"
  19. "k8s.io/klog"
  20. "github.com/pkg/errors"
  21. apps "k8s.io/api/apps/v1"
  22. "k8s.io/api/core/v1"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. clientset "k8s.io/client-go/kubernetes"
  26. clientscheme "k8s.io/client-go/kubernetes/scheme"
  27. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  28. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  29. "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
  30. )
  31. const (
  32. // selfHostingWaitTimeout describes the maximum amount of time a self-hosting wait process should wait before timing out
  33. selfHostingWaitTimeout = 2 * time.Minute
  34. // selfHostingFailureThreshold describes how many times kubeadm will retry creating the DaemonSets
  35. selfHostingFailureThreshold int = 5
  36. )
  37. // CreateSelfHostedControlPlane is responsible for turning a Static Pod-hosted control plane to a self-hosted one
  38. // It achieves that task this way:
  39. // 1. Load the Static Pod specification from disk (from /etc/kubernetes/manifests)
  40. // 2. Extract the PodSpec from that Static Pod specification
  41. // 3. Mutate the PodSpec to be compatible with self-hosting (add the right labels, taints, etc. so it can schedule correctly)
  42. // 4. Build a new DaemonSet object for the self-hosted component in question. Use the above mentioned PodSpec
  43. // 5. Create the DaemonSet resource. Wait until the Pods are running.
  44. // 6. Remove the Static Pod manifest file. The kubelet will stop the original Static Pod-hosted component that was running.
  45. // 7. The self-hosted containers should now step up and take over.
  46. // 8. In order to avoid race conditions, we have to make sure that static pod is deleted correctly before we continue
  47. // Otherwise, there is a race condition when we proceed without kubelet having restarted the API server correctly and the next .Create call flakes
  48. // 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop
  49. func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubeadmapi.InitConfiguration, client clientset.Interface, waiter apiclient.Waiter, dryRun bool, certsInSecrets bool) error {
  50. klog.V(1).Infoln("creating self hosted control plane")
  51. // Adjust the timeout slightly to something self-hosting specific
  52. waiter.SetTimeout(selfHostingWaitTimeout)
  53. // Here the map of different mutators to use for the control plane's PodSpec is stored
  54. klog.V(1).Infoln("getting mutators")
  55. mutators := GetMutatorsFromFeatureGates(certsInSecrets)
  56. if certsInSecrets {
  57. // Upload the certificates and kubeconfig files from disk to the cluster as Secrets
  58. if err := uploadTLSSecrets(client, cfg.CertificatesDir); err != nil {
  59. return err
  60. }
  61. if err := uploadKubeConfigSecrets(client, kubeConfigDir); err != nil {
  62. return err
  63. }
  64. }
  65. for _, componentName := range kubeadmconstants.ControlPlaneComponents {
  66. start := time.Now()
  67. manifestPath := kubeadmconstants.GetStaticPodFilepath(componentName, manifestsDir)
  68. // Since we want this function to be idempotent; just continue and try the next component if this file doesn't exist
  69. if _, err := os.Stat(manifestPath); err != nil {
  70. fmt.Printf("[self-hosted] The Static Pod for the component %q doesn't seem to be on the disk; trying the next one\n", componentName)
  71. continue
  72. }
  73. // Load the Static Pod spec in order to be able to create a self-hosted variant of that file
  74. podSpec, err := loadPodSpecFromFile(manifestPath)
  75. if err != nil {
  76. return err
  77. }
  78. // Build a DaemonSet object from the loaded PodSpec
  79. ds := BuildDaemonSet(componentName, podSpec, mutators)
  80. // Create or update the DaemonSet in the API Server, and retry selfHostingFailureThreshold times if it errors out
  81. if err := apiclient.TryRunCommand(func() error {
  82. return apiclient.CreateOrUpdateDaemonSet(client, ds)
  83. }, selfHostingFailureThreshold); err != nil {
  84. return err
  85. }
  86. // Wait for the self-hosted component to come up
  87. if err := waiter.WaitForPodsWithLabel(BuildSelfHostedComponentLabelQuery(componentName)); err != nil {
  88. return err
  89. }
  90. // Remove the old Static Pod manifest if not dryrunning
  91. if !dryRun {
  92. if err := os.RemoveAll(manifestPath); err != nil {
  93. return errors.Wrapf(err, "unable to delete static pod manifest for %s ", componentName)
  94. }
  95. }
  96. // Wait for the mirror Pod hash to be removed; otherwise we'll run into race conditions here when the kubelet hasn't had time to
  97. // remove the Static Pod (or the mirror Pod respectively). This implicitly also tests that the API server endpoint is healthy,
  98. // because this blocks until the API server returns a 404 Not Found when getting the Static Pod
  99. staticPodName := fmt.Sprintf("%s-%s", componentName, cfg.NodeRegistration.Name)
  100. if err := waiter.WaitForPodToDisappear(staticPodName); err != nil {
  101. return err
  102. }
  103. // Just as an extra safety check; make sure the API server is returning ok at the /healthz endpoint (although we know it could return a GET answer for a Pod above)
  104. if err := waiter.WaitForAPI(); err != nil {
  105. return err
  106. }
  107. fmt.Printf("[self-hosted] self-hosted %s ready after %f seconds\n", componentName, time.Since(start).Seconds())
  108. }
  109. return nil
  110. }
  111. // BuildDaemonSet is responsible for mutating the PodSpec and returns a DaemonSet which is suitable for self-hosting
  112. func BuildDaemonSet(name string, podSpec *v1.PodSpec, mutators map[string][]PodSpecMutatorFunc) *apps.DaemonSet {
  113. // Mutate the PodSpec so it's suitable for self-hosting
  114. mutatePodSpec(mutators, name, podSpec)
  115. // Return a DaemonSet based on that Spec
  116. return &apps.DaemonSet{
  117. ObjectMeta: metav1.ObjectMeta{
  118. Name: kubeadmconstants.AddSelfHostedPrefix(name),
  119. Namespace: metav1.NamespaceSystem,
  120. Labels: BuildSelfhostedComponentLabels(name),
  121. },
  122. Spec: apps.DaemonSetSpec{
  123. Selector: &metav1.LabelSelector{
  124. MatchLabels: BuildSelfhostedComponentLabels(name),
  125. },
  126. Template: v1.PodTemplateSpec{
  127. ObjectMeta: metav1.ObjectMeta{
  128. Labels: BuildSelfhostedComponentLabels(name),
  129. },
  130. Spec: *podSpec,
  131. },
  132. UpdateStrategy: apps.DaemonSetUpdateStrategy{
  133. // Make the DaemonSet utilize the RollingUpdate rollout strategy
  134. Type: apps.RollingUpdateDaemonSetStrategyType,
  135. },
  136. },
  137. }
  138. }
  139. // BuildSelfhostedComponentLabels returns the labels for a self-hosted component
  140. func BuildSelfhostedComponentLabels(component string) map[string]string {
  141. return map[string]string{
  142. "k8s-app": kubeadmconstants.AddSelfHostedPrefix(component),
  143. }
  144. }
  145. // BuildSelfHostedComponentLabelQuery creates the right query for matching a self-hosted Pod
  146. func BuildSelfHostedComponentLabelQuery(componentName string) string {
  147. return fmt.Sprintf("k8s-app=%s", kubeadmconstants.AddSelfHostedPrefix(componentName))
  148. }
  149. func loadPodSpecFromFile(filePath string) (*v1.PodSpec, error) {
  150. podDef, err := ioutil.ReadFile(filePath)
  151. if err != nil {
  152. return nil, errors.Wrapf(err, "failed to read file path %s", filePath)
  153. }
  154. if len(podDef) == 0 {
  155. return nil, errors.Errorf("file was empty: %s", filePath)
  156. }
  157. codec := clientscheme.Codecs.UniversalDecoder()
  158. pod := &v1.Pod{}
  159. if err = runtime.DecodeInto(codec, podDef, pod); err != nil {
  160. return nil, errors.Wrap(err, "failed decoding pod")
  161. }
  162. return &pod.Spec, nil
  163. }