kubectl 명령어로 디버깅 해보기

|

사이드카 컨테이너

k8s 1.25부터 Stable된 명령어로 컨테이너에 경량화나 보안을 위해 Shell 조차 없을 경우 사용가능한 방법

kubectl debug --stdin --tty [디버그대상Pod명] --image=[디버그용컨테이너이미지] --target=[디버그대상컨테이너명]

현재 아래와 같이 컨테이너가 동작 중

$ kubectl get pods                                 

NAME           READY   STATUS    RESTARTS   AGE
snowdeer-app   1/1     Running   0          5h11m

실행

$ kubectl debug --stdin --tty snowdeer-app --image=curlimages/curl:latest --target=hello-server -- sh

Targeting container "hello-server". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
Defaulting debug container name to debugger-55kh7.
If you don't see a command prompt, try pressing enter.
~ $ 

여기에 curl localhost:8080 입력 하면 아래와 같이 결과가 나옴

$ kubectl debug --stdin --tty snowdeer-app --image=curlimages/curl:latest --target=hello-server -- sh

Targeting container "hello-server". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
Defaulting debug container name to debugger-55kh7.
If you don't see a command prompt, try pressing enter.
~ $ curl localhost:8080
Hello, world!~ $ 
~ $ 

--stdin--tty 대신 -it를 쓰면 좀 더 간편해짐

$ kubectl debug -it snowdeer-app --image=curlimages/curl:latest --target=hello-server -- sh

kubectl exec

kubectl exec 명령어로 컨테이너에 직접 접속 가능

예시로 curl Pod를 하나 생성

$ kubectl run snowdeer-curl --image=curlimages/curl:latest --command -- /bin/sh -c "while true; do sleep infinity; done;"

pod/snowdeer-curl created

$ kubectl get pods -o wide     

NAME            READY   STATUS    RESTARTS   AGE     IP           NODE                             NOMINATED NODE   READINESS GATES
snowdeer-app    1/1     Running   0          5h20m   10.244.0.5   snowdeer-cluster-control-plane              
snowdeer-curl   1/1     Running   0          2m22s   10.244.0.7   snowdeer-cluster-control-plane              
</pre>

## 접속

$ kubectl exec -it snowdeer-curl -- /bin/sh                         

# 위에서 확인했던 snowdeer-app의 주소로 명령어를 날려봄
~ $ curl 10.244.0.5:8080
Hello, world!~ $ 
# 포트포워딩을 통한 접속 k8s의 Pod에는 쿠버네티스 내부의 IP 주소가 할당됨. 즉, 외부에서는 기본적으로 접속 불가능. `Service`를 이용해서 접속하는 것이 일반적이나 `port-forward` 명령어를 이용해서 접근 가능 아래 명령어로 로컬PC의 `3000`번 포트를 `snowdeer-app`의 `8080`에 연결할 수 있음
$ kubectl port-forward snowdeer-app 3000:8080  

Forwarding from 127.0.0.1:3000 -> 8080
Forwarding from [::1]:3000 -> 8080
그 이후 로컬 PC의 터미널에서 아래 명령어 호출하면 접속 가능함
$ curl localhost:3000      

Hello, world!                                                 

샘플 Manifest 사용해보기

|

Sample Manifest

snowdeer-app.yaml

apiVersion: v1
kind: Pod
metadata:
  name: snowdeer-app
  labels:
    app: snowdeer-app
spec:
  containers:
    - name: hello-server
      image: blux2/hello-server:1.0
      ports:
        - containerPort: 8080

실행

kubectl apply -f <파일이름>으로 실행할 수 있음

$ kubectl apply -f snowdeer-app.yaml

pod/snowdeer-app created

실행 확인

$ kubectl get pods    

NAME           READY   STATUS    RESTARTS   AGE
snowdeer-app   1/1     Running   0          81s

또는

$ kubectl get pods -o wide                

NAME           READY   STATUS    RESTARTS   AGE   IP           NODE                             NOMINATED NODE   READINESS GATES
snowdeer-app   1/1     Running   0          23m   10.244.0.5   snowdeer-cluster-control-plane   none             none

만약 -o wide 옵션 대신 -o yaml 옵션을 사용하면 다음과 같은 결과가 리턴

$ kubectl get pods snowdeer-app -o yaml    

apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"app":"snowdeer-app"},"name":"snowdeer-app","namespace":"default"},"spec":{"containers":[{"image":"blux2/hello-server:1.0","name":"hello-server","ports":[{"containerPort":8080}]}]}}
  creationTimestamp: "2025-12-07T07:06:14Z"
  generation: 1
  labels:
    app: snowdeer-app
  name: snowdeer-app
  namespace: default
  resourceVersion: "1578"
  uid: cd5a19de-fcf5-4daa-8373-c74295aea3c3
