Introduce Kubernetes manifests for backend, frontend, ingress, storage, and namespace setup under k8s/. Add Gitea Actions workflow for automated build and deployment to Kubernetes. Provide deployment and cluster setup guides in docs/ and project root. Update .gitignore to exclude Kubernetes secret files.
328 lines
12 KiB
YAML
328 lines
12 KiB
YAML
# Gitea Actions Workflow - vexplor 자동 배포
|
|
#
|
|
# 환경 변수:
|
|
# - GITEA_DOMAIN: g.wace.me
|
|
# - HARBOR_REGISTRY: harbor.wace.me
|
|
# - K8S_NAMESPACE: vexplor
|
|
#
|
|
# 필수 Secrets (Repository Settings > Secrets):
|
|
# - HARBOR_USERNAME: Harbor 사용자명
|
|
# - HARBOR_PASSWORD: Harbor 비밀번호
|
|
# - KUBECONFIG: base64로 인코딩된 Kubernetes config
|
|
#
|
|
# Application Secrets:
|
|
# - k8s/vexplor-secret.yaml 파일에서 관리
|
|
|
|
name: Deploy vexplor
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- master
|
|
paths:
|
|
- "backend-node/**"
|
|
- "frontend/**"
|
|
- "docker/**"
|
|
- "k8s/**"
|
|
- ".gitea/workflows/deploy.yml"
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
GITEA_DOMAIN: g.wace.me
|
|
HARBOR_REGISTRY: localhost:5001
|
|
HARBOR_REGISTRY_K8S: 192.168.1.100:5001
|
|
HARBOR_REGISTRY_EXTERNAL: harbor.wace.me
|
|
HARBOR_PROJECT: speefox_vexplor
|
|
K8S_NAMESPACE: vexplor
|
|
|
|
# Frontend 빌드 환경 변수
|
|
NEXT_PUBLIC_API_URL: "https://api.vexplor.com/api"
|
|
NEXT_PUBLIC_ENV: "production"
|
|
INTERNAL_API_URL: "http://vexplor-backend-service:3001"
|
|
|
|
# Frontend 설정
|
|
FRONTEND_IMAGE_NAME: vexplor-frontend
|
|
FRONTEND_DEPLOYMENT_NAME: vexplor-frontend
|
|
FRONTEND_CONTAINER_NAME: vexplor-frontend
|
|
FRONTEND_BUILD_CONTEXT: frontend
|
|
FRONTEND_DOCKERFILE_PATH: docker/deploy/frontend.Dockerfile
|
|
|
|
# Backend 설정
|
|
BACKEND_IMAGE_NAME: vexplor-backend
|
|
BACKEND_DEPLOYMENT_NAME: vexplor-backend
|
|
BACKEND_CONTAINER_NAME: vexplor-backend
|
|
BACKEND_BUILD_CONTEXT: backend-node
|
|
BACKEND_DOCKERFILE_PATH: docker/deploy/backend.Dockerfile
|
|
|
|
jobs:
|
|
build-and-deploy:
|
|
runs-on: ubuntu-24.04
|
|
|
|
steps:
|
|
# 작업 디렉토리 정리
|
|
- name: Clean workspace
|
|
run: |
|
|
echo "작업 디렉토리 정리..."
|
|
cd /workspace
|
|
rm -rf source
|
|
mkdir -p source
|
|
echo "정리 완료"
|
|
|
|
# 필수 도구 설치
|
|
- name: Install required tools
|
|
run: |
|
|
echo "필수 도구 설치 중..."
|
|
apt-get update -qq
|
|
apt-get install -y git curl ca-certificates gnupg
|
|
|
|
# kubectl 설치
|
|
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
|
chmod +x kubectl
|
|
mv kubectl /usr/local/bin/
|
|
|
|
# Docker 클라이언트 설치
|
|
install -m 0755 -d /etc/apt/keyrings
|
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
|
chmod a+r /etc/apt/keyrings/docker.asc
|
|
|
|
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" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
|
|
apt-get update -qq
|
|
apt-get install -y docker-ce-cli
|
|
|
|
echo "설치 완료:"
|
|
git --version
|
|
kubectl version --client
|
|
docker --version
|
|
|
|
export DOCKER_HOST=unix:///var/run/docker.sock
|
|
docker version || echo "소켓 연결 대기 중..."
|
|
|
|
# 저장소 체크아웃
|
|
- name: Checkout code
|
|
run: |
|
|
echo "저장소 체크아웃..."
|
|
cd /workspace/source
|
|
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} \
|
|
https://oauth2:${{ github.token }}@${GITEA_DOMAIN}/${GITHUB_REPOSITORY}.git .
|
|
|
|
echo "체크아웃 완료"
|
|
git log -1 --oneline
|
|
|
|
# 빌드 환경 설정
|
|
- name: Set up build environment
|
|
run: |
|
|
IMAGE_TAG="v$(date +%Y%m%d-%H%M%S)-${GITHUB_SHA::7}"
|
|
echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_ENV
|
|
|
|
# Frontend 이미지
|
|
echo "FRONTEND_FULL_IMAGE=${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${FRONTEND_IMAGE_NAME}" >> $GITHUB_ENV
|
|
echo "FRONTEND_FULL_IMAGE_K8S=${HARBOR_REGISTRY_K8S}/${HARBOR_PROJECT}/${FRONTEND_IMAGE_NAME}" >> $GITHUB_ENV
|
|
|
|
# Backend 이미지
|
|
echo "BACKEND_FULL_IMAGE=${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${BACKEND_IMAGE_NAME}" >> $GITHUB_ENV
|
|
echo "BACKEND_FULL_IMAGE_K8S=${HARBOR_REGISTRY_K8S}/${HARBOR_PROJECT}/${BACKEND_IMAGE_NAME}" >> $GITHUB_ENV
|
|
|
|
echo "빌드 태그: ${IMAGE_TAG}"
|
|
|
|
# Harbor 로그인
|
|
- name: Login to Harbor
|
|
env:
|
|
HARBOR_USER: ${{ secrets.HARBOR_USERNAME }}
|
|
HARBOR_PASS: ${{ secrets.HARBOR_PASSWORD }}
|
|
run: |
|
|
echo "Harbor 로그인..."
|
|
export DOCKER_HOST=unix:///var/run/docker.sock
|
|
echo "${HARBOR_PASS}" | docker login ${HARBOR_REGISTRY} \
|
|
--username ${HARBOR_USER} \
|
|
--password-stdin
|
|
echo "Harbor 로그인 완료"
|
|
|
|
# Backend 빌드 및 푸시
|
|
- name: Build and Push Backend image
|
|
run: |
|
|
echo "Backend 이미지 빌드 및 푸시..."
|
|
export DOCKER_HOST=unix:///var/run/docker.sock
|
|
cd /workspace/source
|
|
|
|
docker build \
|
|
-t ${BACKEND_FULL_IMAGE}:${IMAGE_TAG} \
|
|
-t ${BACKEND_FULL_IMAGE}:latest \
|
|
-f ${BACKEND_DOCKERFILE_PATH} \
|
|
${BACKEND_BUILD_CONTEXT}
|
|
|
|
docker push ${BACKEND_FULL_IMAGE}:${IMAGE_TAG}
|
|
docker push ${BACKEND_FULL_IMAGE}:latest
|
|
echo "Backend 푸시 완료"
|
|
|
|
# Frontend 빌드 및 푸시
|
|
- name: Build and Push Frontend image
|
|
run: |
|
|
echo "Frontend 이미지 빌드 및 푸시..."
|
|
export DOCKER_HOST=unix:///var/run/docker.sock
|
|
cd /workspace/source
|
|
|
|
echo "빌드 환경 변수:"
|
|
echo " NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}"
|
|
echo " NEXT_PUBLIC_ENV=${NEXT_PUBLIC_ENV}"
|
|
|
|
docker build \
|
|
-t ${FRONTEND_FULL_IMAGE}:${IMAGE_TAG} \
|
|
-t ${FRONTEND_FULL_IMAGE}:latest \
|
|
-f ${FRONTEND_DOCKERFILE_PATH} \
|
|
--build-arg NEXT_PUBLIC_API_URL="${NEXT_PUBLIC_API_URL}" \
|
|
${FRONTEND_BUILD_CONTEXT}
|
|
|
|
docker push ${FRONTEND_FULL_IMAGE}:${IMAGE_TAG}
|
|
docker push ${FRONTEND_FULL_IMAGE}:latest
|
|
echo "Frontend 푸시 완료"
|
|
|
|
# Kubernetes 설정
|
|
- name: Setup Kubernetes config
|
|
env:
|
|
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
|
run: |
|
|
echo "Kubernetes 설정..."
|
|
|
|
if [ -z "${KUBECONFIG_CONTENT}" ]; then
|
|
echo "KUBECONFIG secret이 설정되지 않았습니다!"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p ~/.kube
|
|
echo "${KUBECONFIG_CONTENT}" | base64 -d > ~/.kube/config
|
|
chmod 600 ~/.kube/config
|
|
|
|
if [ ! -s ~/.kube/config ]; then
|
|
echo "kubeconfig 파일이 비어있습니다"
|
|
exit 1
|
|
fi
|
|
|
|
echo "kubeconfig 파일 생성 완료"
|
|
kubectl cluster-info > /dev/null 2>&1 && echo "Kubernetes 클러스터 연결 성공"
|
|
|
|
# Kubernetes 배포
|
|
- name: Deploy to Kubernetes
|
|
run: |
|
|
echo "Kubernetes 배포 시작..."
|
|
|
|
cd /workspace/source
|
|
|
|
# 네임스페이스 생성 (없을 때만)
|
|
echo "네임스페이스 확인..."
|
|
kubectl apply -f k8s/namespace.yaml
|
|
|
|
# ConfigMap 적용
|
|
echo "ConfigMap 적용..."
|
|
kubectl apply -f k8s/vexplor-config.yaml -n ${K8S_NAMESPACE}
|
|
|
|
# Secret 적용 (존재하는 경우에만)
|
|
if [ -f k8s/vexplor-secret.yaml ]; then
|
|
echo "Secret 적용..."
|
|
kubectl apply -f k8s/vexplor-secret.yaml -n ${K8S_NAMESPACE}
|
|
fi
|
|
|
|
# Harbor Registry Secret 생성 (없을 때만)
|
|
echo "Harbor Registry Secret 확인..."
|
|
if ! kubectl get secret harbor-registry -n ${K8S_NAMESPACE} > /dev/null 2>&1; then
|
|
echo "Harbor Registry Secret 생성 중..."
|
|
kubectl create secret docker-registry harbor-registry \
|
|
--docker-server=${HARBOR_REGISTRY_K8S} \
|
|
--docker-username=${{ secrets.HARBOR_USERNAME }} \
|
|
--docker-password=${{ secrets.HARBOR_PASSWORD }} \
|
|
-n ${K8S_NAMESPACE}
|
|
echo "Harbor Registry Secret 생성 완료"
|
|
else
|
|
echo "Harbor Registry Secret 이미 존재"
|
|
fi
|
|
|
|
# Backend 배포
|
|
echo "Backend 배포..."
|
|
kubectl apply -f k8s/vexplor-backend-deployment.yaml -n ${K8S_NAMESPACE}
|
|
|
|
if kubectl get deployment ${BACKEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE} > /dev/null 2>&1; then
|
|
echo "Backend 이미지 업데이트..."
|
|
kubectl set image deployment/${BACKEND_DEPLOYMENT_NAME} \
|
|
${BACKEND_CONTAINER_NAME}=${BACKEND_FULL_IMAGE_K8S}:latest \
|
|
-n ${K8S_NAMESPACE}
|
|
kubectl rollout restart deployment/${BACKEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE}
|
|
fi
|
|
|
|
echo "Backend Rolling Update 진행 중..."
|
|
kubectl rollout status deployment/${BACKEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE} --timeout=5m
|
|
echo "Backend 배포 완료"
|
|
|
|
# Frontend 배포
|
|
echo "Frontend 배포..."
|
|
kubectl apply -f k8s/vexplor-frontend-deployment.yaml -n ${K8S_NAMESPACE}
|
|
|
|
if kubectl get deployment ${FRONTEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE} > /dev/null 2>&1; then
|
|
echo "Frontend 이미지 업데이트..."
|
|
kubectl set image deployment/${FRONTEND_DEPLOYMENT_NAME} \
|
|
${FRONTEND_CONTAINER_NAME}=${FRONTEND_FULL_IMAGE_K8S}:latest \
|
|
-n ${K8S_NAMESPACE}
|
|
kubectl rollout restart deployment/${FRONTEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE}
|
|
fi
|
|
|
|
echo "Frontend Rolling Update 진행 중..."
|
|
kubectl rollout status deployment/${FRONTEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE} --timeout=5m
|
|
echo "Frontend 배포 완료"
|
|
|
|
# Ingress 배포
|
|
echo "Ingress 배포..."
|
|
kubectl apply -f k8s/vexplor-ingress.yaml -n ${K8S_NAMESPACE}
|
|
|
|
echo "전체 배포 완료!"
|
|
|
|
# 배포 검증
|
|
- name: Verify deployment
|
|
run: |
|
|
echo "배포 검증..."
|
|
echo ""
|
|
echo "Backend 상태:"
|
|
kubectl get deployment ${BACKEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE}
|
|
kubectl get pods -l app=${BACKEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE}
|
|
echo ""
|
|
echo "Frontend 상태:"
|
|
kubectl get deployment ${FRONTEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE}
|
|
kubectl get pods -l app=${FRONTEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE}
|
|
echo ""
|
|
echo "Services:"
|
|
kubectl get svc -n ${K8S_NAMESPACE}
|
|
echo ""
|
|
echo "Ingress:"
|
|
kubectl get ingress -n ${K8S_NAMESPACE}
|
|
echo ""
|
|
echo "검증 완료"
|
|
|
|
# 배포 요약
|
|
- name: Deployment summary
|
|
if: success()
|
|
run: |
|
|
echo "=========================================="
|
|
echo "배포가 성공적으로 완료되었습니다!"
|
|
echo "=========================================="
|
|
echo "빌드 버전: ${IMAGE_TAG}"
|
|
echo "Frontend: https://v1.vexplor.com"
|
|
echo "Backend API: https://api.vexplor.com"
|
|
echo "=========================================="
|
|
|
|
# 실패 시 롤백
|
|
- name: Rollback on failure
|
|
if: failure()
|
|
run: |
|
|
echo "배포 실패! 이전 버전으로 롤백..."
|
|
kubectl rollout undo deployment/${BACKEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE} || true
|
|
kubectl rollout undo deployment/${FRONTEND_DEPLOYMENT_NAME} -n ${K8S_NAMESPACE} || true
|
|
|
|
# Harbor 로그아웃
|
|
- name: Logout from Harbor
|
|
if: always()
|
|
run: |
|
|
export DOCKER_HOST=unix:///var/run/docker.sock
|
|
docker logout ${HARBOR_REGISTRY} || true
|
|
|