Cloud Tech

Kubernetes CI & CD (ArgoCD, Jenkins)

Hanhorang31 2024. 12. 22. 08:32
 

Overview

지난 글에서 학습한 Jenkins CI 파이프라인을 확장하여 쿠버네티스 CI&CD를 Jenkins와 ArgoCD, Gogs를 통해 구성하겠습니다.

 

 

환경 구성

실습 쿠버네티스 환경은 Kind로 구성하였습니다.

Kind는 테스트 목적으로 로컬 환경에서 쿠버네티스를 구성시키는 도구입니다.

# Install Kind
brew install kind
kind --version

# Install kubectl
brew install kubernetes-cli
kubectl version --client=true

## kubectl -> k 단축키 설정
echo "alias kubectl=kubecolor" >> ~/.zshrc

# Install Helm
brew install helm
helm version
  • apiserverAddress : 쿠버네티스 API 서버 접근 허용을 위한 IP 지정이 필요합니다. 자신의 IP를 등록해야 합니다.
  • nodes.extraPortMappings : 클러스터 내부의 포트를 로컬호스트와 동일한 호스트로 매핑시킵니다. 외부 접근을 위해 사용합니다.
# 자신의 IP 
# ifconfig en0.inet 
MyIP=192.168.1.5

cat > kind-3node.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: "$MyIP"
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
- role: worker
- role: worker
EOF
kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.30.6

# 도커 구성 확인
docker ps 
docker network ls 
# kind 네트워크로 내부 172.18.0.0/16 사용
docker inspect kind 

# 쿠버네티스 정보 확인
kubectl get pods -o wide -v6 
kubectl get nodes -o wide 

Gogs와 Jenkins는 도커로 구성하였습니다.

구성방법과 설정은 필자의 글을 참고해주세요.

 

도커 기반 애플리케이션 CI/CD 구성

CI/CDCI/CD의 목표는 코드 빌드부터 프로덕션 환경으로의 배포까지 전 과정을 자동화된 프로세스를 구성하는 것입니다. 수동으로 애플리케이션을 업데이트하는 경우 사람의 실수로 인해 구성 드

hanhorang.tistory.com

 

 

체크리스트는 다음과 같습니다.

 
  • Jenkins 구성 플러그인 설정 (Docker Pipeline, Gogs, Pipeline Stage)
  • 자격증명 설정 : Jenkins 관리 → Credentials → Globals → Add Credentials
    1. Gogs Repo 자격증명 설정 : gogs-crd
      • Kind : Username with password
      • Username : devops
      • Password : 접근 키 발급 : Your Setting > Application > Generate New Token
      • ID : gogs-crd
    2. 도커 허브 자격증명 설정 : dockerhub-crd
      • Kind : Username with password
      • Username : <도커 계정명>
      • Password : <도커 계정 암호 혹은 토큰>
      • ID : dockerhub-crd
    3. k8s(kind) 자격증명 설정 : k8s-crd
      • Kind : Secret file
      • File : <kubeconfig 파일 업로드> ~/.kube/config
      • ID : k8s-crd

필자의 경우 이전에 구성한 Jenkins를 사용하였으나 속도가 느려 젠킨스 볼륨을 삭제하고 다시 생성하였습니다.

Jenkins URL 이 변경되어 그런 것으로 확인 중입니다.

Jenkins 을 새로 구성하였다면 Docker-out-of-Docker 설정이 필요합니다.

Gogs 에서 개발용, Devops용 Repo 를 생성합니다.

  • New Repository
    • Repository Name : dev-app
    • Visibility : (Check) This repository is Private
    • .gitignore : Python
    • Readme : Default → (Check) initialize this repository with selected files and template
  • New Repository
    • Repository Name : ops-deploy
    • Visibility : (Check) This repository is Private
    • Readme : Default → (Check) initialize this repository with selected files and template

 

Jenkins 파이프라인 테스트

pipeline {
    agent any
    environment {
        DOCKER_IMAGE = 'tmdgh663/dev-app' // Docker 이미지 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.1.5:3000/hanhorang/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }
        stage('Read VERSION') {
            steps {
                script {
                    // VERSION 파일 읽기
                    def version = readFile('VERSION').trim()
                    echo "Version found: ${version}"
                    // 환경 변수 설정
                    env.DOCKER_TAG = version
                }
            }
        }
        stage('Docker Build and Push') {
            steps {
                script {
                    docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
                        // DOCKER_TAG 사용
                        def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.push()
                        appImage.push("latest")
                    }
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}

필자의 경우 Jenkins 내 docker 미설치로 오류가 발생하였습니다.

다음의 스크립트를 통해 docker 설치 후 다시 빌드해주세요

# Jenkins 컨테이너 내부에 도커 실행 파일 설치
docker compose exec --privileged -u root jenkins bash
-----------------------------------------------------
id

curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update && apt install docker-ce-cli curl tree jq -y

docker info
docker ps
which docker

# Jenkins 유저도 docker를 실행할 수 있도록 권한을 부여
groupadd -g 2000 -f daemon
chgrp daemon  /var/run/docker.sock
ls -l /var/run/docker.sock
usermod -aG daemon jenkins
cat /etc/group | grep daemon 

Kind를 통해 쿠버네티스 클러스터 구성 후 생성한 이미지를 기반으로 애플리케이션을 구성합니다.

  • 쿠버네티스에서 도커 허브 접근을 위해 사전 Secert 생성이 필요합니다.
DHUSER=<도커 허브 계정>
DHPASS=<도커 허브 암호 혹은 토큰>

kubectl create secret docker-registry dockerhub-secret \
  --docker-server=https://index.docker.io/v1/ \
  --docker-username=$DHUSER \
  --docker-password=$DHPASS
  
  
cat <

 

 

 

Jenkins CI/CD

Jenkins를 통해 생성한 이미지를 통해 쿠버네티스 배포를 진행하도록 하겠습니다.

Jenkins 에서 쿠버네티스 객체 관리를 위해 kubectl, helm 툴을 설치하겠습니다.


# Install kubectl, helm
docker compose exec --privileged -u root jenkins bash
--------------------------------------------
#curl -LO "https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl" 
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl"  # macOS
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"  # WindowOS

install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client=true

#
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version

exit
--------------------------------------------
docker compose exec jenkins kubectl version --client=true
docker compose exec jenkins helm version

dev-app Repo 에 쿠버네티스 객체를 생성합니다.

git clone http://192.168.1.5:3000/hanhorang/dev-app.git
cd dev-app

cat > deploy/echo-server-blue.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server-blue
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo-server
      version: blue
  template:
    metadata:
      labels:
        app: echo-server
        version: blue
    spec:
      containers:
      - name: echo-server
        image: hashicorp/http-echo
        args:
        - "-text=Hello from Blue"
        ports:
        - containerPort: 5678
EOF

cat > deploy/echo-server-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: echo-server-service
spec:
  selector:
    app: echo-server
    version: blue
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5678
    nodePort: 30000
  type: NodePort
EOF

cat > deploy/echo-server-green.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server-green
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo-server
      version: green
  template:
    metadata:
      labels:
        app: echo-server
        version: green
    spec:
      containers:
      - name: echo-server
        image: hashicorp/http-echo
        args:
        - "-text=Hello from Green"
        ports:
        - containerPort: 5678
EOF

#
git add . && git commit -m "Add echo server yaml" && git push -u origin main

Jenkins 파이프라인을 다음과 같이 구성하여 빌드하면 젠킨스를 통해 쿠버네티스 배포 관리를 할 수 있습니다.

  • returnValue 분기를 통해 젠킨스에서 배포 rollback 및 버전 스위칭을 관리할 수 있게 됩니다.
pipeline {
    agent any

    environment {
        KUBECONFIG = credentials('k8s-crd')
    }

    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.1.5:3000/hanhorang/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }

        stage('container image build') {
            steps {
                echo "container image build"
            }
        }

        stage('container image upload') {
            steps {
                echo "container image upload"
            }
        }

        stage('k8s deployment blue version') {
            steps {
                sh "kubectl apply -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
                sh "kubectl apply -f ./deploy/echo-server-service.yaml --kubeconfig $KUBECONFIG"
            }
        }

        stage('approve green version') {
            steps {
                input message: 'approve green version', ok: "Yes"
            }
        }

        stage('k8s deployment green version') {
            steps {
	        	sh "kubectl apply -f ./deploy/echo-server-green.yaml --kubeconfig $KUBECONFIG"
            }
        }

        stage('approve version switching') {
            steps {
                script {
                    returnValue = input message: 'Green switching?', ok: "Yes", parameters: [booleanParam(defaultValue: true, name: 'IS_SWITCHED')]
                    if (returnValue) {
                        sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"green\"}}}' --kubeconfig $KUBECONFIG"
                    }
                }
            }
        }

        stage('Blue Rollback') {
            steps {
                script {
                    returnValue = input message: 'Blue Rollback?', parameters: [choice(choices: ['done', 'rollback'], name: 'IS_ROLLBACk')]
                    if (returnValue == "done") {
                        sh "kubectl delete -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
                    }
                    if (returnValue == "rollback") {
                        sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"blue\"}}}' --kubeconfig $KUBECONFIG"
                    }
                }
            }
        }
    }
}

위 Jenkins 내부 스크립트를 확인하면 명령어 스크립트를 통해 자동으로 실행하는 도구임을 확인할 수 있습니다.

Jenkins로 쿠버네티스 CD를 구성할 수 있지만, 시각화 대시보드 및 배포 전략 고급화를 손 쉽게 할 수 없어 다른 툴인 ArgoCD를 통해 CD를 구성하겠습니다.

 

 

Argo CD

ArgoCD는 쿠버네티를 위한 CD 툴입니다.

쿠버네티스 객체 모니터링을 통해 대상 클러스터에 배포 및 시각화 기능을 제공합니다.


kubectl create ns argocd
cat < argocd-values.yaml
dex:
  enabled: false

server:
  service:
    type: NodePort
    nodePortHttps: 30002
EOF

# 설치
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 7.7.10 -f argocd-values.yaml --namespace argocd

# 최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
PCdOlwZT8c4naBWK

# Argo CD 웹 접속 주소 확인 : 초기 암호 입력 (admin 계정)
open "https://127.0.0.1:30002" # macOS

ArgoCD 웹 접속 후 코드 관리 대상 클러스터 확인과 코드 저장소 연결이 필요합니다.

  • 클러스터는 기본 Default로 https://kubernetes.default.svc 를 사용합니다.
  • Settings → Repositories → CONNECT REPO에서 다음과 같이 기입 (Repo, 접근키 각자 등록)

Repo, Ops-deploy 에 쿠버네티스 yaml를 구성하여 ArgoCD를 통해 배포하겠습니다.

git clone http://192.168.1.5:3000/hanhorang/ops-deploy.git

cd ops-deploy
mkdir dev-app

VERSION=0.0.1

cat > dev-app/VERSION <<EOF
$VERSION
EOF

cat > dev-app/timeserver.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: timeserver
spec:
  replicas: 2
  selector:
    matchLabels:
      pod: timeserver-pod
  template:
    metadata:
      labels:
        pod: timeserver-pod
    spec:
      containers:
      - name: timeserver-container
        image: docker.io/$DHUSER/dev-app:$VERSION
      imagePullSecrets:
      - name: dockerhub-secret
EOF

cat > dev-app/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: timeserver
spec:
  selector:
    pod: timeserver-pod
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    nodePort: 30000
  type: NodePort
EOF

#
git status && git add . && git commit -m "Add dev-app deployment yaml" && git push -u origin main

ArgoCD CRD application yaml를 정의하여 ArgoCD에서 원본 Repo 에 있는 yaml를 기반으로 대상 클러스터에 배포하도록 설정하겠습니다.

cat <https://kubernetes.default.svc
EOF
  • argocd-application-controller-0 파드가 Repo를 3분 간격으로 모니터링하여 Sync를 확인합니다.

 

 

 

Jenkins CI + Argo CD 구성

이제 Jenkins 에서 ArgoCD를 통해 쿠버네티스 객체가 관리되도록 설정하겠습니다.

핵심은 Jenkins가 ArgoCD가 바라보는 원본 Repo에 버전 정보가 수정되도록 파이프라인을 추가하는 것입니다.아래 ops-deploy Checkout Stage 에서 argoCD가 바라보는 REPO에 버전 수정이 되도록 추가하면 쿠버네티스 배포까지 한번에 진행됩니다.

pipeline {
    agent any
    environment {
        DOCKER_IMAGE = 'tmdgh663/dev-app' // Docker 이미지 이름
        GOGSCRD = credentials('gogs-crd')
    }
    stages {
        stage('dev-app Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.1.5:3000/hanhorang/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }
        stage('Read VERSION') {
            steps {
                script {
                    // VERSION 파일 읽기
                    def version = readFile('VERSION').trim()
                    echo "Version found: ${version}"
                    // 환경 변수 설정
                    env.DOCKER_TAG = version
                }
            }
        }
        stage('Docker Build and Push') {
            steps {
                script {
                    docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
                        // DOCKER_TAG 사용
                        def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.push()
                        appImage.push("latest")
                    }
                }
            }
        }
        stage('ops-deploy Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.1.5:3000/hanhorang/ops-deploy.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }
        stage('ops-deploy version update push') {
            steps {
                sh '''
                OLDVER=$(cat dev-app/VERSION)
                NEWVER=$(echo ${DOCKER_TAG})
                sed -i -e "s/$OLDVER/$NEWVER/" dev-app/timeserver.yaml
                sed -i -e "s/$OLDVER/$NEWVER/" dev-app/VERSION
                git add ./dev-app
                git config user.name "devops"
                git config user.email "a@a.com"
                git commit -m "version update ${DOCKER_TAG}"
                git push http://${GOGSCRD_USR}:${GOGSCRD_PSW}@192.168.1.5:3000/hanhorang/ops-deploy.git
                '''
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}
  • 태그가 변경됨에 따라 파드가 변경됨을 확인