본문 바로가기
HANDS-ON PROJECTS/DEVOPS

[DEVOPS] Deployed 3-tier Web App on ECS Cluster by using AWS CodePipeline(Blue-green)

by junthetechguy 2024. 6. 26.
 

TABLE OF CONTENTS

 

    1. Provisioning ECS Cluster

    지금까지는 EC2라는 가성머신 위에 소스를 직접 배포하는 형태로 서비스를 배포해보았지만 이렇게 되면 소규모 서비스 밖에 구축을 하지 못하므로 이제는 이러한 소스를 Docker image로 만들어서 ECS Cluster에 Docker Container 형태로 배포를 하자. 이렇게 함으로서 규모가 큰 서비스까지 배포할 수 있게 된다

    Docker Registry는 AWS의 ECR을 사용하도록 하고, 개발 환경을 Local PC로 진행하는게 아니라 Cloud9(EC2 Runtime environment 제공; OpenVPN과 같은 바스챤 host 없이도 개발 진행을 가능하도록 하는 용도)을 사용하도록 하자.

     

    ECR Repository 생성하기 => 당연하게 2개의 ECR을 만들때 모두 다 Private으로 설정을 해두자.

    그 후 이제 이 ECR에 Image를 Push할 수 있도록 Cloud9(AWS Systems Manager(SSM)으로 ECR과의 Network Connection을 연결시키도록 하자)을 만들어주기 위해 my-cloud9이라는 Name의 cloud9 envicorment를 생성하자.

    이후 아래와 같이 AWS에서 제공하고 있는 ECR로의 Push Command 동작을 실행해서 Docker image로 말아 놓은 다음 tagging 후 docker push를 frontend ECR로 해주자.

    아래와 같이 Image가 frontend ECR Repo에 Push가 되어 있는 것을 확인하자.

    이후 동일하게 cloud9에서 git clone으로 Backend github source를 clone해서 당겨온 후 application.properties에 내가 사용할 RDS의 endpoint를 적어주도록 하자

    이후 현재 지금 아래와 같은 target이라는 폴더 아래에 maven으로 build된 jar file이 존재하는데

    이 target이라는 폴더를 지운 후(rm -rf) yum install -y maven으로 maven을 설치 후 다시 해당 directory에서 mvn package 명령어를 통해서 Maven build를 하여 새로운 jar file을 만들어보자.

    이후 Maven으로 전체 프로젝트를 build를 해줘서 새롭게 jar file(java runtime)을 만든 다음에 이 java runtime을 그대로 아래와 같이 image(얘도 runtime)으로 Copy 해주도록 한 후 나중에 image를 docker run 시킬 때 그대로 java -jar 명령어로 그냥 image(runtime)을 실행만 시켜주면 된다

    나머지는 뭐 동일하게 docker build하고 docker tag를 해준 다음 docker push를 backend ECR로 해주면 된다.

     

    이제 ECS Cluster을 위한 SG를 만들어주자. 총 3개의 SG를 만들어야 한다. ALB의 SG, ECS Cluster의 SG, RDS의 SG.

    my-ecs-alb-sg : inbound로 80과 443을 둘 다 source를 global로 설정

    my-ecs-sg : 모든 TCP(0 - 65535 port) inbound를 source는 my-ecs-alb-sg로 설정(모든 port를 설정한 이유는 dynamic port를 ECS Cluster 안에서 사용할 것이기 때문이다)

    my-app-rds-sg : 3306으로 my-ecs-sg를 inbound로 설정

     

    이제 ELB 생성해주자. 

    TG 2개(my-ecs-frontend-80(HTTP:80; health check는 /(root)로), my-ecs-backend-8080(HTTP:8080; health check는 /api/v1/healthz))를 대상 유형을 instance로 선택하여(EC2 Instance Type의 ECS 이므로) 생성해주자.

    이후 ALB를 만들어주자. my-ecs-alb를 SG로는 my-ecs-alb-sg를 선택하고, Listener를 2개(HTTP:80 Listner는 my-ecs-frontend-80으로, HTTPS:443 Listener는 my-ecs-backend-8080으로) 설정하고, SSL 인증서를 ACM으로 만들었던 fastcampus-dragon.net으로 설정하자.

    이후 이 ALB의 2개의 Listener 중 HTTP:80은 HTTPS:443으로 redirection시키도록 설정하자. 그리고 HTTPS:443으로 들어왔을때는 my-ecs-frontend-80으로 전달하도록 설정하고, 호스트 헤더에 api.fastcampus-dragon.net으로 들어왔을때(IF)는 my-ecs-backend-8080으로 보내도록 설정(THEN)하자.

    이후 Route53의 fastcampus-dragon.net 호스팅 영역에서 api.fastcampus-dragon.net이라는 레코드가 내가 만들어놓은 my-ecs-alb로 설정해두고, ecs.fastcampus-dragon.net을 아래와 같이 ALB에 대한 별칭 설정으로 my-ecs-alb로 설정하여 레코드를 생성해주자.

    이후 ecs.fastcampus-dragon.net이라는 record는 my-ecs-alb에 잘 붙어있고,

    api.fastcampus-dragon.net이라는 record도 my-ecs-alb에 잘 붙어있는지 확인해주자.

     

    RDS/SG(Subnet Group)로는 이전에 사용한 RDS/SG(Subnet Group) 그대로 사용하므로 이 RDS Endpoint URL만 정상적으로 Cloud9으로 개발하는 backend repo의 application.properties의 spring.datasource.url에 잘 들어가 있는지 확인하고 SG로는 my-ecs-sg으로부터 들어오는 3306 port가 열려있는지(위에서 내가 설정해둔 my-app-rds-sg) 확인하자

     

     

    먼저 Cluster template으로 EC2 Linux + Networking의 조합으로 선택하고(Fargate type인 경우에는 그냥 Networking 전용으로만 선택하자) ECS cluster(my-ecs-cluster)를 만들어주자.

    이때 당연하게 이중화를 위해서 서브넷을 2개의 AZ에 각각 한개씩 만들어 놓은 Private Subnet 2개(a, c)로 설정해두자.

    그리고 Container Instance IAM role은 아래와 같이 새롭게 태그를 달아서 role을 만들어 주도록 하자

    이제 아래와 같이 ECS cluster가 다 생성된 화면을 확인하자.

     

    아래와 같은 ECS Task Definition을 만들어주자.

    2개의 Task Definition을 만들것이다. my-ecs-frontend-task-definition과 my-ecs-backend-task-definition

    그 후 컨테이너 세부 정보에 각각의 컨테이너의 이름과 Image URI(ECR에 존재함)를 설정해주고, Port mapping까지 진행시키도록 하자. 아래와 같이.

    ECS Cluster 안에서 통신을 할때는 Bridge 모드로 Network 통신이 이루어진다. 또한 아래에 보이듯이 Container port만 mapping이 되었고, host port가 mappping이 안되어 있으므로 자동적으로 dynamic하게 호스트 port의 범위 내에 랜덤으로 런타임 기간 동안 설정이 되게 된다는 것을 의미한다

    이제 백엔드 서비스를 위한 Task도 정의해주자. 아래와 같이.

    ECS Cluster에 존재하는 Container Instance는 frontend와 backend 각각 Task를 2개로 설정한다.

    아래와 같이 2개의 Task가 define되어 있는 것을 확인하자.

     

     

    이제 이 ECS Cluster에 service를 생성해보도록 하자

    Frontend 서비스는 Task Definition으로는 frontend Task Definition을 박고 TG로는 미리 생성해둔 80TG를 박고,

    Backend 서비스는 Task Definition으로는 backend Task Definition을 박고 TG로 미리 생성해둔 8080TG를 박아두자.

    그리고 이때 Task 갯수는 곧 container 갯수와 같으므로 프론트엔드와 백엔드 각각 2개로 설정하고, Load balancer로 내가 만들어 놨던 my-ecs-alb를 선택해주자.

    그러면 아래와 같이 정상적으로 프론트엔드 서비스가 동작되는 것을 볼 수 있다

    백엔드 서비스가 정상 동작하는지는 아래와 같은 PATH로 확인해볼 수 있다.

     

    이제 앞 단에 Cloudfront를 붙이고 여기로 ACM 인증(원래는 ALB에 존재)을 옮긴 다음에 ALB와 연동해보도록 해보자

    앞에서 사용했던 Virginia region에 만들어뒀던 SSL 인증서를 그대로 Cloudfront에 사용하도록 하자.

    먼저 Cloudfront를 생성하면서 이 Cloudfront의 origin은 ALB로 설정하고, 뷰어 프로토콜로 Redirect HTTP to HTTPS로 설정하도록 한 다음 CNAME(대체 도메인 이름; ecs.fastcampus-dragon.net)도 설정해주고, SSL 인증서로는 내가 만들어뒀던 SSL 인증서를 사용하자.

    이후 이제 더이상 ALB에서 HTTP:80 Listner로 들어오면 HTTPS:443으로 전달할 규칙이 존재할 필요가 없어진 셈(이미 Cloudfront 단에서 HTTP 필터링이 진행되었기 때문이므로)이므로 규칙으로 HTTP:80으로 들어오게 되면 곧바로 frontend:80으로 전달하도록 하자. 아래와 같이.

    또한 Cloudfront에서 설정한 CNAME으로 들어왔을때(만약 Client가 이 Cloudfront로 접속했을때) 역시 이미 Cloudfront 단에서 SSL 인증이 다 끝나서 필터링이 끝난셈이므로 곧바로 frontend:80으로 바로 전달할 수 있도록 해주자.

     

     

    이제 Route53에서 ALB로 연결하던 레코드(fastcampus-dragon.net이라는 호스팅 영역에 있는 ecs.fastcampus-dragon.net이라는 record)를 수정해서 해당 레코드가 Cloudfront와 연결되도록 해주자

    이후 내 local web browser로 ecs.fastcampus-dragon.net으로 접속하면 제대로 뜨는지 확인해주자.

     

    이제 내 local web browser로 ecs.fastcampus-dragon.net으로 접속해서 F12로 제대로 Hit from cloudfrount인지 확인해주자.

     

    2. Deploying 3-tier Web App on ECS Cluster

    이제 AWS Code Series(AWS CodeBuild & AWS CodeDeploy)를 사용해서 CICD를 자동화를 해줘서 GitHub Repository에 Push만 해주면 자동으로 ECS Cluster에 배포가 되도록 하는 아래의 아키텍쳐를 짜보도록 하자

    먼저 그러기 위해선 GitHub Token을 발급받아서 AWS CodeBuild에 넣음으로서 Authentication에 문제가 없도록 하는 것부터 시작해야한다.

    우선 현재 ECR에서 frontend repo와 backend repo를 분리해서 사용하고 있으므로 내 Github에도 frontend repo와 backend repo를 만들어서 각각 AWS에서 제공하는 buildspec.yaml 파일을 형식에 맞게 내용을 채워서 CI를 위한 yaml 파일(CodeBuild와 ECR 사이의 명령어 정의)를 작성하도록 하고

    그 후 다시 frontend github repo와 backend github repo에 각각 이 buildspec.yaml을 push하도록 하자.

    이후 AWS CodeBuild에서 2개의 Build Project를 생성해서 하나는 frontend용 codebuild(facam-frontend-codebuild) 나머지 하나는 backend용 codebuild(facam-backend-codebuild)로 설정해주도록 하자.

    이때 소스 공급자는 GitHub Repo로 선택하고 GitHub 개인용 액세스 토큰으로 연결을 아래와 같이 선택후 해당 엑세스 토큰으로 내가 GitHub에서 발급받은 Token을 넣어주면 된다.

    그리고 환경 설정으로는 Amazon Linux 2로 선택하자.

    이후 CodeBuild에서 default로 생성한 IAM role에게 ECR을 컨트롤할 수 있는 권한 정책을 아래와 같이 설정해주도록 하자

    이렇게 되면 정상적으로 빌드가 진행되게 되어 ECR에 정상적으로 아래와 같이 새롭게 빌드된 image가 등록이 되게 된다.

     

    자 이제 AWS CodeDeploy를 생성해보도록 하자. 헌데 CodeDeploy가 ECS Cluster에 배포를 진행할때 서비스 자원을 건드려야 하므로 일단 IAM role부터 설정을 해서 이 CodeDeploy가 ECS Cluster에서 돌아가는 서비스 자원을 건드리는 것을 허용하도록 해주자.

    이후 tg를 새롭게 2개를 만든 후 이것을 ALB에 연결시켜줘야 하는데 첫번째 tg(my-ecs-frontend-bluegreen-80)는 ECS Cluster의 Frontend를 Blue Green(=CodeDeploy를 이용한 배포전략)으로 배포하기 위한 tg이고 두번째 tg(my-ecs-backend-bluegreen-8080)는 ECS Cluster의 Backend를 Blue Green으로 배포하기 위한 tg이다.

    이후 ALB의 리스너 추가에 들어간 후 아래와 같이 HTTPS:8443 Listner를 추가해서 ALB에서 8443 port로 들어오게 되면 Frontend BlueGreen tg로 전달하도록 해주자

    이후 ECS Cluster에서 새로운 service를 생성해줘야 한다. 이때 서비스를 블루/그린 배포(CodeDeploy를 이용해서 배포가 되는 서비스) 배포 유형으로 설정한 후 서비스를 2개(Frontend Service와 Backend Service)에 각각에 맞는 task definition를 설정하고, 배포 IAM Role에는 내가 만들었던 ECS 권한이 들어있는 IAM Role을 선택 후 my-ecs-alb를 load balacer로 연결해주자.

    그리고 production listner port는 443:HTTPS를 선택하고, test listner port는 8443:HTTPS를 선택하고 대상 그룹에는 프론트엔드의 경우에는 my-ecs-frontend-80과 my-ecs-frontend-bluegreen-80을 선택하고 백엔드의 경우에는 my-ecs-backend-8080과 my-ecs-backend-bluegreen-8080을 설정하여 ECS Cluster service 생성을 해주자.

    이렇게 되면 아래와 같은 3가지가 자동으로 생성이 되게 된다.

    이 중에서 새롭게 생성된 CodeDeploy application에 들어가서 제대로 CodeDeploy Application이 생성이 되었는지 확인해주자.

     

    그 다음 AWS에서 제공하는 appspec.yaml(S3에 업로드해서 사용할 것이므로 frontend porject directory의 어디 위치에 둬도 상관없음)이라는 CD를 위한 yaml 파일(AWS CodeDeploy에서 ECS Cluster로의 Deploy)에 아래와 같이 ECS의 TaskDefinition의 ARN 정보와 ContinaerName과 ContainerPort를 쭉 입력해주도록 하자.

     

    이후 S3 Bucket(fastcampus-appspec-bucket)을 하나 만든 후 여기에 frontend appspec.yaml과 backend appspec.yaml 파일을 upload한 후 CodeDeploy에 deployment를 2개 Create할 때 S3에 존재하는 appspec.yaml 파일의 위치를 아래와 같이 넣어주도록 하자

    이렇게 되면 배포가 정상적으로 진행이 되게 된다.

    Blue Green 배포로 설정하였으므로 아래와 같이 원본(Blue)가 100%이고 대체(Green)가 0%였다가 배포가 완료되면 원본(Blue)가 0%로 떨어지고 대체(Green)이 100%로 올라가게 된다

    헌데 위에서 4단계와 5단계가 남아있는데 이것은 원본(Blue)에 있던 Task가 완전히 삭제가 되어야 채워지게 된다. 시간을 기다리면 채워진다. 따라서 내가 원하는 Task 갯수가 프론트엔드와 백엔드가 각각 2개씩 이었는데 지금은 아래와 같이 공존하고 있으므로 프론트엔드만 예를 들자면 4개가 존재하게 된다

     

    이후 원래 존재하던 Task가 사라지고 2개만 남게 된다.

     

    자 이제 CodePipeline을 만들어서 지금까지 만들어놨던 CodeBuild와 CodeDeploy를 한번에 진행하도록 해보자. 역시 이것도 프론트엔드와 백엔드 Pipeline을 따로 따로 만들어야하기 때문에 이 둘을 분리해서 만들어주면된다.

     

    먼저 그 전에 taskdef.json 파일을 아래와 같이 만들어야 하는데 여기에는 ECS의 프론트엔드 작업정의(Task Definition)와 백엔드 작업정의(Task Definition)의 Json 정보를 그대로 긁어다가 갖다 각각 붙여놓으면 된다.

     

    그리고 buildspec.yaml의 artifacts 부분의 files와 secendary-artifacts의 files에 appspec.yaml과 taskdef.json으로 들어가 있는지 확인해보자.

    이제 buildspec.yaml과 appspec.yaml과 taskdef.json이 하나의 프로젝트에 같은 위치에 있는 것을 위와 같이 확인을 하고 프론트 github repo와 백엔드 github repo에 각각 push를 해주자.

    이후 AWS CodePipeline을 아래와 같은 느낌으로 2개(프론트용, 백엔드용)만들어주자.

    그리고 아래와 같이 소스 공급자를 GitHub로 설정해두자.

    그 후 아래와 같이 빌드 공급자는 AWS CodeBuild를 선택해두도록 하고 프로젝트 이름도 기존에 만들어뒀던 AWS CodeBuild를 선택하도록 하자.

    그 후 아래와 같이 배포 공급자는 AWS ECS(Blud/Green)으로 선택하도록 하고 AWS CodeDeploy 애플리케이션 이름과 AWS CodeDeploy 배포 그룹과 Amazon ECS 작업 정의 파일과 AWS CodeDeploy AppSpec 파일을 설정해두도록하자.

     

    이렇게 CodePipeline으로 아래의 CodeCommit, CodeBuild, CodeDeploy가 모두 다 하나의 일련된 동작으로 실행이 완료되게 되었다.

     

     

     

    [Reference]

    1. https://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html

     

    What is AWS CodePipeline? - AWS CodePipeline

    Thanks for letting us know this page needs work. We're sorry we let you down. If you've got a moment, please tell us how we can make the documentation better.

    docs.aws.amazon.com

    2. https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/create-container-image.html

     

    Amazon ECS에서 사용할 컨테이너 이미지 생성 - Amazon Elastic Container Service

    경우에 따라서는 ec2-user가 Docker 데몬에 액세스할 수 있는 권한을 제공하기 위해 인스턴스를 재부팅해야 할 수도 있습니다. 다음 오류가 표시될 경우 인스턴스를 재부팅합니다. Cannot connect to the D

    docs.aws.amazon.com