【AWS】DynamoDBのデータ更新時にLambdaを使ってバックアップを作成するプログラムをpythonで実装する


先日公開した記事『【個人開発】ごみのお知らせをしてくれるLINEBot「ごみのお知らせくん」を作りました。』を多くの方に見ていただいており、思った以上の反響に少し驚いています。Qiitaの通知が2桁だったことがなかったので、「何か悪いことをしてしまったのか...!?」とビビりました。LGTMやストックしていただき、ありがとうございます!

さて、本記事ではDynamoDBのデータ更新をトリガーにしてデータのバックアップを作成するというフローを、DynamoDBストリーム、Lambda、Pythonを使って実装する話について書いています。LINEBot『ごみのお知らせくん』で地区情報を更新した際にデータのバックアップを取っておきたいな、と思ったのがきっかけです。それでは早速いきましょう!

■構成


今回のシステム構成です。試行錯誤しながらだったので結構時間がかかったのですが、図にするとかなりシンプルですね。

■作業手順

下記が今回の実行手順です。
やりたい内容に沿ってカスタマイズしてください。

1.DynamoDBの「DynamoDB ストリーム」を有効にする
2.IAMロールとIAMポリシーを作成する
3.Lambda関数を作成する
4.バックアップ作成用のプログラムをアップロードする
5.IAMポリシーを少し修正
6.データを更新してバックアップが作成されることを確認

1.DynamoDBの「DynamoDB ストリーム」を有効にする

まず、DynamoDBのデータ変更をトリガーにするためDynamoDBストリームを有効にします。これを有効にすることでデータ変更を取得できるようになります。

「概要」タブにある項目「DynamoDBストリームの詳細」の「DynamoDBストリームを管理」をクリックします。クリックすると”どの情報を保存するか”という設定ポップアップが開くので、用途に合わせて選択し、有効化をクリックします。僕の場合は変更後の状態を保存しておきたかったので「新しいイメージ」を選択しました。

有効にすると上画像のようにストリーム有効の値が「はい」の表示になります。この時表示されるARNについては後ほど、IAMポリシーの設定で使用します。この値は覚えておかなくても大丈夫です。

2.IAMロールとIAMポリシーを作成する


Lambdaに設定するIAMロールとIAMポリシーを作成します。特に重要なDynamoDBに付与するポリシーについては太字で書いています。

作成するIAMポリシーは下記のようになります。これらのポリシーをIAMロールに付与し、IAMロールをLambdaに付与します。

■DynamoDB
 アクション:書き込み『CreateBackup』
 *リソース:DynamoDBのリソースを選択

 アクション:読み込み
 『GetRecords』『GetShardIterator』『ListStreams』『DescribeStream』
 *リソース:DynamoDBストリームのリソースを選択

■Lambda
 アクション:書き込み『InvokeFunction』
 *リソース:Lambdaの関数を選択

■CloudWatch Logs
 アクション:書き込み『すべて選択』
 リソース:すべてのリソース

ここで、「*」をつけたDynamoDBとLambdaのリソースについては後でまとめて設定するので、一旦は「すべてのリソース」で設定します。

3.Lambda関数を作成する

バックアップを作成するLambda関数を作成します。ここでは「createDynamoDBBackup」という名前で作成しています。

関数作成の際、先ほど作成したIAMロールを実行ロールに設定します。また、今回はpythonで実装するのでランタイムにはpythonを選択しています。

4.バックアップ作成用のプログラムをアップロードする


Lambdaのコードソースからソースをアップします。僕の場合はdockerで構築した開発環境でソースコードを用意しました。

下記が今回使用した、「pyファイル」「docker-compose.yml」「Dockerfile」です。Lambdaにアップロードするときにはoptディレクトリ配下をzipで圧縮します。dockerによる開発環境の構築については『補足』に記載しています。

■ ディレクトリ構成
docker-compose.yml
Dockerfile
opt
 L lambda_function.py
lambda_function.py
import json
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

import boto3

DATABASE = os.environ['DATABASE']
TABLE_NAME = os.environ['TABLE_NAME']

def lambda_handler(event, context):
    logger.info("backup取得開始")
    dynamodb_client = boto3.client(DATABASE)
    res = dynamodb_client.create_backup(
        TableName = TABLE_NAME,          # テーブル名
        BackupName  = TABLE_NAME + '_bk' # バックアップファイル名
    )
    logger.info(res)

    return {
        'statusCode': 200,
        'body': json.dumps('backup取得完了')
    }
