콘텐츠로 이동

3. kubectl — 손 속도가 점수다

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

flowchart LR
    U["당신"] --> KC["kubectl"]
    CFG["kubeconfig<br/>~/.kube/config"] -->|"어디로 · 누구로"| KC
    KC -->|"HTTPS REST 요청"| API["kube-apiserver"]
    API --> ETCD[("etcd")]
    ERR1["연결이 안 된다"] -.->|"= server 주소 문제"| CFG
    ERR2["권한이 없다 403"] -.->|"= user 신원 문제"| CFG

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class CFG key
    class ERR1,ERR2 bad
    class U,KC,API,ETCD mute
  • API 서버에 HTTP 요청을 보내는 클라이언트. 그 이상도 이하도 아니다
  • 어디로, 누구로 보낼지는 kubeconfig가 정한다
  • 기본 경로: ~/.kube/config. KUBECONFIG 환경변수나 --kubeconfig로 바꾼다
Terminal window
kubectl get pods -v=6 # 실제로 어떤 URL을 호출하는지 보인다
# GET https://10.0.0.1:6443/api/v1/namespaces/default/pods?limit=500 200 OK

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

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 # 지금 쓰는 조합
flowchart LR
    CL["clusters<br/>어디로 · server + CA"] --> CTX["contexts<br/>조합"]
    US["users<br/>누구로 · 인증서 · 토큰"] --> CTX
    NS["namespace<br/>기본 네임스페이스"] --> CTX
    CTX --> CUR["current-context<br/>지금 쓰는 조합"]

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class CTX,CUR key
    class CL,US,NS mute

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

Terminal window
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를 합쳐 쓸 수도 있다.

Terminal window
KUBECONFIG=~/.kube/config:~/other.conf kubectl config view --flatten > merged.conf
Terminal window
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 # 주요 워크로드 리소스 (전부는 아니다)
Terminal window
kubectl get pods --sort-by=.metadata.creationTimestamp
kubectl get nodes --sort-by=.metadata.name
kubectl get events --sort-by=.lastTimestamp
Terminal window
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}'

값 하나를 정확히 꺼낼 때. 다른 명령에 넘겨 쓰기 좋다.

kubectl describe

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

kubectl get -o yaml

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

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

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

섹션 제목: “명령형 vs 선언형 — 시험에서는 섞어 쓴다”
flowchart LR
    I["명령형<br/>kubectl create · scale · expose"] -->|"빠르다 · 표현이 제한적"| R1["바로 만들어진다"]
    D["선언형<br/>kubectl apply -f"] -->|"모든 필드 표현 가능 · 파일이 필요"| R2["파일 기준으로 맞춘다"]
    BEST["★ 시험 전략"] --> S1["① 명령형 + dry-run 으로 뼈대 생성"]
    S1 --> S2["② 필요한 필드만 편집"]
    S2 --> S3["③ kubectl apply -f"]

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class BEST key
    class S1,S2,S3 ok
    class I,D,R1,R2 mute

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

섹션 제목: “생성기(generator) — 시험의 핵심 무기”
Terminal window
# 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"

이 목록을 손에 붙이는 것이 곧 시험 시간이다. 19장에 전체 치트시트가 있다.

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

섹션 제목: “dry-run — 명령형으로 YAML을 뽑는다”
Terminal window
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
flowchart LR
    C["kubectl create …"] --> Q{"--dry-run"}
    Q -->|client| CL["클라이언트에서만 만든다<br/>서버에 안 보낸다 → YAML 생성용"]
    Q -->|server| SV["서버에 보내되 저장은 안 한다<br/>admission·검증까지 통과하는지 확인"]
    Q -->|"생략"| REAL["실제로 생성된다"]

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class CL key
    class SV warn
    class C,Q,REAL mute
  1. 전용 서브커맨드 — 가장 빠르다

    Terminal window
    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 — 에디터로 연다

    Terminal window
    kubectl edit deploy web
  3. patch — 한 필드만 정확히

    Terminal window
    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 — 파일 기준

    Terminal window
    kubectl apply -f web.yaml
    kubectl replace -f web.yaml --force # 지우고 다시 만든다
