Cloudera AI(CAI) 워크벤치의 사용자 네임스페이스(<workspace>-user-N)에서 사설 레지스트리 이미지를 당기기 위해 ServiceAccount 에 imagePullSecrets 를 붙이는 방식이 성립하는지 검증했다. 결론은 성립하지 않는다. 컨트롤러가 5분 주기 reconcile 로 네임스페이스를 원상 복구하며, 모델 파드에는 워크로드별 liftie-… 시크릿을 직접 주입한다. 지속되는 해법은 워크벤치나 컨트롤 플레인의 레지스트리 자격증명 설정을 바꾸는 것이다.
# 1. 모델 파드가 쓰는 SA 와 pull secret 확인
kubectl get pods -n <ns> -o custom-columns='POD:.metadata.name,SA:.spec.serviceAccountName,SECRETS:.spec.imagePullSecrets[*].name'
# 2. 시크릿 복제 — 메타데이터를 상속하지 않도록 값만 뽑아 새로 만든다 (bash 프로세스 치환)
kubectl create secret generic <pull-secret> -n <ns> --type=kubernetes.io/dockerconfigjson \
--from-file=.dockerconfigjson=<(kubectl get secret <pull-secret> -n <src-ns> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d)
# 3. SA patch
kubectl patch sa <sa> -n <ns> -p '{"imagePullSecrets":[{"name":"<pull-secret>"}]}'
# 4. 판정 — 테스트 파드의 imagePullSecrets 에 두 개가 보이면 성립, liftie-… 하나만 보이면 불성립
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.imagePullSecrets[*].name}{"\n"}'
kubectl get secret -o yaml | grep -v ... | kubectl apply 식 복제는 ownerReferences, labels, annotations 가 따라와 GC 나 GitOps 컨트롤러가 지울 수 있다. --type 을 생략하면 Opaque 가 되어 kubelet 이 pull secret 으로 인정하지 않는다. .dockerconfigjson 키 이름은 --from-file= 앞에 명시해야 하며, 생략하면 파일 디스크립터 번호가 키가 된다.
| 증상 | 원인 | 확인 |
|---|---|---|
| 생성 직후 수 초 내 삭제 | ownerReferences 잔존으로 GC |
-o jsonpath='{.metadata.ownerReferences}' |
| 불규칙하게 삭제 | ArgoCD·Flux·Helm 라벨을 상속해 prune | {.metadata.labels}{.metadata.annotations} |
| 정확히 일정 주기(5분)로 삭제, SA patch 도 원복 | 네임스페이스 컨트롤러 reconcile | kubectl get secret,sa -n <ns> -w --output-watch-events |
| 시크릿이 아니라 네임스페이스가 새로 생김 | 워크로드 종료 시 네임스페이스 재생성 | kubectl get ns <ns> -o jsonpath='{.metadata.creationTimestamp}' |
삭제 주체는 managedFields 의 manager 이름이 가장 빠른 단서다.
kubectl get secret -n <ns> -o custom-columns='NAME:.metadata.name,TYPE:.type,MANAGER:.metadata.managedFields[*].manager'
CAI 는 모델 배포마다 liftie-<hash>-secret-<hash>-<workload> 형태의 전용 pull secret 을 만들어 파드 spec 에 직접 넣는다. 그 값은 워크벤치 설정(또는 Vault 의 kv-cdp-*/cdp/...)에서 읽으므로 네임스페이스에 직접 밀어 넣은 리소스는 다음 reconcile 때 desired state 로 되돌아간다. .dockerconfigjson 의 auths 키는 문자열 정확 매칭이라 IP 로 등록된 자격증명은 FQDN 요청에 적용되지 않는다.
이미지 pull 실패의 실제 원인은 kubectl describe pod | tail -30 의 Events 로 먼저 가른다. 401 Unauthorized 만 자격증명 문제이고, manifest unknown 은 이미지 부재, x509 는 인증서 신뢰, no such host 는 DNS 문제라 Vault 나 시크릿을 손봐도 해결되지 않는다. Vault CLI 는 전역 플래그를 서브커맨드 뒤에 두고(vault kv list -tls-skip-verify <path>), KV v2 마운트에서는 경로에 data/ 를 직접 넣지 않는다. Vault 의 integrated storage 는 barrier key 로 암호화되어 있어 파일을 직접 열어 토큰을 꺼낼 수 없으며, 토큰은 설치 시 저장된 Kubernetes 시크릿이나 vault operator generate-root 로만 얻는다. umask 077 은 프로세스 속성이라 전역이 아니지만 mktemp 가 이미 0600 으로 만들므로 불필요하다.