One VCL to rule all environments

Write it once, use it everywhere.

by Luke Blaney, FT Labs (@lucas42)

default.vcl

Great to get things up and running straight out of the box.

Just specify your backends and away you go

The problem

More and more small bits of logic get added to each VCL

"It works fine in dev"

Our solution

Compare all the different VCLs running in each environment and find the similarities and differences.

master.vcl

  • Contains all our custom bits of logic.
  • Used across all environments.
  • Pulls in environment-specific files.

Main things which vary

  • Backends
  • Access Control

Backends

Use Puppet DB to find the web nodes

included in puppet manifest

$webhosts = query_facts("env=${env} and Apache",['hostname','ipaddress'])

Puppet Template

template for backend-nodes.vcl


<% @webhosts.sort.map do |fqdn,host| -%>
	backend <%= host['hostname'] %>	{ .host = "<%= host['ipaddress'] %>"; .probe = healthcheck; }
<% end -%>

Using a director

template for backend-pools.vcl


include "backend-nodes.vcl";
director primarypool random {
	<%- @webhosts.sort.map do |fqdn,host|-%>
	 	{ .backend = <%= host['hostname'] %>; .weight = 100; }
	<%- end -%>
}

Using different healthchecks for the same backend

template for backend-nodes.vcl


<% @webhosts.sort.map do |fqdn,host| -%>	
	backend <%= host['hostname'] %>           { .host = "<%= host['ipaddress'] %>"; .probe = healthcheck; }
	backend <%= host['hostname'] %>isnearby   { .host = "<%= host['ipaddress'] %>"; .probe = healthchecknearby; }
	backend <%= host['hostname'] %>nearmaster { .host = "<%= host['ipaddress'] %>"; .probe = healthchecknearmaster; }
	backend <%= host['hostname'] %>failsafe   { .host = "<%= host['ipaddress'] %>"; }
<% end -%>

Using multiple directors

template for backend-pools.vcl


include "backend-nodes.vcl";
director primarypool random {
	<%- @webhosts.sort.map do |fqdn,host|-%>
	 	{ .backend = <%= host['hostname'] %>isnearby; .weight = 100; }
	<%- end -%>
}
director secondarypool random {
    <%- @webhosts.sort.map do |fqdn,host|-%>
    { .backend = <%= host['hostname'] %>; .weight = 100; }
    <%-	end -%>
}
director fallbackpool random {
    <%- @webhosts.sort.map do |fqdn,host|-%>
    { .backend = <%= host['hostname'] %>failsafe; .weight = 100; }
    <%-	end -%>
}
director adminpool fallback {
    <%- @webhosts.sort.map do |fqdn,host|-%>
    { .backend = <%= host['hostname'] %>nearmaster; }
    <%-	end -%>
    <%- @webhosts.sort.map do |fqdn,host|-%>
    { .backend = <%= host['hostname'] %>failsafe; }
    <%- end -%>
}

How we use backends

master.vcl


include "backend-pools.vcl";

sub vcl_recv {

	set req.backend = primarypool;
	set req.grace = 0s;

	if (!req.backend.healthy) {
		set req.backend = secondarypool;
		set req.grace = 0s;

	 	if (!req.backend.healthy) {
			set req.backend = fallbackpool;
			set req.grace = 0s;
	 	}
	}
}

					

Cache Invalidation

Use the same puppetDB trick, but modified slightly

included in puppet manifest

$varnishnodes = query_facts("env=${env} and Varnish",['hostname','ipaddress'])

template for purge-varnish.sh


#!/bin/bash
<% @varnishnodes.sort.map do |fqdn,host| -%>
	echo 'ban.url .*' | nc <%= host['hostname'] %> 6082 >/dev/null
<% end -%>

Access Control

Store access control in hiera

fallback.yaml


allowedusers: |

	/* The public - all IPv4 traffic */
	"0.0.0.0" / 0;

Override for specific environments

dev.yaml


allowedusers: |

	/* Trust myself */
  	"localhost";

  	/* Trust private IP addresses. */
  	"10.0.0.0"/8;
  	"192.168.0.0"/16;
  	"172.16.0.0"/16;

  	/* Trust Rob who is working from home on a static IP address */
  	"1.2.3.4";

Write the ACL

included in puppet manifest

$allowedusers = hiera('allowedusers')

template for access-control.vcl


acl allowedusers {
	<%= @allowedusers %>
}

Summary

  • Put common code in a file which shared across all servers.
  • PuppetDB can decide which backends should be used.
  • Other environment-specific data can be pulled in from hiera.

Thanks!

Contact: