본문 바로가기!

Terraform

AWS 트래픽 스파이크 처리 - Amazon Aurora와 Route 53 가중치 기반 라우팅 활용

728x90
반응형

이번 글에서는 Amazon Aurora Auto Scaling, Amazon Route 53 가중치 기반 라우팅, Amazon CloudWatch, Lambda, EventBridge 등을 활용해 비용 효율적으로 트래픽을 처리하는 방법을 AWS에서 제공하는 Well-Architected Framework를 AWS CloudFormation으로 제공하고 있고, 해당 내용을 Terraform 코드화로 전환하여 사용한 방법에 대해 공유드리려 합니다.

 

 

AWS 트래픽 스파이크 처리 

급격한 트래픽 증가를 효과적으로 처리하는 것은 클라우드 아키텍처 설계에서 중요한 도전 과제 중 하나입니다.

특히, 예기치 않은 트래픽 스파이크가 발생할 때, 리소스가 자동으로 확장하고 안정성을 유지하는 것이 핵심입니다.

 

아래 URL은 AWS에서 제공하는 Well-Architected Framework를 통해 트래픽 스파이크(급격한 트래픽 증가) 상황을 효과적으로 처리하는 방법에 관한 가이드입니다.

 

참고: https://aws.amazon.com/ko/solutions/guidance/handling-data-during-traffic-spikes-on-aws/

 

Guidance for Handling Data during Traffic Spikes on AWS

 

aws.amazon.com

 

참고: https://github.com/aws-solutions-library-samples/guidance-for-handling-data-during-traffic-spikes-on-aws

 

GitHub - aws-solutions-library-samples/guidance-for-handling-data-during-traffic-spikes-on-aws: This Guidance shows how to handl

This Guidance shows how to handle sudden traffic spikes in Amazon Aurora using a mixed-configuration architecture that combines a provisioned Aurora cluster with Aurora Serverless v2 instances and ...

github.com

 

 

 

아키텍쳐 구성 & Step별 설명

https://aws.amazon.com/ko/solutions/guidance/handling-data-during-traffic-spikes-on-aws/

 

  • Step 1
    • Aurora Provisioned 클러스터에 Aurora Serverless v2 리플리카를 추가하고, 각각의 리플리카에 대해 커스텀 엔드포인트를 설정합니다.
  • Step 2
    • Amazon Route 53에 프라이빗 호스티드 존을 구성하고, 각 엔드포인트에 대해 가중치 기반 레코드를 생성합니다. Serverless v2를 가리키는 레코드의 가중치를 초기에는 0으로 설정합니다.
  • Step 3 
    • 모든 인스턴스에서 Enhanced Monitoring을 활성화하여 RDSOSMetrics 정보를 CloudWatch에 자동으로 수집합니다.
  • Step 4 
    • RDSOSMetrics 로그 그룹의 데이터를 기반으로 AWS Lambda Function를 생성하여 대상 인스턴스의 모니터링을 위한 맞춤형 메트릭을 생성하고 CloudWatch에 푸시합니다.
  • Step 5 
    • 수집된 맞춤형 메트릭을 기반으로 CloudWatch 알람을 설정하여 트래픽 분산을 위한 가중치 조정의 알람을 구성합니다.
  • Step 6 
    • 가중치 조정 알람이 발생하면 AWS Step Functions가 호출되어 Route 53 레코드의 가중치를 조정하여 트래픽을 Aurora Serverless v2로 분산합니다.
  • Step 7
    • AWS Auto Scaling을 활용해, 맞춤형 메트릭 기반의 확장 정책을 설정하여 프로비저닝된 리플리카 인스턴스의 오토 스케일링을 활성화합니다.

 

 

GitHub 가이드에서 제공되는 CloudFormation 템플릿에서의 Custom Resource 확인

AWS CloudFormation에서 Custom Resource란?
 - 표준 템플릿 언어로 지원되지 않는 작업을 수행하도록 Lambda Function나 외부 스크립트를 호출하는 기능을 제공합니다.

 

 

GitHub 가이드 코드를 보시면 CloudFormation 템플릿에는 두 개의 Custom Resource가 사용됩니다.

