CI/CD 파이프라인을 구성할 때 고려해야 하는 요소
- 관리 담당: 소스 빌드 → 컨테이너 빌드 → 배포 단계의 CI/CD를 구성한다고 할 때, 한 명이 jenkins pipeline으로 모두 관리하면 기능적으로 편리하다. 하지만 실무에서는 각 단계를 책임지는 담당자가 존재한다. 예를 들어 개발자가 소스 빌드를 담당하고, 나머지를 devops 엔지니어가 담당할 수 있다. 기능적인 측면 vs 관리적인 측면을 고려해야 한다.
- 운영 정책: jenkins로 소스와 컨테이너 빌드를 수행하고 ArgoCD로 배포를 수행한다. 인프라 환경에는 개발과 운영환경이 존재하는데, 이 때 ArgoCD 배포와 인프라를 1:N 형태로 설정할 수 있고, 아니면 인프라 환경마다 ArgoCD를 두어 1:1로 구축할 수 있다. 전자는 ArgoCD 하나만 관리하여 편리하지만, 개발환경에서 장애가 발생하면 운영환경에 영향을 미칠 수 있다. 후자는 이중 관리가 필요하지만 장애시 다른 환경에 영향이 가지 않는다.
- 제품 선정: 데이터 보안을 위해 온라인/오프라인 환경, CI/CD를 통합할건지 분리할것인지의 관점에서 다양한 툴을 선택할 수 있다. 이런 툴들을 선택할 때는 레퍼런스가 많거나 유지보수 업체의 존재유무를 고려해야 한다.
배포 전략을 세울 때 고려해야 하는 요소
v1의 앱을 v2로 업데이트 한다고 할 때, 다음과 같이 4개의 배포전략이 존재한다.
- Recreate: v1의 파드가 모두 중지된 후 v2의 파드가 생성된다.
- 배포 작업: 이미지 버전 변경하여 Deployment 업데이트.
- 유즈 케이스: 데이터 베이스 스키마 변경 시 서비스를 중단할 수 밖에 없다. 먼저 replica를 0으로 변경하고 DB 작업이 끝난 후 다시 replica를 2로 변경하여 서비스를 재개할 수 있다.
- 특징: 자동 배포, 트래픽 제어 불가, 서비스 중단(Downtime)이 발생한다.
- RollingUpdate: v1 파드의 일부만 중지함과 동시에 v2 파드를 생성한다.
- 배포 작업: 이미지 버전 변경하여 Deployment 업데이트
- 특징: 자동 배포, 트래픽 제어 불가, 서비스 중단 발생하지 않음. 다만 v1, v2 파드의 동시 호출이 발생한다.
- Blue/Green: 두 버전의 Deployment를 모두 생성한 후 service selector만 변경한다.
- 배포 작업:
- v2 Deployment 생성
- Service의 selector를 v1에서 v2로 변경
- v1 Deployment 제거
- 유즈 케이스: 운영에서만 테스트 가능한 경우(운영 DB의 데이터를 사용해야 하는 경우) QA 용도로 v2 Deployment를 생성한 상태에서 v2 Service를 QA 담당자에게 오픈하여 테스트할 수 있다.
- 특징: 수동 배포 시 롤백이 빠름, Script를 통해 자동 배포 가능, 서비스가 중단되거나, 동시 호출되는 문제가 발생하지 않는다. 다만 v2에 과도한 트래픽 유입시 문제 발생.
- 배포 작업:
- Canary: v1과 v2 모두 개별의 service와 ingress를 생성하고, Ingress Controller를 통해 각 서비스에 인입되는 트래픽 비율을 조절한다.
- 배포 작업:
- v2 Deployment, Ingress, Service 생성
- v1, v2의 Ingress 가중치 변경
- v1 Deployment, Ingress, Service 제거
- 유즈 케이스: 특정 헤더 값(Source IP, User, Language)에 한해서만 v2로 트래픽이 유입되게 할 수 있다.
- 특징: 콜드 스타트 방지, v1과 v2 버전을 비교하기 위한 A/B 테스트 가능.\
- 배포 작업:
Jenkins Pipeline 기본 구성 만들기 (Step 1)
Jenkins pipeline은 '새로운 Item'을 클릭하여 'Pipeline' 항목을 선택하여 생성한다.
이후 구성(Configure) 메뉴에 들어가면 맨 아래에 pipeline script를 작성하는 곳이 존재한다.
pipeline {
agent any
tools {
gradle 'gradle-7.6.1'
jdk 'jdk-17'
}
environment {
// 본인의 username으로 하실 분은 수정해주세요.
DOCKERHUB_USERNAME = 'taehokang'
GITHUB_URL = 'https://github.com/EBEL21/kubernetes-anotherclass-sprint2.git'
// 실습 넘버링
CLASS_NUM = '2212'
}
stages {
stage('소스파일 체크아웃') {
steps {
// 소스코드를 가져올 Github 주소
git branch: 'main', url: 'https://github.com/k8s-1pro/kubernetes-anotherclass-api-tester.git'
}
}
stage('소스 빌드') {
steps {
// 755권한 필요 (윈도우에서 Git으로 소스 업로드시 권한은 644)
sh "chmod +x ./gradlew"
sh "gradle clean build"
}
}
stage('릴리즈파일 체크아웃') {
steps {
checkout scmGit(branches: [[name: '*/main']],
extensions: [[$class: 'SparseCheckoutPaths',
sparseCheckoutPaths: [[path: "/${CLASS_NUM}"]]]],
userRemoteConfigs: [[url: "${GITHUB_URL}"]])
}
}
stage('컨테이너 빌드') {
steps {
// jar 파일 복사
sh "cp ./build/libs/app-0.0.1-SNAPSHOT.jar ./${CLASS_NUM}/build/docker/app-0.0.1-SNAPSHOT.jar"
// 도커 빌드
sh "docker build -t ${DOCKERHUB_USERNAME}/api-tester:v1.0.0 ./${CLASS_NUM}/build/docker"
}
}
stage('컨테이너 업로드') {
steps {
// DockerHub로 이미지 업로드
script{
if (DOCKERHUB_USERNAME == "1pro") {
echo "docker push ${DOCKERHUB_USERNAME}/api-tester:v1.0.0"
} else {
sh "docker push ${DOCKERHUB_USERNAME}/api-tester:v1.0.0"
}
}
}
}
stage('쿠버네티스 배포') {
steps {
// K8S 배포
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/namespace.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/configmap.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/secret.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/service.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/deployment.yaml"
}
}
stage('배포 확인') {
steps {
// 10초 대기
sh "sleep 10"
// K8S 배포
sh "kubectl get -f ./${CLASS_NUM}/deploy/k8s/namespace.yaml"
sh "kubectl get -f ./${CLASS_NUM}/deploy/k8s/configmap.yaml"
sh "kubectl get -f ./${CLASS_NUM}/deploy/k8s/secret.yaml"
sh "kubectl get -f ./${CLASS_NUM}/deploy/k8s/service.yaml"
sh "kubectl get -f ./${CLASS_NUM}/deploy/k8s/deployment.yaml"
}
}
}
}
Pipeline Script의 내용은 다음과 같다.
- 백엔드 repository의 소스코드를 checkout하고 jar 파일 빌드
- 배포 파일 repository 소스코드를 checkout
- 빌드한 백엔드 jar 파일로 컨테이너 빌드 및 업로드
- 배포 파일 repository 소스코드를 가져와 쿠버네티스 매니페스트 배포
위 이미지는 배포 파일 repository의 디렉토리 구성을 나타낸다.
Docker 컨테이너 빌드를 위한 Dockerfile과 쿠버네티스 yaml 파일들이 존재한다.
해당 파일들 말고 추가적으로 Jenkinsfile이 존재한다.
Jenkins pipeline의 내용은 pipeline script를 직접 jenkins 설정에 작성하는 방법이 있고,
또 다른 방법으로는 동일한 내용의 script를 위 이미지처럼 jenkinsfile에 작성하여 git repository에서 관리하는 방법이 있다.
repository에 있는 jenkinsfile로 pipeline을 실행하기 위해서는 설정에서 GitHub Project를 지정한 후,
Pipeline 설정을 'Pipeline Script'가 아닌 'Pipeline script from SCM'으로 설정해야 한다.
세부 설정에서는 Git repository URL과 jenkinsfile이 위치한 경로를 설정해주면 해당 jenkinsfile에 작성된 script 내용으로 파이프라인을 실행할 수 있다.
Blue/Green 배포 생성
pipeline {
agent any
environment {
// 본인의 username으로 하실 분은 수정해주세요.
GITHUB_URL = 'https://github.com/k8s-1pro/kubernetes-anotherclass-sprint2.git'
// 실습 넘버링
CLASS_NUM = '2213'
}
stages {
stage('릴리즈파일 체크아웃') {
steps {
checkout scmGit(branches: [[name: '*/main']],
extensions: [[$class: 'SparseCheckoutPaths',
sparseCheckoutPaths: [[path: "/${CLASS_NUM}"]]]],
userRemoteConfigs: [[url: "${GITHUB_URL}"]])
}
}
stage('쿠버네티스 Blue배포') {
steps {
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/namespace.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/configmap.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/secret.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/service.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/deployment.yaml"
}
}
stage('배포 시작') {
steps {
input message: '수동배포 시작', ok: "Yes"
}
}
stage('쿠버네티스 Green배포') {
steps {
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/green/deployment.yaml"
sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/green/service.yaml"
}
}
stage('전환여부 확인') {
steps {
script {
returnValue = input message: 'Green 전환?', ok: "Yes", parameters: [booleanParam(defaultValue: true, name: 'IS_SWITCHED')]
if (returnValue) {
sh "kubectl patch -n anotherclass-221 svc api-tester -p '{\"spec\": {\"selector\": {\"blue-green-no\": \"2\"}}}'"
}
}
}
}
stage('롤백 확인') {
steps {
script {
returnValue = input message: 'Blue 롤백?', parameters: [choice(choices: ['done', 'rollback'], name: 'IS_ROLLBACk')]
if (returnValue == "done") {
sh "kubectl delete -f ./${CLASS_NUM}/deploy/k8s/blue/deployment.yaml"
sh "kubectl delete -f ./${CLASS_NUM}/deploy/k8s/green/service.yaml"
sh "kubectl patch -n anotherclass-221 svc api-tester -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
sh "kubectl patch -n anotherclass-221 cm api-tester-properties -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
sh "kubectl patch -n anotherclass-221 secret api-tester-postgresql -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
}
if (returnValue == "rollback") {
sh "kubectl patch -n anotherclass-221 svc api-tester -p '{\"spec\": {\"selector\": {\"blue-green-no\": \"1\"}}}'"
}
}
}
}
}
}
Blue/Green 배포 Script를 보면 Blue 버전을 배포한 후, Green 버전의 배포를 결정하는 부분이 있다.
Green 버전을 배포하게 되면 deployment와 service만 배포 하면 된다.
배포한 Green 버전에 대해 테스트가 완료되면 전환 여부를 물어보게 되는데, 이 때 전환을 선택하면
기존에 외부에 공개되었던 Blue 버전의 서비스의 셀렉터를 변경하여 Green 버전에 트래픽을 전환한다.
만약 Green 버전 배포 후 Blue 버전으로 롤백하는 것도 다시 셀렉터만 변경하면 된다.
해당 예제에서는 'blue-green-no' 라벨으로 Blue, Green 버전을 구분한다.
이를 그림으로 나타내면 다음과 같다.
또한 Jenkins pipeline script의 stage에서 에서 input message를 활용하면 아래와 같이 GUI로 communication을 할 수 있는 pop-up 창이 뜨게 되며, 어떤 버튼을 클릭하냐에 따른 분기도 script로 작성할 수 있다.
'DevOps' 카테고리의 다른 글
[ArgoCD] ArgoCD로 쿠버네티스 클러스터에 App 배포하기 (4) | 2024.10.18 |
---|---|
[ArgoCD] ArgoCD 아키텍처에 대한 이해 (0) | 2024.10.17 |
[Helm] Helm 패키지 생성하고 배포하기 (2) | 2024.10.07 |
[DevOps] 데브옵스 환경 구축 (0) | 2024.05.12 |
[DevOps] 데브옵스 한방 정리 (0) | 2024.05.08 |