Skip to main content

자격증 공부는 쿠버네티스를 손에 익히는 훈련이다

CKA와 CKAD는 쿠버네티스를 "안다"는 느낌을 "할 수 있다"로 바꾸는 시험이다. 객관식 문제를 고르는 시험이 아니라, 주어진 클러스터에서 직접 리소스를 만들고, 고치고, 확인하는 실습형 시험이다. 제한 시간 안에 터미널에서 문제를 풀어야 하므로 개념을 이해하는 것만으로는 부족하다. 손이 먼저 움직여야 한다.

AI 인프라를 운영하는 독자에게도 이 훈련은 꽤 유용하다. GPU, vLLM, KServe, Kueue, KEDA 같은 도구를 다루기 전에 결국 Pod가 왜 Pending인지, Service가 왜 연결되지 않는지, 특정 ServiceAccount가 왜 권한 오류를 내는지 알아야 한다. LLM 추론 서버가 죽어도 먼저 확인하는 것은 여전히 kubectl describe pod, kubectl logs, kubectl get events다.

CKA는 클러스터 운영자 관점의 시험이다. kubeadm, etcd, 노드 장애, 스토리지, 네트워킹, RBAC, 트러블슈팅 비중이 높다. 반면 CKAD는 애플리케이션 개발자 관점의 시험이다. Pod, Deployment, Job, CronJob, ConfigMap, Secret, Probe, Service, Ingress, SecurityContext를 빠르게 작성하고 수정하는 능력을 본다. 두 시험 모두 2시간 동안 실제 터미널에서 문제를 해결하는 방식이며, CKAD 공식 안내에 따르면 CKAD는 클라우드 네이티브 애플리케이션을 설계·빌드·배포하는 능력을 검증한다.1 CKA 역시 다중 클러스터 환경에서 실제 문제를 해결하는 실습형 시험으로 소개된다.2

이 장은 자격증 전문서처럼 모든 범위를 길게 설명하지 않는다. 대신 이 책에서 이미 다룬 쿠버네티스 기본기를 시험장에서 바로 꺼내 쓸 수 있는 형태로 압축한다. 목표는 하나다. 문제를 읽고, 어떤 리소스를 써야 하는지 판단하고, kubectl로 빠르게 뼈대를 만든 뒤, 필요한 부분만 YAML로 고치는 것이다.

CKA와 CKAD 시험 대비 핵심 정리

CKA와 CKAD의 차이를 먼저 구분하자

CKA와 CKAD는 겹치는 부분이 많다. Pod, Deployment, Service, ConfigMap, Secret, NetworkPolicy, RBAC, 리소스 요청과 제한은 둘 다 나온다고 봐야 한다. 하지만 문제의 시선이 다르다.

CKAD 문제는 보통 이런 식이다.

"backend 네임스페이스에 api Deployment를 만들고, 환경 변수는 ConfigMap에서 가져오며, /healthz 경로로 readinessProbe를 설정하라. Service를 만들어 8080 포트로 접근 가능하게 하라."

이 문제의 중심은 애플리케이션이다. 컨테이너가 어떻게 실행되고, 설정을 어떻게 주입하고, 외부에서 어떻게 접근하게 할지가 핵심이다.

CKA 문제는 조금 다르다.

"특정 노드가 NotReady 상태다. 원인을 찾아 kubelet을 복구하라."

또는 다음처럼 나온다.

"ServiceAccount에 특정 네임스페이스의 Pod 목록 조회 권한을 부여하고, 실제로 권한이 있는지 확인하라."

여기서는 클러스터 운영 능력이 중심이다. 노드, kubelet, etcd, CNI, CoreDNS, RBAC, 스토리지 같은 기반 구성요소를 더 많이 만난다. 특히 CKA는 Troubleshooting 비중이 크기 때문에, 리소스를 새로 만드는 능력보다 "이미 망가진 것을 고치는 능력"이 더 중요하다.3

시험을 준비할 때는 다음처럼 나누면 좋다.

구분CKACKAD
주 역할플랫폼 엔지니어, SRE, 클러스터 관리자백엔드 개발자, 애플리케이션 엔지니어
핵심 시선클러스터가 건강하게 동작하는가애플리케이션이 올바르게 배포되는가
자주 만나는 주제kubeadm, etcd, 노드, RBAC, 네트워킹, 스토리지, 장애 대응Pod, Deployment, Job, CronJob, ConfigMap, Secret, Probe, Service, Ingress
AI 인프라와의 연결GPU 노드, 클러스터 네트워크, 스토리지, 권한, 장애 대응모델 서버 배포, 배치 작업, 추론 API 노출, 설정 주입

AI 인프라 엔지니어라면 보통 CKA를 먼저 추천한다. GPU 노드 운영, 네트워크 장애, 스토리지 바인딩, 노드 드레인, RBAC는 실제 운영에서 피할 수 없다. 다만 백엔드 개발자에서 AI 플랫폼 업무로 넘어오는 독자라면 CKAD부터 준비해도 좋다. CKAD를 준비하면 최소한 모델 서버와 워커를 쿠버네티스 리소스로 표현하는 감각이 생긴다.

시험장 첫 3분: 환경부터 세팅한다

시험이 시작되면 곧바로 문제를 풀고 싶어진다. 하지만 처음 2~3분은 터미널을 손에 맞게 만드는 데 써야 한다. 이 시간은 낭비가 아니라 투자다.

