목차
GitOps 소개
CICD 아키텍처 with Jenkins, ArgoCD, Nexus, GitHub
ImageBuild
ImageBuild 실습
2.애플리케이션의 배포 상태, 환경 구성(Infrastructure as Code), Kubernetes 매니페스트(YAML, Helm Chart 등)를 모두 Git 저장소에 선언적으로 저장합니다.
3.모든 변경 사항은 Git을 통해서만 이루어지며, 이는 버전 관리, 변경 이력 추적, 롤백의 용이성을 보장합니다.
4.클러스터 내부의 GitOps 에이전트 (예: ArgoCD, Flux)가 Git 저장소의 의도된 상태(Desired State)를 지속적으로 모니터링합니다.
5.만약 Git의 상태와 실제 클러스터 상태(Live State)가 다르면, 에이전트가 자동으로 클러스터 상태를 Git의 상태와 동기화(Sync)하여 일치시킵니다.
📌GitOps는 "Git을 이용한 운영 자동화"


💡 Webhook Trigger : 자동 응답 스위치" 또는 "이벤트 알림 호출"
💻 GitOps에서의 작동
이벤트 발생: 개발자가 코드를 GitHub (소스 저장소)에 올립니다 (Push Source Code).
Webhook Trigger: GitHub는 이 Push 이벤트를 감지하고, 미리 설정된 Jenkins (CI Pipelines)의 주소로 "코드가 바뀌었으니 일해!"라는 자동 알림(Webhook)을 보냅니다.
결과: Jenkins는 이 알림을 받자마자 빌드, 테스트, 이미지 생성 작업을 즉시 시작하게 됩니다.
결국 Webhook Trigger는 GitOps 파이프라인의 자동화된 시작 버튼 역할을 합니다.
💡 Manifest : "완벽한 최종 결과의 설계도" 또는 "원하는 상태 선언서"
개발자 입장에서 ImageBuild 선택은 굉장히 중요한 요소라고 생각합니다.
| 구분 | Docker | Jib | Buildah (+Podman) | Buildpacks (CNB) | Shipwright |
|---|---|---|---|---|---|
| 데몬 필요 | ✅ 필요 | ❌ 불필요 | ❌ 불필요 | ❌ 불필요 | ❌ 불필요 |
| Dockerfile 필요 | ✅ 필요 | ❌ 불필요 | ⚠️ 선택적 | ❌ 불필요 | ⚠️ 선택적 |
| 루트 권한 | ✅ 필요 | ❌ 불필요 | ❌ 불필요 | ❌ 불필요 | ❌ 불필요 |
| 주요 언어/환경 | 범용 | Java (Maven/Gradle) | 범용 | 범용 (다양한 언어) | Kubernetes 기반 |
| 레이어 캐싱 | ✅ | ✅ 최적화됨 | ✅ | ✅ | ✅ |
| 도구 | 주요 장점 | 주요 단점 | 최적 사용 사례 |
|---|---|---|---|
| Docker | • 가장 널리 사용됨 • 풍부한 생태계 • 직관적인 사용법 • 완전한 기능 제공 | • 데몬 필수 • 보안 권한 문제 • CI/CD에서 권한 관리 복잡 | • 로컬 개발 • 기존 인프라 활용 |
| Jib | • Docker 설치 불필요 • Maven/Gradle 통합 • 빌드 속도 빠름 • 레이어 최적화 자동 | • Java 전용 • 커스터마이징 제한적 | • Java 애플리케이션 • CI/CD 파이프라인 • Jenkins 빌드 |
| Buildah | • 데몬리스 아키텍처 • 저수준 제어 가능 • 스크래치 빌드 지원 • OCI 호환 | • Linux 전용 • 학습 곡선 존재 | • CI/CD 시스템 • 보안 중시 환경 • 경량 이미지 생성 |
| Buildpacks | • 소스코드 자동 분석 • Dockerfile 불필요 • 표준화된 빌드 • Heroku/Cloud Foundry 지원 | • 커스터마이징 제한적 • 러닝 커브 • 특정 언어/프레임워크 의존 | • PaaS 환경 • 표준화된 빌드 프로세스 • 다양한 언어 지원 필요 |
| Shipwright | • Kubernetes 네이티브 • 여러 빌드 전략 지원 • 통합 API 제공 • Tekton 기반 | • Kubernetes 필수 • 복잡한 설정 • 추가 인프라 필요 | • Kubernetes 환경 • GitOps 워크플로우 • 대규모 빌드 자동화 |
| 기능 | Docker | Jib | Buildah | Buildpacks | Shipwright |
|---|---|---|---|---|---|
| 이미지 포맷 | OCI | OCI | OCI | OCI | OCI |
| 멀티 스테이지 빌드 | ✅ | ⚠️ 제한적 | ✅ | ✅ | ✅ |
| CPU 아키텍처 | Multi | Multi | Multi | 제한적 | Multi |
| 레지스트리 푸시 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 로컬 캐싱 | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| 보안 스캔 통합 | ⚠️ 외부 도구 | ⚠️ 외부 도구 | ⚠️ 외부 도구 | ⚠️ 외부 도구 | ✅ Tekton 통합 |
Docker: Dockerfile → Docker Daemon → Image Build → Registry Push
Jib: Source Code → Maven/Gradle Plugin → Direct Image Build → Registry Push
Buildah: Dockerfile/Script → Buildah CLI → Image Build → Registry Push
Buildpacks: Source Code → Auto Detection → Builder → Image Build → Registry Push
Shipwright: K8s API → Build Strategy (Kaniko/Buildah/etc) → Image Build → Registry Push
| 상황 | 권장 도구 | 이유 |
|---|---|---|
| Java 애플리케이션 | Jib | 최적화된 Java 지원, Docker 불필요 |
| CI/CD 파이프라인 | Buildah 또는 Jib | 데몬리스, 보안 강화 |
| Kubernetes 네이티브 | Shipwright | K8s API 통합, 다양한 전략 지원 |
| 표준화된 빌드 필요 | Buildpacks | 자동 탐지, Dockerfile 불필요 |
| 로컬 개발 환경 | Docker | 사용 편의성, 풍부한 도구 |
| 보안 중시 환경 | Buildah | 루트리스, 데몬리스 |
| 경량 이미지 생성 | Buildah | 스크래치 빌드 지원 |
riverjin@gangjin-ung-ui-Macmini python-app % cat Dockerfile
FROM registry.access.redhat.com/ubi8/python-39
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"]%
riverjin@gangjin-ung-ui-Macmini python-app % MYREGISTRY=docker.io
riverjin@gangjin-ung-ui-Macmini python-app % MYUSER=riverjin839
riverjin@gangjin-ung-ui-Macmini python-app % docker build -f Dockerfile -t $MYREGISTRY/$MYUSER/pythonapp:latest .
[+] Building 17.9s (4/9) docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 257B 0.0s
=> [internal] load metadata for registry.access.redhat.com/ubi8/python-39:latest 1.9s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM registry.access.redhat.com/ubi8/python-39:latest@sha256:1f8117d04c016fc6c161d4809e0b89f33c31a545a3217573bf1edbca30d105d 15.9s
=> => resolve registry.access.redhat.com/ubi8/python-39:latest@sha256:1f8117d04c016fc6c161d4809e0b89f33c31a545a3217573bf1edbca30d105da 0.0s
=> => sha256:279765a580b7dcfad086f1102a81681fad0401af62f796aabd7987cce7ad1289 76.55MB / 79.15MB 15.9s
=> => sha256:91bd08c7c4d6aa63f66ff6936e7786ebb18fcf21f51904156f4b6586de067842 75.82MB / 75.82MB 5.2s
=> => sha256:b775cbb23da8981e1c02539f2f3b1935b9209163bedb0d2a50897b225ffd4577 17.74MB / 17.74MB 2.3s
=> => sha256:338b44944473210b4670f0fd46c2e1e2741dbcdb13330749daf2108bac4fee02 146.73MB / 146.73MB 11.6s
=> => extracting sha256:91bd08c7c4d6aa63f66ff6936e7786ebb18fcf21f51904156f4b6586de067842 0.8s
=> => extracting sha256:b775cbb23da8981e1c02539f2f3b1935b9209163bedb0d2a50897b225ffd4577 0.2s
=> => extracting sha256:338b44944473210b4670f0fd46c2e1e2741dbcdb13330749daf2108bac4fee02 1.5s
=> [internal] load build context 0.0s
REPOSITORY TAG IMAGE ID CREATED SIZE
riverjin839/pythonapp latest a1347cbec72a About a minute ago 1.29GB
kindest/node <none> 7416a61b42b1 7 weeks ago 1.48GB
docker history 명령어 출력IMAGE CREATED CREATED BY SIZE
a1347cbec72a 6 minutes ago CMD ["app.py"] 0B
<missing> 6 minutes ago ENTRYPOINT ["python"] 0B
<missing> 6 minutes ago COPY . . # buildkit 24.6kB
<missing> 6 minutes ago RUN /bin/sh -c pip install --no-cache-dir -r… 5.31MB
<missing> 6 minutes ago COPY requirements.txt ./ # buildkit 20.5kB
<missing> 6 minutes ago WORKDIR /usr/src/app 16.4kB
<missing> 6 minutes ago EXPOSE map[8080/tcp:{}] 0B
<missing> 6 minutes ago ENV PORT=8080 0B
여기서 각 명령어와 크기를 확인했습니다.
docker inspect 출력의 RootFS 섹션"RootFS": {
"Type": "layers",
"Layers": [
"sha256:607b1f0096700271e26ad082137979c32952a60b20e8d1f9f25a62d1bb491b58",
"sha256:426b5cb0a28c2c6006f059560f4c24b69f6f7c60b81cf00e68aba0c872a4858f",
"sha256:7f0ee204bbc379a84550f442e0287d5ec15bf4964b448f8752745e378776481f",
"sha256:c389346289d19443829d3eecd6fc23524f012b775d3d8f00d05f086f1e20d929",
"sha256:bf89e309474550ab3ced47aa5793d0768f426368ae74d706e55c85172a5ab6b6",
"sha256:30ed94693736253f6e5bb17417a99ee46f6068d9f57b6f03fac8b20423ed0940",
"sha256:e18427696f7d83789dea7ac96e64252e4208eef6b35d71fd5ac35231b33580f1",
"sha256:5574f62b5715852d2ca49ae5b979485de6871ff6b017aff0cd7bb19aee793ae9"
]
}
실제 레이어 SHA256 해시 (총 8개 레이어).
=> [1/5] FROM registry.access.redhat.com/ubi8/python-39:latest... 16.9s
=> [2/5] WORKDIR /usr/src/app 0.2s
=> [3/5] COPY requirements.txt ./ 0.0s
=> [4/5] RUN pip install --no-cache-dir -r requirements.txt 1.1s
=> [5/5] COPY . . 0.0s
Dockerfile의 5단계 빌드 순서
[1/5] → [5/5])이 3가지 정보를 조합해서 레이어 구조를 분석!
특히 docker history는 역순(최신→과거)으로 표시되기 때문에, 이를 뒤집어서 Dockerfile 순서와 매칭결과
| 레이어 순서 | Dockerfile 명령어 | 레이어 크기 | 설명 | 레이어 SHA256 (일부) |
|---|---|---|---|---|
| BASE | FROM ubi8/python-39:latest | 1.29GB | Red Hat UBI8 Python 3.9 베이스 이미지 | 607b1f009670... (4개 레이어) |
| 1 | ENV PORT 8080 | 0B | 환경변수 설정 (메타데이터만) | - |
| 2 | EXPOSE 8080 | 0B | 포트 노출 선언 (메타데이터만) | - |
| 3 | WORKDIR /usr/src/app | 16.4KB | 작업 디렉터리 생성 및 설정 | bf89e3094745... |
| 4 | COPY requirements.txt ./ | 20.5KB | 의존성 파일 복사 | 30ed94693736... |
| 5 | RUN pip install --no-cache-dir -r requirements.txt | 5.31MB | Python 패키지 설치 | e18427696f7d... |
| 6 | COPY . . | 24.6KB | 애플리케이션 소스 코드 복사 | 5574f62b5715... |
| 7 | ENTRYPOINT ["python"] | 0B | 실행 진입점 설정 (메타데이터만) | - |
| 8 | CMD ["app.py"] | 0B | 기본 실행 명령어 (메타데이터만) | - |
| 빌드 스텝 | 명령어 | 실제 파일시스템 변경 | 캐시 가능 |
|---|---|---|---|
[1/5] | FROM - 베이스 이미지 다운로드 | ✅ 1.29GB | ✅ |
[2/5] | WORKDIR - 디렉터리 생성 | ✅ 16.4KB | ✅ |
[3/5] | COPY requirements.txt | ✅ 20.5KB | ✅ |
[4/5] | RUN pip install | ✅ 5.31MB | ✅ |
[5/5] | COPY . . - 소스 복사 | ✅ 24.6KB | ❌ (자주 변경) |
크기가 0B인 레이어: ENV, EXPOSE, ENTRYPOINT, CMD는 메타데이터만 변경하므로 실제 레이어를 생성하지 않는다.
The push refers to repository [docker.io/riverjin839/pythonapp]
279765a580b7: Pushed
80cc07a95a69: Pushed
22884d4e8c8b: Pushed
cac6bd88a61d: Pushed
e0a9abc3e01c: Pushed
338b44944473: Pushed
00bd7e0f3fb0: Pushed
91bd08c7c4d6: Pushed
b775cbb23da8: Pushed
latest: digest: sha256:a1347cbec72a672d3230043481b3f1775b3773e07837f0d468c155a989a53d85 size: 856
c705ff328777633a4a40bb4fec650666ca5a0396d48d7766cd3b2897999ed9e3
riverjin@gangjin-ung-ui-Macmini python-app % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c705ff328777 riverjin839/pythonapp:latest "python app.py" 6 seconds ago Up 6 seconds 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp myweb
b9b32eda11f4 kindest/node:v1.32.8 "/usr/local/bin/entr…" 2 hours ago Up 2 hours 0.0.0.0:30000-30001->30000-30001/tcp, 127.0.0.1:58791->6443/tcp myk8s-control-plane
2156910188c9 kindest/node:v1.32.8 "/usr/local/bin/entr…" 2 hours ago Up 2 hours myk8s-worker
riverjin@gangjin-ung-ui-Macmini python-app % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
riverjin839/pythonapp latest a1347cbec72a 23 minutes ago 1.29GB
kindest/node <none> 7416a61b42b1 7 weeks ago 1.48GB
kindest/node v1.32.8 abd489f042d2 7 weeks ago 1.51GB
quay.io/minio/minio latest d249d1fb6966 2 months ago 227MB
kindest/node v1.32.2 142f543559cc 7 months ago 1.5GB
riverjin@gangjin-ung-ui-Macmini python-app % curl 127.0.0.1:8080
Hello, World!%
riverjin@gangjin-ung-ui-Macmini python-app % docker logs myweb
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8080
* Running on http://172.17.0.2:8080
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 141-398-529
192.168.65.1 - - [18/Oct/2025 15:01:22] "GET / HTTP/1.1" 200 -
root@myk8s-worker:/chapters/chapters/ch03/springboot-app# docker info
bash: docker: command not found
root@myk8s-worker:/chapters/chapters/ch03/springboot-app# mvn -version
Apache Maven 3.8.7
mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.6:build \
-Dimage=docker.io/riverjin839/jib-example:latest \
-Djib.to.auth.username=riverjin839 \
-Djib.to.auth.password=dckr_pat_klzm9aS3gMmLpuRth4zgyVKDh4Y\
-Djib.from.platforms=linux/arm64

