본문으로 건너뛰기

privileged: true인데 permission denied가 났습니다

· 약 2분

컨테이너가 비 root 사용자로 실행되면 privileged: true를 지정해도 capability가 전달되지 않습니다. 커널이 UID를 전환하면서 permitted 집합과 effective 집합을 비우기 때문입니다.

백업이 조용히 멈췄습니다

velero로 PVC를 백업하는데 어느 날부터 파일시스템 백업만 누락되기 시작했습니다. velero server 파드는 계속 Running이었고 백업 오브젝트도 만들어졌습니다. 상태만 PartiallyFailed였습니다.

파일시스템 백업을 담당하는 쪽은 node-agent DaemonSet입니다. 로그에는 한 줄이 반복되고 있었습니다.

open /host_pods/: permission denied

매니페스트에는 privileged: true가 있었습니다.

요청한 capability와 실제 capability가 다릅니다

privileged: true를 지정했는데 권한이 없다면 요청값과 결과값을 따로 확인해야 합니다. 컨테이너가 실제로 어떤 사용자와 capability로 실행 중인지는 crictl로 봅니다.

crictl inspect <container-id> \
| jq '.info.runtimeSpec.process | {user, capabilities}'

여기에는 요청한 capability 41개가 그대로 보입니다. 프로세스가 실제로 보유한 값은 다릅니다. 같은 uid로 프로브 파드를 띄워 /proc/self/status를 직접 읽으면 차이가 드러납니다.

kubectl run capprobe --rm -it --restart=Never --image=nginx:alpine \
--overrides='{"spec":{"containers":[{"name":"p","image":"nginx:alpine",
"securityContext":{"privileged":true,"runAsUser":1002},
"command":["grep","Cap","/proc/self/status"]}]}}'
집합
CapBnd (bounding)41개 전부
CapPrm (permitted)0
CapEff (effective)0

privileged: true는 bounding set만 채웁니다. 실제로 권한을 행사하는 effective set은 비어 있습니다. 매니페스트만 읽어서는 이 차이가 보이지 않습니다.

커널이 UID를 전환할 때 집합을 비웁니다

capabilities(7) 문서에 규칙이 적혀 있습니다.

If the effective user ID is changed from nonzero to 0, then the permitted set is copied to the effective set.

0에서 0이 아닌 값으로 바뀔 때는 반대로 동작합니다. 커널이 permitted 집합과 effective 집합을 비웁니다. SECBIT_NO_SETUID_FIXUP 시큐어비트를 설정하면 이 동작을 막을 수 있지만, 컨테이너 런타임은 이 비트를 설정하지 않습니다. Docker에서도 같은 동작이 보고되어 있습니다.

bounding set은 앞으로 획득할 수 있는 capability의 상한만 정합니다. 이미 비워진 effective set을 되살리지는 않습니다.

이미지의 기본 사용자가 root가 아니었습니다

velero/velero:v1.15.2의 기본 사용자를 확인하면 cnb:cnb가 나옵니다. uid 1002입니다.

nerdctl image inspect velero/velero:v1.15.2 | jq -r '.[0].Config.User'
# cnb:cnb

호스트의 /var/lib/kubelet/pods는 권한이 0750이고 소유자가 root:root입니다. uid 1002로 실행되는 프로세스가 CAP_DAC_OVERRIDE 없이 이 디렉터리를 열려고 하면 거부당합니다.

파드 레벨 runAsUser를 0으로 둡니다

컨테이너 레벨 privileged가 아니라 파드 레벨 runAsUser가 필요합니다.

spec:
securityContext:
runAsUser: 0
containers:
- name: node-agent
securityContext:
privileged: false

upstream의 velero install --use-node-agent도 같은 조합을 사용합니다. runAsUser: 0이 있으면 containerd 기본 capability 집합에 CAP_DAC_OVERRIDE가 포함되므로 privileged를 켤 이유가 없습니다.

몇 달 동안 정상이던 설정이 배포 한 번에 실패했습니다

이 매니페스트는 처음부터 틀려 있었습니다. 그런데도 몇 달 동안 백업이 정상으로 동작했습니다. 이미지의 기본 사용자가 중간에 바뀌었고, 파드는 그 뒤로 재생성되지 않아 예전 이미지를 계속 사용하고 있었기 때문입니다.

CI가 인프라를 배포하면서 파드를 재생성한 순간 새 이미지를 가져왔고, 그때 장애가 드러났습니다. 이미지 태그가 같아도 파드가 재생성될 때 이미지가 함께 교체됩니다.

감지가 늦은 이유는 따로 있었습니다. velero server 파드는 계속 Running이라 kubectl get pods만 확인해서는 정상으로 보였습니다. 실제로 멈춘 쪽은 DaemonSet이었고, 증상은 백업 오브젝트의 PartiallyFailed 상태에만 남았습니다. 복구까지 25시간이 걸렸습니다.

로그를 확인할 때 시간대를 섞지 않습니다

kubectl이 출력하는 타임스탬프와 velero 백업 이름은 UTC를 씁니다. journalctl--since--until은 로컬 시간을 받습니다. 두 값을 섞으면 9시간 어긋난 구간을 확인하게 됩니다.

참고