Skip to content

Commit 6758f18

Browse files
committed
chore : 자동 배포 코드 추가
1 parent 9566e74 commit 6758f18

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed

.github/workflows/deploy.yml

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: 'deploy'
2+
on:
3+
push:
4+
paths:
5+
- '.github/workflows/**'
6+
- 'src/**'
7+
- 'build.gradle'
8+
- 'Dockerfile'
9+
- 'readme.md'
10+
- 'infraScript/**'
11+
branches:
12+
- 'main'
13+
jobs:
14+
makeTagAndRelease:
15+
runs-on: ubuntu-latest
16+
outputs:
17+
tag_name: ${{ steps.create_tag.outputs.new_tag }}
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Create Tag
21+
id: create_tag
22+
uses: mathieudutour/[email protected]
23+
with:
24+
github_token: ${{ secrets.GITHUB_TOKEN }}
25+
- name: Create Release
26+
id: create_release
27+
uses: actions/create-release@v1
28+
env:
29+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
with:
31+
tag_name: ${{ steps.create_tag.outputs.new_tag }}
32+
release_name: Release ${{ steps.create_tag.outputs.new_tag }}
33+
body: ${{ steps.create_tag.outputs.changelog }}
34+
draft: false
35+
prerelease: false
36+
buildImageAndPush:
37+
name: 도커 이미지 빌드와 푸시
38+
needs: makeTagAndRelease
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v4
42+
- name: Docker Buildx 설치
43+
uses: docker/setup-buildx-action@v2
44+
- name: 레지스트리 로그인
45+
uses: docker/login-action@v2
46+
with:
47+
registry: ghcr.io
48+
username: ${{ github.actor }}
49+
password: ${{ secrets.GITHUB_TOKEN }}
50+
- name: set lower case owner name
51+
run: |
52+
echo "OWNER_LC=${OWNER,,}" >> ${GITHUB_ENV}
53+
env:
54+
OWNER: "${{ github.repository_owner }}"
55+
- name: application-secret.yml 생성
56+
env:
57+
ACTIONS_STEP_DEBUG: true
58+
APPLICATION_SECRET: ${{ secrets.APPLICATION_SECRET_YML }}
59+
run: echo "$APPLICATION_SECRET" > src/main/resources/application-secret.yml
60+
- name: 빌드 앤 푸시
61+
uses: docker/build-push-action@v3
62+
with:
63+
context: .
64+
push: true
65+
tags: |
66+
ghcr.io/${{ env.OWNER_LC }}/commitfield:${{ needs.makeTagAndRelease.outputs.tag_name }},
67+
ghcr.io/${{ env.OWNER_LC }}/commitfield:latest
68+
deploy:
69+
runs-on: ubuntu-latest
70+
needs: [ buildImageAndPush ]
71+
steps:
72+
- name: AWS SSM Send-Command
73+
uses: peterkimzz/aws-ssm-send-command@master
74+
id: ssm
75+
with:
76+
aws-region: ${{ secrets.AWS_REGION }}
77+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
78+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
79+
instance-ids: "i-00847119db824184b"
80+
working-directory: /
81+
comment: Deploy
82+
command: |
83+
mkdir -p /dockerProjects/cmf
84+
curl -o /dockerProjects/cmf/zero_downtime_deploy.py https://raw.githubusercontent.com/CommitField/commitfield/main/infraScript/zero_downtime_deploy.py
85+
chmod +x /dockerProjects/cmf/zero_downtime_deploy.py
86+
/dockerProjects/cmf/zero_downtime_deploy.py
File renamed without changes.

