serverless proxy 서버 구축
기존에 운영 중인 환경에서 proxy 서버를 구축했던 경험을 서술했다
도입배경
일단 원래 인프라 구조는 이러했다
온프레미스쪽 서버에 MSTR이란 제품으로 간단한 데이터 분석을 제공하고 있었는데
(mstr : www.microstrategy.com/ko)
여러가지 이유로 이 MSTR에서 사용할 DB로 redshift를 사용했다
당연히, 그냥 Public 망으로 IP뚫어서 쓰면 트래픽에 대한 어마어마한 비용을 내야하므로 전용선을 써야했고
다행히 다른팀에서 관리 중인 Direct Connect가 구축된게 있어서 이걸 물려서 사용했다
그러니 개념상 위와 같지만 실제로 AWS 콘솔에서 적용된 모습은 다음과 같았다
왜냐면 직접 Direct Connect를 물려주지 않고 방화벽과 연결된 네트워크로 연결을 해줬기 때문이다
이게 Direct Connect의 가장 큰 문제가 AWS로 계정 상관없이 막 업로드가 가능해서
개인이 계정을 생성해서 IDC에 있는 정보를 막 올려버릴 수 있는 문제가 있고
AWS에 있는 리소스는 보안그룹이나 NetworkACL 등을 통해서 IDC쪽 접근을 막을 수 있는데
Direct Connect로 연결된 계정에서 IDC로 접근하는건 막을 수가 없다...
암튼 그래서 잘?쓰고 있었다
그런데 문제는 뭐냐,
기존에 구축했던 Private VPC에 할당된 IP가 매우 적었는데
이유는 Private VPC와 연결된 Virtual Private Gateway는 라우팅을 위해서 서로 IP가 충돌되면 안되고
온프레미스로 구축되어있던 IDC의 경우, 기본적으로 모두 Public IP를 사용했기 때문에
사실상 AWS에서는 Private VPC의 IP는 사설IP이겠지만,
IDC입장에서는 Private VPC도 실제 IP를 할당한다는 개념으로 봐야하기 때문에
Private VPC에 할당할 수 있는 IP의 수가 매우 적었다
즉, Private VPC에 IP를 늘릴 수 없는 상태이고 이미 운영 중인 시스템이 돌고 있다
그런데 이제 이 MSTR이란 제품을 활용해서 새로운 프로젝트 도입이 필요했는데
MSTR의 API를 사용할 필요가 있었다
그냥 단순히 생각하면 저 Private VPC에서 서버 띄워서 운영하면 되지만
위에서 언급되었듯 IP가 부족하고 이 API 접근 아니면 굳이 저 Private VPC에 접근할 필요가 없었다
해결방안1
그래서 처음엔 아래처럼 Public Subnet을 따로 만들어서 NAT를 통해서 MSTR API에 접근하려고 생각했다
문제는 뭐냐... 바로 Private VPC이다
보안상 redshift가 외부에 노출되면 안되므로 해당 VPC에 인터넷 게이트웨이를 쓸 수 없었다
그럼 만약에 Public Subnet에 있는 EC2에 문제가 생겨서 직접 ssh로 접근해야되는 상황이 오면 어떻게 해야되냐...
IDC쪽에 별도로 Gateway용 서버를 만들어서 Private subnet에 있는 Bastion을 거쳐서 새로운 프로젝트 EC2에 접근해야되는 거지같은 구조로 접속을 해야된다
물론 AWS에서 session manager라는 기능으로 인터넷이 안되는 서버도 웹에서 접근할 수 있게 제공은 해준다
(session manager : aws.amazon.com/ko/blogs/korea/new-session-manager/)
하지만 장애라는게 SSM agent가 죽는 상황도 언제나 발생할 수 있기 때문에
이게 안되는 상황이라면 결국 위의 방식으로 접근해야된다
그리고 더 중요한건, 네트워크단에서 개발/스테이징/운영을 분리할 수가 없다는 것이다
사람은 분명 실수하기 때문에 네트워크단에서 아예 분리를 시켜줘야 안심할 수 있는데
하나의 VPC로 관리하기에는 위험부담이 컸고
VPC 내에서 계속 subnet을 추가하면서 관리하기에는 보안그룹이나 라우팅 테이블 등을 관리하기 너무 귀찮았다
(테라폼으로 관리하는것도 테라폼을 테스트해볼 환경이 결국 같은 VPC라는거 아닌가! 결국 실수할 수 있다)
결국, 접근성과 환경분리 이 2개의 문제는 타협할 수 없고 해결할 수 없는 문제였다
그래서 일단 개발환경 분리를 위해서 개발/운영 VPC를 2개 추가했다
해결방안2
어차피 개발환경만 완성되면 운영은 복붙하면 되니 개발환경을 먼저 고민해봤는데
먼저 VPC Transit Gateway에서 Direct Connect 를 지원하기에 이걸 고민했다
하지만 뭐가 문제냐...
위에서 말했듯 내 계정은 Direct Connect로 연결이 안되어있다
Virtual Pirvate Gateway로 연결되어 있기 때문에 위의 방법을 쓸 순 없었다
해결방안3
생각해보니깐 어차피 VPC끼리 연결이고 개발/운영VPC만 추가되면 VPC가 추가될일은 없으니깐
좀 귀찮아도 그냥 VPC Peering 쓰면 되는거 아닌가?라는 생각이 들어서 아래처럼 구축을 해봤다
(해보고 되면 VPC Transit Gateway로 운영하면 되니깐 일단 구축이 쉬운 Peering으로 테스트)
오~ 개꿀이네하고 EC2를 띄워서 MSTR API에 접근이 되는지 테스트 해봤다
안된다 ^^ㅣ발
아니 당연히 되야하는거 아닌가?
내가 라우팅테이블을 잘못했나도 보고 이것저것해봐도 개발용 VPC에서 MSTR API에 접근이 되지 않는다
느낌이 뭔가 또 Limit 같은게 있을거 같아서 문서를 보니 이런게 있었다
지원되지 않는 VPC Peering 구성 : docs.aws.amazon.com/ko_kr/vpc/latest/peering/invalid-peering-configurations.html#transitive-peering
하하하...
그렇다 네트워크 전이(Network Transitive)를 철저하게 지원하지 않는다
사례를 보니 나같은 놈들이 당연히 있을까봐 일부러 막아둔 듯하다
생각해보니 이렇게 관리되어야 네트워크 Flow 파악에 좀 더 유리할거 같긴한데
막상 필요한 상황이 되니 하... 아쉽네
해결방안4
여기서 생각해볼 수 있는건 MSTR API에 직접 접근은 안되지만 개발용 VPC와 Private VPC는 서로 통신이 가능하다는 점이다
그래서 VPC Peering된 상태에서 아래와 같이 Private VPC에서 nginx로 Proxy 서버를 운영하는게 Best인데
당연히 nginx proxy 서버를 운영하는건 EC2를 별도로 띄운다는 것이고
Auto scaling을 건다고 해도 기본적으로 HA구성을 해야되기 때문에 최소 2대가 필요하다
proxy서버는 네트워크 트래픽말고는 다른 리소스가 거의 발생하지 않으므로
2 vCPU, 1GiB짜리인 t3.micro로 1년 운영한다고 했을때 최소 $234이 발생한다
(시간당 $0.013 * 2대 * 750시간 * 12달 = $234)
솔직히 이 신규프로젝트에서 얼마나 저 MSTR을 호출할지도 모르고
아무리 많이 호출해봤자 1초에 1건이 안나올게 뻔한 상황이라
내 돈은 아니지만 이 돈이 너무 아까웠다
해결방안5
사실 VPC Peering도 거슬리고 nginx proxy 서버용 ami 관리하는것도 귀찮을거 같았다
어차피 쓸 API 몇개 안되서 뭘 쓸지 뻔하고 하루에 100번도 호출 안될텐데
굳이 저렇게 어렵게 가야되나라는 생각이 들었다
그래서 혹시나 싶어서 AWS에서 제공하는 Proxy 관련 서비스가 있나 찾아봤는데 없었다...
그런데 생각해보니 트래픽이 거의 없는 서비스는 serverless 가 유리한데
그냥 직접 API Gateway + Lambda로 API를 대신 호출해주는걸 만들면 되지않을까?라는 생각이 들었고
아래와 같이 생각을 고쳐먹었다
VPC Peering은 Private Endpoint 타입의 API Gateway로 대체하고
docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/apigateway-private-apis.html
nginx proxy 서버는 그냥 Private subnet에 Lambda를 할당했다
Lambda는 그냥 MSTR API를 호출하도록 requests 를 써서 아래처럼 짰다
import requests
import json
URL = 'MSTRURL'
def lambda_handler(event, context):
print('event = ', json.dumps(event))
url = URL + event.get('path')
method = event.get('httpMethod')
headers = event.get('headers')
query_string_parameters = event.get('queryStringParameters')
body = event.get('body')
res = requests.request(method, headers=headers,
url=url, params=query_string_parameters, data=body)
print('response body = ', res.text)
print('response headers = ', res.headers)
return {
'statusCode': res.status_code,
'body': res.text,
'headers': dict(res.headers)
}
혹시 API호출하다가 장애날 수도 있으니 로그를 위해서 event와 reponse 값을 찍었다
근데 MSTR API의 경우, 로그인 토큰값을 header에 넣어서 주기 때문에
Lambda에서 굳이 return할때 headers도 넣어줘야했다
테스트해본 결과, 매우 만족스럽게 잘동작했고
신규프로젝트는 저 private endpoint로 호출하면 되서 매우 간단해졌다
그리고 Lambda에서 오류가 나면 cloud watch에서 SNS + Slack으로 알림을 보내도록 설정해서
장애에 대한 대응도 유연하게 할 수 있게 되었다
최종
최종적인 구조는 위와 같이 구성했고 현재 프로젝트는 진행 중에 있다
그리고 내가 만든 저 API Gateway 는 내부적으로 serverless proxy 서버로 부르고 있다
결론적으로
API Gateway와 Lambda는 알아서 auto scaling되고 cloud watch를 통해서 로그나 장애알림을 확인할 수 있고
개발/운영 VPC를 분리가능하며
기존에 redshift를 운영하고 있는 Private VPC를 건들지 않고 IP부족에 대한 이슈도 해결할 수 있었다
뭐 물론 Lambda를 통하기 때문에 일반 nginx proxy랑은 다르게 느리기야 하겠지만
진행할 프로젝트는 API의 응답속도가 ms단위로 중요한 서비스가 아니기 때문에 가능했다
만들어놓고보면 뿌듯하긴 한데
이 전용선이랑 통신할일이 생기면 진짜 너무 골치아픈게 많은듯하다
API통신이야 이렇게 해결했는데 나중에 mysql이나 git 서버에 연결해야되면....
그건 나중에 고민해보는걸로... ㅋㅋ