컨테이너 서버리스 Fargate 배포하기

컨테이너 서버리스 Fargate 배포하기

최근 개발 중인 앱이, 이제 서비스 준비를 앞 두고 있는데, 개발자가 부족하므로, 운영 환경에 최대한 얽메이지 않고 싶다는 욕구에 의해 컨테이너 서버리스인 Fargate 배포에 눈을 돌리게 되었다. 이 글에서는 AWS ECS를 동작하기 위한 가장 기본적인 개념과 Fargate가 무엇인지 간단하게 설명한 다음 사이드 프로젝트로 진행 중이던 프로젝트를 Fargate 위에 띄우는 것까지 해볼 계획이다.

우선 Fargate가 뭔지, AWS에서 뭐라고 소개하는지 살펴보자. Fargate는 ECS의 서버리스 서비스이다. 공식적인 소개로는, 아래와 같이 작성되어있다.

AWS Fargate는 컨테이너에 적합한 서버리스 컴퓨팅 엔진으로, Amazon Elastic Container Service(ECS) 및 Amazon Elastic Kubernetes Service(EKS)에서 모두 작동합니다.
Fargate는 애플리케이션을 빌드하는 데 보다 쉽게 초점을 맞출 수 있도록 해 줍니다.
Fargate에서는 서버를 프로비저닝하고 관리할 필요가 없어 애플리케이션별로 리소스를 지정하고 관련 비용을 지불할 수 있으며, 계획적으로 애플리케이션을 격리함으로써 보안 성능을 향상시킬 수 있습니다.

운영 환경을 직접 관리해야 하는 경우 보다는 당연히 애플리케이션에 집중할 수 있는 장점이 있다. ECS를 기반으로 하고 있기 때문에 ECS에 대한 개념 부분을 먼저 확인해보자. ECS에 대한 내용은 44bits의 글에서 가져온 내용들이 많다. 나에게 필요한 부분들만 선별해 요약했지만, 자세한 내용을 읽고 싶은 사람에게 꼭 추천한다. (아주 잘 정리 되어있음)

클러스터와 컨테이너 인스턴스

ECS의 가장 기본적인 단위는 클러스터이다. 클러스터는 도커 컨테이너를 실행할 수 있는 가상의 공간이다. 클러스터는 프로젝트나 컨테이너의 성격에 따라 나눠질 수 있다. 클러스터는 컴퓨팅 자원을 포함하고 있지 않은 논리적, 가상적인(즉, 실제 컴퓨팅 자원이 필요하지 않다.) 단위이다. 이후에 EC2에 ecs-client라는 컨테이너 인스턴스의 자원을 모니터링하고, 관리하고, 클러스터로 요청된 컨테이너들을 적절하게 실행하는 역할을 하는 서비스를 실행해서 특정 클러스터에 연결할 수 있다. 이렇게 클러스터에 연결 된 EC2 인스턴스를 컨테이너 인스턴스라고 한다. (Fargate에서는 이처럼 관리를 담당하는 컨테이너가 필요하지 않게 된다.)

Task & Task definition

ECS에서 컨테이너를 실행하는 최소 단위는 Task(태스크)이다. Task는 하나 이상의 컨테이너로 구성된다. 하나의 Task로 실행되는 컨테이너들은 모두 같은 컨테이너 인스턴스에서 실행되는 것이 보장된다.

Task를 실행하려면 Task definition(태스크 디피니션)이 필요하다. Task를 통해 컨테이너를 실행하려면 여러 설정이 필요한데, 이러한 설정들을 미리 하나의 집합 단위로 정의해 놓고 사용하는 것이다. 이러한 집합 단위를 Task definition 이라고 한다. 한 번 Task definition을 설정해 주면, 그것을 기반으로 특정 설정을 변경할 수 있다. 이런식으로 변경된 내용들은 모두 리비전으로 저장된다. (리비전이 뭔지 아래 간단하게 언급하게 된다.)

Service

클러스터에는 두 가지 방식으로 태스크를 실행할 수 있다. 먼저 첫 번째 방식은 Task deifinition을 직접 사용해 태스크를 실행하는 것이다. 이런 식으로 실행된 태스크는 실행 이후 관리되지 않는다. 만약 일회성 프로그램의 경우는 명령 이후 바로 실행된 다음 종료되고, 데몬 프로세스는 프로세스를 종료할 때까지 컨테이너를 남겨둔다.

