Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Deploy

on:
workflow_dispatch:
push:
branches:
- deploy
- main

permissions:
contents: read
Expand Down Expand Up @@ -50,6 +51,7 @@ jobs:
deploy-to-ec2:
needs: build-and-push
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -85,6 +87,8 @@ jobs:
BACKEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
BACKEND_CONTAINER_NAME=ontime-container
BACKEND_HTTP_PORT=${{ secrets.BACKEND_HTTP_PORT || '8080' }}
BACKEND_MEMORY_LIMIT=${{ secrets.BACKEND_MEMORY_LIMIT || '768m' }}
BACKEND_CPU_LIMIT=${{ secrets.BACKEND_CPU_LIMIT || '1.0' }}
SERVER_PORT=8080
SPRING_PROFILES_ACTIVE=prod
JAVA_TOOL_OPTIONS=-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom
Expand Down Expand Up @@ -152,14 +156,33 @@ jobs:
FLYWAY_BASELINE="$(get_env_value SPRING_FLYWAY_BASELINE_ON_MIGRATE)"
NORMALIZED_DB_URL="$(printf '%s' "$DB_URL" | tr '[:upper:]' '[:lower:]')"
NORMALIZED_DB_USERNAME="$(printf '%s' "$DB_USERNAME" | tr '[:upper:]' '[:lower:]')"
DB_URL_NO_PREFIX="${DB_URL#jdbc:mysql://}"
[ "$DB_URL_NO_PREFIX" != "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL must start with jdbc:mysql://."
DB_ADDRESS="${DB_URL_NO_PREFIX%%/*}"
DB_HOST="${DB_ADDRESS%%:*}"
DB_PORT="${DB_ADDRESS#*:}"
[ "$DB_PORT" != "$DB_ADDRESS" ] || DB_PORT="3306"
DB_NAME_AND_QUERY="${DB_URL_NO_PREFIX#*/}"
DB_NAME="$(printf '%s' "$DB_NAME_AND_QUERY" | sed 's/[?;].*$//')"

[ -n "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL is required."
[ -n "$DB_USERNAME" ] || fail_deploy "SPRING_DATASOURCE_USERNAME is required."
[ -n "$DB_PASSWORD" ] || fail_deploy "SPRING_DATASOURCE_PASSWORD is required."
[ -n "$DB_HOST" ] || fail_deploy "SPRING_DATASOURCE_URL must include an RDS host."
[ "$DB_NAME" = "ontime_prod" ] || fail_deploy "SPRING_DATASOURCE_URL must use the ontime_prod database."
[ "$NORMALIZED_DB_USERNAME" != "root" ] || fail_deploy "SPRING_DATASOURCE_USERNAME must not be root."
[ "$DDL_AUTO" = "validate" ] || fail_deploy "SPRING_JPA_HIBERNATE_DDL_AUTO must be validate."
[ "$FLYWAY_BASELINE" = "false" ] || fail_deploy "SPRING_FLYWAY_BASELINE_ON_MIGRATE must be false."

case "$NORMALIZED_DB_URL" in
*localhost*|*127.0.0.1*|*host.docker.internal*) fail_deploy "SPRING_DATASOURCE_URL must point to private RDS, not a local database." ;;
esac

case "$(printf '%s' "$DB_HOST" | tr '[:upper:]' '[:lower:]')" in
*.rds.amazonaws.com) ;;
*) fail_deploy "SPRING_DATASOURCE_URL host must be an RDS endpoint." ;;
esac

case "$NORMALIZED_DB_URL" in
*allowpublickeyretrieval=true*) fail_deploy "SPRING_DATASOURCE_URL must not enable allowPublicKeyRetrieval." ;;
esac
Expand All @@ -173,10 +196,17 @@ jobs:
esac

case "$NORMALIZED_DB_URL" in
*sslmode=required*|*sslmode=verify_ca*|*sslmode=verify_identity*) ;;
*) fail_deploy "SPRING_DATASOURCE_URL must declare sslMode=REQUIRED, VERIFY_CA, or VERIFY_IDENTITY." ;;
*sslmode=required*|*sslmode=verify_ca*|*sslmode=verify_identity*|*usessl=true*) ;;
*) fail_deploy "SPRING_DATASOURCE_URL must declare useSSL=true, sslMode=REQUIRED, VERIFY_CA, or VERIFY_IDENTITY." ;;
esac

echo "Checking EC2-to-RDS TCP connectivity for $DB_HOST:$DB_PORT..."
if command -v nc >/dev/null 2>&1; then
nc -zv "$DB_HOST" "$DB_PORT"
else
timeout 5 bash -c "</dev/tcp/$DB_HOST/$DB_PORT"
fi

echo "${{ secrets.GHCR_READ_TOKEN }}" | sudo docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin

if sudo docker compose version >/dev/null 2>&1; then
Expand Down
49 changes: 3 additions & 46 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# PR이 올라왔을 때 자동으로 테스트코드를 실행하고 하나라도 Fail했을 시, PR을 Close하고 실패 코멘트를 달고 성공시에는 머지를 가능케하는 파이프라인.
# PR이 올라왔을 때 자동으로 테스트코드를 실행하고 실패 시 머지를 막는 파이프라인.
name: PR Test

