본문으로 건너뛰기

11.5 인증서 관리

kubeadm 클러스터에서 컴포넌트 사이의 모든 통신은 TLS 위에 있다. kubectl이 apiserver를 호출할 때도, apiserver가 kubelet에 exec 세션을 열 때도, etcd 멤버끼리 복제할 때도 인증서가 양쪽의 신원을 증명한다. kubeadm은 init 시점에 이 인증서 전부를 만들어 주지만, 리프 인증서의 기본 유효기간이 1년이라는 사실이 운영 과제로 남는다. 갱신을 잊은 클러스터는 구축 1년째 되는 날 인증 오류와 함께 관리 기능이 멈춘다. 관리형 서비스에서는 존재조차 느끼지 못하는 영역이 자가 운영에서는 예약된 장애가 되는 셈이다.

kubeadm이 만드는 PKI 트리

인증서는 /etc/kubernetes/pki에 모여 있다.

    • ca.crt, ca.key (클러스터 CA)
    • apiserver.crt, .key (apiserver 서버)
    • apiserver-kubelet-client.crt, .key
    • apiserver-etcd-client.crt, .key
    • front-proxy-ca.crt, .key
    • front-proxy-client.crt, .key
    • sa.key, sa.pub (SA 토큰 서명 키)
      • ca.crt, ca.key (etcd CA)
      • server.crt, peer.crt, healthcheck-client.crt

CA가 세 개라는 점이 이 트리의 뼈대다. 클러스터 CA(ca.crt)는 apiserver와 kubelet, 각종 클라이언트 인증서를 발급하는 본체다. etcd CA는 etcd 관련 인증서만 발급한다. CA를 분리해 두면 etcd CA로 발급된 인증서로는 apiserver 인증을 통과하지 못하고 그 반대도 성립하지 않아, 한쪽 인증서가 유출돼도 피해가 그 신뢰 영역 안에 갇힌다. front-proxy CA는 aggregation layer, 즉 apiserver가 확장 API 서버로 요청을 위임할 때 “이 요청은 apiserver를 거쳐 왔다"는 헤더를 신뢰하는 근거로만 쓰인다. sa.keysa.pub은 ServiceAccount 토큰을 서명하고 검증하는 키 쌍으로, 인증서는 아니지만 같은 디렉토리에 함께 있어 알아 둘 필요가 있다.

컴포넌트 간 mTLS 지도

파일 이름은 통신 경로에서 맡는 역할을 그대로 드러낸다. apiserver-kubelet-client는 apiserver가 kubelet에 접속할 때 제시하는 클라이언트 인증서고, apiserver-etcd-client는 etcd에 접속할 때 제시하는 것이다.

    flowchart TD
  CLI["kubectl"] -->|"admin.conf 인증서"| API["kube-apiserver"]
  KL["kubelet"] -->|"노드 클라이언트"| API
  API -->|"kubelet-client"| KL
  API -->|"etcd-client"| E1["etcd"]
  E1 <-->|"peer 인증서"| E2["etcd 다른 멤버"]
  AGG["확장 API 서버"] -->|"front-proxy"| API
  

화살표마다 방향이 두 개다. mTLS이므로 서버는 자기 서버 인증서를 제시하고 동시에 클라이언트의 인증서를 검증한다. apiserver와 kubelet 사이가 좋은 예로, kubelet이 apiserver의 클라이언트일 때(상태 보고)와 apiserver가 kubelet의 클라이언트일 때(kubectl logs, exec)의 인증서가 서로 다르다. 사용자 인증 관점의 인증서는 Part IX. 보안에서 다뤘고, 이 챕터의 관심은 컴포넌트 간 신뢰의 유지 보수다.

만료 확인과 갱신

현황 파악 명령은 하나다.

sudo kubeadm certs check-expiration
CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY
admin.conf                 Aug 21, 2027 03:12 UTC   364d            ca
apiserver                  Aug 21, 2027 03:12 UTC   364d            ca
apiserver-etcd-client      Aug 21, 2027 03:12 UTC   364d            etcd-ca
...

