1. 개요
- Gitlab과 Jenkins를 이용해 Azure kubernetes Service로 CI/CD하여 서비스를 배포 및 관리합니다.
- Jenkins는 CI/CD 기능을 담당하고 GitLab은 소스코드 관리기능을 합니다.
2. 아키텍처
- Frontend 아키텍처

- Backend 아키텍처

3. 실습 환경 구성
Bastion 가상 머신 구성
1) OS : Windows 10 pro
2) 시작 유형 : 표준 -> WSL & Docker를 Azure VM에서 실행하기 위해서 표준 유형을 선택 해야함.
3) 사전 설치
- Chrome
- VSCode
- Git
- AZ CLI
- MobaXterm
- kubectl
- WSL 활성화
- Docker Desktop
4) Source Code
- gitlab에서 관리하기 위한 web-main , api-main 소스 코드 준비
- web-main : https://github.com/qpsaone2/web.git
- api-main : https://github.com/qpsaone2/api.git
GitLab 가상 머신 구성
1) OS : Ubuntu 24.04
2) GitLab 설치
- sudo update
sudo apt update -y
- Install Gitlab Package
sudo apt-get install -y curl openssh-server ca-certificates tzdata perl
sudo apt-get install -y postfix
# 인터넷 사이트 선택
# 메일이름으로는 VM의 아이피를 선택
# 예시 - 10.0.0.5 (사전에 GITLAB VM의 아이피를 확인합니다.)
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash
- Set up the initial account and install ee
# ExternalDomain을 지정해줍니다.. 본인의 메일과 vm의 아이피 정보로 변경해준후 실행합니다.
sudo GITLAB_ROOT_EMAIL="cccc@xxx.com" EXTERNAL_URL="http://10.0.1.6" apt install gitlab-ee
- 초기 루트 비밀번호 확인
sudo cat /etc/gitlab/initial_root_password
- 웹 브라우저 접속
http://{vmip}

- Project 생성
web-main과 api-main을 위한 새로운 프로젝트 생성
Import Project >> repository by URL을 이용해서 github에 있는 소스코드를 gitlab으로 import 합니다.


- import 완료한 project 확인

Jenkins 가상 머신 구성
1) OS : Ubuntu 24.04
2) Jenkins 설치
- sudo update
sudo apt update -y
- AZ CLI 설치 및 Kubectl 설치
# AZ CLI 설치
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Kubectl 설치
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
chmod +x kubectl
mkdir -p ~/.local/bin
mv ./kubectl ~/.local/bin/kubectl
- Docker 설치
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
sudo usermod -aG docker $USER
- Jenkins Package 설치
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update -y
# JDK를 먼저 설치해주자
sudo apt install fontconfig openjdk-17-jre -y
# JDK 경로
/usr/lib/jvm/java-17-openjdk-amd64/bin/
# 환경변수 등록
sudo vi /etc/environment
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
으로 등록
source /etc/environment
# jenkins 설치
sudo apt-get install jenkins -y
# docker 권한 할당
sudo usermod -aG docker jenkins
- 상태 확인
# jenkins status 확인
sudo systemctl status jenkins
# jenkins 시작
sudo systemctl start jenkins
- 서비스 포트 변경
# 데몬위치
sudo vi /lib/systemd/system/jenkins.service
# 서비스 카테고리 아래에 원하는 포트를 변수로 넣어준다.
[Service]
...
Environment="JENKINS_PORT=8081" -> 기본포트는 8080 이지만 8081로 변경해준다.
# 편집 완료후
sudo systemctl daemon-reload
sudo systemctl restart jenkins
- 초기 admin PW 확인
# 아래와 같이 초기 비밀번호가 출력됩니다.
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
- 브라우저 접속확인
http://{가성머신IP}:{설정한포트}
초기 패스 워드를 입력합니다.

- 초기설정의 경우 기본 값으로 설정한다.
Install suggested plugins 을 선택하여 설치를 진행한다.

