【AWS】AWS Lambda から AWS DocumentDB に接続するまでにやったこと
こんにちは!新卒入社後一週間で風邪を引いて万全の状態じゃない日々を送っていたみっちーです!咳と鼻水が特にやばくて、大好きなお酒も飲めず、同期と飲みにも行けず、こうやってZennに記事書いてます、、、(ここで同期に差をつけられるとhappy)
背景
新卒研修で AWS でアプリを開発しているのですが、AWS Lambda から AWS DocumentDB に接続したいというところで認証まわりや証明書関連で奮闘してたので、自分が接続までにやったことをストーリー形式でアーカイブしておこうかなと思い、本記事を書く流れになりました。
前提
- AWS Lambda の環境は Python3.11
- AWS DocumentDB はすでに用意されており、TLSが有効
- AWS Lambda、AWS DocumentDB のVPC、サブネット、セキュリティグループは同一
- AWS DocumentDB のアクセスパスワードは AWS Secrets Manager で管理している
【第一章】 ロール? ポリシー?
AWS を使っていると絶対に聞く「ロール」、「ポリシー」という言葉。僕は初めて聞いた時、まあ権限とかその辺なんだろうなという抽象的なイメージを持ってましたが、使っていると結構イメージできたので自分なりの解釈を垂れ流そうかなと思います。
ロールとは
日本語訳すると「役割」という意味になります。
例えば、居酒屋のバイトをイメージしてください。バイトといってもいろんな役割があるわけで、キッチンスタッフもいればホールスタッフもいるわけです。キッチンスタッフは料理を作るという「役割」があり、ホールスタッフは料理を作るのではなく持っていく、お客様とコミュニケーションをとるという「役割」があるわけです。
AWSで例えるなら、Lambda関数がECSにアクセスする役割を持っているのか、はたまたDocumentDBにアクセスする役割を持っているのか、両方なのか、などどのような役割を担っているかを定義する必要があるというわけです。
ポリシーとは
どの行為が許可されているのかは役割ごとで違います。
居酒屋のバイトの例だと、キッチンスタッフはお客様とコミュニケーションをとることは許可されていません(許可されてないというのは言い過ぎかもしれませんが、そっちの方がイメージしやすいので一旦これでイメージしてください)。また、ホールスタッフは料理にアクセスする(料理を持っていく)ことはできますが、料理に書き込む(料理を作る)ことは許可されていません。
このように、定義したロールがどのようなポリシーを持つのかを定義する必要があります。
Lambda に持たせたロール
以下の3つの許可ポリシーを持ったロールを Lambda に与えました。
- SecretsManagerReadWrite(シークレットマネージャーにアクセスするためのポリシー)
- AWSLambdaVPCAccessExecutionRole(VPCに接続するためのポリシー)
- AWSLambdaRole(Lambdaの基本的なロール)
【第二章】 どうやって DocumentDB のパスワード入手するんだろう
公式ドキュメントを参考にして、DocumentDB への接続を試みました。
DocumentDB に接続するためには、ユーザID、パスワード、ホスト名などの情報が必要なのですが、当初どうやってパスワード取得するんだろうと思ってました。Slackで「これ、どうやればいいんかな?」と聞いたところ、同期が「AWS Secrets Managerで管理しててそこから取ってきて」という言葉と、以下のコードが飛んできました。優しいし頼もしいですね。
# Use this code snippet in your app.
# If you need more information about configurations
# or implementing the sample code, visit the AWS docs:
# https://aws.amazon.com/developer/language/python/
import boto3
from botocore.exceptions import ClientError
def get_secret():
secret_name = "DocumentDB_Authentication"
region_name = "ap-northeast-1"
# Create a Secrets Manager client
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except ClientError as e:
# For a list of exceptions thrown, see
# https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
raise e
secret = get_secret_value_response['SecretString']
return secret
この secret という変数はDict形式のString型で飛んでくるので、以下のようにパスワードをゲットしてあげます。
my_password = json.loads(get_secret(config))
my_password = my_password['password']
最後にDBのクライアント情報を以下のコードで取ってきました。
client = pymongo.MongoClient(f'mongodb://<USER_NAME>:{my_password}@<HOST_NAME>/?tls=true&tlsCAFile=global-bundle.pem&retryWrites=false')
しかし、これだけだとまだまだエラーが出るんです、、、
【第三章】 No module named 'pymongo'
ローカルだとpip install pymongo
で事足りると思います。しかし、 Lambda はターミナルがなく、Cloud 9 を用いてインストールしてくださいというドキュメントが溢れていました。ただ、それだと Cloud 9 のインスタンスを立ち上げる必要があり、余計なコストがかかってしまう。また、ドキュメントの内容が結構手順踏むものだったので、他に方法はないか模索していました。すると、「レイヤーにpymongoのarnを追加すると良い」という記事を見つけました。手順を以下に記載します。
- レイヤーを追加するをクリック
- ARN を指定を選択し、先人が残してくれた ARN 集から Python3.11 の pymongo の ARN の URL を持ってきて追加する
これでNo module named 'pymongo'
のエラーは解決されます。ただ、まだまだ懸念点があるんです、、、
【第四章】 TLS 有効なので証明書が必要なんだけど、どうやって対応しようか
前提でも述べたように、 DocumentDB の TLS が有効なので、証明書をダウンロードする必要があります。公式ドキュメントによると、以下のコードをターミナルで実行することでダウンロードできるそうです。
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
しかし、第二章でも述べたように、Lambda でターミナルを利用する操作はかなり面倒です。そこで、ローカルに証明書をダウンロードして、Lambda上で global-bundle.pem
という空ファイルを作成し、そこにローカルにダウンロードした証明書の中身をコピペするという強引な打開策をとりました(かなり無理矢理な方法なので有識者の方はこれより簡単な方法あれば教えてください、、、)。
まとめ
AWS Lambda から AWS DocumentDB に接続するまでに以下の4つの手順を行いました。
- Lambda に適切なロールを与える
- AWS Secret Manager に AWS DocumentDB のパスワードを保管しておいて、そこにアクセスしてパスワードを取ってきた
- Lambda のレイヤーに pymongo の arn を追加
- TLS 証明書を Lambda の適切なディレクトリに配置
自分は今までインフラ経験がなく、どういう仕組みでサービスが動いているのかの理解から始まったので、問題の解決にかなり時間がかかってしまいました。しかし、接続さえできればあとは経験したことのあるデータベースにデータを挿入したり持ってきて色々したりするだけだったので経験って本当に大事だなと思いました。なんにせよインフラは実際に使ってみないと何をやっているのかイメージできないと感じたので、今後も色々なサービスを遊び感覚で使いつつ業務にも活かしていきたいなと思いました。
Discussion