Skip to content

Commit 8c2d89c

Browse files
committed
Infra: 배포 방식 변경
- 기존 방식: 최신 이미지를 받아 기존 컨테이너를 지우고 새로운 컨테이너를 생성 (다운 타임 O) - 변경 방식: 최신 이미지를 받아 새로운 컨테이너를 실행 후, NginX의 업스트림을 새로운 컨테이너로 변경하고 기존 이미지를 삭제 (다운 타임 X)
1 parent da612af commit 8c2d89c

File tree

1 file changed

+125
-18
lines changed

1 file changed

+125
-18
lines changed

.github/workflows/backend-cd.yml

Lines changed: 125 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,13 @@ jobs:
7575

7676
deploy:
7777
runs-on: ubuntu-latest
78-
needs: [ buildImageAndPush ]
78+
needs: [ makeTagAndRelease, buildImageAndPush ]
7979
env:
80-
OWNER: ${{ github.repository_owner }}
80+
IMAGE_REPOSITORY: catfe_backend # 도커 이미지 명
81+
CONTAINER_1_NAME: catfe_1 # 슬롯 1
82+
CONTAINER_2_NAME: catfe_2 # 슬롯 2
83+
CONTAINER_PORT: 8080 # 컨테이너 내부 포트
84+
DOCKER_NETWORK: common # 도커 네트워크
8185
EC2_INSTANCE_TAG_NAME: team5-ec2-1
8286

