콘텐츠로 이동
Study NoteCKA

kubectl — 손 속도가 점수다

같은 결과를 30초 만에 만드는 법

아키텍처의 그림에서 모든 화살표는 API 서버를 향했다 — kubectl은 그중 사람이 쏘는 화살표다. 시험의 모든 조작이 이 클라이언트를 거치니, 이 장은 곧 손 속도를 버는 장이다.

kubeconfig 가 kubectl 에게 어디로 · 누구로 접속할지 알려주고, 연결 실패는 server 주소, 403 은 user 신원 문제로 이어진다
  • API 서버에 HTTP 요청을 보내는 클라이언트. 그 이상도 이하도 아니다
  • 어디로, 누구로 보낼지는 kubeconfig가 정한다
  • 기본 경로: ~/.kube/config. KUBECONFIG 환경변수나 --kubeconfig로 바꾼다
터미널 창
kubectl get pods -v=6 # 실제로 어떤 URL을 호출하는지 보인다
# GET https://10.0.0.1:6443/api/v1/namespaces/default/pods?limit=500 200 OK

이걸 알면 “권한이 없다”거나 “연결이 안 된다”는 에러를 “어느 주소로 어떤 신원으로 갔는가”로 환원해서 볼 수 있다.

API 서버는 아무 요청이나 받지 않는다 — 아키텍처에서 본 첫 관문(인증)을 통과해야 한다. 그렇다고 매 명령마다 서버 주소와 인증서를 플래그로 줄 수는 없으니, “어디로 · 누구로”를 파일 하나에 묶어 둔다. 그게 kubeconfig다.

kubeconfig 구조 — 세 덩어리 + 조합

섹션 제목: “kubeconfig 구조 — 세 덩어리 + 조합”
apiVersion: v1
kind: Config
clusters: # 어디로: 주소 + CA
- name: prod
cluster:
server: https://10.0.0.1:6443
certificate-authority: /etc/kubernetes/pki/ca.crt
users: # 누구로: 인증 정보
- name: admin
user:
client-certificate: /etc/kubernetes/pki/admin.crt
client-key: /etc/kubernetes/pki/admin.key
contexts: # 조합: cluster + user + namespace
- name: prod-admin
context:
cluster: prod
user: admin
namespace: default
current-context: prod-admin # 지금 쓰는 조합
kubeconfig 의 clusters · users · namespace 가 contexts 로 조합되고 그중 하나가 current-context 가 되는 구조

context = cluster + user + namespace. 셋을 묶은 것이 컨텍스트다.

certificate-authority는 CA(Certificate Authority · 인증 기관) 인증서다 — “이 API 서버가 진짜인가”를 클라이언트 쪽에서 검증하는 뿌리이고, 반대로 client-certificate는 서버에게 “나는 admin이다”를 증명한다. 인증서 체계 자체는 사용자 인증.

터미널 창
kubectl config get-contexts # 목록 (*가 현재)
kubectl config current-context # 현재 이름만
kubectl config use-context prod-admin # 전환
kubectl config set-context --current --namespace=dev # 현재 컨텍스트의 ns 변경
kubectl config view # 전체 보기 (민감정보 마스킹)
kubectl config view --raw # 마스킹 없이

여러 kubeconfig를 합쳐 쓸 수도 있다.

터미널 창
KUBECONFIG=~/.kube/config:~/other.conf kubectl config view --flatten > merged.conf

합치는 문법까지 외울 필요는 없다 — “이런 것도 된다” 정도만 알아두면 된다. 시험에서는 kubeconfig가 이미 세팅된 채로 시작한다.

트러블슈팅은 실제 상태(status)를 읽는 것에서 시작한다 (커리큘럼의 30%가 여기다). 같은 get이라도 출력 형식을 바꾸면 보이는 정보가 달라지므로, 조회는 “무엇을 보고 싶은가”에 맞춰 형식을 고르는 일이다.

터미널 창
kubectl get pods # 기본
kubectl get pods -o wide # + IP, 노드, NOMINATED NODE
kubectl get pods -o yaml # 전체 오브젝트
kubectl get pods -o json
kubectl get pods --show-labels
kubectl get pods -A # 모든 네임스페이스
kubectl get pod,svc,deploy # 여러 종류 한 번에
kubectl get all # 주요 워크로드 리소스 (전부는 아니다)

시험은 “보시오”가 아니라 “파일에 쓰시오”로 묻는다 — 이름만, 이미지만, 정렬해서. 사람 눈으로 골라 옮겨 적으면 오타로 점수를 잃으니, 처음부터 필요한 값만 뽑는 형식을 쓴다.