리프 인증서는 1년, CA는 10년이 kubeadm 기본값이다. admin.conf처럼 kubeconfig 파일 안에 내장된 클라이언트 인증서도 같은 목록에서 함께 관리된다.

갱신 경로는 두 개다. 첫째, kubeadm upgrade apply는 업그레이드 과정에서 관리 대상 인증서 전부를 자동 갱신한다. 11.2의 주기대로 연 1회 이상 업그레이드하는 클러스터라면 만료를 만날 일이 없다. 업그레이드 주기가 곧 인증서 갱신 주기가 되는 구조다. 둘째는 수동 갱신이다.

sudo kubeadm certs renew all

갱신 후에는 kube-apiserver, kube-controller-manager, kube-scheduler, etcd static Pod를 재시작해야 새 인증서가 반영된다. 프로세스가 인증서를 기동 시점에 읽기 때문이다. 갱신된 admin.conf는 로컬 ~/.kube/config에도 다시 복사한다.

kubelet 인증서 회전

노드 수만큼 존재하는 kubelet 인증서는 수동 갱신 대상에서 빠져 있는데, kubelet에는 자체 회전 체계가 있기 때문이다. KubeletConfiguration의 rotateCertificates가 켜져 있으면(kubeadm 기본값) kubelet은 클라이언트 인증서 만료가 다가올 때 스스로 CSR을 제출하고 새 인증서로 교체한다. 서빙 인증서 쪽은 serverTLSBootstrap 옵션으로 같은 CSR 체계에 통합되지만, 이 CSR은 보안상 자동 승인되지 않아 별도의 승인 절차나 승인 컴포넌트가 필요하다.

자동 회전은 kubelet이 실행 중일 때만 동작한다. 수개월 꺼져 있던 노드를 다시 켜면 만료된 인증서로는 apiserver에 인증할 수 없어 회전 요청 자체가 불가능하다. 이런 노드는 복구하려 하기보다 11.4의 절차로 클러스터에서 제거하고 재합류시키는 편이 빠르다.

만료가 만드는 장애 양상

만료 장애의 공통 신호는 로그와 명령 출력의 x509: certificate has expired or is not yet valid다. 어느 계층이 만료됐는지에 따라 증상 범위가 달라진다. admin.conf만 만료됐다면 kubectl이 실패할 뿐 클러스터는 정상이다. kubelet 클라이언트 인증서가 만료된 노드는 상태 보고가 끊겨 NotReady로 전환되고, apiserver 쪽 인증서가 만료되면 그 apiserver를 향한 모든 통신이 실패한다. etcd 인증서 만료는 apiserver가 저장소를 잃는 것이라 API 전체가 불능이 되는 가장 넓은 양상을 만든다.

이 장애의 고약한 점은 워크로드가 당장은 멀쩡하다는 데 있다. 노드 위의 컨테이너는 control plane 없이도 계속 실행되기 때문에 서비스 지표에는 이상이 없고, 배포나 스케일 같은 변경을 시도하는 순간에야 발견된다. 복구 자체는 kubeadm certs renew all과 static Pod 재시작으로 가능하지만, 발견이 늦을수록 그 사이의 변경 요구가 쌓인 채로 복구하게 된다. check-expiration을 주기 실행하는 점검이나 apiserver의 인증서 만료 관련 메트릭을 경보에 연결해, 만료를 장애가 아니라 달력의 일정으로 만들어 두는 것이 이 챕터의 결론이다.

정리

kubeadm 인증서 운영은 두 문장으로 줄어든다. 정기적으로 업그레이드하면 만료를 만나지 않고, check-expiration을 경보에 연결하면 잊어도 알게 된다. 장애 상황에서는 증상의 범위(kubectl만 실패, 특정 노드 NotReady, API 전체 불능)가 만료된 인증서의 계층을 가리킨다.