sol 개발 블로그 로고
Published on

AWS와 Docker (1)

Authors
  • avatar
    Name
    Chan Sol OH
    Twitter

목차

도커란?

컨테이너에서 SW이다. 이 컨테이너는 표준화되어 있어서 아무 운영체제에나 실행할 수 있다. 즉 앱이 도커 이미지로 패키징되면 어느 OS에서나 같은 방식으로 실행된다. 컨테이너는 마이크로 서비스 아키텍처(MSA)에 사용된다.

도커 이미지?

도커 이미지는 앱 배포를 위한 실행파일이라고 할 수 있고, 도커 에이전트는 이미지를 실행시켜서 컨테이너를 생성한다. 이 컨테이너에서 앱이 동작하는 것이고, OS는 단지 컨테이너가 동작하는 것처럼 보일 뿐이다. 또한 도커 이미지도커 레포지토리에 저장된다. 예를 들어 Docker Hub, AMAZON ECR PUBLIC GALLERY,Amazon ECR(Elastic Container Registry) 같은 공용 혹은 프라이빗 레포지토리를 사용할 수 있다.

도커와 가상 머신의 차이

가상 머신

도커도 가상화 기술의 일종이긴 하지만, 완전히 가상화는 아니다. 아래 이미지처럼 호스트 OS 위에 Hypervisor가 있고 그 위에 게스트 OS가 위치해서 앱을 실행시키는 것이 가상화 머신이고, EC2가 동작하는 원리이다. 가상화 머신들끼리는 리소스를 공유하지 않고 완전히 분리되어 있다.

도커

도커 컨테이너는 Docker Daemon에서 가볍게 실행되는 컨테이너기 때문에 각 컨테이너끼리 네트워킹이나 데이터리소스를 공유할 수 있다.

아마존에서는 도커 이미지를 저장하는 ECR, 도커 플렛폼을 지원하는 ECS, 관리형 Kubernetes를 서비스하는 EKS, 그리고 서버리스로 ECS와 EKS를 지원하는 AWS Fargate가 있다.

ECS

ECS는 Elastic Container Service의 약자이며, AWS에서 도커 컨테이너를 실행시키면 ECS Clusters에서 ECS Tasks를 실행시키는 것이다.

EC2 Clusters는 아래와 같은 구성요소를 가진다.

  1. EC2 Launch Type : 컨테이너를 실행시키고 유지시킬 EC2 인스턴스의 타입. 이러면 인프라를 직접 프로비저닝 및 관리해야한다. 한 ECS Cluster 안에 여러 EC2 인스턴스가 있고, 각 인스턴스는 ECS Cluster에 등록하기 위해 ECS 에이전트를 실행해야한다. 이러면 AWS가 Container의 시작과 종료를 관리하게된다. 즉 새로운 Container가 배포되면 각각의 EC2 인스턴스에 배포된다. ECS Task를 시작하거나 중지하면 컨테이너가 자동으로 배치된다.
  2. Fargate Launch Type : AWS에서 Container를 실행시키지만, 프로비저닝된 EC2를 사용하지는 않는다. 즉 서버리스로 컨테이너를 관리한다. 오직 ECS Tasks만 정의하면 AWS에서 CPU와 RAM 요구사항을 토대로 ECS Tasks를 실행한다. task 마다 컨테이너를 생성하기 때문에 확장하려면 task 수만 늘리면 된다.

IAM Roles for ECS

  • EC2 Launch Type일 경우 ECS agent가 EC2 Instance Profile을 사용한다.
    • ECS agent의 Role
      • ECS agent는 ECS에게 인스턴스를 복원하는 API 호출을 보내고,
      • ECS agent는 CloudWatch Logs에 컨테이너 로그를 전송하는 API 호출을 보내고,
      • ECS agent는 ECR에 도커 이미지를 가져오는 API 호출을 보내고,
      • ECS agent는 시크릿 매니저 또는 SSM Parameter Store에서 민감한 데이터를 참조한다.
  • ECS Task Role
    • EC2 Launch Type과 Fargate 시작 유형 모두에 적용된다.
    • 각 task 별로 구체적인 Role을 등록할 수 있다. 다른 Role을 부여하는 이유는 각 task 마다 다른 AWS 서비스에 연결할 수 있기 때문이다.

