🦥

【CDK v2 Go】Go で IaC - VPNサーバーをたてる -

2022/07/07に公開

モチベーション

  • 我が家では感じの良い営業さんにのせられ SoftbankAir を契約しているのでIPを固定するのは無理
  • 別途、固定IPを契約するほどの熱意もお金も無い
  • いつもはTypeScriptでインフラを書いてるけど、より慣れているGoでも書いてみたい

ソース

https://github.com/ShinsakuYagi/cdk-vpn-server

内容

VPNサーバについては https://github.com/siomiz/SoftEtherVPN を利用しました。
色々あったのですが参考になる記事も多かった+シンプルだったのでこちらを使用させてもらいました。

構成については色々悩んでいてベストな構成が定まりきっていないのですが、現状は 「stack」 と 「resources」 に分けるのが好みです。

stack

stackCFn Stack となるようにしています。
stack パッケージ内で 複数の resources を生成して一つの CFn Stack が生成されるようにしています。
色々書いていた結果、この方法が依存関係の確認や頭の切り分けがしやすいなぁと思っています。

抜粋

stack/vpn_server.go
// VPC を作る
vpc, publicSubnet := resources.NewVPC(scope, stack)

// EC2 作成時には VPC と配置するサブネットが必要なので引数として渡す
resources.NewEC2(scope, stack, &resources.CdkEc2Props{
	VPC:          vpc,
	PublicSubnet: publicSubnet,
})

resources

resourcesAWS サービス のようなイメージです。
以下の画像で言うと EC2 とか VPC とか S3 とかのまとまりで分けたい気持ちです。

最近アクセスしたサービス

今回の例では VPCEC2 を作成しています。 以下ポイントだけピックアップします。

VPC

特になし!
パブリックなサブネットが一つだけあるVPCを切っています。(CIDRは適当)
めちゃくちゃ単純なので内容は割愛します。

EC2

キーペアについてはコンソール上から事前に手動で作成しています。
コストのことを考えて t4g.nano で立てています。個人で使う分には十分です。

resources/ec2.go
const (
	imageId      string = "ami-004332b441f90509b"
	instanceType string = "t4g.nano"
	keyName      string = "VPNServerKey"
)

セキュリティグループについては https://hub.docker.com/r/siomiz/softethervpn/ のSetup に従いポートを開放しています。
SSHは必要ないですが動作の確認とかに使う間は開放しています。

resources/ec2.go
SecurityGroupIngress: &[]*awsec2.CfnSecurityGroup_IngressProperty{
	{
		IpProtocol: jsii.String("tcp"),
		CidrIp:     jsii.String("0.0.0.0/0"),
		FromPort:   jsii.Number(22),
		ToPort:     jsii.Number(22),
	},
	{
		IpProtocol: jsii.String("udp"),
		CidrIp:     jsii.String("0.0.0.0/0"),
		FromPort:   jsii.Number(500),
		ToPort:     jsii.Number(500),
	},
	{
		IpProtocol: jsii.String("udp"),
		CidrIp:     jsii.String("0.0.0.0/0"),
		FromPort:   jsii.Number(4500),
		ToPort:     jsii.Number(4500),
	},
	{
		IpProtocol: jsii.String("tcp"),
		CidrIp:     jsii.String("0.0.0.0/0"),
		FromPort:   jsii.Number(1701),
		ToPort:     jsii.Number(1701),
	},
},

インスタンスの生成部分です。
UserDataSoftEther VPN を走らせるスクリプトを渡します。
これで起動時に自動的に立ち上がってくれるはずです。

resources/ec2.go
instance := awsec2.NewCfnInstance(stack, jsii.String("EC2Instance"), &awsec2.CfnInstanceProps{
	ImageId:          jsii.String(imageId),
	InstanceType:     jsii.String(instanceType),
	SubnetId:         props.PublicSubnet.Ref(),
	SecurityGroupIds: jsii.Strings(*vpnServerSG.AttrGroupId()),
	KeyName:          jsii.String(keyName),
	UserData:         jsii.String(base64.StdEncoding.EncodeToString(scripts)),
	Tags:             &[]*awscdk.CfnTag{{Key: jsii.String("Name"), Value: jsii.String("VPNServer")}},
})

SoftEther VPN を起動させるスクリプトは以下です。
これをどこに置くかが一番悩んだのですが、結局 「embedしたらええか…」となった結果ここに収まりました。(今も悩んでいる)

resources/scripts/user_data.sh
#!/bin/bash
sudo yum update -y
sudo amazon-linux-extras install docker
sudo service docker start
sudo systemctl enable docker

sudo docker run --rm -d --privileged -p 500:500/udp -p 4500:4500/udp -p 1701:1701/tcp \
  -e PSK='yourkey' \
  -e USERS='user:password' \
  siomiz/softethervpn

yourkey,user:password は適宜読み替えて下さい。

デプロイ

> cdk deploy --all --profile your-profile

とやれば一撃で作成してくれます。
権限周りなどややこしい制限がない限り「セキュリティグループ作っていいですか?」ぐらいの確認だけでサクッと作成出来るかと思います。

書いていて嬉しかった事

  • いつもの業務ではGoでお仕事をしているので、書き味が同じなのは嬉しい。
    • Web界隈ではサーバサイド兼インフラも触るみたいな方も多いのかな?と思いますのでこれはメリットだと思います。
  • フォーマットとかリンターの設定も使い回せるのでいつもGoで開発している人は書き始める障壁が低いと思います。
  • package でまとめられるのも可読性が良いと感じました。

つらみ

CDK Go でのつらみ(?)も少しあったので書いておきたいと思います。

  • パラメータが必須なのかどうか分かりづらい。
    • 構造体のタグを見れば分かるのですが何度か失敗しました。
  • 結局CDKnpm管理なので「全てGo」とはいかない。
    • CDK本体のバージョン管理が煩雑なのは変わらないです。(Go関係ない)

最後に(余談)

  • フィールド名の ~~~Id~~~ID に出来ないものか?
  • 構成についてはもっと良い構成がありそうなのでもっと真剣に考えたいです。

参考

追記

コスト

このままだと $2/月 くらい??? (704%増加の文字面は怖い)

overview

あんまり使っていないのもありますが安すぎんか???(嬉しい)

daily

Discussion