Skip to main content

자동으로 나만의 VPN 서버를 배포하자

outline_main

오늘 소개해드릴 프로젝트에 앞서 먼저 알아할 서비스가 하나 있습니다. 바로 Outline 인데요. 이 서비스는 구글 지주회사 알파벳의 집행위원장인 Eric Schmidt가 설립한 '직쏘(JIGSAW)'라는 이름의 자회사가 만든 프로젝트입니다. Outline 공식 홈페이지에서 이 프로젝트를 "Outline을 사용하면 누구나 무료 개방형 인터넷에 액세스할 수 있는 VPN 서버를 쉽게 만들 수 있습니다."라고 소개하고 있습니다.

Outline은 클라우드 또는 자신이 가지고 있는 베어메탈 서버를 VPN 서버로 사용할 수 있도록 직접 도커에 기반한 shadowsocks를 설치하고 Outline Client를 다운로드 받아 VPN 서버에서 발급한 액세스 키를 등록하기만 하면 곧바로 내 VPN 서버로 프록시하여 인터넷을 사용할 수 있습니다. 이 프록시를 통해 나의 공인 아이피를 감출 수 있고 중국과 같이 인터넷 사용에 자유가 없는 국가에 체류 또는 거주하고 있는 외국인들이 해외 서비스를 사용하는 목적으로 사용할 수도 있습니다.

하지만 VPN 서버를 구축하는 과정은 클라우드를 사용하더라도 여간 번거로운 것이 아닙니다. 콘솔에 접속해서 인스턴스 스펙, 이미지 등과 같이 인스턴스 생성에 필요한 옵션을 선택하고 키페어를 생성 후 다운로드 받아놓고 인스턴스 생성 후에 키페어를 통해 생성한 인스턴스에 접속 후 VPN 서버 설정을 진행해야 합니다. 보안을 생각한다면 아이피 테이블 또는 보안그룹에 UDP 또는 TCP 프로토콜에 소스 아이피로 나의 공인 아이피만 허용해야 하는 작업도 동반됩니다. 만약 인스턴스를 생성할 네트워크 기반 설정이 없다면 VPC와 서브넷 같은 네트워크 컨테이너를 생성해야 하기도 합니다.

만약 아웃라인 VPN 서버 선택시 고려해야 하거나 변경해야 하는 옵션 대상만 선택한 뒤 위 구성을 자동으로 진행해준다면 어떨까요?

그래서 만든 것이 오늘 제가 소개할 outline-vpn 프로젝트입니다. 이 프로젝트는 Golang으로 작성하였으며 앤서블 없이 테라폼만을 활용하여 스크립트를 실행하여 자동화 구성을 하게 됩니다.

즉 인스턴스 라이프사이클로 볼 때 인스턴스가 생성되면서 실행되는 사용자 스크립트가 있고 보안그룹 규칙 추가를 위해 인스턴스가 생성된 후에 실행되는 사용자 스크립트가 있습니다. 두 스크립트를 인스턴스 라이프사이클에 맞춰서 동작하도록 만들 때 테라폼에서 발생할 수 있는 문제인 순환 참조 또는 세션 충돌과 같은 문제가 발생하기도 했습니다.

세션 충돌은 -lock=false 옵션을 통해 여러 세션이 하나의 리소스 프로비저닝에 참여할 수 있도록 간편하게 해결됐지만 순환 참조의 경우 terraform graph 명령어를 통해 리소스 생성 순서와 각 리소스간 참조를 확인하여 해결하게 되었고 이제야 프로젝트가 안정되어 이렇게 문서화 작업을 통해 인터넷 사용자들에게 제가 만든 프로젝트를 소개하게 되었습니다.

작년 5월쯤 테라폼을 처음 접한 후 이 프로젝트를 완성한 것은 작년 8월이었지만 짧은 시간이니만큼 테라폼 이해도가 미성숙하여 서비스 완성도가 떨어지게 되었지만 이번에 사내 테라폼 도입 발표 준비와 NHN Cloud에 기반한 테라폼 데모를 작성하다보니 이해도가 성숙하게 되었고 해결하지 못했던 문제들을 해결함으로써 미완성되었던 Outline-vpn 프로젝트도 높은 성숙도로 완성된 것 같습니다.

