vkhitrin.com

Technology And Ramblings

Empusa, A Lightweight Service Registry On Top Of etcd



Tags: #etcd

Warning: Empusa has several limitations regarding accessing etcd clusters. Please refer to the project’s repository.

Warning: The main branch of empusa only supports etcd v2. To use empusa on top of etcd v3 with /v3alpha endpoint, refer to https://gitlab.com/testing-farm/empusa/-/merge_requests/8.

Introduction To etcd

etcd is a distributed key-value data store.
It is easily accessible using HTTP, and data is stored hierarchically, similar to a file system:

├── key1
│   ├── value1
│   └── value2
└── key2
    ├── value1
    └── value2

etcd is a robust and stable tool used in many projects, including Kubernetes.

What Is A Service Registry?

A service registry is a data store containing data structures for applications to consume.

This is useful in cloud-native applications where various microservices need to access information.
A separate service registry allows each microservice to be designed to fill its role in the application while having a single source of truth that all of them use.

┌───────────────────┐
│                   │
│  Microservice 1  ◄──────────┐
│                   │         │
└───────────────────┘    ┌────▼───────────────┐
                         │                    │
                         │  Service Registry  │
                         │                    │
┌───────────────────┐    └────▲───────────────┘
│                   │         │
│  Microservice 2  ◄──────────┘
│                   │
└───────────────────┘

Empusa

empusa is a tool built to enable a service registry-like approach on top of etcd. It is maintained by engineers working on Fedora and Red Hat Enterprise Linux CI.
It leverages the key-value data structure to build a trivial service registry.

empusa only supports etcd v2 natively (refer to merge request for etc v3).

Data Structure

Note: In etcd v3, data structure is different compared to v2, refer to https://etcd.io/docs/v3.3/rfc/.

All empusa data is hosted under a root key/directory (depending on the API version of etcd).
Depending on the entity type, empusa creates a key under the root key.
For example, if we create a service, the service key will be populated under the /example key. In the etcd v2 API, this will look like this:

# Query empusa service example directly from etcd
curl -s http://0.0.0.0:2379/v2/keys/example/service?recursive=true | jq .
# Output
{
  "action": "get",
  "node": {
    "key": "/example/service",
    "dir": true,
    "nodes": [
      {
        "key": "/example/service/TEST",
        "dir": true,
        "nodes": [
          {
            "key": "/example/service/TEST/MY",
            "value": "foo:123",
            "modifiedIndex": 4,
            "createdIndex": 4
          }
        ],
        "modifiedIndex": 4,
        "createdIndex": 4
      }
    ],
    "modifiedIndex": 4,
    "createdIndex": 4
  }
}

# Query empusa service example using empusa
empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/example' service list --format='json' | jq .
# Output
[
  {
    "service": "TEST/MY",
    "location": "foo:123",
    "ttl": null
  }
]

Available Entity Types

empusa has two entity types.

Services

An entity storing a mapping of the service name and its location:

+----------------+------------------+-------+
| Service        | Location         | TTL   |
|----------------+------------------+-------|
| datastore/etcd | 10.0.77.101:2379 |       |
+----------------+------------------+-------+

Services describe the available service in the directory. They have a TTL (time to live) that can be set to refresh or expire using empusa or external automation.

Switches

An entity storing a mapping of switch name and boolean value:

+----------+---------+-------+
| Switch   | Value   | TTL   |
|----------+---------+-------|
| feat_1   | yes     |       |
| feat_2   | yes     |       |
+----------+---------+-------+

Switches describe an entity (like features) that can be toggled based on availability. They have a TTL (time to live) that can be set to refresh or expire using empusa or external automation.

Installing Empusa

empusa is hosted on PyPI and can be downloaded using pip:

pip install empusa

Installing etcd

empusa supports etcd <= 3.3.27.

etcd can be installed as a binary or using a container.
In this blog post, we will be using an etcd container:

# Create a temporary directory
mkdir /tmp/etcd-data
# Set directory permissions
chmod 0700 /tmp/etcd-data/
# Launch container in the foreground
docker run --rm \
           -p 2379:2379 \
           -p 2380:2380 \
           --volume /tmp/etcd-data:/etcd-data:Z \
           --name etcd-gcr-v3.3.27 \
           gcr.io/etcd-development/etcd:v3.3.27 \
           /usr/local/bin/etcd \
           --name etcd-node-01 \
           --data-dir /etcd-data \
           --listen-client-urls http://0.0.0.0:2379 \
           --advertise-client-urls http://0.0.0.0:2379 \
           --listen-peer-urls http://0.0.0.0:2380 \
           --initial-advertise-peer-urls http://0.0.0.0:2380 \
           --initial-cluster etcd-node-01=http://0.0.0.0:2380 \
           --initial-cluster-token tkn

Usage

After configuring etcd and installing empusa, a command line tool empusa will be installed.
empusa can parse specific arguments from the environment variables.
The following table describes which environment variables are parsed to which arguments in empusa:

Environment Variable empusa argument Example
EMPUSA_LOGLEVEL INFO
EMPUSA_ETCD_API_VERSION –etcd-api-version 2
EMPUSA_ETCD_ENDPOINT –etcd-endpoint 0.0.0.0:2379
EMPUSA_ETCD_PROTOCOL –etcd-protocol http
EMPUSA_TREE_ROOT –tree-root /example
EMPUSA_SERVICE_TYPE –service-type datastore
EMPUSA_SERVICE_NAME –service-name etcd
EMPUSA_SERVICE_LOCATION –service-location foo:12345
EMPUSA_SERVICE_TTL –ttl 300
EMPUSA_SERVICE_REFRESH_EVERY –refresh-every 180
EMPUSA_SWITCH_TTL –ttl 300

For detailed help, use the --help argument.

Examples

Registering A Service

There are two ways to register a service:

  1. Using environment variables:

    EMPUSA_ETCD_ENDPOINT='0.0.0.0:2379' EMPUSA_TREE_ROOT='/available' EMPUSA_SERVICE_TYPE='datastore' EMPUSA_SERVICE_NAME='etcd' EMPUSA_SERVICE_LOCATION='0.0.0.0:2379' empusa service register
    # Output
    INFO:root:service etcd (datastore) registered
    
  2. Using full command line arguments:

    empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' service register --service-type="datastore" --service-name="etcd" --service-location="0.0.0.0:2379"
    # Output
    INFO:root:service etcd (datastore) registered
    

Unregestring A Service

There are two ways to unregister a service:

  1. Using environment variables:

    EMPUSA_ETCD_ENDPOINT='0.0.0.0:2379' EMPUSA_TREE_ROOT='/available' EMPUSA_SERVICE_TYPE='datastore' EMPUSA_SERVICE_NAME='etcd' EMPUSA_SERVICE_LOCATION='0.0.0.0:2379' empusa service unregister
    # Output
    INFO:root:service etcd (datastore) unregistered
    
  2. Using full command line arguments:

    empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' service unregister --service-type="datastore" --service-name="etcd"
    # Output
    INFO:root:service etcd (datastore) unregistered
    

Listing Services

There are two available formats to list services:

  1. Table (default):
    empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' service list --format='table'
    # Output
    +----------------+--------------+-------+
    | Service        | Location     | TTL   |
    |----------------+--------------+-------|
    | datastore/etcd | 0.0.0.0:2379 |       |
    +----------------+--------------+-------+
    
  2. JSON:
    empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' service list --format='json'
    # Output
    [{"service": "datastore/etcd", "location": "0.0.0.0:2379", "ttl": null}]
    

Setting A Switch

(refer to previous examples to re-use environment variables if preferred)

Setting a switch (will create if it doesn’t exist or update if exists):

empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' switch set test true
# Output
INFO:root:switch test set to true

Removing A Switch

(refer to previous examples to re-use environment variables if preferred)

Removing a switch:

empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' switch remove test
# Output
INFO:root:switch test removed

Listing Switches

(refer to previous examples to re-use environment variables if preferred)

Listing switches:

empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' switch list
# Output
+----------+---------+-------+
| Switch   | Value   | TTL   |
|----------+---------+-------|
| test     | true    |       |
+----------+---------+-------+

Toggling A Switch

(refer to previous examples to re-use environment variables if preferred)

Toggle a switch, and flip the boolean value:

# Toggle switch
empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' switch toggle test
# Output
INFO:root:switch test set to no
# Toggle switch again
empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' switch toggle test
# Output
INFO:root:switch test set to yes

Get The Value Of A Switch

(refer to previous examples to re-use environment variables if preferred)

Get the value of a switch:

empusa --etcd-endpoint='0.0.0.0:2379' --tree-root='/available' switch get test
# Output
yes

Final Notes

In this blog post, we have discussed how empusa can enable you to build a lightweight service registry on top of a robust tool like etcd.

How well it integrates into your architecture will depend on your requirements from a service registry.

Back To Top