4. ECS 웹 콘솔에서 배포

petclinic-rest라는 스프링 부트로 만들어진 백엔드 서비스를 배포해 보겠습니다. 프로젝트에 이미 도커파일을 통해서 도커 이미지가 정의가 되어있습니다. 도커 이미지를 만들고 만들어진 이미지를 도커 레지스트리에 푸시하고, ECS에서 그 이미지를 바탕으로 작업을 정의해서 서비스를 배포하는 순서로 진행됩니다. 지금부터 하나하나 진행해보겠습니다.

실습은 싱가포르 리전에서 진행되며 링크들은 모두 싱가포르 리전으로 연결됩니다.


4.1 ECRElastic Container Registry 만들기

도커 이미지를 저장할 레지스트리를 만들어 보겠습니다. ECS 콘솔로 이동하여 우측에서 리포지토리Repository를 클릭합니다. 만약 리포지토리가 없으면 시작하기 버튼을 클릭하고 있으면 [리포지토리 생성]을 클릭합니다.

리포지토리 이름에 ‘petclinic-rest’라고 입력하고 [다음 단계]를 클릭합니다. 푸시 명령 관련한 스크립트를 확인하고 완료를 누르면 리포지토리 생성이 완료됩니다. 푸시 명령은 [푸시 명령 보기] 버튼을 통해서 언제든 다시 확인할 수 있습니다.


4.2 ECR에 도커 이미지 푸시하기

Cloud9에서 스프링 부트 앱을 빌드하고 결과물을 가지고 컨테이너로 실행 가능한 도커 이미지를 만듭니다. 명령어를 하나 하나 실행하며 빌드하고 이미지를 레지스트리에 등록해보겠습니다.

cd petclinic-rest
./mvnw clean package
docker build -f src/main/docker/Dockerfile -t petclinic-rest .


이 작업을 통해서 petclinic-rest라는 도커 이미지가 만들어졌습니다. 도커 이미지가 잘 만들어졌는지 테스트합니다.

docker run -it --rm -p 9460:9460 petclinic-rest


아까와 같은 방식으로 브라우저에서 테스트합니다. 잘 동작한다면 이번에는 이미지를 이전 단계에서 만든 리포지토리에 푸시해보겠습니다. 리포지토리 주소를 ECR 웹 콘솔 화면에서 복사합니다. 아니면 다음 명령어를 실행하여 확인할 수 있습니다.

aws ecr describe-repositories --repository-name petclinic-rest


도커의 tag 명령어를 통해서 기존의 이미지를 리포지토리 주소로 이름을 지어 줍니다. 그 이름으로 푸시하게 되면 리포지토리에 등록됩니다. jq 명령어를 통해서 리포지토리 주소만 가져옵니다. 그리고 그 주소를 가지고 태깅합니다.

REPOSITORY_URI=`aws ecr describe-repositories --repository-name petclinic-rest | jq -r ".repositories[0].repositoryUri"`
docker tag petclinic-rest:latest ${REPOSITORY_URI}:latest


이제 docker login 명령어를 통해서 ECR에 푸시할 수 있는 권한을 획득하고 푸시 명령어를 수행하면 ECR 리포지토리에 등록됩니다.

DOCKER_LOGIN=`aws ecr get-login --no-include-email`
${DOCKER_LOGIN}
docker push ${REPOSITORY_URI}:latest


ECR 웹 콘솔에서 등록이 잘 되었는지 확인해봅니다.



4.3 보안 그룹 설정키페어 생성

ECS 관련된 보안 그룹을 미리 만들어둡니다. 먼저 ECS 내부의 네트워크 흐름을 파악하는 것이 필요합니다.

그림 4-1



서비스는 ALBApplication Load Balancer를 통해서 외부로 노출됩니다. ALB가 ECS 인스턴스의 특정 포트를 통해 서비스에 접속합니다. 따라서 ALB의 보안 그룹은 웹 서비스를 위해서 인바운드로 80 포트를 열어둡니다. ALB 보안 그룹 이름은 ‘petclinic-rest-alb-sg’라고 입력합니다.


그림 4-2



그리고 ECS 인스턴스의 보안 그룹은 ALB 보안 그룹으로 들어오는 인바운드 규칙은 전체 포트를 열어둡니다. 그리고 ssh 접속이 가능하도록 22번 포트도 열어둡니다. ECS 인스턴스 보안 그룹 이름은 ‘petclinic-rest-ecs-instance-sg’라고 입력합니다.


