Certified Kubernetes Administrator (CKA) #27 풀스케일 실기 모의고사: 17개 작업 + 해설

#1 시험 환경에서 #26 시험 팁까지 전 도메인을 모두 다뤘습니다. 이번 글은 실제 CKA 형식에 맞춘 실기 모의고사입니다. 전 도메인을 통합한 17개 작업으로 구성했고, 각 작업별로 배점이 정해져 있습니다.

권장 제한 시간은 실제 시험과 같은 2시간입니다. 합격선은 **66%**이며, 17개 작업의 배점을 합산해 채점합니다. 한 작업에 막히면 표시해 두고 넘어간 뒤 배점 높고 손에 익은 작업부터 점수를 쌓는 운영이 합격선을 넘기는 길입니다.

CKA는 클러스터가 여러 개라 context 전환을 가장 먼저 하는 습관이 오답을 막습니다. 각 작업은 지정된 컨텍스트에서 풀어야 채점되며, 노드 안으로 들어가는 작업이 많으므로 SSH,systemctl,etcdctl 감각이 함께 필요합니다. 각 작업은 먼저 스스로 끝까지 푼 뒤 정답을 펼쳐 보십시오. 정답을 먼저 읽으면 손이 익지 않습니다.

풀이 방법 #

  1. kubeadm으로 세운 멀티 노드 클러스터에서 푸는 것이 가장 실전에 가깝습니다. 로컬에서 어렵다면 클라우드 VM 두세 대로 control plane 한 대와 워커 한 대를 세워 두십시오. etcd 백업,복구와 노드 트러블슈팅은 단일 노드 minikube로는 감각이 살지 않습니다.
  2. 작업마다 지정된 컨텍스트를 먼저 전환합니다. 이 시리즈에서 반복한 대로 컨텍스트 오설정은 정답을 써도 0점입니다.
k config use-context <문제에서 지정한 컨텍스트>
  1. SSH로 노드에 들어가는 작업이 있으므로, 시험에서 제시하는 호스트명(node01 등)으로 접속이 되는지 미리 확인합니다. 노드 위에서 root 권한이 필요하면 sudo -i로 전환합니다.
  2. 17개를 끝까지 푼 뒤 정답을 펼쳐 한 번에 채점합니다. 중간에 정답을 보면 실제 시험 감각이 흐려집니다. #1에서 잡은 alias k=kubectlexport do="--dry-run=client -o yaml" 셋업을 먼저 적용하면 시간을 아낍니다.

도메인 분포 #

실제 CKA의 도메인 비중에 맞춰 17개 작업을 배치했습니다. Troubleshooting이 30%로 가장 크므로 작업 수도 가장 많습니다.

#도메인작업 수작업 번호
1Cluster Architecture, Installation, Configuration51, 2, 3, 4, 5
2Workloads and Scheduling36, 7, 8
3Services and Networking39, 10, 11
4Storage212, 13
5Troubleshooting414, 15, 16, 17

배점은 도메인 비중과 작업 난이도를 반영해 합계 100점으로 두었습니다. 채점 기준은 글 끝에 정리했습니다.


작업 1 (8점): Cluster Architecture, Installation, Configuration #

컨텍스트 cluster1에서 실행 중인 etcd의 스냅샷을 /opt/etcd-backup.db에 저장하세요. control plane 노드에 SSH로 접속해 작업하며, etcd는 static Pod로 떠 있고 인증서는 /etc/kubernetes/pki/etcd 아래에 있습니다.

정답

control plane 노드에 접속해 root로 전환한 뒤 스냅샷을 저장합니다.

ssh cluster1-controlplane
sudo -i

ETCDCTL_API=3 etcdctl snapshot save /opt/etcd-backup.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

저장이 끝나면 스냅샷 상태를 확인합니다.

ETCDCTL_API=3 etcdctl --write-out=table snapshot status /opt/etcd-backup.db