8387
steps:
@@ -95,10 +99,6 @@ jobs:
9599
[[ -n "${INSTANCE_ID}" && "${INSTANCE_ID}" != "None" ]] || { echo "No running instance found"; exit 1; }
96100
echo "INSTANCE_ID=${INSTANCE_ID}" >> "${GITHUB_ENV}"
97101
98-
- name: set lower case owner name
99-
run: |
100-
echo "OWNER_LC=${OWNER,,}" >> ${GITHUB_ENV}
101-
102102
- name: AWS SSM Send-Command
103103
uses: peterkimzz/aws-ssm-send-command@master
104104
id: ssm
@@ -110,21 +110,128 @@ jobs:
110110
working-directory: /
111111
comment: Deploy
112112
command: |
113-
# 1. env 변수 확인
114-
echo "OWNER_LC = ${{ env.OWNER_LC }}"
113+
set -Eeuo pipefail
115114
116-
# 2. EC2 인스턴스 아이디 확인
115+
# 1. EC2 인스턴스 아이디 확인
117116
echo "INSTANCE_ID=${INSTANCE_ID}"
118117
119-
# 3. 최신 이미지 pull
120-
docker pull ghcr.io/${{ env.OWNER_LC }}/catfe-backend:latest
118+
# 3. 실행 로그(라인 타임스탬프 부착)
119+
LOG="/tmp/ssm-$(date +%Y%m%d_%H%M%S).log"
120+
exec > >(awk '{ fflush(); print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }' | tee -a "$LOG")
121+
exec 2> >(awk '{ fflush(); print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }' | tee -a "$LOG" >&2)
122+
123+
# 4. 변수 정의
124+
source /etc/environment || true
125+
OWNER_LC="${{ github.repository_owner }}"
126+
OWNER_LC="${OWNER_LC,,}"
127+
IMAGE_TAG="${{ needs.makeTagAndRelease.outputs.tag_name }}"
128+
IMAGE_REPOSITORY="${{ env.IMAGE_REPOSITORY }}"
129+
IMAGE="ghcr.io/${OWNER_LC}/${IMAGE_REPOSITORY}:${IMAGE_TAG}"
130+
SLOT1="${{ env.CONTAINER_1_NAME }}"
131+
SLOT2="${{ env.CONTAINER_2_NAME }}"
132+
PORT_IN="${{ env.CONTAINER_PORT }}"
133+
NET="${{ env.DOCKER_NETWORK }}"
134+
135+
# 도커 이미지 pull 받기
136+
echo "🔹 Use image: ${IMAGE}"
137+
docker pull "${IMAGE}"
138+
139+
#5. NPM API 토큰 발급
140+
TOKEN=$(curl -s -X POST http://127.0.0.1:81/api/tokens \
141+
-H "Content-Type: application/json" \
142+
-d "{\"identity\": \"[email protected]\", \"secret\": \"${PASSWORD:-}\"}" | jq -r '.token')
143+
144+
# 조회한 토큰과 도메인 검증
145+
[[ -n "${TOKEN}" && "${TOKEN}" != "null" ]] || { echo "NPM token issue failed"; exit 1; }
146+
[[ -n "${DOMAIN:-}" ]] || { echo "DOMAIN is empty"; exit 1; }
147+
148+
# 6. 대상 프록시 호스트 ID 조회(도메인 매칭)
149+
PROXY_ID=$(curl -s -X GET "http://127.0.0.1:81/api/nginx/proxy-hosts" \
150+
-H "Authorization: Bearer ${TOKEN}" \
151+
| jq ".[] | select(.domain_names[]==\"${APP_1_DOMAIN}\") | .id")
152+
153+
# 조회한 프록시 호스트 ID 검증
154+
[[ -n "${PROXY_ID}" && "${PROXY_ID}" != "null" ]] || { echo "Proxy host not found for ${APP_1_DOMAIN}"; exit 1; }
155+
156+
# 현재 프록시가 바라보는 업스트림(컨테이너명) 조회
157+
CURRENT_HOST=$(curl -s -X GET "http://127.0.0.1:81/api/nginx/proxy-hosts/${PROXY_ID}" \
158+
-H "Authorization: Bearer ${TOKEN}" \
159+
| jq -r '.forward_host')
160+
161+
echo "🔎 CURRENT_HOST: ${CURRENT_HOST:-none}"
162+
163+
# 7. 역할(blue/green) 판정 (blue -> 현재 운영 중인 서버, green -> 교체할 서버)
164+
if [[ "${CURRENT_HOST:-}" == "${SLOT1}" ]]; then
165+
BLUE="${SLOT1}"
166+
GREEN="${SLOT2}"
167+
168+
elif [[ "${CURRENT_HOST:-}" == "${SLOT2}" ]]; then
169+
BLUE="${SLOT2}"
170+
GREEN="${SLOT1}"
171+
172+
# 초기 배포
173+
else
174+
BLUE="none"
175+
GREEN="${SLOT1}"
176+
177+
# 조건문 종료
178+
fi
179+
echo "🎨 role -> blue(now): ${BLUE}, green(next): ${GREEN}"
180+
181+
# 8. Green 역할 컨테이너
182+
docker rm -f "${Green}" > /dev/null 2>&1 || true
183+
echo "run new container -> ${Green}"
184+
docker run -d --name "${Green}" \
185+
--restart unless-stopped \
186+
--network "${NET}" \
187+
-e TZ=Asia/Seoul \
188+
"${IMAGE}"
189+
190+
# 9. 헬스체크
191+
echo "⏱ health-check: ${GREEN}"
192+
TIMEOUT=120
193+
INTERVAL=3
194+
ELAPSED=0
195+
sleep 8 # 초기부팅 여유
196+
197+
while (( ELAPSED < TIMEOUT )); do
198+
CODE=$(docker exec "${GREEN}" curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${PORT_IN}/actuator/health" || echo 000)
199+
[[ "${CODE}" == "200" ]] && { echo "✅ ${GREEN} healthy"; break; }
200+
sleep "${INTERVAL}"
201+
ELAPSED=$((ELAPSED + INTERVAL))
202+
done
203+
[[ "${CODE:-000}" == "200" ]] || { echo "❌ ${GREEN} health failed"; docker logs --tail=200 "${GREEN}" || true; docker rm -f "${GREEN}" || true; exit 1; }
204+
205+
# 10. 업스트림 전환
206+
NEW_CFG=$(jq -n --arg host "${GREEN}" --argjson port ${PORT_IN} '{forward_host:$host, forward_port:$port}')
207+
curl -s -X PUT "http://127.0.0.1:81/api/nginx/proxy-hosts/${PROXY_ID}" \
208+
-H "Authorization: Bearer ${TOKEN}" \
209+
-H "Content-Type: application/json" \
210+
-d "${NEW_CFG}" >/dev/null
211+
echo "🔁 switch upstream → ${GREEN}:${PORT_IN}"
212+
213+
# 11. 이전 Blue 종료
214+
if [[ "${BLUE}" != "none" ]]; then
215+
docker stop "${BLUE}" >/dev/null 2>&1 || true
216+
docker rm "${BLUE}" >/dev/null 2>&1 || true
217+
echo "🧹 removed old blue: ${BLUE}"
218+
fi
219+
220+
# 12. 이미지 정리
221+
{
222+
docker images --format '{{.Repository}}:{{.Tag}}' \
223+
| grep -F "ghcr.io/${OWNER_LC}/${IMAGE_REPOSITORY}:" \
224+
| grep -v -F ":${IMAGE_TAG}" \
225+
| grep -v -F ":latest" \
226+
| xargs -r docker rmi
227+
} || true
228+
229+
echo "🏁 Blue/Green switch complete. now blue = ${GREEN}"
230+
231+
232+
233+
234+
121235
122-
# 2. 기존 컨테이너 종료 및 제거
123-
docker stop catfe-backend 2>/dev/null
124-
docker rm catfe-backend 2>/dev/null
125236
126-
# 3. 새로운 컨테이너 실행
127-
docker run -d --name catfe-backend -p 8080:8080 ghcr.io/${{ env.OWNER_LC }}/catfe-backend:latest
128237
129-
# 4. dangling 이미지 삭제
130-
docker rmi $(docker images -f "dangling=true" -q)

0 commit comments

Comments
 (0)