그림 4-3


보안 그룹 설정을 마치면 ECS 인스턴스로 직접 ssh 접속을 할 수 있도록 키페어를 생성해둡니다. 키페어 이름은 ‘petclinic’이라고 입력합니다.


그림 4-4



4.4 로드밸런서 생성

ECS 인스턴스들을 외부로 연결해 줄 로드밸런서를 미리 만들어 둡니다. 이름은 ‘petclinic-rest-alb’라고 입력합니다. 인터넷 연결로 선택하고 주소 유형은 ipv4로 선택합니다. 리스너는 HTTP로 선택합니다.


그림 4-5



보안 설정 구성은 HTTP를 선택했으므로 무시하고 넘어갑니다.


그림 4-6



보안 그룹은 미리 만들어둔 ‘petclinic-rest-alb-sg’를 선택합니다.


그림 4-7



대상 그룹을 만들어서 라우팅 구성을 합니다. 새 대상 그룹을 선택하고 이름은 ‘petclinic-rest-target’으로 입력합니다. 리스너는 HTTP로 설정했으므로 프로토콜도 HTTP로 설정하고 포트도 80으로 입력합니다. 대상 유형은 ECS 인스턴스를 가리키기 때문에 instance를 선택합니다.

상태 검사는 spring-boot actuator에서 제공하는 health check 경로인 ‘/actuator/health’를 입력합니다.


그림 4-8



실제 대상 등록은 무시하고 넘어갑니다. 나중에 ECS 서비스 구성을 할 때 설정합니다.


그림 4-9



구성 내용을 검토하고 이상이 없으면 생성합니다.


그림 4-10



4.5 ECS 클러스터 생성

이번에는 ECS 클러스터를 생성합니다. 배포에 필요한 자원들을 정의하고 등록합니다. ECS 웹 콘솔의 오른쪽 메뉴에서 클러스터를 클릭합니다. 클러스터 화면에서 [클러스터 생성] 버튼을 클릭합니다. 

첫 번째 단계인 템플릿을 선택해야 합니다. 템플릿은 싱가포르 리전의 경우 세 가지가 있습니다. 첫 번째는 AWS Fargate라는 이름을 가진 템플릿입니다. 사용자는 EC2 자원에 대한 고민없이 클러스터를 만들고 컨테이너를 배포할 수 있습니다. 첫 번째는 EC2 Linux 인스턴스 기반에서 컨테이너를 배포하는 템플릿입니다. 세 번째는 EC2 윈도우 인스턴스 기반에서 컨테이너를 배포하는 템플릿입니다. 첫 번째는 현재(2018년 9월)기준으로 서울 리전에 없는 서비스입니다. 그래서 이번에는 첫 번째 ‘EC2 Linux + 네트워킹’를 선택해서 실습을 진행합니다.


그림 4-11


다음으로 클러스터 구성을 합니다. 클러스터 이름은 petclinic-rest-cluster 라고 입력합니다. 인스턴스 유형과 개수는 배포할 서비스가 필요한 자원의 크기를 고려하여 선택해야 합니다. 이번에는 인스턴스 유형은 t2.medium, 인스턴스 개수는 2개로 선택합니다. 그 이유는 이후에 오토스케일링 부분에서 자세히 설명할 예정입니다. keypair는 아까 만들어둔 petclinic으로 선택합니다.


그림 4-12



네트워킹 설정을 할 차례입니다. 기존의 VPC와 서브넷을 선택합니다. 보안 그룹은 직전에 만들어둔 ‘petclinic-rest-ecs-instance-sg’를 선택합니다.


그림 4-13



설정을 마치고 생성버튼을 누르게 되면 EC2 인스턴스를 실행하고 IAM 정책을 연결하고 CloudFormation을 통해서 필요한 리소스들을 생성합니다.


4.6 작업Task 정의
ECS는 작업이라는 논리적인 단위로 배포하게 됩니다. 그래서 이번에는 작업을 정의해봅니다. 작업 정의에서는 작업의 일부가 될 컨테이너의 개수, 컨테이너가 사용할 리소스, 컨테이너 간 연결 방식, 컨테이너가 사용할 호스트 포트와 같은 애플리케이션 관련 컨테이너 정보를 지정합니다. 작업 정의는 각 앱의 상황에 맞게 컨테이너들을 잘 배치해야 합니다.

