AWS Client VPN節約術!~AWS Lambdaで関連付け自動化~

2021.9.3 ALHブログ

みなさん、こんにちは!RDUの戸澤です。
システム開発のエンジニアであれば、一度はAWSを使って色々検証してみたいと思ったことがあると思います。
しかし、AWSは従量課金なので個人利用するには何だか不安!という方もいるのではないでしょうか。
そこで、今回はAWS Client VPNにフォーカスして、使用料を節約する術について、みなさんに知ってもらおうと思います!

やりたいこと

まずAWS Client VPNの料金についてですが、AWS公式によると、

AWS Client VPN では、アクティブなクライアント接続の数に対して、および Client VPN に関連付けられているサブネットの数に対してそれぞれ1時間単位の料金が発生します。

引用元:https://aws.amazon.com/jp/vpn/pricing/

とのことなので、誰も利用していない状況においても、サブネットが関連付けられているだけで料金が発生してしまいます。毎回手動で管理するのは面倒なので、今回はこの「Client VPNへのサブネットの関連付け/関連解除」を自動化していきたいと思います。

前提

  • AWS Client VPNについては構築済みという前提で進めます。(これも後で記事にできたらいいかも)
  •  停止するとクライアントの接続も切れてしまうので、「停止時間」「起動時間」については状況に合わせて設定しましょう。
  • AWSコンソールでのLambda関数作成やIAMロール作成など基本的な部分も割と丁寧に書くつもりなので、「そんなの知ってる!」という方は読み流してください。

環境

使用する言語

  • Python 3.9

使用するAWSサービス

  • AWS Client VPN
  • AWS Lambda
  • AWS IAM
  • AWS CloudWatch Events
  • AWS CloudWatch Logs

設定手順

全体の流れ

絶対これ!というわけではないですが、今回は下記の流れで構築します。

  1. Lambda関数作成
    1.  Lambda関数内で動かすPythonコード作成
    2. Lambda関数にアタッチするIAMロール作成
    3. Lambda関数作成
  2. CloudWatch Eventsルールの作成

Lambda関数作成

Lambda関数内で動かすPythonコード作成


Lambda関数に使用するコードを書いていきましょう。
このコードが実際に「Client VPNへのサブネットの関連付け/関連解除」の処理を行う部分となります。
私はプログラマーではないので、綺麗に書けていないかもしれません。お気づきのことがあればご指摘ください!
 
今回はPythonという言語を使用して記述していくのですが、PythonにはAWS SDK for Python (boto3)というライブラリがAWS公式から用意されておりまして、AWS内リソースの多くが、このboto3から操作できるようになっています。今回はこのboto3を活用していきます。

では、「関連付け解除(VPN停止)」から作っていきましょう。

下記のboto3ドキュメントを見ながら記述していきます。

Boto3 Docs 1.18.26 documentation

Boto3 documentation — Boto3 Docs 1.18.26 documentation (amazonaws.com)

 

  • EC2.Clientクラスの使用

boto3にてEC2.Clientクラスを使用するには上記画像赤枠部分の記述を追加します。

#boto3のインポート
import boto3
#ec2リソース情報を変数(client)に代入する。
client = boto3.client('ec2')

これでboto3およびEC2.Clientクラスを使用する準備が整いました!

 

  • AWS Client VPNの操作

EC2.ClientクラスからAWS Client VPNの操作をするメソッドを探します。先ほども使用したboto3ドキュメントを探ってみましょう。

サブネットの関連付けを解除するメソッドが見つかりました!!
こんな感じで書けばよさそうですが、、、


client.disassociate_client_vpn_target_network(
    ClientVpnEndpointId = 'クライアントVPNエンドポイントのID',
    AssociationId = '関連付けID'
)

実はこの「AssociationId(関連付けID)」は関連付けが行われるたびに変動する値なんですよね。。。
なので停止を行う際には毎回この値をとる必要があるということです。
というわけで「AssociationId」を取得できるメソッドを探していきましょう!

見つかりましたね!これを参考に書いてみます!


ClientVpnDat = client.describe_client_vpn_target_networks(
	ClientVpnEndpointId = 'クライアントVPNエンドポイントのID'
)
# 変数に入ったデータからAssociationIdのみ取り出す
AssociationId = ClientVpnData['ClientVpnTargetNetworks'][0]['AssociationId']

 

  • Lambda関数ハンドラー

ここまで来たらあとはAWS Lambdaの関数ハンドラー構文を組み合わせるだけです。


<Lambda関数ハンドラー>
def handler_name(event, context): 
    ...
    return some_value

ちなみにPythonではインデントが半角スペース4つと決まっているので注意してくださいね!

  • 完成

これを組み合わせると、、、

<関連付け解除コード>
import boto3
client = boto3.client('ec2')

def lambda_handler(event,context):
	ClientVpnData = client.describe_client_vpn_target_networks(
		ClientVpnEndpointId = 'クライアントVPNエンドポイントのID'
	)
	AssociationId = ClientVpnData['ClientVpnTargetNetworks'][0]['AssociationId']
	
	client.disassociate_client_vpn_target_network(
    	    ClientVpnEndpointId = 'クライアントVPNエンドポイントのID',
    	    AssociationId = AssociationId
	)

「関連付け解除」のコードが完成しましたーー!!
つづいて関連付け用のコードを書いていきます!

  • 関連付け用コード

解除用のコードと同じ要領で「関連付け」用のコードもささっと書いてしまいます。

<関連付けコード>
import boto3
client = boto3.client('ec2')

def lambda_handler(event, context):
	client.associate_client_vpn_target_network(
            ClientVpnEndpointId = 'クライアントVPNエンドポイントのID',
            SubnetId = '関連付けしたいサブネットID'
	)

「関連付け」の際に使用する値は固定値のみなのでシンプルに記述ができました。
これでLambda関数用のPythonコードは完成です!!

Lambda関数にアタッチするIAMロールの作成


今回作成するLambda関数はClient VPNエンドポイントに対して操作を行うので、関数がこの操作を行うことを許可するためにIAMロールを作成し、アタッチする必要があります。関数作成時にアタッチを行うため事前にIAMロールを準備します。

 

  • IAMポリシーの作成

IAMロールを作成する前提としてまずはIAMポリシーを作成していきます。
簡単なのですが、下記画像の順番にポチポチしていきます。
IAMのサービス画面に移動して、、、

 
この画面だけなんだかごちゃっとコードがありますので、少しだけ説明を書きます。


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0", # ここまではお決まりといった感じです。
            "Effect": "Allow", # 下に書くアクションに対して許可するという意味。「deny」にすれば拒否。
            "Action": [
         "ec2:AssociateClientVpnTargetNetwork", # VPNにサブネットの関連付けをする。
                "ec2:DisassociateClientVpnTargetNetwork", # VPNからサブネットの関連付けを解除する。
                "ec2:DescribeClientVpnTargetNetworks" #VPNに関連づいているサブネットの情報を取得する。
                
            ],
            "Resource": "*" # 影響範囲をすべてのリソースにする。状況に合わせて絞りましょう。
        },
        {
            "Sid": "VisualEditor1", #ここから下はLambdaのお決まりでして、CloudWatchにログを吐くのに必要。
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}

これを画像に示した部分に張り付ければOKです!(コメントが邪魔でしたらすみません笑)
では画像に戻ります。


 
「ポリシーの作成」を押したらIAMポリシーは完成ですので、IAMロールを作成していきます。

 

  • IAMロールの作成

IAMロールの作成ですが、これも簡単なので画像の通りにポチポチしていきます。


いやいや疲れました、、、!!!これでLambda関数にアタッチするIAMロールも完成です!
若干息切れ気味ですが、ラストスパート!肝心のLambda関数本体を作っていきます!

Lambda関数作成


さて、これが肝心な本体の作成なのですが、冒頭でコードは作成済みなので画面をサクサク進めるだけです。
「関連付け」と「関連付け解除」の関数は別々に作るので注意してください!
AWS Lambdaのサービス画面に移動して、、、

 

③のDeployをクリックしてSuccessとなればLambda関数は完成です!
※同じ手順なので省いていますが、この時点で「関連付け」「関連付け解除」の2つの関数が完成しています。

CloudWatch Eventsルールの作成

「Client VPNへのサブネットの関連付け/関連解除」のLambda関数を毎日自動で実行させるために、AWSのCloudWatch Eventsという機能を使用します。

CloudWatch Eventsにて「関連付け」「関連付け解除」のルールを作成していきます。同じ手順なのでルール1つ分の手順説明ですが、「関連付け」「関連付け解除」は別々のルールで作成しています。
では早速ですが、AWS CloudWatchサービスの画面に移動して、、、

この画面で「サブネットを関連付ける時間」「解除する時間」をCron形式で記述していきます。画像にもありますがUTC時間での記述となるので注意してください!(実際に実行したい時間 – 9時間です!)

「ルールの作成」をクリックすればCloudWatch Eventsの設定は完了です!

以上で、「Client VPNへのサブネットの関連付け/関連解除」の自動化は完了となります!!お疲れ様でした!!

まとめ

「Client VPNへのサブネットの関連付け/関連解除」が自動化されたので、時間になると、、、

こうなったり、

こうなったり、、、
自動で行われるようになりました!
個人で利用する場合は特に料金を節約したいと思いますので、是非活用していただけると幸いです!
それでは、最後まで読んでいただきありがとうございました!

戸澤駿介
ALH株式会社 Refined Design Unit(通称:RDU)所属。中途入社。
多趣味。とにかく多趣味。絞りたいです。。。