alias k=kubectl
complete -F __start_kubectl k 2>/dev/null || true
export KUBE_EDITOR=vim

자동완성이 가능한 환경이면 다음도 설정한다.

source <(kubectl completion bash)
complete -F __start_kubectl k

vim을 쓴다면 들여쓰기를 맞춰 둔다.

cat <<EOF > ~/.vimrc
set expandtab
set tabstop=2
set shiftwidth=2
EOF

쿠버네티스 YAML은 들여쓰기 하나 때문에 실패한다. 특히 containers, env, volumeMounts, volumes, securityContext처럼 계층이 깊은 필드는 손으로 고치다 보면 쉽게 틀린다. 시험장에서 에디터와 싸우지 않으려면 평소 연습할 때도 같은 환경을 써야 한다.

그리고 문제를 풀기 전에는 반드시 현재 컨텍스트와 네임스페이스를 확인한다.

kubectl config current-context
kubectl config get-contexts
kubectl get ns

문제에서 특정 컨텍스트로 접속하라고 하면 가장 먼저 실행한다.

kubectl config use-context <context-name>

2026년 응시 후기에 따르면 최근 시험은 문제마다 독립된 환경에 접속해 푸는 방식이 언급되기도 한다. 핵심은 같다. 지금 내가 어느 클러스터, 어느 노드, 어느 네임스페이스에서 작업하는지 계속 확인해야 한다.4 잘못된 곳에 완벽한 YAML을 적용하면 점수는 0점이다.

YAML을 처음부터 쓰지 말고 생성하라

CKA와 CKAD에서 가장 중요한 습관은 --dry-run=client -o yaml이다. 시험은 작문 대회가 아니다. 빈 파일에서 YAML을 아름답게 쓰는 능력을 보지 않는다. 제한 시간 안에 동작하는 리소스를 만드는 능력을 본다.

Pod는 이렇게 만든다.

kubectl run nginx \
  --image=nginx \
  --dry-run=client -o yaml > pod.yaml

Deployment는 이렇게 만든다.

kubectl create deployment web \
  --image=nginx \
  --replicas=3 \
  --dry-run=client -o yaml > deploy.yaml

Service는 이미 있는 Deployment에서 뽑아내는 편이 빠르다.

kubectl expose deployment web \
  --port=80 \
  --target-port=8080 \
  --type=ClusterIP \
  --dry-run=client -o yaml > svc.yaml

Job과 CronJob도 명령어로 뼈대를 만든다.

kubectl create job batch-once \
  --image=busybox \
  --dry-run=client -o yaml \
  -- /bin/sh -c "echo hello" > job.yaml

kubectl create cronjob cleanup \
  --image=busybox \
  --schedule="*/30 * * * *" \
  --dry-run=client -o yaml \
  -- /bin/sh -c "date; echo cleanup" > cronjob.yaml

ConfigMap과 Secret도 직접 base64를 계산하기보다 kubectl create로 만든다.

kubectl create configmap app-config \
  --from-literal=APP_MODE=prod \
  --from-literal=LOG_LEVEL=info \
  --dry-run=client -o yaml > cm.yaml

kubectl create secret generic db-secret \
  --from-literal=username=app \
  --from-literal=password='s3cr3t' \
  --dry-run=client -o yaml > secret.yaml

이 방식의 장점은 세 가지다. 첫째, apiVersion과 kind를 틀릴 가능성이 줄어든다. 둘째, 기본 구조가 맞기 때문에 필요한 필드만 추가하면 된다. 셋째, 적용하기 전 파일을 검토할 수 있다.

vim deploy.yaml
kubectl apply -f deploy.yaml
kubectl get deploy web
kubectl get pod -l app=web

시험장에서 "생성 → 수정 → 적용 → 확인"의 리듬을 몸에 익히면 긴 지문이 나와도 덜 흔들린다.

Pod 문제: 가장 작지만 가장 많이 틀리는 단위

Pod 문제는 단순해 보이지만, 실제로는 많은 주제가 Pod 안에 숨어 있다. 환경 변수, 명령어 오버라이드, 볼륨 마운트, 보안 컨텍스트, ServiceAccount, Probe, 리소스 제한이 모두 Pod 스펙에 들어간다.

가장 기본적인 Pod 생성은 다음과 같다.

kubectl run app \
  --image=busybox \
  --restart=Never \
  --dry-run=client -o yaml \
  -- /bin/sh -c "sleep 3600" > pod.yaml

환경 변수를 바로 넣어야 하면 --env를 쓴다.

kubectl run app \
  --image=nginx \
  --env=APP_ENV=prod \
  --dry-run=client -o yaml > pod.yaml

ConfigMap의 특정 키를 환경 변수로 주입할 때는 YAML을 수정한다.

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: nginx
    env:
    - name: APP_MODE
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: APP_MODE

Secret도 구조는 거의 같다.

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: nginx
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password

전체 ConfigMap이나 Secret을 환경 변수로 넣는 문제도 자주 나온다.

spec:
  containers:
  - name: app
    image: nginx
    envFrom:
    - configMapRef:
        name: app-config
    - secretRef:
        name: db-secret

볼륨으로 마운트할 때는 volumes와 volumeMounts가 서로 맞아야 한다.

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: config-volume
      mountPath: /etc/app-config
  volumes:
  - name: config-volume
    configMap:
      name: app-config

