백엔드 서비스 AKS 배포 및 설정 완료

- Kubernetes 매니페스트 파일 생성 (7개 서비스)
  * user-service, event-service, ai-service, content-service
  * participation-service, analytics-service, distribution-service
  * 공통 리소스: Ingress, ConfigMap, Secret, ImagePullSecret

- analytics-service 배포 문제 해결
  * Hibernate PostgreSQL dialect 추가
  * DB 자격증명 수정 (eventuser/Hi5Jessica!)
  * analytics_db 데이터베이스 생성

- content-service Probe 경로 수정
  * Context path 포함 (/api/v1/content/actuator/health)

- distribution-service 신규 배포
  * Docker 이미지 빌드 및 ACR 푸시
  * K8s 매니페스트 생성 및 배포
  * Ingress 경로 추가 (/distribution)

- Gradle bootJar 설정 추가
  * 5개 서비스에 archiveFileName 설정

- 배포 가이드 문서 추가
  * deployment/k8s/deploy-k8s-guide.md
  * claude/deploy-k8s-back.md
  * deployment/container/build-image.md 업데이트

배포 완료: 모든 백엔드 서비스(7개) 정상 실행 중

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wonho
2025-10-29 10:59:09 +09:00
parent 23265b5849
commit df04f85346
57 changed files with 3478 additions and 353 deletions
+770
View File
@@ -0,0 +1,770 @@
# 백엔드 GitHub Actions 파이프라인 작성 가이드
[요청사항]
- GitHub Actions 기반 CI/CD 파이프라인 구축 가이드 작성
- 환경별(dev/staging/prod) Kustomize 매니페스트 관리 및 자동 배포 구현
- SonarQube 코드 품질 분석과 Quality Gate 포함
- Kustomize 매니페스트 생성부터 배포까지 전체 과정 안내
- '[결과파일]'에 구축 방법 및 파이프라인 작성 가이드 생성
- 아래 작업은 실제 수행하여 파일 생성
- Kustomize 디렉토리 구조 생성
- Base Kustomization 작성
- 환경별 Overlay 작성
- 환경별 Patch 파일 생성
- GitHub Actions 워크플로우 파일 작성
- 환경별 배포 변수 파일 작성
- 수동 배포 스크립트 작성
[작업순서]
- 사전 준비사항 확인
프롬프트의 '[실행정보]'섹션에서 아래정보를 확인
- {ACR_NAME}: Azure Container Registry 이름
- {RESOURCE_GROUP}: Azure 리소스 그룹명
- {AKS_CLUSTER}: AKS 클러스터명
- {NAMESPACE}: Namespace명
예시)
```
[실행정보]
- ACR_NAME: acrdigitalgarage01
- RESOURCE_GROUP: rg-digitalgarage-01
- AKS_CLUSTER: aks-digitalgarage-01
- NAMESPACE: phonebill-dg0500
```
- 시스템명과 서비스명 확인
settings.gradle에서 확인.
- {SYSTEM_NAME}: rootProject.name
- {SERVICE_NAMES}: include 'common'하위의 include문 뒤의 값임
예시) include 'common'하위의 서비스명들.
```
rootProject.name = 'phonebill'
include 'common'
include 'api-gateway'
include 'user-service'
include 'order-service'
include 'payment-service'
```
- JDK버전 확인
루트 build.gradle에서 JDK 버전 확인.
{JDK_VERSION}: 'java' 섹션에서 JDK 버전 확인. 아래 예에서는 21임.
```
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
```
- GitHub 저장소 환경 구성 안내
- GitHub Repository Secrets 설정
- Azure 접근 인증정보 설정
```
# Azure Service Principal
Repository Settings > Secrets and variables > Actions > Repository secrets에 등록
AZURE_CREDENTIALS:
{
"clientId": "{클라이언트ID}",
"clientSecret": "{클라이언트시크릿}",
"subscriptionId": "{구독ID}",
"tenantId": "{테넌트ID}"
}
예시)
{
"clientId": "5e4b5b41-7208-48b7-b821-d6d5acf50ecf",
"clientSecret": "ldu8Q~GQEzFYU.dJX7_QsahR7n7C2xqkIM6hqbV8",
"subscriptionId": "2513dd36-7978-48e3-9a7c-b221d4874f66",
"tenantId": "4f0a3bfd-1156-4cce-8dc2-a049a13dba23",
}
```
- ACR Credentials
Credential 구하는 방법 안내
az acr credential show --name {acr 이름}
예) az acr credential show --name acrdigitalgarage01
```
ACR_USERNAME: {ACR_NAME}
ACR_PASSWORD: {ACR패스워드}
```
- SonarQube URL과 인증 토큰
SONAR_HOST_URL 구하는 방법과 SONAR_TOKEN 작성법 안내
SONAR_HOST_URL: 아래 명령 수행 후 http://{External IP}를 지정
k get svc -n sonarqube
예) http://20.249.187.69
SONAR_TOKEN 값은 아래와 같이 작성
- SonarQube 로그인 후 우측 상단 'Administrator' > My Account 클릭
- Security 탭 선택 후 토큰 생성
```
SONAR_TOKEN: {SonarQube토큰}
SONAR_HOST_URL: {SonarQube서버URL}
```
- Docker Hub (Rate Limit 해결용)
Docker Hub 패스워드 작성 방법 안내
- DockerHub(https://hub.docker.com)에 로그인
- 우측 상단 프로필 아이콘 클릭 후 Account Settings를 선택
- 좌측메뉴에서 'Personal Access Tokens' 클릭하여 생성
```
DOCKERHUB_USERNAME: {Docker Hub 사용자명}
DOCKERHUB_PASSWORD: {Docker Hub 패스워드}
```
- GitHub Repository Variables 설정
```
# Workflow 제어 변수
Repository Settings > Secrets and variables > Actions > Variables > Repository variables에 등록
ENVIRONMENT: dev (기본값, 수동실행시 선택 가능: dev/staging/prod)
SKIP_SONARQUBE: true (기본값, 수동실행시 선택 가능: true/false)
```
**사용 방법:**
- **자동 실행**: Push/PR 시 기본값 사용 (ENVIRONMENT=dev, SKIP_SONARQUBE=true)
- **수동 실행**: Actions 탭 > "Backend Services CI/CD" > "Run workflow" 버튼 클릭
- Environment: dev/staging/prod 선택
- Skip SonarQube Analysis: true/false 선택
- Kustomize 디렉토리 구조 생성
- GitHub Actions 전용 Kustomize 디렉토리 생성
```bash
mkdir -p .github/kustomize/{base,overlays/{dev,staging,prod}}
mkdir -p .github/kustomize/base/{common,{서비스명1},{서비스명2},...}
mkdir -p .github/{config,scripts}
```
- 기존 k8s 매니페스트를 base로 복사
```bash
# 기존 deployment/k8s/* 파일들을 base로 복사
cp deployment/k8s/common/* .github/kustomize/base/common/
cp deployment/k8s/{서비스명}/* .github/kustomize/base/{서비스명}/
# 네임스페이스 하드코딩 제거
find .github/kustomize/base -name "*.yaml" -exec sed -i 's/namespace: .*//' {} \;
```
- Base Kustomization 작성
`.github/kustomize/base/kustomization.yaml` 파일 생성
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: {SYSTEM_NAME}-base
resources:
# Common resources
- common/configmap-common.yaml
- common/secret-common.yaml
- common/secret-imagepull.yaml
- common/ingress.yaml
# 각 서비스별 리소스
- {SERVICE_NAME}/deployment.yaml
- {SERVICE_NAME}/service.yaml
- {SERVICE_NAME}/configmap.yaml
- {SERVICE_NAME}/secret.yaml
images:
- name: {ACR_NAME}.azurecr.io/{SYSTEM_NAME}/{SERVICE_NAME}
newTag: latest
```
- 환경별 Patch 파일 생성
각 환경별로 필요한 patch 파일들을 생성합니다.
**중요원칙**:
- **base 매니페스트에 없는 항목은 추가 안함**
- **base 매니페스트와 항목이 일치해야 함**
- Secret 매니페스트에 'data'가 아닌 'stringData'사용
**1. ConfigMap Common Patch 파일 생성**
`.github/kustomize/overlays/{ENVIRONMENT}/cm-common-patch.yaml`
- base 매니페스트를 환경별로 복사
```
cp .github/kustomize/base/common/cm-common.yaml .github/kustomize/overlays/{ENVIRONMENT}/cm-common-patch.yaml
```
- SPRING_PROFILES_ACTIVE를 환경에 맞게 설정 (dev/staging/prod)
- DDL_AUTO 설정: dev는 "update", staging/prod는 "validate"
- JWT 토큰 유효시간은 prod에서 보안을 위해 짧게 설정
**2. Secret Common Patch 파일 생성**
`.github/kustomize/overlays/{ENVIRONMENT}/secret-common-patch.yaml`
- base 매니페스트를 환경별로 복사
```
cp .github/kustomize/base/common/secret-common.yaml .github/kustomize/overlays/{ENVIRONMENT}/secret-common-patch.yaml
```
**3. Ingress Patch 파일 생성**
`.github/kustomize/overlays/{ENVIRONMENT}/ingress-patch.yaml`
- base의 ingress.yaml을 환경별로 오버라이드
- **⚠️ 중요**: 개발환경 Ingress Host의 기본값은 base의 ingress.yaml과 **정확히 동일하게** 함
- base에서 `host: {SYSTEM_NAME}-api.20.214.196.128.nip.io` 이면
- dev에서도 `host: {SYSTEM_NAME}-api.20.214.196.128.nip.io` 로 동일하게 설정
- **절대** `{SYSTEM_NAME}-dev-api.xxx` 처럼 변경하지 말 것
- Staging/Prod 환경별 도메인 설정: {SYSTEM_NAME}.도메인 형식
- service name을 '{서비스명}'으로 함.
- Staging/prod 환경은 HTTPS 강제 적용 및 SSL 인증서 설정
- staging/prod는 nginx.ingress.kubernetes.io/ssl-redirect: "true"
- dev는 nginx.ingress.kubernetes.io/ssl-redirect: "false"
**4. deployment Patch 파일 생성** ⚠️ **중요**
각 서비스별로 별도 파일 생성
`.github/kustomize/overlays/{ENVIRONMENT}/deployment-{SERVICE_NAME}-patch.yaml`
**필수 포함 사항:**
- ✅ **replicas 설정**: 각 서비스별 Deployment의 replica 수를 환경별로 설정
- dev: 모든 서비스 1 replica (리소스 절약)
- staging: 모든 서비스 2 replicas
- prod: 모든 서비스 3 replicas
- ✅ **resources 설정**: 각 서비스별 Deployment의 resources를 환경별로 설정
- dev: requests(256m CPU, 256Mi Memory), limits(1024m CPU, 1024Mi Memory)
- staging: requests(512m CPU, 512Mi Memory), limits(2048m CPU, 2048Mi Memory)
- prod: requests(1024m CPU, 1024Mi Memory), limits(4096m CPU, 4096Mi Memory)
**5. Secret Service Patch 파일 생성**
각 서비스별로 별도 파일 생성
`.github/kustomize/overlays/{ENVIRONMENT}/secret-{SERVICE_NAME}-patch.yaml`
- base 매니페스트를 환경별로 복사
```
cp .github/kustomize/base/{SERVICE_NAME}/secret-{SERVICE_NAME}.yaml .github/kustomize/overlays/{ENVIRONMENT}/secret-{SERVICE_NAME}-patch.yaml
```
- 환경별 데이터베이스 연결 정보로 수정
- **⚠️ 중요**: 패스워드 등 민감정보는 실제 환경 구축 시 별도 설정
- 환경별 Overlay 작성
각 환경별로 `overlays/{환경}/kustomization.yaml` 생성
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: {NAMESPACE}
resources:
- ../../base
patches:
- path: cm-common-patch.yaml
target:
kind: ConfigMap
name: cm-common
- path: deployment-{SERVICE_NAME}-patch.yaml
target:
kind: Deployment
name: {SERVICE_NAME}
- path: ingress-patch.yaml
target:
kind: Ingress
name: {SYSTEM_NAME}
- path: secret-common-patch.yaml
target:
kind: Secret
name: secret-common
- path: secret-{SERVICE_NAME}-patch.yaml
target:
kind: Secret
name: secret-{SERVICE_NAME}
images:
- name: {ACR_NAME}.azurecr.io/{SYSTEM_NAME}/{SERVICE_NAME}
newTag: {ENVIRONMENT}-latest
```
- GitHub Actions 워크플로우 작성
`.github/workflows/backend-cicd.yaml` 파일 생성 방법을 안내합니다.
주요 구성 요소:
- **Build & Test**: Gradle 기반 빌드 및 단위 테스트
- **SonarQube Analysis**: 코드 품질 분석 및 Quality Gate
- **Container Build & Push**: 환경별 이미지 태그로 빌드 및 푸시
- **Kustomize Deploy**: 환경별 매니페스트 적용
```yaml
name: Backend Services CI/CD
on:
push:
branches: [ main, develop ]
paths:
- '{서비스명1}/**'
- '{서비스명2}/**'
- '{서비스명3}/**'
- '{서비스명N}/**'
- 'common/**'
- '.github/**'
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
ENVIRONMENT:
description: 'Target environment'
required: true
default: 'dev'
type: choice
options:
- dev
- staging
- prod
SKIP_SONARQUBE:
description: 'Skip SonarQube Analysis'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
env:
REGISTRY: ${{ secrets.REGISTRY }}
IMAGE_ORG: ${{ secrets.IMAGE_ORG }}
RESOURCE_GROUP: ${{ secrets.RESOURCE_GROUP }}
AKS_CLUSTER: ${{ secrets.AKS_CLUSTER }}
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.set_outputs.outputs.image_tag }}
environment: ${{ steps.set_outputs.outputs.environment }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up JDK {버전}
uses: actions/setup-java@v3
with:
java-version: '{JDK버전}'
distribution: 'temurin'
cache: 'gradle'
- name: Determine environment
id: determine_env
run: |
# Use input parameter or default to 'dev'
ENVIRONMENT="${{ github.event.inputs.ENVIRONMENT || 'dev' }}"
echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT
- name: Load environment variables
id: env_vars
run: |
ENV=${{ steps.determine_env.outputs.environment }}
# Initialize variables with defaults
REGISTRY="{ACR_NAME}.azurecr.io"
IMAGE_ORG="{SYSTEM_NAME}"
RESOURCE_GROUP="{RESOURCE_GROUP}"
AKS_CLUSTER="{AKS_CLUSTER}"
NAMESPACE="{NAMESPACE}"
# Read environment variables from .github/config file
if [[ -f ".github/config/deploy_env_vars_${ENV}" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
# Skip comments and empty lines
[[ "$line" =~ ^#.*$ ]] && continue
[[ -z "$line" ]] && continue
# Extract key-value pairs
key=$(echo "$line" | cut -d '=' -f1)
value=$(echo "$line" | cut -d '=' -f2-)
# Override defaults if found in config
case "$key" in
"resource_group") RESOURCE_GROUP="$value" ;;
"cluster_name") AKS_CLUSTER="$value" ;;
esac
done < ".github/config/deploy_env_vars_${ENV}"
fi
# Export for other jobs
echo "REGISTRY=$REGISTRY" >> $GITHUB_ENV
echo "IMAGE_ORG=$IMAGE_ORG" >> $GITHUB_ENV
echo "RESOURCE_GROUP=$RESOURCE_GROUP" >> $GITHUB_ENV
echo "AKS_CLUSTER=$AKS_CLUSTER" >> $GITHUB_ENV
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: |
./gradlew build -x test
- name: SonarQube Analysis & Quality Gate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: |
# Check if SonarQube should be skipped
SKIP_SONARQUBE="${{ github.event.inputs.SKIP_SONARQUBE || 'true' }}"
if [[ "$SKIP_SONARQUBE" == "true" ]]; then
echo "⏭️ Skipping SonarQube Analysis (SKIP_SONARQUBE=$SKIP_SONARQUBE)"
exit 0
fi
# Define services array
services=({SERVICE_NAME1} {SERVICE_NAME2} {SERVICE_NAME3} {SERVICE_NAMEN})
# Run tests, coverage reports, and SonarQube analysis for each service
for service in "${services[@]}"; do
./gradlew :$service:test :$service:jacocoTestReport :$service:sonar \
-Dsonar.projectKey={SYSTEM_NAME}-$service-${{ steps.determine_env.outputs.environment }} \
-Dsonar.projectName={SYSTEM_NAME}-$service-${{ steps.determine_env.outputs.environment }} \
-Dsonar.host.url=$SONAR_HOST_URL \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.java.binaries=build/classes/java/main \
-Dsonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml \
-Dsonar.exclusions=**/config/**,**/entity/**,**/dto/**,**/*Application.class,**/exception/**
done
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: app-builds
path: |
{SERVICE_NAME1}/build/libs/*.jar
{SERVICE_NAME2}/build/libs/*.jar
{SERVICE_NAME3}/build/libs/*.jar
{SERVICE_NAMEN}/build/libs/*.jar
- name: Set outputs
id: set_outputs
run: |
# Generate timestamp for image tag
IMAGE_TAG=$(date +%Y%m%d%H%M%S)
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
echo "environment=${{ steps.determine_env.outputs.environment }}" >> $GITHUB_OUTPUT
release:
name: Build and Push Docker Images
needs: build
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: app-builds
- name: Set environment variables from build job
run: |
echo "REGISTRY=${{ needs.build.outputs.registry }}" >> $GITHUB_ENV
echo "IMAGE_ORG=${{ needs.build.outputs.image_org }}" >> $GITHUB_ENV
echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV
echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub (prevent rate limit)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to Azure Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push Docker images for all services
run: |
# Define services array
services=({SERVICE_NAME1} {SERVICE_NAME2} {SERVICE_NAME3} {SERVICE_NAMEN})
# Build and push each service image
for service in "${services[@]}"; do
echo "Building and pushing $service..."
docker build \
--build-arg BUILD_LIB_DIR="$service/build/libs" \
--build-arg ARTIFACTORY_FILE="$service.jar" \
-f deployment/container/Dockerfile-backend \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }} .
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/$service:${{ needs.build.outputs.environment }}-${{ needs.build.outputs.image_tag }}
done
deploy:
name: Deploy to Kubernetes
needs: [build, release]
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set image tag environment variable
run: |
echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV
echo "ENVIRONMENT=${{ needs.build.outputs.environment }}" >> $GITHUB_ENV
- name: Install Azure CLI
run: |
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Setup kubectl
uses: azure/setup-kubectl@v3
- name: Get AKS Credentials
run: |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.AKS_CLUSTER }} --overwrite-existing
- name: Create namespace
run: |
kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f -
- name: Install Kustomize
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
- name: Update Kustomize images and deploy
run: |
# 환경별 디렉토리로 이동
cd deployment/cicd/kustomize/overlays/${{ env.ENVIRONMENT }}
# 각 서비스별 이미지 태그 업데이트
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/api-gateway:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/user-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/bill-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/product-service:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_ORG }}/kos-mock:${{ env.ENVIRONMENT }}-${{ env.IMAGE_TAG }}
# 매니페스트 적용
kubectl apply -k .
- name: Wait for deployments to be ready
run: |
echo "Waiting for deployments to be ready..."
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-api-gateway --timeout=300s
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-user-service --timeout=300s
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-bill-service --timeout=300s
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-product-service --timeout=300s
kubectl -n ${{ env.NAMESPACE }} wait --for=condition=available deployment/${{ env.ENVIRONMENT }}-kos-mock --timeout=300s
```
- GitHub Actions 전용 환경별 설정 파일 작성
`.github/config/deploy_env_vars_{환경}` 파일 생성 방법
**.github/config/deploy_env_vars_dev**
```bash
# dev Environment Configuration
resource_group={RESOURCE_GROUP}
cluster_name={AKS_CLUSTER}
```
**.github/config/deploy_env_vars_staging**
```bash
# staging Environment Configuration
resource_group={RESOURCE_GROUP}
cluster_name={AKS_CLUSTER}
```
**.github/config/deploy_env_vars_prod**
```bash
# prod Environment Configuration
resource_group={RESOURCE_GROUP}
cluster_name={AKS_CLUSTER}
```
**참고**: Kustomize 방식에서는 namespace, replicas, resources 등은 kustomization.yaml과 patch 파일에서 관리됩니다.
- GitHub Actions 전용 수동 배포 스크립트 작성
`.github/scripts/deploy-actions.sh` 파일 생성:
```bash
#!/bin/bash
set -e
ENVIRONMENT=${1:-dev}
IMAGE_TAG=${2:-latest}
echo "🚀 Manual deployment starting..."
echo "Environment: $ENVIRONMENT"
echo "Image Tag: $IMAGE_TAG"
# Check if kustomize is installed
if ! command -v kustomize &> /dev/null; then
echo "Installing Kustomize..."
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
fi
# Load environment variables from .github/config
if [[ -f ".github/config/deploy_env_vars_${ENVIRONMENT}" ]]; then
source ".github/config/deploy_env_vars_${ENVIRONMENT}"
echo "✅ Environment variables loaded for $ENVIRONMENT"
else
echo "❌ Environment configuration file not found: .github/config/deploy_env_vars_${ENVIRONMENT}"
exit 1
fi
# Create namespace
echo "📝 Creating namespace {NAMESPACE}..."
kubectl create namespace {NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
# 환경별 이미지 태그 업데이트 (.github/kustomize 사용)
cd .github/kustomize/overlays/${ENVIRONMENT}
echo "🔄 Updating image tags..."
# 서비스 배열 정의
services=({SERVICE_NAME1} {SERVICE_NAME2} {SERVICE_NAME3} {SERVICE_NAMEN})
# 각 서비스별 이미지 태그 업데이트
for service in "${services[@]}"; do
kustomize edit set image {ACR_NAME}.azurecr.io/{SYSTEM_NAME}/$service:${ENVIRONMENT}-${IMAGE_TAG}
done
echo "🚀 Deploying to Kubernetes..."
# 배포 실행
kubectl apply -k .
echo "⏳ Waiting for deployments to be ready..."
# 서비스별 배포 상태 확인
for service in "${services[@]}"; do
kubectl rollout status deployment/${ENVIRONMENT}-$service -n {NAMESPACE} --timeout=300s
done
echo "🔍 Health check..."
# API Gateway Health Check (첫 번째 서비스가 API Gateway라고 가정)
GATEWAY_SERVICE=${services[0]}
GATEWAY_POD=$(kubectl get pod -n {NAMESPACE} -l app.kubernetes.io/name=${ENVIRONMENT}-$GATEWAY_SERVICE -o jsonpath='{.items[0].metadata.name}')
kubectl -n {NAMESPACE} exec $GATEWAY_POD -- curl -f http://localhost:8080/actuator/health || echo "Health check failed, but deployment completed"
echo "📋 Service Information:"
kubectl get pods -n {NAMESPACE}
kubectl get services -n {NAMESPACE}
kubectl get ingress -n {NAMESPACE}
echo "✅ GitHub Actions deployment completed successfully!"
```
- SonarQube 프로젝트 설정 방법 작성
- SonarQube에서 각 서비스별 프로젝트 생성
- Quality Gate 설정:
```
Coverage: >= 80%
Duplicated Lines: <= 3%
Maintainability Rating: <= A
Reliability Rating: <= A
Security Rating: <= A
```
- 롤백 방법 작성
- GitHub Actions에서 이전 버전으로 롤백:
```bash
# 이전 워크플로우 실행으로 롤백
1. GitHub > Actions > 성공한 이전 워크플로우 선택
2. Re-run all jobs 클릭
```
- kubectl을 이용한 롤백:
```bash
# 특정 버전으로 롤백
kubectl rollout undo deployment/{환경}-{서비스명} -n {NAMESPACE} --to-revision=2
# 롤백 상태 확인
kubectl rollout status deployment/{환경}-{서비스명} -n {NAMESPACE}
```
- 수동 스크립트를 이용한 롤백:
```bash
# 이전 안정 버전 이미지 태그로 배포
./deployment/cicd/scripts/deploy-actions.sh {환경} {이전태그}
```
[체크리스트]
GitHub Actions CI/CD 파이프라인 구축 작업을 누락 없이 진행하기 위한 체크리스트입니다.
## 📋 사전 준비 체크리스트
- [ ] settings.gradle에서 시스템명과 서비스명 확인 완료
- [ ] 실행정보 섹션에서 ACR명, 리소스 그룹, AKS 클러스터명 확인 완료
## 📂 GitHub Actions 전용 Kustomize 구조 생성 체크리스트
- [ ] 디렉토리 구조 생성: `.github/kustomize/{base,overlays/{dev,staging,prod}}`
- [ ] 서비스별 base 디렉토리 생성: `.github/kustomize/base/{common,{서비스명들}}`
- [ ] 기존 k8s 매니페스트를 base로 복사 완료
- [ ] **리소스 누락 방지 검증 완료**:
- [ ] `ls .github/kustomize/base/*/` 명령으로 모든 서비스 디렉토리의 파일 확인
- [ ] 각 서비스별 필수 파일 존재 확인 (deployment.yaml, service.yaml 필수)
- [ ] ConfigMap 파일 존재 시 `cm-{서비스명}.yaml` 명명 규칙 준수 확인
- [ ] Secret 파일 존재 시 `secret-{서비스명}.yaml` 명명 규칙 준수 확인
- [ ] Base kustomization.yaml 파일 생성 완료
- [ ] 모든 서비스의 deployment.yaml, service.yaml 포함 확인
- [ ] 존재하는 모든 ConfigMap 파일 포함 확인 (`cm-{서비스명}.yaml`)
- [ ] 존재하는 모든 Secret 파일 포함 확인 (`secret-{서비스명}.yaml`)
- [ ] **검증 명령어 실행 완료**:
- [ ] `kubectl kustomize .github/kustomize/base/` 정상 실행 확인
- [ ] 에러 메시지 없이 모든 리소스 출력 확인
## 🔧 GitHub Actions 전용 환경별 Overlay 구성 체크리스트
### 중요 체크 사항
- Base Kustomization에서 존재하지 않는 Secret 파일들 제거
### 공통 체크 사항
- **base 매니페스트에 없는 항목을 추가하지 않았는지 체크**
- **base 매니페스트와 항목이 일치 하는지 체크**
- Secret 매니페스트에 'data'가 아닌 'stringData'사용했는지 체크
- **⚠️ Kustomize patch 방법 변경**: `patchesStrategicMerge` → `patches` (target 명시)
### DEV 환경
- [ ] `.github/kustomize/overlays/dev/kustomization.yaml` 생성 완료
- [ ] `.github/kustomize/overlays/dev/cm-common-patch.yaml` 생성 완료 (dev 프로파일, update DDL)
- [ ] `.github/kustomize/overlays/dev/secret-common-patch.yaml` 생성 완료
- [ ] `.github/kustomize/overlays/dev/ingress-patch.yaml` 생성 완료 (**Host 기본값은 base의 ingress.yaml과 동일**)
- [ ] `.github/kustomize/overlays/dev/deployment-{서비스명}-patch.yaml` 생성 완료 (replicas, resources 지정)
- [ ] 각 서비스별 `.github/kustomize/overlays/dev/secret-{서비스명}-patch.yaml` 생성 완료
### STAGING 환경
- [ ] `.github/kustomize/overlays/staging/kustomization.yaml` 생성 완료
- [ ] `.github/kustomize/overlays/staging/cm-common-patch.yaml` 생성 완료 (staging 프로파일, validate DDL)
- [ ] `.github/kustomize/overlays/staging/secret-common-patch.yaml` 생성 완료
- [ ] `.github/kustomize/overlays/staging/ingress-patch.yaml` 생성 완료 (prod 도메인, HTTPS, SSL 인증서)
- [ ] `.github/kustomize/overlays/staging/deployment-{서비스명}-patch.yaml` 생성 완료 (replicas, resources 지정)
- [ ] 각 서비스별 `.github/kustomize/overlays/staging/secret-{서비스명}-patch.yaml` 생성 완료
### PROD 환경
- [ ] `.github/kustomize/overlays/prod/kustomization.yaml` 생성 완료
- [ ] `.github/kustomize/overlays/prod/cm-common-patch.yaml` 생성 완료 (prod 프로파일, validate DDL, 짧은 JWT)
- [ ] `.github/kustomize/overlays/prod/secret-common-patch.yaml` 생성 완료
- [ ] `.github/kustomize/overlays/prod/ingress-patch.yaml` 생성 완료 (prod 도메인, HTTPS, SSL 인증서)
- [ ] `.github/kustomize/overlays/prod/deployment-{서비스명}-patch.yaml` 생성 완료 (replicas, resources 지정)
- [ ] 각 서비스별 `.github/kustomize/overlays/prod/secret-{서비스명}-patch.yaml` 생성 완료
## ⚙️ GitHub Actions 설정 및 스크립트 체크리스트
- [ ] 환경별 설정 파일 생성: `.github/config/deploy_env_vars_{dev,staging,prod}`
- [ ] GitHub Actions 워크플로우 파일 `.github/workflows/backend-cicd.yaml` 생성 완료
- [ ] 워크플로우 주요 내용 확인
- Build, SonarQube, Docker Build & Push, Deploy 단계 포함
- JDK 버전 확인: `java-version: '{JDK버전}'`
- 변수 참조 문법 확인: `${{ needs.build.outputs.* }}` 사용
- 모든 서비스명이 실제 프로젝트 서비스명으로 치환되었는지 확인
- **환경 변수 SKIP_SONARQUBE 처리 확인**: 기본값 'true', 조건부 실행
- **플레이스홀더 사용 확인**: {ACR_NAME}, {SYSTEM_NAME}, {SERVICE_NAME} 등
- [ ] 수동 배포 스크립트 `.github/scripts/deploy-actions.sh` 생성 완료
- [ ] 스크립트 실행 권한 설정 완료 (`chmod +x .github/scripts/*.sh`)
[결과파일]
- 가이드: .github/actions-pipeline-guide.md
- GitHub Actions 워크플로우: .github/workflows/backend-cicd.yaml
- GitHub Actions 전용 Kustomize 매니페스트: .github/kustomize/*
- GitHub Actions 전용 환경별 설정 파일: .github/config/*
- GitHub Actions 전용 수동배포 스크립트: .github/scripts/deploy-actions.sh
+206
View File
@@ -0,0 +1,206 @@
# 백엔드 배포 가이드
[요청사항]
- 백엔드 서비스를 쿠버네티스에 배포하기 위한 매니페스트 파일 작성
- 매니페스트 파일 작성까지만 하고 실제 배포는 수행방법만 가이드
- '[결과파일]'에 수행한 명령어를 포함하여 배포 가이드 레포트 생성
[작업순서]
- 실행정보 확인
프롬프트의 '[실행정보]'섹션에서 아래정보를 확인
- {ACR명}: 컨테이너 레지스트리 이름
- {k8s명}: Kubernetes 클러스터 이름
- {네임스페이스}: 배포할 네임스페이스
- {파드수}: 생성할 파드수
- {리소스(CPU)}: 요청값/최대값
- {리소스(메모리)}: 요청값/최대값
예시)
```
[실행정보]
- ACR명: acrdigitalgarage01
- k8s명: aks-digitalgarage-01
- 네임스페이스: tripgen
- 파드수: 2
- 리소스(CPU): 256m/1024m
- 리소스(메모리): 256Mi/1024Mi
```
- 시스템명과 서비스명 확인
settings.gradle에서 확인.
- 시스템명: rootProject.name
- 서비스명: include 'common'하위의 include문 뒤의 값임
예시) include 'common'하위의 4개가 서비스명임.
```
rootProject.name = 'tripgen'
include 'common'
include 'user-service'
include 'location-service'
include 'ai-service'
include 'trip-service'
```
- 매니페스트 작성 주의사항
- namespace는 명시: {네임스페이스}값 이용
- Database와 Redis의 Host명은 Service 객체 이름으로 함
- 공통 Secret의 JWT_SECRET 값은 반드시 openssl명령으로 생성하여 지정
- 매니페스트 파일 안에 환경변수를 사용하지 말고 실제 값을 지정
예) host: "tripgen.${INGRESS_IP}.nip.io" => host: "tripgen.4.1.2.3.nip.io"
- Secret 매니페스트에서 'data' 대신 'stringData'를 사용
- 객체이름 네이밍룰
- 공통 ConfigMap: cm-common
- 공통 Secret: secret-common
- 서비스별 ConfigMap: cm-{서비스명}
- 서비스별 Secret: secret-{서비스명}
- Ingress: {시스템명}
- Service: {서비스명}
- Deployment: {서비스명}
- 공통 매니페스트 작성: deployment/k8s/common/ 디렉토리 하위에 작성
- Image Pull Secret 매니페스트 작성: secret-imagepull.yaml
- name: {시스템명}
- USERNAME과 PASSWORD을 아래 명령으로 구하여 매니페스트 파일 작성
```
USERNAME=$(az acr credential show -n ${ACR명} --query "username" -o tsv)
PASSWORD=$(az acr credential show -n ${ACR명} --query "passwords[0].value" -o tsv)
```
- USERNAME과 PASSWORD의 실제 값을 매니페스트에 지정
- Ingress 매니페스트 작성: ingress.yaml
- **중요**: Ingress Host는 반드시 아래 명령으로 실제 External IP를 확인하여 사용할 것.
{Ingress External IP}는 실제 확인한 EXTERNAL-IP값.
```
kubectl get svc ingress-nginx-controller -n ingress-nginx
```
출력 예시: EXTERNAL-IP 컬럼에서 실제 IP 확인 (예:20.214.196.128)
- ingressClassName: nginx
- host: {시스템명}-api.{Ingress External IP}.nip.io
**잘못된 예**: tripgen-api.임의IP.nip.io ❌
**올바른 예**: tripgen-api.20.214.196.128.nip.io ✅
- path: 각 서비스 별 Controller 클래스의 '@RequestMapping'과 클래스 내 메소드의 매핑정보를 읽어 지정
- pathType: Prefix
- backend.service.name: {서비스명}
- backend.service.port.number: 80
- **중요**: annotation에 'nginx.ingress.kubernetes.io/rewrite-target' 설정 절대 하지 말것.
- 공통 ConfigMap과 Secret 매니페스트 작성
- 각 서비스의 실행 프로파일({서비스명}/.run/{서비스명}.run.xml)을 읽어 공통된 환경변수를 추출.
- 보안이 필요한 환경변수(암호, 인증토큰 등)는 Secret 매니페스트로 작성: secret-common.yaml(name:cm-common)
- 그 외 일반 환경변수 매니페스트 작성: cm-common.yaml(name:secret-common)
- Redis HOST명은 IP가 아닌 Service 객체명으로 함.
아래 명령으로 'redis'가 포함된 서비스 객체를 찾고 'ClusterIP'유형인 서비스명을 Host명으로 사용
```
kubectl get svc | grep redis
```
- REDIS_DATABASE는 각 서비스별 ConfigMap에 지정
- 주의) Database는 공통 ConfigMap/Secret으로 작성 금지
- 공통 ConfigMap에 CORS_ALLOWED_ORIGINS 설정: 'http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://{시스템명}.{Ingress External IP}.nip.io'
- 서비스별 매니페스트 작성: deployment/k8s/{서비스명}/ 디렉토리 하위에 작성
- ConfigMap과 Secret 매니페스트 작성
- 각 서비스의 실행 프로파일({서비스명}/.run/{서비스명}.run.xml)을 읽어 환경변수를 추출.
- cm-common.yaml과 secret-common.yaml에 있는 공통 환경변수는 중복해서 작성하면 안됨
- 보안이 필요한 환경변수(암호, 인증토큰 등)는 Secret 매니페스트로 작성: secret-{서비스명}.yaml(name:cm-{서비스명})
- 그 외 일반 환경변수 매니페스트 작성: cm-{서비스명}.yaml(name:secret-{서비스명})
- Database HOST명은 IP가 아닌 Service 객체명으로 함.
아래 명령으로 '{서비스명}'과 'db'가 포함된 서비스 객체를 찾고 'ClusterIP'유형인 서비스명을 Host명으로 사용
```
kubectl get svc | grep {서비스명}
```
- REDIS_DATABASE는 실행 프로파일에 지정된 값으로 서비스별 ConfigMap에 지정
- Service 매니페스트 작성
- name: {서비스명}
- port: 80
- targetPort: 실행 프로파일의 SERVER_PORT값
- type: ClusterIP
- Deployment 매니페스트 작성
- name: {서비스명}
- replicas: {파드수}
- ImagePullPolicy: Always
- ImagePullSecrets: {시스템명}
- image: {ACR명}.azurecr.io/{시스템명}/{서비스명}:latest
- ConfigMap과 Secret은 'env'대신에 'envFrom'을 사용하여 지정
- envFrom:
- configMapRef: 공통 ConfigMap 'cm-common'과 각 서비스 ConfigMap 'cm-{서비스명}'을 지정
- secretRef: 공통 Secret 'secret-common'과 각 서비스 Secret 'secret-{서비스명}'을 지정
- resources:
- {리소스(CPU)}: 요청값/최대값
- {리소스(메모리)}: 요청값/최대값
- Probe:
- Startup Probe: Actuator '/actuator/health'로 지정
- Readiness Probe: Actuator '/actuator/health/rediness'로 지정
- Liveness Probe: Actuator '/actuator/health/liveness'로 지정
- initialDelaySeconds, periodSeconds, failureThreshold를 Probe에 맞게 적절히 지정
- 체크 리스트로 수행결과 검증: 반드시 수행하고 그 결과를 배포 가이드에 포함
- 객체이름 네이밍룰 준수 여부
- Redis Host명을 ClusterIP 타입의 Service 객체로 했는가?
'kubectl get svc | grep redis' 명령으로 재확인
- Database Host명을 ClusterIP타입의 Service 객체로 했는가?
'kubectl get svc | grep {서비스명}' 명령으로 재확인
- Secret 매니페스트에서 'data' 대신 'stringData'를 사용 했는가?
- JWT_SECRET을 openssl 명령으로 생성해서 지정했는가?
- 매니페스트 파일 안에 환경변수를 사용하지 않고 실제 값을 지정 했는가?
- Image Pull Secret에 USERNAME과 PASSWORD의 실제 값을 매니페스트에 지정 했는가?
- Image명이 '{ACR명}.azurecr.io/{시스템명}/{서비스명}:latest' 형식인지 재확인
- Ingress Controller External IP 확인 및 매니페스트에 반영 확인
kubectl get svc ingress-nginx-controller -n ingress-nginx
EXTERNAL-IP 컬럼의 실제 값이 ingress.yaml의 host에 정확하게 설정되었는지 재확인할 것
- Ingress 매니페스트의 각 서비스 backend.service.port.number와 Service 매니페스트의 port가 "80"으로 동일한가 ?
- Ingress의 path는 각 서비스 별 Controller 클래스의 '@RequestMapping'과 클래스 내 메소드의 매핑정보를 읽어 지정했는가?
- 보안이 필요한 환경변수는 Secret 매니페스트로 지정했는가?
- REDIS_DATABASE는 각 서비스마다 다르게 지정했는가?
- ConfigMap과 Secret은 'env'대신에 'envFrom'을 사용하였는가?
- (중요) 실행 프로파일 매핑 테이블로 누락된 환경변수 체크
- **필수**: 각 서비스의 실행 프로파일({서비스명}/.run/{서비스명}.run.xml)에 정의된 **전체 환경변수를 빠짐없이 체크**
- **체크 방법**:
1. 각 {서비스명}.run.xml 파일에서 `<entry key="환경변수명" value="값"/>` 형태로 정의된 **모든** 환경변수 추출
2. 추출된 환경변수 **전체**를 대상으로 매핑 테이블 작성 (일부만 하면 안됨)
3. 서비스명 | 환경변수 | 지정 객체명 | 환경변수값 컬럼으로 **전체 환경변수** 체크
- **매핑 테이블 예시** (전체 환경변수 기준):
```
user-service | SERVER_PORT | cm-user-service | 8081
user-service | DB_HOST | secret-user-service | user-db-service
user-service | DB_PASSWORD | secret-user-service | tripgen_user_123
user-service | REDIS_DATABASE | cm-user-service | 0
user-service | JWT_SECRET | secret-common | (base64 encoded)
user-service | CACHE_TTL | cm-user-service | 1800
location-service | SERVER_PORT | cm-location-service | 8082
location-service | GOOGLE_API_KEY | secret-location-service | (base64 encoded)
location-service | REDIS_DATABASE | cm-location-service | 1
ai-service | CLAUDE_API_KEY | secret-ai-service | (base64 encoded)
ai-service | SERVER_PORT | cm-ai-service | 8084
... (실행프로파일의 모든 환경변수 나열)
```
- **주의**: 일부 환경변수만 체크하면 누락 발생, 반드시 **실행프로파일 전체** 환경변수 대상으로 수행
- 누락된 환경변수가 발견되면 해당 ConfigMap/Secret에 추가
- 배포 가이드 작성
- 배포가이드 검증 결과
- 사전확인 방법 가이드
- Azure 로그인 상태 확인
```
az account show
```
- AKS Credential 확인:
```
kubectl cluster-info
```
- namespace 존재 확인
```
kubectl get ns {네임스페이스}
```
- 매니페스트 적용 가이드
```
kubectl apply -f deployment/k8s/common
kubectl apply -f deployment/k8s/{서비스명}
```
- 객체 생성 확인 가이드
[결과파일]
- 배포방법 가이드: deployment/k8s/deploy-k8s-guide.md
- 공통 매니페스트 파일: deployment/k8s/common/*
- 서비스별 매니페스트 파일: deployment/k8s/{서비스명}/*