두 개의 Custom Resource는  Lambda Function를 호출하게 되며 동작되는 Lambda Function 내용은 다음과 같습니다.

  # Invoke Custom Resource - Lambda-Layer
  StartBuild:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt StartBuildFunction.Arn
      ProjectName: !Ref CodeBuildProject
    DependsOn:
      - AmccS3Bucket
      - CodeBuildProject   
    DeletionPolicy: Retain
  # Invoke Custom Resource
  LambdaEnvironmentFunctionResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaEnvironmentFunction.Arn
    DependsOn:
      - AuroraWriterInstance
      - AuroraReaderInstance
      - AuroraServerlessV2Instance
      - PrivateHostedZone
      - RDSOSMetricCollectorFunction

 

 

 

두 개의 Custom Resource를 보면 AWS::CloudFormation::CustomResource 타입으로, ServiceToken 속성에 StartBuildFunction과 LambdaEnvironmentFunction 이라는 각각의 Lambda Function의 ARN을 통해 Lambda를 호출 하는 것을 확인 할 수 있습니다.

 

호출되는 Lambda Function은 다음과 같은 동작이 실행되게 됩니다.

 

  • 1) CodeBuild 프로젝트를 트리거하여 AWS Embedded Metrics Python 라이브러리를 포함한 레이어를 빌드하고, S3 버킷에 업로드
  # Lambda Function to invoke Codebuild
  StartBuildFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: DeployLambdaLayer
      Runtime: python3.12
      Handler: index.lambda_handler
      Role: !GetAtt AmccLambdaLayerRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          build = boto3.client('codebuild')

          def lambda_handler(event, context):
            if event['RequestType'] == 'Create':
              try:
                resp = build.start_build(projectName=event['ResourceProperties']['ProjectName'])
                build_id = resp['build']['id']

                cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                  'BuildId': build_id
                })
              except Exception as e:
                cfnresponse.send(event, context, cfnresponse.FAILED, str(e))
            else:
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})

 

  • 2) Aurora 클러스터의 자동 확장과 읽기 전용 엔드포인트 설정 
    • CPU 사용률에 따라 자동으로 추가하거나 제거할 수 있는 스케일링 정책을 설정
    • Aurora 엔드포인트 생성 (provisioned-ro, serverless-ro라는 두 개의 엔드포인트를 Aurora 클러스터에서 설정)
    • Route 53 레코드 생성 (provisioned-ro 엔드포인트는 가중치 10으로 설정되고, serverless-ro 엔드포인트는 가중치 0으로 설정)
  ## Create Lambda Function for Create Custom Endpoints And Weight-based Record in Route 53
  LambdaEnvironmentFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: LambdaEnvironmentFunction
      Runtime: python3.12
      Handler: index.lambda_handler
      Role: !GetAtt AmccLambdaEnvironmentRole.Arn
      Timeout: 60
      Code:
        ZipFile: |
          import boto3
          import os
          import cfnresponse
          import json

          rds = boto3.client('rds')
          route53 = boto3.client('route53')
          application_autosclaing = boto3.client('application-autoscaling')
          logs = boto3.client('logs')

          CLUSTER_NAME = os.environ['CLUSTER_NAME']
          SERVERLESS_INSTANCE_NAME = os.environ['SERVERLESS_INSTANCE_NAME']
          HOSTED_ZONE_ID = os.environ['HOSTED_ZONE_ID']
          TARGET_VALUE = float(os.environ['TARGET_VALUE'])
          METRIC_NAMESPACE = os.environ['METRIC_NAMESPACE']

          def lambda_handler(event, context):
            if event['RequestType'] == 'Create':
              # Custom AutoSclaing
              # Register Scalable Target
              application_autosclaing.register_scalable_target(
                  ServiceNamespace='rds',
                  ScalableDimension='rds:cluster:ReadReplicaCount',
                  ResourceId=f'cluster:{CLUSTER_NAME}',
                  MinCapacity=1,
                  MaxCapacity=15
              )
              
              # Get Metric Configuration
              custom_metric_data = '''
              {
                  "TargetValue": %s,
                  "CustomizedMetricSpecification": {
                      "MetricName": "CPUUtilization",
                      "Namespace": "%s",
                      "Dimensions": [
                          {
                              "Name": "InstanceType",
                              "Value": "provisioned"
                          },
                          {
                              "Name": "ClusterName",
                              "Value": "%s"
                          }
                      ],
                      "Statistic": "Average",
                      "Unit": "Percent"
                  },
                  "ScaleOutCooldown": 300,
                  "ScaleInCooldown": 300,
                  "DisableScaleIn": false
              }    
              ''' % (TARGET_VALUE, METRIC_NAMESPACE, CLUSTER_NAME)
              
              # Put Scaling Policy
              application_autosclaing.put_scaling_policy(
                  PolicyName='aurora_custom_scaling_policy',
                  ServiceNamespace='rds',
                  ResourceId=f'cluster:{CLUSTER_NAME}',
                  ScalableDimension='rds:cluster:ReadReplicaCount',
                  PolicyType='TargetTrackingScaling',
                  TargetTrackingScalingPolicyConfiguration=json.loads(custom_metric_data)
              )

              ## Aurora Custom Endpoint
              provisioned_ro_endpoint = rds.create_db_cluster_endpoint(
                  DBClusterEndpointIdentifier='provisioned-ro',
                  DBClusterIdentifier=CLUSTER_NAME,
                  EndpointType='READER',
                  ExcludedMembers=[SERVERLESS_INSTANCE_NAME]
              )['Endpoint']

              serverless_ro_endpoint = rds.create_db_cluster_endpoint(
                  DBClusterEndpointIdentifier='serverless-ro',
                  DBClusterIdentifier=CLUSTER_NAME,
                  EndpointType='READER',
                  StaticMembers=[SERVERLESS_INSTANCE_NAME]
              )['Endpoint']

              ## Route53 Weight Based Record
              # Create Provisioned Read-Only weight-based Record
              provisioned_ro_record = {
                  'Changes': [
                      {
                          'Action': 'CREATE',
                          'ResourceRecordSet': {
                              'Name': 'read.amcc-rds.com',
                              'Type': 'CNAME',
                              'SetIdentifier': 'PROVISIONED_RO_RECORD_IDENTIFIER',
                              'Weight': 10,
                              'TTL': 0,
                              'ResourceRecords': [
                                  {
                                      'Value': provisioned_ro_endpoint
                                  }
                              ]
                          }
                      }
                  ]
              }
              route53.change_resource_record_sets(
                  HostedZoneId=HOSTED_ZONE_ID,
                  ChangeBatch=provisioned_ro_record
              )

              serverless_ro_record = {
                  'Changes': [
                      {
                          'Action': 'CREATE',
                          'ResourceRecordSet': {
                              'Name': 'read.amcc-rds.com',
                              'Type': 'CNAME',
                              'SetIdentifier': 'SERVERLESS_RO_RECORD_IDENTIFIER',
                              'Weight': 0,
                              'TTL': 0,
                              'ResourceRecords': [
                                  {
                                      'Value': serverless_ro_endpoint
                                  }
                              ]
                          }
                      }
                  ]
              }
              route53.change_resource_record_sets(
                  HostedZoneId=HOSTED_ZONE_ID,
                  ChangeBatch=serverless_ro_record
              )

              cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                  'ProvisionedReadEndpoint': provisioned_ro_endpoint,
                  'ServerlessReadEndpoint': serverless_ro_endpoint
              })
            else:
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})

      Environment:
        Variables:
          CLUSTER_NAME: !Ref ClusterName
          SERVERLESS_INSTANCE_NAME: !Ref AuroraServerlessV2Instance
          HOSTED_ZONE_ID: !Ref PrivateHostedZone
          TARGET_VALUE: 40
          METRIC_NAMESPACE: !Ref MetricNamespace
    DependsOn:
      - AuroraWriterInstance
      - AuroraReaderInstance
      - AuroraServerlessV2Instance
      - PrivateHostedZone
      - RDSOSMetricCollectorFunction

 

 

 

 

