목록으로

Programming Notes

쿠버네티스에서 흔히 저지르는 7가지 실수 (그리고 피하는 법)

쿠버네티스는 강력하면서도 때로는 좌절감을 안겨줄 수 있다는 것은 누구나 아는 사실입니다. 제가 처음 컨테이너 오케스트레이션에 발을 들였을 때, 저지를 수 있는 모든 실수를 다 저질렀습니다. 그 덕에 이렇게 수많은 함정 목록을 만들 수 있었죠. 이번 글에서는 제가 겪었거나 다른...

쿠버네티스는 강력하면서도 때로는 좌절감을 안겨줄 수 있다는 것은 누구나 아는 사실입니다. 제가 처음 컨테이너 오케스트레이션에 발을 들였을 때, 저지를 수 있는 모든 실수를 다 저질렀습니다. 그 덕에 이렇게 수많은 함정 목록을 만들 수 있었죠. 이번 글에서는 제가 겪었거나 다른 사람들이 겪는 것을 본 7가지 주요 문제점들을 살펴보고, 이를 피하는 방법에 대한 팁을 공유하고자 합니다. 쿠버네티스를 막 시작하는 분이든, 이미 프로덕션 클러스터를 관리하는 분이든, 이 글이 여러분의 불필요한 스트레스를 조금이나마 덜어주는 데 도움이 되기를 바랍니다.

1. 리소스 요청 및 제한을 건너뛰는 실수

함정: Pod 스펙에 CPU 및 메모리 요구 사항을 명시하지 않는 것입니다. 쿠버네티스는 이 필드를 필수로 요구하지 않기 때문에 이런 일이 자주 발생하며, 워크로드가 이 필드 없이도 시작 및 실행될 수 있어 초기 설정이나 빠른 배포 주기 동안 쉽게 간과될 수 있습니다.

배경: 쿠버네티스에서 리소스 요청(requests)과 제한(limits)은 효율적인 클러스터 관리에 매우 중요합니다. 리소스 요청은 스케줄러가 각 Pod에 적절한 양의 CPU와 메모리를 예약하도록 보장하여 Pod가 작동하는 데 필요한 리소스를 확보합니다. 리소스 제한은 Pod가 사용할 수 있는 CPU와 메모리 양을 제한하여 단일 Pod가 과도한 리소스를 소비하여 다른 Pod들을 고갈시키는 것을 방지합니다. 리소스 요청과 제한이 설정되지 않으면 다음과 같은 문제가 발생할 수 있습니다:

  1. 리소스 고갈(Resource Starvation): Pod가 충분한 리소스를 얻지 못해 성능 저하나 실패로 이어질 수 있습니다. 이는 쿠버네티스가 이러한 요청을 기반으로 Pod를 스케줄링하기 때문입니다. 요청이 없으면 스케줄러가 너무 많은 Pod를 단일 노드에 배치하여 리소스 경합 및 성능 병목 현상을 초래할 수 있습니다.
  2. 리소스 독점(Resource Hoarding): 반대로, 제한이 없으면 Pod가 할당된 것보다 더 많은 리소스를 소비하여 동일 노드의 다른 Pod 성능과 안정성에 영향을 미칠 수 있습니다. 이로 인해 다른 Pod가 메모리 부족(OOMKilled)으로 인해 퇴거되거나 종료되는 등의 문제가 발생할 수 있습니다.

피하는 방법:

  • 작은 requests 값(예: CPU 100m, 메모리 128Mi)으로 시작하여 앱 동작을 관찰하세요.
  • 실제 사용량을 모니터링하여 값을 조정하세요. HorizontalPodAutoscaler가 지표 기반의 자동 스케일링을 돕습니다.
  • `kubectl top pods` 명령이나 로깅/모니터링 도구를 사용하여 리소스를 과도하게 또는 부족하게 프로비저닝하지 않았는지 확인하세요.

저의 현실 점검: 초기에는 메모리 제한에 대해 전혀 생각하지 않았습니다. 로컬 클러스터에서는 괜찮아 보였죠. 하지만 더 큰 환경에서는 Pod들이 여기저기서 *OOMKilled* 되었습니다. 그때 배웠습니다. 컨테이너에 대한 리소스 요청 및 제한 구성에 대한 자세한 내용은 컨테이너 및 Pod에 메모리 리소스 할당(공식 쿠버네티스 문서의 일부)을 참조하세요.

