[Clone_Project] CI/CD 파이프라인 구축 및 배포
프로젝트 소개
이번 프로젝트는 지금까지 배운 내용과 다른 기술 블로그의 내용을 참고하여 React, NodeJS,MySQL 등 으로 이루어진
풀스택 어플리케이션을 CI/CD 파이프라인으로 구성하고 온프레미스와 클라우드 환경을 병행하여 하이브리드 클라우드를
통해 적절한 비용을 갖추고 모니터링 ,로깅 시스템을 통하여 클라우드 내 서비스에 가용성을 확보하기 위한 시스템을
구성하는 프로젝트이다.
프로젝트 목표
풀스택 어플리케이션 AWS 환경에서 배포
CICD 파이프라인
1. Github 레포지터리 변경 감지시 Jenkins Webhook을 통해 Docker Image 빌드
2. Githhub 변경 감지시 Github변경 내용을 감지하고 메니페스트와 CLuster, Sync 맞춤
모니터링 시스템 구축
1. Prometheus, Grafnana 구축 및 매트릭 생성
2. API Prometheus API 활용하여 수집기 개발
3. Grafana 대시보드 구성
로깅 시스템 구축하기
Grafana Loki를 사용하여 Logging 시스템 구축하기 (or EKF의 ElasticSearch 사용하기)
CI/CD 파이프라인 구축
📌 전체 파이프라인 구성 개요
사용 구성요소:
- GitHub (App 코드 저장소)
- GitHub (K8s Manifest 저장소)
- Jenkins (CI 서버)
- Docker Hub (이미지 저장소)
- ArgoCD (CD 도구)
- Kubernetes Cluster (서비스 배포 대상)
GitHub 레포지터리 구성
가상의 Developer를 가정하여 FullStack로 서비스를 개발할 때
각각의 파트가 깃 허브에 업로드 될 시 Webhook으로 감지할 수 있도록 환경을 구성하고
인가된 개발자가 접근 가능 할 수 있도록 구성
Developer가 git에 업데이트하였을 시 자동으로 감지하는 Webhook를 구성하고
그를 통해 이미지를 빌드하도록 한다
Source : 소스코드
Menifest : 쿠버네티스 YAML
Webhook 설정

Jenkins <-> GIt 통신을 위한 PAT 설정
Jenkins를 통해 git에 Push pull 등을 진행하기 위해 Passwd 암호방식을 사용하지 못하게 되었으며
이에 대한 해결 방법으로 PAT를 설정하여 토큰을 사용한다.

인가된 사용자 Git 접근 권한 부여

Jenkins 서버 구축
Jenkins Server는 AWS 클라우드 환경이 아닌 온프레미스 환경에 구축을 통해 빌드, 업로드 과정에 웹 서비스에
영향을 최소화 하고 비용 절감이 가능하도록 하였다
Jenkins Server는 git에 업로드 된 내용을 감지하여 Docker 이미지 파일을 빌드하고 재배포를 진행한다.
온프레미스 환경 내 Jenkins는 공인IP를 가지지 않고 내부 환경에서 NAT를 통해 외부와 통신하기에
Ngrok. , Flask를 컨테이너화 하여 Webhook을 가능하도록 구성
설정 주요사항
젠킨스 설치
# Jenkins 공식 저장소 설정 파일을 /etc/yum.repos.d/에 다운로드
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
# Jenkins 패키지 서명 키를 시스템에 등록 (패키지 무결성 확인용)
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
# Jenkins가 의존하는 fontconfig 라이브러리 설치 (그래픽/폰트 처리용)
sudo yum install fontconfig
# Jenkins 실행을 위한 Java 17 설치 (Amazon Corretto는 AWS에서 제공하는 OpenJDK)
sudo dnf -y install java-17-amazon-corretto
# Jenkins 패키지 설치 (이전 단계에서 설정한 저장소에서 설치됨)
sudo yum install jenkins
# systemd 데몬을 다시 로드 (새로운 서비스 유닛 파일을 인식하도록)
sudo systemctl daemon-reload
젠킨스 내 도커를 사용하기 위해 도커 설치
dnf -y install dnf-plugins-core
dnf config-manger --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compos-plugin
systemctl enable --now docker
PipeLine 구성 진행 시 Jenkinsfile을 통해 일련의 작업을 명시
방법 2
젠킨스 도커 컨테이너화
docker run -d -p 8080:8080 --name jenkins -v /home/jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -u root jenkins/jenkins:lts
docker exec jenkins apt update
docker exe jenkins apt install -y docker.io
젠킨스 <-> Git 통신 연동
PipeLine 구성 진행 시 Jenkinsfile을 통해 일련의 작업을 명시하며 Credential 지정을 통해 Github 정보를 다운로드
Credentials -> [ADD] -> [Jenkins]

