CI 는 Continuous Integration 의 약자로 직역하면 ‘지속적 통합’이다.
프로젝트를 진행할 때 여러 명이 각자 맡은 업무를 나누어 코드를 작성하는데, CI 는 모든 개발자가 작업한 코드를 지속적으로 하나로 모아 통합하는 것을 의미한다.
CI 에 포함되는 작업은 빌드, 테스트, 정적 분석, 피드백(알림), 배포가 있다.
예를 들면, 누군가 main 브랜치에 코드를 변경해서 커밋을 올리면 코드를 빌드해서, 테스트에 모두 통과하고, 정적 분석도 통과해야 자동으로 배포하는 것을 가능하게 하는 것이 CI 다.
CI 을 적용하면 얻을 수 있는 장점은 크게 아래와 같이 3가지로 정리할 수 있다.
대표적인 CI 툴로는 Jenkins, Travis Ci, Github Actions 등이 있다.
Jenkins(이하 젠킨스)는 자바로 만들어진 CI 툴이다.
젠킨스는 파이프라인이라고 부르는 스크립트를 작성해서 각 빌드 단계마다 수행할 작업과 순서를 정의할 수 있다.
만약, 특정 단계에서 실패가 발생하면 이 단계의 출력 결과를 사용하는 다음 단계는 실행되지 않고, 빌드 과정 전체가 실패한다.
EC2 인스턴스의 amzon linux 운영체제에서 설치를 진행했다.
편리를 위해 root 계정으로 설치를 진행했다.
sudo su -
젠킨스를 사용하기 위해 자바를 설치해야 한다.
자세한 설치 방법은 aws 공식 문서를 참고했다. (링크)
java17 버전을 설치한다.
sudo yum install fontconfig java-17-amazon-corretto -y
java -version # 자바 버전 확인
#openjdk version "17.0.10" 2024-01-16 LTS
#OpenJDK Runtime Environment Corretto-17.0.10.8.1 (build 17.0.10+8-LTS)
#OpenJDK 64-Bit Server VM Corretto-17.0.10.8.1 (build 17.0.10+8-LTS, mixed mode, sharing)
JAVA_HOME
환경변수를 등록하기 위해 alternatives 명령어를 이용해서 자바 설치 경로를 확인한다.
alternatives 명령어는 프로그램의 여러 버전을 관리하기 위한 명령어다.
이번 예시에서는 java17 버전만 설치했기 때문에 다른 버전이 나오지 않는다.
alternatives --display java
#java - status is auto.
# link currently points to /usr/lib/jvm/java-17-amazon-corretto.x86_64/bin/java
#/usr/lib/jvm/java-17-amazon-corretto.x86_64/bin/java - priority 17100801
# slave java-17-amazon-corretto: /usr/lib/jvm/java-17-amazon-corretto.x86_64
# slave jre: /usr/lib/jvm/java-17-amazon-corretto.x86_64
# slave jre_openjdk: /usr/lib/jvm/java-17-amazon-corretto.x86_64
# slave keytool: /usr/lib/jvm/java-17-amazon-corretto.x86_64/bin/keytool
# slave rmiregistry: /usr/lib/jvm/java-17-amazon-corretto.x86_64/bin/rmiregistry
# slave java.1: /usr/lib/jvm/java-17-amazon-corretto.x86_64/man/man1/java.1
# slave keytool.1: /usr/lib/jvm/java-17-amazon-corretto.x86_64/man/man1/keytool.1
# slave rmiregistry.1: /usr/lib/jvm/java-17-amazon-corretto.x86_64/man/man1/rmiregistry.1
#Current `best' version is /usr/lib/jvm/java-17-amazon-corretto.x86_64/bin/java.
현재 Current ‘best’ version 에 해당하는 경로를 JAVA_HOME
환경변수로 저장한다.
JAVA_HOME=/usr/lib/jvm/java-17-amazon-corretto.x86_64
echo $JAVA_HOME
#/usr/lib/jvm/java-17-amazon-corretto.x86_64
젠킨스는 EC2 인스턴스에서 데몬(daemon)으로 실행하기 위해 아래와 같은 명령어를 실행해서 설치했다.
도커 컨테이너나 쿠버네티스에서도 실행 가능하기 때문에 환경과 사용 목적에 따라 원하는 형태로 설치하면 된다.
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
sudo yum upgrade
sudo yum install jenkins -y
sudo systemctl daemon-reload
sudo systemctl enable jenkins && sudo systemctl start jenkins
설치가 정상적으로 이루어졌는지 확인하기 위해 아래의 명령어를 실행한다.
sudo systemctl status jenkins
정상적으로 설치되었다면 Active: active (running)
으로 표시될 것이다.
● jenkins.service - Jenkins Continuous Integration Server
Loaded: loaded (/usr/lib/systemd/system/jenkins.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2024-04-16 15:12:37 KST; 1min 21s ago
Main PID: 13158 (java)
Tasks: 47
Memory: 1.1G
CGroup: /system.slice/jenkins.service
└─13158 /usr/bin/java -Djava.awt.headless=true -jar /usr/share/jav...
Apr 16 15:12:12 myeks-bastion jenkins[13158]: 9c576da86ed14141afc81646a83fa0c6
Apr 16 15:12:12 myeks-bastion jenkins[13158]: This may also be found at: /va...d
Apr 16 15:12:12 myeks-bastion jenkins[13158]: ******************************...*
Apr 16 15:12:12 myeks-bastion jenkins[13158]: ******************************...*
Apr 16 15:12:12 myeks-bastion jenkins[13158]: ******************************...*
Apr 16 15:12:37 myeks-bastion jenkins[13158]: 2024-04-16 06:12:37.685+0000 [...n
Apr 16 15:12:37 myeks-bastion jenkins[13158]: 2024-04-16 06:12:37.708+0000 [...g
Apr 16 15:12:37 myeks-bastion systemd[1]: Started Jenkins Continuous Integra....
Apr 16 15:12:38 myeks-bastion jenkins[13158]: 2024-04-16 06:12:38.520+0000 [...r
Apr 16 15:12:38 myeks-bastion jenkins[13158]: 2024-04-16 06:12:38.522+0000 [...1
Hint: Some lines were ellipsized, use -l to show in full.
젠킨스는 웹에서 이용할 수 있는 UI 를 제공하는데, 로컬에서는 8080번 포트를 이용해서 접속할 수 있다.
EC2 인스턴스의 퍼블릭 IP 를 확인해서 8080번 포트로 접속하면 젠킨스 UI 로 접속할 수 있다.
curl -s ipinfo.io/ip | awk '{ print "Jenkins = http://"$1":8080" }'
#Jenkins = http://a.b.c.d:8080
접속하면 아래의 이미지와 같이 관리자 초기 비밀번호를 입력하라는 안내 문구가 나온다.
관리자 계정의 초기 암호는 아래의 명령어로 확인할 수 있다.
cat /var/lib/jenkins/secrets/initialAdminPassword
#9c57...
플러그인 설치는 기본으로 추천해주는 것들을 설치한다.
새로운 관리자 계정을 생성한다.
계정명
은 로그인할 때 사용하는 것이고, 이름
은 닉네임과 같다.
젠킨스를 이용할 URL 을 확인하고 Save and Finish
버튼을 클릭한다.
젠킨스의 각 단계(job)에서 사용할 자바의 버전을 설정하기 위해 [Jenkins 관리] - [Tools] 로 이동한다.
[JDK installations] 항목에서 [ADD JDK] 버튼을 클릭한다.
그리고 JAVA_HOME
항목에 위에서 입력했던 값을 그대로 입력해준다.
Name
항목은 버전을 구분하기 위한 환경변수 이름이다.
입력 후 [Save] 버튼 클릭
좌측 메뉴바에서 [새로운 Item] 버튼을 클릭하면 아래의 이미지와 같은 화면이 표시된다.
item name 은 자유롭게 입력하고, [Freestyle project] 를 선택한 다음 하단의 [OK] 버튼을 클릭한다.
[Add build step] 클릭 후 [Execute shell] 선택
간단한 예시를 위해 echo "Hello world from Jenkins"
를 입력하고 [저장] 버튼 클릭
[지금 빌드] 를 클릭하면 [Build History] 항목 밑에 #1
이 새롭게 생성된다.
날짜를 클릭해서 [Console Output] 을 클릭하면 실행 결과를 확인할 수 있다.
아래의 이미지와 같이 echo "Hello world from Jenkins"
명령어가 실행된 것을 확인할 수 있다.
만약 명령어 실행 종료값이 0이 아니라면 아래와 같이 빌드는 실패했다고 표시된다.
아래의 이미지에서는 false 명령어를 실행하게 해서 실행 종료값을 1을 반환하도록 한 것이다.
참고로 + false
에서 +
의 우측이 실행할 명령어라는 것을 의미한다.
shell 스크립트를 아래와 같이 hello.txt
파일을 저장하도록 수정했다.
echo "hello" > hello.txt
[지금 빌드] 실행 후 해당 파일을 찾으려면 /var/lib/jenkins/workspace
에 생성된 워크스페이스 폴더에서 찾을 수 있다.
ls /var/lib/jenkins/workspace/
#demo-item
tree /var/lib/jenkins/workspace/demo-item
#/var/lib/jenkins/workspace/demo-item
#└── hello.txt
#0 directories, 1 file
cat /var/lib/jenkins/workspace/demo-item/hello.txt
#hello
참고로 각각의 job 도 /var/lib/jenkins/jobs
하위 디렉토리에 저장된다.
tree /var/lib/jenkins/jobs/
#/var/lib/jenkins/jobs/
#└── demo-item
# ├── builds
# │ ├── 1
# │ │ ├── build.xml
# │ │ ├── changelog.xml
# │ │ └── log
# │ ├── 2
# │ │ ├── build.xml
# │ │ ├── changelog.xml
# │ │ └── log
# │ ├── 3
# │ │ ├── build.xml
# │ │ ├── changelog.xml
# │ │ └── log
# │ ├── legacyIds
# │ └── permalinks
# ├── config.xml
# └── nextBuildNumber
#
#5 directories, 13 files
3번 job 의 log 파일을 확인해보면 젠킨스 UI 에서 보여주는 로그와 동일한 것을 알 수 있다.
cat /var/lib/jenkins/jobs/demo-item/builds/3/log
출력 결과는 아래와 같다.
Started by user ha:////4O...==Jay
Running as SYSTEM
Building in workspace /var/lib/jenkins/workspace/demo-item
[demo-item] $ /bin/sh -xe /tmp/jenkins11890863677381737251.sh
+ echo 'Hello world from Jenkins'
Hello world from Jenkins
+ echo hello
Finished: SUCCESS
[Build Steps] 에서 아래의 shell 스크립트를 실행하면 도커에 접근 권한이 없다고 표시된다.
docker ps
docker 명령어를 실행하기 위해서는 계정이 docker 그룹에 속해있어야 한다.
이는 docker 소켓 파일을 이용하기 위해서이다.
ls -l /var/run/docker.sock
#srw-rw---- 1 root docker 0 Apr 16 14:25 /var/run/docker.sock
기본으로 root 사용자와 docker 그룹에 속한 계정만 사용할 수 있기 때문이다.
jenkins 계정이 기본적으로 docker 그룹에 속하지 않는다.
id jenkins
#uid=995(jenkins) gid=991(jenkins) groups=991(jenkins)
따라서 docker 명령어를 실행하기 위해 jenkins 계정을 docker 그룹에 추가해준다.
usermod -aG docker jenkins
젠킨스에서 계정 정보를 다시 읽어야 하기 때문에 젠킨스를 다시 실행해준다.
systemctl restart jenkins
Github repository 에 변경 사항이 발생하면 젠킨스 작업을 수행하도록 하는 것도 가능하다.
이번 예시에서는 Github repository 의 특정 경로에 올라가있는 Dockerfile 의 변경사항이 생기면, 이를 젠킨스가 다운로드해서 이미지를 빌드하고 컨테이너로 실행하도록 했다.
Freestyle 로 새로운 아이템을 생성한다.
[빌드 매개변수]
이 빌드는 매개변수가 있습니다.
선택[소스 코드 관리]
[빌드 유발]
[Build Steps]
mkdir -p /var/lib/jenkins/myweb
cd /var/lib/jenkins/myweb
rm -rf Dockerfile
wget https://raw.githubusercontent.com/$NICK/aews-cicd/main/1/Dockerfile
docker build -t myweb:$VERSION .
docker run -d -p 80:80 --rm --name myweb myweb:$VERSION
사용한 도커 파일의 내용은 아래와 같이 아파치 웹 서버를 실행하도록 했다.
FROM ubuntu:20.04
ENV TZ=Asia/Seoul VERSION=1.0.0 NICK=Jay
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y apache2 figlet && \
echo "$NICK Web Server $VERSION<br>" > /var/www/html/index.html && \
echo "<pre>" >> /var/www/html/index.html && \
figlet AEWS Study >> /var/www/html/index.html && \
echo "</pre>" >> /var/www/html/index.html
EXPOSE 80
CMD ["usr/sbin/apache2ctl", "-DFOREGROUND"]
Dockerfile 에서 ENV 의 VERSION 값을 변경하고 커밋을 올리면 1분 후 젠킨스가 이를 감지해서 컨테이너를 실행한다.
버전을 1.9.0 로 변경했는데, 정상적으로 반영된 것을 확인할 수 있다.
파이프라인은 젠킨스에서 CD 를 구현하기 위해 사용하는 스크립트이다.
Dockerfile 처럼 Jenkinsfile 에 코드를 작성해서 사용할 수 있다.
파이프라인 구문은 크게 선언형과 스크립트형으로 나뉜다.
참고: [CI/CD] Jenkins Pipeline 정리 [velog]
위의 이미지에서 선언형의 빨간색 박스는 필수로 작성되어야 하는 부분이다.
선언형을 권장하고 있으며, 2가지의 차이는 간단하게 정리하면 아래와 같다.
pipeline {
agent any
stages {
stage('Build') {
steps {
//
}
}
stage('Test') {
steps {
//
}
}
stage('Deploy') {
steps {
//
}
}
}
}
node {
stage('Build') {
//
}
stage('Test') {
//
}
stage('Deploy') {
//
}
}
선언형에서는 빌드 후 상태에 따라 다른 작업을 수행할 수 있는데, post
에 이를 정의할 수 있다.
예시는 아래와 같다.
pipeline {
agent any
stages {
stage('Compile') {
steps {
echo "Compiled successfully!";
}
}
stage('JUnit') {
steps {
echo "JUnit passed successfully!";
}
}
stage('Code Analysis') {
steps {
echo "Code Analysis completed successfully!";
}
}
stage('Deploy') {
steps {
echo "Deployed successfully!";
}
}
}
post {
always {
echo "This will always run"
}
success {
echo "This will run when the run finished successfully"
}
failure {
echo "This will run if failed"
}
unstable {
echo "This will run when the run was marked as unstable"
}
changed {
echo "This will run when the state of the pipeline has changed"
}
}
}
Docker Hub 의 개인 저장소에 업로드한 이미지를 받아와서 컨테이너로 실행하는 예시를 정리했다.
파이프라인에서 도커를 사용하려면 플러그인을 설치해야 한다.
젠킨스 대시보드에서 [Jenkins 관리] - [Plugins] - [Available plugins] 로 이동해서 docker pipeline
을 검색하고 설치한다.
가끔 Timeout 오류가 발생해서 설치가 완료되지 않는 경우가 있는데, 다시 설치를 하다보면 된다.
젠킨스에서 개인 Docker Hub 계정에 접근할 때 비밀번호가 아닌 Access Token 으로 접근하도록 할 것이다.
Docker Hub 에서 토큰을 발급하는 방법은 아래와 같다.
젠킨스 대시보드로 돌아와서 [Jenkins 관리] - [Credentials] - [System] - [Global credentials] 이동 후 [Add credentials] 버튼 클릭
입력 후 [Create] 버튼 클릭
Docker Hub 개인 저장소에 미리 업로드 해둔 myweb:v1.0.0
이라는 이미지를 가져와서 컨테이너로 실행할 것이다.
pipeline {
environment {
container_name = "myweb" // container 이름
version = "v1.0.0"
repository = "joonhyeokhan/myweb" //docker hub id와 repository 이름
DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials') // credential 등록할 때 입력했던 ID
dockerImage = ''
}
agent any
stages {
stage('Docker Hub Login'){
steps{
sh 'echo $DOCKERHUB_CREDENTIALS_PSW | docker login -u $DOCKERHUB_CREDENTIALS_USR --password-stdin' // docker hub 로그인
}
}
stage('Deploy container') {
steps {
script {
sh 'docker run -d -p 80:80 --rm --name $container_name $repository:$version' // docker container 실행
}
}
}
}
post {
success {
echo "This will run when the run finished successfully"
}
failure {
echo "This will run if failed"
}
changed {
echo "This will run when the state of the pipeline has changed"
}
}
}
environment 블록에 선언한 DOCKERHUB_CREDENTIALS
는 credentials()로 가져올 수 있고, 실제로는 아래와 같이 3가지 환경변수가 선언된다.
DOCKERHUB_CREDENTIALS_CREDS
: username:password
형태의 문자열을 담고 있다.DOCKERHUB_CREDENTIALS_USR
: username 만 담고 있다.DOCKERHUB_CREDENTIALS_PSW
: password 만 담고 있다.자세한 내용은 젠킨스 공식 문서에서 확인할 수 있다. (링크)
이렇게 작성한 파일은 Github Repository 에 업로드했다.
젠킨스 대시보드에서 [새로운 item] 클릭 후 [Pipeline] 을 선택해서 생성한다.
파이프라인은 위에서 Github Repository 에 업로드한 Jenkinsfile 을 가져와서 실행할 것이다.
[Definition]
[SCM]
[Repositories]
[Branches to build]
[Additional Behaviours]
[Script Path]
[지금 빌드]를 클릭해서 Jenkinsfile 을 실행하면 아래의 이미지와 같이 빌드에 성공한 것을 확인할 수 있다.
터미널에서 컨테이너를 확인해봐도 정상적으로 실행 중인 것을 확인할 수 있다.
docker ps
#CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
#160220fa0e5c joonhyeokhan/myweb:v1.0.0 "usr/sbin/apache2ctl…" 27 minutes ago Up 27 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp myweb
이처럼 젠킨스를 이용해서 CD 를 구축할 수도 있다.
지속적인 배포(Continuous Delivery)는 개발된 소프트웨어를 고객이 사용할 수 있는 환경에 끊임없이, 자동화된 방식으로 배포하는 것을 의미한다.
이 방식을 통해 소프트웨어의 품질을 높이고, 새로운 기능이나 버그 수정 등의 업데이트를 빠르게 사용자에게 제공할 수 있다.
이는 고객 만족도를 높이고, 시장 변화에 빠르게 대응하는 데에도 도움이 된다.
이를 위해 개발부터 테스팅, 배포까지의 모든 과정이 자동화되고, 이 과정들이 잘 이루어지고 있는지를 지속적으로 모니터링하게 됩니다.
쿠버네티스에서 CD 를 구축하기 위해 사용하는 대표적인 툴은 Argo CD 가 있다.
Argo 는 쿠버네티스에서 다양한 파이프라인 작업과 CD 를 가능하게 해주는 여러 프로젝트로 구성되어 있다.
Argo 프로젝트에 포함된 라이브러리들은 아래와 같다.
이번 글에서는 Argo CD 와 Argo Rollouts 을 이용하는 예시를 다루었다.
Argo CD 는 GitOps 를 구현할 수 있도록 도와주는 CD 툴이다.
Argo CD 를 이용하면 Github repository 에서 변경이 발생했을 때 쿠버네티스 클러스터로 자동으로 배포하도록 설정할 수 있다.
또한, 배포 결과를 슬랙과 같은 메신저로 발송하는 알림 기능도 지원한다.
helm 차트를 이용해서 설치했다.
Argo CD 는 웹 대시보드를 제공하는데, 이를 argocd.도메인
으로 접속하기 위해 아래와 같은 yaml 파일을 생성했다.
cat <<EOT > argocd-values.yaml
global:
domain: argocd.$MyDomain
configs:
params:
server.insecure: true
controller:
metrics:
enabled: true
serviceMonitor:
enabled: true
server:
ingress:
enabled: true
controller: aws
ingressClassName: alb
hostname: "argocd.$MyDomain"
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/ssl-redirect: '443'
aws:
serviceType: ClusterIP
backendProtocolVersion: GRPC
metrics:
enabled: true
serviceMonitor:
enabled: true
repoServer:
metrics:
enabled: true
serviceMonitor:
enabled: true
applicationSet:
metrics:
enabled: true
serviceMonitor:
enabled: true
notifications:
metrics:
enabled: true
serviceMonitor:
enabled: true
EOT
Argo CD 를 위한 네임스페이스를 생성하고 helm 으로 설치한다.
kubectl create ns argocd
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 6.7.11 -f argocd-values.yaml --namespace argocd
젠킨스와 마찬가지로 admin 계정의 최초 비밀번호가 필요한데, 아래의 명령어로 확인할 수 있다.
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
웹 대시보드로 애플리케이션을 배포할 수도 있지만 cli 로 하는 방법도 소개하고자 한다.
먼저 아래의 명령어로 argocd cli 를 설치한다.
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm -f argocd-linux-amd64
argocd cli 를 이용하기 위해서는 로그인을 해야 한다.
argocd 서버의 주소를 입력하는데, 이번 글에서는 도메인을 이용해서 argocd 서버를 연결했기 때문에 도메인을 입력했다.
argocd login <argocd-서버>
#argocd login argocd.joonhan.link
Username: admin
Password: # 최초 비밀번호 복사해서 붙여넣기
'admin:login' logged in successfully
Context 'argocd.joonhan.link' updated
참고로 argocd 서버는 argocd 네임스페이스에 배포된 argocd-server
를 의미한다.
kubectl get svc -n argocd
#NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
#argocd-applicationset-controller ClusterIP 10.109.19.10 <none> 7000/TCP,8080/TCP 5d6h
#argocd-dex-server ClusterIP 10.104.125.244 <none> 5556/TCP,5557/TCP,5558/TCP 5d6h
#argocd-metrics ClusterIP 10.96.237.220 <none> 8082/TCP 5d6h
#argocd-notifications-controller-metrics ClusterIP 10.100.250.19 <none> 9001/TCP 5d6h
#argocd-redis ClusterIP 10.97.138.210 <none> 6379/TCP 5d6h
#argocd-repo-server ClusterIP 10.99.210.4 <none> 8081/TCP,8084/TCP 5d6h
#argocd-server ClusterIP 10.110.143.245 <none> 80/TCP,443/TCP 5d6h
#argocd-server-metrics ClusterIP 10.101.164.250 <none> 8083/TCP 5d6h
argocd app create
명령어를 이용해서 애플리케이션을 생성할 수 있다.
argocd app create [애플리케이션 이름] \
--repo [github repository 주소] \
--path [repository 내부에 yaml 파일이 위치한 경로] \
--dest-namespace [배포할 kubernetes namespace] \
--dest-server [배포할 대상 kubernetes API 서버 주소]
배포에 사용할 yaml 파일은 아래와 같다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myweb
labels:
gitadd : webedit
spec:
replicas: 4
selector:
matchLabels:
app: mywebs
template:
metadata:
name: myweb
labels:
app: mywebs
spec:
containers:
- name: myweb
image: nginx
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Service
metadata:
name: myweb
spec:
ports:
- name: webport
port: 8080
targetPort: 80
selector:
app: mywebs
type: ClusterIP
---
Public Github Repository 에 업로드 해두었으며, 이 파일을 바탕으로 배포를 진행할 것이다.
참고로 디렉토리 구조는 아래와 같다.
3
├── Dockerfile
└── deploy
└── deployment-svc.yaml
여기서 3/deploy/deployment-svc.yaml
파일을 이용해서 배포를 할 것이다.
argocd app create myweb \
--repo https://github.com/Han-Joon-Hyeok/aews-cicd \
--path 3/deploy \
--dest-namespace myweb \
--dest-server https://kubernetes.default.svc
--sync-option CreateNamespace=true # 네임스페이스가 없으면 자동 생성
#application 'myweb' created
아래의 명령어를 실행하면 STATUS 가 OutOfSync 인 것을 확인할 수 있다.
argocd app list
#NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
#argocd/myweb https://kubernetes.default.svc myweb default OutOfSync Missing <none> <none> https://github.com/Han-Joon-Hyeok/aews-cicd 3/deploy
OutOfSync 는 현재 클러스터에서 배포된 것과 Github Repository 의 내용이 다르다는 것이다.
처음에 애플리케이션을 등록한 것만으로 바로 배포할 수는 없고, sync 를 맞춰야 비로소 배포가 된다.
아래의 명령어를 이용해서 애플리케이션을 배포할 수 있다.
argocd app sync [애플리케이션 이름]
이번 예시에서는 아래와 같이 실행했다.
argocd app sync myweb
실행 결과가 아래와 같이 표시되면서 배포가 완료된 것을 확인할 수 있다.
TIMESTAMP GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
2024-04-17T21:09:32+09:00 Service myweb myweb OutOfSync Missing
2024-04-17T21:09:32+09:00 apps Deployment myweb myweb OutOfSync Missing
2024-04-17T21:09:35+09:00 Namespace myweb Running Synced namespace/myweb created
2024-04-17T21:09:35+09:00 Service myweb myweb Synced Healthy
2024-04-17T21:09:35+09:00 Service myweb myweb Synced Healthy service/myweb created
2024-04-17T21:09:35+09:00 apps Deployment myweb myweb OutOfSync Missing deployment.apps/myweb created
2024-04-17T21:09:35+09:00 apps Deployment myweb myweb Synced Progressing deployment.apps/myweb created
Name: argocd/myweb
Project: default
Server: https://kubernetes.default.svc
Namespace: myweb
URL: https://argocd.joonhan.link/applications/myweb
Repo: https://github.com/Han-Joon-Hyeok/aews-cicd
Target:
Path: 3/deploy
SyncWindow: Sync Allowed
Sync Policy: <none>
Sync Status: Synced to (64c73ab)
Health Status: Progressing
Operation: Sync
Sync Revision: 64c73abdc0777c42514b24ca7ccc1d5cf2d3cfd0
Phase: Succeeded
Start: 2024-04-17 21:09:32 +0900 KST
Finished: 2024-04-17 21:09:35 +0900 KST
Duration: 3s
Message: successfully synced (all tasks run)
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
Namespace myweb Running Synced namespace/myweb created
Service myweb myweb Synced Healthy service/myweb created
apps Deployment myweb myweb Synced Progressing deployment.apps/myweb created
Github Repository 에서 정보를 가져오는 간격은 기본으로 3분이다.
만약 바로 가져오고 싶다면 아래의 명령어를 사용하면 된다.
argocd app get myweb --refresh
그러면 아래와 같이 Sync Status 가 OutOfSync 인 것을 확인할 수 있다.
Name: argocd/myweb
Project: default
Server: https://kubernetes.default.svc
Namespace: myweb
URL: https://argocd.joonhan.link/applications/myweb
Repo: https://github.com/Han-Joon-Hyeok/aews-cicd
Target:
Path: 3/deploy
SyncWindow: Sync Allowed
Sync Policy: <none>
Sync Status: OutOfSync from (359390e)
Health Status: Healthy
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
Service myweb myweb Synced Healthy service/myweb unchanged
apps Deployment myweb myweb OutOfSync Healthy deployment.apps/myweb unchanged
변경 사항을 적용해서 배포하려면 아래의 명령어를 사용하면 된다.
argocd app sync myweb
애플리케이션 삭제는 아래의 명령어를 실행하면 된다.
argocd app delete myweb
Public Repository 는 간단하게 연동할 수 있지만, Private Repository 를 연동하려면 SSH 키를 발급 받아야 한다.
아래의 명령어를 실행해서 ssh 키를 생성한다.
ssh-keygen
#Generating public/private rsa key pair.
#Enter file in which to save the key (/root/.ssh/id_rsa):
#/root/.ssh/id_rsa already exists.
#Overwrite (y/n)?
Github Repository 의 [Settings] - [Security] - [Deploy keys] 메뉴로 진입한다.
그 다음 Add deploy key
버튼을 클릭하고, 생성한 ssh 공개키를 복사해서 Key
항목에 입력한다.
cat ~/.ssh/id_rsa.pub
#ssh-rsa AAAA... root@myeks-bastion
Argo CD 와 Repository 를 연결하기 위해서는 repository 의 ssh git 주소가 필요하다.
ssh git 주소는 아래의 이미지에 표시된 버튼을 클릭하면 확인할 수 있다.
그 다음 아래의 명령어를 실행했다.
argocd repo add \
git@github.com:42Cluster-Seoul/ArgoCD-webhook-demo.git \ # repository 의 ssh git 주소
--ssh-private-key-path ~/.ssh/id_rsa # ssh 개인키 위치
그럼 아래와 같이 성공적으로 추가되었다는 메세지가 표시된다.
Repository 'git@github.com:42Cluster-Seoul/ArgoCD-webhook-demo.git' added
아래의 명령어를 실행하면 현재 연동된 repository 목록을 확인할 수 있다.
argocd repo list
#TYPE NAME REPO INSECURE OCI LFS CREDS STATUS MESSAGE PROJECT
#git git@github.com:42Cluster-Seoul/ArgoCD-webhook-demo.git false false false false Successful
앞서 했던 것과 마찬가지로 생성과 배포는 모두 동일한 흐름을 따른다.
다른 점이 있다면 --repo
옵션에 ssh git 주소를 입력했다는 점이다.
아래의 명령어로 애플리케이션을 생성했다.
argocd app create my-nginx \
--repo git@github.com:42Cluster-Seoul/ArgoCD-webhook-demo.git \
--path deploy \
--dest-namespace my-nginx \
--dest-server https://kubernetes.default.svc \
--sync-option CreateNamespace=true
#application 'my-nginx' created
배포에 사용한 yaml 파일은 아래와 같다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-server
labels:
app: server
spec:
replicas: 1
selector:
matchLabels:
app: server
template:
metadata:
name: nginx-server
labels:
app: server
spec:
containers:
- name: server
image: nginx:1.23.3
ports:
- containerPort: 80
배포는 아래의 명령어를 실행했다.
argocd app sync my-nginx
Argo CD 의 한 가지 아쉬운 점은 배포 전략을 적용할 수 없다는 점이다.
그래서 Argo Rollouts 은 blue/green, canary, rolling 배포 전략을 모두 할 수 있도록 도와준다.
마찬가지로 Argo Rollouts 도 웹 대시보드를 제공하는데, helm 으로 배포하기 위해 아래의 yaml 파일을 사용했다.
cat <<EOT > argorollouts-values.yaml
dashboard:
enabled: true
ingress:
enabled: true
ingressClassName: alb
hosts:
- argorollouts.$MyDomain
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/ssl-redirect: '443'
EOT
argocd-rollouts 라는 네임스페이스를 생성하고 나서 helm 을 이용해서 설치한다.
kubectl create ns argo-rollouts
helm install argo-rollouts argo/argo-rollouts --version 2.35.1 -f argorollouts-values.yaml --namespace argo-rollouts
Argo Rollouts 을 설치하기 위한 사전 조건은 아래와 같다.
kubectl
설치kubeconfig
파일이 ~/.kube/config
디렉토리에 존재argocd rollouts 은 kubectl 을 이용해서 사용한다는 특징이 있다.
argo rollouts 를 cli 로 이용하기 위해 아래의 명령어로 설치한다.
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x ./kubectl-argo-rollouts-linux-amd64
sudo mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
설치가 되었는지 확인하기 위해 아래의 명령어를 실행한다.
kubectl argo rollouts version
#kubectl-argo-rollouts: v1.6.6+737ca89
# BuildDate: 2024-02-13T15:39:31Z
# GitCommit: 737ca89b42e4791e96e05b438c2b8540737a2a1a
# GitTreeState: clean
# GoVersion: go1.20.14
# Compiler: gc
# Platform: linux/amd64
blue 이미지를 먼저 배포하고 나서 green 이미지로 변경하는 예시를 보자.
ALB 를 이용해서 rollouts-active.domain
에서는 현재 서비스 중인 화면(blue)을 보여주고, rollouts-preview.domain
에서는 새롭게 배포할 화면(green)을 보여줄 것이다.
EKS 에서 ALB 를 이용해서 도메인을 연결하는 작업이 가장 어려웠다.
Argo Rollouts 공식 문서와 Argo Rollouts 공식 Github Repository 를 참고해도 원하는 대로 작동하지 않아서 삽질을 많이 했다.
아래의 명령어를 실행해서 ingress yaml 파일을 생성했다.
cat << EOT > blue-green-ingress.yaml
apiVersion: v1
kind: Namespace
metadata:
name: blue-green
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: blue-green
name: rollouts-active-ingress
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
ingressClassName: alb
rules:
- host: "rollouts-active.$MyDomain"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rollout-bluegreen-active
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: blue-green
name: rollouts-preview-ingress
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
ingressClassName: alb
rules:
- host: "rollouts-preview.$MyDomain"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rollout-bluegreen-preview
port:
number: 80
EOT
그 다음 아래의 명령어를 실행해서 ingress 를 생성했다.
kubectl apply -f blue-green-ingress.yaml
정상적으로 생성되었는지 확인하기 위해 아래의 명령어를 실행한다.
kubectl get ingress -n blue-green
#NAME CLASS HOSTS ADDRESS PORTS AGE
#rollouts-active-ingress alb rollouts-active.joonhan.link k8s-bluegree-rollouts-3c4fac8b9a-1336180298.ap-northeast-2.elb.amazonaws.com 80 19m
#rollouts-preview-ingress alb rollouts-preview.joonhan.link k8s-bluegree-rollouts-7992041658-1589460388.ap-northeast-2.elb.amazonaws.com 80 19m
ALB 를 생성하는데 약 3~5분 정도 소요된다.
Rollouts 을 위한 yaml 파일은 아래와 같이 작성했다.
kind 에 입력한 Rollout 은 Deployment 와 동일한 역할을 한다.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-bluegreen
spec:
replicas: 2
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollout-bluegreen
template:
metadata:
labels:
app: rollout-bluegreen
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
#image: argoproj/rollouts-demo:green
imagePullPolicy: Always
ports:
- containerPort: 8080
strategy:
blueGreen:
#activeService는 현재 운영중인 Blue 서비스
activeService: rollout-bluegreen-active
#previewService는 새롭게 배포될 Green 서비스
previewService: rollout-bluegreen-preview
#autoPromotioEnabled 옵션은 Blue/Green 배포를 자동으로 진행할 것인지 여부. false 옵션을 사용해 수동으로 지정
autoPromotionEnabled: false
---
kind: Service
apiVersion: v1
metadata:
name: rollout-bluegreen-active
spec:
type: NodePort
selector:
app: rollout-bluegreen
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
kind: Service
apiVersion: v1
metadata:
name: rollout-bluegreen-preview
spec:
type: NodePort
selector:
app: rollout-bluegreen
ports:
- protocol: TCP
port: 80
targetPort: 8080
strategy 속성에서는 어떤 배포 전략을 사용할 것인지 설정할 수 있다.
Blue/Green 전략에서는 2개의 서비스 preview 와 active 로 나누어서 새로운 배포가 발생하면 어떻게 바뀌는 지 살펴볼 것이다.
이번 예제에서는 처음에 blue 가 보이는 페이지를 표시해주고, 새로운 버전을 배포할 때는 green 이 보이는 페이지를 표시할 것이다.
autoPromotionEnabled 옵션은 관리자의 승인 없이 자동으로 배포할 지, 수동으로 승인할 지 결정하는 옵션이다.
여기서는 Blue/Green 배포가 이루어지는 과정을 확인하기 위해 false 로 지정했다.
strategy:
blueGreen:
#activeService는 현재 운영중인 Blue 서비스
activeService: rollout-bluegreen-active
#previewService는 새롭게 배포될 Green 서비스
previewService: rollout-bluegreen-preview
#autoPromotioEnabled 옵션은 Blue/Green 배포를 자동으로 진행할 것인지 여부. false 옵션을 사용해 수동으로 지정
autoPromotionEnabled: false
애플리케이션을 등록하기 위해 아래의 명령어를 사용했다.
argocd app create rollout-demo \
--repo https://github.com/Han-Joon-Hyeok/aews-cicd \
--path 3/blue-green \
--dest-namespace blue-green \
--dest-server https://kubernetes.default.svc \
--sync-option CreateNamespace=true
그 다음 애플리케이션을 배포한다.
argocd app sync rollout-demo
배포가 되면 ArgoCD 대시보드에서는 아래와 같이 파드가 2개 실행 중인 것을 확인할 수 있다.
rollouts-active.domain
과 rollouts-preview.domain
에 각각 접속해보면 아래의 화면과 같이 모두 파란색 화면을 보여주는 것을 확인할 수 있다.
모두 같은 파란색 화면이 나오는 이유는 처음 배포할 때는 active 와 preview 가 동일한 서비스를 가리키고 있기 때문이다.
blue 이미지를 green 으로 변경하면서 완전히 배포를 하기 전에 active 는 여전히 파란색 화면을 보여주고, preview 는 초록색 화면을 보여주는 것을 확인해보자.
Github Repository 에서 컨테이너 이미지는 아래와 같이 변경했다.
...
spec:
containers:
- name: rollouts-demo
#image: argoproj/rollouts-demo:blue
image: argoproj/rollouts-demo:green # 새롭게 적용할 이미지
imagePullPolicy: Always
ports:
- containerPort: 8080
...
변경 사항을 적용하기 위해 argocd 에서 sync 를 맞춰주었다.
argocd app sync rollout-demo
Argo CD 대시보드에서 확인해보면 파드가 총 4개 실행되고 있는 것을 확인할 수 있다.
기존에 실행되던 파드는 active 이고, revision 1 으로 설정되어있다.
새롭게 생성된 파드는 preview 에 해당하며, revision 2 로 설정되어있다.
아직 완전히 배포를 완료한 것이 아니기 때문에 active 서비스 파드들이 종료되지 않고 있는 것이다.
아래의 이미지처럼 active 는 여전히 파란색 화면을 보여주고 있고, preview 는 초록색 화면을 보여주고 있다.
완전히 green 으로 트래픽을 변경하기 위해 아래의 명령어를 실행해서 promote 한다.
kubectl argo rollouts promote rollout-bluegreen -n blue-green
위의 명령어에서 rollout-bluegreen
은 Rollout 을 정의한 yaml 파일에서 metadata.name 에 해당한다.
Argo CD 대시보드에서 확인해보면 revision 2 에 해당하는 레플리카셋만 남아있는 것을 확인할 수 있다.
도메인을 들어가서 확인해보면 active 와 preview 모두 초록색으로 변해있는 것을 확인할 수 있다.
실습에 사용한 자원을 삭제하기 위해 Argo 애플리케이션과 Ingress(ALB)를 삭제한다.
argocd app delete rollout-demo
kubectl delete -f blue-green-ingress.yaml
blue green 에서 사용했던 것과 동일하지만, 도메인 이름만 rollouts-root.domain
로 변경하기 위해서 새로운 yaml 파일을 생성했다.
cat << EOT > canary-ingress.yaml
apiVersion: v1
kind: Namespace
metadata:
name: canary
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: canary
name: rollouts-root-ingress
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocol: HTTP
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
ingressClassName: alb
rules:
- host: "rollouts-root.$MyDomain"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rollouts-demo-root
port:
name: use-annotation
EOT
그 다음 아래의 명령어를 실행해서 ingress 를 생성했다.
kubectl apply -f canary-ingress.yaml
정상적으로 생성되었는지 확인하기 위해 아래의 명령어를 실행한다.
kubectl get ingress -n canary
#NAME CLASS HOSTS ADDRESS PORTS AGE
#rollouts-root-ingress alb rollouts-root.joonhan.link k8s-canary-rollouts-b68ec588bb-1256422484.ap-northeast-2.elb.amazonaws.com 80 8s
canary 배포 전략을 적용하기 위한 Rollout yaml 파일을 아래와 같이 작성했다.
argo-rollouts 공식 Github Repository 를 참고해서 작성했다.
apiVersion: v1
kind: Namespace
metadata:
name: canary
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
spec:
replicas: 5
strategy:
canary:
canaryService: rollouts-demo-canary
stableService: rollouts-demo-stable
trafficRouting:
alb:
ingress: rollouts-root-ingress
servicePort: 80
rootService: rollouts-demo-root
steps:
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: {duration: 5s}
- setWeight: 60
- pause: {duration: 5s}
- setWeight: 80
- pause: {duration: 5s}
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollouts-demo
template:
metadata:
labels:
app: rollouts-demo
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 5m
---
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-root
spec:
type: NodePort
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
---
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-canary
spec:
type: NodePort
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
---
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-stable
spec:
type: NodePort
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
canary.canaryService 는 새롭게 배포하는 서비스를 의미하고, canary.stableService 는 기존에 실행하던 서비스를 의미한다.
canary:
canaryService: rollouts-demo-canary
stableService: rollouts-demo-stable
canary.trafficRouting 은 alb 를 이용해서 새롭게 배포하는 canary 로 트래픽을 동적으로 옮겨주기 위한 설정이다.
alb.rootService 는 blue green 전략에서 활용했던 active 와 동일한 기능을 한다고 보면 된다.
rootService 는 stable 상태에 있는 트래픽을 canary 로 전달하는 역할을 한다.
새로운 배포가 끝나면 rootService 는 stableService 가 된다.
이렇게 하는 이유는 배포 도중 오류가 발생했을 때 롤백을 원활하게 해주기 위한 것 같은데, 정확한 이유는 모르겠다.
trafficRouting:
alb:
ingress: rollouts-root-ingress
servicePort: 80
rootService: rollouts-demo-root
setWeight
옵션과 pause
옵션을 통해 단계 별로 canary 로 보내야 하는 트래픽의 비율과 rollout 을 잠시 멈추는 간격을 설정할 수 있다.
steps:
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: {duration: 5s}
- setWeight: 60
- pause: {duration: 5s}
- setWeight: 80
- pause: {duration: 5s}
setWeight
옵션은 카나리아로 보내야 하는 트래픽의 비율을 의미한다.
pause
옵션은 duration 에 정의된 시간만큼 기다렸다가 다음 단계로 진행한다.
위의 설정은 트래픽을 canary 로 20% 보내고 나서 직접 배포를 더 진행하고, 그 다음에 40% 에 도달하면 5초 대기, 60% 에 도달하면 5초 대기, 80% 에 도달하면 5초 대기하고 나서 트래픽의 100% 를 canary 로 보내는 것이다.
pause
의 duration 에 사용할 수 있는 시간 단위는 시, 분, 초이며, 기본값은 초로 지정된다.
spec:
strategy:
canary:
steps:
- pause: { duration: 10 } # 10 seconds
- pause: { duration: 10s } # 10 seconds
- pause: { duration: 10m } # 10 minutes
- pause: { duration: 10h } # 10 hours
- pause: {} # pause indefinitely
애플리케이션을 등록하기 위해 아래의 명령어를 사용했다.
argocd app create rollout-demo \
--repo https://github.com/Han-Joon-Hyeok/aews-cicd \
--path 3/canary \
--dest-namespace canary \
--dest-server https://kubernetes.default.svc \
--sync-option CreateNamespace=true
그 다음 애플리케이션을 배포한다.
argocd app sync rollout-demo
Argo CD 대시보드를 확인하면 파드가 5개 실행되고 있는 것을 확인할 수 있다.
도메인으로 접속해도 파란색 화면이 뜨고 있는 것을 확인할 수 있다.
이미지를 blue 에서 yellow 로 변경해보자.
...
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:yellow
...
그 다음 Github Repository 에 push 하고 argocd app sync 명령어를 실행했다.
argocd app sync rollout-demo
ArgoCD 대시보드를 확인하면 아래의 이미지 같이 새로운 파드가 하나 생긴 것을 확인할 수 있다.
도메인으로 접속해보면 노란색 박스가 생긴 것을 확인할 수 있다.
배포를 더 진행하기 위해 promote 를 해준다.
이후에는 자동으로 canary 로 보내는 트래픽의 비율이 높아질 것이다.
kubectl argo rollouts promote rollouts-demo -n canary
아래의 이미지는 revision 2(canary) 의 파드가 늘어나면서 canary 에 해당하는 노란색 박스가 점점 많아지는 것을 확인할 수 있다.
배포가 전부 끝나면 revision 1 (이전 stable service) 의 파드는 모두 삭제되고 revision 2 (현재 stable service) 의 파드만 남은 것을 확인할 수 있다.
Argo CD 에는 배포 중에 발생하는 오류를 이메일이나 슬랙과 같은 메신저로 알림을 보낼 수 있는 기능을 지원한다.
argocd notifications controller 를 argocd 네임스페이스에 별도로 설치해주어야 한다.
Argo CD 뿐만 아니라 Argo Rollouts 를 이용할 때도 argocd 네임스페이스에 설치된 argocd notifications controller 를 이용해서 알림을 보낼 수 있다.
여기서는 슬랙으로 알림을 보내는 예시를 공식 자료를 참고해서 정리했다.
아래의 명령어를 실행해서 argocd 네임스페이스에 argocd notifications controller 를 설치한다.
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/release-1.0/manifests/install.yaml
설치 후 파드를 확인해보면 추가된 것을 확인할 수 있다.
kubectl get pod -n argocd
#NAME READY STATUS RESTARTS AGE
#...
#argocd-notifications-controller-8cc55b789-ljqhn 1/1 Running 3 (16m ago) 8d
#...
슬랙으로 언제 메세지를 보낼 지 결정하는 트리거와 트리거가 작동하면 보낼 메세지의 템플릿 제공해주는데, 이를 설치해주자.
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/release-1.0/catalog/install.yaml
참고로 메세지 형식은 직접 편집이 가능하기 때문에 원하는 형식과 정보를 알림으로 보낼 수 있다.
Argo Rollouts 을 이용하면서 알림 기능을 이용하기 위해서는 argo-rollouts
네임스페이스에 Secret 과 ConfigMap 을 생성해야 한다.
슬랙 봇을 이용해서 메세지를 전송할 것이기 때문에 슬랙 봇 토큰을 저장하는 Secret 을 생성해주었다.
슬랙 봇을 생성하는 방법은 자세히 설명하지 않고 생략한다.
cat << EOT > rollout-notification-secret.yaml
apiVersion: v1
kind: Namespace
metadata:
name: argo-rollouts
---
apiVersion: v1
kind: Secret
metadata:
name: argo-rollouts-notification-secret
stringData:
slack-token: xoxb-...
EOT
Secret 정보와 트리거를 설정하는 ConfigMap 도 생성해주었다.
cat << EOT > rollout-notification-cm.yaml
apiVersion: v1
kind: Namespace
metadata:
name: argo-rollouts
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argo-rollouts-notification-configmap
data:
service.slack: |
token: $slack-token
defaultTriggers: |
- on-rollout-completed
- on-scaling-replica-set
EOT
여기서는 rollout 이 완료되는 경우와 파드가 스케일링 할 때 알림을 전송하도록 트리거를 설정했다.
위에서 사용했던 canary 배포에 사용한 파일을 그대로 활용했다.
배포를 자동으로 이루어지도록 하기 위해 spec.strategy.canary.steps 항목에서 첫 번째 pause 의 duration 을 5초로 변경했다.
알림을 받기 위해서는 metadata.annotations 를 추가해주어야 한다.
notifications.argoproj.io/subscribe.on-rollout-completed.slack
은 rollout 이 완료되면 알림을 받겠다는 것이고, 값에 해당하는 general
은 슬랙 채널 이름이다.
알림을 받고자 하는 채널에는 슬랙 봇이 초대되어 있어야 한다.
apiVersion: v1
kind: Namespace
metadata:
name: canary
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
annotations: # 추가해준 부분
notifications.argoproj.io/subscribe.on-rollout-completed.slack: general
notifications.argoproj.io/subscribe.on-scaling-replica-set.slack: general
spec:
replicas: 5
strategy:
canary:
canaryService: rollouts-demo-canary
stableService: rollouts-demo-stable
trafficRouting:
alb:
ingress: rollouts-root-ingress
servicePort: 80
rootService: rollouts-demo-root
steps:
- setWeight: 20
- pause: {duration: 5s} # duration 추가
- setWeight: 40
- pause: {duration: 5s}
- setWeight: 60
- pause: {duration: 5s}
- setWeight: 80
- pause: {duration: 5s}
...
변경 사항을 적용하기 위해 레플리카의 수만 변경해주었다.
apiVersion: v1
kind: Namespace
metadata:
name: canary
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
annotations: # 추가해준 부분
notifications.argoproj.io/subscribe.on-rollout-completed.slack: general
notifications.argoproj.io/subscribe.on-scaling-replica-set.slack: general
spec:
replicas: 1 # 1개로 변경
strategy:
canary:
...
아래의 명령어를 실행해서 변경 사항을 적용했다.
argocd app sync rollout-demo
슬랙 채널에 아래의 이미지와 같이 레플리카셋의 수를 조절했다는 알림이 발송되었다.
배포가 완전히 끝나면 한번 더 알림을 보내준다.
기존에 진행하고 있던 쿠버네티스 프로젝트에서 Argo CD 를 이용해서 삽질을 많이 했었기에 이번 스터디 주제는 수월하게 진행할 수 있었다.
젠킨스는 처음 사용해봤는데, 2011년에 첫 릴리즈 해서 지금까지 많이 발전해왔기에 굉장히 사용하기 편리했고, 참고할 자료도 많아서 좋았다.
지금 하고 있는 프로젝트에서는 젠킨스보단 Github Actions 로도 충분히 CI 를 구축할 수 있을 것 같긴 한데, 규모가 커지면 젠킨스를 적극적으로 사용해봐도 좋을 것 같다는 생각이 들었다.
이번에 가장 어려웠던 건 Argo Rollouts 이용해서 Blue/Green 배포 전략 실습을 작성하기 위해 ALB 를 이용해서 Ingress 를 생성하는 것이었다.
Argo Rollouts 공식 자료에는 Blue/Green 배포 예시가 아닌 Canary 만 있었고, Ingress 를 직접 생성하는 것은 처음이었기에 많이 헤매기도 했다.
원하는 대로 되지 않아서 적당히 스터디에서 진행한 내용만 정리하고 포기하고 싶은 마음도 들었지만, 포기하기엔 아깝다는 생각도 들면서 오기가 생겼었다.
지금까지 스터디에서 배운 내용을 천천히 되돌아보니, AWS Load Balancer Controller 도 2주차에 배웠고, helm 차트로 대시보드를 띄울 때 yaml 파일에 annotations 부분에서 ALB 관련 설정하는 부분이 있다는 것이 떠올랐기 때문이다.
그래서 다른 블로그 자료도 찾아보고, 이것저것 삽질한 결과로 rollouts-active.domain
과 rollouts-preview.domain
경로로 ALB 를 연결하는 것에 성공했다.
그리고 이전에 Argo CD 를 Github Private Repository 에 연결하는 것과 Argo Rollouts 를 이용해서 배포 전략을 적용하고 알림 기능까지 전부 삽질을 해봤던 터라 비교적 쉽게 내용을 작성할 수 있었다.
내용이 워낙 많아서 자세히 정리하진 못했지만, 실습에 필요한 핵심을 짚고자 노력했다.
스스로 힘든 실습을 선택했지만, 원하는 대로 잘 되어서 자신감도 생기고 뿌듯했다.
결코 쉽지 않았지만, 한 단계 더 성장한 느낌이 들어서 좋다.