M2. k3d 클러스터와 kubectl 안전 조작
1. 왜 필요한가
섹션 제목: “1. 왜 필요한가”예시 3서비스 시스템을 NKS에서 운영할 때 AI가 제안한 kubectl 명령이 어느 cluster와 namespace를
바꾸는지 먼저 판별하지 못하면, 맞는 명령도 잘못된 환경에 적용할 수 있습니다. 이 모듈에서는
NKS 대신 격리된 k3d에서 안전 경계를 고정하고, 장애 확인에 반복해서 쓸 다섯 command family를
손에 익힙니다.
개념 정의의 정본은 Kubernetes Basics입니다. 원본 경로는
content/topics/L5/kubernetes-basics.mdx이며, 이 모듈은 cluster·API Server·Pod·namespace를
다시 정의하지 않고 local cluster 생성과 kubectl 조작을 실행으로 관찰합니다. M1에서 만든
image와 runtime 설정은 이후 M3의 Pod spec으로 연결됩니다.
2. 핵심 개념
섹션 제목: “2. 핵심 개념”flowchart LR User["사용자 / AI가 제안한 명령"] --> Kubectl["kubectl client"] Kubectl --> Kubeconfig["kubeconfig의 context"] Kubeconfig --> API["선택된 cluster의 API Server"] API --> Namespace["명령의 namespace 경계"] Namespace --> Object["Pod 등 API object"]
| 확인 순서 | 질문 | 이번 랩의 안전 장치 |
|---|---|---|
| context | 어느 cluster의 API Server에 요청하는가? | k3d-cs-study-workbook 일치 검사와 --context |
| namespace | 그 cluster 안의 어느 논리 공간을 보는가? | UTC run ID가 붙은 고유 namespace와 명시적 -n |
| resource | 무엇을 읽거나 바꾸는가? | pod/kubectl-lab처럼 type/name을 함께 지정 |
| verb | 읽기인가, 변경인가? | 실행 전에 다섯 command family의 효과를 먼저 구분 |
| command family | API에 요구하는 일 | 상태 변경 | 운영할 때 먼저 묻는 질문 |
|---|---|---|---|
apply | YAML의 원하는 상태를 생성하거나 갱신 | 예 | diff를 리뷰했고 대상 context가 맞는가? |
get | 객체 목록이나 선택한 필드를 빠르게 조회 | 아니요 | 어떤 namespace와 label 범위인가? |
describe | 객체 상세 상태와 관련 Events를 사람이 읽음 | 아니요 | 상태가 막힌 이유가 Events에 남았는가? |
logs | container의 stdout·stderr를 조회 | 아니요 | Pod와 container를 정확히 골랐는가? |
exec | 실행 중인 container 안에 새 process를 실행 | 예 | 진단에 꼭 필요하며 실행 흔적을 설명할 수 있나? |
kubectl은 container를 직접 조작하는 원격 shell이 아니라 API Server에 요청하는 client입니다.
logs와 exec도 먼저 context·namespace·Pod를 고른 뒤 API를 통해 실행됩니다. namespace와
node는 서로 다른 축이므로, namespace를 고른다고 특정 node가 선택되지는 않습니다.
시점 의존 설명은 2026-07-15에 다음 공식 1차 자료로 확인했습니다.
- k3d cluster create: image, server·agent 수, wait option
- kubectl quick reference: apply, get, describe, logs, exec 기본 사용법
- kubeconfig으로 cluster 접근 구성: cluster·user·namespace를 묶는 context
- 여러 cluster 접근 구성: current context 확인과 전환
- Namespace: 한 cluster 안에서 resource group을 나누는 경계
3. 직관 비유
섹션 제목: “3. 직관 비유”| 개념 | 비유 | 비유의 경계 |
|---|---|---|
| k3d cluster | Docker 안에 만든 폐쇄형 운전 연습장 | NKS의 load balancer·IAM·node pool 동작까지 재현하지는 않습니다. |
| context | 요청서에 찍는 목적 기관·사용자·기본 구역 묶음 | 권한 자체를 새로 만드는 것이 아니라 kubeconfig의 선택 정보입니다. |
| namespace | 같은 기관 안에서 문서를 분류하는 별도 업무함 | 별도 물리 server나 완전한 보안 격리를 자동 보장하지 않습니다. |
get | 현황판 | 원인과 시간순 사건까지 모두 설명하지는 않습니다. |
describe | 객체 상세 기록과 최근 사건표 | container 내부 application log는 logs로 따로 봐야 합니다. |
logs | process가 남긴 업무 일지 | process 안에서 지금 무엇이 실행 중인지는 별도 관찰이 필요합니다. |
exec | 가동 중인 장비에 진단 기사를 잠시 들여보내는 작업 | image나 Pod spec을 영구 수정하는 방법이 아닙니다. |
4. 핸즈온 랩
섹션 제목: “4. 핸즈온 랩”검증 환경
섹션 제목: “검증 환경”| 항목 | 검증값 |
|---|---|
| 기준일 | 2026-07-15 |
| host | macOS arm64 |
| Docker client / engine | 29.6.1 / 29.6.1 |
| k3d | 5.9.0 |
| K3s image / API server | rancher/k3s:v1.36.2-k3s1 / v1.36.2+k3s1 |
| kubectl | 1.36.1 |
| Helm | 4.2.3 — 이 모듈에서는 사용하지 않음 |
| Pod image | busybox:1.37.0 + Setup의 고정 multi-platform digest |
| evidence 환경 | local-cluster; 고정 context + k8s-wb-m2-<UTC run ID> |
모든 실행 명령 묶음은 executed-local입니다. k3d의 API server와 node 상태를 NKS 실측값으로
해석하지 않습니다. 실제 NKS kubeconfig, IAM, namespace, endpoint를 사용하지 않으며 NKS 접속은
official-doc-only 경계입니다.
사전 조건
섹션 제목: “사전 조건”- 저장소 root에서 실행합니다.
- Docker Desktop daemon이 실행 중이어야 합니다.
- M0와 M1을 완료했고 YAML 구조, process, stdout, image를 관찰할 수 있어야 합니다.
- 고정 이름
cs-study-workbook의 기존 local k3d cluster는 다시 만듭니다. 다른 이름의 cluster와 NKS context는 삭제하지 않습니다. - 모든 Kubernetes API 명령은
--context k3d-cs-study-workbook을 명시합니다.defaultnamespace나 현재 context에 암묵적으로 의존하지 않습니다. - 예상 소요 시간은 90분입니다.
Setup
섹션 제목: “Setup”고정 local cluster 만들기
섹션 제목: “고정 local cluster 만들기”이 워크북 전용 cluster만 삭제 후 재생성합니다. 성공한 k3d cluster create는 해당 context를
kubeconfig에 추가하고 current context로 선택하지만, 이후 명령도 명시적 --context를 계속
사용합니다. 생성 log는 실패할 때만 출력합니다.
set -euo pipefailrm -rf /tmp/k8s-wb-m2mkdir -p /tmp/k8s-wb-m2node automation/k8s-workbook/preflight.mjsk3d cluster delete cs-study-workbook >/dev/null 2>&1 || trueif ! k3d cluster create cs-study-workbook \ --image rancher/k3s:v1.36.2-k3s1 \ --servers 1 \ --agents 1 \ --wait > /tmp/k8s-wb-m2/k3d-create.log 2>&1; then cat /tmp/k8s-wb-m2/k3d-create.log exit 1fitest "$(kubectl config current-context)" = "k3d-cs-study-workbook"node automation/k8s-workbook/preflight.mjs --require-clusterNODES="$(kubectl --context k3d-cs-study-workbook get nodes --no-headers | wc -l | tr -d ' ')"READY="$(kubectl --context k3d-cs-study-workbook get nodes --no-headers | awk '$2 == "Ready" { count++ } END { print count+0 }')"test "$NODES" = 2test "$READY" = 2echo "cluster=cs-study-workbook,nodes:${NODES},ready:${READY}"예상 출력:
PASS platform: darwin/arm64PASS architecture: arm64PASS docker-cli: v29.6.1PASS docker-engine: v29.6.1PASS k3d: v5.9.0PASS kubectl-skew: v1.36.1PASS helm: v4.2.3PASS platform: darwin/arm64PASS architecture: arm64PASS docker-cli: v29.6.1PASS docker-engine: v29.6.1PASS k3d: v5.9.0PASS kubectl-skew: v1.36.1PASS helm: v4.2.3PASS context: k3d-cs-study-workbookPASS kubernetes-server: v1.36.2+k3s1cluster=cs-study-workbook,nodes:2,ready:2architecture와 설치된 Docker patch version은 지원 범위 안에서 달라질 수 있는 변동 필드입니다. K3s image, context, API server version, server 1개와 agent 1개는 이번 워크북의 고정값입니다.
Lab 1 — apply: 고유 namespace에 fixture 적용하기
섹션 제목: “Lab 1 — apply: 고유 namespace에 fixture 적용하기”UTC run ID를 파일에 보존하여 각 fence가 새 shell에서 실행돼도 같은 namespace를 사용합니다.
Pod는 역할 기반 fixture 이름 web-app만 log로 출력하며 실제 production image나 설정을 담지
않습니다.
set -euo pipefailtest "$(kubectl config current-context)" = "k3d-cs-study-workbook"NS="k8s-wb-m2-$(date -u +%Y%m%d%H%M%S)"printf '%s' "$NS" > /tmp/k8s-wb-m2/namespaceif kubectl --context k3d-cs-study-workbook get namespace "$NS" >/dev/null 2>&1; then echo "namespace collision: $NS" >&2 exit 1fikubectl --context k3d-cs-study-workbook create namespace "$NS"kubectl --context k3d-cs-study-workbook -n "$NS" apply -f - <<'YAML'apiVersion: v1kind: Podmetadata: name: kubectl-lab labels: app: web-appspec: containers: - name: app image: busybox:1.37.0@sha256:9532d8c39891ca2ecde4d30d7710e01fb739c87a8b9299685c63704296b16028 command: ["sh", "-c"] args: - | echo "READY service=web-app" while true; do sleep 30; doneYAMLkubectl --context k3d-cs-study-workbook -n "$NS" \ wait --for=condition=Ready pod/kubectl-lab --timeout=90s예상 출력:
namespace/k8s-wb-m2-<UTC 14자리 run ID> createdpod/kubectl-lab createdpod/kubectl-lab condition metnamespace의 UTC run ID는 변동 필드입니다. apply의 대상은 command에 명시한 context,
-n "$NS", YAML의 kind와 metadata.name을 함께 보고 결정합니다.
Lab 2 — get과 describe: 현황과 원인 단서 분리하기
섹션 제목: “Lab 2 — get과 describe: 현황과 원인 단서 분리하기”get은 반복 관찰하기 좋은 작은 표를 만들고, describe는 상세 상태와 Events를 사람이 읽을
때 사용합니다. 예상 출력은 재현 가능한 핵심 필드만 추출하지만, 장애 시에는 전체
describe의 Events 구간을 함께 읽어야 합니다.
set -euo pipefailtest "$(kubectl config current-context)" = "k3d-cs-study-workbook"NS="$(cat /tmp/k8s-wb-m2/namespace)"kubectl --context k3d-cs-study-workbook -n "$NS" get pod kubectl-lab \ -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,STATUS:.status.phase' \ --no-headerskubectl --context k3d-cs-study-workbook -n "$NS" describe pod kubectl-lab \ | grep -E '^(Name|Namespace|Status):'kubectl --context k3d-cs-study-workbook -n "$NS" describe pod kubectl-lab \ | awk ' /^Events:/ { events=1; next } events && $1 == "Normal" { print "event=" $2 } ' \ | sort -u예상 출력:
kubectl-lab true RunningName: kubectl-labNamespace: k8s-wb-m2-<UTC 14자리 run ID>Status: Runningevent=Createdevent=Pulledevent=Pullingevent=Scheduledevent=Started열 사이 공백과 namespace run ID는 변동 필드입니다. image가 이미 node cache에 있으면
event=Pulling이 생략될 수 있고, 같은 reason이 여러 번 발생해도 sort -u가 한 줄로
정규화합니다. READY=true는 container readiness, STATUS=Running은 Pod phase이므로 서로
같은 의미로 합치지 않습니다. Events의 Scheduled → Pulling/Pulled → Created → Started는
Pod가 실행되기까지 어느 단계가 진행됐는지 보여 줍니다.
Lab 3 — 잘못된 namespace를 의도적으로 실패시키기
섹션 제목: “Lab 3 — 잘못된 namespace를 의도적으로 실패시키기”Pod 이름이 맞아도 namespace가 다르면 다른 API object를 찾게 됩니다. 다음 명령은
default에서 fixture를 찾으므로 실제 exit code 1로 실패해야 합니다.
kubectl --context k3d-cs-study-workbook -n default logs kubectl-lab예상 출력과 exit code:
error: error from server (NotFound): pods "kubectl-lab" not found in namespace "default"exit code: 1NotFound를 보고 Pod를 즉시 재배포하지 않습니다. 먼저 context, namespace, resource type과
name이 모두 맞는지 확인해야 합니다.
Lab 4 — logs로 복구 확인하고 exec로 경계 안을 관찰하기
섹션 제목: “Lab 4 — logs로 복구 확인하고 exec로 경계 안을 관찰하기”저장해 둔 namespace를 명시하면 같은 Pod의 stdout을 찾을 수 있습니다. 이어서 exec가
기존 main process를 바꾸는 것이 아니라 container 안에 진단용 sh process를 하나 더
실행한다는 점을 확인합니다.
set -euo pipefailtest "$(kubectl config current-context)" = "k3d-cs-study-workbook"NS="$(cat /tmp/k8s-wb-m2/namespace)"kubectl --context k3d-cs-study-workbook -n "$NS" logs kubectl-labkubectl --context k3d-cs-study-workbook -n "$NS" exec kubectl-lab -- \ sh -c 'printf "inside=%s\n" "$(hostname)"'예상 출력:
READY service=web-appinside=kubectl-lab단일 container Pod라서 container 이름을 생략했습니다. 여러 container가 있는 Pod에서는
logs -c <container>와 exec -c <container>로 대상을 추가 지정해야 합니다.
Cleanup
섹션 제목: “Cleanup”삭제를 관찰하고 module resource 정리하기
섹션 제목: “삭제를 관찰하고 module resource 정리하기”Pod 삭제 후 조회 실패를 직접 판정하고 namespace 전체를 제거합니다. 공유 학습 기반인
cs-study-workbook cluster는 M3~M12에서 계속 사용하므로 의도적으로 남깁니다.
set -euo pipefailtest "$(kubectl config current-context)" = "k3d-cs-study-workbook"NS="$(cat /tmp/k8s-wb-m2/namespace)"kubectl --context k3d-cs-study-workbook -n "$NS" \ delete pod kubectl-lab --wait=trueif kubectl --context k3d-cs-study-workbook -n "$NS" \ get pod kubectl-lab >/dev/null 2>&1; then echo 'pod still exists' >&2 exit 1fiecho 'pod-after-delete=NotFound'kubectl --context k3d-cs-study-workbook delete namespace "$NS" --wait=true \ >/dev/nullif kubectl --context k3d-cs-study-workbook \ get namespace "$NS" >/dev/null 2>&1; then echo 'namespace still exists' >&2 exit 1firm -rf /tmp/k8s-wb-m2test "$(kubectl config current-context)" = "k3d-cs-study-workbook"echo 'cleanup=namespace:0,pod:0,fixture:absent,cluster:retained'예상 출력:
pod "kubectl-lab" deleted from k8s-wb-m2-<UTC 14자리 run ID> namespacepod-after-delete=NotFoundcleanup=namespace:0,pod:0,fixture:absent,cluster:retainednamespace run ID는 변동 필드입니다. cluster:retained는 누락된 cleanup이 아니라 다음 모듈도
사용하는 Goal 공용 기반이라는 뜻이며, module이 만든 Kubernetes API resource와 /tmp
fixture는 0개입니다.
5. 관찰 포인트
섹션 제목: “5. 관찰 포인트”| 실행 뒤 | 보이는 것 | 운영 판단 |
|---|---|---|
| cluster 생성 + preflight | 고정 context, API version, Ready node 2개 | 이 세 값이 다르면 application 명령을 시작하지 않습니다. |
apply + wait | namespace와 Pod 생성, Ready condition 충족 | created와 application 정상 응답은 같은 판정이 아닙니다. |
get | 작은 현재 상태 표 | 빠른 triage에 쓰되 원인은 describe와 logs로 좁힙니다. |
describe | 객체 상세와 Events | 스케줄링·pull·시작 문제의 API 관측 근거를 찾습니다. |
잘못된 namespace의 logs | NotFound, exit code 1 | 재배포 전에 주소축(context·namespace·name)을 점검합니다. |
올바른 logs와 exec | stdout과 container 내부 진단 결과 | exec 수정은 재현되지 않으므로 영구 해결책으로 쓰지 않습니다. |
| Pod·namespace cleanup | module resource 0, local cluster 유지 | 다음 모듈은 새 namespace에서 시작합니다. |
6. 셀프체크 Q&A
섹션 제목: “6. 셀프체크 Q&A”Q1. AI가 kubectl logs api-service를 제안했지만 NotFound가 나왔습니다. 바로 재배포해도 될까요?
섹션 제목: “Q1. AI가 kubectl logs api-service를 제안했지만 NotFound가 나왔습니다. 바로 재배포해도 될까요?”아니요. 먼저 kubectl config current-context, 명시한 -n, resource type과 name을 확인합니다.
이번 랩처럼 같은 cluster에 Pod가 존재해도 namespace가 다르면 NotFound가 정상입니다. 대상 축을
검증한 뒤 올바른 context와 namespace를 명시해 get과 logs를 다시 실행합니다.
Q2. get pod에서 Running이므로 web-app이 요청을 처리할 준비가 됐다고 승인해도 될까요?
섹션 제목: “Q2. get pod에서 Running이므로 web-app이 요청을 처리할 준비가 됐다고 승인해도 될까요?”승인하면 안 됩니다. Running은 Pod phase이고 application readiness나 실제 요청 성공을
보장하지 않습니다. 이번 랩에서도 READY와 Running은 별도 관측값입니다. M9에서 probe를
추가해 트래픽 수신 가능 상태를 별도로 판정합니다.
Q3. 장애 중 exec로 container 파일을 고쳐 서비스가 살아났습니다. 이를 최종 복구로 기록해도 될까요?
섹션 제목: “Q3. 장애 중 exec로 container 파일을 고쳐 서비스가 살아났습니다. 이를 최종 복구로 기록해도 될까요?”아니요. exec로 만든 변경은 Pod가 교체되면 사라지고 image·Pod spec에도 남지 않습니다.
진단 근거와 임시 조치를 기록한 뒤, 영구 수정은 image 또는 선언형 설정에 반영하고 새 Pod로
재현해야 합니다.
7. 흔한 함정
섹션 제목: “7. 흔한 함정”함정 1. current context를 안전 장치로 착각하기
섹션 제목: “함정 1. current context를 안전 장치로 착각하기”kubectl config use-context만 믿고 이후 명령에서 --context와 -n을 생략합니다. shell이나
AI 세션 사이에 current context가 달라질 수 있으므로 변경 명령은 대상을 명시하고 guard를
먼저 통과시킵니다.
함정 2. Running 한 칸으로 정상 승인하기
섹션 제목: “함정 2. Running 한 칸으로 정상 승인하기”get의 Running 한 칸을 정상 판정으로 사용합니다. Events, container 상태, log와 실제
readiness는 별도 신호입니다.
함정 3. exec 변경을 영구 복구로 남기기
섹션 제목: “함정 3. exec 변경을 영구 복구로 남기기”exec로 고친 상태를 배포 결과로 착각합니다. 임시 container 상태는 재시작·재배포 때
사라지므로 선언 원본을 고쳐야 합니다.
관리형 NKS에서는 control plane, etcd, CNI 내부 구현과 Terraform 작성은 제외합니다. 실제 NKS context·IAM·namespace 변경, production 배포와 Secret 처리는 승인된 non-production 환경 없이는 수행하지 않습니다.
8. 다음 모듈 연결 고리
섹션 제목: “8. 다음 모듈 연결 고리”M3에서는 같은 안전 context와 새 고유 namespace에서 Pod를 직접 지우고, Deployment와 ReplicaSet이 원하는 replica 수를 되살리는 과정을 관찰합니다.