다른 방식으로는 Service를 정의하는 것이다. 간단하게 Service는 클러스터 내의 태스크를 스케줄링 하는 용도로 사용된다. 만약 EC2를 사용해서 프로세스를 배치하는 경우, 직접 어떤 인스턴스에 어떤 프로세스를 언제 배치할지 결정해야 한다. ECS에서는 Service가 이러한 스케줄링을 담당하고 있는 것이다. Service는 인스턴스에 설치된 ecs-client에서 수집된 정보를 기반으로 어디에 어떤 태스크를 언제 실행할지 결정하게 된다.

Service의 타입으로 두 가지 타입이 있는데, 하나는 리플리카타입, 하나는 데몬 타입이다. 데몬 타입은 컨테이너 인스턴스(위에서 설명한 것처럼, 자원 모니터링, 관리, 컨테이너 실행을 담당하는 인스턴스)에 해당하는 태스크가 하나씩만 실행된다. 이 타입은 인스턴스 관리를 위한 용도로 사용된다. 리플리카 타입을 사용하려면 태스크 개수가 지정되어 있어야 한다. Service는 클러스터에서 이 개수만큼만 태스크가 실행되도록 자동적으로 관리해준다. 리플리카 타입은 실제 애플리케이션용으로 주로 사용된다. 리플리카 타입인 경우, 다수의 컨테이너 인스턴스가 올라갈 때 우리는 언제 어디에서 태스크가 실행될지를 알 수는 없다. Fargate 에서는 앞서 언급한 데몬 타입의 컨테이너 인스턴스가 필요하지 않다. 따라서 Fargate 설정을 선택하면 자동적으로 리플리카만 선택할 수 있다.


이제 직접 Fargate를 구성해보자. 먼저 이 글은 주관적인 경험을 작성 하고 있고, 데모를 위한 앱을 준비하지 않았다. 또한 디테일한 앱의 모습이나, 환경 변수 등도 따로 보여주지 않는다. 이미 돌아가는 앱을 Fargate에 올리는 것에만 집중한 글이다.

우선 현재 개발 중인 프로젝트는 다음과 같이 생긴 흔한 Node 프로젝트이다.

.env는 프로젝트의 환경 변수들을 모아둔 곳이고, Dockerfiles는 배포, 개발 환경 별 도커파일을 담아두는 곳이다.
현재는 개발 환경만 dev.Dockerfiledocker-compose.yml로 도커라이징 되어 있다. 배포를 어떤 걸로 해두지 하고 있었는데, 이제 데모도 만들 겸 Fargate로 처음부터 올려보도록 하겠다.

앱 도커라이징

사실 도커를 사용하게 되면서 고민은 환경 변수, SecretKey 등을 어떻게 전달할 것이냐를 자주 고민했고 케이스도 많이 찾아봤다. 몇 가지 방법 중에서 자주 본 방법은

  1. .env 파일을 S3에 저장하고 환경에 맞게 Dockerfile에 다운로드 하고 export가 돌게 해라.
  2. AWS Systems Manager를 사용해라 (Fargate, ECS에서 해당 서비스에서 환경 변수를 불러올 수 있다.)

이 정도가 가장 기억이 난다. 위 두 가지 방법 중에 두 번째 방법을 트라이 해보고 싶은 생각은 들었지만 이번 프로젝트에서는 .env를 이미지 안에 붙여 넣고 dotenv를 사용해서 넣어줄 생각이다. 협업 하는 분들이 .env를 올바른 경로에 가지고 있어야 한다는 불완전성이 있지만, 개발 인원도 작고 프로젝트도 작으니 Fargate 데모에 집중하기 위해서 간단하게 시도해봤다. 우선 prod.Dockerfile을 만들자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM node:12.15

WORKDIR /usr/src/app

ENV TZ Asia/Seoul

COPY package.json ./
COPY yarn.lock ./
COPY .env/prod.env ./.env

RUN yarn install

COPY dist ./dist

EXPOSE 80

CMD ['yarn', 'start:docker']

도커 이미지를 빌드 할 때 이미지를 가볍게 만들고 싶어서 정말 필요한 것만 넣었다. 컨텍스트도 줄이기 위해서 .dockerignore를 만들어서 복사 제외 대상을 모두 뺐다. 정상적으로 원하는 결과를 얻기 위해서는 도커 이미지를 빌드할 때 애플리케이션이 먼저 빌드 되어야 한다. 이 정도로 간단하게만 작성한 다음 AWS의 도커 이미지 레지스트리인 ECR을 사용해서 빌드한 이미지를 올려보자. 이 이미지는 이후 Fargate에서 돌아갈 컨테이너가 될 것이다.

