-
Notifications
You must be signed in to change notification settings - Fork 1
feat: GitHub Actions 배포 워크플로우 Matrix 전략 도입 및 Blue-Green 배포 구조 개선 #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cd17320
d19bd11
378b663
377a329
b7c598e
ad2e1b0
7d7dad9
9690272
27f3059
8a6072e
89f3164
9a2bc77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| name: 'Deploy Module' | ||
| description: 'Build, push and deploy a module to server (dev/prod)' | ||
|
|
||
| inputs: | ||
| environment: | ||
| description: 'Environment (dev or prod)' | ||
| required: true | ||
| module: | ||
| description: 'Module name (apis, admin, batch)' | ||
| required: true | ||
| port: | ||
| description: 'Server port (for logging only - actual port defined in docker-compose.yml)' | ||
| required: false | ||
| default: 'N/A' | ||
| dockerhub-username: | ||
| description: 'Docker Hub username' | ||
| required: true | ||
| dockerhub-token: | ||
| description: 'Docker Hub token' | ||
| required: true | ||
| secret-properties: | ||
| description: 'Secret properties (dev or prod)' | ||
| required: true | ||
| apple-auth-key: | ||
| description: 'Apple Auth Key' | ||
| required: true | ||
| host: | ||
| description: 'Server host' | ||
| required: true | ||
| username: | ||
| description: 'Server username' | ||
| required: true | ||
| ssh-key: | ||
| description: 'Server SSH key' | ||
| required: true | ||
| ssh-port: | ||
| description: 'Server SSH port' | ||
| required: true | ||
| discord-webhook-url: | ||
| description: 'Discord webhook URL' | ||
| required: true | ||
| image-prefix: | ||
| description: 'Docker image prefix' | ||
| required: true | ||
| image-tag-type: | ||
| description: 'Image tag type (development-latest or semver)' | ||
| required: true | ||
| deploy-script: | ||
| description: 'Deploy script name (deploy-dev.sh or deploy-prod.sh)' | ||
| required: true | ||
| default: 'deploy-dev.sh' | ||
|
|
||
| runs: | ||
| using: 'composite' | ||
| steps: | ||
| - name: Inject application-secret.properties from Secrets | ||
| shell: bash | ||
| run: | | ||
| mkdir -p ./secret | ||
| echo "$SECRET_CONTENT" > ./secret/application-${{ inputs.environment }}-secret.properties | ||
| echo "$APPLE_KEY_CONTENT" > ./secret/AuthKey.p8 | ||
| chmod 600 ./secret/* | ||
| env: | ||
| SECRET_CONTENT: ${{ inputs.secret-properties }} | ||
| APPLE_KEY_CONTENT: ${{ inputs.apple-auth-key }} | ||
|
Comment on lines
+56
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🔴 기밀정보 보안: 런타임 시크릿 마운트로 마이그레이션 필수 현재 구현은 GitHub Actions 런너에 기밀정보 파일을 작성한 후 Docker 빌드 컨텍스트에 포함시켜 최종 이미지 레이어에 영구적으로 저장합니다. 빌드 인자와 환경 변수는 최종 이미지에 지속되므로 부적절하며, 대신 시크릿 마운트나 SSH 마운트를 사용해야 합니다. 필수 수정 사항:
secrets: |
"application_secret=${{ inputs.secret-properties }}"
"apple_auth_key=${{ inputs.apple-auth-key }}"
BuildKit은 tmpfs를 사용하여 시크릿을 임시 파일로 마운트하므로 이미지에 시크릿이 남지 않습니다. 시크릿 보안 문제를 검증하기 위해 필요한 정보를 수집하겠습니다. Length of output: 71 🏁 Script executed: #!/bin/bash
# Look for Dockerfile in the repository
echo "=== Finding Dockerfile ==="
fd -t f -i dockerfile | head -10Length of output: 37 🏁 Script executed: #!/bin/bash
# Search for docker/build-push-action usage
echo "=== Searching for docker/build-push-action usage ==="
rg -n "build-push-action" --type yaml -A 10 -B 2 | head -50Length of output: 52 🏁 Script executed: #!/bin/bash
# Check for secrets parameter in build-push-action or any secret handling
echo "=== Checking for secrets parameter in workflows ==="
rg -n "secrets:" --type yaml -A 5 -B 5 | head -80Length of output: 51 🏁 Script executed: #!/bin/bash
# Examine the specific action.yml file
echo "=== Full action.yml content ==="
cat ".github/actions/deploy-module/action.yml" 2>/dev/null || echo "File not found at standard path"Length of output: 6582 Dockerfile를 검증하여 시크릿 처리 방식을 확인하겠습니다. Length of output: 1310 🏁 Script executed: #!/bin/bash
# Search for secret-related patterns in Dockerfile
echo "=== Checking for ./secret references in Dockerfile ==="
grep -n "secret\|SECRET" ./Dockerfile || echo "No secret references found"Length of output: 124 🏁 Script executed: #!/bin/bash
# Check for RUN --mount=type=secret pattern
echo "=== Checking for secret mount patterns ==="
grep -n "mount=type=secret" ./Dockerfile || echo "No secret mount patterns found"Length of output: 73 🏁 Script executed: #!/bin/bash
# Check if .dockerignore exists and what it contains
echo "=== Checking .dockerignore ==="
cat ./.dockerignore 2>/dev/null || echo ".dockerignore not found"Length of output: 54 🔴 중대 보안 이슈: 시크릿이 Docker 이미지 레이어에 영구 저장됨 현재 구현은 시크릿 데이터가 최종 Docker 이미지에 영구적으로 저장되는 심각한 취약점을 가지고 있습니다. 검증 결과:
결과적으로 시크릿이 Docker Hub에 푸시된 이미지 레이어에 평문으로 저장됩니다. 필수 수정 사항:
|
||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Log in to Docker Hub | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| username: ${{ inputs.dockerhub-username }} | ||
| password: ${{ inputs.dockerhub-token }} | ||
|
|
||
| - name: Extract metadata for Docker | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: docker.io/${{ inputs.image-prefix }}-${{ inputs.module }} | ||
| tags: ${{ inputs.image-tag-type }} | ||
|
|
||
| - name: Build and push Docker image | ||
| id: build-and-push | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| platforms: linux/amd64 | ||
| push: true | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| cache-from: type=gha,scope=${{ inputs.module }} | ||
| cache-to: type=gha,mode=max,scope=${{ inputs.module }} | ||
move-hoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| build-args: | | ||
| MODULE=${{ inputs.module }} | ||
| - name: Deploy to Server | ||
| uses: appleboy/[email protected] | ||
| with: | ||
| host: ${{ inputs.host }} | ||
| username: ${{ inputs.username }} | ||
| key: ${{ inputs.ssh-key }} | ||
| port: ${{ inputs.ssh-port }} | ||
| script: | | ||
| export DOCKERHUB_USERNAME="${{ inputs.dockerhub-username }}" | ||
| export DOCKERHUB_TOKEN="${{ inputs.dockerhub-token }}" | ||
| export MODULE="${{ inputs.module }}" | ||
| export SPRING_PROFILE="${{ inputs.environment }}" | ||
| export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)" | ||
| cd ~/deploy | ||
| chmod +x ./${{ inputs.deploy-script }} | ||
| ./${{ inputs.deploy-script }} | ||
| - name: Send Discord notification on success (Development) | ||
| uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 | ||
| if: success() && inputs.environment == 'dev' | ||
| continue-on-error: true | ||
| shell: bash | ||
| with: | ||
| webhook-url: ${{ inputs.discord-webhook-url }} | ||
| embed-title: "✅ [${{ github.repository }}] Development Deploy Succeeded - ${{ inputs.module }}" | ||
| embed-description: | | ||
| **Module**: `${{ inputs.module }}` | ||
| **Commit**: `${{ github.sha }}` | ||
| **Author**: `${{ github.actor }}` | ||
| **Message**: `${{ github.event.head_commit.message }}` | ||
| [View Committed Changes](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | ||
| embed-color: 65280 | ||
|
|
||
| - name: Send Discord notification on success (Production) | ||
| uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 | ||
| if: success() && inputs.environment == 'prod' | ||
| continue-on-error: true | ||
| shell: bash | ||
| with: | ||
| webhook-url: ${{ inputs.discord-webhook-url }} | ||
| content: "🚀 **Production Deploy Succeeded!**" | ||
| embed-title: "✅ [${{ github.repository }}] Production Deploy Succeeded - ${{ inputs.module }}" | ||
| embed-description: | | ||
| **Module**: `${{ inputs.module }}` | ||
| **Deployed by**: `${{ github.actor }}` | ||
| The new version has been successfully deployed to production. | ||
| [View Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | ||
| embed-color: 65280 | ||
|
|
||
| - name: Send Discord notification on failure (Development) | ||
| uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 | ||
| if: failure() && inputs.environment == 'dev' | ||
| continue-on-error: true | ||
| shell: bash | ||
| with: | ||
| webhook-url: ${{ inputs.discord-webhook-url }} | ||
| embed-title: "❌ [${{ github.repository }}] Development Deploy Failed - ${{ inputs.module }}" | ||
| embed-description: | | ||
| **Module**: `${{ inputs.module }}` | ||
| **Commit**: `${{ github.sha }}` | ||
| **Author**: `${{ github.actor }}` | ||
| An error occurred during the workflow execution. | ||
| [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | ||
| embed-color: 16711680 | ||
|
|
||
| - name: Send Discord notification on failure (Production) | ||
| uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 | ||
| if: failure() && inputs.environment == 'prod' | ||
| continue-on-error: true | ||
| shell: bash | ||
| with: | ||
| webhook-url: ${{ inputs.discord-webhook-url }} | ||
| content: "🚨 **Production Deploy Failed!**" | ||
| embed-title: "❌ [${{ github.repository }}] Production Deploy Failed - ${{ inputs.module }}" | ||
| embed-description: | | ||
| **Module**: `${{ inputs.module }}` | ||
| **Deployed by**: `${{ github.actor }}` | ||
| An error occurred during the production deployment workflow. | ||
| [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | ||
| embed-color: 16711680 | ||
|
Comment on lines
+114
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Discord 알림 구성이 적절하며 선택적 개선 가능 환경별 성공/실패 알림이 명확하게 구분되어 있고, 선택사항: 코드 중복 감소 네 개의 Discord 알림 단계에서 상당한 중복이 있습니다. 조건문을 활용하여 단일 단계로 통합할 수 있습니다: - name: Send Discord notification
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: always()
continue-on-error: true
shell: bash
with:
webhook-url: ${{ inputs.discord-webhook-url }}
content: ${{ (success() && inputs.environment == 'prod') && '🚀 **Production Deploy Succeeded!**' || (failure() && inputs.environment == 'prod') && '🚨 **Production Deploy Failed!**' || '' }}
embed-title: |
${{ success() && '✅' || '❌' }} [${{ github.repository }}]
${{ inputs.environment == 'prod' && 'Production' || 'Development' }} Deploy
${{ success() && 'Succeeded' || 'Failed' }} - ${{ inputs.module }}
embed-description: |
**Module**: `${{ inputs.module }}`
...
embed-color: ${{ success() && 65280 || 16711680 }}그러나 현재 구현도 명확하고 유지보수하기 좋습니다. 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,96 +11,89 @@ concurrency: | |
|
|
||
| env: | ||
| REGISTRY: docker.io | ||
| IMAGE_NAME: ninecraft0523/ninecraft-server | ||
| MODULE: apis | ||
| IMAGE_PREFIX: ninecraft0523/ninecraft | ||
|
|
||
| jobs: | ||
| build-push-and-deploy: | ||
| detect-changes: | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 20 | ||
| environment: development | ||
|
|
||
| outputs: | ||
| apis: ${{ steps.filter.outputs.apis }} | ||
| # admin: ${{ steps.filter.outputs.admin }} # TODO: Uncomment when admin module is ready | ||
| batch: ${{ steps.filter.outputs.batch }} | ||
| any: ${{ steps.filter.outputs.any }} | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Inject application-secret.properties from Secrets | ||
| run: | | ||
| mkdir ./secret | ||
| echo "${{ secrets.DEV_SECRET_PROPERTIES }}" > ./secret/application-dev-secret.properties | ||
| echo "${{ secrets.APPLE_AUTH_KEY }}" > ./secret/AuthKey.p8 | ||
| chmod 600 ./secret/* | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Log in to Docker Hub | ||
| uses: docker/login-action@v3 | ||
| - name: Check changed files | ||
| uses: dorny/paths-filter@v3 | ||
| id: filter | ||
| with: | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
| filters: | | ||
| apis: | ||
| - 'apis/**' | ||
| - 'domain/**' | ||
| - 'infra/**' | ||
| - 'global-utils/**' | ||
| - 'observability/**' | ||
| # admin: # TODO: Uncomment when admin module is ready | ||
| # - 'admin/**' | ||
| # - 'domain/**' | ||
| # - 'infra/**' | ||
| # - 'global-utils/**' | ||
| # - 'observability/**' | ||
| batch: | ||
| - 'batch/**' | ||
| - 'domain/**' | ||
| - 'infra/**' | ||
| - 'global-utils/**' | ||
| - 'observability/**' | ||
move-hoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| any: | ||
| - 'apis/**' | ||
| # - 'admin/**' # TODO: Uncomment when admin module is ready | ||
| - 'batch/**' | ||
| - 'domain/**' | ||
| - 'infra/**' | ||
| - 'global-utils/**' | ||
| - 'observability/**' | ||
|
|
||
| - name: Extract metadata for Docker | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
| tags: | | ||
| type=raw,value=development-latest | ||
| build-push-and-deploy: | ||
| needs: detect-changes | ||
| if: needs.detect-changes.outputs.any == 'true' | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 20 | ||
| environment: development | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - module: apis | ||
| changed: ${{ needs.detect-changes.outputs.apis }} | ||
| # - module: admin # TODO: Uncomment when admin module is ready | ||
| # changed: ${{ needs.detect-changes.outputs.admin }} | ||
| - module: batch | ||
| changed: ${{ needs.detect-changes.outputs.batch }} | ||
|
|
||
| - name: Build and push Docker image | ||
| id: build-and-push | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| platforms: linux/amd64 | ||
| push: true | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| build-args: | | ||
| MODULE=${{ env.MODULE }} | ||
| steps: | ||
| - name: Checkout code | ||
| if: matrix.changed == 'true' | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Deploy to Development Server | ||
| uses: appleboy/[email protected] | ||
| - name: Deploy module | ||
| if: matrix.changed == 'true' | ||
| uses: ./.github/actions/deploy-module | ||
| with: | ||
| environment: dev | ||
| module: ${{ matrix.module }} | ||
| dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} | ||
| secret-properties: ${{ secrets.DEV_SECRET_PROPERTIES }} | ||
| apple-auth-key: ${{ secrets.APPLE_AUTH_KEY }} | ||
| host: ${{ secrets.DEV_HOST }} | ||
| username: ${{ secrets.DEV_USERNAME }} | ||
| key: ${{ secrets.DEV_SSH_KEY }} | ||
| port: ${{ secrets.DEV_PORT }} | ||
| script: | | ||
| export DOCKERHUB_USERNAME="${{ secrets.DOCKERHUB_USERNAME }}" | ||
| export DOCKERHUB_TOKEN="${{ secrets.DOCKERHUB_TOKEN }}" | ||
| export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)" | ||
| cd ~/deploy | ||
| chmod +x ./deploy.sh | ||
| ./deploy.sh | ||
|
|
||
| - name: Send Discord notification on success | ||
| uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 | ||
| if: success() | ||
| continue-on-error: true | ||
| with: | ||
| webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }} | ||
| embed-title: "✅ [${{ github.repository }}] Development Deploy Succeeded" | ||
| embed-description: | | ||
| **Commit**: `${{ github.sha }}` | ||
| **Author**: `${{ github.actor }}` | ||
| **Message**: `${{ github.event.head_commit.message }}` | ||
| [View Committed Changes](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | ||
| embed-color: 65280 | ||
|
|
||
| - name: Send Discord notification on failure | ||
| uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 | ||
| if: failure() | ||
| continue-on-error: true | ||
| with: | ||
| webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }} | ||
| embed-title: "❌ [${{ github.repository }}] Development Deploy Failed" | ||
| embed-description: | | ||
| **Commit**: `${{ github.sha }}` | ||
| **Author**: `${{ github.actor }}` | ||
| An error occurred during the workflow execution. | ||
| [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | ||
| embed-color: 16711680 | ||
| ssh-key: ${{ secrets.DEV_SSH_KEY }} | ||
| ssh-port: ${{ secrets.DEV_PORT }} | ||
| discord-webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }} | ||
| image-prefix: ${{ env.IMAGE_PREFIX }} | ||
| image-tag-type: type=raw,value=development-latest | ||
| deploy-script: deploy-dev.sh | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
deploy-script입력 설명 명확화 제안라인 51의 기본값
'deploy-dev.sh'는 설명과 실제 사용이 혼동을 줄 수 있습니다. 의존 워크플로우(dev-ci-cd.yml, prod-ci-cd.yml)에서 명시적으로deploy-dev.sh또는deploy-prod.sh를 전달하고 있으므로, 설명을 다음과 같이 개선하면 좋습니다:이렇게 하면 향후 독자가 기본값이 개발 환경 용도임을 알 수 있고, 워크플로우에서 의도적으로 override됨을 이해할 수 있습니다.
🤖 Prompt for AI Agents