michimani.net

Lambda を VPC 内で実行して外部ネットワークと接続する (ために色々やってみた)

2019-02-15

Lambda を VPC 内で実行する場合、外部ネットワークと接続するためには NAT ゲートウェイを使用する必要があるのですが、 NAT ゲートウェイの利用には 約 4,000 〜 5,000 円の費用がかかるため、なんとかして利用しない方向で構築できないかと色々試してみました。

やりたいこと

やりたいのは、 EC2 に対して、 IP 制限されている FTP ポートへのヘルスチェック (FTP ログイン試行) を実施して、正常にアクセス出来なかった場合に Slack で通知する 、ということです。

対象の EC2 と同じ VPC 内で実行する Lambda では、次のようなスクリプトを実行するとします。(ランタイム: Python 3.6)

import json
from ftplib import FTP
from urllib.request import Request, urlopen

host = 'EC2 のプライベートIP'
user = 'ftp-user'
password = 'ftp-user-pass'
webhook_url = 'slack-webhook-url'

def lambda_handler(event, context):
    try:
        ftp = FTP(host=host, user=user, passwd=password, timeout=10)
        ftp.quit()
    except Exception as e:
        try:
            print(e)
            headers = {"Content-Type": "application/json"}
            data = bytes(json.dumps({'text': str(e)}), 'utf-8')
            request = Request(webhook_url, data=data, headers=headers)
            urlopen(request)
        except Exception as e2:
            print(e2)

結論

試したことはあとに書くとして、まず結論から。

どうしても Lambda を使いたい場合

どうしても Lambda を使って実現するためには、次のような構成でやるしかないようです。

Lambda on VPC

対象の EC2 と同じ VPC 内で ヘルスチェック用の Lambda を実行して、 Slack への通知 (外部ネットワークとの接続) のために NAT ゲートウェイを設置する、という構成です。
ま、これが一般的な構成になるんでしょうね。

構成の構築手順

上図の構成を構築する手順としては、以下のとおりです。(既に VPC とパブリックなサブネットは存在するものとします)

  1. プライベートサブネットを作成
  2. NAT ゲートウェイを作成
  3. NAT ゲートウェイ用のルートテーブルを作成
  4. 作成したサブネット内で Lambda を実行

1. プライベートサブネットを作成

プライベートサブネットと言っても、普通のサブネットです。VPC 内に新たに作成します。

2. NAT ゲートウェイを作成

マネジメントコンソールで VPC の画面左から NAT ゲートウェイを選択し、作成します。

Create NAT Gateway

NAT ゲートウェイには EIP が必要になりますが、その場で作成できます。

Create NAT Gateway

3. NAT ゲートウェイ用のルートテーブルを作成

VPC 内に、新たにルートテーブルを作成します。
Destination 0.0.0.0/0 の Target として、作成した NAT ゲートウェイを指定します。

4. 作成したサブネット内で Lambda を実行

Lambda のネットワーク設定で、 VPC と、作成したサブネットを指定します。

Lambda を使わない

そもそも論になりますが、ヘルスチェックスクリプトを、どこか固定 IP を持っているサーバにおいて、そこで実行するという方法です。
固定の IP があれば、その IP をセキュリティグループで許可すればいいだけです。

既に別でサーバを持っている場合はこの方法が一番簡単で一番コストも低くなるでしょう。

やってみたこと

以下、 NAT ゲートウェイを使わずになんとか出来ないか色々試してみた内容です。

Lambda を パブリックサブネット内で実行してみる

Lambda on pubic subnet

※Amazon SNS と Slack のアイコンは無視してください

一見するとこの構成で Lambda は外部ネットワークに接続できそうですが、できません。

通常、パブリックインターネットを通じてアクセスできないように、リソースは Amazon Virtual Private Cloud (Amazon VPC) 内に作成します。
Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する

とあるように、 Lambda はパブリックサブネット内から外には出られません。
なので、EC2 へのヘルスチェックは可能ですが、その結果を Slack へ投げる (Webhook を叩く) ところでタイムアウトしてしまいます。

Slack への通知を SNS 経由にしてみる

Lambda から、同じ AWS サービスである Amazon SNS を使って Slack へポストするという方法です。
構成は上図のままで、 Lambda のスクリプトを下記のように変更します。

import boto3
import json
from ftplib import FTP
from urllib.request import Request, urlopen

host = 'EC2 のプライベートIP'
user = 'ftp-user'
password = 'ftp-user-pass'
sns_topic_arn = 'SNS-TOPIC-ARN'

def lambda_handler(event, context):
    try:
        ftp = FTP(host=host, user=user, passwd=password, timeout=10)
        ftp.quit()
    except Exception as e:
        try:
            print(e)
            puclish_sns_topic('SNS publish.', str(e))
        except Exception as e2:
            print(e2)

def puclish_sns_topic(subject, message):
    sns_client = boto3.client('sns')
    sns_client.publish(
        TopicArn=sns_topic_arn,
        Message=message,
        Subject=subject
    )

また、 SNS から実行する Slack へのポスト用 Lambda を作成します。

import json
from urllib.request import Request, urlopen

webhook_url = 'slack-webhook-url'

def lambda_handler(event, context):
    try:
        message = str(event['Records'][0]['Sns']['Message'])
        headers = {"Content-Type": "application/json"}
        data = bytes(json.dumps({'text': message}), 'utf-8')
        request = Request(webhook_url, data=data, headers=headers)
        urlopen(request)
    except Exception as e:
        print(e)

結果としては、 Lambda から SNS の API を実行するところでタイムアウトになります。
同じ AWS のサービスとは言え、ネットワークは別物です。当たり前のことですが、雰囲気でイケると閃いたのでやってみました。

NAT ゲートウェイを設置する

use NAT Gateway
ということで、やはり NAT ゲートウェイは必要であるということから、実際に設置してみた構成がこれです。

こうすることで、 Lambda から SNS への Publish が可能になり、無事に Slack への通知もできるようになりました。


… いや、NAT ゲートウェイ使うなら SNS 経由で Slack へ投稿する必要無いな。ということで、冒頭に書いた最初のスクリプトで、やりたいことが実現できるようになりました。

まとめ

色々やってみたようで、最終的には公式ドキュメントに載っているレベルの結論しか得られませんでした。
ただ、個人的には色々試して納得できたので良かったです。ネットワークまわりの構成って面倒くさいイメージがありますが、 AWS だとポチポチしていくだけなので簡単ですね。とは言えネットワークまわりの知識はほとんどないので、単に VPC だサブネットだと言っても奥が深いなと感じました。


comments powered by Disqus