콘텐츠로 이동

M2. k3d 클러스터와 kubectl 안전 조작

예시 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으로 연결됩니다.

kubectl 요청이 도달하는 안전 경로
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 familyAPI에 요구하는 일상태 변경운영할 때 먼저 묻는 질문
applyYAML의 원하는 상태를 생성하거나 갱신diff를 리뷰했고 대상 context가 맞는가?
get객체 목록이나 선택한 필드를 빠르게 조회아니요어떤 namespace와 label 범위인가?
describe객체 상세 상태와 관련 Events를 사람이 읽음아니요상태가 막힌 이유가 Events에 남았는가?
logscontainer의 stdout·stderr를 조회아니요Pod와 container를 정확히 골랐는가?
exec실행 중인 container 안에 새 process를 실행진단에 꼭 필요하며 실행 흔적을 설명할 수 있나?

kubectl은 container를 직접 조작하는 원격 shell이 아니라 API Server에 요청하는 client입니다. logsexec도 먼저 context·namespace·Pod를 고른 뒤 API를 통해 실행됩니다. namespace와 node는 서로 다른 축이므로, namespace를 고른다고 특정 node가 선택되지는 않습니다.

시점 의존 설명은 2026-07-15에 다음 공식 1차 자료로 확인했습니다.

개념비유비유의 경계
k3d clusterDocker 안에 만든 폐쇄형 운전 연습장NKS의 load balancer·IAM·node pool 동작까지 재현하지는 않습니다.
context요청서에 찍는 목적 기관·사용자·기본 구역 묶음권한 자체를 새로 만드는 것이 아니라 kubeconfig의 선택 정보입니다.
namespace같은 기관 안에서 문서를 분류하는 별도 업무함별도 물리 server나 완전한 보안 격리를 자동 보장하지 않습니다.
get현황판원인과 시간순 사건까지 모두 설명하지는 않습니다.
describe객체 상세 기록과 최근 사건표container 내부 application log는 logs로 따로 봐야 합니다.
logsprocess가 남긴 업무 일지process 안에서 지금 무엇이 실행 중인지는 별도 관찰이 필요합니다.
exec가동 중인 장비에 진단 기사를 잠시 들여보내는 작업image나 Pod spec을 영구 수정하는 방법이 아닙니다.
항목검증값
기준일2026-07-15
hostmacOS arm64
Docker client / engine29.6.1 / 29.6.1
k3d5.9.0
K3s image / API serverrancher/k3s:v1.36.2-k3s1 / v1.36.2+k3s1
kubectl1.36.1
Helm4.2.3 — 이 모듈에서는 사용하지 않음
Pod imagebusybox: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을 명시합니다. default namespace나 현재 context에 암묵적으로 의존하지 않습니다.
  • 예상 소요 시간은 90분입니다.

이 워크북 전용 cluster만 삭제 후 재생성합니다. 성공한 k3d cluster create는 해당 context를 kubeconfig에 추가하고 current context로 선택하지만, 이후 명령도 명시적 --context를 계속 사용합니다. 생성 log는 실패할 때만 출력합니다.

Terminal window
set -euo pipefail
rm -rf /tmp/k8s-wb-m2
mkdir -p /tmp/k8s-wb-m2
node automation/k8s-workbook/preflight.mjs
k3d cluster delete cs-study-workbook >/dev/null 2>&1 || true
if ! 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 1
fi
test "$(kubectl config current-context)" = "k3d-cs-study-workbook"
node automation/k8s-workbook/preflight.mjs --require-cluster
NODES="$(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" = 2
test "$READY" = 2
echo "cluster=cs-study-workbook,nodes:${NODES},ready:${READY}"

예상 출력:

PASS platform: darwin/arm64
PASS architecture: arm64
PASS docker-cli: v29.6.1
PASS docker-engine: v29.6.1
PASS k3d: v5.9.0
PASS kubectl-skew: v1.36.1
PASS helm: v4.2.3
PASS platform: darwin/arm64
PASS architecture: arm64
PASS docker-cli: v29.6.1
PASS docker-engine: v29.6.1
PASS k3d: v5.9.0
PASS kubectl-skew: v1.36.1
PASS helm: v4.2.3
PASS context: k3d-cs-study-workbook
PASS kubernetes-server: v1.36.2+k3s1
cluster=cs-study-workbook,nodes:2,ready:2

