카테고리 보관물: Container

kubectl : certificate has expired or is not yet valid

개요

Kubernetes 클러스터를 운영하다 보면 어느 날 갑자기 kubectl 명령이 동작하지 않고 kubectl certificate has expired or is not yet valid 오류가 발생하는 경우가 있습니다. 이는 kubeadm으로 구성된 클러스터의 API 서버 인증서가 기본 1년 유효기간을 초과했을 때 발생합니다. 이 글에서는 오류 원인을 확인하고 kubeadm certs renew all로 인증서를 갱신하는 전체 절차를 정리합니다.

증상

kubectl 명령 실행 시 아래와 같이 x509 인증서 만료 오류가 반복 출력되며 클러스터에 접근하지 못합니다.

E1218 05:21:48.113070 1685746 memcache.go:265] couldn't get current server API group list: Get "https://x.x.x.x:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2024-12-18T05:21:48+09:00 is after 2024-12-05T15:09:04Z
Unable to connect to the server: tls: failed to verify certificate: x509: certificate has expired or is not yet valid

원인 확인 — 인증서 만료 현황 조회

kubeadm certs check-expiration으로 전체 인증서 만료 상태를 확인합니다. API 서버, etcd, controller-manager, scheduler 등 kubeadm이 관리하는 모든 컴포넌트 인증서의 만료일이 동시에 도래하는 경우가 많습니다.

@:~$ sudo kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
apiserver                  Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
apiserver-etcd-client      Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
apiserver-kubelet-client   Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
controller-manager.conf    Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
etcd-healthcheck-client    Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
etcd-peer                  Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
etcd-server                Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
front-proxy-client         Dec 05, 2024 15:09 UTC   <invalid>       front-proxy-ca          no
scheduler.conf             Dec 05, 2024 15:09 UTC   <invalid>       ca                      no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Dec 03, 2033 15:09 UTC   8y              no
etcd-ca                 Dec 03, 2033 15:09 UTC   8y              no
front-proxy-ca          Dec 03, 2033 15:09 UTC   8y              no

해결 방법 — 인증서 갱신

갱신 전 기존 설정 파일을 백업합니다. 이후 kubeadm certs renew all로 모든 인증서를 일괄 갱신합니다.

# 기존 인증서 백업
sudo cp -pr /etc/kubernetes/ /etc/kubernetes_backup

# 인증서 전체 갱신
@:~$ sudo kubeadm certs renew all
[renew] Reading configuration from the cluster...
[renew] Error reading configuration from the Cluster. Falling back to default configuration

certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed

kubeconfig 갱신 및 컴포넌트 재시작

인증서 갱신 후 kubectl을 사용하는 계정의 홈 디렉토리 ~/.kube/config에도 새 인증서를 덮어써야 합니다. 이후 kube-apiserver, kube-controller-manager, kube-scheduler 프로세스에 SIGHUP을 전달해 재로드하고, kubelet을 재시작합니다.

# admin.conf을 kubectl 사용 계정의 kubeconfig로 복사
sudo cp /etc/kubernetes/admin.conf /home//.kube/config
sudo chown : /home//.kube/config

# 컨트롤 플레인 컴포넌트 SIGHUP (재시작 없이 인증서 재로드)
sudo kill -s SIGHUP $(pidof kube-apiserver)
sudo kill -s SIGHUP $(pidof kube-controller-manager)
sudo kill -s SIGHUP $(pidof kube-scheduler)

# kubelet 재시작
sudo systemctl restart kubelet
sudo systemctl daemon-reload

HA 멀티 마스터 클러스터에서의 인증서 갱신

3개 이상의 control-plane 노드로 구성된 HA 클러스터에서는 각 master 노드에서 개별적으로 인증서 갱신 절차를 진행해야 합니다. 하나의 마스터에서 kubeadm certs renew all을 실행해도 다른 마스터 노드의 인증서는 갱신되지 않습니다. 따라서 모든 control-plane 노드에 SSH 접속하여 같은 절차를 반복합니다.

갱신 완료 후 각 노드의 ~/.kube/config도 업데이트해야 합니다. HA 환경에서는 HAProxy 또는 keepalived가 마스터 VIP를 관리하므로, kubeconfig의 server: 주소가 VIP 주소인지 확인합니다. 만약 단일 마스터 주소가 고정되어 있다면 HA LB 주소로 교체합니다.

결과 확인

kubectl 명령이 정상 동작하면 갱신이 완료된 것입니다. 모든 Pod가 Running 상태인지 확인합니다.

kubectl get pods -A