터미널 창
kubectl get pods --sort-by=.metadata.creationTimestamp
kubectl get nodes --sort-by=.metadata.name
kubectl get events --sort-by=.lastTimestamp
터미널 창
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}'
kubectl get node node01 -o jsonpath='{.status.capacity.cpu}'

JSONPath는 -o json으로 받은 응답 안에서 경로로 값을 지목하는 문법이다. .items[*]는 목록 전체, {range}…{end}는 그 목록을 한 줄씩 돌며 찍으라는 뜻이고, {"\t"} · {"\n"} 같은 리터럴로 구분자를 직접 넣는다.

값 하나를 정확히 꺼낼 때. 다른 명령에 넘겨 쓰기 좋다. range나 필터 문법이 기억나지 않으면 시험 중에는 공식 JSONPath 문서의 예시를 복사해 경로만 바꾸는 게 가장 빠르다.

kubectl describe

사람이 읽으라고 만든 요약. 끝에 Events가 붙는다 ← 진단의 핵심. 관련 오브젝트 정보를 합쳐서 보여준다.

kubectl get -o yaml

API가 저장한 원본 그대로. status 안의 정확한 값·조건을 본다. 복사해서 새 오브젝트를 만들 때 쓴다.

터미널 창
kubectl describe pod web # 왜 안 뜨는가 → Events를 읽는다
kubectl get pod web -o yaml # 정확히 어떤 값이 들어갔는가

만드는 것은 결국 spec을 API 서버에 밀어 넣는 일이다 (아키텍처). 방법은 두 갈래인데, 시험에서 갈리는 것은 “무엇을 만드느냐”가 아니라 몇 초 만에 만드느냐다 — 그래서 두 방식의 장단을 알고 섞어 쓰는 것이 이 절의 목적이다.

명령형 vs 선언형 — 시험에서는 섞어 쓴다

섹션 제목: “명령형 vs 선언형 — 시험에서는 섞어 쓴다”
명령형과 선언형의 차이, 그리고 dry-run 으로 뼈대를 만들고 필요한 필드만 고친 뒤 apply 하는 시험 전략

생성기(generator) — 시험의 핵심 무기

섹션 제목: “생성기(generator) — 시험의 핵심 무기”

생성기는 create·run·expose가 플래그 몇 개로 오브젝트 뼈대를 대신 써 주는 기능이다. 손으로 YAML을 치면 20줄인 것이 한 줄이 된다. 지금의 kubectl run은 Pod만 만든다 — Deployment가 필요하면 kubectl create deploy를 쓴다.

터미널 창
# Pod
kubectl run nginx --image=nginx
kubectl run nginx --image=nginx --port=80 --labels=app=web
kubectl run tmp --image=busybox --rm -it --restart=Never -- sh # 일회용 디버그 셸
# Deployment
kubectl create deploy web --image=nginx --replicas=3
# Service
kubectl expose deploy web --port=80 --target-port=8080 --name=web-svc
kubectl create svc clusterip web-svc --tcp=80:8080
# 그 외
kubectl create ns dev
kubectl create cm app-config --from-literal=KEY=value --from-file=./conf/
kubectl create secret generic db --from-literal=password=s3cr3t
kubectl create sa deploy-bot
kubectl create job hello --image=busybox -- echo hi
kubectl create cronjob hello --image=busybox --schedule="*/1 * * * *" -- echo hi
kubectl create ingress web --rule="example.com/*=web-svc:80"

이 목록을 손에 붙이는 것이 곧 시험 시간이다. 시험 전략에 전체 치트시트가 있다. 막히면 시험 중에도 열 수 있는 공식 kubectl Quick Reference에 같은 목록이 정리되어 있다.

dry-run — 명령형으로 YAML을 뽑는다

섹션 제목: “dry-run — 명령형으로 YAML을 뽑는다”

생성기만으로는 표현할 수 없는 필드(볼륨, 프로브, tolerations…)가 많다. 그렇다고 빈 파일에서 시작할 필요는 없다 — 만들지 말고 YAML만 내놓으라고 하면 생성기가 뼈대를 써 주고, 거기에 필요한 필드만 얹으면 된다. 그게 --dry-run=client -o yaml이다.

터미널 창
kubectl create deploy web --image=nginx --dry-run=client -o yaml > web.yaml
kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml
kubectl expose deploy web --port=80 --dry-run=client -o yaml > svc.yaml
--dry-run 이 client 면 클라이언트에서만, server 면 서버 검증까지, 생략하면 실제로 생성된다는 세 갈래

