CD 파이프라인
빌드 버전 관리
하나의 클러스터 내에서 네임스페이스만 별도로 구성하고, production 환경과 staging 환경이 분리된 상황에서의 파이프라인 구성을 시뮬레이션해보자.
1. 테라폼 클라우드에서 생성한 Organization인 PRGMS_COURSE_CICD에서 두 워크스페이스를 생성한다.
하나는 스테이징 워크스페이스인 calculator-dev이고, 다른 하나는 프로덕션 워크스페이스인 calculator-prod이다.
Execution Mode는 로컬로 설정한다.
2. production.conf 파일을 다음과 같이 작성한다.
terraform {
cloud {
organization = "PRGMS_COURSE_CICD"
workspaces {
name = "calculator-prod"
}
}
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}
locals {
deployment = {
namespace = "prod"
service_port = 31000
}
}
3. staging.conf 파일을 다음과 같이 작성한다.
2와 비교했을 때 이용하는 테라폼 클라우드의 워크스페이스가 다르고, namespace와 포트 번호도 다르다.
terraform {
cloud {
organization = "PRGMS_COURSE_CICD"
workspaces {
name = "calculator-dev"
}
}
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}
locals {
deployment = {
namespace = "dev"
service_port = 32000
}
}
4. resources.conf 파일을 작성한다.
- kubernetes_config_path에서는 서로 다른 kubeconfig를 적용하기 위한 변수를 설정한다.
- calculator_img에서는 포드 생성에 적용할 이미지를 변수로 설정한다. 빌드 버전이 달라지면 서로 다른 이미지 태그를 적용하기 위함이다.
- kubernetes_namespace ns에서는 앞에서 지정한 k8s 네임스페이스(prod, dev)를 리소스로 생성한다. 테라폼 디스트로이를 하면 없어진다.
variable "kubernetes_config_path" {
default = "~/.kube/config"
}
variable "calculator_img" {
default = ""
}
provider "kubernetes" {
config_path = var.kubernetes_config_path
}
resource "kubernetes_namespace" "ns" {
metadata {
name = local.deployment.namespace
}
}
resource "kubernetes_deployment" "calculator" {
metadata {
name = "calculator"
labels = {
App = "Calculator"
}
namespace = local.deployment.namespace
}
spec {
replicas = 3
selector {
match_labels = {
App = "Calculator"
}
}
template {
metadata {
labels = {
App = "Calculator"
}
}
spec {
container {
image = var.calculator_img
name = "calculator"
port {
container_port = 8080
}
}
}
}
}
}
resource "kubernetes_service" "calculator" {
metadata {
name = "calculator"
namespace = local.deployment.namespace
}
spec {
selector = {
App = kubernetes_deployment.calculator.spec.0.template.0.metadata[0].labels.App
}
port {
node_port = local.deployment.service_port
port = 8080
target_port = 8080
}
type = "NodePort"
}
}
5. 젠킨스에서 build timestamp 플러그인을 설치하고, 젠킨스 설정의 시스템에서 타임존을 서울로 설정하고, 패턴은 'yyyyMMdd-HHmm'으로 설정한다.
6. 아래 명령으로 staging.conf 파일과 resources.conf 파일을 calculator.tf 파일에 복사한다.
나는 윈도우 운영체제를 사용하고 있기 때문에 cat 대신 type을 사용했다.
type staging.conf resources.conf > calculator.conf
7. 테라폼을 초기화하고, 리소스를 생성한다.
terraform init
terraform apply -var "calculator_img=localhost:30100/calculator:1.0"
8. 제대로 생성되었는지 확인한다.
kubectl get namespaces
kubectl get deployments -n dev
젠킨스 파이프라인
1. Dockerfile을 다음과 같이 작성하고, build & push 해서 레지스트리에 등록한다.
FROM jenkins/jnlp-agent-docker
USER root
COPY entrypoint.sh /entrypoint.sh
RUN chown jenkins:jenkins /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN wget https://releases.hashicorp.com/terraform/1.6.6/terraform_1.6.6_windows_amd64.zip
RUN unzip terraform_1.6.6_windows_amd64.zip
RUN mv terraform /bin && chmod +x /bin/terraform
USER jenkins
ENTRYPOINT ["/entrypoint.sh"]
docker build -t ncherryu/jnlp-terraform
docker push ncherryu/jnlp-terraform
2. 파이프라인 스크립트를 다음과 같이 작성하고, 빌드하여 결과를 확인한다.
pipeline {
agent {
kubernetes {
yaml'''
aliVersion: v1
kind: Pod
spec:
containers:
- name: jnlp
image: ncherryu/jnlp-terraform
env:
- name: DOCKER_HOST
value: "tcp://localhost:2375"
- name: dind
image: ncherryu/dind
command:
- /usr/local/bin/dockerd-entrypoint.sh
env:
- name: DOCKER_TLS_CERTDIR
value: ""
securityContext:
privileged: true
- name: builder
image: ncherryu/jenkins-agent-jdk-17
command:
- cat
tty: true
'''
}
}
environment {
REGISTRY_URI = 'registry-service.registry.svc.cluster.local:30100'
VERSION_TAG = '${BUILD_TIMESTAMP}'
STAGING_URL = 'http://calculator.dev.svc.cluster.local:8080'
PRODUCTION_URL = 'http://calculator.prod.svc.cluster.local:8080'
}
stages {
stage("Checkout") {
steps {
git url: 'git@github.com:ncherryu/calculator.git',
branch: 'master',
credentialsId: 'github-credentials'
}
}
stage("Compile") {
steps {
script {
container('builder') {
sh "./gradlew compileJava"
}
}
}
}
stage("Unit Test") {
steps {
script {
container('builder') {
sh "./gradlew test"
publishHTML(target: [
reportDir: 'build/reports/tests/test',
reportFiles: 'index.html',
reportName: 'JUnit Report'
])
}
}
}
}
stage("Code Coverage") {
steps {
script {
container('builder') {
sh "./gradlew jacocoTestReport"
publishHTML(target: [
reportDir: 'build/reports/jacoco/test/html',
reportFiles: 'index.html',
reportName: 'JaCoCo Report'
])
sh "./gradlew jacocoTestCoverageVerification"
}
}
}
}
stage("Static Analysis") {
steps {
script {
container('builder') {
sh "./gradlew checkstyleMain"
publishHTML(target: [
reportDir: 'build/reports/checkstyle',
reportFiles: 'main.html',
reportName: 'Checkstyle Report'
])
}
}
}
}
stage("Package") {
steps {
script {
container('builder') {
sh "./gradlew build"
}
}
}
}
stage("Docker Build") {
steps {
script {
dockerImage = docker.build "calculator"
}
}
}
stage("Docker Push") {
steps {
script {
docker.withRegistry("https://${REGISTRY_URI}") {
dockerImage.push("${VERSION_TAG}")
}
}
}
}
stage("Deploy to Staging") {
steps {
withCredentials([file(credentialsId: 'KUBECONFIG',
variable: 'KUBECONFIG_PATH'),
string(credentialsId: 'terraform-credentials',
variable: 'TF_TOKEN_app_terraform_io')]) {
sh'''
cd iac
cat staging.conf resources.conf > calculator.tf
terraform init
terraform apply --auto approve \
-var "kubernetes_config_path=${KUBECONFIG_PATH}" \
-var "calculator_img=localhost:30100/calculator;${VERSION_TAG}"
'''
}
}
}
stage("Acceptance Test") {
steps {
script {
container('builder') {
sleep 60
sh "./gradlew acceptanceTest " +
"-Dcalculator.url=${STARTING_URL}"
publishHTML(target: [
reportDir: 'build/reports/tests/acceptanceTest',
reportFiles: 'index.html',
reportName: 'Acceptance Test Report'
])
}
}
}
}
stage("Release") {
steps {
withCredentials([file(credentialsId: 'KUBECONFIG',
variable: 'KUBECONFIG_PATH'),
string(credentialsId: 'terraform-credentials',
variable: 'TF_TOKEN_app_terraform_io')]) {
sh'''
cd iac
cat production.conf resources.conf > calculator.tf
terraform init
terraform apply --auto approve \
-var "kubernetes_config_path=${KUBECONFIG_PATH}" \
-var "calculator_img=localhost:30100/calculator;${VERSION_TAG}"
'''
}
}
}
stage("Smoke Test") {
steps {
script {
container('builder') {
sleep 60
sh "./gradlew acceptanceTest " +
"-Dcalculator .url=${PRODUCTION_URL}"
}
}
}
}
post {
always {
withCredentials([file(credentialsId: 'KUBECONFIG',
variable: 'KUBECONFIG_PATH'),
string(credentialsId: 'terraform-credentials',
variable: 'TF_TOKEN_app_terraform_io')]) {
sh'''
cd iac
cat staging.conf resources.conf > calculator.tf
terraform init
terraform apply --auto approve \
-var "kubernetes_config_path=${KUBECONFIG_PATH}"
'''
}
}
}
}
}
'데브코스' 카테고리의 다른 글
[18주차 - DAY3] 오픈소스 기여(2) (0) | 2024.06.26 |
---|---|
[18주차 - DAY2] 오픈소스 기여(1) (0) | 2024.06.25 |
[17주차 - DAY3] 인수 테스트 정리 (0) | 2024.06.19 |
[17주차 - DAY3] 인수 테스트 (1) | 2024.06.19 |
[17주차 - DAY2] CI 파이프라인 (0) | 2024.06.18 |