【단계별 이미지 포함】AWS Client VPN 서브넷 연결 비용 최소화

AWS Client VPN 서브넷 연결 비용 최소화
  • AWS Client VPN은 서브넷이 연결된 상태에서도 비용이 발생하며, 24시간 가동이 필요하지 않은 경우 불필요한 비용이 발생합니다.
  • 저희 회사는 CloudWatch 알람을 통한 자동 연결 해제와 ALB를 이용한 필요 시 연결을 통해 운영을 효율화하고 있습니다.
  • 본 기사에서는 실제 구성 예제를 통해 쉽게 도입 가능한 비용 절감 방법을 소개합니다!

배경

저희 회사는 원격 근무를 지원하기 위해 직원들에게 Amazon WorkSpaces를 제공하고, 어디서나 작업할 수 있도록 iPad를 배포하고 있습니다.
직원들은 iPad를 통해 Amazon WorkSpaces에 연결하여 원격으로 작업합니다.

하지만 iPad에서 Amazon WorkSpaces를 사용할 때 화살표 키가 작동하지 않는 문제가 발생했습니다.
이 문제가 여전히 해결되지 않았기 때문에 AWS Client VPN을 활용한 대체 구성을 통해 임시로 운영하고 있습니다.
자세한 내용은 다음 기사를 참고하시기 바랍니다.

현재 AWS Client VPN 엔드포인트에 서브넷이 항상 연결된 상태로 비용이 지속적으로 발생하고 있습니다.
iPad 이외의 기기에서는 AWS Client VPN이 필요하지 않기 때문에 필요한 경우에만 연결을 실행함으로써 비용을 절감할 수 있는 방법을 모색하게 되었습니다.

방법: 서브넷 연결 실행

AWS Client VPN이 필요한 경우 직원들이 직접 서브넷 연결을 실행하도록 결정했습니다.
하지만 관리 콘솔이나 AWS CLI 명령어를 사용하는 방식은 기술적으로 어려운 직원도 있기 때문에, 브라우저를 통해 쉽게 연결할 수 있는 시스템을 마련했습니다.

구체적으로는 직원들이 브라우저를 통해 ALB에 접속하도록 했습니다.
ALB의 대상은 Lambda 함수이며, 이 함수가 AWS Client VPN 엔드포인트의 서브넷 연결을 실행합니다.
또한, 직원들만 사용할 수 있도록 ALB 접근에 Amazon Cognito 인증을 설정했습니다.
이 방법을 통해 복잡한 절차 없이 도메인 이름만 직원들에게 제공하면 됩니다.

구성은 다음과 같습니다:

이 구성은 이미 다른 기사에서 소개되었습니다.
자세한 내용은 아래 기사를 참조하십시오:

방법: 서브넷 연결 해제

서브넷 연결 해제를 자동화하기 위해 메커니즘을 구현했습니다.
구체적으로, 방법: 서브넷 연결에서 실행되는 Lambda 함수를 CloudWatch 알람으로 모니터링합니다.
알람이 트리거된 후 일정 기간 동안 Client VPN 엔드포인트로의 클라이언트 연결이 확인되지 않으면, 또 다른 Lambda 함수가 트리거되어 서브넷 연결을 해제합니다.

구성이 복잡하지 않으며, 개념도는 다음과 같습니다:

관리자 단계

이 섹션에서는 이미 서브넷 연결용 Lambda 함수와 Client VPN 엔드포인트가 생성된 상태라고 가정합니다.

Lambda 함수 생성

Client VPN 엔드포인트의 서브넷 연결을 해제하는 Lambda 함수를 생성합니다.

  1. 다음 AWS CLI 명령어를 실행하여 Lambda 함수용 IAM 역할을 생성합니다:
# Create IAM role
aws iam create-role \
    --role-name LambdaRole-ClientVPN \
    --assume-role-policy-document '{
       "Version": "2012-10-17",
       "Statement": [
           {
               "Effect": "Allow",
               "Principal": {
                   "Service": "lambda.amazonaws.com"
               },
               "Action": "sts:AssumeRole"
           }
       ]
    }'