해설: snapshot save는 클라이언트 인증서로 etcd에 접속하므로 --cacert,--cert,--key 세 가지가 모두 필요하며, 경로는 etcd static Pod 매니페스트(/etc/kubernetes/manifests/etcd.yaml)의 --cert-file,--key-file,--trusted-ca-file 값에서 확인할 수 있습니다. ETCDCTL_API=3을 빼면 v2 API로 동작해 명령이 실패하는 것이 가장 흔한 함정입니다.

작업 2 (8점): Cluster Architecture, Installation, Configuration #

작업 1에서 만든 스냅샷 /opt/etcd-backup.db를 새 데이터 디렉터리 /var/lib/etcd-restore로 복구하고, etcd static Pod가 이 디렉터리를 쓰도록 매니페스트를 수정하세요. 복구 후 클러스터가 정상화되는지 확인합니다.

정답

스냅샷을 새 디렉터리로 복원합니다.

ETCDCTL_API=3 etcdctl snapshot restore /opt/etcd-backup.db \
  --data-dir=/var/lib/etcd-restore

etcd static Pod 매니페스트에서 host 경로를 새 디렉터리로 바꿉니다.

vim /etc/kubernetes/manifests/etcd.yaml
  volumes:
    - name: etcd-data
      hostPath:
        path: /var/lib/etcd-restore
        type: DirectoryOrCreate

kubelet이 매니페스트 변경을 감지해 etcd Pod를 다시 띄울 때까지 기다린 뒤 확인합니다.

crictl ps | grep etcd
k get nodes

해설: snapshot restore는 새 데이터 디렉터리를 만들 뿐 etcd를 재시작하지 않습니다. 실제 전환은 static Pod 매니페스트의 hostPath.path를 새 디렉터리로 바꿔, kubelet이 etcd Pod를 새 데이터로 재생성하게 하는 단계에서 일어납니다. --data-dir의 부모 경로 권한과 기존 etcd 컨테이너가 잠시 내려가는 점을 주의합니다.

작업 3 (8점): Cluster Architecture, Installation, Configuration #

컨텍스트 cluster1의 control plane과 워커 노드를 v1.31.0에서 v1.31.1로 업그레이드하세요. 먼저 control plane을 업그레이드한 뒤 워커 node01을 업그레이드합니다. 워커 업그레이드 중에는 워크로드를 비워야 합니다.

정답

control plane 노드에서 kubeadm을 올린 뒤 업그레이드 계획을 확인하고 적용합니다.

ssh cluster1-controlplane
sudo -i

apt-get update && apt-get install -y kubeadm=1.31.1-1.1
kubeadm upgrade plan
kubeadm upgrade apply v1.31.1

control plane의 kubelet과 kubectl을 올린 뒤 drain,uncordon으로 재기동합니다.

kubectl drain cluster1-controlplane --ignore-daemonsets
apt-get install -y kubelet=1.31.1-1.1 kubectl=1.31.1-1.1
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon cluster1-controlplane

워커는 control plane에서 drain한 뒤 노드 위에서 업그레이드합니다.

kubectl drain node01 --ignore-daemonsets

ssh node01
sudo -i
apt-get update && apt-get install -y kubeadm=1.31.1-1.1
kubeadm upgrade node
apt-get install -y kubelet=1.31.1-1.1
systemctl daemon-reload && systemctl restart kubelet
exit

kubectl uncordon node01

해설: control plane은 kubeadm upgrade apply, 워커는 kubeadm upgrade node를 쓰는 것이 핵심 차이입니다. 업그레이드 순서는 kubeadm 설치 → upgrade → kubelet,kubectl 설치 → kubelet 재시작이며, --ignore-daemonsets 없이 drain하면 DaemonSet Pod 때문에 막힙니다. 버전 점프는 한 마이너씩만 허용됩니다.

작업 4 (8점): Cluster Architecture, Installation, Configuration #

