k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/upgrade/postupgrade.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package upgrade 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 26 "github.com/pkg/errors" 27 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/labels" 31 errorsutil "k8s.io/apimachinery/pkg/util/errors" 32 "k8s.io/apimachinery/pkg/util/sets" 33 clientset "k8s.io/client-go/kubernetes" 34 "k8s.io/klog/v2" 35 36 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" 37 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 38 "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" 39 "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy" 40 "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" 41 nodebootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" 42 kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" 43 patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" 44 "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" 45 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" 46 dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" 47 ) 48 49 // PerformPostUpgradeTasks runs nearly the same functions as 'kubeadm init' would do 50 // Note that the mark-control-plane phase is left out, not needed, and no token is created as that doesn't belong to the upgrade 51 func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, patchesDir string, dryRun bool, out io.Writer) error { 52 var errs []error 53 54 // Upload currently used configuration to the cluster 55 // Note: This is done right in the beginning of cluster initialization; as we might want to make other phases 56 // depend on centralized information from this source in the future 57 if err := uploadconfig.UploadConfiguration(cfg, client); err != nil { 58 errs = append(errs, err) 59 } 60 61 // Create the new, version-branched kubelet ComponentConfig ConfigMap 62 if err := kubeletphase.CreateConfigMap(&cfg.ClusterConfiguration, client); err != nil { 63 errs = append(errs, errors.Wrap(err, "error creating kubelet configuration ConfigMap")) 64 } 65 66 // Write the new kubelet config down to disk and the env file if needed 67 if err := WriteKubeletConfigFiles(cfg, patchesDir, dryRun, out); err != nil { 68 errs = append(errs, err) 69 } 70 71 // Annotate the node with the crisocket information, sourced either from the InitConfiguration struct or 72 // --cri-socket. 73 // TODO: In the future we want to use something more official like NodeStatus or similar for detecting this properly 74 if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil { 75 errs = append(errs, errors.Wrap(err, "error uploading crisocket")) 76 } 77 78 // Create RBAC rules that makes the bootstrap tokens able to get nodes 79 if err := nodebootstraptoken.AllowBoostrapTokensToGetNodes(client); err != nil { 80 errs = append(errs, err) 81 } 82 83 // Create/update RBAC rules that makes the bootstrap tokens able to post CSRs 84 if err := nodebootstraptoken.AllowBootstrapTokensToPostCSRs(client); err != nil { 85 errs = append(errs, err) 86 } 87 88 // Create/update RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically 89 if err := nodebootstraptoken.AutoApproveNodeBootstrapTokens(client); err != nil { 90 errs = append(errs, err) 91 } 92 93 // Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically 94 if err := nodebootstraptoken.AutoApproveNodeCertificateRotation(client); err != nil { 95 errs = append(errs, err) 96 } 97 98 // TODO: Is this needed to do here? I think that updating cluster info should probably be separate from a normal upgrade 99 // Create the cluster-info ConfigMap with the associated RBAC rules 100 // if err := clusterinfo.CreateBootstrapConfigMapIfNotExists(client, kubeadmconstants.GetAdminKubeConfigPath()); err != nil { 101 // return err 102 //} 103 // Create/update RBAC rules that makes the cluster-info ConfigMap reachable 104 if err := clusterinfo.CreateClusterInfoRBACRules(client); err != nil { 105 errs = append(errs, err) 106 } 107 108 if err := PerformAddonsUpgrade(client, cfg, patchesDir, out); err != nil { 109 errs = append(errs, err) 110 } 111 112 return errorsutil.NewAggregate(errs) 113 } 114 115 // PerformAddonsUpgrade performs the upgrade of the coredns and kube-proxy addons. 116 func PerformAddonsUpgrade(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, patchesDir string, out io.Writer) error { 117 unupgradedControlPlanes, err := unupgradedControlPlaneInstances(client, cfg.NodeRegistration.Name) 118 if err != nil { 119 return errors.Wrapf(err, "failed to determine whether all the control plane instances have been upgraded") 120 } 121 if len(unupgradedControlPlanes) > 0 { 122 fmt.Fprintf(out, "[upgrade/addons] skip upgrade addons because control plane instances %v have not been upgraded\n", unupgradedControlPlanes) 123 return nil 124 } 125 126 var errs []error 127 128 // If the coredns ConfigMap is missing, show a warning and assume that the 129 // DNS addon was skipped during "kubeadm init", and that its redeployment on upgrade is not desired. 130 // 131 // TODO: remove this once "kubeadm upgrade apply" phases are supported: 132 // https://github.com/kubernetes/kubeadm/issues/1318 133 var missingCoreDNSConfigMap bool 134 if _, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get( 135 context.TODO(), 136 kubeadmconstants.CoreDNSConfigMap, 137 metav1.GetOptions{}, 138 ); err != nil && apierrors.IsNotFound(err) { 139 missingCoreDNSConfigMap = true 140 } 141 if missingCoreDNSConfigMap { 142 klog.Warningf("the ConfigMaps %q in the namespace %q were not found. "+ 143 "Assuming that a DNS server was not deployed for this cluster. "+ 144 "Note that once 'kubeadm upgrade apply' supports phases you "+ 145 "will have to skip the DNS upgrade manually", 146 kubeadmconstants.CoreDNSConfigMap, 147 metav1.NamespaceSystem) 148 } else { 149 // Upgrade CoreDNS 150 if err := dns.EnsureDNSAddon(&cfg.ClusterConfiguration, client, patchesDir, out, false); err != nil { 151 errs = append(errs, err) 152 } 153 } 154 155 // If the kube-proxy ConfigMap is missing, show a warning and assume that kube-proxy 156 // was skipped during "kubeadm init", and that its redeployment on upgrade is not desired. 157 // 158 // TODO: remove this once "kubeadm upgrade apply" phases are supported: 159 // https://github.com/kubernetes/kubeadm/issues/1318 160 if _, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get( 161 context.TODO(), 162 kubeadmconstants.KubeProxyConfigMap, 163 metav1.GetOptions{}, 164 ); err != nil && apierrors.IsNotFound(err) { 165 klog.Warningf("the ConfigMap %q in the namespace %q was not found. "+ 166 "Assuming that kube-proxy was not deployed for this cluster. "+ 167 "Note that once 'kubeadm upgrade apply' supports phases you "+ 168 "will have to skip the kube-proxy upgrade manually", 169 kubeadmconstants.KubeProxyConfigMap, 170 metav1.NamespaceSystem) 171 } else { 172 // Upgrade kube-proxy 173 if err := proxy.EnsureProxyAddon(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, client, out, false); err != nil { 174 errs = append(errs, err) 175 } 176 } 177 178 return errorsutil.NewAggregate(errs) 179 } 180 181 // unupgradedControlPlaneInstances returns a list of control palne instances that have not yet been upgraded. 182 // 183 // NB. This function can only be called after the current control plane instance has been upgraded already. 184 // Because it determines whether the other control plane instances have been upgraded by checking whether 185 // the kube-apiserver image of other control plane instance is the same as that of this instance. 186 func unupgradedControlPlaneInstances(client clientset.Interface, nodeName string) ([]string, error) { 187 selector := labels.SelectorFromSet(labels.Set(map[string]string{ 188 "component": kubeadmconstants.KubeAPIServer, 189 })) 190 pods, err := client.CoreV1().Pods(metav1.NamespaceSystem).List(context.TODO(), metav1.ListOptions{ 191 LabelSelector: selector.String(), 192 }) 193 if err != nil { 194 return nil, errors.Wrap(err, "failed to list kube-apiserver Pod from cluster") 195 } 196 if len(pods.Items) == 0 { 197 return nil, errors.Errorf("cannot find kube-apiserver Pod by label selector: %v", selector.String()) 198 } 199 200 nodeImageMap := map[string]string{} 201 202 for _, pod := range pods.Items { 203 found := false 204 for _, c := range pod.Spec.Containers { 205 if c.Name == kubeadmconstants.KubeAPIServer { 206 nodeImageMap[pod.Spec.NodeName] = c.Image 207 found = true 208 break 209 } 210 } 211 if !found { 212 return nil, errors.Errorf("cannot find container by name %q for Pod %v", kubeadmconstants.KubeAPIServer, klog.KObj(&pod)) 213 } 214 } 215 216 upgradedImage, ok := nodeImageMap[nodeName] 217 if !ok { 218 return nil, errors.Errorf("cannot find kube-apiserver image for current control plane instance %v", nodeName) 219 } 220 221 unupgradedNodes := sets.New[string]() 222 for node, image := range nodeImageMap { 223 if image != upgradedImage { 224 unupgradedNodes.Insert(node) 225 } 226 } 227 228 if len(unupgradedNodes) > 0 { 229 return sets.List(unupgradedNodes), nil 230 } 231 232 return nil, nil 233 } 234 235 // WriteKubeletConfigFiles writes the kubelet config file to disk, but first creates a backup of any existing one. 236 func WriteKubeletConfigFiles(cfg *kubeadmapi.InitConfiguration, patchesDir string, dryRun bool, out io.Writer) error { 237 // Set up the kubelet directory to use. If dry-running, this will return a fake directory 238 kubeletDir, err := GetKubeletDir(dryRun) 239 if err != nil { 240 // The error here should never occur in reality, would only be thrown if /tmp doesn't exist on the machine. 241 return err 242 } 243 244 // Create a copy of the kubelet config file in the /etc/kubernetes/tmp/ folder. 245 backupDir, err := kubeadmconstants.CreateTempDirForKubeadm(kubeadmconstants.KubernetesDir, "kubeadm-kubelet-config") 246 if err != nil { 247 return err 248 } 249 src := filepath.Join(kubeletDir, kubeadmconstants.KubeletConfigurationFileName) 250 dest := filepath.Join(backupDir, kubeadmconstants.KubeletConfigurationFileName) 251 252 if !dryRun { 253 fmt.Printf("[upgrade] Backing up kubelet config file to %s\n", dest) 254 err := kubeadmutil.CopyFile(src, dest) 255 if err != nil { 256 return errors.Wrap(err, "error backing up the kubelet config file") 257 } 258 } else { 259 fmt.Printf("[dryrun] Would back up kubelet config file to %s\n", dest) 260 } 261 262 errs := []error{} 263 // Write the configuration for the kubelet down to disk so the upgraded kubelet can start with fresh config 264 if err := kubeletphase.WriteConfigToDisk(&cfg.ClusterConfiguration, kubeletDir, patchesDir, out); err != nil { 265 errs = append(errs, errors.Wrap(err, "error writing kubelet configuration to file")) 266 } 267 268 if dryRun { // Print what contents would be written 269 err := dryrunutil.PrintDryRunFile(kubeadmconstants.KubeletConfigurationFileName, kubeletDir, kubeadmconstants.KubeletRunDirectory, os.Stdout) 270 if err != nil { 271 errs = append(errs, errors.Wrap(err, "error printing files on dryrun")) 272 } 273 } 274 return errorsutil.NewAggregate(errs) 275 } 276 277 // GetKubeletDir gets the kubelet directory based on whether the user is dry-running this command or not. 278 func GetKubeletDir(dryRun bool) (string, error) { 279 if dryRun { 280 return kubeadmconstants.CreateTempDirForKubeadm("", "kubeadm-upgrade-dryrun") 281 } 282 return kubeadmconstants.KubeletRunDirectory, nil 283 }