ECR(Elastic Container Registery)을 이용해 도커 이미지 버전 관리하기

AWS ECR은 프라이빗 도커 레지스트리 서비스이다. 프라이빗 레지스트리를 관리하기 위해서 AWS의 서비스를 사용하려고 한다. 아래 명령어와는 조금 다르지만, AWS에서도 Push 명령어를 확인할 수 있다.

저장소 만들고 도커 로그인

일단 기본적으로 아래 명령어들은 aws cli가 설치되어 있어야 한다. 하지만 설치 과정을 설명하지는 않겠다. (얼마 전 v2가 정식 릴리즈 된 것으로 알고 있는데, 이건 v1을 기준으로 작성 했다.)

1
2
3
4
5
6
7
8
9
# ECR 저장소 만들기
aws ecr create-repository --repository-name [네임스페이스]/[레포지토리이름]
# 이름 앞에 demo는 namespace 역할을 하고 뒷 부분이 저장소 이름이 된다.
# ...결과가 나옴

# ECR 저장소에 로그인 하기, 파이프라인 뒤에 zsh는 각자 환경의 쉘을 사용하자
aws ecr get-login --no-include-email | zsh
# 12시간동안 특정 레지스터에 유효한 토큰을 받는다. 그리고 docker login 명령어에 사용하면 도커를 업로드할 준비가 된 것. 토큰이 만료될 때까지 도커 이미지를 푸시하고 풀 할 수 있음
# ... 결과가 나옴

이미지를 도커 파일 위치에 맞춰주고 컨텍스트는 현재 폴더로 지정한다. Dockerfile 내부에 package.json, yarn.lock, .env 폴더가 있는 곳을 컨텍스트로 둔 것을 가정하고 작성했기 때문에 컨텍스트는 프로젝트의 최상위가 되어야 한다. 태그는 만들었던 ECR 레포지토리로 맞춰줬는데 푸시 하기 위한 설정인 것 같다. 아래 명령어에서 xxxxxxx 부분은 아이디 값 처럼 생긴 고유 숫자가 들어간다. 아래 명령어를 입력하게 되면 좀 전에 만든 ECR 레포지토리로 빌드된 이미지가 올라가게 된다.

1
2
3
docker image build -tag xxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/demo/fargate-demo:latest -f ./Dockerfiles/prod.Dockerfile . # 도커 이미지의 태그를 ECR 태그로 맞춘다.

docker image push xxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/demo/fargate-demo:latest # 도커 이미지 푸시

위 명령어들은 그 전의 태그들이 untagged 되는 것을 고려 하지 않았다. 기존 latest 태그를 다른 태그로 변경해준 다음 푸시하는 작업을 진행하는 것이 좋을 것 같다.

ECS 시작하기

이제 ECS를 본격적으로 배포 해볼 계획인데 아래부터 나오는 이미지의 구체적인 명명과 설명 내용을 최대한 동일하게 글을 작성했지만, 조금씩 다를 수 있다. 설명은 실제 서비스를 하면서 작성한 것이고, 캡쳐는 데모를 준비하면서 뜬 거라… 그런 것이므로 헷갈려 하지 않았으면 좋겠다.

AWS 콘솔에서 ECS 클러스터를 생성해야 한다. 템플릿이 총 세 가지가 있다. 여기서는 Fargate를 제공하는 네트워킹 전용 템플릿을 선택한다.

그 다음으로 클러스터를 구성할 때는 기존의 VPC를 사용할 예정이므로 VPC 생성은 해제 상태로 두고, 컨테이너 인사이트는 뭔지 궁금하니까 눌러둔다. 예시 이미지의 컨테이너 이름은 demo-cluster로 하기로 했다.

Task definition 정의하기

이제 태스크 디피니션을 만들 것인데, 태스크 디피니션은 위에서 설명한 바와 같이 하나 이상의 컨테이너 디피니션들과 컨테이너 실행 환경에 대한 정보를 담고 있는 리소스이다. 클러스터에 종속 되어 있지 않아서 정의한 다음 다른 클러스터에서도 재사용이 가능하다. 컨테이너에서 사용하는 환경변수와 컨테이너 실행 옵션을 포함하고 있다. 개념적으로는 docker-compose.yml과 비슷한 역할을 한다.