네임스페이스 dev에서만 동작하는 ServiceAccount deployer를 만들고, 이 ServiceAccount가 dev 안에서 Deployment를 생성,조회,수정,삭제할 수 있도록 RBAC을 구성하세요. 권한이 올바른지 kubectl auth can-i로 검증합니다.

정답

ServiceAccount와 Role, RoleBinding을 만듭니다.

k -n dev create serviceaccount deployer
k -n dev create role deploy-manager \
  --verb=create,get,list,update,delete \
  --resource=deployments.apps
k -n dev create rolebinding deployer-binding \
  --role=deploy-manager \
  --serviceaccount=dev:deployer

권한을 검증합니다.

k -n dev auth can-i create deployments --as=system:serviceaccount:dev:deployer
k -n dev auth can-i delete deployments --as=system:serviceaccount:dev:deployer

해설: Role은 네임스페이스 한정 권한이고, RoleBinding이 그 Role을 주체(여기서는 ServiceAccount)에 연결합니다. --resource=deployments.apps처럼 API 그룹까지 명시하지 않으면 core 그룹으로 잡혀 빈 규칙이 될 수 있습니다. 검증 시 주체는 system:serviceaccount:<ns>:<name> 형식으로 적어야 하며, 두 명령 모두 yes가 나와야 합니다.

작업 5 (6점): Cluster Architecture, Installation, Configuration #

/etc/kubernetes/pki/apiserver.crt 인증서의 만료일을 확인하고, kubeadm으로 클러스터 인증서를 모두 갱신하세요. 갱신 후 만료일이 미래로 바뀌었는지 다시 확인합니다.

정답

control plane 노드에서 인증서 만료 현황을 확인합니다.

ssh cluster1-controlplane
sudo -i

kubeadm certs check-expiration

특정 인증서의 만료일을 직접 보고 싶으면 openssl로 확인합니다.

openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddate

모든 인증서를 갱신한 뒤 control plane static Pod를 재기동합니다.

kubeadm certs renew all
systemctl restart kubelet

해설: kubeadm certs check-expiration은 각 인증서와 kubeconfig의 만료일을 표로 보여 줍니다. certs renew all은 인증서 파일만 새로 발급하므로, apiserver 등 static Pod가 새 인증서를 읽도록 컴포넌트를 재기동해야 실제로 반영됩니다. kubeadm은 control plane 업그레이드 때 인증서를 자동 갱신하므로, 정기 업그레이드가 곧 갱신을 겸하기도 합니다.


작업 6 (6점): Workloads and Scheduling #

네임스페이스 apps에 fluentd 로그 수집기를 모든 노드에 정확히 하나씩 띄우는 DaemonSet log-agent를 만드세요. 이미지는 fluent/fluentd:v1.16을 쓰며, control plane 노드의 taint 때문에 거기에는 뜨지 않아도 됩니다.

정답

dry-run으로 뼈대를 만들 수 없는 kind이므로 매니페스트를 직접 작성합니다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-agent
  namespace: apps
spec:
  selector:
    matchLabels:
      app: log-agent
  template:
    metadata:
      labels:
        app: log-agent
    spec:
      containers:
        - name: fluentd
          image: fluent/fluentd:v1.16
k apply -f log-agent.yaml
k -n apps get ds log-agent

해설: DaemonSet은 워커 노드마다 Pod를 하나씩 배치하며 replicas 필드가 없습니다. control plane 노드에는 보통 node-role.kubernetes.io/control-plane:NoSchedule taint가 있어 별도 toleration 없이는 뜨지 않으므로, “거기에는 뜨지 않아도 된다"는 요구는 toleration을 추가하지 않는 것으로 충족됩니다. Deployment와 달리 selector와 template label이 일치해야 합니다.

작업 7 (7점): Workloads and Scheduling #

워커 노드 node01gpu=true:NoSchedule taint를 걸고, 네임스페이스 apps에 이 taint를 견디면서 disktype=ssd 라벨이 있는 노드에만 스케줄되는 Pod ml-job(이미지 nginx)을 만드세요.

정답

