목록으로

Programming Notes

보이지 않는 재작업: Kubernetes 이미지 프로모터 현대화

registry.k8s.io 에서 가져오는 모든 컨테이너 이미지는 kpromo 라는 Kubernetes 이미지 프로모터를 통해 그곳에 도달합니다. 이 도구는 이미지를 스테이징 레지스트리에서 프로덕션으로 복사하고, cosign 으로 서명하며, 20개 이상의 지역 미러에 걸쳐...

registry.k8s.io에서 가져오는 모든 컨테이너 이미지는 kpromo라는 Kubernetes 이미지 프로모터를 통해 그곳에 도달합니다. 이 도구는 이미지를 스테이징 레지스트리에서 프로덕션으로 복사하고, cosign으로 서명하며, 20개 이상의 지역 미러에 걸쳐 서명을 복제하고, SLSA 출처 증명(provenance attestations)을 생성합니다. 이 도구가 고장 나면 Kubernetes 릴리스는 출시될 수 없습니다. 지난 몇 주 동안 우리는 이 도구의 핵심을 처음부터 다시 작성하여 코드베이스의 20%를 삭제하고 속도를 획기적으로 향상시켰지만, 아무도 알아채지 못했습니다. 바로 그것이 핵심이었습니다.

간략한 역사

이미지 프로모터는 2018년 말 Linus Arver의 내부 Google 프로젝트로 시작되었습니다. 목표는 간단했습니다. 수동적이고 Googler에게만 허용되던 k8s.gcr.io로 컨테이너 이미지를 복사하는 프로세스를 커뮤니티가 소유하는 GitOps 기반 워크플로로 대체하는 것이었습니다. 스테이징 레지스트리에 푸시하고, YAML 매니페스트와 함께 PR을 열고, 검토 및 병합을 거치면 나머지는 자동화가 처리합니다. KEP-1734가 이 제안을 공식화했습니다.

2019년 초, 코드는 kubernetes-sigs/k8s-container-image-promoter로 이전되었고 빠르게 성장했습니다. 다음 몇 년 동안 Stephen Augustus는 여러 도구들(cip, gh2gcs, krel promote-images, promobot-files)을 kpromo라는 단일 CLI로 통합했습니다. 리포지토리 이름은 promo-tools로 변경되었습니다. Adolfo Garcia Veytia (Puerco)는 cosign 서명 및 SBOM 지원을 추가했습니다. Tyler Ferrara는 취약점 스캐닝을 구축했습니다. Carlos Panato는 프로젝트를 건강하고 릴리스 가능한 상태로 유지했습니다. 42명의 기여자가 60개 이상의 릴리스에 걸쳐 약 3,500개의 커밋을 했습니다.

이 시스템은 작동했지만, 2025년이 되자 코드베이스는 7년간의 점진적인 추가 기능으로 인한 부담을 안게 되었습니다. README는 명시적으로 다음과 같이 말했습니다: 중복 코드, 동일한 작업을 수행하는 여러 기술, 그리고 여러 TODO를 보게 될 것입니다.

해결해야 할 문제들

Kubernetes 코어 이미지의 프로덕션 프로모션 작업은 정기적으로 30분 이상 소요되었고, 속도 제한(rate limit) 오류로 자주 실패했습니다. 핵심 프로모션 로직은 확장하기 어렵고 테스트하기 힘든 모놀리식으로 성장했으며, 이로 인해 출처(provenance) 또는 취약점 스캐닝과 같은 새로운 기능을 추가하는 것이 고통스러웠습니다.

SIG Release 로드맵에는 "아티팩트 프로모터 재작성"과 "아티팩트 유효성 검사 강화"라는 두 가지 작업 항목이 오랫동안 남아 있었습니다. 우리는 SIG Release 회의와 KubeCon에서 이에 대해 논의했으며, 프로젝트 보드 #171의 공개 연구 스파이크(research spikes)는 진행하기 전에 답변이 필요한 여덟 가지 질문을 담고 있었습니다.

이 모든 질문에 답할 하나의 이슈

2026년 2월, 우리는 이슈 #1701("아티팩트 프로모터 파이프라인 재작성")을 열고 하나의 트래킹 이슈에서 여덟 가지 스파이크에 모두 답변했습니다. 재작업은 각 단계가 독립적으로 검토, 병합 및 검증될 수 있도록 의도적으로 단계별로 진행되었습니다. 우리가 한 일은 다음과 같습니다.

