본문으로 바로가기
728x90
반응형

개요

요즘엔 프로젝트나 개인 Repository에도 소스를 작성하면 거의 도커라이징을 적용하고 있습니다. 어떤 환경(집, 카페, 다른 장소)에서든 스펙에 맞춰 실행하면 빠른 개발 환경을 구성할 수 있어 편리하기 때문이죠.

보통 Django나 Flask를 도커라이징을 많이 적용했습니다만 이번에 겪은 일은 특정 시간에 한 번만 실행하는 Python Script를 컨테이너로 만들어서 ECS에 올려야 했습니다. 

 

ECS가 뭔데?

ECS는 Elastic Container Service로 클러스터에서 컨테이너를 쉽게 실행, 중지, 및 관리할 수 있게 해주는 컨테이너 관리 서비스입니다.

ECS를 사용하기 이전에 먼저 설정해줘야 하는 부분들이 있는데 바로 'ECR'과 'Task Definition'입니다.

ECR은 Elastic Container Registry로서 build 된 컨테이너 image를 올리는 곳이며 'Task Definition'은 ECR에 등록한 컨테이너 이미지로부터 ECS를 통해 실행할 옵션을 설정해 주는 부분입니다.

이 글에서는 ECR에 컨테이너 이미지를 올리는 방법과 Task Definition 설정에 대한 세부 과정을 다루지는 않습니다.

 

Python Script를 실행하는 컨테이너 만들기

ECS에서 Python Script를 Scheduling 하기 위해 Docker 컨테이너를 실행하면 Python Script가 한 번만 실행되고 종료되는 컨테이너를 만들어봅시다.

우선 간단히 Hello World 문자열을 찍는 app.py를 만듭니다.

# app.py
print("Hello, World")

그리고 당연히 이를 컨테이너 만들기 위한 Dockerfile이 필요합니다. 다음과 같이 작성합니다.

# Dockerfile

FROM python:3.10-slim-buster

WORKDIR /usr/src/app

COPY . .

ENTRYPOINT ["python3", "/usr/src/app/app.py"]

주의할 점은 ENTRYPOINT로써 존재하는 명령입니다.

Dockerfile에서 ENTRYPOINT는 CMD와 인스트럭션과 같이 컨테이너가 수행하게 될 명령을 정의합니다. 컨테이너가 무슨 일을 하는지 결정하게 되는 단계를 정의하기 때문에 ENTRYPOINT와 CMD는 다음과 같은 차이가 있습니다.

  • ENTRYPOINT
    • 컨테이너 실행 시 argument를 받지 않는다
  • CMD
    • 컨테이너 실행 시 argument를 지정하여 CMD 인스트럭션에 정의된 명령 외에 다른 명령을 수행할 수 있다. 

여기서 저는 외부로터 인자를 받아 실행할 일이 없기 때문에 ENTRYPOINT를 사용해 Dockerfile을 작성했습니다. 선택 사항이지만 편의를 위해 저는 docker-compose.yml도 같이 작성해 두곤 합니다.

# docker-compose.yml
version: "3"
services:
  app:
    build: .
    command:
      - python3 /usr/src/app/app.py

지금까지 작성한 파일들을 디렉터리에서 보면 다음과 같습니다.

╰─$ tree -L 1
.
├── Dockerfile
├── app.py
└── docker-compose.yml

이제 docker-compose를 수행하여 image를 빌드해 봅시다.

╰─$ sudo docker-compose up -d
Password:
[+] Building 6.6s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                        0.0s
 => => transferring dockerfile: 145B                                                                                                                                                        0.0s
 => [internal] load .dockerignore                                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                                             0.0s
 => [internal] load metadata for docker.io/library/python:3.10-slim-buster                                                                                                                  5.0s
 => [internal] load build context                                                                                                                                                           0.0s
...
 => [2/3] WORKDIR /usr/src/app                                                                                                                                                              0.0s
 => [3/3] COPY . .                                                                                                                                                                          0.0s
 => exporting to image                                                                                                                                                                      0.0s
 => => exporting layers                                                                                                                                                                     0.0s
 => => writing image sha256:1ad76ef6cdc679fb4bc2054414b2d49a4b4768dd3fa99c09c35234a9ce3b5bef                                                                                                0.0s
 => => naming to docker.io/library/script-container-app                                                                                                                                     0.0s
[+] Running 2/2
 ⠿ Network script-container_default  Created                                                                                                                                                0.1s
 ⠿ Container script-container-app-1  Started
 
 ╰─$ sudo docker images
REPOSITORY                  TAG          IMAGE ID       CREATED         SIZE
script-container-app        latest       1ad76ef6cdc6   9 seconds ago   112MB

docker image를 실행하면 Hello World가 출력되는 걸 확인할 수 있습니다.

╰─$ sudo docker run script-container-app
Hello, World

 

이제 ECS에 올려보자

지금까지 구성한 내용을 ECS에 올리기 위해서는 "예약된 작업"이라는 항목을 사용해야 합니다.

이 "예약된 작업"을 생성할 때 스케줄링을 하기 위해서 등록하는 옵션을 설정할 수 있습니다.

제 경우엔 cron 식을 이용했는데 주의할 점은 AWS의 Cron 식은 GMT를 기준으로 합니다. 즉 한국에서 실행하기 위해서는 시간대를 맞춰주는 작업이 필요합니다. 저는 편의를 위해 https://savvytime.com/converter/gmt-to-kst에서 시간대를 참고하여 적용했습니다.

예를 들어, 월요일에서부터 금요일에 한국 시간으로 오후 6시에 실행하고 싶다면 다음과 같이 적용합니다.

cron(0 9 ? * MON-FRI *) # 9시는 GMT이며 한국시간으로 오후 6시

 


잘 돌아가나?

생성에 완료된 경우 ECS 클러스터의 "작업" 탭에서 설정된 시간에 맞춰 인스턴스가 준비되는 결과를 볼 수 있습니다. 또한 이렇게 등록된 Cron 식은 AWS의 EventBridge에서 Scheduling 된 예약 시간을 확인할 수도 있습니다.

EventBridge > 버스 > 규칙 > "클러스터에 매칭된 이름" > 이벤트 일정

사실 이렇게까지 구성하고도 컨테이너 안에 작성된 소스를 모니터링해야 되는 상황도 있습니다. 이럴 땐 Slack으로 Notification을 발생한다든지 혹은 CloudWatch를 이용해 로그를 남기는 선택도 고려해 볼 수 있습니다.

 

끝으로..

ECS를 선택해 환경을 구성하기 전에 최근 사용했던 App Runner를 사용해 볼까도 고려했습니다. App Runner를 사용하지 못한 이유는 다음과 같습니다.

  • App Runner는 특성상 Web에 맞춰져 있기 때문에 Python Script를 실행할 수 있는 방법을 구성할 수 있을지에 대한 정보를 찾지는 못함
  • App Runner는 배포 시간이 ECS보다 늦기 때문에 특정 Event에 알림을 처리해 주는 단발성 작업에 대한 소요 시간이 더 많아질 거라 판단  

컨테이너를 통해 수행할 작업이 무엇인지에 따라 적절한 방법을 택하는 것이 중요하다 봅니다.



 

728x90
반응형