13년차의 서버실

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

[태그:] 클러스터 운영

  • [Kubernetes] 클라이언트 비교: Lens, K9s, kubectl 선택 가이드

    [Kubernetes] 클라이언트 비교: Lens, K9s, kubectl 선택 가이드

    [Kubernetes] 클라이언트 비교: Lens, K9s, kubectl 선택 가이드

    Kubernetes 클라이언트 비교가 왜 중요하냐고 물으시면, 운영 환경이 조금만 커져도 답이 바로 나옵니다. 파드(Pod, 컨테이너 실행 단위) 하나 상태 보는 건 쉬운데, 네임스페이스(Namespace, 리소스 격리 단위) 여러 개를 오가고 로그를 보고 이벤트를 확인하고 배포 상태까지 챙기려면 Kubernetes 관리 도구 선택이 꽤 중요하거든요. 저도 처음엔 kubectl만 붙잡고 버텼었는데, 실제로 써보니까 Lens, K9s, kubectl은 서로 경쟁 관계라기보다 역할이 다른 도구에 더 가깝더라고요. 오늘은 제가 홈랩과 실무에서 겪었던 흐름을 바탕으로, 어떤 상황에서 무엇을 고르면 덜 삽질하는지 정리해보겠습니다.

    특히 이 글은 단순한 스펙 나열이 아니라, Kubernetes 클라이언트 비교 관점에서 실제 운영 감각을 중심으로 풀어볼 생각입니다. 혹시 여러분도 'GUI가 편하긴 한데 결국 터미널로 돌아오게 되던데요?' 같은 경험 있으신가요? 저도 딱 그랬습니다 ㅎㅎ

    Lens, K9s, kubectl이 각각 어느 레이어에서 관리 경험을 제공하는지 한눈에 보여주는 개요 이미지입니다.

    Kubernetes 클라이언트 비교, 먼저 큰 그림부터 보겠습니다

    쉽게 말해 이렇습니다.

    • kubectl: Kubernetes 공식 CLI(Command Line Interface, 명령줄 도구)입니다.
    • K9s: 터미널 기반 TUI(Text User Interface, 텍스트 UI)로 클러스터 상태를 빠르게 탐색하게 도와줍니다.
    • Lens: GUI(Graphical User Interface, 그래픽 화면 기반) 중심으로 리소스를 시각적으로 관리하기 좋습니다.

    여기서 중요한 포인트가 하나 있습니다. 세 도구 모두 결국 kubeconfig(Kubernetes 접속 설정 파일)를 기반으로 클러스터에 접근하는 경우가 많다는 점입니다. 즉, 접근 권한과 컨텍스트(context, 현재 대상 클러스터/네임스페이스 설정)가 핵심이고, 겉모습만 다르다고 생각하시면 이해가 빠릅니다.

    Lens, K9s, kubectl 차이점은 어디서 체감될까요?

    1. kubectl 사용법이 기본기가 되는 이유

    kubectl은 결국 기준점입니다. Deployment(디플로이먼트, 배포 리소스), Service(서비스, 네트워크 노출 리소스), Ingress(인그레스, 외부 트래픽 진입점) 같은 객체를 가장 정확하게 다루기 좋거든요. 자동화 스크립트나 CI/CD에서도 거의 기본 전제로 깔립니다.

    제가 직접 해보니 장애 상황에서는 결국 kubectl로 돌아오게 되더라고요. 이유는 단순합니다. 로그, describe, events, yaml 출력까지 가장 세밀하게 볼 수 있기 때문입니다.

    2. K9s가 빛나는 순간

    K9s는 '터미널 안에서 빠르게 둘러보는 맛'이 있습니다. 방향키와 단축키로 파드 이동하고, 로그 보고, 리소스 상태를 훑는 흐름이 상당히 빠릅니다. SSH로 바로 들어가서 점검하는 환경에서는 특히 강합니다. GUI가 없는 서버 점검 환경에서는 진짜 편하더라고요.

    3. Lens가 편한 이유

    Lens는 시각화가 좋습니다. 여러 리소스를 화면에서 관계적으로 보기가 쉽고, 초보자도 진입 장벽이 낮은 편입니다. 특히 처음 Kubernetes를 접하는 분들은 YAML만 보는 것보다 상태를 한 화면에서 확인하는 방식이 훨씬 이해가 빠를 수 있습니다.

    다만 GUI가 편하다고 해서 운영의 본질이 쉬워지는 건 아닙니다. 여기서 많이 헷갈리시더라고요. 화면은 친절하지만, 권한 문제나 리소스 의미를 모르면 결국 판단은 사람이 해야 하거든요.

    쿠버네티스 관리 도구 비교 표로 정리해보겠습니다

    항목 kubectl K9s Lens
    형태 CLI TUI GUI
    학습 난이도 초반엔 높음 중간 낮음
    속도 익숙하면 매우 빠름 탐색이 빠름 화면 기반 확인이 편함
    자동화 적합성 매우 높음 낮음 낮음
    로그/이벤트 심층 분석 강함 강함 보통 이상
    원격 SSH 환경 매우 적합 매우 적합 제한적
    입문자 친화성 낮음 중간 높음
    추천 대상 운영자, 자동화 담당자 터미널 선호 운영자 초보자, 시각적 관리 선호자

    Lens K9s 비교만 놓고 보면, 초반 적응은 Lens가 쉽고 장기 운영 효율은 K9s가 더 손에 붙는 경우가 많습니다. 하지만 kubectl이 빠지면 결국 한계가 옵니다. 이건 꽤 중요합니다.

    실전 구현: kubectl, K9s, Lens를 같은 흐름에서 써보기

    이론만 보면 감이 잘 안 오죠. 그래서 간단한 점검 흐름으로 묶어보겠습니다. 예시는 특정 배포가 정상인지 확인하는 상황입니다.

    1. 현재 컨텍스트와 네임스페이스를 확인합니다.
    2. 배포와 파드 상태를 봅니다.
    3. 문제가 있는 파드 로그와 이벤트를 확인합니다.
    4. 필요하면 GUI나 TUI에서 리소스 관계를 함께 봅니다.

    1. kubectl로 기본 점검하기

    kubectl config get-contexts
    kubectl config current-context
    kubectl get ns
    kubectl get deploy -A
    kubectl get pods -A -o wide

    처음엔 이게 뭔가 싶었는데, 사실 운영자는 이 네 줄만 자주 써도 클러스터 현재 상태를 꽤 빨리 파악할 수 있습니다. 특히 current-context를 먼저 보는 습관은 꼭 들이시는 걸 추천합니다. 잘못된 클러스터에 명령 넣는 사고, 생각보다 자주 납니다.

    2. 특정 애플리케이션 문제 좁혀가기

    kubectl get pods -n production
    kubectl describe pod myapp-abc123 -n production
    kubectl logs myapp-abc123 -n production --tail=100
    kubectl get events -n production --sort-by=.metadata.creationTimestamp

    실제로 써보니까 장애 대응에서 제일 중요한 건 '무엇이 실패했는가'보다 '어디서부터 실패했는가'를 빠르게 찾는 거였습니다. describe 결과에서 이벤트를 보고, 로그에서 에러 패턴을 보고, 리소스 부족인지 이미지 풀(image pull) 실패인지 분리하는 게 핵심입니다.

    Kubernetes 클라이언트 비교에서 kubectl과 K9s 사용 장면 이미지

    운영자가 터미널 기반으로 파드 상태를 점검하고 로그를 추적하는 실전 흐름을 보여주는 이미지입니다.

    3. K9s로 빠르게 탐색하기

    k9s

    K9s는 실행 자체는 단순합니다. 다만 익숙해지려면 머릿속에 동선이 생겨야 합니다. 보통 저는 이런 순서로 봤습니다.

    1. 네임스페이스를 맞춥니다.
    2. Pods 화면에서 재시작 횟수와 상태를 봅니다.
    3. 문제 파드를 선택해 로그와 describe 성격의 정보를 확인합니다.
    4. 리소스 사용량이나 관련 객체로 이동합니다.

    SSH 세션 하나에서 다 처리되는 게 장점입니다. 홈랩에서 VM 여러 대 만질 때는 이 방식이 정말 편했어요. 마우스 손 안 대도 되니까 흐름이 끊기지 않거든요.

    4. Lens로 시각적으로 확인하기

    Lens는 클러스터를 등록한 뒤 Workloads(워크로드), Pods, Events, Config, Networking 같은 메뉴를 통해 탐색하는 방식입니다. 제가 초급자 분들 온보딩할 때는 Lens를 먼저 보여드린 적이 많았습니다. '아, Kubernetes 안에서 이런 객체들이 연결되는구나'가 빨리 보이기 때문입니다.

    예를 들어 서비스 장애가 났을 때, 파드만 보는 게 아니라 서비스와 엔드포인트(endpoint), 인그레스까지 같이 봐야 할 때가 있거든요. 이때 GUI의 장점이 분명히 있습니다.

    Kubernetes 클라이언트 비교를 위한 GUI 관리 도구 화면 이미지

    워크로드, 서비스, 인그레스 관계를 시각적으로 확인하는 GUI 관리 흐름을 표현한 이미지입니다.

    어떤 사람에게 어떤 도구가 맞을까요?

    kubectl이 맞는 경우

    • 운영 자동화와 스크립트 작업이 많습니다.
    • CI/CD 파이프라인과 연동해야 합니다.
    • 장애 원인을 깊게 파고드는 편입니다.
    • Kubernetes 클라이언트 도구의 기준 문법을 익히고 싶습니다.

    K9s가 맞는 경우

    • 터미널 작업이 익숙합니다.
    • 원격 서버나 SSH 환경에서 자주 일합니다.
    • 클러스터 탐색 속도를 중요하게 봅니다.
    • 마우스보다 키보드가 편합니다.

    Lens가 맞는 경우

    • Kubernetes 입문 단계입니다.
    • 팀원들과 화면 공유하며 상태를 설명해야 합니다.
    • 리소스 관계를 시각적으로 보는 게 편합니다.
    • 한눈에 파악되는 대시보드 느낌을 선호합니다.

    ⚠️ 실제로 자주 겪는 문제와 해결 방법

    여기서는 제가 삽질 좀 했던 부분을 솔직하게 적어보겠습니다. 이런 건 문서보다 경험에서 더 빨리 배우게 되더라고요.

    1. 컨텍스트(context) 착각

    가장 위험합니다. 개발 클러스터인 줄 알고 운영 클러스터를 보고 있던 적, 저도 있었습니다. 다행히 큰 사고는 안 났지만 식은땀이 나더라고요.

    kubectl config current-context
    kubectl config view --minify

    명령 전후로 현재 컨텍스트를 확인하는 습관이 중요합니다. 특히 여러 kubeconfig를 합쳐 쓰는 환경이라면 더 그렇습니다.

    2. 권한 문제(RBAC, Role-Based Access Control)

    Lens든 K9s든 kubectl이든, 권한이 없으면 안 보이거나 실패합니다. GUI가 예쁘게 보여도 백엔드는 결국 API 서버 권한 체크를 거치거든요.

    kubectl auth can-i get pods -n production
    kubectl auth can-i list secrets -n production

    화면 문제처럼 보이는데 사실은 권한 문제인 경우가 꽤 많습니다. 이거 진짜 자주 나옵니다.

    3. 로그만 보고 끝내는 실수

    처음엔 로그만 파봤었는데, 나중에 보니 이벤트(event) 쪽이 더 직접적인 경우가 많았습니다. 예를 들어 스케줄링 실패, 이미지 풀 실패, 프로브(probe) 실패 같은 건 이벤트에 힌트가 잘 남거든요.

    4. GUI 의존도가 너무 높아지는 문제

    Lens가 편해서 계속 쓰다 보면, 막상 터미널만 가능한 환경에서 당황할 수 있습니다. 그래서 제 추천은 이렇습니다. 입문은 Lens, 숙련은 K9s, 기준은 kubectl. 이 조합이 제일 덜 흔들렸습니다.

    검증: 실제로 무엇이 더 빨랐나?

    정량 벤치마크처럼 수치를 들이대고 싶진 않습니다. 확실하지 않은 숫자는 오히려 판단을 흐리거든요. 대신 체감 기준으로 말씀드리면 이렇습니다.

    • 처음 구조를 이해하는 속도는 Lens가 빨랐습니다.
    • 운영 중 다수 리소스를 훑는 속도는 K9s가 좋았습니다.
    • 정확한 원인 분석과 재현 가능한 절차 정리는 kubectl이 가장 강했습니다.

    제가 홈랩에서 여러 네임스페이스를 오가며 점검할 때도 비슷했습니다. 처음 상태 확인은 K9s나 Lens가 편했는데, 최종 확인 커맨드와 기록은 결국 kubectl로 남기게 되더라고요. 여기서 중요한 포인트! 빠른 확인 도구와 정확한 제어 도구는 다를 수 있습니다.

    Kubernetes 클라이언트 비교 결과를 정리한 운영 대시보드 이미지

    파드 상태, 이벤트, 로그 확인 결과가 정리된 운영 검증 화면을 묘사한 이미지입니다.

    정리: 제 선택은 하나가 아니라 조합입니다

    제 결론은 꽤 명확합니다. Kubernetes 클라이언트 비교를 할 때 하나만 고르려고 하면 오히려 답이 꼬입니다. kubectl은 반드시 익혀야 하고, 그 위에 K9s 또는 Lens를 얹는 방식이 가장 현실적입니다.

    • 초보자라면: Lens로 구조를 익히고 kubectl 사용법을 함께 배우세요.
    • 터미널 친화적 운영자라면: K9s와 kubectl 조합이 오래 갑니다.
    • 자동화까지 고려한다면: kubectl 중심 사고는 필수입니다.

    저도 처음엔 '어차피 GUI 있으면 되지 않나?' 싶었는데, 실제로 써보니까 클러스터를 깊게 이해할수록 kubectl 비중이 커졌습니다. 반대로 팀 협업이나 빠른 시각 확인은 Lens가 좋았고요. K9s는 그 사이에서 정말 실용적이었습니다. 이 셋은 배척할 대상이 아니라, 상황에 따라 꺼내 쓰는 공구함에 더 가깝습니다.

    세 도구의 장단점과 추천 대상, 사용 시나리오를 한 장으로 요약한 비교 인포그래픽 이미지입니다.

    FAQ: 많이 받는 질문

    Q1. Kubernetes 입문자는 무엇부터 시작하면 좋을까요?

    Lens로 객체 관계를 익히면서 kubectl 기본 명령을 같이 익히는 걸 추천합니다. 시각적 이해와 명령 기반 이해를 같이 가져가야 오래 갑니다.

    Q2. Lens K9s 비교에서 더 운영자다운 도구는 무엇인가요?

    터미널에 익숙하다면 K9s가 더 손에 붙을 가능성이 큽니다. 다만 팀 내 공유와 설명은 Lens가 더 편할 수 있습니다.

    Q3. kubectl만으로도 충분한가요?

    충분합니다. 다만 충분하다는 것과 편하다는 것은 다릅니다. 그래서 많은 분들이 K9s나 Lens를 보조 도구로 함께 씁니다.

    마무리

    오늘은 Kubernetes 클라이언트 비교라는 주제로 Lens, K9s, kubectl을 함께 봤습니다. 정답은 사람마다 조금 다르지만, 기준점은 분명합니다. 제어와 자동화는 kubectl, 빠른 탐색은 K9s, 시각적 이해는 Lens입니다. 이 프레임만 잡아도 도구 선택이 훨씬 쉬워집니다.

    다음 글에서는 kubectl 사용법을 조금 더 실전적으로 파서, 운영자가 자주 쓰는 조회 명령과 로그/이벤트 확인 루틴을 따로 정리해볼 예정입니다. 이전 글에서 다뤘던 홈랩 클러스터 구성 내용과 함께 보시면 더 이해가 잘 되실 겁니다. 혹시 지금 어떤 도구를 메인으로 쓰고 계신가요? 써보면 취향도 분명히 갈리더라고요.

  • [Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석

    [Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석

    [Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석

    Karpenter 프로비저닝 실패 때문에 새 파드(Pod, 쿠버네티스에서 배포되는 최소 실행 단위)는 계속 Pending 상태인데, 오토스케일링(auto scaling, 부하에 따라 자동으로 늘고 줄어드는 기능)은 움직이지 않고, 로그만 한참 들여다보셨던 적 있으신가요? 저는 홈랩이든 업무 환경이든 이런 상황을 몇 번 겪었는데요. 처음엔 “분명 노드가 부족한데 왜 안 뜨지?” 싶었고, 실제로는 Karpenter 설정 자체보다 IAM(Role, 권한 역할), 서브넷(Subnet, 네트워크 구간) 태그, 인스턴스 제약 조건 같은 바깥 조건에서 막히는 경우가 많더라고요. 이번 글에서는 제가 실제로 디버깅할 때 밟는 순서대로, Karpenter 프로비저닝 실패를 어떻게 좁혀가는지 정리해보겠습니다.

    특히 Kubernetes 노드 문제 해결이나 Karpenter 디버깅이 익숙하지 않은 분들은, 무작정 로그부터 보는 것보다 “스케줄링 실패 → 프로비저닝 판단 → 클라우드 리소스 생성 → 노드 조인(join, 클러스터 참여)” 이 흐름으로 보면 훨씬 덜 헷갈리실 거예요. 저도 처음엔 이걸 한 덩어리로 봐서 삽질 좀 했습니다 ㅎㅎ

    Karpenter 프로비저닝 실패 흐름을 보여주는 Kubernetes 아키텍처 다이어그램

    Karpenter 프로비저닝 실패가 어느 단계에서 발생하는지 한눈에 보여주는 개요 이미지입니다.

    Karpenter 프로비저닝 실패를 볼 때 먼저 이해해야 할 흐름

    쉽게 말해 Karpenter는 “스케줄되지 못한 워크로드가 있네? 그럼 조건에 맞는 노드를 하나 띄워보자” 하고 판단하는 컴포넌트입니다. 여기서 중요한 포인트는, 단순히 노드를 많이 만드는 도구가 아니라 스케줄링 제약 조건을 해석해서 그에 맞는 노드를 계산한다는 점입니다.

    문제가 나는 지점은 보통 4군데입니다

    1. 파드 스케줄링 조건이 너무 빡빡한 경우
      예: nodeSelector, affinity, toleration, resource request가 충돌합니다.
    2. Karpenter가 적절한 노드 후보를 계산하지 못하는 경우
      예: 인스턴스 타입 제약, capacity type, 아키텍처 제약이 과합니다.
    3. 클라우드 리소스 생성 단계에서 실패하는 경우
      예: IAM 권한, 보안 그룹(Security Group), 서브넷 태그, 용량 부족 등이 걸립니다.
    4. 노드는 생성됐지만 클러스터에 조인하지 못하는 경우
      예: 부트스트랩(user data), 인증, 네트워크 경로 문제가 있습니다.

    이 네 구간만 분리해서 봐도 디버깅 난이도가 확 내려갑니다. 실제로 써보니까 “Karpenter가 고장났다”기보다는, 주변 조건 하나가 꼬여서 연쇄적으로 실패하는 경우가 더 많았습니다.

    Kubernetes 노드 문제 해결을 위한 기본 점검 순서

    제가 현장에서 가장 먼저 하는 건 거창한 분석이 아니라 증상 분리입니다. 파드가 문제인지, Karpenter 컨트롤러(controller, 제어 루프) 판단이 문제인지, 아니면 클라우드 API 호출이 막히는지부터 확인해야 하거든요.

    1. Pending 파드부터 확인

    kubectl get pods -A
    kubectl describe pod <pod-name> -n <namespace>

    여기서 꼭 보는 건 Events입니다. 예를 들어 다음 같은 메시지가 보이면 방향이 잡힙니다.

    • 0/3 nodes are available: 기존 노드에 스케줄이 안 되는 상황입니다.
    • Insufficient cpu 또는 Insufficient memory: 리소스 부족입니다.
    • node(s) had untolerated taint: 톨러레이션(toleration, 테인트 허용 설정) 문제입니다.
    • node affinity/selector mismatch: 파드 조건이 너무 좁습니다.

    혹시 여기서 이미 affinity나 nodeSelector 충돌이 보이면, Karpenter 이전에 워크로드 정의부터 손봐야 합니다. 이걸 놓치면 Karpenter 로그만 하루 종일 보게 되거든요.

    2. Karpenter 로그 확인

    kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=200
    kubectl get events -A --sort-by=.lastTimestamp

    로그에서는 이런 식의 단서를 찾습니다.

    • 프로비저닝 후보를 계산했는지
    • 요구사항(requirements) 때문에 제외된 인스턴스 타입이 있는지
    • 클라우드 제공자(provider) 호출에서 에러가 나는지
    • 생성 이후 노드 등록(register)이 안 되는지

    제가 직접 해보니, 로그를 길게 읽는 것보다 에러 키워드를 먼저 잡는 게 훨씬 빨랐습니다. 예를 들면 access denied, no subnets found, insufficient capacity, launch template 관련 문구 같은 것들이요.

    3. 노드 생성 여부와 조인 여부 분리

    kubectl get nodes
    kubectl get nodeclaims
    kubectl get nodepools
    kubectl describe nodeclaim <name>

    환경에 따라 리소스 이름은 다를 수 있지만, 핵심은 같습니다. 클라우드 인스턴스는 떴는데 노드가 안 보이는지, 아니면 인스턴스 자체가 생성되지 않았는지를 나눠서 봐야 합니다. 이 차이가 엄청 큽니다.

    Karpenter 디버깅을 위한 파드 이벤트와 로그 비교 이미지

    파드 이벤트와 Karpenter 로그를 같이 놓고 보면 어디서 막히는지 훨씬 빨리 찾을 수 있습니다.

    실전 사례: Karpenter 디버깅을 이렇게 풀었습니다

    이건 제가 자주 보는 전형적인 패턴입니다. 특정 워크로드가 배포된 뒤 Pending 상태가 길게 이어졌고, 클러스터 오토스케일링 오류 분석이 필요했던 상황이었습니다. 기존 노드는 여유가 없었고, Karpenter가 새 노드를 올려줘야 정상인데 아무 변화가 없었죠.

    증상

    • 파드는 Pending 상태 유지
    • Karpenter는 동작 중
    • 기존 노드는 리소스 부족
    • 새 노드는 추가되지 않음

    제가 먼저 확인한 파드 스펙

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-workload
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: sample-workload
      template:
        metadata:
          labels:
            app: sample-workload
        spec:
          nodeSelector:
            kubernetes.io/arch: amd64
          tolerations:
            - key: "workload-type"
              operator: "Equal"
              value: "batch"
              effect: "NoSchedule"
          containers:
            - name: app
              image: nginx:stable
              resources:
                requests:
                  cpu: "1"
                  memory: "1Gi"

    겉으로 보면 큰 문제 없어 보이죠. 저도 처음엔 워크로드 쪽은 괜찮다고 생각했었습니다. 근데 여기서 Karpenter 쪽 제약과 같이 봐야 했습니다.

    확인한 Karpenter 측 제약 예시

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: general
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values:
                - amd64
            - key: kubernetes.io/os
              operator: In
              values:
                - linux
            - key: karpenter.sh/capacity-type
              operator: In
              values:
                - spot

    여기서 문제는 단순 설정 문법이 아니었습니다. 워크로드는 특정 톨러레이션을 요구하고, NodePool은 spot만 허용하며, 실제 가용 영역이나 계정 조건상 적절한 인스턴스 풀이 너무 좁아진 상태였던 거죠. 게다가 서브넷 태그도 일부 누락돼 있었습니다. 결국 Karpenter 입장에서는 만들 수 있는 노드 후보가 사실상 거의 없었던 셈입니다.

    정리하면 원인은 두 개였습니다

    구분 문제 내용 영향
    네트워크 일부 서브넷 태그 누락 적절한 서브넷 탐색 실패
    용량 제약 spot만 허용하고 요구사항이 좁음 프로비저닝 후보 부족

    이런 경우 진짜 헷갈립니다. 로그에는 하나의 에러만 크게 보일 때도 있지만, 실제론 조건이 두세 개 겹쳐서 실패하는 경우가 많거든요.

    제가 적용했던 점검 명령어와 확인 포인트

    아래 명령어들은 특정 클라우드에 종속되지 않게, 쿠버네티스 관점에서 먼저 확인할 수 있는 것들입니다. 이 순서대로 보면 꽤 안정적으로 원인을 줄여갈 수 있었습니다.

    1. 파드 이벤트 확인
    2. Karpenter 로그에서 프로비저닝 시도 여부 확인
    3. NodePool/NodeClaim 조건 확인
    4. 생성된 노드 존재 여부 확인
    5. 클라우드 콘솔 또는 API에서 인스턴스 생성 실패 원인 확인
    kubectl describe pod <pod-name> -n <namespace>
    kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --since=30m
    kubectl get nodepools -o yaml
    kubectl get nodeclaims -o yaml
    kubectl get nodes -o wide

    여기서 중요한 포인트! Karpenter 디버깅은 “쿠버네티스 안쪽 정보”와 “클라우드 바깥 정보”를 같이 봐야 끝이 납니다. 쿠버네티스 쪽만 보면 “왜 안 되지?”에서 끝나고, 클라우드 쪽만 보면 “인스턴스가 왜 안 뜨지?”로 끝나더라고요.

    Karpenter 프로비저닝 실패 원인인 NodePool 제약 조건 다이어그램

    NodePool 제약 조건이 겹칠수록 프로비저닝 가능한 후보가 급격히 줄어드는 상황을 설명하는 이미지입니다.

    ⚠️ 실제로 많이 만나는 Karpenter 프로비저닝 실패 원인

    이 섹션은 제가 여러 번 겪으면서 “아, 이건 체크리스트로 빼야겠다” 싶었던 것들입니다. 운영 중인 분들이라면 특히 공감하실 거예요.

    1. 파드 요구사항이 너무 강한 경우

    • nodeSelector가 특정 아키텍처나 라벨만 강제
    • podAntiAffinity가 과도하게 설정됨
    • requests가 커서 맞는 노드가 매우 제한적
    • toleration이 없어서 tainted node에 못 올라감

    처음엔 Karpenter가 노드를 안 만드는 줄 알았는데, 실제론 만들어도 스케줄이 안 맞는 상태라 계산상 제외되는 경우가 있었습니다.

    2. NodePool 요구사항이 좁은 경우

    • 특정 인스턴스 계열만 허용
    • spot만 허용
    • 특정 zone만 허용
    • 아키텍처와 운영체제 조건이 불필요하게 제한됨

    이건 비용 최적화하려다 자주 생깁니다. 저도 spot 위주로 예쁘게 짜보려다가, 정작 트래픽이 몰릴 때 후보가 없어서 자동 확장이 안 된 적이 있었거든요. 그래서 운영 환경에서는 제약은 최소화하고, 꼭 필요한 정책만 남기는 쪽이 더 안전했습니다.

    3. 클라우드 리소스 검색 실패

    • 서브넷 태그 누락
    • 보안 그룹 태그 누락
    • IAM 권한 부족

    이 부분은 쿠버네티스 YAML만 아무리 봐도 안 나옵니다. 특히 태그 기반 디스커버리(discovery, 자동 탐색)를 쓰는 경우라면, 네트워크 리소스가 올바르게 식별되는지 꼭 확인하셔야 합니다.

    4. 노드 생성 후 조인 실패

    • 부트스트랩 스크립트 문제
    • 클러스터 API 엔드포인트 접근 문제
    • 노드 인증 관련 설정 누락

    이 경우는 인스턴스는 생성되는데 kubectl get nodes에 안 보입니다. 그래서 “Karpenter가 아무것도 안 했다”고 오해하기 쉬워요. 근데 사실 한 단계는 진행된 상태인 거죠.

    문제를 줄이기 위한 운영 팁

    실제로 써보니까 아래 네 가지가 제일 효과 있었습니다.

    • NodePool 제약을 처음부터 너무 촘촘하게 만들지 않기
    • 파드 requests/limits를 현실적으로 설정하기
    • 서브넷, 보안 그룹, IAM 같은 외부 조건을 체크리스트화하기
    • 이벤트와 로그를 함께 보기

    특히 Kubernetes 노드 문제 해결이 필요한 상황에서 오토스케일링 오류 분석은 재현이 어려운 경우가 많아서, 문제가 생겼을 때 바로 볼 수 있는 로그 수집 체계를 미리 만들어두는 게 좋습니다. 이전 글에서 다뤘던 클러스터 이벤트 정리 방식이 있다면 같이 묶어보셔도 좋고요. 이 부분은 다음 글에서 더 깊게 다뤄볼 예정입니다.

    검증: 수정 후 무엇을 확인했나

    저는 설정을 손본 뒤 항상 세 가지를 확인합니다. 단순히 노드가 떠도 끝이 아니거든요.

    1. Pending 파드가 실제로 Running으로 전환되는지
    2. 새 노드가 기대한 라벨과 용량으로 조인되는지
    3. 불필요하게 과한 스케일아웃이 일어나지 않는지
    kubectl get pods -A -w
    kubectl get nodes --show-labels
    kubectl top nodes

    문제가 해결되면 보통 흐름이 이렇게 바뀝니다.

    • Pending 파드 발생
    • Karpenter가 새 노드 계산 및 생성
    • 노드가 클러스터에 조인
    • 파드가 새 노드에 스케줄됨

    드디어 됐다! 하는 순간이 여기입니다. 특히 오래 Pending이던 워크로드가 새 노드에 바로 올라가는 걸 보면, 이거 진짜 편하더라고요. 다만 성공했다고 끝내지 말고, 왜 실패했는지 기록해두는 게 다음 장애 때 훨씬 큰 도움이 됩니다.

    Karpenter 프로비저닝 실패 해결 후 Pending 파드가 Running으로 전환된 결과 이미지

    수정 전후를 비교해 Pending 파드가 Running으로 바뀌는 결과 확인 이미지입니다.

    정리: Karpenter 디버깅은 순서가 전부입니다

    Karpenter 프로비저닝 실패를 잡을 때 가장 중요한 건, 증상을 한 번에 해석하려고 하지 않는 겁니다. 저도 처음엔 로그 한 줄에서 답을 찾으려 했는데, 실제론 파드 조건, NodePool 제약, 클라우드 리소스 검색, 노드 조인 이 네 단계를 분리해서 봐야 풀리더라고요.

    혹시 지금도 Kubernetes 노드 문제 해결 때문에 막혀 계시다면, 아래 체크리스트부터 차근차근 보시면 좋겠습니다.

    • 파드 Events에 스케줄링 실패 원인이 보이는가
    • Karpenter가 실제로 프로비저닝을 시도했는가
    • NodePool 제약이 과하지 않은가
    • 서브넷, 보안 그룹, IAM 조건이 맞는가
    • 생성된 노드가 클러스터에 정상 조인하는가

    이 순서만 익혀도 Karpenter 디버깅 속도가 꽤 빨라집니다. 저도 처음엔 헷갈렸는데, 몇 번 정리해두고 나니 장애 대응 시간이 확 줄었습니다. 마무리 전에 요약 하나 더 남겨볼게요.

    Karpenter 프로비저닝 실패 디버깅 체크리스트 인포그래픽

    Karpenter 프로비저닝 실패 원인과 대응 순서를 한 장으로 정리한 요약 이미지입니다.

    FAQ: 자주 헷갈리는 포인트

    Q1. 파드가 Pending이면 무조건 Karpenter 문제인가요?

    아닙니다. 파드 스펙의 affinity, toleration, resource request가 원인인 경우도 많습니다.

    Q2. 노드가 생성되지 않으면 어디부터 봐야 하나요?

    파드 이벤트, Karpenter 로그, NodePool 조건을 먼저 보고, 그 다음 클라우드 리소스 조건을 확인하는 게 좋습니다.

    Q3. spot만 허용해도 괜찮을까요?

    가능은 하지만, 워크로드 특성과 가용성 요구사항을 고려해야 합니다. 운영에선 제약을 너무 좁히면 Karpenter 프로비저닝 실패 가능성이 올라가더라고요.

    마무리

    이번 글은 특정 버전 문법을 깊게 파기보다는, 문제가 났을 때 어떤 순서로 생각해야 하는지에 집중해봤습니다. 사실 운영에서는 정답 YAML 하나보다, 실패를 분해해서 보는 관점이 더 오래 가거든요. 다음 글에서는 Karpenter와 Cluster Autoscaler를 어떤 기준으로 나눠서 볼지, 그리고 워크로드별로 어떤 제약을 어디까지 허용할지 이어서 정리해보겠습니다.

  • [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] Pod Security Standards 마이그레이션: Kubernetes PSP에서 안전하게 전환하기

    [k8s] Pod Security Standards 마이그레이션: Kubernetes PSP에서 안전하게 전환하기

    Pod Security Standards 마이그레이션: Kubernetes PSP에서 안전하게 전환하기

    Kubernetes 클러스터를 오래 운영했다면 PSP(PodSecurityPolicy) 때문에 한 번쯤 머리가 아팠을 겁니다. 저도 그랬습니다. 그런데 PSP는 Kubernetes 1.21에서 deprecated 되었고, Kubernetes 1.25에서 완전히 제거됐습니다. 그래서 Pod Security Standards 마이그레이션은 이제 선택이 아니라 업그레이드와 운영 안정성을 위해 꼭 챙겨야 할 작업이 됐습니다. 이번 글에서는 Kubernetes 보안 관점에서 PSP 대체 수단인 PSS(Pod Security Standards)와 Pod Security Admission으로 어떻게 안전하게 전환하는지, 실무 기준으로 차근차근 정리해보겠습니다.

    처음엔 저도 PSS가 너무 단순해 보여서 오히려 불안했습니다. PSP처럼 세부 필드를 다 적는 방식에 익숙하면 더 그렇게 느껴지거든요. 그런데 실제로 운영해보니 팀 공통 보안 기준을 맞추는 데는 훨씬 현실적이더라고요. 대신 전환 순서를 잘못 잡으면 배포가 갑자기 막힐 수 있습니다. 여기서 핵심은 처음부터 enforce로 가지 말고 warn, audit로 먼저 관찰하는 것입니다.

    Pod Security Standards 마이그레이션 개요를 보여주는 Kubernetes 보안 아키텍처 이미지

    PSP에서 PSS와 Pod Security Admission으로 넘어가는 흐름을 한눈에 보여주는 개요 이미지입니다.

    왜 Pod Security Standards 마이그레이션이 중요한가

    예전 방식인 PSP는 표현력은 강했지만 운영 피로도도 높았습니다. 반면 PSS는 Privileged, Baseline, Restricted라는 공통 보안 레벨을 제공해서 팀 간 기준을 맞추기 훨씬 쉽습니다. 여러 네임스페이스를 운영하는 환경에서는 긴 정책 객체를 계속 유지하는 것보다, 보안 수준을 레벨로 선언하는 편이 관리가 훨씬 단순합니다.

    제가 직접 겪어보니 Pod Security Standards 마이그레이션의 장점은 분명했습니다. 신규 팀원에게 설명하기 쉽고, 클러스터 업그레이드 때 정책 호환성 이슈가 크게 줄어듭니다. 다만 PSP처럼 아주 세밀한 예외 제어를 기대하면 아쉬울 수 있습니다. 그런 경우에는 OPA Gatekeeper나 Kyverno 같은 정책 엔진을 함께 쓰는 구성이 더 잘 맞습니다.

    Pod Security Standards 마이그레이션 기준: PSP와 PSS는 무엇이 다를까

    처음 보면 이름만 바뀐 것처럼 느껴질 수 있는데 구조는 꽤 다릅니다. PSP는 정책 리소스를 만들고 RBAC으로 사용자나 서비스어카운트와 연결하는 방식이었습니다. 반면 PSS는 네임스페이스에 보안 수준을 라벨로 선언하고, Pod Security Admission이 그 기준으로 파드를 검사합니다.

    항목 PSP PSS + Pod Security Admission
    정책 방식 세부 필드 기반 정책 객체 사전 정의된 보안 표준 레벨
    적용 범위 RBAC과 연결해 사용 네임스페이스 라벨 기반 적용
    운영 난이도 높은 편 상대적으로 단순
    이식성 클러스터별 편차 큼 표준화하기 좋음
    세밀한 예외 처리 강점 제한적, 필요 시 별도 정책 엔진 보완

    PSS의 핵심 레벨은 아래처럼 이해하면 편합니다.

    • Privileged: 사실상 제한이 거의 없는 수준입니다. 시스템 워크로드나 인프라성 컴포넌트에 가깝습니다.
    • Baseline: 일반 애플리케이션에 적용하기 좋은 현실적인 최소 보호선입니다.
    • Restricted: 현재 권장되는 강한 하드닝 기준입니다. 대신 기존 애플리케이션은 여기서 많이 걸릴 수 있습니다.

    Pod Security Admission 적용 방법: 전환 전에 꼭 확인할 것

    Pod Security Admission은 파드 생성 시점에 PSS 기준을 검사하는 내장 Admission Controller입니다. 다만 여기서 하나 짚고 갈 점이 있습니다. warn과 audit는 Deployment 같은 워크로드 리소스에도 경고를 보여주지만, enforce는 최종적으로 생성되는 Pod에 적용됩니다. 이 차이를 알고 있어야 배포 시 메시지를 덜 헷갈립니다.

    1. enforce: 정책을 어기면 Pod 생성이 거부됩니다.
    2. audit: 생성은 허용되지만 감사 로그에 위반 정보가 남습니다.
    3. warn: 생성은 허용되지만 클라이언트에 경고가 표시됩니다.

    실무에서 자주 하는 실수가 바로 여기입니다. 바로 enforce부터 켜버리면 그동안 관성적으로 돌아가던 워크로드가 한꺼번에 실패할 수 있거든요. 저도 테스트 환경에서 ingress controller가 안 떠서 꽤 오래 본 적이 있습니다. 알고 보니 hostNetwork와 securityContext 설정이 기준에 안 맞았더라고요. 이런 경험을 한 번 하면 warn, audit부터 가야 하는 이유가 확실히 보입니다.

    전환 전 체크리스트

    • 현재 클러스터에서 PSP를 실제로 쓰는 네임스페이스가 어디인지 확인합니다.
    • 권한 상승 허용 여부를 점검합니다.
    • root 실행, hostPath 마운트, hostNetwork 사용 여부를 확인합니다.
    • DaemonSet, CSI, 모니터링 에이전트처럼 예외가 필요한 워크로드를 분리합니다.
    • 애플리케이션 네임스페이스와 시스템 네임스페이스를 같은 기준으로 묶지 않습니다.

    실전 구현: Pod Security Standards 마이그레이션 순서

    여기서는 제가 실제로 권장하는 순서를 정리해보겠습니다. 핵심은 단순합니다. 관찰 먼저, 강제는 나중. 이 순서만 지켜도 사고 확률이 확실히 줄어듭니다.

    1. 현재 네임스페이스 라벨 상태 확인

    kubectl get ns --show-labels

    기존에 이미 pod-security 관련 라벨이 붙어 있는지 먼저 확인합니다. 다른 운영자가 먼저 설정해둔 경우도 생각보다 자주 있습니다. 이 상태를 모르고 덮어쓰면 원인 파악이 더 어려워집니다.

    2. 먼저 warn, audit 모드로 Baseline 적용

    kubectl label ns my-app \
      pod-security.kubernetes.io/warn=baseline \
      pod-security.kubernetes.io/audit=baseline \
      --overwrite

    처음부터 Restricted로 가고 싶어도 일단 Baseline부터 보는 편이 안전합니다. 여기서 어떤 워크로드가 경고를 내는지 파악해야 다음 단계가 보입니다. 이 단계가 생각보다 중요하더라고요.

    3. 버전 라벨도 함께 명시해 드리프트 줄이기

    kubectl label ns my-app \
      pod-security.kubernetes.io/warn-version=v1.36 \
      pod-security.kubernetes.io/audit-version=v1.36 \
      --overwrite

    버전 라벨은 선택 사항이지만 운영에선 거의 필수에 가깝습니다. PSS 해석 기준은 Kubernetes 마이너 버전에 따라 달라질 수 있어서, 테스트와 운영 클러스터의 결과가 어긋나는 일을 줄여주거든요. 다만 예시 값을 그대로 복붙하기보다는 현재 클러스터의 마이너 버전에 맞춰 적는 게 맞습니다.

    4. 경고 메시지로 문제 워크로드 찾기

    kubectl apply -f deployment.yaml

    배포 시 warn 메시지가 뜨면 그 내용을 기준으로 수정합니다. warn과 audit는 Deployment 같은 워크로드 리소스에도 적용되기 때문에, 이 단계에서 꽤 많은 힌트를 얻을 수 있습니다. 흔히 손보게 되는 포인트는 아래와 같습니다.

    • runAsNonRoot: non-root 실행 강제
    • allowPrivilegeEscalation: false 설정
    • seccompProfile: RuntimeDefault 적용
    • capabilities: 불필요한 Linux capability 제거
    Pod Security Admission과 네임스페이스 라벨 기반 PSS 적용 방법을 설명하는 이미지

    네임스페이스에 warn, audit, enforce 라벨을 붙이는 방식과 적용 흐름을 보여주는 이미지입니다.

    5. 매니페스트 보안 컨텍스트 정리

    아래 예시는 Restricted로 가기 전에 가장 자주 쓰는 기본 골격입니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-app
      namespace: my-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: web-app
      template:
        metadata:
          labels:
            app: web-app
        spec:
          securityContext:
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
          containers:
            - name: web-app
              image: nginx:stable
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                    - ALL
              ports:
                - containerPort: 80

    이 예시는 아주 화려하지는 않지만, 실제로 많이 걸리는 조건을 담고 있습니다. 특히 runAsNonRoot는 이미지 자체가 non-root 실행을 지원해야 제대로 동작합니다. 오래된 이미지라면 매니페스트만 고쳐서는 해결이 안 되고, 컨테이너 이미지 베이스부터 손봐야 할 때가 있습니다.

    6. 문제 없으면 enforce 전환

    kubectl label ns my-app \
      pod-security.kubernetes.io/enforce=baseline \
      pod-security.kubernetes.io/enforce-version=v1.36 \
      --overwrite

    여기서도 한 번에 모든 네임스페이스를 바꾸는 건 추천하지 않습니다. 서비스 영향이 낮은 곳부터 묶어서 적용하고, 모니터링으로 이상이 없는지 확인한 뒤 확대하는 방식이 제일 덜 아픕니다. 이거 진짜 차이가 큽니다.

    Restricted까지 가려면 어떤 점을 더 봐야 하나

    Pod Security Standards 마이그레이션을 시작하면 많은 팀이 최종 목표를 Restricted로 잡습니다. 방향은 맞습니다. 다만 현실적으로 모든 워크로드가 바로 들어가지는 않더라고요. 특히 아래 항목은 자주 발목을 잡습니다.

    1. 레거시 이미지가 root 실행을 전제로 만들어져 있음
    2. initContainer에서 과한 권한을 사용함
    3. 모니터링, 스토리지, 네트워크 에이전트가 호스트 접근을 필요로 함
    4. hostPath 사용이 습관처럼 들어가 있음

    그래서 저는 보통 네임스페이스를 이렇게 나눕니다.

    네임스페이스 유형 권장 레벨 비고
    일반 애플리케이션 Baseline 또는 Restricted 신규 서비스는 Restricted 목표
    운영 도구 Baseline 필요 권한 확인 후 점진 강화
    시스템 워크로드 Privileged 또는 별도 예외 관리 CNI, CSI, 일부 에이전트 주의

    트러블슈팅: Pod Security Standards 마이그레이션에서 자주 막히는 지점

    문서만 보면 금방 끝날 것 같지만, 운영 환경에 붙이면 여기서 한 번씩 다 막힙니다. 저도 몇 번은 꽤 돌아갔습니다. 그래서 아래 포인트는 미리 보고 들어가는 편이 좋습니다.

    1. 배포가 갑자기 거부될 때

    enforce가 켜진 네임스페이스에 정책 위반 Pod가 들어가면 바로 거부됩니다. 이 경우 이벤트와 매니페스트를 같이 봐야 합니다.

    kubectl describe pod <pod-name> -n my-app
    kubectl get events -n my-app --sort-by=.lastTimestamp

    경고 문구만 보고 대충 고치면 같은 문제를 반복하기 쉽습니다. 어떤 필드가 위반인지 정확히 확인하고, Pod 템플릿 기준으로 수정해야 합니다.

    2. 시스템 네임스페이스까지 묶어서 막힐 때

    이 부분은 정말 조심해야 합니다. kube-system 같은 곳에 일반 앱과 같은 Restricted를 걸면 예상치 못한 장애가 날 수 있습니다. 네트워크 플러그인, 스토리지 드라이버, 노드 에이전트가 영향을 받는 경우가 대표적입니다. 실제 운영에선 시스템성 워크로드를 별도 기준으로 다루는 게 훨씬 안전합니다.

    3. warn은 뜨는데 배포가 되니까 그냥 넘길 때

    처음엔 “배포는 되네” 하고 넘기기 쉽습니다. 그런데 나중에 enforce로 바꾸는 순간 한꺼번에 터집니다. 그래서 warn 단계에서 바로 티켓을 만들고, 팀별 수정 일정을 잡아두는 편이 훨씬 낫습니다.

    4. PSP 기능과 1:1 대응을 기대할 때

    PSP 대체라고 해서 완전히 같은 경험을 기대하면 실망할 수 있습니다. PSS는 표준 보안 레벨을 제공하는 방식이고, 세밀한 조직별 규칙은 별도 정책 도구가 더 적합합니다. 이 차이를 이해하고 설계해야 전환 이후 불만도 줄어듭니다.

    검증: Pod Security Admission이 제대로 동작하는지 확인하는 방법

    적용 후에는 라벨만 붙였다고 끝이 아닙니다. 검증이 꼭 필요합니다. 제가 보통 확인하는 항목은 아래와 같습니다.

    1. 네임스페이스 라벨이 의도한 값으로 들어갔는지 확인
    2. warn 메시지가 줄거나 사라졌는지 확인
    3. 문제 Pod 없이 롤링 업데이트가 완료되는지 확인
    4. 시스템 워크로드가 영향 없이 유지되는지 확인
    kubectl get ns my-app --show-labels
    kubectl rollout status deploy/web-app -n my-app
    kubectl get pods -n my-app

    조금 더 확실히 보려면 의도적으로 정책 위반 Pod를 하나 던져보는 것도 방법입니다. 다만 이 테스트는 운영 네임스페이스가 아니라 테스트 네임스페이스에서만 하는 편이 안전합니다.

    apiVersion: v1
    kind: Pod
    metadata:
      name: pss-test
      namespace: my-app
    spec:
      hostNetwork: true
      containers:
        - name: busybox
          image: busybox:1.36
          command: ["sh", "-c", "sleep 3600"]
          securityContext:
            allowPrivilegeEscalation: true

    Baseline이나 Restricted enforce가 걸려 있다면 이런 Pod는 거부되는 게 정상입니다. hostNetwork는 Baseline부터 금지되고, allowPrivilegeEscalation은 Restricted에서 금지되기 때문에 테스트용으로 확인하기 좋습니다.

    Pod Security Standards 마이그레이션 검증 결과를 보여주는 Kubernetes 보안 이미지

    정책 위반 테스트와 정상 배포가 어떻게 다르게 보이는지 검증 결과를 시각화한 이미지입니다.

    운영 팁: 네임스페이스 전략을 먼저 정하면 훨씬 편합니다

    제가 여러 번 해보니, PSS 적용 방법에서 제일 중요한 건 YAML 문법보다 운영 기준이었습니다. 다시 말해 “어떤 네임스페이스를 어떤 신뢰 수준으로 볼 것인가”를 먼저 정해야 합니다. 이 기준이 없으면 팀마다 예외 요청이 쌓이고, 결국 정책이 누더기가 되더라고요.

    • 신규 서비스는 기본적으로 Baseline 또는 Restricted로 시작합니다.
    • 예외가 필요한 시스템성 워크로드는 별도 네임스페이스로 분리합니다.
    • warn과 audit 결과를 주기적으로 점검해 enforce 후보를 올립니다.
    • 정책 예외는 문서화하고 만료 시점을 정합니다.

    이런 방식으로 가면 Kubernetes 보안이 특정 담당자 감에 의존하지 않고 팀 표준으로 자리 잡습니다. 이전에 정리한 RBAC 글이 있다면 같이 연결해두는 것도 좋습니다. 내부 링크 흐름이 생기면 독자 입장에서도 이해가 더 빨라지고, SEO 측면에서도 분명 도움이 됩니다.

    정리: PSP에서 PSS로 갈아탈 때 기억할 것

    오늘 내용을 짧게 정리하면 이렇습니다. Pod Security Standards 마이그레이션은 단순한 기능 교체가 아니라, 보안 운영 방식을 표준화하는 작업입니다. PSP보다 세밀한 제어는 줄었지만, 네임스페이스 단위로 기준을 맞추기 쉬워졌고, Pod Security Admission으로 warn, audit, enforce를 단계적으로 운영할 수 있게 됐습니다.

    저도 처음엔 이 구조가 너무 단순한 것 아닌가 싶었습니다. 그런데 실제로는 팀 단위 운영에 더 잘 맞더라고요. 다만 무작정 enforce부터 걸면 안 됩니다. 꼭 warn, audit으로 현재 상태를 먼저 파악하고, Restricted는 목표로 두되 시스템 워크로드 예외를 현실적으로 분리해 가져가면 훨씬 수월합니다.

    지금 클러스터 업그레이드를 앞두고 있다면 이번 주에라도 테스트 네임스페이스 하나 만들어 Baseline warn부터 붙여보세요. 생각보다 빨리 문제가 보입니다. 그리고 이전에 다뤘던 RBAC 정리 글도 함께 보면 흐름이 더 잘 잡힐 겁니다. 다음 글에서는 Kyverno나 Gatekeeper로 PSS의 빈틈을 어떻게 보완하는지도 이어서 정리해보겠습니다.

    PSP 대체와 Pod Security Standards 보안 레벨 비교를 정리한 인포그래픽 이미지

    PSP와 PSS의 차이, 적용 순서, 운영 체크포인트를 한 장으로 정리한 요약 인포그래픽입니다.

    자주 묻는 질문

    PSS만으로 PSP를 완전히 대체할 수 있나요?

    기본적인 표준화 목적에는 충분한 경우가 많습니다. 다만 세밀한 조건 제어나 예외 검증이 많다면 Kyverno나 Gatekeeper 같은 정책 엔진을 함께 검토하는 편이 좋습니다.

    처음부터 Restricted로 가도 되나요?

    신규 서비스만 있고 이미지와 매니페스트를 처음부터 통제할 수 있는 환경이라면 가능합니다. 하지만 기존 워크로드가 있다면 Baseline warn, audit부터 시작하는 편이 훨씬 안전합니다.

    Pod Security Admission은 어디에 적용되나요?

    네임스페이스 라벨 기준으로 해당 네임스페이스의 Pod 생성에 영향을 줍니다. 또 warn과 audit는 Deployment 같은 워크로드 리소스에도 힌트를 주기 때문에, 네임스페이스 전략과 배포 검증 루틴을 함께 가져가는 게 중요합니다.