새소식

AWS

AWS에서 Terraform, ECS Fargate 및 CloudMap을 사용하여 마이크로 서비스 인프라를 구축하는 방법 | part 2

  • -

2023.05.30 - [AWS] - AWS에서 Terraform, ECS Fargate 및 CloudMap을 사용하여 마이크로 서비스 인프라를 구축하는 방법 | part 1

 

AWS에서 Terraform, ECS Fargate 및 CloudMap을 사용하여 마이크로 서비스 인프라를 구축하는 방법 | part 1

소개 이 블로그 게시물에서는 Terraform, ECS Fargate 및 CloudMap을 사용하여 AWS에서 마이크로서비스 인프라를 구축하는 방법을 알아봅니다. 마이크로서비스는 애플리케이션을 잘 정의된 인터페이스를

rhrlfdud.tistory.com

 

이 게시물에서는 ECR Repository를 배포하는 방법과 서비스 코드 및 repository 대해 알아봅니다.

 

 

/stacks/ecr/main.tf

resource "aws_ecr_repository" "gyko-uno" { name = "gyko-uno" } resource "aws_ecr_repository" "gyko-due" { name = "gyko-due" } resource "aws_ecr_repository" "gyko-tre" { name = "gyko-tre" }

리소스 aws_ecr_repository 는 AWS Elastic Container Registry(ECR) 리포지토리를 생성합니다. 이 경우 fgms-uno, fgms-due 및 fgms-tre의 세개의 ECR 리포지토리가 생성됩니다.

 

각 리포지토리는 다음 형식의 코드 블록으로 정의됩니다.

resource "aws_ecr_repository" "REPOSITORY_NAME" { name = "REPOSITORY_NAME" }

name 에는 ECR 리포지토리의 이름을 지정합니다. 이는 provider 에 지정된 리전 및 계정 내에서 고유해야 합니다.

 

Terraform 코드가 적용되면 이 세 개의 ECR 리포지토리가 지정된 AWS 계정 및 리에 생성됩니다. Docker 이미지를 저장하고 수명 주기를 관리하는 데 사용할 수 있습니다.

 

 


각 애플리케이션이 수행하는 작업입니다.

— uno-service는 로드 밸런서로부터 HTTP 요청을 수신하고 Cloud Map을 내부 DNS로 사용하여 Request를 due-service 및 tre-service로 라우팅합니다.
— uno-service는 due-service 및 tre-service의 Response를 사용하여 로드 밸런서에 대한 응답을 준비합니다.

 

uno-service는 마이크로서비스에서 정보를 수집하고 외부의 요청에 응답하는 프런트엔드 앱으로 생각할 수 있습니다.

 

 

어플리케이션 코드는 정적 응답을 반환하는 기본 nodejs express 앱입니다. uno-service는 프록시 역할을 가지고 있기 때문에 due-service 및 tre-service에 대한 Axios의 HTTP 요청 코드가 포함되어 있습니다.

 

const express = require('express') const fetch = require('node-fetch'); const app = express() const port = 3000 app.get('/', async (req, res) => { const dueResponse = await fetch(`${process.env.DUE_SERVICE_API_BASE}:3000`) const treResponse = await fetch(`${process.env.TRE_SERVICE_API_BASE}:3000`) const dueData = await dueResponse.json(); const treData = await treResponse.json(); res.json({ msg: "Hello world! (from uno)", due:{ url: process.env.DUE_SERVICE_API_BASE, data: dueData, }, uno:{ url: process.env.TRE_SERVICE_API_BASE, data: treData, } }) }) app.get('/healthcheck', (req, res) => { res.send('Hello World!') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })

 

const express = require('express') const fetch = require('node-fetch'); const app = express() const port = 3000 app.get('/', (req, res) => { res.json({ msg: "Hello world! (from tre)", }) }) app.get('/healthcheck', (req, res) => { res.send('Hello World!') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })

 

FROM node:8-alpine WORKDIR /usr/app COPY package.json . RUN npm i --quiet COPY . . RUN npm install -g pm2@4.2.1 CMD ["pm2-runtime", "./index.js"]

FROM node:8-alpine 빌드 중인 Docker 이미지의 Base Image를 지정합니다. 여기에서는 이미지가 node:8-alpineAlpine Linux 배포를 기반으로 하는 Node.js 런타임 환경의 경량 버전(alpine)인 이미지를 기반으로 해야 함을 지정합니다.

 

WORKDIR /usr/app Docker 파일의 후속 명령에 대한 작업 디렉터리를 설정합니다. 작업 디렉토리를 /usr/app으로 설정했습니다.

 

COPY package.json . Dockerfile이 포함된 디렉터리에서 작업 디렉터리(/usr/app)로 package.json 파일을 복사하는 Dockerfile 지시어입니다.

RUN npm i --quietnpm i --quiet 컨테이너에서 npm i --quiet 명령을 실행하는 Dockerfile 지시어입니다. 이 명령은 package.json 파일에 지정된 모든 종속성을 설치합니다. --quiet 플래그는 npm의 진행률 출력을 표시하지 않습니다.

 

COPY . . 모든 파일을 컨텍스트(Docker 파일이 들어 있는 디렉토리)에서 작업 디렉토리(/usr/app)로 복사합니다.

 

RUN npm install -g pm2@4.2.1 컨테이너에 전체적으로 pm2 패키지(버전 4.2.1)를 설치하는 도커 파일 지시어입니다. pm2는 Node.js 응용 프로그램의 프로세스 관리자입니다.

 

CMD ["pm2-runtime", "./index.js"] 컨테이너가 시작될 때 실행할 기본 명령을 지정하는 도커 파일 지시문입니다. 이 경우 pm2-runtime 명령을 ./index.js 인수로 실행해야 함을 지정합니다. 그러면 pm2를 사용하여 ./index.js의 Node.js Application이 시작됩니다.

 

ecr-deploy-script.sh 파일에는 ECR에 컨테이너 이미지를 업로드하기 위한 AWS-CLI와 bash 명령어가 포함되어 있습니다.

모든 앱(uno, due, tre)에 대해 다음 명령을 실행해야 합니다.

aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin <YOUR AWS ACCOUNT ID>.dkr.ecr.ap-northeast-2.amazonaws.com docker build -t uno ./uno # docker buildx build --platform=linux/amd64 -t uno ./uno !!apple m1 칩을 사용하는 경우 위 명령 대신 이 명령을 사용하세요!! docker tag uno <YOUR AWS ACCOUNT ID>.dkr.ecr.ap-northeast-2.amazonaws.com/gyko-uno:v1 docker push <YOUR AWS ACCOUNT ID>.dkr.ecr.ap-northeast-2.amazonaws.com/gyko-uno:v1

aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin <YOUR AWS ACCOUNT ID>.dkr.ecr.ap-northeast-2.amazonaws.com

AWS Identity and Access Management(IAM)에서 인증 토큰을 검색한 다음 이를 사용하여 ap-northeast-2 리전의 Amazon Elastic Container Registry(ECR)에 로그인하는 명령입니다. | 기호는 aws 명령의 결과를 docker login 명령으로 전달합니다.

docker build -t uno ./uno./uno 디렉토리의 Dockerfile 에서 Docker 이미지를 빌드하고 이미지에 uno 라는 태그를 지정하는 명령입니다.

 

docker push .dkr.ecr.ap-northeast-2.amazonaws.com/gyko-uno:v1</your aws account id> Docker 이미지를 지정된 Amazon ECR Repository 로 push 하는 명령입니다.

 

stacks/services 폴더 안에 Infra를 생성하는데 필요한 모든 AWS 서비스 리소스가 있습니다.

resource "aws_ecs_task_definition" "gyko_uno_td" { family = "gyko_uno_td" network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] cpu = "256" memory = "512" execution_role_arn = aws_iam_role.gyko_uno_task_role.arn container_definitions = jsonencode( [ { cpu : 256, image : "248581660709.dkr.ecr.ap-northeast-1.amazonaws.com/gyko-uno:v1", memory : 512, name : "gyko-uno", networkMode : "awsvpc", environment : [ { name : "DUE_SERVICE_API_BASE", value : "http://${data.terraform_remote_state.services-due.outputs.gyko_due_service_namespace}.${data.terraform_remote_state.dns.outputs.gyko_private_dns_namespace}" }, { name : "TRE_SERVICE_API_BASE", value : "http://${data.terraform_remote_state.services-tre.outputs.gyko_tre_service_namespace}.${data.terraform_remote_state.dns.outputs.gyko_private_dns_namespace}" } ], portMappings : [ { containerPort : 3000, hostPort : 3000 } ], logConfiguration : { logDriver : "awslogs", options : { awslogs-group : "/ecs/gyko_log_group", awslogs-region : "ap-northeast-1", awslogs-stream-prefix : "uno" } } } ] ) } resource "aws_ecs_service" "gyko_uno_td_service" { name = "gyko_uno_td_service" cluster = data.terraform_remote_state.ecs_cluster.outputs.gyko_ecs_cluster_id task_definition = aws_ecs_task_definition.gyko_uno_td.arn desired_count = "1" launch_type = "FARGATE" network_configuration { security_groups = ["${aws_security_group.ecs_tasks_sg.id}"] subnets = ["${data.terraform_remote_state.vpc.outputs.gyko_private_subnets_ids[0]}"] } load_balancer { target_group_arn = aws_alb_target_group.gyko_uno_tg.id container_name = "gyko-uno" container_port = 3000 } service_registries { registry_arn = aws_service_discovery_service.gyko_uno_service.arn } } resource "aws_security_group" "ecs_tasks_sg" { name = "ecs_tasks_sg" description = "allow inbound access from the ALB only" vpc_id = data.terraform_remote_state.vpc.outputs.gyko_vpc_id ingress { protocol = "tcp" from_port = "3000" to_port = "3000" security_groups = ["${data.terraform_remote_state.alb.outputs.gyko_alb_sg_id}"] } ingress { protocol = -1 from_port = 0 to_port = 0 self = true } egress { protocol = "-1" from_port = 0 to_port = 0 cidr_blocks = ["0.0.0.0/0"] } } resource "aws_alb_target_group" "gyko_uno_tg" { name = "gyko-uno-tg" port = 80 protocol = "HTTP" vpc_id = data.terraform_remote_state.vpc.outputs.gyko_vpc_id target_type = "ip" health_check { path = "/healthcheck" } } resource "aws_alb_listener" "gyko_uno_tg_listener" { load_balancer_arn = data.terraform_remote_state.alb.outputs.gyko_alb_id port = "80" protocol = "HTTP" default_action { target_group_arn = aws_alb_target_group.gyko_uno_tg.id type = "forward" } }

 

리소스 aws_ecs_task_definitionECS 작업 정의(Task Definition)를 생성합니다. 이 정의는 ECS 작업의 Blueprint입니다. 작업 정의는 작업에 사용할 도커 이미지, 사용할 CPU 및 메모리 장치 수 및 작업이 시작될 때 실행할 명령을 지정합니다. 또한 작업 정의는 태스크가 사용해야 하는 IAM 역할과 태스크가 시작될 때 컨테이너로 전달되어야 하는 환경 변수를 지정합니다.

 

리소스 aws_ecs_serviceECS 서비스를 생성합니다. ECS 서비스는 일반적으로 웹 애플리케이션을 호스팅하는 데 사용되는 장시간 실행 작업입니다. 이 서비스는 코드 블록에 정의된 ECS 클러스터 및 작업 정의와 연결되며 실행할 작업 수와 시작 유형(Fargate)을 지정합니다. 서비스는 네트워크 구성 및 로드 밸런서 설정도 지정합니다. 서비스는 다른 서비스에서 검색할 수 있도록 서비스 검색 서비스에도 등록됩니다.

 