- Plugin 설치 완료 후 admin 계정을 생성합니다.
계정명 : admin
암호, 이름, 이메일 주소를 자신이 원하는 값을 입력합니다.
- Jenkins URL을 선택합니다.
추후 빌드에 사용되니 IP보다 도메인을 권장합니다. 다만, 지금은 테스트 단계이므로 IP로 진행합니다.
기본적으로 IP가 할당되어 있으니 그대로 진행합니다.
- 초기 화면으로 접속하게 되면 [Manage Jenkins] >> [Plugins]로 이동합니다.
[plugins]으로 들어가서 다음을 설치해줍니다.
GitLab , Publish Over SSH , NodeJS

- [Manage Jenkins] >> [Tools] 로 이동합니다.
다음과 같이 Tool을 설정합니다.
a. [Gradle installations] 설정 진행
- name : gradle
- version : Gradle7.5
b. [NodeJS installations] 설정 진행
- name : nodejs
- install automatically 체크
- version : NodeJS 20.16.0
- Global npm packages to install : npm install -g nextjs
- [Managed Jenkins] >> [System]으로 이동합니다.
a. GitLab
- Connection Name : gitlab connection
- GitLab host URL : http://10.0.1.6
- Credential 추가
kind : username with password
username : gitlab 아이디 입력
API Token : Gitlab에서 PAT로 만든 토큰값을 붙여 넣습니다.
ID : cheol03
- 우측 하단 접속 테스트 진행

Jenkins Backend Pipeline 구성
- jenkins backend pipeline 생성
[New Item] >> Enter an item name : backend pipeline >> Pipeline

- [This project is parameterized] 선택 후 String Parameter를 선택
Name : RELEASE_VERSION에 대해 기본 값과 설명을 추가 합니다.

- Pipeline Script 작성
아래 스크립트를 실습 환경에 맞게 작성하고 저장합니다.
pipeline {
tools {
gradle "gradle"
}
agent any
parameters {
string(name: 'RELEASE_VERSION', defaultValue: '1.0.0', description: 'Release version')
}
environment {
DOCKER_REGISTRY = 'cheolaksacr001.azurecr.io' // ACR 이름
IMAGE_REPO_NAME = 'api' // Repository 이름
IMAGE_TAG = "${params.RELEASE_VERSION}" // 이미지 태그
REPOSITORY_URI = "${DOCKER_REGISTRY}/${IMAGE_REPO_NAME}"
AKS_RESOURCE_GROUP = 'cheol-kbhc-rg' // AKS 클러스터 리소스 그룹 이름
AKS_CLUSTER_NAME = 'cheol-olla-aks-001' // AKS 클러스터 이름
KUBERNETES_NAMESPACE = 'default' // Deployment할 네임스페이스 이름
GIT_CREDENTIAL_ID = 'cheol03' // Jenkins Credential ID
GIT_URL = 'http://10.0.1.6/root/api.git' // GitHub URL
DEPLOYMENT_NAME = "api" // Deployment 이름
CONTAINER_NAME = "api" // Container 이름
}
stages {
stage('Clone Repository') {
steps {
git(
url: "${GIT_URL}",
// branch: "release/${params.RELEASE_VERSION}",
branch: "main",
credentialsId: "${GIT_CREDENTIAL_ID}"
)
}
}
stage('Version Check') {
steps {
script {
API_VERSION = sh (
script: 'grep "^version" build.gradle | cut -d "\'" -f 2',
returnStdout: true
).trim()
echo "RELEASE_VERSION: ${params.RELEASE_VERSION}"
echo "API_VERSION: ${API_VERSION}"
if (API_VERSION == params.RELEASE_VERSION) {
echo "Build version check success: current version is " + API_VERSION
} else {
echo "Build version check failed: current version is " + API_VERSION
error "Version check failed"
}
}
}
}
stage('Gradle Build') {
steps {
sh 'gradle clean build -x test --parallel'
}
}
stage('Build Docker Image') {
steps {
script {
sh "docker build -f Dockerfile -t ${REPOSITORY_URI}:${IMAGE_TAG} ."
}
}
}
stage('Login to Azure ACR with Managed Identity') {
steps {
script {
sh 'az login --identity'
sh "az acr login --name ${DOCKER_REGISTRY.split('\\.')[0]}"
}
}
}
stage('Push Docker Image to ACR') {
steps {
script {
sh "docker push ${REPOSITORY_URI}:${IMAGE_TAG}"
}
}
}
stage('Deploy to AKS') {
steps {
script {
// AKS에 로그인하여 kubectl을 사용하도록 설정
sh """
az aks get-credentials --resource-group ${AKS_RESOURCE_GROUP} --name ${AKS_CLUSTER_NAME} --overwrite-existing
kubectl config set-context \$(kubectl config current-context) --namespace=${KUBERNETES_NAMESPACE}
"""
// Kubernetes Deployment 업데이트
sh """
kubectl set image deployment/${DEPLOYMENT_NAME} ${CONTAINER_NAME}=${REPOSITORY_URI}:${IMAGE_TAG}
kubectl rollout restart deployment/${DEPLOYMENT_NAME}
kubectl rollout status deployment/${DEPLOYMENT_NAME}
"""
}
}
}
}
post {
always {
script {
sh 'docker logout ${DOCKER_REGISTRY}'
}
}
success {
echo 'Docker image successfully built and pushed to ACR, and deployed to AKS!'
}
failure {
echo 'Build, push, or deploy failed.'
}
}
}
- 저장 후 [Build with Parameters] 실행