kubectl·kubeadm은 --flag=value와 --flag value를 둘 다 받는다. 하지만 값 생략이 허용되는 플래그는 =만 안전하다 — --dry-run client라고 띄면 client가 플래그 값이 아니라 별도 인자로 해석된다(값 없는 --dry-run도 유효한 표기라서, 뒤의 단어를 값으로 붙여 주지 않는다). 불리언 플래그(--dry-run false 시절 표기)와 값이 -로 시작하는 경우도 같은 함정이다. 어느 플래그가 어느 형태였는지 고민하지 않도록 습관을 = 하나로 통일한다.

따옴표는 CLI가 아니라 셸을 위한 것이다. --pod-network-cidr=172.17.0.0/16 같은 값은 점·슬래시가 셸에 평범한 글자라 안 묶어도 되고, 공백이나 *·?·$ 같은 셸 특수문자가 들어 있을 때만 묶는다(apt-get install kubeadm='1.35.0-*'의 따옴표가 그 경우 — 없으면 셸이 *를 파일명 글롭으로 펼치려 든다).

만드는 문제만큼 자주 나오는 것이 “이미 있는 것을 고치시오”다. 고치는 길이 넷이나 되는 이유는 각각 대가가 다르기 때문이다 — 빠르지만 표현이 제한적인 것부터, 느리지만 무엇이든 되는 것까지 순서대로 본다.

  1. 전용 서브커맨드 — 가장 빠르다

    터미널 창
    kubectl scale deploy web --replicas=5
    kubectl set image deploy/web nginx=nginx:1.27
    kubectl set env deploy/web LOG_LEVEL=debug
    kubectl set resources deploy/web --limits=cpu=500m,memory=256Mi
    kubectl set serviceaccount deploy/web deploy-bot
  2. edit — 에디터로 연다

    터미널 창
    kubectl edit deploy web
  3. patch — 한 필드만 정확히

    터미널 창
    kubectl patch deploy web -p '{"spec":{"replicas":5}}'
    kubectl patch pod web --type=json -p='[{"op":"replace","path":"/spec/containers/0/image","value":"nginx:1.27"}]'
  4. apply / replace — 파일 기준

    터미널 창
    kubectl apply -f web.yaml
    kubectl replace -f web.yaml --force # 지우고 다시 만든다

patch의 기본 형식은 strategic merge다 — 준 필드만 병합하고 나머지는 그대로 둔다. --type=json은 op(연산)와 path(위치)로 지목하는 방식이라, 위 예처럼 배열 원소 하나(/spec/containers/0/image)를 정확히 바꿀 때 쓴다. 세 번째 형식인 --type=merge도 있지만 깊게 팔 필요는 없다 — 이름만 알아두자.

같은 오브젝트를 파일과 명령형 조작이 번갈아 건드린다. 파일을 그대로 덮어쓰면 파일에 안 적힌 것(다른 도구가 넣은 필드)이 통째로 날아간다. apply가 단순 덮어쓰기가 아닌 이유가 이것이다.

apply 의 3-way merge 는 내 파일·현재 상태·last-applied 애노테이션을 합쳐 파일에 없는 필드를 지키지만, replace 는 통째로 교체한다
  • apply는 파일 / 클러스터의 현재 / 마지막으로 적용한 것 셋을 비교한다
  • 그래서 파일에 없는 필드를 함부로 지우지 않는다 (다른 도구가 넣은 것을 보존)
  • 마지막으로 적용한 내용은 애노테이션 kubectl.kubernetes.io/last-applied-configuration 에 저장
터미널 창
kubectl diff -f web.yaml # 적용하면 무엇이 바뀌는지 미리 본다

병합을 클라이언트가 아니라 서버가 하는 --server-side 방식도 있다. 시험에서는 기본(클라이언트 쪽 3-way merge)으로 충분하다 — 이름만 알아두자.

터미널 창
kubectl delete pod web
kubectl delete -f web.yaml
kubectl delete pod -l app=web # 라벨로
kubectl delete pods --all -n dev
kubectl delete pod web --force --grace-period=0 # 즉시 (graceful 생략)
kubectl delete deploy web --cascade=orphan # 자식(Pod)을 남긴다
Pod 를 지웠을 때 상위 컨트롤러가 있으면 다시 생기고 없으면 사라진다는 갈림길, 그리고 Terminating 이 안 끝나면 finalizer 를 의심한다
  • 기본 삭제는 graceful이다 — terminationGracePeriodSeconds(기본 30초)를 기다린다
  • 시험에서 Pod을 지우고 다시 만들 일이 많으니 --force --grace-period=0이 유용하다

