애플리케이션 로드 밸런서(ALB)에 고정 IP 주소 설정 및 사용하기
2016년에 Elastic Load Balancing는 애플리케이션 로드 밸런서(Application Load Balancer, ALB)를 출시하였고, HTTP 통신의 7계층의 많은 기능들을 지원하게 되었습니다. 클라이언트는 DNS 주소를 해석하여 ALB를 이용하여 문제없이 연결할 수 있습니다. 하지만, ALB의 IP 주소목록은 추가되거나 변경될 수 있기 때문에 클라이언트가 인터넷 상에서 항상 ALB의 동일한 모든 IP주소로 연결할 수 있지 않고, 이 때문에 오래된 디바이스를 쓰고 있거나 보안에 엄격한 네트워크 관리자에겐 ALB가 까다롭게 여겨질 수 있습니다. 고정 IP는 이런 문제를 해결해 줄 수 있으며 클라이언트가 임시 해결적으로서 현재 IP 주소들을 업데이트하거나 방화벽에 넣어주는 스크립트를 실행할 필요가 없어집니다.
2017년에 4계층 TCP 로드 밸런서인 네트워크 로드 밸런서(Network Load Balancer, NLB)가 런칭되었습니다. NLB는 각 가용영역에 고정 IP주소들을 두고 쓸 수 있습니다. 이러한 고정 IP주소들은 변하지 않기 때문에 방화벽의 화이트리스트에 추가될 수 있습니다. 하지만 NLB는 TCP 통신만 허용하기 때문에 HTTPS를 처리하지 않으며, ALB의 7계층 관련 기능들이 없습니다.
이제까지는 NLB 또는 ALB의 이점 중에 하나만을 선택해야 했으며 두 가지의 장점 모두를 가질 수는 없었습니다. 이 블로그는NLB를 ALB 앞에 둠으로서 두 가지 장점을 모두 가지는 방법을 소개합니다.
사전 요구 사항
먼저 다음 사전 요구 사항이 준비되어있는지 확인합니다.
- 내부 또는 외부(인터넷) NLB를 준비합니다. 클라이언트가 VPC 안에 있다면 내부 NLB를 준비하고 그 외의 경우는 외부 NLB를 준비합니다.
- 내부 ALB를 준비합니다. 여기에서 HTTPS 처리 또는 멋진 라우팅처리를 할 수 있습니다. 여기에서 서버가 트래픽을 받아서 처리하게 됩니다.
- 내부 ALB와 NLB는 같은 가용영역에 있어야 합니다.
- IP 주소 기반 대상그룹을 NLB에 만듭니다. 대상그룹의 프로토콜은 TCP입니다. AWS Lamdba 함수가 이 대상그룹에 ALB의 주소를 등록시킵니다.
- ALB의 IP주소 등의 정보가 담길 Amazon S3 버킷을 준비합니다.
- AWS Lambda가 필요한 리소스들을 만들 수 있는 IAM 정책을 가지고 있는 IAM 역할을 준비합니다. (부록 A 참고)
구성 방법
NLB에 트래픽을 받아서 내부 ALB로 넘기는 TCP리스너를 만듭니다. ALB는 TLS 처리를 하고 HTTP 헤더를 검사한 후 룰에 기반하여 요청을 인스턴스, 서버, 컨테이너들로 구성된 대상그룹에 라우팅합니다. AWS Lambda 함수는 ALB의 IP 주소들이 바뀌는 것을 감시하고 있다가 NLB의 대상그룹에 업데이트하여 동기화 합니다. 마지막으로 몇 개의 고정 IP로 쉽게 방화벽에 화이트리스팅 함으로서 ALB의 장점을 모두 활용합니다. 모든 트래픽은 두 개의 로드밸런서를 통과한다는 것을 잊지 마세요.
주의 : 각 로드 밸런서를 통과하는 데이터 처리에 따른 비용이 발생합니다. 두 로드 밸런스의 시간당 과금 및 Lambda 함수, S3, Amazon Cloudwatch 의 비용도 이 솔루션에서 발생함을 알아두세요.
아키텍처
Lambda 함수가 하는 작업 개요
단계별 Lambda 함수 작업
- ALB에서 쓰이는 IP 주소들을 DNS 쿼리로 알아온 뒤 S3 버킷에 결과(NEW IP LIST)를 업로드
- describe-target-health API 액션으로 현재 NLB에 등록된 IP 주소들의 리스트(REGISTERED LIST)를 가져옴.
- 이전의 IP 주소 리스트(OLD LIST)를 다운로드함. 만약에 이번이 첫 Lambda 함수의 실행이라면 IP 주소 리스트는 비어있음.
- NEW LIST를 Lambda 함수의 CloudWatch Logs 로그스트림에 출력함. 이는 ALB에서 쓰이던 IP 주소들을 찾는데 쓰일 수 있음.
- 첫 실행에서 만들어졌던 내부 ALB IP주소들의 갯수를 추적하는 CloudWatch 지표를 업데이트 함. 이 지표는 얼마나 많은 IP주소들이 그 동안 변경되었는지 보여줌. CW_METRIC_FLAG_IP_COUNT를 ‘false’로 설정하여 비활성화 시킬 수 있음. 여기에 ALB의 IP주소가 20개에서 24개, 그 다음에는 28개로 변하는 것을 보여주는 CloudWatch 지표의 예제가 있음.
- OLD LIST나 REGISTERED LIST에 있는 것을 제외한 NEW LIST에 있는 IP 주소들을 NLB에 등록함.
- OLD LIST 중 NEW LIST에 없는 IP주소들은 등록해지함.
시작하기
두가지 방법으로 이 솔루션을 셋업해봅니다. 첫번째는 AWS 관리 콘솔을 이용하는 것이고 두번째는 AWS CloudFormation을 이용하는 것입니다. CloudFormation 템플릿은 여기에서 사용가능하고 Lambda 함수의 ZIP 패키지는 여기에서 다운 받을 수 있습니다.
AWS 관리 콘솔로 설정하기
STEP 1: IAM 정책 만들기
IAM 콘솔에서 Lamdba 함수가 필요로하는 권한들로 IAM 정책을 만듭니다. 부록 A에서 샘플 IAM 정책을 확인할 수 있습니다. 더 알고 싶으시면 IAM 정책 생성 문서를 참고하세요. AWS Lambda를 위한 IAM 역할을 만드는 것에 대해서 더 알고 싶으면 AWS 서비스에 대한 역할 생성(콘솔) 문서를 참고하세요.
STEP 2: IAM 역할 만들기
IAM 정책이 준비되었다면 IAM 역할을 만들고 STEP 1에서 만든 IAM 정책을 할당합니다.
STEP 3: Lambda 함수 만들기
IAM 역할을 만들었으므로 이제 AWS Lambda 함수 콘솔에서 함수를 만듭니다. 함수를 만들 때 STEP 2에서 만든 IAM 역할을 지정하고 실행환경은 Python2.7로 선택합니다.
STEP 4: Lambda 함수 설정
핸들러 이름을 populate_NLB_TG_with_ALB.lambda_handle로 변경하여 AWS Lambda가 해당 함수코드를 활용할 수 있도록 합니다. 그리고 나서 “업로드” 버튼을 누르고 다운로드 받은 Lambda 함수 ZIP 파일을 업로드합니다.
STEP 5: Lambda 환경변수 설정
Lambda 콘솔에서 함수 코드를 확인하고, 다음 환경 변수 값들을 설정하여 네트워크 로드 밸런서의 대상그룹에 ALB의 IP 주소들을 등록할 수 있도록 합니다.
- ALB_DNS_NAME : ALB의 전체 DNS이름 (FQDN)
- ALB_LISTENER : ALB의 리스너의 통신 포트
- S3_BUCKET : Lambda 함수의 실행 간에 변경을 추적하기 위한 버킷
- NLB_TG_ARN : NLB의 대상그룹의 ARN
- MAX_LOOKUP_PER_INVOCATION : 실행 시 DNS 조회 최대 횟수. 기본 값은 CloudFormation 템플릿에서 50으로 설정
- INVOCATIONS_BEFORE_DEREGISTRATION : IP 주소가 등록해지 되기 위해 필요한 실행 횟수. 기본 값은 CloudFormation 템플릿에서3회
- CW_METRIC_FLAG_IP_COUNT : IP주소 카운트를 위한 CloudWatch 지표 컨트롤 플래그. 기본 값은CloudFormation 템플릿에서 ‘true’
로드밸런서에 대한 한 번의 DNS 조회로 최대 8개의 IP 주소를 가져올 수 있습니다. 그러므로 ALB가 8개 이상의 IP 주소를 가지고 있다면 여러 번의 DNS 쿼리를 실행하여 모든 IP 주소를 가져와야합니다. 이를 위해서 MAX_LOOKUP_PER_INVOCATION 과 INVOCATIONS_BEFORE_DEREGISTRATION의 값을 설정합니다.
MAX_LOOKUP_PER_INVOCATION은 첫 DNS 응답에서 8개 이상의 IP 주소가 있다면 몇 번이나 DNS 조회를 Lambda 함수가 수행할지를 결정합니다. 기본 값은 50입니다. 더 높은 값일 수록 모든 IP 주소를 얻을 수 있습니다. 테스트 결과 20~40번의 쿼리로 모든 IP주소를 알아낼 수 있었습니다. 결과에서 빠진 IP 주소가 있다면 이 값을 조절하길 바랍니다.
INVOCATIONS_BEFORE_DEREGISTRATION은 등록해지를 하기 전에 DNS 결과에서 IP 주소를 발견하지 못한 횟수를 설정합니다. 정상적인 동작이라면 ALB의 IP 주소는 DNS에서 삭제가 되더라도 짧은 기간동안은 ALB에서 사용할 수 있습니다. 놓친 IP 주소가 있더라도 NLB의 헬스체크에서 유효하지 않은 ALB의IP 주소를 탐지하므로 즉각적인 등록해지가 문제가 되지 않습니다. 기본 값은 3으로 설정되어있고 ALB의 IP 주소가 DNS 결과에서 보이지 않기 시작한 3분 뒤에 등록해지되게 됩니다.
테스트 결과, Lambda함수가 드물게 1분 이상 실행되기도 합니다. 그러므로 실행되기 충분한 시간인 5분을 타임아웃으로 설정합니다. Lambda함수의 설정에 대한 더 많은 정보는 Lambda 함수 구성 문서를 참고 바랍니다.
STEP 6: CloudWatch Event 생성
Lambda함수를 만든 뒤, 다음은 CloudWatch 콘솔을 열고 CloudWatch Event를 만들어서 방금 만든 Lambda함수를 트리거하게 합니다.
STEP 7: CloudWatch Event 설정
CloudWatch Event 콘솔에서 분당 1번의 주기로 작업이 실행되도록 합니다. 아까 만든 Lambda함수를 이벤트의 대상으로 하도록 합니다.
설정이 완료되면 저장합니다.
AWS CloudFormation을 사용하여 설정하기
ALB의 IP주소를 NLB의 대상그룹으로 등록/해제하기 위한 도구를 설치하기 위한 CloudFormation 템플릿을 만들어 두었습니다. 템플릿이 생성하는 AWS 리소스는 다음과 같습니다.
- Lambda함수
- Lambda함수에서 쓰이는 IAM 정책과 역할
- CloudWatch Event
CloudFormation 콘솔에서 다음과 같은 스택 시작 버튼으로 US 동부(N.Virginia) 리전에 설치합니다.
검증하기
이제 이 솔루션이 제대로 동작하는지 확인해보겠습니다.
- 여러분의 사이트가 NLB의 DNS 이름이나 리스닝 포트의 IP 주소로 접속이 되는지 확인하겠습니다. 예를 들어서 NLB가 80포트를 리스닝하고 있다면 다음 커맨드로 사이트 페이지가 정상적으로 열리는 지 확인할 수 있습니다.
curl http://yourNLB-DNS.elb.us-east-1.amazonaws.com
- Lambda함수가 IP 주소들를 NLB의 대상그룹으로 잘 등록시키는지 확인하고, IP 주소들이 정상 상태임을 확인합니다.
- 정상상태임을 확인하고 CloudWatch 지표가 동일한 수의 IP 주소의 갯수를 나타내는지 봅니다. CloudWatch 매트릭을 비활성화 시켰다면 이 부분은 넘어갑니다.
- IP 주소들이 CloudWatch 로그에 기록되었음을 확인합니다.
- 모든 것이 예상한 대로 동작함을 확인했다면, Amazon Route 53에서 여러분의 DNS 이름을 가중치 기반의 CNAME DNS 레코드 세트로 NLB의 DNS이름으로 생성하고 트래픽을 점진적으로 기존의 스택에서 새로 만들어진 NLB-ALB 스택으로 옮기기 시작합니다. 이 소스 스택은 마이그레이션 동안에 정상 작동하며 기존의 운영하던 스택으로 언제든지 롤백할 수 있습니다. Route53의 가중치 기반 DNS 레코드에 대한 더 자세한 설명은 가중치 기반 라우팅 문서를 참고하세요.
주의
- cron 표현식의 가장 작은 단위는 분 단위이며 이는 CloudFormation 템플릿에서 제공하는 기본 구성입니다. 언제든 상황에 맞게 변경가능합니다.
- CloudFormation 템플릿으로 만들어진 AWS 서비스의 리소스의 비용은 다음을 포함합니다.
- Lambda함수
- Amazon S3 저장 용량 및 액세스
- CloudWatch 지표
- CloudWatch log
- IP 주소를 프록시의 대상으로 사용했기 때문에 클라이언트의 실제 IP 주소를 X-Forwarded-For 헤더에서 볼 수 없습니다. 만약 클라이언트의 실제 IP 주소를 알고자하는 경우엔 Amazon VPC Flow Logs에서 NLB가 보낸 트래픽을 보거나 클라이언트 측의 방법을 써야합니다.
- 네트워크 로드 밸런서는 기본적으로 350초의 유휴 연결 제한 시간을 가집니다. 그렇기 때문에 ALB의 유휴 타임아웃을 350초 이하로 설정해야합니다.
- 반드시 이 솔루션을 테스트 후에 실제 운영환경에 적용하세요
부록 A – IAM 정책
{
"Version":"2012-10-17",
"Statement":[
{
"Action":[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource":[
"arn:aws:logs:*:*:*"
],
"Effect":"Allow",
"Sid":"LambdaLogging"
},
{
"Action":[
"s3:Get*",
"s3:PutObject",
"s3:CreateBucket",
"s3:ListBucket",
"s3:ListAllMyBuckets"
],
"Resource":"*",
"Effect":"Allow",
"Sid":"S3"
},
{
"Action":[
"elasticloadbalancing:Describe*",
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:DeregisterTargets"
],
"Resource":"*",
"Effect":"Allow",
"Sid":"ELB"
},
{
"Action":[
"cloudwatch:putMetricData"
],
"Resource":"*",
"Effect":"Allow",
"Sid":"CW"
}
]
}
이 글은 AWS Network & Content Delievery 블로그의 Using static IP addresses for Application Load Balancers의 한국어 번역본으로 김준형 AWS 솔루션즈아키텍트께서 수고해 주셨습니다.
Leave a Reply