architecture와 설치된 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나 설정을 담지 않습니다.

Terminal window
set -euo pipefail
test "$(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/namespace
if kubectl --context k3d-cs-study-workbook get namespace "$NS" >/dev/null 2>&1; then
echo "namespace collision: $NS" >&2
exit 1
fi
kubectl --context k3d-cs-study-workbook create namespace "$NS"
kubectl --context k3d-cs-study-workbook -n "$NS" apply -f - <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: kubectl-lab
labels:
app: web-app
spec:
containers:
- name: app
image: busybox:1.37.0@sha256:9532d8c39891ca2ecde4d30d7710e01fb739c87a8b9299685c63704296b16028
command: ["sh", "-c"]
args:
- |
echo "READY service=web-app"
while true; do sleep 30; done
YAML
kubectl --context k3d-cs-study-workbook -n "$NS" \
wait --for=condition=Ready pod/kubectl-lab --timeout=90s

예상 출력:

namespace/k8s-wb-m2-<UTC 14자리 run ID> created
pod/kubectl-lab created
pod/kubectl-lab condition met

namespace의 UTC run ID는 변동 필드입니다. apply의 대상은 command에 명시한 context, -n "$NS", YAML의 kindmetadata.name을 함께 보고 결정합니다.

Lab 2 — getdescribe: 현황과 원인 단서 분리하기

섹션 제목: “Lab 2 — get과 describe: 현황과 원인 단서 분리하기”

get은 반복 관찰하기 좋은 작은 표를 만들고, describe는 상세 상태와 Events를 사람이 읽을 때 사용합니다. 예상 출력은 재현 가능한 핵심 필드만 추출하지만, 장애 시에는 전체 describeEvents 구간을 함께 읽어야 합니다.

Terminal window
set -euo pipefail
test "$(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-headers
kubectl --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 Running
Name: kubectl-lab
Namespace: k8s-wb-m2-<UTC 14자리 run ID>
Status: Running
event=Created
event=Pulled
event=Pulling
event=Scheduled
event=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로 실패해야 합니다.

Terminal window
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: 1

NotFound를 보고 Pod를 즉시 재배포하지 않습니다. 먼저 context, namespace, resource type과 name이 모두 맞는지 확인해야 합니다.

Lab 4 — logs로 복구 확인하고 exec로 경계 안을 관찰하기

섹션 제목: “Lab 4 — logs로 복구 확인하고 exec로 경계 안을 관찰하기”

저장해 둔 namespace를 명시하면 같은 Pod의 stdout을 찾을 수 있습니다. 이어서 exec가 기존 main process를 바꾸는 것이 아니라 container 안에 진단용 sh process를 하나 더 실행한다는 점을 확인합니다.

Terminal window
set -euo pipefail
test "$(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-lab
kubectl --context k3d-cs-study-workbook -n "$NS" exec kubectl-lab -- \
sh -c 'printf "inside=%s\n" "$(hostname)"'

예상 출력:

READY service=web-app
inside=kubectl-lab

단일 container Pod라서 container 이름을 생략했습니다. 여러 container가 있는 Pod에서는 logs -c <container>exec -c <container>로 대상을 추가 지정해야 합니다.

삭제를 관찰하고 module resource 정리하기

섹션 제목: “삭제를 관찰하고 module resource 정리하기”

Pod 삭제 후 조회 실패를 직접 판정하고 namespace 전체를 제거합니다. 공유 학습 기반인 cs-study-workbook cluster는 M3~M12에서 계속 사용하므로 의도적으로 남깁니다.

Terminal window
set -euo pipefail
test "$(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=true
if kubectl --context k3d-cs-study-workbook -n "$NS" \
get pod kubectl-lab >/dev/null 2>&1; then
echo 'pod still exists' >&2
exit 1
fi
echo 'pod-after-delete=NotFound'
kubectl --context k3d-cs-study-workbook delete namespace "$NS" --wait=true \
>/dev/null
if kubectl --context k3d-cs-study-workbook \
get namespace "$NS" >/dev/null 2>&1; then
echo 'namespace still exists' >&2
exit 1
fi
rm -rf /tmp/k8s-wb-m2
test "$(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> namespace
pod-after-delete=NotFound
cleanup=namespace:0,pod:0,fixture:absent,cluster:retained