AI 모델 서버에서도 이 구조는 그대로 쓰인다. 다만 app-config 대신 모델 이름, 최대 토큰 수, GPU 메모리 사용률, 외부 API 주소 같은 설정이 들어갈 뿐이다. 시험 문제는 작지만, 운영 문제의 축소판이라고 생각하면 된다.

Deployment 문제: 배포, 수정, 롤백까지 한 번에 익힌다

Deployment는 CKAD의 핵심이고 CKA에서도 자주 나온다. 만들고, 스케일하고, 이미지를 바꾸고, 롤백하는 명령어는 외워야 한다.

kubectl create deployment api \
  --image=nginx:1.25 \
  --replicas=3

kubectl get deploy api
kubectl get rs
kubectl get pod -l app=api

레플리카 수를 바꾼다.

kubectl scale deployment api --replicas=5

이미지를 바꾼다.

kubectl set image deployment/api nginx=nginx:1.26

롤아웃 상태를 확인한다.

kubectl rollout status deployment/api
kubectl rollout history deployment/api

문제가 생기면 되돌린다.

kubectl rollout undo deployment/api

특정 리비전으로 되돌릴 수도 있다.

kubectl rollout undo deployment/api --to-revision=2

시험 문제에서 maxSurge, maxUnavailable을 지정하라고 하면 Deployment의 strategy를 수정한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: nginx
        image: nginx:1.26

여기서 중요한 것은 selector.matchLabels와 template.metadata.labels가 맞아야 한다는 점이다. 이 둘이 어긋나면 Deployment는 자기 Pod를 찾지 못한다. 시험장에서 YAML을 복사해 수정하다가 이름이나 라벨만 바꾸고 selector를 놓치는 일이 흔하다.

AI 추론 서버를 배포할 때도 Deployment는 기본 선택지다. vLLM 서버를 여러 개 띄우고 Service로 묶는 구조는 시험의 nginx Deployment와 본질적으로 같다. 차이는 컨테이너 이미지와 GPU 리소스 요청, 모델 볼륨, readinessProbe가 더 복잡할 뿐이다.

Service, Ingress, Gateway 문제: 트래픽의 길을 확인하라

Service 문제는 대부분 "Pod까지 트래픽이 가는가"를 묻는다. Service가 만들어졌는데 연결이 안 되면 대개 selector, port, targetPort 중 하나가 틀렸다.

Deployment를 Service로 노출한다.

kubectl expose deployment api \
  --name=api-svc \
  --port=80 \
  --target-port=8080 \
  --type=ClusterIP

확인한다.

kubectl get svc api-svc
kubectl get endpoints api-svc
kubectl describe svc api-svc

endpoints가 비어 있으면 Service가 Pod를 선택하지 못하고 있다는 뜻이다. 이때는 라벨을 본다.

kubectl get pod --show-labels
kubectl get svc api-svc -o yaml

Service의 selector가 app=api인데 Pod 라벨이 app=web이면 연결되지 않는다.

kubectl label pod <pod-name> app=api --overwrite

또는 Service selector를 고친다.

kubectl edit svc api-svc

Ingress는 외부 HTTP 트래픽을 Service로 라우팅한다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-svc
            port:
              number: 80

2026년 기준 학습 자료에서는 CKA 커리큘럼에 Gateway API를 다루는 흐름도 보인다.5 Gateway API는 Ingress보다 역할을 더 명확히 나눈다. Gateway는 "어디에서 트래픽을 받을 것인가"를 정의하고, HTTPRoute는 "받은 트래픽을 어디로 보낼 것인가"를 정의한다.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: web-gateway
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
spec:
  parentRefs:
  - name: web-gateway
  rules:
  - backendRefs:
    - name: api-svc
      port: 80

Gateway API 문제는 kubectl create로 뼈대를 만들기 어렵다. 이럴 때는 공식 문서 예제를 빠르게 찾아 수정하거나, kubectl explain으로 필드 구조를 확인한다.

kubectl explain gateway.spec
kubectl explain httproute.spec.rules

LLM 추론 API를 외부에 노출할 때도 같은 원리다. 사용자는 https://api.example.com/v1/chat/completions로 요청하지만, 쿠버네티스 안에서는 Gateway 또는 Ingress가 Service를 거쳐 실제 모델 서버 Pod로 보낸다. 시험 문제의 nginx와 운영 환경의 vLLM은 트래픽 경로만 놓고 보면 같은 지도 위에 있다.

ConfigMap과 Secret: 설정은 이미지 밖으로 빼라

시험에서 ConfigMap과 Secret은 거의 점수 자판기처럼 나온다. 하지만 구조를 헷갈리면 시간을 많이 잃는다.

ConfigMap은 평문 설정이다.

kubectl create configmap app-config \
  --from-literal=MODEL_NAME=llama \
  --from-literal=LOG_LEVEL=debug

파일에서 만들 수도 있다.

kubectl create configmap nginx-config --from-file=nginx.conf

Secret은 민감한 값이다.

kubectl create secret generic openai-secret \
  --from-literal=AZURE_OPENAI_API_KEY='xxx'

환경 변수로 하나의 키를 넣는 구조는 다음과 같다.

env:
- name: AZURE_OPENAI_API_KEY
  valueFrom:
    secretKeyRef:
      name: openai-secret
      key: AZURE_OPENAI_API_KEY