태스크 디피니션은 내부적으로 버전 관리가 이루어지는데, 처음 task라는 태스크 디피니션을 만들면, 실제로는 task:1 이런 식으로 버전이 표기 된다. 이런 버전 하나를 리비전이라고 한다. 태스크 디피니션은 0개 이상의 리비전들로 구성된다. task:1인 상태에서 특정 환경 변수를 수정하고 싶을 때는 리비전을 새로 만들어야 한다. 새로 만들어진 리비전은 task:2가 될 것이다.

콘솔에서 태스크 디피니션 (한국어로 작업 정의라고 표현되어있다.)을 만들자.

파게이트로 호환 방식을 설정하고 그 다음 단계에서 이름(작업 정의 이름)과 롤(작업 역할)을 지정해야 한다. 예시에서의 이름은 demo-task-def로 만들고 롤을 따로 지정하지 않았다. 여기서 롤은 IAM 권한을 뜻한다.

그 다음 태스크 실행 롤(작업 실행 역할)과 태스크 사이즈(작업 크기)를 설정해 줘야 한다. 태스크 실행 롤은 태스크에서 컨테이너 이미지를 가져오고, 사용자를 대신해서 CloudWatch에 컨테이너 로그를 게시할 때 이 역할이 필요하다고 한다. (태스크 실행에 IAM을 붙여주는 것 같음) 태스크 사이즈는 세세한 부분까지는 명확하게 이해하지는 못 했는데 태스크가 실행될 때 컴퓨팅 스팩을 정의하는 것 같았다. 우선 둘 다 가장 작은 사이즈로 골랐다. 사이즈 별로 파게이트의 요금 페이지에서 자세한 내용을 확인할 수 있다고 한다.

마지막으로는 컨테이너 정의를 추가 해야 한다. 컨테이너 추가 버튼을 눌러서 컨테이너를 추가해준다.
컨테이너 이름으로 demo-app, 이미지 부분에는 xxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/demo/fargate-demo를 넣어준다. 아래 많은 옵션이 있긴 한데, 작업 디렉토리를 실제 Dockerfile에서 작성한 곳으로 설정 해주는 것 외는 모두 디폴트로 두었다. 이후 실제 도입을 준비하면서 알아보도록 하고, 우선 기본 WORKINGDIR을 맞춰주는 옵션과, 80 포트와 매핑해주는 것으로 마무리 한다. 이후 아래 옵션들도 디폴트 값으로 두고 작업 정의를 생성한다.

컨테이너 외부에 공개

Fargate의 특성상 태스크가 어떤 인스턴스에서 실행될지 알 수 없기 때문에, 외부로 노출하기 위해서는 실행된 서비스의 위치를 파악할 필요가 있다. 이런 것을 디스커버리라고 하는데, AWS에서는 이러한 기능을 LB를 사용해 해결할 수 있다. 일반적으로 로드밸런서를 만들 듯, 이름은 fargate-demo-lb, ALB로 구성하고, http를 열어주었다. 4번째 단계인 라우팅 구성에서 대상 그룹을 새로 만들고, 이름을 fargate-demo-group으로 만들었다. 하지만 ECS 서비스 생성할 때 대상 그룹을 새로 생성해서 지금 만들어지는 대상 그룹을 사용하지 않는다 (넘어가기 위해 만든 거라고 보면 된다). 5단계에서도 바로 넘어가도록 한다.

만들어진 LB에 가서 이전에 http를 연결해둔 대상그룹을 삭제한다. (리스너를 삭제한다.) 마찬가지로 방금 만들었던 fargate-demo-group을 삭제한다.

ECS 서비스 만들기

이제 서비스를 만들어보자. demo-cluster를 누르고 들어간 곳에서 서비스 탭에서 서비스를 생성할 수 있다. 서비스는 서비스 설정, 네트워크 설정, 오토 스케일링 설정, 리뷰 총 네 단계로 구성된다.

먼저 서비스를 구성할텐데, 시작 유형은 Fargate로 설정하고, 태스크 디피니션 (작업 정의)에는 아까 만들어둔 것을 올리고, 클러스터도 demo-cluster로 설정한다. 플랫폼 버전은 시작 유형을 Fargate로 설정해야 나오는데, LATEST를 그대로 사용한다 (아마 Fargate의 버전을 말하는 게 아닐까 싶다). 작업 개수는 2개로 설정한다. 작업 개수는 태스크가 최대 몇 개나 동작할 것인가를 정하는 것이다.

