Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

ECS

ECS는 Amazon에서 제공하는 컨테이너 서비스다. ECS를 이용하면 도커(Docker)컨테이너를 쉽게 실행학 확장 할 수 있다.

익숙한 EC2대신 도커 컨테이너를 이용해서 배포하려는 이유는 "뭔가 운영체제(EC2)환경, 설정 맞추는 것 너무 귀찮아서"다. 프러덕트 레벨에서는 "운영체제가 노출"되는 것만으로 해야 할 일이 산더미처럼 생긴다.
  • 유저 추가/삭제 관리
  • 각 유저에 대한 보안 설정
  • 운영체제 보안 설정. 필요할 경우 서버 백신을 설치해야 한다.
  • CI/CD 환경. 컨테이너로 한다고 해서 CI/CD 환경이 사라지는 건 아니다. 하지만 애플리케이션만 격리하면 되므로 과정을 단순화 할 수 있다.
  • Fargate 서비스를 사용하면 클러스터를 유지할 필요조차 없다.
단, 서버리스 혹은 서버리스에 준하는 환경이 보안을 AWS 위임하기 때문에 더 안전한가 ? 에 대해서는 다른 의견이 있다. EC2에 애플리케이션을 올릴 경우, 운영체제에서는 각종 보안솔류션을 설치 할 수 있어서, 다층적인 보안 환경을 만들 수 있다. 하지만 서버리스 환경에서는 이러한 툴들을 사용 할 수 없기 때문에, 보안계층이 얇아져서 오히려 보안에 더 취약하다는 의견도 있다. "대신 운영체제 레벨에서의 보안환경을 제대로 관리하지 않을거면, 운영체제 고유의 취약점만 추가될 뿐이라는 의견도 있다.

도커 레지스트리 만들기

AWS는 도커 이미지 관리를 위한 ECR(Elastic Container Registry)서비스를 제공한다. 먼저 ECR을 이용 도커 레지스트리를 만들기로 했다.

Next를 누르면 아래와 같은 형식의 레지스트리가 만들어진다.
xxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/yundream/repo

aws cli를 이용해서 레지스트리에 로그인 한다.
$ aws ecr get-login --no-include-email --region ap-northeast-2
docker login -u AWS -p eyJwYXlsb2Fk...... https://xxxxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com
위 명령을 실행하면 로그인에 필요한 docker 명령어를 출력한다. docker 명령어를 실행하자.
# docker login -u AWS -p eyJwYXlsb2Fk...... https://xxxxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded

이제 도커이미지를 만들어서 레지스트리에 등록하면 된다. 간단한 go 프로그램을 만들었다.
package main

import (
	"fmt"
	"log"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}
Dockerfile 이다.
FROM scratch
ADD hello /
CMD ["/hello"]
도커이미지를 만들자.
# docker build -t yundream/repo .
이미지를 레지스트리로 태깅하고 push 한다.
# docker tag yundream/repo:latest xxxx.dkr.ecr.ap-northeast-2.amazonaws.com/yundream/repo:latest
# docker push xxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/yundream/repo:latestThe push refers to repository [xxxx.dkr.ecr.ap-northeast-2.amazonaws.com/yundream/repo]
f6f3f2fdea50: Pushed
latest: digest: sha256:6b40f2b34xxxxxxxxxxxx7bb3fb586125032 size: 528
ERS 대시보드에서 등록한 이미지목록을 확인 할 수 있다.

Task Definitions

ECS는 Clusters와 Task Definitions 두 개의 요소로 구성된다.

 Clusters and Task definitions

  1. Clusters : Docker 클러스터다. 이 클러스터는 하나 이상의 EC2 인스턴스로 구성된다. 컨테이너는 이 클러스터에 배치된다.
  2. Task Definitions : Cluster에 전개할 컨테이너의 세부 속성을 설정한다. 네트워크 모드, 컨테이너가 사용할 메모리의 크기, vCPU 갯수, 볼륨의 크기, 사용할 도커 이미지들을 설정한다.
따라서 먼저 Task Definitions에서 Task를 만들어야 한다.

Create new Task Definition을 클릭하면 Create a Task Definition 화면으로 넘어간다. 입력 내용이 많아서 일부만 스크린샷으로 남겼다. 설명은 텍스트로 대신한다.

Task Definition Name : Task 이름이다. "hello"로 설정했다.

Task Role : IAM룰을 설정한다.

Network Mode: 도커 컨테이너가 사용할 네트워크 모드다. Bridge, Host, awsvpc, None이 있다. 네트워크 모드를 살펴보자. Bridge는 도커의 기본 네트워크인 docker0 브릿지 네트워크를 이용해서 연결한다. Bridge 네트워크는 EC2 인스턴스 내부에서만 작동하기 때문에, 외부와 통신하기 위해서는 EC2의 네트워크 인터페이스를 이용해야 한다. 즉 컨테이너 단위의 네트워크 ACL, 시큐리티 그룹을 설정할 수 없다. 대신 VPC와 EC2에서 제공하는 네트워크 ACL과 시큐리티그룹을 이용해야 한다. 네트워크가 공유되지 않으므로 컨테이너들을 연결하는 네트워크 정책을 만들기가 어렵다. Bridge 네트워크는 컨테이너들이 공용으로 사용하기 때문에, 포트 충돌이 나지 않도록 주의해야 문제도 있다.

awsvpc 네트워킹 모드는 task 단위로 Elastic network interface를 붙인다. 네트워크 인터페이스가 task 마다 독립적으로 할당되기 때문에, 네트워크 설정이 단순해진다.

Task Memory : Task에 할당할 메모리 크기