여기부터는 루프가 어디서 멈췄는지를 알아내는 도구다 (시작하기 전에의 축, 커리큘럼의 30%). 순서가 있다 — 이벤트는 “스케줄링·이미지·볼륨” 같은 Pod이 뜨기까지의 실패를, 로그는 “떴는데 앱이 죽는” 뜬 다음의 실패를 보여준다. exec·debug는 그래도 모를 때 안으로 들어가는 것이다.

로그 — 커리큘럼의 “컨테이너 출력 스트림”

섹션 제목: “로그 — 커리큘럼의 “컨테이너 출력 스트림””
터미널 창
kubectl logs web
kubectl logs web -c sidecar # 멀티 컨테이너면 -c 필수
kubectl logs web --previous # 죽기 직전 컨테이너의 로그 ★
kubectl logs web -f # 따라가기
kubectl logs web --tail=50
kubectl logs web --since=10m
kubectl logs web --timestamps
kubectl logs -l app=web --all-containers --prefix # 라벨로 여러 Pod 한 번에
kubectl logs deploy/web # 컨트롤러를 지정해도 된다

로그의 실체는 노드의 /var/log/pods/<ns>_<pod>_<uid>/<container>/*.log 다. API 서버가 죽어 kubectl logs가 안 될 때 직접 읽는다.

셋 다 컨테이너 안쪽에 손을 넣는 명령이다 — 역할이 다르다. exec는 이미 도는 컨테이너에서 명령을 실행하고, cp는 파일을 넣거나 꺼내고, port-forward는 클러스터 안의 포트를 내 로컬 포트로 끌어온다. port-forward는 Service를 만들지 않고도 앱에 붙어 볼 때 쓰지만, 시험에서는 클러스터 안에서 임시 Pod으로 확인하는 쪽이 더 흔하다 — 쓰임을 알아 두는 정도면 된다.

터미널 창
kubectl exec web -- ls /app
kubectl exec -it web -- sh # 대화형
kubectl exec -it web -c sidecar -- sh
kubectl cp ./local.txt web:/tmp/local.txt
kubectl cp web:/var/log/app.log ./app.log
kubectl port-forward pod/web 8080:80 # 로컬 8080 → Pod 80
kubectl port-forward svc/web-svc 8080:80

임시 디버그 Pod — 클러스터 안에서 네트워크를 확인할 때.

터미널 창
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- sh
# 안에서: wget -qO- http://web-svc.default.svc.cluster.local
# nslookup web-svc
셸이 있으면 kubectl exec, distroless 라 셸이 없으면 kubectl debug 로 임시 컨테이너를 붙인다

임시 컨테이너는 Pod을 다시 만들지 않고 나중에 끼워 넣는 컨테이너다. --target=web을 주면 대상 컨테이너와 프로세스 네임스페이스를 공유해 그쪽 프로세스까지 보인다. 한 번 붙인 것은 뗄 수 없고, Pod을 지우면 같이 사라진다.

이벤트 — 시간순으로 보는 클러스터의 일기

섹션 제목: “이벤트 — 시간순으로 보는 클러스터의 일기”
터미널 창
kubectl get events --sort-by=.lastTimestamp
kubectl get events -A --sort-by=.lastTimestamp | tail -30
kubectl get events --field-selector type=Warning
kubectl get events --field-selector involvedObject.name=web
kubectl events --for pod/web # 새 전용 명령

이벤트도 etcd에 저장되는 오브젝트다 — 스케줄러·kubelet·컨트롤러가 “무엇을 했는지, 왜 못 했는지”를 남긴 기록이다. 로그와 달리 컴포넌트를 가리지 않고 한 줄로 모이기 때문에 “Pod이 안 뜬다”류에서 가장 먼저 볼 곳이 된다.

  • 이벤트는 기본 1시간 후 사라진다. 오래된 문제는 이벤트로 못 잡는다 (무한정 쌓으면 etcd가 감당하지 못하니 수명을 짧게 둔다)
  • describe의 아래쪽 Events 섹션이 사실 이것이다
  • kubectl은 kubeconfig로 결정된 주소·신원으로 API를 호출하는 클라이언트다
  • context = cluster + user + namespace. 시험은 컨텍스트 전환에서 갈린다
  • 명령형으로 만들고 --dry-run=client -o yaml로 뽑아 고친다 — 이게 가장 빠르다
  • 조회는 -o wide → describe(Events) → -o yaml 순으로 좁혀 간다
  • --sort-by, custom-columns, jsonpath 는 “파일에 저장하시오” 문제 전용 무기
  • logs --previous 와 get events --sort-by 는 진단의 두 기둥
  • Pod은 대부분 필드를 못 고친다 → --force로 다시 만든다 (리소스만 --subresource=resize 예외)