Convert frontend to ArgoCD CI/CD

This commit is contained in:
Ubuntu 2025-06-19 10:04:35 +00:00
parent 537b5d40c7
commit 4900c86e72
3 changed files with 462 additions and 258 deletions

422
deployment/Jenkinsfile vendored
View File

@ -1,4 +1,5 @@
// deployment/Jenkinsfile // deployment/Jenkinsfile_ArgoCD
def PIPELINE_ID = "${env.BUILD_NUMBER}" def PIPELINE_ID = "${env.BUILD_NUMBER}"
def getImageTag() { def getImageTag() {
@ -13,148 +14,327 @@ podTemplate(
containers: [ containers: [
containerTemplate(name: 'node', image: 'node:20-slim', ttyEnabled: true, command: 'cat'), containerTemplate(name: 'node', image: 'node:20-slim', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true), containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true),
containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true), containerTemplate(name: 'git', image: 'alpine/git:latest', command: 'cat', ttyEnabled: true)
containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h')
], ],
volumes: [ volumes: [
emptyDirVolume(mountPath: '/root/.azure', memory: false),
emptyDirVolume(mountPath: '/run/podman', memory: false) emptyDirVolume(mountPath: '/run/podman', memory: false)
] ]
) { ) {
node(PIPELINE_ID) { node(PIPELINE_ID) {
def props def props
def imageTag = getImageTag() def imageTag = getImageTag()
def manifest = "deploy.yaml"
def namespace
stage("Get Source") { // Manifest Repository 설정
checkout scm def MANIFEST_REPO = 'https://github.com/won-ktds/smarketing-manifest.git'
def MANIFEST_CREDENTIAL_ID = 'github-credentials-smarketing'
// 환경변수 파일 확인 및 읽기 try {
if (!fileExists('deployment/deploy_env_vars')) { stage("Get Source") {
error "deployment/deploy_env_vars 파일이 없습니다!" checkout scm
// 환경변수 파일 확인 및 읽기
if (!fileExists('deployment/deploy_env_vars')) {
error "deployment/deploy_env_vars 파일이 없습니다!"
}
props = readProperties file: "deployment/deploy_env_vars"
// 필수 환경변수 검증
if (!props.registry || !props.image_org || !props.namespace) {
error "필수 환경변수가 누락되었습니다. registry, image_org, namespace를 확인하세요."
}
echo "=== Build Information ==="
echo "Service: smarketing-frontend"
echo "Image Tag: ${imageTag}"
echo "Registry: ${props.registry}"
echo "Image Org: ${props.image_org}"
echo "Namespace: ${props.namespace}"
} }
props = readProperties file: "deployment/deploy_env_vars" stage("Check Changes") {
namespace = "${props.namespace}" script {
def changes = sh(
script: "git diff --name-only HEAD~1 HEAD",
returnStdout: true
).trim()
// 필수 환경변수 검증 if (!changes.contains("src/") && !changes.contains("public/") && !changes.contains("package")) {
if (!props.registry || !props.image_org || !props.namespace) { echo "No significant frontend changes detected, skipping build"
error "필수 환경변수가 누락되었습니다. registry, image_org, namespace를 확인하세요." currentBuild.result = 'SUCCESS'
error("Stopping pipeline - no frontend changes detected")
}
echo "Frontend changes detected, proceeding with build"
}
} }
echo "Registry: ${props.registry}" stage('Build & Push Frontend Image') {
echo "Image Org: ${props.image_org}" container('podman') {
echo "Namespace: ${namespace}" sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2'
echo "Image Tag: ${imageTag}"
}
stage("Setup AKS") { withCredentials([usernamePassword(
container('azure-cli') { credentialsId: 'acr-credentials',
withCredentials([azureServicePrincipal('azure-credentials')]) { usernameVariable: 'ACR_USERNAME',
sh """ passwordVariable: 'ACR_PASSWORD'
az login --service-principal -u \$AZURE_CLIENT_ID -p \$AZURE_CLIENT_SECRET -t \$AZURE_TENANT_ID )]) {
az aks get-credentials --resource-group rg-digitalgarage-02 --name aks-digitalgarage-02 --overwrite-existing def imagePath = "${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}"
kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f -
sh """
echo "=========================================="
echo "Building smarketing-frontend"
echo "Image Tag: ${imageTag}"
echo "Image Path: ${imagePath}"
echo "=========================================="
# ACR 로그인
echo \$ACR_PASSWORD | podman login ${props.registry} --username \$ACR_USERNAME --password-stdin
# Docker 이미지 빌드
podman build \\
--build-arg PROJECT_FOLDER="." \\
--build-arg VUE_APP_AUTH_URL="${props.auth_url}" \\
--build-arg VUE_APP_MEMBER_URL="${props.member_url}" \\
--build-arg VUE_APP_STORE_URL="${props.store_url}" \\
--build-arg VUE_APP_MENU_URL="${props.menu_url}" \\
--build-arg VUE_APP_SALES_URL="${props.sales_url}" \\
--build-arg VUE_APP_CONTENT_URL="${props.content_url}" \\
--build-arg VUE_APP_RECOMMEND_URL="${props.recommend_url}" \\
--build-arg BUILD_FOLDER="deployment/container" \\
--build-arg EXPORT_PORT="${props.export_port}" \\
-f deployment/container/Dockerfile-smarketing-frontend \\
-t ${imagePath} .
# 이미지 푸시
podman push ${imagePath}
echo "✅ Frontend image pushed successfully: ${imagePath}"
"""
}
}
}
stage('Update Manifest Repository') {
container('git') {
script {
// Manifest Repository Clone
withCredentials([usernamePassword(
credentialsId: MANIFEST_CREDENTIAL_ID,
usernameVariable: 'GIT_USERNAME',
passwordVariable: 'GIT_PASSWORD'
)]) {
sh """
echo "=== Git 설정 ==="
git config --global user.name "Jenkins CI"
git config --global user.email "jenkins@company.com"
echo "=== Manifest Repository Clone ==="
rm -rf manifest-repo
git clone https://\$GIT_USERNAME:\$GIT_PASSWORD@github.com/won-ktds/smarketing-manifest.git manifest-repo
cd manifest-repo
"""
def fullImageName = "${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}"
def deploymentFile = "smarketing-frontend/deployments/frontend-deployment.yaml"
sh """
cd manifest-repo
echo "=== smarketing-frontend 이미지 태그 업데이트 ==="
if [ -f "${deploymentFile}" ]; then
# 이미지 태그 업데이트 (sed 사용)
sed -i 's|image: ${props.registry}/${props.image_org}/smarketing-frontend:.*|image: ${fullImageName}|g' "${deploymentFile}"
echo "Updated ${deploymentFile} with new image: ${fullImageName}"
# 변경사항 확인
echo "=== 변경된 내용 확인 ==="
grep "image: ${props.registry}/${props.image_org}/smarketing-frontend" "${deploymentFile}" || echo "이미지 태그 업데이트 확인 실패"
else
echo "Warning: ${deploymentFile} not found"
echo "Creating manifest directory structure..."
# 기본 구조 생성
mkdir -p smarketing-frontend/deployments
# 기본 deployment 파일 생성
cat > "${deploymentFile}" << EOF
---
apiVersion: v1
kind: ConfigMap
metadata:
name: smarketing-frontend-config
namespace: ${props.namespace}
data:
runtime-env.js: |
window.__runtime_config__ = {
AUTH_URL: '${props.auth_url}',
MEMBER_URL: '${props.member_url}',
STORE_URL: '${props.store_url}',
MENU_URL: '${props.menu_url}',
SALES_URL: '${props.sales_url}',
CONTENT_URL: '${props.content_url}',
RECOMMEND_URL: '${props.recommend_url}',
GATEWAY_URL: 'http://${props.ingress_host}',
ENV: 'production',
DEBUG: false
};
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: smarketing-frontend
namespace: ${props.namespace}
labels:
app: smarketing-frontend
spec:
replicas: ${props.replicas}
selector:
matchLabels:
app: smarketing-frontend
template:
metadata:
labels:
app: smarketing-frontend
spec:
imagePullSecrets:
- name: acr-secret
containers:
- name: smarketing-frontend
image: ${fullImageName}
imagePullPolicy: Always
ports:
- containerPort: ${props.export_port}
resources:
requests:
cpu: ${props.resources_requests_cpu}
memory: ${props.resources_requests_memory}
limits:
cpu: ${props.resources_limits_cpu}
memory: ${props.resources_limits_memory}
volumeMounts:
- name: runtime-config
mountPath: /usr/share/nginx/html/runtime-env.js
subPath: runtime-env.js
livenessProbe:
httpGet:
path: /health
port: ${props.export_port}
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: ${props.export_port}
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: runtime-config
configMap:
name: smarketing-frontend-config
---
apiVersion: v1
kind: Service
metadata:
name: smarketing-frontend-service
namespace: ${props.namespace}
labels:
app: smarketing-frontend
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: ${props.export_port}
protocol: TCP
name: http
selector:
app: smarketing-frontend
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: smarketing-frontend-ingress
namespace: ${props.namespace}
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: ${props.ingress_host}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: smarketing-frontend-service
port:
number: 80
EOF
echo "Created basic frontend-deployment.yaml"
fi
"""
sh """
cd manifest-repo
echo "=== Git 변경사항 확인 ==="
git status
git diff
# 변경사항이 있으면 커밋 및 푸시
if [ -n "\$(git status --porcelain)" ]; then
git add .
git commit -m "Update smarketing-frontend to ${imageTag} - Build ${env.BUILD_NUMBER}"
git push origin main
echo "✅ Successfully updated manifest repository"
else
echo " No changes to commit"
fi
"""
}
}
}
}
stage('Trigger ArgoCD Sync') {
script {
echo """
🎯 Frontend CI Pipeline 완료!
📦 빌드된 이미지:
- ${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}
🔄 ArgoCD 동작:
- ArgoCD가 manifest repository 변경사항을 자동으로 감지합니다
- smarketing-frontend Application이 새로운 이미지로 동기화됩니다
- ArgoCD UI에서 배포 상태를 모니터링하세요
🌐 ArgoCD UI: [ArgoCD 접속 URL]
📁 Manifest Repo: ${MANIFEST_REPO}
""" """
} }
} }
}
stage('Build & Push Image') { // 성공 시 처리
echo """
✅ Frontend CI Pipeline 성공!
🏷️ 새로운 이미지 태그: ${imageTag}
🔄 ArgoCD가 자동으로 배포를 시작합니다
"""
} catch (Exception e) {
// 실패 시 처리
echo "❌ Frontend CI Pipeline 실패: ${e.getMessage()}"
throw e
} finally {
// 정리 작업 (항상 실행)
container('podman') { container('podman') {
sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2' sh 'podman system prune -f || true'
withCredentials([usernamePassword(
credentialsId: 'acr-credentials',
usernameVariable: 'ACR_USERNAME',
passwordVariable: 'ACR_PASSWORD'
)]) {
def imagePath = "${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}"
sh """
echo "=========================================="
echo "Building smarketing-frontend"
echo "Image Tag: ${imageTag}"
echo "Image Path: ${imagePath}"
echo "=========================================="
# ACR 로그인
echo \$ACR_PASSWORD | podman login ${props.registry} --username \$ACR_USERNAME --password-stdin
# Docker 이미지 빌드
podman build \\
--build-arg PROJECT_FOLDER="." \\
--build-arg VUE_APP_AUTH_URL="${props.auth_url}" \\
--build-arg VUE_APP_MEMBER_URL="${props.member_url}" \\
--build-arg VUE_APP_STORE_URL="${props.store_url}" \\
--build-arg VUE_APP_MENU_URL="${props.menu_url}" \\
--build-arg VUE_APP_SALES_URL="${props.sales_url}" \\
--build-arg VUE_APP_CONTENT_URL="${props.content_url}" \\
--build-arg VUE_APP_RECOMMEND_URL="${props.recommend_url}" \\
--build-arg BUILD_FOLDER="deployment/container" \\
--build-arg EXPORT_PORT="${props.export_port}" \\
-f deployment/container/Dockerfile-smarketing-frontend \\
-t ${imagePath} .
# 이미지 푸시
podman push ${imagePath}
echo "Image pushed successfully: ${imagePath}"
"""
}
}
}
stage('Generate & Apply Manifest') {
container('envsubst') {
def imagePath = "${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}"
sh """
export namespace=${namespace}
export smarketing_frontend_image_path=${imagePath}
export replicas=${props.replicas}
export export_port=${props.export_port}
export ingress_host=${props.ingress_host}
export resources_requests_cpu=${props.resources_requests_cpu}
export resources_requests_memory=${props.resources_requests_memory}
export resources_limits_cpu=${props.resources_limits_cpu}
export resources_limits_memory=${props.resources_limits_memory}
# API URLs도 export (혹시 사용할 수도 있으니)
export auth_url=${props.auth_url}
export member_url=${props.member_url}
export store_url=${props.store_url}
export menu_url=${props.menu_url}
export sales_url=${props.sales_url}
export content_url=${props.content_url}
export recommend_url=${props.recommend_url}
echo "=== 환경변수 확인 ==="
echo "namespace: \$namespace"
echo "ingress_host: \$ingress_host"
echo "export_port: \$export_port"
echo "========================="
envsubst < deployment/${manifest}.template > deployment/${manifest}
echo "Generated manifest file:"
cat deployment/${manifest}
"""
}
container('azure-cli') {
sh """
kubectl apply -f deployment/${manifest}
echo "Waiting for deployment to be ready..."
kubectl -n ${namespace} wait --for=condition=available deployment/smarketing-frontend --timeout=300s
echo "Deployment completed successfully!"
kubectl -n ${namespace} get pods -l app=smarketing-frontend
kubectl -n ${namespace} get svc smarketing-frontend-service
kubectl -n ${namespace} get ingress
"""
} }
sh 'rm -rf manifest-repo || true'
} }
} }
} }

