Skip to content

Commit 464974b

Browse files
authored
Merge pull request #75 from next-engineer/feature/68-ec2-deploy-add
✨ feat: ec2.yml, ecr.yml 수정
2 parents b8b259f + 698a28e commit 464974b

File tree

2 files changed

+102
-177
lines changed

2 files changed

+102
-177
lines changed

.github/workflows/ec2.yml

Lines changed: 54 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,74 @@
1-
name: EC2 - Deploy (dispatch)
1+
name: EC2 Deploy (short)
22

33
on:
44
repository_dispatch:
55
types: [deploy-ec2]
6-
workflow_dispatch:
7-
8-
concurrency:
9-
group: ec2-deploy
10-
cancel-in-progress: false
11-
12-
permissions:
13-
contents: read
6+
workflow_dispatch: {}
147

158
env:
169
AWS_REGION: ap-northeast-2
17-
# ✅ 배포 대상 EC2 리스트(JSON 배열). 필요에 맞게 교체하세요.
18-
INSTANCE_IDS_JSON: '["i-08f17d18d83292c07","i-08316767f2bd4a5ed"]'
19-
20-
# ✅ 앱/컨테이너 설정
21-
APP_NAME: kickytime # 기존 컨테이너 기본 이름 (예: kickytime)
22-
CONTAINER_PORT: "8080" # 컨테이너 내부 포트
23-
HOST_PORT_BASE: "8080" # 기존 컨테이너가 바인드하는 호스트 포트
24-
HEALTH_PATH: "/actuator/health" # 헬스체크 경로
25-
HEALTH_TIMEOUT_SEC: "180" # 헬스체크 최대 대기시간
26-
ENV_FILE_PATH: "" # 예: /opt/kickytime/.env (없으면 빈 값)
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"
2715

2816
jobs:
2917
deploy:
3018
runs-on: ubuntu-latest
31-
strategy:
32-
max-parallel: 1
33-
matrix:
34-
instance_id: ${{ fromJson(env.INSTANCE_IDS_JSON) }}
35-
36-
env:
37-
PAYLOAD_IMAGE_URI: ${{ github.event.client_payload.image_uri }}
38-
PAYLOAD_TAG: ${{ github.event.client_payload.tag }}
39-
PAYLOAD_BRANCH: ${{ github.event.client_payload.branch }}
40-
PAYLOAD_SHA: ${{ github.event.client_payload.sha }}
41-
4219
steps:
43-
- name: Gate - only deploy for main
44-
id: gate
45-
run: |
46-
if [ "${PAYLOAD_BRANCH}" = "main" ]; then
47-
echo "GO=true" >> $GITHUB_OUTPUT
48-
else
49-
echo "GO=false" >> $GITHUB_OUTPUT
50-
echo "Non-deploy branch: ${PAYLOAD_BRANCH}"
51-
fi
52-
53-
- name: Checkout (same commit as build)
54-
if: steps.gate.outputs.GO == 'true'
55-
uses: actions/checkout@v4
56-
with:
57-
ref: ${{ env.PAYLOAD_SHA }}
58-
59-
- name: Configure AWS credentials
60-
if: steps.gate.outputs.GO == 'true'
61-
uses: aws-actions/configure-aws-credentials@v4
20+
- uses: aws-actions/configure-aws-credentials@v4
6221
with:
63-
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
22+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
6423
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
65-
aws-region: ${{ env.AWS_REGION }}
24+
aws-region: ${{ env.AWS_REGION }}
6625

