[CI/CD] AWS SSM, Docker, Github Action으로 EC2에 Flask 컨테이너 배포 자동화하기

lighteko·2025년 1월 27일

개발새발

목록 보기
3/3
post-thumbnail

들어가며

이전 포스트에서 AWS EKS와 씨름을 하면서, 요금 폭탄의 낌새를 감지하고 EC2 컨테이너로 눈을 돌렸었다.
EC2는 프리티어가 적용되기 때문에 EKS에 비하면 요금 걱정을 하지 않아도 된다.

여기서 새로 등장한 AWS SSM은 Github Action에서 EC2 연결을 할 때 중요한 역할을 담당한다.
기존의 SSH 연결을 활용하게 되면, EC2에서 Github Action이 사용하는 IP 범위를 모두 허용해야하기 때문에 보안 위협이 생긴다.

그래서 SSM을 활용하여 안전하게 Github Action을 이용할 계획이다.

1. EC2 시작하기

EC2 생성하는 것은 스킵하고,
인바운드 규칙에 우리 웹서버가 동작할 커스텀 TCP 포트를 열어주어야 한다.
또한 고정 아이피를 할당해야 하기 때문에 Elastic IP Address를 생성해준다.
실제로 API 요청을 이 고정 아이피로 보내게 된다.

2. SSM 시작하기

먼저 AWS의 Systems Manager 대시보드에 가서 활성화한다.
그리고, EC2에 AmazonEC2RoleforSSM 정책이 달린 IAM 역할을 할당해준다.

3. Docker 및 Docker Compose 설치하기

이제 EC2 인스턴스에 SSH로 접속해서 Docker Compose를 설치해주어야 한다.

나는 PuTTy를 사용해서 SSH로 원격 연결을 했는데, 다른 방법도 있다.
어쨋든 접속 후에는, ubuntu 유저로 로그인하면 된다.

# Update and install Docker
sudo apt update -y
sudo apt install docker.io -y
sudo systemctl start docker
sudo systemctl enable docker

# Install Docker Compose (Optional for multi-container setups)
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

이렇게 Docker와 Docker Compose를 EC2 인스턴스에 설치해준다.

4. Docker Compose 파일 작성

Docker Compose 파일을 작성해서 다중 컨테이너 관리를 편리하게 할 수 있다.

version: "3"
services:
  service1:
    image: <도커 허브 / ECR에 올린 이미지 주소>
    container_name: <컨테이너 이름>
    ports:
      - "<아웃 바운드 포트>:<인 바운드 포트>"
    restart: always
    environment:
      - 환경변수 작성

  service2:
    image:  <도커 허브 / ECR에 올린 이미지 주소>
    container_name: <컨테이너 이름>
    ports:
      - "<아웃 바운드 포트>:<인 바운드 포트>"
    restart: always
    environment:
      - 환경변수 작성
...

이런 식으로 작성하면 된다.

어디에 작성하느냐!

cd ~ 명령으로 홈으로 가준 뒤, mkdir projectname 명령으로 프로젝트 폴더를 만든다.
vi docker-compose.yaml 명령으로 편집기를 열고 작성하면 된다.
저장은 이미 알고 있으시겠지만, esc 키 누른 후 :wq 입력하면 된다.

이 파일이 하는 역할은, docker compose로 빌드 명령이 왔을 때 서비스별로 정해진 이미지로 컨테이너를 빌드하는 것이다.

5. Dockerfile 작성

Flask 서버에 대해 Dockerfile을 작성해서 컨테이너로 만들 수 있게 해주어야 한다.

FROM python:3.11-slim

WORKDIR /app

COPY . .

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 5000

CMD ["python", "main.py"]

프로젝트 루트 디렉토리에 이런 식으로 작성하면 된다.
requirements.txt에는 버전 부분을 다 지우는 것을 추천한다.

6. Github Workflow 생성

이제 Flask 서버를 자동으로 올리기 위해 프로젝트 루트 디렉토리에 .github/workflows 폴더를 새로 만들어준다.

그리고 deploy.yaml이라는 파일 하나를 생성한다.

name: Deploy Flask App to EC2
on:
  pull_request:
    branches:
      - main
    types:
      - closed # Trigger when the PR is closed (merged)

jobs:
  deploy:
    if: github.event.pull_request.merged == true 
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push Docker image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/<image>
          docker push ${{ secrets.DOCKER_USERNAME }}/<image>

      - name: Deploy to EC2
        uses: peterkimzz/aws-ssm-send-command@v1.1.1
        id: ssm
        with:
          aws-region: ${{ secrets.AWS_REGION }}
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          instance-ids: ${{ secrets.INSTANCE_ID }}

          working-directory: /home/ubuntu/<project directory>
          command: docker-compose -f docker-compose.yaml up -d <service name>

이런 식으로 파일을 작성하면 된다.

차근 차근 읽어보자.

on:
  pull_request:
    branches:
      - main
    types:
      - closed # Trigger when the PR is closed (merged)

이 부분은 이 액션이 언제 트리거되는지를 명시한다.
main 브랜치에 PR이 closed 되면 트리거되는 것이다.
그런데, merge하지 않고 리젝돼서 closed 된 PR도 있을 것이다.

jobs:
  deploy:
    if: github.event.pull_request.merged == true 
    runs-on: ubuntu-latest

그래서 이렇게 조건을 걸어서 merge 되었을 때만 작업이 실행될 수 있게 세팅해준다.

steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push Docker image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/<image>
          docker push ${{ secrets.DOCKER_USERNAME }}/<image>

이 부분은 Docker hub에 로그인하고 이미지를 빌드하는 스텝이다.
레포지토리 설정에서 secrets 에 가면, 환경변수를 설정해줄 수 있다.

이렇게 하면 Docker hub에 이미지가 업로드된다. Docker hub 말고 AWS의 ECR을 사용할 수도 있다. 그러면 IAM 사용자 Configuration 하는 스텝이 먼저 필요할 것이다.

- name: Deploy to EC2
       uses: peterkimzz/aws-ssm-send-command@v1.1.1
       id: ssm
       with:
         aws-region: ${{ secrets.AWS_REGION }}
         aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
         aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
         instance-ids: ${{ secrets.INSTANCE_ID }}

         working-directory: /home/ubuntu/<project directory>
         command: docker-compose -f docker-compose.yaml up -d <service name>

마지막 스텝으로, EC2에 Deploy 하는 스텝이다.

누군가 굉장히 좋은 액션을 구조화 해놓으셔서 그대로 이용했다.
command 필드에 실행할 명령을 작성해놓으면, SSH연결 없이도 EC2에서 명령을 실행할 수 있다.

docker-compose 파일에서 명시해놨던 서비스에서 docker hub에 저장된 이미지를 받아오고 자동으로 빌드해준다.

0개의 댓글