AWS Fargate를 이용한 API Service

글쓴이 상배 윤 날짜

이 문서를 읽기 위해서는 도커에 대한 이해가 필수다.

2017년 AWS는 Fargate라는 컨테이너 오케스트레이션 서비스를 발표했다. 기존에 사용하고 있던 ECS(Elastic Container Service)이라는 컨테이너 관리 서비스와 매우 유사한 서비스다.

실제 Fargate 서비스를 보면 독립적인 서비스 카테고리가 아닌 ECS 카테고리에 위치하고 있다. Fargate는 EC2 클러스터를 직접 구성해야 하는 ECS와 달리 완전 관리형 서비스다. Fargate 사용자는 컨테이너의 용량을 산정해서 EC2의 사양을 선택하고 클러스터를 구성 할 필요가 없다. 메모리와 CPU의 크기만 설정하면 된다.

클러스터를 구성하기 위한 작업이 필요없다는 것을 제외하고 ECS와 동일하기 때문에 ECS를 사용해 봤다면, 쉽게 Fargate로 넘어올 수 있다.

테스트용 도커 애플리케이션

Go언어를 이용해서 2 개의 REST API 서버를 만들었다.

  1. GET /user/{username} : “Hello {username}”을 출력한다.
  2. GET /time : 현재 시간을 출력한다.

구성은 아래와 같다.

Fargate 클러스터는 Private Subnet에 배치한다. Application Load Balancer는 Public Subnet에 배치해서 인터넷에서 접근 할 수 있도록 했다. 구성도에는 나타나있지 않지만 Application Load Balancer를 배치하기 위해서 가용성 영역도 만들었다.

Fargate는 VPC안애 전개되는 자원이 아니고 DynamoDB와 같은 AWS 전역 서비스다. Fargate는 ENI를 이용해서 특정 서브넷에서 Fargate task와 네트워킹 하도록 구성한다.

Fargate 사용

ECR을 이용한 도커 이미지 Registry 생성

컨테이너를 실행하기 위해서는 도커 이미지 registry가 필요하다. AWS ECR을 이용해서 도커 이미지 registry를 만들 수 있다.

이름은 joinc/user로 했다. 이렇게 만든 도커 이미지 레지스트리에 대한 사용법은 웹 콘솔에서 (운영체제별로) 메뉴얼 형식으로 제공하고 있으므로 간단히 사용 할 수 있다. ECR에 접근 할 수 있는 IAM 만 가지고 있으면 된다.

같은 방법으로 joinc/time 레지스트리도 만들었다. joinc/user은 user API, joinc/time은 time API를 서비스 한다.

컨테이너 이미지 만들기

Golang 으로 GET /user/{username} 과 GET /time 을 서비스하기 위한 코드를 만들었다. 아래는 user API 코드다.

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func h_hello(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    name := vars["name"]
    fmt.Fprintf(w, "Hello %s!", name)
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/user/{name}", h_hello)
    log.Fatal(http.ListenAndServe(":8080", r))
}

빌드를 위해서 아래와 같은 Makefile을 만들었다.

build:
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o user .
docker:
    docker build -t joinc/hello:0.1 -t joinc/hello:latest . 

아래는 Dockerfile 이다.

From scratch
ADD  user /                    
CMD  ["/user"]

먼저 로컬 PC에서 이미지가 제대로 작동하는지를 테스트 했다.

$ make docker
docker build -t joinc/user:0.1 -t joinc/user:latest . 
Sending build context to Docker daemon   6.87MB
Step 1/3 : From scratch
 ---> 
Step 2/3 : ADD  user /
 ---> 4093e1100250
Step 3/3 : CMD  ["/user"]
 ---> Running in 4c6b502ed2fb
Removing intermediate container 4c6b502ed2fb
 ---> 5fa7426c1767
Successfully built 5fa7426c1767
Successfully tagged joinc/user:0.1
Successfully tagged joinc/user:latest
$ docker images
REPOSITORY         TAG      IMAGE ID     CREATED          SIZE
joinc/user         0.1      5fa7426c1767 27 seconds ago   6.86MB
joinc/user         latest   5fa7426c1767 27 seconds ago   6.86MB

컨테이너를 실행하고 curl로 제대로 작동하는지 테스트를 한다.

$ docker run -d --name user_api joinc/user 
1d705e17709ee19ac08af6210c2d70dc3649ed2d8530ad274727bd7dc429b9e3
$ curl 172.17.0.2:8080/user/yundream
Hello yundream!

