스터디(Study)/CI·CD Study

GitOps Cookbook 3장: 컨테이너

Chann._.y 2025. 10. 18.
728x90

본 정리는 macOS(Apple Silicon/Intel 공통)에서 Docker Desktop + kind 환경을 기반으로, 컨테이너 이미지를 빌드/검증/배포하는 방법을 한 번에 따라 할 수 있도록 요약한 실습 가이드다. 예시의 모든 레지스트리 경로는 docker.io/chaany로 통일했다.


3.0 들어가며

  • 컨테이너는 앱·런타임·라이브러리를 계층(layer) 구조로 패키징한 표준 포맷이며, OCI(https://opencontainers.org/) 표준을 따른다.
  • 개방형 표준(OCI) 덕분에 Docker, Jib, Buildah/Podman, Buildpacks, Shipwright(BuildKit) 등 다양한 도구로 이미지를 만들고 상호운용 가능하다.
  • 이하 절에서는 Dockerfile, Jib(자바), Buildpacks, Shipwright(BuildKit) 를 macOS에서 수행하고, Buildah/Podman은 kind 클러스터 컨테이너 내부에서 실습하도록 변경했다.

3.1 Docker로 컨테이너 이미지 빌드/푸시 (Python 예제)

실습 후 Python app이 layer에 존재하는 Image를 얻을 수 있다.

3.1.1 예제 소스 가져오기

# 예제 저장소
git clone https://github.com/gitops-cookbook/chapters
cd chapters/chapters/ch03/python-app
ls

 

3.1.2 Dockerfile 확인 (예제)

# ch03/python-app/Dockerfile
FROM registry.access.redhat.com/ubi8/python-39   # UBI 기반(무료), macOS arm64에서도 동작 사례 많음
ENV PORT 8080
EXPOSE 8080
WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
ENTRYPOINT ["python"]
CMD ["app.py"]

3.1.3 macOS에서 빌드/검증/푸시

# 변수
REGISTRY=docker.io
USER=chaany
APP=pythonapp
TAG=latest

# 빌드
docker build -f Dockerfile -t ${REGISTRY}/${USER}/${APP}:${TAG} .

# 이미지/레이어 확인
docker images | grep ${USER}/${APP}
docker inspect ${REGISTRY}/${USER}/${APP}:${TAG} | jq '.RootFS'

# 로컬 실행 확인
docker run -d --rm --name myweb -p 8080:8080 ${REGISTRY}/${USER}/${APP}:${TAG}
sleep 1
curl -s 127.0.0.1:8080 | head -n 5
docker logs myweb

# Docker Hub 로그인 및 푸시
docker login -u ${USER}
docker push ${REGISTRY}/${USER}/${APP}:${TAG}

# 정리
docker rm -f myweb

Docker 빌드는 레이어 캐시를 적극 재사용한다. COPY requirements.txtRUN pip installCOPY . . 순서 최적화는 재빌드 속도를 좌우한다.


3.2 Docker 없이 자바 이미지를 만드는 Jib (Maven 플러그인)

실습 후 Java app이 layer에 존재하는 Image를 얻을 수 있다.

3.2.1 macOS에 Java/Maven 설치

brew install openjdk@17 maven
java -version
mvn -version

3.2.2 예제 소스 및 Jib 실행

# 소스
cd ~/ && git clone https://github.com/gitops-cookbook/chapters jib-lab
cd jib-lab/chapters/ch03/springboot-app

# Docker 데몬 불필요, Dockerfile 불필요. 바로 원격 레지스트리로 빌드+푸시
# macOS는 일반적으로 linux/arm64 이미지를 생산하는 편이 자연스럽다.
REGISTRY=docker.io
USER=chaany
IMAGE=${REGISTRY}/${USER}/jib-example:latest

mvn -q -e \
  -Dimage=${IMAGE} \
  -Djib.to.auth.username=${USER} \
  -Djib.to.auth.password="$(security find-generic-password -a ${USER} -s docker-hub -w 2>/dev/null || echo '<DOCKER_PAT_OR_PASSWORD>')" \
  -Djib.from.platforms=linux/arm64 \
  com.google.cloud.tools:jib-maven-plugin:3.4.6:build

# 확인
docker pull ${IMAGE}
docker run -d --rm --name myweb2 -p 8080:8080 ${IMAGE}
sleep 2
curl -s 127.0.0.1:8080/hello | head -n 1
docker rm -f myweb2

Jib은 앱 아티팩트(클래스/리소스/의존성)를 레이어링해 변경분만 다시 올리므로, 자바 서비스 반복개발에 유리하다.


3.3 Buildah/Podman은 kind 클러스터에서 진행 권장 (컨트롤플레인 컨테이너 내부 실습)

macOS 네이티브 환경에서 buildah/podman를 직접 운용하기보다, kind 노드 컨테이너 내부(우분투 베이스) 에서 설치·실행하는 방식이 가장 단순하다.

실습 후 httpd로 웹서버를 호스팅하는 layer가 존재하는 Image를 얻을 수 있다.

3.3.1 사전 준비

# kind 클러스터가 없으면 생성 (2장에서 이미 생성했다면 생략)
kind create cluster --name myk8s --image kindest/node:v1.32.8
kind get nodes

3.3.2 컨트롤플레인 컨테이너에 진입해 podman/buildah 설치

# 컨트롤플레인 컨테이너 이름 확인
docker ps --format 'table {{.Names}}\t{{.Image}}' | grep myk8s

# 컨테이너 내부 진입
docker exec -it myk8s-control-plane bash

# 컨테이너 내부 (우분투 베이스)
apt-get update
mkdir -p /usr/share/man/man1
apt-get install -y podman buildah curl jq ca-certificates

podman version
buildah version

3.3.3 Buildah로 이미지 빌드 → Podman으로 실행 → Docker Hub(chaany)로 푸시

# 컨테이너 내부 계속
# 샘플 정적 웹 서버 이미지 생성 (httpd)
cat > Dockerfile <<'EOF'
FROM registry.access.redhat.com/ubi9/httpd-24
COPY index.html /var/www/html/index.html
EXPOSE 8080
CMD ["/usr/sbin/httpd","-D","FOREGROUND","-f","/etc/httpd/conf/httpd.conf","-k","start","-e","debug","-E","/dev/stderr","-DFOREGROUND"]
EOF

cat > index.html <<'EOF'
<html>
  <head><title>Cloudneta CICD Study</title></head>
  <body><h1>Hello from Buildah on kind!</h1></body>
</html>
EOF

# buildah 빌드 (컨테이너 내부의 buildah-daemonless)
REGISTRY=docker.io
USER=chaany
IMG=${REGISTRY}/${USER}/gitops-website:buildah-latest

buildah build -t ${IMG} .

# podman으로 컨테이너 실행 테스트 (컨테이너 내부 포트 노출)
podman run -d --name myweb -p 8080:8080 -it ${IMG}
podman ps
curl -s 127.0.0.1:8080 | head -n 1

# Docker Hub 푸시 (컨테이너 내부에서 레지스트리 로그인)
podman login ${REGISTRY} -u ${USER} -p '<YOUR_DOCKER_PAT_OR_PASSWORD>'
buildah push ${IMG}

# 정리
podman rm -f myweb
exit

3.3.4 호스트에서 이미지 확인 및 실행

# 호스트(macOS)로 돌아와서
docker pull docker.io/chaany/gitops-website:buildah-latest
docker run -d --rm --name web-bh -p 8080:80 docker.io/chaany/gitops-website:buildah-latest
sleep 1
curl -s 127.0.0.1:8080 | head -n 1
docker rm -f web-bh

buildah/podman는 kind 노드 컨테이너 내부에서 빌드/검증/푸시까지 수행하고, 결과 이미지는 호스트에서도 그대로 사용할 수 있다.

3.4 Buildpacks로 Dockerfile 없이 이미지 빌드 (Node.js 예제)

3.4.1 pack 설치

brew install buildpacks/tap/pack
pack version

3.4.2 Node.js 앱 빌드/실행/푸시

cd ~/ && git clone https://github.com/gitops-cookbook/chapters buildpacks-lab
cd buildpacks-lab/chapters/ch03/nodejs-app
ls
cat package.json

# 추천 빌더 확인
pack builder suggest | sed -n '1,12p'

# Apple Silicon(macOS)에서 linux/arm64 타겟으로 빌드
REGISTRY=docker.io
USER=chaany
IMAGE=${REGISTRY}/${USER}/nodejs-app:latest

pack build ${IMAGE} \
  --platform linux/arm64 \
  --builder heroku/builder:24

# 로컬 확인
docker run -d --rm --name nodeapp -p 3000:3000 ${IMAGE}
sleep 2
curl -s 127.0.0.1:3000 | head -n 3

# 푸시
docker login -u ${USER}
docker push ${IMAGE}

# 정리
docker rm -f nodeapp

Buildpacks는 앱 소스(package.json, pom.xml, requirements.txt 등)를 탐지(Detection) 후 알맞은 빌드팩을 자동 선택해 컨테이너 이미지를 만든다. 대규모 팀·플랫폼에서 표준화된 빌드 경험을 제공하기 적합하다.


3.5 Shipwright(쿠버네티스 빌드 프레임워크) + BuildKit 전략으로 K8s에서 빌드

전제: kind 클러스터(myk8s)가 실행 중이어야 한다. (2장에서 생성)

실습 후 Go app이 layer에 존재하는 Image를 얻을 수 있다.



3.5.1 Tekton + Shipwright 설치 (버전은 예제 기준)

# Tekton Pipelines (예: v0.70.0)
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.70.0/release.yaml

# Ready 확인(시간 소요)
kubectl get ns | grep tekton
kubectl get all -n tekton-pipelines
kubectl get all -n tekton-pipelines-resolvers

# Shipwright Builds (예: v0.11.0)
kubectl apply -f https://github.com/shipwright-io/build/releases/download/v0.11.0/release.yaml

# CRD/리소스 확인
kubectl get crd | grep shipwright
kubectl get all -n shipwright-build

# 샘플 빌드 전략(여러 전략 포함: buildkit, buildah, buildpacks 등)
kubectl apply -f https://github.com/shipwright-io/build/releases/download/v0.11.0/sample-strategies.yaml

# 전략 목록 확인
kubectl get clusterbuildstrategy

Kaniko는 최근 아카이브 이슈가 있어, BuildKit 또는 Buildpacks 전략을 쓰는 구성을 추천한다.

3.5.2 Docker Hub 푸시용 시크릿 생성 (chaany 계정)

REGISTRY_SERVER="https://index.docker.io/v1/"
REGISTRY_USER="chaany"
REGISTRY_PASSWORD="<YOUR_DOCKER_PAT_OR_PASSWORD>"   # Personal Access Token 권장
EMAIL="dev@example.com"

kubectl create secret docker-registry push-secret \
  --docker-server=${REGISTRY_SERVER} \
  --docker-username=${REGISTRY_USER} \
  --docker-password=${REGISTRY_PASSWORD} \
  --docker-email=${EMAIL}

kubectl get secret push-secret

3.5.3 BuildKit 전략 기반 Build + BuildRun (Golang 샘플)

# Build (BuildKit 사용) - shipwright sample 사용
cat <<'YAML' | kubectl apply -f -
apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  name: buildkit-golang-build
spec:
  source:
    url: https://github.com/shipwright-io/sample-go
    contextDir: docker-build
  strategy:
    name: buildkit
    kind: ClusterBuildStrategy
  dockerfile: Dockerfile
  output:
    image: docker.io/chaany/sample-golang:latest
    credentials:
      name: push-secret
YAML

kubectl get builds
kubectl get build buildkit-golang-build -o yaml | sed -n '1,60p'

# BuildRun 생성 (빌드 시작)
cat <<'YAML' > buildrun-go.yaml
apiVersion: shipwright.io/v1alpha1
kind: BuildRun
metadata:
  generateName: buildkit-golang-buildrun-
spec:
  buildRef:
    name: buildkit-golang-build
YAML

kubectl create -f buildrun-go.yaml

# 빌드 파드 모니터링
kubectl get pods -w

3.5.4 빌드 결과 확인/로그/정리

# 완료 후 상태
kubectl get buildruns.shipwright.io
kubectl logs -l clusterbuildstrategy.shipwright.io/name=buildkit -c step-source-default --tail=200
kubectl logs -l clusterbuildstrategy.shipwright.io/name=buildkit -c step-build-and-push --tail=200

# Docker Hub에서 이미지 확인 후, 로컬 실행 테스트
docker pull docker.io/chaany/sample-golang:latest
docker run -d --rm --name golang-sample -p 8080:8080 docker.io/chaany/sample-golang:latest
sleep 2
curl -s 127.0.0.1:8080 | head -n 5
docker rm -f golang-sample

# (선택) 정리
# kubectl delete build,buildrun --all

실습을 전부 진행한 뒤 4개의 레포지토리에 각각 이미지가 푸시되어 있는 것을 확인할 수 있다.

참고

  • Dockerfile 빌드: 가장 직관적. 레이어 캐시 활용과 명령 순서 최적화가 중요.
  • Jib(자바): Docker/Daemon 없이 빌드·푸시. CI 친화적이며 레이어링이 효율적.
  • Buildpacks: Dockerfile 없이도 언어/프레임워크 자동 감지로 표준화된 이미지 생성.
  • Shipwright(+Tekton): K8s 네이티브 빌드 API. BuildKit/Buildpacks 등 전략(Strategy)을 바꿔 확장.
    • macOS에서 현업/학습 난이도는 Docker → Buildpacks → Jib → Shipwright(BuildKit) 순으로 높아진다. 로컬 단일 노드 kind로도 CI-유사 파이프라인 감을 충분히 잡을 수 있다.
728x90

댓글