Outline-vpn 프로젝트는 유연성있고 빠르게 인스턴스 프로비저닝이 가능한 AWS 클라우드의 강점과 테라폼의 일관성과 확장성을 합하여 사용자에게 빠르게 VPN 서버를 제공하기 위한 목적으로 만든 프로젝트입니다.

사용자에게는 인스턴스가 생성될 AWS 리전EC2 이미지, 인스턴스 타입, 가용성 영역과 같은 네 가지 옵션만을 선택받도록 하고 나머지 VPC 생성에는 기본 VPC 생성을 통해 빠르게 생성하도록 하였습니다. 기본 VPC 생성을 통해 서브넷, 라우팅 테이블과 같은 리소스를 생성해야 하는 부담을 덜었습니다. 따라서 인스턴스를 생성하기 전에 사용자가 위 네가지 옵션을 선택하였다면 선택한 리전을 기준으로 기본 VPC가 생성되어 있는지 확인하여 생성되어 있지 않다면 생성할 수 있도록 사용자에게 의사를 물어보게 됩니다. 사용자별로 모든 AWS 리전이 활성화되어 있지는 않아서 만약 활성화되어 있지 않은 리전을 사용자가 선택하게 되면 콘솔에서 리전을 활성화하라는 에러 문구를 보여주게 됩니다.

(글을 쓰다보니 생각나게 된 기능이 하나 있는데 사용가능한 모든 리전을 사용자에게 리스트로 보여줄 것이 아니라 활성화된 리전 리스트를 사용자에게 리스트로 보여주게 된다면 활성화되지 않은 리전을 선택함으로써 발생하게 되는 에러처리를 덜 수가 있겠네요. AWS의 API와 CLI 옵션이 있는지 확인해보고 신규 기능으로 추가해야겠어요.)

위와 관련된 리전, 이미지, 인스턴스 타입, 가용성 영역과 관련된 설정에는 AWS에서 제공하는 SDK를 활용하였으며 나머지 기능은 테라폼 기능을 사용하였습니다. 테라폼을 선택하기를 잘했다고 생각했던 기능들이 몇 가지가 있는데 그중 한가지는 사용자가 리전을 선택하게 되면 테라폼은 리전별로 워크스페이스를 생성하게 되는데 해당 워크스페이스를 삭제하게 되면 terraform.tfstate.d 아래 워크스페이스 폴더도 동일하게 삭제되면서 리소스 삭제시 남게되는 테라폼 파일(메타데이터) 삭제를 하지 않아도 되었습니다. 또한 리전별로 워크스페이스를 생성하게 되면서 하나의 VPN 서버만 생성하는 것이 아닌 여러 리전에 VPN 서버를 여러 개 생성하여 사용할 수 있도록 하였습니다.

만약 리전별 구분을 넘어서 동일한 리전안에 가용성 영역을 구분으로 VPN 서버를 생성해야 한다면 기능 수정을 통해 워크스페이스명을 리전이름으로 하는 것이 아니라 가용성 영역으로 변경하면 됩니다.

이 프로젝트를 진행하면서 위에서 말씀드렸다시피 오랫동안 해결되지 않았던 문제는 AWS 인스턴스 타입 선택시에 리전 또는 가용성 영역별로 선택할 수 있는 인스턴스 타입이 각각 달랐습니다. 이 기준이 어떠한 기준으로 선택되어야 하는지 몰랐는데 결국엔 콘솔에 들어가서 해결방법을 찾게 되었습니다.

AWS CLI 옵션 하나하나 선택하여 나오는 이미지 리스트 결과와 콘솔에서 인스턴스 생성 화면에서 선택 가능한 이미지 리스트를 비교해가면서 노가다했던 기억이 새록 새록 나네요. 필터 옵션을 찾았을 때 희열은 아직도 잊을 수가 없습니다. 이 맛에 프로그램을 개발하고 문제 해결에 많은 시간을 쏟는 것 같아요.

제가 찾은 AWS CLI 필터 옵션은 아래와 같습니다.

	aws ec2 describe-images \
--region us-east-1 \
--owners amazon \
--filters "Name=state,Values=available" "Name=architecture,Values=x86_64" "Name=root-device-type,Values=ebs" \
--query 'Images[*].[ImageId]'

이 필터를 기반으로 작성한 AWS SDK를 활용한 Golang 코드는 아래와 같습니다.

