[쿠버네티스 패턴] 30장 Image Builder

bocopile·2026년 2월 28일

쿠버네티스 패턴

목록 보기
28/28
post-thumbnail

1. Problem — 왜 Image Builder 패턴이 필요한가?

1.1 빌드와 배포가 분리될 때 생기는 문제

CI VM에서 빌드하고, 쿠버네티스에서 실행하는 구조는 익숙하지만 운영 관점에서는 불일치가 누적됩니다.

  • 빌드 환경과 런타임 환경의 네트워크/권한/시크릿 정책이 다릅니다.
  • 베이스 이미지 패치가 나와도 재빌드/재배포 체인이 수동이면 대응이 늦습니다.
  • 감사 추적(누가 어떤 소스로 어떤 이미지를 만들었는지)이 분산됩니다.

예시

  • python:3.12-slim 보안 패치가 배포되었는데, 수동 트리거에 의존해서 1~2주 뒤에야 재빌드되는 경우가 실제로 자주 발생합니다.
  • Jenkins VM에 등록된 자격증명과 클러스터 Secret이 따로 관리되어 갱신 시 누락이 생기는 경우도 빈번합니다.

1.2 보안 관점 — 왜 Docker-in-Docker(Privileged)를 지양하나?

전통적인 Docker daemon 기반 빌드는 privileged 권한이나 소켓 마운트를 요구하는 경우가 많아 공격면이 커집니다.

방식위험 요소
docker.sock 마운트소켓에 접근한 컨테이너가 호스트 권한 상승 가능
privileged: true컨테이너 탈출 시 노드 전체 장악 가능
DinD(Docker-in-Docker)중첩 daemon 운영으로 격리 경계 모호

반면 Kaniko/BuildKit(루트리스 구성)/Buildah 등은 daemon 의존을 줄이거나 제거하여 운영 보안성을 높일 수 있습니다.

2. Solution — 도구별 동작 원리와 선택 기준

2.1 전체 지형도

항목KanikoBuildKitCNBS2I
입력DockerfileDockerfile/LLB소스코드(+빌드팩)소스코드(+builder 이미지)
강점단순 도입성능/캐시/고급 기능Dockerfile 없이 표준화OpenShift 통합
운영 난이도낮음중간중간OpenShift 기준 낮음
주의점프로젝트 아카이브 상태설정 복잡도빌드팩 학습 필요OpenShift 종속

2.2 Kaniko — 동작 원리

Kaniko는 Docker daemon 없이 Dockerfile 명령을 userspace에서 처리합니다. 각 명령 실행 후 파일시스템 변화를 스냅샷으로 계산하고, 변화분을 레이어 tar로 구성해 최종 이미지를 레지스트리에 푸시합니다.

주요 옵션 설명

옵션설명
--snapshot-mode=redo변경 감지 정밀도를 높여 레이어 누락을 방지. 빌드 시간이 늘어날 수 있어 캐시와 함께 사용
--cache=true레이어 캐시 활성화
--cache-repo캐시를 저장할 레지스트리 경로 지정

운영 주의: Kaniko 저장소는 현재 GitHub에서 아카이브 상태입니다. 신규 플랫폼 설계 시에는 BuildKit/Buildah 또는 관리형 빌더를 병행 검토하시는 것이 안전합니다.

2.3 BuildKit — 동작 원리

BuildKit은 Dockerfile을 내부 빌드 그래프(LLB, Low-Level Build)로 변환해 병렬 실행고급 캐시를 지원합니다. Kaniko 대비 대규모 빌드 팜에 적합합니다.

핵심 기능

  • -mount=type=cache : 의존성 캐시(npm/pip/maven) 컨테이너 레이어 밖에서 재사용
  • -mount=type=secret : 빌드 시 비밀을 주입하되 이미지 레이어에는 포함되지 않음
  • inline/registry cache : CI 파이프라인 간 캐시 공유

예시 — Maven 캐시 마운트

# syntax=docker/dockerfile:1.7
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests dependency:go-offline
COPY . .
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests package

/root/.m2 캐시가 빌드 간에 유지되어 의존성 다운로드 없이 재빌드가 가능합니다.

