쿠버네티스 클러스터 TLS 인증서 구조: kubeadm certs 갱신

💡 핵심 요약 (Executive Summary)

본 가이드는 certs 핵심 가이드 및 실전 아키텍처의 핵심 개념, 실전 구성 명령어 및 아키텍처 베스트 프랙티스를 다룹니다. 클라우드 네이티브 및 인프라 엔지니어링 실무에 즉시 활용 가능하도록 체계적으로 정리했습니다.

📑 Table of Contents
자동 스크립트
  • – 만료 이후 갱신시

    부트스트래핑 시도

    부트스트래핑 설정이 되어 있지 않고, 이미 컨트롤 플레인과 kubelet의 인증서가 모두 만료된 최악의 상황을 가정하여 복구 순서를 정리해 드립니다.

    이 경우 API 서버가 죽어있으므로 kubectl이 작동하지 않습니다. 따라서 컨트롤 플레인을 먼저 살린 후, 노드의 kubelet을 순차적으로 살리는 방식으로 진행해야 합니다.

    —

    ### 1단계: 컨트롤 플레인(Master) 인증서 갱신

    가장 먼저 API 서버, 스케줄러, 컨트롤러 매니저가 사용하는 인증서를 갱신해야 클러스터의 “뇌”가 살아납니다.

    1. 현재 만료 상태 확인Bash

    kubeadm certs check-expiration

    2. 모든 컨트롤 플레인 인증서 갱신Bash

    kubeadm certs renew all

    3. 정적 포드(Static Pods) 재시작
    인증서 파일은 갱신되었지만, 이미 메모리에 올라가 있는 kube-apiserver 등은 옛날 인증서를 물고 있습니다. 파일을 다시 읽도록 강제 재시작합니다.Bash

    # 매니페스트 파일을 잠시 옮겼다 다시 가져오면 컨테이너가 재시작됩니다.
    mv /etc/kubernetes/manifests/*.yaml /tmp/
    sleep 10
    mv /tmp/*.yaml /etc/kubernetes/manifests/

    4. 관리자 설정 파일(admin.conf) 갱신kubectl을 사용할 수 있도록 내 로컬의 .kube/config를 새 파일로 교체합니다.Bash

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

    —

    ### 2단계: Kubelet 인증서 수동 갱신 준비 (부트스트랩 미설정 시)

    부트스트래핑(bootstrap-kubelet.conf)이 없다면, kubelet은 스스로 인증서를 받아올 통로가 없습니다. 이때는 kubeadm의 기능을 이용해 임시로 인증 통로를 열어줘야 합니다.

    1. RBAC 권한 허용 (마스터에서 실행)
    kubelet이 CSR(인증서 서명 요청)을 보낼 수 있도록 권한을 일시적으로 허용합니다. (이미 설정되어 있다면 생략 가능)Bash

    kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

    2. 새로운 부트스트랩 토큰 생성Bash

    kubeadm token create --print-join-command

    여기서 생성된 토큰을 복사해두세요.

    —

    ### 3단계: 각 노드(Worker/Master)의 Kubelet 복구

    이제 각 노드로 이동하여 만료된 kubelet-client-current.pem을 무시하고 새 인증서를 발급받게 합니다.

    1. Kubelet 중지 및 기존 인증서 제거Bash

    systemctl stop kubelet
    # 만료된 인증서와 심볼릭 링크 제거
    rm /var/lib/kubelet/pki/kubelet-client-*.pem
    rm /var/lib/kubelet/pki/kubelet-client-current.pem

    2. kubelet.conf 재생성 (중요)
    부트스트래핑이 안 되어 있다면 /etc/kubernetes/kubelet.conf 안의 인증서 정보도 만료되었을 것입니다. kubeadm을 이용해 이 파일을 다시 구성합니다.Bash

    # 노드에서 실행 (마스터에서 받은 토큰과 주소 사용)
    kubeadm join :6443 --token --discovery-token-ca-cert-hash sha256: --control-plane (마스터일 경우만 추가)

    이미 조인된 노드라면 --force 옵션이 필요하거나, 기존 /etc/kubernetes/ 내의 .conf 파일들을 백업 후 실행해야 할 수 있습니다.

    3. Kubelet 시작Bash

    systemctl start kubelet

    —

    ### 4단계: CSR 승인 (마스터에서 실행)

    Kubelet이 살아나면서 마스터에 “나 새로 왔는데 인증 좀 해줘”라고 요청을 보냅니다.

    1. 요청 목록 확인Bash

    kubectl get csr

    2. 승인Bash

    # Pending 상태인 모든 CSR 승인
    kubectl get csr | grep Pending | awk '{print $1}' | xargs kubectl certificate approve

    —

    ### 요약 순서

    1. Master: kubeadm certs renew all 후 Static Pod 재시작.
    2. Master: admin.conf 업데이트하여 kubectl 살리기.
    3. Master: kubeadm token create로 새 통로 확보.
    4. Node: 기존 kubelet-client-*.pem 삭제.
    5. Node: kubeadm join 명령(또는 관련 설정 업데이트)으로 재인증 시도.
    6. Master: kubectl certificate approve로 최종 승인.

    이 절차를 마치면 kubectl get nodes에서 모든 노드가 Ready 상태로 돌아오게 됩니다. 1.23 버전은 특히 kubeadm의 인증서 관리 명령어가 안정화된 버전이므로 위 명령어들이 잘 작동할 것입니다.

    부트스트래핑 무시

    부트스트래핑(Token 기반 자동 조인) 기능을 사용하지 않으면서, 만료된 컨트롤 플레인과 kubelet을 수동으로 복구하는 핵심은 \*\*”직접 인증서 파일을 생성하고 kubeconfig를 수동으로 업데이트”\*\*하는 것입니다.

    이 방식은 kubeadm join이나 token 없이, 오직 kubeadm의 인증서 갱신 기능과 파일 복사만으로 진행됩니다.

    —

    ### 1단계: 컨트롤 플레인(Master) 인증서 수동 갱신

    가장 먼저 ‘인증 기관(CA)’과 API 서버 관련 인증서를 살려야 합니다.

    1. 컨트롤 플레인 인증서 일괄 갱신Bash

    kubeadm certs renew all

    2. 정적 포드(Static Pods) 재시작 (인증서 로드)Bash

    # 매니페스트를 잠시 옮겼다 복사하여 컨테이너 재시작 유도
    mv /etc/kubernetes/manifests/*.yaml /tmp/
    sleep 5
    mv /tmp/*.yaml /etc/kubernetes/manifests/

    3. 관리자용 kubeconfig 업데이트Bash

    cp /etc/kubernetes/admin.conf $HOME/.kube/config

    —

    ### 2단계: Kubelet용 설정 파일(kubelet.conf) 수동 갱신

    부트스트래핑을 쓰지 않는다면, /etc/kubernetes/kubelet.conf 파일 내부에 있는 클라이언트 인증서 데이터를 직접 갱신해줘야 합니다. kubeadm은 이 과정을 위한 전용 명령어를 제공합니다.

    \[모든 노드(Master 포함)에서 수행\]

    1. kubelet 설정 파일 내 인증서 갱신Bash

    kubeadm init phase kubeconfig kubelet --config /etc/kubernetes/kubeadm-config.yaml (또는 기본값 사용 시 생략 가능)

    이 명령은 /etc/kubernetes/kubelet.conf 파일을 새로 생성하며, 내부에 새로운 임베디드 인증서를 포함시킵니다.

    2. 기존의 개별 PEM 파일 정리Bash

    부트스트래핑을 안 쓴다면 /var/lib/kubelet/pki/에 있는 kubelet-client-current.pem이 아니라 /etc/kubernetes/kubelet.conf 내의 데이터를 우선 참조하게 됩니다. 충돌 방지를 위해 기존 파일은 치워둡니다.

    rm -f /var/lib/kubelet/pki/kubelet-client-*.pem
    rm -f /var/lib/kubelet/pki/kubelet-client-current.pem

    —

    ### 3단계: Kubelet 서비스 재시작 및 권한 확인

    이제 각 노드의 kubelet이 새로운 kubelet.conf를 읽고 API 서버와 통신하게 합니다.

    1. Kubelet 재시작Bash

    systemctl restart kubelet

    2. CSR 수동 승인 (필요 시)Bash

    일부 환경에서는 kubelet.conf 갱신 후에도 kubelet이 추가적인 서빙 인증서를 요청할 수 있습니다. 마스터에서 확인 후 승인합니다.

    kubectl get csr
    kubectl certificate approve

    —

    ### 4단계: kubelet-client-current.pem이 다시 생기게 하려면?

    사용자께서 질문 초기에 언급하신 /var/lib/kubelet/pki/kubelet-client-current.pem 파일은 사실 Kubelet의 Rotation 기능이 켜져 있을 때 생성되는 파일입니다.

    부트스트래핑 없이 이 파일을 다시 유지하고 싶다면, kubelet.conf가 갱신된 상태에서 kubelet 설정(config.yaml)에 다음이 포함되어야 합니다:

    – rotateCertificates: true
    – serverTLSBootstrap: true (선택 사항)

    ### 정리된 명령어 흐름도

    단계 실행 위치 명령어
    1. CP 갱신 Master kubeadm certs renew all
    2. 설정 갱신 모든 노드 kubeadm init phase kubeconfig kubelet
    3. 파일 정리 모든 노드 rm -f /var/lib/kubelet/pki/kubelet-client*
    4. 재시작 모든 노드 systemctl restart kubelet

    이렇게 하면 토큰 기반의 부트스트래핑(bootstrap-kubelet.conf) 과정을 거치지 않고도, kubeadm이 생성해주는 정적 설정 파일을 통해 인증서를 수동으로 교체하고 정상 동작시킬 수 있습니다.

    혹시 kubeadm init phase 실행 시 특정 설정 파일을 찾지 못한다는 에러가 발생하나요? 해당 클러스터의 kubeadm-config 덤프가 필요하다면 알려주세요.

근거
Reference & Guidelines (NOTE)

kubeadm으로 생성된 클라이언트 인증서는 1년 후에 만료된다.

bash
# kubectl 명령어 사용시
Unable to connect to the server: x509: certificate has expired or is not yet valid.
인증서 만료시점 조회
shell
sudo kubeadm certs check-expiration
#>>> 출력
#[check-expiration] Reading configuration from the cluster...
#[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
#W0818 13:43:59.620410 4001964 utils.go:69] The recommended value for "clusterDNS" in "KubeletConfiguration" is: [10.233.0.10]; the provided value is: [10.233.0.3]
#
#CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
#admin.conf                 Aug 17, 2024 01:21 UTC   364d            ca                      no
#apiserver                  Aug 17, 2024 01:21 UTC   364d            ca                      no
#apiserver-kubelet-client   Aug 17, 2024 01:21 UTC   364d            ca                      no
#controller-manager.conf    Aug 17, 2024 01:21 UTC   364d            ca                      no
#front-proxy-client         Aug 17, 2024 01:21 UTC   364d            front-proxy-ca          no
#scheduler.conf             Aug 17, 2024 01:21 UTC   364d            ca                      no
#
#CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
#ca                      Aug 14, 2032 13:09 UTC   8y              no
#front-proxy-ca          Aug 14, 2032 13:09 UTC   8y              no
#<<<
수행
작업
bash
# 모든 master 노드
kubeadm certs renew all #or 다른 방법으로 갱신

# 갱신된 kubeapiserver config CLI 인증에 사용
mv ~/.kube/config ~/.kube/config-backup
cp /etc/kubernetes/admin.conf ~/.kube/config

# !! kubeconfig를 커스텀해서 사용중이라면 전체 복사/붙여넣기가 아니라 아래의 내용만 갱신하면 됩니다.
#    client-certificate-data: <crt-sting>
#    client-key-data: <key-string>
추가작업
Reference & Guidelines (NOTE)

아래의 로직중에 kubeadm init phase kubeconfig까지는 kubeadm certs renew all 에서 반영되었을 가능성도 있으니 확인필요합니다.

  • 실제로 admin.conf는 반영된것을 확인함
  • kubelet.conf는 반영되지 않음을 확인함.
  • scheduler.conf, controller-manager.conf 확인필요

static pod 재시작이 되지 않아서 unauthorized 발생가능성이 있음.

  • master nodes
Error Logs (kube-scheduler)
bash
W0820 05:54:38.069851       1 reflector.go:324] vendor/k8s.io/client-go/informers/factory.go:134: failed to list *v1.PersistentVolumeClaim: Unauthorized
  E0820 05:54:38.069907       1 reflector.go:138] vendor/k8s.io/client-go/informers/factory.go:134: Failed to watch *v1.PersistentVolumeClaim: failed to list *v1.PersistentVolumeClaim: Unauthorized
  W0820 05:54:38.943924       1 reflector.go:324] vendor/k8s.io/client-go/informers/factory.go:134: failed to list *v1.StatefulSet: Unauthorized
  E0820 05:54:38.943996       1 reflector.go:138] vendor/k8s.io/client-go/informers/factory.go:134: Failed to watch *v1.StatefulSet: failed to list *v1.StatefulSet: Unauthorized
  W0820 05:54:39.911036       1 reflector.go:324] vendor/k8s.io/client-go/informers/factory.go:134: failed to list *v1.CSINode: Unauthorized
  E0820 05:54:39.911123       1 reflector.go:138] vendor/k8s.io/client-go/informers/factory.go:134: Failed to watch *v1.CSINode: failed to list *v1.CSINode: Unauthorized
  W0820 05:54:41.927021       1 reflector.go:324] vendor/k8s.io/client-go/informers/factory.go:134: failed to list *v1.Pod: Unauthorized
  E0820 05:54:41.927132       1 reflector.go:138] vendor/k8s.io/client-go/informers/factory.go:134: Failed to watch *v1.Pod: failed to list *v1.Pod: Unauthorized
  • 인증서 재발급 하는 법 (필요하지 않을 확률이 높음)
bash
# 인증서 갱신 후 kube-scheduler, kube-controller-manager 인증에러 발생
    # 클라이언트인증서 미사용으로 인함

    # 백업 후 삭제
    mv /etc/kubernetes/scheduler.conf /etc/kubernetes/scheduler.conf.bak
    mv /etc/kubernetes/controller-manager.conf /etc/kubernetes/controller-manager.conf.bak
    mv /etc/kubernetes/kubelet.conf /etc/kubernetes/kubelet.conf.bak

    # 삭제되어있어야 신규발급됨
    kubeadm init phase kubeconfig scheduler
    kubeadm init phase kubeconfig controller-manager
    kubeadm init phase kubeconfig kubelet
  • 재발급한 인증서를 적용하는 법
bash
# kubelet 재시작
    mkdir ~/kubelet-backup
    mv /var/lib/kubelet/pki/* ~/kubelet-backup
    systemctl restart kubelet

    cd /etc/kubernetes/manifests
    # 기존 프로세스 종료
    mv kube-controller-manager.yaml ..
    mv kube-scheduler.yaml ..
    # 중지 확인
    kubectl -n kube-system get pods -l 'component in (kube-controller-manager,kube-scheduler)'
    # 재시작
    mv ../kube-controller-manager.yaml .
    mv ../kube-scheduler.yaml .
  • worker nodes
bash
# 아래와같이 파일로명시되어있으면 자동갱신 되고 있을 가능성이 높음. 마스터노드도 해당설정되어있으면 조치X 
  grep client-certificate /etc/kubernetes/kubelet.conf
      client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
Olds
old

##### 조치방법\*\*\*\*: 각 master node에서 실행\*\*\*\*

1. 백업

shell
# [[ master node ]]
   ## kube pki backup
   sudo mkdir -p $HOME/fcik8s-old-certs/pki
   sudo mkdir -p $HOME/fcik8s-old-certs/kubelet
   sudo mkdir -p $HOME/fcik8s-old-certs/.kube
   sudo cp -p /etc/kubernetes/pki/*.* $HOME/fcik8s-old-certs/pki
   sudo cp -p /etc/kubernetes/*.conf $HOME/fcik8s-old-certs
   sudo cp -p /var/lib/kubelet/pki/* $HOME/fcik8s-old-certs/kubelet
   sudo cp -p ~/.kube/config $HOME/fcik8s-old-certs/.kube/.

2. 갱신

shell
sudo kubeadm certs renew all --config=/etc/kubernetes/kubeadm-config.yaml
   >>>
   W0818 10:21:42.676045 3888005 utils.go:69] The recommended value for "clusterDNS" in "KubeletConfiguration" is: [10.233.0.10]; the provided value is: [10.233.0.3]
   certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
   certificate for serving the Kubernetes API 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 the front proxy client renewed
   certificate embedded in the kubeconfig file for the scheduler manager to use renewed

   Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.
   <<<
   # 확인
   sudo kubeadm certs check-expiration

   # kublet 재시작
   #systemctl daemon-reload&&systemctl restart kubelet

   # control-plane components 종료
   sudo cp -pr /etc/kubernetes/manifest $HOME/fcik8s-old-certs
   sudo rm /etc/kubernetes/manifest/*

   # componets container 프로세스 확인
   sudo nerdctl ps

   # control-plane components 재시작
   sudo cp -p $HOME/fcik8s-old-certs/manifest/* /etc/kubernetes/manifest

   kubectl get pods -n kube-system -l component=kube-apiserver
   kubectl get pods -n kube-system -l component=kube-controller-manager
   kubectl get pods -n kube-system -l component=kube-scheduler
   kubectl get pods -n kube-system -l component=etcd

   kubectl get nodes

3. 문제 추적

shell
# log 확인
   journalctl -u kubelet -f
   journalctl -u etcd -f
   journalctl -u containerd -f
   kubectl logs -n kube-system -l component=kube-apiserver
   kubectl logs -n kube-system -l component=kube-controller-manager
   kubectl logs -n kube-system -l component=kube-scheduler
   kubectl logs -n kube-system -l component=etcd

   #!! kubeadm 1.17 이전의 버전에서 kubeadm init 으로 작성된 Node 에는 kubelet.conf 파일을 수동으로 수정해야 하는 버그가 있다.
   ## 최대 5분동안 적용여부 모니터링 필요
   kubectl get nodes #정상조회 불가하다면 kubelet 구성 혹은 kubeconfig 파일 확인 필요

   ### error: You must be logged in to the server (Unauthorized) 발생시
   ### -> [2] 조치사항 확인

   ### [1] kubelet.conf 미갱신의 경우
   sudo diff $HOME/fcik8s-old-certs/kubelet.conf /etc/kubernetes/kubelet.conf #차이가 없으면 미갱신

   ## 사용 전 분석 필요
   #cd /etc/kubernetes
   sudo kubeadm kubeconfig user --org system:nodes --client-name system:node:$(hostname) > kubelet.conf

   # kubelet.conf에 파일로 명시한다면
   ### [1-2] v1.23.7에서 확인시 갱신하지 않은것이 정상으로 보임
   sudo diff $HOME/fcik8s-old-certs/kubelet.conf /etc/kubernetes/kubelet.conf
   sudo diff $HOME/fcik8s-old-certs/kubelet/kubelet-client-current.pem /var/lib/kubelet/pki/kubelet-client-current.pem
   sudo diff $HOME/fcik8s-old-certs/kubelet/kubelet.key /var/lib/kubelet/pki/kubelet.key
   sudo diff $HOME/fcik8s-old-certs/kubelet/kubelet.crt /var/lib/kubelet/pki/kubelet.crt

   ### [2] kubeconfig 미갱신의 경우
   sudo diff ~/.kube/config $HOME/fcik8s-old-certs/.kube/config #차이가 없으면 미갱신
   cat /etc/kubernetes/kubelet.conf # 내용 참조하여 삽입. 파일의 경우 [여기](<https://www.notion.so/cmd-cm-base64-d7e4a9892c6a4eaf8abc0e9ec1a13684?pvs=21>) 참조
   client-certificate -> ~/.kube./config:client-certificate-data
   client-key -> ~/.kube./config:client-key-data

   systemctl daemon-reload&&systemctl restart kubelet
   kubectl get nodes

4. 참고자료: 이전버전 갱신

[SKT Enterprise](https://www.sktenterprise.com/bizInsight/blogDetail/dev/2918)

shell
# [[ master node ]]
   ### **1. 인증서가 존재하는 pki 디렉토리를 백업합니다.**
   cp -f /etc/kubernetes/pki /etc/kubernetes/pki-backup

   ### **2. 인증서를 일괄적으로 업데이트 합니다.**
   kubeadm certs renew all

   ### **3. 갱신 여부를 다시 체크합니다.**
   [12:56:54][danawa@kube3 /etc/kubernetes/pki]
   $ openssl x509 -in apiserver.crt -noout -text |grep ' Not '
               Not Before: Mar 25 08:12:56 2020 GMT
               Not After : Mar 28 02:26:40 2023 GMT

   #정상적으로 1년 연장 되었습니다.

   ### **4. 사용자 구성을 업데이트 하기 위해 kubernetes 디렉토리를 백업합니다.**
   cp -f /etc/kubernetes /etc/kubernetes-backup

   ### **5. 사용자 구성을 업데이트합니다.**
   kubeadm kubeconfig user --client-name=admin --config=/etc/kubernetes/kubeadm-config.yaml
   kubeadm kubeconfig user --org system:masters --client-name=kubernetes-admin --config=/etc/kubernetes/kubeadm-config.yaml > /etc/kubernetes/admin.conf
   kubeadm kubeconfig user --client-name system:kube-controller-manager --config=/etc/kubernetes/kubeadm-config.yaml > /etc/kubernetes/controller-manager.conf
   kubeadm kubeconfig user --org system:nodes --client-name system:node:$(hostname) --config=/etc/kubernetes/kubeadm-config.yaml > /etc/kubernetes/kubelet.conf
   kubeadm kubeconfig user --client-name system:kube-scheduler --config=/etc/kubernetes/kubeadm-config.yaml > /etc/kubernetes/scheduler.conf

   ### **6. /root/.kube/config를 교체합니다.**
   cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
   chown $(id -u):$(id -g) $HOME/.kube/config

   ### **7. 서비스를 재시작합니다.**
   systemctl daemon-reload&&systemctl restart kubelet

   ### csr approve
   kubectl certificate approve $(kubectl get csr | grep Pending | awk '{print $1}' |  sed -z 's/[\\n\\r]/ /g')

   # [[ worker node ]]
   ## master에서 복사한 후 붙여넣기
   vi /etc/kubernetes/kubelet.conf
   >>>
   # 아래 ip locahost로 수정
   server: <https://localhost:6443>
   <<<
   systemctl restart kubelet
shell
# systemctl 서비스에서 사용할 경우
   systemctl restart kube-apiserver
   systemctl restart kube-scheduler

   # 도커 컨테이너를 사용할 경우
   docker restart [apiserver 컨테이너]
   docker restart [scheduler 컨테이너]
   docker restart [controller 컨테이너]

##### 그 외의 방법

###### 참고 자료

[Certificate Management with kubeadm](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/#automatic-certificate-renewal)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *