✍ Posted by Immersive Builder Seong
0. 실습 환경 구성
Kind 설치
WSL2 환경에 Kind(Kubernetes in Docker)를 설치합니다. 그리고 Kind 클러스터를 1회 배포/삭제하여 Kind 도커 네트워크를 구성합니다.
# Kind 설치
$ curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.26.0/kind-linux-amd64
$ chmod +x ./kind
$ sudo mv ./kind /usr/bin
$ kind --version
kind version 0.26.0
# Docker Network - Kind 구성
$ kind create cluster
$ kind delete cluster
$ docker network ls
064c5a3308c5 kind bridge local
Jenkins & Gogs 컨테이너 작업
도커 컴포즈를 사용하여 Jenkins와 Gogs 2대의 컨테이너를 실행합니다. Jenkins와 Gogs를 사용하여 CI/CD 실습 환경을 구성하는 방법은 이전 포스팅을 참고하시기 바랍니다.
Jenkins와 Gogs 기본 세팅이 완료되고 나면 개발팀용(dev-app)과 데브옵스팀용(ops-deploy) 저장소를 구분하여 각각 생성합니다.
그리고 로컬 환경에서 Gogs 저장소를 연동한 후, 파일을 커밋/푸시하여 업로드 여부를 확인합니다.
Docker Hub 토큰 발급
자신의 도커 허브 계정에서 토큰을 발급합니다.
- Account settings > Security > Personal access tokens > Create new token
1. Jenkins CI + Kubernetes
Kind 쿠버네티스 클러스터 배포
Kind를 사용하여 쿠버네티스 클러스터를 배포합니다. 클러스터는 컨트롤플레인 1, 워커노드 2대로 구성되며, 호스트 30000-30003 포트를 컨테이너에 매핑하여 사용할 수 있습니다.
kube-ops-view 유틸리티를 설치하고 30001 포트로 매핑하여 사용합니다. kube-ops-view는 클러스터에 파드가 라이브로 배포되는 상황을 보여주는 간단한 시각화 도구입니다.
# kube-ops-view 설치
$ helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
$ helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30001 --set env.TZ="Asia/Seoul" --namespace kube-system
# 설치 확인
$ kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kube-ops-view 1/1 1 1 69s
NAME READY STATUS RESTARTS AGE
pod/kube-ops-view-6658c477d4-kvssh 1/1 Running 0 69s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-ops-view NodePort 10.96.133.232 <none> 8080:30001/TCP 69s
NAME ENDPOINTS AGE
endpoints/kube-ops-view 10.244.1.2:8080 69s
Jenkins 자격증명 설정
Jenkins에서 설정해야할 자격증명은 Gogs, Docker Hub, K8s 3가지입니다.
- Gogs : 저장소에서 소스 코드를 가져오기 위한 용도입니다.
- Kind : Username with password
- Username : devops
- Password : <Gogs Token>
- ID : gogs-crd
- Docker Hub : 컨테이너 저장소에 이미지를 푸시하기 위한 용도입니다.
- Kind : Username with password
- Username : <Docker Hub 계정명>
- Password : <Docker Hub 암호 | Token>
- ID : dockerhub-crd
- K8s(Kind) : 쿠버네티스 클러스터에 접근하기 위한 용도입니다.
- Kind : Secret file
- File : <~/.kube/config>
- ID : k8s-crd
CI 파이프라인 테스트
다음과 같이 파이프라인 스크립트를 작성하여 Jenkins를 빌드합니다. Gogs로부터 소스 코드를 가져와서 이미지를 빌드하고 Docker Hub에 업로드하는 일련의 과정을 포함합니다.
pipeline {
agent any
environment {
DOCKER_IMAGE = 'okms1017/dev-app'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.251:3000/devops/dev-app.git',
credentialsId: 'gogs-crd'
}
}
stage('Read VERSION') {
steps {
script {
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') {
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."
}
}
}
Deploying to Kubernetes
도커 허브에 업로드된 컨테이너 이미지를 쿠버네티스 환경에서 디플로이먼트로 배포해봅니다.
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:0.0.1
실행 결과, ErrImagePull/ImagePullBackOff 에러가 발생합니다. 해당 에러는 이미지 정보를 잘못 입력한 경우, 또는 저장소에 이미지가 없거나 자격증명이 없는 경우에 발생합니다. 지금은 자격증명을 따로 지정하지 않아서 발생한 경우에 해당합니다.
자격증명을 시크릿(dockerhub-secret)으로 생성하고, 디플로이먼트 구성 파일에 시크릿을 추가로 명시합니다.
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:0.0.1
imagePullSecrets:
- name: dockerhub-secret
이제 정상적으로 디플로이먼트가 배포됨을 확인할 수 있습니다. 접속을 위한 curl 파드를 하나 생성하고 timeserver 파드에 접속이 되는지 테스트합니다.
SCM 파이프라인 테스트
SCM 파이프라인 스크립트와 구성 방법은 이전 포스팅을 참고하시기 바랍니다.
쿠버네티스 환경에 신규 버전의 이미지를 적용하기 위해 아래의 명령어를 실행합니다. 신규 버전(0.0.3)으로 롤링 업데이트된 것을 확인할 수 있습니다. 그러나 신규 버전이 배포될 때마다 수동으로 업데이트하는 작업은 매우 번거롭습니다.
2. Jenkins CI/CD + Kubernetes
Jenkins 컨테이너 내부에 툴 설치 : Helm, Kubectl
# Install kubectl, helm
$ docker compose exec --privileged -u root jenkins bash
--------------------------------------------
$ 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
Jenkins에서 파드 정보 조회하기
pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('List Pods') {
steps {
sh '''
# Fetch and display Pods
kubectl get pods -A --kubeconfig "$KUBECONFIG"
'''
}
}
}
}
Jenkins를 이용한 Blue/Green 배포하기
쿠버네티스 클러스터에 서비스 1개, 디플로이먼트 2개(Blue/Green)을 아래와 같이 배포합니다. 그리고 서비스의 selector를 version:blue에서 version:green으로 변경하여 수동으로 Blue/Green 업데이트를 실행합니다.
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
---
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
---
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
- 업데이트 이전 (version=blue)
- 업데이트 이후 (version=green)
이번에는 Jenkins 파이프라인 스크립트를 작성하여 Blue/Green 업데이트를 자동으로 실행합니다. '소스 코드 - 빌드 - 이미지 업로드 - 오브젝트 배포 - Blue/Green 업데이트 - (RollBack)' 등 일련의 과정을 단계적으로 수행합니다.
pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.251:3000/devops/dev-app.git',
credentialsId: 'gogs-crd'
}
}
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를 사용하여 쿠버네티스 환경에 배포하는 경우, 여러가지 아쉬운 부분이 존재합니다. 우선 파이프라인을 작성하고 관리하는 작업이 투박합니다. 또한, 쿠버네티스 매니페스트와 젠킨스 스크립트를 함께 관리하다보면 코드의 복잡도가 더욱 증가하게 됩니다. 쿠버네티스와의 통합을 위해 플러그인에 의존하므로 플러그인의 버전 및 호환성 관리도 필요합니다. 그리고 배포 과정을 가시화하여 모니터링하는 기능이 부족하며, ArgoCD나 Flux만큼 선언적 방식으로 배포하는데 최적화되어 있지 않습니다.
3. Jenkins CI + ArgoCD + Kubernetes
개발팀과 데브옵스팀이 동일한 저장소를 공유하면 개발 소스와 구성 파일이 혼재되어 관리하기 매우 복잡해집니다. 심지어 개발 소스가 아닌 오브젝트 구성 파일을 업로드 하였을 뿐인데 Jenkins가 트리거되어 빌드가 수행될 수도 있습니다. 이러한 문제를 방지하고 관리의 효율성을 가져가기 위해 개발용/배포용으로 저장소를 구분하여 사용합니다. 또한, 쿠버네티스 환경에 친화적이고 어플리케이션 배포를 최적화하기 위해 ArgoCD를 도입하여 구성해볼 것입니다.
▶ ArgoCD 소개 : https://okms1017.tistory.com/54
ArgoCD 설치 및 기본 설정
- ArgoCD 설치
$ kubectl create ns argocd
$ cat <<EOF > 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 get pod,svc,ep -n argocd
$ kubectl get crd | grep argo
- ArgoCD 기본 설정
관리자(admin) 계정의 패스워드를 변경합니다.
데브옵스팀용 저장소(ops-deploy)를 연동합니다.
ArgoCD 배포하기 1 : 대시보드 UI
ops-deploy 저장소에 nginx helm chart를 푸시하고 ArgoCD 대시보드를 통해 수동 배포를 진행해봅니다.
$ VERSION=1.26.1
$ mkdir nginx-chart
$ mkdir nginx-chart/templates
$ cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
$ cat > nginx-chart/templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF
$ cat > nginx-chart/templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: nginx
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: 80
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name: {{ .Release.Name }}
EOF
$ cat > nginx-chart/templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
selector:
app: {{ .Release.Name }}
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
EOF
$ cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 1
EOF
$ cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>PRD : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
$ cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF
헬름 차트를 업로드하고 나서 ArgoCD 대시보드로 이동하여 새로운 App을 등록합니다.
아래와 같이 dev-nginx App이 등록되고, 세부 정보와 구성도를 확인할 수 있습니다. 원천 소스와 쿠버네티스 클러스터 간에 동기화 정책은 수동으로 설정하였으므로 아직 OutOfSync 상태입니다.
따라서 UI 화면에서 SYNC 버튼을 클릭하여 수동으로 동기화를 진행합니다.
이번에는 코드의 버전(1.26.2)을 수정하여 반영 여부를 확인해보겠습니다.
$ VERSION=1.26.2
$ cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
$ cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
$ cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>PRD : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
소스 버전이 변경되었으나 ArgoCD 대시보드에는 변화가 없습니다. 그 이유는 ArgoCD와 원천 소스 간에 3분 간격으로 변경 사항을 확인하기 때문입니다. 이 때, REFRESH 버튼을 클릭하여 즉시 반영할 수 있습니다.
다시 SYNC 버튼을 통해 동기화를 진행합니다.
ArgoCD 배포하기 2 : Declarative Setup
ArgoCD 대시보드에서 수행할 수 있는 모든 작업은 선언형 코드로 작성할 수 있습니다. 이번에는 App 등록과 Auto Sync 작업을 코드로 선언하여 실행해보겠습니다.
$ cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dev-nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
helm:
valueFiles:
- values-dev.yaml
path: nginx-chart
repoURL: http://192.168.35.251:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
코드를 실행함과 동시에 App이 생성되고, 관련 쿠버네티스 리소스들이 즉시 배포됩니다. (Auto Sync)
대시보드를 통해서 헬스체크에 실패한 파드를 발견하고 그 원인을 분석할 수 있습니다.
또한, 대시보드를 먼저 구성한 다음 선언형 코드로 변환하는 작업도 가능합니다.
4. Argo Rollout + Kubernetes
Argo Rollout
Argo Rollout은 Blue-Green, Canary, Canary Analysis 등 향상된 배포 전략을 제공하는 쿠버네티스 컨트롤러입니다.
Components
- Argo Rollouts Controller
- Rollout Resource
- ReplicaSets for old and new version
- Ingress/Service
- AnalysisTemplate and AnalysisRun
- Metric Providers
- CLI & UI
배포 전략
- Blue/Green 전략 : 이전 버전(Blue)과 새로운 버전(Green)의 어플리케이션을 병렬로 실행하여 새로운 버전이 테스트를 통과하면 이전 버전의 리소스를 정리하고 새로운 버전으로 전환하는 배포 방식입니다.
- Canary 전략 : 라이브한 운영환경에서 새로운 버전의 어플리케이션을 일부 사용자에게 노출시키고 새로운 버전의 가중치를 점진적으로 늘려나가면서 전환하는 배포 방식입니다.
Argo Rollouts 설치
$ kubectl create ns argo-rollouts
$ cat <<EOT > argorollouts-values.yaml
dashboard:
enabled: true
service:
type: NodePort
nodePort: 30003
EOT
$ helm install argo-rollouts argo/argo-rollouts --version 2.35.1 -f argorollouts-values.yaml --namespace argo-rollouts
$ kubectl get all -n argo-rollouts
$ kubectl get crds
$ echo "http://127.0.0.1:30003"
Canary 배포하기
Replica를 5개 생성하고 새로운 버전의 가중치를 20%씩 점진적으로 늘려가는 정책을 설정합니다.
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: {duration: 10}
- setWeight: 60
- pause: {duration: 10}
- setWeight: 80
- pause: {duration: 10}
Rollout과 Service를 배포합니다.
$ kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/rollout.yaml
$ kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/service.yaml
배포 과정을 모니터링합니다.
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
[출처]
1) CloudNet@, CI/CD Study
'CI·CD' 카테고리의 다른 글
[CI/CD] GitHub Actions CI/CD Workflow (2) | 2024.12.15 |
---|---|
[CI/CD] 도커 기반 어플리케이션 CI/CD 구성하기 (0) | 2024.12.12 |
[CI/CD] Jenkins 기본 사용하기 (1) | 2024.12.08 |
[CD/CD] CI/CD 실습 환경 구성하기 (1) | 2024.12.08 |
[CI/CD] 컨테이너를 활용하여 어플리케이션 개발하기 (0) | 2024.12.07 |