본문 바로가기
IT/k8s

[k8s] Pod Security Standards(PSS) 완벽 적용 가이드

by 수누다 2026. 4. 12.

쿠버네티스 보안, 이제 PSP는 없다 — Pod Security Standards(PSS)를 써야 하는 이유

혹시 아직도 PodSecurityPolicy(PSP)로 클러스터 보안을 잡고 계신 분 있으신가요? 저도 한동안 PSP를 쓰면서 "뭐, 이 정도면 됐지" 했었는데요. 쿠버네티스 1.21에서 deprecated(더 이상 권장하지 않음)되고 1.25에서 완전히 제거되면서 진짜 당황했습니다. 급하게 마이그레이션 하면서 삽질을 꽤 했거든요.

그 경험을 바탕으로 오늘은 Pod Security Standards(PSS, 파드 보안 표준)를 처음부터 제대로 적용하는 방법을 정리해 보겠습니다. PSA(Pod Security Admission, 파드 보안 어드미션)와 함께 써야 실제로 동작하는 구조라서, 이 두 가지를 묶어서 설명할게요.

쿠버네티스 Pod Security Standards 세 가지 정책 레벨과 PSA 네임스페이스 적용 아키텍처 다이어그램

▲ Pod Security Standards의 세 가지 정책 레벨과 네임스페이스 적용 흐름 — 실제 운영 환경에서 어떤 레벨을 어디에 적용할지 한눈에 볼 수 있습니다.


Pod Security Standards(PSS)가 뭔지 먼저 제대로 이해하고 가자

쉽게 말해서, PSS는 "파드가 얼마나 위험한 권한을 가질 수 있는지"를 정의한 표준 스펙입니다. 쿠버네티스 공식 문서에서 정의한 세 가지 레벨이 있어요.

레벨 설명 사용 권장 환경
Privileged (특권) 제한 없음. 모든 권한 허용 시스템 컴포넌트, 인프라 에이전트
Baseline (기준) 최소한의 제한. 일반적인 워크로드에 적합 일반 애플리케이션 네임스페이스
Restricted (제한) 강력한 제한. 보안 모범 사례 완전 적용 보안 민감 워크로드, 멀티테넌트 환경

그리고 PSA(Pod Security Admission)는 이 PSS 정책을 실제로 네임스페이스(Namespace) 단위로 강제 적용하는 어드미션 컨트롤러입니다. PSS는 "규칙"이고, PSA는 "규칙을 집행하는 경찰관" 같은 역할이라고 보시면 돼요.

PSA에는 세 가지 모드가 있어요.

  • enforce (강제): 정책 위반 시 파드 생성 자체를 거부
  • audit (감사): 위반을 로그에 기록하지만 거부하지는 않음
  • warn (경고): 사용자에게 경고 메시지를 보여주지만 거부하지는 않음

💡 : 처음 PSS를 적용할 때는 enforce를 바로 쓰면 운영 중인 워크로드가 갑자기 죽을 수 있어요. 저는 항상 audit → warn → enforce 순서로 단계적으로 올립니다. 이게 진짜 중요한 포인트예요!


실전 적용 — Pod Security Standards 단계별로 따라해 보자

1단계: 현재 클러스터 상태 확인

제일 먼저 내 클러스터가 PSA를 지원하는 버전인지 확인해야 합니다. Pod Security Standards와 PSA는 쿠버네티스 1.23부터 beta, 1.25부터 GA(정식 출시)로 들어왔어요.

# 클러스터 버전 확인
kubectl version --short

# 현재 네임스페이스 레이블 확인
kubectl get namespaces --show-labels

2단계: audit 모드로 먼저 테스트

저는 신규 정책을 적용할 때 절대 enforce부터 시작하지 않아요. 먼저 audit 모드로 현재 워크로드가 어떤 정책을 위반하는지 파악합니다.

# 특정 네임스페이스에 audit 모드 적용
kubectl label namespace my-app-ns \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/audit-version=latest

# warn 모드도 함께 달아두면 개발자들이 바로 피드백 받을 수 있음
kubectl label namespace my-app-ns \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/warn-version=latest

이렇게 하고 나서 API 서버 로그를 보면 어떤 파드가 restricted 정책을 위반하는지 나옵니다. 저는 이걸 며칠 모니터링하면서 워크로드를 하나씩 고쳤어요.

3단계: 워크로드 YAML 수정

