본 포스팅은 생성형 AI의 도움으로 작성했음을 알립니다.
VMware에서 구성한 쿠버네티스 클러스터(CP 1대, Worker 2대)가 있었다. 오랫동안 VM을 켜두지 않았더니 kubectl get nodes 명령어 실행 시 아래와 같은 에러가 발생했다.
couldn't get current server API group list:
Get "https://192.168.52.130:6443/api?timeout=32s": dial tcp 192.168.52.130:6443: connect: connection refused
API Server에 연결이 거부되는 상황이었고, kubelet도 계속 FAILURE 상태로 재시작을 반복하고 있었다.
쿠버네티스는 내부 컴포넌트 간 통신을 TLS 인증서로 보호한다. API Server, etcd, kubelet, controller-manager, scheduler 등이 서로 통신할 때 이 인증서를 사용한다.
kubeadm으로 구성한 클러스터의 인증서 기본 유효기간은 1년이다. 1년이 지나면 인증서가 만료되어 컴포넌트 간 통신이 불가능해지고 클러스터 전체가 다운된다.
| 인증서 | 역할 |
|---|---|
apiserver.crt | API Server TLS 인증서 |
kubelet.conf | kubelet이 API Server에 접근할 때 사용 |
bootstrap-kubelet.conf | kubelet 최초 등록 시 사용 |
etcd 관련 | etcd 클러스터 내부 통신 |
kube-apiserver, etcd, kube-controller-manager, kube-scheduler는 systemd 서비스가 아니라 static pod로 동작한다./etc/kubernetes/manifests/ 디렉토리에 yaml 파일이 있으면 kubelet이 자동으로 컨테이너를 띄우는 방식이다. 따라서 systemctl restart kube-apiserver 같은 명령어는 동작하지 않는다.
이 클러스터의 CP(컨트롤플레인)은 containerd, worker 노드들은 CRI-O를 컨테이너 런타임으로 사용하고 있었다. kubelet은 컨테이너 런타임 소켓(crio.sock, containerd.sock)을 통해 컨테이너를 관리하기 때문에 CRI-O가 중지되어 있으면 kubelet도 동작하지 않는다.
kubeadm certs check-expiration
모든 인증서가 <invalid> 상태로 만료된 것을 확인한다.
kubeadm certs renew all
아래와 같이 각 인증서가 갱신됐다는 메시지가 출력되면 성공이다.
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
...
Done renewing certificates.
cp /etc/kubernetes/admin.conf ~/.kube/config
인증서 갱신 후 API Server 등 static pod들이 새 인증서를 사용하도록 재시작해야 한다. systemctl 로는 재시작이 안 되므로 manifest 파일을 잠깐 이동했다가 되돌리는 방법을 사용한다.
cd /etc/kubernetes/manifests/
mv kube-apiserver.yaml /tmp/
mv kube-controller-manager.yaml /tmp/
mv kube-scheduler.yaml /tmp/
mv etcd.yaml /tmp/
sleep 15
mv /tmp/kube-apiserver.yaml .
mv /tmp/kube-controller-manager.yaml .
mv /tmp/kube-scheduler.yaml .
mv /tmp/etcd.yaml .
kubeadm certs renew all을 해도 kubelet.conf 는 별도로 갱신해야 한다. kubelet.conf 가 만료된 상태면 kubelet이 system:anonymous로 API Server에 접근하여 권한 거부 에러가 발생한다.
kubeadm kubeconfig user --org system:nodes --client-name system:node:rhel-cp > /etc/kubernetes/kubelet.conf
cp /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf
systemctl restart kubelet
worker 노드에서 CRI-O가 중지되어 있으면 kubelet이 동작하지 않는다.
# worker 노드에서 실행
systemctl restart crio
systemctl enable crio
systemctl restart kubelet
enable crio를 함께 실행해두지 않으면 재부팅 시 CRI-O가 자동 시작되지 않아 같은 문제가 반복된다.
worker 노드는 kubeadm certs renew가 동작하지 않는다. CP에서 worker용 kubelet.conf 를 생성해서 배포해야 한다.
CP에서 실행
kubeadm kubeconfig user --org system:nodes --client-name system:node:rhel-worker1 > /tmp/worker1-kubelet.conf
kubeadm kubeconfig user --org system:nodes --client-name system:node:rhel-worker2 > /tmp/worker2-kubelet.conf
scp /tmp/worker1-kubelet.conf hwi1013@rhel-worker1:/tmp/
scp /tmp/worker2-kubelet.conf hwi1013@rhel-worker2:/tmp/
worker1에서 실행
sudo mv /tmp/worker1-kubelet.conf /etc/kubernetes/kubelet.conf
sudo cp /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf
sudo systemctl restart kubelet
worker2에서 동일하게 실행
sudo mv /tmp/worker2-kubelet.conf /etc/kubernetes/kubelet.conf
sudo cp /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf
sudo systemctl restart kubelet
kubectl get nodes
모든 노드가 Ready 상태로 바뀌면 복구 완료다.
NAME STATUS ROLES AGE VERSION
rhel-cp Ready control-plane 626d v1.30.3
rhel-worker1 Ready <none> 626d v1.30.3
rhel-worker2 Ready <none> 626d v1.30.3
kubectl get all -A
kubectl get pods -n kube-system
모든 Pod가 Running 상태인지 확인한다.
인증서 만료는 예고 없이 클러스터를 다운시키기 때문에 주기적으로 확인하는 것이 좋다.
# 인증서 만료일 확인
kubeadm certs check-expiration
# 매년 갱신 (만료 전에 미리)
kubeadm certs renew all
CA 인증서는 기본 10년이므로 갱신하지 않아도 되지만, 나머지 인증서는 1년마다 갱신해야 한다.
이 방식 대신 쿠버네티스 버전 업그레이드를 정기적으로(매 1년 이내 업그레이드 수행) 수행해도 된다. 이렇게 할 경우, kubeadm은 클러스터를 최신 상태로 유지하고 합리적으로 보안을 유지한다.