spec:
  containers:
  - image: blux2/hello-server:1.0
    imagePullPolicy: IfNotPresent
    name: hello-server
    ports:
    - containerPort: 8080
      protocol: TCP
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-bh7dz
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: snowdeer-cluster-control-plane
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-bh7dz
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2025-12-07T07:06:23Z"
    observedGeneration: 1
    status: "True"
    type: PodReadyToStartContainers
  - lastProbeTime: null
    lastTransitionTime: "2025-12-07T07:06:14Z"
    observedGeneration: 1
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2025-12-07T07:06:23Z"
    observedGeneration: 1
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2025-12-07T07:06:23Z"
    observedGeneration: 1
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2025-12-07T07:06:14Z"
    observedGeneration: 1
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: containerd://f540bc09b42828c9e0a6783fe2e35bddd5a03e1ce07a1f024319097f7b721d8c
    image: docker.io/blux2/hello-server:1.0
    imageID: docker.io/blux2/hello-server@sha256:35ab584cbe96a15ad1fb6212824b3220935d6ac9d25b3703ba259973fac5697d
    lastState: {}
    name: hello-server
    ready: true
    resources: {}
    restartCount: 0
    started: true
    state:
      running:
        startedAt: "2025-12-07T07:06:23Z"
    user:
      linux:
        gid: 0
        supplementalGroups:
        - 0
        uid: 0
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-bh7dz
      readOnly: true
      recursiveReadOnly: Disabled
  hostIP: 172.18.0.2
  hostIPs:
  - ip: 172.18.0.2
  observedGeneration: 1
  phase: Running
  podIP: 10.244.0.5
  podIPs:
  - ip: 10.244.0.5
  qosClass: BestEffort
  startTime: "2025-12-07T07:06:14Z"

kubectl get <리소스이름> -o yaml | less 옵션을 이용해서 key가 되는 문자열을 검색하기도 함

하지만, 다른 이유보다 아래와 같은 명령어를 이용하면 현재 클러스터에 적용된 오브젝트의 내용과 Manifest의 차이를 확인할 수 있는 유용성이 있음

$ kubectl get pods snowdeer-app -o yaml > pod.yaml

그 이후 아래 명령어로 차이를 확인

$ diff pod.yaml snowdeer-app.yaml

