13년차의 서버실

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

[태그:] Kubernetes 보안

  • [보안] Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    [보안] Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    Kubernetes 보안 체크리스트를 운영하다 보면 병목이 꽤 자주 이미지 스캔에서 드러납니다. 배포 파이프라인은 분 단위로 빨라졌는데 보안 판정이 여전히 사람 손에 걸려 있으면, 속도와 통제가 같이 무너지더라고요. 저도 홈랩이든 사내 테스트 클러스터든 처음에는 릴리스 직전 일회성 스캔으로 시작했는데, 운영으로 넘어가면 금방 한계가 보였습니다. 이미지 변경 속도, 베이스 이미지 교체 주기, 레지스트리 정책, 클러스터 반입 기준이 따로 놀면 자동화는 이름만 자동화가 되거든요.

    실무에서는 스캐너를 한 번 붙이는 것으로 끝나지 않습니다. 빌드 시점, 레지스트리 저장 시점, 클러스터 반입 시점, 운영 중 재평가 시점을 나눠서 봐야 합니다. 이유는 단순합니다. 배포 당시엔 통과한 이미지라도, 며칠 뒤 취약점 데이터베이스가 갱신되면 같은 다이제스트가 다른 판정을 받을 수 있습니다. 이 차이를 놓치면 팀은 바로 “어제는 괜찮았는데 왜 오늘 막히지?”라는 혼란에 빠집니다.

    CI, 레지스트리, Kubernetes 클러스터, 보고 체계를 한눈에 보여주는 전체 구성도입니다.

    Kubernetes 보안 체크리스트에서 이미지 스캔이 중요한 이유

    현장에서 필요한 건 CVE 숫자 나열이 아닙니다. 무엇을 기준으로 배포를 막을지, 무엇은 추적만 하고 무엇은 즉시 조치할지, 그 판정을 누가 재현할 수 있는지가 핵심입니다. 실제 스캔 결과를 보면 문제는 대체로 세 부류로 갈립니다. 베이스 이미지 교체로 끝나는 항목, 애플리케이션 의존성 업데이트가 필요한 항목, 이미지가 아니라 배포 매니페스트나 실행 권한 설정을 손봐야 하는 항목입니다. 같은 High라도 대응 경로가 완전히 다릅니다.

    그래서 체크리스트를 만들 때 제가 먼저 적는 건 도구 이름이 아니라 다음 네 가지입니다. 첫째, 어떤 시점에 검사할지. 둘째, 어떤 대상을 검사할지. 셋째, 어떤 심각도와 조건에서 실패로 볼지. 넷째, 예외를 누구 승인으로 얼마나 오래 둘지. 이 네 줄이 빠지면 결과는 늘 많고, 책임은 늘 흐려집니다.

    Kubernetes 보안 체크리스트로 정리한 컨테이너 이미지 스캔 자동화 체크리스트

    1. 이미지 태그보다 다이제스트를 기준으로 기록: repo:tag만 저장하지 말고 repo@sha256:...를 남겨야 스캔 결과와 배포 대상을 정확히 연결할 수 있습니다.
    2. 빌드 직후 스캔: CI에서 이미지 생성 직후 취약점과 시크릿 혼입 여부를 확인합니다.
    3. 실패 기준을 코드로 고정: CRITICAL 즉시 차단, HIGH는 팀 정책에 따라 차단 또는 예외 관리 등으로 명시합니다.
    4. 데이터베이스 갱신 시점 기록: 로컬, CI, 클러스터의 스캔 결과가 다른 흔한 원인입니다.
    5. 레지스트리 인증 분리: 빌드용 자격 증명과 스캔용 읽기 전용 자격 증명을 분리해 두면 사고 범위를 줄이기 좋습니다.
    6. 클러스터 내 재스캔: 이미 떠 있는 워크로드를 주기적으로 다시 평가해야 운영 중 신규 공개 취약점을 따라잡을 수 있습니다.
    7. 예외 처리 만료일 강제: 예외는 허가가 아니라 부채입니다. 사유, 승인자, 만료일이 없으면 결국 영구 면제가 됩니다.
    8. 결과 저장 위치 통일: CI 로그만 믿지 말고 리포트 시스템이나 Kubernetes 리소스로 남겨 반복 조회 가능하게 해야 합니다.
    9. 로컬과 CI 판정 일치: 같은 옵션, 같은 대상, 가능하면 같은 DB 갱신 흐름을 써야 팀이 결과를 신뢰합니다.
    10. 최소 이미지 우선: Distroless나 slim 계열 검토는 보안 때문이기도 하지만, 스캔 노이즈와 패치 범위를 줄이는 데도 효과가 큽니다.
    11. Admission 단계 연계 여부 결정: 규정이 엄격하면 배포 허가 단계에서 차단하고, 초기 도입기라면 관찰 모드부터 들어가는 편이 덜 부서집니다.
    12. 성능 예산 확보: 대형 이미지 재스캔은 노드 자원과 레지스트리 호출을 먹습니다. 운영 클러스터에서 무작정 스캔 빈도를 높이면 다른 워크로드가 피해를 볼 수 있습니다.

    도구 선택 전에 먼저 정할 기준

    Trivy, Grype처럼 널리 쓰이는 스캐너는 출발점으로 충분히 좋습니다. 저는 빠르게 붙일 때는 Trivy를 자주 쓰는 편입니다. 로컬 점검, CI, Kubernetes 연계까지 흐름을 만들기 편해서 이거 진짜 실무에서 손이 덜 가더라고요. 다만 도구보다 먼저 정해야 할 것이 있습니다. 결과 해석 단위가 이미지인지, 레포지토리인지, 실제 클러스터 워크로드인지부터 정해야 합니다. 이 기준이 흔들리면 스캐너를 바꿔도 운영은 그대로 혼란스럽습니다.

    선택 포인트 A를 택할 때 B를 택할 때 제가 실제로 권하는 기준
    스캔 시점 CI만 스캔: 배포 속도와 단순성이 우선일 때 CI + 클러스터 재스캔: 운영 중 노출 추적이 중요할 때 운영 클러스터가 있으면 병행이 맞습니다. CI만으로는 신규 공개 취약점을 놓치기 쉽습니다.
    식별 방식 태그 기준: 초기에 단순하게 시작할 때 다이제스트 기준: 결과 재현성과 감사 추적이 필요할 때 실서비스는 다이제스트 기준으로 가는 편이 안전합니다. 태그는 너무 쉽게 바뀝니다.
    실패 정책 Critical만 차단: 도입 초기에 반발을 줄여야 할 때 Critical + High 차단: 규정과 배포 품질 기준이 이미 성숙했을 때 처음부터 전부 막기보다 Critical 차단, High는 만료일 있는 예외 관리가 현실적입니다.
    스캔 대상 OS 패키지 위주: 베이스 이미지 관리가 주 이슈일 때 OS + 애플리케이션 의존성 + 시크릿: 공급망 전체를 봐야 할 때 실무에서는 최소한 취약점과 시크릿은 같이 보는 편이 낫습니다.
    운영 방식 관찰 모드: 현재 상태 파악이 먼저일 때 차단 모드: 배포 기준을 강제해야 할 때 새로 도입하는 팀은 1~2주 정도 관찰 모드로 데이터부터 모으는 편이 덜 아픕니다.

    실전 구현 1: Kubernetes 보안 체크리스트 기준으로 로컬과 CI 맞추기

    가장 먼저 할 일은 개발자 로컬과 CI가 가능한 한 같은 판정을 내도록 맞추는 것입니다. 로컬에선 통과했는데 CI에서만 실패하면 팀은 금방 스캐너보다 파이프라인을 더 싫어하게 됩니다. 그래서 이 단계에서는 “옵션을 많이 주는 것”보다 “같은 옵션을 고정하는 것”이 더 중요합니다. 이 기준만 맞춰도 운영 피로도가 꽤 줄어듭니다.

    1. 이미지 빌드 후 Trivy로 즉시 검사

    아래 예시는 로컬에서 이미지 빌드 직후 취약점과 시크릿을 같이 보는 가장 단순한 흐름입니다. 팀 공통 기준을 잡을 때는 이런 명령부터 통일하는 게 훨씬 편합니다.

    docker build -t myapp:scan-test .
    trivy image \
      --severity HIGH,CRITICAL \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format table \
      myapp:scan-test

    여기서 중요한 건 옵션 의미보다 운영상의 함정입니다. --ignore-unfixed는 노이즈를 줄이는 데 유용하지만, “아직 수정본이 없으니 위험이 없다”는 뜻은 아닙니다. 패치가 없어도 우회책이나 완화 조치가 필요한 경우가 있습니다. 그래서 저는 이 옵션을 쓰더라도 예외 문서에는 “미조치”보다 “공급사 수정 대기”처럼 상태를 분리해 적는 편입니다.

    --scanners vuln,secret 조합도 실무 효율이 좋습니다. 이미지 레이어에 테스트 토큰, 오래된 인증서, 샘플 키가 남아 있는 경우가 의외로 자주 나옵니다. 이건 CVE보다 발견 즉시 우선순위를 높게 잡아야 하는 유형입니다. 공격 난도가 낮고 노출 즉시 영향이 커질 수 있어서 그렇습니다.

    2. CI에서 실패 기준을 강제하고 결과를 아티팩트로 남기기

    CI에서는 사람 눈으로만 보는 표 형식과, 후처리용 JSON 결과를 같이 남겨 두는 편이 좋습니다. 나중에 추세 비교나 예외 검토할 때 이 차이가 꽤 크게 느껴집니다.

    IMAGE_REF="registry.example.com/team/myapp:${GIT_COMMIT}"
    
    trivy image \
      --severity CRITICAL,HIGH \
      --exit-code 1 \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format json \
      --output trivy-report.json \
      "$IMAGE_REF"
    
    trivy image \
      --severity CRITICAL,HIGH \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format table \
      "$IMAGE_REF"

    --exit-code 1이 없으면 자동화는 반쪽입니다. 로그에 경고가 많아도 배포가 계속되면 운영팀은 곧 “보안 경고는 참고용”으로 받아들이게 됩니다. 그 순간부터 정책은 문서에만 남습니다. 그리고 JSON 결과를 별도 파일로 남기는 이유도 분명합니다. 사람이 읽기 쉬운 표와 기계가 다시 처리하기 쉬운 형식을 분리해야 나중에 재검증이 편해집니다.

    초기에 자주 보는 실패 모드는 “로컬은 어제 DB로 검사했고, CI는 오늘 DB로 검사했다”는 불일치입니다. 겉으로는 같은 이미지, 같은 명령처럼 보여도 결과가 달라집니다. 그래서 운영 문서에는 최소한 이미지 다이제스트, 스캔 시각, 스캐너 버전, DB 갱신 시각 네 가지를 같이 남겨 두는 게 좋습니다.

    컨테이너 이미지 스캔이 포함된 Kubernetes 보안 체크리스트 CI 흐름

    빌드, 스캔, 실패 기준 적용, 배포 차단으로 이어지는 파이프라인 흐름 예시입니다.

    실전 구현 2: Kubernetes 클러스터 안에서 재스캔 자동화

    CI 스캔은 배포 직전의 사진 한 장에 가깝습니다. 운영은 동영상에 더 가깝고요. 이미지 자체는 그대로인데 취약점 데이터베이스가 갱신되거나, 오래된 워크로드가 남아 있거나, 수동 롤백으로 이전 태그가 다시 떠 있을 수 있습니다. 그래서 운영 클러스터에서는 주기 재평가가 꼭 필요합니다.

    1. Helm으로 Trivy Operator 설치

    Trivy Operator는 Kubernetes 안에서 보안 리포트를 CRD 형태로 남겨 주기 때문에, 클러스터 관점 추적이 필요한 팀에는 꽤 잘 맞습니다. 별도 콘솔 없이도 kubectl로 상태를 확인할 수 있다는 점도 편합니다.

    helm repo add aqua https://aquasecurity.github.io/helm-charts/
    helm repo update
    helm upgrade --install trivy-operator aqua/trivy-operator \
      --namespace trivy-system \
      --create-namespace

    이 방식의 장점은 결과가 Kubernetes 리소스로 남는다는 점입니다. CI 로그는 흩어지기 쉽지만 리소스는 조회 경로가 고정됩니다. 운영팀과 플랫폼팀이 같은 화면을 본다는 점도 생각보다 큽니다. 인수인계할 때 특히 편하더라고요.

    2. 스캔 결과 조회

    설치 후에는 먼저 실제로 어떤 리포트가 생성되는지 확인하는 게 좋습니다. 환경과 활성화된 기능에 따라 보이는 리포트 종류가 조금씩 다를 수 있습니다.

    kubectl get vulnerabilityreports -A
    kubectl describe vulnerabilityreport -n default <report-name>

    결과를 읽을 때는 숫자보다 맥락을 먼저 봅니다. 저는 보통 세 가지를 바로 확인합니다. 첫째, 어떤 워크로드의 어떤 컨테이너인지. 둘째, 문제가 베이스 이미지 계층인지 애플리케이션 의존성인지. 셋째, 지금 당장 수정 가능한지입니다. 같은 High라도 베이스 이미지 교체로 끝나는 항목과 코드 호환성 검토가 필요한 라이브러리 문제는 대응 일정이 다릅니다.

    3. 실제 배포 중인 이미지 목록부터 확인

    보고서가 아니라 실제 실행 대상을 먼저 보는 습관이 중요합니다. 선언상 최신 이미지가 적혀 있어도, 운영 클러스터에는 예전 이미지가 남아 있는 경우가 꽤 있습니다.

    kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'

    이 명령은 단순해 보여도 중요합니다. Git 저장소 기준으로는 최신 태그를 본다고 해도, 실제 클러스터에선 예전 태그나 수동 롤백된 이미지가 남아 있을 수 있습니다. 스캔 체계가 배포 선언만 보고 실제 실행 대상을 안 보면, 보고서는 깨끗한데 운영은 더러운 상태가 됩니다.

    4. 사설 레지스트리 인증 문제가 있는지 먼저 분리 진단

    리포트가 안 생길 때는 스캐너 자체를 의심하기 전에 인증 흐름부터 확인하는 편이 빠릅니다. 여기서 시간 많이 쓰는 팀이 정말 많습니다.

    kubectl get events -A --sort-by=.lastTimestamp
    kubectl get secrets -n trivy-system
    kubectl describe serviceaccount -n trivy-system trivy-operator

    클러스터 내 스캐너가 리포트를 못 만드는 가장 흔한 이유는 인증 연결 누락입니다. 이미지 풀은 애플리케이션 서비스 계정이 잘하는데, 스캐너는 다른 서비스 계정이라 접근을 못 하는 경우가 잦습니다. 표면 증상은 “리포트가 안 생긴다”인데 근본 원인은 권한 경계가 다른 데 있습니다. 이건 설정 몇 줄 더 넣는다고 해결되지 않고, 누가 어느 레지스트리를 어떤 자격 증명으로 읽는지 모델을 분리해서 봐야 합니다.

    재현 가능한 시나리오로 보는 운영 포인트

    이런 시나리오는 실제 운영에서 자주 나옵니다. 내부 API 서비스가 registry.example.com/team/api:2026-09-01로 배포됐고, 배포 당시 CI 스캔은 통과했습니다. 며칠 뒤 베이스 이미지에 대한 신규 취약점 정보가 스캐너 DB에 반영됩니다. 코드도, 이미지 다이제스트도 바뀌지 않았는데 클러스터 리포트에는 새로운 Critical이 생길 수 있습니다. CI만 보는 팀은 “왜 갑자기 숫자가 늘었지?”에서 멈추고, 클러스터 재스캔이 있는 팀은 “지금 떠 있는 워크로드 중 어느 네임스페이스가 영향권인지”까지 바로 확인합니다.

    이 지점에서 중요한 것은 억울함을 줄이는 기록입니다. 결과가 바뀌었는데 이미지가 안 바뀌었을 때 스캐너 오작동으로 몰아가는 경우를 여러 번 봤습니다. 실제로는 취약점 DB 갱신이나 메타데이터 해석 차이인 경우가 많았습니다. 그래서 운영 기준에는 반드시 스캔 시각, DB 갱신 시각, 대상 다이제스트를 함께 남겨 두는 게 좋습니다.

    실제로 많이 막히는 문제와 해결법

    Private Registry 인증이 안 붙는 경우

    증상은 단순합니다. 리포트가 생성되지 않거나 이벤트에 인증 실패가 남습니다. 그런데 근본 원인은 보통 “앱 배포에 쓰는 이미지 풀 자격 증명”과 “스캐너가 읽는 자격 증명”을 같은 것으로 착각한 데 있습니다. 해결은 권한을 더 크게 주는 것이 아니라, 스캐너 전용 읽기 권한이 어디에 연결돼 있는지 확인하는 것입니다.

    취약점이 너무 많이 떠서 운영이 멈추는 경우

    초기 도입기에는 거의 반드시 겪습니다. 이때 모든 심각도를 한 번에 차단하면 실제로는 보안 수준이 올라가는 게 아니라 파이프라인이 멈춥니다. 저는 보통 Critical 즉시 차단, High는 예외 문서와 만료일 부여, Medium 이하는 추세 관찰로 시작합니다. 운영이 감당할 수 있는 속도로 올라가야 자동화도 습관으로 남습니다.

    태그만 보고 이미지를 추적하는 경우

    latest나 재사용되는 릴리스 태그는 감사 추적에 불리합니다. 스캔 결과가 어느 바이너리 집합을 가리키는지 불명확해집니다. 실제 배포 검증이나 사고 대응까지 생각하면 다이제스트 기준 저장이 맞습니다. 태그는 사람이 보기 좋고, 다이제스트는 시스템이 책임지기 좋습니다.

    운영 판단 없이 숫자만 보는 경우

    스캔 수치는 우선순위의 단서일 뿐, 우선순위 그 자체는 아닙니다. 외부에 노출된 서비스의 런타임 라이브러리 문제와 내부 배치 컨테이너의 개발 도구 패키지 문제는 같은 High여도 대응 순서가 달라집니다. 저는 아래 네 항목을 같이 봅니다.

    • 노출 면적: Ingress 뒤 서비스인지, 내부 잡인지 구분합니다.
    • 권한 수준: root 실행 여부, 추가 capability, privileged 여부를 확인합니다.
    • 수정 가능성: 베이스 이미지 교체로 끝나는지, 코드 수정과 검증이 필요한지 나눕니다.
    • 예외 만료: 지금 못 고치는 항목은 다시 볼 날짜가 없으면 영구 미해결이 됩니다.

    스캔이 클러스터 자원을 갉아먹는 경우

    운영 규모가 커지면 이것도 무시하기 어렵습니다. 큰 이미지가 많고 네임스페이스가 많으면 스캔 자체가 CPU, 메모리, 레지스트리 호출량을 소모합니다. 증상은 오퍼레이터가 느려지거나 리포트 생성이 밀리는 식으로 나타납니다. 재스캔은 많이 돌린다고 무조건 좋은 게 아니라, 업데이트 빈도, 클러스터 자원, 조치 가능한 인력에 맞춰야 합니다.

    클러스터 보안 관점의 Kubernetes 보안 체크리스트 리포트 구성도

    오퍼레이터가 워크로드를 스캔하고 리포트를 남기는 클러스터 내부 흐름 예시입니다.

    검증: 자동화가 제대로 작동했는지 확인하는 방법

    자동화 검증은 명령 성공 여부만 보면 부족합니다. 실패해야 할 때 진짜 실패하는지, 운영 중 판정 변화가 반영되는지, 팀원이 바뀌어도 같은 경로로 확인 가능한지가 핵심입니다. 제가 점검할 때는 다음 순서로 봅니다.

    1. 테스트용 이미지를 하나 빌드하고 로컬 결과를 저장합니다.
    2. 같은 이미지를 CI에서 같은 심각도 기준으로 스캔해 동일하게 실패 또는 통과하는지 확인합니다.
    3. 가능하면 배포 시점의 이미지 다이제스트를 기록해 로컬 결과와 CI 결과를 같은 대상으로 묶습니다.
    4. 클러스터에 배포한 뒤 vulnerabilityreports 리소스가 생성되는지 확인합니다.
    5. 사설 레지스트리 이미지는 인증 실패 없이 조회되는지 이벤트와 서비스 계정 기준으로 검증합니다.
    6. 예외 처리한 항목이 문서, CI 정책, 클러스터 결과에서 같은 의미로 반영되는지 비교합니다.

    환경에 따라 추가 리포트가 생성될 수 있으니, 아래처럼 어떤 CRD가 실제로 보이는지 함께 확인하면 좋습니다. 다만 활성화된 기능과 Trivy Operator 설정에 따라 결과는 달라질 수 있습니다.

    kubectl get vulnerabilityreports -A
    kubectl get configauditreports -A
    kubectl get clustercompliancereports -A

    중요한 것은 결과를 반복 조회 가능한 형태로 남기는 것입니다. 사람이 한 번 보고 지나가는 로그는 운영 자산이 아닙니다. 조회 경로가 고정되고 같은 명령으로 다시 확인할 수 있어야 팀 지식이 됩니다. 그리고 Pod Security Standards, RBAC, NetworkPolicy 관련 글도 함께 보면 Kubernetes 보안 체크리스트를 더 입체적으로 잡는 데 도움이 됩니다.

    Kubernetes 보안 체크리스트 결과 검증용 이미지 스캔 대시보드

    배포된 워크로드별 취약점 현황과 확인 포인트를 보여주는 결과 화면 예시입니다.

    FAQ: 현장에서 자주 받는 질문

    Q. 스캔 결과가 바뀌었는데 이미지는 안 바뀌었습니다.

    A. 충분히 가능한 일입니다. 가장 흔한 원인은 취약점 데이터베이스 갱신입니다. 그래서 스캔 시각, DB 갱신 시각, 이미지 다이제스트를 같이 기록해야 나중에 원인 추적이 쉬워집니다.

    Q. 모든 High를 바로 차단해야 하나요?

    A. 팀 상태에 따라 다릅니다. 초기 도입기라면 Critical 우선 차단, High는 예외와 만료일 관리가 현실적입니다. 이미 배포 품질 게이트가 정착된 조직이라면 High까지 차단해도 됩니다. 중요한 건 기준을 문서가 아니라 파이프라인에 넣는 것입니다.

    Q. Kubernetes 보안 체크리스트에서 이미지 스캔만 하면 충분한가요?

    A. 부족합니다. 이미지 스캔은 공급망과 배포 전 검증의 한 축일 뿐입니다. Pod Security Standards, RBAC, NetworkPolicy, Secret 관리, 감사 로그까지 함께 봐야 운영 리스크가 줄어듭니다. 다만 시작점으로는 이미지 스캔이 체감 효과가 빠른 편입니다.

    운영 기준은 이렇게 잡으시면 됩니다

    제가 오래 운영을 보면서 내린 기준은 단순합니다. 팀이 아직 수동 검토에 기대고 있다면 Trivy CLI로 CI 차단부터 붙이세요. 이 단계에서는 로컬과 CI 판정 일치, 다이제스트 기록, 예외 만료일 관리가 핵심입니다. 운영 클러스터가 여러 개이거나 롤백이 잦다면 클러스터 재스캔도 같이 가져가세요. 배포 당시엔 멀쩡했던 이미지가 나중에 문제로 바뀌는 순간을 잡아내려면 이 경로가 필요합니다.

    반대로 아직 팀이 결과 해석도 못 따라가는데 Admission 단계에서 전부 막는 것은 추천하지 않습니다. 그건 통제가 아니라 마찰이 됩니다. 이럴 땐 관찰 모드로 현재 상태 파악, 그다음 Critical 차단, 마지막으로 High까지 확대 순서가 덜 부서집니다. 규정이 엄격하고 변경 이력 감사가 중요하다면, 그때 배포 허가 단계 연계까지 가면 됩니다.

    제 권고를 한 줄로 줄이면 이렇습니다. 작게 시작하는 팀은 CI 스캔 + 실패 코드 적용, 운영 가시성이 필요한 팀은 클러스터 재스캔 추가, 강제 통제가 필요한 조직은 Admission 정책 연계. 이 순서가 실무에서 가장 덜 아프고, 결과가 남고, 다음 담당자도 이해하기 쉽습니다.

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    CI 스캔, 클러스터 재스캔, 예외 관리, 정책 연계를 한 장으로 정리한 요약 이미지입니다.

  • [보안 사례] 컨테이너 이미지 보안 강화: Trivy를 활용한 CI/CD 통합 운영 후기

    [보안 사례] 컨테이너 이미지 보안 강화: Trivy를 활용한 CI/CD 통합 운영 후기

    [보안 사례] 컨테이너 이미지 보안 강화: Trivy를 활용한 CI/CD 통합 운영 후기

    컨테이너 보안 사례를 찾다 보면 다들 비슷한 이야기만 하더라고요. “스캔하세요”, “취약점 관리하세요” 같은 말은 맞는데, 막상 운영 환경에 넣으려면 어디서부터 손대야 할지 애매합니다. 저도 홈랩과 사내 비슷한 구조의 테스트 환경에서 Trivy(트리비, 오픈소스 취약점 스캐너)를 붙여서 이미지 스캔을 자동화해봤는데, 처음엔 이게 생각보다 손이 많이 가더라고요. 특히 CI/CD 보안 관점에서 “스캔은 했는데 배포는 누가 막지?”, “베이스 이미지가 바뀌면 기존 정책은 어떻게 하지?” 같은 현실적인 문제가 꼭 나옵니다.

    이번 글은 그런 분들을 위한 컨테이너 보안 사례 정리입니다. Trivy를 활용해서 CI/CD 보안 흐름에 이미지 검사를 붙이고, Docker 보안과 Kubernetes 보안까지 운영 관점에서 어떻게 연결했는지 경험 기반으로 풀어보겠습니다. 제가 직접 해보니 도구 자체보다도 언제 검사하고, 어떤 기준으로 실패 처리할지가 진짜 핵심이었습니다.

    컨테이너 보안 사례 기반 Trivy CI/CD 파이프라인 아키텍처 이미지

    CI 단계에서 이미지 빌드, Trivy 스캔, 레지스트리 푸시, Kubernetes 배포 전 검증까지 이어지는 전체 흐름을 보여주는 이미지입니다.

    1. 왜 컨테이너 이미지 보안이 운영에서 중요할까

    쉽게 말해 컨테이너 이미지는 서버의 골격을 압축해 둔 결과물입니다. 애플리케이션 코드만 들어가는 게 아니라, 베이스 OS 패키지, 런타임, 시스템 라이브러리까지 같이 들어가거든요. 그래서 이미지 안에 취약한 패키지가 하나만 있어도, 실제 운영에서는 꽤 민감한 이슈가 됩니다.

    예전엔 VM(Virtual Machine, 가상머신) 단위로 관리하던 걸 컨테이너로 옮기면서 배포 속도는 빨라졌는데, 그만큼 이미지가 더 자주 만들어지고 더 자주 배포됩니다. 문제는 속도가 빨라질수록 사람이 수동으로 체크할 여유가 없어집니다. 저도 처음엔 배포 직전에 한 번만 확인하면 되겠지 싶었는데, 실제로 써보니까 그 방식은 오래 못 갑니다. 빌드마다 자동으로 검사하고, 위험 수준이 높은 건 파이프라인에서 바로 걸러야 하더라고요.

    • 빌드 시점: 취약한 패키지가 이미지에 포함되는지 확인
    • 푸시 시점: 레지스트리(Registry, 이미지 저장소)에 위험한 이미지가 쌓이지 않게 제어
    • 배포 시점: Kubernetes(쿠버네티스, 컨테이너 오케스트레이션)에서 검증되지 않은 이미지 실행 방지

    여기서 중요한 포인트! 컨테이너 보안 사례를 보면 대부분 “도구 설치”에서 끝나는데, 운영은 그 다음부터 시작입니다.

    2. Trivy가 뭐고, 왜 많이 쓰는가

    Trivy는 Aqua Security에서 공개한 오픈소스 보안 스캐너로, 컨테이너 이미지 안의 OS 패키지와 애플리케이션 의존성 취약점을 확인할 수 있습니다. 그리고 이게 편한 이유가 뭔가 하면, 명령어가 비교적 단순하고 CI/CD에 넣기 쉽습니다. 저도 처음엔 Clair나 다른 스캐너도 같이 봤었는데, 실무에서는 빠르게 붙고 운영 부담이 적은 쪽이 오래 갑니다.

    쉽게 말해 Trivy는 “이 이미지 안에 알려진 취약점이 있는지”를 검사해주는 첫 번째 관문입니다. 그리고 필요하면 설정 파일 오탐도 잡고, 파일시스템이나 Git 저장소 스캔에도 활용할 수 있죠. 다만 오늘은 범위를 넓히기보다 이미지 스캔 중심의 CI/CD 보안 이야기에 집중하겠습니다.

    항목 Trivy 적용 전 Trivy 적용 후
    취약점 발견 시점 배포 후 또는 수동 점검 빌드 단계에서 자동 확인
    운영자 개입 높음 정책 기반 자동화 가능
    재현성 담당자 경험 의존 파이프라인 기준으로 일관됨
    Docker 보안 관리 개별 확인 공통 정책으로 통제

    3. 운영 기준 먼저 정하기: 실패 조건을 정하지 않으면 자동화가 흔들립니다

    이건 제가 꽤 삽질했던 부분입니다. 처음엔 취약점이 하나라도 나오면 실패 처리해봤거든요. 결과는 뻔했습니다. 팀이 바로 우회 플래그를 찾기 시작합니다. 왜냐하면 현실적으로 베이스 이미지 하나만 바뀌어도 Low나 Medium 수준이 여러 개 잡힐 수 있기 때문입니다.

    그래서 운영 기준을 이렇게 나눴습니다.

    1. 개발 브랜치: 결과는 남기되 즉시 차단하지 않음
    2. 메인 브랜치: High, Critical 취약점이 있으면 실패
    3. 릴리스 태그: 스캔 결과 아티팩트 보관 + 예외 승인된 항목만 통과
    4. 운영 배포: 승인된 이미지 다이제스트(Digest, 내용 기반 식별자)만 사용

    이렇게 나누니까 팀도 덜 답답해하고, 보안팀이나 인프라 담당도 기준을 설명하기 쉬워졌습니다. 결국 CI/CD 보안은 기술보다 합의가 먼저더라고요.

    4. 실전 구현: Trivy를 CI/CD 파이프라인에 붙이는 방식

    제가 가장 단순하게 시작했던 흐름은 이렇습니다. Docker 이미지 빌드 후 Trivy로 스캔하고, 결과가 기준을 넘으면 푸시나 배포를 막는 구조입니다. 여기서는 예시로 GitHub Actions 스타일 YAML을 보여드리지만, GitLab CI나 Jenkins에서도 개념은 거의 같습니다.

    4-1. 로컬에서 먼저 이미지 스캔해보기

    자동화 전에 로컬에서 결과를 보는 게 좋습니다. 그래야 나중에 파이프라인 로그를 봐도 덜 헷갈립니다.

    docker build -t sample-app:local .
    trivy image sample-app:local
    

    조금 더 실무적으로 보려면 심각도(Severity, 취약점 등급)를 제한해서 보는 게 편합니다.

    trivy image --severity HIGH,CRITICAL sample-app:local
    

    JSON 결과를 파일로 저장해두면 후처리에도 좋습니다.

    trivy image --format json --output trivy-result.json sample-app:local
    

    4-2. GitHub Actions 예시

    name: container-security
    
    on:
      push:
        branches:
          - main
      pull_request:
    
    jobs:
      build-and-scan:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Set up Docker Buildx
            uses: docker/setup-buildx-action@v3
    
          - name: Build image
            run: docker build -t sample-app:${{ github.sha }} .
    
          - name: Run Trivy scan
            run: |
              trivy image \
                --exit-code 1 \
                --severity HIGH,CRITICAL \
                --format table \
                sample-app:${{ github.sha }}
    
          - name: Save JSON report
            if: always()
            run: |
              trivy image \
                --format json \
                --output trivy-report.json \
                sample-app:${{ github.sha }}
    
          - name: Upload report artifact
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: trivy-report
              path: trivy-report.json
    

    여기서 중요한 건 –exit-code 1입니다. 이 옵션이 있어야 기준에 걸렸을 때 파이프라인이 실제로 실패합니다. 처음엔 결과만 출력하고 끝내는 식으로 구성했었는데, 그러면 운영에서 아무도 안 봅니다. 진짜입니다. 로그는 쌓이는데 아무도 안 봐요.

    컨테이너 보안 사례에서 Trivy 이미지 스캔이 포함된 CI/CD 보안 구성 이미지

    개발 브랜치, 메인 브랜치, 릴리스 태그에 따라 서로 다른 스캔 정책이 적용되는 예시 화면 또는 구성 다이어그램용 이미지입니다.

    4-3. 무시할 취약점은 최소한으로 관리하기

    운영하다 보면 당장 패치가 어려운 항목이 나옵니다. 이럴 때 무작정 무시하지 말고, 예외를 추적 가능한 방식으로 남겨야 합니다.

    # .trivyignore 예시
    CVE-2023-00000
    CVE-2023-11111
    

    저는 이 파일을 넣을 때 리뷰 규칙을 따로 뒀습니다.

    • 왜 무시하는지 이슈 번호 남기기
    • 대체 패치 일정 적기
    • 베이스 이미지 교체 가능 여부 확인하기

    안 그러면 .trivyignore가 점점 커지고, 나중엔 형식적인 보안이 되어버립니다.

    4-4. Kubernetes 배포 전 검증 포인트

    Kubernetes 보안 관점에서는 “스캔 완료된 이미지인가”와 “검증된 이미지 버전만 쓰는가”가 핵심입니다. 가장 기본은 태그(tag)보다 다이제스트 고정입니다. latest 같은 태그는 진짜 편해 보여도 운영에서는 추적이 어려워집니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: sample-app
      template:
        metadata:
          labels:
            app: sample-app
        spec:
          containers:
            - name: sample-app
              image: registry.example.com/sample-app@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
              imagePullPolicy: IfNotPresent
              securityContext:
                runAsNonRoot: true
                readOnlyRootFilesystem: true
    

    이미지 스캔만으로 Kubernetes 보안이 끝나는 건 아니고요, securityContext(시큐리티 컨텍스트), 비루트 실행, 읽기 전용 파일시스템 같은 기본 하드닝(hardening, 보안 강화)도 같이 가야 합니다.

    5. 제가 실제로 겪은 문제들: 트러블슈팅과 운영 팁

    여기서부터가 좀 현실적인 이야기입니다. 도구 소개 글에는 잘 안 나오는데, 실제로 붙이면 은근히 이런 문제를 많이 겪습니다.

    5-1. 베이스 이미지가 원인인 경우가 많습니다

    애플리케이션 코드는 멀쩡한데 계속 High가 뜨는 경우가 있었습니다. 알고 보니 대부분 베이스 이미지 문제였어요. 예를 들어 오래된 패키지를 포함한 이미지라면 애플리케이션 수정으로 해결이 안 됩니다. 이럴 땐 Dockerfile부터 봐야 합니다.

    FROM python:3.11-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    COPY . .
    CMD ["python", "app.py"]
    

    여기서도 끝이 아닙니다. 같은 slim 계열이라도 시점에 따라 포함 패키지가 달라질 수 있거든요. 그래서 정기 재빌드가 중요합니다. 코드를 안 바꿔도 다시 빌드하면 해결되는 취약점이 생각보다 많습니다.

    5-2. 스캔 시간 때문에 파이프라인이 느려질 수 있습니다

    처음엔 모든 브랜치에서 전체 스캔을 돌렸더니 CI 시간이 확 늘어났습니다. 이거 진짜 불만 많이 나옵니다. 그래서 저는 전략을 나눴습니다.

    • Pull Request 단계: High, Critical 중심 빠른 검사
    • Main 병합 후: 전체 이미지 스캔 + 결과 보관
    • 야간 배치: 주요 이미지 일괄 재스캔

    이렇게 하면 개발 흐름을 너무 막지 않으면서도 운영 기준은 유지할 수 있습니다.

    5-3. 취약점 수보다 맥락이 중요합니다

    숫자만 보고 “취약점 27개”라고 하면 다들 놀랍니다. 근데 실제로는 실행 경로와 무관한 패키지일 수도 있고, 이미 수정판이 준비된 경우도 있습니다. 반대로 개수가 적어도 원격 코드 실행(RCE, Remote Code Execution) 관련이면 우선순위가 훨씬 높죠. 그래서 대시보드에는 단순 개수보다 Critical 존재 여부, 고정 가능한지 여부, 운영 노출도를 같이 봤습니다.

    컨테이너 보안 사례의 Trivy 취약점 결과와 이미지 스캔 대시보드 이미지

    Critical, High, 수정 가능 여부, 베이스 이미지 기인 여부 등을 기준으로 결과를 정리한 보안 리포트 대시보드 이미지입니다.

    6. 검증과 결과: 무엇이 달라졌나

    몇 달 운영해보니 체감되는 변화가 꽤 분명했습니다. 가장 큰 건 “배포 후 발견”이 줄어든 겁니다. 이전에는 이미지가 이미 레지스트리에 올라가고 나서 보안 이슈를 알게 되는 경우가 있었는데, 지금은 빌드 단계에서 대부분 걸러집니다. 이 차이가 큽니다.

    • 개발자가 PR 단계에서 위험한 이미지 변경을 바로 인지
    • 운영자는 승인된 기준으로만 배포 가능
    • 레지스트리에 누적되는 불량 이미지 감소
    • Docker 보안 점검이 개별 담당자 감에 덜 의존

    또 하나 좋았던 건 커뮤니케이션입니다. 예전엔 보안 이슈가 나오면 “누가 확인했나요?”부터 시작했는데, 이제는 “이 이미지는 Trivy 기준을 통과했는가”로 대화가 정리됩니다. 기준이 생기니까 감정 소모가 줄더라고요.

    검증 체크리스트

    1. 빌드 직후 Trivy 스캔이 자동 실행되는가
    2. High, Critical 기준에서 파이프라인 실패가 동작하는가
    3. JSON 결과가 아티팩트로 저장되는가
    4. 운영 배포에서 다이제스트 기반 이미지를 사용하는가
    5. Kubernetes 보안 설정이 최소 기준을 만족하는가
    trivy image --severity HIGH,CRITICAL --exit-code 1 registry.example.com/sample-app:release
    kubectl get deploy sample-app -o yaml
    

    위 두 줄만 봐도 1차 확인은 꽤 됩니다. 간단하지만 실전에서는 이 기본기가 중요합니다.

    7. 운영하면서 정리한 추천 정책

    혹시 이제 막 도입하시는 분이라면, 처음부터 너무 거창하게 가지 않는 걸 추천드립니다. 저도 처음엔 모든 걸 다 통제하려다 오히려 반발만 샀거든요.

    단계 추천 정책 이유
    1단계 메인 브랜치만 차단 개발 흐름 충격 최소화
    2단계 리포트 아티팩트 보관 추적성과 감사 대응 확보
    3단계 예외 관리 프로세스 도입 무분별한 ignore 방지
    4단계 Kubernetes 배포 정책 연계 CI와 런타임 보안 연결

    이 흐름으로 가면 도입 난이도도 적당하고, 팀이 적응할 시간도 벌 수 있습니다. 특히 컨테이너 보안 사례는 도구 선정보다 운영 규칙 설계가 더 중요하다는 점, 꼭 기억하시면 좋겠습니다.

    8. 마무리: Trivy는 시작점이고, 운영 기준이 완성도를 만듭니다

    정리해보면 Trivy 자체는 비교적 가볍게 시작할 수 있는 좋은 도구입니다. 다만 진짜 차이는 이미지 스캔 결과를 어떻게 의사결정에 반영하느냐에서 나옵니다. 제가 직접 해보니, 단순히 “스캔했다”보다 “배포를 막을 수 있다”, “예외를 기록할 수 있다”, “Kubernetes 보안까지 연결된다”가 훨씬 중요했습니다.

    그리고 이건 꼭 말씀드리고 싶네요. 처음부터 완벽하게 하려고 하면 오래 못 갑니다. 먼저 CI/CD 보안 흐름에 Trivy를 넣고, 그 다음 Docker 보안 기준을 정리하고, 마지막으로 Kubernetes 보안 정책과 연결하는 순서가 현실적이었습니다. 드디어 됐다 싶었던 순간도 있었고, 베이스 이미지 하나 때문에 몇 시간 날린 적도 있었는데요, 그런 삽질이 결국 운영 기준을 더 단단하게 만들어주더라고요.

    컨테이너 보안 사례 도입 전후와 CI/CD 보안 체크포인트 요약 이미지

    도입 전후 차이, 운영 체크리스트, 다음 단계 액션 아이템을 한눈에 보여주는 요약 인포그래픽용 이미지입니다.

    다음 단계로 해볼 만한 것

    • 레지스트리 단계 스캔 자동화 추가
    • SBOM(Software Bill of Materials, 소프트웨어 구성 명세) 관리 연계
    • Admission Controller(어드미션 컨트롤러, 배포 승인 제어) 기반 배포 정책 강화
    • 이전 글의 Dockerfile 경량화 내용과 연결해 베이스 이미지 개선

    다음 글에서는 컨테이너 보안 사례를 이어서, Kubernetes 배포 정책과 Admission Controller를 어떻게 엮으면 좋은지 좀 더 깊게 다뤄보려고 합니다. 그 부분도 운영에서 체감이 크거든요.

    정리 FAQ

    Q1. Trivy는 어느 시점에 넣는 게 가장 효과적인가요?

    가장 먼저는 이미지 빌드 직후입니다. 그 다음이 레지스트리 푸시 전, 마지막이 배포 전 검증입니다.

    Q2. 모든 취약점을 차단해야 하나요?

    아닙니다. 운영에서는 보통 High, Critical부터 시작하는 편이 현실적입니다. 대신 예외 기준은 명확해야 합니다.

    Q3. 이미지 스캔만 하면 Kubernetes 보안도 충분한가요?

    아쉽지만 아닙니다. 비루트 실행, 읽기 전용 파일시스템, 권한 최소화 같은 런타임 보안 설정이 같이 필요합니다.

  • [Kubernetes] Kubernetes PodSecurity 도입 전후 비교: 보안 강화 사례 연구

    [Kubernetes] Kubernetes PodSecurity 도입 전후 비교: 보안 강화 사례 연구

    [Kubernetes] Kubernetes PodSecurity 도입 전후 비교: 보안 강화 사례 연구

    Kubernetes PodSecurity 도입을 고민하시는 분들은 아마 한 번쯤 이런 순간이 있으셨을 겁니다. 워크로드는 잘 돌아가는데, 막상 보안 점검을 해보면 privileged(특권 모드) 컨테이너가 섞여 있거나, hostPath(호스트 경로 마운트) 같은 위험한 설정이 아무 제어 없이 배포되는 경우 말이죠. 저도 처음엔 “네임스페이스만 잘 나누면 되지 않을까?” 싶었는데, 실제로 운영 환경과 홈랩에서 계속 만져보니 결국 배포 시점의 기본 보안 가드레일이 있어야 하더라고요. 그래서 이번 글에서는 Kubernetes PodSecurity 도입 전후를 어떻게 비교했는지, 어떤 문제가 줄었는지, 그리고 적용하면서 어디서 삽질했는지 경험 기반으로 정리해보겠습니다.

    특히 이 글은 Kubernetes 보안, Pod Security Standards(파드 보안 표준), 보안 정책을 실제 운영에 녹이는 관점으로 읽으시면 도움이 됩니다. 이론만 설명하는 글이 아니라, 제가 직접 해보니 어디서 걸리고 어떤 기준으로 정리해야 덜 힘든지 그 흐름을 보여드릴게요.

    PodSecurity 도입 전에는 제어가 느슨하고, 도입 후에는 네임스페이스 정책과 허용 범위가 명확해진 구조를 한눈에 보여주는 개요 이미지입니다.

    Kubernetes PodSecurity 도입이 왜 중요한가요

    쉽게 말해 PodSecurity는 “이 파드(Pod)는 어디까지 위험한 설정을 써도 되는가”를 네임스페이스 단위로 관리하는 방식입니다. 예전에는 팀마다 매니페스트를 제각각 작성하다 보니, 어떤 워크로드는 runAsNonRoot가 잘 들어가 있고, 어떤 건 아예 빠져 있고, 또 어떤 건 디버깅한다고 privileged: true를 넣어둔 채 남아 있기도 했습니다. 이게 한두 개면 눈으로 보겠는데, 서비스가 늘어나면 사람의 기억으로 통제하는 건 불가능하더라고요.

    여기서 중요한 포인트! 보안은 좋은 설정을 권장하는 것만으로는 부족합니다. 실제로는 잘못된 설정이 들어왔을 때 막아줘야 합니다. 제가 현장에서 느낀 것도 그거였습니다. 리뷰에서 빠지고, 급한 장애 대응 중엔 원칙이 밀리고, 결국 가장 약한 워크로드가 전체 클러스터 위험도를 끌어올리거든요.

    Pod Security Standards(파드 보안 표준)를 먼저 이해해야 합니다

    Pod Security Standards는 파드 보안 수준을 세 단계로 나눠서 생각하게 해줍니다. 저도 처음엔 이름만 보고 좀 딱딱하다고 느꼈는데, 실제로 써보니까 기준점이 분명해서 오히려 팀 합의가 빨라졌습니다.

    수준 설명 적합한 상황
    Privileged 제한이 거의 없는 수준입니다. 특수한 시스템 워크로드, 아주 제한적인 운영 도구
    Baseline 명백히 위험한 설정을 막는 기본선입니다. 일반적인 애플리케이션 네임스페이스의 첫 단계
    Restricted 가장 엄격한 수준으로, 안전한 기본값을 강하게 요구합니다. 보안 민감 서비스, 표준화가 잘 된 앱 워크로드

    제가 보통 권하는 방식은 이렇습니다.

    • 처음부터 전부 Restricted로 밀어붙이지 않습니다.
    • 기존 레거시 워크로드가 많다면 먼저 Baseline으로 현재 상태를 정리합니다.
    • 새로 만드는 네임스페이스나 표준화된 앱은 Restricted를 기본으로 잡습니다.

    이 접근이 중요한 이유는, 보안 정책은 강하면 좋은 게 아니라 운영에 정착해야 좋은 것이기 때문입니다. 너무 급하게 올리면 예외만 늘어나고 결국 정책이 무력화되더라고요.

    도입 전 환경에서 실제로 보였던 문제들

    제가 PodSecurity를 넣기 전에 먼저 한 일은 “무슨 설정이 돌아다니고 있는지”를 보는 것이었습니다. 막연히 위험할 것 같다가 아니라, 실제 매니페스트와 실행 중인 파드를 기준으로 봐야 하거든요. 그때 자주 보였던 문제는 아래와 같았습니다.

    • securityContext가 아예 없는 배포가 생각보다 많았습니다.
    • allowPrivilegeEscalation 설정이 빠져 있었습니다.
    • 루트(root) 권한 기반 이미지가 관성적으로 사용되고 있었습니다.
    • 운영 중 잠깐 쓰려고 만든 디버그용 파드가 그대로 남아 있었습니다.
    • 예외가 문서화되지 않아, 왜 허용했는지 아무도 설명 못 하는 설정이 있었습니다.

    이런 상태에서 보안 점검이 들어오면 굉장히 피곤해집니다. “왜 이 컨테이너는 루트로 뜨나요?” 같은 질문이 나오면, 실제 이유가 있어서가 아니라 그냥 기본값이라서 그런 경우가 많거든요. 사실 제일 무서운 건 악의적인 공격보다도 무심코 허용된 기본값입니다.

    Kubernetes PodSecurity 도입 절차: 제가 실제로 밟은 순서

    여기서는 복잡하게 가지 않고, 운영에서 비교적 덜 아픈 순서로 설명해볼게요. 핵심은 한 번에 차단(enforce)하지 말고 먼저 관찰부터 하는 겁니다.

    1. 보호할 네임스페이스를 분류합니다.
    2. audit(감사)와 warn(경고) 중심으로 먼저 적용합니다.
    3. 실패하는 워크로드를 수정합니다.
    4. 문제가 없는 네임스페이스부터 enforce(강제)로 전환합니다.
    5. 예외 워크로드는 별도 네임스페이스로 분리합니다.

    예를 들어 애플리케이션용 네임스페이스에 먼저 Baseline 경고와 감사 정책을 걸어볼 수 있습니다.

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

    이 상태에서는 배포가 바로 막히지는 않지만, 어떤 설정이 기준을 벗어나는지 확인하기 좋아요. 저는 처음부터 차단했다가 CI/CD 파이프라인이 우르르 깨지는 바람에 한 번 크게 삽질했었습니다. 그 뒤로는 무조건 경고와 감사부터 봅니다.

    조금 정리됐다 싶으면 강제로 올립니다.

    kubectl label namespace app-team \
      pod-security.kubernetes.io/enforce=baseline \
      pod-security.kubernetes.io/enforce-version=latest --overwrite

    운영에서는 latest 대신 조직에서 검증한 기준 버전을 명시적으로 관리하는 팀도 많습니다. 저는 팀 표준이 아직 흔들릴 때는 버전 기준을 문서화해두는 쪽이 더 낫다고 봅니다. 기준이 자주 바뀌면 개발팀이 체감상 더 힘들어하거든요.

    Kubernetes PodSecurity 도입 시 네임스페이스 정책 적용 흐름 이미지

    네임스페이스에 audit, warn, enforce 라벨을 붙이면서 단계적으로 정책을 강화하는 흐름을 설명하는 이미지입니다.

    Restricted(제한) 수준을 목표로 할 때 매니페스트 예시

    다음은 비교적 안전한 기본 형태의 예시입니다. 모든 앱이 이대로 되진 않겠지만, 팀 표준의 시작점으로 잡기 좋습니다.

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

    여기서 자주 보는 포인트는 아래입니다.

    • runAsNonRoot: true로 비루트 실행을 강제합니다.
    • allowPrivilegeEscalation: false로 권한 상승 가능성을 줄입니다.
    • capabilities.drop: [ALL]로 불필요한 리눅스 capability(리눅스 커널 권한 집합)를 제거합니다.
    • seccompProfile.type: RuntimeDefault로 기본 시스템 호출 필터를 사용합니다.

    도입 전후 비교: 무엇이 달라졌나

    정량 수치를 억지로 붙이기보다, 운영 체감 기준으로 말씀드리는 게 더 정확할 것 같습니다. Kubernetes PodSecurity 도입 전에는 “문제 있는 매니페스트가 배포된 뒤 나중에 발견되는 구조”였다면, 도입 후에는 “기준에 맞지 않으면 배포 단계에서 바로 드러나는 구조”로 바뀌었습니다. 이 차이가 생각보다 큽니다.

    비교 항목 도입 전 도입 후
    보안 기준 문서나 리뷰에 의존 네임스페이스 정책으로 강제
    위험 설정 발견 시점 배포 후 또는 점검 시 배포 직전 또는 배포 중
    팀 간 일관성 사람마다 다름 정책 기준으로 통일
    예외 관리 구두 합의가 많음 별도 네임스페이스와 문서로 관리

    제가 직접 해보니 제일 편했던 건, 리뷰에서 감정 소모가 줄어든다는 점이었습니다. 예전엔 “이 설정 위험하지 않나요?” 같은 대화가 사람 대 사람의 의견 충돌처럼 번질 때가 있었는데, 정책을 두고 나면 “이건 Baseline 기준에서 걸립니다”처럼 훨씬 명확해집니다. 이거 진짜 편하더라고요.

    ⚠️ 적용하면서 많이 막혔던 문제와 해결 방법

    이 섹션은 꼭 넣고 싶었습니다. 문서만 보면 간단해 보이는데, 실제로는 여기서 다들 한 번씩 막히거든요.

    1. 기존 이미지가 non-root 실행을 전제로 만들어지지 않은 경우

    Restricted 수준으로 올리면 가장 먼저 터지는 부분 중 하나입니다. 애플리케이션은 문제 없어 보여도, 이미지 내부 디렉터리 권한이나 엔트리포인트 스크립트가 루트 권한을 전제하는 경우가 있습니다. 저도 처음엔 “분명 앱은 잘 뜨는데 왜 이게 안 되지?” 싶었는데, 알고 보니 컨테이너 시작 시 쓰기 권한이 필요한 경로가 있었어요.

    이럴 때는 아래 순서로 봤습니다.

    1. 이미지가 비루트 실행을 지원하는지 확인합니다.
    2. 쓰기 경로가 꼭 필요한지 줄입니다.
    3. 필요하면 Dockerfile 단계에서 권한을 정리합니다.
    4. 정말 예외가 필요한 워크로드만 별도 분리합니다.

    2. 운영 도구와 일반 앱을 같은 기준으로 묶은 경우

    로그 수집기, CNI 관련 컴포넌트, 노드 접근이 필요한 에이전트는 일반 앱과 요구 조건이 다를 수 있습니다. 그런데 이걸 한 네임스페이스 정책으로 퉁치면 꼭 어딘가서 충돌이 납니다. 저는 한동안 “왜 어떤 건 꼭 예외가 생기지?” 하다가, 결국 애플리케이션 네임스페이스와 인프라 네임스페이스를 분리하면서 정리가 되더라고요.

    3. warn만 보고 끝내는 경우

    이건 진짜 자주 봅니다. 경고는 보이는데 바빠서 미루다 보면, 3개월 뒤에도 같은 경고가 그대로 쌓여 있어요. 그래서 저는 warn → audit → enforce 전환 일정을 미리 잡아두는 편입니다. 일정이 없으면 정책은 거의 장식품이 됩니다.

    Kubernetes PodSecurity 도입 후 경고와 배포 실패 분석 이미지

    경고 단계에서 어떤 항목이 걸리는지 확인하고, enforce 전환 전에 수정 포인트를 찾는 과정을 보여주는 이미지입니다.

    검증은 이렇게 했습니다

    보안 정책은 넣는 것보다 검증이 더 중요합니다. 정책이 존재해도 실제로 막히지 않으면 의미가 없으니까요. 제가 보통 하는 검증은 두 가지입니다. 하나는 정상 매니페스트가 잘 배포되는지, 다른 하나는 의도적으로 위험한 설정을 넣었을 때 차단되는지 확인하는 겁니다.

    예를 들어 아래처럼 위험한 설정이 들어간 테스트 파드를 만들어 봅니다.

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

    그리고 배포를 시도합니다.

    kubectl apply -f privileged-test.yaml

    정상이라면 보안 정책 위반으로 거부되는 흐름이 나와야 합니다. 반대로, 팀 표준 매니페스트는 무리 없이 통과해야 하고요. 여기서 중요한 건 단순히 “막혔다”가 아니라, 왜 막혔는지 개발자도 이해할 수 있어야 한다

    추가로 확인했던 항목은 아래와 같습니다.

    • 새 네임스페이스 생성 시 기본 라벨 정책이 누락되지 않는지
    • CI/CD에서 배포 실패 메시지가 충분히 읽히는지
    • 예외 네임스페이스가 무분별하게 늘어나지 않는지
    • 운영 문서와 실제 클러스터 라벨 상태가 일치하는지
    Kubernetes PodSecurity 도입 후 배포 검증 결과 이미지

    정상 배포와 정책 위반 배포가 어떻게 구분되는지, 운영자가 어떤 식으로 결과를 확인하는지 보여주는 이미지입니다.

    운영 기준을 문서화해야 도입이 끝납니다

    사실 PodSecurity는 라벨 몇 개 붙인다고 끝나지 않습니다. 진짜 끝은 팀 운영 기준이 문서로 남고, 다음 사람도 같은 방식으로 적용할 수 있을 때입니다. 저도 처음엔 클러스터에만 반영해두고 안심했었는데, 시간이 지나니 “이 네임스페이스는 왜 baseline이지?” 같은 질문이 계속 나오더라고요.

    그래서 저는 아래 항목을 최소 문서 세트로 남깁니다.

    • 네임스페이스별 목표 수준: baseline 또는 restricted
    • 예외 허용 기준: 누가 승인하고 언제 재검토하는지
    • 개발팀용 체크리스트: 보안 컨텍스트 기본 예시
    • 배포 실패 시 확인 순서: 어떤 필드를 먼저 볼지

    혹시 이런 경험 있으신가요? 정책은 분명 있는데, 새 프로젝트가 생길 때마다 같은 설명을 또 하고 또 하게 되는 상황 말이죠. 이런 반복을 줄이려면 결국 문서화와 템플릿화가 답입니다. 다음 글에서는 OPA Gatekeeper(오픈 정책 에이전트 게이트키퍼)나 Kyverno(카이버노) 같은 정책 엔진과 PodSecurity를 어떻게 역할 분담할지 다뤄볼 예정입니다. 이전 글에서 다룬 네임스페이스 표준화 내용이 있으시다면 같이 연결해서 보시는 것도 좋습니다.

    도입 전후의 차이, 운영 체크리스트, 다음 단계까지 한 장으로 요약한 인포그래픽 이미지입니다.

    정리: Kubernetes 보안은 작은 강제에서 시작됩니다

    Kubernetes PodSecurity 도입은 거창한 보안 프로젝트처럼 보일 수 있지만, 실제로는 “위험한 기본값을 그냥 두지 않겠다”는 아주 현실적인 출발점에 가깝습니다. 제가 여러 번 적용해보니 가장 효과적인 방식은 이렇습니다. 먼저 경고와 감사로 현재 상태를 파악하고, 그다음 Baseline으로 정리한 뒤, 준비된 워크로드부터 Restricted로 올리는 흐름이었습니다. 처음엔 이게 뭔가 싶었는데, 한 번 기준이 잡히고 나면 운영이 훨씬 편해집니다. 드디어 됐다! 싶은 순간이 오더라고요.

    마지막으로 한 줄 요약하면 이겁니다. Pod Security Standards는 문서가 아니라 운영 습관으로 들어와야 합니다. 보안 정책이 실제 배포 과정에 녹아들면, 리뷰 품질도 좋아지고 예외도 줄고, 나중에 감사 대응도 덜 힘들어집니다. Kubernetes 보안을 어디서부터 손대야 할지 막막하셨다면, PodSecurity부터 차근차근 시작해보세요.

    FAQ: 자주 묻는 질문

    모든 네임스페이스를 바로 Restricted로 가야 하나요?

    아닙니다. 레거시 워크로드가 많다면 Baseline부터 정리하는 편이 현실적입니다. 무리하게 올리면 예외만 늘어날 수 있습니다.

    PodSecurity만 있으면 보안 정책이 충분한가요?

    기본 보안 가드레일로는 매우 유용하지만, 조직별 세부 규칙까지 모두 대신하진 못합니다. 필요한 경우 별도 정책 엔진과 역할을 나눠가는 게 좋습니다.

    가장 먼저 점검할 필드는 무엇인가요?

    runAsNonRoot, allowPrivilegeEscalation, privileged, capabilities, hostPath 같은 항목부터 보는 걸 권합니다.

  • [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 같은 워크로드 리소스에도 힌트를 주기 때문에, 네임스페이스 전략과 배포 검증 루틴을 함께 가져가는 게 중요합니다.