13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Kubernetes

  • [Cloud] ArgoCD로 Kubernetes에 Ollama 배포: 6개월 운영 후기 및 최적화 전략

    [Cloud] ArgoCD로 Kubernetes에 Ollama 배포: 6개월 운영 후기 및 최적화 전략

    목차

    [Cloud] ArgoCD로 Kubernetes에 Ollama 배포: 6개월 운영 후기 및 최적화 전략

    ArgoCD Ollama 배포를 처음 붙일 때만 해도, 솔직히 저는 “로컬에서 잘 돌던 걸 굳이 Kubernetes(쿠버네티스, 컨테이너 오케스트레이션 플랫폼)까지 올려야 하나?” 싶었습니다. 그런데 팀이나 홈랩에서 여러 워크로드를 같이 운영하다 보면 얘기가 달라지더라고요. 모델 파일은 크고, 노드는 자꾸 바뀌고, 누가 어떤 설정을 바꿨는지 추적도 필요합니다. 결국 ArgoCD(아르고CD, GitOps 배포 도구)와 Ollama(올라마, 로컬/서버 환경에서 LLM을 실행하는 런타임) 조합으로 정리해 두니 운영 피로도가 확 줄었더라고요.

    이번 글은 제가 홈랩과 테스트 클러스터에서 6개월 정도 굴려보면서 정리한 ArgoCD Ollama 배포 운영 후기입니다. 단순 설치 가이드가 아니라, 어디서 삽질했는지, 어떤 최적화가 체감이 컸는지, 그리고 Kubernetes GitOps 관점에서 무엇을 꼭 잡아야 하는지 중심으로 풀어보겠습니다. 비슷하게 LLM 클라우드나 사내 추론 환경을 작게 시작해 보려는 분들께 꽤 현실적인 기준점이 될 겁니다.

    ArgoCD가 Git 저장소의 선언형 설정을 읽어 Kubernetes 클러스터에 Ollama를 배포하고, 내부 서비스와 스토리지를 연결하는 전체 구조 예시입니다.

    왜 ArgoCD Ollama 배포가 생각보다 중요했는지

    쉽게 말해, Ollama 하나만 띄우는 건 어렵지 않습니다. 문제는 운영이거든요. 처음엔 Docker 하나로 시작했었습니다. 그때는 편했습니다. 근데 모델을 추가하고, 스토리지를 옮기고, 노드를 교체하고, Ingress(인그레스, 외부 트래픽 진입점) 뒤에 붙이고, 다시 재현하려고 하니 슬슬 꼬이기 시작했습니다.

    제가 직접 해보니 진짜 차이는 설치가 아니라 재현성(reproducibility, 같은 상태를 다시 만드는 능력)에서 나왔습니다. Git에 원하는 상태를 남기고, ArgoCD가 그 상태를 계속 맞춰 주니까 “어제는 됐는데 오늘은 왜 안 되지” 같은 상황이 확 줄더라고요. 특히 LLM 워크로드는 모델 파일과 디스크 사용량이 크기 때문에, 사람 손으로 운영하면 금방 흔들립니다.

    • 변경 이력 추적: 누가 리소스 제한을 바꿨는지 Git commit으로 남습니다.
    • 복구 속도: 노드가 바뀌어도 선언형 매니페스트로 다시 맞추기 쉽습니다.
    • 운영 표준화: dev, lab, prod 비슷한 구조로 가져가기 좋습니다.
    • 드리프트 방지: kubectl로 급하게 만진 설정이 오래 남지 않습니다.

    핵심 개념 정리: Kubernetes GitOps와 Ollama를 같이 볼 때

    여기서 중요한 포인트가 있습니다. Ollama는 애플리케이션이고, ArgoCD는 상태 관리자입니다. 이 둘의 역할을 섞어서 생각하면 금방 헷갈립니다. 저도 처음엔 ArgoCD가 모델까지 알아서 관리해 주는 느낌으로 생각했었는데, 실제로는 그렇지 않더라고요.

    1. ArgoCD는 원하는 상태를 맞추는 도구입니다

    Git 저장소에 있는 YAML이 기준입니다. Deployment(디플로이먼트, 파드 배포 정의), Service(서비스, 네트워크 노출), PersistentVolumeClaim(PVC, 영구 스토리지 요청) 같은 리소스를 계속 감시하고 맞춰 줍니다.

    2. Ollama는 모델 실행 환경입니다

    모델 파일을 저장하고, 요청을 받아 추론을 수행합니다. 그래서 CPU/GPU보다도 처음엔 디스크와 네트워크가 더 자주 병목이 되기도 합니다. 특히 모델을 여러 번 다시 내려받는 구조가 되면 운영이 굉장히 피곤해집니다.

    3. 모델 관리와 애플리케이션 관리를 분리해야 덜 꼬입니다

    제가 6개월 운영하면서 얻은 결론은 이겁니다. 애플리케이션 배포와 모델 프리로드(preload, 미리 받아두기)를 같은 단계로 억지로 묶지 않는 게 좋습니다. 처음엔 한 번에 끝내고 싶어서 initContainer(초기화 컨테이너) 쪽으로 몰아넣었는데, 재배포 때마다 모델 다운로드가 걸려서 시간도 오래 걸리고 실패 지점도 늘었습니다.

    구분 역할 운영 팁
    ArgoCD Git 기준 상태 동기화 자동 동기화와 self-heal은 켜되, 삭제 정책은 팀 기준에 맞춰 보수적으로
    Kubernetes 파드 실행, 스토리지, 네트워크 관리 리소스 요청값과 스토리지 클래스부터 먼저 정리
    Ollama LLM 실행과 모델 저장 모델 캐시 경로와 영구 볼륨 전략이 핵심
    GitOps 운영 변경 이력과 재현성 확보 핫픽스 후에는 반드시 Git 원복 반영

    실전 구현: 제가 쓰는 ArgoCD Ollama 배포 기본 구조

    이제 본론입니다. 아래 구조는 제가 홈랩에서 가장 무난하게 굴렸던 방식입니다. 아주 화려하진 않지만, 유지보수는 편했습니다. 처음엔 이게 뭔가 싶었는데, 결국 오래 가는 건 단순한 구조더라고요.

    디렉터리 구조

    platform/
      argocd/
        applications/
          ollama.yaml
      apps/
        ollama/
          namespace.yaml
          pvc.yaml
          deployment.yaml
          service.yaml
          kustomization.yaml

    1. Namespace와 PVC부터 고정합니다

    Ollama는 모델 파일이 남아야 의미가 있습니다. 그래서 저는 거의 항상 PVC를 먼저 잡습니다. 여기서 대충 가면 나중에 재배포할 때 모델을 다시 받느라 시간 다 씁니다.

    apiVersion: v1
    kind: Namespace
    metadata:
      name: ollama
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: ollama-data
      namespace: ollama
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 100Gi

    2. Deployment는 최대한 단순하게 갑니다

    실제로 써보니까 처음부터 옵션을 너무 많이 넣는 것보다, 먼저 떠야 합니다. 그리고 그다음에 리소스 제한과 노드 스케줄링을 붙이는 쪽이 덜 위험했습니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ollama
      namespace: ollama
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: ollama
      template:
        metadata:
          labels:
            app: ollama
        spec:
          containers:
            - name: ollama
              image: ollama/ollama:latest
              ports:
                - containerPort: 11434
              volumeMounts:
                - name: ollama-data
                  mountPath: /root/.ollama
              resources:
                requests:
                  cpu: "2"
                  memory: "8Gi"
                limits:
                  cpu: "4"
                  memory: "16Gi"
          volumes:
            - name: ollama-data
              persistentVolumeClaim:
                claimName: ollama-data
    apiVersion: v1
    kind: Service
    metadata:
      name: ollama
      namespace: ollama
    spec:
      selector:
        app: ollama
      ports:
        - name: http
          port: 11434
          targetPort: 11434

    3. Kustomize와 ArgoCD Application으로 묶습니다

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    namespace: ollama
    resources:
      - namespace.yaml
      - pvc.yaml
      - deployment.yaml
      - service.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: ollama
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://git.example.com/platform.git
        targetRevision: main
        path: apps/ollama
      destination:
        server: https://kubernetes.default.svc
        namespace: ollama
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
    1. Git 저장소에 Ollama 매니페스트를 커밋합니다.
    2. ArgoCD에 Application을 등록합니다.
    3. 첫 Sync 후 파드와 PVC가 정상 생성되는지 확인합니다.
    4. 그다음 모델 다운로드 전략을 별도로 붙입니다.
    ArgoCD Ollama 배포 설정과 동기화 구성을 표현한 이미지

    ArgoCD에서 애플리케이션이 Synced 상태로 보이고, Ollama Deployment와 PVC가 함께 연결된 구성을 확인하는 장면을 설명하는 이미지 자리입니다.

    4. 초기 검증 명령어

    kubectl get pods -n ollama
    kubectl get pvc -n ollama
    kubectl get svc -n ollama
    kubectl logs -n ollama deploy/ollama

    여기까지 오면 기본 배포는 끝입니다. 드디어 됐다! 싶죠. 근데 진짜 운영은 지금부터입니다 ㅎㅎ

    6개월 운영하면서 효과 컸던 최적화 전략

    이 섹션이 사실 핵심입니다. 단순 배포보다 중요한 건 계속 안정적으로 돌리는 법이거든요. 제가 직접 해보니 아래 네 가지가 체감이 제일 컸습니다.

    스토리지와 모델 캐시를 먼저 설계합니다

    Ollama는 모델 파일 크기 특성상 스토리지 전략이 중요합니다. 저는 초반에 스토리지를 임시 볼륨처럼 다뤘다가, 노드 교체 시 모델을 다시 받느라 시간을 꽤 날렸습니다. 그 뒤로는 모델 저장 경로를 PVC에 고정하고, 이미지 재배포와 모델 캐시를 분리했습니다.

    • PVC 고정: 파드가 재생성돼도 모델 캐시는 유지
    • 노드 디스크 여유 확인: 디스크 압박은 CPU 부족보다 더 먼저 터질 때가 많음
    • 백업 기준 마련: 모델 자체보다 설정과 프롬프트 자산 백업 기준 분리

    리소스 요청값은 보수적으로 시작합니다

    처음부터 크게 잡으면 클러스터 전체 밸런스가 깨집니다. 반대로 너무 작게 잡으면 파드가 뜨더라도 응답이 흔들립니다. 저는 초반에 메모리 요청값을 낮게 잡았다가, 다른 워크로드와 겹치는 시간대에 지연이 튀는 걸 봤습니다. 이후엔 실제 사용 패턴을 보고 천천히 올렸습니다.

    모델 프리로드는 배포 단계와 분리합니다

    이거 진짜 편하더라고요. 배포는 배포대로 끝내고, 모델 다운로드는 Job(잡, 일회성 실행 리소스)이나 운영 스크립트로 분리하니까 실패 지점이 줄었습니다. 특히 ArgoCD Ollama 배포를 여러 환경에 복제할 때 차이가 컸습니다.

    kubectl exec -n ollama deploy/ollama -- ollama pull llama3
    kubectl exec -n ollama deploy/ollama -- ollama list

    모델명은 실제 사용 환경에 맞게 바꾸시면 됩니다. 중요한 건 “어디에서 다운로드를 책임질지”를 명확히 하는 겁니다.

    Ingress와 프록시 타임아웃을 반드시 확인합니다

    LLM 요청은 일반 API보다 응답 시간이 길 수 있습니다. 그래서 Ingress나 리버스 프록시 설정이 보수적이면 중간에서 연결이 끊깁니다. 저는 처음에 앱이 느린 줄 알고 한참 봤는데, 실제 원인은 앞단 타임아웃이었습니다. 이런 건 로그를 함께 봐야 보입니다.

    ⚠️ 실제로 겪었던 문제와 해결법

    이 부분은 좀 현실적으로 적어보겠습니다. 문서만 보면 다 쉬워 보이는데, 운영에선 꼭 예상 밖 포인트가 나오더라고요.

    문제 1. 파드는 떴는데 모델이 매번 다시 내려받아졌습니다

    원인: 모델 저장 경로가 영구 볼륨에 제대로 붙지 않았거나, 다른 경로를 보고 있었습니다.

    해결: 컨테이너 내부 경로와 volumeMount를 다시 확인했습니다. 그리고 재배포 후에도 같은 PVC가 붙는지 꼭 확인했습니다.

    문제 2. ArgoCD는 Synced인데 실제 동작은 불안정했습니다

    원인: Git 기준 리소스 상태와 애플리케이션 런타임 상태는 다를 수 있습니다. Synced는 선언형 상태 일치이지, 성능 보장까지 해주진 않거든요.

    해결: readiness/liveness보다 먼저 실제 요청 테스트와 로그 수집을 붙였습니다. ArgoCD 상태만 보고 안심하면 안 됩니다.

    문제 3. 노드 이동 후 성능 체감이 달라졌습니다

    원인: 같은 Kubernetes라도 노드 디스크 성능, 메모리 여유, 다른 워크로드 간섭이 다릅니다.

    해결: nodeSelector(노드 셀렉터, 특정 노드 선택)나 taint/toleration(테인트/톨러레이션, 스케줄링 제어)을 검토했고, 최소한 LLM 워크로드가 너무 자주 이사 다니지 않게 잡았습니다.

    문제 4. 급한 핫픽스가 Git과 어긋났습니다

    원인: 운영 중 kubectl edit로 바로 고친 뒤 Git에 반영하지 않았습니다. 며칠 후 ArgoCD 재동기화에서 다시 원래 값으로 돌아가더라고요. 네, 이거 은근 자주 나옵니다.

    해결: 핫픽스 후 바로 Git PR로 반영하는 습관을 들였습니다. Kubernetes GitOps는 결국 Git이 진실 공급원(single source of truth)이니까요.

    ArgoCD Ollama 배포 트러블슈팅 흐름을 설명하는 이미지

    운영 중 자주 만나는 문제인 스토리지 마운트 오류, Git 드리프트, 프록시 타임아웃을 단계별로 추적하는 트러블슈팅 흐름을 설명하는 이미지 자리입니다.

    검증과 결과: 무엇을 기준으로 성공이라고 봤는가

    저는 “파드가 떴다”를 성공으로 보지 않았습니다. 진짜 중요한 건 재배포 후에도 동일하게 동작하는지, 그리고 운영자가 덜 불안한지였거든요.

    제가 보는 검증 체크리스트

    1. ArgoCD에서 애플리케이션이 지속적으로 Synced/Healthy로 유지되는가
    2. 파드 재생성 후에도 기존 모델 캐시가 유지되는가
    3. 간단한 추론 요청이 내부 네트워크에서 안정적으로 응답하는가
    4. 노드 변경이나 롤링 업데이트 후에도 서비스 재현성이 유지되는가
    5. 운영 변경 사항이 모두 Git commit으로 추적되는가
    kubectl rollout restart deploy/ollama -n ollama
    kubectl get pods -n ollama -w
    kubectl exec -n ollama deploy/ollama -- ollama list

    실제로 써보니까, 위 세 줄만으로도 꽤 많은 걸 확인할 수 있었습니다. 롤링 후 파드가 다시 뜨고, 기존 모델이 그대로 보이면 일단 큰 산은 넘은 겁니다. 여기에 사내 서비스나 실험용 앱에서 실제 API 호출까지 붙여보면 더 좋고요.

    ArgoCD Ollama 배포 운영 결과와 안정성을 보여주는 대시보드 이미지

    재배포 이후에도 Ollama 모델 캐시가 유지되고, ArgoCD와 Kubernetes 상태가 안정적으로 보이는 운영 결과 대시보드 이미지 자리입니다.

    운영 관점에서 느낀 장단점

    장점은 명확합니다. ArgoCD Ollama 배포 구조를 한 번 정리해 두면 환경 복제와 복구가 빨라집니다. 특히 여러 사람이 만지는 환경에서는 “누가 뭘 바꿨는지”가 보이는 것만으로도 가치가 큽니다.

    • 장점: 선언형 관리, 재현성, 운영 표준화, 장애 복구 속도
    • 단점: 스토리지와 네트워크를 모르면 초반 진입 장벽이 있음
    • 주의점: Synced 상태와 서비스 품질은 별개라서 모니터링이 반드시 필요

    반대로 단점도 있습니다. 작은 단일 서버 환경에서는 오히려 Kubernetes가 과할 수 있습니다. 그래서 저는 항상 “정말 GitOps가 필요한 규모인가”를 먼저 봅니다. 혼자 잠깐 실험하는 정도라면 Docker Compose로 시작하는 게 더 낫기도 합니다. 하지만 팀이 붙고, 재현성이 필요하고, LLM 클라우드 실험을 계속 이어갈 생각이라면 이야기가 달라집니다.

    정리와 다음 단계

    정리하자면, 6개월 운영 기준으로 가장 중요했던 건 세 가지였습니다. 스토리지 고정, Git 기준 운영, 배포와 모델 관리를 분리. 이 세 가지만 지켜도 안정감이 꽤 올라갑니다. 저도 처음엔 이것저것 한 번에 자동화하려다가 삽질 좀 했습니다 ㅎㅎ 근데 결국 오래 살아남는 구성은 단순하고, 역할이 분리된 구성이더라고요.

    혹시 지금 ArgoCD로 LLM 워크로드를 올리려는 중이신가요? 그렇다면 먼저 작은 범위로 시작해 보세요. Ollama 하나, PVC 하나, Service 하나부터요. 그리고 동작이 확인되면 그다음에 Ingress, 인증, 모니터링을 붙이시면 됩니다. 이전 글에서 다룬 Kubernetes 스토리지 운영 팁과도 연결되는 부분이고, 다음 글에서는 ArgoCD Ollama 배포 뒤에 인증 프록시와 관측성(observability, 관측 가능성) 붙이는 이야기도 정리해보겠습니다.

    배포 전후 운영 복잡도, 모델 캐시 유지 여부, GitOps 적용 효과를 한눈에 비교하는 요약 인포그래픽 이미지 자리입니다.

    자주 묻는 질문

    Q. Ollama를 Kubernetes에 꼭 올려야 하나요?

    아닙니다. 단일 사용자, 단일 서버라면 더 단순한 방법이 맞을 수 있습니다. 다만 재현성과 팀 협업이 중요해지면 Kubernetes와 GitOps가 힘을 발휘합니다.

    Q. GPU가 없으면 의미가 없나요?

    꼭 그렇진 않습니다. 다만 모델 크기와 응답 기대치에 따라 체감이 다릅니다. 저는 처음 검증은 CPU 환경부터 시작했고, 그 과정에서 오히려 스토리지와 운영 흐름을 먼저 정리할 수 있었습니다.

    Q. 운영 후기 기준으로 가장 먼저 볼 지표는 뭔가요?

    저는 순서가 이렇습니다. 파드 상태, PVC 유지 여부, 실제 요청 성공, 재배포 후 재현성. 화려한 대시보드보다 이 네 가지가 먼저입니다.

  • [K8s] Karpenter 온프레미스 도입의 효율성 검증 및 실전 분석

    [K8s] Karpenter 온프레미스 도입의 효율성 검증 및 실전 분석

    [K8s] Karpenter 온프레미스 도입의 효율성 검증 및 실전 분석

    Karpenter 온프레미스가 얼마나 효율적일까—이 질문은 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션)를 운영하다 보면 자연스럽게 나오더라고요. 노드(Node, 작업이 실제로 돌아가는 서버)까지 더 똑똑하게 자동으로 늘리고 줄일 수 없을까 하는 생각 말입니다. 저도 홈랩과 실무에서 이런 고민을 꽤 했었어요. 처음엔 이게 뭔가 싶었는데, 막상 파고들어 보니 Karpenter는 분명 매력적이면서도, 온프레미스에서는 생각보다 전제 조건이 많더라고요.

    결론부터 말씀드리면, Karpenter 온프레미스는 “기술적으로 이름만 가져와서 붙이는 문제”가 아니라 서버 수명주기(lifecycle), 네트워크, IP 주소 할당, OS 부팅 자동화까지 전부 자동화돼 있어야 진정한 효율이 나오는 구조입니다. 이 글에서는 제가 왜 그렇게 판단했는지, 어떤 식으로 검증했고 어디서 막혔는지, 그리고 어떤 팀에 맞고 어떤 팀에는 안 맞는지 정리해보겠습니다.

    Karpenter 온프레미스 도입 검토를 위한 전체 아키텍처 개요 이미지

    온프레미스 쿠버네티스 클러스터에서 스케줄러, Pending Pod, 노드 프로비저닝 흐름을 한눈에 보여주는 개요 이미지가 들어갈 자리입니다.

    Karpenter란 무엇인가, 쉽게 말해

    쉽게 말해 Karpenter는 스케줄링되지 못한 파드(Pod, 쿠버네티스의 실행 단위)를 보고, 거기에 맞는 노드를 빠르게 만들어 붙이는 데 초점을 둔 도구입니다. 기존 Cluster Autoscaler가 노드 그룹(Node Group) 중심으로 움직였다면, Karpenter는 워크로드 요구사항 중심으로 더 유연하게 판단하는 느낌이 훨씬 강하죠.

    공식 문서 기준으로 많이 다뤄지는 프로바이더(provider, 인프라 연동 구현체)는 AWS, Azure, Alibaba Cloud 쪽입니다. 여기서 중요한 포인트! Karpenter의 핵심 아이디어는 범용적이지만, 실제 노드를 만들어내는 동작은 프로바이더 구현에 크게 의존합니다. 즉, 온프레미스에서 “Karpenter를 쓴다”는 말은 결국 내 환경에 맞는 프로비저닝 백엔드를 어떻게 연결하느냐의 문제거든요.

    Karpenter 온프레미스가 매력적으로 보이는 이유

    솔직히 말해서, 아이디어만 보면 정말 좋습니다. 저도 처음엔 “이거 진짜 편하겠는데?” 싶었거든요.

    • 빈 노드 상시 대기를 줄일 수 있습니다.
    • 워크로드별로 CPU, 메모리, 아키텍처, taint/toleration 조건을 세밀하게 나눌 수 있습니다.
    • 배치 작업(Batch Job)이나 CI 러너(Runner)처럼 들쭉날쭉한 부하에 정말 잘 맞습니다.
    • Karpenter 효율성 관점에서 보면, 과하게 큰 노드를 오래 유지하지 않아도 된다는 기대가 생기죠.

    특히 온프레미스는 클라우드처럼 분 단위 과금은 아니어도, 전력, 랙 공간, 라이선스, 예비 장비 운용비가 은근히 큽니다. 그래서 온프레미스 오토스케일링에 관심이 가는 건 아주 자연스러운 흐름이에요.

    하지만 왜 온프레미스에서는 바로 풀리지 않나

    여기서부터 현실이 시작됩니다. 퍼블릭 클라우드에서는 API 한 번으로 가상머신(VM, 가상 서버)을 만들고, 네트워크도 붙이고, IAM 권한도 맞추고, 부팅 후 조인(join)까지 이어집니다. 반면 온프레미스는 보통 이게 한 덩어리로 묶여 있지 않습니다. 제가 직접 검토해보니, 진짜 어려운 건 Karpenter 자체보다 주변 자동화의 빈틈이더라고요.

    항목 퍼블릭 클라우드 온프레미스
    서버 생성 API로 즉시 생성 가상화 플랫폼 또는 베어메탈 절차 연동 필요
    네트워크 기본 VPC/서브넷 모델 존재 VLAN, IPAM, DHCP, DNS를 따로 맞춰야 함
    부팅/조인 이미지와 userdata로 표준화 PXE, 템플릿, 클라우드이닛 대체 절차 필요
    폐기 종료 API로 간단 디스크 정리, 모니터링 해제, CMDB 반영까지 고려
    확장 속도 대체로 빠름 사내 자동화 성숙도에 따라 편차 큼

    그래서 Karpenter 사용 후기를 물어보면, 저는 항상 이렇게 답합니다. “노드를 만드는 API가 이미 예쁘게 정리된 팀이라면 검토할 만하고, 아니면 Karpenter보다 선행 과제가 더 큽니다.”

    실전 구현: 제가 검증할 때 먼저 했던 순서

    저는 Karpenter를 바로 설치해서 결론 내리지 않았습니다. 먼저 정말 노드 자동 확장이 필요한 패턴인지부터 확인했거든요. 이거 안 하고 시작하면 높은 확률로 삽질하게 됩니다.

    1. Pending Pod가 얼마나 자주 생기는지 확인합니다.
    2. 그 Pending이 CPU/메모리 부족인지, taint/toleration 문제인지 구분합니다.
    3. 노드 추가 시간이 실제 서비스 요구와 맞는지 봅니다.
    4. 온프레미스에서 노드 생성 API가 있는지, 없으면 누가 그 역할을 할지 정합니다.

    1. 병목부터 확인하기

    kubectl get pods -A --field-selector=status.phase=Pending
    kubectl get events -A --sort-by=.lastTimestamp
    kubectl describe pod -n default cpu-burner-0
    kubectl top nodes
    

    이 단계에서 FailedScheduling 이벤트를 꼭 봐야 합니다. 실제로 써보니까 “노드가 부족해서”가 아니라 노드 셀렉터(nodeSelector), 어피니티(affinity), 테인트(taint) 때문에 못 붙는 경우가 꽤 많더라고요.

    2. 의도적으로 스케줄 압박 만들기

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: cpu-burner
      namespace: default
    spec:
      replicas: 6
      selector:
        matchLabels:
          app: cpu-burner
      template:
        metadata:
          labels:
            app: cpu-burner
        spec:
          containers:
            - name: pause
              image: registry.k8s.io/pause:3.9
              resources:
                requests:
                  cpu: "4"
                  memory: "4Gi"
                limits:
                  cpu: "4"
                  memory: "4Gi"
    

    이런 식으로 일부러 Pending 상황을 만들고, 노드가 추가됐을 때 실제로 대기 시간이 얼마나 줄어드는지 봤습니다. 이 단계가 없으면 Karpenter 효율성을 논하기가 정말 어렵거든요.

    Karpenter 온프레미스 환경에서 Pending Pod와 노드 증설 흐름을 보여주는 구성 이미지

    Pending Pod 감지, 스케줄러 판단, 노드 프로비저닝 요청 흐름을 시각화한 구성 다이어그램이 들어갈 자리입니다.

    3. 지원 환경에서 기준선 만들기

    여기서 중요한 포인트가 하나 더 있습니다. Karpenter 동작 모델을 이해하려면, 우선 공식적으로 많이 다뤄지는 환경에서 기준선을 봐야 한다는 거예요. 예를 들어 AWS 계열에서는 NodePool, NodeClass 같은 리소스로 요구사항을 선언합니다. 온프레미스에서는 이 선언형 모델을 흉내 내더라도, 실제 서버 생성과 회수는 별도 자동화가 뒷받침돼야 합니다.

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: general
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
          expireAfter: 720h
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
    

    이 YAML 자체보다 중요한 건, 온프레미스에서는 이 선언을 받아줄 실제 인프라 제어면(control plane)이 있느냐입니다. VMware vSphere, OpenStack, Proxmox, 베어메탈 PXE 자동화, BMC 연동 같은 것들이 이미 정리돼 있어야 이야기가 될 수 있거든요.

    ⚠️ 트러블슈팅: 제가 막혔던 지점들

    여기는 정말 경험상 중요합니다. 문서만 보면 쉬워 보이는데, 실제 운영에서는 대부분 여기서 시간이 날아갑니다.

    1. 노드 생성보다 노드 조인이 더 오래 걸림

    VM이 떠도 kubelet(큐블릿, 노드를 클러스터에 붙이는 에이전트) 조인이 늦으면 의미가 없습니다. 이미지 템플릿, 컨테이너 런타임, CNI(Container Network Interface, 컨테이너 네트워크), 인증서 부트스트랩이 전부 맞아야 하거든요.

    2. IPAM이 병목이 됨

    온프레미스 오토스케일링에서 자주 빠지는 함정입니다. 서버는 생겼는데 IP가 없거나, DNS 등록이 늦거나, 방화벽 정책 반영이 늦으면 결국 Pod는 계속 Pending 상태가 됩니다. 드디어 됐다! 싶었는데 네트워크 쪽에서 막히는 경우, 진짜 많습니다.

    3. 회수(deprovision) 정책이 더 민감함

    클라우드에서는 종료가 비교적 단순하지만, 온프레미스는 로그 수집, 백업, 모니터링 타깃 해제, 자산관리 반영까지 연결될 수 있습니다. 그래서 비용 최적화보다 운영 일관성이 더 우선인 팀도 많아요.

    4. 베어메탈은 더 신중해야 함

    가상머신은 그래도 템플릿 자동화가 익숙한 편인데, 물리 서버는 전원 제어, PXE, 디스크 초기화, 펌웨어 상태까지 섞입니다. 여기서는 Karpenter보다 먼저 서버 프로비저닝 파이프라인이 안정적이어야 해요.

    검증 결과: 그래서 효율적이었나

    제 결론은 꽤 단순합니다. Karpenter 온프레미스는 “이론상 가능”과 “운영상 효율적” 사이의 간격이 생각보다 크다는 거예요.

    • 효율적인 경우: VM 생성 API가 있고, 네트워크 자동화가 있으며, 노드 조인 시간이 짧고, 워크로드 변동성이 큽니다.
    • 비효율적인 경우: 서버 준비 시간이 길고, 수동 승인 절차가 많고, IP/DNS/보안정책 반영이 느립니다.
    • 애매한 경우: 항상 켜둬야 하는 기본 용량(base capacity)이 큰데, 추가 증설 빈도는 낮은 환경입니다.

    실제로 써보니까 Karpenter 자체는 정말 똑똑한데, 온프레미스에선 그 똑똑함이 발휘될 무대가 준비돼 있어야 하더라고요. 무대가 없으면 결국 “자동화된 척하는 수동 시스템”이 되기 쉽습니다.

    Karpenter 온프레미스 검증 결과를 보여주는 대시보드 이미지

    PoC 전후로 Pending Pod 수, 노드 사용률, 증설 후 조인 시간을 비교하는 대시보드 이미지가 들어갈 자리입니다.

    대안과 비교: 굳이 Karpenter여야 하나

    이 질문도 꼭 해봐야 합니다. 저도 처음엔 Karpenter에 시선이 갔는데, 나중엔 오히려 문제 정의를 다시 하는 게 더 중요하다고 느꼈거든요.

    선택지 잘 맞는 상황 한계
    Karpenter 인프라 API와 노드 자동화가 이미 성숙한 환경 주변 자동화 미성숙 시 효과 반감
    Cluster Autoscaler 기존 노드 그룹 운영이 익숙한 환경 세밀한 유연성은 상대적으로 제한
    고정 노드 + 요청/제한 재정비 부하 패턴이 예측 가능한 환경 유휴 자원 증가 가능
    배치 전용 풀 분리 CI, 렌더링, 분석 잡이 분리 가능한 환경 운영 구조가 다소 복잡해짐

    혹시 이런 경험 있으신가요? 노드가 부족한 줄 알았는데, 알고 보니 리소스 요청값(request)이 너무 공격적으로 잡혀 있었던 경우요. 이런 환경이면 Karpenter보다 먼저 requests/limits, affinity, PDB(PodDisruptionBudget, 중단 허용 범위)를 정리하는 쪽이 체감 효과가 훨씬 크더라고요.

    정리: 제가 내린 판단

    Karpenter 온프레미스는 분명 흥미로운 주제입니다. 다만 “쿠버네티스 노드 오토스케일링 도구 하나 더 설치하면 끝” 같은 그림으로 접근하면 거의 실패합니다. 제가 회고해보면, 성공 조건은 세 가지였어요.

    1. 노드 생성 API가 일관돼 있어야 합니다.
    2. 네트워크/IPAM/보안 정책 반영이 자동화돼 있어야 합니다.
    3. 노드 조인 시간이 서비스 요구를 만족해야 합니다.

    이 세 가지가 되면 Karpenter 효율성은 충분히 검토할 가치가 있습니다. 반대로 이 중 둘 이상이 수동이면, Karpenter는 멋진 이름이고 운영은 더 복잡해질 가능성이 크거든요. 그래서 저는 온프레미스에서 Karpenter를 볼 때 항상 도구보다 자동화 성숙도를 먼저 봅니다.

    Karpenter 온프레미스 도입 적합성과 대안을 정리한 요약 인포그래픽

    Karpenter 도입 적합성 체크리스트와 대안 선택 기준을 한 장으로 요약한 인포그래픽이 들어갈 자리입니다.

    FAQ와 다음 단계

    Q1. 온프레미스에서 Karpenter를 아예 못 쓰는 건가요?

    아예 불가능하다는 뜻은 아닙니다. 다만 공식적으로 널리 소개되는 흐름은 주로 클라우드 프로바이더 중심이고, 온프레미스에서는 프로비저닝 연동을 직접 설계해야 하는 비중이 훨씬 커요.

    Q2. 어떤 팀에 추천하나요?

    가상화 API, 템플릿 배포, DHCP/DNS/IPAM, 쿠버네티스 조인이 이미 자동화된 팀이라면 충분히 검토할 만합니다.

    Q3. 어떤 팀에는 비추천인가요?

    노드 한 대 늘릴 때 승인 절차가 여러 단계거나, 보안 정책 반영이 수동이면 비추천입니다. 이 경우엔 고정 노드 전략과 워크로드 정리가 훨씬 더 현실적이거든요.

    이전 글에서 다뤘던 리소스 요청값 튜닝과 스케줄링 정책 정리도 같이 보시면 정말 도움이 됩니다. 다음 글에서는 온프레미스 오토스케일링 관점에서 Karpenter 대신 어떤 선택지가 더 실용적인지, Cluster Autoscaler와 고정 노드 전략을 비교해볼 예정입니다.

    참고할 공식 자료

  • [k8s] Kubeadm vs ClusterAPI: Kubernetes 클러스터 프로비저닝 도구 비교

    [k8s] Kubeadm vs ClusterAPI: Kubernetes 클러스터 프로비저닝 도구 비교

    Kubeadm vs ClusterAPI: Kubernetes 클러스터 프로비저닝 도구 비교

    Kubeadm과 ClusterAPI를 비교할 때, 많은 분들이 같은 지점에서 막혀 있더라고요. “클러스터 하나 빨리 올리면 되는 건가?”, 아니면 “여러 환경에서 반복 가능하게 클러스터 구축 체계를 만들어야 하나?” 같은 고민 말이에요. 저도 홈랩에서 처음엔 kubeadm(쿠버네티스 클러스터 초기화 도구)만으로 시작했었는데, 노드 수가 늘고 재현 가능한 배포가 필요해지니까 Cluster API(CAPI, 쿠버네티스 방식으로 클러스터 자체를 관리하는 프로젝트) 쪽이 갑자기 눈에 들어오더라고요. 처음엔 이게 뭔가 싶었습니다. 컨트롤러도 많고, 프로바이더도 많고, YAML도 길고요. 근데 구조를 한 번 이해하고 나니 왜 운영팀들이 이 조합을 진지하게 보는지 감이 왔습니다.

    이 글에서는 단순히 기능 나열만 하지 않고, Kubeadm vs ClusterAPI를 실제 운영 관점에서 풀어보겠습니다. 쉽게 말해, kubeadm은 클러스터를 시작하게 해주는 공구에 가깝고, ClusterAPI는 클러스터 생애주기 자체를 선언형으로 다루는 운영 프레임워크에 가깝습니다. 여기서 중요한 포인트! 둘은 경쟁 관계이면서도, 실전에서는 같이 엮이는 경우가 꽤 많습니다.

    Kubeadm ClusterAPI 비교를 보여주는 쿠버네티스 클러스터 프로비저닝 아키텍처 이미지

    kubeadm은 개별 클러스터 부트스트랩 흐름에, Cluster API는 관리 클러스터에서 워크로드 클러스터를 선언형으로 제어하는 흐름에 초점이 있습니다.

    Kubernetes 클러스터 프로비저닝, 뭐가 다른가요?

    쉽게 말해 kubeadm은 노드에 들어가서 클러스터를 만드는 도구입니다. 컨트롤 플레인(클러스터 제어 영역) 초기화, 조인 토큰 생성, 인증서 배포 같은 부트스트랩 작업을 잘 해주죠. 반면 Cluster API는 클러스터를 쿠버네티스 리소스처럼 다루는 방식입니다. 즉, “이런 모양의 클러스터를 만들어줘”라고 선언하면 컨트롤러가 그 상태를 맞추려고 움직입니다.

    제가 직접 해보니 kubeadm은 구조가 비교적 단순해서 학습 진입 장벽이 낮았습니다. 특히 베어메탈(가상화 없이 물리 서버 직접 운영)이나 홈랩처럼 손으로 만질 수 있는 환경에서는 진짜 빠릅니다. 반대로 Cluster API는 관리 클러스터(다른 클러스터를 제어하는 클러스터)라는 개념부터 잡아야 해서 초반 허들이 있습니다. 대신 익숙해지면 반복 배포, 업그레이드, 스케일링, 클러스터 폐기까지 흐름이 꽤 깔끔해집니다.

    • kubeadm: 단일 클러스터 프로비저닝에 강함
    • Cluster API: 여러 클러스터의 선언형 운영과 생애주기 관리에 강함
    • CAPI + kubeadm: Cluster API가 kubeadm 기반 부트스트랩을 활용하는 조합이 현실

    Kubeadm vs ClusterAPI 핵심 구조 비교

    비교 항목 kubeadm Cluster API
    주요 목적 쿠버네티스 클러스터 초기 구성 클러스터 생성, 변경, 업그레이드, 삭제까지 생애주기 관리
    운영 방식 명령형(명령 실행 중심) 선언형(원하는 상태 기술)
    주요 실행 위치 대상 노드 관리 클러스터의 컨트롤러
    멀티 클러스터 운영 수동 설계 필요 기본 개념 자체가 멀티 클러스터 친화적
    인프라 연동 직접 구성 필요 Infrastructure Provider(인프라 프로바이더)로 연동
    업그레이드 자동화 운영 스크립트나 절차 설계 필요 버전 선언 후 컨트롤러 기반 롤링 변경 가능
    초기 난이도 상대적으로 낮음 상대적으로 높음

    여기서 중요한 포인트! Cluster API가 kubeadm을 대체하는 느낌으로 보일 수 있는데, 실제로는 Cluster API 안에서 kubeadm bootstrap provider를 사용하는 사례가 많습니다. 즉, kubeadm은 여전히 중요한 구성요소고, Cluster API는 그걸 더 큰 자동화 체계 안에 넣는 느낌이라고 보시면 이해가 편합니다.

    언제 kubeadm이 더 잘 맞을까요?

    • 처음 쿠버네티스 구조를 익히는 단계일 때
    • 노드 수가 적고 클러스터 수가 많지 않을 때
    • 베어메탈이나 제한된 홈랩 환경에서 빠르게 검증할 때
    • 구성 과정을 손으로 제어해야 안심되는 팀 문화일 때

    언제 Cluster API가 빛날까요?

    • 동일한 형태의 클러스터를 반복 생성해야 할 때
    • 개발, 스테이징, 운영 환경을 비슷한 클러스터 구축 템플릿으로 관리할 때
    • 클러스터 업그레이드와 노드 교체를 체계화하고 싶을 때
    • GitOps(Git 기반 선언형 운영)와 잘 엮고 싶을 때

    실전 구현 1: kubeadm으로 클러스터 올리기

    먼저 kubeadm 흐름부터 보겠습니다. 실제로 써보니까 kubeadm은 클러스터 내부 동작을 이해하는 데 정말 좋았습니다. 인증서, kubelet(노드 에이전트), CNI(컨테이너 네트워크 플러그인) 같은 개념이 어디서 필요한지 몸으로 익히게 되거든요. 다만 그만큼 수동 단계도 많습니다. 삽질 좀 했습니다 ㅎㅎ

    1. 런타임(container runtime)과 kubelet, kubeadm, kubectl 설치
    2. 컨트롤 플레인 노드에서 초기화
    3. CNI 설치
    4. 워커 노드 조인
    5. 상태 검증
    sudo kubeadm init \
      --pod-network-cidr=192.168.0.0/16
    
    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    
    kubectl get nodes

    초기화가 끝나면 바로 Ready가 안 뜰 수 있습니다. 여기서 당황 많이 하시더라고요. 대부분은 아직 CNI가 없어서 그렇습니다.

    kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/master/manifests/calico.yaml
    kubectl get pods -A

    워커 노드 조인은 `kubeadm token create –print-join-command`로 받은 명령을 그대로 실행하면 됩니다.

    kubeadm token create --print-join-command

    이 방식의 장점은 명확합니다. 어디서 뭐가 실패하는지 눈에 보입니다. 반대로 단점도 명확해요. 같은 클러스터를 세 번, 네 번 반복해서 만들다 보면 사람이 실수할 지점이 계속 생깁니다. 바로 이 지점에서 “클러스터 구축 도구를 더 선언형으로 가져가야 하나?”라는 생각이 들기 시작하더라고요.

    실전 구현 2: Cluster API로 선언형 클러스터 만들기

    이제 Cluster API 쪽입니다. 처음엔 관리 클러스터가 또 필요하다고 해서 부담스러웠는데, 실제 구조를 이해하고 나니 왜 필요한지 납득됐습니다. Cluster API는 몇 가지 핵심 컴포넌트로 나뉩니다.

    • Core Provider: Cluster API 핵심 리소스와 컨트롤러
    • Bootstrap Provider: 노드 초기화 데이터 생성(kubeadm 활용)
    • Control Plane Provider: 컨트롤 플레인 구성 관리
    • Infrastructure Provider: 실제 VM, 인스턴스, 머신, 네트워크 리소스 생성

    기본 흐름은 이렇습니다. 관리 클러스터에 Cluster API 컴포넌트를 설치하고, 그 위에 워크로드 클러스터(실제 애플리케이션이 올라갈 대상 클러스터)의 정의를 YAML로 적용합니다.

    clusterctl init --infrastructure <provider-name>

    프로바이더 이름은 사용하는 환경에 따라 달라집니다. 예를 들어 퍼블릭 클라우드, 프라이빗 클라우드, 베어메탈 쪽에서 선택지가 갈리죠. 여기서는 원리를 이해하는 데 집중하겠습니다.

    Cluster API 관리 클러스터와 워크로드 클러스터 구조를 설명하는 Kubeadm ClusterAPI 비교 이미지

    관리 클러스터에서 컨트롤러가 동작하고, 선언된 리소스를 바탕으로 워크로드 클러스터의 머신과 컨트롤 플레인을 조립하는 구조입니다.

    apiVersion: cluster.x-k8s.io/v1beta1
    kind: Cluster
    metadata:
      name: demo-cluster
    spec:
      controlPlaneRef:
        apiVersion: controlplane.cluster.x-k8s.io/v1beta1
        kind: KubeadmControlPlane
        name: demo-control-plane
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
        kind: GenericInfrastructureCluster
        name: demo-infra
    ---
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    metadata:
      name: demo-control-plane
    spec:
      replicas: 1
      version: v1.29.0
      kubeadmConfigSpec:
        clusterConfiguration: {}
        initConfiguration: {}
        joinConfiguration: {}
    ---
    apiVersion: cluster.x-k8s.io/v1beta1
    kind: MachineDeployment
    metadata:
      name: demo-md-0
    spec:
      clusterName: demo-cluster
      replicas: 2
      selector:
        matchLabels: {}
      template:
        spec:
          version: v1.29.0
          clusterName: demo-cluster

    위 YAML은 Cluster API 구조 예시입니다. 실제 배포에서는 인프라 프로바이더 리소스가 더 들어가고, 환경 변수나 자격 증명도 필요합니다. 중요한 건 클러스터를 명령으로 만드는 게 아니라 상태로 선언한다는 점입니다. 이게 Cluster API의 본질이에요.

    kubectl apply -f cluster.yaml
    kubectl get cluster
    kubectl get machinedeployments
    kubectl get machines

    제가 직접 해보니 여기서 재미있는 포인트가 있었습니다. kubeadm은 한 번 만들고 나면 이후 운영 자동화는 별도로 설계해야 하는데, Cluster API는 처음부터 “운영까지 생각한 구조”라는 느낌이 강합니다. 대신 디버깅은 더 추상적입니다. 노드 한 대에서 끝나는 문제가 아니라 컨트롤러, 프로바이더, 템플릿이 다 얽혀 있거든요.

    주의사항과 트러블슈팅: 실제로 많이 막히는 지점

    ⚠️ 여기 진짜 중요합니다. Kubeadm ClusterAPI 비교에서 많은 글이 장점만 말하는데, 실전에서는 막히는 지점이 선택 기준이 되더라고요.

    1. kubeadm은 CNI 전까지 정상처럼 보여도 정상 아닐 수 있습니다

    `kubeadm init`이 끝났다고 클러스터가 다 살아난 건 아닙니다. `kubectl get nodes`에서 `NotReady`가 보이면 CNI부터 의심하세요. 저도 처음엔 인증서 문제인 줄 알고 엉뚱한 로그만 한참 봤었습니다.

    kubectl describe node
    kubectl get pods -A
    journalctl -u kubelet

    2. Cluster API는 실패 지점이 더 분산됩니다

    예를 들어 머신이 안 생기면 인프라 프로바이더 문제인지, bootstrap 데이터 생성 문제인지, control plane reconciliation 문제인지 나눠서 봐야 합니다.

    kubectl get pods -A
    kubectl get clusters,machines,machinedeployments,kubeadmcontrolplanes
    kubectl describe machine <machine-name>
    kubectl logs -n capi-system deployment/capi-controller-manager

    혹시 이런 경험 있으신가요? 리소스는 분명히 생성됐는데 상태가 계속 `Provisioning`에서 안 넘어가는 경우요. 이럴 땐 대부분 인프라 쪽 자격 증명, 이미지 템플릿, 네트워크 전제조건이 빠져 있는 경우가 많았습니다.

    3. kubeadm은 단순한 대신 표준화가 흔들리기 쉽습니다

    운영자가 둘 이상만 돼도 설치 옵션, OS 준비 상태, 네트워크 플러그인 선택이 조금씩 달라집니다. 결국 문서화와 스크립트화가 필수입니다. 즉, kubeadm이 간단하다고 해서 운영 체계까지 단순한 건 아니더라고요.

    4. Cluster API는 관리 클러스터 자체의 안정성이 중요합니다

    관리 클러스터가 흔들리면 워크로드 클러스터 변경 작업도 영향받습니다. 그래서 초기에 “관리 클러스터를 어디에 둘 것인가”를 꽤 신중하게 봐야 합니다. 홈랩에서는 한 대에 몰아넣으면 편하긴 한데, 장애 분리 측면에서는 아쉬움이 남습니다.

    검증과 결과 확인: 뭐가 더 운영 친화적인가

    검증은 단순히 `kubectl get nodes`만 보면 부족합니다. 생성, 변경, 교체, 삭제 흐름까지 봐야 합니다. 이 부분이 Kubernetes 클러스터 프로비저닝 도구를 비교할 때 핵심이거든요.

    1. 클러스터 최초 생성 시간과 절차 복잡도 확인
    2. 워커 노드 확장 방식 확인
    3. 버전 변경 시 운영 개입량 확인
    4. 장애 시 복구 절차 문서화 가능성 확인
    kubectl get nodes -o wide
    kubectl get cluster
    kubectl get machines
    kubectl get events --sort-by=.lastTimestamp
    Kubeadm ClusterAPI 비교 결과를 검증하는 쿠버네티스 상태 확인 이미지

    노드 Ready 상태, Machine 리소스 변화, 이벤트 흐름을 통해 선언한 상태와 실제 상태가 맞아가는지 확인하는 장면을 상상하시면 됩니다.

    실제로 써보니까 결과는 꽤 분명했습니다. 하나의 클러스터를 깊게 이해하고 빠르게 구축하려면 kubeadm이 좋았고, 반복 가능한 다수의 클러스터를 체계적으로 관리하려면 Cluster API가 훨씬 운영 친화적이었습니다. 특히 업그레이드나 머신 교체 시나리오를 생각하면 차이가 더 벌어집니다.

    상황별 선택 가이드: 클러스터 구축 도구 어떤 게 맞을까

    상황 추천 이유
    홈랩에서 쿠버네티스 구조 학습 kubeadm 구성 요소를 직접 체감하기 좋음
    단일 운영 클러스터를 신중하게 수동 관리 kubeadm 절차가 명확하고 제어감이 높음
    여러 환경에 동일한 클러스터 배포 Cluster API 선언형 템플릿 재사용이 쉬움
    업그레이드/교체 자동화를 운영 정책으로 추진 Cluster API 생애주기 관리 철학에 잘 맞음
    베어메탈 자동화까지 포함한 확장 설계 환경에 따라 다름 인프라 프로바이더 성숙도와 운영 역량이 중요
    • 빠른 시작과 학습이 목표면 kubeadm
    • 반복성과 선언형 운영이 목표면 Cluster API
    • 둘 중 하나만 고집할 필요는 없음: kubeadm으로 원리 이해 후 CAPI로 확장하는 경로가 현실적
    Kubeadm ClusterAPI 비교 선택 기준을 정리한 요약 인포그래픽

    팀 규모, 클러스터 수, 자동화 요구사항, 관리 방식에 따라 어떤 도구가 더 어울리는지 한눈에 정리한 비교 이미지입니다.

    자주 묻는 질문

    Q1. Cluster API가 있으면 kubeadm은 이제 안 써도 되나요?

    그렇진 않습니다. 오히려 Cluster API 내부에서 kubeadm 기반 부트스트랩을 활용하는 경우가 흔합니다. kubeadm은 여전히 중요한 기반 기술입니다.

    Q2. 소규모 팀도 Cluster API를 바로 도입해야 할까요?

    반드시 그렇진 않습니다. 클러스터 수가 적고 운영자가 구조를 아직 익히는 단계라면 kubeadm이 더 현실적일 수 있습니다. 자동화 비용보다 복잡도 비용이 더 클 수 있거든요.

    Q3. GitOps와 더 잘 맞는 쪽은 무엇인가요?

    Cluster API가 더 자연스럽습니다. 클러스터 정의 자체를 리소스로 관리하니까 Git 저장소 기반 운영 모델과 궁합이 좋습니다.

    마무리: 현실적인 접근

    정리해보면 이렇습니다. Kubeadm vs ClusterAPI 비교는 단순한 우열 비교가 아니라, 운영 성숙도와 목표가 다른 두 접근입니다. 제가 직접 해보니 kubeadm은 쿠버네티스를 제대로 이해하게 해주는 좋은 선생님이었고, Cluster API는 그 다음 단계에서 운영 자동화를 체계로 바꿔주는 도구였습니다. 둘 중 하나가 무조건 정답은 아니었습니다.

    개인적으로는 이런 순서를 추천드립니다. 처음엔 kubeadm으로 컨트롤 플레인 초기화, CNI 연결, 노드 조인, 인증서 구조를 손으로 한 번 겪어보세요. 그다음 반복 배포가 필요해지는 시점에 Cluster API를 붙이면 훨씬 덜 헷갈립니다. 저도 처음엔 YAML만 보고 멍했었는데, 기반 개념을 알고 나니까 드디어 됐다! 싶은 순간이 오더라고요.

    다음 글에서는 kubeadm으로 만든 클러스터를 운영 표준화하는 방법이나, Cluster API와 GitOps를 연결하는 흐름을 따로 다뤄보겠습니다. 이전 글에서 다룬 네트워크 플러그인 비교나 Ingress(외부 트래픽 진입점) 구성 글과 함께 보시면 더 이해가 잘 되실 겁니다.

    처음엔 수동 구축으로 원리를 익히고, 이후 선언형 운영으로 넘어가는 학습 및 운영 로드맵을 보여주는 마무리 이미지입니다.

  • [k8s] Karpenter 오토스케일링 문제 해결: 노드 프로비저닝 실패 디버깅 가이드

    [k8s] Karpenter 오토스케일링 문제 해결: 노드 프로비저닝 실패 디버깅 가이드

    [Kubernetes] Karpenter 문제 해결: 노드 프로비저닝 실패 디버깅 가이드

    Karpenter 문제 해결 때문에 밤에 이벤트 로그 붙잡고 계신 적, 한 번쯤 있으시죠? 저도 처음엔 “오토스케일링은 붙여놓으면 알아서 되겠지” 했다가, 정작 트래픽이 몰리는 순간 Pod(파드, 쿠버네티스의 최소 배포 단위)는 Pending(펜딩, 스케줄 대기)인데 노드는 안 뜨고, Karpenter 로그는 뭔가 말은 많은데 핵심은 안 보이는 상황을 여러 번 겪었습니다. 특히 Kubernetes 노드 프로비저닝이 실패하면 서비스 지연이 바로 드러나기 때문에, 이 구간은 감으로 보면 안 되더라고요.

    이번 글은 AWS 환경에서 Karpenter를 운영할 때 자주 만나는 오토스케일링 실패 패턴을 기준으로 정리했습니다. 제가 홈랩과 실무 환경에서 직접 부딪히며 정리한 흐름이라, 문서만 읽었을 때보다 훨씬 빠르게 원인을 좁히는 데 도움이 되실 겁니다. 핵심은 하나입니다. Karpenter 디버깅은 “Pending Pod → 스케줄링 조건 → Karpenter 로그 → 클라우드 리소스” 순서로 봐야 한다는 점입니다.

    Karpenter 문제 해결을 위한 오토스케일링 아키텍처 개요 이미지

    Pending Pod, Karpenter controller, AWS 인프라 리소스가 어떻게 연결되는지 보여주는 전체 흐름도입니다.

    Karpenter 문제 해결, 왜 이렇게 헷갈릴까요?

    쉽게 말해 Karpenter는 “Pod가 요구하는 조건에 맞춰 새 노드를 빠르게 계산하고 띄워주는 컨트롤러”입니다. 그런데 실제로는 중간에 확인할 레이어가 꽤 많습니다. Kubernetes scheduler(스케줄러), Karpenter controller(컨트롤러), IAM(권한 체계), subnet/security group(서브넷/보안 그룹), instance type(인스턴스 타입) 조건, 그리고 quota(할당량)까지 다 연결돼 있거든요.

    저도 처음엔 Karpenter 로그만 뒤졌습니다. 근데 실제로 써보니까 문제의 출발점은 의외로 Pod 스펙인 경우가 많더라고요. 예를 들어 CPU 요청량이 과하게 잡혀 있거나, 특정 아키텍처만 허용했거나, taint/toleration(테인트/톨러레이션) 조합이 안 맞으면 Karpenter가 아무리 열심히 계산해도 답이 없습니다.

    • Pod 조건 문제: requests/limits, nodeSelector, affinity, toleration 충돌
    • Karpenter 설정 문제: NodePool(노드풀) 또는 기존 Provisioner(프로비저너) 제약이 너무 빡빡함
    • AWS 리소스 문제: IAM 누락, 태그 미설정, 서브넷 발견 실패
    • 용량 문제: 가용 영역 용량 부족, 인스턴스 타입 제한 과다, 계정 할당량 부족

    여기서 중요한 포인트! Karpenter 문제 해결은 “왜 노드가 안 떴지?”가 아니라 “왜 이 Pod를 만족하는 노드를 계산하지 못했지?”로 질문을 바꾸면 훨씬 빨라집니다.

    Karpenter 디버깅의 기본 원리

    제가 실제로 보는 순서는 거의 고정입니다. 삽질 좀 했습니다 ㅎㅎ 결국 순서를 정해두는 게 제일 낫더라고요.

    1. Pending 상태의 Pod를 확인합니다.
    2. 해당 Pod의 이벤트와 스케줄링 실패 메시지를 읽습니다.
    3. Karpenter controller 로그에서 같은 시점의 에러를 확인합니다.
    4. NodePool/EC2NodeClass 또는 기존 Provisioner/AWSNodeTemplate 조건을 검토합니다.
    5. AWS 측 리소스 발견, 권한, 용량 문제를 점검합니다.

    이 흐름을 안 지키면 로그는 잔뜩 보는데 정작 원인을 놓치기 쉽습니다. 특히 이벤트 메시지 한 줄이 전체 방향을 정해주는 경우가 많아요.

    Karpenter 디버깅 1단계: Pending Pod부터 확인하기

    가장 먼저 해야 할 일은 “안 뜨는 노드”가 아니라 “대기 중인 Pod”를 보는 겁니다. 아래 명령어부터 시작해 보세요.

    kubectl get pods -A --field-selector=status.phase=Pending
    kubectl describe pod -n <namespace> <pod-name>
    kubectl get events -A --sort-by=.lastTimestamp

    여기서 제가 제일 먼저 보는 건 다음 세 가지입니다.

    • Insufficient cpu/memory: 리소스 요청량이 현재/예상 노드와 맞지 않는 경우
    • node affinity mismatch: nodeSelector 또는 affinity 조건이 과도한 경우
    • untolerated taint: 노드 taint를 Pod가 허용하지 않는 경우

    예를 들어 이런 Pod 스펙은 흔히 문제를 만듭니다.

    apiVersion: v1
    kind: Pod
    metadata:
      name: inflate
    spec:
      nodeSelector:
        kubernetes.io/arch: amd64
      containers:
        - name: pause
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: "2"
              memory: "2Gi"

    이 자체는 틀린 YAML은 아닙니다. 다만 NodePool이 arm64만 허용하거나, 너무 작은 인스턴스 타입만 열어놨다면 Karpenter는 새 노드를 못 만듭니다. 결국 Kubernetes 노드 프로비저닝 실패처럼 보이지만, 시작점은 Pod 조건이죠.

    Karpenter 디버깅 2단계: 컨트롤러 로그에서 에러 패턴 찾기

    그다음은 Karpenter 로그입니다. 저는 보통 아래처럼 확인합니다.

    kubectl logs -n karpenter deploy/karpenter --since=30m
    kubectl logs -n karpenter deploy/karpenter --since=30m | grep -i "error"
    kubectl logs -n karpenter deploy/karpenter --since=30m | grep -i "provision"

    로그에서 자주 보는 키워드는 대략 이렇습니다.

    로그/증상 의미 우선 확인할 것
    no instance type met the scheduling requirements 조건에 맞는 인스턴스 타입이 없음 NodePool requirements, Pod requests
    insufficient capacity 가용 영역 또는 타입의 실시간 용량 부족 타입 범위 확대, AZ 분산
    access denied / unauthorized IAM 권한 문제 controller role, instance profile
    subnet/security group not found AWS 리소스 발견 실패 태그, selector 조건

    처음엔 이게 뭔가 싶었는데, 로그를 “읽는” 게 아니라 “분류”하기 시작하니까 속도가 확 달라지더라고요. 특히 access denied 계열은 쿠버네티스 문제가 아니라 거의 AWS 권한 문제였습니다.

    Karpenter 디버깅 과정에서 컨트롤러 로그와 NodePool을 점검하는 이미지

    Karpenter 컨트롤러 로그, NodePool 조건, EC2 관련 리소스를 함께 점검하는 디버깅 흐름 예시입니다.

    실전 구현: NodePool/Provisioner 조건 점검하기

    운영 중인 Karpenter 버전에 따라 리소스 이름이 다를 수 있습니다. 최근 계열은 NodePool/EC2NodeClass 조합을 많이 쓰고, 예전 문서에서는 Provisioner/AWSNodeTemplate 예제가 남아 있습니다. 그래서 제 습관은 “내 클러스터에 실제로 어떤 CRD가 있는지 먼저 보는 것”입니다.

    kubectl api-resources | grep -i karpenter
    kubectl get nodepools
    kubectl get ec2nodeclasses
    kubectl get provisioners

    예를 들어 NodePool이 너무 제한적으로 잡혀 있으면 문제가 생깁니다.

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: default
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: kubernetes.io/os
              operator: In
              values: ["linux"]

    여기서 흔한 실수는 이런 겁니다.

    • 인스턴스 패밀리 조건을 너무 좁게 설정
    • 특정 가용 영역만 허용
    • spot(스팟)만 허용했는데 해당 시점에 용량이 부족
    • daemonset(데몬셋) 오버헤드를 고려하지 않고 너무 작은 타입만 허용

    제가 직접 해보니 조건을 처음부터 정교하게 만드는 것보다, 일단 넉넉하게 열고 정상 프로비저닝을 확인한 뒤 점진적으로 좁히는 방식이 훨씬 덜 고생합니다.

    권장 점검 명령어

    kubectl describe nodepool <nodepool-name>
    kubectl get ec2nodeclass <class-name> -o yaml
    kubectl get provisioner <name> -o yaml

    설정에서 확인할 항목은 다음과 같습니다.

    1. 아키텍처 제약이 Pod와 맞는지
    2. 용량 타입(on-demand/spot) 제약이 과하지 않은지
    3. 서브넷/보안 그룹 선택 조건이 실제 AWS 태그와 일치하는지
    4. 노드 만료, 통합(consolidation) 정책이 과도하게 공격적이지 않은지

    ⚠️ 오토스케일링 실패를 만드는 대표 원인 5가지

    이 섹션은 제가 실제로 많이 봤던 순서대로 적겠습니다. Karpenter 디버깅에서 제일 시간을 많이 잡아먹는 부분이기도 합니다.

    1. IAM(권한 체계) 누락

    컨트롤러가 EC2 인스턴스를 생성하거나 조회할 권한이 없으면 노드가 당연히 안 뜹니다. 로그에 access denied, unauthorized 류 메시지가 보이면 거의 이쪽입니다. EKS에서 IRSA(IAM Roles for Service Accounts, 서비스어카운트용 IAM 역할)를 쓰는 경우, role annotation과 정책 연결 상태를 다시 확인해 보세요.

    2. Subnet/Security Group 태그 문제

    Karpenter는 보통 태그 기반으로 서브넷과 보안 그룹을 찾습니다. 근데 태그가 빠져 있거나 selector 조건과 안 맞으면 리소스를 발견하지 못합니다. 저는 이걸 네트워크 문제로 착각해서 한참 헤맸었는데, 사실은 태그 오타 하나였던 적도 있습니다.

    3. 인스턴스 타입 제한 과다

    예를 들어 특정 세대, 특정 패밀리, 특정 크기만 허용해 놓으면 실시간 수용 용량이 없을 때 바로 막힙니다. 운영에서는 후보군을 넓혀야 합니다. 특히 spot만 쓰는 환경은 더 그렇습니다.

    4. Pod 요청량 과다 또는 DaemonSet 오버헤드 미반영

    실제로 써보니까 작은 워크로드 하나만 보지 말고, CNI(컨테이너 네트워크 인터페이스), 로그 에이전트, 모니터링 에이전트 같은 데몬셋 자원까지 같이 봐야 하더라고요. 남는 것 같아도 실제 스케줄 가능 용량은 더 작습니다.

    5. AWS 계정 할당량 또는 AZ 용량 이슈

    설정이 멀쩡해도 특정 가용 영역에서 타입 수급이 안 될 수 있습니다. 이럴 땐 Karpenter 자체 버그보다 클라우드 용량 이슈일 가능성도 큽니다. 그래서 저는 가능한 한 여러 AZ와 여러 타입을 열어둡니다.

    kubectl get events -A --sort-by=.lastTimestamp
    kubectl describe pod -n <namespace> <pod-name>
    kubectl describe nodepool <nodepool-name>

    설정 예시: 디버깅용으로 조건을 단순화해보기

    문제가 길어질 때는 조건을 줄여서 정상 동작 여부부터 확인하는 게 좋습니다. 아래처럼 Pod를 단순화해서 테스트해 보세요.

    apiVersion: v1
    kind: Pod
    metadata:
      name: karpenter-debug
    spec:
      containers:
        - name: pause
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"

    이 Pod조차 스케줄이 안 되면, 애플리케이션 문제가 아니라 Karpenter 또는 인프라 레이어 문제로 보는 게 맞습니다. 반대로 이건 뜨는데 실제 서비스 Pod만 안 뜬다면, 그때는 requests/affinity/toleration을 더 자세히 보시면 됩니다.

    Kubernetes 노드 프로비저닝 점검을 위한 YAML 설정과 kubectl 디버깅 이미지

    디버깅용 Pod YAML, kubectl describe 출력, NodePool 설정을 함께 비교하는 장면을 표현한 이미지입니다.

    검증 방법: 노드가 정말 의도대로 프로비저닝됐는지 확인

    노드가 하나 떴다고 끝이 아닙니다. 저는 아래 순서로 꼭 확인합니다.

    1. 새 노드가 생성됐는지 확인
    2. Pending Pod가 Running으로 전환됐는지 확인
    3. 노드 라벨, taint, allocatable 자원이 기대와 맞는지 확인
    4. 불필요하게 큰 인스턴스가 뜨지 않았는지 확인
    kubectl get nodes
    kubectl get pods -A -o wide
    kubectl describe node <node-name>
    kubectl top nodes

    확인 포인트는 단순합니다.

    • Pod가 실제로 새 노드에 배치됐는가
    • 노드 라벨이 workload 조건과 맞는가
    • CPU/메모리 여유가 너무 적거나 너무 많지 않은가
    • 오토스케일링 실패가 반복되지 않는가

    드디어 됐다! 싶은 순간이 여기인데요, 여기서 끝내면 안 됩니다. 몇 분 후 consolidation이나 만료 정책 때문에 노드가 정리되면서 다시 Pending이 생길 수도 있거든요. 그래서 최소한 짧은 부하 테스트나 재배포 한 번은 꼭 해보시는 걸 권합니다.

    새 노드 생성 이후 Pending Pod가 Running으로 바뀌고 자원 사용량이 안정화된 결과 화면 예시입니다.

    운영 팁: 제가 실제로 적용하는 Karpenter 문제 해결 체크리스트

    혹시 이런 경험 있으신가요? 로그는 많은데 뭘 먼저 봐야 할지 몰라서 계속 왔다 갔다 하게 되는 상황이요. 그래서 저는 아래 체크리스트를 만들어 두고 그대로 봅니다.

    체크 항목 질문 판단 기준
    Pod 이벤트 왜 Pending인가? 스케줄링 실패 메시지가 명확해야 함
    Karpenter 로그 프로비저닝 계산을 했는가? instance type, subnet, 권한 관련 힌트 확인
    설정 제약 NodePool/Provisioner가 너무 좁은가? AZ, 타입, 아키텍처, 용량 타입 검토
    AWS 리소스 태그와 권한이 맞는가? subnet/security group 발견 가능해야 함
    검증 한 번 뜬 뒤 안정적으로 유지되는가? 재배포/부하 시 반복 확인

    이 체크리스트만 잘 지켜도 Karpenter 문제 해결 시간이 꽤 줄어듭니다. 그리고 이전 글에서 다룬 클러스터 리소스 요청량 설계 내용과 같이 보시면 더 이해가 쉬우실 겁니다. 다음 글에서는 Karpenter와 Cluster Autoscaler를 어떤 기준으로 나눠 볼지 정리해보려고 합니다.

    Karpenter 문제 해결 체크리스트를 요약한 인포그래픽 이미지

    Karpenter 문제 해결 순서를 한 장으로 요약한 체크리스트 인포그래픽입니다.

    정리: Karpenter 문제 해결은 순서가 전부입니다

    오늘 내용의 핵심은 복잡하지 않습니다. 오토스케일링 실패가 보이면 바로 AWS 콘솔부터 열지 말고, 먼저 Pending Pod 이벤트를 확인하세요. 그다음 Karpenter 로그, 그리고 NodePool/Provisioner 제약, 마지막으로 IAM과 네트워크 태그를 보는 흐름이 가장 효율적이었습니다.

    저도 처음엔 Karpenter가 너무 똑똑해서 알아서 다 해줄 줄 알았는데, 실제로는 “조건을 어떻게 열고, 어떤 로그를 먼저 읽느냐”가 훨씬 중요하더라고요. 특히 Kubernetes 노드 프로비저닝 이슈는 한 군데만 봐서는 잘 안 풀립니다. 대신 순서를 지키면 생각보다 금방 풀립니다.

    혹시 지금도 Karpenter 디버깅 중이시라면, 오늘 소개한 명령어 세트부터 그대로 실행해 보세요. 그리고 Pod 이벤트 한 줄, Karpenter 로그 한 줄만 정확히 읽어도 방향이 확 잡힐 겁니다. 이거 진짜 편하더라고요.

  • [k8s] AKS 워크로드 성능 벤치마크: 노드 타입별 최적화 전략

    [k8s] AKS 워크로드 성능 벤치마크: 노드 타입별 최적화 전략

    [k8s] AKS 워크로드 성능 벤치마크: 노드 타입별 최적화 전략

    AKS 성능 벤치마크를 제대로 해보면, 같은 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션) 클러스터라도 노드 타입 하나 바꿨을 뿐인데 체감이 꽤 달라집니다. 저도 처음엔 “어차피 Pod(파드, 컨테이너 실행 단위)만 잘 나뉘면 되는 거 아닌가?” 싶었거든요. 근데 실제로 운영해보니까 CPU bound(연산 집약형) 워크로드, memory bound(메모리 집약형) 워크로드, burst traffic(순간 트래픽) 대응이 전부 다르게 움직이더라고요. 특히 AKS 노드 타입을 대충 고르면 비용은 비용대로 나가고, 성능은 애매한 상황이 생기는 거 있죠.

    이번 글은 특정 벤치마크 수치를 과장해서 보여주는 글이 아닙니다. 대신 제가 실무와 홈랩에서 클러스터를 굴리면서 정리한 방식대로, 어떻게 AKS 성능 벤치마크를 설계하고, 어떤 워크로드에 어떤 노드 타입을 우선 검토해야 하는지, 그리고 쿠버네티스 워크로드 최적화를 어떤 순서로 접근하면 덜 삽질하는지 정리해보겠습니다. 숫자보다 중요한 건 비교 기준이거든요.

    AKS 성능 벤치마크를 위한 노드 타입별 아키텍처 다이어그램

    AKS 클러스터에서 일반형, 컴퓨트 최적화형, 메모리 최적화형 노드를 분리하고 워크로드를 비교 측정하는 전체 구조 예시입니다.

    왜 AKS 성능 벤치마크가 중요한가

    쉽게 말해, AKS는 관리형 쿠버네티스(Managed Kubernetes)라서 제어 플레인(control plane)은 편해졌지만, 노드 선택 책임까지 사라진 건 아닙니다. 오히려 운영이 쉬워진 만큼 워크로드 특성에 맞는 인프라 설계가 더 중요해졌어요.

    예를 들어 이런 상황 있으셨을 겁니다.

    • 웹 API는 평소엔 한가한데 특정 시간대에 응답 지연(latency, 지연 시간)이 튑니다.
    • 배치 작업(batch job)은 CPU를 많이 먹는데, 일반형 노드에서 오래 끕니다.
    • 캐시나 분석용 애플리케이션은 메모리를 넉넉히 잡아야 하는데 OOMKilled가 납니다.
    • 오토스케일링(autoscaling)이 걸리긴 하는데 생각보다 늦게 반응합니다.

    이럴 때 필요한 게 감으로 고르는 인프라가 아니라, 클라우드 네이티브 벤치마크 방식으로 실제 워크로드를 분류하고 비교하는 작업입니다. 저도 예전엔 노드 스펙만 보고 판단했다가, 정작 병목은 CPU가 아니라 스토리지 I/O 혹은 스케줄링 밀집도(bin packing)였던 적이 꽤 있었어요. 정말 여러 번 삽질했습니다 ㅎㅎ

    AKS 노드 타입을 어떻게 봐야 하나

    Azure K8s 성능을 볼 때는 노드를 단순히 “비싼 게 좋다”로 보면 안 됩니다. AKS 노드 타입은 대체로 다음 관점으로 나눠서 보면 훨씬 판단이 쉬워집니다.

    분류 대표 성격 잘 맞는 워크로드 주의할 점
    General Purpose(범용형) CPU/메모리 균형 일반 웹 서비스, API, 운영도구 특정 자원 한쪽이 치우치면 효율이 애매할 수 있음
    Compute Optimized(컴퓨트 최적화형) vCPU 비중 높음 트랜스코딩, 빌드, 배치, 계산 작업 메모리 요구량이 큰 앱은 오히려 불리할 수 있음
    Memory Optimized(메모리 최적화형) 메모리 비중 높음 캐시, 인메모리 DB, JVM 계열 앱 CPU 대비 비용 효율을 따로 봐야 함
    GPU/특수형 가속기 탑재 AI/ML, 병렬 연산 일반 서비스에는 과한 경우가 많음

    실무에서는 Azure VM 계열로 풀어보면 범용형은 D 시리즈, 컴퓨트 최적화형은 F 시리즈, 메모리 최적화형은 E 시리즈를 먼저 비교하는 경우가 많습니다. 물론 세부 세대와 로컬 디스크 유무, 네트워크 성능, 가용 영역 배치까지 들어가면 이야기가 더 길어지지만, 첫 AKS 성능 벤치마크는 너무 복잡하게 시작하지 않는 게 좋습니다.

    여기서 중요한 포인트! 벤치마크의 목적은 “최고 성능 머신 찾기”가 아니라, 내 워크로드에 가장 안정적으로 맞는 조합 찾기입니다.

    벤치마크 전에 먼저 정해야 할 기준

    AKS 성능 벤치마크를 시작하기 전에 저는 보통 아래 네 가지부터 적어둡니다.

    1. 측정 대상: API 서버인지, 배치 잡인지, 스트리밍 처리인지 구분합니다.
    2. 핵심 지표: 처리량(throughput), 응답 지연(latency), 재시작 횟수, CPU throttling 여부를 봅니다.
    3. 비교 조건: 동일한 리전(region), 동일한 애플리케이션 이미지, 동일한 requests/limits로 맞춥니다.
    4. 종료 조건: 어느 지점에서 “이 노드는 부적합”이라고 판단할지 기준을 세웁니다.

    이걸 안 정하면 나중에 그래프는 많은데 결론이 없어요. 저도 처음엔 대시보드만 잔뜩 띄워놓고 뿌듯했는데, 정작 뭘 비교한 건지 흐려져서 다시 했던 기억이 납니다.

    실전 구현: AKS 벤치마크용 클러스터와 워크로드 준비

    이제 실제로 해보겠습니다. 여기서는 가장 이해하기 쉬운 방식으로, 노드 풀(node pool, 용도별 노드 그룹)을 분리하고 동일 워크로드를 각각에 붙여 비교하는 흐름으로 가보겠습니다.

    1. AKS 클러스터와 기본 노드 풀 생성

    RESOURCE_GROUP="rg-aks-bench"
    CLUSTER_NAME="aks-bench-lab"
    LOCATION="koreacentral"
    
    az group create \
      --name $RESOURCE_GROUP \
      --location $LOCATION
    
    az aks create \
      --resource-group $RESOURCE_GROUP \
      --name $CLUSTER_NAME \
      --node-count 1 \
      --enable-managed-identity \
      --generate-ssh-keys
    
    az aks get-credentials \
      --resource-group $RESOURCE_GROUP \
      --name $CLUSTER_NAME

    처음부터 노드를 많이 올리기보다, 기본 클러스터를 만들고 나중에 목적별 node pool을 추가하는 게 관리가 편합니다.

    2. 용도별 AKS 노드 타입 추가

    az aks nodepool add \
      --resource-group $RESOURCE_GROUP \
      --cluster-name $CLUSTER_NAME \
      --name gp \
      --node-count 1 \
      --node-vm-size Standard_D4s_v5
    
    az aks nodepool add \
      --resource-group $RESOURCE_GROUP \
      --cluster-name $CLUSTER_NAME \
      --name cpu \
      --node-count 1 \
      --node-vm-size Standard_F4s_v2
    
    az aks nodepool add \
      --resource-group $RESOURCE_GROUP \
      --cluster-name $CLUSTER_NAME \
      --name mem \
      --node-count 1 \
      --node-vm-size Standard_E4s_v5

    예시는 범용형, 컴퓨트 최적화형, 메모리 최적화형을 나눈 것입니다. 실제 운영에서는 가용성, 예산, 리전 지원 여부를 같이 봐야 합니다.

    AKS 노드 타입에 따라 워크로드를 분리 배치하는 구성도

    서로 다른 AKS 노드 풀에 동일한 애플리케이션을 배치하고, nodeSelector로 대상을 분리하는 흐름을 보여주는 구성도입니다.

    3. 테스트용 애플리케이션 배포

    여기서는 가장 단순한 웹 애플리케이션 예시로 갑니다. 핵심은 애플리케이션 자체가 아니라 동일 조건으로 여러 노드 타입에 붙여 비교하는 것입니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: bench-web-gp
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: bench-web-gp
      template:
        metadata:
          labels:
            app: bench-web-gp
        spec:
          nodeSelector:
            agentpool: gp
          containers:
          - name: web
            image: nginx:stable
            resources:
              requests:
                cpu: "250m"
                memory: "256Mi"
              limits:
                cpu: "500m"
                memory: "512Mi"
            ports:
            - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: bench-web-gp
    spec:
      selector:
        app: bench-web-gp
      ports:
      - port: 80
        targetPort: 80
      type: ClusterIP

    이 YAML은 `gp`, `cpu`, `mem` 용으로 이름과 `nodeSelector`만 바꿔서 각각 하나씩 배포하면 됩니다. 실제로 해보니까 여기서 가장 많이 틀리는 게 라벨명이더라고요. AKS 노드 풀 라벨은 환경에 따라 꼭 확인해보는 게 안전합니다.

    4. 리소스 사용량과 이벤트 확인

    kubectl get nodes --show-labels
    kubectl get pods -o wide
    kubectl top nodes
    kubectl top pods
    kubectl describe pod <pod-name>

    이 단계에서 확인할 건 세 가지입니다.

    • Pod가 의도한 node pool에 올라갔는지
    • CPU throttling 징후가 있는지
    • 메모리 압박이나 재시작이 있는지

    5. 부하 테스트 실행

    HTTP 워크로드라면 외부 부하 도구를 붙여도 되고, 간단히 클러스터 내부에서 반복 요청을 보내도 됩니다.

    kubectl run loadtest --rm -it \
      --image=busybox \
      --restart=Never -- /bin/sh
    
    while true; do
      wget -q -O- http://bench-web-gp; 
      sleep 0.2;
    done

    물론 이건 아주 가벼운 예시입니다. 실제 AKS 성능 벤치마크라면 동시성(concurrency), 지속 시간(duration), 오류율(error rate)을 더 정교하게 잡아야 합니다. 그래도 첫 비교는 이렇게 작게 시작하는 게 좋습니다. 처음부터 너무 큰 도구를 붙이면 원인 분석이 더 어려워지거든요.

    쿠버네티스 워크로드 최적화에서 놓치기 쉬운 포인트

    노드 타입만 바꾸면 끝일 것 같지만, 실은 워크로드 정의가 더 중요할 때가 많습니다.

    체크 항목 왜 중요한가 실무 팁
    requests/limits 스케줄링과 throttling에 직접 영향 너무 보수적으로 잡으면 밀집도가 떨어집니다
    HPA 트래픽 대응 자동화 CPU만 보지 말고 메모리나 커스텀 메트릭도 검토합니다
    Pod 분산 장애 도메인 분리 anti-affinity와 topology spread를 함께 봅니다
    스토리지 특성 상태 저장 앱의 체감 성능 좌우 디스크 병목이면 노드 변경 효과가 작을 수 있습니다

    예를 들어 CPU가 부족한 줄 알고 컴퓨트 최적화 노드로 옮겼는데, 알고 보니 컨테이너 `limits`가 너무 타이트해서 throttling만 심했던 경우도 있습니다. 저도 이거 한 번 당하고 나서는, 무조건 애플리케이션 정의와 노드 타입을 같이 봅니다.

    ⚠️ 실제로 겪었던 트러블슈팅

    여기서는 제가 자주 봤던 문제를 정리해볼게요.

    1. Pod가 원하는 노드에 안 올라가는 문제

    대부분 `nodeSelector`, `taint/toleration`, 리소스 부족 셋 중 하나입니다.

    kubectl describe pod <pod-name>

    `Events`를 보면 대개 이유가 나옵니다. 처음엔 저도 스케줄러가 이상한 줄 알았는데, 실제로는 라벨 오타가 제일 흔했어요.

    2. 벤치마크 결과가 들쭉날쭉한 문제

    이건 생각보다 흔합니다. 원인은 보통 아래 쪽입니다.

    • 테스트 시간대가 다름
    • 이미지 풀(image pull) 시간이 섞임
    • 오토스케일링 반응 시간이 포함됨
    • 클러스터 내부의 다른 작업이 간섭함

    그래서 저는 AKS 성능 벤치마크할 때 최소한 워밍업 구간과 실측 구간을 분리합니다. 이거 안 하면 첫 요청 지연이 평균값을 망치더라고요.

    3. 메모리 최적화 노드인데 성능 향상이 애매한 문제

    메모리를 많이 준다고 모든 앱이 빨라지진 않습니다. 애플리케이션이 실제로 heap(힙), cache(캐시), page cache를 활용하는 구조인지 봐야 합니다. 단순한 stateless API라면 메모리보다 CPU 클럭이나 네트워크 특성이 더 체감될 때가 많습니다.

    4. 비용은 늘었는데 개선 폭이 작은 문제

    이건 가장 뼈아픈 케이스죠. 그래서 AKS 성능 벤치마크 결과는 반드시 성능/비용 관점으로 같이 봐야 합니다. 절대 성능만 보고 결정하면 운영비가 금방 불어나니까요.

    AKS 성능 벤치마크 결과를 시각화한 대시보드 이미지

    CPU 사용률, 메모리 사용량, Pod 재시작, 응답 지연을 한 화면에서 비교하는 벤치마크 대시보드 예시입니다.

    검증: 어떤 결과를 보면 의미 있는가

    결과 검증은 단순 평균보다 분포를 보는 게 좋습니다. 특히 웹 서비스는 p95 latency(p95 지연 시간), 오류율, 재시작 여부가 중요합니다.

    1. 범용형 노드에서 안정적이지만 피크 구간에서 지연이 튀는지 봅니다.
    2. 컴퓨트 최적화형에서 CPU 사용률은 낮아졌는지, 처리량은 늘었는지 봅니다.
    3. 메모리 최적화형에서 OOMKilled가 줄고 캐시 적중 효과가 있는지 확인합니다.
    4. 같은 성능을 더 적은 노드 수로 낼 수 있는지도 함께 봅니다.

    제가 직접 해보니, 정답이 하나로 고정되기보다 워크로드 특성에 따라 꽤 명확하게 갈리더라고요.

    • 일반 웹/API는 범용형에서 시작해도 충분한 경우가 많습니다.
    • 빌드, 계산, 배치성 작업은 컴퓨트 최적화형이 먼저 후보가 됩니다.
    • 캐시, JVM, 데이터 집약형 앱은 메모리 최적화형 검토 우선순위가 올라갑니다.

    결론적으로 Azure K8s 성능은 노드 스펙 하나가 아니라, 워크로드 패턴과 리소스 설정, 스케일 전략의 합으로 결정됩니다.

    정리: AKS 노드 타입 선택 전략

    마지막으로 실무에서 바로 써먹기 좋은 방식으로 정리해보겠습니다.

    1. 기준 워크로드를 하나 정합니다. 가장 중요한 서비스부터입니다.
    2. 범용형 node pool을 기준선으로 삼습니다.
    3. 같은 앱을 컴퓨트 최적화형, 메모리 최적화형에 각각 배치합니다.
    4. `requests/limits`, HPA, 분산 정책을 동일하게 맞춥니다.
    5. 처리량, p95 지연, 재시작, 비용 관점을 같이 봅니다.
    6. 결과가 좋으면 그때 운영 표준으로 가져갑니다.

    이 순서대로 하면 적어도 “왜 느린지 모르겠는데 일단 큰 VM으로 바꾸자” 같은 결정을 줄일 수 있습니다. 사실 저도 예전엔 그렇게 했었는데요, 나중에 보면 대부분 비효율이었어요.

    혹시 지금 AKS에서 특정 워크로드가 계속 애매하게 느리다면, 이번엔 감으로 바꾸지 말고 AKS 성능 벤치마크 기준부터 잡아보세요. 그 다음에야 AKS 노드 타입 선택이 훨씬 쉬워집니다. 다음 글에서는 Cluster Autoscaler(클러스터 오토스케일러)와 HPA를 같이 묶어서, 실제 트래픽 변동 환경에서 어떻게 튜닝하는지 이어서 다뤄보겠습니다. 이전 글에서 다뤘던 Ingress(인그레스, 외부 트래픽 진입점) 최적화 내용과 연결해서 보셔도 흐름이 잘 이어질 겁니다.

    AKS 노드 타입 선택 기준과 최적화 전략 요약 인포그래픽

    범용형, 컴퓨트 최적화형, 메모리 최적화형 노드의 추천 워크로드와 판단 기준을 한눈에 요약한 인포그래픽입니다.

    FAQ: 자주 묻는 질문

    AKS 성능 벤치마크는 운영 환경에서 바로 해야 하나요?

    가능하면 운영과 유사한 별도 환경에서 먼저 하시는 걸 권합니다. 운영에서 바로 하면 다른 변수 때문에 결과 해석이 어려워져요.

    노드 타입만 바꾸면 성능 문제가 해결되나요?

    아닙니다. `requests/limits`, 애플리케이션 구조, 스토리지, 네트워크까지 같이 봐야 합니다.

    처음 시작할 때 가장 무난한 방법은 뭔가요?

    범용형을 기준선으로 잡고, CPU 중심 워크로드와 메모리 중심 워크로드를 분리해 비교해보는 게 가장 안전합니다.

  • [Kubernetes] ConfigMap 운영 중 흔히 겪는 문제와 해결 전략

    [Kubernetes] ConfigMap 운영 중 흔히 겪는 문제와 해결 전략

    [Kubernetes] ConfigMap 운영 중 흔히 겪는 문제와 해결 전략

    Kubernetes ConfigMap 문제 해결은 운영하다 보면 한 번쯤 꼭 붙잡게 되는 주제예요. 배포는 멀쩡히 끝났는데 환경 변수는 안 바뀌어 있고, YAML은 분명 맞는 것 같은데 애플리케이션이 예전 설정으로 뜨는 경우가 정말 많더라고요. 저도 처음엔 이게 뭔가 싶었거든요. 특히 홈랩에서 여러 서비스를 굴리면서 ConfigMap(컨피그맵, 설정 데이터를 담는 리소스)과 Secret(시크릿, 민감 정보 저장 리소스)을 섞어 쓰다 보니 어디서 꼬였는지 찾는 데 정말 시간이 걸렸어요. 이번 글에서는 제가 실제로 자주 봤던 ConfigMap 업데이트 이슈, 환경 변수 미적용 문제, 설정 오류 디버깅 흐름을 기준으로 정리해보겠습니다.

    핵심만 먼저 말씀드리면 이렇습니다. ConfigMap이 바뀌었다고 해서 모든 Pod(파드, 컨테이너 실행 단위)가 자동으로 기대한 방식으로 반영되지는 않습니다. 값을 어떤 방식으로 주입했는지에 따라 반영 방식이 다르고, 애플리케이션이 설정 재로딩(reload, 설정 다시 읽기)을 지원하는지도 함께 봐야 하거든요. 여기서 중요한 포인트! Kubernetes 자체 문제처럼 보여도 실제 원인은 애플리케이션 기동 방식이나 Deployment(디플로이먼트, 배포 리소스) 선언에 있는 경우가 정말 많아요.

    ConfigMap이 Pod에 주입되는 경로와, 업데이트 후 반영 지점을 한눈에 보여주는 개요 다이어그램입니다.

    1. 왜 ConfigMap 문제가 자주 생길까

    쉽게 말해 ConfigMap은 애플리케이션 이미지와 설정을 분리하려고 쓰는 도구예요. 이미지 자체를 다시 빌드하지 않고도 환경별 설정을 바꿀 수 있으니 정말 편하죠. 그런데 편한 만큼 함정도 있어요.

    • 환경 변수(env)로 주입했는지, 파일(volume)로 마운트했는지에 따라 동작이 달라집니다.
    • 애플리케이션이 시작할 때만 설정을 읽는지, 실행 중 재로딩을 지원하는지 다릅니다.
    • YAML 들여쓰기나 키 이름 오타처럼 아주 사소한 실수도 바로 장애로 이어져요.
    • 운영 중에는 여러 팀이 같은 설정을 만지다 보니 변경 이력 추적도 중요해집니다.

    실제로 써보니까 ConfigMap은 단순한 설정 저장소가 아니라, 배포 전략과 디버깅 방식까지 같이 설계해야 하는 운영 요소였어요. 처음에는 그냥 값만 넣으면 끝인 줄 알았는데, 나중에 보니 반영 타이밍 때문에 삽질 좀 했습니다 ㅎㅎ

    2. ConfigMap 개념 설명: 어디에 쓰는가

    ConfigMap은 문자열 기반 설정 데이터를 Kubernetes 클러스터 안에서 관리하기 위한 리소스예요. 보통 다음 두 가지 방식으로 많이 써요.

    주입 방식 설명 운영 시 주의점
    환경 변수(Environment Variables) 컨테이너 시작 시 값을 환경 변수로 주입 대체로 Pod 재시작 없이는 값 변경 체감이 어려워요
    볼륨 마운트(Volume Mount) ConfigMap 내용을 파일 형태로 컨테이너 내부에 제공 파일이 갱신돼도 애플리케이션이 재로딩하지 않으면 반영이 안 될 수 있어요

    여기서 많이 헷갈리는 부분이 있어요. ConfigMap 업데이트와 애플리케이션 설정 반영은 같은 일이 아닙니다. Kubernetes 입장에서는 데이터를 바꿨을 뿐이고, 그걸 애플리케이션이 언제 다시 읽을지는 별개의 문제거든요. 저도 처음엔 kubectl로 ConfigMap만 바꿔놓고 왜 서비스 동작이 그대로인지 한참 봤었어요.

    자주 쓰는 예시 ConfigMap

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: app-config
    data:
      APP_MODE: "production"
      LOG_LEVEL: "info"
      app.properties: |
        server.port=8080
        feature.toggle=true
    

    위 예시처럼 간단한 key-value도 넣을 수 있고, 설정 파일 전체를 문자열로 넣을 수도 있어요. 운영에서는 둘 다 많이 써요.

    3. 실전 구현: ConfigMap 생성과 Pod 연결

    이제 실전으로 가봅시다. Kubernetes ConfigMap 문제 해결의 시작은 항상 현재 어떤 방식으로 연결되어 있는지 확인하는 것이에요. 환경 변수인지, 파일 마운트인지, 둘 다인지 먼저 봐야 한다는 뜻입니다.

    3-1. ConfigMap 생성

    kubectl create configmap app-config \
      --from-literal=APP_MODE=production \
      --from-literal=LOG_LEVEL=info

    또는 선언형으로 관리하려면 YAML 파일을 두고 적용하는 방식이 더 좋아요. GitOps(깃옵스, Git 기반 운영) 흐름에도 잘 맞고요.

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: app-config
    data:
      APP_MODE: "production"
      LOG_LEVEL: "info"
    
    kubectl apply -f configmap.yaml

    3-2. 환경 변수로 주입

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-app
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: demo-app
      template:
        metadata:
          labels:
            app: demo-app
        spec:
          containers:
            - name: app
              image: nginx
              env:
                - name: APP_MODE
                  valueFrom:
                    configMapKeyRef:
                      name: app-config
                      key: APP_MODE
                - name: LOG_LEVEL
                  valueFrom:
                    configMapKeyRef:
                      name: app-config
                      key: LOG_LEVEL
    

    이 방식은 선언이 명확해서 좋아요. 다만 환경 변수 미적용 이슈가 가장 자주 보이는 방식이기도 합니다. 왜냐하면 컨테이너는 보통 시작할 때 환경 변수를 읽고 끝이거든요.

    3-3. 파일로 마운트

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-app-file
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: demo-app-file
      template:
        metadata:
          labels:
            app: demo-app-file
        spec:
          containers:
            - name: app
              image: nginx
              volumeMounts:
                - name: config-volume
                  mountPath: /etc/app-config
          volumes:
            - name: config-volume
              configMap:
                name: app-config
    

    파일 마운트 방식은 애플리케이션이 파일 변경을 감지하거나 재시작 시 다시 읽는 구조일 때 특히 유용해요. 근데 여기서도 안심하면 안 돼요. 애플리케이션이 그 파일을 다시 안 읽으면 소용이 없거든요.

    Kubernetes ConfigMap 업데이트 방식 비교 이미지

    Deployment에서 ConfigMap을 환경 변수와 볼륨으로 주입하는 구성을 비교하는 다이어그램입니다.

    4. ConfigMap 업데이트 시 가장 많이 터지는 문제

    이 섹션은 제가 운영하면서 가장 많이 봤던 증상들 위주로 적어볼게요. 혹시 이런 경험 있으신가요? 분명 ConfigMap은 바뀌었는데 서비스가 그대로인 상황 말이에요. 대부분 아래 범주 안에 들어가요.

    4-1. ConfigMap 업데이트 후 환경 변수 미적용

    가장 흔한 증상이에요. 원인은 단순한 편입니다. 환경 변수는 보통 컨테이너 시작 시점에 주입되기 때문이에요. 그래서 ConfigMap만 바꿔서는 이미 떠 있는 Pod 내부 환경 변수가 즉시 바뀌지 않아요.

    제가 직접 해보니 여기서 중요한 건 Kubernetes가 아니라 주입 방식이었어요. 해결은 보통 다음 중 하나입니다.

    1. Deployment를 다시 롤아웃해서 새 Pod를 띄웁니다.
    2. 설정 변경 시점에 맞춰 배포 파이프라인에서 재시작 절차를 함께 실행해요.
    3. 가능하면 파일 기반 설정 + 애플리케이션 재로딩 구조를 검토해야 해요.
    kubectl rollout restart deployment demo-app
    kubectl rollout status deployment demo-app

    4-2. 볼륨 파일은 바뀌었는데 애플리케이션 동작은 그대로

    이 경우는 더 헷갈려요. 컨테이너 안 파일을 열어보면 값이 바뀐 것 같은데, 서비스는 예전 설정대로 동작하거든요. 원인은 대개 애플리케이션이 시작할 때만 파일을 읽고 메모리에 들고 있기 때문이에요.

    이럴 때는 아래를 점검해야 해요.

    • 애플리케이션이 SIGHUP 같은 재로딩 신호를 받는지
    • 파일 변경 감지(watch)를 지원하는지
    • 설정 파일 경로가 실제 읽는 경로와 같은지
    • 서브패스(subPath) 사용 여부

    특히 subPath로 마운트한 파일은 기대한 방식의 갱신이 안 보이는 사례가 있어서 운영 시 더 조심하게 되더라고요. 저도 예전에 특정 설정 파일 하나만 예쁘게 꽂으려고 subPath를 썼다가, 왜 변경 반영이 안 되지 하고 한참 봤었어요.

    4-3. 키 이름 오타, 네임스페이스 착오

    은근히 자주 나와요. ConfigMap 이름은 맞는데 key가 다르거나, 적용한 namespace(네임스페이스, 리소스 격리 단위)가 달라서 Pod가 다른 리소스를 보고 있는 경우죠.

    kubectl get configmap app-config -n default -o yaml
    kubectl get deployment demo-app -n default -o yaml
    kubectl describe pod <pod-name> -n default

    이 세 가지를 같이 보면 생각보다 금방 풀려요. 저는 디버깅할 때 항상 ConfigMap 원본, Deployment 참조, 실제 Pod 상태를 한 세트로 봐요.

    5. 설정 오류 디버깅: 제가 실제로 보는 순서

    문제가 터졌을 때는 감으로 보면 더 꼬여요. 순서를 정해두는 게 좋습니다. 저는 아래 흐름으로 봐요.

    1. ConfigMap 값 확인
      kubectl get configmap으로 현재 값이 정말 바뀌었는지 확인합니다.
    2. Deployment 선언 확인
      env, envFrom, volumeMounts, volumes가 예상대로 연결됐는지 봐요.
    3. Pod 재생성 여부 확인
      환경 변수 방식이라면 새 Pod가 떴는지 확인합니다.
    4. 컨테이너 내부 확인
      printenv, cat 등으로 실제 반영 상태를 확인해요.
    5. 애플리케이션 로그 확인
      설정 파싱 오류나 fallback 값 사용 흔적이 없는지 봅니다.
    kubectl get configmap app-config -o yaml
    kubectl get deployment demo-app -o yaml
    kubectl get pods -l app=demo-app
    kubectl exec -it <pod-name> -- printenv | grep APP_MODE
    kubectl exec -it <pod-name> -- sh -c 'cat /etc/app-config/app.properties'
    kubectl logs <pod-name>

    여기서 중요한 포인트! 컨테이너 내부 확인 없이 추측으로 넘어가면 시간만 더 써요. 실제로 써보니까 운영 이슈는 눈으로 확인하는 게 제일 빠르더라고요.

    Kubernetes ConfigMap 문제 해결을 위한 설정 오류 디버깅 이미지

    kubectl 명령으로 ConfigMap 값, Pod 환경 변수, 마운트 파일을 순서대로 점검하는 디버깅 흐름 이미지입니다.

    6. 운영에서 쓰는 해결 전략 정리

    단순히 문제를 푸는 것보다, 다시 안 터지게 만드는 게 더 중요해요. 제가 지금은 아래 전략을 기본으로 가져가고 있습니다.

    상황 권장 전략 이유
    환경 변수 기반 설정 변경 후 롤아웃 재시작 절차 포함 Pod 재기동이 반영 경로가 되기 쉬워요
    파일 기반 설정 애플리케이션 재로딩 지원 여부 확인 파일 변경과 동작 반영은 별개예요
    설정 변경 잦음 선언형 관리와 변경 이력 추적 누가 무엇을 바꿨는지 파악하기 쉬워요
    장애 대응 검증 명령어를 런북(runbook, 운영 절차서)화 야간 대응 속도가 크게 향상돼요
    • ConfigMap 이름에 버전성 힌트를 두는 방식도 운영에 따라 정말 유용해요.
    • Deployment annotation 변경으로 롤링 업데이트를 유도하는 패턴도 자주 써요.
    • 애플리케이션 기본값(fallback) 존재 여부를 꼭 확인해야 해요. 설정이 안 먹었는데도 앱이 떠버리면 더 위험하거든요.
    • Secret과 ConfigMap 역할 분리도 중요해요. 민감 정보는 ConfigMap에 넣지 않는 게 기본입니다.

    그리고 k8s 설정 관리는 결국 사람 문제이기도 해요. 파일명, 키 네이밍, 네임스페이스 규칙을 팀 단위로 맞추지 않으면 나중에 디버깅 비용이 훨씬 커져요.

    7. 검증과 결과 확인: 바뀐 설정이 정말 적용됐는지

    배포가 끝났다고 끝난 게 아니에요. 저는 항상 아래 세 가지를 같이 봐요.

    1. 리소스 기준 검증: ConfigMap과 Deployment 선언 확인
    2. 컨테이너 기준 검증: 환경 변수 또는 파일 내용 확인
    3. 애플리케이션 기준 검증: 로그, 헬스체크, 실제 동작 확인
    kubectl rollout status deployment demo-app
    kubectl exec -it <pod-name> -- printenv | grep LOG_LEVEL
    kubectl exec -it <pod-name> -- sh -c 'ls -l /etc/app-config && cat /etc/app-config/app.properties'
    kubectl logs <pod-name>

    여기서 드디어 됐다! 하는 순간이 와요. 그런데 한 단계 더 가야 해요. 애플리케이션이 실제로 새 설정으로 동작하는지 확인해야 하거든요. 예를 들어 로그 레벨이 바뀌었는지, 특정 기능 토글이 반영됐는지, 외부 연결 정보가 새 값으로 적용됐는지까지 봐야 진짜 검증이에요.

    처음엔 kubectl 출력만 보고 끝냈었는데, 나중에 보니 앱은 예전 설정으로 캐시해서 쓰고 있더라고요. 그래서 지금은 결과 검증을 더 꼼꼼히 하고 있어요.

    Kubernetes ConfigMap 적용 결과 검증 대시보드 이미지

    롤아웃 완료, 환경 변수 확인, 로그 검증까지 끝난 상태를 보여주는 결과 확인 이미지입니다.

    8. 자주 묻는 질문 정리

    Q1. ConfigMap 업데이트만 하면 바로 반영되나요?

    항상 그렇지는 않아요. 주입 방식과 애플리케이션 동작 방식에 따라 다르거든요. 환경 변수 기반이면 보통 재시작 관점으로 보는 게 안전해요.

    Q2. 환경 변수 미적용 문제는 Kubernetes 버그인가요?

    대부분은 버그라기보다 동작 방식 이해 차이에서 나와요. 저도 처음엔 그렇게 오해했는데, 실제로는 컨테이너 재기동 시점과 설정 읽는 타이밍 문제인 경우가 정말 많았어요.

    Q3. 설정 오류 디버깅은 어디서부터 봐야 하나요?

    ConfigMap 원본, Deployment 참조, Pod 내부 상태를 순서대로 보세요. 이 흐름만 지켜도 절반은 빨리 해결돼요.

    Q4. k8s 설정 관리를 더 안정적으로 하려면?

    선언형 관리, 변경 이력 추적, 운영 런북 정리 이 세 가지가 효과적이에요. 이전 글에서 다뤘던 배포 점검 체크리스트와 함께 보면 더 도움이 될 거예요. 다음 글에서는 Secret 운영 패턴과 ConfigMap 분리 기준도 다뤄볼 계획입니다.

    9. 마무리: ConfigMap은 단순한 설정 파일이 아니었습니다

    Kubernetes ConfigMap 문제 해결은 결국 설정 저장보다 설정 반영 경로를 이해하는 일에 가까워요. 제가 직접 해보니 ConfigMap 업데이트 자체보다, 그 값이 언제 어떻게 애플리케이션에 전달되는지 파악하는 게 핵심이었어요. 특히 환경 변수 미적용, 설정 오류 디버깅, k8s 설정 관리 이 세 가지는 따로 떨어진 주제가 아니라 한 흐름으로 묶여 있더라고요.

    혹시 지금 운영 중인 서비스에서 ConfigMap 업데이트 후 반영이 이상하다면, 오늘 글의 순서대로만 점검해보세요. 꽤 빨리 원인을 좁힐 수 있을 거예요. 완벽한 정답 하나가 있다기보다, 주입 방식에 맞는 운영 전략을 정해두는 것이 제일 중요했어요. 저도 처음엔 많이 헷갈렸는데, 한 번 패턴이 잡히고 나니까 훨씬 덜 흔들리더라고요.

    Kubernetes ConfigMap 문제 해결 전략 요약 인포그래픽

    환경 변수 방식과 파일 마운트 방식의 차이, 그리고 운영 체크포인트를 요약한 인포그래픽입니다.

    정리하면 이렇습니다.

    • ConfigMap 업데이트와 애플리케이션 반영은 같은 일이 아니에요.
    • 환경 변수 방식은 재시작 관점을 기본으로 가져가는 게 안전해요.
    • 파일 마운트 방식은 애플리케이션 재로딩 지원 여부를 꼭 확인해야 해요.
    • 설정 오류 디버깅은 ConfigMap, Deployment, Pod 내부 확인 순서로 보면 빨라져요.

    운영은 결국 반복 가능한 습관 싸움이더라고요. 다음 글에서는 Secret과 ConfigMap 분리 기준, 그리고 배포 자동화 파이프라인에서 설정 반영을 안전하게 묶는 방법도 이어서 정리해보겠습니다.

  • [Kubernetes] Karpenter vs Cluster Autoscaler 비교 분석

    [Kubernetes] Karpenter vs Cluster Autoscaler 비교 분석

    [Kubernetes] Karpenter vs Cluster Autoscaler 비교 분석

    Karpenter vs Cluster Autoscaler 이야기는 Kubernetes(쿠버네티스) 운영하시는 분들이 한 번쯤 꼭 부딪히는 주제입니다. 파드(Pod)가 늘어나는데 노드(Node)가 제때 안 붙으면 서비스가 밀리고, 반대로 너무 빨리 늘어나면 비용이 튀거든요. 저도 처음엔 HPA(Horizontal Pod Autoscaler, 파드 수평 확장)만 잘 걸어두면 끝인 줄 알았는데, 실제로 운영해보니 워크로드 확장과 노드 확장은 완전히 다른 문제더라고요. 특히 EKS 같은 클라우드 환경에서 직접 써보니까 Karpenter와 Cluster Autoscaler는 비슷해 보여도 철학이 꽤 다릅니다.

    이번 글에서는 Karpenter vs Cluster Autoscaler를 실무 관점에서 비교해보겠습니다. 단순히 기능 목록만 나열하는 게 아니라, 어떤 워크로드에서 무엇이 더 잘 맞는지, 설치할 때 어디서 삽질하는지, 검증은 어떻게 해야 하는지까지 정리해보겠습니다. 혹시 지금 Kubernetes 오토스케일링 구성을 만지시는 중이라면 꽤 바로 도움이 되실 겁니다.

    Cluster Autoscaler와 Karpenter가 각각 어떤 방식으로 노드를 늘리고 줄이는지 한눈에 보여주는 개요 다이어그램이 들어갈 자리입니다.

    Kubernetes 오토스케일링이 왜 생각보다 어렵냐면요

    쉽게 말해, Kubernetes 오토스케일링은 두 층으로 나뉩니다. 하나는 파드 수를 늘리는 것이고, 다른 하나는 그 파드를 올릴 노드를 확보하는 것입니다. HPA가 앞단이라면, Karpenter나 Cluster Autoscaler는 뒷단이라고 보시면 됩니다.

    • HPA: CPU, 메모리, 커스텀 메트릭 기준으로 파드 수를 늘리고 줄립니다.
    • Cluster Autoscaler: 기존 노드 그룹(Node Group, 노드 묶음)의 크기를 늘리거나 줄입니다.
    • Karpenter: 스케줄링되지 못한 파드를 보고, 필요한 조건에 맞는 노드를 더 직접적으로 프로비저닝(Provisioning, 자원 생성)합니다.

    여기서 중요한 포인트가 하나 있습니다. Cluster Autoscaler는 미리 정의된 노드 그룹 중심으로 생각하고, Karpenter는 파드 요구사항 중심으로 생각합니다. 이 차이가 운영 난이도, 비용 최적화, 확장 속도에 꽤 큰 영향을 줍니다.

    Karpenter vs Cluster Autoscaler: 핵심 개념 차이

    처음 문서를 보면 둘 다 자동으로 노드를 늘려주는 도구라서 별 차이 없어 보이는데요, 실제로 써보면 운영 감각이 다릅니다. 제가 직접 비교하면서 느낀 건 아래 표 하나로 많이 정리되더라고요.

    항목 Cluster Autoscaler Karpenter
    기본 동작 방식 기존 노드 그룹 크기 조절 파드 요구사항에 맞춰 노드 직접 생성
    확장 기준 스케줄 불가 파드를 보고 적절한 노드 그룹 선택 스케줄 불가 파드의 리소스 조건을 바로 계산
    노드 유연성 노드 그룹 설계에 크게 의존 인스턴스 타입 선택 폭이 넓음
    운영 모델 ASG/Managed Node Group 중심 Provisioner/NodePool 성격의 선언적 관리
    장점 구조가 익숙하고 보수적 운영에 적합 유연성, 속도, 비용 최적화 측면에서 강점
    주의점 노드 그룹이 많아지면 복잡도 증가 초기 설계와 제약조건 관리가 중요

    정리하면 이렇습니다. Cluster Autoscaler는 전통적인 방식입니다. 이미 만들어 둔 노드 그룹이 있고, 그 그룹의 min/max 범위 안에서 증감시키죠. 반면 Karpenter는 필요한 자원을 그때그때 맞춰 뽑아내는 쪽에 가깝습니다. 그래서 워크로드 패턴이 자주 바뀌거나, 다양한 인스턴스 타입을 섞어 쓰고 싶을 때 체감이 꽤 납니다.

    언제 Karpenter가 유리하고, 언제 Cluster Autoscaler가 편한가

    이건 솔직히 정답이 하나는 아닙니다. 운영팀 성향, 클러스터 규모, 클라우드 의존도에 따라 달라지거든요. 저도 처음엔 무조건 신형이 좋아 보였는데, 몇 번 굴려보니 케이스가 나뉘더라고요.

    Cluster Autoscaler가 잘 맞는 경우

    • 이미 Managed Node Group 기반 운영 체계가 안정적으로 잡혀 있는 경우
    • 보안, 승인, 변경 관리 때문에 노드 타입을 엄격히 통제해야 하는 경우
    • 여러 팀이 공용으로 쓰는 클러스터라서 예측 가능한 증설 패턴이 중요한 경우
    • 기존 운영팀이 노드 그룹 기반 모델에 익숙한 경우

    Karpenter가 잘 맞는 경우

    • 워크로드 확장 패턴이 들쭉날쭉해서 빠른 대응이 필요한 경우
    • 다양한 인스턴스 타입 조합으로 비용 최적화를 하고 싶은 경우
    • 스케줄링 요구사항이 세밀해서 노드 그룹을 많이 쪼개기 싫은 경우
    • 빈번한 배치 작업, CI 러너, 이벤트성 트래픽처럼 탄력성이 중요한 경우

    제 경험상 노드 그룹이 많아질수록 Cluster Autoscaler는 운영 피로도가 올라갑니다. 반대로 Karpenter는 초반 설계만 잘못 잡으면 예상치 못한 노드 생성 패턴 때문에 당황하게 되더라고요. 그러니까 둘 중 뭐가 무조건 더 좋다기보다, 운영 모델에 맞는 선택이 더 중요합니다.

    실전 구현 1: Cluster Autoscaler 구성 흐름

    먼저 Cluster Autoscaler부터 보겠습니다. 예시는 EKS 환경 기준으로 적겠습니다. 다른 클라우드에서도 개념은 비슷합니다. 핵심은 오토스케일 대상 노드 그룹이 먼저 존재해야 한다는 점입니다.

    1. 오토스케일 가능한 노드 그룹을 준비합니다.
    2. 노드 그룹의 최소/최대 크기를 정합니다.
    3. Cluster Autoscaler를 클러스터에 배포합니다.
    4. 스케줄 불가 파드가 생기면 해당 노드 그룹 크기를 늘립니다.
    helm repo add autoscaler https://kubernetes.github.io/autoscaler
    helm repo update
    
    helm upgrade --install cluster-autoscaler autoscaler/cluster-autoscaler \
      --namespace kube-system \
      --set autoDiscovery.clusterName=my-cluster \
      --set awsRegion=ap-northeast-2 \
      --set rbac.serviceAccount.create=true

    설치 자체는 그렇게 복잡하지 않습니다. 근데 실제로는 IAM(Role, 권한 역할), 오토디스커버리 태그, 노드 그룹 min/max 값 같은 주변 설정에서 삽질을 많이 하게 됩니다. 특히 노드 그룹 태그가 안 맞으면 파드가 Pending(펜딩, 스케줄 대기) 상태로 남아 있는데도 노드가 안 늘어납니다. 저도 처음엔 로그만 한참 봤네요.

    kubectl -n kube-system logs deploy/cluster-autoscaler
    kubectl get pods -A
    kubectl describe pod <pending-pod-name>

    여기서 보는 포인트는 간단합니다. 파드가 왜 스케줄링되지 않았는지, 그리고 Cluster Autoscaler가 어떤 노드 그룹을 확장 후보로 판단했는지입니다.

    실전 구현 2: Karpenter 구성 흐름

    Karpenter는 접근 방식이 다릅니다. 미리 노드 그룹을 세세하게 많이 만들기보다, 어떤 종류의 노드를 허용할지 정책으로 정의하는 느낌에 가깝습니다. 그래서 처음엔 구조가 낯선 감이 있더라고요. 저도 처음엔 이게 뭔가 싶었는데, 한번 감을 잡고 나니 꽤 편하더라고요.

    1. Karpenter 컨트롤러를 설치합니다.
    2. 클라우드 권한과 네트워크 조건을 연결합니다.
    3. 어떤 노드를 만들 수 있는지 정책을 정의합니다.
    4. 스케줄 불가 파드가 생기면 그 요구사항을 기반으로 노드를 생성합니다.
    helm repo add karpenter https://charts.karpenter.sh
    helm repo update
    
    helm upgrade --install karpenter karpenter/karpenter \
      --namespace karpenter \
      --create-namespace
    apiVersion: karpenter.sh/v1alpha5
    kind: Provisioner
    metadata:
      name: default
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values:
            - amd64
        - key: kubernetes.io/os
          operator: In
          values:
            - linux
      limits:
        resources:
          cpu: "1000"
      ttlSecondsAfterEmpty: 30

    요즘 Karpenter 관련 리소스 모델은 환경과 시점에 따라 구성이 조금씩 달라질 수 있어서, 실제 배포 전에는 현재 사용하는 배포 가이드와 CRD(Custom Resource Definition, 사용자 정의 리소스 정의)를 반드시 맞춰보셔야 합니다. 여기서는 비교 분석이 핵심이라, 파드 요구사항 기반으로 노드를 뽑아낸다는 운영 개념에 집중해서 보시면 됩니다.

    Karpenter 정책 기반 노드 생성 흐름을 보여주는 Kubernetes 오토스케일링 이미지

    Karpenter가 파드의 요구 리소스를 읽고 적절한 노드를 동적으로 생성하는 흐름을 설명하는 구성 이미지 자리입니다.

    워크로드 확장 테스트: 직접 확인하는 방법

    설치보다 중요한 게 검증입니다. 오토스케일링은 붙어 있다고 끝이 아니거든요. 원하는 타이밍에, 원하는 방식으로, 과하지 않게 확장되는지를 봐야 합니다. 저는 보통 부하 테스트용 디플로이먼트(Deployment)를 하나 띄워두고 확인합니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: inflate
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: inflate
      template:
        metadata:
          labels:
            app: inflate
        spec:
          containers:
            - name: inflate
              image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
              resources:
                requests:
                  cpu: "1"
                  memory: "1Gi"
                limits:
                  cpu: "1"
                  memory: "1Gi"
    kubectl apply -f inflate.yaml
    kubectl scale deployment inflate --replicas=20
    kubectl get pods -w
    kubectl get nodes -w

    이 테스트에서 봐야 할 건 세 가지입니다.

    • 확장 속도: Pending 파드가 얼마나 빨리 해소되는지
    • 적합성: 필요한 크기의 노드가 제대로 붙는지
    • 정리 속도: 부하가 끝난 뒤 빈 노드가 얼마나 깔끔하게 제거되는지

    실제로 써보니까 Karpenter는 워크로드 확장에 좀 더 민감하게 반응하는 느낌이 있었고, Cluster Autoscaler는 노드 그룹 구조를 잘 짜놨을 때 안정감이 있었습니다. 드디어 됐다! 싶은 순간이 오긴 오는데, 그 전에 로그와 이벤트를 많이 보게 됩니다 ㅎㅎ

    워크로드 확장 과정에서 Pending 파드가 노드 증설로 해결되는 Kubernetes 오토스케일링 이미지

    파드가 Pending 상태가 되었다가 새 노드가 생성되고 Running으로 전환되는 과정을 단계별로 보여주는 이미지 자리입니다.

    ⚠️ 제가 실제로 자주 겪었던 트러블슈팅 포인트

    이 섹션이 제일 중요할 수도 있습니다. 문서대로만 보면 금방 될 것 같지만, 현실은 그렇지 않더라고요.

    1. 파드는 Pending인데 노드가 안 늘어나는 경우

    • 리소스 요청값(requests)이 비현실적으로 큰지 확인합니다.
    • 노드 선택 조건(nodeSelector, affinity, taint/toleration)이 너무 빡빡한지 봅니다.
    • Cluster Autoscaler라면 대상 노드 그룹 태그와 범위를 확인합니다.
    • Karpenter라면 허용된 인스턴스 조건과 서브넷/보안 그룹 연결을 확인합니다.

    처음엔 저도 컨트롤러 문제인 줄 알았는데, 알고 보니 파드 스펙 자체가 너무 까다로운 경우가 꽤 있었습니다. 특히 GPU나 특정 아키텍처를 요구하는 워크로드는 더 그렇습니다.

    2. 노드는 늘어나는데 생각보다 비효율적인 경우

    이건 비용 문제로 바로 이어집니다. Cluster Autoscaler는 노드 그룹 단위로 움직이기 때문에, 작은 파드 몇 개 때문에 상대적으로 큰 노드가 붙는 상황이 생기곤 합니다. Karpenter도 제약 조건을 널널하게 열어두면 예상보다 다양한 노드가 만들어지곤 해요. 그래서 요청 리소스(requests) 튜닝이 정말 중요합니다.

    3. 축소(scale down)가 너무 늦거나 안 되는 경우

    • PodDisruptionBudget(PDB, 파드 중단 예산) 때문에 축소가 막힐 수 있습니다.
    • DaemonSet(데몬셋)과 로컬 스토리지 사용 여부도 확인해야 합니다.
    • 노드가 비어 보여도 eviction(축출) 조건 때문에 남아 있을 수 있습니다.

    이 부분은 진짜 많이 놓칩니다. 저도 한동안 왜 비용이 안 줄지 봤더니, 특정 시스템 파드가 노드 정리를 막고 있더라고요.

    검증과 결과: 무엇을 기준으로 비교해야 하나

    Karpenter vs Cluster Autoscaler를 비교할 때 단순히 “누가 더 빠르냐”만 보면 아쉽습니다. 운영에서는 아래 기준으로 보시는 걸 추천드립니다.

    검증 항목 확인 포인트
    확장 지연 시간 Pending 파드 발생 후 Running까지 걸리는 체감 시간
    리소스 적합도 워크로드 요구사항에 맞는 노드가 붙는지 여부
    운영 복잡도 노드 그룹/정책 관리 난이도, 변경 영향 범위
    비용 효율성 불필요한 큰 노드가 자주 붙는지, 축소가 잘 되는지
    장애 대응성 예외 상황에서 로그와 원인 파악이 쉬운지

    제가 여러 번 테스트하면서 느낀 결론은 이렇습니다.

    • 보수적이고 예측 가능한 운영이 중요하면 Cluster Autoscaler가 편합니다.
    • 유연한 워크로드 확장과 세밀한 자원 최적화가 중요하면 Karpenter가 매력적입니다.
    • 둘 다 잘 쓰려면 결국 파드 스펙, 요청 리소스, 스케줄링 제약조건을 먼저 정리해야 합니다.
    kubectl top pods -A
    kubectl top nodes
    kubectl get events --sort-by=.lastTimestamp
    kubectl describe node <node-name>

    이 명령어들만 잘 봐도 현재 Kubernetes 오토스케일링이 어디에서 막히는지 감이 꽤 옵니다. 대시보드만 보지 마시고 이벤트(event)와 describe 출력도 꼭 같이 보세요. 진짜 차이가 여기서 보이거든요.

    Karpenter vs Cluster Autoscaler 검증 결과를 보여주는 Kubernetes 대시보드 이미지

    노드 수 변화, 파드 Pending 해소, 리소스 사용률 등을 한눈에 보여주는 결과 검증용 대시보드 이미지 자리입니다.

    실무 선택 가이드: 그래서 무엇을 고르면 되냐

    질문을 많이 받는 부분이라 아주 현실적으로 정리해보겠습니다.

    1. 기존에 노드 그룹 체계가 잘 잡혀 있고 변경 리스크를 줄이고 싶다면 Cluster Autoscaler부터 가는 게 안전합니다.
    2. 새 클러스터를 설계하거나, 워크로드 종류가 다양하고 변동폭이 크다면 Karpenter를 적극 검토할 만합니다.
    3. 어떤 도구를 쓰든 HPA, requests/limits, affinity 정책을 먼저 정리하지 않으면 효과가 반감됩니다.
    4. 운영팀이 디버깅하기 쉬운 구조인지도 꼭 보셔야 합니다. 기술적으로 좋아 보여도 팀이 못 다루면 오래 못 갑니다.

    여기서 중요한 포인트! Karpenter vs Cluster Autoscaler의 승부는 기능표가 아니라 운영 방식에서 갈립니다. 홈랩에서 실험할 때도 그렇고 실제 서비스에서도 그렇고, 결국 오래 버티는 건 팀이 이해하고 통제할 수 있는 구조였습니다.

    정리와 FAQ

    정리하자면, Cluster Autoscaler는 익숙하고 보수적인 선택지입니다. Karpenter는 더 유연하고 워크로드 중심적인 선택지이고요. 어느 쪽이든 워크로드 확장의 핵심은 파드와 노드의 관계를 제대로 이해하는 데 있습니다. 저도 처음엔 단순히 “자동으로 늘려준다” 정도로 생각했었는데, 실제로 써보니까 오토스케일링은 설계의 문제더라고요.

    다음 글에서는 HPA와 VPA(Vertical Pod Autoscaler, 수직 오토스케일러)를 함께 붙였을 때 어떤 점을 조심해야 하는지 다뤄볼 예정입니다. 이전 글에서 다뤘던 Ingress(인그레스, 외부 트래픽 진입점)와 서비스 분리 구조를 같이 보시면 더 이해가 잘 되실 겁니다.

    자주 묻는 질문

    • Q. 둘을 동시에 써도 되나요?
      A. 환경과 목적에 따라 가능은 하지만, 노드 프로비저닝 책임이 겹치지 않도록 설계를 분명히 나누는 게 중요합니다.
    • Q. Kubernetes 오토스케일링에서 가장 먼저 봐야 할 건 뭔가요?
      A. 파드 requests/limits와 스케줄링 제약조건입니다. 여기서 꼬이면 어떤 오토스케일러를 써도 답답합니다.
    • Q. 초보 운영자에게는 뭐가 더 쉬운가요?
      A. 이미 노드 그룹 개념이 익숙하다면 Cluster Autoscaler가 더 이해하기 쉬운 편입니다. 다만 장기적으로는 Karpenter의 유연성이 매력적일 수 있습니다.
    Karpenter vs Cluster Autoscaler 핵심 차이를 요약한 비교 인포그래픽

    두 오토스케일러의 장단점, 추천 시나리오, 선택 기준을 한 장으로 정리한 마무리용 인포그래픽 자리입니다.

    ✅ 결론만 짧게 말하면 이렇습니다. 안정성과 익숙함이면 Cluster Autoscaler, 유연성과 최적화면 Karpenter입니다. 다만 둘 다 만능은 아니고, 결국 좋은 오토스케일링은 좋은 워크로드 설계에서 시작합니다. 이거 진짜 중요합니다.

  • [OpenStack] OpenStack Magnum Kubernetes 운영: 6개월 회고와 교훈

    [OpenStack] OpenStack Magnum Kubernetes 운영: 6개월 회고와 교훈

    OpenStack Magnum Kubernetes 운영: 6개월 회고와 교훈

    OpenStack Magnum Kubernetes 조합으로 클러스터를 6개월 정도 운영해보면, 처음 기대했던 포인트와 실제 운영 포인트가 꽤 다르다는 걸 느끼게 됩니다. 저도 처음엔 'OpenStack 위에서 Kubernetes를 좀 더 쉽게 만들 수 있겠네?' 정도로 접근했는데, 막상 돌려보니 편한 부분은 분명했고 반대로 운영자가 꼭 알아야 하는 제약도 꽤 있더라고요. 특히 사내 프라이빗 클라우드나 홈랩처럼 OpenStack 자원이 이미 깔린 환경에서는 Magnum 사용 후기를 많이 찾게 되는데, 실제로는 설치보다 운영이 더 중요했습니다. 이번 글은 OpenStack Magnum Kubernetes 환경을 6개월 운용하면서 느낀 점, 부딪힌 문제, 그리고 어떤 팀에 잘 맞는지 차분하게 정리한 회고입니다.

    혹시 이런 경험 있으신가요? 클러스터는 금방 만들었는데 업그레이드, 노드 교체, 네트워크 연결, 외부 로드밸런서 연동에서 갑자기 난도가 확 올라가는 순간 말이죠. 저도 딱 그랬습니다. 생성이 끝났다고 안심했는데, 운영은 또 다른 문제더라고요.

    Magnum, OpenStack, Kubernetes 워커 노드와 로드밸런서 관계를 보여주는 전체 구성도입니다.

    OpenStack Magnum이 여전히 의미 있는 이유

    쉽게 말해 Magnum은 OpenStack에서 COE(Container Orchestration Engine) 클러스터를 프로비저닝하는 서비스입니다. 과거 문서에는 Mesos나 Swarm도 함께 언급됐지만, 최근 공식 문서 기준으로는 사실상 Kubernetes 중심으로 보는 편이 맞습니다. OpenStack을 이미 쓰고 있다면 가상머신 인프라를 따로 만들고 그 위에 수동으로 kubeadm을 올리는 대신, OpenStack API와 템플릿 기반으로 클러스터 생성 흐름을 어느 정도 표준화할 수 있다는 점이 장점이거든요.

    제가 직접 써보니 이 지점이 꽤 중요했습니다. 인프라 팀 입장에서는 Nova, Neutron, Cinder, Octavia 같은 OpenStack 리소스 운영 경험이 이미 있잖아요. Magnum은 그 위에서 Kubernetes 클러스터를 조금 더 OpenStack스럽게 관리하게 해줍니다. 다만 완전한 Managed Kubernetes처럼 기대하면 실망할 수 있습니다. 제 경험상 Magnum은 '운영을 없애주는 도구'라기보다 'OpenStack 친화적인 클러스터 생성 자동화 계층'으로 이해할 때 만족도가 높았습니다.

    OpenStack Magnum 핵심 개념: Magnum, Cluster Template, Heat

    처음엔 이 구조가 꽤 헷갈렸는데, 큰 그림을 이해하고 나면 훨씬 수월해집니다. 여기서 중요한 포인트는 Magnum이 혼자 모든 걸 하는 게 아니라는 점입니다.

    • Magnum: Kubernetes 같은 COE 클러스터를 생성하고 관리하는 OpenStack 서비스입니다.
    • Cluster Template: 어떤 이미지, 네트워크, 플러그인, 노드 사양으로 클러스터를 만들지 정의하는 청사진입니다.
    • Heat: 전통적인 Magnum 배포에서 실제 인프라 스택을 구성하는 오케스트레이션 계층입니다. 장애를 파고들다 보면 결국 Heat 이벤트를 보게 되더라고요.
    • BayModel: 과거 명칭입니다. 최신 문서와 운영 맥락에서는 보통 Cluster Template 기준으로 보면 됩니다.

    한 줄로 요약하면 이렇습니다. Magnum이 요청을 받고, Cluster Template을 참고해 OpenStack 자원을 만들고, 전통적인 드라이버 환경에서는 Heat가 실제 스택 생성을 수행하는 구조입니다. 참고로 최근 공식 문서에서는 Heat 기반 드라이버가 deprecated로 안내되고 있어서, 운영 중인 환경이 어떤 드라이버를 쓰는지도 꼭 확인해두는 게 좋습니다.

    구성 요소 역할 운영할 때 봐야 할 포인트
    Magnum 클러스터 생성/삭제 요청 처리 API 상태, 템플릿 파라미터, 사용 중인 드라이버
    Heat 스택 생성과 자원 오케스트레이션 이벤트 로그, 실패 리소스 추적
    Neutron/Octavia 네트워크와 로드밸런서 외부 연결, 서비스 노출, 포트/보안그룹
    Kubernetes 워크로드 스케줄링과 운영 노드 상태, CNI, Ingress, 스토리지

    시작 전에 체크했던 항목들

    Magnum 사용 후기에서 잘 안 보이는데, 실제로는 사전 점검이 절반입니다. 저도 처음엔 클러스터 생성 명령부터 쳤다가 네트워크랑 이미지 때문에 한참 돌아갔거든요.

    1. OpenStack 서비스 상태 확인
      Magnum만 살아 있어도 되는 게 아니라 Keystone, Nova, Neutron, Glance, Heat가 기본적으로 안정적이어야 합니다.
    2. Kubernetes용 이미지 준비
      이미지 이름보다 더 중요한 건 클러스터 드라이버 요구사항입니다. 최근 공식 문서 기준으로는 Kubernetes용 이미지의 os_distro 메타데이터가 드라이버와 맞아야 하고, 기본 예시는 Fedora CoreOS 계열을 전제로 설명합니다.
    3. 외부 네트워크와 DNS 설계
      API 접근, 노드 egress, 이미지 pull 경로를 미리 봐야 합니다.
    4. Load Balancer 정책 확인
      마스터 API 엔드포인트와 Service 노출 방식이 Octavia와 어떻게 연결되는지 미리 확인해야 합니다.
    5. 스토리지 전략 결정
      Cinder 연동 방식을 어떻게 가져갈지, 동적 볼륨 전략을 초반부터 쓸지 미리 정해두는 게 좋습니다.

    OpenStack Magnum으로 Kubernetes 클러스터 만들기

    이제 실전입니다. 아래 예시는 홈랩과 테스트 환경에서 자주 확인하던 흐름을 기준으로 정리했습니다. 다만 배포판, 드라이버, OpenStack 릴리스에 따라 옵션 이름이나 권장 이미지가 달라질 수 있습니다. 그래서 핵심은 명령어를 외우는 것보다 어떤 리소스를 먼저 확인해야 하는지를 잡는 데 있습니다.

    1. 기본 리소스 확인

    openstack coe service list
    openstack network list
    openstack subnet list
    openstack image list
    openstack flavor list
    openstack keypair list

    여기서 네트워크, 서브넷, 이미지, flavor, keypair가 실제로 준비되어 있는지 먼저 봅니다. 이거 안 보고 바로 템플릿 만들면 높은 확률로 다시 돌아오게 됩니다.

    2. Cluster Template 생성

    openstack coe cluster template create k8s-template \
      --coe kubernetes \
      --image fedora-coreos-k8s \
      --external-network public \
      --fixed-network private-net \
      --fixed-subnet private-subnet \
      --flavor m1.medium \
      --master-flavor m1.medium \
      --docker-storage-driver overlay2 \
      --network-driver flannel \
      --volume-driver cinder \
      --floating-ip-enabled \
      --master-lb-enabled

    여기서 실수가 가장 많이 나왔습니다. 특히 external-network, fixed-network, image 조합이 안 맞으면 생성이 중간에 터지더라고요. 처음엔 Magnum 문제인 줄 알았는데, 파고 들어가면 Neutron 설정이나 이미지 메타데이터 문제인 경우가 많았습니다. 이미지 이름은 예시일 뿐이고, 실제 환경에서는 해당 드라이버가 요구하는 이미지 속성을 꼭 확인하셔야 합니다.

    OpenStack Magnum Cluster Template과 Kubernetes 리소스 매핑 이미지

    Cluster Template이 네트워크, 이미지, 플레버, 로드밸런서와 연결되는 흐름을 설명하는 다이어그램입니다.

    3. 클러스터 생성

    openstack coe cluster create k8s-prod-like \
      --cluster-template k8s-template \
      --master-count 1 \
      --node-count 3 \
      --keypair mykey

    테스트 환경에서는 master-count와 node-count를 보수적으로 시작하는 게 좋습니다. 처음부터 크게 만들면 실패 원인도 커지고, 디버깅 비용도 같이 올라갑니다. 저는 1 control plane, 2~3 worker부터 시작해서 네트워크와 스토리지가 안정적인지 먼저 봤습니다.

    4. cluster config 가져와서 접속

    mkdir -p k8s-prod-like-config
    openstack coe cluster config k8s-prod-like --dir k8s-prod-like-config
    export KUBECONFIG=$(pwd)/k8s-prod-like-config/config
    kubectl get nodes
    kubectl get pods -A

    이 단계는 환경에 따라 조금 다릅니다. 어떤 환경에서는 eval $(openstack coe cluster config <cluster-name>) 형태로 바로 접속하기도 하고, 어떤 환경에서는 생성된 config 파일을 KUBECONFIG로 잡아 쓰는 식이 더 명확했습니다. 생성 직후에는 CNI가 아직 완전히 올라오지 않은 경우도 있으니 몇 분 정도 여유를 두고 확인하는 게 좋습니다.

    5. 간단한 워크로드 배포

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: demo-nginx
      template:
        metadata:
          labels:
            app: demo-nginx
        spec:
          containers:
          - name: nginx
            image: nginx:stable
            ports:
            - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: demo-nginx
    spec:
      selector:
        app: demo-nginx
      ports:
      - port: 80
        targetPort: 80
      type: LoadBalancer
    kubectl apply -f demo-nginx.yaml
    kubectl get deploy,svc,pods

    이 단계에서 OpenStack Magnum Kubernetes 환경이 정말 내 환경에서 굴러가는지 감이 옵니다. Service가 외부 IP를 잘 받는지, Pod가 정상 스케줄링되는지, 노드 간 통신이 되는지까지 같이 볼 수 있거든요.

    6개월 운영하면서 좋았던 점

    장점은 분명합니다. 특히 인프라 팀이 OpenStack에 익숙할수록 체감이 큽니다.

    • 클러스터 생성 표준화: 템플릿 기반이라 환경 복제가 수월했습니다.
    • OpenStack 권한 체계와 잘 맞음: 프로젝트 단위 자원 분리가 익숙해서 운영 흐름이 덜 낯설었습니다.
    • 홈랩 실험에 좋음: kubeadm을 매번 손으로 치는 것보다 반복 실험이 편하더라고요.
    • 자원 가시성 확보: VM, 볼륨, 포트, 로드밸런서를 OpenStack 관점에서 같이 추적할 수 있었습니다.

    특히 여러 팀이 비슷한 Kubernetes 클러스터 운영 패턴을 가져가야 한다면 Cluster Template은 생각보다 강력합니다. 누가 만들어도 기본 뼈대가 크게 흔들리지 않거든요. 이건 실제로 꽤 편했습니다.

    운영하면서 부딪힌 문제와 트러블슈팅

    여기가 진짜 중요했습니다. OpenStack Magnum Kubernetes 글을 보면 설치 데모는 많아도, 운영 중 생기는 문제는 상대적으로 덜 보이더라고요. 그런데 결국 운영은 장애와 변경 관리 싸움이었습니다.

    1. 문제를 Kubernetes에서만 찾으면 늦습니다

    Pod가 안 뜨거나 Service 외부 노출이 안 되면 처음엔 kubectl만 보게 됩니다. 저도 그랬습니다. 그런데 실제 원인은 Heat 스택 실패, 보안그룹 규칙, Neutron 포트 상태, Octavia 리스너 문제인 경우가 적지 않았습니다.

    openstack stack list
    openstack stack resource list <stack-name>
    openstack stack event list <stack-name>

    여기서 중요한 포인트는 Magnum 장애 분석에는 Kubernetes와 OpenStack을 같이 보는 시야가 필요하다는 점입니다. 한쪽만 보면 원인을 놓치기 쉽습니다.

    2. 업그레이드 전략은 미리 정해야 합니다

    Kubernetes 클러스터 운영에서 가장 민감한 건 업그레이드입니다. Magnum이 있다고 해서 업그레이드가 마법처럼 쉬워지는 건 아니었습니다. 오히려 템플릿, 이미지, 애드온, CNI 버전 호환성을 같이 보게 되더라고요. 그래서 운영 중반부터는 '인플레이스 업그레이드에 집착하지 말고, 새 템플릿 기반 병행 클러스터를 검증한 뒤 전환하자' 쪽으로 사고를 바꿨습니다.

    조금 번거로워 보여도 결과적으로는 더 안전했습니다. 특히 사내 서비스나 장기 운영 워크로드가 얹힌 상태라면 더 그렇습니다.

    3. 네트워크 플러그인과 외부 노출은 가장 먼저 검증해야 합니다

    Ingress나 LoadBalancer 서비스가 기대대로 동작하지 않으면, 사용자 입장에서는 클러스터가 죽은 것처럼 보입니다. 실제로 써보니까 네트워크 드라이버와 OpenStack 네트워크 설계가 맞물리는 구간이 생각보다 까다로웠습니다.

    • Node 간 Pod 통신 확인
    • API 서버 접근 경로 확인
    • 외부 IP 할당 정책 확인
    • 보안그룹 인바운드/아웃바운드 확인

    처음엔 애플리케이션 문제인 줄 알았는데, 보안그룹 한 줄 빠진 거여서 허탈했던 적도 있습니다. 이런 삽질이 은근 많더라고요.

    4. 노드 장애 복구는 '재현 가능성'이 핵심입니다

    노드 하나가 망가졌을 때 수동 조치만으로 복구가 되면 당장은 편합니다. 그런데 그게 반복 가능하지 않으면 다음 장애 때 더 힘들어집니다. 저는 6개월 운영하면서 로그를 많이 남겼고, 결국에는 '노드 문제는 수동 응급조치보다 템플릿과 생성 절차 정비가 우선'이라는 결론을 내렸습니다.

    검증과 결과: 운영 기준으로 무엇을 확인했나

    클러스터가 떴다는 것과 운영 가능한 상태라는 것은 다릅니다. 저는 아래 순서로 검증했습니다.

    1. 노드 Ready 상태 확인
    2. CoreDNS, CNI, kube-proxy 같은 기본 시스템 Pod 확인
    3. LoadBalancer 서비스 외부 노출 확인
    4. Persistent Volume 동작 확인
    5. 재부팅 또는 노드 교체 후 상태 재확인
    kubectl get nodes
    kubectl get pods -A
    kubectl get svc -A
    kubectl get storageclass
    kubectl describe node <node-name>

    간단하지만 이 체크리스트가 꽤 유효했습니다. 6개월 동안 느낀 건, OpenStack Magnum Kubernetes 환경에서는 처음 생성 성공보다 반복 검증 성공이 훨씬 중요하다는 점입니다.

    OpenStack Magnum Kubernetes 클러스터 검증 결과 대시보드 이미지

    노드 Ready 상태, 시스템 Pod, 서비스 외부 IP 상태를 함께 보여주는 결과 확인 이미지입니다.

    운영 결과를 한 줄로 정리하면 이렇습니다. 소규모 프라이빗 클라우드나 내부 플랫폼 팀 환경에서는 충분히 의미가 있지만, 모든 운영 복잡도를 감춰주는 도구는 아니다. 이 기대치만 정확하면 꽤 만족스럽게 사용할 수 있습니다.

    어떤 환경에 잘 맞고, 어떤 경우엔 아쉬운가

    상황 Magnum 적합도 이유
    OpenStack 중심의 내부 인프라 팀 높음 기존 운영 지식과 연결되기 좋습니다.
    홈랩/검증 환경 높음 반복 생성과 실험에 유리합니다.
    완전관리형 서비스 기대 낮음 운영자가 알아야 할 영역이 여전히 넓습니다.
    빠른 버전 전환이 잦은 환경 중간 이미지, 템플릿, 네트워크 호환성 검증이 필요합니다.

    저도 처음엔 Magnum이 조금 더 많은 걸 알아서 해주길 기대했었습니다. 그런데 6개월 써보니 관점을 바꾸게 되더라고요. Magnum은 운영을 없애주는 도구가 아니라, OpenStack 안에서 Kubernetes 클러스터 운영의 출발점을 정리해주는 도구에 가깝습니다. 이 차이를 이해하면 만족도가 확 올라갑니다.

    OpenStack Magnum 도입 전후 Kubernetes 운영 방식 비교 이미지

    수동 kubeadm 방식과 Magnum 기반 운영 방식의 차이를 요약한 비교 인포그래픽입니다.

    정리와 다음 단계

    정리해보면, OpenStack Magnum으로 Kubernetes 클러스터 운영을 해보면서 얻은 교훈은 꽤 명확했습니다.

    • Magnum은 생성 자동화에 강점이 있습니다.
    • 운영 문제는 결국 OpenStack과 Kubernetes를 함께 봐야 풀립니다.
    • 업그레이드와 네트워크 검증은 초반 설계 단계에서 방향을 잡아야 덜 힘듭니다.
    • 작게 시작해서 반복 검증하는 방식이 가장 현실적이었습니다.

    지금 OpenStack 기반으로 클라우드 네이티브 환경을 고민하고 계시다면 Magnum은 충분히 검토할 만한 선택지입니다. 다만 Managed Kubernetes처럼 생각하면 실망할 수 있고, OpenStack 친화적인 클러스터 프로비저닝 계층으로 이해하면 훨씬 잘 맞습니다. 관련해서 이전에 정리한 OpenStack 네트워크 기본기 글이나 스토리지 구성 글도 함께 보면 이해가 훨씬 빨라집니다.

    다음 글에서는 이번 회고에서 살짝 언급했던 Ingress 구성, Cinder 기반 스토리지 연결, 그리고 운영 중 모니터링 포인트를 따로 묶어서 정리해보려 합니다. 이어서 보면 실제 운영 흐름이 더 선명하게 보이실 거예요.

    자주 묻는 질문 FAQ

    Magnum이 있으면 Kubernetes 운영이 쉬워지나요?

    일부는 맞고, 일부는 아닙니다. 클러스터 생성과 표준화는 확실히 편해집니다. 하지만 장애 분석, 네트워크, 업그레이드 전략은 여전히 운영자의 몫입니다.

    Magnum과 kubeadm 중 무엇이 더 낫나요?

    OpenStack 환경 표준화가 중요하면 Magnum이 편합니다. 반대로 세밀한 수동 제어가 더 중요하고 OpenStack 통합이 핵심이 아니라면 kubeadm 쪽이 더 단순하게 느껴질 수도 있습니다.

    6개월 운영 기준으로 가장 먼저 챙길 것은 무엇인가요?

    네트워크와 업그레이드 전략입니다. 이 두 가지를 초반에 대충 잡으면 나중에 운영 피로도가 크게 올라갑니다.

  • [Kubernetes] VPA vs HPA: 리소스 오토스케일링 최적화 전략 비교

    [Kubernetes] VPA vs HPA: 리소스 오토스케일링 최적화 전략 비교

    [Kubernetes] VPA vs HPA: 리소스 오토스케일링 최적화 전략 비교

    Kubernetes VPA HPA를 처음 비교할 때 가장 헷갈리는 지점이 바로 “뭘 늘리는 거지?”였습니다. Replica(레플리카, Pod 개수)를 늘리는 건지, 아니면 Pod 하나가 먹는 CPU/Memory(메모리) 요청값을 바꾸는 건지요. 저도 처음엔 HPA(Horizontal Pod Autoscaler, 수평 Pod 오토스케일링)만 걸어두고 끝난 줄 알았는데, 실제로 운영해보니 Pod 수는 늘어나도 개별 Pod 요청값이 너무 작아서 계속 Throttling(스로틀링, CPU 제한으로 인한 성능 저하)이 나는 경우가 있더라고요. 반대로 VPA(Vertical Pod Autoscaler, 수직 Pod 오토스케일링)를 무턱대고 적용했다가 재시작 타이밍 때문에 서비스가 흔들리는 경험도 있었습니다.

    그래서 이번 글에서는 Kubernetes VPA HPA를 비교 중심으로 정리해보겠습니다. 단순 개념 비교가 아니라, 오토스케일링 비교 관점에서 어떤 워크로드에 무엇이 맞는지, 그리고 리소스 최적화와 Pod 스케일링을 어떻게 나눠서 생각해야 하는지 실제 운영자 시선으로 풀어보겠습니다. 혹시 지금 클러스터에서 CPU는 남는데 응답 속도는 들쭉날쭉하고, 어떤 서비스는 OOMKilled(메모리 부족 종료)까지 난다면 이 주제가 꽤 중요하실 거예요.

    HPA는 Pod 개수를, VPA는 Pod 자원 요청값을 조정한다는 차이를 한눈에 보여주는 개요 이미지입니다.

    1. 왜 Kubernetes VPA HPA 비교가 중요한가

    쉽게 말해 HPA는 옆으로 늘리는 방식이고, VPA는 위로 키우는 방식이에요. 둘 다 오토스케일링이긴 한데 해결하는 문제가 달라요. 여기서 중요한 포인트가 있어요. 트래픽이 몰릴 때 모든 문제가 Pod 개수 부족 때문에 생기는 건 아니거든요. 어떤 앱은 싱글 Pod당 메모리를 더 줘야 안정적이고, 어떤 앱은 그냥 복제본을 여러 개 띄우는 게 훨씬 낫다니까요.

    • HPA: 평균 CPU 사용률이나 메모리, 혹은 Custom Metric(커스텀 메트릭), External Metric(외부 메트릭)을 기준으로 Replica 수를 조절해요.
    • VPA: Pod의 requests/limits 같은 리소스 설정을 추천하거나 자동 조정합니다.
    • 핵심 차이: HPA는 분산 처리에 강하고, VPA는 개별 Pod의 자원 부족 문제를 다루는 데 유리해요.

    제가 직접 해보니 웹 애플리케이션처럼 상태가 거의 없고 수평 확장이 쉬운 경우에는 HPA가 훨씬 직관적이었어요. 반면 배치 작업이나 메모리 사용량이 시간이 지나면서 조금씩 커지는 워크로드는 VPA의 추천값이 꽤 도움이 됐습니다. 이거 진짜 편하더라고요. 다만 자동 적용은 신중해야 했어요.

    2. 개념 정리: VPA vs HPA를 쉽게 말해보면

    2-1. HPA는 언제 쓰나

    HPA는 요청이 갑자기 몰리는 서비스에 잘 맞아요. 예를 들어 API 서버, 프론트엔드 백엔드, 이벤트 소비자를 여러 개 띄워 병렬 처리할 수 있는 구조라면 HPA가 기본 선택지가 되거든요. Metrics Server(메트릭 서버)나 Prometheus Adapter(프로메테우스 어댑터)와 함께 쓰는 경우가 많습니다.

    • 장점: 빠르게 Replica를 늘려 트래픽을 분산할 수 있어요.
    • 장점: 무상태(Stateless, 상태 비저장) 서비스에 특히 잘 맞습니다.
    • 주의: 시작 시간이 긴 앱은 스케일 아웃이 늦게 체감될 수 있어요.

    2-2. VPA는 언제 쓰나

    VPA는 “이 Pod에 CPU 100m만 준 게 애초에 잘못된 거 아닌가?” 같은 상황에서 정말 빛을 봐요. 실제 사용량을 보고 requests를 추천해주기 때문에, 운영자가 감으로 자원값을 넣던 습관에서 벗어나는 데 정말 도움이 돼요. 처음엔 이게 뭔가 싶었는데 추천 모드부터 보기 시작하면 생각보다 실용적이더라고요.

    • 장점: 과소 설정된 requests/limits를 바로잡는 데 좋아요.
    • 장점: 장기적으로 노드 자원 낭비를 줄이는 리소스 최적화에 유리해요.
    • 주의: 자원값 변경을 적용하는 과정에서 Pod 재시작이 개입될 수 있습니다.

    2-3. 한눈에 보는 오토스케일링 비교

    항목 HPA VPA
    무엇을 조절하나 Replica 수 Pod requests/limits
    잘 맞는 대상 웹/API, 무상태 서비스 배치, 메모리 민감 워크로드
    주요 지표 CPU, 메모리, 커스텀/외부 메트릭 실사용 리소스 기반 추천
    반응 방식 수평 확장/축소 수직 조정
    운영 포인트 급격한 부하 대응 초기 자원 설정 보정
    주의점 잘못된 메트릭 설계 시 오동작 적용 시 재시작 영향 가능

    정리하면 HPA는 트래픽 대응, VPA는 자원 설정 보정에 가깝습니다. 둘 중 하나만 정답이라기보다 워크로드 성격에 따라 고르는 문제예요.

    3. 실전 구현: HPA부터 적용해보겠습니다

    실제로 써보니까 대부분 팀은 HPA부터 시작하는 편이 안정적이었어요. 이유가 간단해요. 애플리케이션을 재시작하지 않고도 Replica 수 조절로 대응 가능한 경우가 많기 때문입니다.

    3-1. 전제 조건 확인

    1. 클러스터에 Metrics Server가 있어야 해요.
    2. Deployment에 requests 값이 어느 정도 합리적으로 들어가 있어야 합니다.
    3. readinessProbe(레디니스 프로브)와 livenessProbe(라이브니스 프로브)가 정리되어 있으면 더 좋아요.

    여기서 requests가 엉망이면 HPA 기준도 같이 흔들려버려요. 이 부분을 무시하고 들어가면 나중에 “왜 평균 CPU가 이상하지?” 하면서 삽질 좀 했습니다 ㅎㅎ

    3-2. 샘플 Deployment

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-api
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: demo-api
      template:
        metadata:
          labels:
            app: demo-api
        spec:
          containers:
            - name: demo-api
              image: nginx:stable
              resources:
                requests:
                  cpu: "100m"
                  memory: "128Mi"
                limits:
                  cpu: "500m"
                  memory: "512Mi"
              ports:
                - containerPort: 80

    3-3. HPA 리소스 생성

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: demo-api-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: demo-api
      minReplicas: 2
      maxReplicas: 10
      metrics:
        - type: Resource
          resource:
            name: cpu
            target:
              type: Utilization
              averageUtilization: 70
    kubectl apply -f deployment.yaml
    kubectl apply -f hpa.yaml
    kubectl get hpa
    kubectl describe hpa demo-api-hpa

    이 상태에서 부하 테스트를 걸면 평균 CPU 사용률을 기준으로 Replica가 늘어나요. 물론 바로 늘지 않는다고 당황하실 필요는 없습니다. 메트릭 수집 주기와 안정화 구간 때문에 약간의 시간차가 생기거든요.

    Kubernetes HPA가 Metrics Server 기반으로 Pod 스케일링하는 구성도

    Metrics Server에서 수집한 지표를 바탕으로 HPA가 Deployment Replica 수를 조정하는 구성 예시입니다.

    4. 실전 구현: VPA는 추천 모드부터 시작하는 게 안전합니다

    VPA는 개인적으로 처음부터 Auto 모드로 넣기보다 Recommendation(추천) 확인부터 시작하는 걸 권장해요. 저도 초반에는 자동 조정이 멋져 보여서 바로 적용하고 싶었는데, 운영 중 Pod 교체 타이밍을 무시하면 생각보다 거칠게 느껴질 수 있더라고요.

    4-1. VPA 샘플 리소스

    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: demo-api-vpa
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: demo-api
      updatePolicy:
        updateMode: "Off"
      resourcePolicy:
        containerPolicies:
          - containerName: demo-api
            controlledResources: ["cpu", "memory"]
    kubectl apply -f vpa.yaml
    kubectl describe vpa demo-api-vpa

    updateMode: "Off"로 두면 자동 반영은 하지 않고 추천값 위주로 볼 수 있어요. 운영 초반에는 이 방식이 훨씬 덜 위험합니다.

    4-2. 추천값을 어떻게 읽나

    보통 운영자가 보는 포인트는 이렇습니다.

    • 현재 requests가 실제 사용량보다 너무 작은가
    • 메모리 사용 패턴이 일정한가, 피크가 큰가
    • 권장값을 적용했을 때 노드 밀도(Node Density, 노드당 수용량)가 나빠지지 않는가

    제가 직접 해보니 CPU는 HPA가 어느 정도 흡수해주는데, 메모리는 오히려 VPA 추천이 더 실무적으로 도움이 되는 경우가 많았어요. 특히 Java 계열이나 캐시가 붙은 워크로드는 순간 피크보다 장기 패턴을 보는 게 중요하더라고요.

    5. 같이 쓰면 안 되는 건가요? 조합 전략이 핵심입니다

    이 질문 정말 많이 나와요. 결론부터 말하면 무조건 같이 쓰면 안 된다는 아니에요. 다만 같은 CPU/메모리 지표를 기준으로 HPA와 VPA가 동시에 서로를 흔드는 구조는 피하는 게 좋습니다. 이건 운영해보면 왜 위험한지 금방 느껴져요.

    조합 방식 추천 여부 이유
    HPA on CPU + VPA on CPU/Memory 자동 주의 requests 변경이 HPA 계산에 영향 주어 피드백 루프 가능
    HPA on Custom Metric + VPA on requests 추천 권장 역할 분리가 비교적 명확해요
    HPA만 사용 권장 무상태 웹 서비스 기본 선택지로 단순해요
    VPA 추천만 사용 권장 초기 자원 튜닝 단계에 안정적이에요

    즉, Pod 스케일링은 HPA가 맡고, VPA는 추천과 보정 역할로 두는 식이 현실적입니다. 특히 트래픽 기반 서비스라면 HPA를 메인으로 두고, VPA는 운영 관찰 도구처럼 활용하는 접근이 꽤 괜찮았어요.

    Kubernetes VPA HPA 함께 사용할 때의 권장 조합과 충돌 위험 다이어그램

    HPA와 VPA를 함께 사용할 때 역할을 분리하는 권장 패턴과 충돌 위험 구간을 시각화한 이미지입니다.

    6. ⚠️ 실제 운영에서 자주 만난 문제와 해결법

    6-1. HPA가 안 늘어나는 문제

    • 원인: Metrics Server 미설치 또는 메트릭 수집 실패
    • 원인: requests 값이 비현실적이라 CPU 사용률 계산이 왜곡돼요
    • 해결: kubectl top pod, kubectl describe hpa로 먼저 메트릭 상태 확인
    kubectl top pod
    kubectl describe hpa demo-api-hpa
    kubectl get apiservices

    저도 처음엔 애플리케이션 성능 문제인 줄 알고 로그만 뒤졌는데, 알고 보니 메트릭 자체가 비어 있던 적이 있었어요. 이런 건 진짜 허무합니다.

    6-2. VPA 적용 후 Pod 재시작 때문에 놀라는 문제

    • 원인: 자원값 변경을 적용하려면 기존 Pod 교체가 필요할 수 있거든요
    • 해결: 먼저 추천 모드로 충분히 관찰하고, PDB(PodDisruptionBudget, Pod 중단 예산)와 롤링 업데이트 전략 점검
    • 해결: 단일 Replica 서비스는 특히 조심해야 해요

    여기서 중요한 포인트! 단일 Pod 서비스에 VPA 자동 적용은 생각보다 부담이 커요. 실제로 써보니까 “자원은 맞아졌는데 순간 끊김이 생겼네?” 같은 상황이 나올 수 있더라고요.

    6-3. HPA와 VPA를 동시에 걸었더니 지표가 이상한 문제

    • 원인: VPA가 requests를 바꾸면 HPA의 utilization 계산 기준도 달라질 수 있거든요
    • 해결: HPA는 CPU 대신 QPS(Requests Per Second, 초당 요청 수)나 큐 길이 같은 Custom/External Metric 기반으로 분리 검토해요

    이 부분은 문서만 읽으면 감이 잘 안 오는데, 운영 그래프를 보면 이해가 돼요. 기준점이 움직이는 상태에서 자동화 둘이 같이 판단하니 안정적이지 않더라고요.

    7. 검증과 결과 확인: 무엇을 봐야 제대로 적용한 걸까

    오토스케일링 비교는 설정 파일만 보고 끝내면 안 돼요. 검증 항목을 꼭 정해두셔야 합니다.

    1. 부하 테스트 전후 평균 응답 시간 변화
    2. Replica 증가 시 에러율 변화
    3. OOMKilled 발생 여부
    4. 노드 자원 사용률과 Bin Packing(빈 패킹, 노드 자원 배치 효율)
    5. 스케일 아웃/인 빈도와 안정성
    kubectl get hpa -w
    kubectl get vpa
    kubectl top pod
    kubectl top node

    제가 홈랩에서 테스트했을 때도, HPA만 걸어둔 서비스는 트래픽 대응은 빨랐지만 requests가 너무 작게 잡힌 컨테이너는 CPU 제한에 자주 걸렸어요. 반대로 VPA 추천을 반영하고 나니 자원 낭비는 줄고 불안정성도 꽤 줄었습니다. 드디어 됐다! 싶은 순간이 오긴 하더라고요. 물론 그 전에 requests 값과 메트릭 파이프라인 때문에 몇 번 헤맸습니다.

    Kubernetes VPA HPA 적용 결과를 검증하는 모니터링 대시보드

    HPA와 VPA 적용 이후 Replica 변화, CPU/메모리 추이, 권장 자원값을 검증하는 모니터링 예시입니다.

    8. 어떤 워크로드에 무엇이 맞나: 실무 선택 기준

    • 웹/API 서버: HPA 우선. 빠른 Pod 스케일링이 중요해요.
    • 배치 작업: VPA 추천이 유용할 수 있어요. 작업 특성상 개별 Pod 자원량이 중요하거든요.
    • 메모리 민감 애플리케이션: VPA 추천으로 기준값 정리 후 신중히 반영
    • 큐 소비자/워커: HPA를 큐 길이나 지연 시간 기반으로 설계하면 좋습니다.
    • 단일 인스턴스성 서비스: VPA 자동 적용은 매우 조심해야 해요. 먼저 수동 조정 검토

    결국 Kubernetes VPA HPA는 경쟁 관계라기보다 역할이 다른 도구예요. 리소스 최적화가 우선인지, 트래픽 분산이 우선인지부터 정해야 선택이 쉬워집니다.

    9. 정리와 FAQ

    정리하면, HPA는 서비스 확장성에, VPA는 자원 적정화에 강합니다. 저는 운영 초기에 HPA로 안정적인 Pod 스케일링 구조를 먼저 만들고, 그다음 VPA 추천으로 requests/limits를 보정하는 순서를 권장해요. 이 흐름이 가장 덜 아프고, 실수했을 때 되돌리기도 쉽습니다.

    다음 글에서는 Cluster Autoscaler(클러스터 오토스케일러, 노드 수 자동 조절)까지 포함해서 노드 레벨 확장 전략도 다뤄볼 예정입니다. 이전 글에서 다룬 Ingress와 Observability(옵저버빌리티, 관측성) 구성이 되어 있으면 검증이 훨씬 수월해요.

    워크로드 유형에 따라 VPA와 HPA를 어떻게 선택할지 빠르게 판단할 수 있는 요약 인포그래픽입니다.

    자주 묻는 질문

    • Q. 둘 중 하나만 써야 하나요?
      A. 아니에요. 다만 같은 CPU/메모리 기준으로 동시에 자동 제어하는 구성은 주의가 필요합니다.
    • Q. 초보자는 무엇부터 시작하면 좋을까요?
      A. HPA부터 시작하고, VPA는 추천 모드로 관찰하는 접근이 가장 무난했어요.
    • Q. 리소스 최적화 목적이면 바로 VPA Auto로 가도 되나요?
      A. 운영 중 서비스라면 추천값 검증 후 단계적으로 가는 쪽이 안전합니다.
  • [k8s] Loki로 Kubernetes 로그 중앙화: 효율적인 모니터링 구축 가이드

    [k8s] Loki로 Kubernetes 로그 중앙화: 효율적인 모니터링 구축 가이드

    [k8s] Loki로 Kubernetes 로그 중앙화: 효율적인 모니터링 구축 가이드

    Kubernetes 환경을 운영하다 보면 결국 로그 때문에 한 번은 크게 삽질하게 됩니다. Pod(파드)가 재시작되면 이전 로그가 날아가고, 노드가 여러 대로 늘어나면 어디서 무슨 에러가 터졌는지 찾는 데 시간이 꽤 걸리거든요. 저도 홈랩과 실무 환경에서 비슷한 문제를 겪으면서 Loki Kubernetes 로그 구성을 진지하게 붙잡게 됐습니다. 처음엔 “그냥 kubectl logs로 보면 되지 않나?” 싶었는데, 운영 규모가 조금만 커져도 그 방식은 금방 한계가 오더라고요.

    그래서 이번 글에서는 Grafana Loki를 기준으로 Kubernetes 로그 수집과 중앙 집중형 로깅 구성을 어떻게 가져가면 좋은지, 제가 직접 해보면서 정리한 흐름으로 풀어보겠습니다. 너무 이론만 길게 가지 않고, 바로 써먹을 수 있는 형태로 설명드릴게요.

    Loki, Promtail, Grafana, Kubernetes 노드와 애플리케이션 로그 흐름을 한눈에 보여주는 전체 구성도입니다.

    Loki로 Kubernetes 로그를 모아야 하는 이유

    쉽게 말해 Loki는 로그를 저장하고 검색하기 위한 시스템입니다. 로그 전체를 무겁게 색인(indexing)하는 방식보다는, 메타데이터(label) 중심으로 다루는 접근이 특징이죠. 이게 왜 좋냐면, 운영 입장에서 필요한 로그를 비교적 효율적으로 모으고 찾는 데 유리하거든요.

    제가 처음 Loki를 붙여봤을 때 좋았던 점은 딱 세 가지였습니다.

    • Grafana(그라파나)와 궁합이 좋습니다. 로그 조회 화면이 익숙해서 진입 장벽이 낮더라고요.
    • Kubernetes 로그 수집 흐름을 만들기 편합니다. 특히 DaemonSet(데몬셋) 기반 수집기가 노드별 로그를 긁어오는 구조가 이해하기 쉬웠습니다.
    • 메트릭(metrics), 로그(logs), 추적(traces) 관측 체계를 한 방향으로 정리하기 좋습니다.

    물론 무조건 Loki만 정답은 아닙니다. Elasticsearch(엘라스틱서치) 계열이 더 맞는 조직도 있습니다. 다만 “복잡도를 조금 낮추면서 Kubernetes 로그를 중앙에서 보고 싶다”는 목적이라면 Loki는 꽤 현실적인 선택지에요.

    Grafana Loki 핵심 개념부터 먼저 잡아보겠습니다

    Loki, Promtail, Grafana 역할 구분

    구성요소 역할 운영 포인트
    Loki 로그 저장 및 조회 API 제공 라벨 설계가 중요합니다
    Promtail 노드의 로그 파일 수집 후 Loki로 전송 DaemonSet으로 배포하는 경우가 많습니다
    Grafana 로그 검색, 필터링, 시각화 처리 대시보드와 Explore 기능이 편합니다

    여기서 중요한 포인트! Promtail(프롬테일)이 하는 역할은 로그를 읽어 Loki로 보내는 수집기 일이죠. Kubernetes에서는 보통 컨테이너 로그가 노드 파일 시스템에 쌓이기 때문에, 노드마다 하나씩 떠 있는 DaemonSet이 그 로그를 읽는 구조를 많이 씁니다.

    라벨(Label)이 왜 중요할까요?

    Loki는 라벨 기반으로 로그를 찾습니다. 예를 들면 namespace, pod, container, app 같은 값들이죠. 실제로 써보니까 라벨을 너무 많이 붙이면 관리가 복잡해지고, 반대로 너무 적으면 검색이 답답하더라고요. 저도 처음엔 이것저것 다 넣었다가 쿼리가 지저분해져서 다시 정리했었습니다 ㅎㅎ

    • 자주 검색하는 기준만 라벨로 둡니다
    • 변동성이 큰 값은 라벨 남발을 피합니다
    • 운영팀이 실제로 찾는 축을 먼저 정합니다

    Kubernetes 로그 중앙화 구성 흐름

    전체 흐름은 생각보다 단순합니다.

    1. 애플리케이션 컨테이너가 표준 출력(stdout/stderr)으로 로그를 남깁니다.
    2. Kubernetes 노드가 해당 로그를 파일 형태로 보관합니다.
    3. Promtail이 노드에서 로그를 읽고 라벨을 붙입니다.
    4. Loki가 로그를 저장합니다.
    5. Grafana에서 검색하고 필터링합니다.

    이 구조가 좋은 이유는 애플리케이션 코드를 크게 건드리지 않아도 된다는 거거든요. 이미 컨테이너 표준 출력으로 로그를 잘 남기고 있다면, 인프라 쪽에서 수집 체계를 붙이기 좋습니다.

    실전 구현: Helm으로 Loki와 Promtail 배포하기

    이제 본격적으로 들어가 보겠습니다. 여기서는 많이 쓰는 Helm(헬름) 기반 예시로 설명드릴게요. 실제 운영 환경에서는 스토리지, 보존 기간, 인증, 멀티 테넌시 여부 등을 더 따져야 하지만, 처음 시작할 때는 흐름을 먼저 잡는 게 중요합니다.

    1. 네임스페이스 생성

    kubectl create namespace observability

    저는 보통 모니터링/로깅 계열 리소스를 따로 분리하려고 observability 같은 네임스페이스를 씁니다. 꼭 이 이름일 필요는 없어요.

    2. Helm 저장소 추가

    helm repo add grafana https://grafana.github.io/helm-charts
    helm repo update

    3. Loki values 파일 작성

    처음엔 기본값으로도 올라가긴 하는데, 최소한 배포 형태와 저장 방식은 눈으로 확인하고 가는 게 좋습니다.

    loki:
      auth_enabled: false
    
    singleBinary:
      replicas: 1
    
    gateway:
      enabled: true
    
    monitoring:
      dashboards:
        enabled: true
      rules:
        enabled: true

    테스트나 홈랩에서는 이렇게 단순하게 시작해도 됩니다. 다만 운영에서는 스토리지 백엔드와 고가용성 구성을 별도로 검토하셔야 합니다. 여기서 무작정 단일 인스턴스로 오래 끌고 가면 나중에 확장 시점에 다시 손이 많이 가더라고요.

    4. Loki 설치

    helm install loki grafana/loki -n observability -f loki-values.yaml

    5. Promtail values 파일 작성

    config:
      clients:
        - url: http://loki-gateway.observability.svc.cluster.local/loki/api/v1/push
    
      snippets:
        pipelineStages:
          - cri: {}
    
      positions:
        filename: /run/promtail/positions.yaml
    
      scrapeConfigs:
        - job_name: kubernetes-pods
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            - source_labels: [__meta_kubernetes_namespace]
              target_label: namespace
            - source_labels: [__meta_kubernetes_pod_name]
              target_label: pod
            - source_labels: [__meta_kubernetes_pod_container_name]
              target_label: container
            - source_labels: [__meta_kubernetes_pod_label_app]
              target_label: app

    이 부분이 핵심입니다. 어떤 Kubernetes 메타데이터를 라벨로 가져갈지 결정하는 구간이거든요. 제가 실제로 써보니까 namespace, pod, container, app 정도부터 시작하는 게 무난했습니다.

    Kubernetes 로그 수집을 위한 Promtail과 Grafana Loki 구성도

    Promtail DaemonSet이 각 노드의 컨테이너 로그 파일을 읽고 라벨을 붙여 Loki로 보내는 흐름을 설명하는 이미지입니다.

    6. Promtail 설치

    helm install promtail grafana/promtail -n observability -f promtail-values.yaml

    7. Grafana에서 데이터 소스 연결

    Grafana를 이미 쓰고 계신다면 Loki 데이터 소스를 추가하면 됩니다. 같은 클러스터 안에 있다면 서비스 이름으로 연결하는 경우가 많습니다.

    kubectl get svc -n observability

    Grafana Explore 화면에서 Loki를 선택하고 아래처럼 조회해보면 기본 파이프라인이 제대로 도는지 확인할 수 있어요.

    {namespace="default"}

    처음 이 쿼리로 로그가 딱 뜨는 순간, 드디어 됐다! 싶은 느낌이 있습니다. 로그 중앙화는 여기서부터가 시작이거든요.

    운영하면서 유용했던 로그 쿼리 예시

    Loki는 LogQL(로그쿼리언어) 형태로 조회합니다. 익숙해지면 꽤 편합니다.

    {namespace="production", app="nginx"}
    {namespace="production", pod=~"api-.*"}
    {container="backend"} |= "error"
    {app="payments"} |= "timeout"
    • |= 는 특정 문자열 포함 검색에 자주 씁니다
    • 정규식 매칭은 필요한 곳에만 씁니다
    • 라벨 기준 필터를 먼저 좁히고 문자열 검색을 붙이면 보기가 편합니다

    이 부분은 팀 내에서 공통 조회 패턴을 만들어두면 좋습니다. 예를 들어 장애 대응 때 “namespace 먼저, app 다음, error 문자열 마지막” 같은 식으로 말이죠.

    ⚠️ 실제로 많이 겪는 문제와 트러블슈팅

    여기서부터는 제가 꽤 자주 봤던 문제들입니다. 저도 처음엔 헷갈렸는데, 한 번 원리를 이해하고 나면 금방 잡힙니다.

    1. 로그가 아예 안 들어오는 경우

    • Promtail Pod가 정상 실행 중인지 확인합니다
    • Loki push URL이 올바른지 봅니다
    • 네트워크 정책(NetworkPolicy)으로 막히지 않았는지 확인합니다
    • 애플리케이션이 파일이 아니라 표준 출력으로 로그를 남기는지 확인합니다
    kubectl get pods -n observability
    kubectl logs -n observability daemonset/promtail
    kubectl logs -n observability deploy/loki

    2. 라벨이 너무 많아져서 조회가 복잡한 경우

    이건 정말 흔합니다. Kubernetes 메타데이터를 다 가져오고 싶은 마음이 들거든요. 근데 운영해보면 자주 안 쓰는 라벨은 오히려 방해가 됩니다. 중앙 집중형 로깅의 목적은 데이터를 많이 붙이는 게 아니라, 필요한 순간 빨리 찾는 데 있다는 점을 잊지 않는 게 중요합니다.

    3. 특정 Pod 로그만 유독 안 보이는 경우

    보통 라벨 매핑 문제이거나, 애플리케이션 로그 출력 방식 차이인 경우가 많았습니다. 사이드카(sidecar)를 쓰는 구조나 멀티 컨테이너 Pod에서는 컨테이너 이름 기준으로 먼저 좁혀보는 게 좋더라고요.

    4. 과거 로그를 오래 보관하고 싶은 경우

    이건 저장소 설계와 연결됩니다. 홈랩에서는 일단 짧게 가져가도 되는데, 운영에서는 보존 정책(retention policy)을 꼭 먼저 정하셔야 합니다. 로그는 쌓이는 속도가 생각보다 빠릅니다. 처음엔 괜찮다가 어느 날 저장소가 가득 차서 당황하는 경우가 생기거든요.

    검증: Loki Kubernetes 로그가 제대로 모이는지 확인하기

    설치가 끝났다고 바로 안심하면 안 됩니다. 꼭 검증 단계를 거치셔야 합니다. 저는 아래 순서로 확인합니다.

    1. 테스트용 Pod를 띄워 의도적으로 로그를 발생시킵니다.
    2. Promtail 로그에서 수집 관련 에러가 없는지 봅니다.
    3. Grafana Explore에서 namespace와 pod 기준으로 조회합니다.
    4. 문자열 필터로 원하는 로그를 찾을 수 있는지 확인합니다.
    kubectl run log-check --image=busybox --restart=Never -- /bin/sh -c 'i=0; while true; do echo "log-check-$i"; i=$((i+1)); sleep 5; done'

    그 다음 Grafana에서 아래처럼 조회합니다.

    {pod="log-check"}

    이렇게 테스트 로그가 보이면 기본 파이프라인은 정상입니다. 이후엔 에러 패턴, 특정 앱 로그, 운영 네임스페이스 로그를 차례로 점검하면 됩니다.

    Grafana Loki로 Kubernetes 로그를 조회하는 대시보드 이미지

    Grafana Explore 또는 대시보드에서 namespace, pod, app 라벨로 로그를 필터링한 결과 예시 이미지입니다.

    비교: Loki와 다른 로그 수집 접근의 차이

    항목 Loki 중심 구성 전통적 대용량 검색 중심 구성
    초기 진입 난이도 상대적으로 단순한 편 구성 요소가 많은 편
    Grafana 연동 매우 자연스러움 별도 연계 구성이 필요할 수 있음
    라벨 설계 중요도 매우 높음 상대적으로 검색 중심 접근 가능
    홈랩/소규모 시작 부담이 덜함 조금 무거울 수 있음

    이 표를 보면 감이 오실 겁니다. “빠르게 Kubernetes 로그 중앙화를 시작하고 싶다”면 Loki가 꽤 괜찮습니다. 반대로 아주 복잡한 검색 시나리오, 대규모 장기 보관 전략, 조직 표준 스택이 이미 정해져 있다면 다른 선택지가 더 맞을 수도 있어요.

    💡 운영 팁: 제가 나중에 꼭 챙기게 된 것들

    • 라벨 최소화: 처음부터 과하게 넣지 않습니다
    • 보존 정책: 저장소 사용량을 반드시 같이 봅니다
    • 대시보드 표준화: 팀이 자주 보는 쿼리를 정리합니다
    • 애플리케이션 로그 형식: JSON 로그 여부를 팀 기준으로 맞추면 훨씬 편합니다
    • 장애 대응 문서화: “로그가 안 보일 때 체크리스트”를 만들어두면 좋습니다

    특히 JSON 형태 로그를 쓰는 팀이라면 이후 파싱과 필드 추출도 훨씬 수월해집니다. Kubernetes 로그 수집을 이렇게 중앙화하면 메트릭 모니터링과 함께 관측성(Observability, 시스템 상태를 외부 신호로 이해하는 능력)이 한 단계 올라가는 걸 체감하실 겁니다.

    중앙 집중형 로깅 도입 전후 Loki Kubernetes 로그 운영 비교 이미지

    kubectl logs 중심 운영과 Loki 기반 중앙 집중형 로깅 운영의 차이를 한눈에 정리한 비교 이미지입니다.

    자주 묻는 질문

    Q1. Kubernetes 로그 수집은 꼭 Promtail이어야 하나요?

    꼭 그렇진 않습니다. 다만 Loki와 가장 자연스럽게 엮이는 수집기로 많이 사용됩니다. 시작 단계에서는 조합이 단순한 쪽이 유지보수에 유리하더라고요.

    Q2. Grafana Loki만 설치하면 끝인가요?

    아닙니다. 저장소, 보존 정책, 접근 제어, 대시보드 표준화까지 같이 봐야 운영 품질이 올라갑니다. 설치보다 운영 기준을 세우는 게 더 중요합니다.

    Q3. 중앙 집중형 로깅이 왜 필요한가요?

    장애 대응 속도가 달라집니다. 노드와 Pod를 옮겨 다니며 로그를 찾는 시간을 줄일 수 있거든요. 특히 여러 서비스가 동시에 얽히는 환경에서는 체감 차이가 큽니다.

    마무리: Loki Kubernetes 로그 구성, 처음엔 단순하게 시작하세요

    정리해보면 Loki Kubernetes 로그 구성의 핵심은 화려한 기능보다도, 어떤 로그를 어떤 라벨로 모을지를 먼저 정하는 데 있습니다. 저도 처음엔 설정 파일만 붙잡고 씨름했었는데, 결국 답은 단순하더라고요. 필요한 로그를 빨리 찾을 수 있느냐, 장애 때 팀이 같은 화면을 보고 이야기할 수 있느냐, 그게 제일 중요했습니다.

    처음 구축하신다면 작은 범위에서 시작해보세요. 특정 네임스페이스 하나, 서비스 하나부터 붙여보고, 쿼리 패턴을 정리한 다음 확장하는 방식이 훨씬 덜 힘듭니다. 실제로 써보니까 이게 가장 덜 삽질하는 길이었습니다. 혹시 지금 Grafana Loki 도입을 고민 중이시라면, 이번 주말 홈랩에서라도 한 번 꼭 올려보세요. 생각보다 금방 감이 옵니다.