이번 실습에서 작업 정의는 간단합니다. 작업 내에는 스프링을 포함한 컨테이너 하나만 포함되어 있습니다. 그러면 시작해보겠습니다.

Fargate를 지원하는 리전의 경우에는 작업 정의 시에 다음처럼 시작 유형 호환성 선택을 합니다. 저희는 이번에 EC2 인스턴스 기반의 ECS를 실습하고 있으므로 EC2를 선택합니다.


그림 4-14


그 다음에는 작업 정의 이름을 ‘petclinic-rest-task’라고 입력합니다.


그림 4-15



그다음에는 작업 크기를 설정합니다. EC2 유형에서는 인스턴스의 자원이 한정적이므로 작업 크기 입력이 선택사항이지만 Fargate 유형의 경우 어떤 인스턴스에서 작업으로 정의된 컨테이너들이 실행되는지 유저가 알 수 없으므로 작업 크기를 한정해야 합니다. 저는 이번에 작업 크기를 한정해보았습니다.


그림 4-16


그다음에는 컨테이너를 추가합니다. 컨테이너 이름은 ‘petclinic-rest-app’이라고 하고 이미지 주소를 입력합니다. 이미지 주소를 모른다면 왼쪽 메뉴에 있는 리포지토리로 가서 확인합니다. 컨테이너의 메모리는 소프트 제한soft limit으로 625를 입력합니다. 포트 매핑의 경우 호스트 포트를 0으로 해주어야 합니다. 이렇게 해야 호스트의 포트가 동적으로 매핑이 되고 그 포트에 따라 로드밸런싱됩니다.




그림 4-17



컨테이너를 추가하게 되면 다음처럼 메모리와 CPU 할당이 보여집니다. 


그림 4-18



그리고 제약은 아직 없으므로 무시하고 생성합니다.


그림 4-19


이렇게 작업 정의가 완료되었습니다.

작업 정의 완료 되면 그 작업을 바로 EC2 인스턴스에서 실행해 볼 수 있습니다. 클러스터를 선택하고 작업 탭에서 [새 작업 실행]이라는 버튼을 클릭합니다.


그림 4-20



그리고 시작 유형을 EC2로 설정하고 작업 정의와 클러스터명, 작업 개수를 입력합니다. 작업 개수를 2로 입력하는 이유는 현재 인스턴스가 2개이기 때문입니다.


그림 4-21


해당 인스턴스에 ssh로 접속하여 확인해보면 컨테이너가 실행됨을 확인할 수 있습니다.


그림 4-22



그러나 이것은 아직 외부로 노출되지는 않았습니다. 보안 그룹상 해당 인스턴스는 petclinc-rest-alb-sg 보안 그룹을 가진 서비스 즉, 로드밸런서에서만 접근이 가능합니다. 해당 인스턴스의 보안그룹을 수정하여 접근할 수는 있지만 이는 정상적인 방법은 아닙니다. 이제 서비스를 생성하고 서비스 내에서 작업을 배포하는 것을 통해서 정상적인 서비스 배포를 진행해보겠습니다.


4.7 서비스 생성 및 배포

ECS 클러스터에서 지정된 수의 작업을 실행하고 관리할 수 있습니다. 그 관리 주체를 서비스라고 합니다. 또한 서비스를 통해서 컨테이너들을 상태 검사health check, 로드밸런싱, 오토스케일링을 할 수 있습니다.

서비스를 생성해보겠습니다. 클러스터를 선택하고 서비스 탭에서 [생성] 버튼을 누릅니다.


그림 4-23



이제 서비스를 구성합니다. 시작 유형은 ECS를 선택합니다. 작업 정의는 지난 번에 정의했던 최신 버전으로 선택합니다. (저는 두 번의 실수 때문에 리비전이 3입니다.) 클러스터를 선택하고 서비스 이름은 ‘petclinic-rest-service’라고 입력합니다. 서비스 유형은 REPLICA(기본값)를 선택합니다. 작업 개수는 인스턴스 개수인 2개로 맞춥니다. EC2 인스턴스가 감당할 수 있을 만큼 작업 개수를 선택할 수 있지만 저는 우선 2개로 정했습니다. 

