[Kubernetes] Cluster: 내 손으로 클러스터 구성하기 - 4.3. Provisioning a CA and Generating TLS Certificates
서종호(가시다)님의 On-Premise K8s Hands-on Study 1주차 학습 내용을 기반으로 합니다.
TL;DR
이번 글의 목표는 Kubernetes 클러스터를 위한 TLS 인증서 생성 및 배포다. Kubernetes the Hard Way 튜토리얼의 Provisioning a CA and Generating TLS Certificates 단계를 수행한다.
- Root CA 생성: Self-Signed 방식으로 클러스터 CA 생성
- 컴포넌트 인증서 생성: admin, kubelet, kube-proxy, kube-scheduler, kube-controller-manager, kube-api-server, service-accounts
- 인증서 배포: 각 노드에 필요한 인증서만 선별적으로 배포
Prerequisite
Kubernetes the Hard Way 튜토리얼에서 제공하는 ca.conf kube-scheduler 섹션의 O 필드에 system이 두 번 들어가 있다.
# ca.conf 오타 확인
cat ca.conf | grep -A 1 "\[kube-scheduler\]"
[kube-scheduler]
CN = system:kube-scheduler
O = system:system:kube-scheduler # system이 두 번!
Kubernetes에서 인증서의 O(Organization) 필드는 사용자가 속한 그룹을 나타낸다. RBAC에서 권한 부여 시 이 그룹명을 참조하므로, 오타가 있으면 kube-scheduler가 올바른 권한을 받지 못한다.
# (jumpbox) 오타 수정
sed -i 's/system:system:kube-scheduler/system:kube-scheduler/' ca.conf
# 수정 확인
cat ca.conf | grep -A 2 "\[kube-scheduler\]"
[kube-scheduler]
CN = system:kube-scheduler
O = system:kube-scheduler # 수정됨
Root CA
CA(Certificate Authority)는 다른 인증서에 서명하는 역할을 한다. 이 단계에서는 클러스터 RootCA의 개인키와 인증서를 생성한다.
| 파일 | 설명 |
|---|---|
ca.key |
CA 개인키. 다른 인증서에 서명할 때 사용 |
ca.crt |
CA 인증서(루트 인증서). CA의 공개키 포함 |
개인키 생성
openssl genrsa 명령어로 4096비트 RSA 개인키를 생성한다.
# (jumpbox) #
openssl genrsa -out ca.key 4096
개인키 파일의 권한이 600(-rw——-)인 것을 확인한다. 개인키는 소유자만 읽을 수 있어야 한다.
# 결과 확인
ls -l ca.key
-rw------- 1 root root 3272 Jan 7 23:21 ca.key
개인키 내용 확인
openssl rsa -text 명령어로 개인키의 구성 요소를 확인할 수 있다.
openssl rsa -in ca.key -text -noout
# 출력 예시 (축약)
Private-Key: (4096 bit, 2 primes)
modulus:
00:af:9c:95:c5:6f:69:2c:fa:9f:15:20:7a:52:c0:
... (생략)
publicExponent: 65537 (0x10001)
privateExponent:
22:59:d3:54:24:f8:62:53:7d:d5:c0:9e:1c:dc:b7:
... (생략)
prime1, prime2, exponent1, exponent2, coefficient:
... (생략)
인증서 생성
Root CA는 상위 CA가 없으므로, Self-Signed** 방식으로 생성한다. 즉, CA가 자기 자신의 개인키로 자신의 인증서에 서명한다. 이 때, 위에서 생성한 개인키를 이용한다.
-section 옵션을 지정하지 않았으므로, OpenSSL은 ca.conf의 기본 [req] 섹션을 사용한다. 뒤에서 이어질 다른 컴포넌트 인증서 생성 시에는 -section 옵션으로 각 섹션을 명시적으로 지정한다.
# (jumpbox)
# -x509: CSR 대신 Self-Signed 인증서 직접 생성
# -new: 새 인증서 생성 요청
# -sha512: SHA-512 해시 알고리즘으로 서명
# -noenc: 개인키 암호호 없음(패스프레이즈 없음)
# -key ca.key: 서명에 사용할 개인키(위에서 생성한 개인키)
# -days 3653: 유효 기간 약 10년
# -config ca.conf: 설정 파일에서 DN 정보 읽기
# -out ca.crt: 출력 인증서 파일
openssl req -x509 -new -sha512 -noenc \
-key ca.key -days 3653 \
-config ca.conf \
-out ca.crt
# 결과 확인
ls -l ca.crt
-rw-r--r-- 1 root root 1899 Jan 7 23:23 ca.crt
인증서 내용 확인
cat 명령어로 인증서 파일을 확인하면 PEM 형식의 Base64 인코딩 텍스트가 출력된다. -----BEGIN CERTIFICATE-----와 -----END CERTIFICATE----- 사이가 인증서 데이터이다.
cat ca.crt
-----BEGIN CERTIFICATE-----
MIIFTDCCAzSgAwIBAgIUW8mqGlfxAkjpEL+xuhLrax2itNcwDQYJKoZIhvcNAQEN
BQAwQTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM
...
-----END CERTIFICATE-----
상세 정보 확인
openssl x509 -text 명령어로 인증서의 상세 정보를 확인한다. 이전 글에서 살펴 보았던 X.509 인증서 구조를 확인할 수 있다.
Self-Signed 인증서이므로 Issuer와 Subject가 동일하며, X509v3 Basic Constraints: CA:TRUE 항목 덕분에 이 인증서로 다른 인증서에 서명할 수 있다.
openssl x509 -in ca.crt -text -noout
# 출력 내용 (축약)
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
5b:c9:aa:1a:57:f1:02:48:e9:10:bf:b1:ba:12:eb:6b:1d:a2:b4:d7
Signature Algorithm: sha512WithRSAEncryption
Issuer: C = US, ST = Washington, L = Seattle, CN = CA # 발급자. Self-Signed이므로 Subject와 동일
Validity # 유효 기간. 약 10년
Not Before: Jan 7 14:23:41 2026 GMT
Not After : Jan 8 14:23:41 2036 GMT
Subject: C = US, ST = Washington, L = Seattle, CN = CA # 인증서 소유자. CN = CA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit) # 공개키. 4096 bit RSA
Modulus:
00:af:9c:95:c5:6f:69:2c:... (생략)
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:TRUE # CA 인증서임을 나타냄
X509v3 Key Usage: # Certificate Sign, CRL Sign 권한
Certificate Sign, CRL Sign
X509v3 Subject Key Identifier:
F0:A4:CE:7F:0F:4A:C4:8D:74:7B:ED:C1:CD:50:4E:80:1C:09:DC:8D
Signature Algorithm: sha512WithRSAEncryption
Signature Value:
2e:6d:a0:cc:9f:32:16:56:... (생략)
생성된 파일 정리
이 단계를 마치면 아래와 같은 파일이 생성되어야 한다. 이후 다음 각 단계에서 ca.key를 이용해 각 컴포넌트의 인증서에 서명하고, ca.crt는 모든 노드에 복사하는 과정을 진행한다. 거듭 말하지만, 클러스터 내 모든 컴포넌트는 ca.crt를 가지고 있어야 상대방 인증서를 검증할 수 있다.
ls -la ca.*
-rw-r--r-- 1 root root 5863 Jan 6 00:15 ca.conf
-rw-r--r-- 1 root root 1899 Jan 7 23:23 ca.crt # CA 인증서 (공개)
-rw------- 1 root root 3272 Jan 7 23:21 ca.key # CA 개인키 (비공개)
Admin
상위 CA가 없었던 위 단계와 달리, CSR 생성 단계가 추가된다.
개인키 생성
# (jumpbox) #
openssl genrsa -out admin.key 4096
# 결과 확인
ls -l admin.key
-rw------- 1 root root 3272 Jan 7 23:47 admin.key
개인키 내용 확인
openssl rsa -in admin.key -text -noout으로 ca.key를 확인했던 방법과 마찬가지로 구조를 볼 수 있다. 여기서는 생략한다.
CSR 생성
# csr 파일 생성
# admin.key 개인키를 사용해
# 'CN=admin, O=system:masters'인 Kubernetes 관리자용 클라이언트 인증서 요청(admin.csr) 생성
openssl req -new -key admin.key -sha256 \
-config ca.conf -section admin \
-out admin.csr
ls -l admin.csr
CSR 내용 확인
openssl req -in admin.csr -text -noout
CSR의 서명은 CA가 아닌 요청자 본인의 개인키로 이루어진다. 즉, 아직 서명되지 않은 인증서 요청이므로 Issuer나 Validity 정보가 없다. 대신 Subject와 Requested Extensions가 핵심이다.
Certificate Request:
Data:
Version: 1 (0x0)
Subject: CN = admin, O = system:masters # Kubernetes 관리자 그룹: Kubernetes 관리자 권한 부여
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:b1:e3:ca:89:7e:67:95:14:99:ea:10:8e:68:d7:
... (생략)
Exponent: 65537 (0x10001)
Attributes:
Requested Extensions:
X509v3 Basic Constraints:
CA:FALSE # 일반 인증서로, 다른 인증서에 서명 불가
X509v3 Extended Key Usage:
TLS Web Client Authentication # 클라이언트 인증 용도: kubectl 같은 클라이언트에서 사용
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
Netscape Cert Type:
SSL Client
Netscape Comment:
Admin Client Certificate
Signature Algorithm: sha256WithRSAEncryption # CSR 자체의 서명 (자기 개인키로 서명)
인증서 생성
CSR을 이용해 CA에 인증서를 요청해 생성한다.
# ca에 csr 요청을 통한 crt 파일 생성
## -req : CSR를 입력으로 받아 인증서를 생성, self-signed 아님, CA가 서명하는 방식
## -days 3653 : 인증서 유효기간 3653일 (약 10년)
## -copy_extensions copyall : CSR에 포함된 모든 X.509 extensions를 인증서로 복사
## -CAcreateserial : CA 시리얼 번호 파일 자동 생성, 다음 인증서 발급 시 재사용, 기본 생성 파일(ca.srl)
openssl x509 -req -days 3653 -in admin.csr \
-copy_extensions copyall \
-sha256 -CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out admin.crt
Certificate request self-signature ok
subject=CN = admin, O = system:masters
인증서 내용 확인
# (jumpbox) #
openssl x509 -in admin.crt -text -noout
CSR과 달리 CA가 서명한 인증서에는 Issuer, Validity 정보가 추가되고, X509v3 Authority Key Identifier를 통해 어떤 CA가 서명했는지 확인할 수 있다.
# 출력 내용 (축약)
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
02:da:41:2b:39:d1:c6:ff:97:8a:06:3c:e1:fd:65:2b:59:0a:fd:ed
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = Washington, L = Seattle, CN = CA # CA가 발급
Validity
Not Before: Jan 7 14:55:57 2026 GMT
Not After : Jan 8 14:55:57 2036 GMT # 10년
Subject: CN = admin, O = system:masters
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:b1:e3:ca:89:... (생략)
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
Netscape Cert Type:
SSL Client
Netscape Comment:
Admin Client Certificate
X509v3 Subject Key Identifier: # 이 인증서의 식별자
B5:E2:57:FC:79:31:DB:B2:B4:68:9A:CC:3A:E7:02:41:CA:6C:E7:5B
X509v3 Authority Key Identifier: # 서명한 CA의 식별자 (ca.crt와 동일)
F0:A4:CE:7F:0F:4A:C4:8D:74:7B:ED:C1:CD:50:4E:80:1C:09:DC:8D
Signature Algorithm: sha256WithRSAEncryption # CA 개인키로 서명
Signature Value:
81:f4:08:5a:a7:3e:6f:47:... (생략)
참고: system:masters 그룹 사용 주의
system:masters는 Kubernetes의 내장(built-in) 슈퍼유저 그룹이다.
이 그룹에 속한 사용자는 막강한(?) 권한을 가진다:
- 인증(Authentication) 통과 후 → 인가(Authorization) 우회
- RBAC(
Role/ClusterRole)이나 Webhook 기반 인가 검사를 거치지 않음 - API Server에서 모든 동작을 무조건 허용 (클러스터 슈퍼유저 권한)
따라서, 이 그룹에 대해서는 아래와 같은 보안 권고 사항이 제시된다:
- Kubernetes 공식 문서에서도 이 그룹 사용을 최소화하라고 권고
- 실무에서는 AWS Root 계정처럼 관리해야 함:
- 클러스터 부트스트랩 목적으로만 사용
- 일상적인 관리 작업에는
cluster-adminClusterRole을 사용 - 인증서가 탈취되면 복구가 매우 어려움
- 필요한 경우,
cluster-adminClusterRole과 ClusterRoleBinding을 통해 관리자 권한을 부여하는 것이 안전
기타
이후 다른 컴포넌트의 인증서 생성은 비슷한 단계를 거친다.
- 컴포넌트 개인키 생성
- 컴포넌트 CSR 생성
- 컴포넌트 인증서 생성
반복 작업이기 때문에, 각 실행 내용 및 결과만 간단히 기록한다.
반복 작업
나머지 컴포넌트 인증서를 반복문으로 생성한다.
# (jumpbox) 인증서 목록 변수 지정
certs=(
"node-0" "node-1"
"kube-proxy" "kube-scheduler"
"kube-controller-manager"
"kube-api-server"
"service-accounts"
)
# 반복문으로 각 컴포넌트 인증서 생성
for cert in "${certs[@]}"; do
openssl genrsa -out ${cert}.key 4096
openssl req -new -key ${cert}.key -sha256 \
-config ca.conf -section ${cert} \
-out ${cert}.csr
openssl x509 -req -days 3653 -in ${cert}.csr \
-copy_extensions copyall \
-sha256 -CA ca.crt -CAkey ca.key -CAcreateserial \
-out ${cert}.crt
done
각 인증서 생성 시 CA로부터 서명받는 과정이 성공하면 아래와 같은 메시지가 출력된다.
Certificate request self-signature ok
subject=CN = system:node:node-0, O = system:nodes, ...
Certificate request self-signature ok
subject=CN = system:node:node-1, O = system:nodes, ...
... (각 컴포넌트별 출력)
생성된 파일 확인
모든 인증서 생성이 완료되면 각 컴포넌트별로 .key, .csr, .crt 파일 3개씩 생성된다.
# (jumpbox) 생성된 파일 확인
ls -1 *.{crt,key,csr} | wc -l
27 # 9개 컴포넌트 × 3개 파일 = 27개
# 컴포넌트별 파일 확인
ls -1 *.crt
admin.crt
ca.crt
kube-api-server.crt
kube-controller-manager.crt
kube-proxy.crt
kube-scheduler.crt
node-0.crt
node-1.crt
service-accounts.crt
각 인증서 확인
각 인증서별로 주요 필드만 확인한다.
# node-0: 워커 노드 인증서. Client + Server 역할
openssl x509 -in node-0.crt -text -noout | grep -A 2 "Subject:"
Subject: CN = system:node:node-0, O = system:nodes, C = US, ST = Washington, L = Seattle
X509v3 extensions:
X509v3 Extended Key Usage:
TLS Web Client Authentication, TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:node-0, IP Address:127.0.0.1
# node-1: 워커 노드 인증서. Client + Server 역할
openssl x509 -in node-1.crt -text -noout | grep -A 1 "Subject:"
Subject: CN = system:node:node-1, O = system:nodes
X509v3 Subject Alternative Name:
DNS:node-1, IP Address:127.0.0.1
# kube-proxy: 클라이언트 인증서
openssl x509 -in kube-proxy.crt -text -noout | grep "Subject:"
Subject: CN = system:kube-proxy, O = system:node-proxier
# kube-scheduler: 클라이언트 인증서
openssl x509 -in kube-scheduler.crt -text -noout | grep "Subject:"
Subject: CN = system:kube-scheduler, O = system:kube-scheduler
# kube-controller-manager: 클라이언트 인증서
openssl x509 -in kube-controller-manager.crt -text -noout | grep "Subject:"
Subject: CN = system:kube-controller-manager, O = system:kube-controller-manager
# kube-api-server: Client + Server 역할. SAN에 여러 DNS/IP 포함
openssl x509 -in kube-api-server.crt -text -noout | grep -A 2 "Subject:"
Subject: CN = kubernetes, C = US, ST = Washington, L = Seattle
Netscape Cert Type:
SSL Client, SSL Server # 클라이언트이자 서버 역할
X509v3 Subject Alternative Name:
IP Address:127.0.0.1, IP Address:10.32.0.1, # 10.32.0.1은 kubernetes Service ClusterIP
DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc,
DNS:kubernetes.default.svc.cluster, DNS:kubernetes.svc.cluster.local,
DNS:server.kubernetes.local, DNS:api-server.kubernetes.local
API Server 인증서
API Server 인증서의 SAN에는 10.32.0.1이 포함되어 있다. 중요하게 확인하자.
이는 클러스터 내부에서 API Server에 접근할 때 사용하는 kubernetes Service의 ClusterIP이다. 참고로 실습 환경에서 사용하는 네트워크 대역은 다음과 같다.
| 항목 | 네트워크 대역 or IP | 설명 |
|---|---|---|
| clusterCIDR | 10.200.0.0/16 | Pod들이 사용하는 전체 네트워크 대역 |
| → node-0 PodCIDR | 10.200.0.0/24 | node-0에 할당된 Pod IP 대역 |
| → node-1 PodCIDR | 10.200.1.0/24 | node-1에 할당된 Pod IP 대역 |
| ServiceCIDR | 10.32.0.0/24 | Service들이 사용하는 네트워크 대역 |
| → kubernetes Service | 10.32.0.1 | API Server에 접근하기 위한 ClusterIP |
클러스터 내부의 Pod들은 kubernetes DNS 이름 또는 10.32.0.1 IP를 통해 API Server에 접근한다. 따라서 API Server 인증서의 SAN에는 이러한 모든 접근 경로가 포함되어야 한다.
# service-accounts: ServiceAccount 토큰 서명용 인증서
openssl x509 -in service-accounts.crt -text -noout | grep "Subject:"
Subject: CN = service-accounts
인증서 배포
생성한 인증서를 각 노드에 배포한다. 각 노드는 역할에 따라 필요한 인증서만 받는다.
배포 전략
클러스터는 Control Plane 노드(server)와 Worker 노드(node-0, node-1)로 구성되어 있다. 각 노드 유형에 따라 필요한 인증서가 다르다.
| 노드 유형 | 필요한 인증서 | 이유 |
|---|---|---|
| Worker Node | ca.crt, node-X.crt, node-X.key |
kubelet이 API Server와 통신하기 위해 필요 |
| Control Plane | ca.key, ca.crt, kube-api-server.*, service-accounts.* |
API Server 운영 및 ServiceAccount 토큰 서명에 필요 |
Worker Node 배포
Worker Node(node-0, node-1)에는 kubelet이 실행된다. kubelet은 아래 역할을 한다.
- 클라이언트 역할: API Server에 노드 상태와 Pod 정보를 보고
- 서버 역할: API Server와 다른 컴포넌트로부터 요청을 받음
따라서 각 Worker Node에는 다음이 필요하다:
ca.crt: API Server 인증서 검증용kubelet.crt,kubelet.key: kubelet의 클라이언트/서버 인증서
# (jumpbox) Worker Node에 인증서 배포
for host in node-0 node-1; do
ssh root@${host} mkdir /var/lib/kubelet/
scp ca.crt root@${host}:/var/lib/kubelet/
scp ${host}.crt \
root@${host}:/var/lib/kubelet/kubelet.crt
scp ${host}.key \
root@${host}:/var/lib/kubelet/kubelet.key
done
배포 후 node-0에 접속해 확인해 보자.
# (node-0) 배포된 인증서 확인
root@node-0:~# ls -l /var/lib/kubelet
total 12
-rw-r--r-- 1 root root 1899 Jan 8 00:14 ca.crt
-rw-r--r-- 1 root root 2147 Jan 8 00:14 kubelet.crt
-rw------- 1 root root 3272 Jan 8 00:14 kubelet.key
Control Plane 배포
Control Plane(server)에는 API Server를 비롯한 핵심 컴포넌트들이 실행된다. 필요한 인증서 목록은 다음과 같다.
ca.key,ca.crt: 다른 인증서 서명 및 검증kube-api-server.key,kube-api-server.crt: API Server의 TLS 통신service-accounts.key,service-accounts.crt: ServiceAccount 토큰 생성 및 검증
아래와 같이 배포한다. root@server:~/ 경로는 server 노드의 root 사용자 홈 디렉토리(/root)를 의미한다. 이후 단계에서 API Server 설정 시 이 경로의 인증서를 참조한다.
# (jumpbox) Control Plane에 인증서 배포
scp ca.key ca.crt \
kube-api-server.key kube-api-server.crt \
service-accounts.key service-accounts.crt \
root@server:~/
배포 후 server 노드에서 확인해 보자.
# (server) 배포된 인증서 확인
root@server:~# ls -l /root
total 24
-rw-r--r-- 1 root root 1899 Jan 8 00:21 ca.crt
-rw------- 1 root root 3272 Jan 8 00:21 ca.key
-rw-r--r-- 1 root root 2354 Jan 8 00:21 kube-api-server.crt
-rw------- 1 root root 3272 Jan 8 00:21 kube-api-server.key
-rw-r--r-- 1 root root 2004 Jan 8 00:21 service-accounts.crt
-rw------- 1 root root 3272 Jan 8 00:21 service-accounts.key
나머지 인증서들(admin, kube-proxy, kube-scheduler, kube-controller-manager)은 다음 단계에서 kubeconfig 파일 생성에 사용된다.
결과
이 단계를 완료하면 다음과 같은 결과를 얻을 수 있다:
- Root CA 생성: Self-Signed 방식으로
ca.key,ca.crt생성 - 컴포넌트 인증서 생성: admin, kubelet(node-0, node-1), kube-proxy, kube-scheduler, kube-controller-manager, kube-api-server, service-accounts
- 인증서 배포: Worker Node에는
ca.crt와 각 노드의 kubelet 인증서, Control Plane에는 CA 키/인증서와 API Server, ServiceAccount 인증서 배포
이번 실습을 통해 Kubernetes 클러스터의 mTLS 통신에 필요한 인증서들을 직접 생성하고 배포해 보았다. 각 컴포넌트의 Subject(CN, O)가 Kubernetes RBAC과 연동되는 방식, API Server 인증서에 다양한 SAN이 필요한 이유 등을 이해할 수 있었다. CA 개인키는 Control Plane 외에 배포하지 않도록 주의해야 한다.
다음 글에서는 생성한 인증서를 이용해 kubeconfig 파일을 생성한다.
댓글남기기