2. Liveness 및 Readiness 프로브의 중요성 간과

함정: 쿠버네티스가 컨테이너의 상태나 준비 상태를 어떻게 확인해야 하는지 명시적으로 정의하지 않고 컨테이너를 배포하는 것입니다. 쿠버네티스는 컨테이너 내부의 프로세스가 종료되지 않는 한 컨테이너를 "실행 중"으로 간주하기 때문에 이런 일이 발생하기 쉽습니다. 추가적인 신호 없이는 쿠버네티스가 워크로드가 정상적으로 작동한다고 가정합니다. 설령 애플리케이션 내부가 응답하지 않거나, 초기화 중이거나, 멈춰 있더라도 말입니다.

배경:
Liveness, Readiness, Startup 프로브는 쿠버네티스가 컨테이너의 건강 상태와 가용성을 모니터링하는 데 사용하는 메커니즘입니다.

  • Liveness 프로브는 애플리케이션이 여전히 살아있는지 여부를 판단합니다. Liveness 검사가 실패하면 컨테이너는 재시작됩니다.
  • Readiness 프로브는 컨테이너가 트래픽을 처리할 준비가 되었는지 제어합니다. Readiness 프로브가 통과할 때까지 컨테이너는 Service 엔드포인트에서 제외됩니다.
  • Startup 프로브는 긴 시작 시간과 실제 실패를 구별하는 데 도움이 됩니다.

피하는 방법:

  • 간단한 HTTP livenessProbe를 추가하여 헬스 엔드포인트(예: /healthz)를 확인하도록 하세요. 이를 통해 쿠버네티스가 멈춘 컨테이너를 재시작할 수 있습니다.
  • `readinessProbe`를 사용하여 앱이 완전히 준비될 때까지 트래픽이 도달하지 않도록 하세요.
  • 프로브는 간단하게 유지하세요. 지나치게 복잡한 검사는 오탐과 불필요한 재시작을 유발할 수 있습니다.

저의 현실 점검: 한번은 로딩 시간이 오래 걸리는 웹 서비스에 readiness 프로브를 설정하는 것을 잊었습니다. 사용자들이 미리 접속하여 이상한 타임아웃 오류를 겪었고, 저는 몇 시간 동안 머리를 싸매야 했습니다. 3줄짜리 readiness 프로브가 저를 살렸을 겁니다.

컨테이너에 대한 liveness, readiness, startup 프로브 구성에 대한 포괄적인 지침은 Liveness, Readiness 및 Startup 프로브 구성을 참조하세요. (공식 쿠버네티스 문서)

3. "그냥 컨테이너 로그를 보면 돼" (불길한 예감)

함정: kubectl logs 명령으로 검색되는 컨테이너 로그에만 의존하는 것입니다. 이 명령은 빠르고 편리하며, 많은 설정에서 개발 또는 초기 문제 해결 중에 로그에 접근할 수 있기 때문에 이런 일이 자주 발생합니다. 하지만 kubectl logs는 현재 실행 중이거나 최근에 종료된 컨테이너의 로그만 검색하며, 이 로그는 노드의 로컬 디스크에 저장됩니다. 컨테이너가 삭제되거나, 퇴거되거나, 노드가 재시작되는 즉시 로그 파일은 로테이션되거나 영구적으로 손실될 수 있습니다.

피하는 방법:

  • 모든 Pod의 출력을 통합하기 위해 Fluentd 또는 Fluent Bit와 같은 CNCF 도구를 사용하여 로그를 중앙 집중화하세요.
  • 로그, 메트릭, 그리고 (필요하다면) 트레이스에 대한 통합된 시야를 위해 OpenTelemetry를 도입하세요. 이를 통해 인프라 이벤트와 앱 수준 동작 간의 상관관계를 파악할 수 있습니다.
  • Prometheus 메트릭과 함께 로그를 사용하여 애플리케이션 로그와 더불어 클러스터 수준 데이터를 추적하세요. 분산 트레이싱이 필요하다면 Jaeger와 같은 CNCF 프로젝트를 고려해 보세요.