컨테이너가 죽는 장애라던지 서비스 업데이트 같은 부분에 있어서 중요한 숫자가 ‘최소 정상 상태 백분율’과 ‘최대 백분율’입니다. 최소 정상 상태 백분율을 배포 과정에서 실행 중인 작업 개수의 하한선을 백분율로 나타낸 것을 의미합니다. 그 백분율은 서비스의 작업 개수에 대한 백분율입니다. 최대 백분율은 배포 과정에서 실행 중인 작업 개수의 상한선을 백분율로 나타낸 것이며 이 또한 서비스의 작업 개수에 대한 백분율입니다.

예를 들어서 작업 개수가 2개이고 최소 정상 상태 백분율이 50이고 최대 백분율이 100이면 배포 과정에서 작업이 하나가 죽어도 정상 상태입니다. 그렇기 때문에 두 작업 중 하나를 먼저 종료하고 새로운 작업을 배포하고, 작업 하나가 완료되면 다시 나머지 하나를 종료하고 업데이트하는 방식으로 서비스 중단 없이 배포가 가능합니다.

만약 작업 개수가 2개이고 최소 정상 상태 백분율이 100 최대 백분율이 200이라면 현재 실행 중인 서비스를 종료하지 않고 새로운 작업들을 2개 미리 실행한 후에 완료가 되면 이전 두 작업을 종료합니다. 배포 과정 중에 4개의 작업이 실행되는 것이죠.

시작 유형이 EC2인 경우에 이런 백분율은 인스턴스의 자원과 작업이 필요한 자원을 잘 계산해서 설정해야 합니다.


그림 4-24



상태 검사 유예 시간을 입력하기 전에 로드밸런서를 선택합니다. 이전 단계에서 만들었던 로드밸런서를 사용합니다. 그 다음 상태 검사 유예 기간을 60으로 입력합니다. 상태 검사 유예 기간은 로드 밸런서가 실행 중인 작업이 실행되고 나서 상태 검사health check를 하기까지 유예하는 시간입니다. 

60초로 설정할 경우 작업이 시작된 후 60초 뒤에 상태 검사를 하고 만약 healthy이면 해당 작업을 로드밸런싱하게 되고 unhealthy하게 되면 해당 작업을 종료 후에 다시 작업을 실행합니다. 만약 설정에 문제가 있어서 계속 unhealthy 상태가 되면 서비스는 끊임 없이 작업을 죽이고 살리는 일을 반복합니다.


그림 4-25


로드밸런싱할 컨테이너를 선택합니다. 우리의 작업 정의에는 컨테이너가 하나 뿐이기 때문에 그 컨테이너를 선택하고 ELB 추가 버튼을 누릅니다. 그러면 다음처럼 화면이 바뀌게 됩니다. 이 부분은 로드밸런서 입장에서 컨테이너를 대상 그룹에 추가하는 작업입니다. 대상 그룹에 이미 관련된 내용이 정의가 되어 있으므로 대상 그룹만 선택하게 되면 경로 패턴이나 상태 확인 경로가 자동으로 입력됩니다.


그림 4-26




오토스케일링은 지금은 생략하고 다음 단계로 갑니다.


그림 4-27



검토를 통해서 입력값들을 확인하고 문제가 없다면 [서비스 생성] 버튼을 누릅니다. 그러면 서비스를 생성하고 서비스가 실행 주체로서 작업을 실행하게 됩니다. ECS 화면에서 만들어진 서비스를 클릭해서 서비스 화면을 확인해보면 서비스에 대한 내용을 확인할 수 있습니다. 서비스 화면에서 이벤트 탭을 확인해서 문제가 있는지 없는 지 확인할 수 있습니다. 문제가 없다면 배포는 완료입니다.


그림 4-28



로드밸런서가 컨테이너 상태 검사를 하는 부분은 EC2 콘솔 → 대상 그룹 → 하단 화면의 대상 탭에서 확인할 수 있습니다.


그림 4-29



이제 서비스가 인터넷에 공개되었습니다. 브라우저에서 확인해보겠습니다. EC2 콘솔 → 로드밸런서 → 하단 화면의 설명 탭에서 DNS 이름을 복사합니다.