ECS와 Load Balancer 통합

아래 예시는 EC2 Launch Type 유형으로 설명하지만, Fargate에도 적용된다.

현재 한 ECS Cluster의 여러 ECS 태스크가 각 EC2 인스턴스 내부에서 실행 중일 때, 이 태스크를 HTTP 또는 HTTPS 엔드포인트로 노출하려고 한다. 이때 ALB를 클러스터 앞에서 실행하며 사용자들은 ALB와 함께 백엔드의 ECS 태스크에 직접 연결하게 된다.

ecs cluster의 ecs task가 alb와 연결
  1. Application Load Balancer는 대부분의 경우에서 잘 동작한다.
  2. Network Load Balancer는 높은 스루풋이나 성능이 요구되는 활용 사례 또는 AWS Private Link를 사용하는 경우에만 권장된다.

ECS의 데이터 지속성

ECS도 컴퓨팅하는 인스턴스기 때문에 Data Volume이 필요하다. 이 Data Volume의 종류도 다양하지만 주목할 것은 EFS이다. 여러 Type으로 생성성된 ecs task들은 데이터 공유를 위해 네트워크 파일 시스템인 EFS를 사용하여 마운트해야한다. EFS를 사용하면 어떤 AZ에서 실행 중인 테스크든 데이터를 공유하고 파일 시스템을 통해 서로 통신할 수 있다.

따라서 최상의 조합은 Fargate + EFS이며, Fargate를 이용해 서버리스 방식으로 ECS 태스크를 실행하고 Amazon EFS를 파일 시스템 지속성에 이용하는 것이다. EFS도 서버리스 방식이어서 서버를 관리할 필요가 없고 사용량을 기반으로 요금이 청구되고 사전에 프로비저닝하면 사용 가능하다. 서버리스인데 하드디스크를 다는 것과 유사한 효과????!!!!

**주의할 점은 S3를 ECS 태스크에 파일 시스템으로 마운트 할 수 없다. **

ECS Service의 Auto Scaling

ECS task 수를 수동으로 늘린다면 서버리스의 장점이 퇴색된다. 이를 위해서 AWS Auto Scaling이라는 서비스를 사용하면 아래 3가지 지표에 대해서 Auto Scaling할 수 있다.

  1. ECS 서비스의 평균 CPU 사용률에 따라
  2. ECS 서비스의 평균 메모리 사용률에 따라
  3. ALB 관련 지표인 타켓당 요청 수에 따라 확장할 수 있다.

위와 같은 지표를 사용하는 Auto Scaling 말고도 타겟의 CloudWatch의 지표를 기반으로한 Target Tracking, CloudWatch Alarm에 따른 Step Scaling, 그리고 미리 ECS 서비스 확장을 설정하는 Scheduled Scaling 등이 있다.

**주의할 점은 Fargate와 달리 ECS Service Auto Scaling (task level)과 EC2 Auto Scaling (EC2 instance level)**은 다르다는 점이다. 따라서 EC2 오토스케일링이 없다면 Fargate를 사용하는 것이 Service Auto Scaling에 도움이 된다!! 시험에서도 Fargate를 권장한다.

EC2 Instance를 Auto Scaling하는 방법

  1. Auto Scaling Group Scaling : 예로 EC2 instance의 CPU 사용량에 따라 EC2 Instance를 추가할 수 있다.
  2. ECS Cluster Capacity Provider : 새 태스크를 실행할 용량이 부족하면 자동으로 ASG를 확장한다. 즉 Capacity Provider는 ASG와 함께 사용되며, RAM이나 CPU가 모자랄 때 EC2 인스턴스를 추가한다. 자동으로 ASG를 관리하기에 추천한다.

ECS 서비스를 업데이트하는 방법

만약 ECS 서비스를 V1에서 V2로 **무중단 배포(Rolling Update)**를 할 때는 한번에 얼마나 많은 테스크가 시작되고 중단될지를 제어할 수 있다. ECS 업데이트가 있을 때 새로운 task 정의 숫자를 선택하고 ECS 서비스를 업데이트하고자 할 때는 **최소 상태 비율(Minimum healthy percent)**와 **최대 비율(Maximum percent)**인 2가지 지표를 설정할 수 있다. 각각의 기본 값은 100과 200이다.

