
환경: VirtualBox / Ubuntu 24.04 / kubeadm / Flannel /
Jenkins + ArgoCD (CI/CD) / PARKIT 프로젝트 (React, Spring Boot, Kafka, MongoDB, Redis)
들어가며
쿠버네티스로 프로젝트를 배포하다 보면 한 번쯤은 마주치게 되는 공포의 상태가 있다. 바로 CrashLoopBackOff. Pod 하나가 계속 죽고, 그 영향으로 다른 Pod들도 연쇄적으로 죽어나가는 상황. 우리 팀도 예외가 없었다.
VirtualBox 위에 kubeadm으로 구성한 3노드 클러스터(master-yh, worker1, worker2)에서 PARKIT 프로젝트를 CI/CD 파이프라인(Jenkins + ArgoCD)으로 운영하려는데, Pod들이 항시 러닝 상태가 아니었다.

프로젝트의 핵심 서비스들이 전부 정상적으로 뜨는 순간이 없었다. 잘 돼도 꼭 하나씩은 망가져 있었다.
이 글은 그 원인을 찾아가는 과정과, 최종적으로 해결한 방법을 날 것 그대로 기록한 글이다.
요약 - 문제 원인 및 해결책 총정리
| 1 | Flannel CrashLoop | VXLAN 백엔드가 NAT 환경에서 UDP 터널 불가 | Flannel 백엔드를 vxlan → host-gw로 변경 |
| 2 | Pod들 연쇄 SIGTERM | Flannel 재시작 시 CNI conflist 파일 삭제 → sandbox 변경 | Pod 삭제로 backoff 리셋, containerd 재시작 |
| 3 | Redis CrashLoop | Probe 포트가 6379가 아닌 27017(MongoDB 포트)로 잘못 설정 | yaml에서 세 개의 probe 포트 모두 6379로 수정 |
| 4 | /etc/kubernetes/manifests 없음 | worker 노드에 폴더 미생성 | sudo mkdir -p /etc/kubernetes/manifests |
| 5 | Flannel이 API 서버 연결 못함 (근본 원인) | containerd cgroup 드라이버(cgroupfs)와 kubelet cgroup 드라이버(systemd) 불일치 | containerd SystemdCgroup = true로 설정 후 재시작 |
시작 - 문제 상황
kubectl get pods --all-namespaces
를 실행하면 아래와 같은 상황이 펼쳐졌다.

