본문 바로가기
CI&CD

CI/CD Study [1주차]

by e-pd 2025. 10. 18.

CloudNet@ CICD Study 1기 정리

 GitOps의 정의

GitOps는 **Git을 단일한 진실의 원천(Single Source of Truth)**으로 사용하는 인프라 관리 방식입니다.

  • 제안자: Weaveworks CEO Alexis Richardson (2017년)
  • 등장 배경: 클라우드 네이티브 애플리케이션의 복잡성과 전통적 배포 방식의 한계를 해결하기 위해 등장
  • 핵심 개념: 시스템의 원하는 상태(Desired State)를 Git에 선언적(Declarative)으로 정의하여 인프라를 코드로 관리하는 방법론

GitOps의 3대 핵심 원칙

원칙설명
1. Git은 단일 소스 Git이 모든 환경(Dev, Staging, Prod)의 신뢰 가능한 정보 원천으로 작동. 모든 변경은 Git을 통해 수행.
2. 모든 것을 코드로 인프라뿐 아니라 애플리케이션 매니페스트, 보안 정책, 모니터링 설정 등 모든 요소를 코드로 선언적 관리.
3. Git 워크플로 기반 운영 모든 변경은 **Pull Request(PR)**를 통해 수행되며, 리뷰·버전관리·감사 추적이 가능.

 CNCF OpenGitOps 4대 원칙 (표준 정의)

원칙설명
1. 선언적 (Declarative) 원하는 상태(Desired State)를 코드로 명확히 표현해야 함.
2. 버전 및 불변성 (Versioned & Immutable) Git 커밋 히스토리로 완전한 변경 이력 유지 및 불변성 보장.
3. 자동 반영 (Pulled Automatically) GitOps 에이전트(Argo CD, Flux 등)가 Git에서 상태를 자동으로 끌어와 반영.
4. 지속적 조정 (Continuously Reconciled) 실제 상태를 지속적으로 관찰하고 Git의 원하는 상태와 자동 동기화.

 

MacOS에서 Homebrew를 사용하여 필요한 도구들을 설치

brew install kind Kind (Kubernetes IN Docker) 설치

brew install kubectl 쿠버네티스 클러스터 관리 도구 설치  
brew install jq tree JSON 처리 및 디렉토리 구조 확인 도구 설치  
git clone <https://github.com/gitops-cookbook/chapters> 실습용 소스 코드 저장소 복제  
cd chapters/chapters 작업 디렉토리 이동  

필수 준비: Docker Hub 또는 Quay.io에 가입하고, 이미지 푸시를 위해 Access Token을 생성하여 준비.

로컬 쿠버네티스 클러스터 (Kind) 구성

로컬 쿠버네티스 클러스터 (Kind) 구성

Kind를 사용하여 로컬에 쿠버네티스 클러스터를 생성.

단계 명령어 설명

1. 클러스터 생성 아래 설정을 kind-config.yaml로 저장 후 실행 Kind 클러스터 (1 Control Plane + 1 Worker) 생성 및 포트 매핑
  kind create cluster --name gitops-study --config kind-config.yaml  
2. 설정 파일 (kind-config.yaml) yaml<br>kind: Cluster<br>apiVersion: kind.x-k8s.io/v1alpha4<br>nodes:<br>- role: control-plane<br>- role: worker<br>  
3. 상태 확인 kubectl get nodes -o wide Kind 클러스터 노드 상태 확인 (Ready 상태여야 함)
  kubectl cluster-info 클러스터 정보 확인
cat kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  # 호스트 포트 30000, 30001번을 Control Plane 컨테이너에 매핑 (포트 매핑)
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    protocol: TCP
  - containerPort: 30001
    hostPort: 30001
    protocol: TCP
- role: worker

apiVersion: Kind 설정 파일의 API 버전을 명시합니다.

  • nodes: 클러스터를 구성하는 노드들의 목록을 정의합니다.

◦ role: control-plane: 제어 평면 역할을 하는 노드를 지정합니다.

◦ extraPortMappings: Host OS와 Control Plane 컨테이너 간의 포트 매핑을 정의 (예: NodePort 서비스 접근 시 유용).

◦ role: worker: 작업 부하(Pod)를 실행할 워커 노드를 지정.

클러스터 생성

kind create cluster --name gitops-study --config kind-config.yaml

Kind 클러스터 노드 상태 확인

kubectl get nodes -o wide
NAME                         STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION     CONTAINER-RUNTIME
gitops-study-control-plane   Ready    control-plane   42s   v1.34.0   172.18.0.4    <none>        Debian GNU/Linux 12 (bookworm)   6.10.14-linuxkit   containerd://2.1.3
gitops-study-worker          Ready    <none>          33s   v1.34.0   172.18.0.3    <none>        Debian GNU/Linux 12 (bookworm)   6.10.14-linuxkit   containerd://2.1.3

클러스터 정보확인

kubectl cluster-info
Kubernetes control plane is running at <https://127.0.0.1:62844>
CoreDNS is running at <https://127.0.0.1:62844/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy>

컨테이너 이미지를 레지스트리에 푸시하려면 인증이 필요

 

 

 

예제 이미지 빌드

docker build -f Dockerfile -t $MYREGISTRY/$MYUSER/pythonapp:latest .

빌드된 이미지 확인

docker images | grep pythonapp
wish2rich/pythonapp                   latest           cc8839a919d7   2 minutes ago   892MB

이미지 푸쉬