ecs task의 확장 비율 설정

위 그림처럼 무중단 배포를 위해 최소 상태 비율과 최대 비율의 설정 값에 따라 기존 버전의 task를 얼마나 종료하고, 새로운 버전의 task를 얼마나 생성할지 결정할 수 있다. 아래와 같은 순서로 무중단 배포가 진행된다.

  1. 초기에 기존 버전의 task들이 실행 중
  2. Minimum healthy percent까지 기존 버전의 task를 종료
  3. Maximum percent까지 새로운 버전의 task 생성
  4. 나머지 기존 버전 task 종료
  5. task 용량이 100%가 될 때까지 새로운 버전의 task 생성 (롤링 업데이트 완료!)

ECS에서 task를 정의하는 방법

Task는 JSON 형식으로 ECS에게 어떻게 docker Container를 실행시킬지 정의된다. 아래와 같은 중요한 정보가 담겨있다.

  1. Image Name
  2. Port Binding for Container and Host
  3. Memory and CPU required for Container
  4. 환경 변수 및 네트워크 정보
  5. IAM Role
  6. CloudWatch와 같은 Logging configuration을 포함한다.

EC2를 사용해서 컨테이너를 실행시키는 경우, 로컬에서 서버를 실행시키는 것처럼 컨테이너 포트와 호스트 포트를 연결시켜줘야 컨테이너를 외부와 통신할 수 있게한다. 하지만, Fargate는 그럴 필요 없다. 그리고 task 정의 하나당 최대 10개의 컨테이너를 정의할 수 있다.

이렇게 보면 task 정의는 docker-compose 파일과 비슷해보인다.

Load Balancing for EC2 Launch Type

EC2 Launch Type에서 로드 밸런싱이 있고, 테스트 정의 내에서 컨테이너 포트 만을 정의했다면, 동적 호스트 포트 매핑을 한다. 즉 컨테이너의 포트만 지정한 경우 EC2 인스턴스에서 자유로운 포트를 컨테이너에 할당한다. 호스트 포트가 동적으로 변화하는 것을 ALB가 동적 호스트 포트 매핑 기능을 통해 알아내서 연결할 수 있게한다.

Security Group 관점에서는 task에 어떤 포트가 할당될지 모르기 때문에 EC2 인스턴스의 SG가 ALB의 SG로부터 모든 포트를 허용해야한다.

Load Balancing for Fargate

Fargate를 사용하여 task를 실행하는 경우 각 ECS task가 Private IP를 가지게된다. 또한 Fargate는 호스트가 없기 때문에 컨테이너 포트만 정의하면 각 task의 ENI는 동일한 컨테이너 포트를 가지게 된다. ALB와 연결할 때는 컨테이너 포트에 확정적으로 연결된다.

ECS ENI Security Group은 ALB 보안 그룹으로부터 컨테이너 포트를 허용해야하며 ALB Security Group은 80 or 442 포트를 웹에 허용해야한다.

Task 정의 당 IAM Role

Task 정의 당 고유의 ECS Task Role이 할당된다. 이런 IAM Role 정의를 통해 task가 다른 AWS 서비스에 접근할 수 있게한다. 보통 task가 S3나 DynamoDB에 접근할 때 자주 사용된다.

Task 정의 환경변수

task 정의는 환경변수를 아래와 같이 여러 가지 방식으로 설정할 수 있다.

  1. Hardcoded : 고정된 퍼블릭 URL을 설정하는 경우
  2. SSM Parameter Store or Secrets Manager: API 키, 혹은 공유 구성(데이터베이스 비밀번호) 같이 민감한 정보를 따로 저장해두고 ECS task를 시작할 때 ECS task 정의 내로 이들을 참조할 수 있다.
  3. Environment Files : S3 버킷으로부터 환경변수 파일을 직접 로드하는 방법이 있는데, 이는 파일을 통한 Bulk 환경 변수 로딩이라 불린다.

ECS Data Bolumes

ECS 태스크는 하나의 컨테이너를 사용하고, 동일 task 정의로 생성한 다수의 컨테이너끼리 데이터를 공유할 수 있다. 하나의 데이터 저장 전용 컨테이너(Sidecar)에 다른 컨테이너들이 연결되어 logging이나 metrix를 저장할 수 있다. 또한 이 과정은 EC2 Launch Type이나 Fargate로 동작하는 task 모두 가능하다.