2.4 CNB(Cloud Native Buildpacks) — 동작 원리

CNB는 Dockerfile 없이 소스코드 타입을 탐지하여 빌드합니다. 라이프사이클은 5단계로 구성됩니다.

단계역할
Analyze기존 이미지/캐시 메타데이터 확인
Detect소스 분석 후 적용할 buildpack 조합 결정
Restore이전 빌드 캐시 복원
Buildbuildpack 실행으로 아티팩트 생성
Export최종 OCI 이미지 조합 및 레지스트리 푸시

CNB는 builder image(빌드 도구 포함)와 run image(최소 런타임)를 분리하여, 최종 이미지를 가볍고 안전하게 유지합니다.

예시

  • Node.js 프로젝트를 pack build 한 줄로 빌드하면, Paketo Buildpack이 자동으로 런타임 버전을 탐지하고 최적화된 이미지를 생성합니다.

2.5 S2I(Source-to-Image) — 동작 원리

S2I는 OpenShift에서 오래 검증된 방식으로, builder 이미지 안의 스크립트로 소스를 이미지로 변환합니다.

  • assemble 스크립트: 소스와 의존성을 빌드
  • run 스크립트: 런타임 시작 커맨드 정의

BuildConfig + ImageStream + Trigger를 조합하면 Git Push 하나로 빌드~배포까지 자동화할 수 있습니다.

예시

  • Java 팀 전체가 fabric8/s2i-java builder를 공통 표준으로 쓰면, 팀별 Dockerfile 편차와 유지보수 부담을 줄일 수 있습니다.

2.6 참고 문서

직접 이미지 URL은 시간이 지나면 깨질 수 있어, 아래 공식 페이지의 최신 다이어그램을 함께 참고하시기 바랍니다.

3. 실습 A — Kaniko Build Pod (운영형)

단순한 빌드 Pod 예시입니다. initContainer에서 소스를 클론하고, Kaniko가 Dockerfile을 읽어 이미지를 레지스트리에 푸시합니다.

3.1 ServiceAccount / RBAC 설정

빌드 Pod는 전용 ServiceAccount로 실행해 권한을 최소화하는 것이 원칙입니다.

apiVersion: v1
kind: Namespace
metadata:
  name: image-builder
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: image-builder-sa
  namespace: image-builder
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: image-builder-role
  namespace: image-builder
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: image-builder-rb
  namespace: image-builder
subjects:
- kind: ServiceAccount
  name: image-builder-sa
  namespace: image-builder
roleRef:
  kind: Role
  name: image-builder-role
  apiGroup: rbac.authorization.k8s.io

3.2 레지스트리 Secret 생성

kubectl create ns image-builder
kubectl -n image-builder create secret docker-registry kaniko-secret \\
  --docker-server=index.docker.io \\
  --docker-username=<DOCKERHUB_USER> \\
  --docker-password=<DOCKERHUB_TOKEN>

3.3 Build Pod (리소스 / 캐시 포함)

Kaniko는 root로 실행되므로 Pod 레벨 runAsNonRoot는 적용하지 않고, 대신 전용 네임스페이스와 RBAC으로 보완합니다.

apiVersion: v1
kind: Pod
metadata:
  name: random-generator-build
  namespace: image-builder
spec:
  serviceAccountName: image-builder-sa
  restartPolicy: Never
  volumes:
  - name: workspace
    emptyDir: {}
  - name: docker-config
    secret:
      secretName: kaniko-secret
      items:
      - key: .dockerconfigjson
        path: config.json

  initContainers:
  - name: fetch-source
    image: alpine/git:2.45.2
    command: ["sh", "-c"]
    args:
    - git clone <https://github.com/k8spatterns/random-generator> /workspace
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
        ephemeral-storage: 256Mi
      limits:
        cpu: 500m
        memory: 512Mi
        ephemeral-storage: 1Gi
    volumeMounts:
    - name: workspace
      mountPath: /workspace

  containers:
  - name: build-with-kaniko
    image: gcr.io/kaniko-project/executor:v1.23.2
    args:
    - --dockerfile=/workspace/Dockerfile
    - --context=/workspace
    - --destination=index.docker.io/<DOCKERHUB_USER>/random-generator:kaniko
    - --snapshot-mode=redo
    - --cache=true
    - --cache-repo=index.docker.io/<DOCKERHUB_USER>/kaniko-cache
    resources:
      requests:
        cpu: 500m
        memory: 1Gi
        ephemeral-storage: 2Gi
      limits:
        cpu: "2"
        memory: 4Gi
        ephemeral-storage: 8Gi
    volumeMounts:
    - name: workspace
      mountPath: /workspace
    - name: docker-config
      mountPath: /kaniko/.docker

