This workshop relies on Laravel, Bref and CDK.
If you don't know what Bref is or what CDK is, please recap these references listed below.
ps. Deploy Laravel to Lambda With Bref by AWS CDK
Laravel is a popular PHP Framework and has a large community base.
References:
AWS Lambda is not native support for PHP but provides AWS Lambda runtime API and layers. You can build an supportable implementation of PHP by yourself, but it's not easy in my experience.
Now, you can use Bref tool to easily deploy and run serverless PHP applicatons.
Bref relies serverless framework and PHP 7.2+.
But what if you don't want to use serverless framework?
You can deploy application manually, by CloudFormation template or by AWS CDK.
References:
AWS CDK is a software development framework and can implement "Infrastructure IS Code".
Did you notice it? It is not "AS" but "IS".
AWS CDK defines cloud infrastructure in code and provisions it through AWS CloudFormation.
AWS CDK supports TypeScript, JavaScript, and Python. In preview, it also supports C#/.NET and Java.
References:
I like AWS CDK very much.
If you have done the AWS CDK workshop, I believe that you have the same feeling.
AWS CDK use object-oriented technicals to create model, use logic to define your infra, and organize your project into logical modules.
As a software engineer, I am so familiar with AWS CDK.
Therefore, I want to use AWS CDK to deploy my PHP Application with Bref.
First, please implement the bref workshop in my another repo.
If you have already done it, you now have an laravel project with Bref.
After you execute serverless deploy
, serverless framework generates two CloudFormation Template json files in .serverless
folder.
One is for creation and another is for updation.
./serverless/cloudformation-template-create-stack.json
just creates an S3 bucket for uploading lambda code.
./serverless/cloudformation-template-update-stack.json
is more complicated.
To steamline the process, we revmoe some step in serverless.yml
.
For example, we remove the building artisan lambda step.
Copy this new serverless.yml from doc folder to your bref project and deploy it again (or just synthesize it).
The new CloudFormation Template json files will be generated in .serverless folder.
Observe ./serverless/cloudformation-template-update-stack.json
, which services does it create?
(You also can observe these results in CloudFormation Stack AWS Console.)
Now, you know which services you need to build.
Do you notice that? We haven't done anything yet, just deploying an empty laravel project. There are so many services we have to build and the CloudFormation Template json file has 347 lines.
Let's try AWS CDK and see how it implements this infra.
Amogo the resources listed above, the most important services are Lambda Function and API Gateway.
Before we create these services, we have to create laravel project in advance.
composer create-project laravel/laravel laravel58-cdk --prefer-dist
cd laravel58-cdk
composer require bref/bref
We create another project and initial it by AWS CDK.
If you don't know how to use AWS CDK, please refer to AWS CDK Workshop or my previous article first.
# the same place as Laravel Project, not inside it.
mkdir laravel58-bref-cdk && cd laravel58-bref-cdk
cdk init -l typescript
cdk bootstrap
npm i @aws-cdk/aws-lambda
Edit lib/laravel58-bref-cdk-stack.ts
import cdk = require('@aws-cdk/core');
import lambda = require('@aws-cdk/aws-lambda');
export class Laravel58CdkDeployStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Get Bref layer ARN from https://runtimes.bref.sh/
// At this page, select correct Region and PHP version
const phpRuntimeLayer = lambda.LayerVersion.fromLayerVersionArn(this, 'lambda-php72-runtime', 'arn:aws:lambda:us-west-2:209497400698:layer:php-72-fpm:10');
const fn = new lambda.Function(this, 'CDK-Bref-fn', {
runtime: lambda.Runtime.PROVIDED, // for custom runtime
code: lambda.Code.fromAsset('../laravel58-cdk'),
handler: 'public/index.php',
layers: [phpRuntimeLayer],
});
}
Notice that you have to decide where the Laravel Project Code is located.
After we deploy this Laravel application to lambda, we login to AWS Console and test this lambda function.
You can select "Amazon API Gateway AWS Proxy" as event template and update path from "/path/to/resource" to "/" and httpMethod from "POST" to "GET".
{
"body": "eyJ0ZXN0IjoiYm9keSJ9",
"resource": "/{proxy+}",
"path": "/",
"httpMethod": "GET",
"isBase64Encoded": true,
....
}
And then, click "Test" button.
Boom! You will get an failed result and an error message as below:
{
"errorMessage": "2019-08-20T05:19:34.565Z xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Task timed out after 3.00 seconds"
}
Scroll donw the web page, you will see the Basic Settings section.
Try to config Memory to 1024 MB and Timeout to 30 seconds and test again.
This time, you can get status code 200 response!
This means that the PHP Runtime Layer works and we deply the Laravel application by CDK successfully.
It is still incomplete because we need to make some settings in AWS Console.
AWS provides the good API Reference.
At the API reference page, serach "lambda" and you will find the guide of aws-lambda construct.
Study these documents and you will know how to do it.
import cdk = require('@aws-cdk/core');
import lambda = require('@aws-cdk/aws-lambda');
export class Laravel58CdkDeployStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const phpRuntimeLayer = lambda.LayerVersion.fromLayerVersionArn(this, 'lambda-php72-runtime', 'arn:aws:lambda:us-west-2:209497400698:layer:php-72-fpm:10');
const fn = new lambda.Function(this, 'CDK-Bref-fn', {
runtime: lambda.Runtime.PROVIDED, // for custom runtime
code: lambda.Code.fromAsset('../laravel58-cdk'),
handler: 'public/index.php',
layers: [phpRuntimeLayer],
// set timeout to 30 seconds
timeout: Duration.seconds(30),
// set memory to 1024 MB
memorySize: 1024,
});
}
After edit the code, you deploy it again and will get the expected result.
So far, we have successfully deployed the Laravel application with Bref to lambda.
That is, we finished the IamRoleLambdaExecution and WebsiteLambdaFunction task in CloudWatch Template json file.
But we still can't connect our website by browser, we need AWS API Gateway to pushlish APIs.
Observe AWS CloudWatch Template, it create a Rest API, set a ANY method to this Rest API, add a proxy resource, and set a ANY method to the proxy resource.
Let's start with a simple one - create a Rest API and add a ANY method to it.
Also, AWS API Reference is your good friend.
Study the documents listed as follows:
// ... after lambda function
// ApiGatewayRestApi
const api = new apigw.RestApi(this, 'CDK-Bref-api', {
endpointTypes: [apigw.EndpointType.EDGE],
});
// Integration with Lambda function
const postAPIIntegration = new apigw.LambdaIntegration(fn, {
proxy: true,
});
// ApiGatewayMethodAny
api.root.addMethod('ANY', postAPIIntegration);
Look, it is very simple! Just only 3 functions and you complete the tasks.
Deploy it and go to AWS API Gateway Console.
You will get "CDK-Bref-api" and go to stages to find out the invoke URL.
Open that invode URL and you can open the laravel application index page.
Until now, we already successfully published this laravel website.
It still has a little bug.
When you try to open http://[Invoke URL]/prod/test, you will get "Missing Authentication Token".
{
"message": "Missing Authentication Token"
}
Why and how to solve this problem?
Error logs in many cases are very helpful when you're troubleshooting.
By default, the log of API Gateway is disable. You have to set up it by yourself.
Please refer to this link: Set Up CloudWatch API Logging in API Gateway
Do you notice that? You have to handle permissions first.
In AWS CDK, it is more easier than you think. Just add deployOptions listed below:
// ApiGatewayRestApi
const api = new apigw.RestApi(this, 'CDK-Bref-api', {
endpointTypes: [apigw.EndpointType.EDGE],
// enable logging
deployOptions: {
loggingLevel: apigw.MethodLoggingLevel.INFO,
}
});
By default, an IAM role will be created and associated with API Gateway to allow it to write logs and metrics to AWS CloudWatch unless cloudWatchRole is set to false. -- AWS CDK API Reference
When we create API Gateway, we don't need to handle permissions.
After you enable logging for API Gateway, the API Gateway will create the log group named API-Gateway-Execution-Logs_{rest-api-id}/{stage_name} and also create hundreds of log stream for future requests. Don't panic for these hundreds log streama, it's normal.
When the requests start coming in, you'll see those log streams start to record logs.
I told earlier that error logs are very helpful in many cases. Unfortunately, in this case it is not helpful.
We won't get any logs for "Missing Authentication Token".
Try to search in Google and AWS Document, here are some references:
According these document, we need create a proxy resource with greedy path variable of {proxy+}
and then set a ANY method on the proxy resource.
In fact, we can know it with CloudFormation Tempplate json file generated by serverless framework.
In AWS CDK, it is still easy to create a proxy resource. Just add two lines after add method to root.
...
// ApiGatewayMethodAny
api.root.addMethod('ANY', postAPIIntegration);
// ApiGatewayResourceProxyVar
// create a proxy resource
const resource = api.root.addResource("{proxy+}");
// set a ANY method to this proxy resource
resource.addMethod('ANY', postAPIIntegration);
...
Again, deploy and test.
Now, you can open http://[Invoke URL]/prod/test and will get 404 Not Found page.
It's correct because our laravel application doesn't implement the test path route.
The laravel application receives this request and return 404.
Compare to the CloudWatch Template:
lib/laravel58-bref-cdk-stack.ts only has 43 lines.
Do you remember? The CloudWatch Template json file has 347 lines.
If you don't want to rely on serverless framework, AWS CDK is a good choice.
In next session, I'll show you why I prefer AWS CDK than serverless framework.
Create a new file under lib folder called php-deployer.ts with the following content:
import cdk = require('@aws-cdk/core');
export interface PHPDeployerProps {
phpRuntimeLayerARN: string,
phpCodePath: string,
handler: string,
}
export class PHPDeployer extends cdk.Construct {
constructor(scope: cdk.Construct, id: string, props: PHPDeployerProps) {
super(scope, id);
// TODO
}
}
Move the deploy code from Laravel58CdkDeployStack to this class and replace some hardcode strings to props.
Please refer to lib/php-develpers.ts file.
Use PHPDeployer to replace the original code in laravel58-cdk-deploy-stack.ts file.
import cdk = require('@aws-cdk/core');
import { PHPDeployer } from './php-deployer';
export class Laravel58CdkDeployStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Get Bref layer ARN from https://runtimes.bref.sh/
new PHPDeployer(this, 'HelloPHPDeployer', {
phpRuntimeLayerARN: 'arn:aws:lambda:us-west-2:209497400698:layer:php-72-fpm:10',
phpCodePath: '../laravel58-cdk',
handler: 'public/index.php'
});
}
}
Now, you can publish the PHPDeployer construct as a module or package on npm to your team or to community.
It's great, right?
In this practice, I try to deploy the Laravel application with Bref by AWS CDK.
In the original demo, Bref uses the serverless framework to deploy the PHP application.
I just want to practice AWS CDK and compare the differences between AWS CDK and serverless framework.
In my personal opinion, I prefer to develop AWS CDK.
You can try it yourself and decide which one is better for you.
Keep in mind that there is no perfect solution. It depends on what you want and what kind of problem you want to solve.
Enjoy the whole process of trying new stuff and you'll learn more you expect.