Troubleshooting 2026.11.04 5 min read

curl은 401인데 Kubernetes는 x509였던 Registry Pull 오류

처음에는 특정 Worker에 고정한 작은 Pod를 사용했다. 불필요한 서비스 계정 토큰은 마운트하지 않았고 imagePullPolicy: Always로 새 Pull을 요청했다. 그런데 Pod는 배치됐어도 컨테이너가 시작하지 못했다.

사설 Registry에 이미지를 Push한 뒤 Kubernetes에서 Pull을 시험했다. CI Agent의 Podman에서는 이미지 게시까지 진행됐지만 클러스터의 컨테이너 런타임은 별도 클라이언트였다. 노드에 CA를 배포하고 containerd의 Registry 설정을 준비한 뒤, 시험 namespace의 imagePullSecret으로 같은 이미지를 가져오게 했다.

처음에는 특정 Worker에 고정한 작은 Pod를 사용했다. 불필요한 서비스 계정 토큰은 마운트하지 않았고 imagePullPolicy: Always로 새 Pull을 요청했다. 그런데 Pod는 배치됐어도 컨테이너가 시작하지 못했다.

스케줄링과 네트워크 초기화 다음에 막혔다

describe에는 PodScheduled: True와 Pod IP가 나타났다. PodReadyToStartContainers도 True였지만 Ready는 False였고 상태는 ImagePullBackOff였다. 이 상태 이름만으로 원인을 정하지 않고 하단 이벤트를 읽었다.

Reason: ImagePullBackOff
failed to resolve image: failed to do request
tls: failed to verify certificate:
x509: certificate signed by unknown authority

Registry manifest에 대한 HTTPS HEAD 요청이 인증서를 신뢰하지 못하고 있었다. 스케줄링과 CNI 초기화가 진행된 상태에서 TLS 단계가 막힌 것이었다. imagePullSecret을 준비했다는 이유만으로 암호 오류부터 의심할 상황은 아니었다.

먼저 실패한 Worker의 CA 파일과 hosts.toml을 확인했다. hosts.toml에는 HTTPS Registry 주소, pull과 resolve capability, CA 파일 경로가 들어 있었다. 파일이 존재하는 사실과 런타임이 그 설정을 실제 사용한다는 사실은 따로 확인해야 했다.

같은 노드의 curl과 containerd를 비교했다

결정적인 비교는 같은 Worker에서 나왔다. hosts.toml에 지정한 CA를 curl에 명시해 /v2/를 요청하자 결과는 401이었다. TLS 검증을 끝낸 뒤 Registry의 인증 요구까지 도달한 것이다. 같은 환경의 Kubernetes Pull은 계속 x509로 실패했다.

CA 파일 자체와 서버 인증서 조합을 다시 만드는 대신, containerd가 해당 CA 설정을 읽는 경로로 점검 대상을 좁혔다. 당시 실제 버전 출력은 containerd 2.2.1이었다. 이미지 Registry 플러그인의 설정은 아래처럼 두 경로를 연결하고 있었다.

[plugins.'io.containerd.cri.v1.images'.registry]
  config_path = '/etc/containerd/certs.d:/etc/docker/certs.d'

설정 파일 뒤에는 이름이 같은 빈 config_path도 있었다. 다른 플러그인의 항목이어서 단어 검색 결과만 보고 수정하면 엉뚱한 곳을 건드릴 수 있었다. 키와 함께 소속 섹션을 읽었다. containerd config dump도 설정 해석 결과로 사용했으며 실행 중 프로세스가 언제 값을 읽었는지의 직접 증거와는 구분했다.

다른 Worker에서 한 경로로 줄여 시험했다

복수 설정 경로의 처리를 의심하고 두 번째 Worker에서 먼저 변경했다. 기존 설정을 백업하고 이미지 Registry 플러그인이 읽을 경로를 하나로 줄인 뒤 containerd를 재시작했다. 이어 containerd와 kubelet의 서비스 상태를 확인했다.

[plugins.'io.containerd.cri.v1.images'.registry]
  config_path = '/etc/containerd/certs.d'

서버 인증서를 재발급하거나 TLS 검증을 비활성화하지 않았다. curl로 신뢰를 확인한 CA를 그대로 사용하면서 런타임의 설정 경로만 바꿨다. 두 번째 Worker에 같은 이미지를 Always 정책으로 요청한 새 Pod를 배치했고, 당시 성공을 확인했다고 기록했다.

그 결과를 본 뒤 첫 번째 Worker에도 순서대로 적용했다. 여러 노드의 런타임을 한꺼번에 재시작하는 방식으로 진행하지 않았다. 다만 이 결과로 containerd 2.2 전체의 버그를 확정하지는 않았다. 이 환경에서는 복수 경로를 단일 경로로 바꾼 다음 Pull이 성공했다는 관측을 남겼다.

BackOff를 보고 있는 것과 새 Pull은 달랐다

첫 번째 Worker의 기존 시험 Pod는 변경 후에도 한동안 ImagePullBackOff로 남았다. kubectl get ... -w는 상태를 지켜보는 명령이었다. 화면을 계속 보고 있다는 사실이 즉시 새 Pull을 실행했다는 뜻은 아니었다.

설정과 curl 401을 다시 확인한 뒤 새 이름으로 시험 Pod를 만들었다. namespace, Secret, 이미지, 대상 Worker를 유지하고 Always 정책으로 요청하는 조건이었다. 두 번째 Worker에서 성공한 조건을 첫 번째 Worker에서도 구분해서 확인하려 했다.

첫 번째 Worker의 최종 확인은 후속 배포 점검에서 이어졌다. 새 Always Pull 시험의 실제 Pulled 이벤트와 두 Worker의 Registry 이미지 실행을 재확인했다는 결과가 남았다. 이 후속 점검에서는 containerd 설정을 추가로 바꾸지 않았다. 다른 Control Plane까지 설정을 통일했다고 결과를 확대하지 않았다.

이번 문제에서는 같은 서버에 접근하는 두 클라이언트의 차이가 진단의 출발점이 됐다. curl의 401과 CRI의 x509를 나란히 보니 인증서 발급이나 Registry 암호를 무작정 바꿀 이유가 줄었다. Pod 배치, TLS 신뢰, 인증, 이미지 Pull을 구분하고 새 요청까지 확인한 뒤 전달 경로를 닫을 수 있었다.

원인 분석은 당시의 curl·containerd 설정 기록을, 현재 배포 확인은 2026년 10월 19일 두 Worker의 Pod imageID 메타데이터를 각각 근거로 사용했다. 과거 오류를 촬영하기 위해 Pull 실패를 다시 만들지는 않았다.

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동