Where I work we build and run our own infrastructure hosted in various colocation facilities. Compute is primarily provisioned through vSphere and load balancing and network configuration is handled with F5 BigIP appliances. Together these are very powerful systems that allow a lot of flexibility and control in how infrastructure is put together. Unfortunately the automation offerings, especially for the BigIP, are sparse. Those that do exist are either woefully incomplete or convoluted. Additionally, our internal process for provisioning infrastructure on these systems involves a lot of ticket entry, change docs and manual steps. As development progressed these changes would have to be rolled though our various environments.
To improve this situation I thought about ways to tie the configuration of the BigIP back to a git commit. That way network configuration could be versioned just like everything else making it easier to deploy, test, rollback and audit.
Ansible and Puppet both have modules that support the F5 but I quickly ran into trouble wrestling with dependency versions. Also (at the time) neither supported all of the options that I needed. This is when I came across Terraform!
Terraform
Terraform is a tool for interacting with infrastructure providers (GCE, AWS, vSphere, etc). Like Puppet, you define the desired end state and Terraform plans out how to get there. Out of the box it does not support the BigIP but it does have a well documented plugin system for adding new providers written in Go.
Go’ing forth
Building on an existing Go interface for the BigIP I’ve started terraform-provider-bigip. This allows us to create configurations like so:
variable "node_ips" {} variable "f5_host" {} variable "f5_user" {} variable "f5_pass" {} variable "url" {} variable "vip_ip" {} variable "ssl_cert" {} provider "bigip" { address = "${var.f5_host}" username = "${var.f5_user}" password = "${var.f5_pass}" } resource "bigip_ltm_virtual_server" "http" { name = "${var.url}_http" destination = "${var.vip_ip}" port = 80 profiles = ["tcp", "http"] rules = ["/Common/HTTP2HTTPS"] } resource "bigip_ltm_virtual_server" "https" { name = "${var.url}_https" destination = "${var.vip_ip}" port = 443 pool = "${bigip_ltm_pool.pool.name}" profiles = ["tcp","${var.ssl_cert}","http"] source_address_translation = "automap" } resource "bigip_ltm_pool" "pool" { name = "my-pool" load_balancing_mode = "round-robin" nodes = ["${formatlist("%s:%d", bigip_ltm_node.node.*.name, 80)}"] monitors = ["/Common/${bigip_ltm_monitor.proxy_monitor.name}"] allow_snat = true allow_nat = true } resource "bigip_ltm_node" "node" { count = "${length(split(",", var.node_ips))}" name = "${format("%s%02d", "node", count.index+1)}" address = "${element(split(",", var.node_ips), count.index)}" } resource "bigip_ltm_monitor" "proxy_monitor" { name = "my-monitor" parent = "http" timeout = "5" interval = "16" send = "GET / HTTP/1.1\\r\\nHost: my.domain.com\\r\\n\\r\\n" receive = "HTTP/1.1 302 Found" }
Neat! This is all still very much a work in progress. Please feel free to submit an issue (or a pull request) to help prioritize which features get added next. Also, stay tuned for a follow up post on the workflow for maintaining and applying configs across environments.