View File

@ -1,136 +0,0 @@
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: podman
image: quay.io/podman/stable:latest
command:
- cat
tty: true
securityContext:
privileged: true
- name: git
image: alpine/git:latest
command:
- cat
tty: true
"""
}
}
environment {
imageTag = sh(script: "echo ${BUILD_NUMBER}", returnStdout: true).trim()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Load Deploy Variables') {
steps {
script {
// deploy_env_vars 파일에서 환경변수 로드
if (fileExists('deploy_env_vars')) {
def envVars = readFile('deploy_env_vars').trim()
envVars.split('\n').each { line ->
if (line.contains('=')) {
def (key, value) = line.split('=', 2)
env."${key}" = value
echo "Loaded: ${key} = ${value}"
}
}
} else {
error "deploy_env_vars 파일이 없습니다!"
}
// 이미지 경로 설정
env.imagePath = "${env.REGISTRY}/${env.IMAGE_ORG}/smarketing-frontend:${imageTag}"
echo "Image Path: ${env.imagePath}"
}
}
}
stage('Build & Push Image') {
steps {
container('podman') {
script {
sh """
podman build \\
--build-arg PROJECT_FOLDER="." \\
--build-arg REACT_APP_AUTH_URL="${env.AUTH_URL}" \\
--build-arg REACT_APP_MEMBER_URL="${env.MEMBER_URL}" \\
--build-arg REACT_APP_STORE_URL="${env.STORE_URL}" \\
--build-arg REACT_APP_CONTENT_URL="${env.CONTENT_URL}" \\
--build-arg REACT_APP_RECOMMEND_URL="${env.RECOMMEND_URL}" \\
--build-arg BUILD_FOLDER="deployment/container" \\
--build-arg EXPORT_PORT="${env.EXPORT_PORT}" \\
-f deployment/container/Dockerfile-smarketing-frontend \\
-t ${env.imagePath} .
podman push ${env.imagePath}
"""
}
}
}
}
stage('Update Manifest Repository') {
steps {
container('git') {
withCredentials([usernamePassword(
credentialsId: 'github-credentials-${env.TEAMID}',
usernameVariable: 'GIT_USERNAME',
passwordVariable: 'GIT_PASSWORD'
)]) {
sh """
# Git 설정
git config --global user.name "Jenkins"
git config --global user.email "jenkins@company.com"
# Manifest Repository Clone
git clone https://\$GIT_USERNAME:\$GIT_PASSWORD@github.com/${env.GITHUB_ORG}/smarketing-manifest.git
cd smarketing-manifest
# Frontend 이미지 태그 업데이트
echo "Updating smarketing-frontend deployment with image tag: ${imageTag}"
if [ -f "smarketing-frontend/deployment.yaml" ]; then
# 기존 이미지 태그를 새 태그로 교체
sed -i "s|image: ${env.REGISTRY}/${env.IMAGE_ORG}/smarketing-frontend:.*|image: ${env.imagePath}|g" smarketing-frontend/deployment.yaml
echo "Updated frontend deployment file:"
cat smarketing-frontend/deployment.yaml | grep "image:"
else
echo "Warning: Frontend deployment file not found"
fi
# 변경사항 커밋 및 푸시
git add .
git commit -m "Update smarketing-frontend image tag to ${imageTag}" || echo "No changes to commit"
git push origin main
"""
}
}
}
}
}
post {
always {
cleanWs()
}
success {
echo "✅ smarketing-frontend 이미지 빌드 및 Manifest 업데이트가 완료되었습니다!"
}
failure {
echo "❌ smarketing-frontend CI/CD 파이프라인 중 오류가 발생했습니다."
}
}
}

View File

@ -0,0 +1,160 @@
// deployment/Jenkinsfile
def PIPELINE_ID = "${env.BUILD_NUMBER}"
def getImageTag() {
def dateFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmss')
def currentDate = new Date()
return dateFormat.format(currentDate)
}
podTemplate(
label: "${PIPELINE_ID}",
serviceAccount: 'jenkins',
containers: [
containerTemplate(name: 'node', image: 'node:20-slim', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'podman', image: "mgoltzsche/podman", ttyEnabled: true, command: 'cat', privileged: true),
containerTemplate(name: 'azure-cli', image: 'hiondal/azure-kubectl:latest', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'envsubst', image: "hiondal/envsubst", command: 'sleep', args: '1h')
],
volumes: [
emptyDirVolume(mountPath: '/root/.azure', memory: false),
emptyDirVolume(mountPath: '/run/podman', memory: false)
]
) {
node(PIPELINE_ID) {
def props
def imageTag = getImageTag()
def manifest = "deploy.yaml"
def namespace
stage("Get Source") {
checkout scm
// 환경변수 파일 확인 및 읽기
if (!fileExists('deployment/deploy_env_vars')) {
error "deployment/deploy_env_vars 파일이 없습니다!"
}
props = readProperties file: "deployment/deploy_env_vars"
namespace = "${props.namespace}"
// 필수 환경변수 검증
if (!props.registry || !props.image_org || !props.namespace) {
error "필수 환경변수가 누락되었습니다. registry, image_org, namespace를 확인하세요."
}
echo "Registry: ${props.registry}"
echo "Image Org: ${props.image_org}"
echo "Namespace: ${namespace}"
echo "Image Tag: ${imageTag}"
}
stage("Setup AKS") {
container('azure-cli') {
withCredentials([azureServicePrincipal('azure-credentials')]) {
sh """
az login --service-principal -u \$AZURE_CLIENT_ID -p \$AZURE_CLIENT_SECRET -t \$AZURE_TENANT_ID
az aks get-credentials --resource-group rg-digitalgarage-02 --name aks-digitalgarage-02 --overwrite-existing
kubectl create namespace ${namespace} --dry-run=client -o yaml | kubectl apply -f -
"""
}
}
}
stage('Build & Push Image') {
container('podman') {
sh 'podman system service -t 0 unix:///run/podman/podman.sock & sleep 2'
withCredentials([usernamePassword(
credentialsId: 'acr-credentials',
usernameVariable: 'ACR_USERNAME',
passwordVariable: 'ACR_PASSWORD'
)]) {
def imagePath = "${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}"
sh """
echo "=========================================="
echo "Building smarketing-frontend"
echo "Image Tag: ${imageTag}"
echo "Image Path: ${imagePath}"
echo "=========================================="
# ACR 로그인
echo \$ACR_PASSWORD | podman login ${props.registry} --username \$ACR_USERNAME --password-stdin
# Docker 이미지 빌드
podman build \\
--build-arg PROJECT_FOLDER="." \\
--build-arg VUE_APP_AUTH_URL="${props.auth_url}" \\
--build-arg VUE_APP_MEMBER_URL="${props.member_url}" \\
--build-arg VUE_APP_STORE_URL="${props.store_url}" \\
--build-arg VUE_APP_MENU_URL="${props.menu_url}" \\
--build-arg VUE_APP_SALES_URL="${props.sales_url}" \\
--build-arg VUE_APP_CONTENT_URL="${props.content_url}" \\
--build-arg VUE_APP_RECOMMEND_URL="${props.recommend_url}" \\
--build-arg BUILD_FOLDER="deployment/container" \\
--build-arg EXPORT_PORT="${props.export_port}" \\
-f deployment/container/Dockerfile-smarketing-frontend \\
-t ${imagePath} .
# 이미지 푸시
podman push ${imagePath}
echo "Image pushed successfully: ${imagePath}"
"""
}
}
}
stage('Generate & Apply Manifest') {
container('envsubst') {
def imagePath = "${props.registry}/${props.image_org}/smarketing-frontend:${imageTag}"
sh """
export namespace=${namespace}
export smarketing_frontend_image_path=${imagePath}
export replicas=${props.replicas}
export export_port=${props.export_port}
export ingress_host=${props.ingress_host}
export resources_requests_cpu=${props.resources_requests_cpu}
export resources_requests_memory=${props.resources_requests_memory}
export resources_limits_cpu=${props.resources_limits_cpu}
export resources_limits_memory=${props.resources_limits_memory}
# API URLs도 export (혹시 사용할 수도 있으니)
export auth_url=${props.auth_url}
export member_url=${props.member_url}
export store_url=${props.store_url}
export menu_url=${props.menu_url}
export sales_url=${props.sales_url}
export content_url=${props.content_url}
export recommend_url=${props.recommend_url}
echo "=== 환경변수 확인 ==="
echo "namespace: \$namespace"
echo "ingress_host: \$ingress_host"
echo "export_port: \$export_port"
echo "========================="
envsubst < deployment/${manifest}.template > deployment/${manifest}
echo "Generated manifest file:"
cat deployment/${manifest}
"""
}
container('azure-cli') {
sh """
kubectl apply -f deployment/${manifest}
echo "Waiting for deployment to be ready..."
kubectl -n ${namespace} wait --for=condition=available deployment/smarketing-frontend --timeout=300s
echo "Deployment completed successfully!"
kubectl -n ${namespace} get pods -l app=smarketing-frontend
kubectl -n ${namespace} get svc smarketing-frontend-service
kubectl -n ${namespace} get ingress
"""
}
}
}
}