Build 및 배포 과정은 해당 Pipeline의 Console Output에서 볼 수 있습니다.


- Azure Kubernetes Service에 접속하여 API deployment의 이미지가 정상적으로 바뀌었는지 확인합니다.
원래 ACR의 api:latest에 해당하는 이미지였으나, Jenkins CI/CD를 통해서 Image가 api:1.0.0으로 변경 되었다.

- Azure Container Registry의 이미지를 확인 한다.

Jenkins Frontend Pipeline 구성
- jenkins frontend pipeline 생성
위 backend pipeline을 만든 과정과 동일하게 frontend pipeline을 생성한다.
[This project is parameterized] 과정은 생략했다.
아래 스크립트를 pipeline Definition에 입력하면된다. 해당 스크립트는 실습환경에 맞게 진행해야한다.
pipeline {
agent any
environment {
DEPLOY_MODE = 'production' // 배포 모드
SERVICE_NAME = 'web' // 서비스 이름
DOCKER_IMAGE_NAME = "${SERVICE_NAME}"
DOCKER_CONTAINER_NAME = "${SERVICE_NAME}"
DOCKER_TAG_NAME = '1.0.0' // 도커 태그 이름
ACR_REPOSITORY_NAME = 'cheolaksacr001.azurecr.io' // ACR 리포지토리 이름
AKS_RESOURCE_GROUP = 'cheol-kbhc-rg' // AKS 리소스 그룹
AKS_CLUSTER_NAME = 'cheol-olla-aks-001' // AKS 클러스터 이름
KUBERNETES_NAMESPACE = 'default' // 네임스페이스 이름
GIT_URL = 'http://10.0.1.6/root/web.git'
GIT_CREDENTIAL_ID = 'cheol03'
BRANCH_NAME = 'main' // 브랜치 이름
NEXT_PUBLIC_DEPLOY_MODE = "${DEPLOY_MODE}"
}
stages {
stage('Clone Repository') {
steps {
git url: "${GIT_URL}", branch: "${BRANCH_NAME}", credentialsId: "${GIT_CREDENTIAL_ID}"
}
}
stage('Docker Image Clean') {
steps {
script {
sh 'docker image prune -af --filter until=24h'
}
}
}
stage('Docker Build') {
steps {
script {
sh "docker build -t ${ACR_REPOSITORY_NAME}/${DOCKER_IMAGE_NAME}:${DOCKER_TAG_NAME} ."
}
}
}
stage('Login to Azure ACR') {
steps {
script {
sh 'az login --identity'
sh "az acr login --name ${ACR_REPOSITORY_NAME.split('\\.')[0]}"
}
}
}
stage('Docker Push') {
steps {
script {
sh "docker push ${ACR_REPOSITORY_NAME}/${DOCKER_IMAGE_NAME}:${DOCKER_TAG_NAME}"
}
}
}
stage('AKS Authentication') {
steps {
script {
sh """
az aks get-credentials --resource-group ${AKS_RESOURCE_GROUP} --name ${AKS_CLUSTER_NAME} --overwrite-existing
kubectl config set-context \$(kubectl config current-context) --namespace=${KUBERNETES_NAMESPACE}
"""
}
}
}
stage('Deploy to AKS') {
steps {
script {
sh """
kubectl set image deployment/${SERVICE_NAME} ${DOCKER_CONTAINER_NAME}=${ACR_REPOSITORY_NAME}/${DOCKER_IMAGE_NAME}:${DOCKER_TAG_NAME}
kubectl rollout restart deployment/${SERVICE_NAME}
kubectl rollout status deployment/${SERVICE_NAME}
"""
}
}
}
}
post {
always {
script {
sh 'docker logout ${ACR_REPOSITORY_NAME}'
}
}
success {
echo 'Docker image successfully built and pushed to ACR, and deployed to AKS!'
}
failure {
echo 'Build, push, or deploy failed.'
}
}
}
- 정상적으로 CICD되어 Pod에 접근해본다.
가상머신에서 AKS Pod로 직접 접근한 것이다. 해당 Pod IP는 배포 될 때마다 변경 되기 때문에 Pod IP로 접근하는것은 의미가 없다. 그렇기때문에 Service가 필요한 것이고 해당 서비스의 External IP를 통해서 접근해야한다.