restricted 레벨을 통과하려면 파드 스펙에 몇 가지를 반드시 설정해야 해요. 처음엔 이게 뭔가 싶었는데, 알고 보면 다 이유가 있는 설정들이더라고요.

쿠버네티스 Restricted 레벨 통과를 위한 파드 securityContext 필수 설정 항목 다이어그램

▲ Restricted 레벨 통과를 위한 파드 securityContext 필수 설정 항목 — 각 필드가 어떤 보안 위협을 막는지 함께 표시했습니다.

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: my-app-ns
spec:
  securityContext:
    # 루트가 아닌 사용자로 실행 강제
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    # Seccomp 프로파일 설정 (restricted 필수)
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: my-app:latest
    securityContext:
      # 권한 상승 차단
      allowPrivilegeEscalation: false
      # 모든 Linux Capabilities 제거
      capabilities:
        drop:
        - ALL
      # 읽기 전용 루트 파일시스템
      readOnlyRootFilesystem: true
    # 리소스 제한도 함께 설정
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

여기서 제가 제일 많이 틀렸던 부분이 seccompProfile이에요. restricted 레벨에서는 이게 반드시 있어야 하는데, 기존 워크로드에 없는 경우가 대부분이라서 꼭 추가해 주셔야 합니다.

4단계: enforce 모드 적용

audit/warn으로 충분히 검증하고 워크로드 수정이 끝났다면, 이제 진짜 enforce를 붙일 차례입니다.

# enforce 레이블 추가
kubectl label namespace my-app-ns \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest

# 현재 네임스페이스 레이블 최종 확인
kubectl get namespace my-app-ns -o yaml

아래처럼 세 가지 모드를 함께 달아두는 게 제가 실제 운영에서 쓰는 패턴이에요.

apiVersion: v1
kind: Namespace
metadata:
  name: my-app-ns
  labels:
    # 정책 위반 시 거부
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # 감사 로그 기록
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest
    # 개발자에게 경고 표시
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest

5단계: 시스템 네임스페이스 처리

kube-system 같은 시스템 네임스페이스는 privileged로 두어야 해요. 저처음에 이걸 모르고 kube-system에 baseline을 붙였다가 CNI 플러그인이 죽어버린 적이 있습니다. 진짜 아찔했어요.

# 시스템 네임스페이스는 privileged 유지
kubectl label namespace kube-system \
  pod-security.kubernetes.io/enforce=privileged

kubectl label namespace kube-node-lease \
  pod-security.kubernetes.io/enforce=privileged

kubectl label namespace kube-public \
  pod-security.kubernetes.io/enforce=privileged

⚠️ 실제로 겪은 트러블슈팅 — 이것만 조심하세요

문제 1: DaemonSet이 갑자기 파드를 못 띄우는 경우

Fluentd나 Datadog Agent 같은 모니터링 에이전트는 보통 hostPath 볼륨이나 hostNetwork를 쓰거든요. 이게 restricted는 물론이고 baseline도 위반하는 경우가 있어요. 이런 경우엔 해당 에이전트가 뜨는 네임스페이스만 privileged로 따로 분리하거나, baseline 레벨로 낮추는 게 현실적입니다.

# 모니터링 에이전트 전용 네임스페이스
kubectl label namespace monitoring \
  pod-security.kubernetes.io/enforce=privileged

문제 2: Helm 차트 배포 시 경고 폭탄

Helm으로 서드파티 차트를 배포하면 warn 모드에서 경고가 엄청 나와요. 이게 실제로 거부되는 건 아니지만 CI/CD 파이프라인에서 로그가 지저분해져서 처음엔 당황했습니다. 이 경우 차트의 values.yaml에서 securityContext를 직접 오버라이드해 주거나, 해당 네임스페이스의 레벨을 baseline으로 조정하는 방식으로 해결했어요.

문제 3: 버전 고정 vs latest

enforce-version에 latest 대신 특정 버전(예: v1.29)을 고정하면 쿠버네티스 업그레이드 시 새로 추가된 정책 때문에 갑자기 워크로드가 죽는 걸 방지할 수 있어요. 운영 환경에서는 버전을 고정하는 걸 강력히 추천합니다.

# 버전 고정 예시
kubectl label namespace my-app-ns \
  pod-security.kubernetes.io/enforce-version=v1.29

✅ Pod Security Standards 적용 결과 검증하기

enforce를 붙인 다음에 제대로 동작하는지 테스트해 보는 게 중요합니다. 저는 항상 일부러 "나쁜" 파드를 배포해서 거부되는지 확인해요.

