この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
サーバーレス開発部@大阪の岩田です。 この記事は AWS Lambda Custom Runtimes芸人 Advent Calendar 2018 の 17日目 です。
はじめに
これまでLambdaのCustom Runtimesを利用してPHPで遊んで来ました。
今回はPHPシリーズの仕上げとしてECサイトを構築できるOSSとして有名なEC-CUBEをLambdaで動かしてみます。 なお、heroku使えよという突っ込みは受け付けておりません。
最終的な成果物はGitHubで公開しているので、必要に応じて参照して下さい。
目標
EC-CUBEの全機能を動かすのはハードルが高いので、シンプルな購入フローが正常終了し、DBに受注データが生成されるまでを目標として設定しました。 HTTPSへの対応も行いません。
環境
今回構築する環境です
- EC-CUBE:4.0.1
- PHP:7.1
- カスタムランタイム:STACKERY社提供のレイヤーarn:aws:lambda:${AWS::Region}:887080169480:layer:php71:5をベースに使用
- DB:MySQL5.7(RDSを利用)
PHPのExtension用レイヤー作成
まずはEC-CUBEの動作に必要なPHPのExtensionを準備します。
build.shという名前で以下のシェルスクリプトを作成します。
build.sh
#!/bin/bash
yum install -y php71-pdo php71-intl php71-mysqlnd php71-mbstring
mkdir -p /tmp/layer/
cd /tmp/layer
mkdir -p lib/php/7.1/modules
for lib in intl.so pdo.so mysqlnd.so pdo_mysql.so mbstring.so; do
cp "/usr/lib64/php/7.1/modules/${lib}" lib/php/7.1/modules
done
zip -r /opt/layer/eccube_ext.zip .
STACKERY社推奨のDockerイメージを利用して上記シェルスクリプトを実行し、必要なExtensionをZIPに固めます。
docker run --rm -v $(PWD):/opt/layer lambci/lambda:build-nodejs8.10 /opt/layer/build.sh
ZIPが作成できたらレイヤーを作成します
aws lambda publish-layer-version --layer-name eccube_ext --zip-file fileb://eccube_ext.zip
bootstrap用のレイヤー作成
STACKERY社から提供されているレイヤーをそのまま利用するとEC-CUBEが正常に動作しないため、独自にレイヤーを作成してbootstrapを上書きます。
STACKERY社のGitHubリボジトリからbootstrapを取得し、修正していきます。
curlのリダイレクトを無効化
STACKERY社提供のbootstrapはレスポンスヘッダのLocationを再帰的に辿る仕様となっており、EC-CUBE側でリダイレクトをかけた時にERR_RESPONSE_HEADERS_MULTIPLE_LOCATION
が発生してしまいます。
Locationを辿るのはブラウザに任せるように該当ロジックをコメントアウトします。
//curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
HTTPメソッドがPOSTの場合に、バックエンドにデータを送信するように修正
STACKERY社提供のbootstrapはPOSTのリクエストを受けた際に、バックエンドのPHPビルトインサーバーにデータを流してくれません。(普通にバグってる気が・・・) データを流すように下記のコードを追加します。
このPOST対応のプルリクがマージされたので、バージョン6以後のレイヤーは修正無しでそのまま利用できそうです
if($event['httpMethod'] === 'POST'){
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
最終的なbootstrapのコードです
bootstrap
#!/opt/bin/php -c/opt/php.ini
<?php
error_reporting(E_ALL | E_STRICT);
$AWS_LAMBDA_RUNTIME_API = getenv('AWS_LAMBDA_RUNTIME_API');
/* https://gist.github.com/henriquemoody/6580488 */
$http_codes = [100=>'Continue',101=>'Switching Protocols',102=>'Processing',200=>'OK',201=>'Created',202=>'Accepted',203=>'Non-Authoritative Information',204=>'No Content',205=>'Reset Content',206=>'Partial Content',207=>'Multi-Status',208=>'Already Reported',226=>'IM Used',300=>'Multiple Choices',301=>'Moved Permanently',302=>'Found',303=>'See Other',304=>'Not Modified',305=>'Use Proxy',306=>'Switch Proxy',307=>'Temporary Redirect',308=>'Permanent Redirect',400=>'Bad Request',401=>'Unauthorized',402=>'Payment Required',403=>'Forbidden',404=>'Not Found',405=>'Method Not Allowed',406=>'Not Acceptable',407=>'Proxy Authentication Required',408=>'Request Timeout',409=>'Conflict',410=>'Gone',411=>'Length Required',412=>'Precondition Failed',413=>'Request Entity Too Large',414=>'Request-URI Too Long',415=>'Unsupported Media Type',416=>'Requested Range Not Satisfiable',417=>'Expectation Failed',418=>'I\'m a teapot',419=>'Authentication Timeout',420=>'Enhance Your Calm',420=>'Method Failure',422=>'Unprocessable Entity',423=>'Locked',424=>'Failed Dependency',424=>'Method Failure',425=>'Unordered Collection',426=>'Upgrade Required',428=>'Precondition Required',429=>'Too Many Requests',431=>'Request Header Fields Too Large',444=>'No Response',449=>'Retry With',450=>'Blocked by Windows Parental Controls',451=>'Redirect',451=>'Unavailable For Legal Reasons',494=>'Request Header Too Large',495=>'Cert Error',496=>'No Cert',497=>'HTTP to HTTPS',499=>'Client Closed Request',500=>'Internal Server Error',501=>'Not Implemented',502=>'Bad Gateway',503=>'Service Unavailable',504=>'Gateway Timeout',505=>'HTTP Version Not Supported',506=>'Variant Also Negotiates',507=>'Insufficient Storage',508=>'Loop Detected',509=>'Bandwidth Limit Exceeded',510=>'Not Extended',511=>'Network Authentication Required',598=>'Network read timeout error',599=>'Network connect timeout error'];
function start_webserver() {
$pid = pcntl_fork();
switch($pid) {
case -1:
die('Failed to fork webserver process');
case 0:
// exec the command
$HANDLER = getenv('_HANDLER');
chdir('/var/task');
exec("PHP_INI_SCAN_DIR=/opt/etc/php-7.1.d/:/var/task/php-7.1.d/ php -S localhost:8000 -c /var/task/php.ini -d extension_dir=/opt/lib/php/7.1/modules '$HANDLER'");
exit;
// return the child pid to parent
default:
// Wait for child server to start
sleep(1);
return $pid;
}
}
function fail($AWS_LAMBDA_RUNTIME_API, $invocation_id, $message) {
$ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/$invocation_id/response");
$response = array();
$response['statusCode'] = 500;
$response['body'] = $message;
$response_json = json_encode($response);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($response_json)
));
curl_exec($ch);
curl_close($ch);
}
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);
if (!$body) {
die("Empty Lambda invocation response\n");
}
$event = json_decode($body, TRUE);
if (!array_key_exists('requestContext', $event)) {
fail($AWS_LAMBDA_RUNTIME_API, $invocation_id, 'Event is not an API Gateway request');
continue;
}
$uri = $event['path'];
if (array_key_exists('multiValueQueryStringParameters', $event) && $event['multiValueQueryStringParameters']) {
$first = TRUE;
foreach ($event['multiValueQueryStringParameters'] as $name => $values) {
foreach ($values as $value) {
if ($first) {
$uri .= "?";
$first = FALSE;
} else {
$uri .= "&";
}
$uri .= $name;
if ($value != '') {
$uri .= '=' . $value;
}
}
}
}
$ch = curl_init("http://localhost:8000$uri");
//curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
if (array_key_exists('multiValueHeaders', $event)) {
$headers = array();
foreach ($event['multiValueHeaders'] as $name => $values) {
foreach ($values as $value) {
array_push($headers, "${name}: ${value}");
}
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $event['httpMethod']);
if (array_key_exists('body', $event)) {
$body = $event['body'];
if (array_key_exists('isBase64Encoded', $event) && $event['isBase64Encoded']) {
$body = base64_decode($body);
}
} else {
$body = '';
}
if (strlen($body) > 0) {
if($event['httpMethod'] === 'POST'){
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['multiValueHeaders'] = array();
$response['body'] = '';
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$response) {
if (preg_match('/HTTP\/1.1 (\d+) .*/', $header, $matches)) {
$response['statusCode'] = intval($matches[1]);
return strlen($header);
}
if (!preg_match('/:\s*/', $header)) {
return strlen($header);
}
[$name, $value] = preg_split('/:\s*/', $header, 2);
$name = trim($name);
$value = trim($value);
if ($name == '') {
return strlen($header);
}
if (!array_key_exists($name, $response['multiValueHeaders'])) {
$response['multiValueHeaders'][$name] = array();
}
array_push($response['multiValueHeaders'][$name], $value);
return strlen($header);
});
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");
$isALB = array_key_exists("elb", $event['requestContext']);
if ($isALB) { // Add Headers For ALB
$status = $response["statusCode"];
if (array_key_exists($status, $http_codes)) {
$response["statusDescription"] = "$status ". $http_codes[$status];
} else {
$response["statusDescription"] = "$status Unknown";
}
$response["isBase64Encoded"] = false;
}
$response_json = json_encode($response);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json);
if (!$isALB){
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($response_json)
));
}
curl_exec($ch);
curl_close($ch);
}
?>
bootstrap用のレイヤを作成します。
chmod +x bootstrap
zip bootstrap.zip bootstrap
aws lambda publish-layer-version --layer-name php71_bootstrap_ex --zip-file fileb://bootstrap.zip
このレイヤーをSTACKERY社提供のレイヤーより後からマージすることでbootstrapを上書きします。
EC-CUBEのソースを準備
次にEC-CUBEのソースを準備します。
git clone https://github.com/EC-CUBE/ec-cube
でソースコードを取得した後、README.mdを参考にローカル環境でライブラリの導入等EC-CUBEのインストール作業を完了させます。
ライブラリの導入
composer install --no-dev
初期設定
./bin/console e:i
インストールできたらLambda環境で動作するようにEC-CUBEのソースを修正していきます。
環境変数の取得方法を変更
STACKERY社提供のレイヤーではスーパーグローバル変数$_SERVER
からLambdaの環境変数が取得できないので、$_SERVER
を $_ENV
に置き換えていきます。
index.php
// The check is to ensure we don't use .env in production
-if (!isset($_SERVER['APP_ENV'])) {
+if (!isset($_ENV['APP_ENV'])) {
-$env = isset($_SERVER['APP_ENV']) ? $_SERVER['APP_ENV'] : 'dev';
-$debug = isset($_SERVER['APP_DEBUG']) ? $_SERVER['APP_DEBUG'] : ('prod' !== $env);
+$env = isset($_ENV['APP_ENV']) ? $_ENV['APP_ENV'] : 'dev';
+$debug = isset($_ENV['APP_DEBUG']) ? $_ENV['APP_DEBUG'] : ('prod' !== $env);
-$trustedProxies = isset($_SERVER['TRUSTED_PROXIES']) ? $_SERVER['TRUSTED_PROXIES'] : false;
+$trustedProxies = isset($_ENV['TRUSTED_PROXIES']) ? $_ENV['TRUSTED_PROXIES'] : false;
-$trustedHosts = isset($_SERVER['TRUSTED_HOSTS']) ? $_SERVER['TRUSTED_HOSTS'] : false;
+$trustedHosts = isset($_ENV['TRUSTED_HOSTS']) ? $_ENV['TRUSTED_HOSTS'] : false;
ログの出力先を変更
次にログの出力先を標準エラー出力に変更します。
app/config/eccube/packages/dev/monolog.yml
class: EasyCorp\EasyLog\EasyLogHandler
public: false
arguments:
- - '%kernel.logs_dir%/%kernel.environment%/site.log'
+ - "php://stderr"
app/config/eccube/packages/prod/monolog.yml
main_rotating_file:
- type: rotating_file
- max_files: 60
- path: '%kernel.logs_dir%/%kernel.environment%/site.log'
- formatter: eccube.log.formatter.line
- level: debug
+ type: stream
+ path: "php://stderr"
+ level: debug
front_rotating_file:
- type: rotating_file
- max_files: 60
- path: '%kernel.logs_dir%/%kernel.environment%/front.log'
- formatter: eccube.log.formatter.line
- level: debug
+ type: stream
+ path: "php://stderr"
+ level: debug
admin_rotating_file:
- type: rotating_file
- max_files: 60
- path: '%kernel.logs_dir%/%kernel.environment%/admin.log'
- formatter: eccube.log.formatter.line
+ type: stream
+ path: "php://stderr"
キャッシュの保存先を変更
次に各種のキャッシュファイル保存場所をLambdaから書き込めるディレクトリに変更します。一応ログの出力先も書き換えておきます。
src/Eccube/Kernel.php
public function getCacheDir()
{
- return $this->getProjectDir().'/var/cache/'.$this->environment;
+ return '/tmp/var/cache/'.$this->environment;
}
public function getLogDir()
{
- return $this->getProjectDir().'/var/log';
+ return '/tmp/var/log';
}
セッションの保存先を変更
セッションの保存先をファイルからDBに変更します。 詳細はSymfonyの公式ドキュメントを参照して下さい。
まずセッション情報を保存するためのテーブルを作成します。
CREATE TABLE `sessions` (
`sess_id` VARCHAR(128) NOT NULL PRIMARY KEY,
`sess_data` BLOB NOT NULL,
`sess_time` INTEGER UNSIGNED NOT NULL,
`sess_lifetime` MEDIUMINT NOT NULL
) COLLATE utf8_bin, ENGINE = InnoDB;
各種設定ファイルを修正していきます。
まずservices.yamlに下記の定義を追加します。
app/config/eccube/services.yaml
+ Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
+ public: false
+ arguments:
+ - '%env(DATABASE_URL)%'
セッションのハンドラとして追加した定義を指定します
app/config/eccube/packages/framework.yaml
session:
- handler_id: session.handler.native_file
- save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
+ handler_id: 'Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler'
静的コンテンツの取得元を変更
JS,CSS,画像ファイルといった静的コンテンツはLambdaから取得せずにS3から取得するように設定します。
app/config/eccube/packages/framework.yaml
assets:
- base_path: '/html/template/%eccube.theme%'
+ base_urls: ['https://s3-%env(AWS_DEFAULT_REGION)%.amazonaws.com/%env(S3_ASSET_BUCKET_NAME)%/']
packages:
admin:
base_path: '/html/template/admin'
save_image:
- base_path: '/html/upload/save_image'
+ base_urls: ['https://s3-%env(AWS_DEFAULT_REGION)%.amazonaws.com/%env(S3_ASSET_BUCKET_NAME)%/save_image']
php.iniの追加
PHPのExtensionを読み込むためにプロジェクトのルートディレクトリにphp.iniを用意します。
php.ini
extension=ctype.so
extension=curl.so
extension=dom.so
extension=iconv.so
extension=intl.so
extension=json.so
extension=mbstring.so
extension=mysqlnd.so
extension=pdo.so
extension=pdo_mysql.so
extension=phar.so
extension=simplexml.so
extension=tokenizer.so
extension=xml.so
extension=zip.so
AWS環境の構築
準備ができたのでAWS環境を構築していきます。 ALBのターゲットグループにLambdaを設定したいのですが、現状CloudFormationが対応していないようなので、ある程度までSAMテンプレートで構築しつつ、残りは手作業で構築します。
まずは下記のSAMテンプレートを使います。
AWSTemplateFormatVersion: 2010-09-09
Description: My PHP Application
Transform: AWS::Serverless-2016-10-31
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
InstanceTenancy: default
SubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
SubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: true
VPCInternetGateway:
Type: AWS::EC2::InternetGateway
VPCAttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref VPCInternetGateway
VPCPublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
VPCRoute:
Type: AWS::EC2::Route
DependsOn: VPCInternetGateway
Properties:
RouteTableId: !Ref VPCPublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref VPCInternetGateway
SubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetA
RouteTableId: !Ref VPCPublicRouteTable
SubnetCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetC
RouteTableId: !Ref VPCPublicRouteTable
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Lambda Security Group
VpcId: !Ref VPC
LambdaSecurityGroupMySQL:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref LambdaSecurityGroup
IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref LambdaSecurityGroup
LambdaSecurityGroupHTTP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref LambdaSecurityGroup
IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
MyDBSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupDescription: EC-DB Subnet
SubnetIds:
- !Ref SubnetA
- !Ref SubnetC
Database:
Type: AWS::RDS::DBInstance
Properties:
VPCSecurityGroups:
- Ref: LambdaSecurityGroup
AllocatedStorage: 20
DBInstanceClass: db.t2.micro
Engine: mysql
EngineVersion: 5.7.22
MasterUsername: root
MasterUserPassword: hogehogehoge
DBSubnetGroupName: !Ref MyDBSubnetGroup
DBName: eccube
DeletionPolicy: Delete
AssetBucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
DeletionPolicy: Delete
AssetBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref 'AssetBucket'
PolicyDocument:
Statement:
- Effect: Allow
Principal: '*'
Action: s3:GetObject
Resource: !Join ['', ['arn:aws:s3:::', !Ref 'AssetBucket', /*]]
Alb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
SecurityGroups:
- !Ref LambdaSecurityGroup
Subnets:
- !Ref SubnetA
- !Ref SubnetC
Type: application
phpserver:
Type: AWS::Serverless::Function
Properties:
Description: PHP Webserver
CodeUri: index.php
Runtime: provided
Handler: index.php
MemorySize: 512
Timeout: 30
Tracing: Active
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource: '*'
VpcConfig:
SecurityGroupIds:
- !Ref LambdaSecurityGroup
SubnetIds:
- !Ref SubnetA
- !Ref SubnetC
Layers:
- !Sub arn:aws:lambda:${AWS::Region}:887080169480:layer:php71:5
# 自作したレイヤーのARNを設定する
- !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:eccube_ext:1
- !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:php71_bootstrap_ex:1
Environment:
Variables:
APP_ENV: prod
APP_DEBUG: 0
DATABASE_URL:
!Sub
- "mysql://root:hogehogehoge@${EndPoint}/eccube"
- { EndPoint : !GetAtt Database.Endpoint.Address}
# ローカルでEC-CUBEをインストールした際に作成された.envファイルを参考に設定する
DATABASE_SERVER_VERSION: 5
MAILER_URL: "null://localhost"
ECCUBE_AUTH_MAGIC: "hogehogehoge"
ECCUBE_ADMIN_ROUTE: "admin"
ECCUBE_TEMPLATE_CODE: default
ECCUBE_LOCALE: ja
S3_ASSET_BUCKET_NAME: !Ref AssetBucket
sam package --template-file sam.yml --s3-bucket <デプロイに使える適当なS3バケット名> --output-template-file output.yml
sam deploy --template-file output.yml --stack-name eccube --capabilities CAPABILITY_IAM
RDSにDBのダンプを復元
一時的にEC2を立てて、ローカル環境で取得したmysqlのダンプをRDSにリストアします。 詳細な手順は割愛します。
S3に静的コンテンツを同期
SAMテンプレートで作成したS3バケットに静的コンテンツを同期します
aws s3 sync html/template/default/assets s3://<SAMテンプレートで作成したS3バケット名>/assets
aws s3 sync html/upload/save_image s3://<SAMテンプレートで作成したS3バケット名>/save_image
Lambdaのデプロイ
ソースコードの準備ができたのでZIPに固めてデプロイします。
zip -r eccube.zip app bin codeception html src vendor php.ini index.php composer.json
aws s3 cp eccube.zip s3://<デプロイに使える適当なS3バケット名>/eccube.zip
aws lambda update-function-code --function-name <Lambda functionの名前> --s3-bucket <上記で指定したS3バケット名> --s3-key eccube.zip
ALBとLambdaの紐付け
ここからは手動でやっていきます。
ターゲットグループの作成
ターゲットグループを作成し、作成したLambda functionを実行するように設定します。「複数値のヘッダー」をONにするのを忘れないで下さい。
リスナーの作成
HTTPでアクセスを受け付けるようにリスナーを作成し、転送先に作成したターゲットグループを指定します。
動作確認
いよいよ動作確認です。 初回アクセス時はキャッシュの生成に結構な時間がかかるので、辛抱強く待ちましょう。
TOP
商品詳細
カート
ログイン
ゲスト購入
注文手続き
注文内容の確認
購入完了
まとめ
LambdaのCustom Runtimesを使ってEC-CUBEの購入フローを動かしてみました。 今回やってみた感想として、画像アップロード系の処理をS3と使うように修正したり、各種メール配信にSESを利用するように設定したりすれば、EC-CUBEのほとんどの機能はLambda上でも動かせそうな感触を得ました。 Custom Runtimesを利用することで、LambdaをPaaSチックに利用する可能性が広がって来たのではないでしょうか?
ログやセッションの保存先変更はステートフルなWebアプリをLambda上で動かす際の共通した注意事項になるので、この記事を参考にWordPressなど他のOSSを動かしてみるのも一興かと思います。