3.4 실행 및 결과 확인

# 적용 및 상태 확인
kubectl -n image-builder apply -f build-pod.yaml
kubectl -n image-builder get pod random-generator-build -w

# 빌드 로그 확인
kubectl -n image-builder logs -f pod/random-generator-build -c build-with-kaniko

# 이벤트/리소스 현황 확인
kubectl -n image-builder describe pod random-generator-build

트러블슈팅 가이드

증상원인조치
로그에 Pushed index.docker.io/...성공없음
OOMKilled 이벤트memory limit 초과memory limit 상향
CPU throttling 이벤트cpu limit 낮음cpu limit 상향
Evicted 이벤트디스크 부족ephemeral-storage 상향 또는 캐시 전략 재설계

4. 실습 B — Tekton + Trivy + Cosign 보안 파이프라인

"빌드 성공"에서 멈추지 않고, 취약점 스캔 통과 + 이미지 서명 완료까지 파이프라인에 포함하는 운영형 예시입니다.

4.1 파이프라인 흐름

각 단계는 runAfter로 순서가 보장되며, scan 단계에서 HIGH/CRITICAL 취약점이 발견되면 sign과 배포는 실행되지 않습니다.

4.2 kaniko-build-task Task 정의

Pipeline에서 재사용할 Kaniko 빌드 Task를 별도로 정의합니다. workspace에서 Dockerfile을 읽어 이미지를 빌드하고 레지스트리에 푸시합니다.

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: kaniko-build-task
  namespace: image-builder
spec:
  params:
  - name: image
    type: string
  workspaces:
  - name: source
  steps:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:v1.23.2
    args:
    - --dockerfile=$(workspaces.source.path)/Dockerfile
    - --context=$(workspaces.source.path)
    - --destination=$(params.image)
    - --cache=true
    - --cache-repo=index.docker.io/<DOCKERHUB_USER>/kaniko-cache
    volumeMounts:
    - name: docker-config
      mountPath: /kaniko/.docker
  volumes:
  - name: docker-config
    secret:
      secretName: kaniko-secret

Tip: Tekton에서는 ServiceAccount에 registry secret을 secrets 필드로 연결하는 방식도 일반적입니다. 사내 표준에 따라 선택하시면 됩니다.

4.3 Pipeline 정의

4개 Task를 순서대로 연결합니다. fetch-source는 Tekton Hub의 git-clone Task를 resolver로 참조합니다.

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: secure-image-pipeline
  namespace: image-builder
spec:
  params:
  - name: image
    type: string
  - name: repo-url
    type: string
  workspaces:
  - name: source

  tasks:
  # 1단계: 소스 클론
  - name: fetch-source
    taskRef:
      resolver: hub
      params:
      - name: name
        value: git-clone
      - name: version
        value: "0.9"
    params:
    - name: url
      value: $(params.repo-url)
    workspaces:
    - name: output
      workspace: source

  # 2단계: Kaniko 빌드 & 레지스트리 푸시
  - name: build
    runAfter: [fetch-source]
    taskRef:
      name: kaniko-build-task
    params:
    - name: image
      value: $(params.image)
    workspaces:
    - name: source
      workspace: source

  # 3단계: Trivy 취약점 스캔
  - name: scan
    runAfter: [build]
    taskSpec:
      params:
      - name: image
        type: string
      steps:
      - name: trivy
        image: aquasec/trivy:0.58.0
        script: |
          trivy image --severity HIGH,CRITICAL --exit-code 1 $(params.image)
    params:
    - name: image
      value: $(params.image)

  # 4단계: Cosign 이미지 서명
  - name: sign
    runAfter: [scan]
    taskSpec:
      params:
      - name: image
        type: string
      steps:
      - name: cosign
        image: cgr.dev/chainguard/cosign:v2.4.1
        script: |
          cosign sign --yes $(params.image)
    params:
    - name: image
      value: $(params.image)