저의 현실 점검: 빠른 재시작으로 Pod 로그를 처음 잃었을 때, 'kubectl logs'만으로는 얼마나 불안정한지 깨달았습니다. 그 이후로는 중요한 단서를 놓치지 않기 위해 모든 클러스터에 적절한 파이프라인을 구축했습니다.

4. 개발 환경과 프로덕션 환경을 똑같이 취급하는 실수

함정: 개발, 스테이징, 프로덕션 환경 전반에 걸쳐 동일한 설정으로 같은 쿠버네티스 매니페스트를 배포하는 것입니다. 이런 일은 팀이 일관성과 재사용을 목표로 할 때 자주 발생하지만, 트래픽 패턴, 리소스 가용성, 스케일링 요구 사항, 접근 제어와 같은 환경별 요소가 크게 다를 수 있다는 점을 간과합니다. 사용자 정의 없이는 한 환경에 최적화된 구성이 다른 환경에서 불안정성, 낮은 성능 또는 보안 결함을 유발할 수 있습니다.

피하는 방법:

  • 환경 오버레이 또는 kustomize를 사용하여 공유 베이스를 유지하면서 각 환경에 맞게 리소스 요청, 복제본 수 또는 구성을 사용자 정의하세요.
  • 환경별 설정을 ConfigMap 및/또는 Secret으로 추출하세요. Sealed Secrets와 같은 특수 도구를 사용하여 기밀 데이터를 관리할 수 있습니다.
  • 프로덕션 환경에서는 확장을 계획하세요. 개발 클러스터는 최소한의 CPU/메모리로도 작동할 수 있지만, 프로덕션 환경은 훨씬 더 많은 리소스가 필요할 수 있습니다.

저의 현실 점검: 한번은 "테스트"를 위해 작은 개발 환경에서 replicaCount를 2에서 10으로 늘렸습니다. 즉시 리소스가 고갈되었고, 그 여파를 수습하는 데 반나절을 보냈습니다. 맙소사.

5. 오래된 리소스 방치

함정: 클러스터에 사용되지 않거나 오래된 리소스(예: Deployment, Service, ConfigMap, PersistentVolumeClaim)를 방치하는 것입니다. 쿠버네티스는 명시적으로 지시하지 않는 한 리소스를 자동으로 제거하지 않으며, 소유권이나 만료를 추적하는 내장 메커니즘이 없기 때문에 이런 일이 자주 발생합니다. 시간이 지남에 따라 이러한 잊혀진 객체들이 축적되어 클러스터 리소스를 소비하고, 클라우드 비용을 증가시키며, 특히 오래된 Service나 LoadBalancer가 계속해서 트래픽을 라우팅할 때 운영상의 혼란을 야기할 수 있습니다.

피하는 방법:

  • 모든 것에 라벨을 붙이세요. 목적이나 소유자 라벨을 붙여 더 이상 필요 없는 리소스를 쉽게 조회할 수 있도록 하세요.
  • 클러스터를 **정기적으로 감사하세요.** kubectl get all -n <namespace>를 실행하여 실제로 실행 중인 모든 것을 확인하고, 유효한 리소스인지 확인하세요.
  • **쿠버네티스의 가비지 컬렉션을 활용하세요.** K8s 문서는 종속 객체를 자동으로 제거하는 방법을 보여줍니다.
  • **정책 자동화를 활용하세요.** Kyverno와 같은 도구는 특정 기간 후에 오래된 리소스를 자동으로 삭제하거나 차단할 수 있으며, 수동으로 모든 정리 단계를 기억할 필요 없이 라이프사이클 정책을 적용할 수 있습니다.

저의 현실 점검: 해커톤 후에 외부 로드 밸런서에 연결된 "test-svc"를 해체하는 것을 잊었습니다. 3주 후, 저는 그 로드 밸런서에 계속 비용을 지불하고 있었다는 것을 깨달았습니다. 이런.

6. 너무 일찍 복잡한 네트워킹에 깊이 빠져드는 실수