CloudFormation (Custom Resource) --> Terraform (aws_lambda_invocation)

먼저 Custom Resource를 Terraform의 어떤 Resource Block으로 대체했는지는 바로?!!!

 

😎😎😎  aws_lambda_invocation라는 Resource Block입니다.

 

Terraform에서의 aws_lambda_invocation Resource Block은 Lambda Function을 실행할 수 있는 Terraform 구성 요소입니다.

 

aws_lambda_invocation을 사용하게 되면 Lambda 호출과 관련된 설정 및 작업을 명확하게 관리할 수 있으며, 기존 CloudFormation의 워크플로우와 같이 유기적으로 Terraform에서도 하나의 워크플로우로 기능을 구현하기 위해 사용하였습니다.

 

먼저 테스트한 Terraform Code는 다음과 같이 작성하였습니다.

resource "aws_lambda_invocation" "invoke_codebuild" {
  function_name = aws_lambda_function.start_build_function.function_name

  input = jsonencode({
    RequestType = "Create",
    ResourceProperties = {
      ProjectName = "${aws_codebuild_project.codebuild_project.name}"
    }
  })

  depends_on = [aws_s3_bucket.amcc_s3_bucket, aws_codebuild_project.codebuild_project]
}

resource "null_resource" "trigger_codebuild" {
  triggers = {
    lambda_result = aws_lambda_invocation.invoke_codebuild.result
  }

  provisioner "local-exec" {
    command = "echo Lambda executed with result: ${aws_lambda_invocation.invoke_codebuild.result}"
  }
}

 