# Attach the IAM policy required for Client VPN
aws iam attach-role-policy \
    --role-name LambdaRole-ClientVPN \
    --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess

# Attach the IAM policy required for logging
aws iam attach-role-policy \
    --role-name LambdaRole-ClientVPN \
    --policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
  1. 다음 URL을 사용하여 Lambda 함수를 생성합니다:
    https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/function
  2. 함수 설정:
    • 함수 이름: AWSClientVPN-DisassociatingSubnets
    • 런타임: Python 3.13
    • 실행 역할: LambdaRole-ClientVPN
이미지
  1. 다음 URL을 사용하여 타임아웃을 1분으로 변경합니다:
    https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions/AWSClientVPN-DisassociatingSubnets/edit/basic-settings?tab=configure
    • 타임아웃: 1분
이미지
  1. Lambda 함수의 코드를 추가합니다.
    코드를 입력한 후 배포를 클릭하거나 Ctrl+Shift+U를 사용하여 배포해야 합니다.
    코드의 대부분은 ChatGPT를 사용하여 생성되었으며 코드는 다음과 같습니다:
import boto3
import os
import json

def lambda_handler(event, context):

    # Initialize the EC2 client
    ec2 = boto3.client('ec2')
    
    # Input parameters
    client_vpn_endpoint_id = '<Your Client VPN endpoint ID>'
    
    try:
        # Describe the Client VPN target network associations
        associations_response = ec2.describe_client_vpn_target_networks(
            ClientVpnEndpointId=client_vpn_endpoint_id
        )
        
        # Collect all Association IDs
        association_ids = [association['AssociationId'] for association in associations_response['ClientVpnTargetNetworks']]
        
        if not association_ids:
            return {
                'statusCode': 404,
                'body': json.dumps({
                    'Message': 'No associations found for the given Client VPN endpoint.',
                    'ClientVpnEndpointId': client_vpn_endpoint_id
                })
            }
        
        # Loop through and disassociate each target network
        results = []
        for association_id in association_ids:
            try:
                response = ec2.disassociate_client_vpn_target_network(
                    ClientVpnEndpointId=client_vpn_endpoint_id,
                    AssociationId=association_id
                )
                results.append({
                    'AssociationId': association_id,
                    'Status': response['Status']
                })
            except Exception as e:
                results.append({
                    'AssociationId': association_id,
                    'Error': str(e)
                })
        
        # Return response details
        return {
            'statusCode': 200,
            'body': json.dumps({
                'Message': 'Processed all associations for disassociation.',
                'Results': results
            })
        }
        
    except Exception as e:
        print(f"Error disassociating Client VPN endpoint: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps({
                'Message': 'Failed to process disassociations for Client VPN endpoint.',
                'Error': str(e)
            })
        }

CloudWatch 알람 생성

  1. 필요한 메트릭이 설정된 상태로 CloudWatch 알람을 생성할 수 있는 URL입니다. 이 URL을 사용하여 Lambda 함수를 트리거하는 CloudWatch 알람을 생성합니다:
    https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#alarmsV2:create?~(Page~'Preview~AlarmType~'MetricAlarm~AlarmData~(Metrics~(~(Id~'e1~Expression~'m1*2bm2~ReturnData~true~Label~'UsingClientVPN)~(MetricStat~(Metric~(MetricName~'Invocations~Namespace~'AWS*2fLambda~Dimensions~(~(Name~'FunctionName~Value~'AWSClientVPN-AssociatingSubnets)))~Period~300~Stat~'Average)~Id~'m1~ReturnData~false)~(MetricStat~(Metric~(MetricName~'ActiveConnectionsCount~Namespace~'AWS*2fClientVPN~Dimensions~(~(Name~'Endpoint~Value~'<Your Client VPN endpoint ID>)))~Period~300~Stat~'Average)~Id~'m2~ReturnData~false))~AlarmName~'AWSClientVPN-DisassociatingSubnets~AlarmDescription~'~ActionsEnabled~true~ComparisonOperator~'LessThanThreshold~Threshold~1~DatapointsToAlarm~6~EvaluationPeriods~6~TreatMissingData~'missing~AlarmActions~(~'arn*3aaws*3alambda*3aus-east-1*3a<Your AWS account ID>*3afunction*3aAWSClientVPN-DisassociatingSubnets)~InsufficientDataActions~(~)~OKActions~(~))~AlarmRecommendation~false~MultiTimeSeries~false)
    • URL의 플레이스홀더를 사용자의 리전 및 리소스 ID로 교체하십시오:
      • <Your Client VPN endpoint ID> → 사용자의 Client VPN 엔드포인트 ID
      • <Your AWS account ID> → 사용자의 AWS 계정 ID
    • 설명: 이 CloudWatch 알람은 서브넷 연결 Lambda 함수가 트리거된 후, 약 30분 동안 Client VPN 엔드포인트에 활성 연결이 없을 경우 서브넷 연결 해제 Lambda 함수를 트리거합니다.
    • 모니터링 메트릭: 연결 Lambda 함수의 실행 횟수와 Client VPN 끝점에 대한 연결 수의 합계
    • 알람 조건: 모니터링 메트릭이 30분 내 6개 데이터 포인트에서 1 미만이면 알람이 트리거됩니다.
    • 알람 소스: 다음 AWS CLI 명령을 통해서도 알람을 생성할 수 있습니다:
AWS CLI Command
ACCOUNT_ID="<Your AWS account  ID>"

aws cloudwatch put-metric-alarm \
    --alarm-name 'AWSClientVPN-DisassociatingSubnets' \
    --actions-enabled \
    --alarm-actions 'arn:aws:lambda:us-east-1:$ACCOUNT_ID:function:AWSClientVPN-DisassociatingSubnets' \
    --evaluation-periods 6 \
    --datapoints-to-alarm 6 \
    --threshold 1 \
    --comparison-operator 'LessThanThreshold' \
    --treat-missing-data 'missing' \
    --metrics '[{"Id":"e1","Label":"UsingClientVPN","ReturnData":true,"Expression":"m1+m2"},{"Id":"m1","ReturnData":false,"MetricStat":{"Metric":{"Namespace":"AWS/ClientVPN","MetricName":"ActiveConnectionsCount","Dimensions":[{"Name":"Endpoint","Value":"cvpn-endpoint-01234example56789"}]},"Period":300,"Stat":"Average"}},{"Id":"m2","ReturnData":false,"MetricStat":{"Metric":{"Namespace":"AWS/Lambda","MetricName":"Invocations","Dimensions":[{"Name":"FunctionName","Value":"AWSClientVPN-AssociatingSubnets"}]},"Period":300,"Stat":"Average"}}]'
이미지

Lambda 함수에 리소스 기반 정책 추가

  1. 다음 AWS CLI 명령어를 실행하여 Lambda 함수에 리소스 기반 정책을 추가하여 CloudWatch 알람에서 트리거를 허용합니다.
LAMBDA_FUNCTION_NAME="AWSClientVPN-DisassociatingSubnets"

# Get the ARN of CloudWatch alarm
CLOUDWATCH_ALARM_ARN=$(aws cloudwatch describe-alarms \
--alarm-names "AWSClientVPN-DisassociatingSubnets" \
--query "MetricAlarms[0].AlarmArn" \
--output text)

# Add permission for the CloudWatch alarm to invoke the Lambda function
aws lambda add-permission \
--function-name $LAMBDA_FUNCTION_NAME \
--statement-id "AWS-CW_Invoke-alarm" \
--action lambda:InvokeFunction \
--principal lambda.alarms.cloudwatch.amazonaws.com \
--source-arn $CLOUDWATCH_ALARM_ARN

이 설정을 통해 일정 시간 동안 연결이 없을 경우 Lambda 함수가 트리거되어 AWS Client VPN 엔드포인트에서 서브넷 연결을 자동으로 해제합니다.

방법: 서브넷 연결방법: 서브넷 연결 해제를 결합하여 서브넷 연결을 최소화함으로써 비용을 더욱 최적화할 수 있었습니다.

이 예제가 동일한 유스케이스를 가진 분들에게 유용하기를 바랍니다.

제목과 URL을 복사했습니다