Movable Type Advent Calendar 2020 12日目です。
つい先日、AWS LambdaがDockerコンテナをサポートしました。
これはアツい!ということでMTを(途中まで)動かしてみた、という話です。
Movable Type Serverless
最終的にはこんなイメージでいます。
MTの静的出力という特徴を活かし、Webサーバーだけ常時起動させますが、MTは普段存在すらしておらず、編集者が使うときだけ起動します。
- 夜中や休日など編集者が使わない時間はコンピューティングコスト節約!
- 利用者が増えたらRDSやEFSの限界まで事実上無限に性能拡張!
- もし再構築を並列で実行できたら時間を圧倒的に短縮!
なにこれすごい!
とりあえずログインできるところまでやってみた
今回はRDSと連携してログインできるところまでやってみました。
- EFSとの連携以降はやってません。
- セキュリティ考慮していません。
- 日本語だと文字化けしたのでとりあえず英語で動かします。
とまあ、PoCレベルなので誰か遺志を継いでください。
手順1 RDSインスタンスを起動
RDSにMT用のMySQLデータベースインスタンスとユーザーを作成します。
一時的な実験なのでセキュリティは考慮していません! 随時読み替えて適切に設定してください。
create user 'mt_serverless'@'%' identified by '(データベースのパスワード)';
grant all on mt.* to 'mt_serverless'@'%' with grant option;
flush privileges;
手順2 MTのプログラム一式を展開する
ここからはRDSに接続可能な環境で作業します。
開発者ライセンスでMTを入手します。
手順3 mt-config.cgi
MTを展開したディレクトリ直下に設定ファイルmt-config.cgi
を作成します。
※ 日本語が文字化けしちゃったので英語UIに逃げます。
CGIPath /mt/
StaticWebPath /mt/mt-static/
StaticFilePath /app/mt-static
ObjectDriver DBI::mysql
Database mt
DBUser mt_serverless
DBPassword (データベースのパスワード)
DBHost (RDSインスタンスのホスト名)
EmailAddressMain (管理者メールアドレス)
DefaultLanguage en
ImageDriver Imager
手順4 Dockerfile
ひとまずplack/PSGIで動かすため、MTを展開したディレクトリ直下にこんなDockerfile
を作ります。
個人的に慣れてるUbuntuを使っていますが、ディストリビューションはお好きにどうぞ。
FROM ubuntu:20.04
RUN apt-get update \
&& apt-get install -y perl cpanminus build-essential \
libdbi-perl libdbd-mysql-perl \
libimager-perl libxmlrpc-lite-perl \
libplack-perl libcgi-psgi-perl \
&& cpanm XMLRPC::Transport::HTTP::Plack
RUN cpanm AWS::Lambda
RUN apt-get -y remove build-essential \
&& apt-get -y clean \
&& apt-get -y autoremove \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
ADD . /app
WORKDIR /app
CMD [ "plackup", "mt.psgi" ]
ここでインストールしているナイスなモジュールAWS::Lambda
が、Lambda - Dockerコンテナ - PSGIアプリケーションとしてのMT
を繋いでくれます。
手順5 PSGI版で動作確認兼DBセットアップ
docker build -t mt-serverless .
docker run -it --rm -p 5000:5000 mt-serverless
ブラウザから、http://localhost:5000/mt/mt.cgi
(localhostは作業環境に合わせて)にアクセスすると、セットアップウィザードが開始されます。
文字化けの問題で、Language
はEnglish
でひとまず進めてください。
データベースのセットアップができたらOKです。
手順6 コンテナ用エントリーポイント bootstrap
コンテナサポート ≒ カスタムランタイム
AWS Lambdaのコンテナサポートは、2年前にリリースされたカスタムランタイム
のプロトコルを踏襲しています。
カスタムランタイムはHTTPによりLambdaサービスと連携しますが、コンテナも同様です。なのでコンテナの情報はまだ少ないのですが、カスタムランタイムについて調べると大抵の問題は解決できます。
この試みでもAWS Lambdaカスタムランタイム用のモジュールAWS::Lambda
を大いに活用します。
MTのディレクトリ直下にbootstrap
を作成し、実行権限を付与します。
#!/usr/bin/perl
use strict;
use AWS::Lambda::Bootstrap;
use File::Basename;
# Lambdaサービスから渡されそうだが…
$ENV{'LAMBDA_TASK_ROOT'} = dirname(__FILE__);
# Lambdaにハンドラの入力欄がないからこちらは来ないかも?
# AWS::Lambda向けに固定
$ENV{'_HANDLER'} = 'mt-lambda.handler';
my $bootstrap = AWS::Lambda::Bootstrap->new;
$bootstrap->handle_events;
カスタムランタイムと違い、名前はbootstrap
でなくてもよいのですが慣習に沿います。
chmod +x bootstrap
手順7 Lambdaコンテナ用MTエントリーポイント mt-lambda.pl
次にbootstrap
から呼び出されるMTのエントリーポイントmt-lambda.pl
を作成します。
use strict;
use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/lib" : 'lib';
use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/extlib" : 'extlib';
use MT::PSGI;
use AWS::Lambda::PSGI;
my $app = MT::PSGI->new()->to_app();
my $func = AWS::Lambda::PSGI->wrap($app);
sub handler {
my $payload = shift;
return $func->($payload);
}
1;
このファイル名とsub handler
の名称は、下記のようにbootstrap
の記述と関連しているので変更する場合は併せて行う必要があります。
$ENV{'_HANDLER'} = 'mt-lambda.handler';
# mt-lambda → mt-lambda.pl
# handler → sub handler
最初はbootstrap
ファイル内ですべて完結させる予定でしたが、AWS::Lambda
モジュールの実装によりそれが難しかったのでbootstrap
とmt-lambda.pl
に分かれています。
手順8 ECRにプッシュ
ECR上のリポジトリにイメージをプッシュします。細かい説明は省略します。
docker tag mt-serverless (固有のID).dkr.ecr.ap-northeast-1.amazonaws.com/mt-serverless:latest
docker push (固有のID).dkr.ecr.ap-northeast-1.amazonaws.com/mt-serverless:latest
手順9 Lambdaを作成
コンテナイメージを元にLambda関数を作成します。
今のコンテナイメージはplackup
でPSGI版のMTを起動するので、CMD
にLabmda用のエントリポイント/app/bootstrap
を指定します。
作成後にLambdaのメモリとタイムアウトを変更します。
適切な設定は要検討ですが、とりあえずタイムアウト3秒は短いので30秒にしました。
手順10 Elastic Load Balancerの作成
やっと終わりが見えてきました…
Application Load Balancerを作成してLambda関数を割り当てます。
- Application Load Balancer (HTTP/HTTPS) を作成します。
- リスナーは必要に応じてHTTPSを追加してください。
- サブネットはふたつ必要です。
次に証明書とセキュリティグループを選択します。
セキュリティグループは当然、ポート80と443でのインバウンド接続を許可していください。
新しいターゲットグループとして、種類=Lambda関数を選択します。
最後に作成したLambda関数を選択します。
ついにMovable Type Serverlessにアクセス
DNSの反映に少し時間がかかるようですが、ブラウザで次のURLを開くと…
http://(ロードバランサーのDNS名)/mt/mt.cgi
き…きたー! MTのログイン画面!
セットアップ時に登録したユーザーでログインすると…
ダッシュボードだー!
しかも意外と動作が軽快。
PSGIをインターフェースとして使っていますが、ライフサイクルとしてはリクエストごとにプロセスが発生するので、挙動としてはCGIに近いはずです。もしかしたらLambdaサービスはPSGI的にプロセスがリクエスト間で使い回しているかもですが。
でもコンテナを読み込むオーバーヘッドは感じられず、実用性も期待できます。
続きを期待します
他の動作確認はしていません。そもそもEFSなどでファイルシステムを永続化しないとサイトも公開できません。
でも使った分だけ課金、並列爆速再構築など、期待が膨らみますね。
あなたがここまで読んだということは、私はすでに力尽きているだろう。ここから先のチャレンジは、あなたに道を譲ろうと思う。検討を祈る!