4,8c4
<   annotations:
<     kubectl.kubernetes.io/last-applied-configuration: |
<       {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"app":"snowdeer-app"},"name":"snowdeer-app","namespace":"default"},"spec":{"containers":[{"image":"blux2/hello-server:1.0","name":"hello-server","ports":[{"containerPort":8080}]}]}}
<   creationTimestamp: "2025-12-07T07:06:14Z"
<   generation: 1
---
>   name: snowdeer-app
11,14d6
<   name: snowdeer-app
<   namespace: default
<   resourceVersion: "1578"
<   uid: cd5a19de-fcf5-4daa-8373-c74295aea3c3
17,129c9,12
<   - image: blux2/hello-server:1.0
<     imagePullPolicy: IfNotPresent
<     name: hello-server
<     ports:
<     - containerPort: 8080
<       protocol: TCP
<     resources: {}
<     terminationMessagePath: /dev/termination-log
<     terminationMessagePolicy: File
<     volumeMounts:
<     - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
<       name: kube-api-access-bh7dz
<       readOnly: true
<   dnsPolicy: ClusterFirst
<   enableServiceLinks: true
<   nodeName: snowdeer-cluster-control-plane
<   preemptionPolicy: PreemptLowerPriority
<   priority: 0
<   restartPolicy: Always
<   schedulerName: default-scheduler
<   securityContext: {}
<   serviceAccount: default
<   serviceAccountName: default
<   terminationGracePeriodSeconds: 30
<   tolerations:
<   - effect: NoExecute
<     key: node.kubernetes.io/not-ready
<     operator: Exists
<     tolerationSeconds: 300
<   - effect: NoExecute
<     key: node.kubernetes.io/unreachable
<     operator: Exists
<     tolerationSeconds: 300
<   volumes:
<   - name: kube-api-access-bh7dz
<     projected:
<       defaultMode: 420
<       sources:
<       - serviceAccountToken:
<           expirationSeconds: 3607
<           path: token
<       - configMap:
<           items:
<           - key: ca.crt
<             path: ca.crt
<           name: kube-root-ca.crt
<       - downwardAPI:
<           items:
<           - fieldRef:
<               apiVersion: v1
<               fieldPath: metadata.namespace
<             path: namespace
< status:
<   conditions:
<   - lastProbeTime: null
<     lastTransitionTime: "2025-12-07T07:06:23Z"
<     observedGeneration: 1
<     status: "True"
<     type: PodReadyToStartContainers
<   - lastProbeTime: null
<     lastTransitionTime: "2025-12-07T07:06:14Z"
<     observedGeneration: 1
<     status: "True"
<     type: Initialized
<   - lastProbeTime: null
<     lastTransitionTime: "2025-12-07T07:06:23Z"
<     observedGeneration: 1
<     status: "True"
<     type: Ready
<   - lastProbeTime: null
<     lastTransitionTime: "2025-12-07T07:06:23Z"
<     observedGeneration: 1
<     status: "True"
<     type: ContainersReady
<   - lastProbeTime: null
<     lastTransitionTime: "2025-12-07T07:06:14Z"
<     observedGeneration: 1
<     status: "True"
<     type: PodScheduled
<   containerStatuses:
<   - containerID: containerd://f540bc09b42828c9e0a6783fe2e35bddd5a03e1ce07a1f024319097f7b721d8c
<     image: docker.io/blux2/hello-server:1.0
<     imageID: docker.io/blux2/hello-server@sha256:35ab584cbe96a15ad1fb6212824b3220935d6ac9d25b3703ba259973fac5697d
<     lastState: {}
<     name: hello-server
<     ready: true
<     resources: {}
<     restartCount: 0
<     started: true
<     state:
<       running:
<         startedAt: "2025-12-07T07:06:23Z"
<     user:
<       linux:
<         gid: 0
<         supplementalGroups:
<         - 0
<         uid: 0
<     volumeMounts:
<     - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
<       name: kube-api-access-bh7dz
<       readOnly: true
<       recursiveReadOnly: Disabled
<   hostIP: 172.18.0.2
<   hostIPs:
<   - ip: 172.18.0.2
<   observedGeneration: 1
<   phase: Running
<   podIP: 10.244.0.5
<   podIPs:
<   - ip: 10.244.0.5
<   qosClass: BestEffort
<   startTime: "2025-12-07T07:06:14Z"
---
>     - name: hello-server
>       image: blux2/hello-server:1.0
>       ports:
>         - containerPort: 8080

실행해보면 차이가 많음. Manifest에서는 필수적인 내용만 기재되지만, 실제로 k8s에서 구동되면 더 많은 정보가 필요함

Kind 이용 클러스터 생성

|

k8s 클러스터 생성

아래와 같이 명령을 내리면 kind-가 prefix로 붙어서 kind-snowdeer-cluster 이름의 클러스터가 생성됨

$ kind create cluster --name snowdeer-cluster

Creating cluster "snowdeer-cluster" ...
 ✓ Ensuring node image (kindest/node:v1.34.0) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-snowdeer-cluster"
You can now use your cluster with:

kubectl cluster-info --context kind-snowdeer-cluster

Have a nice day! 👋

k8s 클러스터 정보 확인

$ kubectl cluster-info --context kind-snowdeer-cluster

Kubernetes control plane is running at https://127.0.0.1:57730
CoreDNS is running at https://127.0.0.1:57730/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

위에서 --context 옵션을 붙여서 클러스터의 context를 지정해야 하지만, 만약 하나의 클러스터만 존재하면 해당 옵션은 생략가능함. 여러 클러스터일 경우 --context 옵션 없이 기본 클러스터를 지정하기 위해서 아래 명령어 사용 가능

$ kubectl config use-context kind-snowdeer-cluster

Switched to context "kind-snowdeer-cluster".

클러스터 리스트 확인

kind를 이용해서 클러스터를 생성했기 때문에 명령어를 kind를 이용해서 조회할 수 있음

$ kind get clusters   

snowdeer-cluster

Node 정보 확인

$ kubectl get nodes                               

NAME                             STATUS   ROLES           AGE     VERSION
snowdeer-cluster-control-plane   Ready    control-plane   6m41s   v1.34.0

MAC OS에 kind 설치하는 방법

|

kind

https://kind.sigs.k8s.io 멀티 노드 클러스터 생성가능한 툴. Docker 내부에 Docker을 실행하기 때문에 Docker in Docker 라고도 함

MAC OS에 kind 설치

brew를 이용해서 설치 가능

$ brew install kind

설치 확인

$ kind version

kind v0.30.0 go1.25.4 darwin/arm64

k8s 클러스터 생성

Docker가 먼저 실행 중이어야 함

$ kind create cluster

Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.34.0) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a nice day! 👋

