GitHub Actions를 활용한 ECS 배포자동화

Wynn
19 min readFeb 27, 2021

--

Deployment Automation (Django + Docker + ECS + Fargate)

Amazon ECS

클라우드는 우리에게 없어서는 안 될 공기 같은 존재가 되었다.

나도 혼신의 힘을 다해 만들었던 엉터리 웹 어플리케이션을 처음으로 EC2 인스턴스에 배포했던 그때 그 느낌(뽕맛..)을 아직까지도 잊지 못한다.

하지만 당시에 정말 개발 왕초보였기 때문에 수많은 난관에 봉착했었다. 특히 애먹었던 부분은

  • Nginx
  • Security Group
  • SSH Key
  • 서버를 백그라운드에서 돌리기

당시 이것들은 서버가 동작하지 않는 이유에 대해서 내 수준에 알맞은 단서를 주지 않았고, 덕분에 삽질을 많이 했던 거 같다.

수동 배포

다른 건 초기 배포할 때만 들어가는 비용이니 그렇다 치고 수정사항이 생길 때마다 SSH Key로 인스턴스에 접속해서 git pull 커맨드를 입력하는 건 매우 번거로운 작업이다.

사실 SSH Key로 인스턴스에 접속하는 건 어렵지 않은 작업이지만 초보자 입장에서 직접 인스턴스에 접근하는 행위는 은근한 거부감을 준다. (왠지 모르게 기분 나쁜 결계가 느껴진달까…)

수동배포를 하면 이렇게 매번 귀찮음을 감수해야 하다 보니 자연스레 업데이트 주기가 늦어지고, 소스 코드에 큰 변화가 있을 때만 배포를 하게 되는데 이는 내가 개발하고 있는 서비스에 대한 관심도를 떨어뜨릴 수도 있다.

서론이 길었다. 본론으로 들어가서 우리는 제목처럼 GitHub Actions로 우리의 어플리케이션을 도커라이징해서 AWS 서비스에 배포하는 일련의 프로세스를 자동화할 것이다.

말만 들었을 때는 어려워 보일 수 있는데 너무 겁먹을 필요는 없다. 우리는 AWS에서 제공하는 서비스를 이용하는 것이고, 간단하게 생각해보면 우리는 그저 AWS가 우리에게 이 서비스를 제공하기 위해 받아야 하는 설정값들을 적법하게 입력해주면 되는 것이다.

배포는 프로세스를 정립하는 순간부터는 결국 지루한 반복작업이다. 우리는 개발자다. 중복 작업을 피하는 것이 우리들의 숙명이라는 걸 늘 기억하자.

선행작업

도커 이미지 빌드

도커 파일을 작성하기 전에 사용할 파이썬 라이브러리들을 requirements.txt 파일에 정리해보자. 나는 django, mysqlclient 두 가지를 사용하려고 한다.

requirements.txt

Dockerfile 은 컨테이너를 구성할 때 필요한 특정 명령어(DSL) 집합들을 실행시켜 도커 이미지를 생성하는 역할을 한다. 각 명령어에 대해 line by line으로 설명하지는 않을 예정이니 궁금하다면 이곳을 참고하자. (생각보다 양이 많으니 우측 nav bar를 잘 활용하면 좋을 것이다)

프로젝트 루트에 Dockerfile 을 아래와 같이 생성해보자.

Dockerfile

이 파일이 핵심적으로 수행하는 역할에 대해 열거해보자면 이렇다.

  • python 3.7 버전의 이미지를 베이스 이미지로 사용
  • 이미지를 생성하기 위해 필요한 파일들 복사 및 설치
  • 8080 포트를 열고, 해당 포트로 서버 실행

그럼 이제 도커 이미지를 빌드해보자.

docker build [Dockerfile path] -t [your image name]:[tag]

tag 는 required 한 값이 아니므로 생략해도 괜찮다. 나는 여기서 이미지 이름을 ecs-tutorial 로 해서 아래와 같은 커맨드로 빌드했다.

> docker build . -t ecs-tutorialStep 1/8 : FROM python:3.7
---> 2699987679cd
Step 2/8 : ENV PYTHONUNBUFFERED=1
---> Using cache
---> b1ecf661981c
Step 3/8 : WORKDIR /app
---> Using cache
---> 4f89f55da03a
Step 4/8 : COPY requirements.txt /app/
---> Using cache
---> 363862418ed6
Step 5/8 : RUN pip install -r requirements.txt
---> Using cache
---> ee9a55e82551
Step 6/8 : COPY . /app/
---> Using cache
---> c0b955af8faf
Step 7/8 : EXPOSE 8080
---> Using cache
---> ebb82e5e1931
Step 8/8 : CMD [ "python3", "manage.py", "runserver", "0.0.0.0:8080" ]
---> Using cache
---> ba3d6c22cbb9
Successfully built ba3d6c22cbb9
Successfully tagged ecs-tutorial:latest

위처럼 로그가 찍혔다면 이미지 빌드는 성공한 것이다.

docker images 명령어를 입력하면 내가 생성한 이미지 리스트를 볼 수 있다.

REPOSITORY      TAG     IMAGE ID            CREATED           SIZE
ecs-tutorial latest 8b9365a9f930 2 seconds ago 919MB
...

그럼 이제 우리가 생성한 이미지를 동작시켜보자.

> docker run -d -p 8080:8080 ecs-tutorial
1036a0c040c0cf8005301a3af41feeaeaadd7ec770137da753f968496f24f

-d 옵션을 사용하면 컨테이너를 백그라운드에서 동작시킬 수 있고 -p 옵션은 바인딩할 호스트 머신의 포트와 컨테이너의 포트를 인자로 받는다. 만약 -p 80:8080 옵션을 사용하면 호스트의 80번 포트로 들어오는 요청이 컨테이너의 8080번 포트로 들어간다.

자 그럼 이제 127.0.0.1:8080 포트로 접속해보자. Django 로켓이 우릴 반겨줄 것이다.

Welcome to Django world!

AWS ECR에서 Repository 생성하기

ECR Repository를 생성하기 전에 ECR에 대한 개념이 생소할 수 있어 간단히 짚고 넘어가려고 한다.

Amazon Elastic Container Registry (Amazon ECR) is an AWS managed container image registry service that is secure, scalable, and reliable. Amazon ECR supports private container image repositories with resource-based permissions using AWS IAM. This is so that specified users or Amazon EC2 instances can access your container repositories and images. You can use your preferred CLI to push, pull, and manage Docker images, Open Container Initiative (OCI) images, and OCI compatible artifacts.

ECR은 AWS에서 관리하는 이미지 레지스트리 서비스다. Docker Hub랑 수행하는 역할이 굉장히 비슷하다고 하는데 나는 Docker Hub를 많이 사용해본 적이 없어서 잘 모르겠다. 어쨌든 이곳에서 생성하는 Repository는 우리가 만든 도커 이미지를 보관하는 역할을 하고, ECS, Code build 등 AWS에서 제공하는 서비스들을 사용할 때 아무래도 관리주체가 같아 액세스가 비교적 안전하고 간단하다.

ECR Repository를 생성하려면 Services에서 ECR을 검색해서 들어온 후에 Create repository 버튼을 클릭하면 아래와 같은 화면이 나올 것이다. 나는 여기서 Private Access로 만들었고, Repo 이름은 repo-for-ecs-tutorial로 만들었다.

Repository 생성은 이게 끝이다. 우리는 나중에GitHub Actions 를 활용해서 우리가 생성한 도커 이미지를 이곳에 업로드할 것이다.

작업 정의(task definition) 생성하기

task definition은 docker-compose와 비슷한 역할을 한다고 생각하면 된다. 어떤 도커 이미지를 사용할 것인지, 포트 매핑은 어떻게 할 것인지, 인스턴스 타입은 어떤 것을 쓸 것인지, CPU나 메모리는 얼마나 쓸 건지 등을 이 친구에게 알려주면 주문에 맞게 컨테이너를 올려준다.

작업 정의는 AWS console에서도 할 수 있지만, 우리는 이걸 직접 json 파일로 만들려고 한다. 이유는 다양하겠지만 내 개인적인 의견으로 설득해보자면

  • 편하다.
  • 다룰 수 있는 설정들이 더 많다. (어드밴스드 파트에서 Secret 설정도 다뤄보려고 하는데 콘솔에서는 어디서 설정하는지 모르겠다)
  • 자동배포와 잘 맞는다.

이 실습은 자동배포를 구축하는 것이고, 작업 정의를 바꾸게 되면 우리의 컨테이너에 변경사항을 적용할 수 있다. 이 점에서 자동배포랑 잘 맞는 방식이라고 생각했다. 자동배포를 구축해놨는데 컨테이너 설정에 변경사항이 있을 때마다 콘솔에 가서 직접 task definition을 수정하는건 좀 이상하지 않은가.

먼저 프로젝트 루트에 가서 task-definition.json 파일을 생성해보자. 여기에 형식에 맞도록 설정값을 넣어주면 되는데 AWS의 개발자 가이드에 가면 template을 볼 수 있다. 하지만 제공되는 템플릿에는 모든 설정값이 명시되어 있어서 좀 복잡해 보인다. 이곳task-def-example.json 파일을 간결하게 만들어봤다. 내가 사용했던 파일과 비교해보면서 대괄호 안에 있는 설정값들만 변경해주면 되니 참고하길 바란다.

task-definition.json 의 설정값 중 executionRoleArn 이라는 값이 있는데 만약 만들어둔 게 없다면 IAM에 가서 만들어줘야 한다. 먼저 이 역할을 만든 거 같은데 잘 기억이 안 난다면 AWS 콘솔에서 ECS > Task Definition > Create Task Definition으로 가보자. 만약 만들어진 게 있다면 Task Role의 드롭다운에 항목이 있을 것이다. IAM의 Role에 가서 ARN을 복사한 후에 넣어주면 된다.

executionRoleARN 을 만드는 방법은 공식문서에서 아래와 같이 설명해주고 있다. 어렵지 않으니 이 튜토리얼에서 따로 다루진 않으려고 한다.

To create the ecsTaskExecutionRole IAM role

1. Open the IAM console at https://console.aws.amazon.com/iam/.

2. In the navigation pane, choose Roles, Create role.

3. In the Select type of trusted entity section, choose Elastic Container Service.

4. For Select your use case, choose Elastic Container Service Task, then choose Next: Permissions.

5. In the Attach permissions policy section, search for AmazonECSTaskExecutionRolePolicy, select the policy, and then choose Next: Review.

6. For Role Name, type ecsTaskExecutionRole and choose Create role.

task-definition.json 을 완성했다면 이제 사용 중인 AWS 계정에 등록해줘야 한다. 여기서는 aws-cli 를 사용해서 등록할 것이다. 만약 이전에 aws-cli 를 사용해본 적이 없다면 초기 설정을 해야 하고, 이 과정에서 Access Key와 Secret Key가 필요한데 만약 따로 가지고 있지 않거나 기억이 나지 않는다면 IAM에서 본인 계정을 찾아서 Security Credential 탭에 들어가면 새로 발급받을 수 있다.

json 파일을 등록하기 위해서는 아래 명령어를 입력하면 된다.

aws ecs register-task-definition --cli-input-json file://[your task-definition.json name with path]

이 작업이 완료되면 AWS 콘솔에서도 직접 확인해볼 수 있다.

Task Definitions in AWS console

클러스터랑 서비스를 생성하기 전에 이해를 돕기위해 우리가 생성한 것들을 간단한 다이어그램으로 만들어봤다. 한번 살펴보고 넘어가자.

ECR and Task Defitnition are added into the AWS area

ECS 클러스터 & 서비스 생성

이번에는 네트워크에 대한 설정도 필요해서 많이 헷갈릴 수도 있다. 급하게 하지말고 천천히 읽으면서 따라와 주길 바란다.

Cluster에 대한 정의도 잠깐 보고 가자.

An Amazon ECS cluster is a logical grouping of tasks or services.

클러스터란 우리의 task 혹은 service가 돌아가는 논리적인 그룹을 말한다. 이 말은 우리가 정의한 tasks, 그리고 아직 다루진 않았지만, 우리가 정의한 작업들을 관리해주는 Services가 이 위에서 동작한다는 것을 의미한다.

AWS에서는 유저가 ECS를 처음 사용할 때 기본적으로 default라는 이름의 기본 클러스터를 제공한다. 하지만 우리는 이걸 사용하지 않고 새로 만들 것이다. 클러스터는 단순히 클릭 몇 번이면 생성할 수 있다.

AWS 콘솔에서 ECS에 들어가 보면 좌측에 Cluster라는 탭이 있고, 이곳에 들어가면 클러스터를 생성할 수 있다. Create Cluster 버튼을 클릭해보자.

우리는 인스턴스 타입으로 Fargate를 사용할 것이기 때문에 Select Cluster Template 단계에서는 Network Only를 선택하고 넘어가자. 인스턴스 타입으로 Fargate와 EC2 인스턴스가 있는데 이 둘의 비교를 보고 싶다면 이 글을 읽어보는 걸 추천한다.

다음 단계에서는 클러스터의 이름을 지어주고(나는 cluster-for-ecs-tutorial 로 지었다) 따로 새로운 VPC를 생성하지는 않을 것이기 때문에 그냥 Create 버튼을 눌러서 생성하자.

클러스터 생성이 완료되면 스크린샷을 참고하여 위에서 언급했던 서비스를 생성해야 한다.

Configure service 단계에서는 아래와 같이 우리가 만들었던 Cluster, Task Definition을 선택해서 아래처럼 완성해준 후에 다음 단계로 넘어가자.

Service creation example

다음 단계에서는 네트워크에 대한 설정을 해줘야 한다. 여기서 Application Load Balancer 생성, 보안 그룹 설정 등의 작업을 하게 된다.

먼저 기존에 사용하고 있는 VPC와 Subnet(서브넷은 최소 2개가 있어야한다)을 설정한 후에 Edit 버튼을 눌러서 보안그룹을 설정해보자.

Security Group Setting

우리가 앞서 만들었던 task-definition.json 에서 8080번 포트로 매핑해두었기 때문에 보안그룹에서도 8080 포트를 Anywhere로 열어주도록 하자. 개인적으로 진행하던 팀 프로젝트에서 배포 자동화를 구축하다가 이 부분을 잘못 설정해서 애를 먹었다. 꼭 80번 포트가 아닌 8080번 포트를 열어주자.

밑으로 내려가면 Load Balancer 타입을 설정하고 없으면 생성하라고 하는데 타입은 Application Load Balancer(이하 ALB)를 선택하고, ALB를 생성하기 위해 EC2 console로 넘어가 보자.

EC2 console의 Load Balancer 탭에서 Create Load Balancer를 클릭해보자.

타입은 ALB로 선택하고, 적당한 이름을 지어주자. (모두가 예상했겠지만 alb-for-ecs-tutorial로 지었다) 80번 포트만 사용할 것이기 때문에 Listener도 따로 건드릴 필요는 없고 본인이 사용할 VPC와 Subnet을 선택하고 넘어가자.

Configure Security Settings 단계로 넘어가면 보안 리스너를 사용하라고 하는데 이건 https를 사용할 수 있도록 설정한 후에 443 포트를 사용하라고 권장하는 것이다. 따로 어드밴스드 파트에서 다뤄볼 예정이니 이번엔 무시하고 넘어가자.

그다음에는 보안그룹을 설정하라고 하는데 적당한 이름과 함께 80번 포트를 Anywhere로 열어주고 넘어가자.

Configure Routing

라우팅을 설정하는 파트에서는 Target Group을 생성해야 한다. Target Group은 ALB가 요청을 받았을 때 어디로 보낼지를 알려주는 역할을 한다. 우리는 Fargate를 사용하기 때문에 Target Type을 IP로 설정한 후에 적당한 이름을 주고 넘어가자. 그러면 이번엔 또 Target을 등록하라는데 이 부분은 그냥 스킵하면 된다.

Review 페이지에서 잘 설정했는지 확인하고 생성해주면 ALB 생성이 완료된다. 그럼 다시 서비스를 생성하던 페이지로 돌아와서 ALB를 선택하고, 아까 만들어둔 Target Group을 선택해주자.

그다음 Auto Scaling을 설정하는 부분은 건너뛰고 서비스를 생성하자.

드디어 서비스 생성을 완료했다!

자 그럼 지금까지 우리가 했던 것들을 정리해보자

  • ECR 생성
  • Task Definition 생성
  • Cluster, Service 생성
  • ECS에서 사용할 ALB와 Target Group 생성

