ステートフルなECサイトもLambdaで動くんです!! AWS Lambda の Custom Runtimes を利用してEC-CUBEを動かしてみる #reinvent
サーバーレス開発部@大阪の岩田です。 この記事は 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という名前で以下のシェルスクリプトを作成します。
#!/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のコードです
#!/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
に置き換えていきます。
// 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;
ログの出力先を変更
次にログの出力先を標準エラー出力に変更します。
class: EasyCorp\EasyLog\EasyLogHandler public: false arguments: - - '%kernel.logs_dir%/%kernel.environment%/site.log' + - "php://stderr"
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から書き込めるディレクトリに変更します。一応ログの出力先も書き換えておきます。
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に下記の定義を追加します。
+ Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler: + public: false + arguments: + - '%env(DATABASE_URL)%'
セッションのハンドラとして追加した定義を指定します
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から取得するように設定します。
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を用意します。
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を動かしてみるのも一興かと思います。