on:
pull_request:
branches:
- main # main 브랜치로 머지되는 PR에서 실행
- dev
- main

jobs:
test:
runs-on: ubuntu-latest
outputs:
test_result: ${{ steps.run-tests.outcome }} # 테스트 결과를 output으로 저장

services:
mysql:
Expand Down Expand Up @@ -64,8 +63,6 @@ jobs:

# 4. Gradle 빌드 & JUnit 테스트 실행
- name: Run Tests with Gradle
id: run-tests # 실행 결과를 output으로 저장할 id 추가
continue-on-error: true
env:
SPRING_PROFILES_ACTIVE: test
run: |
Expand All @@ -76,43 +73,3 @@ jobs:
run: |
echo "Checking Flyway migration history..."
mysql -h 127.0.0.1 -u test_user --password=test_password -e "SELECT * FROM test_db.flyway_schema_history;"


handle-failure:
needs: test
if: needs.test.outputs.test_result == 'failure' # test job의 output 값이 실패(failure)일 때 실행
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- name: Close PR
uses: octokit/request-action@v2.x
with:
route: PATCH /repos/{owner}/{repo}/pulls/{pull_number}
owner: ${{ github.repository_owner }}
repo: ${{ github.event.repository.name }}
pull_number: ${{ github.event.pull_request.number }}
state: closed
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Wait for PR to close
run: sleep 5

- name: Comment on PR
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request?.number || context.payload.issue?.number;
if (!prNumber) {
console.log("PR 번호를 찾을 수 없습니다.");
return;
}
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: "실패하는 테스트코드가 있어 PR이 자동으로 닫혔습니다.\nGithub Action에서 자세한 실패 로그를 확인하고 코드를 수정하세요."
});
30 changes: 20 additions & 10 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@ Deployment access:
Runtime image and port:

- `BACKEND_HTTP_PORT` (optional, defaults to `8080`)
- `BACKEND_MEMORY_LIMIT` (optional, defaults to `768m`; use `640m` if the EC2 instance is memory constrained)
- `BACKEND_CPU_LIMIT` (optional, defaults to `1.0`)

Spring and database:

- `SPRING_APPLICATION_NAME`
- `SPRING_DATASOURCE_URL`
- `SPRING_DATASOURCE_USERNAME`
- `SPRING_DATASOURCE_URL` (`jdbc:mysql://ontime-prod.cpoeguokwaq5.ap-northeast-2.rds.amazonaws.com:3306/ontime_prod?useSSL=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8`)
- `SPRING_DATASOURCE_USERNAME` (`ontimeadmin`)
- `SPRING_DATASOURCE_PASSWORD`
- `SPRING_DATASOURCE_DRIVER_CLASS_NAME`
- `SPRING_JPA_HIBERNATE_DDL_AUTO`
- `SPRING_FLYWAY_URL`
- `SPRING_FLYWAY_USER`
- `SPRING_FLYWAY_PASSWORD`

The deploy workflow hardcodes safe production defaults for the datasource driver, JPA DDL mode, and Flyway:

- `SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver`
- `SPRING_JPA_HIBERNATE_DDL_AUTO=validate`
- `SPRING_FLYWAY_ENABLED=true`
- `SPRING_FLYWAY_BASELINE_ON_MIGRATE=false`

It also fails before restart if the datasource URL does not point to an RDS MySQL endpoint using the `ontime_prod` database, or if EC2 cannot reach RDS on `3306`.

Authentication and OAuth:

Expand Down Expand Up @@ -77,7 +83,9 @@ base64 -i ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8 | tr -d '\n'

## Build And Release Flow

Push to the `deploy` branch to trigger `.github/workflows/deploy.yml`.
Push to the `main` branch, or run `.github/workflows/deploy.yml` manually, to deploy production.

Pushes to `dev` run CI only. There is no dev-server deploy workflow in the one-EC2 plan.

The workflow:

Expand All @@ -87,8 +95,9 @@ The workflow:
- `ghcr.io/devkor-github/ontime-back:deploy-latest`
3. Uploads `docker-compose.yml` to `/home/ubuntu/OnTime-back`.
4. Writes `/home/ubuntu/OnTime-back/.env` from GitHub secrets.
5. Runs `docker compose pull && docker compose up -d --remove-orphans`.
6. Waits until the `ontime-container` Docker health status is `healthy`.
5. Verifies EC2 can reach private RDS on `3306`.
6. Runs `docker compose pull && docker compose up -d --remove-orphans`.
7. Waits until the `ontime-container` Docker health status is `healthy`.

## Health Verification

Expand All @@ -105,6 +114,7 @@ cd /home/ubuntu/OnTime-back
sudo docker compose ps
sudo docker inspect -f '{{.State.Health.Status}}' ontime-container
curl -fsS http://localhost:8080/actuator/health/readiness
nc -zv ontime-prod.cpoeguokwaq5.ap-northeast-2.rds.amazonaws.com 3306
```

## Rollback
Expand Down
Loading
Loading