컴포넌트 인증서 — TLS 연결과 client·server 역할
apiserver.crt, apiserver-kubelet-client.crt, kubelet.crt, kubelet-client-current.pem.
kubeadm 클러스터에는 인증서 파일이 열 개 넘게 있고, 시험은 “이 연결에 쓰이는 인증서가 어느 파일인가”를
묻는다. 파일명을 외우는 대신 연결마다 여는 쪽과 받는 쪽이 있고, 각자 자기 역할 이름의 인증서를
내민다는 규칙 하나로 전부 풀린다. 이 장은 그 규칙과, 인증서가 실제로 연결의 어느 시점에 오가는지를
다룬다. 인증서와 개인 키의 차이는 사용자 인증에서,
만료와 갱신은 인증서 관리에서 이어진다.
연결에는 여는 쪽과 받는 쪽이 있다
섹션 제목: “연결에는 여는 쪽과 받는 쪽이 있다”TLS(Transport Layer Security) 연결은 항상 한쪽이 열고 다른 쪽이 받는다. 여는 쪽이 client, 받는 쪽이 server다. 일반 웹사이트 HTTPS에서는 server만 인증서를 내밀고 client(브라우저)는 내밀지 않는다. 그러면 server는 상대가 누구인지 모른다. 쿠버네티스 컴포넌트 사이에서는 “누가 부르는가”가 곧 권한이므로 양쪽 모두 인증서를 내민다. 이것이 mTLS(mutual TLS·상호 TLS)다.
한 컴포넌트가 상대에 따라 client도 되고 server도 된다. kubelet이 대표적이다.
| 연결 | client (여는 쪽) | server (받는 쪽) | client가 내미는 인증서 | server가 내미는 인증서 |
|---|---|---|---|---|
kubectl → apiserver | kubectl | apiserver | kubeconfig의 사용자 인증서 | apiserver.crt |
| kubelet → apiserver (상태 보고·watch) | kubelet | apiserver | /var/lib/kubelet/pki/kubelet-client-current.pem | apiserver.crt |
apiserver → kubelet:10250 (logs·exec) | apiserver | kubelet | apiserver-kubelet-client.crt | /var/lib/kubelet/pki/kubelet.crt |
| apiserver → etcd | apiserver | etcd | apiserver-etcd-client.crt | etcd/server.crt |
경로를 생략한 파일은 컨트롤 플레인의 /etc/kubernetes/pki/ 아래에 있다. 이 표에서 볼 것은
kubelet 행이 둘이라는 점이다. 같은 kubelet이 두 번째 행에서는 client, 세 번째 행에서는 server라서
인증서도 두 개를 따로 가진다.
핸드셰이크 — 인증서는 연결당 한 번 오간다
섹션 제목: “핸드셰이크 — 인증서는 연결당 한 번 오간다”인증서를 주고받는 곳은 연결을 맺는 첫 단계인 핸드셰이크뿐이다. 그 뒤의 요청은 핸드셰이크에서 합의한 세션 키로 암호화되어 흐르고, 인증서는 다시 내밀지 않는다. kubelet이 apiserver로 연결하는 경우를 따라가면 순서는 이렇다.
-
client가 연결을 연다.
-
server가 자기 server 인증서를 보낸다. client는 자기가 믿는 CA(
ca.crt)로 서명을 검증하고, 접속한 주소가 인증서의 SAN(Subject Alternative Name)에 있는지 본다. 여기서 실패하면x509: certificate signed by unknown authority같은 오류가 client 쪽에 찍힌다. -
server가 client에게도 인증서를 요구한다. 이 단계가 있어야 mTLS다.
-
client가 client 인증서를 보내고, 그 인증서와 짝인 개인 키로 만든 서명을 함께 보낸다. server는 인증서 안의 공개 키로 서명을 검증해 “이 인증서의 주인이 맞다”를 확인한다. 개인 키 자체는 네트워크로 나가지 않는다. 검증이 끝나면 인증서의 CN을 사용자 이름, O를 그룹으로 받아들인다. kubelet이라면
system:node:<노드명>과system:nodes다. -
양쪽이 세션 키를 합의한다. 이후 요청은 전부 이 키로 암호화되고 인증서는 다시 오가지 않는다.
“연결당 한 번”이라는 사실에서 운영에 중요한 결론 두 개가 나온다.
- 만료된 인증서도 이미 맺은 연결은 계속 쓴다. kubelet은 apiserver와 watch 연결을 오래 유지하므로, 인증서가 만료돼도 당장은 멀쩡해 보이다가 재연결하는 순간 실패한다. “어제까지 되던 노드가 kubelet 재시작 뒤 NotReady”가 이 패턴이다.
- 인증서를 갱신했으면 새 연결을 맺어야 반영된다. 인증서 관리에서 갱신 뒤 컨트롤 플레인 스태틱 Pod을 재시작하라는 이유가 이것이다. 실행 중인 프로세스는 옛 인증서로 맺은 연결을 그대로 쓰고 있다.
이름 관례 — 소유자와 그 연결에서의 역할
섹션 제목: “이름 관례 — 소유자와 그 연결에서의 역할”인증서 파일 이름은 두 부분으로 읽는다. 앞부분은 누구의 인증서인가, client·server는
그 소유자가 그 연결에서 맡는 역할이다. 상대가 누구인지는 이름에 없거나 가운데에 붙는다.
| 파일 | 소유자 | 역할 | 상대 |
|---|---|---|---|
apiserver.crt | apiserver | server | kubectl·kubelet·컨트롤러 등 apiserver를 부르는 모두 |
apiserver-kubelet-client.crt | apiserver | client | kubelet |
apiserver-etcd-client.crt | apiserver | client | etcd |
etcd/server.crt | etcd | server | apiserver |
etcd/peer.crt | etcd | client이자 server | 다른 etcd 멤버 |
/var/lib/kubelet/pki/kubelet-client-current.pem | kubelet | client | apiserver |
/var/lib/kubelet/pki/kubelet.crt | kubelet | server | apiserver |
server가 이름에 없는 apiserver.crt·kubelet.crt는 관례상 server 인증서다. 소유자의 “기본”
역할이 server라서 접미사를 생략한 것이고, client 역할이 추가될 때만 -client를 붙인다.
kubelet의 두 인증서
섹션 제목: “kubelet의 두 인증서”kubelet은 위 표에서 유일하게 컨트롤 플레인 밖 워커 노드에 인증서를 두는 컴포넌트다. 경로는
/var/lib/kubelet/pki/이고 두 인증서의 출처가 다르다.
client 인증서인 kubelet-client-current.pem은 kubelet이 apiserver로 나갈 때 쓴다.
kubeadm join 때 kubelet이 임시 bootstrap 토큰으로 apiserver에 접속해 CSR을 올리고, 클러스터 CA가
서명한 인증서를 받아 이 파일로 저장한다. 이 과정이
TLS bootstrapping이다.
그래서 Issuer가 클러스터 CA(CN = kubernetes)다. 파일은 심볼릭 링크이고 실제 파일은
kubelet-client-<발급 시각>.pem이다. kubelet이 만료 전에
자동으로 새 CSR을 올려 갱신하고
링크를 새 파일로 옮기므로, kubeadm certs renew 대상에 kubelet client 인증서가 없는 것이 정상이다.
server 인증서인 kubelet.crt는 apiserver가 kubelet의 10250 포트로 들어올 때 kubelet이 내민다.
kubeadm 기본 설정에서는 kubelet이 스스로 만들어 스스로 서명한다. 그래서 Issuer가
CN = <노드명>-ca@<시각>처럼 노드 이름을 딴 자체 CA로 나오고, apiserver는 이 인증서를 검증하지
않는다(--kubelet-certificate-authority가 비어 있다). 클러스터 CA가 서명한 server 인증서를 쓰려면
KubeletConfiguration에 serverTLSBootstrap: true를 두고 kubernetes.io/kubelet-serving signer의 CSR을
승인해야 한다. 절차는
kubeadm 문서의 kubelet serving certs에 있다.
| client 인증서 | server 인증서 | |
|---|---|---|
| 파일 | kubelet-client-current.pem (심볼릭 링크) | kubelet.crt + kubelet.key |
| 발급자 | 클러스터 CA (CN = kubernetes) | 기본은 kubelet 자체 서명 |
| 발급 경로 | TLS bootstrapping, CSR 자동 승인 | kubelet 시작 시 자체 생성 |
| 갱신 | kubelet이 자동 로테이션 | 기본은 자체 재생성 |
경로가 기억나지 않을 때
섹션 제목: “경로가 기억나지 않을 때”/var/lib/kubelet/pki를 외우지 못했다면 kubelet이 읽는 설정에서 역추적한다. 위에서부터 빠른 순서다.
ps aux | grep kubelet # --kubeconfig, --config 경로가 보인다grep client-certificate /etc/kubernetes/kubelet.confkubelet --help | grep -A1 cert-dir # 기본값 /var/lib/kubelet/pkifind / -name "kubelet*.pem" -o -name "kubelet.crt" 2>/dev/nullTLS bootstrapping을 거친 노드의 kubelet.conf는 인증서를 base64로 품지 않고 파일 경로를 가리킨다.
그래서 kubeconfig 한 파일에서 client 인증서 경로와 CA 경로를 모두 얻는다. server 인증서는
kubelet 설정에 tlsCertFile이 따로 없으면 --cert-dir 기본값 아래의 kubelet.crt다.
Extended Key Usage로 역할 판별하기
섹션 제목: “Extended Key Usage로 역할 판별하기”파일명 없이도 인증서 내용만으로 client용인지 server용인지 알 수 있다. CA가 서명할 때
Extended Key Usage(EKU·확장 키 용도)를 함께 박아 넣고, 검증하는 쪽은 용도가 맞는지 본다.
핸드셰이크 4번 단계에서 server는 받은 client 인증서에 Client Authentication이 없으면 거부한다.
openssl x509 -noout -text -in /var/lib/kubelet/pki/kubelet-client-current.pem \ | grep -E "Issuer|Extended Key Usage" -A1# Issuer: CN = kubernetes# X509v3 Extended Key Usage:# TLS Web Client Authentication
openssl x509 -noout -text -in /var/lib/kubelet/pki/kubelet.crt \ | grep -E "Issuer|Extended Key Usage" -A1# Issuer: CN = node01-ca@1730211854# X509v3 Extended Key Usage:# TLS Web Server Authentication| EKU 값 | 뜻 | 붙는 인증서 |
|---|---|---|
TLS Web Client Authentication | 연결을 여는 쪽이 자기를 증명하는 용도 | *-client.*, kubelet-client-current.pem, kubeconfig 사용자 인증서 |
TLS Web Server Authentication | 연결을 받는 쪽이 자기를 증명하는 용도 | apiserver.crt, kubelet.crt, etcd/server.crt |
| 둘 다 | 한 인증서로 양쪽 역할 | etcd/peer.crt |
openssl x509 -text를 읽을 때 Subject·Issuer·SAN·Validity에 더해 EKU까지 다섯 항목을 보면
그 인증서가 누구의 것이고, 누가 발급했고, 어느 역할로, 언제까지 쓰이는지가 전부 나온다.
이해 확인
섹션 제목: “이해 확인”- apiserver가 etcd에 연결할 때 apiserver가 내미는 인증서와 그 EKU는? apiserver가 연결을 여는
client이므로
apiserver-etcd-client.crt, EKU는TLS Web Client Authentication이다. - kubelet client 인증서가 만료됐는데 노드가 아직 Ready인 이유는? 이미 맺은 watch 연결은 세션 키로 계속 흐르기 때문이다. 재연결 시점에 실패한다.
kubelet.crt의 Issuer가 클러스터 CA가 아닌데 정상인가? kubeadm 기본값에서는 정상이다. kubelet이 자체 서명하며 apiserver는 이 인증서를 검증하지 않는다.
- 컴포넌트 인증서 이름의 client·server는 그 인증서 주인이 연결에서 맡는 역할이고, 인증서는 TLS 핸드셰이크에서 한 번만 오간다.
- 이름은 “소유자 + 역할”로 읽는다.
apiserver-kubelet-client.crt는 apiserver가 kubelet에 접속할 때 내미는 apiserver의 인증서다. - kubelet은 client 인증서(클러스터 CA 발급, 자동 로테이션)와 server 인증서(기본 자체 서명)를
/var/lib/kubelet/pki에 따로 둔다. - 역할은 EKU로 판별한다.
Client Authentication은 여는 쪽,Server Authentication은 받는 쪽이다. - 만료된 인증서도 이미 맺은 연결은 유지하므로 갱신 뒤에는 재연결(프로세스 재시작)이 필요하다.