Task CPU : Task에 할당할 CPU.

Add container : 컨테이너를 설정한다. 여기에서 도커 이미지, 컨테이너가 사용할 메모리, 포트 설정등의 작업을 한다. 그리고 컨테이너에 대한 헬스체크도 할 수 있다. 기타 컨테이너를 실행하기 위해서 필요한 Link, Hostname, DNS server, Extra hosts, 볼륨 설정도 할 수 있다. 도커 컨테이너 설정을 해봤다면 익숙한 작업들일 것이다.

이렇게 해서 "hello" Task를 정의했다. Task의 상세내용은 Json 정보로 확인할 수 있다.
{
  "executionRoleArn": null,
  "containerDefinitions": [
    {
      "dnsSearchDomains": null,
      "logConfiguration": null,
      "entryPoint": null,
      "portMappings": [
        {
          "hostPort": 8080,
          "protocol": "tcp",
          "containerPort": 8080
        }
      ],
      "command": null,
      "linuxParameters": null,
      "cpu": 0,
      "environment": [],
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "dockerSecurityOptions": null,
      "memory": 128,
      "memoryReservation": null,
      "volumesFrom": [],
      "image": "xxxxxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/yundream/repo:latest",
      "disableNetworking": null,
      "healthCheck": null,
      "essential": true,
      "links": null,
      "hostname": null,
      "extraHosts": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "privileged": null,
      "name": "hello",
      "expanded": true
    }
  ],
  "placementConstraints": [],
  "memory": "256",
  "taskRoleArn": "arn:aws:iam::xxxxxxxx:role/ecsTaskExecutionRole",
  "compatibilities": [
    "EC2"
  ],
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-2:xxxxxxxxx:task-definition/hello:1",
  "family": "hello",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.ecr-auth"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.task-iam-role"
    }
  ],
  "requiresCompatibilities": null,
  "networkMode": "bridge",
  "cpu": "2048",
  "revision": 1,
  "status": "ACTIVE",
  "volumes": []
}

Clusters 만들기

Task는 container 서비스 명세만 담고 있다. 이 명세서를 읽어서 서비스를 하려면, 컨테이너를 실행할 ECS 클러스터를 만들어야 한다. Amazon ECS > Clusters > Create Cluster 에서 클러스터를 만들 수 있다.

리눅스 환경이니 "EC2 Linux + Networking"을 선택한다.

클러스터를 설정한다.
  • Cluster name : 클러스터 이름
  • Provisioning Model : 지속적인 서비스라면 On-Demand, 배치성 작업이라면 Spot을 선택한다.
  • Ec2 instance type & Number of instances : 인스턴스의 타입과 갯수를 선택한다.
  • EBS storage : 스토리지 크기
  • Key pair : 접근이 필요할 경우 선택한다.
  • Networking : 클러스터를 배치할 VPC, Subnet, Security 등을 설정한다.

서비스 생성

도커 이미지, 클러스터, Task가 모두 준비됐다. 이제 서비스를 만들면 된다. 클러스터와 Task 양쪽 모두에서 서비스를 만들 수 있다. 어떤 방식을 선택하던 동일한 과정을 거쳐서 서비스를 만들게 된다.

우리가 만들 서비스는 HTTP기반의 인터넷 서비스로 아래와 같은 구성을 가질거다.

 ECS 서비스 구성

그림 처럼 서비스를 만들기 위해서는 ELB 를 먼저 구성해야 한다. ECS 클러스터를 배치한 VPC와 Subnet을 목적지로 하는 ELB를 만든다. Port는 반드시 Task에서 설정한 호스트 포트로 향하게 해야 한다. Port가 틀리면 아예 ELB를 선택할 수 조차 없다. 나는 "80(HTTP) forwarding to 8080(HTTP)"로 ELB를 구성했다.

이제 서비스를 설정한다.

  • Task Definition : 실행할 태스크
  • Cluster : Task를 전개할 ECS Cluster 이름
  • Service name : 서비스 이름
  • Number of tasks : 몇 개의 태스크를 실행 할건지 설정한다. 컨테이너의 갯수라고 보면 된다.
다음 네트워크 정보를 설정한다.

Load balancing가 가장 중요한 부분이다. 앞서 만든 로드밸런서를 선택하고, 다음 단계에서 스케일링 설정만 하면 서비스가 만들어진다.

컨테이너 인스턴스의 갯수와 서비스, 실행중인 테스크를 확인 할 수 있다. curl을 이용 ELB 도메인으로 테스트를 했다.
# curl hello-lb-xxxxxx.ap-northeast-2.elb.amazonaws.com
Hi there, I love !yundream

서비스 설계를 위한 몇 가지 제안

거대한 클러스터에서 다양한 서비스를 올리는 것에 대한 로망 같은게 있는 것 같은데 그렇게 하지 말자. 물론 커다란 클러스터를 운용하면 자원을 꽉꽈 눌러서 사용 할 수 있긴하며, 백앤드에서 데이터를 처리하거나 하는 등의 목적으로 사용한다면 괜찮은 방법일 수도 있다. 하지만 서비스는 그렇지 않다. 멋진 도커 클러스터의 아닌 "쉬운 배포와 관리"의 차원에서 구성하는게 더 낫다.

앞으로 할 것들

  • Fargate 살펴본다.
  • EKS 살펴봐야 겠다.
  • CodePipeLine 과 연결해서 컨테이너 기반의 CI/CD 환경을 구성한다.
    • 롤링업데이트 & 블루그린 업데이트 살펴본다.
    • API 문서화 및 테스트 통합을 검토한다.
  • 로깅/감사 시스템과 통합한다.
할거 많네..