67-
- name: Send rolling deploy command via SSM
68-
if: steps.gate.outputs.GO == 'true'
69-
id: ssm
26+
# ✅ 추가된 최소 스텝: 실행중 + 태그(Role=app) + SSM Online 교집합 → ids_list 출력
27+
- name: Resolve running + tagged + SSM-online
28+
id: resolve
29+
shell: bash
7030
run: |
71-
set -e
72-
73-
TARGET_ID="${{ matrix.instance_id }}"
74-
echo "Deploying to instance: $TARGET_ID"
75-
76-
# 새 컨테이너가 바인드할 임시 포트(현재 HOST_PORT_BASE+1)
77-
NEXT_PORT=$(( ${HOST_PORT_BASE} + 1 ))
78-
79-
# SSM에 보낼 스크립트를 JSON 안전하게 변환
80-
read -r -d '' SCRIPT <<'EOSCRIPT'
81-
#!/usr/bin/env bash
8231
set -euo pipefail
83-
84-
APP_NAME="${APP_NAME}"
85-
IMAGE_URI="${PAYLOAD_IMAGE_URI}"
86-
CONTAINER_PORT="${CONTAINER_PORT}"
87-
HOST_PORT_BASE="${HOST_PORT_BASE}"
88-
NEXT_PORT=$(( HOST_PORT_BASE + 1 ))
89-
HEALTH_PATH="${HEALTH_PATH}"
90-
HEALTH_TIMEOUT="${HEALTH_TIMEOUT_SEC}"
91-
ENV_FILE_PATH="${ENV_FILE_PATH}"
92-
93-
OLD_NAME="${APP_NAME}"
94-
NEW_NAME="${APP_NAME}_new"
95-
96-
echo "[1/7] ECR 로그인"
97-
REGISTRY_HOST="$(echo "$IMAGE_URI" | awk -F'/' '{print $1}')"
98-
aws ecr get-login-password --region "${AWS_REGION}" | docker login --username AWS --password-stdin "${REGISTRY_HOST}"
99-
100-
echo "[2/7] 새 이미지 Pull: ${IMAGE_URI}"
101-
docker pull "${IMAGE_URI}"
102-
103-
echo "[3/7] 이전 NEW 컨테이너 정리(있으면)"
104-
if docker ps -a --format '{{.Names}}' | grep -q "^${NEW_NAME}$"; then
105-
docker rm -f "${NEW_NAME}" || true
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
10643
fi
44+
echo "ids_list=$(printf '%s ' $TARGET_IDS)" >> "$GITHUB_OUTPUT"
10745
108-
echo "[4/7] 새 컨테이너 기동: ${NEW_NAME} (host ${NEXT_PORT} -> container ${CONTAINER_PORT})"
109-
RUN_ENV_OPTS=()
110-
if [ -n "${ENV_FILE_PATH}" ]; then
111-
RUN_ENV_OPTS+=( --env-file "${ENV_FILE_PATH}" )
112-
fi
113-
114-
docker run -d \
115-
--name "${NEW_NAME}" \
116-
-p "${NEXT_PORT}:${CONTAINER_PORT}" \
117-
"${RUN_ENV_OPTS[@]}" \
118-
"${IMAGE_URI}"
119-
120-
echo "[5/7] 헬스체크: http://127.0.0.1:${NEXT_PORT}${HEALTH_PATH} (timeout: ${HEALTH_TIMEOUT}s)"
121-
SECS=0
122-
until curl -fsS "http://127.0.0.1:${NEXT_PORT}${HEALTH_PATH}" >/dev/null 2>&1; do
123-
sleep 3
124-
SECS=$(( SECS + 3 ))
125-
if [ "${SECS}" -ge "${HEALTH_TIMEOUT}" ]; then
126-
echo "❌ 새 컨테이너 헬스체크 실패. 로그:"
127-
docker logs --tail 200 "${NEW_NAME}" || true
128-
exit 1
129-
fi
130-
done
131-
echo "✅ 새 컨테이너 Healthy"
132-
133-
echo "[6/7] 기존 컨테이너 ${OLD_NAME} 종료/삭제 및 포트 스위칭"
134-
if docker ps -a --format '{{.Names}}' | grep -q "^${OLD_NAME}$"; then
135-
docker rm -f "${OLD_NAME}" || true
136-
fi
137-
138-
# 새 컨테이너를 기본 이름으로 승격
139-
docker rename "${NEW_NAME}" "${OLD_NAME}"
140-
141-
echo "[7/7] 청소 (Dangling Images/Containers)"
142-
docker system prune -f || true
143-
144-
echo "🎉 롤링 완료: $(docker ps --filter "name=${OLD_NAME}" --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}')"
145-
EOSCRIPT
146-
147-
# jq로 이스케이프하여 commands에 넣기
148-
JSON_SCRIPT=$(jq -Rn --arg s "$SCRIPT" '$s')
149-
150-
CMD_ID=$(aws ssm send-command \
151-
--document-name "AWS-RunShellScript" \
152-
--instance-ids "$TARGET_ID" \
153-
--parameters commands="[$JSON_SCRIPT]" \
154-
--comment "Rolling deploy ${APP_NAME} -> ${PAYLOAD_IMAGE_URI}" \
155-
--timeout-seconds 1800 \
156-
--query "Command.CommandId" \
157-
--output text)
158-
159-
echo "SSM CommandId: ${CMD_ID}"
160-
aws ssm wait command-executed --command-id "$CMD_ID" --instance-id "$TARGET_ID"
161-
162-
# 실패 시 로그 출력
163-
STATUS=$(aws ssm list-command-invocations --command-id "$CMD_ID" --instance-id "$TARGET_ID" --details \
164-
--query "CommandInvocations[0].Status" --output text)
165-
echo "SSM Status: $STATUS"
166-
if [ "$STATUS" != "Success" ]; then
167-
echo "---- SSM Output ----"
168-
aws ssm list-command-invocations --command-id "$CMD_ID" --instance-id "$TARGET_ID" --details \
169-
--query "CommandInvocations[0].CommandPlugins[0].{StdOut:Output,StdErr:StandardErrorContent}" --output json
170-
exit 1
171-
fi
172-
173-
- name: Summary
174-
if: steps.gate.outputs.GO == 'true'
46+
- name: Run on all tagged instances via SSM (by instance-ids)
47+
env:
48+
IDS_LIST: ${{ steps.resolve.outputs.ids_list }}
17549
run: |
176-
echo "### ✅ EC2 Rolling Deploy Completed" >> $GITHUB_STEP_SUMMARY
177-
echo "- Instance: \`${{ matrix.instance_id }}\`" >> $GITHUB_STEP_SUMMARY
178-
echo "- Image: \`${PAYLOAD_IMAGE_URI}\`" >> $GITHUB_STEP_SUMMARY
179-
echo "- Branch: \`${PAYLOAD_BRANCH}\`" >> $GITHUB_STEP_SUMMARY
180-
echo "- Commit: \`${PAYLOAD_SHA}\`" >> $GITHUB_STEP_SUMMARY
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: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: ECR - Build & Push
22