Tip (Trivy 레지스트리 인증): private 레지스트리 이미지를 스캔할 때는 TRIVY_USERNAME / TRIVY_PASSWORD 환경변수로 인증 정보를 전달하셔야 합니다. Tekton에서는 kaniko-secret의 값을 env로 주입하는 방식이 일반적입니다.

Tip (Cosign keyless 동작 조건): cosign sign --yes의 keyless 서명은 OIDC 토큰 공급자가 있어야 동작합니다. GKE Workload Identity, EKS IRSA, GitHub Actions OIDC 환경이 전제됩니다. 로컬/바닐라 클러스터에서는 KMS 키 방식(cosign sign --key gcpkms://...)으로 대체하셔야 합니다.

4.4 PipelineRun 실행

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: secure-image-pipelinerun
  namespace: image-builder
spec:
  pipelineRef:
    name: secure-image-pipeline
  params:
  - name: image
    value: index.docker.io/<DOCKERHUB_USER>/random-generator:secure
  - name: repo-url
    value: <https://github.com/k8spatterns/random-generator>
  workspaces:
  - name: source
    volumeClaimTemplate:
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi
# 파이프라인 전체 적용
kubectl -n image-builder apply -f secure-pipeline.yaml

# 실행 상태 확인
kubectl -n image-builder get pipelinerun

# 실시간 로그 확인
kubectl -n image-builder logs -l tekton.dev/pipelineRun=secure-image-pipelinerun -f

Tip: Tekton Hub의 공식 Task(git-clone 등)를 사용할 때는 버전을 고정하고 내부 보안 검토를 거친 후 사내 표준 Task로 관리하시는 것을 권장드립니다.

5. 운영 관점 추가 정리

5.1 캐시 전략 선택

빌드 속도에 가장 큰 영향을 미치는 요소는 캐시입니다. 운영 규모와 환경에 따라 전략을 선택하셔야 합니다.

전략장점단점추천 상황
로컬(emptyDir/PVC) 캐시구성 단순, 속도 빠름노드 종속, 주기적 정리 필요단일 클러스터 소규모 반복 빌드
Registry 캐시멀티 노드 공유 가능레지스트리 비용, 네트워크 I/O멀티 노드/멀티 파이프라인 환경
원격 오브젝트 스토리지(S3/GCS)대규모 운영에 유리, 비용 효율설정 복잡도 높음플랫폼팀 빌드 팜 표준 구성

예시

  • Kaniko는 -cache-repo로 레지스트리 캐시를 지원합니다.
  • BuildKit은 -cache-to type=registry, -cache-from type=registry로 원격 캐시를 활성화하실 수 있습니다.

5.2 멀티 아키텍처 빌드(amd64/arm64)

ARM 기반 노드(AWS Graviton, Apple Silicon CI 환경 등)가 늘어나면서 단일 아키텍처 이미지만 제공하면 스케줄링 제약이 생깁니다.

예시

  • BuildKit/buildx를 사용하면 linux/amd64,linux/arm64를 동시에 빌드해 manifest list로 푸시할 수 있습니다.
  • 노드는 자신의 아키텍처에 맞는 이미지를 자동 선택하므로, 별도 nodeSelector 설정 없이 혼합 클러스터를 운영하실 수 있습니다.
# buildx 멀티 아키텍처 빌드 예시
docker buildx build \\
  --platform linux/amd64,linux/arm64 \\
  --push \\
  -t <REGISTRY>/myapp:latest .

5.3 Kaniko 아카이브 상태 — 대응 전략

Kaniko GitHub 저장소는 현재 아카이브 상태로, 신규 기능 개발과 보안 패치가 사실상 중단되었습니다. 장기 운영에서는 다음 전략이 필요합니다.

상황권장 조치
신규 플랫폼 설계BuildKit 또는 관리형 빌드 서비스(Cloud Build 등)로 시작
기존 Kaniko 파이프라인 보유현재 버전 고정 + 대체 경로 병행 검토
보안 패치 필요 시Buildah 또는 BuildKit으로 단계적 전환

5.4 정책 / 거버넌스

운영 환경에서 Image Builder를 안전하게 유지하기 위해 아래 정책을 함께 설계하셔야 합니다.

  • PSA 프로파일: 빌드 네임스페이스는 baseline 적용 — Kaniko는 root로 실행되므로 restricted(runAsNonRoot 강제)와 호환 불가. RBAC으로 접근 범위를 보완
  • 이미지 서명 검증: 서명 없는 이미지 배포를 Admission 정책으로 차단 (Kyverno/OPA Gatekeeper)
  • digest 배포: 이미지 태그(latest) 대신 digest(@sha256:...) 기반 배포를 기본값으로 운영해 재현성 확보

6. Kubernetes v1.35 보강

Kubernetes v1.35는 2025년 12월 17일 릴리스되었고, 현재(2026-02-28) 활성 브랜치입니다.

6.1 In-place Pod Resize GA

빌드 워크로드는 피크 시간대에 CPU/메모리 요구량이 급변합니다. v1.35부터 GA된 In-place Resize를 활용하면 Pod 재생성 없이 리소스를 조정할 수 있어 빌드 큐 적체 상황에서 유연하게 대응하실 수 있습니다.

운영 팁

  • VPA(Vertical Pod Autoscaler)와 연동하면 빌드 부하 패턴을 학습해 자동 조정 정책을 만들 수 있습니다.

6.2 Pod Certificates Beta

podCertificate projected volume을 통해 mTLS 인증서를 Pod에 네이티브하게 주입/자동 회전할 수 있습니다. 사내 레지스트리와 mTLS를 강제하는 환경에서 cert-manager + sidecar 조합을 대체할 수 있는 방향입니다.

6.3 Storage Version Migration Beta

Tekton/Argo 등 CRD를 사용하는 빌드 도구의 API 버전 전환 시 저장 버전 마이그레이션이 자동화됩니다. v1.35 릴리스 블로그에서는 "beta, 기본 활성화"로 소개하지만, 문서 페이지 표기와 차이가 있을 수 있으므로 업그레이드 후 클러스터 설정을 직접 확인하시기 바랍니다.

6.4 containerd v1.x 지원 종료 예고

v1.35는 containerd 1.x 지원의 마지막 릴리스입니다. v1.36 업그레이드 전에는 반드시 노드 런타임을 containerd 2.x로 전환하는 계획을 세우셔야 합니다.

# 노드별 런타임 버전 일괄 확인
kubectl get nodes \\
  -o jsonpath='{range .items[*]}{.metadata.name}{"\\t"}{.status.nodeInfo.containerRuntimeVersion}{"\\n"}{end}'

운영 팁

  • kubelet_cri_losing_support 메트릭을 모니터링해 조기 경보를 설정하시면 업그레이드 타이밍을 놓치지 않으실 수 있습니다.

7. 결론

30장 Image Builder 패턴의 핵심은 "빌드도 배포처럼 쿠버네티스 워크로드로 운영한다" 는 점입니다. 클러스터 안으로 빌드를 가져오면 정책/감사/리소스 관리가 일관되게 적용되고, 이벤트 기반 재빌드 체인으로 보안 패치 대응도 빨라집니다.

2026년 현재 실무에서 "빌드 성공" 하나만으로는 부족합니다. 아래 요소를 함께 설계해야 완성도 있는 플랫폼이 됩니다.

영역핵심 고려 사항
보안daemon-less 빌더 선택, PSA baseline, digest 배포
공급망Trivy 스캔 게이트, Cosign 서명, Admission 검증
성능캐시 전략(레지스트리/오브젝트 스토리지), 멀티아키 빌드
지속성Kaniko 아카이브 대응, containerd 2.x 전환 계획
운영 유연성In-place Resize, Pod Certificates 점진 도입

도구 선택보다 중요한 것은 빌드 파이프라인 전체를 하나의 운영 대상으로 바라보는 관점입니다. 이 장의 패턴이 그 출발점이 됩니다.


출처

profile
DevOps Engineer

0개의 댓글