A common problem that F5 deals with for Cloud Native Applications (CNA) is how to add and remove pool members and create virtual servers on an F5 BIG-IP. It doesn’t sound like a particularly hard problem to solve, but with a growing ecosystem this can be a daunting task.  

For PaaS platforms like Kubernetes, OpenShift, Mesos, and Cloud Foundry, we use the Container Connector to solve this problem. Behind the scenes, F5 uses the Common Controller Core Library (CCCL, pronounced “see-sol”) to bind these solutions, using a shared schema for deploying L4-L7 services in a declarative interface.

Divide and Conquer

When we introduce automation into an existing environment we are always challenged with “what is the source of truth?”. The problem we are trying to avoid is a Network/System Operator making a change that creates a conflict with an Automation system, or vice versa. Here’s a timeline of a scenario we would like to avoid:

  1. Operator creates a UCS backup of BIG-IP config.
  2. Automation system makes a change on the BIG-IP.
  3. Operator makes a change and decides to revert to the UCS backup.
  4. The change made by the Automation system is lost.

CCCL takes a couple of safe guards to avoid this issue:

  1. It will “own” an Administrative F5 Partition (subset of F5 config file).
  2. Any configuration that is not recognized is removed/reverted by CCCL.

Babel of Load Balancing

babel-of-load-balancing

Everyone has their own definition of a virtual server, pool, and node. In Kubernetes this could refer to a Service IP, App label, and Pod. The F5 BIG-IP Controller for Kubernetes (the Kubernetes-specific implementation of the F5 Container Connector) converts Kubernetes nomenclature to a JSON schema that is defined by CCCL.

Let’s take a look at how CCCL normalizes the same configuration from Mesos and Kubernetes using two different input formats. In Mesos, you would define as service as follows (JSON pseudo-code):

{
    "id": "mesos-app",
    "instances": 2,
    "container": {
        "type": "DOCKER",
        "docker": {
            "portMappings": [{
                    "containerPort": 8088,
                    "hostPort": 0,
                    "protocol": "tcp"
                }
            ]
        }
    },
    "labels": {
        "F5_PARTITION": "mesos",
        "F5_0_BIND_ADDR": "10.1.10.10",
        "F5_0_MODE": "http",
        "F5_0_PORT": "8080",
    },
    "healthChecks": [{
            "protocol": "HTTP",
            "portIndex": 0,
            "path": "/",
            "gracePeriodSeconds": 5,
            "intervalSeconds": 20,
            "maxConsecutiveFailures": 3
        }
    ]
}

In Kubernetes, the Service definition looks like this (YAML pseudo-code):

kind: ConfigMap
apiVersion: v1
metadata:
  name: k8s.vs
  namespace: default
  labels: 
    f5type: virtual-server
data:
  schema: "f5schemadb://bigip-virtual-server_v0.1.3.json"
  data: |
    {
      "virtualServer": {
        "backend": {
          "servicePort": 3000,
          "serviceName": "myService",
          "healthMonitors": [{
            "interval": 30,
            "protocol": "http",
            "send": "GET",
            "timeout": 86400
          }]
        },
        "frontend": {
          "virtualAddress": {
            "port": 80,
            "bindAddr": "10.1.10.20"
          },
          "partition": "kubernetes",
          "balance": "round-robin",
          "mode": "http"
        }
      }
    }

CCCL reads in the JSON input and uses the F5 Python SDK/iControl REST to update the designated partition on the BIG-IP.

Example of the normalized CCCL input (JSON pseudo-code):

{
  "name": "test1",

  "virtualAddresses": [
	{
	  "name": "MyVaddr",
	  "autoDelete": "false",
	  "enabled": "no",
	  "address": "192.168.0.2"
	}
  ],
  "virtualServers": [{
	"name": "vs1",
	"destination": "/test/MyVaddr:80",
	"pool": "/test/pool1",
	"profiles": [
	  {"name": "http", "partition": "Common", "context": "all"},
	]
  }],
  "pools": [
    { "name": "pool2",
      "members": [
        {"address": "172.16.0.100", "port": 8080},
        {"address": "172.16.0.101", "port": 8080}
      ],
      "monitors": ["/Common/http"]
    }
  ]
}

Example of updating the BIG-IP using CCCL (Python pseudo-code):

 

from f5_cccl.api import F5CloudServiceManager
from f5.bigip import ManagementRoot
import json
service_config = json.load(open(‘kubernetes.json’))
bigip = ManagementRoot('192.168.1.245', 'admin', 'admin')
cccl = F5CloudServiceManager(bigip, “kubernetes”)
cccl.apply_config(service_config)

Creating your own Controller

CCCL makes it possible for F5 to provide standard services across a broad spectrum of providers. An example of using a CSV file as an input can be found on the F5 DevCentral GitHub site at:

This allows you to create an input CSV file, like so:

#type,name,ip:port,pool members
tcp,"test_tcp",192.168.0.1:8000,"172.16.0.100:8080"
fastl4,"test_fastl4",192.168.0.2:9000,"172.16.0.100:8080"
http,"test_http",192.168.0.2:80,"172.16.0.100:8080"
https,"test_https",192.168.0.2:443,"172.16.0.100:8080"
iapp,"test_iapp",192.168.0.2:8888,"172.16.0.100:8080"

Take a look at the F5 BIG-IP Controller for Kubernetes for a more advanced example.

F5 Contributed Software

Please note that CCCL is F5 Contributed Software. From the project page:

This project is used internally by other F5 projects; we're not yet ready to accept contributions. Please check back later or see if another project, such as https://github.com/F5Networks/f5-common-python would be a good place for your contribution.

For the Python coders; use this as an alternative to creating your own controller.  For the non-coders;  you now know more about the inner workings of the F5 Container Connector.

Thank you to @edarzins for providing the sample Python code to create this article.