최소한의 승인된 이미지 빌드

DockerBeginner
지금 연습하기

소개

공급망 보안은 워크로드가 클러스터에서 실행되기 전부터 시작됩니다. 이미지에 불필요한 도구가 포함되어 있거나, 출처가 불분명하거나, 승인되지 않은 태그가 붙어 있다면, 앞서 학습한 런타임 제어 장치들이 더 큰 위험에 노출될 수 있습니다. 최소한의 승인된 이미지는 작은 빌드 컨텍스트, 명확한 베이스 이미지, 명시적인 애플리케이션 진입점 (entrypoint), 유용한 메타데이터 레이블, 그리고 대상 클러스터에서 실행 가능하다는 증거를 갖추어야 합니다.

이번 실습에서는 작은 로컬 이미지 빌드 컨텍스트를 생성하고, labex-v134 minikube 프로필 내부에서 이미지를 빌드한 뒤, 결과 이미지의 메타데이터를 검사하고, imagePullPolicy: Never를 사용하여 배포한 다음, Kubernetes 에서 런타임 내용을 검증합니다. 이 실습은 실무적인 범위에 집중합니다. 아직 이미지 스캔이나 레지스트리 정책 강제화 단계는 아니지만, 공급망 보안의 첫 번째 습관인 '작고, 이름이 지정되어 있으며, 검사 가능한 이미지를 빌드하고 그 내부를 증명하는 것'을 배웁니다.

작은 이미지 빌드 컨텍스트 생성

이 단계에서는 이미지를 구성할 파일들을 생성합니다. 빌드 컨텍스트는 이미지 빌더로 전송되는 디렉토리입니다. 빌드 컨텍스트를 작게 유지하는 것은 중요합니다. 불필요한 파일이 하나라도 있으면 나중에 이미지에 포함될 가능성이 있기 때문입니다.

실습 작업 공간에서 시작합니다:

cd /home/labex/project/minimal-approved-image

실습 노트를 읽어봅니다:

sed -n '1,160p' IMAGE_BUILD_NOTES.md

CKS 컨텍스트와 네임스페이스를 확인합니다:

kubectl config current-context | tee context-check.txt
kubectl get namespace cks-prep-image | tee namespace-check.txt

애플리케이션 스크립트를 생성합니다. 이 스크립트는 준비 완료 메시지와 런타임 UID 를 출력한 뒤, Kubernetes 가 컨테이너를 검사할 수 있도록 대기 (sleep) 상태를 유지합니다.

cat > app.sh <<'EOF'
#!/bin/sh
set -eu

echo "minimal approved image ready"
echo "runtime uid: $(id -u)"
sleep 300
EOF
chmod 0755 app.sh

Dockerfile 을 생성합니다. busybox:1.36은 이번 실습을 위한 작은 베이스 이미지입니다. OCI 스타일 레이블은 이 이미지가 실습에서 승인된 이유를 기록하며, USER 65534:65534는 이미지가 기본적으로 루트가 아닌 숫자형 사용자로 실행되도록 합니다.

cat > Dockerfile <<'EOF'
FROM busybox:1.36

LABEL org.opencontainers.image.title="minimal-approved-app"
LABEL org.opencontainers.image.source="local-cks-prep"
LABEL security.labex.io.approved="true"

WORKDIR /app
COPY app.sh /app/app.sh
RUN chmod 0555 /app/app.sh

USER 65534:65534
ENTRYPOINT ["/app/app.sh"]
EOF

빌드 컨텍스트 인벤토리와 애플리케이션 스크립트의 체크섬을 저장합니다:

find . -maxdepth 1 -type f -printf '%f\n' | sort | tee build-context-files.txt
sha256sum app.sh | tee app-sha256.txt
sed -n '1,120p' Dockerfile | tee dockerfile-review.txt

이제 디렉토리에는 Dockerfile, app.sh, 노트, 그리고 저장한 증거 파일들이 포함됩니다. 중요한 이미지 경계는 Dockerfile 의 COPY 라인입니다. 오직 app.sh만이 이미지 내부의 /app/app.sh로 복사됩니다.

Minikube 프로필 내부에서 이미지 빌드

이 단계에서는 labex-v134 minikube 프로필에서 이미지를 빌드합니다. 여기서 빌드하는 이유는 Kubernetes 가 minikube 노드 런타임에서 컨테이너를 실행하기 때문입니다. 외부 VM Docker 데몬에서만 이미지를 빌드하면 minikube 노드가 이를 인식하지 못할 수 있습니다.

명확한 로컬 태그를 사용하여 이미지를 빌드합니다:

minikube -p labex-v134 image build -t labex/minimal-approved:1.0 .

minikube 프로필 내부의 이미지를 검사합니다. 첫 번째 명령어는 이미지가 노드 런타임에 존재하는지 확인합니다. 두 번째 명령어는 구성된 사용자, 진입점, 레이블을 포함한 주요 메타데이터를 저장합니다.

