# kubeadm 클러스터 업그레이드 가이드 ## 구성 요소 및 버전 * kubeadm, kubelet, kubectl ## Prerequisites * upgrade 할 kubeadm version 선택 ```bash yum list --showduplicates kubeadm --disableexcludes=kubernetes ``` * 하나의 MINOR 버전에서 다음 MINOR 버전으로, 또는 동일한 MINOR의 PATCH 버전 사이에서만 업그레이드할 수 있다. * 즉, 업그레이드할 때 MINOR 버전을 건너 뛸 수 없다. 예를 들어, 1.y에서 1.y+1로 업그레이드할 수 있지만, 1.y에서 1.y+2로 업그레이드할 수는 없다. * ex) 1.15 버전에서 1.17 버전으로 한번에 업그레이드는 불가능 하다. 1.15 -> 1.16 -> 1.17 스텝을 진행 해야 한다. * runtime으로 crio 사용시, CRI-O 메이저와 마이너 버전은 쿠버네티스 메이저와 마이너 버전이 일치해야 한다. 따라서 업데이트한 쿠버네티스 버전에 따라 crio 버전도 함께 업데이트 한다. ## 폐쇄망 가이드 1. **폐쇄망에서 설치하는 경우** 아래 가이드를 참고 하여 image registry를 먼저 구축한다. * https://github.com/tmax-cloud/install-registry/tree/5.0 2. 사용하는 image repository에 k8s 설치 시 필요한 이미지를 push한다. * 작업 디렉토리 생성 및 환경 설정 ```bash $ mkdir -p ~/k8s-install $ cd ~/k8s-install ``` * 외부 네트워크 통신이 가능한 환경에서 필요한 이미지를 다운받는다. (1.15.x -> 1.17.x으로 upgrade 하는 경우 두 버전의 image들이 모두 필요하다) * v1.16.15 images ```bash $ sudo docker pull k8s.gcr.io/kube-proxy:v1.16.15 $ sudo docker pull k8s.gcr.io/kube-apiserver:v1.16.15 $ sudo docker pull k8s.gcr.io/kube-controller-manager:v1.16.15 $ sudo docker pull k8s.gcr.io/kube-scheduler:v1.16.15 $ sudo docker pull k8s.gcr.io/etcd:3.3.15-0 $ sudo docker pull k8s.gcr.io/coredns:1.6.2 $ sudo docker pull k8s.gcr.io/pause:3.1 ``` * v1.17.8 images ```bash $ sudo docker pull k8s.gcr.io/kube-proxy:v1.17.8 $ sudo docker pull k8s.gcr.io/kube-apiserver:v1.17.8 $ sudo docker pull k8s.gcr.io/kube-controller-manager:v1.17.8 $ sudo docker pull k8s.gcr.io/kube-scheduler:v1.17.8 $ sudo docker pull k8s.gcr.io/etcd:3.4.3-0 $ sudo docker pull k8s.gcr.io/coredns:1.6.5 $ sudo docker pull k8s.gcr.io/pause:3.1 ``` * docker image를 tar로 저장한다. * v1.16.15 images ```bash $ sudo docker save -o kube-proxy-1.16.tar k8s.gcr.io/kube-proxy:v1.16.15 $ sudo docker save -o kube-controller-manager-1.16.tar k8s.gcr.io/kube-apiserver:v1.16.15 $ sudo docker save -o etcd-1.16.tar k8s.gcr.io/etcd:3.3.15-0 $ sudo docker save -o coredns-1.16.tar k8s.gcr.io/coredns:1.6.2 $ sudo docker save -o kube-scheduler-1.16.tar k8s.gcr.io/kube-scheduler:v1.16.15 $ sudo docker save -o kube-apiserver-1.16.tar k8s.gcr.io/kube-apiserver:v1.16.15 $ sudo docker save -o pause-1.16.tar k8s.gcr.io/pause:3.1 ``` * v1.17.8 images ```bash $ sudo docker save -o kube-proxy-1.17.tar k8s.gcr.io/kube-proxy:v1.17.8 $ sudo docker save -o kube-controller-manager-1.17.tar k8s.gcr.io/kube-controller-manager:v1.17.8 $ sudo docker save -o etcd-1.17.tar k8s.gcr.io/etcd:3.4.3-0 $ sudo docker save -o coredns-1.17.tar k8s.gcr.io/coredns:1.6.5 $ sudo docker save -o kube-scheduler-1.17.tar k8s.gcr.io/kube-scheduler:v1.17.8 $ sudo docker save -o kube-apiserver-1.17.tar k8s.gcr.io/kube-apiserver:v1.17.8 $ sudo docker save -o pause-1.17.tar k8s.gcr.io/pause:3.1 ``` 3. 위의 과정에서 생성한 tar 파일들을 폐쇄망 환경으로 이동시킨 뒤 사용하려는 registry에 이미지를 push한다. * v1.16.15 images ```bash $ sudo docker load -i kube-apiserver-1.16.tar $ sudo docker load -i kube-scheduler-1.16.tar $ sudo docker load -i kube-controller-manager-1.16.tar $ sudo docker load -i kube-proxy-1.16.tar $ sudo docker load -i etcd-1.16.tar $ sudo docker load -i coredns-1.16.tar $ sudo docker load -i pause-1.16.tar ``` ```bash $ sudo docker tag k8s.gcr.io/kube-apiserver:v1.16.15 ${REGISTRY}/k8s.gcr.io/kube-apiserver:v1.16.15 $ sudo docker tag k8s.gcr.io/kube-proxy:v1.16.15 ${REGISTRY}/k8s.gcr.io/kube-proxy:v1.16.15 $ sudo docker tag k8s.gcr.io/kube-controller-manager:v1.16.15 ${REGISTRY}/k8s.gcr.io/kube-controller-manager:v1.16.15 $ sudo docker tag k8s.gcr.io/etcd:3.3.15-0 ${REGISTRY}/k8s.gcr.io/etcd:3.3.15-0 $ sudo docker tag k8s.gcr.io/coredns:1.6.2 ${REGISTRY}/k8s.gcr.io/coredns:1.6.2 $ sudo docker tag k8s.gcr.io/kube-scheduler:v1.16.15 ${REGISTRY}/k8s.gcr.io/kube-scheduler:v1.16.15 $ sudo docker tag k8s.gcr.io/pause:3.1 ${REGISTRY}/k8s.gcr.io/pause:3.1 ``` ```bash $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-apiserver:v1.16.15 $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-proxy:v1.16.15 $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-controller-manager:v1.16.15 $ sudo docker push ${REGISTRY}/k8s.gcr.io/etcd:3.3.15-0 $ sudo docker push ${REGISTRY}/k8s.gcr.io/coredns:1.6.2 $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-scheduler:v1.16.15 $ sudo docker push ${REGISTRY}/k8s.gcr.io/pause:3.1 ``` * v1.17.8 images ```bash $ sudo docker load -i kube-apiserver-1.17.tar $ sudo docker load -i kube-scheduler-1.17.tar $ sudo docker load -i kube-controller-manager-1.17.tar $ sudo docker load -i kube-proxy-1.17.tar $ sudo docker load -i etcd-1.17.tar $ sudo docker load -i coredns-1.17.tar $ sudo docker load -i pause-1.17.tar ``` ```bash $ sudo docker tag k8s.gcr.io/kube-apiserver:v1.17.8 ${REGISTRY}/k8s.gcr.io/kube-apiserver:v1.17.8 $ sudo docker tag k8s.gcr.io/kube-proxy:v1.17.8 ${REGISTRY}/k8s.gcr.io/kube-proxy:v1.17.8 $ sudo docker tag k8s.gcr.io/kube-controller-manager:v1.17.8 ${REGISTRY}/k8s.gcr.io/kube-controller-manager:v1.17.8 $ sudo docker tag k8s.gcr.io/etcd:3.4.3-0 ${REGISTRY}/k8s.gcr.io/etcd:3.4.3-0 $ sudo docker tag k8s.gcr.io/coredns:1.6.5 ${REGISTRY}/k8s.gcr.io/coredns:1.6.5 $ sudo docker tag k8s.gcr.io/kube-scheduler:v1.17.8 ${REGISTRY}/k8s.gcr.io/kube-scheduler:v1.17.8 $ sudo docker tag k8s.gcr.io/pause:3.1 ${REGISTRY}/k8s.gcr.io/pause:3.1 ``` ```bash $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-apiserver:v1.17.8 $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-proxy:v1.17.8 $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-controller-manager:v1.17.8 $ sudo docker push ${REGISTRY}/k8s.gcr.io/etcd:3.4.3-0 $ sudo docker push ${REGISTRY}/k8s.gcr.io/coredns:1.6.5 $ sudo docker push ${REGISTRY}/k8s.gcr.io/kube-scheduler:v1.17.8 $ sudo docker push ${REGISTRY}/k8s.gcr.io/pause:3.1 ``` ## Steps 0. [master upgrade](/KUBE_VERSION_UPGRADE_README.md#step0-kubernetes-master-upgrade) 1. [node upgrade](/KUBE_VERSION_UPGRADE_README.md#step1-kubernetes-node-upgrade) ## Step0. kubernetes master upgrade * master에서 kubeadm을 upgrade 한다. ```bash yum install -y kubeadm-설치버전 --disableexcludes=kubernetes ex) yum install -y kubeadm-1.16.0-0 --disableexcludes=kubernetes ex) yum install -y kubeadm-1.17.8-0 --disableexcludes=kubernetes ``` * 버전 확인 ```bash kubeadm version ``` * node drain * node drain 전 체크 사항 * PDB가 존재하는 Pod가 drain하려는 node에 생성되어있는 경우 evict가 제한 되기 때문에, 아래 명령어로 drain이 가능한 상태인지 확인한다. ```bash kubectl get pdb -A or kubectl get pdb -oyaml ``` * ALLOWED DISRUPTIONS 및 drain 시키려는 node의 pod 상태를 확인한다. * PDB의 ALLOWED DISRUPTIONS가 drain을 시도하는 node에 뜬 pod(pdb 설정 pod) 개수보다 적을 경우 아래와 같이 다른 노드로 재스케줄링이 필요하다. * ex) virt-api pod가 drain하려는 node에 2개 떠있는데, ALLOWED DISRUPTIONS는 0 또는 1일 경우 * 해당 조건에 만족하지 않는 경우 'Cannot evict pod as it would violate the pod's disruption budget' 와 같은 에러가 발생할 수 있다. * 해결 방법 * 1) 해당 Pod를 다른 Node로 재스케줄링을 시도한다. ```bash kubectl delete pod ``` * 2) 다른 Node의 리소스 부족, noScheduling 설정 등으로 인해 a번 재스케줄링이 불가할 경우엔 PDB 데이터를 삭제하고 drain한 후에 PDB 데이터를 복구한다. ```bash kubectl get pdb -o yaml > pdb-backup.yaml kubectl drain --ignore-daemonsets --delete-local-data kubectl apply -f pdb-backup.yaml ``` * node drain 실행 * warning: node drain시 해당 node상의 pod가 evict되기 때문에, pod의 local-data의 경우 보존되지 않음 ```bash kubectl drain --ignore-daemonsets --delete-local-data ex) kubectl drain k8s-master --ignore-daemonsets --delete-local-data ``` * 업그레이드 plan 변경 ```bash sudo kubeadm upgrade plan ``` * 업그레이드 시 kubeadm config 변경이 필요할 경우 ```bash sudo kubeadm upgrade plan --config=kubeadm_config.yaml ``` ```bash [upgrade/config] Making sure the configuration is correct: [upgrade/config] Reading configuration from the cluster... [upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml' [preflight] Running pre-flight checks. [upgrade] Running cluster health checks [upgrade] Fetching available versions to upgrade to [upgrade/versions] Cluster version: v1.15.3 [upgrade/versions] kubeadm version: v1.16.0 [upgrade/versions] Latest stable version: v1.16.0 [upgrade/versions] Latest version in the v1.15 series: v1.16.0 Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT CURRENT AVAILABLE Kubelet 1 x v1.15.3 v1.16.0 Upgrade to the latest version in the v1.17 series: COMPONENT CURRENT AVAILABLE API Server v1.15.3 v1.16.0 Controller Manager v1.15.3 v1.16.0 Scheduler v1.15.3 v1.16.0 Kube Proxy v1.15.3 v1.16.0 CoreDNS 1.6.5 1.6.7 Etcd 3.4.3 3.4.3-0 You can now apply the upgrade by executing the following command: kubeadm upgrade apply v1.16.0 _____________________________________________________________________ ``` * 업그레이드 실행 ```bash (1.15.x-> 1.16.x) sudo kubeadm upgrade apply v1.16.x (1.16.x-> 1.17.x) sudo kubeadm upgrade apply v1.17.x ``` ```bash [upgrade/config] Making sure the configuration is correct: [upgrade/config] Reading configuration from the cluster... [upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml' [preflight] Running pre-flight checks. [upgrade] Running cluster health checks [upgrade/version] You have chosen to change the cluster version to "v1.16.0" [upgrade/versions] Cluster version: v1.15.3 [upgrade/versions] kubeadm version: v1.16.0 [upgrade/confirm] Are you sure you want to proceed with the upgrade? [y/N]: y [upgrade/prepull] Will prepull images for components [kube-apiserver kube-controller-manager kube-scheduler etcd] [upgrade/prepull] Prepulling image for component etcd. [upgrade/prepull] Prepulling image for component kube-apiserver. [upgrade/prepull] Prepulling image for component kube-controller-manager. [upgrade/prepull] Prepulling image for component kube-scheduler. [apiclient] Found 1 Pods for label selector k8s-app=upgrade-prepull-kube-controller-manager [apiclient] Found 0 Pods for label selector k8s-app=upgrade-prepull-etcd [apiclient] Found 0 Pods for label selector k8s-app=upgrade-prepull-kube-scheduler [apiclient] Found 1 Pods for label selector k8s-app=upgrade-prepull-kube-apiserver [apiclient] Found 1 Pods for label selector k8s-app=upgrade-prepull-etcd [apiclient] Found 1 Pods for label selector k8s-app=upgrade-prepull-kube-scheduler [upgrade/prepull] Prepulled image for component etcd. [upgrade/prepull] Prepulled image for component kube-apiserver. [upgrade/prepull] Prepulled image for component kube-controller-manager. [upgrade/prepull] Prepulled image for component kube-scheduler. [upgrade/prepull] Successfully prepulled the images for all the control plane components [upgrade/apply] Upgrading your Static Pod-hosted control plane to version "v1.16.0"... ...... [apiclient] Found 1 Pods for label selector component=kube-scheduler [upgrade/staticpods] Component "kube-scheduler" upgraded successfully! [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace [kubelet] Creating a ConfigMap "kubelet-config-1.16" in namespace kube-system with the configuration for the kubelets in the cluster [kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.16" ConfigMap in the kube-system namespace [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials [bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token [bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy [upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.16.0". Enjoy! [upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so. ``` * 적용된 cordon을 해제한다. ```bash kubectl uncordon ex) kubectl uncordon k8s-master ``` * master와 node에 kubelet 및 kubectl을 업그레이드한다. ```bash (1.15.x-> 1.16.x) yum install -y kubelet-1.16.x-0 kubectl-1.16.x-0 --disableexcludes=kubernetes (1.16.x-> 1.17.x) yum install -y kubelet-1.17.x-0 kubectl-1.17.x-0 --disableexcludes=kubernetes ``` * kubelet을 재시작 한다. ```bash sudo systemctl daemon-reload sudo systemctl restart kubelet ``` * 비고 : * master 다중화 구성 클러스터 업그레이드 시에는 다음과 같은 명령어를 실행한다. * 첫번째 컨트롤 플레인 업그레이드 시에는 위에 step을 진행하고, 나머지 컨트롤 플레인 업그레이드 시에는 아래의 명령어를 실행한다. * 추가된 master에서 kubeadm을 upgrade 한다. ```bash yum install -y kubeadm-설치버전 --disableexcludes=kubernetes ex) yum install -y kubeadm-1.16.0-0 --disableexcludes=kubernetes ex) yum install -y kubeadm-1.17.8-0 --disableexcludes=kubernetes ``` * 버전 확인 ```bash kubeadm version ``` * node drain * 추가 컨트롤 플레인에서도 첫번째 컨트롤 플레인 node drain 전 체크 사항을 참고하여 drain 가능한 상태인지 체크한다. * node drain 실행 * node drain시 해당 node상의 pod가 evict되기 때문에, pod의 local-data의 경우 보존되지 않음 ```bash kubectl drain --ignore-daemonsets --delete-local-data ex) kubectl drain k8s-master2 --ignore-daemonsets --delete-local-data ``` * 추가 컨트롤 프레인에서는 해당 명령어를 실행하지 않는다. (sudo kubeadm upgrade plan) * sudo kubeadm upgrade apply 명령어 대신에 sudo kubeadm upgrade node 명령어를 실행한다. ```bash sudo kubeadm upgrade node ``` * 적용된 cordon을 해제한다. ```bash kubectl uncordon ex) kubectl uncordon k8s-master2 ``` * master와 node에 kubelet 및 kubectl을 업그레이드한다. ```bash (1.15.x-> 1.16.x) yum install -y kubelet-1.16.x-0 kubectl-1.16.x-0 --disableexcludes=kubernetes (1.16.x-> 1.17.x) yum install -y kubelet-1.17.x-0 kubectl-1.17.x-0 --disableexcludes=kubernetes ``` * kubelet을 재시작 한다. ```bash sudo systemctl daemon-reload sudo systemctl restart kubelet ``` * 업그레이드 후 노드가 ready -> not ready 상태로 바뀐 경우 * Failed to initialize CSINode: error updating CSINode annotation: timed out waiting for the condition; caused by: the server could not find the requested resource ```bash sudo vi /var/lib/kubelet/config.yaml에 아래 옵션 추가 featureGates: CSIMigration: false sudo systemctl restart kubelet ``` * 업그레이드시 runtime 변경을 하는 경우 (docker -> cri-o) * crio 설치는 https://github.com/tmax-cloud/install-k8s/tree/5.0를 참조한다. ```bash systemctl stop kubelet sudo vi /var/lib/kubelet/kubeadm-flags.env에 옵션 변경 기존 (docker) : KUBELET_KUBEADM_ARGS="--cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.1" 변경 (cri-o) : KUBELET_KUBEADM_ARGS="--container-runtime=remote --cgroup-driver=systemd --container-runtime-endpoint=/var/run/crio/crio.sock" systemctl restart kubelet systemctl restart docker ( #docker image registry node는 systemctl restart docker 명령어를 실행한다. ) ``` ## Step1. kubernetes node upgrade * 워커 노드의 업그레이드 절차는 워크로드를 실행하는 데 필요한 최소 용량을 보장하면서, 한 번에 하나의 노드 또는 한 번에 몇 개의 노드로 실행해야 한다. * 모든 worker node에서 kubeadm을 업그레이드한다. ```bash yum install -y kubeadm-설치버전 --disableexcludes=kubernetes ex) (1.15.x-> 1.16.x) yum install -y kubeadm-1.16.x-0 --disableexcludes=kubernetes ex) (1.15.x-> 1.16.x) yum install -y kubeadm-1.17.x-0 --disableexcludes=kubernetes ``` * node drain * node drain 전 체크 사항 * PDB가 존재하는 Pod가 drain하려는 node에 생성되어있는 경우 evict가 제한 되기 때문에, 아래 명령어로 drain이 가능한 상태인지 확인한다. ```bash kubectl get pdb -A or kubectl get pdb -oyaml ``` * ALLOWED DISRUPTIONS 및 drain 시키려는 node의 pod 상태를 확인한다. * PDB의 ALLOWED DISRUPTIONS가 drain을 시도하는 node에 뜬 pod(pdb 설정 pod) 개수보다 적을 경우 아래와 같이 다른 노드로 재스케줄링이 필요하다. * ex) virt-api pod가 drain하려는 node에 2개 떠있는데, ALLOWED DISRUPTIONS는 0 또는 1일 경우 * 해당 조건에 만족하지 않는 경우 'Cannot evict pod as it would violate the pod's disruption budget' 와 같은 에러가 발생할 수 있다. * 해결 방법 * 1) 해당 Pod를 다른 Node로 재스케줄링을 시도한다. ```bash kubectl delete pod ``` * 2) 다른 Node의 리소스 부족, noScheduling 설정 등으로 인해 a번 재스케줄링이 불가할 경우엔 PDB 데이터를 삭제하고 drain한 후에 PDB 데이터를 복구한다. ```bash kubectl get pdb -o yaml > pdb-backup.yaml kubectl drain --ignore-daemonsets --delete-local-data kubectl apply -f pdb-backup.yaml ``` * node drain 실행 * warning : node drain시 해당 node상의 pod가 evict되기 때문에, pod의 local-data의 경우 보존되지 않음 ```bash kubectl drain --ignore-daemonsets --delete-local-data ex) kubectl drain k8s-node --ignore-daemonsets --delete-local-data ``` * kubelet 구성 업그레이드 ```bash sudo kubeadm upgrade node ``` * kubelet과 kubectl 업그레이드 ```bash (1.15.x-> 1.16.x) yum install -y kubelet-1.16.x-0 kubectl-1.16.x-0 --disableexcludes=kubernetes (1.16.x-> 1.17.x) yum install -y kubelet-1.17.x-0 kubectl-1.17.x-0 --disableexcludes=kubernetes sudo systemctl daemon-reload sudo systemctl restart kubelet ``` * 적용된 cordon을 해제한다. ```bash kubectl uncordon ex) kubectl uncordon k8s-node ``` * 비고 : * 1.16.x -> 1.17.x로 업그레이드시 버전에 맞추어 위에 작업을 실행한다. * 업그레이드 후 노드가 ready -> not ready 상태로 바뀐 경우 * Failed to initialize CSINode: error updating CSINode annotation: timed out waiting for the condition; caused by: the server could not find the requested resource ```bash sudo vi /var/lib/kubelet/config.yaml에 아래 옵션 추가 featureGates: CSIMigration: false sudo systemctl restart kubelet ``` * 업그레이드시 runtime 변경을 하는 경우 (docker -> cri-o) * crio 설치는 https://github.com/tmax-cloud/hypercloud-install-guide/tree/master/K8S_Master#step-1-cri-o-%EC%84%A4%EC%B9%98를 참조한다. ```bash systemctl stop kubelet sudo vi /var/lib/kubelet/kubeadm-flags.env에 옵션 변경 기존 (docker) : KUBELET_KUBEADM_ARGS="--cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.1 변경 (cri-o) : KUBELET_KUBEADM_ARGS="--container-runtime=remote --cgroup-driver=systemd --container-runtime-endpoint=/var/run/crio/crio.sock" systemctl restart kubelet systemctl stop docker ( #docker image registry node는 systemctl restart docker 명령어를 실행한다. ) ```