볼륨으로 마운트하면 각 키가 파일이 된다.

volumes:
- name: secret-volume
  secret:
    secretName: openai-secret
containers:
- name: app
  image: nginx
  volumeMounts:
  - name: secret-volume
    mountPath: /etc/secrets
    readOnly: true

시험장에서는 kubectl create secret을 쓰는 편이 안전하다. Secret YAML을 직접 쓰면 base64 인코딩을 놓치기 쉽다. 물론 다음 명령으로 확인할 수는 있다.

echo -n 'mypassword' | base64

하지만 제한 시간 안에서는 사람이 직접 인코딩하고 붙여넣는 과정이 실수의 원인이 된다. 가능한 한 명령어로 생성하자.

Probe 문제: 컨테이너를 죽일지, 트래픽만 뺄지 구분한다

Probe는 CKAD에서 매우 중요하고, 실무에서도 매우 중요하다. 특히 LLM 서버처럼 모델 로딩 시간이 긴 애플리케이션은 Probe를 잘못 설정하면 정상 컨테이너가 계속 재시작된다.

livenessProbe는 "이 컨테이너가 살아 있는가"를 본다. 실패하면 kubelet이 컨테이너를 재시작한다.

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 10

readinessProbe는 "지금 트래픽을 받아도 되는가"를 본다. 실패해도 컨테이너를 재시작하지 않는다. 대신 Service 엔드포인트에서 빠진다.

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

startupProbe는 느리게 시작하는 애플리케이션을 보호한다. startupProbe가 성공하기 전까지 livenessProbe와 readinessProbe의 판단을 늦출 수 있다.

startupProbe:
  httpGet:
    path: /startup
    port: 8080
  failureThreshold: 30
  periodSeconds: 10

판별 기준은 간단하다. 죽은 프로세스를 다시 살리고 싶으면 livenessProbe, 준비되지 않은 Pod에 트래픽을 보내지 않으려면 readinessProbe, 시작이 오래 걸리는 앱을 기다리려면 startupProbe다.

LLM 서버에서는 readinessProbe가 특히 중요하다. 컨테이너 프로세스가 떠 있어도 모델 가중치가 GPU 메모리에 올라가기 전에는 요청을 받으면 안 된다. 시험에서는 nginx나 busybox로 나오지만, 실무에서는 이 Probe 하나가 장애와 무중단 배포를 가른다.

RBAC와 ServiceAccount: 누가 무엇을 할 수 있는가

RBAC 문제는 처음엔 어렵게 느껴지지만 구조는 단순하다. "누가", "무엇을", "어디에 대해", "어떤 동작을" 할 수 있는지를 연결하는 문제다.

ServiceAccount를 만든다.

kubectl create serviceaccount log-reader -n app

Role을 만든다. 예를 들어 app 네임스페이스에서 Pod와 Pod 로그를 읽을 수 있게 한다.

kubectl create role pod-log-reader \
  --verb=get,list,watch \
  --resource=pods,pods/log \
  -n app

RoleBinding으로 ServiceAccount에 Role을 묶는다.

kubectl create rolebinding pod-log-reader-binding \
  --role=pod-log-reader \
  --serviceaccount=app:log-reader \
  -n app

권한을 확인한다.

kubectl auth can-i list pods \
  --as=system:serviceaccount:app:log-reader \
  -n app

kubectl auth can-i get pods/log \
  --as=system:serviceaccount:app:log-reader \
  -n app

Pod에서 ServiceAccount를 쓰려면 다음 필드를 넣는다.

spec:
  serviceAccountName: log-reader

Role과 ClusterRole의 차이도 시험에서 자주 묻는다. Role은 특정 네임스페이스 안에서 권한을 준다. ClusterRole은 클러스터 범위 리소스나 여러 네임스페이스에 걸친 권한에 쓴다. 하지만 ClusterRole도 RoleBinding과 묶으면 특정 네임스페이스 안에서만 사용할 수 있다.

AI 에이전트 운영에서는 이 개념이 더 중요해진다. 에이전트가 도구를 호출하거나, 로그를 읽거나, 특정 Job을 만들 수 있다면 그 권한은 반드시 ServiceAccount와 RBAC로 제한해야 한다. 시험 문제의 pod-reader는 운영 환경의 agent-tool-runner가 된다.

NetworkPolicy: 허용 목록을 만드는 문제다

NetworkPolicy는 "차단 규칙"이 아니라 "허용 규칙"에 가깝다. 아무 정책도 Pod를 선택하지 않으면 기본적으로 트래픽은 허용된다. 하지만 어떤 NetworkPolicy가 Pod를 선택하는 순간, 명시적으로 허용한 트래픽만 통과한다.

모든 ingress를 막는 정책은 다음과 같다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: app
spec:
  podSelector: {}
  policyTypes:
  - Ingress

app=backend Pod에 대해 app=frontend Pod에서 오는 8080 포트만 허용하려면 다음처럼 쓴다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: app
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

다른 네임스페이스에서 오는 트래픽을 허용하려면 namespaceSelector를 쓴다.

ingress:
- from:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: frontend
    podSelector:
      matchLabels:
        app: web

여기서 중요한 함정이 있다. namespaceSelector와 podSelector가 같은 항목 안에 있으면 AND 조건이다. 즉 "해당 네임스페이스에 있으면서 해당 라벨을 가진 Pod"만 허용된다. 반대로 from 아래에 항목을 둘로 나누면 OR 조건이 된다.