resource "aws_lambda_invocation" "invoke_environment" {
  function_name = aws_lambda_function.lambda_environment_function.function_name

  input = jsonencode({
    RequestType = "Create"
  })
  depends_on = [
    aws_rds_cluster_instance.aurora_reader_instance,
    aws_rds_cluster_instance.aurora_writer_instance,
    aws_rds_cluster_instance.aurora_serverless_v2_instance,
    aws_route53_zone.private_hosted_zone,
    aws_lambda_function.rds_os_metric_collector_function,
    aws_iam_role_policy_attachment.attach_lambda_environment_policy
  ]
}

resource "null_resource" "trigger_environment" {
  triggers = {
    lambda_result = aws_lambda_invocation.invoke_environment.result
  }

  provisioner "local-exec" {
    command = "echo invoke_environment Lambda executed with result: ${aws_lambda_invocation.invoke_environment.result}"
  }
}

 

 

위와 같이 aws_lambda_invocation Resource Block으로 사용하면 Lambda Function 호출의 반환값( 함수의 return값)을 통해 의존성 관리를 더욱 효과적으로 자동화할 수 있습니다.

 

 

aws_lambda_invocation Resource Block을 사용한 이유에서 Lambda Function 호출의 반환값을 가져와서 의존성 관리를 할 수 있다가 무슨 의미인지??

 

 

좀더 구체적인 설명을 드리자면 해당 CloudFormation 가이드에서는 Lambda Funtion(LambdaEnvironmentFunction)이 동작하면 RDS의 Custom Endpoint가 생성되며, 여기서 생성된 Custom Endpoint는 다른 Funtion에서 참조하여 Route53에 해당 Endpoint로 등록된 레코드에 대해 가중치를 조정하는 부분이 있습니다.

 

LambdaEnvironmentFunction 라는 Lambda Function을 호출하여 실행하는 것이 CloudFormation에서는 Custom Resource이며, 아래 코드블럭의 LambdaEnvironmentFunctionResource 부분입니다.

  ## Invoke Custom Resource
  LambdaEnvironmentFunctionResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaEnvironmentFunction.Arn
    DependsOn:
      - AuroraWriterInstance
      - AuroraReaderInstance
      - AuroraServerlessV2Instance
      - PrivateHostedZone
      - RDSOSMetricCollectorFunction
      
  ...
  ...
  ...

  IncreaseRoute53WeightsFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: IncreaseRoute53Weights
      Handler: index.lambda_handler
      Runtime: python3.12
      ...
      ...
      ...
      Environment:
        Variables:
          REGION_NAME: !Ref AWS::Region
          RECORD_NAME: !Ref RecordName
          ZONE_NAME: !Ref HostedZoneName
          SERVERLESS_IDENTIFIER: 'SERVERLESS_RO_RECORD_IDENTIFIER'
          SERVERLESS_RECORD: !GetAtt LambdaEnvironmentFunctionResource.ServerlessReadEndpoint
          HOSTED_ZONE_ID: !Ref PrivateHostedZone
          MAX_WEIGHT: !Ref MaxWeight
          MIN_WEIGHT: !Ref MinWeight
          STEP_SIZE: !Ref StepSize
          DNS_TTL: !Ref DNSTtl

위 코드에서 Lambda Function의 Environment을 보시면

!GetAtt LambdaEnvironmentFunctionResource.ServerlessReadEndpoint 이라는 부분이 바로 Custom Resource가 실행된 후 LambdaEnvironmentFunctionResource라는 리소스에서 ServerlessReadEndpoint 라는 속성 값을 가져오는 것입니다.

 

ServerlessReadEndpoint라는 속성 값은 그럼 어디서 가져오느냐? Lambda Function에서 확인할 수 있습니다.

