こんにちは。虎の穴ラボの鷺山です。
この記事は「虎の穴ラボ 夏のアドベントカレンダー」の3日目の記事です。
2日目は植竹さんによる「GCPの監視機能 Monitoring の推しポイント紹介」が投稿されました。
4日目はH.Y.さんによる「Amazon WorkSpacesで色々試してみる。」が投稿されます。こちらもぜひご覧ください。
はじめに
データベースを運用していると、「挿入・変更・削除などのちょっとしたデータ操作を、シェルスクリプトの中にSQLを書いて実行」したりすることはあると思います。
今回はそのような処理をAmazon ECSのタスク実行を使ってサーバーレスに実現する方法をご紹介したいと思います。
コンテナの起動にAWS Lambdaも使用します。
環境
- データベース: MySQL 5.7 (Aurora 2.10.2)
- この記事の付録にPostgreSQLでの方法もご紹介しています。
- シェルスクリプト: Bash 5.0.17
- AWS CLI (2.7.9)
- AWSのアカウントID:
111111111111
- AWSリージョン:
ap-northeast-1
ECRリポジトリの作成
まず、ECSのタスク実行に使用するDockerイメージを登録するためのECRリポジトリを作成します。
ここではリポジトリ名をdatabase-executor
としています。
Dockerイメージの準備・登録
次にSQL実行用のシェルスクリプトentrypoint.sh
と、それを内包するイメージを作るためのDockerfile
を作成します。
▼ entrypoint.sh
#!/bin/bash set -Ceuo pipefail # MySQLのパスワードを環境変数に設定 export MYSQL_PWD=$DB_PASSWORD mysql \ --host="$DB_HOSTNAME" \ --port="$DB_PORT" \ --database="$DB_DATABASE" \ --user="$DB_USERNAME" \ --execute="$@"
このスクリプトでは、データベースの接続情報を環境変数で指定できるようにしています。
DB_HOSTNAME | データベースのホスト名 |
---|---|
DB_PORT | データベースのポート番号 |
DB_DATABASE | データベース名 |
DB_USERNAME | ユーザー名 |
DB_PASSWORD | パスワード |
また、実行するSQLをシェルのコマンドの引数 $@
で指定できるようにしています。
▼ Dockerfile
FROM ubuntu:20.04 RUN apt-get update && \ apt-get install -y \ default-mysql-client WORKDIR /app COPY ./entrypoint.sh /app RUN chmod +x /app/entrypoint.sh ENTRYPOINT ["/app/entrypoint.sh"]
このDockerfileでは、MySQLのクライアントdefault-mysql-client
をインストールしています。
また、ENTRYPOINT
に上記のentrypoint.sh
を指定しています。
上記2つのファイルを作成したら、Dockerイメージを以下のコマンドでビルドし、ECRへプッシュします。
イメージ名はdatabase-executor
、タグ名はtag01
としています。
# 1. イメージのビルド $ docker build --no-cache -t 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/database-executor:tag01 . # 2. ECRにログイン (要AWS CLI) $ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com # 3. ECRにイメージをプッシュ $ docker push 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/database-executor:tag01
ECSの準備
ECRへのイメージのプッシュが完了したら、それを実行するECSクラスターとECSタスク定義を作成します。
ECSクラスターの作成
database-executor-cluster
という名前のクラスターを作成します。
ECSタスク定義の作成
以下の設定でECSタスク定義を作成します。
- タスク定義名:
database-executor-task
- 起動タイプ:
FARGATE
- オペレーティングシステムファミリー:
Linux
- コンテナの定義
- コンテナ名:
database-executor
- イメージ:
111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/database-executor:tag01
- コンテナ名:
タスク定義の作成が完了すると、以下のようなARNが (「JSON」タブのtaskDefinitionArn
属性から) 確認できます。次のステップで使用するので控えておいて下さい。
arn:aws:ecs:ap-northeast-1:111111111111:task-definition/database-executor-task:1
Lambda関数の作成
ECSのクラスターやタスク定義の作成が完了したら、それを起動するためのAWS Lambda関数を作成します。
▼ lambda_function.py
import json import boto3 ecs = boto3.client("ecs") def lambda_handler(event, context): # ECSタスクの起動 response = ecs.run_task( cluster="database-executor-cluster", taskDefinition="arn:aws:ecs:ap-northeast-1:111111111111:task-definition/database-executor-task:1", launchType="FARGATE", networkConfiguration={ "awsvpcConfiguration": { "subnets": ["subnet-11111111"], "assignPublicIp": "ENABLED", }, }, overrides={ "containerOverrides": [ { "name": "database-executor", "command": ["select now();"], "environment": [ {"name": "DB_HOSTNAME", "value": "xxxxxx.cluster-xxxxxxx.ap-northeast-1.rds.amazonaws.com"}, {"name": "DB_PORT", "value": "3306"}, {"name": "DB_DATABASE", "value": "xxxxxx"}, {"name": "DB_USERNAME", "value": "admin"}, {"name": "DB_PASSWORD", "value": "xxxxxx"}, ], }, ], }, ) if len(response["failures"]) == 0: # 正常終了 return {"statusCode": 200, "body": json.dumps("OK")} # エラー終了 print(response["failures"]) return {"statusCode": 500, "body": json.dumps("Error")}
ecs.run_task()
でECSタスクを起動します。cluster
にECSクラスター名を、taskDefinition
にECSタスク定義のARNを指定します。subnets
に対象のデータベースに接続可能なサブネットのIDを指定します。containerOverrides
に実行するコンテナのパラメータを指定します。name
にコンテナ名を指定します。command
に実行するSQLを指定します。- 上記の例では説明を簡単にするために、データ操作の代わりに現在の時刻を取得するSQL (
select now();
) を指定しています。
- 上記の例では説明を簡単にするために、データ操作の代わりに現在の時刻を取得するSQL (
environment
にデータベースの接続情報を環境変数で指定します。- 上記の例では説明を簡単にするために、データベースの接続情報をコードの中に直接記述していますが、実際の環境では接続情報はLambdaの環境変数に設定して読み出すほうが望ましいと思われます。
また、このLambda関数にはiam:PassRole
とecs:RunTask
の権限を実行ロールに付与する必要があります。
▼ 実行ロールに付与するポリシーの例 (JSON)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:PassRole", "ecs:RunTask" ], "Resource": [ "arn:aws:iam::111111111111:role/*", "arn:aws:ecs:*:111111111111:task-definition/*:*" ] } ] }
実行
Lambdaの「テスト」から関数を実行します。
この関数の実行にはパラメータは不要なので、テストイベントJSONには {}
を指定すればOKです。
関数が実行されて正常終了し、ECS側のログにSQL (select now();
) の結果が出力されていることが確認できればOKです。
定期実行するには
上記の関数を定期的に実行するには、Amazon EventBridgeにて上記の関数を実行するCron式のルールを作成することで実現することができます。
まとめ
Amazon ECSやAWS Lambdaを使ってシェルスクリプトからSQLをサーバーレスに実行する方法をご紹介しました。
この方法を応用すれば、これまでサーバーを立てて実施していた夜間の定期バッチなどの処理もサーバーレスに切り替えることができるかもしれません。運用のサーバーレス化をご検討中の方の一助になれば幸いです。
付録: PostgreSQLの場合
PostgreSQLの場合はentrypoint.sh
は以下のようになります。
▼ entrypoint.sh
#!/bin/bash set -Ceuo pipefail # PosgtreSQLのパスワードを環境変数に設定 export PGPASSWORD=$DB_PASSWORD psql \ -h "$DB_HOSTNAME" \ -p "$DB_PORT" \ "$DB_DATABASE" \ -U "$DB_USERNAME" \ --command "$@"
また、DockerfileではPostgreSQL用のクライアントpostgresql-client
をインストールする必要があります。
▼ Dockerfile (抜粋)
RUN apt-get update && \
apt-get install -y \
postgresql-client
P.S.
採用情報