시험장에서 NetworkPolicy가 나오면 먼저 세 가지를 확인한다.

kubectl get netpol -n app
kubectl describe netpol -n app
kubectl get pod -n app --show-labels

정책 자체를 고치지 못하게 하고 Pod 라벨만 고치라는 문제도 나올 수 있다. 이때는 NetworkPolicy의 selector가 무엇을 기대하는지 보고 Pod에 라벨을 붙인다.

kubectl label pod client role=frontend -n app --overwrite

AI 워크로드에서는 NetworkPolicy가 비용과 보안 모두에 연결된다. LLM 서버가 벡터 DB에는 접근해야 하지만, 임의의 외부 주소로 나가면 안 되는 상황이 흔하다. 시험에서는 작은 네임스페이스 두 개로 나오지만, 실무에서는 테넌트와 도구 호출 권한을 나누는 경계가 된다.

SecurityContext: 어디에 넣는지가 절반이다

SecurityContext 문제는 옵션보다 위치가 더 중요하다. 어떤 필드는 Pod 레벨에 들어가고, 어떤 필드는 컨테이너 레벨에 들어간다.

Pod 전체에 적용하는 설정은 spec.securityContext에 둔다.

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: app
    image: nginx

컨테이너별 설정은 containers[].securityContext에 둔다.

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  containers:
  - name: app
    image: nginx
    securityContext:
      runAsNonRoot: true
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE

시험에서 자주 나오는 조합은 runAsUser, allowPrivilegeEscalation: false, capabilities.add/drop이다. runAsUser를 Pod 레벨에 넣어도 되고 컨테이너 레벨에 넣어도 되지만, allowPrivilegeEscalation과 capabilities는 컨테이너 보안 컨텍스트에 넣는다고 기억하면 안전하다.

적용 후에는 실제 사용자로 실행되는지 확인할 수 있다.

kubectl exec secure-app -- id

컨테이너 안에서 프로세스를 확인한다.

kubectl exec secure-app -- ps aux

AI 에이전트가 코드를 실행하는 환경이라면 이 설정은 단순 시험 문제가 아니다. 루트 권한으로 실행되는 도구 컨테이너, 쓰기 가능한 루트 파일시스템, 불필요한 Linux capability는 모두 침해 사고의 통로가 될 수 있다.

Job, CronJob, DaemonSet, StatefulSet을 빠르게 판별하는 법

시험장에서 어떤 워크로드 리소스를 써야 하는지 헷갈리면 시간을 많이 잃는다. 다음 기준으로 판단하자.

요구사항선택할 리소스판단 기준
항상 떠 있어야 하는 웹/API 서버Deployment레플리카를 유지하고 롤링 업데이트가 필요하다
한 번 실행하고 끝나는 작업Job성공적으로 완료되는 것이 목표다
정해진 시간마다 반복 실행CronJob스케줄 표현식이 있다
모든 노드 또는 특정 노드마다 하나씩 실행DaemonSet로그 수집기, 노드 에이전트, GPU 플러그인처럼 노드 단위로 필요하다
안정적인 이름과 저장소가 필요한 상태ful 앱StatefulSetPod 이름과 PVC가 안정적으로 유지되어야 한다

Job은 완료가 목표다.

kubectl create job pi \
  --image=perl \
  -- perl -Mbignum=bpi -wle 'print bpi(2000)'

Job YAML에서 자주 수정하는 필드는 completions, parallelism, backoffLimit, restartPolicy다.

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  completions: 3
  parallelism: 2
  backoffLimit: 4
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: worker
        image: busybox
        command: ["/bin/sh", "-c", "echo hello"]

CronJob은 스케줄이 핵심이다.

kubectl create cronjob cleanup \
  --image=busybox \
  --schedule="*/30 * * * *" \
  -- /bin/sh -c "echo cleanup"

크론 표현식은 왼쪽부터 분, 시, 일, 월, 요일이다.

*/30 * * * *
  │  │ │ │ │
  │  │ │ │ └─ 요일
  │  │ │ └─── 월
  │  │ └───── 일
  │  └─────── 시
  └────────── 분

DaemonSet은 직접 명령형으로 만들기 애매한 경우가 많다. 빠른 방법은 Deployment YAML을 만든 뒤 kind, selector, template 구조를 유지하면서 DaemonSet으로 바꾸는 것이다. 또는 공식 문서 예제를 찾아 수정한다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      containers:
      - name: agent
        image: busybox
        command: ["/bin/sh", "-c", "sleep 3600"]

StatefulSet은 안정적인 Pod 이름이 필요할 때 쓴다. web-0, web-1처럼 이름이 유지되고, volumeClaimTemplates로 Pod마다 PVC를 가질 수 있다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db
  replicas: 2
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
      - name: db
        image: nginx
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

AI 관점으로 보면 학습 작업은 Job, 정기 데이터 처리나 임베딩 갱신은 CronJob, GPU 드라이버나 로그 에이전트는 DaemonSet, 벡터 DB나 큐 같은 상태 저장 컴포넌트는 StatefulSet에 가깝다. 시험에서 익히는 판별 기준은 실무 설계 기준이기도 하다.

kubectl explain: 모르는 필드는 문서보다 먼저 물어본다

