개요
사이드 프로젝트에서 ECS로 인프라 구성을 진행하는데,
여러 Application이 Fargate로 배포되어 비용이 많이 발생하는 문제가 있었다.
비용을 효율적으로 사용하기 위해 태스크 하나를 EC2 프리티어로 사용해보고자 했고,
오랜 삽질 끝에 성공적으로 마무리하여 해당 과정을 기록하게 되었다.
ECS 클러스터 생성
먼저 AWS에 로그인한 후 Elastic Container Service 콘솔에 들어가서 클러스터를 생성하자.
인프라 섹션에서 Amazon EC2 인스턴스를 선택하고,
EC2 인스턴스 유형을 프리티어가 가능한 t2.micro로 선택하자.
원하는 용량에 최소 1, 최대 1을 입력하자.
최소가 0이면 최초 생성되는 EC2가 ECS 클러스터 인스턴스로 등록이 되지 않는다.
EC2의 보안 그룹을 설정할 때, 하나의 EC2 인스턴스 내에서 포트를 바꿔가며 배포가 되므로 인바운드에 모든 트래픽(모든 포트)이 설정되어 있어야 한다.
추후 ELB가 추가되면 ELB의 보안 설정으로 소스를 변경하여 보안 처리를 진행할 예정이므로 지금은 소스를 위치 무관으로 설정하고 넘어가자.
또한 퍼블릭 IP 자동 지정은 꼭 켜기로 설정해야 한다.
NAT 설정이 따로 없는 경우 퍼블릭 IP가 없으면 인터넷 통신이 되지 않아 최초 생성되는 EC2가 ECS 클러스터 인스턴스로 등록이 되지 않는다.
위처럼 설정하고 생성을 누르면 ECS 클러스터가 생성된다.
ELB 생성
위처럼 설정하고 ECS 클러스터를 생성하면, 잠시 뒤 EC2가 자동으로 생성된다.
해당 EC2에 Application이 배포될 예정이므로, 해당 EC2에 연결할 ELB를 설정해 보자.
(Fargate의 경우 ECS 서비스 생성 시 ELB를 자동으로 생성할 수 있는데, EC2는 직접 생성해 줘야 한다.)
EC2 > 로드 밸런서 > 로드 밸런서 생성으로 들어가서 Application Load Balancer를 선택한다.
로드밸런서 이름을 입력하고, 나머지는 기본값 그대로 유지한다.
테스트 용도이므로 Subnet 가용 영역은 존재하는 Subnet 모두 선택했다.
이후 바로 아래에서 보안 그룹을 설정해야 한다.
해당 페이지에서 "새 보안 그룹을 생성" 버튼을 눌러 보안 그룹 생성 페이지로 이동하자.
ELB의 보안 그룹에서 인바운드는 HTTP 요청을 수신해야 하므로 80 포트를 모두 열어줘야 한다.
(물론 아웃바운드는 모든 트래픽 허용)
위처럼 구성하고 보안 그룹을 생성한 이후, 해당 보안 그룹을 ELB에 연결해 주자.
그다음 리스너 및 라우팅 섹션에서 ELB가 연결할 인스턴스를 설정해야 한다.
마찬가지로 "대상 그룹 생성" 버튼을 통해 대상 그룹을 생성해 보자.
대상 유형은 꼭 인스턴스로 설정해 주자.
(이유는 뒤에서 설명한다)
상태 검사 경로는 우리가 띄울 Application의 Health Check 경로로,
여기에서는 root 경로인 / 를 통해 상태 검사를 진행하도록 구성하고 넘어가자.
위처럼 구성하고 로드 밸런서 생성 버튼을 누르면 인스턴스 선택 페이지가 나타난다.
여기에서 생성된 인스턴스를 선택하고, 80 포트를 입력 후 "아래에 보류 중인 것으로 포함" 버튼을 눌러 대상으로 추가한다.
이렇게 두고 "대상 그룹 생성" 버튼을 누르면 대상 그룹 생성이 완료된다.
다시 ELB 생성 페이지로 돌아와서, 위에서 생성한 대상 그룹을 연결해 주자.
이후 로드 밸런서 생성 버튼을 누르면 드디어 로드 밸런서 생성이 완료된다.
마지막으로 위에서 생성했던 EC2의 인바운드 규칙을 ELB에서만 통신이 가능하도록 수정해야 한다.
EC2 > 보안 그룹에서 해당 인스턴스를 선택하고, 인바운드 편집 버튼을 눌러 수정을 진행해 보자.
인바운드 규칙 내 소스를 ELB에서 생성했던 보안 그룹으로 연결해 주고 규칙 저장을 눌러서 완료하자.
이를 통해 ELB에서만 EC2로 접근이 가능하도록 설정할 수 있다.
ECR 리포지토리 생성
다음으로 Application Image를 저장할 ECR 내 리포지토리를 생성해 보자.
Elastic Container Registry로 접속하여 생성 버튼을 통해 리포지토리를 생성할 수 있다.
ECS 태스크 정의 생성
이제 다시 Elastic Container Service 콘솔로 돌아와서,
Application을 띄울 환경을 정의하는 태스크 정의를 생성할 것이다.
그전에, 우리가 생성한 EC2의 정확한 사양을 알아야 맞춰서 환경을 정의할 수 있다.
ECS 콘솔의 클러스터 > 인프라에서 컨테이너 인스턴스로 활성화되어 있는 EC2를 클릭해 보자.
EC2에서 사용 가능한 CPU는 1,024MB (1 vCPU), 사용 가능 메모리는 952MB인 것을 확인했다.
이를 참고하여 태스크 정의에서 서버 스펙을 정의해 보자.
태스크 정의 > "새 태스크 정의 생성" 버튼을 클릭하여 접속할 수 있다.
시작 유형은 Amazon EC2 인스턴스만을 선택하고,
네트워크 모드를 꼭 bridge로 설정해야 한다.
t2.micro 프리티어는 ENI 최대 개수가 2개인데, awsvpc 네트워크 모드를 사용하게 되면 기본 네트워크 인터페이스에 ENI 하나가 연결되어 태스크에 연결할 수 있는 ENI가 하나밖에 남지 않는다.
따라서 최초 태스크 실행 이후 추가적인 배포가 불가능하다.
awsvpc 네트워크 모드를 사용하는 각 Amazon ECS 태스크는 고유한 탄력적 네트워크 인터페이스(ENI)를 받습니다. 이 ENI는 이를 호스팅하는 Amazon EC2 인스턴스에 연결되어 있습니다. Amazon EC2 Linux 인스턴스에 연결할 수 있는 네트워크 인터페이스 수에 대한 기본 할당량이 있습니다. 기본 네트워크 인터페이스는 해당 할당량에 대해 하나로 계산됩니다. 예를 들어 기본적으로 c5.large 인스턴스에는 ENI를 3개까지만 연결할 수 있습니다. 인스턴스에 대한 기본 네트워크 인터페이스는 한 개로 계산됩니다. 인스턴스에 ENI를 2개 더 연결할 수 있습니다. awsvpc 네트워크 모드를 사용하는 각 태스크에는 ENI가 필요하므로 대개 이 인스턴스 유형에서는 이러한 태스크를 2개까지만 실행할 수 있습니다. 각 인스턴스 유형의 기본 ENI 제한에 자세한 내용은 Amazon EC2 사용 설명서의 인스턴스 유형별 네트워크 인터페이스당 IP 주소를 참조하세요.
출처 : https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/task-networking-awsvpc.html
또한 위에서 ELB의 대상 그룹 설정을 인스턴스로 설정했던 이유가 여기에 있다.
네트워크 모드가 bridge면 ELB의 대상 그룹 설정을 꼭 인스턴스로 설정해야만 한다.
설정하지 않으면 아래와 같은 오류가 발생한다.
Error: The provided target group arn:aws:elasticloadbalancing:ap-northeast-2:***:targetgroup/test-target-group/~~~ has target type ip, which is incompatible with the bridge network mode specified in the task definition.
다음으로 태스크 크기에서는 위에서 우리가 본 EC2의 서버 스펙의 반씩을 기입해 주면 된다.
(반을 초과하면 배포 시 새로운 컨테이너가 생성될 수 없다.)
따라서 CPU는 1 vCPU의 반인 0.5 vCPU로 기입하고,
메모리는 대략 952MB의 반인 0.46 GB로 기입하였다.
다음으로 컨테이너 관련 정보를 기입해 보자.
컨테이너 이름은 우리가 사용할 Application의 컨테이너 명을 임의로 기입하면 되고,
이미지 URI는 우리가 위에서 생성한 ECR 리포지토리의 경로를 입력하면 된다.
포트 매핑의 호스트 포트는 Application이 EC2 내에 여러 포트로 번갈아 매핑될 수 있도록 0으로 두어야 하고,
컨테이너 포트는 컨테이너 내에서 Application이 실행될 내부 포트를 입력하면 된다.
리소스 할당 제한의 경우 위에서 기입한 CPU와 메모리 값과 동일하게 설정하면 되고,
GPU는 사용하지 않을 것이므로 비워두면 된다.
마지막으로 환경 변수를 기입해 보자.
선택사항이지만 TZ를 Asia/Seoul로 기입하면 인스턴스가 서울 시각에 맞춰 생성되므로 여러 면에서 편리하다.
위처럼 구성하고 생성 버튼을 눌러 태스크 정의 생성을 마무리하자.
ECS 서비스 생성
이제 드디어 ECS의 서비스를 생성할 차례다.
우리가 생성한 ECS 클러스터를 선택한 후 서비스 생성 버튼을 눌러 진입해 보자.
컴퓨팅 옵션은 용량 공급자 전략을 선택해도 되고, 시작 유형의 EC2를 선택해도 무방하다.
여기에서는 용량 공급자 전략을 선택하고 진행해 보자.
애플리케이션 유형은 서비스로 선택하고,
패밀리에 위에서 정의한 태스크를 연결해 주자.
이후 아래로 내려가서 접혀있는 로드 밸런싱 탭을 열어보자.
위에서 생성한 로드 밸런서를 연결해 주자.
상태 검사 유예 기간은 컨테이너가 실행되고 Health Check를 시작하기까지의 대기 시간이다.
우리가 테스트할 Application은 금방 실행될 예정이므로 30초로 설정하고 넘어가자.
리스너도 마찬가지로 기존 리스너를 사용하고, 80 리스너를 연결해 주자.
마지막으로 대상 그룹도 기존에 생성했던 대상 그룹을 연결하면 끝이다.
이제 맨 아래 생성 버튼을 눌러 ECS 서비스를 생성하자.
GitHub Secret 설정
GitHub Actions에서 AWS 리소스에 접근할 수 있도록 권한 설정을 해줘야 한다.
권한 설정을 위해 AWS IAM > 사용자 > 사용자 생성을 통해 GitHub Actions 전용 사용자를 생성하고,
아래 3개의 권한을 추가해 주자.
- AmazonEC2ContainerRegistryFullAccess
- AmazonECS_FullAccess
- AmazonECSTaskExecutionRolePolicy
사용자를 생성하고, 보안 자격 증명 탭에서 액세스 키 만들기를 클릭하여 키를 생성하자.
여기서 만든 액세스 키와 비밀 액세스 키를 잘 기록해 두자.
이제 GitHub에서 프로젝트 리포지토리 설정을 진행해 보자.
프로젝트 리포지토리를 만들고, Settings > Secrets and variables > Actions에서 Repository secrets에 아래와 같이 키를 추가하자.
- AWS_ACCESS_KEY_ID : 액세스 키
- AWS_SECRET_ACCESS_KEY : 비밀 액세스 키
프로젝트 코드 작성
예제에서는 JDK 21 Java Spring Boot 프로젝트로 진행한다.
아래와 같은 컨트롤러 클래스를 작성해 보자.
@RestController
public class TestController {
@GetMapping("/")
public String index() {
return "Hello World!";
}
}
위에서 ELB Health Check 경로를 / 로 두었기 때문에, 해당 경로에 EndPoint가 존재해야 한다.
이후 컨테이너에 필요한 Dockerfile을 작성해 보자.
FROM amazoncorretto:21-alpine
EXPOSE 8080
COPY build/libs/test-spring-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
간단하게 빌드 결과물 jar 파일을 컨테이너에 복사 후 실행하도록 구성했다.
마지막으로 GitHub Actions에서 읽어 들일 task-definition.json 파일을 추가해야 한다.
기존에 만들었던 태스크 정의를 Json으로 다운로드하고, 프로젝트에 추가해 주자.
(예제에서는 .aws/test-task-definition.json 으로 생성하였다.)
GitHub Actions Workflow 작성
이제 GitHub Actions Workflow만 작성하면 끝이다.
GitHub 리포지토리에서 Actions > New workflow에 들어가면 "Deploy to Amazon ECS"라는 이미 만들어진 workflow 템플릿이 있다.
이를 기반으로 아래와 같이 작성해 보자.
name: Deploy to Amazon ECS
on:
push:
branches: [ "main" ]
env:
AWS_REGION: ap-northeast-2 # AWS Region
ECR_REPOSITORY: test-registry # ECR Repository 명
ECS_SERVICE: test-service # ECS 서비스 명
ECS_CLUSTER: test-cluster # ECS 클러스터 명
ECS_TASK_DEFINITION: .aws/test-task-definition.json # ECS 태스크 정의 파일 경로
CONTAINER_NAME: test-container # ECS 태스크 정의에 기입한 컨테이너 명
permissions:
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'corretto'
- name: Build with Gradle
uses: gradle/gradle-build-action@v3
with:
arguments: clean build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.ECS_TASK_DEFINITION }}
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
프로젝트 빌드를 위해 Set Up JDK 및 Build with Gradle 스텝을 추가했고,
나머지는 기존 "Deploy to Amazon ECS" 템플릿에서 env만 프로젝트에 맞춰서 설정했다.
workflow를 생성하고, main 브랜치를 푸시하면 배포가 진행될 것이다.
배포에 성공한 후 ELB DNS로 접속하면 Hello World가 찍히는 것을 확인할 수 있다.
'Setting' 카테고리의 다른 글
Mac OS에서 Docker로 Oracle DB 띄우기 (0) | 2024.07.07 |
---|---|
GitHub 잔디 잘 심기 (+ Bitbucket, GitLab 커밋 이력 연동) (1) | 2024.03.19 |
Gmail에 도메인 DKIM 설정하기 with AWS Route 53 (3) | 2023.11.15 |
Gmail에 도메인 SPF 설정하기 with AWS Route 53 (0) | 2023.11.15 |
유용한 사이트 정리 (0) | 2023.09.23 |