docker-compose.yml
version: "3"
services:
  python3:
    build: .
    container_name: "python3_lambda"
    working_dir: "/root/opt"
    tty: true
    volumes:
      - ./opt:/root/opt
    ports:
      - "8000:80"
FROM python:3
USER root

RUN apt-get update

ADD . /code
WORKDIR /code
RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools

5.IAMポリシーを少し修正


Lambda関数が作成できたらDynamoDBをトリガーに追加します。

部品が揃ったので、IAMポリシーを少し修正していきます。

Lambdaのリソースを修正


IAMポリシーのLambdaリソースを先ほど作成したLambda関数に修正します。入力するのはLambdaの「関数のARN」の値です。「Lambda_functionのARNの設定」に入力すれば下の「Region」「Account」「Function name」欄にも自動的に入力されます。

DynamoDBのリソースを修正


▲DynamoDBストリームのARN


▲DynamoDBのARN

IAMポリシーのDynamoDBリソースに「DynamoDBストリームのARN」と「DynamoDBのARN」を入力します。ARNはそれぞれ上画像の部分です。

6.データを更新してバックアップが作成されることを確認


ここまで出来れば後は確認です。
Lambda関数は簡単にテストができるので確認してみます。

テストした際に該当テーブルの「バックアップ」タブに「テーブル名_bk」というファイルができていれば成功です。次は、DynamoDBのデータを更新してバックアップが出来ているか確認します。

バックアップができていない場合はどこかでエラーが起きているので、Lambdaの「モニタリング」タブでログを確認します。

バックアップファイルが確認できたら「バックアップの復元」をクリックして、更新したデータでテーブルができるかどうか確認します。問題なく出来ていれば完了です。

補足

pythonファイル、docker-compose.yml、Dockerfileついて

githubにアップしました。

コンテナ起動後、python3コンテナに入り、下記コマンドを実行してください。

・docker-compose.ymlがあるディレクトリで
docker-compose up -d

・ステータスがupになったら
docker-compose exec python3 bash

・boto3をインストール
pip install boto3 -t ./

boto3はpythonでAWSを操作するためのライブラリです。
Lambdaにzipでアップする必要があるので、現ディレクトリ(/opt内)にインストールするようにしています。

pythonコード内のos.environ['-']について

プログラム内にはDBの情報を直接記載せず、Lambdaの環境変数で設定しています。環境変数は、Lambdaの「設定」タブ→環境変数から設定ができます。

最後に

DynamoDBのバッグアップには「オンデマンドバックアップ」と「ポイントインタイムリカバリ」の2種類あります。オンデマンドバックアップはバックアップを取得した時点のテーブル状態を保存し、ポイントインタイムリカバリは有効にした時点からテーブルに対する更新処理が記録されます。

今回は「データが更新された際にバックアップを作成したいこと」「無料の範囲で実装したいこと」があったので、これらのバックアップ方法は使わず、DynamoDBストリームとLambdaを組み合わせました。

ポイントインタイムリカバリを有効にしようとすると「追加料金がかかります」というように表示がされるため、『この方法は料金がかかってしまう!これでは実現できない!』と考え、本記事の実装をしました。ですが、ポイントタイムリカバリの説明を改めて見てみると、もしかしたらポイントタイムリカバリでもできるんですかね...?この部分についてはまた改めて調べてみたいと思います。ご存知の方がいたら教えて欲しいです。

それでは!

参考サイト

Amazon DynamoDB
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/dynamodb.html

CreateBackup
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/APIReference/API_CreateBackup.html

DynamoDB StreamをトリガーにしてLambdaを実行する
https://qiita.com/Fujimon_fn/items/1f18360ee9ebf6832617

チュートリアル: DynamoDB Streams と Lambda を使用して新しい項目を処理する
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.Lambda.Tutorial.html#Streams.Lambda.Tutorial.CreateTrigger

DynamoDB テーブルのバックアップ
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Backup.Tutorial.html

AWS SDK for Python (Boto3) の “Client API” と “Resource API” の違いについて調べてみた
https://dev.classmethod.jp/articles/boto3-client-api-and-resource-api/

ステップ 3: Python で項目を作成、読み込み、更新、削除する
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GettingStarted.Python.03.html

AWS Lambda 実行ロール
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-intro-execution-role.html