Skip to content

Commit 25c808c

Browse files
authored
Merge pull request #77 from next-engineer/develop
1차 프로덕션 배포 (kickytime-server)
2 parents 3669abb + 522bdf8 commit 25c808c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+10874
-15
lines changed

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/gradlew text eol=lf
2+
*.bat text eol=crlf
3+
*.jar binary

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [ main, develop ]
6+
push:
7+
branches: [ main, develop ]
8+
9+
10+
jobs:
11+
build-and-lint:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Gradle Wrapper 스크립트(gradlew 및 관련 JAR) 무결성 검증
19+
uses: gradle/wrapper-validation-action@v3
20+
timeout-minutes: 2
21+
continue-on-error: true
22+
23+
- name: Set up JDK 17
24+
uses: actions/setup-java@v4
25+
with:
26+
distribution: temurin
27+
java-version: 17
28+
29+
- name: Gradle 실행 환경 최적화 + 캐시
30+
uses: gradle/actions/setup-gradle@v4
31+
32+
- name: Grant execute permission for gradlew
33+
run: chmod +x gradlew
34+
35+
- name: Build (includes spotless/checkstyle/test via `check` dependency)
36+
run: ./gradlew --no-daemon build
37+
env:
38+
SPRING_PROFILES_ACTIVE: test

.github/workflows/ec2.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: EC2 Deploy (short)
2+
3+
on:
4+
repository_dispatch:
5+
types: [deploy-ec2]
6+
workflow_dispatch: {}
7+
8+
env:
9+
AWS_REGION: ap-northeast-2
10+
IMAGE: ${{ github.event.client_payload.image_uri || '077672914621.dkr.ecr.ap-northeast-2.amazonaws.com/kickytime-repo:latest' }}
11+
TARGET_TAG_KEY: Role
12+
TARGET_TAG_VALUE: app
13+
PORT: "8080"
14+
HEALTH: "/actuator/health"
15+
16+
jobs:
17+
deploy:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: aws-actions/configure-aws-credentials@v4
21+
with:
22+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
23+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
24+
aws-region: ${{ env.AWS_REGION }}
25+
26+
# ✅ 추가된 최소 스텝: 실행중 + 태그(Role=app) + SSM Online 교집합 → ids_list 출력
27+
- name: Resolve running + tagged + SSM-online
28+
id: resolve
29+
shell: bash
30+
run: |
31+
set -euo pipefail
32+
RUNNING_IDS=$(aws ec2 describe-instances \
33+
--region "${AWS_REGION}" \
34+
--filters "Name=tag:${TARGET_TAG_KEY},Values=${TARGET_TAG_VALUE}" "Name=instance-state-name,Values=running" \
35+
--query 'Reservations[].Instances[].InstanceId' --output text | tr '\t' '\n' || true)
36+
SSM_ONLINE_IDS=$(aws ssm describe-instance-information \
37+
--region "${AWS_REGION}" \
38+
--filters "Key=PingStatus,Values=Online" \
39+
--query 'InstanceInformationList[].InstanceId' --output text | tr '\t' '\n' || true)
40+
TARGET_IDS=$(comm -12 <(printf '%s\n' $RUNNING_IDS | sort) <(printf '%s\n' $SSM_ONLINE_IDS | sort) || true)
41+
if [ -z "${TARGET_IDS:-}" ]; then
42+
echo "No targets (running+tagged+ssm-online)."; exit 1
43+
fi
44+
echo "ids_list=$(printf '%s ' $TARGET_IDS)" >> "$GITHUB_OUTPUT"
45+
46+
- name: Run on all tagged instances via SSM (by instance-ids)
47+
env:
48+
IDS_LIST: ${{ steps.resolve.outputs.ids_list }}
49+
run: |
50+
echo "Deploying image: $IMAGE"
51+
52+
aws ssm send-command \
53+
--region $AWS_REGION \
54+
--document-name "AWS-RunShellScript" \
55+
--instance-ids $IDS_LIST \
56+
--cloud-watch-output-config CloudWatchOutputEnabled=true,CloudWatchLogGroupName=/aws/ssm/kickytime \
57+
--parameters '{
58+
"commands": [
59+
"set -e",
60+
"command -v docker >/dev/null 2>&1 || (dnf -y install docker && systemctl enable --now docker)",
61+
"REG=$(echo \"'$IMAGE'\" | cut -d/ -f1)",
62+
"aws ecr get-login-password --region '$AWS_REGION' | docker login --username AWS --password-stdin $REG",
63+
"docker rm -f kickytime || true",
64+
"docker pull \"'$IMAGE'\"",
65+
"docker run -d --name kickytime -p '$PORT':'$PORT' \\",
66+
" -e SPRING_DATASOURCE_URL=\"'${{ secrets.DB_URL }}'\" \\",
67+
" -e SPRING_DATASOURCE_USERNAME=\"'${{ secrets.DB_USERNAME }}'\" \\",
68+
" -e SPRING_DATASOURCE_PASSWORD=\"'${{ secrets.DB_PASSWORD }}'\" \\",
69+
" \"'$IMAGE'\"",
70+
"for i in $(seq 1 60); do curl -fsS http://127.0.0.1:'$PORT$HEALTH' && break || sleep 2; done"
71+
],
72+
"executionTimeout": ["1800"]
73+
}' \
74+
--comment "Deploy $IMAGE"