재시작 횟수가 무려 800회에 달하고 있었다.
일단 인프라 부분은 내 part가 아니었기 때문에 안전하게 버추얼 박스 스냅샷을 찍어두고 시작했다.
진단 과정
1단계 — 어디서부터 시작할까?
CrashLoopBackOff가 왜 일어나는지 파악하기 위해 로그를 봐야 한다.
kube-proxy-cvszp의 cvszp는 만들 때마다 랜덤으로 나오기 때문에 겟 pod 후 각자 이름에 맞게 고쳐야 한다.
# kube-proxy 로그 확인
kubectl logs -n kube-system kube-proxy-cvszp --previous
# 종료 이유 확인 (OOMKilled인지 등)
kubectl describe pod -n kube-system kube-proxy-cvszp | tail -30
kube-proxy 로그에는 별다른 에러가 없었고 정상 시작하다가 죽는 패턴이었다.
그런데 describe에서는 flannel 문제라는 단서가 나왔다.
Normal SandboxChanged kubelet Pod sandbox changed, it will be killed and re-created.
Pod sandbox changed. 이게 반복적으로 찍히고 있었다. Pod sandbox가 바뀌면 해당 Pod의 네트워크 네임스페이스가 날아가고, k8s는 Pod를 강제로 재시작한다. kube-proxy가 죽는 게 아니라 외부에서 죽임을 당하고 있었던 것이다.
그렇다면 sandbox를 바꾸는 건 누구인가? 네트워크 플러그인, 즉 Flannel이다.
flannel 죽음 → sandbox 변경 → kube-proxy 죽음 → 나머지 Pod 연쇄 죽음
2단계 — Flannel 로그 분석
kubectl logs -n kube-flannel kube-flannel-ds-7rbkl --previous
I0316 07:57:22 flannel 시작...
I0316 07:57:23 Running backend...
I0316 07:58:24 shutdownHandler sent cancel signal...
I0316 07:58:24 Exiting cleanly...
딱 1분 만에 flannel이 스스로 종료하고 있었다.
Exit code 0, 즉 에러가 아니라 정상 종료였다. 에러가 아니라 flannel이 뭔가를 감지하고 스스로 나가고 있는 것이다.
3단계 — 근본 원인 1: Flannel VXLAN과 NAT Network의 충돌
우리 VirtualBox 환경은 NAT Network 타입이었다. 복붙을 쉽게 하기 위해 포트포워딩 후, ssh를 통해 cmd로 꺼내 사용했기 때문이다.
기본 flannel 백엔드인 VXLAN은 UDP 8472 포트를 사용해서 노드 간 터널을 만든다.
문제는 NAT 환경에서 UDP 터널이 제대로 동작하지 않는다는 것이다. Flannel이 네트워크 터널을 만들지 못하기 때문에 스스로 "난 할 수 없어"라고 판단하고 exit 0으로 종료하고 있었다.
해결책: VXLAN 백엔드를 host-gw로 변경했다.
host-gw는 UDP 터널 없이 일반 IP 라우팅으로 노드 간 통신을 처리한다.
NAT Network 환경에서도 노드 간 직접 통신이 가능하므로 host-gw가 적합하다.
kubectl edit configmap kube-flannel-cfg -n kube-flannel
# 변경 전
"Backend": {
"Type": "vxlan"
}
# 변경 후
"Backend": {
"Type": "host-gw"
}
kubectl rollout restart daemonset kube-flannel-ds -n kube-flannel
그리고 2분 정도 기다렸다.
kubectl get pods -A
basic@master-yh:~$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
argocd argocd-application-controller-0 1/1 Running 1 (26m ago) 3d2h
argocd argocd-applicationset-controller-7f49c9d867-k59pd 0/1 CrashLoopBackOff 413 (4m42s ago) 3d2h
argocd argocd-dex-server-ff4ccc94b-d6ghx 0/1 CrashLoopBackOff 427 (4m42s ago) 3d2h
argocd argocd-notifications-controller-985b5dc96-ht9vh 1/1 Running 391 (79s ago) 3d2h
argocd argocd-redis-d48646d8f-fvwwh 0/1 CrashLoopBackOff 403 (50s ago) 3d2h
argocd argocd-repo-server-cd44ff98c-nbhrw 1/1 Running 1 (26m ago) 3d2h
argocd argocd-server-55ffdc9ffb-bvlk8 1/1 Running 1 (26m ago) 3d2h
default nginx-test-574bc578fc-l99rp 0/1 Completed 0 4d7h
default nginx-test-574bc578fc-s977m 1/1 Running 460 (9m18s ago) 4d7h
ingress-nginx ingress-nginx-controller-5694c7d7d-4rgpc 1/1 Running 2 (26m ago) 3d3h
kube-flannel kube-flannel-ds-6nzzq 1/1 Running 0 70s
kube-flannel kube-flannel-ds-kzxz2 1/1 Running 1 (71s ago) 74s
kube-flannel kube-flannel-ds-vwt7t 0/1 Completed 1 74s
kube-system coredns-84589754b9-dlxrk 0/1 CrashLoopBackOff 798 (4m14s ago) 3d2h
kube-system coredns-84589754b9-tps52 1/1 Running 1 (26m ago) 3d2h
kube-system etcd-master-yh 1/1 Running 16 (26m ago) 24d
kube-system kube-apiserver-master-yh 1/1 Running 17 (26m ago) 24d
kube-system kube-controller-manager-master-yh 1/1 Running 17 (26m ago) 24d
kube-system kube-proxy-cvszp 1/1 Running 909 (9m10s ago) 3d22h
kube-system kube-proxy-g8jg8 0/1 CrashLoopBackOff 1023 (71s ago) 3d22h
kube-system kube-proxy-tcrq7 1/1 Running 9 (26m ago) 3d22h
kube-system kube-scheduler-master-yh 1/1 Running 17 (26m ago) 24d
local-path-storage local-path-provisioner-6f7d7b87c7-nqzvv 1/1 Running 3 (26m ago) 3d4h
parkit analysis-service-64b7fcff68-2bthx 0/1 CrashLoopBackOff 426 (69s ago) 3d3h
parkit analysis-service-bc4576788-g5xsl 0/1 Init:0/1 0 3d2h
parkit client-service-6ddbbf67cb-lqrtg 0/1 CrashLoopBackOff 437 (4s ago) 3d3h
parkit grafana-7b5959b487-5tzdd 1/1 Running 437 (7m40s ago) 3d4h
parkit kafka-broker-0 1/1 Running 3 (26m ago) 3d4h
parkit kafka-exporter-prometheus-kafka-exporter-7cbdf966f4-dsqwf 0/1 CrashLoopBackOff 821 (49s ago) 3d4h
parkit mongodb-0 0/1 CrashLoopBackOff 752 (60s ago) 3d4h
parkit mongodb-exporter-7665cc8f74-f8hp4 1/1 Running 832 (5m12s ago) 3d4h
parkit prometheus-5d8f787b7d-rs685 0/1 CrashLoopBackOff 439 (2m22s ago) 3d4h
parkit redis-0 0/1 CrashLoopBackOff 828 (90s ago) 3d1h
parkit redis-exporter-5d89845b7d-l9m6r 0/1 CrashLoopBackOff 445 (2m24s ago) 3d4h
parkit report-service-6f89596b45-pbw7l 1/1 Running 462 (5m42s ago) 3d4h
parkit socket-service-7d5fd4fd48-tcflv 0/1 CrashLoopBackOff 844 (2m6s ago) 3d4h
Flannel이 Crash에서 벗어났다!
그러나 flannel은 전보다는 덜하지만 종종 죽었고 다른 pod들은 여전히 crashloopbackoff 상태였다.
4단계 — 또 다른 문제: Pod들이 여전히 SIGTERM으로 죽는다
Flannel을 고쳤음에도 mongodb, redis, argocd-redis 등이 계속 죽었다. 로그를 보면 이런 패턴이었다.
MongoDB: 08:28:07 시작 → 08:28:31 서비스 연결 → 08:30:33 Received SIGTERM → 종료
Redis: 08:17:18 시작 → 08:17:23 Ready → Received SIGTERM → 종료
외부에서 SIGTERM을 보내고 있었다.
describe를 통해 이벤트를 확인했다.
kubectl describe pod -n parkit redis-0 | grep -A10 "Events:"
Warning FailedCreatePodSandBox Failed to create pod sandbox:
plugin type="flannel" failed (add):
failed to load flannel 'subnet.env' file:
open /run/flannel/subnet.env: no such file or directory
Normal SandboxChanged Pod sandbox changed, it will be killed and re-created.
해석... = flannel이 재시작될 때마다 /run/flannel/subnet.env 파일이 잠깐 사라지고, containerd가 CNI 설정을 잃어버리면서 모든 Pod의 네트워크 샌드박스가 변경되고, k8s가 SIGTERM을 보내서 Pod를 강제 재시작하는 악순환이었다.
이는 flannel이 계속 재시작되던 당시의 후폭풍이었다. 재시작 횟수가 너무 많이 쌓여 backoff가 5분씩 걸리고 있었으므로, Pod를 직접 삭제해서 backoff를 리셋했다.
kubectl delete pod -n parkit mongodb-0 redis-0
kubectl delete pod -n argocd argocd-redis-d48646d8f-fvwwh
2분 정도 기다린 결과
basic@master-yh:~$ kubectl get pods -n parkit
NAME READY STATUS RESTARTS AGE
...
mongodb-0 1/1 Running 1 (49s ago) 50s
mongodb-exporter-7665cc8f74-f8hp4 1/1 Running 835 (37s ago) 3d4h
redis-0 0/1 CrashLoopBackOff 2 (9s ago) 49s
...
basic@master-yh:~$ kubectl get pods -n argocd
...
argocd-redis-d48646d8f-kgv84 1/1 Running 0 55s
...
MongoDB와 argocd-redis는 살아났다. 그러나 redis-0는 여전히 문제였다.
5단계 — Redis probe 포트 오설정 발견
redis-0 로그를 보면 정상 시작 후 바로 SIGTERM을 받고 있었다. describe를 통해 probe 설정을 확인했다.
kubectl get statefulset -n parkit redis -o yaml | grep -A3 "tcpSocket"
livenessProbe:
tcpSocket:
port: 27017 # ← 27017는 MongoDB
readinessProbe:
tcpSocket:
port: 27017
startupProbe:
tcpSocket:
port: 27017
Redis 포트는 6379인데, 세 개의 probe 포트가 전부 MongoDB 포트인 27017로 설정되어 있었다.
startupProbe가 failureThreshold 30번 실패하면 k8s가 SIGTERM을 보내서 컨테이너를 재시작한다.
바로 이것 때문에 Redis가 계속 죽고 있었다.
Github ArgoCD 레포에서 직접 redis StatefulSet yaml을 수정했다.
# 세 곳 모두 수정
livenessProbe:
tcpSocket:
port: 6379
readinessProbe:
tcpSocket:
port: 6379
startupProbe:
tcpSocket:
port: 6379
ArgoCD에서 Sync를 누른 후, StatefulSet 변경은 자동으로 Pod를 재생성하지 않으므로 직접 삭제했다.
kubectl delete pod -n parkit redis-0
그러나 Redis는 여전히 죽었다.(?)