현재 AKS의 CNI는 Azure CNI 노드 서브넷이므로 가상머신에서 Pod로 접근이 가능하다.
Azure CNI Node Subnet은 AKS Cluster가 배포된 가상 네트워크의 서브넷 CIDR에서 IP를 배정한다.
그러므로 해당 가상네트워크와 통신이 된다면 Pod에 접근이 가능하다.

web에 해당 하는 Pod는 web이라는 이름을 가지는 service와 연결되어있다.
service external IP로 접근해도 Pod에 정상적으로 접근이된다.
external IP를 가진 Service (internal LB)를 만들기위해서는 serivce 생성 시 type을 LB로 하면된다.




web service에 대한 yaml파일은 다음과 같다
apiVersion: v1
kind: Service
metadata:
name: web
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
spec:
type: LoadBalancer
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
- CICD 정상 작동 확인
Gitlab 소스코드를 업데이트 후 다시 파이프라인을 실행하여 web Pod가 정상적으로 적용되는지 확인한다.


파이프라인 실행 후 웹 페이지 내용이 Sample APP003이 나오면 성공이다.

web service로 접근한 브라우저 화면이다.

- Azure Container Registry 이미지 확인

'Azure' 카테고리의 다른 글
| Managed Grafana & Prometheus를 이용한 AKS Monitoring (0) | 2024.10.04 |
|---|---|
| AKS에서 Grafana Loki & Prometheus를 이용해서 모니터링 (0) | 2024.10.01 |
| Azure Kubernetes Service & Cert-Manager (0) | 2024.09.07 |
| AWS EC2(docker swarm)에서 Azure Kubernetes Service Migration (0) | 2024.07.17 |
| Azure DataFactory를 통한 Azure Cosmos DB 데이터 적재 (0) | 2024.07.17 |