아래는 time API 서버다. 동일한 방법으로 Dockerfile 을 만들었다.

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "time"
)

func h_hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Current Time : %s", time.Now().String())
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/time", h_hello).Methods("GET")
    log.Fatal(http.ListenAndServe(":8080", r))
}

로컬에서 테스트를 수행했으니 이 이미지를 Fargate cluster에 올리면 된다.

ECR에 이미지 올리기

joinc/user 컨테이너를 올려보자. ECR 컨테이너 레지스트리에 로그인 한다.

# $(aws ecr get-login --no-include-email --region ap-northeast-2)
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded

joinc/user 컨테이너를 ECR에 올리기 위해서 태깅을 한다.

$ docker tag joinc/user:0.1 522373083963.dkr.ecr.ap-northeast-2.amazonaws.com/joinc/user:0.1
$ docker tag joinc/user:latest 522373083963.dkr.ecr.ap-northeast-2.amazonaws.com/joinc/user:latest

태깅한 컨테이너를 ECR 레지스트리에 Push 한다.

# docker push 522373083963.dkr.ecr.ap-northeast-2.amazonaws.com/joinc/user:0.1
# docker push 522373083963.dkr.ecr.ap-northeast-2.amazonaws.com/joinc/user:latest

time api 서버도 동일한 방법으로 push 한다. 이제 Fargate Cluster를 만들고 여기에 컨테이너를 실행하면 된다.

Fargate Cluster 생성

Fargate Cluster를 만들어보자.

Create Cluster 를 선택하면 아래와 같이 클러스터 템플릿을 선택하는 화면이 나온다. 3가지 타입이 있는데, Networking only(Powered by AWS Fargate) 템플릿을 선택하면 된다. Fargate를 사용할 경우, 사용자는 단지 네트워크만 제공하기 때문에 Networking only 라고 명명했다. VPC와 Subnets는 옵션이다. VPC와 Subnet을 선택할 경우, 태스크 컨테이너에 ENI가 붙어서 subnet에 연결된다.

클러스터 생성은 두 단계로 끝난다. Cluster name과 Tag 정도를 입력하고 Create 버튼을 누르면 즉시 클러스터가 만들어진다. 이미 만들어진 VPC를 사용할 계획이라서 Create VPC는 체크하지 않았다.

클러스터 정보를 보자.

작업(Tasks)가 실행되지 않는 만큼 깨끗하다. 이제 Task를 만들어보자.

Task 작성

Task는 작업 명세서다. 도커 시스템의 Dockerfile 과 비슷한 일을 한다. 여기에는 컨테이너를 실행할 도커 이미지, 컨테이너에 할당할 메모리와 CPU의 크기, 디스크에 대한 정보가 기술된다.

Task Definitions > Create new Task Definition 으로 Task를 만들 수 있다.

Select launch type compatibility 에서 FARGATE를 선택한다. Fargate 설명에서 몇 가지 특징을 확인 할 수 있다.

  • Price based on task size : EC2가 아닌 Task 즉, 프로세스 단위로 비용을 지불한다.
  • Requires network mode awsvpc : awsvpc 네트워크 모드를 사용한다. Task마다 ENI를 할당되며 Private IP, Private Domain을 설정 할 수 있다. 사용자 입장에서는 EC2와 동일한 네트워크로 보인다. Fargate 클러스터를 public subnet에 전개한다면 컨테이너마다 EIP(혹은 Public ip)를 할당 할 수도 있다.
  • AWS-managed infrastructure, no Amazon EC2 instances to manage : 더 이상 EC2를 관리 할 필요가 없다.

작업 정의서(Task definition Name)을 설정했다. IAM 권한이 필요하다면 Task Role로 설정한다. S3, SNS, SQS 등에 접근하길 원한다면 이 작업이 필요하다. Network Mode는 awsvpc만 사용 할 수 있다.

컨테이너를 실행하기 위해서는 ECR(Elastic Container Registry)에서 컨테이너 이미지를 가져오고, Amazon CloudWatch에 컨테이너 로그를 게시 할 수 있는 권한이 있어야 한다. AWS는 Task를 실행하기 위한 필수 권한을 담고 있는 ecsTaskExectionRole 을 가지고 있다. ecsTaskExectionRole이 없다면 자동으로 권한을 부여한다.

Task size에서 Task가 사용할 메모리와 CPU의 크기를 설정한다.

Container Definitions에서 joinc-user 태스크가 실행할 컨테이너를 추가한다

  • Container name : 컨테이너 이름
  • Image : 태스크가 실행할 컨테이너 이미지 이름
  • Port mappings : 컨테이너의 서비스 포트번호. Application Load Balancer 로 매핑하기 위해서 반드시 필요하다.