6단계 — containerd CNI 설정 파일 문제
worker1의 containerd 로그를 확인했다.
sudo journalctl -u containerd --since "08:59:00" --until "08:59:30"
level=error msg="failed to reload cni configuration after receiving
fs change event(REMOVE \"/etc/cni/net.d/10-flannel.conflist\")"
error="cni config load failed: no network config found in /etc/cni/net.d"
flannel이 재시작될 때마다 install-cni init 컨테이너가 /etc/cni/net.d/10-flannel.conflist 파일을 지웠다가 다시 만든다. 그 순간 containerd가 CNI 설정을 잃어버리고 네트워크 샌드박스가 변경되어 Pod들이 죽는 것이었다.
또한 worker1에서 kubelet이 계속 이런 에러를 내고 있었다.
Unable to read config path: /etc/kubernetes/manifests - path does not exist
worker 노드에는 /etc/kubernetes/manifests 폴더가 없었다. kubelet 설정(staticPodPath)에서 이 경로를 참조하고 있었으므로 폴더를 생성해줬다.
sudo mkdir -p /etc/kubernetes/manifests
그리고 containerd를 재시작했다.
sudo systemctl restart containerd
그래도 flannel은 계속 1~2분 만에 Completed(exit 0)로 죽었다.

7단계 — 진짜 원인 : cgroup 드라이버 불일치
flannel이 계속 Waiting 10m0s for node controller to sync에서 멈춘 채 로그를 더 이상 출력하지 않는 것을 보고 이상함을 느꼈다. API 서버에 연결을 못 하고 있는 것이다.
containerd 기본 설정 파일이 아예 없는 것을 발견했다.
cat /etc/containerd/config.toml
# cat: /etc/containerd/config.toml: No such file or directory
기본 설정을 생성하고 cgroup 드라이버를 확인했다.
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
# containerd 설정 확인
sudo cat /etc/containerd/config.toml | grep -i "SystemdCgroup"
# SystemdCgroup = false ← cgroupfs 사용
# kubelet 설정 확인
sudo cat /var/lib/kubelet/config.yaml | grep -i "cgroup"
# cgroupDriver: systemd ← systemd 사용
발견!
kubelet: cgroupDriver: systemd
containerd: SystemdCgroup = false (cgroupfs)
cgroup 드라이버가 불일치하고 있었다! kubelet은 systemd cgroup을 사용하는데, containerd는 cgroupfs를 사용하고 있었다. 이 불일치 때문에 flannel이 API 서버에 정상적으로 연결하지 못하고 timeout 후 종료하고 있었던 것이다.
해결:
# worker1, worker2 모두 적용
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl restart kubelet
# master에서 flannel Pod 전체 재시작
kubectl delete pod -n kube-flannel --all
kubectl get pods -n kube-flannel -w
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-bsg84 1/1 Running 0 3m8s
kube-flannel-ds-rn6jd 1/1 Running 0 3m8s
kube-flannel-ds-znvh8 1/1 Running 0 3m8s
시간이 지나도 flannel이 죽지 않았다.
8단계 — 최종 확인
kubectl get pods -A | grep -v Running | grep -v Completed
오류는 하나도 출력되지 않았다! 모든 Pod가 정상 상태였다.

핵심 교훈
1. NAT VirtualBox 환경에서 Flannel은 host-gw를 쓰자
NAT, NAT Network 환경에서 VXLAN은 UDP 터널링이 막혀서 동작하지 않는다. host-gw는 일반 IP 라우팅을 사용하므로 같은 네트워크 대역의 VM들 사이에서는 잘 동작한다.
// kube-flannel-cfg ConfigMap
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
2. kubeadm 설치 시 containerd 설정은 반드시 먼저
kubeadm으로 클러스터를 구성할 때 containerd 기본 설정 파일이 없는 경우가 있다. 이 경우 기본값이 cgroupfs인데, Ubuntu 22.04 이후 systemd가 기본 cgroup 드라이버이므로 반드시 일치시켜야 한다.
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
sudo systemctl restart containerd
이걸 클러스터 구성 전에 하지 않으면 나중에 아주 이상한 증상들을 만나게 된다.
3. Exit Code 0은 에러가 아니다
CrashLoopBackOff라고 해서 무조건 애플리케이션 에러가 아니다. Exit Code 0은 정상 종료를 의미한다. 즉, 외부에서 SIGTERM을 받았거나, 스스로 판단하여 종료한 것이다. 이 경우 애플리케이션 로그보다 kubelet 이벤트와 containerd 로그를 확인해야 진짜 원인을 찾을 수 있다.
kubectl describe pod <pod명> | grep -A10 "Events:"
sudo journalctl -u kubelet --since "..." | grep -i "kill\|sandbox\|signal"
sudo journalctl -u containerd --since "..." | grep -i "error\|fail\|cni"
4. CrashLoopBackOff는 연쇄 반응이다
하나의 인프라 컴포넌트(특히 네트워크 플러그인)가 불안정하면 그 위에 올라탄 모든 Pod가 도미노처럼 무너진다. 애플리케이션 Pod의 로그만 볼 게 아니라 kube-system, kube-flannel 네임스페이스의 인프라 컴포넌트부터 먼저 확인해야 한다.
# 인프라 먼저 확인
kubectl get pods -n kube-system
kubectl get pods -n kube-flannel
# 그 다음 애플리케이션
kubectl get pods -n <namespace>
5. probe 설정은 꼼꼼히
yaml을 작성하다 보면 복붙 과정에서 포트 번호를 잘못 설정하는 실수가 생긴다. 특히 probe 포트가 잘못되면 애플리케이션이 정상 동작하더라도 k8s가 계속 재시작시킨다. liveness, readiness, startupProbe 세 개 모두 확인하자.
마치며
이번 트러블슈팅을 통해 쿠버네티스 네트워크 스택이 얼마나 복잡하게 얽혀있는지 체감할 수 있었다.
cgroup 드라이버 불일치
→ containerd가 제대로 동작 안 함
→ flannel이 API 서버 연결 못하고 종료
→ flannel 재시작마다 CNI conflist 파일 삭제
→ containerd가 CNI 잃음
→ Pod sandbox 변경
→ 모든 Pod SIGTERM
→ CrashLoopBackOff 지옥
결국 시작은 containerd 설정 파일 하나였다. 이 작은 설정 하나가 클러스터 전체를 수십 시간 동안 불안정하게 만들었다.
VirtualBox로 쿠버네티스 실습 환경을 구성하는 분들이 이 글을 보고 같은 고통을 겪지 않기를 바란다.
이 글은 PARKIT 프로젝트 개발 중 실제로 겪은 트러블슈팅 경험을 바탕으로 작성되었습니다.