최소 정상 상태 백분율은 (정상 상태인 태스크의 비율이 최소 얼마여야 하는가) 50으로 두고 최대 백분율을 (태스크는 최대 몇 퍼센트까지 뜰 수 있게 할 것인지) 200으로 둔다. 위에 작업 개수를 2개로 했으니 방금 설정에 따르면 Fargate에서 관리하는 태스크는 최소 1개, 최대 4개까지 뜨는 것이다.


그 다음으로 네트워크를 설정해줘야 한다. 이 글에서는 외부 접근이 제한된 RDS와 연결을 해야 하는 상황을 작성했다. 단지 Fargate 배포 데모 정도 구경하는 과정은 여기까지만 읽고, 네트워크 설정을 기본 디폴트로 한 다음, 자동 할당 퍼블릭 IP를 열어두는 정도로 마무리 하면 배포가 완료될 것이다.

프라이빗한 RDS와 연결해주려면 같은 VPC를 선택 해야 한다. 그러고나서 서브넷을 퍼블릭으로(인터넷 게이트웨이가 붙은 라우터 테이블과 연결된 서브넷) 해줬다. (설명에 프라이빗만 가능하다고 되어 있는데, 이유를 잘 모르겠다. 읽어보지 않고 했는데 잘 됐다.) 그 다음 보안 그룹을 RDS에서 접근을 허용하는 보안 그룹을 포함하고 있어야 한다. 또한 HTTP 통신만 할 예정이므로, HTTP 역시 포함 되어 있어야 한다. (HTTPS는 LB에 붙일 생각이다. HTTPS 포트로 LB에 요청이 들어오더라도, LB는 요청을 컨테이너의 80번 포트로 전달해준다.)

마지막으로(서비스 검색은 체크 해제한다) LB를 붙여주면 되는데, 전에 만들었던 로드밸런서를 붙이고 80번 포트를 리슨하는 것으로 해준다.

그 다음 단계인 오토스케일링 단계 역시 이번 데모에서 사용하지 않으므로 건너 뛰자.

배포가 잘 이루어 지는 것을 확인했고, LB의 DNS 주소로 접근 할 수 있게 된다.

후기

아쉽게도 이번 데모는 성공한 여러 버전들이 프라이빗인 관계로 레포를 준비할 수 없었다. (삽질한 것들은 그냥 지웠다.) 아마 다음 오토스케일링을 볼 때에도, 프라이빗일 것 같다. 설명서를 이번에는 뒤늦게 자주 보게 되었는데, 좀 더 천천히 살펴볼 필요가 있을 것 같다. (ECS 설명서는 목차가 나름 깔끔하고 내용도 잘 읽힌다. 아래 레퍼런스 링크에서 한 번쯤은 읽어봐도 좋을 것 같다.)

ECS Fargate의 가격적인 측면을 사실 간과할 수 없다고들 하는데, 경험하지 못해서 그런지, 설명만 봐서는 그렇게 무자비한 가격은 아니라고 생각한다. Lambda와 비교하자면 더 비쌀 것 같긴 한데, (작은 서비스일수록, 프리티어가 해결해주는 범위 내면 싸다고 볼 수 있다.) 일반적으로 만들어지는 모놀리식한 방식의 앱을 배포할 수 있는 좋은 방법이 될 것 같다. 사실 도커라이징을 한 다음, EC2에 배포하기에 뭔가 아쉬운 점이 있어서 지금까지 그렇게 해본 적은 없는데, 항상 바라던 서비스가 아닌가 싶은 느낌이 든다 (쿠버네티스는 사용한 적 없어서 비교할 수 없다.).

다만 다시 한 번 확인해야 하는 점은 오토 스케일링인데, 서버리스라는 이름과 맞지 않게 오토 스케일링을 적용한다? 라는 말이 있어서 갸웃 했다. 맥시멈, 미니멈만 정해주면 알아서 오토 스케일링이 적용 되는 게 아닌가? 싶었다. 고민 해보면 기본적으로 원래 ECS가 EC2컨테이너를 사용한 도커 서비스라는 점 때문에(결국에 EC2를 어떻게 돌릴 것인가에 대한 설정이 필요한 건지?.. 내공이 부족하여 디테일 함 없이 둥실둥실 떠다니는 정도의 생각밖에 못 한다.) 람다와 같은 완전 서버리스같은 느낌이 덜 나는 것이 아닌가 싶다. ECS Fargate의 오토스케일링에 대해서 다음 번에는 공부해보고 도입해봐야겠다.

Reference

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×