Application Containers는 Shared Storage에 데이터를 쓸 수 있고 Sidecar Container가 Shared Storage로부터 지표 및 로그 데이터를 읽을 수 있다.

  • EC2 Task일 경우 바인트 탑재 자체가 EC2 인스턴스 스토리지이므로 탑재된 데이터가 EC2 인스턴스 수명 동안은 살아있다.
  • Fargate Task일 경우 임시 스토리지를 사용해 데이터는 컨테이너의 수명 동안 살아있다. 따라서 Fargate Task가 사라질 때마다 스토리지 또한 사라진다. Fargate에는 20~200GB의 Shared Storage를 제공한다.

ECS Tasks Placement

EC2 유형의 task를 생성하면 ECS는 EC2 인스턴스의 CPU, memory, 그리고 이용가능한 포트 번호를 알아야한다. 만약 ECS 서비스가 새로운 컨테이너를 생성하고자 할 때 어떤 EC2 인스턴스 내부에 생성할지 알 수 있어야한다. 또한 Scale-in task를 삭제할 때도 ECS 서비스가 어떤 ECS task를 종료할지를 판단할 수 있어야한다.

이를 위해 태스크 배치 전략(task placement strategy)과 테스크 제한(task placement constraints)라는 기능이 존재한다. 위 2가지 기능을 통해서 어디에 컨테이너를 생성하고 어디에 있는 컨테이너를 제거할지 판단할 수 있게된다. 이는 EC2 유형의 task만 필요하며 Fargate는 AWS가 자체적으로 파악해주기 때문에 해당하지 않는다.

태스크 배치 과정

태스크 배치 과정은 항상 최선의 선택을 하려 노력한다. ECS가 태스크를 배치할 때 다음의 과정을 거쳐 배치한다.

  1. 태스크 정의 내의 CPU, 메모리, 그리고 포트 조건을 만족하는 EC2 instance를 찾는다.
  2. instance가 태스크 배치 제한을 만족하는지 확인한다.
  3. 태스크 배치 전략에 최대한 적합한 인스턴스를 식별하기 위한 시도를 한 후 인스턴스를 선택해 그 인스턴스에 태스크를 배치한다.
  • Binpack : 가장 많이 채워져 있는 CPU나 메모리에 task를 배치하려고한다. 한 인스턴스에 컨테이너를 계속 생성하다 더 이상 생성할 수 없을 때 다음 인스턴스에 생성한다. 이는 인스턴스의 능력을 최대한 활용하여 사용하는 인스턴스의 수를 최소화하는 전략이다. 이는 아래와 같이 정의할 수 있다.

    "placementStrategy":[
      {
        "field":"memory",
        "type":"binpack"
      }
    ]
    
  • Random : task를 아무런 로직 없이 랜덤으로 배치하는 전략이다.

    "placementStrategy":[
      {
        "type":"random"
      }
    ]
    
  • 분산 전략(Spread) : 서로 다른 AZ에 여러 인스턴스가 있을 때 분산 전략을 사용하면 태스크가 특정 값을 기반으로 해 분산되어 배치된다. 예를 들어 instanceIdattribute:ecs.availability-zone과 같은 값을 기반으로 분산한다.

    "placementStrategy":[
      {
        "field":"attribute:ecs.availability-zone",
        "type":"spread"
      }
    ]
    

위와 같은 task 전략을 섞어서 사용할 수 있다.

테스크 제한

  • distinctInstance : ECS 서비스 전반에 걸쳐 동일 EC2 인스턴스 상에 두 개의 테스크를 배치할 수 없다.

    "placementConstraints":[
      {
        "type":"distinctInstance"
      }
    ]
    
  • memberOf : 클러스터 쿼리 언어를 사용해 정의된 표현식을 만족하는 인스턴스 상에만 테스크를 배치할 수 있다. 인스턴스 유형이 t2인 경우에만 생성가능하도록 아래처럼 작성할 수 있다.

    "placementConstraints":[
      {
        "expression":"attribute:ecs.instance-type =~ t2.*",
        "type":"memberOf"
      }
    ]