Dockerfile

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# 첫 번째 스테이지: 빌드 스테이지
2+
FROM gradle:jdk21-graal-jammy as builder
3+
4+
# 작업 디렉토리 설정
5+
WORKDIR /app
6+
7+
# 소스 코드와 Gradle 래퍼 복사
8+
COPY gradlew .
9+
COPY gradle gradle
10+
COPY build.gradle.kts .
11+
COPY settings.gradle.kts .
12+
13+
# Gradle 래퍼에 실행 권한 부여
14+
RUN chmod +x ./gradlew
15+
16+
# 종속성 설치
17+
RUN ./gradlew dependencies --no-daemon
18+
19+
# 소스 코드 복사
20+
COPY src src
21+
22+
# 애플리케이션 빌드 (테스트 스킵)
23+
RUN ./gradlew clean build -x test --no-daemon
24+
25+
# 두 번째 스테이지: 실행 스테이지
26+
FROM ghcr.io/graalvm/jdk-community:21
27+
28+
# 작업 디렉토리 설정
29+
WORKDIR /app
30+
31+
# 첫 번째 스테이지에서 빌드된 JAR 파일 복사
32+
COPY --from=builder /app/build/libs/*.jar app.jar
33+
34+
# 실행할 JAR 파일 지정
35+
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"]

infraScript/zero_downtime_deploy.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import requests # HTTP 요청을 위한 모듈 추가
5+
import subprocess
6+
import time
7+
from typing import Dict, Optional
8+
9+
10+
class ServiceManager:
11+
# 초기화 함수
12+
def __init__(self, socat_port: int = 8081, sleep_duration: int = 3) -> None:
13+
self.socat_port: int = socat_port
14+
self.sleep_duration: int = sleep_duration
15+
self.services: Dict[str, int] = {
16+
'cmf_1': 8082,
17+
'cmf_2': 8083
18+
}
19+
self.current_name: Optional[str] = None
20+
self.current_port: Optional[int] = None
21+
self.next_name: Optional[str] = None
22+
self.next_port: Optional[int] = None
23+
24+
# 현재 실행 중인 서비스를 찾는 함수
25+
def _find_current_service(self) -> None:
26+
cmd: str = f"ps aux | grep 'socat -t0 TCP-LISTEN:{self.socat_port}' | grep -v grep | awk '{{print $NF}}'"
27+
current_service: str = subprocess.getoutput(cmd)
28+
if not current_service:
29+
self.current_name, self.current_port = 'cmf_2', self.services['cmf_2']
30+
else:
31+
self.current_port = int(current_service.split(':')[-1])
32+
self.current_name = next((name for name, port in self.services.items() if port == self.current_port), None)
33+
34+
# 다음에 실행할 서비스를 찾는 함수
35+
def _find_next_service(self) -> None:
36+
self.next_name, self.next_port = next(
37+
((name, port) for name, port in self.services.items() if name != self.current_name),
38+
(None, None)
39+
)
40+
41+
# Docker 컨테이너를 제거하는 함수
42+
def _remove_container(self, name: str) -> None:
43+
os.system(f"docker stop {name} 2> /dev/null")
44+
os.system(f"docker rm -f {name} 2> /dev/null")
45+
46+
# Docker 컨테이너를 실행하는 함수
47+
def _run_container(self, name: str, port: int) -> None:
48+
os.system(
49+
f"docker run -d --name={name} --restart unless-stopped -p {port}:8090 -e TZ=Asia/Seoul -v /dockerProjects/cmf/volumes/gen:/gen --pull always ghcr.io/commitfield/commitfield")
50+
51+
def _switch_port(self) -> None:
52+
# Socat 포트를 전환하는 함수
53+
cmd: str = f"ps aux | grep 'socat -t0 TCP-LISTEN:{self.socat_port}' | grep -v grep | awk '{{print $2}}'"
54+
pid: str = subprocess.getoutput(cmd)
55+
56+
if pid:
57+
os.system(f"kill -9 {pid} 2>/dev/null")
58+
59+
time.sleep(5)
60+
61+
os.system(
62+
f"nohup socat -t0 TCP-LISTEN:{self.socat_port},fork,reuseaddr TCP:localhost:{self.next_port} &>/dev/null &")
63+
64+
# 서비스 상태를 확인하는 함수
65+
66+
def _is_service_up(self, port: int) -> bool:
67+
url = f"http://127.0.0.1:{port}/actuator/health"
68+
try:
69+
response = requests.get(url, timeout=5) # 5초 이내 응답 없으면 예외 발생
70+
if response.status_code == 200 and response.json().get('status') == 'UP':
71+
return True
72+
except requests.RequestException:
73+
pass
74+
return False
75+
76+
# 서비스를 업데이트하는 함수
77+
def update_service(self) -> None:
78+
self._find_current_service()
79+
self._find_next_service()
80+
81+
self._remove_container(self.next_name)
82+
self._run_container(self.next_name, self.next_port)
83+
84+
# 새 서비스가 'UP' 상태가 될 때까지 기다림
85+
while not self._is_service_up(self.next_port):
86+
print(f"Waiting for {self.next_name} to be 'UP'...")
87+
time.sleep(self.sleep_duration)
88+
89+
self._switch_port()
90+
91+
if self.current_name is not None:
92+
self._remove_container(self.current_name)
93+
94+
print("Switched service successfully!")
95+
96+
97+
if __name__ == "__main__":
98+
manager = ServiceManager()
99+
manager.update_service()

0 commit comments

Comments
 (0)