포스트를 읽기 앞서, 이전 글을 확인하지 못하신 분들께선 해당 링크에서 간단한 인프라 구조와 Github Actions에 대해 이해하시면 좋습니다!
1. IAM Roles
궁극적으로 ECS를 이용하여 Task를 실행하여 서버를 운용하고자 할 때, 적절한 role이 필요합니다. 이를 위한 Role은 다음과 같이 설정하였습니다. 참고할 AWS 가이드 문서도 함께 참조하였습니다.
User Permissions
사용하고자 하는 AWS ACCESS-KEY, SECRET-KEY 계정의 권한입니다. 해당 권한을 가진 계정의 access key와 secret key를 github actions에서 활용합니다.
ecsTaskExecutionRole
Task definition에서 사용하는 Role 입니다.
2. Private Subnet / Route Table
이번 실습에서는 Public이 아닌 Private Subnet에 ECS Task를 구동할 계획입니다. 따라서 Private Subnet과 Route Table 설정, 그리고 NAT Gateway 설정을 해주어야 합니다.
Private Subnet을 할당받은 Fargate container에서 외부(Outbound)로 나가기 위해서는 별도의 작업을 요구합니다. 이를 위해 구축하고자 하는 구조는 다음과 같습니다.
Fargate in private subnet, outbound traffic
Private Subnets
Create private subnets
먼저 private subnet을 생성해주어야 하는데, VPC로 들어가 Subnets로 들어갑니다. Create Subnet을 누르면 다음과 같이 생성할 수 있습니다. 원하는 subnet 이름을 정해주고, Availability Zone(이하 AZ)을 선택해주어야 합니다. 저는 ap-northeast-2a를 선택해주었습니다.
총 두 개의 private-subnet을 생성해주었으며, AZ는 각각 2a, 2b를 선택하여 생성해주었습니다.
Private Route Table
이제 private subnet을 사용하기 위해 private route table을 설정해주어야 합니다. 별도의 Table을 만드는 이유는 다음과 같은 이유가 있다고 합니다.
인터넷 게이트웨이와 NAT 게이트웨이를 하나의 라우팅테이블로 둘 경우, 인터넷 게이트웨이가 방화벽기능으로 NAT 게이트웨이의 통신을 차단한다고 함
Create private route table
VPC의 Route tables에서 Create route table을 선택하여 이름을 정해줍니다. 생성된 route table에 들어가 “Edit subnet associations”를 선택하여 연동할 서브넷을 선택합니다.
Edit subnet associations
여기서 Private Route Table은 방금전 생성한 private subnet 두 개를 선택합니다.
NAT Gateway
이제 Private Subnet 내에서 외부로 통신이 이루어지기 위해 NAT 게이트웨이를 생성해주어야 합니다.
Create NAT gateway
NAT 게이트웨이 이름을 설정하고 원하는 서브넷을 선택합니다. 이때 Subnet을 선택하는 기준은 어디를 통해서 밖으로 나가느냐 입니다. 생성해주는 목적이 NAT Gateway를 통해 트래픽이 외부로 나가야 하는 것이니 public-subnet을 선택해야합니다.
만약 private으로 선택하게 되면 다음과 같은 주의 메시지를 확인할 수 있습니다.
Edit routes
이제 NAT Gateway도 생성했으니, 밖으로 나가는 목적지(0.0.0.0/0)로 NAT Gateway를 설정해줍니다. 여기까지 하면 private-subnet-2a, 2b를 통해 외부로 통신할 경우 NAT Gateway를 통해 이루어지게 됩니다.
작업을 완료하였다면 VPC에서 Resource Map을 확인해볼 수 있습니다.
만약 네트워크 설정에 이슈가 있다면 Task 생성 시 다음과 같은 에러가 발생할 수 있습니다. 아래의 에러는 NAT Gateway를 통해 외부(ECR 서버)로 요청을 날릴 수 없어 task 생성에 문제가 발생했을 때의 예입니다.
3. Application Load Balancer
네트워크 설정을 완료하였다면, 본격적으로 ALB를 설정해주어야 합니다. ALB와 연결하는 것이니 만큼 Domain 연결이나 기타 로드밸런싱도 가능합니다. 단, 해당 포스트에서는 그 내용을 다루진 않을 것입니다.
Create load balancer
아래의 두 subnet은 public-subnet-2a, 2b입니다.
Create security group & Set
Create Target group & Set
Target group은 IP address로 설정해주어야 합니다.
HTTP Port settings
No register any targets
EC2와 다르게 별도 instance나 ip를 타겟에 설정해주진 않습니다. 추후 ECS의 Task가 올라오면 자동으로 Taget group에 속하도록 설정해줄 수 있습니다. 단, 이때는 반드시 ECS Cluster 내의 Service를 생성할 때 설정을 잘 해주어야 합니다.
Target Group 생성을 완료하였다면, ALB에 설정해주도록 합니다.
원하는 Certificate를 설정해주도록 합니다.
필요하다면 80번 포트로 접근한 요청의 HTTPS로 Redirect 해주도록 Listener를 추가해줄 수 있습니다.
4. Elastic Container Service / Registry
Github Actions에서 Image를 빌드하고 ECR에 저장후, 저장된 이미지를 활용하여 ECS에 Task를 생성하도록 설정해두었습니다. 이에 맞춰 ECR과 ECS에 적절한 세팅을 해주어야 합니다.
ECR
Github Actions 구동 시 workflows에 선언된 이름대로 repo 이름을 작성해주어야 합니다. 그리고 다른 유저에게 보이지 않도록 하기 위해서는 Private로 설정해줍니다.
ECS - Cluster
Cluster는 Fargate 형태로 만들어줍니다.
ECS - Service
ECS Cluster 내에 Service를 별도로 선언해주어야 하는데, 해당 설정이 중요합니다. 앞서 선언해두었던 ALB, TG, Private Subnet 등을 적절히 선택해주어야 하기 때문입니다. 또한 Task definition 연결도 중요하기 때문에 유의하셔야 합니다.
Create CloudWatch log group
먼저 Service 생성에 앞서, CloudWatch log group을 먼저 생성해주도록 합니다. 저는 로그 그룹을 설정하지 않아 다음과 같은 에러가 발생했었습니다. 아래는 CloudWatch의 Log Group을 생성하는 권한이 주어지지 않았을 때 발생한 에러입니다.
CloudWatch Log group 권한이 부족할 경우 log group을 생성할 수 없어 발생하는 에러
Create task definition
Service를 생성하기 앞서 task definition을 등록해주어야 합니다. 여기서 등록한 task definition과 service는 밀접한 관계가 있기 때문에 유의해야합니다.
아래는 Task definition 예제입니다. role에는 앞서 IAM Role에 설정해주었던 ecsTaskExecutionRole 을 사용합니다.
{
"containerDefinitions": [
{
"name": "ecs-deploy-tester-container",
"image": "[AWS ACCOUNT NUMBER].dkr.ecr.ap-northeast-2.amazonaws.com/ecs-deploy-tester-image-repo",
"cpu": 512,
"memory": 1024,
"portMappings": [
{
"containerPort": 8080,
"hostPort": 8080,
"protocol": "tcp"
}
],
"essential": true,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/ecs-deploy-tester",
"awslogs-region": "ap-northeast-2",
"awslogs-stream-prefix": "development"
}
}
}
],
"family": "ecs-deploy-tester-development",
"taskRoleArn": "arn:aws:iam::[AWS ACCOUNT NUMBER]:role/ecsTaskExecutionRole",
"executionRoleArn": "arn:aws:iam::[AWS ACCOUNT NUMBER]:role/ecsTaskExecutionRole",
"networkMode": "awsvpc",
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "512",
"memory": "1024"
}
YAML
복사
Create security group
이제 서비스에서 사용할 Security Group도 적절히 생성해주어야 합니다. 예제 코드에서는 포트번호가 8080이기 때문에 해당 포트만 열어주도록 합니다.
Create service
아래의 Task definition을 선택할 때, 앞서 설정해두었던 family를 선택하시면 됩니다. Revision은 버전이라고 보시면 되는데, 저는 실습하다 12까지 올라갔더군요 ㅎㅎ;
Network 세팅할 때 앞서 설정해주었던 Private subnet을 선택해주고, 설정한 Security Group을 선택해줍니다 그리고 Public IP는 Turned off 해줍니다.
Load balancer와 Container를 선택할 때 exist로 선택해주고, Container에는 task definition에 있는 name과 일치하는지 다시 한 번 확인해줍니다.
여기까지 하면 ECS 설정까지 마무리 되었습니다. 이제 Task를 run 해주거나 Github actions에서 Re-runs 혹은 브랜치에 push 하게 되면 Github Actions가 동작할 것입니다.
5. Conclusion
이번 포스트를 작성하면서 학습하고자 했던 목표는 두 가지였습니다.
•
ECS를 통해 Backend 서버를 배포하기 위한 지식 습득
•
Private subnet을 이용한 서비스 구축 방법과 원리 학습
이번 포스트를 작성하며 NAT 개념을 다시 한 번 복습했고, Github Actions를 활용하여 배포하는 방법을 익혀보았습니다. 덕분에 정말 많은 삽질을 했던 것 같습니다. 하지만 그런데로 의미 있는 삽질이었다 생각합니다. Private subnet을 어떻게 외부와 통신하는지 긴가 민가 하였는데 NAT Gateway를 활용해야 함을 알았고, VMware를 활용했던 시절을 떠올리며 NAT Gateway가 AWS에서는 어떻게 동작하는지에 대해서도 학습이 필요했습니다. 그리고 ECS 에서 Task를 올릴 때 ECR 외부 서버로 요청을 날린다는 점도 알게 되어 의미 있는 학습이 되었습니다.