Certified Kubernetes Administrator (CKA) #25 Troubleshooting 4: Networking, DNS, RBAC, 인증서 만료
#24 Troubleshooting 3에서 control plane 컴포넌트가 죽었을 때의 진단과 etcd 복구까지 다뤘습니다. 노드도 살아 있고 control plane도 정상인데, 통신이 안 되거나 권한이 막히는 경우가 남았습니다. 트러블슈팅 마지막 글인 이번 #25에서는 네트워크와 권한 영역, 즉 서비스 통신 실패, DNS 해석 실패, RBAC 거부, 인증서 만료를 차례로 진단하겠습니다.
이 영역의 공통점은 에러 메시지가 원인을 직접 가리키지 않는다는 점입니다. Pod는 Running인데 curl이 타임아웃되고, 클러스터는 멀쩡한데 kubectl이 갑자기 거부당합니다. 그래서 증상에서 원인까지 좁혀 들어가는 진단 순서를 손에 익히는 것이 핵심입니다.
서비스 통신이 안 될 때 #
가장 자주 나오는 증상입니다. 클라이언트 Pod에서 서비스 이름이나 ClusterIP로 접속했는데 응답이 없습니다. 서비스 통신은 이름 해석 → 서비스 → Endpoints → 대상 Pod → 네트워크 정책의 단계로 흐르므로, 이 흐름을 따라 한 단계씩 끊어 보면 원인이 좁혀집니다.
1단계: 진짜 네트워크 문제인지 분리한다 #
먼저 클라이언트 Pod 안에서 직접 호출해 봅니다. 서비스 이름으로 실패하면 DNS 문제일 수 있으니, ClusterIP로도 한번 호출해 두 경우를 갈라 둡니다.
# 클라이언트 Pod 안에서 서비스 이름으로
k exec -it client -- curl -sS http://my-svc:80
# 같은 호출을 ClusterIP로 (이름 해석을 건너뜀)
k get svc my-svc # CLUSTER-IP 확인
k exec -it client -- curl -sS http://10.96.0.42:80서비스 이름은 실패하는데 ClusterIP는 되면 DNS 문제이므로 뒤의 DNS 절로 넘어갑니다. ClusterIP도 안 되면 서비스 또는 Endpoints, 네트워크 정책 쪽을 봅니다.
2단계: Endpoints가 비었는지 본다 #
서비스 트러블슈팅의 절반은 여기서 끝납니다. 서비스가 트래픽을 보낼 대상 Pod 목록이 Endpoints(또는 EndpointSlice)인데, 이게 비어 있으면 서비스는 ClusterIP만 있고 받아 줄 곳이 없습니다.
# Endpoints 확인 (비어 있으면 <none>)
k get endpoints my-svc
k get endpointslices -l kubernetes.io/service-name=my-svcEndpoints가 비어 있다면 원인은 셋 중 하나입니다.
| 원인 | 확인 | 조치 |
|---|---|---|
| selector 불일치 | 서비스 selector와 Pod labels를 비교 | 라벨 또는 selector를 맞춤 |
| 대상 Pod가 Ready 아님 | k get pod -l app=...의 READY 열 | readinessProbe 또는 앱 자체를 고침 |
| targetPort 불일치 | 서비스 targetPort와 컨테이너 containerPort | 포트를 맞춤 |
selector 불일치가 가장 흔합니다. 서비스의 selector와 Pod의 라벨을 직접 비교합니다.
# 서비스의 selector
k get svc my-svc -o jsonpath='{.spec.selector}'
# 그 selector로 실제 Pod가 잡히는지
k get pod --selector app=webselector로 Pod가 하나도 안 잡히면 라벨이 어긋난 것입니다. 또 하나 흔한 함정은 Pod는 Running인데 Ready가 아닌 경우입니다. Endpoints에는 Ready 상태인 Pod만 올라가므로, readinessProbe가 실패하면 Running이어도 Endpoints에서 빠집니다.
3단계: kube-proxy를 확인한다 #
Endpoints는 채워져 있는데 ClusterIP로 접속이 안 되면, ClusterIP를 실제 Pod로 전달하는 kube-proxy를 의심합니다. kube-proxy는 보통 각 노드에 DaemonSet으로 떠 있으면서 iptables 또는 IPVS 규칙을 노드에 깝니다.
# kube-proxy DaemonSet 상태
k -n kube-system get pods -l k8s-app=kube-proxy -o wide
# 특정 노드의 kube-proxy 로그
k -n kube-system logs ds/kube-proxykube-proxy Pod가 일부 노드에서 죽어 있으면 그 노드에 뜬 클라이언트만 통신이 안 되는, 위치에 따라 달라지는 증상이 나옵니다. CNI 플러그인(Calico, Cilium 등) Pod도 같은 방식으로 상태를 확인합니다.
4단계: NetworkPolicy가 막았는지 본다 #
위가 다 정상인데도 막힌다면 NetworkPolicy가 트래픽을 차단했을 가능성이 큽니다. 네트워크 정책은 일단 한 Pod에 하나라도 걸리면 명시적으로 허용하지 않은 트래픽을 기본 거부로 바꿉니다.
# 대상 네임스페이스의 정책 목록
k -n prod get networkpolicy
# 특정 정책 상세 (어떤 ingress/egress를 허용하는지)
k -n prod describe networkpolicy default-denydescribe에서 봐야 할 곳은 두 가지입니다. podSelector가 어떤 Pod에 걸리는지, 그리고 Ingress/Egress 규칙이 어떤 출발지,도착지를 허용하는지입니다. default-deny 정책만 있고 허용 규칙이 없으면 그 네임스페이스의 통신이 전부 막힙니다. 또 흔히 빠뜨리는 함정은 egress와 ingress를 양쪽 다 열어야 한다는 점입니다. 클라이언트의 egress가 허용되어도 서버의 ingress가 막혀 있으면 통신은 실패합니다. DNS를 막아 버리는 실수도 잦은데, egress 정책을 걸 때 kube-system의 CoreDNS로 가는 UDP/TCP 53번을 빠뜨리면 이름 해석부터 깨집니다. 정책 문법과 동작은 #20 Networking 3에서 자세히 다뤘습니다.
DNS 해석이 실패할 때 #
서비스 이름은 실패하고 ClusterIP는 성공한 경우, 또는 외부 도메인 해석이 안 되는 경우입니다. 클러스터 DNS는 CoreDNS가 담당하므로 여기를 중심으로 좁힙니다.
CoreDNS Pod 상태부터 #
# CoreDNS Pod와 서비스
k -n kube-system get pods -l k8s-app=kube-dns -o wide
k -n kube-system get svc kube-dns
# CoreDNS 로그 (에러가 찍히는지)
k -n kube-system logs -l k8s-app=kube-dnsCoreDNS Pod가 CrashLoop이거나 0개로 떠 있으면 클러스터 전체의 이름 해석이 깨집니다. 로그에 plugin/errors 같은 메시지가 보이면 ConfigMap의 Corefile 설정을 확인합니다.
k -n kube-system get configmap coredns -o yamlnslookup으로 직접 테스트한다 #
진단용 Pod를 띄워 클러스터 안에서 직접 이름을 해석해 봅니다. 외부 이미지가 막혀 있으면 기존 Pod 안에서 nslookup이나 getent hosts를 써도 됩니다.
# 일회용 진단 Pod
k run dnsutils --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 \
--rm -it --restart=Never -- bash
# 클러스터 내부 서비스 해석
nslookup my-svc.default.svc.cluster.local
# kubernetes 기본 서비스 (이게 안 되면 DNS 자체가 죽음)
nslookup kubernetes.default
# 외부 도메인 (이것만 안 되면 upstream/forward 문제)
nslookup example.com해석 결과로 원인이 갈립니다. kubernetes.default조차 안 되면 CoreDNS 또는 kube-dns 서비스 자체의 문제이고, 내부는 되는데 외부만 안 되면 CoreDNS의 forward 설정이나 노드의 upstream DNS 문제입니다.
/etc/resolv.conf를 본다 #
Pod의 DNS 설정은 /etc/resolv.conf에 들어갑니다. nameserver가 kube-dns 서비스의 ClusterIP를 가리키고, search도메인이 클러스터 접미사를 포함해야 짧은 이름 해석이 동작합니다.
# Pod 안의 resolv.conf
k exec -it client -- cat /etc/resolv.confnameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5nameserver의 IP가 k -n kube-system get svc kube-dns의 ClusterIP와 다르면 DNS가 엉뚱한 곳을 가리키는 것입니다. search 줄이 비어 있으면 my-svc 같은 짧은 이름이 안 풀리고 FQDN으로만 풀립니다. 노드 자체의 DNS가 의심되면 노드의 /etc/resolv.conf와 kubelet의 --resolv-conf 설정까지 확인합니다.
DNS 진단 순서 정리 #
| 증상 | 의심 지점 | 확인 명령 |
|---|---|---|
| 모든 이름 해석 실패 | CoreDNS Pod 다운 | k -n kube-system get pods -l k8s-app=kube-dns |
| 내부는 되나 외부 실패 | CoreDNS forward / upstream | logs, configmap coredns |
| 짧은 이름만 실패 | resolv.conf의 search | k exec ... cat /etc/resolv.conf |
| 한 Pod만 실패 | 그 Pod의 dnsPolicy/resolv.conf | Pod spec의 dnsPolicy |
RBAC으로 거부될 때 #
kubectl이나 ServiceAccount가 Forbidden에러로 막히는 경우입니다. 클러스터는 멀쩡하므로 네트워크가 아니라 권한의 문제입니다.
Forbidden 에러를 읽는다 #
RBAC 에러 메시지는 형식이 정해져 있어, 그 자체가 진단의 출발점입니다.
Error from server (Forbidden): pods is forbidden:
User "dev" cannot list resource "pods" in API group "" in the namespace "prod"이 한 줄에 필요한 정보가 다 들어 있습니다. 주체는 User "dev", 동사는 list, 리소스는 pods, API 그룹은 ""(core), 네임스페이스는 prod입니다. 즉 dev라는 사용자에게 prod 네임스페이스의 pods를 list할 권한이 없다는 뜻이므로, 이 다섯 가지를 충족하는 Role과 Binding이 있는지 확인하면 됩니다.
auth can-i로 권한을 확인한다 #
권한 여부는 추측하지 말고 auth can-i로 직접 묻습니다.
# 내 권한
k auth can-i list pods -n prod
# 특정 사용자/ServiceAccount의 권한 (관리자가 대신 확인)
k auth can-i list pods -n prod --as=dev
k auth can-i create deployments -n prod \
--as=system:serviceaccount:prod:builder
# 가진 권한 전체 나열
k auth can-i --list -n prod --as=dev--as로 다른 주체를 흉내 내어 묻는 것이 진단의 핵심입니다. --list는 그 주체가 가진 모든 권한을 표로 보여 주므로, 무엇이 빠졌는지 한눈에 들어옵니다.
Role/Binding이 누락됐는지 본다 #
no가 나오면 권한 사슬의 어디가 끊겼는지 짚습니다. 권한은 주체 → Binding → Role → 규칙으로 이어지므로, Binding이 없거나 Role이 없거나 Role의 규칙이 부족한 세 경우입니다.
# 네임스페이스의 Role과 RoleBinding
k -n prod get role,rolebinding
k -n prod describe rolebinding dev-binding
# 클러스터 범위라면
k get clusterrole,clusterrolebinding | grep -i devdescribe rolebinding에서 두 가지를 맞춰 봅니다. Subjects에 문제의 사용자나 ServiceAccount가 정확한 이름,종류로 들어 있는지, 그리고 Role이 가리키는 대상이 실제로 존재하며 필요한 동사,리소스를 담고 있는지입니다. 자주 나오는 함정은 다음과 같습니다.
- RoleBinding은 있는데 가리키는 Role이 없음. roleRef의 오타나 삭제된 Role을 가리키면 권한이 0이 됩니다
- Subject의 종류 혼동.
User,Group,ServiceAccount는 다릅니다. ServiceAccount는kind: ServiceAccount에 네임스페이스까지 맞아야 합니다 - 범위 불일치. 네임스페이스 권한이 필요한데 ClusterRole만 있거나, 그 반대인 경우입니다. 클러스터 범위 권한은 RoleBinding이 아니라 ClusterRoleBinding으로 묶어야 전 네임스페이스에 적용됩니다
RBAC의 객체 모델과 ServiceAccount 토큰까지는 #9 RBAC에서 다뤘으니, 여기서는 막혔을 때 좁혀 들어가는 흐름에 집중하겠습니다.
인증서가 만료됐을 때 #
가장 당황스러운 증상은 어느 날 kubectl이 통째로 거부당하는 것입니다. 클러스터는 돌고 있는데 인증이 안 되면 인증서 만료를 가장 먼저 의심합니다. kubeadm 클러스터의 control plane 인증서는 기본 유효 기간이 1년이라, 업그레이드 없이 오래 둔 클러스터에서 잘 터집니다.
증상: kubectl 인증 실패 #
Unable to connect to the server: x509: certificate has expired or is not yet valid이 x509 ... expired 메시지가 보이면 거의 확정입니다. 네트워크 오류(connection refused)와는 다른 메시지이므로 헷갈리지 않습니다.
check-expiration으로 확인한다 #
control plane 노드에서 kubeadm으로 모든 인증서의 만료일을 한번에 봅니다.
# control plane 노드 안에서
kubeadm certs check-expirationCERTIFICATE EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
admin.conf Jun 01, 2026 09:00 UTC <invalid> no
apiserver Jun 01, 2026 09:00 UTC <invalid> no
apiserver-etcd-client Jun 01, 2026 09:00 UTC <invalid> no
...RESIDUAL TIME이 <invalid>이거나 음수면 이미 만료된 것입니다. 개별 인증서 파일을 직접 확인하려면 openssl로도 만료일을 읽을 수 있습니다.
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddate갱신하고 kubeconfig를 다시 받는다 #
kubeadm으로 만료된 인증서를 갱신합니다. 갱신 후에는 control plane 컴포넌트(static Pod)를 새 인증서로 다시 띄우고, 관리자용 kubeconfig도 새로 복사해야 합니다.
# 모든 인증서 갱신
kubeadm certs renew all
# static Pod 재시작 (manifests를 잠시 빼냈다 되돌려 강제 재기동)
# 또는 kubelet 재시작으로 새 인증서를 반영
systemctl restart kubelet
# 갱신된 admin.conf를 다시 사용
cp /etc/kubernetes/admin.conf ~/.kube/config
# 다시 정상 동작하는지
kubeadm certs check-expiration
k get nodes갱신 직후에도 kubectl이 같은 에러를 내면, 현재 사용 중인 사용자 kubeconfig가 옛 인증서를 그대로 담고 있는 경우입니다.
갱신은 /etc/kubernetes/admin.conf를 새로 쓰므로, 그 파일을 사용자 kubeconfig 위치로 다시 복사해야 반영됩니다. PKI 구조와 갱신 절차의 배경은 #8 인증서 관리에서 다뤘습니다.
증상별 진단 순서 한눈에 #
네트워크,권한 영역의 트러블슈팅을 증상에서 출발해 정리하면 다음과 같습니다. 시험에서는 이 표의 첫 칸(증상)만 주어지므로, 다음 두 칸을 머릿속에서 곧장 떠올릴 수 있어야 시간을 아낍니다.
| 증상 | 먼저 볼 곳 | 핵심 명령 |
|---|---|---|
| 서비스 이름,IP 모두 응답 없음 | Endpoints 비었나 → selector | k get endpoints, k get svc -o jsonpath |
| 이름만 실패, ClusterIP는 성공 | CoreDNS → resolv.conf | nslookup, k -n kube-system logs -l k8s-app=kube-dns |
| Endpoints는 정상인데 막힘 | kube-proxy → NetworkPolicy | k -n kube-system get pods -l k8s-app=kube-proxy, k get netpol |
Forbidden에러 | 에러 4요소 → Role/Binding | k auth can-i --as=, k get role,rolebinding |
x509 certificate has expired | 인증서 만료 | kubeadm certs check-expiration |
kubectl connection refused | apiserver 다운 (#24) | crictl ps, static Pod 로그 |
시험 포인트: 트러블슈팅 종합 #
#22부터 이어 온 트러블슈팅 4편을 시험 관점에서 묶겠습니다.
- 증상에서 시작해 한 단계씩 끊는다. 서비스 통신은 Endpoints, DNS는 CoreDNS와 resolv.conf, 권한은 Forbidden 4요소, 인증은 x509 메시지가 첫 분기점입니다
- Endpoints가 비었는지부터 본다. 서비스 트러블슈팅에서 가장 점수가 빠른 확인이며, 원인은 거의 selector 불일치이거나 Pod가 Ready가 아닌 경우입니다
auth can-i --as로 추측을 없앤다. 권한 문제는 묻지 말고 직접 흉내 내어 물으면 빠집니다.--list로 빠진 권한을 한눈에 봅니다- 인증서는
check-expiration한 줄. 갱신 후~/.kube/config재복사를 잊지 않습니다 - 고친 뒤에는 반드시 재현해 확인한다. curl이 다시 통하는지,
k get nodes가 다시 도는지 결과로 검증해야 채점됩니다 - context를 먼저 맞춘다. 트러블슈팅 문제는 지정된 클러스터,노드에서 풀어야 채점되므로
use-context를 먼저 실행합니다
트러블슈팅은 비중 30%로 단일 도메인 중 가장 크고, 다른 도메인의 지식을 전제로 합니다. 여기까지 따라왔다면 아키텍처,워크로드,네트워킹,스토리지의 지식이 진단 흐름으로 연결됐을 것입니다.
다음: 시험 팁 #
트러블슈팅까지 전 도메인을 모두 다뤘습니다. 이제 남은 것은 2시간을 어떻게 운영하느냐입니다.
#26 시험 팁과 시간 관리, 자주 틀리는 패턴에서는 작업 순서를 정하는 법, 부분 점수를 노리는 전략, 막혔을 때 넘어가는 기준, 그리고 selector 불일치,context 누락,kubeconfig 미복사처럼 합격선을 깎아 먹는 단골 실수를 모아 정리하겠습니다.