pipeline{
agent any
environment {
dockerHubRegistry = '[Docker Hub Registry 명]'
dockerHubRegistryCredential = 'docker-hub'
githubCredential = 'k8s_manifest_git'
}
stages {
stage('check out application git branch'){
steps {
checkout scm
}
post {
failure {
echo 'repository checkout failure'
}
success {
echo 'repository checkout success'
}
}
}
stage('docker image build'){
steps{
sh "docker build -t ${dockerHubRegistry}/react:${currentBuild.number} ./frontend"
sh "docker build -t ${dockerHubRegistry}/react:latest ./frontend"
sh "docker build -t ${dockerHubRegistry}/nodejs:${currentBuild.number} ./backend"
sh "docker build -t ${dockerHubRegistry}/nodejs:latest ./backend"
sh "docker build -t ${dockerHubRegistry}/db:${currentBuild.number} ./mysql"
sh "docker build -t ${dockerHubRegistry}/db:latest ./mysql"
}
post {
failure {
echo 'Docker image build failure !'
}
success {
echo 'Docker image build success !'
}
}
}
stage('Docker Image Push') {
steps {
withDockerRegistry([ credentialsId: dockerHubRegistryCredential, url: "" ]) {
sh "docker push ${dockerHubRegistry}/react:${currentBuild.number}"
sh "docker push ${dockerHubRegistry}/react:latest"
sh "docker push ${dockerHubRegistry}/nodejs:${currentBuild.number}"
sh "docker push ${dockerHubRegistry}/nodejs:latest"
sh "docker push ${dockerHubRegistry}/db:${currentBuild.number}"
sh "docker push ${dockerHubRegistry}/db:latest"
sleep 10 /* Wait uploading */
}
}
post {
failure {
echo 'Docker Image Push failure !'
sh "docker rmi ${dockerHubRegistry}/react:${currentBuild.number}"
sh "docker rmi ${dockerHubRegistry}/react:latest"
sh "docker rmi ${dockerHubRegistry}/nodejs:${currentBuild.number}"
sh "docker rmi ${dockerHubRegistry}/nodejs:latest"
sh "docker rmi ${dockerHubRegistry}/db:${currentBuild.number}"
sh "docker rmi ${dockerHubRegistry}/db:latest"
}
success {
echo 'Docker image push success !'
sh "docker rmi ${dockerHubRegistry}/react:${currentBuild.number}"
sh "docker rmi ${dockerHubRegistry}/react:latest"
sh "docker rmi ${dockerHubRegistry}/nodejs:${currentBuild.number}"
sh "docker rmi ${dockerHubRegistry}/nodejs:latest"
sh "docker rmi ${dockerHubRegistry}/db:${currentBuild.number}"
sh "docker rmi ${dockerHubRegistry}/db:latest"
}
}
}
stage('K8S Manifest Update') {
steps {
sh "ls"
sh 'mkdir -p gitOpsRepo'
dir("gitOpsRepo")
{
git branch: "main",
credentialsId: githubCredential,
url: '[GitHub Repository Https Clone Url]'
sh "git config --global user.email '[GitHub Email]'"
sh "git config --global user.name '[GitHub Name]'"
sh "sed -i 's/react:.*\$/react:${currentBuild.number}/g' web-deployment.yaml"
sh "sed -i 's/nodejs:.*\$/nodejs:${currentBuild.number}/g' api-deployment.yaml"
sh "sed -i 's/db:.*\$/mysql:${currentBuild.number}/g' mysql-deployment.yaml"
sh "git add ."
sh "git commit -m '[UPDATE] k8s ${currentBuild.number} image versioning'"
withCredentials([gitUsernamePassword(credentialsId: githubCredential,
gitToolName: 'git-tool')]) {
sh "git remote set-url origin [GitHub Repository URL]"
sh "git push -u origin main"
}
}
}
post {
failure {
echo 'K8S Manifest Update failure !'
}
success {
echo 'K8S Manifest Update success !'
}
}
}
}
}
Jenkins 외부 통신 Server 구축
클라우드 내 비용을 절감하고 보안성을 높이기 위해 DB Server와 Jenkins는 온프레미스 환경에 구축하였다
온프레미스에 위치한 각 서버는 할당된 공인 IP가 아닌 내부 IP로 Nat를 통해 외부와 통신 하므로
ngrok 과 flask 패키지를 이용하여 jenkins 서버의 webhook을 수행 할 수 있도록 구성
ngrok 설치
# 다운로드
curl -LO https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
# 압축 해제
unzip ngrok-stable-linux-amd64.zip
# 실행 파일 이동
sudo mv ngrok /usr/local/bin
chmod +x /usr/local/bin/ngrok
flask 서버 준비
Flask 구성 python3 코드
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
data = request.json
print("Received webhook:", data)
# Jenkins Trigger
jenkins_url = "http://<jenkins-ip>:8080/job/<job-name>/build?token=<trigger-token>"
auth = ("jenkins-user", "jenkins-api-token") # 필요시
response = requests.post(jenkins_url, auth=auth)
print("Triggered Jenkins, Status Code:", response.status_code)
return "OK", 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Jenkins 작업 프로세스 및 빌드 구성
구성한 Jenkins, Docker 환경 을 통해 소스파일이 변경 시 자동으로 Jenkins를 통해 Docker hub로 이미지가 빌드되며
추후 빌드된 이미지를 활용하여 쿠버네티스를 통해 클라우드 내 서비스를 진행하는 인스턴스에서 사용이 가능해졌다.
아키텍쳐 구성도
[GitHub Webhook Event]
↓
[ngrok Public URL] ──> [Flask Server (Docker Container)]
↓
[Jenkins 서버의 Webhook Endpoint 호출]
↓
[Jenkins Pipeline Trigger 및 빌드 수행]
ArgoCD를 활용한 CD환경 구축
일전에 구성한 Jenkins 와 더불어 ArgoCD를 통해 클라우드 환경에 위치한 쿠버네티스에 서비스를 배포하여
kube-api를 통해 클러스터를 관리하고
서비스를 자동 배포 관리가 가능하도록 하고자 한다.
쿠버네티스 환경 구성
방법 1
EKS설치 (AWS)
#kubectl 설치
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.32.0/2024-12-20/bin/linux/amd64/kubectl
#eksctl 설치
ARCH=amd64
PLATFORM=$(uname -s)_$ARCH #환경 변수 설정
#패키지 설치
curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"
#압축해제 및 적용
tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
sudo mv /tmp/eksctl /usr/local/bin
-참고 : https://eksctl.io/installation
#클러스터 생성
eksctl create cluster --name CICD_FULL --region ap-northeast-2 --node-type t2.micro --nodes=2
방법 2
GKS 설치( Google)
#클러스터 생성
gcloud container clusters create my-cluster \
--zone=asia-northeast3-a \
--num-nodes=2 \
--machine-type=e2-medium \
--network=gke-vpc \
--subnetwork=gke-subnet \
--enable-ip-alias \
--project=nth-pier-459310-f1
#kubectl 클러스터 인증 연결
gcloud container clusters get-credentials my-cluster \
--zone asia-northeast3-a \
--project nth-pier-459310-f1
Helm 설치
#helm 설치
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
# ingress-nginx 설치
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
#helm 레포 추가
helm repo add eks https://aws.github.io/eks-charts
helm repo update
#필요한 서비스 계정 생성
kubectl create serviceaccount aws-load-balancer-controller -n kube-system
#Loadbalnacer controller 설치
helm upgrade aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--install \
--set clusterName=<클러스터 이름> \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller \
--set region=<AWS 리전> \
--set vpcId=<VPC ID> \
--set image.repository=602401143452.dkr.ecr.<AWS 리전>.amazonaws.com/amazon/aws-load-balancer-controller
#Flannel 설치
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
#사용자 계정 생성
kubectl create serviceaccount aws-load-balancer-controller -n kube-system
ArgoCD 환경 구성
#Argo 설치
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
#설치 확인
kubectl get all -n argocd
#Argo Pod 타입 변경 -> Loadbalancer
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
# ARGO 타입 확인
kubectl get svc argocd-server -n argocd

Argo 로그인 하기 위해서는 계정이 필요하다 ,
# argocd 초기 비밀번호 확인
ARGO_PWD=`kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d`
echo $ARGO_PWD
# argocd 비밀번호 변경 (생략 가능)
kubectl exec -it -n argocd deployment/argocd-server -- /bin/bash
argocd login localhost:8080
argocd account update-password
해당 코드를 통해 확인 후 입력

ArgoCD 를 통한 자동 통합 배포
자동화를 위한 어플리케이션 배포
1. APP 이름 지정
2. Name Space 자동 구성
3. Github 레포지터리 주소 및 경로 등록
4. Cluster URL = api-server 이므로 https://kubernetes.default.svc 입력 및 네임스페이스 default 등록
5. Kustomization 로 변경



모니터링 시스템 구축
Prometheus 와 Grafana를 사용해 모니터링 시스템을 구축하여 다음과 같은 감시를 진행하고자 한다.
- 클러스터 전체에 떠있는 Pod 개수 추적
- 클러스터 전체에서 각 Pod의 Memory, Cpu 상태
- Unhealty 상태에 있는 Pod 개수 추적
참고: 링크