LoginSignup
3
5

More than 5 years have passed since last update.

AWS Lambda のCustom RuntimeでPHP用のLayer作成(API Gatewayを使わない)

Last updated at Posted at 2019-02-24

前置き

この記事が初投稿になります。
アウトプットする機会を増やすために投稿することにしました。

初投稿の記事は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

作業はLinuxサーバー内で行いました。
暇なときにMacOSでも試してみます。
DockerをいれたMacOSでもLayer作成とAWSへのアップロードできました。

手順

Stackery社のphp-lambda-layerをcloneします。

git clone https://github.com/stackery/php-lambda-layer.git

ファイルの変更箇所が非常におおいため、ファイルの中身を丸ごと載せます。

Makefile:

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をファイル名と関数にわけたものを環境変数で渡します。

bootstrap
#!/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のベース環境になるので、過不足ないようにします。

build.sh
#!/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にファイル名.関数名で記述できるようになります。

init.php
<?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作成

php72.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のソースファイルからのインストール

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5