output, err := client.DescribeImages(ctx,
&ec2.DescribeImagesInput{
Owners: []string{"amazon"},
Filters: []ec2_types.Filter{
{Name: aws.String("state"), Values: []string{"available"}},
{Name: aws.String("architecture"), Values: []string{"x86_64"}},
{Name: aws.String("root-device-type"), Values: []string{"ebs"}},
{Name: aws.String("is-public"), Values: []string{"true"}},
},
},
)

동일한 필터 옵션으로 들어가 있는 것을 확인할 수 있습니다. 이를 통해 사용자는 선택한 리전과 가용성 영역별로 선택할 수 있는 인스턴스 타입을 리스트로 제공받음으로써 선택할 수 있는 이미지 타입을 찾는 노력을 덜게 됩니다. 만약 해당 로케이션에서 제공하지 않는 인스턴스 타입을 선택하게 되면 프로비저닝 도중 에러가 발생하여 정상적으로 모든 리소스가 생성되지 않습니다.

위 네가지 옵션(인스턴스 타입, 이미지, 리전, 가용성 영역)은 테라폼 변수 항목으로 들어가게 되며 이 프로젝트의 동작에 대해서 쉽게 설명드리자면 테라폼 모듈을 뼈대로 가지고 생성할 수 있는 인스턴스를 리전 단위로 구분하게 되며 나머지 변수 입력이나 인스턴스 프로비저닝 후에 보안그룹 TF 파일 생성등에는 스크립트 또는 Golang 코드가 동작하게 됩니다.

기존에는 테라폼 모듈을 로컬로 관리하였으나 테라폼 레지스트리에 등록하여 테라폼 스크립트와 모듈을 다른 저장소에서 가져와 사용하게 되었습니다. 레지스트리 사용 전에는 Golang 코드로 따로 모듈 파일을 저장하고 있는 깃헙 저장소를 풀하는 과정이 필요하였지만 모듈을 레지스트리로 등록하게 되면서 이 과정을 생략하게 되었습니다.

프로젝트 소개에는 보통 이 프로젝트를 어떻게 사용해야 하며 어떻게 사용해야 잘 사용하는 것인지를 작성하게 되지만 오늘은 그렇지 못했네요. 오랜기간 동안 이 프로젝트를 어떻게 더 개선시킬지 고민하게 되었고 고민했던 문제를 해결하게 되면서 테라폼에 대해 많은 이해도를 갖게 되어 결과보다 과정을 피력하고 싶었나봅니다.

각설하고 다음은 CLI 명령어 옵션별로 동작과 영상들을 소개드리겠습니다.

Apply

AWS 인스턴스 생성 (VPC, 서브넷, 키페어)

하단 명령어를 실행하게 되면 가장 먼저 짧은 시간동안 사용자의 공인 아이피를 체크하게 되는데 아이클라우드의 비공개 릴레이 서비스를 사용하게 될 경우 애플의 자체 프록시 서버를 경유하게 되면서 공인 아이피가 클라이언트 아이피가 아닌 비공개 릴레이 서버(프록시 서버)의 아이피로 잡히게 됩니다.

이는 인스턴스 프로비저닝 과정에서 나의 공인 아이피만 접근할 수 있도록 보안그룹으로 접근제어를 하게 되는데 프록시 서버의 아이피로 잡히게 되면서 접근이 불가능하게 되므로 비공개 릴레이 아이피 대역을 체크하여 해당 대역에 포함하게 될 경우 비공개 릴레이 기능을 사용하는 것으로 간주하여 프로비저닝이 되지 않도록 합니다.

이 기능과 관련하여 이 포스트에서 아이클라우드의 비공개 릴레이 동작 과정을 확인하실 수 있습니다.

outline-vpn apply

apply

Destroy

생성한 인스턴스 리소스(VPC, 서브넷, 키페어 - 로컬 포함)를 삭제합니다.

outline-vpn destroy

destroy

Find

태그를 기반으로 outline-vpn으로 생성한 AWS 인스턴스를 조회합니다.

outline-vpn find # 생성된 인스턴스 조회

find

AccessKey

Outline에서 제공하는 API를 기반으로 액세스키를 발급 또는 삭제합니다.

outline-vpn get accesskey # 액세스키 리스트 조회
outline-vpn create accesskey # 액세스키 생성
outline-vpn delete accesskey # 액세스키 삭제

accesskey

설치 및 사용법

brew tap ghdwlsgur/outline-vpn
brew install outline-vpn
brew upgrade outline-vpn

소스코드는 outline-vpn에서 확인하실 수 있습니다.