그림 4-30



상태 체크 URL을 통해서 확인해보겠습니다. DNS 이름 뒤에 ‘/actuator/health’를 브라우저 주소창에 입력합니다. JSON 형태로 status : "UP" 이라고 나오면 정상입니다.


그림 4-31



처음에 실습을 따라하다보면 상태가 unhealthy가 되는 경우가 왕왕 있습니다. 개인적으로 했던 두 가지 실수는 대상 그룹을 설정할 때 상태 확인 경로에 오타가 났을 경우, 상태 검사 유예 시간을 너무 짧게 주었을 때입니다. 이런 실수들은 나중에 도움이 많이 됩니다.


그림 4-32



4.8 서비스 업데이트

이번에는 서비스 업데이트를 한번 해보겠습니다. 서비스 업데이트 테스트를 위해서 전용 엔드포인트를 만들어두었습니다. 아까 복사해둔 로드밸런서 DNS 이름에 ‘/test/updste’를 추가해서 브라우저에 입력해봅니다.


그림 4-33



코드를 수정해서 응답의 문구를 수정해보겠습니다. cloud9에서 petclinic-rest → src → main → java → vw.demo.petclinic → interfaces → tests → UpdateTestController.java 파일을 수정합니다. 한글로 바꿔보겠습니다.


그림 4-34



코드를 수정합니다. 그리고 도커 이미지를 빌드하고 3.2.2와 같은 방식으로 ECR에 도커 이미지를 푸시합니다. 

cd petclinic-rest
./mvnw clean package
docker build -f src/main/docker/Dockerfile -t petclinic-rest .
REPOSITORY_URI=`aws ecr describe-repositories --repository-name petclinic-rest | jq -r ".repositories[0].repositoryUri"`
docker tag petclinic-rest:latest ${REPOSITORY_URI}:latest
DOCKER_LOGIN=`aws ecr get-login --no-include-email`
${DOCKER_LOGIN}
docker push ${REPOSITORY_URI}:latest



정상적으로 푸시가 되었다면 ECR 페이지에서 확인할 수 있습니다. 이제 서비스 업데이트를 통해서 수정본을 반영해 보겠습니다. 클러스터를 선택하고 petclinic-rest-service를 클릭하여 서비스 화면에 진입합니다. 우측 상단에 업데이트라는 버튼을 클릭하면 서비스 구성 화면이 나옵니다. 작업 정의는 바뀐게 없으므로 따로 수정하지 않습니다. 도커 이미지가 변경되었음에도 작업 정의가 바뀌지 않은 것은 도커 이미지의 이름이 바뀌지 않았기 때문입니다. 그래서 새 배포 적용<sup>Force new deployment</sup>을 선택해서 강제로 이미지가 바뀌도록 해야합니다. 나머지도 모두 그대로 둡니다. 단계 4까지 스킵하고 서비스 업데이트 버튼을 누릅니다.


그림 4-35



ECS 서비스의 이벤트 탭과 EC2 → 대상그룹 하단의 대상 탭을 통해서 진행 상황을 확인합니다.


그림 4-36


이벤트는 기본적으로 최신순입니다. 아래 내용부터 메시지를 내용을 확인해보겠습니다.

우선 서비스가 새로운 작업 2개를 실행시켰습니다. 그리고 그 이후 타겟 그룹에 새로운 타겟(컨테이너)를 등록합니다. 새로운 타겟에 대한 상태 체크를 마친 뒤에 기존의 타겟을 제거합니다. 그 다음에 작업을 종료하는 일을 시작합니다. 타겟 그룹 속성에 있는 등록 취소 지연 시간(60초로 설정) 뒤에 작업을 종료합니다. 등록 취소 지연 시간은 서버가 현재까지 들어온 요청을 완료할 수 있도록 설정하는 시간입니다. 얼마 뒤에 안정적인 상태라는 메시지가 출력됩니다.

실제로 서비스가 수정이 반영되었는지 확인해보겠습니다.


그림 4-37



변경이 되었습니다. 만약 도커 이미지의 이름이 변경되거나 새로운 이미지를 통해서 컨테이너를 추가하거나 여러 가지 방법으로 작업 정의 자체가 변경되었을 때는 서비스 업데이트할 때 변경된 작업 리비전을 입력해서 업데이트하면 됩니다.