드디어 앱이 배포될 인프라 작업을 끝마쳤다! 우리가 만든 것들을 다시 다이어그램으로 만들어보자면 이렇다.

AWS에서는 어느정도 인프라가 완성된 모습

이제 main 브랜치에 Push 했을 때 우리가 만들어둔 인프라에 우리의 앱 어플리케이션을 올려주기만 하면 된다.

GitHub Actions 생성

이제 Container Image를 ECR에 배포할 수 있도록 설정해줘야 한다. 우리가 마지막으로 하려고 하는 작업을 다이어그램으로 그려보자면 이렇다.

Container Image와 Container Registry 사이에서 GitHub Actions가 다리 역할을 하고 있다.

이 작업에 앞서 workflow가 진행될 때 사용해야 할 환경변수들을 Secrets에 등록해주자.

Register Secrets

AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY 의 정보는 본인이 보유하고 있는 csv 파일에서 확인 가능하고, 만약 이게 없더라도 IAM에서 새로 발급받고 파일을 내려받을 수 있다.

본격적으로 workflow를 만들어보자. GitHub Actions는 GitHub에서 우리가 만든 소프트웨어의 workflow를 쉽게 자동화할 수 있게 해주는 서비스다. 보통 이 서비스를 활용해서 앱을 빌드하고 테스트하고 배포하는 과정 등을 거친다. 뿐만 아니라 다른 사람들이 정의해놓은 workflow 파일도 가져다 사용할 수 있다. 이번에도 우리는 똑똑한 사람들이 잘 만들어놓은 workflow 파일을 사용해서 우리의 앱을 배포할 것이다.

GitHub Actions Tab

앱이 올라가 있는 GitHub Repository에서 Action 탭을 눌러 New workflow 버튼을 클릭하면 workflow 템플릿을 선택할 수 있다. 스크롤을 내려서 AWS에서 만든 Deploy to Amazon ECS 서비스를 찾아 Set up this workflow를 클릭한다.

github/workflows/aws.yml 파일을 생성하는 페이지가 나타날 것이다. 우리가 수정할 부분을 정리해보면 이렇다.

  • on (main 브랜치에 push 될 때 workflow를 실행시킬 예정)
  • aws-region
  • ECR_REPOSITORY
  • container-name
  • service
  • cluster

이번에도 헷갈릴 수 있으니 내가 만들었던 aws.yml 파일을 공유한다. 다소 시간이 걸리지만 몇분 후면 이런 화면을 볼 수 있을 것이다.

드디어 자동배포 구축!

우리는 이제 배포라는 중요한 과정을 자동화함으로써 서비스를 개발하는 데 집중할 수 있게 되었다!

다음 어드밴스드 파트에서는 Optional한 부분들이지만 적용하면 좋은 것들에 대해 다뤄볼 것이다. 생각 중인 것들을 열거해보자면 아래와 같다.

  • AWS Certificate Manager를 이용해서 SSL 인증서 발급
  • ALB에 https 적용
  • AWS System Manager를 활용해 환경변수(DB 정보)를 Docker 이미지에 적용하기

마치며

AWS는 정말 모든 것들에 대해 문서화를 해놨기 때문에 머릿속에 설계해놓고 만든다기 보다는 문서를 보면서 따라가다가 막히면 검색하고 stackoverflow에도 올려보면서 피드백을 받아 완성하곤 했다.

보통 AWS에 한번 구축해둔 것들은 큰 변동사항이 없다면 다시 볼일은 많지 않은 거 같다. 그러다 보니 완성하고나서 정리를 하지 않는다면 결국 타인의 지식을 잠시 대여한 꼴이다. 이미 사용해본 서비스라도 설명해보라고 하면 시원하게 답을 하지 못했었다. 그래도 이렇게 블로그에 정리해놓으니 머릿속에서 많이 정리된 느낌이 들어서 뿌듯하다.

더불어서 누군가가 읽을 수도 있다고 생각하니 좀 더 객관적인 정보를 전달하기 위해 레퍼런스도 많이 찾아보게 된 거 같다. 이러한 과정에서 애매한 부분들에 대해 좀 더 학습할 기회가 되었다.

참고한 글

--

--