새소식

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 대해 알아봅니다.

 

ECR

 

/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 이미지를 저장하고 수명 주기를 관리하는 데 사용할 수 있습니다.

 

 

Service Architecture


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

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

 

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

 

 

Appliations Code

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

 

uno-service code

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}`)
})

 

due/tre-service code

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}`)
})

 

Appications Dockerfile

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 리포지토리에 도커 이미지 업로드

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 하는 명령입니다.

 

Service Terraform stack

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 구성을 지정합니다. 상태 확인 상태가 실패로 간주되기 전에 필요한 연속 실패 횟수를 지정합니다.

 

Deploy

이 자습서의 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

 

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

 

TEST

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

 

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

 

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

 

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

The End.

Contents

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

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