前置き
この記事が初投稿になります。
アウトプットする機会を増やすために投稿することにしました。
初投稿の記事はAWS lambdaのCustom Runtime機能でPHPを扱うものになります。
PythonのようにHandlerとeventを扱いたいと思ったので、自分用にまとめた記事です。
API Gate Wayを使用したくない&Handlerでファイル名.関数を記述したい&phpファイルにmain();を書きたくない人向けです。
Stackery社のphp-lambad-layerを改造します。
pythonみたいにhandlerにファイル名.関数を記述すると、該当のファイルの関数が呼ばれるLayerにすることが目的です。
更新
2019/03/30 build.shでのphpのインストールをzipファイルからremiリポジトリでのインストールに変更しました。php7.3に変更しました。
作業環境
LayerのZip作成にdockerを、AWSへのアップロードにaws-cliを使います。
- pyenv
- 1.2.9-2
- pip
- 19.0.1(python2.7)
- aws-cli
- 1.16.96(python2.7.15)
- docker
- 19.09.2
- 19.09.2
作業はLinuxサーバー内で行いました。
暇なときにMacOSでも試してみます。
DockerをいれたMacOSでもLayer作成とAWSへのアップロードできました。
手順
Stackery社のphp-lambda-layerをcloneします。
git clone https://github.com/stackery/php-lambda-layer.git
ファイルの変更箇所が非常におおいため、ファイルの中身を丸ごと載せます。
Makefile:
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
php73.zip:
docker run --rm -v $(ROOT_DIR):/opt/layer lambci/lambda:build-nodejs8.10 /opt/layer/build.sh
bootstrap:
ビルトインサーバーで扱うファイルを変更し、不要な処理を全て削除します。
ビルトインサーバーとして起動するファイルに、handlerをファイル名と関数にわけたものを環境変数で渡します。
#!/opt/bin/php -c/opt/php.ini
<?php
error_reporting(E_ALL | E_STRICT);
$AWS_LAMBDA_RUNTIME_API = getenv('AWS_LAMBDA_RUNTIME_API');
$HTTP_METHOD = 'POST';
function start_webserver() {
$pid = pcntl_fork();
switch($pid) {
case -1:
die('Failed to fork webserver process');
case 0:
$HANDLER = getenv('_HANDLER');
$HANDLER_ARRAY = explode('.', $HANDLER);
$handler_function = $HANDLER_ARRAY["1"];
$handler_components = explode('/', $HANDLER_ARRAY["0"]);
$handler_path = implode('/', array_merge(['/var/task'], $handler_components));
$handler_path .= ".php";
putenv("handler_function=$handler_function");
putenv("handler_path=$handler_path");
exec("PHP_INI_SCAN_DIR=/opt/etc/php-7.3.d/:/var/task/php-7.3.d/ php -S localhost:8000 -c /var/task/php.ini -d extension_dir=/opt/lib/php/7.3/modules '/opt/init.php'");
exit;
// return the child pid to parent
default:
// Wait for child server to start
sleep(1);
return $pid;
}
}
start_webserver();
while (true) {
$ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/next");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
$invocation_id = '';
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$invocation_id) {
if (!preg_match('/:\s*/', $header)) {
return strlen($header);
}
[$name, $value] = preg_split('/:\s*/', $header, 2);
if (strtolower($name) == 'lambda-runtime-aws-request-id') {
$invocation_id = trim($value);
}
return strlen($header);
});
$body = '';
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$body) {
$body .= $chunk;
return strlen($chunk);
});
curl_exec($ch);
if (curl_error($ch)) {
die('Failed to fetch next Lambda invocation: ' . curl_error($ch) . "\n");
}
if ($invocation_id == '') {
die('Failed to determine Lambda invocation ID');
}
curl_close($ch);
$ch = curl_init("http://localhost:8000");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $HTTP_METHOD);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($body));
curl_setopt($ch, CURLOPT_READFUNCTION, function ($ch, $fd, $length) use ($body) {
return $body;
});
$response = array();
$response['body'] = '';
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$response) {
$response['body'] .= $chunk;
return strlen($chunk);
});
curl_exec($ch);
curl_close($ch);
$ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/$invocation_id/response");
echo $response['body'];
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $response['body']);
curl_exec($ch);
curl_close($ch);
}
?>
build.sh
PHPで必要なパッケージをインストールするようにbuildも変更します。
ここで作成するzipファイルの中身がCustom Runtimeのベース環境になるので、過不足ないようにします。
#!/bin/bash
echo "Building layer for PHP 7.3 - using Remi repository"
yum install -y wget
yum install -y yum-utils
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
wget https://rpms.remirepo.net/enterprise/remi-release-6.rpm
rpm -Uvh remi-release-6.rpm
rpm -Uvh epel-release-latest-6.noarch.rpm
yum-config-manager --enable remi-php73
yum install -y httpd
yum install -y postgresql-devel
yum install -y libargon2-devel
yum install -y --disablerepo="*" --enablerepo="remi,remi-php73" php php-mbstring php-pdo php-mysql php-pgsql
mkdir /tmp/layer
cd /tmp/layer
cp /opt/layer/bootstrap bootstrap
cp /opt/layer/init.php init.php
cp /opt/layer/php.ini php.ini
mkdir bin
cp /usr/bin/php bin/
mkdir lib
for lib in libncurses.so.5 libtinfo.so.5 libpcre.so.0; do
cp "/lib64/${lib}" lib/
done
cp /usr/lib64/libedit.so.0 lib/
cp /usr/lib64/libargon2.so.0 lib/
cp /usr/lib64/libpq.so.5 lib/
mkdir -p lib/php/7.3
cp -a /usr/lib64/php/modules lib/php/7.3/
zip -r /opt/layer/php73.zip .
init.php
ビルトインサーバーで使用するphpファイルを新規作成。
bootstrapから渡される環境変数をもとに、Lambda関数のphpファイルにある関数を呼びます。
bootstrapとinit.phpによってHandlerにファイル名.関数名で記述できるようになります。
<?php
$handler_path = getenv('handler_path');
$handler_function = getenv('handler_function');
require_once($handler_path);
$lambda_event_json = file_get_contents('php://input');
$lambda_event = json_decode($lambda_event_json, TRUE);
if(function_exists($handler_function))
{
$handler_function($lambda_event);
}
変更後、makeでzip作成
make php72.zip
Stackery社のMakefileをそのまま使用してAWSにアップロードしてもいいですが、今回は直接アップロードします。
(個人環境へのアップロードで、使用しているregionも1つならaws-cli直の方が安心かと)
aws lambda --profile <profile設定> publish-layer-version --layer-name <Layer名> --zip-file fileb://php72.zip --description "PHP7.2 Custom Runtime."
まとめ
Custom Runtime Layerの作成は以上になります。
今回はPHP7.2.12を使用していますが、公式からzipさえ落とせるのであれば、どのバージョンでも問題なさそうです。
変更した箇所のdiffを見ていただければわかりますが、APIではなく、Invoke起動などにも幅広く対応させています。
Lambda側に処理を書くことになりますが、CloudWatchのイベント、Invoke起動など全てに対応可能です。
API GateWayを使用する場合は素直にStackery社のものをそのまま使いましょう。
今回のLayerを用いたPHP関数と作成手順は、後日qiitaにあげます。
参考
Stackery/php-lambda-layer
PHP7.2が使えるLambdaのカスタムランタイム環境を作ってみた #reinvent
CentOS 7 PHP 7.2.5のソースファイルからのインストール