Dissecting Serverless Stacks (IV)
This content is more than 4 years old and the cloud moves fast so some information may be slightly out of date.
Dissecting Serverless Stacks (IV)
After we figured out how to implement a sls
command line option to switch between the usual behaviour and a way to conditionally omit IAM in our deployments, we will get deeper into it and build a small hack on how we could hand over all artefacts of our project to somebody who does not even know SLS at all.
If you looked around a bit inside your development directory, you probably already know that there is a command sls package
which creates the CloudFormation document which the whole stack is based on. This is basically a step right before deployment and we have that JSON file as .serverless/cloudformation-template-update-stack.json
in the project directory. Next to it we also find the ZIP file of our Lambda.
So we are almost at a point where we could hand over a iam.yaml
, the cloudformation-template-update-stack.json
and the ZIP file to someone who deploys it manually.
But wait, one thing is not quite right: By default, Serverless will create an S3 bucket for our project, upload the ZIP there and the Stack only references this for the deployment. This won’t work for a manual deployment where we need to have some Parameters to hand over Bucket and S3 key.
For this, I built a small Ruby script which will
- read the resulting JSON
- add Parameters of type String for
ServerlessDeploymentBucket
andServerlessDeploymentArtifact
- replace the
Code
block of the Lambda CloudFormation Resource - write the result back as YAML
As I implemented this as a task within Rake, my Rakefile
contains this:
desc "Export separately"
task :export do
sh <<~EOS, { verbose: false }
sls package --deployment no_includes
cp .serverless/*.zip pkg/
cp cloudformation-resources/* pkg/
EOS
require 'json'
require 'yaml'
project_base = Rake.application.find_rakefile_location[1]
filename = File.join(project_base, '.serverless/cloudformation-template-update-stack.json')
json = JSON.parse(File.read(filename))
# Replace S3 resource by Parameter
json['Resources'].delete('ServerlessDeploymentBucket')
json['Parameters'] = {}
json['Parameters']['ServerlessDeploymentBucket'] = {
"Type" => 'String'
}
zip_file = Dir.glob(File.join(project_base, '.serverless/*.zip')).first
json['Parameters']['ServerlessDeploymentArtifact'] = {
'Type' => 'String',
'Default' => File.basename(zip_file)
}
functions = json['Resources'].select { |k,v| v['Type'] == 'AWS::Lambda::Function' }
functions.each do |function|
source = File.join(project_base, function[1]['Properties']['Handler'].gsub(/\.[a-z_]+$/, '.rb'))
function[1]['Properties']['Code'] = {
'S3Bucket' => {
'Ref' => "ServerlessDeploymentBucket"
},
'S3Key' => {
'Ref' => "ServerlessDeploymentArtifact"
}
}
end
fp = File.open(File.join(project_base, 'pkg/stack.yaml'), 'w')
fp.write json.to_yaml
fp.close
end
At the end of this journey, we have three ways of deploying our SLS project
sls deploy
as the standard way, which will deploy all parts automaticallysls deploy --deployment no_includes
for the “IAM first, Serverless second” approachrake export
for handing over artefacts to a third party and manual deployment.
Summary
Of course, these approaches might not work well for you. I created it especially for smaller, 1-Lambda projects and not for complicated microservices architectures. But if you work with customers and want to recycle your small Lambda-based helpers across different types of organizations, you might find it handy to follow the patterns.
And even if you don’t want to use these deployment styles, I hope I could show you some lesser known ways of structuring your serverless.yml
(externalized sub-stacks, references) or how to use custom CLI options (with mappings and double references).