.github/workflows/ecr.yml

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: ECR - Build & Push
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: ecr-main
10+
cancel-in-progress: false
11+
12+
permissions:
13+
contents: write # repository_dispatch에 필요
14+
15+
env:
16+
AWS_REGION: ap-northeast-2
17+
ECR_REPOSITORY: kickytime-repo
18+
19+
jobs:
20+
build_and_push:
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@v4
26+
27+
- name: Ensure jq is installed
28+
run: |
29+
sudo apt-get update -y
30+
sudo apt-get install -y jq
31+
32+
# 멀티아치 에뮬레이션(QEMU) 세팅
33+
- name: Set up QEMU
34+
uses: docker/setup-qemu-action@v3
35+
with:
36+
platforms: all
37+
38+
- name: Set up Docker Buildx
39+
uses: docker/setup-buildx-action@v3
40+
41+
- name: Configure AWS credentials (Access Keys)
42+
uses: aws-actions/configure-aws-credentials@v4
43+
with:
44+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
45+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
46+
aws-region: ${{ env.AWS_REGION }}
47+
48+
- name: Login to Amazon ECR
49+
id: ecr
50+
uses: aws-actions/amazon-ecr-login@v2
51+
52+
- name: Set TAG from commit SHA
53+
id: tag
54+
run: echo "TAG=${GITHUB_SHA::12}" >> $GITHUB_OUTPUT
55+
56+
- name: Build & Push multi-arch image
57+
id: build
58+
run: |
59+
set -e
60+
REGISTRY="${{ steps.ecr.outputs.registry }}"
61+
TAG="${{ steps.tag.outputs.TAG }}"
62+
IMAGE_URI="$REGISTRY/${{ env.ECR_REPOSITORY }}:$TAG"
63+
LATEST_URI="$REGISTRY/${{ env.ECR_REPOSITORY }}:latest"
64+
65+
docker buildx build \
66+
--platform linux/amd64,linux/arm64 \
67+
--push \
68+
--tag "$IMAGE_URI" \
69+
--tag "$LATEST_URI" \
70+
.
71+
72+
echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_OUTPUT
73+
74+
- name: Verify multi-arch manifest (Fixed)
75+
run: |
76+
set -e
77+
REGISTRY="${{ steps.ecr.outputs.registry }}"
78+
TAG="${{ steps.tag.outputs.TAG }}"
79+
IMAGE_URI="$REGISTRY/${{ env.ECR_REPOSITORY }}:$TAG"
80+
81+
ARCHES=$(docker manifest inspect "$IMAGE_URI" | jq -r '.manifests[]?.platform | "\(.os)/\(.architecture)"' | sort -u)
82+
echo "Manifests:"
83+
echo "$ARCHES"
84+
85+
# 정확한 매칭으로 수정
86+
if ! echo "$ARCHES" | grep -x 'linux/amd64' > /dev/null; then
87+
echo "❌ Missing linux/amd64 in manifest"
88+
echo "Available platforms: $ARCHES"
89+
exit 1
90+
fi
91+
92+
if ! echo "$ARCHES" | grep -x 'linux/arm64' > /dev/null; then
93+
echo "❌ Missing linux/arm64 in manifest"
94+
echo "Available platforms: $ARCHES"
95+
exit 1
96+
fi
97+
98+
echo "✅ Both linux/amd64 and linux/arm64 found"
99+
100+
# repository_dispatch 전송 + HTTP 204 확인
101+
- name: Trigger ECS/EC2 Deploy via repository_dispatch
102+
if: success()
103+
run: |
104+
set -e
105+
image_uri="${{ steps.build.outputs.IMAGE_URI }}"
106+
tag="${{ steps.tag.outputs.TAG }}"
107+
branch="${{ github.ref_name }}"
108+
sha="${{ github.sha }}"
109+
110+
# ECS
111+
resp=$(curl -s -o /tmp/resp.txt -w "%{http_code}" -X POST \
112+
"https://api.github.com/repos/${{ github.repository }}/dispatches" \
113+
-H "Accept: application/vnd.github+json" \
114+
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
115+
-d "{\"event_type\":\"deploy-ecs\",\"client_payload\":{\"image_uri\":\"$image_uri\",\"tag\":\"$tag\",\"branch\":\"$branch\",\"sha\":\"$sha\"}}")
116+
[ "$resp" = "204" ] || (echo "ECS dispatch failed:" && cat /tmp/resp.txt && exit 1)
117+
118+
# EC2
119+
resp2=$(curl -s -o /tmp/resp2.txt -w "%{http_code}" -X POST \
120+
"https://api.github.com/repos/${{ github.repository }}/dispatches" \
121+
-H "Accept: application/vnd.github+json" \
122+
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
123+
-d "{\"event_type\":\"deploy-ec2\",\"client_payload\":{\"image_uri\":\"$image_uri\",\"tag\":\"$tag\",\"branch\":\"$branch\",\"sha\":\"$sha\"}}")
124+
[ "$resp2" = "204" ] || (echo "EC2 dispatch failed:" && cat /tmp/resp2.txt && exit 1)
125+
126+
- name: Summary
127+
run: |
128+
echo "### ✅ ECR Push & Dispatch OK (Multi-arch)" >> $GITHUB_STEP_SUMMARY
129+
echo "- Repo: \`${{ env.ECR_REPOSITORY }}\`" >> $GITHUB_STEP_SUMMARY
130+
echo "- Tag: \`${{ steps.tag.outputs.TAG }}\`" >> $GITHUB_STEP_SUMMARY
131+
echo "- Image: \`${{ steps.build.outputs.IMAGE_URI }}\`" >> $GITHUB_STEP_SUMMARY
132+
echo "- Arches: \`linux/amd64, linux/arm64\`" >> $GITHUB_STEP_SUMMARY
133+
echo "- Sent: \`repository_dispatch: deploy-ecs\`" >> $GITHUB_STEP_SUMMARY
134+
echo "- Sent: \`repository_dispatch: deploy-ec2\`" >> $GITHUB_STEP_SUMMARY