함정: 쿠버네티스의 네이티브 네트워킹 기본 요소들(Pod 간 통신, ClusterIP Service, DNS 해석, 기본 Ingress 트래픽 처리 등)을 완전히 이해하기도 전에 서비스 메시, 커스텀 CNI 플러그인, 멀티 클러스터 통신과 같은 고급 네트워킹 솔루션을 도입하는 것입니다. 이런 일은 팀이 트래픽 라우팅, 가시성, mTLS와 같은 기능을 외부 도구를 사용하여 구현하려 할 때 자주 발생합니다. 그 결과, 네트워크 관련 문제 해결이 더 어려워지며, 특히 오버레이가 추가적인 추상화와 실패 지점을 도입할 때 더욱 그렇습니다.

피하는 방법:

  • 작게 시작하세요. Deployment, Service, 그리고 NGINX 기반의 기본적인 인그레스 컨트롤러(예: Ingress-NGINX)로 시작하세요.
  • 클러스터 내에서 트래픽이 어떻게 흐르는지, 서비스 디스커버리가 어떻게 작동하는지, 그리고 DNS가 어떻게 구성되는지 확실히 이해하세요.
  • 실제로 필요할 때만 본격적인 메시 또는 고급 CNI 기능으로 넘어가세요. 복잡한 네트워킹은 오버헤드를 추가합니다.

저의 현실 점검: 한번은 작은 내부 앱에 Istio를 적용했다가, 실제 앱보다 Istio 자체를 디버깅하는 데 더 많은 시간을 보냈습니다. 결국 한 발 물러서서 Istio를 제거했고, 모든 것이 잘 작동했습니다.

7. 보안 및 RBAC을 너무 가볍게 여기는 실수

함정: 컨테이너를 root 사용자로 실행하거나, latest 이미지 태그를 사용하거나, 보안 컨텍스트를 비활성화하거나, cluster-admin과 같이 지나치게 광범위한 RBAC 역할을 할당하는 등 불안정한 구성으로 워크로드를 배포하는 것입니다. 이런 관행은 쿠버네티스가 기본적으로 엄격한 보안 설정을 강제하지 않으며, 플랫폼이 의견을 강요하기보다 유연하도록 설계되었기 때문에 지속됩니다. 명시적인 보안 정책이 없으면, 클러스터는 컨테이너 탈출, 무단 권한 상승, 고정되지 않은 이미지로 인한 의도치 않은 프로덕션 변경과 같은 위험에 노출될 수 있습니다.

피하는 방법:

  • RBAC를 사용하여 쿠버네티스 내에서 역할과 권한을 정의하세요. RBAC는 기본이자 가장 널리 지원되는 인증 메커니즘이지만, 쿠버네티스는 대체 인증자 사용도 허용합니다. 더 고급적이거나 외부 정책이 필요한 경우, OPA Gatekeeper(Rego 기반), Kyverno 또는 CEL이나 Cedar와 같은 정책 언어를 사용하는 사용자 정의 웹훅 솔루션을 고려해 보세요.
  • 이미지를 특정 버전으로 고정하세요(더 이상 :latest는 안 됩니다!). 이는 실제로 배포된 것이 무엇인지 파악하는 데 도움이 됩니다.
  • Pod Security Admission(또는 Kyverno와 같은 다른 솔루션)을 사용하여 비-루트 컨테이너, 읽기 전용 파일 시스템 등을 강제하세요.

저의 현실 점검: 저는 큰 보안 침해를 겪은 적은 없지만, 수많은 경고성 이야기들을 들었습니다. 보안을 강화하지 않으면, 문제가 발생하는 것은 시간 문제입니다.

마지막 생각

쿠버네티스는 훌륭하지만, 염력을 가진 것은 아닙니다. 무엇이 필요한지 알려주지 않으면 마법처럼 올바른 일을 하지 않습니다. 이러한 함정들을 염두에 두면 많은 골칫거리와 시간 낭비를 피할 수 있을 것입니다. 실수는 발생합니다(저를 믿으세요, 저도 많이 저질렀으니까요). 하지만 각각의 실수는 쿠버네티스가 내부적으로 어떻게 작동하는지 더 많이 배울 수 있는 기회입니다. 더 깊이 파고들고 싶다면 공식 문서커뮤니티 Slack이 훌륭한 다음 단계가 될 것입니다. 물론, 여러분의 끔찍한 경험담이나 성공 팁을 자유롭게 공유해 주세요. 결국 우리는 이 클라우드 네이티브 모험을 함께하고 있으니까요.

성공적인 배포를 기원합니다!