Lambda Funtion는 다음과 같이 Python으로 코드가 짜여있습니다.

      Code:
        ZipFile: |
          import boto3
          import os
          import cfnresponse
          import json

          rds = boto3.client('rds')
          route53 = boto3.client('route53')
          application_autosclaing = boto3.client('application-autoscaling')
          logs = boto3.client('logs')

          CLUSTER_NAME = os.environ['CLUSTER_NAME']
          SERVERLESS_INSTANCE_NAME = os.environ['SERVERLESS_INSTANCE_NAME']
          HOSTED_ZONE_ID = os.environ['HOSTED_ZONE_ID']
          TARGET_VALUE = float(os.environ['TARGET_VALUE'])
          METRIC_NAMESPACE = os.environ['METRIC_NAMESPACE']

          def lambda_handler(event, context):
            if event['RequestType'] == 'Create':
              # Custom AutoSclaing
              # Register Scalable Target
              application_autosclaing.register_scalable_target(
                  ServiceNamespace='rds',
                  ScalableDimension='rds:cluster:ReadReplicaCount',
                  ResourceId=f'cluster:{CLUSTER_NAME}',
                  MinCapacity=1,
                  MaxCapacity=15
              )
              
          ...
          ...
          ...

              cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                  'ProvisionedReadEndpoint': provisioned_ro_endpoint,
                  'ServerlessReadEndpoint': serverless_ro_endpoint
              })
            else:
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})

위 코드에서 cfnresponse.send( {...}) 구문은 AWS Lambda 함수 내에서 CloudFormation Custom Resource의 응답을 전송하는 역할을 합니다.

 

Custom Resource에서 AWS Lambda가 작업을 완료하고 상태를 반환하게 되는데, Lambda 함수가 성공적으로 실행되었는지 여부를 CloudFormation에 알리고, 추가로 필요한 데이터를 전달하는 것입니다.

 

 

 

 

aws_lambda_invocation Resource Block을 사용하면 위와 같이 Custom Resource의 동작과 같이 Python으로 작성된 Lambda Function을 호출하게 되면, 해당 Function이 성공적으로 실행되고 반환값(Return값)을 제공합니다.

 

이 반환값(Return값)은 다른 Terraform 리소스에서 참조할 수 있어 의존성이 강화되고, Terraform 내에서 리소스 간 상호작용이 보다 유기적으로 이루어집니다.

 

작성하여 사용한 Terraform Code를 간단히 보여드리면 다음과 같습니다.

 

resource "aws_lambda_invocation" "invoke_environment" {
  function_name = aws_lambda_function.lambda_environment_function.function_name

  input = jsonencode({
    RequestType = "Create"
  })
  depends_on = [
    aws_rds_cluster_instance.aurora_reader_instance,
    aws_rds_cluster_instance.aurora_writer_instance,
    aws_rds_cluster_instance.aurora_serverless_v2_instance,
    aws_route53_zone.private_hosted_zone,
    aws_lambda_function.rds_os_metric_collector_function,
    aws_iam_role_policy_attachment.attach_lambda_environment_policy
  ]
}
  • 먼저 aws_lambda_invocation Resource Block을 사용하여 Python으로 작성된 Lambda Function을 호출합니다.

 

locals {
  # Lambda 결과를 JSON으로 디코딩하여 리스트로 변환
  endpoints = jsondecode(aws_lambda_invocation.invoke_environment.result)

  # "serverless-ro"로 시작하는 엔드포인트만 선택
  custom_serverless_endpoint = element([for endpoint in local.endpoints : endpoint if can(regex("serverless-ro", endpoint))], 0)
}
  • Function 호출과 함께 동작한 Function의 반환값(Return값)을 local 변수로 저장합니다.

 

resource "aws_lambda_function" "increase_route53_weights_function" {
  function_name = "IncreaseRoute53Weights"
  role          = aws_iam_role.amcc_lambda_route53_role.arn
  handler       = "lambda_function_increase_route53_weights.lambda_handler"
  runtime       = "python3.12"

  environment {
    variables = {
      REGION_NAME           = data.aws_region.current.name
      RECORD_NAME           = var.RecordName
      ZONE_NAME             = var.HostedZoneName
      SERVERLESS_IDENTIFIER = "SERVERLESS_RO_RECORD_IDENTIFIER"
      SERVERLESS_RECORD     = local.custom_serverless_endpoint
      HOSTED_ZONE_ID        = aws_route53_zone.private_hosted_zone.zone_id
      MAX_WEIGHT            = var.MaxWeight
      MIN_WEIGHT            = var.MinWeight
      STEP_SIZE             = var.StepSize
      DNS_TTL               = var.DNSTtl
    }
  }

  filename         = "lambda_function_increase_route53_weights.zip"
  source_code_hash = filebase64sha256("lambda_function_increase_route53_weights.zip")

  depends_on = [
    aws_rds_cluster_instance.aurora_writer_instance,
    aws_rds_cluster_instance.aurora_reader_instance,
    aws_rds_cluster_instance.aurora_serverless_v2_instance,
    aws_route53_zone.private_hosted_zone,
    aws_lambda_function.lambda_environment_function
  ]
}

 

  • local 변수로 저장한 custom_serverless_endpoint를 Lambda Function의 환경변수로 지정하여 Route53 가중치 조절 Function을 정의합니다.

 