flowchart LR
    F["① 내 파일 web.yaml"] --> M{"3-way merge"}
    C["② 클러스터의 현재 상태"] --> M
    L["③ 마지막으로 적용한 것<br/>last-applied-configuration 애노테이션"] --> M
    M --> R["파일에 없는 필드를<br/>함부로 지우지 않는다 ✅"]
    REP["kubectl replace"] -.->|"3-way merge 를 안 한다"| R2["통째로 교체<br/>파일에 없는 필드가 사라진다 ❌"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class R ok
    class R2 bad
    class M key
    class F,C,L,REP mute
  • apply파일 / 클러스터의 현재 / 마지막으로 적용한 것 셋을 비교한다
  • 그래서 파일에 없는 필드를 함부로 지우지 않는다 (다른 도구가 넣은 것을 보존)
  • 마지막으로 적용한 내용은 애노테이션 kubectl.kubernetes.io/last-applied-configuration 에 저장
Terminal window
kubectl diff -f web.yaml # 적용하면 무엇이 바뀌는지 미리 본다
Terminal window
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)을 남긴다
flowchart TD
    D["kubectl delete pod web"] --> Q{"상위 컨트롤러가 있는가"}
    Q -->|"있다 · Deployment 등"| BACK["즉시 다시 생긴다 ⚠️<br/>→ 컨트롤러를 지워야 한다"]
    Q -->|"없다 · 단독 Pod"| GONE["사라진다 ✅"]
    NS["네임스페이스가 Terminating 에서 안 끝난다"] --> FIN["finalizer 를 의심한다 · 18장"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class GONE ok
    class BACK warn
    class FIN bad
    class D,Q,NS mute
  • 기본 삭제는 graceful이다 — terminationGracePeriodSeconds(기본 30초)를 기다린다
  • 시험에서 Pod을 지우고 다시 만들 일이 많으니 --force --grace-period=0이 유용하다

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

섹션 제목: “로그 — 커리큘럼의 “컨테이너 출력 스트림””
Terminal window
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가 안 될 때 직접 읽는다.

Terminal window
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 — 클러스터 안에서 네트워크를 확인할 때.

Terminal window
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- sh
# 안에서: wget -qO- http://web-svc.default.svc.cluster.local
# nslookup web-svc
flowchart LR
    Q{"컨테이너 안을 봐야 한다"} -->|"셸이 있다"| E["kubectl exec -it … -- sh ✅"]
    Q -->|"distroless · 셸이 없다"| D["kubectl debug -it web<br/>--image=busybox --target=web"]
    D --> EPH["임시 컨테이너 ephemeral container 를 붙인다"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class E ok
    class D,EPH key
    class Q mute

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

섹션 제목: “이벤트 — 시간순으로 보는 클러스터의 일기”
Terminal window
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 # 새 전용 명령
  • 이벤트는 기본 1시간 후 사라진다. 오래된 문제는 이벤트로 못 잡는다
  • describe의 아래쪽 Events 섹션이 사실 이것이다
  • kubectl은 kubeconfig로 결정된 주소·신원으로 API를 호출하는 클라이언트다
  • context = cluster + user + namespace. 시험은 컨텍스트 전환에서 갈린다
  • 명령형으로 만들고 --dry-run=client -o yaml로 뽑아 고친다 — 이게 가장 빠르다
  • 조회는 -o widedescribe(Events) → -o yaml 순으로 좁혀 간다
  • --sort-by, custom-columns, jsonpath 는 “파일에 저장하시오” 문제 전용 무기
  • logs --previousget events --sort-by 는 진단의 두 기둥
  • Pod은 대부분 필드를 못 고친다 → --force로 다시 만든다