33
on:
44
push:
5-
branches: [develop]
5+
branches: [main]
66
workflow_dispatch:
77

88
concurrency:
@@ -24,6 +24,17 @@ jobs:
2424
- name: Checkout code
2525
uses: actions/checkout@v4
2626

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+
2738
- name: Set up Docker Buildx
2839
uses: docker/setup-buildx-action@v3
2940

@@ -42,7 +53,7 @@ jobs:
4253
id: tag
4354
run: echo "TAG=${GITHUB_SHA::12}" >> $GITHUB_OUTPUT
4455

45-
- name: Build & Push ARM64 image
56+
- name: Build & Push multi-arch image
4657
id: build
4758
run: |
4859
set -e
@@ -52,24 +63,42 @@ jobs:
5263
LATEST_URI="$REGISTRY/${{ env.ECR_REPOSITORY }}:latest"
5364
5465
docker buildx build \
55-
--platform linux/arm64 \
66+
--platform linux/amd64,linux/arm64 \
5667
--push \
5768
--tag "$IMAGE_URI" \
5869
--tag "$LATEST_URI" \
5970
.
6071
6172
echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_OUTPUT
6273
63-
- name: Verify ARM64 architecture
74+
- name: Verify multi-arch manifest (Fixed)
6475
run: |
76+
set -e
6577
REGISTRY="${{ steps.ecr.outputs.registry }}"
6678
TAG="${{ steps.tag.outputs.TAG }}"
6779
IMAGE_URI="$REGISTRY/${{ env.ECR_REPOSITORY }}:$TAG"
68-
ARCH=$(docker manifest inspect "$IMAGE_URI" | jq -r '.manifests[0].platform.architecture // .architecture')
69-
[ "$ARCH" = "arm64" ] || (echo "Expected arm64, got $ARCH" && exit 1)
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"
7099
71100
# repository_dispatch 전송 + HTTP 204 확인
72-
- name: Trigger ECS Deploy via repository_dispatch
101+
- name: Trigger ECS/EC2 Deploy via repository_dispatch
73102
if: success()
74103
run: |
75104
set -e
@@ -78,26 +107,28 @@ jobs:
78107
branch="${{ github.ref_name }}"
79108
sha="${{ github.sha }}"
80109
110+
# ECS
81111
resp=$(curl -s -o /tmp/resp.txt -w "%{http_code}" -X POST \
82112
"https://api.github.com/repos/${{ github.repository }}/dispatches" \
83113
-H "Accept: application/vnd.github+json" \
84114
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
85115
-d "{\"event_type\":\"deploy-ecs\",\"client_payload\":{\"image_uri\":\"$image_uri\",\"tag\":\"$tag\",\"branch\":\"$branch\",\"sha\":\"$sha\"}}")
86-
[ "$resp" = "204" ] || (cat /tmp/resp.txt && exit 1)
87-
116+
[ "$resp" = "204" ] || (echo "ECS dispatch failed:" && cat /tmp/resp.txt && exit 1)
117+
118+
# EC2
88119
resp2=$(curl -s -o /tmp/resp2.txt -w "%{http_code}" -X POST \
89120
"https://api.github.com/repos/${{ github.repository }}/dispatches" \
90121
-H "Accept: application/vnd.github+json" \
91122
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
92123
-d "{\"event_type\":\"deploy-ec2\",\"client_payload\":{\"image_uri\":\"$image_uri\",\"tag\":\"$tag\",\"branch\":\"$branch\",\"sha\":\"$sha\"}}")
93-
[ "$resp2" = "204" ] || (cat /tmp/resp2.txt && exit 1)
94-
124+
[ "$resp2" = "204" ] || (echo "EC2 dispatch failed:" && cat /tmp/resp2.txt && exit 1)
95125
96126
- name: Summary
97127
run: |
98-
echo "### ✅ ECR Push & Dispatch OK" >> $GITHUB_STEP_SUMMARY
99-
echo "- Repo: \`${{ env.ECR_REPOSITORY }}\`" >> $GITHUB_STEP_SUMMARY
100-
echo "- Tag: \`${{ steps.tag.outputs.TAG }}\`" >> $GITHUB_STEP_SUMMARY
101-
echo "- Image: \`${{ steps.build.outputs.IMAGE_URI }}\`" >> $GITHUB_STEP_SUMMARY
102-
echo "- Sent: \`repository_dispatch: deploy-ecs\`" >> $GITHUB_STEP_SUMMARY
103-
echo "- Sent: \`repository_dispatch: deploy-ec2\`" >> $GITHUB_STEP_SUMMARY
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

0 commit comments

Comments
 (0)