$darkmode
Elektra 0.11.0
How to Write a Specification in Elektra for dockerd

Overview

Introduction

In this tutorial you will learn how to interactively use the SpecElektra specification language and kdb to write a configuration specification for dockerd.

What you should already know

What you’ll learn

  • how to create and mount a specification using kdb
  • how to add keys with different types, defaults, enums, array specifications, wildcard specifications and examples to your specification and how to validate them

What you'll do

  • use kdb to create and mount a specification for dockerd
  • define defaults, array / wildcard specifications, examples and checks for keys in the validation
  • use the specification as a starting point for customizing the configuration of installed applications

Scope

In this tutorial we will introduce a possible specification for dockerd. Using kdb we will configure the specification.

NOTE: As the specification for dockerd is quite big we will only present a sample for the above mentioned metakeys and link to a full specification dockerd-full-spec.

Getting Started

Before we start just an overview of the structure:

  • Specification file location: /docker/daemon.json
  • Parent specification key: spec:/sw/dockerd/dockerd/#0/current

Specification Types (values)

Elektra supports multiple types which leads to a more flexible specification. See type plugin for information about all the types that are supported.

Mount Setup

We will be mounting an existing example dockerd-spec.

Step 1: Mount dockerd specification

First you need to mount a specification file, in this case dockerd.ini to the spec:/ namespace. You can define the path inside the spec:/ namespace as /sw/docker/dockerd/#0/current, refer to the documentation to find out more about constructing the name.

sudo kdb mount "$PWD/examples/spec/dockerd.ini" spec:/sw/docker/dockerd/#0/current ni
# RET: 0

NOTE: If you encounter any error saying that you have already mounted some specification with the same name you can run sudo kdb umount spec:/sw/docker/dockerd/#0/current and rerun the above command.

Note: ni is the format which is used for the specification in the file. You can also choose to use json, then you need use yajl instead of ni.

Step 2: Define a mountpoint

Next you can define, that this specification uses a specific mountpoint for a concrete application configuration. So you can say the concrete configuration should be written to dockerd.ini.

kdb meta-set spec:/sw/dockerd/dockerd/#0/current mountpoint /docker/daemon.json
# RET: 0

Your dockerd.ini file should now contain the mountpoint metakey:

NOTE: Excerpt of cat $(kdb file spec:/sw/docker/dockerd/#0/current).

# ;Ni1
# ; Generated by the ni plugin using Elektra (see libelektra.org).
# =
# []
# meta:/mountpoint = /dockerd/daemon.json

Step 3: Define <tt>json</tt> as plugin

Next we will define that our configuration should be written json.

We can do this by running:

kdb meta-set spec:/sw/dockerd/dockerd/#0/current infos/plugin "yajl"
# RET: 0

Step 4: Do a specification mount

sudo kdb spec-mount "/sw/docker/dockerd/#0/current" ni
# RET: 0

This specification mount makes sure that the paths where the concrete configuration should be (daemon.json) are ready to fulfill our specification (dockerd.ini). Be aware that different files get mounted for different namespaces. You've a specification file (dockerd.ini) for the spec-namespace and three files (daemon.json) on different locations for the dir- user- and system-namespaces.

You can see the files by providing the namespace as prefix to the kdb file command (each shows a different path):

kdb file system:/sw/docker/dockerd/#0/current
# /dockerd/daemon.json
kdb file user:/sw/docker/dockerd/#0/current
# STDOUT-REGEX: /dockerd/daemon.json
kdb file dir:/sw/docker/dockerd/#0/current
# STDOUT-REGEX: /dockerd/daemon.json

NOTE: The $PWD should equal the PWD where you run sudo kdb mount "$PWD/examples/spec/dockerd.ini" spec:/sw/docker/dockerd/#0/current ni.

**_Note_**: The files only exist, when configuration values are stored there, i.e. they are created on the first kdb set and removed with the last kdb rm.

For more information about namespaces in Elektra please see here, a tutorial about the topic is available here.

Writing specification for keys (manually)

NOTE: All output we display for cat $(kdb file spec:/sw/docker/dockerd/#0/current) is an excerpt of the whole file output.

In this example for dockerd we will be using 3 types of specifications:

  • Simple specification (type and description)
  • Enum specifications (for keys were only a set of possible options can be used)
  • Array and wildcard specifications (for keys where a list of possible options can be used)

As the dockerd specification is big we will just present one of each of the above mentioned specification types.

NOTE: In Elektra we use / instead of - to seperate key names. This results in a hierarchical structure of key names. This commands will automatically store the specification in the dockerd-daemon.ni specification file.

Array specification

[data/root]
meta:/type = string
meta:/description = Root directory of persistent Docker state
meta:/default = /var/lib/docker

In order to get the above specification we will need the following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/data/root type "string"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/data/root description "Root directory of persistent Docker state"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/data/root default "/var/lib/docker"
# RET: 0

In case no data/root key gets configured the value /var/lib/docker is used.

Let us verify that the metakeys have been set correctly:

NOTE: Excerpt of cat $(kdb file spec:/sw/docker/dockerd/#0/current).

cat $(kdb file spec:/sw/docker/dockerd/#0/current)
# [data/root]
# meta:/type = string
# meta:/description = Root directory of persistent Docker state
# meta:/default = /var/lib/docker

Enum specification (for keys were only a set of possible options can be used)

[default/cgroupns/mode]
meta:/description = Default mode for containers cgroup namespace
meta:/default = private
meta:/check/enum = #1
meta:/check/enum/#0 = host
meta:/check/enum/#1 = private

In order to get the above specification we will need the following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode description "Default mode for containers cgroup namespace"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode default "private"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode check/enum "#1"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode check/enum/#0 "host"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/cgroupns/mode check/enum/#1 "private"
# RET: 0

With this configuration we have managed to allow two possible values for default/cgroupns/mode. The values are host and private. The default value if we do not set any configuration is private.

Let us verify that the metakeys have been set correctly:

NOTE: Excerpt of cat $(kdb file spec:/sw/docker/dockerd/#0/current).

cat $(kdb file spec:/sw/docker/dockerd/#0/current)
# [default/cgroupns/mode]
# meta:/check/enum/#0 = host
# meta:/check/enum/#1 = private
# meta:/description = Default mode for containers cgroup namespace
# meta:/default = private
# meta:/check/enum = #1
# [data/root]
# meta:/type = string
# meta:/description = Root directory of persistent Docker state
# meta:/default = /var/lib/docker

Wildcard specifications (for keys where a list of possible options can be used)

[default/ulimits/_]
meta:/type = long
meta:/description = Default ulimits for containers
meta:/example = 64000

For this specification we want to allow an arbitrary number of default ulimits. The name of the ulimits does not matter but all should have the same metakeys.

In order to get the above specification we will need following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/default/ulimits/_ type "long"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/ulimits/_ description "Default ulimits for containers"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/ulimits/_ example "64000"
# RET: 0

The above specification will allow us to create any name below the default/ulimits key. The value needs to be a string. The sample value is 64000.

Let us verify that the metakeys have been set correctly:

NOTE: Excerpt of cat $(kdb file spec:/sw/docker/dockerd/#0/current).

cat $(kdb file spec:/sw/docker/dockerd/#0/current)
# [default/ulimits/_]
# meta:/type = long
# meta:/example = 64000
# meta:/description = Default ulimits for containers
# [default/cgroupns/mode]
# meta:/check/enum/#0 = host
# meta:/check/enum/#1 = private
# meta:/description = Default mode for containers cgroup namespace
# meta:/default = private
# meta:/check/enum = #1
# [data/root]
# meta:/type = string
# meta:/description = Root directory of persistent Docker state
# meta:/default = /var/lib/docker

Array specifications (for keys where a list of possible options can be used)

[default/address/pools]
meta:/array/min = 0
meta:/description = Default address pools for node specific local networks (list)
[default/address/pools/#/base]
meta:/type = string
meta:/description = Ip address (ipv4) + subnet
meta:/example = 172.30.0.0/16
[default/address/pools/#/size]
meta:/type = short
meta:/description = Number of ip addresses in this pool with base
meta:/example = 24

The specification above shows the use of an array specification with the # character. We define the array to have a minimum value of 0 and arbitrary max length. We use type, description and example as metakeys on the keys beneath each array element.

This configuration above assures that we can configure pools with base and size.

It prevents a configuration like:

default/address/pools/#0/size = "test"

It will fail as default/address/pools/#/size is required to be of type short when set.

In order to get the above specification we will need following commands:

kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools array/min "0"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools description "Default address pools for node specific local networks (list)"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/base type "string"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/base description "Ip address (ipv4) + subnet"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/base example "172.30.0.0/16"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/size type "short"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/size description "Number of ip addresses in this pool with base"
# RET: 0
kdb meta-set spec:/sw/docker/dockerd/#0/current/default/address/pools/#/size example "24"
# RET: 0

The above specification defines that we can create array elements and each can have base or size.

Final specification code

Your specification should be complete now! After adding all the keys that are necessary for our application, you can verify that all specification keys are contained by running:

cat $(kdb file spec:/sw/docker/dockerd/#0/current)

NOTE: We want display the output because it is too long to display (~ 400-500 lines).

Adding full example specification (with kdb import)

The above tutorial has given a good overview of how to write a specification. You might want to add a full example specification for dockerd using kdb import. To do so, follow the next steps.

To make sure we don't run into errors we will clean up everything we have done by now.

  1. sudo kdb rm -r spec:/sw/docker/dockerd/#0/current
  2. sudo kdb umount spec:/sw/docker/dockerd/#0/current
  3. rm -rf $PWD/dockerd (make sure that you are in the same PWD as when you run the sudo kdb mount)

Now we are going to add an example of dockerd-full-spec.

Make sure you are in the root of the cloned libelektra repository:

  1. sudo kdb mount "$PWD/dockerd/dockerd-daemon.ni" spec:/sw/docker/dockerd/#0/current ni
  2. kdb meta-set spec:/sw/dockerd/dockerd/#0/current mountpoint /dockerd/daemon.ni
  3. kdb meta-set spec:/sw/dockerd/dockerd/#0/current infos/plugin "yajl"
  4. sudo kdb spec-mount "/sw/docker/dockerd/#0/current"
  5. sudo kdb import spec:/sw/docker/dockerd/#0/current ni < ./examples/spec/dockerd.ini

To verify that everything was created successfully, run:

cat $(kdb file spec:/sw/docker/dockerd/#0/current)

NOTE: We want display the output because it is too long to display (~ 400-500 lines).

Appendix (full specification)

The full specification can be viewed at dockerd-full-spec.