GitOps 이전까지 쓸 배포 파이프라인과 Portal 모드 Chart를 추가한다
GitOps 저장소도 ArgoCD Application도 아직 없어, 그때까지 이 저장소가 push 방식 파이프라인(.gitea/workflows/)을 임시로 소유한다. 무엇을 포기하는지와 넘길 때 할 일은 deploy/README.md에 적었다. Chart는 portal과 bundles 두 배포 모델을 모두 렌더링한다. ADR-0013이 ADR-0007을 대체했으므로 운영은 portal이 기준이지만, bundles 경로를 언제 삭제할지는 아직 정하지 않았다. - .gitea/workflows/ci.yaml, deploy-openshift.yaml - deploy/ci/render-manifests.sh, deploy/examples/ - Chart: mode 분기, selectedDeployment/tier helper, imagePullSecrets, toolService.apiKeySecret 참조 - extension-points.md에 미결 항목 9~12 추가 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
82
.gitea/workflows/ci.yaml
Normal file
82
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
name: CI
|
||||
|
||||
# main push와 PR에서 같은 검증을 돌린다. 이 워크플로는 배포하지 않는다.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# 빌드·렌더링 도구는 러너에 설치하지 않고 컨테이너로 가져온다.
|
||||
# 러너에 JDK나 helm이 깔려 있는지에 파이프라인이 의존하지 않게 하기 위해서다.
|
||||
# 러너가 host 모드(docker가 러너 호스트에서 직접 도는 구성)라는 전제다.
|
||||
# 현재 .gitea/workflows/deploy.yaml이 호스트의 docker compose 스크립트를 부르므로 그 전제가 성립한다.
|
||||
JDK_IMAGE: eclipse-temurin:21-jdk
|
||||
HELM_IMAGE: alpine/helm:3.14.4
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# 컨테이너를 러너 사용자로 돌린다. root로 돌면 build/ 산출물이 root 소유가 되어
|
||||
# 다음 실행의 checkout이 그 디렉터리를 지우지 못한다.
|
||||
# ideaFormatCheck는 IDEA_FORMATTER가 없으면 경고만 남기고 건너뛴다.
|
||||
# 들여쓰기·줄바꿈은 CI에서 검증되지 않는다는 뜻이다(README).
|
||||
- name: Gradle check
|
||||
run: |
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-v "$PWD":/workspace -w /workspace \
|
||||
-e GRADLE_USER_HOME=/workspace/.gradle/ci-home \
|
||||
"$JDK_IMAGE" ./gradlew check --no-daemon
|
||||
|
||||
# HelmDeploymentContractTest는 values와 template의 정적 규칙만 본다.
|
||||
# helper 오류와 조건 분기 실수는 실제로 렌더링해야 드러난다.
|
||||
- name: Helm lint and template
|
||||
run: |
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-v "$PWD":/workspace -w /workspace \
|
||||
--entrypoint sh \
|
||||
"$HELM_IMAGE" deploy/ci/render-manifests.sh
|
||||
|
||||
# GitOps 저장소가 생기기 전까지, 이 산출물이 "무엇이 배포되는가"를 확인할 수 있는 유일한 형태다.
|
||||
- name: Upload rendered manifests
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: rendered-manifests
|
||||
path: build/rendered
|
||||
|
||||
image:
|
||||
runs-on: ubuntu
|
||||
needs: verify
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build image
|
||||
run: docker build -t "ax-hub-mcp-server:${{ github.sha }}" .
|
||||
|
||||
# registry가 아직 확정되지 않았으면 빌드까지만 하고 멈춘다.
|
||||
# 없는 secret 때문에 파이프라인 전체가 실패로 보이는 것을 막는다.
|
||||
# TODO: 사내 registry 확정 시 저장소 secret에 REGISTRY_HOST·REGISTRY_USER·REGISTRY_PASSWORD·IMAGE_REPOSITORY를 등록한다.
|
||||
- name: Push image
|
||||
env:
|
||||
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }}
|
||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
IMAGE_REPOSITORY: ${{ secrets.IMAGE_REPOSITORY }}
|
||||
IMAGE_TAG: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$REGISTRY_HOST" ] || [ -z "$IMAGE_REPOSITORY" ]; then
|
||||
echo "REGISTRY_HOST 또는 IMAGE_REPOSITORY secret이 없어 push를 건너뛴다."
|
||||
echo "이미지는 러너 로컬에만 있다: ax-hub-mcp-server:$IMAGE_TAG"
|
||||
exit 0
|
||||
fi
|
||||
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
|
||||
docker tag "ax-hub-mcp-server:$IMAGE_TAG" "$IMAGE_REPOSITORY:$IMAGE_TAG"
|
||||
docker push "$IMAGE_REPOSITORY:$IMAGE_TAG"
|
||||
echo "배포에 쓸 tag: $IMAGE_TAG"
|
||||
114
.gitea/workflows/deploy-openshift.yaml
Normal file
114
.gitea/workflows/deploy-openshift.yaml
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Deploy to OpenShift
|
||||
|
||||
# GitOps 저장소와 ArgoCD Application이 아직 없어 CD를 push 방식으로 돌린다.
|
||||
# 파이프라인이 클러스터에 직접 helm upgrade를 건다.
|
||||
#
|
||||
# 이 방식의 대가를 숨기지 않는다.
|
||||
# - 클러스터의 실제 상태가 저장소와 자동으로 맞춰지지 않는다. 누가 oc edit으로 고치면 그대로 남는다.
|
||||
# - 배포 이력이 Helm release history에만 남는다. git revert로 되돌릴 수 없다.
|
||||
# - 파이프라인이 클러스터 자격증명을 들고 있어야 한다.
|
||||
#
|
||||
# 그래서 자동 트리거를 두지 않고 사람이 값을 확인하고 실행한다.
|
||||
# GitOps 저장소가 준비되면 이 워크플로를 삭제하고, ArgoCD Application이 이 Chart를 당겨 가게 한다.
|
||||
# 그때까지의 인수인계 형태는 CI가 올리는 rendered-manifests 아티팩트다.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 배포 환경 (dev | test | prod)
|
||||
required: true
|
||||
default: dev
|
||||
mode:
|
||||
description: 배포 모델 (portal | bundles)
|
||||
required: true
|
||||
default: portal
|
||||
deploymentKey:
|
||||
description: mode=bundles일 때 설치할 배포 key. portal이면 비워 둔다
|
||||
required: false
|
||||
default: ""
|
||||
imageTag:
|
||||
description: 배포할 이미지 tag. CI가 push한 commit sha를 그대로 넣는다
|
||||
required: true
|
||||
|
||||
env:
|
||||
HELM_IMAGE: alpine/helm:3.14.4
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy
|
||||
env:
|
||||
ENVIRONMENT: ${{ github.event.inputs.environment }}
|
||||
MODE: ${{ github.event.inputs.mode }}
|
||||
DEPLOYMENT_KEY: ${{ github.event.inputs.deploymentKey }}
|
||||
IMAGE_TAG: ${{ github.event.inputs.imageTag }}
|
||||
IMAGE_REPOSITORY: ${{ secrets.IMAGE_REPOSITORY }}
|
||||
OCP_SERVER: ${{ secrets.OCP_SERVER }}
|
||||
OCP_TOKEN: ${{ secrets.OCP_TOKEN }}
|
||||
# 사내 CA가 서명한 API 인증서일 때 PEM 전체를 넣는다. 비어 있으면 러너의 신뢰 저장소를 쓴다.
|
||||
OCP_CA_CERT: ${{ secrets.OCP_CA_CERT }}
|
||||
OCP_NAMESPACE_DEV: ${{ secrets.OCP_NAMESPACE_DEV }}
|
||||
OCP_NAMESPACE_TEST: ${{ secrets.OCP_NAMESPACE_TEST }}
|
||||
OCP_NAMESPACE_PROD: ${{ secrets.OCP_NAMESPACE_PROD }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
CA_FILE=.ocp-ca.crt
|
||||
trap 'rm -f "$CA_FILE"' EXIT
|
||||
|
||||
case "$ENVIRONMENT" in
|
||||
dev) NAMESPACE=$OCP_NAMESPACE_DEV ;;
|
||||
test) NAMESPACE=$OCP_NAMESPACE_TEST ;;
|
||||
prod) NAMESPACE=$OCP_NAMESPACE_PROD ;;
|
||||
*) echo "environment는 dev|test|prod여야 한다: $ENVIRONMENT" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
for required in OCP_SERVER OCP_TOKEN IMAGE_REPOSITORY; do
|
||||
eval "value=\${$required}"
|
||||
if [ -z "$value" ]; then
|
||||
echo "$required secret이 없다. 플랫폼 담당자에게 발급받아 저장소 secret에 등록한다." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ -z "$NAMESPACE" ]; then
|
||||
echo "$ENVIRONMENT namespace secret이 없다." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# release 이름은 mode가 정한다. portal은 배포가 하나이고, bundles는 배포 key마다 하나다.
|
||||
if [ "$MODE" = "portal" ]; then
|
||||
RELEASE=axhub-mcp
|
||||
EXTRA=""
|
||||
else
|
||||
if [ -z "$DEPLOYMENT_KEY" ]; then
|
||||
echo "mode=bundles에는 deploymentKey가 필요하다." >&2
|
||||
exit 1
|
||||
fi
|
||||
RELEASE="$DEPLOYMENT_KEY-mcp"
|
||||
EXTRA="--set deploymentKey=$DEPLOYMENT_KEY"
|
||||
fi
|
||||
|
||||
# 사내 CA를 신뢰시키는 정상 경로다. TLS 검증을 끄는 스위치는 두지 않는다.
|
||||
if [ -n "$OCP_CA_CERT" ]; then
|
||||
printf '%s\n' "$OCP_CA_CERT" > "$CA_FILE"
|
||||
EXTRA="$EXTRA --kube-ca-file /workspace/$CA_FILE"
|
||||
fi
|
||||
|
||||
# token은 helm 인자로 들어간다. 컨테이너는 명령마다 새로 뜨고 바로 사라지지만,
|
||||
# 러너를 여러 팀이 공유하게 되면 kubeconfig 파일 방식으로 바꾼다.
|
||||
# --atomic: 실패하면 직전 revision으로 되돌린다. 반쯤 배포된 상태로 두지 않는다.
|
||||
docker run --rm \
|
||||
-v "$PWD":/workspace -w /workspace \
|
||||
"$HELM_IMAGE" upgrade --install "$RELEASE" deploy/helm/mcp-server \
|
||||
-f "deploy/helm/mcp-server/values-$ENVIRONMENT.yaml" \
|
||||
--namespace "$NAMESPACE" \
|
||||
--set "mode=$MODE" \
|
||||
$EXTRA \
|
||||
--set "image.repository=$IMAGE_REPOSITORY" \
|
||||
--set "image.tag=$IMAGE_TAG" \
|
||||
--kube-apiserver "$OCP_SERVER" \
|
||||
--kube-token "$OCP_TOKEN" \
|
||||
--atomic --timeout 10m
|
||||
Reference in New Issue
Block a user