1단계: 속도 제한 (#1702). 적응형 백오프(adaptive backoff)를 사용하여 모든 레지스트리 작업을 적절히 조절하도록 속도 제한 로직을 재작성했습니다.

2단계: 인터페이스 (#1704). 레지스트리 및 인증 작업을 깔끔한 인터페이스 뒤에 두어 독립적으로 교체하고 테스트할 수 있도록 했습니다.

3단계: 파이프라인 엔진 (#1705). 프로모션을 하나의 큰 함수 대신 별개의 단계 시퀀스로 실행하는 파이프라인 엔진을 구축했습니다.

4단계: 출처(Provenance) (#1706). 스테이징 이미지에 대한 SLSA 출처 검증을 추가했습니다.

5단계: 스캐너 및 SBOM (#1709). 취약점 스캐닝 및 SBOM 지원을 추가했습니다. 새로운 파이프라인 엔진을 기본값으로 설정했습니다. 이 시점에서 v4.2.0을 출시하고 프로덕션에서 충분히 검증한 후 다음 단계로 진행했습니다.

6단계: 서명과 복제 분리 (#1713). 이미지 서명과 서명 복제를 별도의 파이프라인 단계로 분리하여, 대부분의 프로덕션 실패를 야기했던 속도 제한 경쟁을 제거했습니다.

7단계: 레거시 파이프라인 제거 (#1712). 오래된 코드 경로를 완전히 삭제했습니다.

8단계: 레거시 의존성 제거 (#1716). 감사(audit) 서브시스템, 더 이상 사용되지 않는 도구, E2E 테스트 인프라를 삭제했습니다.

9단계: 모놀리스 삭제 (#1718). 오래된 모놀리식 코어와 이를 지원하는 패키지들을 제거했습니다. 7단계부터 9단계에 걸쳐 수천 라인의 코드가 삭제되었습니다.

각 단계는 독립적으로 배포되었습니다. v4.3.0은 다음 날 레거시 코드가 완전히 제거된 상태로 출시되었습니다.

새로운 아키텍처가 자리를 잡으면서 일련의 후속 개선 사항들이 적용되었습니다: 병렬 레지스트리 읽기 (#1736), 모든 네트워크 작업에 대한 재시도 로직 (#1742), 파이프라인 중단(hang)을 방지하기 위한 요청별 타임아웃 (#1763), HTTP 연결 재사용 (#1759), 로컬 레지스트리 통합 테스트 (#1746), 더 이상 사용되지 않는 자격 증명 파일 지원 제거 (#1758), cosign의 OCI API를 사용하도록 증명(attestation) 처리 방식을 재작업하고 더 이상 사용되지 않는 SBOM 지원을 제거 (#1764), 그리고 in-toto attestation framework에 등록된 전용 프로모션 기록 프레디케이트(predicate) 유형 (#1767). 재작성이 제공한 깔끔한 분리가 없었다면 이러한 개선 사항들을 구현하기는 훨씬 더 어려웠을 것입니다. v4.4.0은 이 모든 개선 사항을 포함하고 출처 생성 및 검증을 기본으로 활성화했습니다.

새로운 파이프라인

이제 프로모션 파이프라인에는 일곱 가지 명확하게 분리된 단계가 있습니다.

graph LR
Setup --> Plan --> Provenance --> Validate --> Promote --> Sign --> Attest
단계 수행 작업
Setup 옵션 검증, TUF 캐시 예열.
Plan 매니페스트 파싱, 레지스트리 읽기, 프로모션이 필요한 이미지 계산.
Provenance 스테이징 이미지에 대한 SLSA 증명 검증.
Validate cosign 서명 확인, 드라이 런(dry run)의 경우 여기서 종료.
Promote 다이제스트를 보존하면서 이미지를 서버 측에서 복사.
Sign 키리스(keyless) cosign으로 프로모션된 이미지 서명.
Attest 전용 in-toto 프레디케이트 유형을 사용하여 프로모션 출처 증명 생성.

단계는 순차적으로 실행되므로, 각 단계는 전체 속도 제한 예산에 대한 독점적인 액세스를 얻습니다. 더 이상 경쟁이 발생하지 않습니다. 미러 레지스트리로의 서명 복제는 더 이상 이 파이프라인의 일부가 아니며, 대신 전용 주기적 프라우(Prow) 작업으로 실행됩니다.

속도 향상

새로운 아키텍처가 자리를 잡으면서, 우리는 성능 개선에 착수했습니다.

병렬 레지스트리 읽기 (#1736): 계획 단계에서는 1,350개의 레지스트리를 읽습니다. 이를 병렬화하여 계획 단계가 약 20분에서 약 2분으로 단축되었습니다.

2단계 태그 목록화 (#1761): 20개 이상의 미러에 걸쳐 있는 46,000개의 이미지 그룹을 모두 확인하는 대신, 먼저 소스 리포지토리만 확인합니다. 이미지의 약 57%는 서명 기능이 활성화되기 전에 프로모션되었기 때문에 서명이 전혀 없습니다. 이러한 이미지들은 완전히 건너뛰어 API 호출을 대략 절반으로 줄였습니다.

복제 전 소스 확인 (#1727): 주어진 이미지에 대한 모든 미러를 반복하기 전에, 먼저 기본 레지스트리에 서명이 존재하는지 확인합니다. 대부분의 서명이 이미 복제된 안정 상태에서는 이 작업이 약 17시간에서 약 15분으로 단축되었습니다.

요청별 타임아웃 (#1763): 중단된 연결이 파이프라인을 9시간 이상 차단하는 간헐적인 중단(hang) 현상이 관찰되었습니다. 이제 모든 네트워크 작업에는 자체 타임아웃이 있으며, 일시적인 실패는 자동으로 재시도됩니다.

연결 재사용 (#1759): HTTP 연결 및 인증 상태를 작업 전반에 걸쳐 재사용하기 시작하여 중복된 토큰 협상을 없앴습니다. 이는 2023년부터 있었던 오랜 요청을 해결했습니다.

수치로 보는 변화

재작업의 전체적인 모습은 다음과 같습니다.

  • 40개 이상의 PR 병합, 3개 릴리스 출시 (v4.2.0, v4.3.0, v4.4.0)
  • 10,000줄 이상 추가 및 16,000줄 이상 삭제, 결과적으로 약 5,000줄 감소 (코드베이스 20% 축소)
  • 전반적인 성능이 획기적으로 향상되었습니다.
  • 재시도 로직, 요청별 타임아웃, 적응형 속도 제한으로 견고성이 향상되었습니다.
  • 19개의 오랜 이슈가 해결되었습니다.

코드베이스는 5분의 1로 줄어들었지만, 출처 증명, 파이프라인 엔진, 취약점 스캐닝 통합, 병렬화된 작업, 재시도 로직, 로컬 레지스트리에 대한 통합 테스트, 그리고 독립적인 서명 복제 모드를 얻었습니다.

사용자에게는 변화 없음

이것은 엄격한 요구 사항이었습니다. kpromo cip 명령은 동일한 플래그를 받아들이고 동일한 YAML 매니페스트를 읽습니다. post-k8sio-image-promo Prow 작업은 내내 계속 작동했습니다. kubernetes/k8s.io의 프로모션 매니페스트는 변경되지 않았습니다. 아무도 워크플로 또는 구성을 업데이트할 필요가 없었습니다.

프로덕션 초기 단계에서 두 가지 회귀(regression)를 발견했습니다. 하나(#1731)는 레지스트리 키 불일치로 인해 모든 이미지가 "손실"된 것처럼 보여 아무것도 프로모션되지 않았습니다. 다른 하나(#1733)는 기본 스레드 수가 0으로 설정되어 모든 고루틴(goroutine)이 차단되었습니다. 둘 다 몇 시간 내에 수정되었습니다. 단계별 릴리스 전략(v4.2.0은 새 엔진 포함, v4.3.0은 레거시 코드 제거)은 다행히 한 번도 필요하지 않았던 명확한 롤백 경로를 제공했습니다.

다음은 무엇인가

모든 미러 레지스트리에 걸친 서명 복제는 프로모션 주기에서 여전히 가장 비용이 많이 드는 부분입니다. 이슈 #1762archeio (registry.k8s.io 리다이렉트 서비스)가 서명 태그 요청을 지역별 백엔드 대신 단일 정식(canonical) 업스트림으로 라우팅하도록 하여 이를 완전히 제거하는 것을 제안합니다. 또 다른 옵션은 서명 작업을 레지스트리 인프라 자체에 더 가깝게 이동하는 것입니다. 두 접근 방식 모두 SIG Release 및 인프라 팀과의 추가 논의가 필요하지만, 어느 쪽이든 프로모션 주기당 수천 건의 API 호출을 제거하고 코드베이스를 더욱 단순화할 것입니다.

감사의 말씀

이 프로젝트는 7년에 걸친 커뮤니티의 노력이었습니다. Linus, Stephen, Adolfo, Carlos, Ben, Marko, Lauri, Tyler, Arnaud, 그리고 수년간 코드, 검토, 계획에 기여해주신 많은 다른 분들께 감사드립니다. SIG Release 및 Release Engineering 커뮤니티는 모든 Kubernetes 릴리스가 의존하는 인프라 재작업에 대한 맥락, 논의, 그리고 인내심을 제공해 주었습니다.

참여하고 싶다면 Kubernetes Slack의 #release-management 채널에 참여하시거나 리포지토리를 확인해보세요.