- Published on
GKE Autopilot privileged·hostPath 금지 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
GKE Autopilot로 옮기거나(또는 새로 시작하면서) 가장 먼저 부딪히는 벽 중 하나가 privileged 컨테이너와 hostPath 볼륨입니다. 기존 GKE Standard/EKS/온프레미스에서 잘 돌아가던 DaemonSet(로그/모니터링 에이전트, CNI 보조 컴포넌트, 디바이스 플러그인, 노드 튜닝 스크립트 등)이 Autopilot에서는 배포 단계에서 바로 막힙니다.
대표적인 증상은 아래와 같습니다.
admission webhook "autopilot.gke.io" denied the request: ... privileged containers are not allowed... hostPath volume is not allowed... violates Autopilot workload restrictions
이 글에서는 왜 Autopilot에서 금지되는지를 먼저 짚고, 이어서 대체 가능한 설계/구현 패턴을 “바로 적용 가능한 수준”으로 정리합니다. (결론부터 말하면, Autopilot은 노드에 대한 직접 접근을 전제로 한 워크로드를 허용하지 않으므로, 기능을 “노드 의존”에서 “플랫폼/관리형 API 의존”으로 바꾸는 방향이 핵심입니다.)
> 참고: 쿠버네티스 장애/복구 관점에서 노드 계층 문제를 다루는 글은 K8s NodeNotReady - CNI 플러그인 장애 복구 가이드도 함께 보면 맥락 파악에 도움이 됩니다.
Autopilot에서 privileged·hostPath가 막히는 이유
1) Autopilot의 책임 경계: “노드는 Google이 관리”
Autopilot은 노드(머신 타입/OS 이미지/커널 파라미터/대부분의 데몬 구성)를 사용자가 직접 제어하지 못하도록 설계되어 있습니다. 대신 클러스터 운영을 단순화하고, 보안 경계를 강하게 가져갑니다.
privileged: true는 컨테이너가 사실상 노드 권한을 얻게 만드는 설정입니다.hostPath는 노드 파일시스템을 컨테이너에 그대로 노출합니다.
이 둘은 멀티테넌시/관리형 운영 모델에서 가장 위험한 축에 속합니다. 따라서 Autopilot은 정책(Admission) 레벨에서 차단합니다.
2) Pod Security(PSA)와 Autopilot 제약의 교집합
쿠버네티스 자체도 Pod Security Standards(Restricted/Baseline/Privileged)를 통해 유사한 제약을 권장합니다. Autopilot은 그보다 더 강하게, 특정 필드 자체를 “사용 불가”로 막습니다.
즉, “PSA를 완화해서 해결” 같은 접근은 Autopilot에서 통하지 않는 경우가 많습니다(클러스터 운영자가 정책을 바꿀 수 없거나 제한적).
3) 실제로 막히는 YAML 패턴
아래는 Autopilot에서 흔히 거부되는 예시입니다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-agent
spec:
selector:
matchLabels:
app: node-agent
template:
metadata:
labels:
app: node-agent
spec:
containers:
- name: agent
image: example/agent:1.0
securityContext:
privileged: true
volumeMounts:
- name: host-root
mountPath: /host
volumes:
- name: host-root
hostPath:
path: /
type: Directory
이런 형태는 “노드 내부를 직접 보고/조작”하는 워크로드로 분류되며 Autopilot에서 원천적으로 불가합니다.
먼저 해야 할 진단: 무엇 때문에 privileged/hostPath가 필요한가?
해결은 결국 “대체 설계”이므로, 요구사항을 분해해야 합니다. 보통 아래 중 하나입니다.
- 로그 수집:
/var/log, container runtime 로그, journald 접근 - 메트릭 수집: cAdvisor/노드 exporter, 커널/디스크/네트워크 지표
- 보안/감사: eBPF, Falco류, 커널 이벤트
- 스토리지: 로컬 디스크를 직접 마운트하거나, 특정 경로를 공유
- 네트워크: iptables, sysctl, CNI 구성 변경
Autopilot에서 45번은 특히 “노드 조작”이 강해 어렵고, 13번은 관리형 기능 또는 sidecar/SDK 기반으로 우회 가능한 경우가 많습니다.
해결 전략 A: 노드 의존 에이전트를 관리형 Observability로 교체
가장 흔한 케이스는 “로그/메트릭 DaemonSet”입니다. Autopilot에서는 노드에 붙는 형태의 에이전트가 제약을 받기 쉬우므로, 가능한 경우 **Google Cloud Managed Service(Cloud Logging/Monitoring)**로 전환하는 것이 정석입니다.
로그 수집 대안
- 애플리케이션 로그는 stdout/stderr로 내보내고, Cloud Logging이 수집하도록 구성
- 파일 기반 로그를 hostPath로 읽는 패턴은 지양
애플리케이션에서 파일로만 로그를 쓰고 있다면, 아래처럼 stdout으로 전환하거나(권장), 부득이하면 앱 내부에서 sidecar로 파일을 읽어 stdout으로 전달하는 형태로 바꿉니다(단, 이 또한 “공유 볼륨”이 필요하며 hostPath가 아닌 emptyDir/PVC로 설계해야 합니다).
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
volumes:
- name: app-logs
emptyDir: {}
containers:
- name: app
image: example/app:1.0
volumeMounts:
- name: app-logs
mountPath: /var/log/app
- name: log-forwarder
image: busybox
command: ["sh", "-c", "tail -n+1 -F /var/log/app/app.log"]
volumeMounts:
- name: app-logs
mountPath: /var/log/app
핵심은 노드 파일시스템이 아니라 Pod 내부 볼륨을 사용하도록 바꾸는 것입니다.
메트릭 수집 대안
노드 exporter류를 DaemonSet으로 깔아 /proc, /sys를 hostPath로 읽는 방식은 Autopilot에서 막힐 가능성이 큽니다. 대신:
- Cloud Monitoring의 Kubernetes 통합(관리형 수집)
- 애플리케이션 메트릭은 Prometheus 형식으로 노출하고(HTTP), 관리형 Prometheus/스크래핑을 사용
애플리케이션 메트릭은 아래처럼 “노드 접근 없이”도 충분히 제공합니다.
apiVersion: v1
kind: Service
metadata:
name: app-metrics
labels:
app: app
spec:
selector:
app: app
ports:
- name: http-metrics
port: 9090
targetPort: 9090
해결 전략 B: hostPath가 필요한 경우 → PVC/CSI/Config로 재설계
hostPath는 보통 “노드의 특정 디렉터리를 데이터 저장소로 쓰고 싶다”는 의도에서 등장합니다. Autopilot에서는 노드가 유동적이고, 노드 로컬에 의존하면 스케일/복구 시 데이터 유실 리스크가 커집니다.
1) 영속 데이터는 PVC로
예: /data를 hostPath로 붙이던 워크로드는 아래처럼 PVC로 바꿉니다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
storageClassName: standard-rwo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: example/app:1.0
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
2) 설정/인증서는 ConfigMap/Secret로
노드에 /etc/foo/config.yaml 같은 파일을 두고 hostPath로 읽던 패턴은 ConfigMap/Secret로 치환합니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
config.yaml: |
featureA: true
endpoint: https://api.example.com
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: example/app:1.0
volumeMounts:
- name: config
mountPath: /etc/app
volumes:
- name: config
configMap:
name: app-config
해결 전략 C: privileged 대신 capability 최소화(가능한 경우)
Autopilot에서 privileged는 대개 불가하지만, 워크로드가 실제로는 “일부 capability만” 필요했던 경우가 있습니다. 예를 들어 raw socket, time 설정, 특정 네트워크 작업 등.
다만 Autopilot의 정책은 capability 추가도 제한될 수 있으니, 가능성 체크용 템플릿으로 이해하는 것이 좋습니다.
securityContext:
privileged: false
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
이 접근의 요점은:
privileged를 쓰지 말고- 필요한 capability만 최소로 추가하고
- 나머지는 전부 drop
하지만 “노드 커널/iptables 조작” 같은 건 capability만으로도 해결이 안 되며, Autopilot 철학과도 충돌합니다.
해결 전략 D: DaemonSet/노드 튜닝 워크로드는 Autopilot에선 포기하고 Standard로 분리
가끔은 정답이 “Autopilot에서 안 하는 것”입니다.
- 커널 파라미터(sysctl) 튜닝
- 커스텀 CNI/iptables 조작
- 로컬 디스크/디바이스 직접 제어(GPU/특수 HW는 예외적으로 지원되기도 하지만, 일반화하면 노드 직접 제어는 어려움)
이런 요구가 제품의 핵심이라면 선택지는 보통 두 가지입니다.
- 해당 워크로드만 GKE Standard 클러스터로 분리
- Autopilot은 “일반 앱/웹/API” 중심으로 쓰고, 노드 의존 컴포넌트는 다른 실행 환경으로 이동
운영 관점에서는 “클러스터를 2개로 쪼개는 비용”과 “노드 접근이 주는 리스크/운영부담”을 비교해야 합니다.
실제 오류 메시지 기반 트러블슈팅 체크리스트
1) 이벤트에서 거부 사유를 정확히 확인
kubectl describe pod <pod>
kubectl get events --sort-by=.metadata.creationTimestamp
Autopilot 거부는 대개 Admission 단계에서 발생하므로, Pod가 아예 생성되지 않거나 곧바로 Failed로 떨어집니다. 이벤트에 autopilot.gke.io 또는 workload restriction 관련 문구가 있으면 정책 위반입니다.
2) YAML에서 금지 필드를 빠르게 탐색
securityContext.privileged: trueallowPrivilegeEscalation: truehostPath:hostNetwork: true/hostPID: true/hostIPC: true(이들도 종종 제한)
간단히 grep으로도 찾을 수 있습니다.
grep -R "privileged:\|hostPath:\|hostNetwork:\|hostPID:\|hostIPC:" -n k8s/
3) “왜 필요한가”를 한 단계 내려서 재정의
예:
- 목적: 노드의
/var/log/containers읽기 - 진짜 목적: 애플리케이션 로그 수집
- 대안: stdout 로깅 + 관리형 로깅
이렇게 목적을 재정의하면 Autopilot 제약 안에서 풀리는 경우가 많습니다.
마이그레이션 예시: node-exporter DaemonSet → 애플리케이션 메트릭 + 관리형 수집
기존:
- node-exporter가 hostPath로
/proc,/sys를 읽음 - privileged 또는 hostPID가 섞여 있음
변경:
- 인프라 메트릭은 Cloud Monitoring이 제공하는 범위에서 수용
- 애플리케이션 메트릭은
/metrics로 노출 - 필요 시 SLO/알림은 앱 메트릭 기반으로 재설계
이때 중요한 포인트는 “노드 레벨 지표를 100% 동일하게 복제”하려고 하면 Autopilot과 충돌한다는 점입니다. Autopilot에서는 노드가 구현 디테일이고, 사용자는 **서비스 레벨(요청 성공률/지연/에러율/큐 적체)**에 집중하는 쪽이 맞습니다.
보안/운영 관점에서의 결론
- Autopilot의
privileged/hostPath금지는 단순 제한이 아니라 운영 모델 자체의 전제입니다. - 해결은 “정책 우회”가 아니라 아키텍처 변경(관리형 기능 활용, PVC/ConfigMap/Secret 전환, capability 최소화, 필요 시 Standard로 분리)로 접근해야 합니다.
- 특히 로그/메트릭 목적의 DaemonSet은 대부분 stdout 기반 + 관리형 Observability로 치환 가능합니다.
클라우드/쿠버네티스 운영에서 자주 나오는 권한/정책 이슈는 결국 “플랫폼이 책임지는 경계”를 이해하는 게 핵심입니다. 비슷한 맥락으로, AWS 쪽에서 권한 경계로 장애가 터질 때는 GitHub Actions OIDC로 AWS 권한 오류 해결하기처럼 ‘토큰/권한 모델’을 먼저 정리하면 해결이 빨라집니다.
필요하면, 실제로 막히는 리소스 YAML(Deployment/DaemonSet)과 에러 이벤트를 기준으로 “Autopilot 호환 형태로 어떻게 바꿀지”를 케이스별로 리라이팅 예시까지 이어서 정리해 드릴 수 있습니다.