https://www.udemy.com/course/docker-kubernetes-2022/
자체 EC2 인스턴스 관리의 부담을 덜기 위해 클라우드 서비스 프로바이더 기술을 사용한다.
이번 시간에는 AWS ECS를 활용한 프로덕션 환경 구성에 대해 알아본다.
ECS 아키텍쳐, 개념
ECS는 AWS의 컨테이너 오케스트레이션 도구로, 다음 4가지 계층으로 구성된다.
| 개념 | 설명 | 역할 |
| Cluster | 컨테이너가 실행되는 논리적 네트워크 공간 | 인프라의 경계 (VPC 설정 등) |
| Service | 테스크의 실행 관리자 (Auto Scaling, 로드 밸런싱) | n개 테스크 유지와 같은 역할을 수행 |
| Task Definition | 애플리케이션의 블루프린트 - Docker Image, CPU/RAM, Env |
docker-compose.yml과 유사한 역할 |
| Container | 실제 실행되는 프로세스 | 개별 Docker 컨테이너 |
AWS Fargate
- EC2 인스턴스를 직접 관리하지 않는 서버리스 컴퓨팅 엔진
- 클러스터 생성 시 Fargate 선택 시 인프라 관리 없이 컨테이너 실행 리소스에 대해서만 비용을 지불한다.
근데 docker-compose 쓰면.. 되는 거 아닌가?
로컬 개발 환경에서는 docker-compose up 명령어 하나로 DB와 서버를 띄우고 네트워크도 자동 연결되어 매우 편리하다.
하지만 프로덕션(Cloud) 환경에서는 적합하지 않다.
| 구분 | Docker Compose (Local) | AWS ECS (Production) |
| 실행 위치 | 내 컴퓨터 한 대에서 모든 컨테이너 실행 | 여러 데이터 센터의 수십 대 서버에 분산 실행 |
| 장애 대응 | 컴퓨터가 꺼지면 서비스 전체 중단 | 서버 하나가 고장 나도 다른 서버의 컨테이너가 작동 |
| 스케일링 | 수동으로만 가능 | 트래픽에 따라 자동으로 컨테이너 개수를 조절(Auto Scaling) |
| 네트워크 | 내부망에서 컨테이너 이름으로 통신 | IP가 수시로 바뀌므로 ALB가 필수 |
따라서 Docker Compose 파일은 배포의 Reference 역할만 하는 것이 좋고, 클라우드 환경에서는 AWS 방식인 Task Definition으로 다시 정의해야 한다.
서버 컨테이너 구성
서버 애플리케이션(etc: Node.js)을 배포하기 위한 Dockerfile 구성 및 이미지 빌드 과정에 대해 간단히 알아본다.
Dockerfile 작성
- 프로덕션 환경에서는 nodemon 같은 개발 도구를 제외하고, 가볍고 안정적인 런타임 환경을 구성한다.
# Node.js 런타임 베이스 이미지
FROM node:14-alpine
WORKDIR /app
# 종속성 설치
COPY package.json .
RUN npm install
# 소스 코드 복사
COPY . .
# 프로덕션 실행을 위한 포트 노출
EXPOSE 80
# [중요] 개발용(nodemon)이 아닌 node 런타임으로 직접 실행
CMD ["node", "app.js"]
이미지 빌드 및 푸시 (Docker Command)
- ECS가 이미지를 가져갈 수 있도록 Docker Hub(또는 ECR)에 이미지를 업로드해야 한다.
# 1. 이미지 빌드 (태그 지정)
docker build -t my-server-app .
# 2. 리모트 저장소용 태그 생성 (docker hub ID 필요)
docker tag my-server-app my-docker-id/my-server-app
# 3. Docker Hub로 푸시
docker push my-docker-id/my-server-app
프론트엔드 컨테이너 배포
SPA(Single Page Application)인 React 배포에 대해 알아본다.
- React는 브라우저에서 실행되므로 Node.js가 아닌 웹 서버(Nginx)가 필요하다.
- 이를 위해 Multi-stage-Build 기법을 사용한다.
??
왜 Node.js가 아니라 Nginx이고, 멀티 스테이지 빌드가 뭔데요...
??
우선 이해해야 할 것은 개발 환경(npm start)과 프로덕션 환경의 차이다.
- 개발 환경 (Node.js)
- 우리가 코드를 수정하면 npm start가 돈다.
- 이때 Node.js는 파일을 감시하고, 즉시 변환(Transpiling)해서 브라우저에 띄워준다.
- 즉, 개발 중에는 Node.js가 "요리사" 역할을 하며 주방(서버)에서 계속 요리를 한다.
- 프로덕션 환경 (Browser + Nginx)
- React 코드는 브라우저가 직접 해석할 수 없는 문법(JSX, 최신 JS)을 포함한다.
- 그래서 빌드(npm run build) 과정을 거치면, 브라우저가 이해할 수 있는 순수한 정적 파일(HTML, CSS, JS)로 변환된다.
- 이제 더 이상 요리사(Node.js)는 필요 없다.
- 완성된 요리(정적 파일)를 손님(브라우저)에게 전달해 줄 "웨이터"만 있으면 된다.
- 이 웨이터 역할에 최적화된 것이 바로 Nginx다.
짧게 말해서 Node.js는 연산이 가능한 무거운 서버 엔진이고, Nginx는 파일을 아주 빠르게 전달하는 경량 웹 서버다. 정적 파일만 주면 되는 상황에서 Node.js를 쓰는 것은 비효율적이다.
멀티 스테이지 빌드는 곧
- Docker 이미지를 만들 때 빌드 도구, 실행 환경을 분리하여 이미지를 가볍게 만드는 기술이다.
1단계: 빌드 스테이지
여기서는 Node.js 이미지를 사용한다. 목적은 npm install, npm run build를 실행하는 것이다.
- 입력: 소스 코드 전체, node_modules (수백 MB)
- 작업: 코드 압축, 최적화, 변환
- 출력: /app/build 폴더 안에 생성된 정적 파일들 (몇 MB)
2단계: 실행 스테이지
여기서는 Nginx 이미지를 사용한다. Node.js는 없다.
- 작업: 1단계(공장)에서 생성된 결과물(/app/build)만 쏙 뽑아서 가져온다.
- 결과: 소스 코드도 없고, node_modules도 없는, 오직 실행에 필요한 파일만 담긴 가벼운 이미지가 완성된다.
3단계: Dockerfile.prod 작성 (Multi-stage)
# [Stage 1] 공장 (Builder)
# 'as build'라고 별명을 붙여서 나중에 이 단계를 참조할 수 있게 한다.
FROM node:14-alpine as build
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
# 여기서 React 코드가 HTML/CSS/JS로 변환된다.
# 결과물은 /app/build 폴더에 생깁니다.
RUN npm run build
# [Stage 2] 배송 (Runner)
# 새로운 베이스 이미지로 시작한다. (이전 단계의 node 환경은 다 버려진다)
FROM nginx:stable-alpine
# [핵심] --from=build 옵션 사용
# "내 로컬 컴퓨터가 아니라, 아까 'build' 단계의 /app/build 폴더에서 파일을 가져와"
COPY --from=build /app/build /usr/share/nginx/html
# Nginx가 80번 포트로 서비스를 제공한다.
EXPOSE 80
# Nginx 실행
CMD ["nginx", "-g", "daemon off;"]
- 이 방식을 사용하면 최종 이미지에는 Node.js 런타임이나 원본 소스 코드(etc: node_modules)가 남지 않아 이미지 크기가 획기적으로 줄어든다.
ECS 설정과 Docker 명령어
AWS ECS 콘솔에서 설정하는 항목들은 실제 docker run 명령어의 옵션들과 1:1로 대응된다.
| ECS Task Definition 설정 | docker run 명령어 옵션 | 설명 |
| Image URI | image_name:tag | 사용할 이미지 주소 (Docker Hub/ECR) |
| Port Mappings | -p 80:80 | 호스트 포트, 컨테이너 포트 연결 |
| Environment Variables | --env KEY=VALUE | 환경 변수 주입 (DB 주소, 비번 등) |
| Command | command override | Dockerfile의 CMD를 덮어씌울 때 사용 |
| CPU / Memory | --cpus, --memory | 컨테이너가 사용할 리소스 제한 |
| Working Directory | --workdir | 컨테이너가 실행될 때 기본 위치 폴더(현재 위치)를 지정 |
| Mount points | -v | 외부 저장 공간(볼륨)을 컨테이너 내부의 특정 폴더와 연결 |
주의 사항
배포 과정에서 가장 빈번하게 발생하는 문제, 그에 대한 솔루션이다.
A. 무한 재시작 (Restart Loop) & Health Check 실패
- 현상: 로드 밸런서(ALB) 연결 후 태스크가 RUNNING → STOPPED를 반복함.
- 원인
- ALB는 기본적으로 루트 경로(/)로 상태 확인(Health Check) 요청을 보낸다.
- 하지만 백엔드 API가 / 경로에 대한 응답을 처리하지 않으면(404 Not Found), ALB는 컨테이너가 고장 났다고 판단해 강제로 재시작시킨다.
- 해결
- ALB의 Target Group 설정에서 Health Check Path를 실제 응답 가능한 경로(예: /api/health, /goals 등)로 변경해야 한다.
B. 데이터베이스 연결 실패 (네트워킹)
- 현상: 컨테이너 실행 중 DB 연결 에러 발생.
- 원인
- 로컬 사고방식: ECS에서는 localhost가 자기 자신(컨테이너)을 가리키므로, DB가 다른 컨테이너나 외부에 있다면 접속할 수 없다. (단, 같은 Task 내에 Sidecar로 떠 있는 경우는 localhost 통신 가능)
- 보안 그룹(Security Group): DB(RDS/Atlas)가 ECS 서비스의 보안 그룹에서의 접근을 허용하지 않음.
- 해결
- DB 접속 주소를 환경 변수로 주입 (MongoDB Atlas URL 등).
- DB의 보안 그룹/IP 화이트리스트에 "0.0.0.0/0 (테스트용)" 혹은 "ECS Cluster의 VPC 대역"을 허용.
C. 데이터 소실 (Stateless)
- 현상: 배포(업데이트)할 때마다 저장된 파일이나 DB 데이터가 초기화됨.
- 이론: 컨테이너는 Stateless(무상태)이므로 삭제 시 내부 데이터도 삭제된다.
- 해결
- 파일: AWS EFS(Elastic File System)를 볼륨으로 마운트하여 사용.
- DB: 컨테이너 내부에 DB를 두지 말고, AWS RDS나 MongoDB Atlas 같은 관리형 DB 서비스를 외부에서 연결하여 사용.
자동 배포 및 업데이트 흐름
소스 코드가 수정되었을 때 프로덕션에 반영하는 절차에 대해 알아보자.
- Local: 코드 수정 및 테스트.
- Build, Push
docker build -t my-app:latest . docker push my-app:latest - Deploy (ECS):
- ECS 서비스 업데이트 화면 진입.
- Force new deployment 체크 후 업데이트 실행.
- 작동 원리
- ECS는 기존 태스크 정의를 유지하되, 레지스트리에서 최신 이미지를 새로 다운로드하여 컨테이너를 교체(Rolling Update)한다.
반응형
'Infra > Docker' 카테고리의 다른 글
| 컨테이너 배포: 수동(자체 관리형) (0) | 2025.12.14 |
|---|---|
| Docker Networks (0) | 2025.12.12 |
| Docker Data & Volumes (0) | 2025.12.12 |
| Docker Image & Container 명령어 정리 (0) | 2025.12.12 |
| Container의 근반이 되는 기술에 대해 알아보자(완) UnionFS (0) | 2025.12.11 |