시험 중 YAML 필드가 기억나지 않을 때 공식 문서를 검색할 수도 있다. 하지만 구조만 확인하면 되는 경우 kubectl explain이 더 빠르다.

Deployment의 strategy 구조를 확인한다.

kubectl explain deployment.spec.strategy
kubectl explain deployment.spec.strategy.rollingUpdate

Pod의 securityContext를 확인한다.

kubectl explain pod.spec.securityContext
kubectl explain pod.spec.containers.securityContext

CronJob의 필드를 확인한다.

kubectl explain cronjob.spec
kubectl explain cronjob.spec.jobTemplate.spec.template.spec

--recursive를 붙이면 하위 필드를 길게 볼 수 있다.

kubectl explain pod.spec --recursive | less

물론 kubectl explain이 모든 예제를 대신해 주지는 않는다. NetworkPolicy처럼 구조가 까다로운 리소스는 예제가 더 도움이 될 때가 많다. 하지만 시험장에서 "이 필드가 어디 들어가지?"라는 순간에는 kubectl explain이 가장 빠른 나침반이다.

jsonpath: 원하는 값만 뽑아 파일에 저장한다

CKA/CKAD에는 "특정 조건의 리소스 이름을 파일에 저장하라"는 유형이 나온다. 이때 jsonpath가 유용하다.

모든 Pod 이름을 출력한다.

kubectl get pods -o jsonpath='{.items[*].metadata.name}'

줄바꿈을 넣고 싶으면 range를 쓴다.

kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"n"}{end}'

특정 노드에 올라간 Pod 이름을 저장한다.

kubectl get pods -A \
  --field-selector spec.nodeName=worker1 \
  -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"n"}{end}' \
  > /tmp/pods-on-worker1.txt

노드의 InternalIP를 뽑는다.

kubectl get node worker1 \
  -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}'

Secret 값을 확인할 때도 쓴다.

kubectl get secret db-secret \
  -o jsonpath='{.data.password}' | base64 -d

jsonpath는 처음엔 낯설지만, 몇 가지 패턴만 익히면 충분하다. 시험장에서 복잡한 jq 표현식을 고민하기보다 자주 쓰는 jsonpath를 손에 익혀 두는 편이 낫다.

Label selector: 쿠버네티스 문제의 절반은 라벨 문제다

Service가 Pod를 찾지 못하는 것도 라벨 문제고, NetworkPolicy가 적용되지 않는 것도 라벨 문제고, Deployment가 자기 Pod를 관리하지 못하는 것도 라벨 문제다.

라벨을 확인한다.

kubectl get pod --show-labels
kubectl get deploy --show-labels

특정 라벨만 조회한다.

kubectl get pod -l app=api
kubectl get pod -l 'app in (api,worker)'
kubectl get pod -l env!=prod

라벨을 추가하거나 수정한다.

kubectl label pod mypod app=api
kubectl label pod mypod app=api --overwrite

라벨을 제거한다.

kubectl label pod mypod app-

Service의 selector를 확인한다.

kubectl get svc api-svc -o jsonpath='{.spec.selector}'

Deployment의 selector와 Pod template 라벨을 비교한다.

kubectl get deploy api -o yaml | grep -A5 selector
kubectl get deploy api -o yaml | grep -A8 template:

라벨은 단순한 이름표가 아니다. 쿠버네티스 컨트롤러들이 대상을 찾는 방식이다. 시험 문제를 읽을 때도 "어떤 리소스가 어떤 라벨로 어떤 대상을 선택하는가"를 계속 추적해야 한다.

dry-run과 diff: 적용하기 전에 틀린 곳을 줄인다

리소스를 만들기 전에 YAML을 생성하고 검토하는 습관은 이미 봤다. 여기에 kubectl diff를 더하면 실수를 더 줄일 수 있다.

kubectl create deployment api \
  --image=nginx \
  --dry-run=client -o yaml > deploy.yaml

vim deploy.yaml
kubectl diff -f deploy.yaml
kubectl apply -f deploy.yaml

이미 있는 리소스를 수정할 때는 다음 방식이 안전하다.

kubectl get deploy api -o yaml > api.yaml
cp api.yaml api.bak.yaml
vim api.yaml
kubectl diff -f api.yaml
kubectl apply -f api.yaml

다만 시험장에서는 시간이 부족하다. 모든 문제에 백업 파일을 만들 필요는 없다. 하지만 복잡한 Deployment, StatefulSet, NetworkPolicy, RBAC 문제에서는 원본을 저장해 두면 되돌리기 쉽다.

간단한 수정은 kubectl edit가 빠르다.

kubectl edit deployment api

이미지만 바꾼다면 kubectl set image가 더 빠르다.

kubectl set image deployment/api api=nginx:1.26

레플리카만 바꾼다면 kubectl scale이다.

kubectl scale deployment api --replicas=5

핵심은 "가장 짧고 안전한 도구를 선택하는 것"이다. YAML을 열 필요가 없는 문제에 YAML을 열면 시간이 줄어든다.

트러블슈팅 순서: describe, logs, events, exec

장애 문제는 당황하면 끝이 없다. 순서를 정해 두어야 한다.

Pod가 안 뜨면 먼저 상태를 본다.

kubectl get pod -n app
kubectl describe pod <pod-name> -n app

describe의 Events를 읽는다. 여기에는 ImagePullBackOff, CrashLoopBackOff, FailedScheduling, MountVolume 실패 같은 단서가 나온다.