k8s 클러스터 정보 확인

$ kubectl cluster-info --context kind-kind

Kubernetes control plane is running at https://127.0.0.1:54204
CoreDNS is running at https://127.0.0.1:54204/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

config

k8s 환경의 설정은 .kube/config 파일 안에 기록되어 있음

$ cat .kube/config      
apiVersion: v1
clusters:
- context:
    cluster: kind-kind
    user: kind-kind
  name: kind-kind
current-context: kind-kind
kind: Config
preferences: {}
users:
- name: kind-kind
  user:
    client-certificate-data: 어쩌고저쩌고
    client-key-data: 어쩌고저쩌고

클러스터 삭제

$ kind delete cluster

Deleting cluster "kind" ...
Deleted nodes: ["kind-control-plane"]

Ubuntu(zsh) 이미지 실행하는 방법

|

Docker 컨테이너에 zsh 설치하기

이미 빌드된 이미지 사용하기

이미 Docker Hub에 위 이미지가 올라가 있기 때문에, 별도의 과정 필요없이 아래 명령어로 바로 사용할 수 있습니다.

$ docker run -it snowdeer/ubuntu-22p04 zsh

이미지 생성하는 방법

위의 이미지를 생성하는 방법입니다. Dockerfile을 이용해서 생성할 수도 있지만, 실제 Ubuntu를 사용하면서 신규 패키지를 설치하는 경우도 있기 때문에 이번에는 컨테이너에 다양한 패키지를 직접 설치한다음 이미지로 만드는 방법으로 실행해보았습니다.

zsh가 설치된 Docker 이미지는 이미 Docker Hub에서 받을 수 있지만, 제가 개인적으로 사용하는 Ubuntu 이미지에 zsh를 설치해서 사용하는 것을 더 좋아합니다.

기본 ubuntu 이미지에 zsh만 설치했을 때는 몇 가지 문제(ex. Locale 설정 등)가 발생해서 그 해결 방법을 아래와 같이 포스팅합니다.

Ubuntu 이미지 다운로드

# 최신 Ubuntu 이미지 다운로드(현 시점 기준 22.04 버전)
$ docker pull ubuntu

$ docker run -it ubuntu

필요 패키지 설치

Docker 컨테이너 속의 터미널에서는 다음과 같이 입력합니다.

$ apt update

# vi를 써도 되지만 개인적으론 nano가 더 편해서 nano도 설치
$ apt install -y git curl nano zsh

# 생략해도 되지만 network 관련 유틸리티이기 때문에 그냥 설치 
$ apt install -y iputils-ping net-tools iproute2 dnsutils

# oh-my-zsh 설치
$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

... 
Time to change your default shell to zsh:
Do you want to change your default shell to zsh? [Y/n] y

ZSH 설정 편집

nano ~/.zshrc 명령어로 편집기를 실행해서 ZSH_THEME="robbyrussell" 부분만 ZSH_THEME="agnoster"으로 수정합니다.

이 상태에서 zsh 명령어로 zshell을 실행하면 아래와 같은 오류가 발생합니다.

$ zsh

(anon):12: character not in range

ZSH Locale 문제 해결

이 문제는 Locale 때문에 발생하는 문제로 아래와 같이 해결할 수 있습니다.

$ apt install -y locales

$ locale-gen en_US.UTF-8

자, 제가 개인적으로 즐겨 사용하는 ubuntu 이미지가 생성 완료되었습니다. 이 상태에서 해당 컨테이너의 내용으로 이미지를 만들어줍니다. (컨테이너 터미널 외부에서 입력합니다.)

Docker 이미지 commit

# 먼저 컨네이터의 ID를 확인합니다.
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS                     PORTS     NAMES
d0b9b5314e29   ubuntu    "/bin/bash"   8 minutes ago   Exited (0) 2 seconds ago             adoring_carson

$ docker commit d0b9 snowdeer/ubuntu-22p04
sha256:b4a5a887e027af8a49062c67e2a53ea6cac8fd0c22d92a013ec2aba3b40896dc

$ docker images
REPOSITORY                    TAG       IMAGE ID       CREATED         SIZE
snowdeer/ubuntu-22p04         latest    b4a5a887e027   3 seconds ago   226MB

Docker Hub에 업로드

$ docker push snowdeer/ubuntu-22p04
Using default tag: latest
The push refers to repository [docker.io/snowdeer/ubuntu-22p04]
f5048f19797c: Pushed
c5ca84f245d3: Pushed
latest: digest: sha256:206d44f8646bb6bd041728531b74e193f325c340c8537b4881a3daf2f0522349 size: 741