리소스 aws_security_group은 ECS 작업으로 들어오고 나가는 인바운드 및 아웃바운드 트래픽을 제어하는 가상 방화벽인 Amazon Elastic Compute Cloud(Amazon EC2) Security Group을 생성합니다. Security Group은 지정된 Security Group의 포트 3000에 대한 인바운드 트래픽만 허용하고 모든 아웃바운드 트래픽을 허용합니다.

 

리소스 aws_alb_target_group은 트래픽을 ECS 작업으로 라우팅하는 데 사용되는 Amazon Elastic Load Balancer(ALB) 대상 그룹을 생성합니다. 대상 그룹은 지정된 보안 그룹 및 포트와 연결됩니다.

 

리소스 aws_alb_listener_rule은 대상 그룹으로 트래픽을 라우팅하는 방법을 지정하는 ALB 수신기 규칙을 생성합니다. 리스너 규칙은 지정된 호스트 헤더 및 경로 패턴을 기반으로 지정된 ALB 리스너에서 대상 그룹으로 트래픽을 라우팅합니다.

 

 

클라우드 맵과 관련하여 DNS 스택을 종속성으로 지정한 리소스는 다음과 같습니다

resource "aws_service_discovery_service" "gyko_uno_service" { name = var.gyko_uno_service_namespace dns_config { namespace_id = data.terraform_remote_state.dns.outputs.gyko_dns_discovery_id dns_records { ttl = 10 type = "A" } routing_policy = "MULTIVALUE" } health_check_custom_config { failure_threshold = 2 } }

aws_service_discovery_service리소스는 Amazon Route 53 서비스 검색 서비스를 생성하여 서비스가 DNS를 통해 서로를 검색할 수 있도록 합니다. 서비스에 name 매개 변수를 사용하여 지정되고 변수에서 파생된 이름이 지정됩니다.

 

dns_config블록은 서비스 검색 서비스에 대한 DNS 구성을 지정합니다. 서비스가 사용해야 하는 Amazon Route 53 네임스페이스의 ID와 서비스에 대한 DNS 레코드 및 라우팅 정책을 지정합니다. DNS 레코드는 TTL(Time To Live) 값과 서비스 유형을 지정합니다.

 

health_check_custom_config블록서비스 검색 서비스에 대한 health check 구성을 지정합니다. 상태 확인 상태가 실패로 간주되기 전에 필요한 연속 실패 횟수를 지정합니다.

 

이 자습서의 part one 에서 설명한 Terraform 명령을 사용하여 모든 서비스(ECR에 도커 이미지를 업로드한 후)를 배포해 보겠습니다 .

$ cd stacks/dns $ terraform init $ terraform plan $ terraform apply

서비스 배포 순서는 다음과 같습니다.

  1. due
  2. tre
  3. uno

uno는 due 및 tre의 클라우드 맵 DNS 레코드 값이 필요하므로 마지막에 배포되어야 합니다.

$ cd stacks/services/due $ terraform init $ terraform plan $ terraform apply $ cd stacks/services/tre $ terraform init $ terraform plan $ terraform apply $ cd stacks/services/uno $ terraform init $ terraform plan $ terraform apply

 

다음 이미지는 프라이빗 클라우드 맵 호스팅 영역 내의 모든 레코드를 나타냅니다.

 

모든 구성이 완료되었습니다. Application 을 테스트 하겠습니다.

 

Terraform 으로 생성한 ALB 콘솔 화면으로 이동합니다.

 

콘솔화면의 DNS name 을 복사하여  브라우저에서 해당 URL로 이동하면 다음과 같은 응답을 받아야 합니다.

 

이와 같은 출력이 표시되면 이 자습서를 성공적으로 완료하고, 그렇지 않으면 이 페이지에서 누락된 내용이 있는지 확인합니다.

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.