minikube -p labex-v134 ssh -- 'docker image ls labex/minimal-approved:1.0' | tee built-image-list.txt
minikube -p labex-v134 ssh -- 'docker image inspect labex/minimal-approved:1.0' | tee built-image-inspect.json

jq를 사용하여 승인 증거로 중요한 필드들을 추출합니다:

jq '.[0].Config | {User, Entrypoint, Labels}' built-image-inspect.json | tee built-image-approval.json

built-image-list.txt에서 이미지 태그를, User가 65534:65534로 설정된 것을, Entrypoint가 /app/app.sh로 설정된 것을, 그리고 승인 레이블이 true로 설정된 것을 확인할 수 있습니다.

Kubernetes 에서 승인된 이미지 실행

이 단계에서는 로컬 이미지를 Kubernetes 에서 실행합니다. 이미지가 레지스트리에 푸시되지 않고 minikube 프로필 내부에서 빌드되었기 때문에 Pod 는 imagePullPolicy: Never를 사용합니다. 이 설정은 Kubernetes 에게 로컬 노드 이미지를 사용하도록 지시하며, 이미지가 없으면 실패하게 만듭니다.

Pod 매니페스트를 생성합니다:

cat > approved-image-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: approved-image-app
  namespace: cks-prep-image
  labels:
    app: approved-image-app
    supply-chain.labex.io/approved: "true"
spec:
  restartPolicy: Never
  securityContext:
    runAsNonRoot: true
    runAsUser: 65534
    runAsGroup: 65534
  containers:
  - name: app
    image: labex/minimal-approved:1.0
    imagePullPolicy: Never
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
EOF

Pod 를 적용하고 준비될 때까지 기다립니다:

kubectl apply -f approved-image-pod.yaml
kubectl wait --for=condition=Ready pod/approved-image-app -n cks-prep-image --timeout=120s

Pod 로그와 현재 Pod YAML 을 저장합니다:

kubectl logs approved-image-app -n cks-prep-image | tee approved-image-logs.txt
kubectl get pod approved-image-app -n cks-prep-image -o yaml | tee approved-image-pod.current.yaml

로그에는 minimal approved image ready와 runtime uid: 65534가 표시되어야 합니다. 이는 Kubernetes 가 의도한 대로 루트가 아닌 사용자로 이미지 진입점을 실행하고 있음을 증명합니다.

런타임 내용 검증

이 단계에서는 Kubernetes 에서 실행 중인 컨테이너를 검사합니다. 이미지 승인은 태그 이름에서 멈춰서는 안 됩니다. 런타임에 예상된 애플리케이션 경로가 포함되어 있는지, 그리고 이미지의 일부가 되어서는 안 될 불필요한 추가 파일이 포함되어 있지 않은지에 대한 증거가 필요합니다.

애플리케이션 디렉토리를 검사합니다:

kubectl exec -n cks-prep-image approved-image-app -- sh -c 'ls -la /app; test -f /app/app.sh; echo APP_PATH_OK' | tee runtime-app-path.txt

런타임 사용자를 확인하고, 불필요한 디버그 파일이 없는지 간단한 부정 검사를 수행합니다:

kubectl exec -n cks-prep-image approved-image-app -- sh -c 'id -u; test ! -e /app/debug-tool && echo NO_DEBUG_TOOL' | tee runtime-content-check.txt

Kubernetes 상태와 이미지 메타데이터를 결합한 최종 승인 기록을 저장합니다:

kubectl get pod approved-image-app -n cks-prep-image -o json | jq '{image: .spec.containers[0].image, imagePullPolicy: .spec.containers[0].imagePullPolicy, podSecurityContext: .spec.securityContext, containerSecurityContext: .spec.containers[0].securityContext}' | tee final-pod-approval.json
jq '.[0].Config | {User, Entrypoint, Labels}' built-image-inspect.json | tee final-image-approval.json

최종 증거에는 로컬 승인 이미지 태그, imagePullPolicy: Never, 루트가 아닌 런타임 설정, /app/app.sh 진입점, 그리고 승인 레이블이 포함되어야 합니다.

요약

이번 CKS 준비 과정의 첫 번째 공급망 워크플로우인 '최소한의 승인된 이미지 빌드 및 증명'을 실습했습니다. 작은 빌드 컨텍스트를 생성하고, labex-v134 minikube 프로필 내에서 labex/minimal-approved:1.0을 빌드했으며, 이미지 메타데이터와 레이블을 검사하고, imagePullPolicy: Never를 사용하여 로컬 이미지를 배포한 뒤, Kubernetes 에서 런타임 사용자와 애플리케이션 내용을 검증했습니다. 이후 공급망 실습에서는 매니페스트 스캔, Helm 출력 검토, SBOM 및 체크섬 증거, 신뢰할 수 있는 레지스트리 제어, 빌드 시크릿 정리 등을 통해 이 기반을 더욱 확장할 것입니다.