이렇게 해서 태스크를 만들었다. 이제 이 태스크를 서비스 형태로 실행하면 된다.

서비스 만들기

지금까지 joinc-time-task와 joinc-user-task 두 개의 태스크를 만들었다. 현재 태스크의 정보는 아래와 같다.

이들 태스크를 이용해서 서비스를 만들고 인터넷에 노출하면 된다. 인터넷에 노출하기 위한 가장 쉬운 방법은 ALB(Application Load Balancer)를 사용하는 거다.

ALB 생성

EC2 대시보드로 이동해서 ALB를 만들기로 했다. 이 문서는 ALB를 설명하는 문서는 아니므로 중요 부분만 설명하도록 하겠다.

인터넷에 노출할 계획이므로 internet-facing를 선택했다. 로드밸런서 프로토콜은 HTTP(80)으로 설정.

로드 밸런서는 public subnet에 배치한다.

새로운 타켓그룹을 만든다. Fragate service를 이 타겟그룹으로 묶는 것으로 로드밸런서와 연결된다.

서비스를 ALB와 연결

이제 서비스를 만들고 위에서 만든 ALB와 연결해보자.

joinc-test-cluster의 Services 탭에서 서비스를 만들 수 있다.

  • Launch type : Fargate 를 선택한다.
  • Task Definition : joinc-time-task를 선택했다.
  • Cluster : 서비스를 실행할 클러스터
  • Service name : 서비스 이름
  • Number of tasks : 태스크의 갯수
  • Deployment type : Rolling update와 Blue/green deployment 중 하나를 선택 할 수 있다. 이들 타입을 이용해서 무중단 서비스 배포를 수행 할 수 있다. Blue/Green deployment 는 AWS CodeDeploy와 연동해서 서비스 개발과 배포까지의 과정을 자동화 할 수 있다. CodeDeploy를 사용하지는 않을 거라서 Rolling update를 선택했다.
  • Cluster VPC : Fargate를 전개할 VPC를 선택한다.
  • Subnet : Fargate를 전개할 Subnet을 선택한다. 가용성을 위해서 2개의 private subnet을 선택했다. Fargate cluster에서 실행되는 서비스 태스크는 AWS 공용 망에 있는 ECR에 접근 할 수 있어야 하기 때문에 NAT Gateway가 설정돼 있어야 한다. 혹은 private subnet이 아닌 public subnet에 배치해야 한다.
  • Auto-assign public IP : 인터넷에 직접 노출할 필요는 없으므로 DISABLED를 선택했다.

다음 Auto Scaling 단계인데, 나중에 다루기로 하고 건너뛰기로 했다.

이렇게 해서 joinc-test-cluster에 2개의 서비스를 올렸다.

모든 서비스가 성공적으로 실행된 걸 확인 할 수 있다. 이제 ALB를 통해서 서비스를 이용하면 된다.

자동으로 ALB의 Listener 설정이 완료된 걸 확인 할 수 있다. 지금은 테스트를 위해서 2개의 ALB를 설정했는데, 하나의 ALB로 라우팅 할 수도 있다. 동일한 ALB이름으로 Target group만 다르게 설정하면 된다.

마지막으로 테스트를 해봤다.

$ curl joinc-api-108413602.ap-northeast-2.elb.amazonaws.com/time
Current Time : 2019-01-24 14:46:36.559604364 +0000 
$ curl joinc-api-108413602.ap-northeast-2.elb.amazonaws.com/user/yundream
Hello yundream!

정리

  • Fargate를 사용하면 EC2를 관리 할 필요가 없다. EC2를 관리할 필요가 없지만 ENI를 이용해서 VPC Subnet에 배치 할 수 있으므로, 기존 VPC에 배치된 다른 자원들(RDS, ElasticCache 같은)과 자연스럽게 연동 할 수 있다.
  • ALB와 쉽게 붙일 수 있다. AWS CLI 혹은 AWS API를 이용하면, 자동으로 API를 인터넷으로 전개 할 수 있다.
  • API Gateway를 이용한 Fargate 연동과 비교해볼 수는 있겠다. 이건 나중에 따로 다뤄볼 생각이다.
  • Fargate와 ALB 혹은 API Gateway를 이용한 MSA 구현에 대해서 정리해봐야 겠다.
  • EKS와 비교도 해봐야 겠다.(할 거 많네)

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

Bitnami