노드에 taint를 걸고 라벨이 있는지 확인합니다.

k taint node node01 gpu=true:NoSchedule
k label node node01 disktype=ssd

toleration과 nodeAffinity를 가진 Pod를 만듭니다.

apiVersion: v1
kind: Pod
metadata:
  name: ml-job
  namespace: apps
spec:
  tolerations:
    - key: gpu
      operator: Equal
      value: "true"
      effect: NoSchedule
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disktype
                operator: In
                values:
                  - ssd
  containers:
    - name: ml-job
      image: nginx
k apply -f ml-job.yaml
k -n apps get pod ml-job -o wide

해설: taint는 노드가 Pod를 밀어내는 장치이고, toleration은 Pod가 그 taint를 견디겠다는 선언입니다. toleration만 있으면 다른 노드에도 뜰 수 있으므로, 특정 노드로 고정하려면 nodeAffinity나 nodeSelector를 함께 써야 합니다. toleration의 value는 따옴표로 문자열로 두어야 "true" 불리언 해석 오류를 피합니다.

작업 8 (7점): Workloads and Scheduling #

네임스페이스 data에 StatefulSet cache를 만드세요. 이미지는 redis:7, replicas는 3이며, headless Service cache로 Pod에 안정적인 DNS 이름을 부여하세요. 각 Pod는 volumeClaimTemplates로 1Gi PVC를 갖습니다.

정답

headless Service와 StatefulSet을 함께 정의합니다.

apiVersion: v1
kind: Service
metadata:
  name: cache
  namespace: data
spec:
  clusterIP: None
  selector:
    app: cache
  ports:
    - port: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cache
  namespace: data
spec:
  serviceName: cache
  replicas: 3
  selector:
    matchLabels:
      app: cache
  template:
    metadata:
      labels:
        app: cache
    spec:
      containers:
        - name: redis
          image: redis:7
          ports:
            - containerPort: 6379
          volumeMounts:
            - name: data
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
k apply -f cache.yaml
k -n data get pods -l app=cache

해설: StatefulSet은 serviceName으로 headless Service(clusterIP: None)와 묶여야 cache-0.cache.data.svc.cluster.local 형태의 안정적 DNS를 부여합니다. volumeClaimTemplates는 Pod마다 별도 PVC를 자동 생성하며, Pod가 재생성돼도 같은 PVC에 다시 붙습니다. Pod 이름이 0부터 순서대로 붙는 것도 Deployment와의 차이입니다.


작업 9 (6점): Services and Networking #

네임스페이스 net에 nginx 이미지의 Deployment webreplicas 2로 만들고, 클러스터 외부에서 노드의 30080 포트로 접근 가능한 NodePort Service web-np로 노출하세요. Service의 포트는 80, 타깃 포트는 80입니다.

정답
k -n net create deploy web --image=nginx --replicas=2
k -n net expose deploy web --name=web-np --type=NodePort --port=80 --target-port=80

nodePort 값을 30080으로 고정합니다.

k -n net patch svc web-np --type='json' \
  -p='[{"op":"replace","path":"/spec/ports/0/nodePort","value":30080}]'

해설: expose로 NodePort를 만들면 nodePort는 30000〜32767 범위에서 자동 할당되므로, 특정 값으로 고정하려면 patch하거나 dry-run YAML에 nodePort: 30080을 직접 적어 apply합니다. 검증은 curl <노드IP>:30080으로 응답을 확인합니다.

작업 10 (6점): Services and Networking #

네임스페이스 net에 Ingress web-ing를 만드세요. 호스트 web.example.com의 경로 /로 들어오는 트래픽을 작업 9의 Service web-np의 80 포트로 라우팅하며 pathType은 Prefix, ingressClassName은 nginx를 사용하세요.

정답
k -n net create ingress web-ing \
  --class=nginx \
  --rule="web.example.com/*=web-np:80" $do > ing.yaml

생성된 매니페스트는 다음과 같습니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ing
  namespace: net
