As the title suggests, this is my impression of AWS CloudFormation.
impetus
When calling AWS Lambda from a URL, you have to use the AWS API Gateway GUI to do the mapping, such as "call this Lambda for this method of this URL," but this is tedious.
At first, it is interesting because it is unusual, but the process becomes repetitive and similar, and eventually becomes tedious and cumbersome.
Because GUI operations are manual, they are prone to omissions, omissions, and errors, and it also requires care to record work procedures and reproduce them later.
I thought, "Why not use AWS CloudFormation in such a case? I decided to give it a try.
What is AWS CloudFormation?
Various AWS services are created and configured using GUI or command line tools, but the method of doing so varies from service to service.
CloudFormation can create and update services all at once based on these templates.
Compared to creating services directly with command line tools, CloudFormation provides a higher level of abstraction, allowing you to create several different types of services at once by associating them, parameterizing parts of the template, and resolving parameter values at CloudFormation runtime. CloudFormation provides a higher level of abstraction, allowing you to associate and group different types of services together.
I would say procedure...
I have included a sample template (overview) for creating Lambda and API Gateway. (I put it at the end because it is quite long)
A few hundred lines with various omissions for the article.... In fact, it is over a thousand lines, so if we were to do it in earnest, it would be at the level of several thousand lines. ......
It is beyond the limit of how much a person can edit. ...... This was the story of .......
A brief description of the template
In CloudFormation, each service to be created, such as Lambda, API Gateway, EC2, etc., is called a "resource" and is defined in "Resources".
Which type of resource is specified in "Type" and its settings are defined in "Properties".
The manual file describes which resources are available and what parameters they have, so when editing the template, you will need to refer to the manual and the GUI for each resource to set the values.
Then, when the template is specified and executed in CloudFormation, the defined resource is created. If the template is changed and CloudFormation is executed again, the changes will be applied to the created resources.
Points to note
There are some pretty important things to keep in mind when using CloudFormation.
- After a resource has been created, changing the configuration of that resource outside of CloudFormation may cause inconsistencies that will subsequently prevent CloudFormation from making changes.
- CloudFormations can be created and updated as well as deleted. Since CloudFormation is created on a per-template basis, deleting it will delete all the resources listed in the template.
- If the configuration changes exceed the change level, replace the resource, i.e., delete the resource and create a new resource from scratch. For example, if a database has been created as a resource, be aware that the intended change may result in the deletion of the database and its data.
At first, I was puzzled when I heard the specifications around this, but it seemed to fit well if I thought of it as something that is always being replaced with the latest version, rather than adding modifications after initial upload, as is the case with docker-compose, Kubernetes, and Lambda.
impressions
It is like a low-level API for generating AWS resources.
Although CloudFormation can be used to generate all resources, I felt that CloudFormation should not be manipulated directly, but rather used indirectly through some kind of generator.
For example, as for the "Lambda+API Gateway" that triggered the project, we ended up using Serverless Framework, but the Serverless Framework also generates a CloudFormation template and uses CloudFormation to create the Lambda+API Gateway is created using CloudFormation.
One thing that I find particularly difficult when writing CloudFormation templates is that sometimes I have to explicitly set values when writing a template that are set as default values when I create it in the AWS console.
If you try to create the same thing in CloudFormation as you created in the console and set only the parameters visible in the console in CloudFormation, you will not be able to create it because there are not enough parameters...and so on. Then, you have to look up the parameters from the one created in the console and set them each time, which is quite difficult.
There is a tool called CloudFormer that can create CloudFormation templates from existing resources, but it is not very user-friendly and I have fallen behind...
Policies for use
I felt that once a decision is made to use CloudFormation, it is better to make sure that changes are also made via CloudFormation. I also felt that it is necessary to design and operate the system in such a way that there will be no problems even if resources are rebuilt.
We often see styles like Docker and Lambda, which are replaced by updates, so it may be more current.
CloudFormation Designer
CloudFormation has a tool called "CloudFormation Designer" that allows you to create templates in a GUI. It looks cool at first glance, so you might expect that you can easily create templates by drag-and-drop, but it is limited in what it can do, and manual editing of templates is required.
Furthermore, the templates output by the CloudFormation designer are automatically formatted and have GUI information added to them, making them difficult to read, so their use is not recommended.
Even if you are using a generator, you may need to modify the template to configure settings that are not supported by the generator.
Sample Templates
It is long and has been considerably abbreviated.
{ "AWSTemplateFormatVersion": "2010-09-09", "Resources": { "ldLogin": { "Type": "AWS::Lambda::Function", "Properties": { "FunctionName": { "Fn::Sub": "cf-test-lambda-login-${paramStage}" }, "Handler": "login.handler", "Runtime": "nodejs8.10", "Code": { "S3Bucket": "cf-test", "S3Key": { "Fn::Sub": "lambda/${paramLambdaZipFileName}" } }, "Role": "arn:aws:iam::xxxx:role/cf-test-role-lambda-${paramStage}" } }, "api": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Name": { "Fn::Sub": "cf-test-api-${paramStage}" }, "EndpointConfiguration": { "Types": [ "REGIONAL" ] } } }, "ptLogin": { "Type": "AWS::ApiGateway::Resource", "Properties": { "ParentId": { "Fn::GetAtt": [ "api", "RootResourceId" ] }, "PathPart": "login", "RestApiId": { "Ref": "api" } } }, "mtLoginGET": { "Type": "AWS::ApiGateway::Method", "Properties": { "HttpMethod": "GET", "RestApiId": { "Ref": "api" }, "Integration": { "Type": "AWS", "Uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ldLogin.Arn}/invocations" }, "PassthroughBehavior": "NEVER", "IntegrationHttpMethod": "GET", "RequestTemplates": { "application/json": { "Fn::Sub": "${paramMappingTemplate}" } }, "IntegrationResponses": [ { "ResponseTemplates": { "application/json": "" }, "StatusCode": "200", "ResponseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" } } ] }, "ResourceId": { "Ref": "ptLogin" }, "MethodResponses": [ { "ResponseModels": { "application/json": "Empty" }, "ResponseParameters": { "method.response.header.Access-Control-Allow-Origin": true }, "StatusCode": "200" } ] }, "DependsOn": [ "ldLogin" ] }, "mtLoginOPTIONS": { "Type": "AWS::ApiGateway::Method", "Properties": { "ResourceId": { "Ref": "ptLogin" }, "RestApiId": { "Ref": "api" }, "AuthorizationType": "NONE", "HttpMethod": "OPTIONS", "MethodResponses": [ { "StatusCode": "200", "ResponseModels": { "application/json": "Empty" }, "ResponseParameters": { "method.response.header.Access-Control-Allow-Headers": true, "method.response.header.Access-Control-Allow-Methods": true, "method.response.header.Access-Control-Allow-Origin": true } } ], "Integration": { "Type": "MOCK", "RequestTemplates": { "application/json": "{\"statusCode\": 200}" }, "IntegrationResponses": [ { "ResponseTemplates": { "application/json": "" }, "StatusCode": "200", "ResponseParameters": { "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } ] } } }, "stage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "StageName": { "Ref": "paramStage" }, "DeploymentId": { "Ref": "deployment" }, "RestApiId": { "Ref": "api" } }, "DependsOn": [ "mtLoginOPTIONS", "mtLoginGET" ] }, "deployment": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "api" } }, "DependsOn": [ "mtLoginOPTIONS", "mtLoginGET" ] } }, "Parameters": { "paramStage": { "Type": "String", "Default": "cloudformation" }, "paramLambdaZipFileName": { "Type": "String" }, "paramMappingTemplate": { "Type": "String", "Default": "{}" } }, "Outputs": { "url": { "Value": { "Fn::Sub": "https://${api}.execute-api.${AWS::Region}.amazonaws.com/${paramStage}" } } } }