예방을 위해 인증서 갱신을 자동화하거나, 클러스터 업그레이드 시 kubeadm이 자동으로 인증서를 갱신하는 특성을 활용해 정기 업그레이드 주기를 유지하는 것을 권장합니다. 인증서 유효기간 만료 30일 전에 알림을 보내는 스크립트를 cron으로 등록하면 예고 없는 인증서 만료를 방지할 수 있습니다.

참고

관련 포스트:

참고 문서: kubeadm 인증서 관리 공식 문서 · kubeadm certs 커맨드 레퍼런스

Kubernetes 기본 설치(Version 1.28)

개요

이 글에서는 Ubuntu 22.04 환경에서 kubeadm을 사용하여 Kubernetes 1.28 클러스터를 구축하는 전체 절차를 정리합니다. Kubernetes 1.24부터 Docker shim이 제거되었기 때문에 Docker 런타임을 계속 사용하려면 cri-dockerd를 컨테이너 런타임 인터페이스(CRI)로 별도 설치해야 합니다. CNI 플러그인은 Calico를 사용하며, 마스터 노드 1개와 워커 노드 2개로 구성하는 것을 기준으로 작성하였습니다.

이 가이드는 단일 마스터 클러스터 기준입니다. HA(고가용성) 멀티 마스터 구성은 HAProxy 로드밸런서와 etcd 클러스터 구성이 추가로 필요합니다.

구성 내역

설치 환경은 다음과 같습니다.

Kubernetes 1.28.2
Ubuntu 22.04.3 LTS
Container : cri-dockerd
CNI : calico
구성용 계정 : <admin-user>
Master node : <master-node>
Worker node : <worker-node-01> ~ <worker-node-02>

시작 전 모든 노드에서 swap을 비활성화해야 합니다. kubelet은 swap이 활성화된 환경에서 기본적으로 동작을 거부합니다.

sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#/g' /etc/fstab

[공통]

cri-docker 설치

Kubernetes 1.24 이후 Docker shim이 제거되면서 Docker를 컨테이너 런타임으로 사용하려면 cri-dockerd를 별도로 설치해야 합니다. cri-dockerd는 Mirantis가 관리하는 오픈소스 프로젝트로, Docker Engine과 kubelet 사이의 CRI(Container Runtime Interface) 어댑터 역할을 합니다. 먼저 Docker Engine을 설치한 뒤 cri-dockerd 바이너리를 받아 systemd 서비스로 등록합니다.

Docker cgroup driver를 systemd로 맞추는 것이 중요합니다. kubelet도 기본적으로 systemd cgroup driver를 사용하므로, driver 불일치 시 노드가 NotReady 상태가 됩니다.

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo systemctl enable --now docker && sudo systemctl status docker --no-pager
sudo usermod -aG docker <admin-user>
sudo docker container ls