# 고의로 root 권한 파드 배포 시도 (거부되어야 정상)
kubectl run test-root \
  --image=nginx \
  --namespace=my-app-ns \
  --overrides='{"spec":{"securityContext":{"runAsUser":0}}}'

# 예상 출력:
# Error from server (Forbidden): pods "test-root" is forbidden:
# violates PodSecurity "restricted:latest": ...

# 정상 파드는 잘 뜨는지 확인
kubectl run test-ok \
  --image=nginx \
  --namespace=my-app-ns \
  --overrides='{
    "spec":{
      "securityContext":{
        "runAsNonRoot":true,
        "runAsUser":1000,
        "seccompProfile":{"type":"RuntimeDefault"}
      },
      "containers":[{
        "name":"test-ok",
        "image":"nginx",
        "securityContext":{
          "allowPrivilegeEscalation":false,
          "capabilities":{"drop":["ALL"]}
        }
      }]
    }
  }'

# 파드 상태 확인
kubectl get pods -n my-app-ns

🎉 "Error from server (Forbidden)" 메시지가 뜨면 PSS가 제대로 동작하고 있는 겁니다! 처음 이 메시지 봤을 때 진짜 뿌듯하더라고요.

Pod Security Standards enforce 모드 적용 후 정책 위반 파드 거부 및 정상 파드 생성 검증 결과

▲ enforce 모드 적용 후 검증 결과 — 정책 위반 파드는 거부되고, 보안 설정이 올바른 파드는 정상 생성되는 것을 확인할 수 있습니다.


자주 묻는 질문 (FAQ)

Q. PSP에서 Pod Security Standards로 마이그레이션할 때 제일 중요한 게 뭔가요?

A. audit 모드로 먼저 현황 파악하는 게 핵심입니다. PSP는 클러스터 전체에 적용됐지만, PSS는 네임스페이스 단위라서 네임스페이스별로 적절한 레벨을 선택하는 전략이 필요해요.

Q. restricted 레벨이 너무 빡빡한데 baseline이랑 차이가 뭔가요?

A. 가장 큰 차이는 seccompProfile 필수 여부와 allowPrivilegeEscalation 기본값이에요. baseline은 seccompProfile 없어도 되고 권한 상승도 기본 허용이지만, restricted는 둘 다 명시적으로 설정해야 합니다.

Q. 기존 워크로드가 너무 많아서 한 번에 못 바꾸겠어요.

A. 저도 그랬어요. 이럴 땐 새로 만드는 네임스페이스부터 restricted를 적용하고, 기존 네임스페이스는 baseline부터 시작해서 점진적으로 올리는 전략을 추천합니다.


마무리 — Pod Security Standards 적용 완벽 가이드

▲ PSP에서 PSS로의 마이그레이션 로드맵 요약 — audit 모니터링 → 워크로드 수정 → enforce 적용의 3단계 전략을 한눈에 볼 수 있습니다.

오늘 다룬 내용을 정리하면 이렇습니다.

  1. Pod Security Standards는 Privileged / Baseline / Restricted 세 레벨로 파드 보안을 정의합니다
  2. PSA가 네임스페이스 레이블을 통해 PSS 정책을 실제로 강제합니다
  3. enforce 전에 반드시 audit → warn 모드로 현황을 파악하세요
  4. restricted 레벨은 seccompProfile, allowPrivilegeEscalation, capabilities 설정이 필수입니다
  5. kube-system 같은 시스템 네임스페이스는 privileged로 유지해야 합니다
  6. 운영 환경에서는 latest 대신 버전을 고정하세요

쿠버네티스 보안은 한 번에 완벽하게 하려고 하면 오히려 지쳐요. 저도 여러 번의 마이그레이션을 하면서 느낀 건데, 조금씩 레벨을 올리면서 워크로드를 맞춰가는 게 현실적인 방법입니다. audit 모드라는 안전망이 있으니까 겁내지 말고 일단 달아보세요!

다음 글에서는 OPA(Open Policy Agent, 오픈 정책 에이전트) Gatekeeper를 이용해서 PSS보다 더 세밀한 커스텀 정책을 만드는 방법을 다룰 예정입니다. PSS로 큰 틀을 잡고, Gatekeeper로 세부 규칙을 추가하면 진짜 탄탄한 보안 체계가 완성되거든요. 기대해 주세요!

궁금한 점이나 PSS 적용하면서 막히는 부분 있으면 댓글로 남겨주세요. 제가 직접 겪은 케이스라면 같이 해결해 볼게요.