근묵자흑
Kubernetes Patterns: ImageBuilder 본문
쿠버네티스 내부 컨테이너 이미지 빌드 패턴
ImageBuilder 패턴은 Kubernetes 클러스터 내부에서 컨테이너 이미지를 빌드하는 방법을 다룹니다. 빌드 인프라를 별도로 운영하지 않고 Kubernetes 자체를 빌드 플랫폼으로 활용하는 접근 방식입니다.
2025년 6월, Google이 Kaniko 리포지토리를 아카이브하면서 인클러스터 빌드 도구 선택에 변화가 생겼습니다. BuildKit이 범용 빌드 엔진으로 자리잡고, Kubernetes User Namespaces가 v1.33에서 GA가 되면서 빌드 보안 모델도 함께 변화했습니다.
이 글에서는 책의 핵심 내용을 정리하고, 2026년 현재의 빌드 도구 생태계를 분석하며, minikube 환경에서 Kaniko와 BuildKit으로 실제 빌드를 수행한 결과를 공유합니다.
1. 클러스터 내부 이미지 빌드의 배경
기존에는 클러스터 외부의 CI 시스템(Jenkins 등)에서 이미지를 빌드하고, 레지스트리에 push한 뒤 Kubernetes Deployment에서 참조하는 방식이 일반적이었습니다. 클러스터 내부 빌드는 다음과 같은 이점이 있습니다.
- 첫째, 빌드와 실행을 하나의 클러스터에서 관리하면 유지보수 비용과 인프라 오버헤드가 줄어듭니다.
- 둘째, Kubernetes 스케줄러가 빌드 작업에 적합한 리소스를 할당할 수 있어 CI 시스템의 빌드 스케줄링 문제를 대체할 수 있습니다.
- 셋째, 빌드와 배포가 같은 클러스터에 있으면 베이스 이미지 취약점 발견 시 자동 재빌드와 재배포가 가능합니다. OpenShift의 ImageStream 트리거가 이 패턴의 대표적인 구현입니다.
graph LR
A[소스 코드 저장소] --> B[클러스터 내부 빌더]
B --> C[컨테이너 레지스트리]
C --> D[Deployment 재배포]
E[베이스 이미지 업데이트] --> B
subgraph Kubernetes 클러스터
B
C
D
end
2. 인클러스터 빌드 도구 분류
책에서는 인클러스터 빌드 도구를 두 가지로 분류합니다.
- Container Image Builder는 클러스터 내부에서 컨테이너 이미지를 생성하는 도구입니다. 비특권(unprivileged) 모드로 실행할 수 있으며, 이미지 생성 자체에만 집중합니다.
- Build Orchestrator는 상위 추상화 계층에서 동작하며, 이미지 빌더를 트리거하고 빌드 후 배포 디스크립터 업데이트까지 처리합니다. Tekton, Argo CD, OpenShift Build 등이 여기에 해당합니다.
graph TB
subgraph Build Orchestrator
T[Tekton / Argo CD / OpenShift Build]
end
subgraph Container Image Builder
direction LR
DF[Dockerfile 기반<br/>Buildah, Kaniko, BuildKit]
ML[멀티 언어<br/>Cloud Native Buildpacks, S2I]
SP[언어 특화<br/>Jib, ko, apko]
end
T --> DF
T --> ML
T --> SP
DF --> REG[컨테이너 레지스트리]
ML --> REG
SP --> REG
2.1 Dockerfile 기반 빌더
- Buildah/Podman은 Docker 데몬 없이 OCI 호환 이미지를 빌드합니다. Buildah는 빌드에 집중하고, Podman은 컨테이너 실행까지 포함하는 상위 도구입니다.
- Kaniko는 컨테이너 내부에서 UID 0으로 실행되지만 Pod 자체는 비특권(unprivileged)으로 동작합니다. 사용자 공간에서 Dockerfile 명령을 직접 실행하는 방식입니다.
- BuildKit은 Docker에서 분리된 독립 빌드 엔진입니다. 클라이언트-서버 아키텍처로 동작하며, LLB(Low-Level Build) 정의 포맷을 통해 복잡한 빌드 그래프와 병렬 처리를 지원합니다.
2.2 멀티 언어 빌더
Cloud Native Buildpacks(CNB)는 2012년 Heroku에서 시작되어 2018년 CNCF 산하로 통합됐습니다. detect, build, export 3단계 라이프사이클로 소스 코드를 자동 감지하고 컨테이너 이미지로 변환합니다. Kubernetes에서는 kpack Operator를 통해 CRD로 빌드팩을 설정합니다.
Source-to-Image(S2I)는 OpenShift의 빌드 방식으로, 빌더 이미지와 소스 코드를 결합합니다. assemble(빌드)과 run(실행) 두 가지 스크립트로 구성됩니다.
2.3 언어 특화 빌더
Jib는 Java용 빌드 라이브러리로, Maven/Gradle과 통합됩니다. 의존성, 리소스, 클래스를 별도 레이어로 분리하여 재빌드 시간을 최적화합니다.
ko는 Go 소스 코드에서 직접 이미지를 생성하고, Pod 스펙의 이미지 참조까지 자동 업데이트합니다.
apko는 Alpine APK 패키지를 빌딩 블록으로 사용하여 Dockerfile 없이 이미지를 구성합니다.
3. 책의 핵심 예제: Build Pod
책에서는 Kubernetes Pod 하나로 전체 빌드-배포 사이클을 수행하는 예제를 제시합니다. 모든 빌드 오케스트레이터가 수행하는 핵심 작업을 이해하기 위한 참고 구현입니다.
sequenceDiagram
participant IC1 as initContainer 1<br/>git-sync
participant IC2 as initContainer 2<br/>Kaniko
participant MC as Container<br/>image-update
participant R as Registry
participant D as Deployment
IC1->>IC1: Git 리포지토리에서 소스 코드 체크아웃
IC1-->>IC2: emptyDir 볼륨으로 소스 전달
IC2->>IC2: Dockerfile로 이미지 빌드
IC2->>R: 이미지 push
IC2-->>MC: 이미지 참조를 파일로 전달
MC->>D: kubectl patch로 Deployment 이미지 업데이트
D->>D: 롤링 업데이트 시작
빌드 Pod는 세 단계로 구성됩니다.
1단계 - 소스 코드 가져오기: git-sync init container가 원격 Git 리포지토리에서 소스 코드를 가져와 emptyDir 볼륨에 저장합니다.
2단계 - 이미지 빌드: Kaniko init container가 Dockerfile을 읽고 이미지를 빌드한 뒤 레지스트리에 push합니다. 레지스트리 인증 정보는 docker-registry 타입의 Secret으로 제공합니다.
3단계 - 배포 업데이트: 애플리케이션 컨테이너가 kubectl patch로 Deployment의 이미지 참조를 갱신하여 롤아웃을 트리거합니다. 이 컨테이너의 ServiceAccount에는 Deployment 패치 권한이 필요합니다.
책에서도 언급하듯이 이 Build Pod 예제는 빌드 시스템의 핵심 구성 요소를 이해하기 위한 것이며, 프로덕션에서는 Tekton 같은 CI 시스템을 사용하는 것이 적절합니다.
4. OpenShift Build와 ImageStream
OpenShift는 Kubernetes에 네이티브 이미지 빌드 기능을 추가한 엔터프라이즈 배포판입니다. Image Builder 패턴의 가장 완성도 높은 구현을 제공합니다.
4.1 ImageStream과 Trigger
ImageStream은 하나 이상의 컨테이너 이미지를 참조하는 OpenShift 리소스입니다. Docker 리포지토리와 유사하지만, 이미지가 업데이트되면 이벤트를 발행할 수 있다는 점이 다릅니다. 이 이벤트를 Trigger가 수신하여 새로운 빌드를 시작하거나 배포를 갱신합니다.
graph LR
SRC[소스 코드 변경] --> BC1[S2I BuildConfig<br/>소스를 JAR로 컴파일]
BC1 --> IS1[ImageStream<br/>빌드 결과]
IS1 -->|imageChange trigger| BC2[Docker BuildConfig<br/>런타임 이미지 생성]
BC2 --> IS2[ImageStream<br/>애플리케이션 이미지]
IS2 -->|imageChange trigger| DC[DeploymentConfig<br/>재배포]
4.2 S2I 빌드와 Chained Build
S2I 빌드는 소스 코드와 빌더 이미지를 입력받아 실행 가능한 이미지를 생성합니다. 빌드 프로세스가 빌더 이미지의 통제 하에 있어 보안적으로 유리하지만, 생성된 이미지에 빌드 환경 전체가 포함되어 이미지 크기가 커지는 단점이 있습니다.
이 문제를 해결하기 위해 OpenShift는 Chained Build를 지원합니다. S2I 빌드로 생성한 아티팩트(예: JAR 파일)를 Docker 빌드에서 받아 슬림한 런타임 이미지를 만드는 방식으로, Kubernetes의 멀티스테이지 Dockerfile과 유사한 개념입니다.
5. Kaniko 아카이브와 도구 생태계 변화 (2025-2026)
5.1 아카이브 타임라인
Google은 2025년 6월 3일 GoogleContainerTools/kaniko 리포지토리를 읽기 전용으로 전환했습니다. 커뮤니티에서는 2024년 10월(GitHub Issue #3348)부터 유지보수 부재에 대한 우려가 제기됐고, 최종 릴리스는 v1.24.0(2025년 5월 23일)이었습니다.
아카이브 직후 Chainguard가 chainguard-dev/kaniko로 포크했습니다. Kaniko의 원래 창시자인 Priya Wadhwa와 Dan Lorenc가 이끄는 이 포크는 보안 패치와 의존성 업데이트만 진행하며, 새로운 기능 개발은 없습니다. 기존 공식 이미지 gcr.io/kaniko-project/executor는 더 이상 업데이트되지 않습니다.
5.2 Kaniko의 장점과 한계
Kaniko가 널리 사용된 이유는 설정의 단순함이었습니다. Docker 데몬이 불필요하고, privileged 컨테이너도 불필요하며, 사용자 공간에서 Dockerfile 명령을 직접 실행합니다. AWS Fargate처럼 privileged 모드를 지원하지 않는 환경에서 사실상 유일한 선택지였습니다.
시간이 지나면서 다음과 같은 한계가 드러났습니다.
| 한계 | 영향 |
|---|---|
| 순차 빌드만 지원 | 멀티 스테이지 병렬화 불가, 대형 이미지 빌드 시간 증가 |
RUN --mount=type=secret 미지원 |
빌드 시간 시크릿 관리가 불편함 |
| 멀티 아키텍처 빌드 미지원 | AMD64/ARM64 동시 빌드 불가 |
| VFS 전용 스토리지 | 대형 이미지에서 메모리 소비 증가 |
| SLSA 출처 증명 미지원 | 공급망 보안 요구사항 충족이 어려움 |
| SBOM 생성 미지원 | 소프트웨어 구성 투명성 부재 |
5.3 Kimia - 호환 마이그레이션 경로
RapidFort가 2025년 12월 오픈소스로 공개한 Kimia는 Kaniko 설정과 100% 하위 호환되면서 BuildKit/Buildah 백엔드, 루트리스 운영, 멀티 아키텍처 지원, 내장 Cosign 이미지 서명을 제공합니다. Kaniko에서 마이그레이션을 고려하는 경우 참고할 수 있습니다.
6. BuildKit - 범용 빌드 엔진으로의 통합
6.1 현재 상태
BuildKit은 v0.27.1(2026년 1월 29일) 기준으로 Docker, Dagger, Depot이 모두 핵심 엔진으로 사용하는 범용 빌드 엔진입니다. 최신 릴리스에서는 서명된 릴리스 이미지, SLSA v1.0 기본 출처 증명, 캐시 내보내기와 병렬 이미지 push가 추가됐습니다.
6.2 핵심 기술적 차이
Kaniko와 BuildKit의 가장 큰 차이는 빌드 실행 방식입니다. Kaniko는 Dockerfile 명령을 순차적으로 처리하지만, BuildKit은 DAG(Directed Acyclic Graph) 기반으로 독립 스테이지를 병렬 실행합니다.
graph LR
subgraph Kaniko: 순차 처리
K1[Stage 1: 의존성 설치] --> K2[Stage 2: 테스트] --> K3[Stage 3: 런타임 이미지]
end
subgraph BuildKit: DAG 병렬 처리
B1[Stage 1: 의존성 설치]
B2[Stage 2: 테스트]
B1 --> B3[Stage 3: 런타임 이미지]
B2 --> B3
end
BuildKit의 주요 기능은 다음과 같습니다.
| 기능 | 설명 |
|---|---|
| DAG 기반 병렬 실행 | 멀티 스테이지 빌드에서 독립 스테이지를 동시 실행합니다 |
| 콘텐츠 주소 기반 캐시 | 입력이 동일하면 레이어를 재사용하여 캐시 적중률을 높입니다 |
| 시크릿 마운트 | RUN --mount=type=secret으로 빌드 중 시크릿을 안전하게 사용합니다 |
| SSH 포워딩 | 빌드 중 SSH 에이전트 접근이 가능합니다 (private repo clone 등) |
| 네이티브 멀티 플랫폼 | QEMU + 멀티 노드로 AMD64/ARM64 동시 빌드를 지원합니다 |
| SLSA v1.0 출처 증명 | 빌드 출처를 암호학적으로 증명합니다 |
6.3 실제 성능 차이
Fortune 500 기업의 150개 이상 마이크로서비스 마이그레이션 사례에서 보고된 수치입니다.
| 지표 | Kaniko | BuildKit | 변화 |
|---|---|---|---|
| 빌드 시간 | 247초 | 92초 | 약 63% 감소 |
| 실패율 | 8.7% | 1.3% | 약 85% 감소 |
QEMU 에뮬레이션 기반 크로스 아키텍처 빌드에서는 차이가 더 큽니다. Parca 프로젝트 측정에서 QEMU는 33분 이상, 네이티브 빌드는 1분 26초가 소요됐습니다.
7. 3대 빌드 엔진 비교
CERN의 2025년 6월 containerd 2.x 환경 벤치마크를 참고한 비교입니다.
| 항목 | BuildKit | Buildah/Podman | Kaniko (Chainguard) |
|---|---|---|---|
| 빌드 속도 (대형 이미지) | 가장 빠름 (병렬 DAG) | 빠름 (overlay 사용 시) | 느림 (순차, VFS) |
| 메모리 사용량 | 낮음 | 낮음 | 대형 이미지에서 높음 |
| 멀티 스테이지 병렬화 | 지원 | 미지원 | 미지원 |
| 멀티 아키텍처 | 네이티브 지원 | farm build/manifest로 지원 | 미지원 |
| 시크릿 마운트 | --mount=type=secret |
--mount=type=secret |
미지원 |
| SLSA 출처 증명 | 네이티브 (v1.0) | 제한적 | 미지원 |
| SBOM 생성 | attestation 방식 | 네이티브 (--sbom) |
미지원 |
| privileged 없이 루트리스 | 불가 (privileged + user NS 필요) | 가능 | 해당 없음 (root 실행, privileged 불필요) |
| 데몬 필요 여부 | 필요 (buildkitd) | 불필요 | 불필요 |
| K8s 설정 복잡도 | 중간 | 중간 | 가장 낮음 |
| 개발 활발도 | 매우 활발 | 매우 활발 | 유지보수만 |
용도별 권장 도구는 다음과 같습니다.
- 일반 Kubernetes CI/CD: BuildKit (
hostUsers: false와 결합) - privileged 컨테이너 금지 환경: Buildah
- AWS Fargate 등 최소 권한 환경: Kaniko (Chainguard 포크)
- Red Hat/OpenShift: Buildah (네이티브 통합)
8. Buildah/Podman - CNCF 후원과 성숙
2025년 1월 Red Hat이 Podman을 CNCF에 기부하고, 2026년 2월 Podman Desktop 엔터프라이즈 지원을 시작하면서 Buildah/Podman 생태계가 한 단계 성숙했습니다. 최신 버전은 Buildah v1.43.0, Podman 5.8.0입니다.
Buildah는 설계 단계부터 루트리스를 목표로 만들어졌습니다. 데몬 없이 fork-exec 모델로 동작하며, runAsUser: 1000과 hostUsers: false로 privileged 모드 없이 빌드할 수 있는 유일한 범용 도구입니다.
2024-2025년에 추가된 주요 기능은 다음과 같습니다.
- v1.35.0:
--sbom플래그로 내장 SBOM 생성 - v1.38.0: Dockerfile ADD에서 Git 소스 지원,
COPY --exclude - v1.39.0:
--mount=type=cache에from인자 추가 - v1.40.0:
--inherit-labels, COPY에--parents옵션 - v1.41.0:
ADD/COPY --link지원, 다중--output
9. 실습: minikube에서 3가지 빌드 방식 구현
이 섹션에서는 minikube 클러스터에서 Kaniko와 BuildKit(daemonless, daemon+client)으로 이미지를 빌드하고 검증합니다. 모든 결과는 실측 데이터입니다.
9.1 아키텍처 개요
graph TB
CM[ConfigMap<br/>Dockerfile + index.html] --> IC1[initContainer<br/>busybox:1.36]
IC1 -->|복사| WS1[emptyDir workspace]
subgraph 방식 1: Kaniko Job
WS1 --> KE[kaniko-project/executor<br/>privileged 불필요]
end
CM --> IC2[initContainer]
IC2 -->|복사| WS2[emptyDir workspace]
subgraph 방식 2: BuildKit Daemonless Job
WS2 --> BD[buildctl-daemonless.sh<br/>privileged: true]
end
CM --> IC3[initContainer]
IC3 -->|복사| WS3[emptyDir workspace]
subgraph 방식 3: BuildKit Daemon + Client
BKD[buildkitd Deployment<br/>Service :1234]
WS3 --> BC[buildctl client<br/>BUILDKIT_HOST=tcp://buildkitd:1234]
BC -->|gRPC| BKD
end
KE -->|push| REG[minikube Registry<br/>registry.kube-system.svc.cluster.local:80]
BD -->|push| REG
BKD -->|push| REG
REG -->|pull| APP[검증 Pod<br/>nginx with custom content]
9.2 빌드 컨텍스트: ConfigMap 패턴
hostPath 볼륨 대신 ConfigMap으로 빌드 컨텍스트를 패키징합니다. 이 방식은 모든 minikube 드라이버(docker, hyperkit, qemu 등)에서 안정적으로 동작합니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: build-context
labels:
pattern: ImageBuilder
data:
Dockerfile: |
FROM nginx:1.27-alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
index.html: |
<!DOCTYPE html>
<html>
<body>
<h1>Built inside Kubernetes!</h1>
<p>ImageBuilder Pattern - k8spatterns</p>
</body>
</html>
ConfigMap 마운트는 심볼릭 링크를 생성하기 때문에 Kaniko와 BuildKit이 기대하는 실제 파일과 맞지 않습니다. initContainer로 emptyDir workspace에 복사하는 패턴을 사용합니다.
initContainers:
- name: prepare-context
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
cp /configmap/Dockerfile /workspace/Dockerfile
cp /configmap/index.html /workspace/index.html
volumeMounts:
- name: build-context
mountPath: /configmap
- name: workspace
mountPath: /workspace
9.3 방식 1: Kaniko
sequenceDiagram
participant IC as initContainer
participant K as Kaniko Executor
participant R as Registry
IC->>IC: ConfigMap에서 workspace로 복사
K->>K: Dockerfile 파싱
K->>K: FROM nginx:1.27-alpine 풀
K->>K: COPY index.html 파일시스템 스냅샷
K->>K: 레이어 생성
K->>R: 이미지 push (HTTP insecure)
Note over K,R: 12초 소요 (minikube 실측)
apiVersion: batch/v1
kind: Job
metadata:
name: kaniko-build
labels:
pattern: ImageBuilder
builder: kaniko
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
initContainers:
- name: prepare-context
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
cp /configmap/Dockerfile /workspace/Dockerfile
cp /configmap/index.html /workspace/index.html
volumeMounts:
- name: build-context
mountPath: /configmap
- name: workspace
mountPath: /workspace
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--dockerfile=/workspace/Dockerfile"
- "--context=dir:///workspace"
- "--destination=registry.kube-system.svc.cluster.local/imagebuilder-test:kaniko"
- "--insecure"
- "--insecure-pull"
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: build-context
configMap:
name: build-context
- name: workspace
emptyDir: {}
설계 포인트는 다음과 같습니다.
backoffLimit: 0: 실패 시 재시도 없이 즉시 종료합니다.restartPolicy: Never: Job 완료 후 Pod가 종료됩니다.--insecure: minikube 내장 레지스트리는 HTTP 전용이므로 필요합니다.--context=dir:///workspace: 로컬 디렉토리를 빌드 컨텍스트로 사용합니다.- privileged가 불필요하다는 점이 Kaniko 사용의 가장 큰 이점입니다.
실제 빌드 로그는 다음과 같습니다.
INFO[0001] Building stage 'nginx:1.27-alpine' [idx: '0', base-idx: '-1']
INFO[0001] Unpacking rootfs as cmd COPY requires it.
INFO[0005] COPY index.html /usr/share/nginx/html/index.html
INFO[0005] Taking snapshot of files...
INFO[0005] EXPOSE 80
INFO[0005] Pushing image to registry.kube-system.svc.cluster.local/imagebuilder-test:kaniko
INFO[0007] Pushed ...@sha256:b9c337f5b55ca81330e4510318dd0be96a58dd384714ffaaccddfcced433ad6a
9.4 방식 2: BuildKit Daemonless
buildctl-daemonless.sh는 BuildKit을 Kubernetes Job으로 간단하게 실행하는 방법입니다. buildkitd 데몬을 프로세스 내부에서 임시로 시작하고, buildctl로 빌드를 실행한 뒤, 빌드 완료 후 데몬이 자동 종료됩니다.
apiVersion: batch/v1
kind: Job
metadata:
name: buildkit-daemonless-build
labels:
pattern: ImageBuilder
builder: buildkit
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
initContainers:
- name: prepare-context
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
cp /configmap/Dockerfile /workspace/Dockerfile
cp /configmap/index.html /workspace/index.html
volumeMounts:
- name: build-context
mountPath: /configmap
- name: workspace
mountPath: /workspace
containers:
- name: buildkit
image: moby/buildkit:latest
command:
- buildctl-daemonless.sh
args:
- build
- --frontend=dockerfile.v0
- --local
- context=/workspace
- --local
- dockerfile=/workspace
- --output
- type=image,name=registry.kube-system.svc.cluster.local/imagebuilder-test:buildkit,push=true,registry.insecure=true
securityContext:
privileged: true
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: build-context
configMap:
name: build-context
- name: workspace
emptyDir: {}
실습에서 발견한 주의 사항
moby/buildkit:latest의 기본 entrypoint는 buildkitd입니다. buildctl-daemonless.sh를 실행하려면 command:(Kubernetes의 entrypoint 오버라이드)로 지정해야 합니다. args:에만 넣으면 buildkitd에 인자로 전달되어 데몬만 시작되고 빌드가 실행되지 않습니다.
securityContext.privileged: true가 필요한 이유는 BuildKit이 커널 네임스페이스(user, mount, network)를 사용하여 빌드 프로세스를 격리하기 때문입니다. 로컬 개발 환경에서는 이 설정이 수용 가능하며, 프로덕션에서는 Kubernetes User Namespaces(hostUsers: false)와 결합하여 위험을 완화합니다.
9.5 방식 3: BuildKit Daemon + Client
프로덕션에서 권장하는 패턴입니다. BuildKit 데몬을 Deployment로 장기 실행하고, 빌드 클라이언트가 gRPC로 연결합니다.
graph TB
subgraph BuildKit Daemon 아키텍처
D[buildkitd Deployment<br/>replicas: 1, privileged: true]
S[buildkitd Service<br/>ClusterIP, port: 1234]
D --- S
end
C1[Client Job 1<br/>privileged 불필요] -->|BUILDKIT_HOST=tcp://buildkitd:1234| S
C2[Client Job 2] -->|BUILDKIT_HOST| S
C3[Client Job N] -->|BUILDKIT_HOST| S
D -->|push| R[내장 Registry]
Daemon Deployment와 Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: buildkitd
spec:
replicas: 1
selector:
matchLabels:
app: buildkitd
template:
metadata:
labels:
app: buildkitd
spec:
containers:
- name: buildkitd
image: moby/buildkit:latest
args: ["--addr", "tcp://0.0.0.0:1234"]
ports:
- containerPort: 1234
securityContext:
privileged: true
readinessProbe:
exec:
command:
- buildctl
- --addr
- tcp://127.0.0.1:1234
- debug
- workers
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: buildkitd
spec:
selector:
app: buildkitd
ports:
- port: 1234
targetPort: 1234
readinessProbe 관련 주의 사항
--addr tcp://0.0.0.0:1234로 buildkitd를 시작하면 기본 Unix 소켓(/run/buildkit/buildkitd.sock)이 비활성화됩니다. buildctl debug workers는 기본적으로 Unix 소켓에 연결을 시도하므로, readinessProbe에 --addr tcp://127.0.0.1:1234를 반드시 명시해야 합니다. 이를 빠뜨리면 Pod가 NotReady 상태에 머무릅니다.
Client Job
apiVersion: batch/v1
kind: Job
metadata:
name: buildkit-client-build
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
initContainers:
- name: prepare-context
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
cp /configmap/Dockerfile /workspace/Dockerfile
cp /configmap/index.html /workspace/index.html
volumeMounts:
- name: build-context
mountPath: /configmap
- name: workspace
mountPath: /workspace
containers:
- name: buildctl
image: moby/buildkit:latest
env:
- name: BUILDKIT_HOST
value: "tcp://buildkitd:1234"
command: [buildctl]
args:
- build
- --frontend=dockerfile.v0
- --local
- context=/workspace
- --local
- dockerfile=/workspace
- --output
- type=image,name=registry.kube-system.svc.cluster.local/imagebuilder-test:buildkit-daemon,push=true,registry.insecure=true
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: build-context
configMap:
name: build-context
- name: workspace
emptyDir: {}
Daemon 모드의 주요 장점은 세 가지입니다.
첫째, 데몬이 살아있는 동안 콘텐츠 주소 캐시가 유지되어 반복 빌드 시 성능이 크게 향상됩니다.
둘째, 클라이언트에는 privileged가 불필요하므로 리소스 소비가 적습니다.
셋째, 여러 CI/CD 파이프라인이 하나의 빌드 데몬을 공유할 수 있습니다.
10. minikube 내장 레지스트리 - 실습에서 발견한 이슈
10.1 기본 설정
minikube start --insecure-registry="10.0.0.0/8"
minikube addons enable registry
레지스트리 주소는 registry.kube-system.svc.cluster.local:80(포트 80)이며, HTTP 프로토콜을 사용합니다.
10.2 insecure-registry의 DNS vs IP 문제
minikube의 --insecure-registry 설정은 IP 범위에만 적용되고 DNS 이름에는 적용되지 않습니다. 빌드 도구(Kaniko의 --insecure, BuildKit의 registry.insecure=true)에서는 DNS 이름으로 push가 가능하지만, kubelet이 같은 DNS 이름으로 pull할 때는 HTTPS를 시도하여 실패합니다.
Error: Get "https://registry.kube-system.svc.cluster.local/v2/": context deadline exceeded
해결 방법은 이미지를 pull할 때 DNS 이름 대신 ClusterIP를 동적으로 조회하여 사용하는 것입니다. 10.0.0.0/8 범위에 포함되는 ClusterIP는 insecure로 처리됩니다.
REGISTRY_IP=$(kubectl get svc registry -n kube-system -o jsonpath='{.spec.clusterIP}')
kubectl run verify-pod --image="${REGISTRY_IP}/imagebuilder-test:kaniko"
10.3 빌드-배포 흐름 요약
graph TD
A[ConfigMap<br/>빌드 컨텍스트] --> B[initContainer<br/>busybox:1.36]
B -->|복사| C[emptyDir workspace<br/>실제 파일]
C --> D[Builder Job<br/>Kaniko / BuildKit]
D -->|push, DNS 이름, HTTP| E[내장 Registry<br/>registry.kube-system.svc.cluster.local:80]
E -->|pull, ClusterIP, HTTP| F[Application Pod<br/>빌드된 이미지 실행]
11. 실측 결과
11.1 테스트 환경
| 항목 | 값 |
|---|---|
| 날짜 | 2026-02-28 |
| minikube | v1.37.0 (Docker driver, arm64) |
| Registry | minikube registry addon (HTTP, port 80) |
| insecure-registry | 10.0.0.0/8 |
| 빌드 대상 | nginx:1.27-alpine + custom index.html |
11.2 빌드 성능 비교
| 빌드 엔진 | 소요 시간 | privileged | 데몬 | 비고 |
|---|---|---|---|---|
| Kaniko | 12s | 불필요 | 불필요 | 가장 간단한 설정 |
| BuildKit (daemonless) | 9s | 필요 | 임시 | 단발성에 적합 |
| BuildKit (daemon+client) | 9s | 데몬만 | 장기 실행 | 캐시 유지, 프로덕션 권장 |
간단한 nginx 이미지 빌드에서는 세 엔진 모두 비슷한 성능을 보입니다. 멀티 스테이지 대형 이미지에서는 BuildKit의 DAG 병렬화가 차이를 만듭니다.
11.3 클러스터 배포 테스트 결과
job.batch/kaniko-build Complete 1/1 12s
job.batch/buildkit-daemonless-build Complete 1/1 9s
job.batch/buildkit-client-build Complete 1/1 9s
deployment.apps/buildkitd 1/1 1 1 (Ready)
pod/verify-pod 1/1 Running (HTTP 검증 성공)
검증 결과는 다음과 같습니다.
- 레지스트리 카탈로그에서
imagebuilder-test이미지 확인 - ClusterIP를 통한 이미지 pull 성공
- HTTP 응답에서 "Built inside Kubernetes!" 내용 확인
11.4 전체 테스트 결과
| 테스트 | 항목 수 | 결과 |
|---|---|---|
| Test 1: YAML 구문 검증 | 6/6 | PASS |
| Test 2: 매니페스트 구조 검증 | 26/26 | PASS |
| Test 3: 블로그 핵심 개념 검증 | 18/18 | PASS |
| Test 4: 클러스터 배포 테스트 | 14/14 | PASS |
| 합계 | 64/64 | ALL PASS |
12. Kubernetes 최신 기능과 빌드 보안
12.1 User Namespaces (GA in v1.33)
Kubernetes v1.33에서 GA가 된 User Namespaces는 인클러스터 빌드 보안에서 영향력이 큰 개선 사항입니다. hostUsers: false 설정으로 컨테이너 내부의 root(UID 0)를 호스트의 고번호 비특권 사용자로 매핑합니다. 컨테이너 탈출(container escape)의 주요 위험을 줄이는 메커니즘입니다.
CERN의 2025년 6월 평가에서 권장하는 접근 방식은 User Namespaces 내부에서 rootful BuildKit을 실행하는 것입니다.
spec:
hostUsers: false # User Namespaces 활성화
containers:
- name: buildkitd
image: moby/buildkit:latest
securityContext:
privileged: true # user namespace 내부에서만 privileged
Pod 내부에서는 root로 동작하지만, 호스트에서는 비특권 고번호 UID로 매핑되므로 격리가 유지됩니다. BuildKit의 rootless 모드(moby/buildkit:rootless)는 rootlesskit을 사용하는데, Kubernetes User Namespaces와 충돌하기 때문에 위 방식이 더 실용적입니다.
12.2 KEP-4639 Image Volumes
OCI 이미지를 Pod의 볼륨으로 직접 마운트할 수 있는 기능입니다.
| Kubernetes 버전 | 상태 | 날짜 | 핵심 변경 |
|---|---|---|---|
| v1.31 | Alpha | 2024년 8월 | 최초 도입, 기본 비활성 |
| v1.33 | Beta | 2025년 4월 | subPath/subPathExpr 지원 |
| v1.35 | Beta (기본 활성) | 2025년 12월 | CRI-O, containerd에서 즉시 사용 가능 |
| v1.36 | GA (목표) | 계획 중 | 안정 릴리스 |
volumes:
- name: model-weights
image:
reference: registry.example.com/ml-models/llm-weights:v2
pullPolicy: IfNotPresent
ML 모델 가중치 마운트, 보안 서명 배포, OpenTelemetry 에이전트 공유, 런타임과 애플리케이션 코드 분리 등의 활용 사례가 있습니다.
12.3 프로덕션 보안 체크리스트
| 영역 | 권장 도구/방식 |
|---|---|
| 이미지 서명 | Cosign (Sigstore) - OIDC + Fulcio + Rekor 키리스 서명 |
| 서명 검증 정책 | Kyverno verifyImages 또는 Sigstore Policy Controller |
| 출처 증명 | BuildKit SLSA v1.0 / Tekton Chains SLSA L2/L3 |
| SBOM 생성 | Buildah (--sbom), BuildKit (--sbom=true), Syft, Trivy |
| 베이스 이미지 | Docker Hardened Images 또는 Chainguard Images |
| 취약점 스캐닝 | Trivy/Grype (빌드 시간), Kyverno/OPA (CVSS 임계값 정책) |
| 네트워크 격리 | 빌드 전용 네임스페이스, NetworkPolicy (egress를 레지스트리/리포로 제한) |
| 시크릿 관리 | BuildKit --mount=type=secret, Pod Identity/IRSA, Vault |
| 샌드박싱 | gVisor (runsc) 추가 격리 |
13. 멀티 아키텍처 빌드
QEMU 에뮬레이션 기반 크로스 아키텍처 빌드는 네이티브 컴파일 대비 10-22배 느립니다.
BuildKit Kubernetes 드라이버를 사용하면 아키텍처별 노드에서 네이티브로 빌드할 수 있습니다.
# AMD64, ARM64 노드를 스패닝하는 멀티 아키텍처 빌더 생성
docker buildx create --name=kube --driver=kubernetes \
--platform=linux/amd64 --node=builder-amd64 \
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=amd64"
docker buildx create --append --name=kube --driver=kubernetes \
--platform=linux/arm64 --node=builder-arm64 \
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=arm64"
# 양쪽 아키텍처에서 네이티브 빌드
docker buildx build --builder=kube \
--platform=linux/amd64,linux/arm64 -t myimage:latest --push .
Go 크로스 컴파일과 Java 플랫폼 독립성 덕분에 ko(Go)와 Jib(Java)는 QEMU 없이 네이티브 멀티 아키텍처를 지원합니다.
AWS에서 Karpenter + Graviton NodePools는 arm64 네이티브 빌드에서 약 30% 더 나은 가격 대비 성능을 제공합니다.
14. CI/CD 생태계의 변화
14.1 Tekton Pipelines v1.0
https://youtu.be/TWxKD9dLpmk?si=0OSJ89iR1h0zPasx
간단히 말해, Tekton Pipelines 프로젝트는 CI/CD 스타일 파이프라인을 선언하기 위한 Kubernetes 스타일 리소스를 제공합니다. 이러한 리소스는 자연스럽게 yaml에 설명되어 있으며 코드 저장소에 저장됩니다. 이 코드형 파이프라인 접근 방식은 버전 관리 및 소스 제어의 이점을 제공합니다.
Tekton은 파이프라인 및 관련 개념을 정의하는 Kubernetes에 대한 사용자 지정 리소스 확장 세트를 제공합니다. 다음은 기본 Tekton Pipeline 구성 요소입니다.
- 작업: 코드 컴파일, 테스트 실행, 이미지 빌드 및 배포와 같은 빌드 단계 집합을 정의합니다.
- 파이프라인: 파이프라인을 구성하는 작업 집합을 정의합니다.
- PipelineResource: 파이프라인의 입력(예: Git 리포지토리) 또는 아웃풋(예: Docker 이미지)인 개체를 정의합니다.
- PipelineRun: 파이프라인의 실행을 정의합니다. 이 리소스는 실행할 파이프라인과 입력 및 아웃풋으로 사용할 PipelineResource를 참조합니다.
Tekton 파이프라인은 Kubernetes 네이티브일 뿐만 아니라 모든 환경(Kubernetes 클러스터, Cloud Foundry, 가상 머신(VM) 등)에 배포하는 데 사용할 수 있으며 파이프라인 작업은 격리된 상태에서 안전하게 실행되므로 현대적인 지속적 배포의 요구 사항을 유연하게 충족할 수 있습니다.
https://github.com/tektoncd/pipeline
14.2 Dagger
Docker 공동 창업자 Solomon Hykes가 만든 Dagger는 Kubernetes 클러스터에 DaemonSet으로 배포되어 로컬 NVMe 스토리지에 지속적인 BuildKit 캐시를 제공합니다. 파이프라인을 YAML이 아닌 Go, Python, TypeScript 등의 코드로 정의합니다.
15. 언어별 빌드 도구
| 도구 | 대상 | 최신 버전 | 핵심 특징 |
|---|---|---|---|
| ko | Go | v0.18.1 (2025.12) | 약 3MB 이미지, Docker 불필요, 자동 SBOM, Cosign 통합 |
| Jib | Java | v3.5.1 (2025.11) | Java 25 지원, 최적 레이어링, Docker 불필요 |
| Cloud Native Buildpacks | 범용 | pack v0.40.0 | CNCF Incubating, Platform API 0.15 |
| kpack | 범용 (K8s) | v0.17.1 (2025.12) | 소스/베이스 이미지 변경 시 자동 재빌드, GitOps 통합 |
| apko + melange | 최소 이미지 | apko v1.1.8 (2026.02) | 선언적 YAML, 완전 재현 가능, 단일 레이어, 자동 SBOM |
ko는 Knative, Tekton, Sigstore 자체를 빌드하는 데 사용되고 있습니다. Kaniko 아카이브 이후 Jib는 Google의 유일한 주요 컨테이너 빌드 도구가 됐습니다. apko + melange는 Wolfi OS/Chainguard 생태계와 통합되어 보안에 집중된 이미지 빌드 방식을 제공합니다.
16. 빌드 도구 선택 가이드
graph TD
A[이미지 빌드 도구 선택] --> B{privileged 허용 여부}
B -->|불가| C{Fargate/제한 환경?}
C -->|예| D[Kaniko<br/>Chainguard 포크]
C -->|아니오| E[Buildah<br/>완전 rootless]
B -->|가능| F{빌드 캐시 필요?}
F -->|예| G[BuildKit Daemon<br/>Deployment + Service]
F -->|아니오| H[BuildKit Daemonless<br/>단발성 Job]
A --> I{언어별 최적화}
I -->|Go| J[ko]
I -->|Java| K[Jib]
I -->|최소 보안 이미지| L[apko + melange]
| 시나리오 | 권장 도구 | 이유 |
|---|---|---|
| 일반 Kubernetes CI/CD | BuildKit + hostUsers: false |
성능, 보안, 기능의 균형이 적절합니다 |
| privileged 금지 환경 | Buildah | privileged 없이 동작하는 유일한 범용 빌드 도구입니다 |
| AWS Fargate | Kaniko (Chainguard 포크) | privileged 미지원 환경에서 사용 가능한 선택지입니다 |
| Red Hat/OpenShift | Buildah | 네이티브 통합, 엔터프라이즈 지원이 있습니다 |
| Go 애플리케이션 | ko | 약 3MB 이미지, Docker 불필요, 자동 SBOM을 지원합니다 |
| Java 애플리케이션 | Jib | 최적 레이어링, Docker 불필요합니다 |
| 최소 이미지 (보안) | apko + melange | 완전 재현 가능, 단일 레이어, CVE 최소화가 가능합니다 |
| Kubernetes 네이티브 CI/CD | Tekton + Tekton Chains | K8s 네이티브 파이프라인 + 공급망 보안을 제공합니다 |
| 코드 기반 파이프라인 | Dagger | Go/Python/TS SDK, 지속적 BuildKit 캐시를 제공합니다 |
17. 정리
이 글에서 다룬 내용을 정리하면 다음과 같습니다.
책에서 설명하는 Image Builder 패턴의 핵심은, 빌드와 배포를 같은 클러스터에서 관리하면 인프라 효율성과 보안 자동화에서 이점이 있다는 것입니다. Build Pod 예제는 소스 체크아웃, 이미지 빌드, 레지스트리 push, Deployment 업데이트라는 인클러스터 빌드의 핵심 단계를 보여줍니다.
2025-2026년 생태계 변화의 핵심은 세 가지입니다.
첫째, BuildKit이 Docker, Dagger, Depot의 핵심 엔진으로 범용 빌드 엔진으로 자리잡았습니다. Kaniko 아카이브 이후 대부분의 클러스터에서 BuildKit으로의 전환이 진행되고 있습니다.
둘째, Kubernetes User Namespaces GA(v1.33)로 hostUsers: false + privileged BuildKit 조합이 프로덕션 보안 표준으로 자리잡고 있습니다.
셋째, Depot, Docker Build Cloud, Dagger DaemonSet 모드 등 원격 빌드 서비스가 자체 관리 인클러스터 빌드의 대안으로 자리잡고 있습니다.
minikube 실습에서 확인한 실용적 교훈은 다음과 같습니다.
| 교훈 | 상세 |
|---|---|
| ConfigMap 빌드 컨텍스트 | hostPath 대신 ConfigMap에서 initContainer, emptyDir 패턴으로 이식성을 확보합니다 |
| BuildKit entrypoint | moby/buildkit:latest의 기본 entrypoint는 buildkitd이므로 command로 오버라이드가 필요합니다 |
| readinessProbe TCP 주소 | --addr tcp:// 모드에서는 Unix 소켓이 비활성되므로 readinessProbe에 TCP 주소를 명시합니다 |
| insecure-registry는 IP만 | minikube --insecure-registry는 DNS 이름이 아닌 IP 범위에만 적용되므로 ClusterIP를 사용합니다 |
| Kaniko는 여전히 유효 | 아카이브됐지만 Fargate 등 제한된 환경에서 가장 간단한 선택지입니다 |
KEP-4639 Image Volumes(Kubernetes v1.35부터 기본 활성)는 OCI 아티팩트를 Pod 볼륨으로 직접 마운트할 수 있게 합니다. ML 모델 배포, 에이전트 사이드카 분리 등 이미지 빌드와 배포 방식에 새로운 가능성을 제공하는 기능입니다.
참고 자료
- Kubernetes Patterns - Chapter 30: Image Builder (https://github.com/k8spatterns/examples)
- BuildKit GitHub (https://github.com/moby/buildkit) - v0.27.1 (2026.01)
- Kaniko Chainguard Fork (https://github.com/chainguard-dev/kaniko)
- Buildah (https://github.com/containers/buildah) - v1.43.0
- Podman CNCF (https://github.com/containers/podman) - v5.8.0
- Tekton Pipelines (https://github.com/tektoncd/pipeline) - v1.0.0 / LTS v1.9.0
- Tekton Chains (https://github.com/tektoncd/chains) - v0.26.2
- Dagger (https://dagger.io)
- ko (https://github.com/ko-build/ko) - v0.18.1
- Jib (https://github.com/GoogleContainerTools/jib) - v3.5.1
- apko (https://github.com/chainguard-dev/apko) - v1.1.8
- Cloud Native Buildpacks (https://buildpacks.io) - pack v0.40.0
- kpack (https://github.com/buildpacks-community/kpack) - v0.17.1
- KEP-4639: Image Volumes (https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/4639-oci-volume-source)
- Kubernetes User Namespaces (https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/)
- CERN: Rootless container builds on Kubernetes (https://kubernetes.web.cern.ch/blog/2025/06/19/rootless-container-builds-on-kubernetes/)
- Docker Hardened Images (https://hub.docker.com/r/docker/hardened-images)
- Chainguard Images (https://www.chainguard.dev/chainguard-images)
'k8s > kubernetes-pattern' 카테고리의 다른 글
| Kubernetes Patterns : Elastic Scale (0) | 2026.02.21 |
|---|---|
| Kubernetes Patterns : Operator (Datadog Operator) (0) | 2026.02.13 |
| Kubernetes Patterns: Operator (0) | 2026.02.07 |
| Kubernetes Pattern: Controller (0) | 2026.01.31 |
| Kubernetes Pattern: Access Control (0) | 2026.01.24 |