CA 설정 및 TLS 인증서 생성 (PKI 구성)**
목표 : CA 설정 및 TLS 인증서 생성 (PKI 구성)
- 쿠버네티스의 모든 컴포넌트는 서로를 신뢰하기 위해 CA(Certificate Authority)를 통해 서명된 인증서가 필요하다.
- 크게 CA 생성 → 클라이언트/서버 인증서 생성 → 각 노드로 배포 순서로 진행한다.
| 항목 | 개인키 | CSR | 인증서 | 참고 정보 (DN / SAN) | Extended Key Usage |
|---|---|---|---|---|---|
| Root CA | ca.key | X | ca.crt | CN = CA | - |
| admin | admin.key | admin.csr | admin.crt | CN = admin, O = system:masters | TLS Web Client Authentication |
| node-0 | node-0.key | node-0.csr | node-0.crt | CN = system:node:node-0, O = system:nodes | TLS Web Server / Client Authentication |
| node-1 | node-1.key | node-1.csr | node-1.crt | CN = system:node:node-1, O = system:nodes | TLS Web Server / Client Authentication |
| kube-proxy | kube-proxy.key | kube-proxy.csr | kube-proxy.crt | CN = system:kube-proxy, O = system:node-proxier | TLS Web Server / Client Authentication |
| kube-scheduler | kube-scheduler.key | kube-scheduler.csr | kube-scheduler.crt | CN = system:kube-scheduler, O = system:kube-scheduler | TLS Web Server / Client Authentication |
| kube-controller-manager | kube-controller-manager.key | kube-controller-manager.csr | kube-controller-manager.crt | CN = system:kube-controller-manager, O = system:kube-controller-manager | TLS Web Server / Client Authentication |
| kube-api-server | kube-api-server.key | kube-api-server.csr | kube-api-server.crt | CN = kubernetes, SAN: IP(127.0.0.1, 10.32.0.1), DNS(kubernetes,..) | TLS Web Server / Client Authentication |
| service-accounts | service-accounts.key | service-accounts.csr | service-accounts.crt | CN = service-accounts | TLS Web Client Authentication |
| 항목 | 네트워크 대역 or IP | 비고 |
|---|---|---|
| clusterCIDR | 10.200.0.0/16 | 전체 Pod 네트워크 |
| node-0 PodCIDR | 10.200.0.0/24 | node-0 할당 대역 |
| node-1 PodCIDR | 10.200.1.0/24 | node-1 할당 대역 |
| ServiceCIDR | 10.32.0.0/24 | Service 네트워크 |
| api clusterIP | 10.32.0.1 | Kubernetes Service IP |
먼저 인증서 생성에 필요한 설정 파일(ca.conf).
여기서 각 컴포넌트의 CN(Common Name)과 O(Organization), 그리고 SAN(Subject Alternative Name)이 정의된다.
💡 핵심 포인트:
- Admin:
O=system:masters(이 그룹은 RBAC 검사를 무시하고 무조건 슈퍼유저 권한을 가짐. 매우 중요!)- API Server:
10.32.0.1(Service Network 첫 IP)와kubernetes도메인 등이 SAN에 포함되어야 함.
| 구분 | 역할 |
|---|---|
[req] | OpenSSL 요청 기본 동작 |
[ca_*] | CA 인증서 |
[admin] | 관리자 (kubectl) |
[service-accounts] | ServiceAccount 토큰 서명 |
[node-*] | 워커 노드(kubelet) |
[kube-proxy] | kube-proxy |
[kube-controller-manager] | 컨트롤러 |
[kube-scheduler] | 스케줄러 |
[kube-api-server] | API Server |
[default_req_extensions] | 공통 CSR 옵션 |
# 작업 디렉토리 이동 (필요 시)
cd ~/
# 1. ca.conf 생성 (OpenSSL 설정 파일)
cat <<EOF > ca.conf
[req]
distinguished_name = req_distinguished_name
prompt = no
x509_extensions = ca_x509_extensions
[ca_x509_extensions]
basicConstraints = CA:TRUE
keyUsage = cRLSign, keyCertSign
[req_distinguished_name]
C = US
ST = Washington
L = Seattle
CN = CA
[admin]
distinguished_name = admin_distinguished_name
prompt = no
req_extensions = default_req_extensions
[admin_distinguished_name]
CN = admin
O = system:masters
[service-accounts]
distinguished_name = service-accounts_distinguished_name
prompt = no
req_extensions = default_req_extensions
[service-accounts_distinguished_name]
CN = service-accounts
[node-0]
distinguished_name = node-0_distinguished_name
prompt = no
req_extensions = node-0_req_extensions
[node-0_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
nsComment = "Node-0 Certificate"
subjectAltName = DNS:node-0, IP:127.0.0.1
subjectKeyIdentifier = hash
[node-0_distinguished_name]
CN = system:node:node-0
O = system:nodes
C = US
ST = Washington
L = Seattle
[node-1]
distinguished_name = node-1_distinguished_name
prompt = no
req_extensions = node-1_req_extensions
[node-1_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
nsComment = "Node-1 Certificate"
subjectAltName = DNS:node-1, IP:127.0.0.1
subjectKeyIdentifier = hash
[node-1_distinguished_name]
CN = system:node:node-1
O = system:nodes
C = US
ST = Washington
L = Seattle
[kube-proxy]
distinguished_name = kube-proxy_distinguished_name
prompt = no
req_extensions = kube-proxy_req_extensions
[kube-proxy_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
nsComment = "Kube Proxy Certificate"
subjectAltName = DNS:kube-proxy, IP:127.0.0.1
subjectKeyIdentifier = hash
[kube-proxy_distinguished_name]
CN = system:kube-proxy
O = system:node-proxier
C = US
ST = Washington
L = Seattle
[kube-controller-manager]
distinguished_name = kube-controller-manager_distinguished_name
prompt = no
req_extensions = kube-controller-manager_req_extensions
[kube-controller-manager_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
nsComment = "Kube Controller Manager Certificate"
subjectAltName = DNS:kube-controller-manager, IP:127.0.0.1
subjectKeyIdentifier = hash
[kube-controller-manager_distinguished_name]
CN = system:kube-controller-manager
O = system:kube-controller-manager
C = US
ST = Washington
L = Seattle
[kube-scheduler]
distinguished_name = kube-scheduler_distinguished_name
prompt = no
req_extensions = kube-scheduler_req_extensions
[kube-scheduler_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
nsComment = "Kube Scheduler Certificate"
subjectAltName = DNS:kube-scheduler, IP:127.0.0.1
subjectKeyIdentifier = hash
[kube-scheduler_distinguished_name]
CN = system:kube-scheduler
O = system:kube-scheduler
C = US
ST = Washington
L = Seattle
[kube-api-server]
distinguished_name = kube-api-server_distinguished_name
prompt = no
req_extensions = kube-api-server_req_extensions
[kube-api-server_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client, server
nsComment = "Kube API Server Certificate"
subjectAltName = @kube-api-server_alt_names
subjectKeyIdentifier = hash
[kube-api-server_alt_names]
IP.0 = 127.0.0.1
IP.1 = 10.32.0.1
DNS.0 = kubernetes
DNS.1 = kubernetes.default
DNS.2 = kubernetes.default.svc
DNS.3 = kubernetes.default.svc.cluster
DNS.4 = kubernetes.svc.cluster.local
DNS.5 = server.kubernetes.local
DNS.6 = api-server.kubernetes.local
[kube-api-server_distinguished_name]
CN = kubernetes
C = US
ST = Washington
L = Seattle
[default_req_extensions]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
nsComment = "Admin Client Certificate"
subjectKeyIdentifier = hash
EOF
ca.conf 파일은 OpenSSL에게 "이런 조건으로 인증서를 만들어줘"라고 지시하는 설계도와 같다.
| 용어 (Term) | 풀이 (Description) | 쿠버네티스에서의 의미 |
|---|---|---|
| CN (Common Name) | 이름. 인증서의 주인. | 사용자 ID(admin, kubelet) 또는 호스트네임(kubernetes)으로 사용됨. |
| O (Organization) | 조직/소속. | 그룹(Group)으로 매핑됨. RBAC 권한 제어의 핵심. (예: system:masters) |
| DN (Distinguished Name) | 식별자. | CN, O, 국가(C), 지역(L) 등을 합쳐서 부르는 고유한 이름 정보. |
| basicConstraints | 제약 조건. | CA:TRUE면 이 인증서로 다른 인증서를 발급(서명)할 수 있음. (일반 인증서는 FALSE) |
| keyUsage | 키 용도. | 이 열쇠를 어디에 쓸지 제한함. (서명용, 암호화용 등) |
| extendedKeyUsage | 확장 용도. | clientAuth(클라이언트 신분증), serverAuth(서버 신분증) 구분. |
| SAN (Subject Alt Name) | 대체 이름. | 하나의 인증서로 여러 IP나 도메인을 커버할 때 사용. (API 서버가 대표적) |
[req] 섹션OpenSSL이 CSR(인증서 서명 요청)을 만들 때 기본적으로 따를 규칙이다.
distinguished_name: "이름 입력 양식은 어디에 정의되어 있니?" (req_distinguished_name 참조)x509_extensions: "인증서 만들 때 확장 기능(옵션)은 뭘 넣을까?" (ca_x509_extensions 참조)keyUsage (열쇠의 용도)extendedKeyUsage (서버 vs 클라이언트)kubelet이나 api-server는 양쪽 역할(서버이면서 누군가의 클라이언트)을 다 하기 때문에 둘 다(serverAuth, clientAuth) 가지고 있음.O = system:masters (절대 권력)admin 섹션에 있는 이 설정이 제일 무서운 권력자.
O)을 가진 인증서가 들어오면 모든 권한 검사(RBAC)를 프리패스한다..SAN (Subject Alternative Name)kube-api-server 섹션을 보면 주소가 엄청 많다.
127.0.0.1: 로컬에서 접속할 때.10.32.0.1: 클러스터 내부에서 Service IP로 접속할 때.kubernetes.default...: 파드들이 도메인으로 접속할 때.| 항목 | 개인키 | CSR | 인증서 | 참고 정보 (DN / SAN) | Extended Key Usage |
|---|---|---|---|---|---|
| Root CA | ca.key | X | ca.crt | CN = CA | - |
# 1. CA 개인키 생성 (ca.key)
openssl genrsa -out ca.key 4096
# 2. CA 인증서 생성 (ca.crt) - Self Signed
openssl req -x509 -new -sha512 -noenc \
-key ca.key -days 3653 \
-config ca.conf \
-out ca.crt
# 확인
ls -l ca.*
#
-rw-r--r-- 1 root root 4820 Jan 11 00:09 ca.conf
-rw-r--r-- 1 root root 1899 Jan 11 00:09 ca.crt
-rw------- 1 root root 3268 Jan 11 00:09 ca.key
openssl x509 -in ca.crt -text -noout | grep -A 2 "Validity"
#
Validity
Not Before: Jan 10 15:09:26 2026 GMT
Not After : Jan 11 15:09:26 2036 GMT
| 항목 | 개인키 | CSR | 인증서 | 참고 정보 (DN / SAN) | Extended Key Usage |
|---|---|---|---|---|---|
| admin | admin.key | admin.csr | admin.crt | CN = admin, O = system:masters | TLS Web Client Authentication |
| node-0 | node-0.key | node-0.csr | node-0.crt | CN = system:node:node-0, O = system:nodes | TLS Web Server / Client Authentication |
| node-1 | node-1.key | node-1.csr | node-1.crt | CN = system:node:node-1, O = system:nodes | TLS Web Server / Client Authentication |
| kube-proxy | kube-proxy.key | kube-proxy.csr | kube-proxy.crt | CN = system:kube-proxy, O = system:node-proxier | TLS Web Server / Client Authentication |
| kube-scheduler | kube-scheduler.key | kube-scheduler.csr | kube-scheduler.crt | CN = system:kube-scheduler, O = system:kube-scheduler | TLS Web Server / Client Authentication |
| kube-controller-manager | kube-controller-manager.key | kube-controller-manager.csr | kube-controller-manager.crt | CN = system:kube-controller-manager, O = system:kube-controller-manager | TLS Web Server / Client Authentication |
| kube-api-server | kube-api-server.key | kube-api-server.csr | kube-api-server.crt | CN = kubernetes, SAN: IP(127.0.0.1, 10.32.0.1), DNS(kubernetes,..) | TLS Web Server / Client Authentication |
| service-accounts | service-accounts.key | service-accounts.csr | service-accounts.crt | CN = service-accounts | TLS Web Client Authentication |
# 1. 생성할 인증서 목록 정의
certs=(
"admin"
"node-0" "node-1"
"kube-proxy" "kube-scheduler"
"kube-controller-manager"
"kube-api-server"
"service-accounts"
)
# 2. 루프: 개인키 -> CSR -> CA 서명 -> CRT 생성
for i in ${certs[*]}; do
echo "Generating ${i} certificate..."
# 개인키 생성
openssl genrsa -out "${i}.key" 4096
# CSR 생성 (ca.conf의 섹션 참조)
openssl req -new -key "${i}.key" -sha256 \
-config "ca.conf" -section ${i} \
-out "${i}.csr"
# CA로 서명하여 인증서(CRT) 생성
openssl x509 -req -days 3653 -in "${i}.csr" \
-copy_extensions copyall \
-sha256 -CA "ca.crt" \
-CAkey "ca.key" \
-CAcreateserial \
-out "${i}.crt"
done
#
Generating admin certificate...
Certificate request self-signature ok
subject=CN = admin, O = system:masters
Generating node-0 certificate...
Certificate request self-signature ok
subject=CN = system:node:node-0, O = system:nodes, C = US, ST = Washington, L = Seattle
Generating node-1 certificate...
Certificate request self-signature ok
subject=CN = system:node:node-1, O = system:nodes, C = US, ST = Washington, L = Seattle
Generating kube-proxy certificate...
Certificate request self-signature ok
subject=CN = system:kube-proxy, O = system:node-proxier, C = US, ST = Washington, L = Seattle
Generating kube-scheduler certificate...
Certificate request self-signature ok
subject=CN = system:kube-scheduler, O = system:kube-scheduler, C = US, ST = Washington, L = Seattle
Generating kube-controller-manager certificate...
Certificate request self-signature ok
subject=CN = system:kube-controller-manager, O = system:kube-controller-manager, C = US, ST = Washington, L = Seattle
Generating kube-api-server certificate...
Certificate request self-signature ok
subject=CN = kubernetes, C = US, ST = Washington, L = Seattle
Generating service-accounts certificate...
Certificate request self-signature ok
subject=CN = service-accounts
# 3. 결과 확인
ls -1 *.crt *.key *.csr
#
admin.crt
admin.csr
admin.key
ca.crt
ca.key
kube-api-server.crt
kube-api-server.csr
kube-api-server.key
kube-controller-manager.crt
kube-controller-manager.csr
kube-controller-manager.key
kube-proxy.crt
kube-proxy.csr
kube-proxy.key
kube-scheduler.crt
kube-scheduler.csr
kube-scheduler.key
node-0.crt
node-0.csr
node-0.key
node-1.crt
node-1.csr
node-1.key
service-accounts.crt
service-accounts.csr
service-accounts.key
📚 Study Note: `system:masters
admin.crt를 확인해보면O=system:masters`로 되어 있어.
쿠버네티스 API 서버는 이 그룹에 속한 요청은 RBAC 검사를 아예 생략하고 슈퍼유저 권한을 부여해. 만약 이 키가 탈취되면 클러스터는 끝장나는 거야. (AWS Root 계정급)
생성된 파일들을 각 서버의 적절한 위치로 보내자.
node-0, node-1): CA 인증서와 본인들의 Kubelet 인증서 필요.server): CA 키/인증서, API Server 인증서, Service Account 키 필요.# 1. Worker Node 배포 (node-0, node-1)
for host in node-0 node-1; do
echo "--- Copying certs to ${host} ---"
# 원격 디렉토리 생성
ssh root@${host} mkdir -p /var/lib/kubelet/
# CA 인증서 복사
scp ca.crt root@${host}:/var/lib/kubelet/
# 해당 노드의 인증서와 키를 'kubelet.crt/key'라는 이름으로 복사
scp ${host}.crt root@${host}:/var/lib/kubelet/kubelet.crt
scp ${host}.key root@${host}:/var/lib/kubelet/kubelet.key
done
# 2. Control Plane 배포 (server)
echo "--- Copying certs to server ---"
scp \
ca.key ca.crt \
kube-api-server.key kube-api-server.crt \
service-accounts.key service-accounts.crt \
root@server:~/
# 3. 배포 확인
echo "Checking node-0..."
ssh node-0 ls -l /var/lib/kubelet/
#
total 12
-rw-r--r-- 1 root root 1899 Jan 11 00:50 ca.crt
-rw-r--r-- 1 root root 2147 Jan 11 00:50 kubelet.crt
-rw------- 1 root root 3272 Jan 11 00:50 kubelet.key
echo "Checking server..."
ssh server ls -l /root/
#
total 24
-rw-r--r-- 1 root root 1899 Jan 11 00:50 ca.crt
-rw------- 1 root root 3268 Jan 11 00:50 ca.key
-rw-r--r-- 1 root root 2354 Jan 11 00:50 kube-api-server.crt
-rw------- 1 root root 3272 Jan 11 00:50 kube-api-server.key
-rw-r--r-- 1 root root 2004 Jan 11 00:50 service-accounts.crt
-rw------- 1 root root 3272 Jan 11 00:50 service-accounts.key
참고용으로 가져온 Kind 클러스터 정보를 보면 우리가 하는 작업이 표준과 일치함을 알 수 있어.
364d), 우리는 10년(3653 days)으로 설정했어. (실무에선 보통 1년 후 갱신)10.96.0.1(Service IP), 127.0.0.1, kubernetes DNS 등을 SAN에 포함하고 있어. 우리 설정(10.32.0.1 등)도 이 규칙을 따른 거야.--service-account-key-file과 --service-account-signing-key-file을 사용하여 토큰을 서명하고 검증해. 우리도 service-accounts.key를 생성해서 server로 보냈지.다음 단계:
인증서 준비가 끝났으니, 이제 이 인증서들을 사용해서 Chapter 5. Kubernetes Configuration Files (kubeconfig) 생성 단계로 넘어가면 되겠어. 각 컴포넌트가 사용할 kubeconfig 파일을 만들어야 해.
이 이미지는 "쿠버네티스 The Hard Way"의 4단계(인증서)와 네트워크 설정이다.
딱 두 가지 주제로 나누면 이해가 쉽다.
쿠버네티스는 "모두가 서로를 의심하는 세계(Zero Trust)"다.
그래서 서버, 관리자, 노드 할 것 없이 전부 신분증(인증서)이 필요하다.
이 표는 "누구에게(Name), 어떤 권한(Group)으로 신분증을 만들어 줄 것인가?"를 정리한 것이다.
ca.crt)admin.crt)kubectl 명령어를 칠 때 이 신분증을 쓴다.O = system:masters ← 이 부분이 "신(God)" 등급 권한을 의미함. 이게 없으면 명령 거부당함.node.crt)CN에는 노드 이름이 들어가고, O = system:nodes 그룹에 속해야 노드로 인정받음.kube-api-server.crt)kubernetes야." (DNS)127.0.0.1이야." (로컬)10.32.0.1이야." (내부 서비스 IP)쿠버네티스는 IP 대역을 용도별로 철저하게 나눠 쓴다. "이 IP는 누구네 집 주소인가"를 정한 것이다.
10.200.0.0/16)10.200.0.0/24, 10.200.1.0/24)node-0에 뜨는 파드는 무조건 10.200.0.x를 받음.node-1에 뜨는 파드는 무조건 10.200.1.x를 받음.10.32.0.0/24)10.32.0.1)Service CIDR의 첫 번째 IP는 관례적으로 API 서버가 가져감.10.32.0.1)를 씀.kube-api-server 인증서의 SAN에 10.32.0.1이 적혀있는 이유가 바로 이것 때문임.
CFSSL은 한마디로 "Cloudflare가 만든 인증서 발급 자판기"다.
원래 리눅스에서 인증서 만들려면 openssl이라는 오래된 툴을 써야 하는데, 이게 설정 파일(cnf) 만들기도 어렵고 명령어가 너무 복잡하다.
그래서 Cloudflare 에서 내놓은 게 바로 CFSSL이다.
openssl vs cfssl)KTHW 가이드가 굳이 cfssl을 선택한 이유는 딱 하나다. "편하니까."
| 비교 | OpenSSL (구 방식) | CFSSL (요즘 방식) |
|---|---|---|
| 설정 방식 | 이상한 문법의 .cnf 파일 작성 | 깔끔한 JSON 파일 작성 |
| 명령어 | 옵션이 수십 개라 외우기 힘듦 | gencert 하나면 끝 |
| 자동화 | 스크립트 짜기 어려움 | JSON이라 자동화/API 연동 쉬움 |
내 생각:
openssl로 CA 만들고 서명하려면 명령어 3~4번 쳐야 하는데,cfssl은 JSON 파일 하나 던져주면 알아서 CA랑 키랑 인증서까지 한 방에 뱉어준다.
명령어를 보면 항상 cfssl이랑 cfssljson이 세트로 다닌다.
cfssl gencert ... | cfssljson -bare admin
이게 무슨 짓이냐면:
cfssl gencert ...:| (파이프):cfssljson -bare admin:.pem, .key)로 저장해."admin.pem, admin-key.pem, admin.csr 파일 3개 생성.openssl 대신 쓰는 모던한 인증서 툴이다.| cfssljson을 꼭 붙여야 실제 파일로 떨어진다.