파일질라 배포에서 ECS Fargate 무중단 배포까지 — 왜 매번 인프라를 바꿨나
FileZilla 수동 배포 → CodeDeploy → ECS Fargate까지, 두 번의 배포 개선을 관통하는 이야기. 수동 배포의 진짜 비용과 컨테이너로 넘어간 이유.
파일질라 배포에서 ECS Fargate 무중단 배포까지 — 왜 매번 인프라를 바꿨나
들어가며
배포 방식을 두 번 바꿨다. 첫 번째는 교육 플랫폼에서, 두 번째는 의료 플랫폼에서.
바꿀 때마다 이유가 달랐다. 첫 번째는 "사람이 하면 실수가 난다"는 문제였고, 두 번째는 "EC2로는 서비스 중단 없이 배포할 수 없다"는 문제였다. 이 두 번의 경험을 같이 쓰는 이유는, 배포 자동화는 한 번에 완성되는 게 아니라 팀과 서비스가 성장하면서 단계적으로 발전하는 것이라는 걸 보여주고 싶어서다.
1단계: 파일질라 배포 (교육 플랫폼)
어떻게 배포했나
당시 배포 과정은 이랬다.
- 로컬에서
mvn package로 JAR 빌드 - FileZilla로 EC2 서버에 접속
- 기존 JAR 백업
- 새 JAR 업로드
- SSH 접속 → 프로세스 종료 → 재시작
- 로그 확인
담당자가 이 순서를 외우고 있어야 했다. 문서가 있긴 했지만 최신이 아닌 경우도 있었다.
진짜 문제가 뭐였나
"사람이 직접 하면 실수가 난다"는 말은 너무 당연해서 오히려 와닿지 않을 수 있다. 실제로 겪었던 상황을 말하면 더 구체적이다.
- 스테이징 서버에 배포해야 하는데 프로덕션 서버에 배포한 적이 있었다
- JAR 업로드 중간에 연결이 끊겨서 반쯤 깨진 JAR이 올라간 적이 있었다
- 테스트가 실패했는데도 "일단 배포하고 보자"는 결정이 가능했다
이런 문제들의 공통점은 배포 과정이 사람의 기억과 판단에 의존한다는 것이었다. 문서가 있어도 사람이 실행하는 이상 오차가 생긴다.
AWS CodeDeploy 도입
배포를 자동화하자고 제안했다. 선택한 도구는 AWS CodeDeploy였다.
# appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/app
hooks:
BeforeInstall:
- location: scripts/stop_server.sh
AfterInstall:
- location: scripts/start_server.sh
ValidateService:
- location: scripts/validate_service.shJenkins에 빌드 파이프라인을 연결하고, 코드가 머지되면 자동으로 빌드 → 테스트 → CodeDeploy 배포까지 이어지게 했다.
코드 머지 → Jenkins 트리거
→ Maven 빌드
→ JUnit 테스트 (실패 시 중단)
→ S3에 아티팩트 업로드
→ CodeDeploy 배포 시작
→ EC2에 새 JAR 배포 및 재시작
이제 테스트가 실패하면 배포가 중단됐다. 서버를 직접 만질 필요가 없어졌다. 배포 로그가 CodeDeploy 콘솔에 남았다.
이 단계의 한계
CodeDeploy로 자동화는 됐는데, 한 가지 문제가 남았다. 배포할 때마다 프로세스를 내리고 올리는 과정에서 서비스가 잠깐 중단됐다. 짧게는 10초, 길게는 30초 정도.
교육 플랫폼이라 낮 시간대 트래픽이 집중됐다. 배포를 새벽에 해야 하는 제약이 생겼다. 새벽 배포는 또 다른 리스크였다. 이 문제는 다음 회사에서 풀게 된다.
2단계: ECS Fargate + GitHub Actions (의료 플랫폼)
새 환경의 상황
새로운 환경에 합류했을 때 배포 방식은 1단계 초기 상태와 비슷했다. EC2 서버에 직접 SSH 접속해서 스크립트를 실행하는 방식이었다.
다른 점이 있다면, 이제 내가 인프라 전체를 설계할 수 있는 상황이었다는 것이다. 처음부터 제대로 구성할 기회였다.
왜 EC2가 아닌 ECS Fargate였나
CodeDeploy + EC2도 나쁜 선택은 아니다. 이미 검증된 조합이다. 그런데 이번에는 처음부터 무중단 배포가 요구사항이었다. 의료 플랫폼이라 병원 운영 시간 중에도 배포가 가능해야 했다.
EC2에서 무중단 배포를 하려면 Blue/Green 배포나 로드밸런서 설정이 복잡해진다. 인스턴스를 2개 유지해야 하고, AMI 관리도 해야 한다.
컨테이너 기반으로 가면 이 복잡함이 상당히 줄어든다.
ECS Fargate를 선택한 이유
- 롤링 업데이트 내장: 새 태스크가 헬스 체크를 통과한 뒤 기존 태스크를 내린다. 서비스 중단이 없다
- 서버 관리 불필요: Fargate는 서버리스 컨테이너다. EC2 인스턴스 패치, 용량 관리를 신경 쓰지 않아도 된다
- 태스크 정의 버전 관리: 배포할 때마다 태스크 정의 새 버전이 생긴다. 롤백이 이전 버전 번호 하나로 해결된다
- ALB와의 자연스러운 통합: Application Load Balancer와 연결하면 헬스 체크 기반 트래픽 전환이 자동이다
전체 파이프라인 구조
코드 푸시 (main 브랜치)
→ GitHub Actions 트리거
→ Gradle 빌드 + 테스트
→ Docker 이미지 빌드
→ GHCR (GitHub Container Registry) 푸시
→ ECS 태스크 정의 새 버전 생성
→ ECS 서비스 업데이트
→ 롤링 업데이트 시작
→ 새 태스크 시작
→ ALB 헬스 체크 통과 확인
→ 기존 태스크 종료
→ 배포 완료
# .github/workflows/deploy.yml (핵심 부분)
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Build and push Docker image
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Update ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api
image: ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: snsb-api-service
cluster: snsb-cluster
wait-for-service-stability: true롤링 업데이트가 어떻게 무중단을 보장하나
ECS의 롤링 업데이트 흐름을 구체적으로 보면 이렇다.
기존 상태: 태스크 A (구버전) 실행 중, ALB가 트래픽 전달 중
1. 새 태스크 B (신버전) 시작
2. B가 ALB 헬스 체크 통과할 때까지 대기
GET /actuator/health → 200 OK
3. ALB가 B로 트래픽 전환
4. A에 새 요청이 오지 않음
5. A가 처리 중인 요청 완료 후 종료 (Graceful Shutdown)
6. 배포 완료
핵심은 헬스 체크를 통과한 뒤에만 트래픽이 전환된다는 것이다. 새 버전에 버그가 있어서 헬스 체크가 실패하면 기존 태스크가 계속 살아서 서비스를 유지한다.
롤백은 어떻게
배포가 잘못됐을 때 롤백이 쉬워야 한다. CodeDeploy 시절엔 이전 JAR을 찾아서 다시 올려야 했다.
ECS는 태스크 정의가 버전으로 관리된다.
# 이전 태스크 정의 버전으로 롤백
aws ecs update-service \
--cluster snsb-cluster \
--service snsb-api-service \
--task-definition snsb-api-task:42 # 이전 버전 번호명령어 한 줄로 이전 버전으로 돌아간다. 이전 버전의 Docker 이미지는 GHCR에 남아 있다.
검증 서버와 운영 서버 분리
같은 파이프라인에서 환경을 분리했다.
main 브랜치 푸시 → 검증(Staging) 환경 자동 배포
태그(v*.*.*) 푸시 → 운영(Production) 환경 배포
운영 배포는 태그를 생성해야만 가능하다. 실수로 main에 푸시했다고 운영이 배포되는 일이 없다.
두 번의 개선을 돌아보며
| 항목 | 파일질라 | CodeDeploy + EC2 | ECS Fargate |
|---|---|---|---|
| 배포 방식 | 수동 | 자동 | 자동 |
| 테스트 게이트 | 없음 | 있음 | 있음 |
| 서비스 중단 | 있음 (수분) | 있음 (수십 초) | 없음 |
| 롤백 | 수동 (파일 교체) | CodeDeploy 롤백 | 태스크 정의 버전 |
| 서버 관리 | EC2 직접 | EC2 직접 | Fargate (불필요) |
| 인프라 비용 | EC2 고정 비용 | EC2 고정 비용 | 태스크 수 조정 가능 |
배포 방식을 두 번 바꾸면서 느낀 건, **배포 자동화의 목표는 "사람의 개입을 줄이는 것"이 아니라 "배포가 예측 가능하고 반복 가능하게 만드는 것"**이라는 것이다.
파일질라 → CodeDeploy는 "사람의 실수를 없애는 것"이었다. CodeDeploy → ECS는 "서비스 안정성을 배포 과정에서도 유지하는 것"이었다. 각 단계마다 해결해야 할 문제가 달랐고, 그에 맞는 도구를 선택했다.
처음부터 ECS가 정답이 아닐 수 있다. 팀 규모, 서비스 규모, 가용한 인프라 경험에 따라 적절한 단계가 다르다. 중요한 건 지금 겪고 있는 문제가 무엇인지를 정확히 파악하고, 그 문제를 푸는 도구를 고르는 것이다.