riverjin@gangjin-ung-ui-Macmini ~ % docker pull docker.io/riverjin839/jib-example
Using default tag: latest
latest: Pulling from riverjin839/jib-example
d3589b3a4a64: Pull complete
b8a35db46e38: Pull complete
2dc442b1af81: Pull complete
e92330854f64: Pull complete
8dff0f972162: Pull complete
ff877827746c: Pull complete
80179ad604dc: Pull complete
d2bcf32f8182: Pull complete
6d6cf0398708: Pull complete
Digest: sha256:831004986344dbce49163cb2baa01510de51b63a1e5107657ea4377bd28b06fe
Status: Downloaded newer image for riverjin839/jib-example:latest
docker.io/riverjin839/jib-example:latest
riverjin@gangjin-ung-ui-Macmini ~ % docker run -d --name myweb2 -p 8080:8080 -it docker.io/riverjin839/jib-example
87fd99bd4e3c0291bbc8a816109d7b141cbde17f475112e84243b437ac0fd46c
riverjin@gangjin-ung-ui-Macmini ~ % curl -s 127.0.0.1:8080/hello | jq
{
"id": 1,
"content": "Hello, World!"
}
riverjin@gangjin-ung-ui-Macmini ~ % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
..
riverjin839/jib-example latest 831004986344 55 years ago 425MB