로그를 본다.

kubectl logs <pod-name> -n app
kubectl logs <pod-name> -n app --previous

이벤트를 시간순으로 본다.

kubectl get events -n app --sort-by=.metadata.creationTimestamp

컨테이너 안으로 들어간다.

kubectl exec -it <pod-name> -n app -- /bin/sh

Service 연결 문제라면 endpoints를 본다.

kubectl get svc -n app
kubectl get endpoints -n app
kubectl describe svc <service-name> -n app

DNS 문제라면 임시 Pod를 띄워 확인한다.

kubectl run tmp \
  --image=busybox \
  --rm -it \
  --restart=Never \
  -- nslookup kubernetes.default.svc.cluster.local

HTTP 연결을 확인한다.

kubectl run curl \
  --image=curlimages/curl \
  --rm -it \
  --restart=Never \
  -- http://api-svc.app.svc.cluster.local

노드 문제라면 범위가 CKA 쪽으로 넘어간다.

kubectl get nodes
kubectl describe node <node-name>

노드에 접속할 수 있다면 kubelet 상태를 본다.

sudo systemctl status kubelet
sudo journalctl -u kubelet -n 100 --no-pager

이 순서만 지켜도 많은 문제를 풀 수 있다. 상태를 보고, 이벤트를 읽고, 로그를 확인하고, 필요하면 안으로 들어간다. 운영 환경에서 GPU Pod가 Pending일 때도 같다. 차이는 이벤트에 Insufficient nvidia.com/gpu 같은 메시지가 나올 뿐이다.

시간 관리: 문제를 이기려 하지 말고 시험을 이겨라

CKA/CKAD는 모든 문제를 완벽하게 풀려고 하면 오히려 떨어질 수 있다. 2시간은 생각보다 짧다. 15~20개 안팎의 작업을 푼다고 가정하면 문제당 평균 6~8분이다. 한 문제에 15분을 쓰면 뒤의 쉬운 문제를 놓친다.

좋은 전략은 두 번 도는 것이다. 첫 번째 순회에서는 확실히 풀 수 있는 문제를 먼저 처리한다. Pod 생성, ConfigMap 주입, Service 생성, Deployment scale, Secret 생성 같은 문제는 빠르게 점수를 확보한다. 두 번째 순회에서 NetworkPolicy, Gateway, RBAC, 트러블슈팅처럼 시간이 걸리는 문제로 돌아온다.

문제를 풀 때는 항상 확인 명령을 마지막에 실행한다.

kubectl get <resource> -n <namespace>
kubectl describe <resource> <name> -n <namespace>

Deployment라면 Pod까지 확인한다.

kubectl get deploy api -n app
kubectl get pod -n app -l app=api
kubectl rollout status deployment/api -n app

Service라면 endpoints를 확인한다.

kubectl get endpoints api-svc -n app

Job이라면 완료 여부를 확인한다.

kubectl get job -n app
kubectl get pod -n app
kubectl logs job/<job-name> -n app

확인하지 않은 문제는 풀었다고 착각하기 쉽다. 특히 YAML은 적용됐지만 Pod가 CrashLoopBackOff인 상태라면 실제로는 실패에 가깝다.

시험장에서 자주 쓰는 명령어 모음

다음 명령어들은 따로 외운다기보다 매일 손으로 쳐 봐야 한다. 눈으로 보면 쉬운데, 시험장에서 손이 멈추면 시간이 사라진다.

# 기본 조회
kubectl get pods
kubectl get pods -A
kubectl get pods -o wide
kubectl get pods --show-labels
kubectl describe pod <pod>
kubectl logs <pod>
kubectl logs <pod> --previous
kubectl exec -it <pod> -- /bin/sh

# 생성
kubectl run nginx --image=nginx
kubectl create deployment web --image=nginx --replicas=3
kubectl expose deployment web --port=80 --target-port=8080
kubectl create configmap app-config --from-literal=key=value
kubectl create secret generic app-secret --from-literal=password=1234
kubectl create serviceaccount app-sa

# dry-run YAML 생성
kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml
kubectl create deployment web --image=nginx --dry-run=client -o yaml > deploy.yaml
kubectl expose deployment web --port=80 --target-port=8080 --dry-run=client -o yaml > svc.yaml
kubectl create job hello --image=busybox --dry-run=client -o yaml -- echo hello > job.yaml
kubectl create cronjob hello --image=busybox --schedule="*/5 * * * *" --dry-run=client -o yaml -- date > cron.yaml

# 수정과 배포
kubectl apply -f file.yaml
kubectl edit deployment web
kubectl scale deployment web --replicas=5
kubectl set image deployment/web nginx=nginx:1.26
kubectl rollout status deployment/web
kubectl rollout history deployment/web
kubectl rollout undo deployment/web

# 라벨과 셀렉터
kubectl label pod mypod app=web
kubectl label pod mypod app=web --overwrite
kubectl get pod -l app=web
kubectl get pod -l 'app in (web,api)'

# RBAC
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n app
kubectl create rolebinding read-pods --role=pod-reader --serviceaccount=app:app-sa -n app
kubectl auth can-i list pods --as=system:serviceaccount:app:app-sa -n app

# 디버깅
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl get endpoints
kubectl run tmp --image=busybox --rm -it --restart=Never -- nslookup kubernetes.default
kubectl explain pod.spec.containers