.github/workflows/ecs.yml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: ECS - Deploy (dispatch)
2+
3+
on:
4+
repository_dispatch:
5+
types: [deploy-ecs]
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: ecs-deploy
10+
cancel-in-progress: false
11+
12+
permissions:
13+
contents: read
14+
15+
env:
16+
AWS_REGION: ap-northeast-2
17+
ECR_REPOSITORY: kickytime-repo
18+
ECS_CLUSTER: kickytime-cluster
19+
ECS_SERVICE: kickytime-task-service
20+
TASK_FAMILY: kickytime-task
21+
CONTAINER_NAME: kickytime-ecr
22+
23+
jobs:
24+
deploy:
25+
runs-on: ubuntu-latest
26+
env:
27+
PAYLOAD_IMAGE_URI: ${{ github.event.client_payload.image_uri }}
28+
PAYLOAD_TAG: ${{ github.event.client_payload.tag }}
29+
PAYLOAD_BRANCH: ${{ github.event.client_payload.branch }}
30+
PAYLOAD_SHA: ${{ github.event.client_payload.sha }}
31+
32+
steps:
33+
- name: Gate - only deploy for main
34+
id: gate
35+
run: |
36+
if [ "$PAYLOAD_BRANCH" = "main" ]; then
37+
echo "GO=true" >> $GITHUB_OUTPUT
38+
else
39+
echo "GO=false" >> $GITHUB_OUTPUT
40+
echo "Non-deploy branch: $PAYLOAD_BRANCH"
41+
fi
42+
43+
- name: Checkout (same commit as build)
44+
if: steps.gate.outputs.GO == 'true'
45+
uses: actions/checkout@v4
46+
with:
47+
ref: ${{ env.PAYLOAD_SHA }}
48+
49+
- name: Configure AWS credentials
50+
if: steps.gate.outputs.GO == 'true'
51+
uses: aws-actions/configure-aws-credentials@v4
52+
with:
53+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
54+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
55+
aws-region: ${{ env.AWS_REGION }}
56+
57+
- name: Login to Amazon ECR
58+
if: steps.gate.outputs.GO == 'true'
59+
id: ecr
60+
uses: aws-actions/amazon-ecr-login@v2
61+
62+
- name: Describe current Task Definition
63+
if: steps.gate.outputs.GO == 'true'
64+
run: |
65+
aws ecs describe-task-definition \
66+
--task-definition "${{ env.TASK_FAMILY }}" \
67+
--query 'taskDefinition' > td.json
68+
69+
- name: Patch image & ensure ARM64 platform
70+
if: steps.gate.outputs.GO == 'true'
71+
run: |
72+
jq --arg NAME "${{ env.CONTAINER_NAME }}" --arg IMG "${PAYLOAD_IMAGE_URI}" '
73+
.containerDefinitions |= map(if .name == $NAME then .image = $IMG else . end)
74+
| .runtimePlatform = {"cpuArchitecture":"ARM64","operatingSystemFamily":"LINUX"}
75+
| del(.taskDefinitionArn,.revision,.status,.requiresAttributes,.compatibilities,.registeredAt,.registeredBy)
76+
' td.json > td-new.json
77+
78+
- name: Register new Task Definition
79+
if: steps.gate.outputs.GO == 'true'
80+
id: register
81+
run: |
82+
NEW_TD_ARN=$(aws ecs register-task-definition \
83+
--cli-input-json file://td-new.json \
84+
--query 'taskDefinition.taskDefinitionArn' \
85+
--output text)
86+
echo "TASK_DEF_ARN=$NEW_TD_ARN" >> $GITHUB_OUTPUT
87+
88+
- name: Update service (rolling deployment)
89+
if: steps.gate.outputs.GO == 'true'
90+
run: |
91+
aws ecs update-service \
92+
--cluster "${{ env.ECS_CLUSTER }}" \
93+
--service "${{ env.ECS_SERVICE }}" \
94+
--task-definition "${{ steps.register.outputs.TASK_DEF_ARN }}" \
95+
--force-new-deployment
96+
97+
- name: Wait for deployment (timeout 10m)
98+
if: steps.gate.outputs.GO == 'true'
99+
id: wait
100+
continue-on-error: true
101+
run: |
102+
timeout 600 aws ecs wait services-stable \
103+
--cluster "${{ env.ECS_CLUSTER }}" \
104+
--services "${{ env.ECS_SERVICE }}" || echo "WAIT_FAILED=true" >> $GITHUB_OUTPUT
105+
106+
- name: Summary
107+
if: steps.gate.outputs.GO == 'true'
108+
run: |
109+
if [ "${{ steps.wait.outputs.WAIT_FAILED }}" == "true" ]; then
110+
echo "### ❌ ECS Deploy Failed" >> $GITHUB_STEP_SUMMARY
111+
exit 1
112+
else
113+
echo "### ✅ ECS Deploy Completed (ARM64)" >> $GITHUB_STEP_SUMMARY
114+
echo "- Cluster: \`${{ env.ECS_CLUSTER }}\`"
115+
echo "- Service: \`${{ env.ECS_SERVICE }}\`"
116+
echo "- TD: \`${{ steps.register.outputs.TASK_DEF_ARN }}\`"
117+
echo "- Image: \`${PAYLOAD_IMAGE_URI}\`"
118+
fi

0 commit comments

Comments
 (0)