docker push $MYREGISTRY/$MYUSER/pythonapp:latest
The push refers to repository [docker.io/wish2rich/pythonapp]
832ff574d03b: Pushed
951f234ec339: Pushed
93df63592272: Pushed
60f598c3b559: Pushed
c389346289d1: Pushed
7f0ee204bbc3: Pushed
426b5cb0a28c: Pushed
607b1f009670: Pushed
latest: digest: sha256:3bc7831a01170be6cd02d2b2cc1161c27ffad070dfce7bc5d5015a2485b678ad size: 1999

로컬테스트

docker run -d --name myweb -p 8080:8080 $MYREGISTRY/$MYUSER/pythonapp:latest
curl <http://localhost:8080>
Hello, World!

컨테이너 정리

docker rm -f myweb

Dockerless 빌드 - Jib 사용 (Java 애플리케이션)

Jib은 Docker 데몬이나 Dockerfile 없이 Maven/Gradle 플러그인을 통해 JVM 기반 언어 이미지를 빌드하고 레지스트리에 푸시

Worker 노드 접속

docker exec -it gitops-study-worker bash
root@gitops-study-worker:/#

Maven/Java 설치

명령어 설명 출처

apt update 패키지 리스트를 최신 정보로 업데이트  
mkdir -p /usr/share/man/man1 일부 리눅스 환경에서 설치 시 발생하는 오류 방지를 위해 디렉토리를 생성  
apt install openjdk-17-jdk -y OpenJDK 17 JDK를 설치.  
apt install maven -y Maven 빌드 도구를 설치.  
apt install git tree wget curl jq -y 실습에 필요한 추가적인 툴(Git, Tree, Wget, Curl, jq 등)을 설치.  
java -version 설치된 Java 버전을 확인 (17 버전이 나와야 함).  
mvn -version 설치된 Maven 버전을 확인.  

 

 

springboot-app 이동

mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.6:build \\
-Dimage=docker.io/<자신의-ID>/jib-example:latest \\  # <-- 1번
-Djib.to.auth.username=<자신의-ID> \\                  # <-- 2번
-Djib.to.auth.password=<자신의-Access-Token> \\       # <-- 3번
-Djib.from.platforms=linux/arm64

푸쉬된 docker 이미지 실행

docker run -d --name myweb2 -p 8080:8080 docker.io/$MYUSER/jib-example
curl -s 127.0.0.1:8080/hello
{"id":1,"content":"Hello, World!"}%

Kustomize 실습 (Base와 Overlay 패턴)

Kustomize는 YAML 템플릿 없이 Base와 Overlay 구조를 통해 환경별 설정을 관리

mkdir -p kustomize-test/{base,dev,prod}

depployment.yaml 생성

cat << EOF > base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2  # 기본 복제본 수
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
EOF

base/service.yaml 생성

cat << EOF > base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF

Base Kustomization YAML 생성

cat << EOF > base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
EOF
tree base
base
├── deployment.yaml
├── kustomization.yaml
└── service.yaml

Dev Overlay 파일 작성 (dev/kustomization.yaml)

cat << EOF > dev/kustomization.yaml
resources:
- ../base       # Base 디렉토리에 정의된 Deployment, Service 등을 참조
namePrefix: dev-    # Base의 리소스 이름 앞에 'dev-' 접두사 추가 
EOF

Prod Overlay 파일 작성 (prod/kustomization.yaml)

cat << EOF > prod/kustomization.yaml
resources:
- ../base       # Base 디렉토리를 참조
namePrefix: prod-   # Base의 리소스 이름 앞에 'prod-' 접두사 추가
EOF

Dev 환경 배포 확인

kubectl kustomize dev
apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: dev-my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dev-my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - image: nginx:alpine
        name: my-nginx
        ports:
        - containerPort: 80

적용

kubectl apply -k dev/

Prod 환경 배포 확인

kubectl kustomize prod/
kubectl kustomize prod/
apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: prod-my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prod-my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - image: nginx:alpine
        name: my-nginx
        ports:
        - containerPort: 80
kubectl apply -k prod/

배포된 리소스 확인

kubectl get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/dev-my-nginx-945b795f8-848hj    1/1     Running   0          2m14s
pod/dev-my-nginx-945b795f8-pz25v    1/1     Running   0          2m14s
pod/prod-my-nginx-945b795f8-4jzl5   1/1     Running   0          48s
pod/prod-my-nginx-945b795f8-f7g22   1/1     Running   0          48s

NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/dev-my-nginx    ClusterIP   10.96.228.23   <none>        80/TCP    2m14s
service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP   60m
service/prod-my-nginx   ClusterIP   10.96.68.81    <none>        80/TCP    48s

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dev-my-nginx    2/2     2            2           2m14s
deployment.apps/prod-my-nginx   2/2     2            2           48s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/dev-my-nginx-945b795f8    2         2         2       2m14s
replicaset.apps/prod-my-nginx-945b795f8   2         2         2       48s

정리

kubectl delete -k dev
kubectl delete -k prod

Buildpacks를 사용한 Dockerless 빌드

Buildpacks는 Dockerfile 없이 소스 코드를 자동으로 감지하여 이미지를 빌드.

Pack CLI 설치

brew install buildpacks/tap/pack

 

빌드

pack build nodejs-app --platform linux/arm64 --builder heroku/builder:24

실행확인

docker images | grep nodejs-app
nodejs-app                            latest           7989ff2a6843   45 years ago        706MB
docker run -d --name myapp --rm -p 3000:3000 nodejs-app
6f3feca8eef92390a1446a70d93925b59638c61ef5158f91bec31944dad4c565
curl -s 127.0.0.1:3000
Hello Buildpacks!

클러스터 삭제

kind delete cluster --name gitops-study