이 목록은 치트시트가 아니라 훈련 목록이다. 다음 부록에서 더 긴 kubectl과 Helm 치트시트를 제공하겠지만, 시험 준비에서는 짧은 명령어를 빠르게 정확히 치는 능력이 더 중요하다.

작은 모의 문제로 감각을 확인하자

마지막으로 시험 문제처럼 짧은 상황을 몇 개 풀어 보자. 답을 보기 전에 직접 명령어를 떠올려 보는 것이 좋다.

문제 1: ConfigMap을 Pod 환경 변수로 주입하기

app 네임스페이스에 APP_MODE=prod 값을 가진 ConfigMap app-config를 만들고, nginx 이미지의 Pod web이 이 값을 환경 변수로 사용하게 하라.

한 가지 풀이 흐름은 다음과 같다.

kubectl create ns app
kubectl create configmap app-config \
  --from-literal=APP_MODE=prod \
  -n app

kubectl run web \
  --image=nginx \
  -n app \
  --dry-run=client -o yaml > web.yaml

web.yaml을 수정한다.

apiVersion: v1
kind: Pod
metadata:
  name: web
  namespace: app
spec:
  containers:
  - name: web
    image: nginx
    env:
    - name: APP_MODE
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: APP_MODE

적용하고 확인한다.

kubectl apply -f web.yaml
kubectl get pod web -n app
kubectl exec web -n app -- printenv APP_MODE

문제 2: Service selector 오류 고치기

api-svc Service가 있지만 endpoints가 비어 있다. api Pod의 라벨은 app=api다. Service가 이 Pod를 바라보게 고쳐라.

먼저 확인한다.

kubectl get pod --show-labels
kubectl get svc api-svc -o yaml
kubectl get endpoints api-svc

Service selector가 틀렸다면 수정한다.

kubectl edit svc api-svc

다음처럼 맞춘다.

spec:
  selector:
    app: api

확인한다.

kubectl get endpoints api-svc

문제 3: ServiceAccount 권한 확인하기

app 네임스페이스의 ServiceAccount viewer가 Pod 목록을 조회할 수 있게 하라.

kubectl create serviceaccount viewer -n app

kubectl create role pod-viewer \
  --verb=get,list,watch \
  --resource=pods \
  -n app

kubectl create rolebinding viewer-binding \
  --role=pod-viewer \
  --serviceaccount=app:viewer \
  -n app

kubectl auth can-i list pods \
  --as=system:serviceaccount:app:viewer \
  -n app

마지막 명령이 yes를 반환해야 한다.

문제 4: 30분마다 실행되는 CronJob 만들기

busybox 이미지로 30분마다 date를 실행하는 CronJob time-check를 만들어라.

kubectl create cronjob time-check \
  --image=busybox \
  --schedule="*/30 * * * *" \
  -- /bin/sh -c "date"

kubectl get cronjob time-check

YAML로 확인한다.

kubectl get cronjob time-check -o yaml

문제 5: Deployment 이미지 업데이트 후 롤백하기

web Deployment의 이미지를 nginx:1.26으로 바꾸고, 롤아웃을 확인한 뒤 이전 버전으로 되돌려라.

kubectl set image deployment/web nginx=nginx:1.26
kubectl rollout status deployment/web
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl rollout status deployment/web

이런 작은 문제를 반복하면 긴 문제도 결국 작은 조각으로 보인다. 시험 지문이 길어졌다고 해서 본질이 달라지는 것은 아니다. 대부분은 리소스 생성, 필드 수정, 권한 연결, 트래픽 확인, 상태 점검의 조합이다.

자격증 이후가 진짜 시작이다

CKA와 CKAD는 끝이 아니라 시작이다. 자격증을 준비하면서 익힌 것은 "쿠버네티스와 대화하는 속도"다. 어떤 Pod가 왜 죽는지, 어떤 Service가 왜 연결되지 않는지, 어떤 권한이 왜 거부되는지 빠르게 물어보고 답을 찾는 능력이다.

AI 인프라에서는 이 능력이 더 중요해진다. GPU Pod가 Pending이면 스케줄링과 리소스 요청을 봐야 한다. LLM 서버가 준비되지 않았는데 트래픽을 받으면 readinessProbe를 봐야 한다. 에이전트가 과한 권한을 가지면 ServiceAccount와 RBAC를 봐야 한다. 벡터 DB 연결이 막히면 NetworkPolicy와 DNS를 봐야 한다.

시험은 작은 클러스터에서 치르지만, 그 안에 운영의 핵심이 들어 있다. 이 장의 명령어를 손에 익히면 다음에 장애를 만났을 때 화면 앞에서 멈춰 있지 않게 된다. 쿠버네티스는 결국 선언과 관찰의 시스템이다. 원하는 상태를 선언하고, 실제 상태를 관찰하고, 차이를 좁힌다. CKA와 CKAD 준비는 그 기본기를 가장 압축적으로 훈련하는 방법이다.

참고

1Certified Kubernetes Application Developer (CKAD)

2Certified Kubernetes Administrator (CKA) Exam Guide

3CKA Certification Guide (2026): Requirements & Study Plan

42026 CKA, CKAD 시험 합격 후기

5Certified Kubernetes Administrator (CKA) Preparation Guide

책 목록

AI 시대의 쿠버네티스: LLM 추론과 에이전트를 운영하는 인프라 전략

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기