Imagine we have Deployment Manager’s config file that creates a virtual machine from certain image and assigns an ephemeral public IP address to it. Something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
resources: - name: tiny-vm type: compute.v1.instance properties: zone: us-central1-a machineType: zones/us-central1-a/machineTypes/f1-micro disks: - deviceName: boot type: PERSISTENT boot: true autoDelete: true initializeParams: sourceImage: projects/ubuntu-os-cloud/global/images/family/ubuntu-1804-lts networkInterfaces: - network: global/networks/default accessConfigs: - name: External NAT type: ONE_TO_ONE_NAT |
If I decided to create 5 other VMs, similar to this one, I’d probably have to copy-paste the config, changing just the tiny pieces: the name and probably the zone with the image.
However, Deployment manager supports Jinja and Python templates, so we can move a repetitive blocks into those, leaving only customizable parts on the surface. Let’s see is how it works for Python.
How it works
Instead of using predefined resource types, like compute.v1.instance
(see line #3 in YAML above) or others, we can create a custom template file and use that one as a type
. Then, when executed, template will take the ‘surrounding’ YAML properties from a config file, process it using whatever rules that you define, and produce actual resource(es) YAML to be deployed. It’s way simple than it sounds, trust me. To prove the point, let’s have a look at an example.
Moving VM config to a template
Here’s the task. I want to turn the original config into a template, which will accept only four parameters: a name, a zone, a source image and whether or not to create an external IP address. Everything else should come predefined. In other words, I want the template which will make the following config YAML possible:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
imports: - path: vm.py # This is going to be a template resources: - name: first-vm type: vm.py properties: zone: us-central1-a sourceImage: projects/ubuntu-os-cloud/global/images/family/debian-9 assignPublicIP: true - name: second-vm type: vm.py properties: zone: us-central1-c sourceImage: projects/ubuntu-os-cloud/global/images/family/ubuntu-1804-lts |
This config simply creates two virtual machines with different OSes and in different availability zones. What’s more, one VM will have an external IP, while the other one will not. This is how the template for it might look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
def generate_config(context): """ Process source YAML and produce actual resources config """ name = context.env['name'] zone = context.properties['zone'] source_image = context.properties['sourceImage'] assign_public_ip = context.properties.get('assignPublicIP') vm = { 'type': 'compute.v1.instance', 'name': name, 'properties': { 'zone': zone, 'machineType': 'zones/{}/machineTypes/f1-micro'.format(zone), 'disks': [{ 'deviceName': 'boot', 'type': 'PERSISTENT', 'boot': True, 'autoDelete': True, 'initializeParams': { 'sourceImage': source_image } }], 'networkInterfaces': [{ 'network': 'global/networks/default' }] } } if assign_public_ip: vm['properties']['networkInterfaces'][0]['accessConfig'] = [{ 'name': 'External NAT', 'type': 'ONE_TO_ONE_NAT' }] return { 'resources': [vm] } |
The template is pretty straightforward. create_config
function is the one that receives ‘surrounding’ YAML config as input parameter (context
) and returns a dictionary, which would turned out to be a perfectly valid YAML config, if we’d ever bothered to serialize it.
As you can see, some of template parameters (e.g. zone
) are coming from the context.properties
dictionary, whereas the other (name
) from the context.env
. There’s no special magical reason for that. context.properties
maps one to one to config’s YAML properties
and this is where customizable parameters usually come from. env['name']
on the other hand is one of predefined environmental variables, which in this case carries the resource name (- name: first-vm
). That was a shortcut that allowed me to avoid adding one more property to properties
collection and reuse the resource name as a VM name.
Everything else is trivial. We populate the properties, check if assign_public_ip
was provided, and if yes, populate accessConfig
network section, etc.
Unfortunately, at the moment of writing deployment manager can’t preprocess templates and show resulting YAML, so we could validate if a template we’re developing actually makes sense. gcloud deployment-manager deployments create
has a --preview
option, but it has a different purpose.
After deploying new config with gcloud deployment-manager deployment my-cluster --config templated-deployment.yaml
we’ll see two new VMs coming to life, plus all the details about what resulting configuration looks like and which templates were involved.
Summary
Deployment Manager templates are incredibly powerful. Not only they save the keystrokes and lines of otherwise repeatable configuration, but because it’s backed by the power of general purpose language, we could do all sorts of crazy tricks with them. Generate more than one resource per config entry, nest templates, combine multiple resources and their configuration as a single building block – just to name a few.
Google also has a few dozens of samples. Though somewhat messy and out of date, they are still very good aid for getting started. Plus, there’s new branch under active development, which has newer, cleaner and more powerful templates. But it’s just about one month old, so there’s not much to choose from yet.