Skip to content

Commit 391f94a

Browse files
authored
Merge branch 'dev' into Refactor/134
2 parents f8b47ef + 4b33ece commit 391f94a

File tree

8 files changed

+584
-23
lines changed

8 files changed

+584
-23
lines changed

.github/workflows/backend-cd.yml

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,29 @@ 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 # 도커 네트워크
85+
EC2_INSTANCE_TAG_NAME: team5-ec2-1
86+
8187
steps:
82-
- name: set lower case owner name
88+
- uses: aws-actions/configure-aws-credentials@v4
89+
with:
90+
aws-region: ${{ secrets.AWS_REGION }}
91+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
92+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
93+
94+
- name: get instance id
8395
run: |
84-
echo "OWNER_LC=${OWNER,,}" >> ${GITHUB_ENV}
96+
INSTANCE_ID=$(aws ec2 describe-instances \
97+
--filters "Name=tag:Name, Values=${{ env.EC2_INSTANCE_TAG_NAME }}" "Name=instance-state-name, Values=running" \
98+
--query "Reservations[].Instances[].InstanceId" --output text)
99+
[[ -n "${INSTANCE_ID}" && "${INSTANCE_ID}" != "None" ]] || { echo "No running instance found"; exit 1; }
100+
echo "INSTANCE_ID=${INSTANCE_ID}" >> "${GITHUB_ENV}"
85101
86102
- name: AWS SSM Send-Command
87103
uses: peterkimzz/aws-ssm-send-command@master
@@ -90,22 +106,132 @@ jobs:
90106
aws-region: ${{ secrets.AWS_REGION }}
91107
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
92108
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
93-
instance-ids: "i-00e384163ab61f6cc"
109+
instance-ids: ${{ env.INSTANCE_ID }}
94110
working-directory: /
95111
comment: Deploy
96112
command: |
97-
# 0. env 변수 확인
98-
echo "OWNER_LC = ${{ env.OWNER_LC }}"
113+
set -Eeuo pipefail
114+
115+
# 1. EC2 인스턴스 아이디 확인
116+
echo "INSTANCE_ID=${INSTANCE_ID}"
117+
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+
99234
100-
# 1. 최신 이미지 pull
101-
docker pull ghcr.io/${{ env.OWNER_LC }}/catfe-backend:latest
102235
103-
# 2. 기존 컨테이너 종료 및 제거
104-
docker stop catfe-backend 2>/dev/null
105-
docker rm catfe-backend 2>/dev/null
106236
107-
# 3. 새로운 컨테이너 실행
108-
docker run -d --name catfe-backend -p 8080:8080 ghcr.io/${{ env.OWNER_LC }}/catfe-backend:latest
109237
110-
# 4. dangling 이미지 삭제
111-
docker rmi $(docker images -f "dangling=true" -q)

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies {
3030
implementation("org.springframework.boot:spring-boot-starter-websocket")
3131
implementation("org.springframework.boot:spring-boot-starter-validation")
3232
implementation("org.springframework.boot:spring-boot-starter-mail")
33+
implementation("org.springframework.boot:spring-boot-starter-actuator")
3334

3435
// Database & JPA
3536
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

infra/terraform/main.tf

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,15 @@ sudo mkswap /swapfile
194194
sudo swapon /swapfile
195195
sudo sh -c 'echo "/swapfile swap swap defaults 0 0" >> /etc/fstab'
196196
197-
# git 설치
198-
yum install git -y
199-
200-
#도커 설치 및 실행/활성화
197+
# 환경변수 세팅(/etc/environment)
198+
echo "PASSWORD=${var.password_1}" >> /etc/environment
199+
echo "DOMAIN=${var.catfe_domain_1}" >> /etc/environment
200+
echo "GITHUB_ACCESS_TOKEN_OWNER=${var.github_access_token_1_owner}" >> /etc/environment
201+
ehco "GITHUB_ACCESS_TOKEN=${var.github_access_token_1}" >> /etc/environment
202+
# EC2 환경변수 등록
203+
source /etc/environment
204+
205+
# 도커 설치 및 실행/활성화
201206
yum install docker -y
202207
systemctl enable docker
203208
systemctl start docker
@@ -229,6 +234,8 @@ docker run -d \
229234
-v /dockerProjects/npm_1/volumes/etc/letsencrypt:/etc/letsencrypt \
230235
jc21/nginx-proxy-manager:latest
231236
237+
# ghcr.io 로그인
238+
echo "${var.github_access_token_1}" | docker login ghcr.io -u ${var.github_access_token_1_owner} --password-stdin
232239
233240
END_OF_FILE
234241
}

infra/terraform/variables.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
variable "catfe_domain_1" {
2+
description = "api.catfe.site"
3+
}

src/main/java/com/back/domain/study/todo/controller/TodoController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public ResponseEntity<RsData<TodoResponseDto>> updateTodo(
7373
}
7474

7575
// 할 일 완료/미완료 토글
76-
@PutMapping("/{todoId}/complete")
76+
@PutMapping("/{todoId}/toggle")
7777
@Operation(summary = "할 일 완료 상태 토글", description = "할 일의 완료 상태를 변경합니다.")
7878
public ResponseEntity<RsData<TodoResponseDto>> toggleTodoComplete(
7979
@AuthenticationPrincipal CustomUserDetails userDetails,

src/main/java/com/back/domain/study/todo/repository/TodoRepository.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77

88
import java.time.LocalDate;
99
import java.util.List;
10-
import java.util.Optional;
1110

1211
@Repository
1312
public interface TodoRepository extends JpaRepository<Todo, Long> {
1413
List<Todo> findByUserIdAndDate(Long userId, LocalDate date);
1514
List<Todo> findByUserId(Long userId);
16-
Todo findByIdAndUser(Long id, User user);
1715
}

src/main/java/com/back/global/security/SecurityConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
4444
//.requestMatchers("/api/rooms/RoomChatApiControllerTest").permitAll() // 테스트용 임시 허용
4545
.requestMatchers("/","/swagger-ui/**", "/v3/api-docs/**").permitAll() // Swagger 허용
4646
.requestMatchers("/h2-console/**").permitAll() // H2 Console 허용
47+
.requestMatchers("/actuator/health").permitAll() // 헬스 체크 허용
4748
.anyRequest().authenticated()
4849
)
4950

0 commit comments

Comments
 (0)