[CI/CD]3주차 - Jenkins CI + Argo CD + K8S(Kind)
쿠버네티스 배포 자동화 툴로는 ArgoCD가 유명하다.
다양한 배포 방식을 지원하기도 하고 GitOps 패턴을 가져갈 수 있다
Argo CD - Declarative GitOps CD for Kubernetes
ArgoCD는 Git에서 애플리케이션이 정의된 yaml파일을 관리하고 이를 기반으로 배포 자동화를 수행한다. 변경점이 발생하면 이를 감지하고 선언적 방식으로 관리할 수 있다.
또한 지속적으로 Git과 쿠버네티스 서비스가 일치하는지 체크하고 상이할 시에 자동 동기화를 제공한다.
Argo CD 설치 및 기본 설정
kubectl create ns argocd로 우선 네임스페이스를 생성한다
파라미터 파일을 아래와 같이 작성하고
cat <<EOF > argocd-values.yaml
dex:
enabled: false
server:
service:
type: NodePort
nodePortHttps: 30002
EOF
helm을 통해 argocd를 설치한다
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
argocd 리소스가 생성된 것을 확인한다.
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
로 초기 패스워드를 확인한 후
User info > UPDATE PASSWORD에서 admin 계정 패스워드를 변경한다
Settings에 들어가면 기본 정보를 확인할 수 있다
Clusters, Projects, Accounts 정보를 확인한다
ArgoCD에 Gogs 레포를 추가해 준다
Settings > Repogitories > CONNECT REPO를 눌러서
VIA HTTPS로 git 레포 정보를 추가해 준다
CONNECTION STATUS에 Successful이 뜬 걸 확인할 수 있다
helm chart 를 통한 배포 실습
helm으로 애플리케이션을 배포할 수 있다.
각 쿠버네티스 리소스를 templates 디렉토리에 yaml파일로 정의한다.
cat > templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name:
data:
index.html: |
EOF
- ConfigMap에 index.html을 저장한다
cat > templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name:
spec:
replicas:
selector:
matchLabels:
app:
template:
metadata:
labels:
app:
spec:
containers:
- name: nginx
image: :
ports:
- containerPort: 80
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name:
EOF
.Values
로 시작하면 values.yaml에 정의된 값을 가지고 올 수 있다- ConfigMap에 정의한 index.html파일을 마운트해서 nginx 컨테이너를 80포트로 서비스하는 deployment 파일이다.
cat > templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name:
spec:
selector:
app:
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
EOF
- 80포트로 서비스하는 애플리케이션을 nodeport 30000 포트로 노출시킨다.
values.yaml파일은 templates에 있는 쿠버네티스 리소스 파일에 값을 유연하게 설정해 줄 수 있는 파일로, 보통 templates에 있는 파일은 구조 변경이 없는 한 그대로 둔 채로 values.yaml 파일 값을 변경하면서 사용할 수 있다.
cat > values.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>Nginx version 1.26.1</p>
</body>
</html>
image:
repository: nginx
tag: 1.26.1
replicaCount: 1
EOF
Chart.yaml파일은 차트의 메타데이터로 버전관리할 수 있는 파일이다
cat > 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: "1.26.1"
EOF
디렉토리 구조는 아래처럼 되어 있다
helm install dev-nginx . -f values.yaml
로 해당 애플리케이션을 한 번에 배포한다.
http://127.0.0.1:30000에 접속해 보면 nginx가 1.26.1 버전으로 서비스됨을 확인할 수 있다.
values.yaml파일에 정의된 replicaCount
값을 2로 변경하고 nginx 이미지 태그를 1.26.2로 변경하고
helm upgrade dev-nginx . -f values.yaml
로 helm chart 업그레이드 적용해 보자
REVISION 값이 2로 변경되었다
helm uninstall dev-nginx
로 배포된 서비스를 삭제할 수 있다
Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 1
사전에 만들어 둔 ops-deploy 레포를 로컬로 clone해 오고 자격증명을 저장해 준다.
git config user.name "devops"
git config user.email "devops@example.com"
git config init.defaultBranch **main**
git config credential.helper **store**
VERSION=1.26.1 변수를 세팅하고 nginx-chart 디렉토리와 nginx-chart/templates 디렉토리를 생성해 준다
버전 파일을 하나 생성하고
cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
templates를 하나 씩 생성해 준다
cat > nginx-chart/templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name:
data:
index.html: |
EOF
- 마찬가지로 index.html을 저장할 ConfigMap 파일이다
cat > nginx-chart/templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name:
spec:
replicas:
selector:
matchLabels:
app:
template:
metadata:
labels:
app:
spec:
containers:
- name: nginx
image: :
ports:
- containerPort: 80
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name:
EOF
- nginx 애플리케이션의 deployment를 생성해 준다.
- ConfigMap에 저장한 index.html파일을 마운트해 준다
.Values
로 시작하는 값은 values.yaml 파일에서 가져올 수 있다.
cat > nginx-chart/templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name:
spec:
selector:
app:
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
EOF
- 80으로 서비스하는 애플리케이션을 nodeport 30000포트로 노출시켜 준다
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
values.yaml 파일은 개발용과 운영용으로 나눠서 replicaCount
를 다르게 지정해 준다.
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
Chart.yaml파일에 버전을 명시해 준다.
전체적인 디렉토리 구조는 아래와 같다.
저장소에 방금 생성한 파일들을 push해 준다
이제 ArgoCD에 애플리케이션을 생성해 줄 것이다.
- Application Name을 지정하고
- 프로젝트 명은 Settings에 있는 거다
- AUTO-CREATE NAMESPACE를 선택해서 네임스페이스가 없으면 생성해 줄 수 있다.
설정해 둔 Repo를 지정하고 nginx-chart 경로에 있는 헬름차트 파일을 바라보게 만든다.
Settings에 있던 Cluster URL을 설정해 주고 네임스페이스와 valuse file을 지정해 준다.
create하고 나면 하나의 애플리케이션이 보이고 현재 쿠버네티스 클러스터에 생성된 리소스를 감지해서 diff를 통해 어느 부분이 달라지는지 확인 후에 SYNC로 선언한 파일로 동기화하여 배포해 줄 수 있다
현재는 OutOfSync 상태이다.
SYNC 버튼을 눌러 바로 배포가 가능하다
다시 확인해 보면 Synced 상태로 변경되었다
성공적으로 배포되었다!
VERSION을 1.26.2로 변경하고 vaules-dev.yaml 파일에 replicas를 2로 변경해서 git push 해 보자!
REFRESH 버튼을 누르면 바로 OutOfSync로 변경된 걸 확인할 수 있다.
다시 SYNC눌러서 동기화시키면 1.26.2로 바로 변경되어 배포되었다
ArgoCD에서 DELETE 버튼만 눌러도 곧바로 모든 리소스를 삭제할 수 있다
확인해 보면 정상 삭제되었다
Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 2
이번엔 ArgoCD 애플리케이션 자체를 웹UI가 아닌 yaml로 생성해 볼 수 있다
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.219.100:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
- 현재 클러스터에 dev-nginx 네임스페이스에 dev-nginx라는 이름을 가진 애플리케이션을 생성할 건데 argocd 네임스페이스에 있는 ArgoCD pod로 생성할 거라 해당 네임스페이스도 지정해 준다
- values 파일은 개발용으로 사용하고 nginx-chart 경로를 명시해 준다
- ArgoCD 프로젝트명도 지정하고 repoURL 레포도 지정한다
syncPolicy
에서automated: prune: true
설정을 통해 자동으로 리소스 삭제/추가를 동기화해 줄 수 있다syncOptions
에CreateNamespace=true
옵션을 추가하여 네임스페이스가 없을 시에 생성하게 만들어 준다
kubectl apply로 바로 적용하면 웹 UI에서도 해당 애플리케이션을 확인할 수 있다
처음 생성은 당연히 Synced 상태이다
애플리케이션 접속도 가능하다
kubectl delete applications -n argocd dev-nginx
로 애플리케이션을 삭제해 줄 수 있다
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prd-nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
destination:
namespace: prd-nginx
server: https://kubernetes.default.svc
project: default
source:
helm:
valueFiles:
- values-prd.yaml
path: nginx-chart
repoURL: http://192.168.219.100:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
EOF
- 이번엔 운영용 애플리케이션을 배포할 거라 values 파일만 운영용으로 배포해 주면 된다.
마무리는 kubectl delete applications -n argocd prd-nginx
로 삭제해 준다
Full CI/CD 구성
마지막으로 개발팀에서 소스코드를 변경하면 Jenkins에서 자동으로 CI 돌리고 쿠버네티스 리소스 구성이 들어 있는 레포에 변경된 버전을 업데이트하고 CD 서버가 최신이미지를 쿠버네티스 클러스터에 배포하는 GitOps 구성이다.
참조 그림: 가시다님 노션
ops-deploy와 ops-deploy/dev-app 디렉토리를 생성해서 VERSION 파일을 만들어 준다
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
- docker hub에서 최신이미지를 가지고 올 수 있는 deployment이다
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
- nodeport 30000으로 노출시키는 서비스를 배포한다
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: timeserver
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
path: dev-app
repoURL: http://192.168.219.100:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: default
server: https://kubernetes.default.svc
EOF
- 애플리케이션을 자동 배포하기 위한 ArgoCD 애플리케이션을 생성해 준다
최종 파이프라인 프로세스는 아래와 같다
“개발자가 dev-app에 신규 코드 배포 → dev-app Checkout → 현재 VERSION 파일 읽기 → 현재 버전으로 도커이미지 생성 및 푸시 → ops-deploy Checkout → dev-app/VERSION, timeserver.yaml 2개 파일에 기존 버전을 신규 버전으로 값 변경 → ops-deploy에 git push → ArgoCD가 3분 이내 AutoSync로 신규 버전 업데이트 진행”
pipeline {
agent any
environment {
DOCKER_IMAGE = '6ain/dev-app' // Docker 이미지 이름
GOGSCRD = credentials('gogs-crd')
}
stages {
stage('dev-app Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.219.100:3000/devops/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.219.100:3000/devops/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 "devops@example.com"
git commit -m "version update ${DOCKER_TAG}"
git push http://${GOGSCRD_USR}:${GOGSCRD_PSW}@192.168.219.100:3000/devops/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."
}
}
}
개발자가 git push를 하면 자동으로 파이프라인이 실행된다
ArgoCD가 계속해서 애플리케이션 현 상태를 감지해서 OutOfSync 상태이다가 최대 3분 이내 자동 동기화를 실행하면 아래와 같이 버전 업그레이드된 최신 이미지를 사용하는 애플리케이션을 확인할 수 있다.
마무리는 kubectl delete applications -n argocd timeserver
로 삭제해 준다!