namespace run ID는 변동 필드입니다. cluster:retained는 누락된 cleanup이 아니라 다음 모듈도 사용하는 Goal 공용 기반이라는 뜻이며, module이 만든 Kubernetes API resource와 /tmp fixture는 0개입니다.

실행 뒤보이는 것운영 판단
cluster 생성 + preflight고정 context, API version, Ready node 2개이 세 값이 다르면 application 명령을 시작하지 않습니다.
apply + waitnamespace와 Pod 생성, Ready condition 충족created와 application 정상 응답은 같은 판정이 아닙니다.
get작은 현재 상태 표빠른 triage에 쓰되 원인은 describelogs로 좁힙니다.
describe객체 상세와 Events스케줄링·pull·시작 문제의 API 관측 근거를 찾습니다.
잘못된 namespace의 logsNotFound, exit code 1재배포 전에 주소축(context·namespace·name)을 점검합니다.
올바른 logsexecstdout과 container 내부 진단 결과exec 수정은 재현되지 않으므로 영구 해결책으로 쓰지 않습니다.
Pod·namespace cleanupmodule resource 0, local cluster 유지다음 모듈은 새 namespace에서 시작합니다.

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를 명시해 getlogs를 다시 실행합니다.

Q2. get pod에서 Running이므로 web-app이 요청을 처리할 준비가 됐다고 승인해도 될까요?

섹션 제목: “Q2. get pod에서 Running이므로 web-app이 요청을 처리할 준비가 됐다고 승인해도 될까요?”

승인하면 안 됩니다. Running은 Pod phase이고 application readiness나 실제 요청 성공을 보장하지 않습니다. 이번 랩에서도 READYRunning은 별도 관측값입니다. M9에서 probe를 추가해 트래픽 수신 가능 상태를 별도로 판정합니다.

Q3. 장애 중 exec로 container 파일을 고쳐 서비스가 살아났습니다. 이를 최종 복구로 기록해도 될까요?

섹션 제목: “Q3. 장애 중 exec로 container 파일을 고쳐 서비스가 살아났습니다. 이를 최종 복구로 기록해도 될까요?”

아니요. exec로 만든 변경은 Pod가 교체되면 사라지고 image·Pod spec에도 남지 않습니다. 진단 근거와 임시 조치를 기록한 뒤, 영구 수정은 image 또는 선언형 설정에 반영하고 새 Pod로 재현해야 합니다.

함정 1. current context를 안전 장치로 착각하기

섹션 제목: “함정 1. current context를 안전 장치로 착각하기”

kubectl config use-context만 믿고 이후 명령에서 --context-n을 생략합니다. shell이나 AI 세션 사이에 current context가 달라질 수 있으므로 변경 명령은 대상을 명시하고 guard를 먼저 통과시킵니다.

함정 2. Running 한 칸으로 정상 승인하기

섹션 제목: “함정 2. Running 한 칸으로 정상 승인하기”

getRunning 한 칸을 정상 판정으로 사용합니다. Events, container 상태, log와 실제 readiness는 별도 신호입니다.

함정 3. exec 변경을 영구 복구로 남기기

섹션 제목: “함정 3. exec 변경을 영구 복구로 남기기”

exec로 고친 상태를 배포 결과로 착각합니다. 임시 container 상태는 재시작·재배포 때 사라지므로 선언 원본을 고쳐야 합니다.

관리형 NKS에서는 control plane, etcd, CNI 내부 구현과 Terraform 작성은 제외합니다. 실제 NKS context·IAM·namespace 변경, production 배포와 Secret 처리는 승인된 non-production 환경 없이는 수행하지 않습니다.

M3에서는 같은 안전 context와 새 고유 namespace에서 Pod를 직접 지우고, Deployment와 ReplicaSet이 원하는 replica 수를 되살리는 과정을 관찰합니다.