lambdaでシンプルにmysqldump to s3
定期mysqldumpをFargateやEC2じゃなくてlambdaでかるーくやりたいが、ググるとメンテが終わったnpmパッケージを使ったものや、ローカルとかで引っこ抜いたバイナリをlambdaに突っ込んでpythonでゴニョるみたいなのしか出てこない。
後者はまぁいいっちゃいいんだけど、そもそもshellだと5行くらいで終わるのにやたらコードを書かされている印象で、なんだかなぁ。と思っている。
ということでもっとスッキリした感じでやりたい
いい感じのmysqldumpバイナリの取得方法やいい感じの言語のパッケージが無いかと探したが無い。
そこでshellの方向で模索すると、lambdaでカスタムランタイムならshellが使える(というかshellがデフォルト)ということに気付く。
かつベースはamazonlinux2なので、普通にyumで色々インストールすればok。いけそう。
まずはローカルでモノを作る。
Lambdaのカスタムランタイムのベースイメージはこれらしい
とりあえずbashで潜ってyumとかやってみよう。ということで
$ docker run -it public.ecr.aws/lambda/provided:al2.2022.11.15.11 bash
Unable to find image 'public.ecr.aws/lambda/provided:al2.2022.11.15.11' locally
al2.2022.11.15.11: Pulling from lambda/provided
625fcf1e5e5f: Pull complete
8df5433d9fb6: Pull complete
bae685131515: Pull complete
94130148b8a0: Pull complete
Digest: sha256:023dd5403feaffcb92f8585c664444111723cf89ca42b0ebbec50a7acd080581
Status: Downloaded newer image for public.ecr.aws/lambda/provided:al2.2022.11.15.11
23 Nov 2022 03:30:25,642 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
^C23 Nov 2022 03:30:43,926 [INFO] (rapid) Received signal signal=interrupt
23 Nov 2022 03:30:43,926 [INFO] (rapid) Shutting down...
23 Nov 2022 03:30:43,926 [WARNING] (rapid) Reset initiated: SandboxTerminated
ん?
ってこれENTRYPOINT入ってんじゃん、雑にbashで入れないわ。
※途中でCtrl-Cで抜けた
でECR Publicのページをよく見ると、Usageのところにちゃんと触り方書いてある。ごめんなさい。
考え直してentrypoint上書きで入った。
$ docker run -it --entrypoint bash public.ecr.aws/lambda/provided:al2.2022.11.15
.11
bash-4.2# pwd
/var/task
bash-4.2# cat /etc/os-release
NAME="Amazon Linux"
VERSION="2"
ID="amzn"
ID_LIKE="centos rhel fedora"
VERSION_ID="2"
PRETTY_NAME="Amazon Linux 2"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2"
HOME_URL="https://amazonlinux.com/"
VARIANT_ID="202211081638-2.0.979.0"
ヨシ!
ちなみに、カスタムで入れるべきbootstrapやfunction.shはデフォルトでは存在しないっぽい。
entrypointっぽいファイルとして/lambda-entrypoint.sh
があるので覗くと
bash-4.2# cat /lambda-entrypoint.sh
#!/bin/sh
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
if [ $# -ne 1 ]; then
echo "entrypoint requires the handler name to be the first argument" 1>&2
exit 142
fi
export _HANDLER="$1"
RUNTIME_ENTRYPOINT=/var/runtime/bootstrap
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
exec /usr/local/bin/aws-lambda-rie $RUNTIME_ENTRYPOINT
else
exec $RUNTIME_ENTRYPOINT
fi
bootstrapがなければ/usr/local/bin/aws-lambda-rie
をexecするらしい。ちなみにこのファイルはなんかバイナリだった。
めっちゃ見間違えた、ifでチェックしてるのはAWS_LAMBDA_RUNTIME_API
であってbootstrapではなかった。実lambda環境で動いてたらbootstrapをそのまま動かす、そうじゃなければ(ローカルのdockerなどでは)aws-lambda-rieラッパーをかますということっぽい。
bashはできたので、必要そうなパッケージの所在を確認する。
欲しいのは
- mysqldump
- gzip
- awscli
かな。
手始めにgzipから
bash-4.2# which gzip
bash: which: command not found
bash-4.2# gzip
bash: gzip: command not found
bash-4.2# yum search gzip
Loaded plugins: ovl
=========================================== N/S matched: gzip ============================================
gzip.x86_64 : The GNU data compression program
pigz.x86_64 : Parallel implementation of gzip
Name and summary matches only, use "search all" for everything.
whichがなかった...けどgzipの入手はok
次はawscli
bash-4.2# yum search awscli
Loaded plugins: ovl
========================================== N/S matched: awscli ===========================================
awscli.noarch : Universal Command Line Environment for AWS
Name and summary matches only, use "search all" for everything.
bash-4.2# yum info awscli
Loaded plugins: ovl
Available Packages
Name : awscli
Arch : noarch
Version : 1.18.147
Release : 1.amzn2.0.1
Size : 2.1 M
Repo : amzn2-core/2/x86_64
Summary : Universal Command Line Environment for AWS
URL : http://aws.amazon.com/cli
License : ASL 2.0 and MIT
Description : This package provides a unified
: command line interface to Amazon Web Services.
awscliの1系が入るらしい。まぁ大したことやらんので1系でいいです。
最後に大本命mysqldump
bash-4.2# yum search mysql
Loaded plugins: ovl
=========================================== N/S matched: mysql ===========================================
MySQL-python.x86_64 : An interface to MySQL
apr-util-mysql.x86_64 : APR utility library MySQL DBD driver
dovecot-mysql.x86_64 : MySQL back end for dovecot
freeradius-mysql.x86_64 : MySQL support for freeradius
libdbi-dbd-mysql.x86_64 : MySQL plugin for libdbi
mysql-connector-java.noarch : Official JDBC driver for MySQL
mysql-connector-odbc.x86_64 : ODBC driver for MySQL
pcp-pmda-mysql.x86_64 : Performance Co-Pilot (PCP) metrics for MySQL
perl-DBD-MySQL.x86_64 : A MySQL interface for Perl
php-mysql.x86_64 : A module for PHP applications that use MySQL databases
php-mysqlnd.x86_64 : A module for PHP applications that use MySQL databases
qt-mysql.x86_64 : MySQL driver for Qt's SQL classes
qt-mysql.i686 : MySQL driver for Qt's SQL classes
qt3-MySQL.x86_64 : MySQL drivers for Qt 3's SQL classes
qt3-MySQL.i686 : MySQL drivers for Qt 3's SQL classes
qt5-qtbase-mysql.x86_64 : MySQL driver for Qt5's SQL classes
qt5-qtbase-mysql.i686 : MySQL driver for Qt5's SQL classes
redland-mysql.x86_64 : MySQL storage support for Redland
rsyslog-mysql.x86_64 : MySQL support for rsyslog
mariadb.x86_64 : A community developed branch of MySQL
mariadb-devel.x86_64 : Files for development of MariaDB/MySQL applications
mariadb-libs.x86_64 : The shared libraries required for MariaDB/MySQL clients
mariadb-libs.i686 : The shared libraries required for MariaDB/MySQL clients
Name and summary matches only, use "search all" for everything.
そういやmysqlはデフォルトではいないんだった。厳密にmysqlにしたければそれでもいいけど、正直dumpだけならmariadbのでいい気がする。
というわけで
bash-4.2# yum search mariadb
Loaded plugins: ovl
========================================== N/S matched: mariadb ==========================================
mariadb-bench.x86_64 : MariaDB benchmark scripts and data
mariadb-devel.x86_64 : Files for development of MariaDB/MySQL applications
mariadb-embedded.x86_64 : MariaDB as an embeddable library
mariadb-embedded-devel.x86_64 : Development files for MariaDB as an embeddable library
mariadb-libs.x86_64 : The shared libraries required for MariaDB/MySQL clients
mariadb-libs.i686 : The shared libraries required for MariaDB/MySQL clients
mariadb-server.x86_64 : The MariaDB server and related files
mariadb.x86_64 : A community developed branch of MySQL
mariadb-test.x86_64 : The test suite distributed with MariaD
Name and summary matches only, use "search all" for everything.
どれだろ。
パッケージで何が入るか知りたいので、下記を参考にrepoquery
を手配する。
bash-4.2# repoquery --list mariadb-devel | grep mysqldump
bash-4.2# repoquery --list mariadb-libs | grep mysqldump
bash-4.2# repoquery --list mariadb-server | grep mysqldump
/usr/bin/mysqldumpslow
/usr/share/man/man1/mysqldumpslow.1.gz
bash-4.2# repoquery --list mariadb | grep mysqldump
/usr/bin/mysqldump
/usr/share/man/man1/mysqldump.1.gz
普通にmariadb
のところに入ってるらしい。
てかmysqldumpslowって知らないな。調べると「スロークエリーログファイルの要約」とのこと。そうなんだ。豆知識。普通にログファイル見たことしかなかったわ。
ローカル環境を作ろうと思ったら、dockerイメージの探検で終わってしまった。
今度こそローカル環境を。
Usageの説明にあるbootstrapやfunction.shについては下記チュートリアルにサンプルがある。
まずは考えることをやめてサンプルを忠実にコピペする。
#!/bin/sh
set -euo pipefail
# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
# Processing
while true
do
HEADERS="$(mktemp)"
# Get an event. The HTTP request will block until one is received
EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
# Extract request ID by scraping response headers received above
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
# Run the handler function from the script
RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
# Send the response
curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE"
done
function handler () {
EVENT_DATA=$1
echo "$EVENT_DATA" 1>&2;
RESPONSE="Echoing request: '$EVENT_DATA'"
echo $RESPONSE
}
FROM public.ecr.aws/lambda/provided:al2
# Copy custom runtime bootstrap
COPY bootstrap ${LAMBDA_RUNTIME_DIR}
# Copy function code
COPY function.sh ${LAMBDA_TASK_ROOT}
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "function.handler" ]
とファイルを用意して
$ docker build -t lambda-local .
(出力略)
$ docker run -p 9000:8080 lambda-local
23 Nov 2022 04:31:54,086 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
で別shellから
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations"
-d '{"payload":"hello world!"}'
とすると、docker runしてたやつに出力が出る
$ docker run -p 9000:8080 lambda-local
23 Nov 2022 04:31:54,086 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
23 Nov 2022 04:32:51,598 [INFO] (rapid) extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory
23 Nov 2022 04:32:51,598 [WARNING] (rapid) Cannot list external agents error=open /opt/extensions: no such file or directory
START RequestId: 4641b1fe-fc97-438d-b846-f5098c5b682f Version: $LATEST
23 Nov 2022 04:32:51,598 [WARNING] (rapid) First fatal error stored in appctx: Runtime.InvalidEntrypoint
23 Nov 2022 04:32:51,598 [ERROR] (rapid) Init failed error=fork/exec /var/runtime/bootstrap: permission denied InvokeID=
23 Nov 2022 04:32:51,599 [WARNING] (rapid) Reset initiated: ReserveFail
23 Nov 2022 04:32:51,599 [WARNING] (rapid) Cannot list external agents error=open /opt/extensions: no such file or directory
23 Nov 2022 04:32:51,599 [WARNING] (rapid) First fatal error stored in appctx: Runtime.InvalidEntrypoint
ヨシ...いやまて、ヨシではない、エラーしている。
Init failed error=fork/exec /var/runtime/bootstrap: permission denied InvokeID=
ごめんなさい。
chmod +x bootstrap
してビルドからやりなおすと
$ docker run -p 9000:8080 lambda-local
23 Nov 2022 04:34:52,088 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
23 Nov 2022 04:34:53,583 [INFO] (rapid) extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory
23 Nov 2022 04:34:53,583 [WARNING] (rapid) Cannot list external agents error=open /opt/extensions: no such file or directory
START RequestId: 8958d653-4fd5-4083-a4a7-fb03400dbc25 Version: $LATEST
{"payload":"hello world!"}
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 61 100 16 100 45 22598 63559 --:--:-- --:--:-- --:--:-- 61000
{"status":"OK"}
END RequestId: 8958d653-4fd5-4083-a4a7-fb03400dbc25
REPORT RequestId: 8958d653-4fd5-4083-a4a7-fb03400dbc25 Init Duration: 0.28 ms Duration: 23.11 ms Billed Duration: 24 ms Memory Size: 3008 MB Max Memory Used: 3008 MB
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations"
-d '{"payload":"hello world!"}'
Echoing request: '{"payload":"hello world!"}'
よさそう。なんかno such file or directory
が見えるけど、それ以外の出力は見慣れたlambdaなので大丈夫なのでしょう。
ここからいい感じにスクリプトをmountして更新されるようにしたり、curlを楽に発火できるように整えてもいいのだけど、今回作りたいのはあんまり試行錯誤の無いやつなので、そこの整備は我慢するやらない。
都度ビルドしてrunしてcurlすることにしよう。
結局作ってしまった...
まぁmysqldumpのテストにmysqlもほしいからね。仕方ないね。
version: "3"
services:
lambda:
image: public.ecr.aws/lambda/provided:al2.2022.11.15.11
command: function.handler
volumes:
- ./runtime:/var/runtime
- ./task:/var/task
invoker:
image: curlimages/curl
command: sh -c 'curl -s -XPOST "$$URL" -d "$$DATA"; echo ""'
environment:
- URL=http://lambda:8080/2015-03-31/functions/function/invocations
- DATA={"payload":"hello world!"}
depends_on:
- lambda
db:
image: mysql:8.0
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
- db:/var/lib/mysql
volumes:
db:
Dockerfileは無し、bootstrapとfunction.shをそれぞれ./runtime
と./task
ディレクトリに移した。
これでdocker compose up
すればそれぞれ実行されてログが出る。現実的にはdbがうるさいのでdocker compose up --attach lambda --attach invoker
となる。なんかいつからかlogging: driver: none
が動かなくなってた。
ちなみにdocker compose start invoker
すれば再実行できるけど、lambda側のコードは再読み込みされないので、現実的にはlambdaも再起動することになりそう。
またcurlコマンドの指定が変なことになっているのは、curlのレスポンスが末尾改行なしなのでdocker composeのログにflushされない問題の回避。
サクッと動作確認する。
まずbootstrapでコマンド群を入れる
# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
+yum install -y gzip awscli mariadb >/dev/null
+
# Processing
while true
devnullしてるのはdocker composeでログが鬱陶しいから。試行錯誤が終わったら消す予定。
で、docker compose upしてlambdaコンテナにbashで入り、疎通テストがてらmysqladmin ping -h db -u root
すると
bash-4.2# mysqladmin ping -h db -u root
mysqladmin: connect to server at 'db' failed
error: 'Authentication plugin 'caching_sha2_password' cannot be loaded: /usr/lib64/mysql/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory'
どうやらmysql8からのデフォルト設定により、雑につなぐにはひと工夫いるようだ。mysql8は実運用したことなくて知らなかった。
db:
image: mysql:8.0
+ command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
これでok
bash-4.2# mysqladmin ping -h db -u root
mysqld is alive
次はawscliを動かす。
正直docker compose環境で ~/.aws/
をねじ込むのはアレなので、環境変数で渡すことにする。
手元にaws sts assume-role
してsecretsを環境変数に展開するスクリプトがあるので、これを使う想定で
lambda:
image: public.ecr.aws/lambda/provided:al2.2022.11.15.11
command: function.handler
volumes:
- ./runtime:/var/runtime
- ./task:/var/task
+ environment:
+ - AWS_ACCESS_KEY_ID
+ - AWS_SECRET_ACCESS_KEY
+ - AWS_SESSION_TOKEN
こう。MFA強制のIAM Role運用をしていてterraformとかしてる人にはお馴染みのやつな気がする。
で、bashで入ってaws sts get-caller-identity
したらいい感じに通ってた。ヨシ!
ところでbootstrapのコードを見るとlambdaのアーキテクチャがなんとなく見える。
「イベント」をランタイムサーバ?へのロングポーリングで待ち受け、イベントが来たらハンドラを呼び出し、結果をランタイムサーバにHTTPで送り返す。つまりインスタンス的な成分はハンドラを実行していなくてもしばらく起動しっぱなしということになる。
実際、下記ページにあるライフサイクルを見るとそうなっていることがわかる。
Shutdown: このフェーズは、Lambda 関数が一定期間呼び出しを受け入れないと、トリガーされます。
とのことなので、実行間隔が空いたときにいわゆる「コールドスタート」になるのはShutdownされてInitが走っているとき、ということか。なるほどなー。おもしろい。
ローカル環境ができたので、本命のスクリプトを書いていく。毎度のことながら環境構築をエンジョイしすぎた
まず前提環境として、dumpされるDBの中身と、dumpがアップロードされるS3バケットを用意する。
S3は適当にAWSコンソールから作る。名前は sbox-lambda-simple-mysqldump-s3
にした。設定もデフォルト。なんでもいい。
DBの中身は適当にググるとそれっぽい記事があったので、そのまま使わせてもらう。
ぶっちゃけ1行でもレコードあればよいので、適当にinsertして32レコードほど作成。中身は本当になんでもいい。
一応ここでmysqldumpのコマンドを試しておく。lambdaのコンテナにbashで入り
bash-4.2# mysqldump -u root -h db db
-- MySQL dump 10.14 Distrib 5.5.68-MariaDB, for Linux (x86_64)
--
-- Host: db Database: db
-- ------------------------------------------------------
-- Server version 8.0.31
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
...
よし。ちなみにcreate database db;
としたのでコマンドの最後がdb db
となっているが、正しい。
mysqldumpして圧縮してs3に投げつけるやつは、とてもシンプルにやってる記事があったので参考にする。
ファイルに吐かずにawscliのstdinに投げるのは考えたことなかった。まぁそれでいいな。
これを見つつ、それっぽくfunction.shを書く。
function handler () {
filename=$(date '+%Y-%m-%d_%H%M%S').sql.gz
mysqldump -u root -h db db | \
gzip | \
aws s3 cp - s3://sbox-lambda-simple-mysqldump-s3/dump/$filename
echo $filename
}
docker composeを起動し、lambdaを発火させると
ヨシ!
良いんだけど、なんらかdumpに失敗したときに気付けない。失敗するとどうなるかというと、スクショの1つ目のファイルみたいなのが出来上がる。これは一応対処したい。
というのと、DB接続情報を直接貼っているのはなんか考える必要がある。
失敗については、多分失敗扱いにはなっている。ゴミファイルは出来上がるけど、まぁ失敗検知さえできればあとは上の運用レイヤで考えればいいか。
接続情報については、ハンドラとしては環境変数で受けつつ、bootstrapではパラメータストアを読むようにしてみた。
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
+ MYSQL_ROOT_PASSWORD: password
volumes:
- db:/var/lib/mysql
function handler () {
filename=$(date '+%Y-%m-%d_%H%M%S').sql.gz
- mysqldump -u root -h db db | \
+ mysqldump -u $DBUSER -h $DBHOST $DBNAME -p$DBPASS | \
gzip | \
aws s3 cp - s3://sbox-lambda-simple-mysqldump-s3/dump/$filename
yum install -y gzip awscli mariadb >/dev/null
+function getparam() {
+ aws ssm get-parameter --with-decryption --region ap-northeast-1 --name $1 --query 'Parameter.Value' --output text
+}
+export DBUSER=$(getparam sbox-mysqldump-dbuser)
+export DBHOST=$(getparam sbox-mysqldump-dbhost)
+export DBNAME=$(getparam sbox-mysqldump-dbname)
+export DBPASS=$(getparam sbox-mysqldump-dbpass)
+
# Processing
while true
do
これでもちゃんと動いた。ヨシ!
ハンドラとしては環境変数を読めば良いだけなので、パラメータストアを使うつもりがなければlambdaの環境変数に直接入れてもいいはず。
ところで、当初の動機に
後者はまぁいいっちゃいいんだけど、そもそもshellだと5行くらいで終わるのにやたらコードを書かされている印象で、なんだかなぁ。と思っている。
ということでもっとスッキリした感じでやりたい
とあるんだけど、bootstrapは結構ごちゃごちゃ書いてあるんだよな...半分以上テンプレとはいえ。
でも処理本体はパイプ繋がりの1行(?)で終わってるし、まぁいいか。
あとはAWS上の実環境で動作を試していく。
やることは
- lambdaを作り、コードをデプロイする
- 実行ロールも作らないといけない。S3とSSMとKMSかな
- Aurora serverless v1でも作り、実行してみる
まずはDBを手配。scale to zeroな仕様でお砂場テストに使いやすいAurora serverless v1を使う。
※使ったことないけど...
AWSコンソールからポチポチしていくが、右に出ているトピックサイドバー的なやつを見ると、serverless v1対応のエンジンバージョンはmysql5.7系しかない。最初何も考えずに最新にしたらv2しか選択できなかった。
仕方ないのでv1対応最新の 2.07.1
にする。ローカル環境はノリでちょっと苦労しながらmysql8で作ったのに...まぁいいか。
設定はv1にすること以外はだいたいデフォルト。 クラスターがアイドル状態のときに容量を 0 ACU にスケールする
を有効にしておくくらい。
おぉ、セキュリティグループはとりあえず新規に作っていじろうと思って「新規作成」にしたら、自動でweb閲覧元のグローバルIPから3306が通るような設定で作られた。ありがたい。
手元から初期データ作成のクエリ打とうと思ってたんだ。
ということを見ながら作成完了を待つ。RDSではあるのでそこそこ時間かかるっぽい。
セキュリティグループがいい感じだったので勘違いしてたけど、冷静に考えてパブリックIPなかった。
先にlambdaをセットアップしてそこからやろう。
なおインスタンスのreadyは2~3分ってところだった。
まずハコのlambdaをAWSコンソールで適当に作る。ランタイムをカスタムのamzn2にするのと、VPCの有効化をする。それ以外はデフォルト。
カスタムランタイムで作成すると、見慣れたエディタ画面に bootstrap.sample
と hello.sh.sample
というファイルができていた。これをリネームして編集すればGUIでテストできそう。
手元に動く(はず)のコードはあるので、それをコピペする。
bootstrapはそのままコピペとして、functionは先にテストデータを入れる必要があるので、dumpは動かない。
のでまず疎通テストで下記。
function handler () {
mysqladmin ping -u $DBUSER -h $DBHOST -p$DBPASS
}
あとSSMのパラメータはAuroraのインスタンスに合わせて調整。
で実行すると
Function Logs
/var/task/bootstrap: line 10: yum: command not found
/var/task/bootstrap: line 10: yum: command not found
START RequestId: f0df7134-0df7-4e69-954d-26e015d78fce Version: $LATEST
RequestId: f0df7134-0df7-4e69-954d-26e015d78fce Error: Runtime exited with error: exit status 127
Runtime.ExitError
そんなあ。
調べた感じ、たぶんlambdaのこのイメージにyumは無い。いや無いという情報な無いのだけど、誰もやってないのである気配が無い。
いや公式に提供してるdockerイメージにあるからあると思ったんだけど...普通に別物じゃん...それベースイメージと呼ばないんだけど...
仕方ないのでyumはrun timeでは使わない方向を模索。
ダメだった
lambdaでyumを使えない前提のデプロイパックの生成を模索
色々試したけど全くもってシンプルにならないことがわかった。とりあえず記録だけ残す。
まずyumを使わない成果物の生成は以下のようにできる。
FROM public.ecr.aws/lambda/provided:al2.2022.11.15.11 as build
RUN yum install -y gzip mariadb unzip
ADD https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip /opt/awscli.zip
RUN cd /opt && unzip awscli.zip
FROM public.ecr.aws/lambda/provided:al2.2022.11.15.11
COPY /usr/bin/gzip /usr/bin/mysqldump /usr/local/bin/
COPY /opt/aws /opt/aws
RUN ln -s /opt/aws/dist/aws /usr/local/bin/
dockerになっているが、要するにビルド用イメージでインストールした成果物をコピーしただけ。これならランタイムではyumをしなくて済む。
で、デプロイ用のzipは下記のようにして作れる。
#!/bin/bash
set -eu
rm -rf dist
mkdir dist
cp runtime/bootstrap task/function.sh dist/
tag=$(docker build --quiet --target build . | cut -d: -f2)
container=$(docker create $tag)
docker cp $container:/usr/bin/gzip dist/gzip
docker cp $container:/usr/bin/mysqldump dist/mysqldump
docker cp $container:/opt/aws dist/aws
docker rm $container
cd dist
zip -qr ../function.zip ./
ちなみにawscliはランタイムでpipを入れてインストールする手がある。
が、どうせビルドタイムで他のコマンドを用意するのであれば、全部ビルドタイムでやってしまおうという観点でのawscli v2の採用。
yumならともかく、そうでもないホストのスクリプトなインストーラを都度実行するのは信頼性の観点からやりたくない。
前述のスクリプトで必要なものは揃うが、気になるポイントがいくつか。
- lambda実行はほとんどのディレクトリはread-only
- 実行ファイルのsymlinkを貼りたいが、cwdにもbinにも貼れない。特にawscliは毎回
./aws/dist/aws
みたいにしないと実行できない
- 実行ファイルのsymlinkを貼りたいが、cwdにもbinにも貼れない。特にawscliは毎回
- awscliのdistがデカく、コンソールからzipをアップロードする際のサイズ制限(50MB)に対してかなりギリギリ(48MBくらい)
それぞれそこまでの障害ではないのだが、既に面倒なビルド・デプロイフローを組んでおり、カオス度が高い。
もともと「ネットに転がってるやつよりもっとシンプルに実現できないか」が焦点であり、既にシンプルさを達成できてない。
というわけでこの方向は不採用。
今回はコードをシンプルにすることを目標に置くので不採用だが、コードを書いてでもインフラ側のシンプルさを取りたいのであれば、aws s3 cp
相当をgoなりrustなりで実装すれば気になりは削減できてアリかもしれない。
カスタムランタイムのdocker image版でいってみる。
一応、できればlambda起動時にpullするイメージのサイズは最小化したい。
下記記事によると、lambdaのベースイメージ的なやつはキャッシュされているようなので、ベースは(alpineなどより)そっちのほうがよいみたい。
というわけで、その上でパターンをいくつか試してサイズを見る。
まずビルドイメージから必要なやつだけ取り出してくるパターン。
FROM public.ecr.aws/lambda/provided:al2 as build
RUN yum install -y gzip mariadb unzip
ADD https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip /opt/awscli.zip
RUN cd /opt && unzip -q awscli.zip
FROM public.ecr.aws/lambda/provided:al2
COPY /usr/bin/gzip /usr/bin/mysqldump /usr/local/bin/
COPY /opt/aws /opt/aws
RUN ln -s /opt/aws/dist/aws /usr/local/bin/
レイヤのリスト
$ docker history lambda-local
IMAGE CREATED CREATED BY SIZE COMMENT
3b5f2bd5f2ad About an hour ago /bin/sh -c ln -s /opt/aws/dist/aws /usr/loca… 17B
d32fee162833 About an hour ago /bin/sh -c #(nop) COPY dir:71e7a6526e5cb2598… 168MB
028557407f88 About an hour ago /bin/sh -c #(nop) COPY multi:b3e5c2b0ab7627b… 3.27MB
bdd84b649c1d 11 days ago ENTRYPOINT [ "/lambda-entrypoint.sh" ] 0B
...
続いてシンプルにyumで全部入れるパターン
FROM public.ecr.aws/lambda/provided:al2
RUN yum install -y gzip mariadb awscli && \
yum clean all && rm -rf /var/cache/yum
$ docker history lambda-local
IMAGE CREATED CREATED BY SIZE COMMENT
a0c7ad338830 3 seconds ago /bin/sh -c yum install -y gzip mariadb awscl… 250MB
bdd84b649c1d 11 days ago ENTRYPOINT [ "/lambda-entrypoint.sh" ] 0B
...
前者のほうがだいぶ小さい。やっぱawscliをパッケージで入れるとpythonシリーズがゴロッと含まれてしまうからかな。
前者のやつで適当にECRにpushする。適当にリポジトリを作り、「プッシュコマンドを表示」のところを見ながらビルドしてpushする。
ECR上でのサイズが153.77MBとなっている。ここに表示されるのは確かストレージ使用量で圧縮済サイズだったはずだが、それにしてもローカルで見えるサイズ(480MB)と比べて小さすぎる。
ここにもAWSのベースイメージはキャッシュされてる的な成分が関係してるんだろうか。
lambda関数は作り直す。古いのを削除して作り直すが、まぁコンソールに従って入れる。
イメージはタグなどではなくshaを参照しているもよう。
また、作成画面ではVPC設置にできないので、設置後に設定する。
テストすると何をやってもタイムアウトする。エラーではなく。
で、print debugによるとタイムアウトしているのはawscli。ここで思い至ったのはVPC内からだとssmやらs3やらに直接アクセスできないのではないかという点。
VPCエンドポイントを作ってやってみる
エンドポイントだとssmとs3と...あとkmsっているのかな...とめんどくさいのでNATゲートウェイにした。
ひとまずパラメータ取得は乗り越えた
そしてそのままコードをもとに戻したら通った。Auroraの初期データ投入は面倒なのでEC2立てた(lambdaのイメージはmysqldumpしかない)
権限周りは少々あったが、特に難しいことはなく成功した。
長かった...