# cri-docker Install
VER=$(curl -s https://api.github.com/repos/Mirantis/cri-dockerd/releases/latest|grep tag_name | cut -d '"' -f 4|sed 's/v//g')
echo $VER
wget https://github.com/Mirantis/cri-dockerd/releases/download/v${VER}/cri-dockerd-${VER}.amd64.tgz
tar xvf cri-dockerd-${VER}.amd64.tgz
sudo mv cri-dockerd/cri-dockerd /usr/local/bin/

# cri-docker Version Check
cri-dockerd --version

wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service
wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket
sudo mv cri-docker.socket cri-docker.service /etc/systemd/system/
sudo sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service

sudo systemctl daemon-reload
sudo systemctl enable cri-docker.service
sudo systemctl enable --now cri-docker.socket

# cri-docker Active Check
sudo systemctl restart docker && sudo systemctl restart cri-docker
sudo systemctl status cri-docker.socket --no-pager

# Docker cgroup Change Require to Systemd
sudo mkdir /etc/docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF

sudo systemctl restart docker && sudo systemctl restart cri-docker
sudo docker info | grep Cgroup

환경 설정

Kubernetes 네트워킹이 정상 동작하려면 커널 모듈과 sysctl 파라미터를 설정해야 합니다. br_netfilter 모듈은 Linux Bridge를 통과하는 패킷을 iptables/ip6tables에서 처리할 수 있게 해주며, overlay 모듈은 컨테이너 파일시스템에 사용하는 OverlayFS를 위해 필요합니다. net.ipv4.ip_forward = 1은 노드가 패킷을 다른 노드로 포워딩할 수 있도록 허용합니다. 이 설정은 모든 노드(마스터 + 워커)에 적용해야 합니다.

# Kernel Forwarding
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sudo sysctl --system

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 필요한 sysctl 파라미터를 설정하면, 재부팅 후에도 값이 유지된다.
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

# 재부팅하지 않고 sysctl 파라미터 적용하기
sudo sysctl --system

Package 설치

kubeadm, kubelet, kubectl 세 패키지를 설치합니다. 각 패키지의 역할은 다음과 같습니다.

  • kubeadm — 클러스터 초기화(init)와 노드 합류(join)를 담당하는 부트스트랩 도구
  • kubelet — 각 노드에서 실행되며 Pod 생명주기를 관리하는 에이전트
  • kubectl — 클러스터를 제어하는 CLI 클라이언트

apt-mark hold로 패키지를 고정하면 apt upgrade로 인한 의도치 않은 버전 업그레이드를 방지할 수 있습니다. Kubernetes는 컴포넌트 버전을 맞춰 관리해야 하므로 반드시 hold 설정을 유지하고, 업그레이드 시에는 kubeadm upgrade 절차를 따릅니다.

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl

sudo curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://dl.k8s.io/apt/doc/apt-key.gpg && echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

위 방법으로 패키지를 찾지 못하는 경우(E: Unable to locate package kubelet), pkgs.k8s.io 신규 리포지토리를 사용합니다.

$ sudo apt-get install -y kubelet kubeadm kubectl
E: Unable to locate package kubelet
E: Unable to locate package kubeadm
E: Unable to locate package kubectl

# kubelet kubeadm kubectl 설치시 위 에러 발생할 경우 아래 방안으로 설치
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

[Master node]

kubeadm init으로 마스터 노드를 초기화합니다. --cri-socket 옵션으로 cri-dockerd 소켓을 명시하지 않으면 kubeadm이 런타임을 자동 감지하는 과정에서 오류가 발생할 수 있습니다. 초기화가 완료되면 출력 마지막에 워커 노드 join 명령어가 표시됩니다. 이 명령어의 토큰은 24시간 후 만료되므로 즉시 복사해 두거나 kubeadm token create --print-join-command로 재발급합니다.

kubeconfig 설정 후 Calico CNI를 설치합니다. CNI가 설치되기 전까지는 노드 상태가 NotReady로 표시되며, calico.yaml 적용 후 수 분 내에 Ready로 전환됩니다.

sudo kubeadm config images pull --cri-socket unix:///run/cri-dockerd.sock
sudo kubeadm init --cri-socket /var/run/cri-dockerd.sock

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

kubectl get nodes -o wide
kubectl get pods -A
kubectl describe node <master-node>

# Calico CNI 설치
curl https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml -O
kubectl apply -f calico.yaml

kubectl get nodes
kubectl get pod --all-namespaces

[worker node]

워커 노드에서 kubeadm join 명령어를 실행하여 클러스터에 합류합니다. kubeadm init 출력에서 복사한 명령어를 그대로 사용하되, --cri-socket 옵션을 추가해야 합니다. join이 완료된 후 마스터 노드에서 kubectl get nodes를 실행하여 워커 노드가 Ready 상태로 등록되는지 확인합니다.

# kubeadm init 실행시 마지막 출력되는 명령어 사용
sudo kubeadm join x.x.x.x:6443 --token xxxxxxxxxxxx --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxx --cri-socket /var/run/cri-dockerd.sock

kubectl get nodes

kubectl 명령어 자동 완성

kubectl bash completion을 설정하면 Tab 키로 리소스명, 네임스페이스 등을 자동 완성할 수 있어 운영 편의성이 크게 향상됩니다. alias k=kubectl과 함께 설정하면 짧은 명령어로 빠르게 조작할 수 있습니다. 자세한 내용은 kubectl 자동 완성 설정 — Kubernetes 공식 문서를 참고하세요.

echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
source ~/.bashrc

설치 결과 확인

모든 노드가 정상적으로 등록되고 kube-system 파드들이 Running 상태가 되면 클러스터 구성이 완료된 것입니다. 아래 명령어로 최종 상태를 확인합니다.

# 노드 상태 확인 — 모든 노드 Ready 여야 정상
kubectl get nodes -o wide

# 시스템 파드 상태 확인 — 모두 Running 또는 Completed
kubectl get pods -n kube-system

# 클러스터 정보 확인
kubectl cluster-info

워커 노드가 NotReady로 유지될 경우 해당 노드에서 sudo systemctl status kubeletjournalctl -u kubelet -n 50으로 에러 메시지를 확인합니다. 대부분은 cgroup driver 불일치 또는 cri-dockerd 미기동으로 인한 문제입니다.

참고

참고 문서: kubeadm으로 클러스터 생성 — Kubernetes 공식 문서 · Calico 온프레미스 설치 문서