Skip to the content.

Adding Consul Configuration

Introduction

Services and applications need to be passed configuration to control integrations and behaviours. In this guide will show how to bootstrap Consul integration and pass non-sensitive key value pairs through manifest.yml.

Goals

Prerequisites

Bootstrapping Consul

When a service is started inside MSX it is passed the required Consul configuration as environment variables:

SPRING_CLOUD_CONSUL_HOST = https://localhost
SPRING_CLOUD_CONSUL_PORT = 8500

We also need a convenient way to configure Consul when developing locally, and a common mechanism to surface those key value pairs to our service or application. Your project will look like this once you have added the required files.


helloworld.yml

This is where we pass the values to bootstrap Consul, some of which will be overridden by environment variables at runtime when deployed in to MSX. Create helloworld.yml with the Consul bootstrapping values below.

consul:
  host: "http://127.0.0.1"               # Bound to env var SPRING_CLOUD_CONSUL_HOST at runtime.
  port: "8500"                           # Bound to env var SPRING_CLOUD_CONSUL_PORT at runtime.
  cacert: "/etc/ssl/certs/ca-bundle.crt" # Required by MSX.


manifest.yml

We have to update manifest.yml that we introduced in the last example to tell the container where to pick up the bootstrap configuration, and create some Consul key value pairs for testing.

.
.
.
ConfigFiles:
  - Name: "helloworld.yml"
    MountTo:
      Container: "helloworldservice"
      Path: "/helloworld.yml"

ConsulKeys:
  - Name: "favourite.color"
    Value: "Green"
  - Name: "favourite.food"
    Value: "Pizza"
  - Name: "favourite.dinosaur"
    Value: "Moros Intrepidus"
.
.
.


config.py

The file config.py defines the configuration structures for Hello World Service. It also binds the environment variables passed by MSX to the configuration.

import pkgutil
from os import environ
from collections import namedtuple
import yaml
from consul import ACLPermissionDenied

ConsulConfig = namedtuple("ConsulConfig", ["host", "port", "cacert"])
VaultConfig = namedtuple("VaultConfig", ["scheme", "host", "port", "token", "cacert"])
CockroachConfig = namedtuple("CockroachConfig", ["host", "port", "databasename","username", "sslmode", "cacert"])
SwaggerConfig = namedtuple("SwaggerConfig", ["rootpath", "secure", "ssourl", "clientid", "swaggerjsonpath"])
SecurityConfig = namedtuple("SecurityConfig", ["ssourl", "clientid", "clientsecret"])


class Config(object):
    def __init__(self, resource_name):
        # Load and parse the configuration.
        resource = pkgutil.get_data(__name__, resource_name)
        config = yaml.safe_load(resource)
        self._config_prefix = None

        # Apply environment variables and create Consul config object.
        config["consul"]["host"] = environ.get("SPRING_CLOUD_CONSUL_HOST", config["consul"]["host"])
        config["consul"]["port"] = environ.get("SPRING_CLOUD_CONSUL_PORT", config["consul"]["port"])
        self.consul = ConsulConfig(**config["consul"])

    # Find the correct Consul/Vault prefix.
    def find_consul_vault_prefix(self, consul_helper):
        try:
            test_value = consul_helper.get_string("thirdpartycomponents/defaultapplication/swagger.security.sso.baseUrl", None)
        except ACLPermissionDenied:
            test_value = None
        self._config_prefix = "thirdpartycomponents" if test_value else "thirdpartyservices"
        return self._config_prefix

    @property
    def config_prefix(self):
        return self._config_prefix


helpers/consul_helper.py

The module helpers/consul_helper.py provides the code to connect to Consul and retrieve the value for a given key. We also include a method that retrieves and logs some values from Consul.

import consul
import urllib3.util
import logging

from config import ConsulConfig


class ConsulHelper(object):
    def __init__(self, config: ConsulConfig):
        url = urllib3.util.parse_url(config.host)
        self._client = consul.Consul(
            host=url.hostname,
            port=config.port,
            scheme=url.scheme,
            verify=config.cacert)

    def get_string(self, key, default):
        index, data = self._client.kv.get(key)
        return data["Value"].decode("utf-8") if data else default

    def test(self, prefix):
        # Read our favourites from Consul and print them to the console.
        # Do not leak config in production as it is a security violation.
        favourite_colour = self.get_string(f"{prefix}/helloworldservice/favourite.color", "UNKNOWN")
        logging.info(f"My favourite color is {favourite_colour}")
        favourite_food = self.get_string(f"{prefix}/helloworldservice/favourite.food", "UNKNOWN")
        logging.info(f"My favourite food is {favourite_food}")
        favourite_dinosaur = self.get_string(f"{prefix}/helloworldservice/favourite.dinosaur", "UNKNOWN")
        logging.info(f"My favourite dinosaur is {favourite_dinosaur}")

Pay attention to the key paths in the test method as there are different patterns for different MSX versions and uses.

Pattern Description
{prefix}/helloworldservice/my.key for service specific values
{prefix}/defaultapplication/my.key for common system values


The prefix depends on the version of MSX you are running:

MSX Version Prefix
<= 4.0.0 thirdpartyservices
>= 4.1.0 thirdpartycomponents


app.py

We have to do a few things in app.py, for brevity we only include the new code.

.
.
.
from config import Config
from helpers.consul_helper import ConsulHelper

config = Config("helloworld.yml")
consul_helper = ConsulHelper(config.consul)
config.find_consul_vault_prefix(consul_helper)

app = Flask(__name__)
consul_helper.test(config.config_prefix)
.
.
.


Dockerfile

No changes are required in the Dockerfile.


Makefile

If you followed the project structure described in the guides, then your service will find the configuration file helloworld.yml in the working folder when you start the service locally. We have to update the Makefile to include it on our MSX Component tarball.

.
.
.
package:
    docker build -t ${NAME}:${VERSION} .
    docker save ${NAME}:${VERSION} | gzip > ${IMAGE}
    # REGION ADD BOOTSTRAP FILE
    tar -czvf ${OUTPUT} manifest.yml helloworld.yml ${IMAGE}
    # ENDREGION ADD BOOTSTRAP FILE
    rm -f ${IMAGE}
.
.
.


requirements.txt

The code we added above has dependencies on PyYAML and Consul, so we have to include references to them in requirements.txt.

Flask==1.1.2
Flask-Cors==3.0.10
flask-restplus==0.13.0
Werkzeug==0.16.1
PyYAML==5.4.1
python-consul==1.1.0
urllib3==1.26.5

Building the Component

Like we did in earlier guides build the component helloworldservice-1.0.0-component.tar.gz by calling make with component “NAME” and “VERSION” parameters. If you do not see helloworld.yml being added to the tarball you need to back and check the Makefile.

$ make NAME=helloworldservice VERSION=1.0.0
.
.
.
docker save helloworldservice:1.0.0 | gzip > helloworldservice-1.0.0.tar.gz
tar -czvf helloworldservice-1.0.0-component.tar.gz manifest.yml helloworld.yml helloworldservice-1.0.0.tar.gz
a manifest.yml
a helloworld.yml
a helloworldservice-1.0.0.tar.gz
rm -f helloworldservice-1.0.0.tar.gz

Deploying the Component

Log in to your MSX environment and deploy helloworldservice-1.0.0-component.tar.gz using MSX UI->Settings->Components (help me). If the helloworldservice is already deployed, delete it before uploading it again.

Inspecting the Server Log

Leaking Consul configuration to the console is a security violation, but it is convenient for testing this example. Recall that we specified some Consul key value pairs in manifest.yml, and retrieved and printed them in main.go. To prove that it worked we will use Kibana.

Click on “System Logs” in the left-hand navigation panel of the Cisco MSX Portal.


Kibana will launch in a new window. This is not a Kibana tutorial, but we will scratch the surface deep enough to dig out the messages we wrote to the console.


Click on “Discover” in the left-hand navigation panel of the Kibana interface. Then add a new filter of “kubernetes.labels.app is helloworldservice” as shown.


We only care about the time and log so add “log” from the “Available Fields” as shown.


One of the tricks to finding what you are looking for is being aware of “when” you are looking. Setting the time window to “Daily” is an easy way to make sure we see logs for the service we just deployed.


The last step is to filter on the word “favourite” in the search box at the top of the screen. If you do not see my favourite dinosaur then check back through your work.


You have now boot-strapped Consul into your service and passed some configuration. In the next guide we add support for Vault.

Conclusion

In this guide we added configuration to Hello World Service to bootstrap Consul support. Then we set some Consul values in the manifest before retrieving them at runtime and inspecting them using Kibana. Do not leak configuration in production as it is a security risk.

PREVIOUS NEXT HOME