이와 같이 aws_lambda_invocation를 활용해 Terraform 내에서 Lambda 함수를 직접 호출하고, 해당 함수의 반환값을 다른 리소스에 참조하여 사용할 수 있습니다. 이는 Terraform에서 리소스 간 의존성을 명확히 정의하고, 다른 Resource Block과도 유기적으로 설계할 수 있습니다!!!

 

물론 해당 글에서는 작성하지 않았지만 Lambda Function의 내용에서 cfnresponse 부분은 Terraform에서 사용할 수 없기에 일반적으로 사용하는 Return 값을 통해 해당 ServerlessReadEndpoint 를 반환하도록 변경하였습니다!

 

 

트래픽 스파이크 시뮬레이션을 위한 Sysbench 스크립트 실행

AWS 환경에서 RDS에 대한 트래픽 급증 상황을 시뮬레이션하기 위해 Sysbench를 사용하여 MySQL 읽기 부하를 지속적으로 발생시켰습니다.

 

10개의 스레드로 작업을 동시에 실행하며 초당 1000개의 요청을 보내도록 스크립트를 아래와 같이 사용하여 부하테스트를 진행할 수 있습니다.

nohup sysbench \
    --db-driver=mysql \
    --mysql-host='read.amcc-rds.com' \
    --mysql-port=3306 \
    --mysql-user='{RDS USERNAME}' \
    --mysql-password='{RDS PASSWORD}' \
    --mysql-db='sysbench' \
    --threads=10 \
    --time=0 \
    --rate=1000 \
    --report-interval=10 \
    /usr/share/sysbench/oltp_read_only.lua run \
    > nohup.out 2>&1 &

 

 

트래픽 증가 및 RDS 인스턴스 생성 확인

Sysbench를 통해 트래픽 스파이크 시뮬레이션을 실행한 후, AWS Aurora 콘솔에서 CPU 사용률과 추가 생성된 리더 인스턴스를 통해 자동 확장이 정상적으로 작동하는지 확인합니다.

 

CPU 사용률이 특정 임계값 이상으로 올라가면, 자동으로 새로운 리더 인스턴스가 추가되며, 콘솔에서 각 인스턴스의 CPU 사용률과 상태를 확인이 가능합니다.

 

 

 

트래픽 증가에 따라 가중치 값이 조정되어 요청이 분산되는지 Route 53 레코드를 통해 확인할 수 있습니다.

 

 

트래픽 부하가 줄어들면 CloudWatch 경보가 다시 임계값 이하로 떨어지는 것을 감지하고, EventBridge와 Lambda가 Route 53의 가중치 설정을 조정하여 Aurora 리더 인스턴스를 순차적으로 삭제합니다. 이를 통해 자동 확장이 트래픽 상태에 따라 유연하게 조정되는지 확인할 수 있습니다. 

 

 

 

 

정리

 

처음에는 Terraform에서 Custom Resource를 Null Resource로 대체하려 했습니다. Null Resource는 인프라 리소스를 생성하지 않지만 특정 명령을 실행할 수 있어 간단한 작업에는 유용했습니다. 그러나 Lambda 호출의 성공 여부 확인, 함수에서 생성된 RDS Custom Endpoint 값을 가져오는 작업 등의 복잡한 로직과 의존성을 관리하기에는 한계가 있었습니다.

 

결국, aws_lambda_invocation 리소스로 전환했습니다. 이 리소스를 통해 Lambda 함수를 Terraform 내에서 직접 호출할 수 있었고, 리소스 간의 의존성을 명확하게 정의할 수 있었습니다. 이를 통해 워크플로우의 일관성을 유지하면서도 Lambda 호출 결과를 활용한 구성 관리가 가능해집니다.

 

결론적으로, CloudFormation의 Custom Resources로 수행하던 복잡한 작업을 Terraform에서 aws_lambda_invocation을 통해 자연스럽게 대체할 수 있었으며, 이를 통해 인프라의 유연성과 안정성을 더욱 높일 수 있습니다.

 

728x90
반응형