📜 ⬆️ ⬇️

SparkleFormation - CloudFormation template generator with rainbows and unicorns.

SparkleFormation

If you are seriously using AWS (Amazon Web Services), then you probably know about the ability to describe the infrastructure using JSON templates. In AWS, this service is called CloudFormation . In essence, this solution allows you to describe the desired state of any resources available in AWS (instances, opsworks layers, ELB, security groups, etc.). A collection of resources is called a stack. After loading the CloudFormation template, the system will either create the necessary resources on the stack itself, if they are not already there, or try to update the existing ones to the desired state.

This works well if you have a small amount of resources, but as the infrastructure grows, problems arise:

In order to avoid such problems, engineers from Heavy Water wrote on ruby ​​DSL and CLI for generating and working with these templates called SparkleFormation ( github ).

DRY


When I came to my current project we had a CloudFormation template containing about 1500 lines of resources description and about 0 lines of comments. After using SparkleFormation, the template began to occupy 300 lines, many of which are comments. How did we do it? First, let's look at how CloudFormation works, a typical resource description looks like this:
Create ELB
"AppsElb": { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Scheme": "internal", "Subnets": [ {"Ref": "Subnet1"}, {"Ref": "Subnet2"} ], "SecurityGroups": [ {"Ref": "SG"} ], "HealthCheck": { "HealthyThreshold": "2", "Interval": "5", "Target": "TCP:80", "Timeout": "2", "UnhealthyThreshold": "2" }, "Listeners": [ { "InstancePort": "80", "LoadBalancerPort": "80", "Protocol": "TCP", "InstanceProtocol": "TCP" }, { "InstancePort": "22", "LoadBalancerPort": "2222", "Protocol": "TCP", "InstanceProtocol": "TCP" }, { "InstancePort": "8500", "LoadBalancerPort": "8500", "Protocol": "TCP", "InstanceProtocol": "TCP" } ] } } 


Since SparkleFormation allows you to use normal ruby ​​code inside DSL, you can rewrite it like this:
Creating ELB in SparkleFormation
  resources(:AppsElb) do type 'AWS::ElasticLoadBalancing::LoadBalancer' properties do scheme 'internal' subnets [PARAMS[:Subnet1], PARAMS[:Subnet2]] security_groups [ref!(:SG)] # port mapping 80->80, 22 -> 2222, etc. listeners = { :'80' => 80, :'2222' => 22, :'8500' => 8500 }.map do |k, v| { 'LoadBalancerPort' => k.to_s, 'InstancePort' => v, 'Protocol' => 'TCP', 'InstanceProtocol' => 'TCP' } end listeners listeners health_check do target 'TCP:80' healthy_threshold '2' unhealthy_threshold '2' interval '5' timeout '2' end end end 


As you can see, we are no longer repeated in the description of each port and adding a new one will take us only one line. Moreover, if we need to create many almost of the same type resources, but differing in 1-2 parameters, SparkleFormation provides such an entity as dynamics, where you can describe an abstract resource, to which parameters are passed:
Example from documentation
 # dynamics/node.rb SparkleFormation.dynamic(:node) do |_name, _config={}| unless(_config[:ssh_key]) parameters.set!("#{_name}_ssh_key".to_sym) do type 'String' end end dynamic!(:ec2_instance, _name).properties do key_name _config[:ssh_key] ? _config[:ssh_key] : ref!("#{_name}_ssh_key".to_sym) end end 

And then we can call this abstract resource in the template:
 SparkleFormation.new(:node_stack) do dynamic!(:node, :fubar) dynamic!(:node, :foobar, :ssh_key => 'default') end 


Thus, we can reuse the resources we need and, if necessary, change everything in one place.
')

Cloud init


We often take the opportunity to transfer an instance when loading a cloud-init config as a yaml file and use it to install packages, configure CoreOS, individual services and other settings. The problem is that yaml should pass the instance to the user-data in the CloudFormation template and it looked like this:
Mad escaping
  "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "#cloud-config\n", "\n", "coreos:\n", " etcd:\n", " discovery: ", {"Ref": "AppDiscoveryURL"}, "\n", " addr: $private_ipv4:4001\n", " peer-addr: $private_ipv4:7001\n", " etcd2:\n", ... 


As you can see, this is absolutely unreadable, ugly and poorly supported, not to mention the fact that syntax highlighting can be forgotten. Due to the fact that ruby ​​code can be used inside DSL, then the entire yaml can be put into a separate file and simply called:
 user_data Base64.encode64(IO.read('files/cloud-init.yml')) 

As you can see, this is much nicer than editing it inside JSON. Instead of using IO.read, you can use an HTTP call for any parameters, if you need it.

CLI


In our project, we use our own wrapper for managing templates, but the same command provides the CLI (Command Line Interface) for managing templates, called sfn . With it, you can load, delete and update CloudFormation stacks, with the sfn create, sfn destroy and sfn update commands. There is also implemented integration with knife.

In general, after 4 months of using SparkleFormation, I am satisfied with it and I hope no longer return to plain JSON to describe the infrastructure. Plans to try the entire workflow, including sfn, offered by the Heavy Water team.

Source: https://habr.com/ru/post/269501/


All Articles