[cicd] cloudeNet Study 1주차

진웅·2025년 10월 18일

CI/CD

목록 보기
1/7

목차

GitOps 소개

CICD 아키텍처 with Jenkins, ArgoCD, Nexus, GitHub

ImageBuild

ImageBuild 실습

GitOps 소개

  1. GitOps는 두 단어를 결합하여 만들어진 합성어
    • Git (버전 관리 시스템)
      • 단일 진실의 근원(Single Source of Truth, SOT)으로 사용
      • 즉 절대 기준점이다.
    • Ops (Operations, 운영)
      • 운영 과정을 자동화하는 것을 의미합니다.

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을 이용한 운영 자동화"

  • GitOps는 Git 저장소를 인프라 코드의 단일 진실 원천으로 사용하는 방법론입니다.
  • DevOps 기둥을 바탕으로 IaC를 통해 Kubernetes 같은 플랫폼에서 자동화된 배포를 구현합니다.
  • GitOps의 장점은 자동화, 위험 감소, 서비스 품질 향상이며, Kubernetes CI/CD 파이프라인에서 컨테이너 이미지를 빌드하고 Git 워크플로를 통해 배포합니다.
  • GitOps 루프는 배포, 모니터링, 드리프트 감지(, 액션으로 구성되며, DevOps와 결합해 애자일성을 높인다.

CICD 아키텍처 with Jenkins, ArgoCD, Nexus, GitHub


  • 개발자 환경
    개발자 노트북에서 애플리케이션 소스 코드와 K8s 매니페스트(Helm 값 파일)를 관리
    GitHub에 두 개의 저장소를 사용:
    앱 소스 코드 저장소: 애플리케이션 소스 코드 관리
    앱 매니페스트 저장소 : K8s 배포를 위한 Helm 값 파일 및 설정 관리
  • CI 파이프라인 (Jenkins)
    GitHub의 앱 소스 코드 저장소에 코드 푸시 시 Webhook으로 Jenkins 트리거
    Jenkins 작업:
    소스 코드 빌드 및 테스트
    도커 이미지 생성 및 Nexus 레지스트리에 푸시
    Helm 차트 패키징 및 Nexus Helm 저장소에 푸시
    앱 매니페스트 저장소의 이미지 태그 업데이트 (GitOps 핵심)
  • 아티팩트 저장소 (Nexus )
    Nexus를 프라이빗 레지스트리로 사용:
    도커 이미지 저장소: 빌드된 애플리케이션 이미지 저장
    Helm 차트 저장소: 애플리케이션 배포용 Helm 차트 저장
  • CD 파이프라인 (ArgoCD )
    GitOps 엔진으로 ArgoCD 사용
    앱 매니페스트 저장소의 변경 사항을 자동으로 감지
    K8s 클러스터에 애플리케이션 동기화:
    Dev 환경 : 개발 및 테스트 환경
    Prod 환경 : 프로덕션 환경
  • GitOps 워크플로우
    개발자가 소스 코드를 GitHub에 푸시
    Jenkins가 Webhook으로 트리거되어 빌드 및 테스트 실행
    빌드된 아티팩트(이미지, Helm 차트)를 Nexus에 저장
    Jenkins가 앱 매니페스트 저장소의 이미지 태그를 업데이트
    ArgoCD가 앱 매니페스트 저장소의 변경을 감지
    ArgoCD가 K8s 환경(Dev/Prod)에 변경 사항을 자동으로 동기화

💡 Webhook Trigger : 자동 응답 스위치" 또는 "이벤트 알림 호출"

💻 GitOps에서의 작동

이벤트 발생: 개발자가 코드를 GitHub (소스 저장소)에 올립니다 (Push Source Code).

Webhook Trigger: GitHub는 이 Push 이벤트를 감지하고, 미리 설정된 Jenkins (CI Pipelines)의 주소로 "코드가 바뀌었으니 일해!"라는 자동 알림(Webhook)을 보냅니다.

결과: Jenkins는 이 알림을 받자마자 빌드, 테스트, 이미지 생성 작업을 즉시 시작하게 됩니다.

결국 Webhook Trigger는 GitOps 파이프라인의 자동화된 시작 버튼 역할을 합니다.

💡 Manifest : "완벽한 최종 결과의 설계도" 또는 "원하는 상태 선언서"

ImageBuild

개발자 입장에서 ImageBuild 선택은 굉장히 중요한 요소라고 생각합니다.

컨테이너 이미지 빌드 도구 비교

구분DockerJibBuildah (+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 워크플로우
• 대규모 빌드 자동화

기술적 특징

기능DockerJibBuildahBuildpacksShipwright
이미지 포맷OCIOCIOCIOCIOCI
멀티 스테이지 빌드⚠️ 제한적
CPU 아키텍처MultiMultiMulti제한적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 네이티브ShipwrightK8s API 통합, 다양한 전략 지원
표준화된 빌드 필요Buildpacks자동 탐지, Dockerfile 불필요
로컬 개발 환경Docker사용 편의성, 풍부한 도구
보안 중시 환경Buildah루트리스, 데몬리스
경량 이미지 생성Buildah스크래치 빌드 지원

imagebuild 실습해보기

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
  • 빌드 확인
    • riverjin@gangjin-ung-ui-Macmini python-app % docker images
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개 레이어).

Docker 빌드 로그

=> [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. 빌드 로그: Dockerfile의 순서 파악 ([1/5][5/5])
  2. docker history: 각 명령어의 크기와 시간 역순으로 확인
  3. docker inspect: 실제 파일시스템 레이어 해시 확인

이 3가지 정보를 조합해서 레이어 구조를 분석!

특히 docker history역순(최신→과거)으로 표시되기 때문에, 이를 뒤집어서 Dockerfile 순서와 매칭결과

빌드된 레이어 구조
레이어 순서Dockerfile 명령어레이어 크기설명레이어 SHA256 (일부)
BASEFROM ubi8/python-39:latest1.29GBRed Hat UBI8 Python 3.9 베이스 이미지607b1f009670... (4개 레이어)
1ENV PORT 80800B환경변수 설정 (메타데이터만)-
2EXPOSE 80800B포트 노출 선언 (메타데이터만)-
3WORKDIR /usr/src/app16.4KB작업 디렉터리 생성 및 설정bf89e3094745...
4COPY requirements.txt ./20.5KB의존성 파일 복사30ed94693736...
5RUN pip install --no-cache-dir -r requirements.txt5.31MBPython 패키지 설치e18427696f7d...
6COPY . .24.6KB애플리케이션 소스 코드 복사5574f62b5715...
7ENTRYPOINT ["python"]0B실행 진입점 설정 (메타데이터만)-
8CMD ["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는 메타데이터만 변경하므로 실제 레이어를 생성하지 않는다.

docker push 해보기
  • riverjin@gangjin-ung-ui-Macmini python-app % MYREGISTRY=docker.io
    MYUSER=riverjin839
  • riverjin@gangjin-ung-ui-Macmini python-app % docker push MYREGISTRY/MYREGISTRY/MYUSER/pythonapp:latest
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

docker 기동

  • riverjin@gangjin-ung-ui-Macmini python-app % docker run -d --name myweb -p 8080:8080 -it MYREGISTRY/MYREGISTRY/MYUSER/pythonapp:latest
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 -

jib 실습

  • docker 엔진없이도 이미지 빌드가 가능하다. dockerfile도 안만들어도 된다.
  • 하지만 only java 만 가능하다.
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

  • docker 엔진 없는 pod 내부에서 만든 이미지
  • 정상적으로 가져와서 실행까지 완료
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
profile
bytebliss

0개의 댓글