spec:
  ingressClassName: nginx
  rules:
    - host: web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-np
                port:
                  number: 80
k apply -f ing.yaml

해설: create ingress--rulehost/path=service:port 형식이며, 경로 끝의 /*pathType: Prefix로 변환됩니다. --class=nginx를 빼면 ingressClassName이 비어 기본 IngressClass가 없는 클러스터에서 라우팅이 안 됩니다. 실제 동작에는 ingress-nginx 컨트롤러가 필요하지만 채점은 리소스 정의의 정확성을 봅니다.

작업 11 (6점): Services and Networking #

네임스페이스 net에 NetworkPolicy db-allow를 만드세요. 라벨 app=db를 가진 Pod로의 ingress 트래픽을, 같은 네임스페이스에서 라벨 role=api를 가진 Pod가 TCP 5432 포트로 접근할 때만 허용하고 그 외 ingress는 모두 차단하세요.

정답
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow
  namespace: net
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: api
      ports:
        - protocol: TCP
          port: 5432
k apply -f db-allow.yaml

해설: podSelector는 정책이 적용될 대상 Pod(app=db)를 고르고, ingress.from은 허용할 출발지(role=api)를 고릅니다. NetworkPolicy는 한 Pod에 하나라도 적용되면 명시되지 않은 트래픽이 모두 차단되므로 별도 deny-all 없이도 그 외 ingress가 막힙니다. fromports를 같은 규칙에 두면 두 조건의 AND가 되며, Calico 같은 정책 지원 CNI에서만 강제됩니다.


작업 12 (8점): Storage #

네임스페이스 storage에 호스트 경로 /mnt/data를 쓰는 2Gi PersistentVolume pv-data(accessMode ReadWriteOnce, reclaimPolicy Retain)를 만들고, 이를 정확히 바인딩하는 1Gi PVC pvc-data를 만든 뒤, 이 PVC를 /usr/share/nginx/html에 마운트하는 Pod pv-user(nginx)를 만드세요.

정답

PV, PVC, Pod를 차례로 정의합니다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-data
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /mnt/data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-data
  namespace: storage
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pv-user
  namespace: storage
spec:
  containers:
    - name: nginx
      image: nginx
      volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: pvc-data
k apply -f pv.yaml
k -n storage get pvc pvc-data

해설: PVC가 PV에 바인딩되려면 accessMode가 같고 PV의 용량이 PVC 요청보다 크거나 같아야 합니다(2Gi ≥ 1Gi). PV는 클러스터 전역 리소스라 namespace가 없고, PVC와 Pod는 같은 네임스페이스에 있어야 합니다. reclaimPolicy: Retain이면 PVC를 지워도 PV의 데이터가 남으며 PV는 Released 상태가 됩니다.

작업 13 (6점): Storage #

네임스페이스 storage에 동적 프로비저닝용 StorageClass fast를 만드세요. provisioner는 rancher.io/local-path, volumeBindingModeWaitForFirstConsumer, allowVolumeExpansion은 true입니다. 그다음 이 StorageClass로 3Gi PVC pvc-fast를 만드세요.

정답

StorageClass와 PVC를 정의합니다.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-fast
  namespace: storage
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast
  resources:
    requests:
      storage: 3Gi
k apply -f sc.yaml
k -n storage get pvc pvc-fast

해설: WaitForFirstConsumer는 PVC를 쓰는 Pod가 스케줄되기 전까지 볼륨 생성을 미루므로, PVC만 만들면 Pending 상태로 머무는 것이 정상입니다. allowVolumeExpansion: true여야 나중에 PVC의 resources.requests.storage를 키워 온라인 확장을 할 수 있습니다. PVC에 storageClassName을 지정하지 않으면 기본 StorageClass가 쓰입니다.


작업 14 (7점): Troubleshooting #

컨텍스트 cluster1의 워커 노드 node01NotReady 상태입니다. 원인을 진단하고 노드를 Ready로 되돌리세요.

정답

먼저 노드 상태와 condition을 확인합니다.

k get nodes
k describe node node01

노드에 접속해 kubelet 상태와 로그를 봅니다.

ssh node01
sudo -i
systemctl status kubelet
journalctl -u kubelet -e

kubelet이 멈춰 있으면 재기동하고, 설정 오류면 로그가 가리키는 파일을 고친 뒤 재기동합니다.

systemctl enable --now kubelet
systemctl restart kubelet

해설: NotReady의 가장 흔한 원인은 kubelet 서비스가 죽었거나(systemctl restart kubelet), kubeconfig,CA 경로 오류, 디스크,메모리 pressure입니다. describe node의 Conditions와 journalctl -u kubelet이 원인을 가르며, kubelet이 부팅 시 비활성이면 enable --now로 부팅 자동 시작까지 켜 둡니다. control plane 쪽 문제가 아니라 노드 자체를 봐야 한다는 점이 핵심입니다.

작업 15 (7점): Troubleshooting #

컨텍스트 cluster2의 control plane에서 kubectlconnection refused로 응답하지 않습니다. apiserver static Pod 매니페스트가 잘못 수정되었습니다. 원인을 찾아 고치고 apiserver를 정상화하세요.

정답

control plane 노드에 접속해 apiserver 컨테이너 상태를 봅니다.

ssh cluster2-controlplane
sudo -i

crictl ps -a | grep kube-apiserver
crictl logs <apiserver-container-id>

static Pod 매니페스트를 검토해 잘못된 필드(예: 오타 난 포트, 잘못된 인증서 경로, 깨진 들여쓰기)를 고칩니다.

vim /etc/kubernetes/manifests/kube-apiserver.yaml

kubelet이 매니페스트 변경을 감지해 apiserver Pod를 다시 띄울 때까지 기다린 뒤 확인합니다.

crictl ps | grep kube-apiserver
k get nodes

해설: apiserver가 떠야 kubectl이 동작하므로 이 상태에서는 kubectl 대신 crictl로 컨테이너 로그를 직접 읽는 것이 핵심입니다. static Pod는 매니페스트 파일을 고치면 kubelet이 자동으로 재생성하므로, 파일을 잠깐 다른 곳으로 옮겼다 되돌리면 강제 재시작 효과가 납니다. --etcd-servers 경로나 인증서 경로 오타가 흔한 원인입니다.

작업 16 (7점): Troubleshooting #

네임스페이스 apps의 Service frontend로 보내는 요청이 응답하지 않습니다. Pod는 모두 Running이지만 Service의 endpoint가 비어 있습니다. 원인을 진단해 트래픽이 Pod에 닿도록 고치세요.

정답

Service와 endpoint, Pod 라벨을 비교합니다.

k -n apps get svc frontend -o wide
k -n apps get endpoints frontend
k -n apps describe svc frontend
k -n apps get pods --show-labels

Service의 selector와 Pod의 라벨이 어긋나 있으면 selector를 Pod 라벨에 맞게 고칩니다.

k -n apps patch svc frontend \
  -p '{"spec":{"selector":{"app":"frontend"}}}'
k -n apps get endpoints frontend

해설: endpoint가 비어 있다는 것은 Service의 selector가 어떤 Pod 라벨과도 일치하지 않는다는 신호입니다. Pod가 Running이어도 selector가 어긋나면 endpoint에 등록되지 않아 트래픽이 닿지 않습니다. selector를 고친 뒤 get endpoints에 Pod IP가 채워지는지로 검증하며, 포트 이름,번호 불일치도 함께 점검합니다.

작업 17 (7점): Troubleshooting #

네임스페이스 apps의 Pod reportCrashLoopBackOff 상태입니다. 원인을 진단하고, 가장 최근에 종료된 컨테이너의 직전 로그를 파일 /tmp/report.log에 저장한 뒤, 원인이 메모리 부족(OOMKilled)이라면 메모리 limit을 256Mi로 올려 정상화하세요.

정답

상태와 종료 원인을 진단합니다.

k -n apps describe pod report
k -n apps logs report --previous > /tmp/report.log

Last StateOOMKilled이면 메모리 limit을 올립니다. Pod의 resources는 직접 수정할 수 없으므로 매니페스트를 받아 고친 뒤 재생성합니다.

k -n apps get pod report -o yaml > report.yaml
    resources:
      limits:
        memory: "256Mi"
      requests:
        memory: "128Mi"
k -n apps delete pod report
k apply -f report.yaml

해설: CrashLoopBackOff는 컨테이너가 반복 종료되는 상태라 현재 로그가 비어 있을 수 있으므로 --previous(-p)로 직전 컨테이너 로그를 가져옵니다. describeLast State Reason이 OOMKilled이면 메모리 limit 부족이 원인입니다. 실행 중인 Pod의 resources는 불변이라 매니페스트를 고쳐 삭제,재생성해야 하며, Deployment가 관리하는 Pod라면 Deployment 쪽 template을 고칩니다.


채점 기준 #

각 작업의 배점을 합산해 채점합니다. 합계는 100점이며 66점 이상이 합격권입니다.

도메인작업,배점소계
Cluster Architecture, Installation, Configuration1(8) , 2(8) , 3(8) , 4(8) , 5(6)38
Workloads and Scheduling6(6) , 7(7) , 8(7)20
Services and Networking9(6) , 10(6) , 11(6)18
Storage12(8) , 13(6)14
Troubleshooting14(7) , 15(7) , 16(7) , 17(7)28
합계(합계)100

채점은 실제 시험과 같이 결과물 기준입니다. 명령을 어떻게 쳤는지가 아니라 만들어진 리소스와 복구된 클러스터 상태가 요구사항과 일치하는지를 봅니다. 한 작업 안에서도 라벨,포트,경로,필드 같은 항목별로 부분 점수가 나뉘므로, 막히는 항목이 있어도 채울 수 있는 부분은 끝까지 채우는 편이 점수에 유리합니다.

도메인별 약점 복습 #

채점 후 점수가 낮은 도메인을 아래 표의 해당 글로 돌아가 복습하십시오.

도메인관련 작업복습할 글
Cluster Architecture, Installation, Configuration1, 2, 3, 4, 5#6 , #7 , #8 , #9
Workloads and Scheduling6, 7, 8#11 , #13 , #14
Services and Networking9, 10, 11#18 , #19 , #20
Storage12, 13#16 , #17
Troubleshooting14, 15, 16, 17#22 , #23 , #24 , #25

특정 작업에서 시간이 부족했다면 도메인 지식보다 손 빠르기 문제일 수 있습니다. 이 경우 #1 셋업#26 시간 관리를 다시 읽고, 같은 17개 작업을 시간을 재며 한 번 더 푸십시오. etcd 백업,복구와 노드 트러블슈팅은 한 번 손에 익으면 작업당 소요 시간이 눈에 띄게 줄어듭니다.

시리즈를 마치며 #

#1의 시험 환경 셋업에서 출발해 control plane,노드,kubeadm 설치,HA,업그레이드,etcd 백업복구,인증서,RBAC,워크로드,스케줄링,리소스,스토리지,Service,Ingress,NetworkPolicy,트러블슈팅 4종까지 CKA 전 도메인을 27편에 걸쳐 모두 정리했습니다. 이 모의고사에서 66점을 넘겼다면 실제 시험장에서도 충분히 합격선을 넘길 수 있는 실기 감각이 갖춰진 것입니다. 축하합니다.

CKA가 클러스터 운영자의 실기였다면, 다음 단계는 그 클러스터를 안전하게 지키는 **CKS(Certified Kubernetes Security Specialist)**이며, 여기서 익힌 etcd,인증서,RBAC,트러블슈팅 감각이 그대로 발판이 되니 합격의 기세를 이어 다음 실기로 넘어가시기를 권합니다.

X