The instructions in this guide describe how to download and install the latest version of CFEngine Community in a Docker containerized environment using pre-compiled rpm packages and ubi9 images.

This guide describes how to set up a client-server model with CFEngine and, through policy, manage both containers.

Docker containers will be created, one container to be the Policy Server (server), and another container that will be the Host Agent (client).

Both the containers will run ubi9-init images and communicate on a container network. Upon completion, you are ready to start working with CFEngine.

Requirements

Note: This document considers Docker Engine for all examples. Use of Podman shall be similar with adequate adaptations. (_Ref_: Emulating Docker CLI with Podman).

Overview

  1. Installing container engine
  2. Preparing CFEngine hub in container
  3. Preparing CFEngine host in container
  4. Using docker compose
    1. Preparing container image for CFEngine
    2. Using docker compose service
  5. Glossary
  6. References

Installing container engine

Ref: Install Docker Engine

OR

Ref: Podman Installation Instructions (_Optionally_: Emulating Docker CLI with Podman)

Preparing CFEngine hub in container

Run the container with systemd

command
docker run --privileged -dit --name=cfengine-hub registry.access.redhat.com/ubi9-init /usr/sbin/init

Prepare the container for cfengine-hub

command
docker exec cfengine-hub bash -c "dnf -y update; dnf -y install procps-ng iproute sudo pip; pip install cf-remote"

Install cfengine-community package

command
docker exec cfengine-hub bash -c "cf-remote install --edition community --clients localhost"

Bootstrap cf-agent

command
docker exec cfengine-hub bash -c "/usr/local/sbin/cf-agent --bootstrap \$(ip -4 -o addr show eth0 | awk '{print \$4}' | cut -d'/' -f1)"

Preparing CFEngine host in container

The procedure to setup cfengine-host is similar to the cfengine-hub deployment. The changes are to name of the host container for better identification and bootstrap IP of the cfengine-hub.

command
docker run --privileged -dit --name=cfengine-host registry.access.redhat.com/ubi9-init /usr/sbin/init

Prepare the container for cfengine-host

command
docker exec cfengine-host bash -c "dnf -y update; dnf -y install procps-ng iproute sudo pip; pip install cf-remote"

Install cfengine-community package

command
docker exec cfengine-host bash -c "cf-remote install --edition community --clients localhost"

Bootstrap cfengine-host to the policy server container.

Find IP address of cfengine-hub:

command
CFENGINE_HUB_IP=$(docker exec cfengine-hub bash -c "ip -4 -o addr show eth0 | awk '{print \$4}' | cut -d'/' -f1")

Bootstrap cfengine-host to cfengine-hub:

command
docker exec cfengine-host bash -c "/usr/local/sbin/cf-agent --bootstrap ${CFENGINE_HUB_IP}"

Using docker compose

Preparing container image for CFEngine

Create a Dockerfile with following contents:

code
FROM registry.access.redhat.com/ubi9-init:latest
LABEL description="This Dockerfile builds container image based on ubi9-init and latest LTS release of cfengine-community."

RUN dnf -y update \
&& dnf -y install bind-utils iproute sudo pip procps-ng \
&& pip install cf-remote \
&& cf-remote install --edition community --clients localhost

HEALTHCHECK --interval=5s --timeout=15s --retries=3 \
    CMD /usr/local/sbin/cf-agent --self-diagnostics || exit 1

ENTRYPOINT ["/usr/sbin/init"]

Validate the Dockerfile

command
docker build -t cfengine:lts -f Dockerfile . --check
output
[+] Building 0.1s (3/3) FINISHED                                        docker:default
 => [internal] load build definition from Dockerfile                            0.0s
 => => transferring dockerfile: 596B                                            0.0s
 => [internal] load metadata for registry.access.redhat.com/ubi9-init:latest    0.0s
 => [internal] load .dockerignore                                               0.0s
 => => transferring context: 2B                                                 0s
Check complete, no warnings found.

Note: You can skip to Using docker compose service, as the image would be built as per compose.yaml file, if not present.

Build the docker image based on above Dockerfile:

command
docker build -t cfengine:lts -f Dockerfile .

Verify created image:

command
docker image ls cfengine
output
REPOSITORY   TAG        IMAGE ID       CREATED             SIZE
cfengine     lts        <IMAGE_ID>     About an hour ago   302MB

Using docker compose service

Create a compose.yaml file with following contents:

compose.yaml
name: cfengine-demo

services:
  cfengine-hub:
    container_name: cfengine-hub
    image: cfengine:lts
    build:
      context: .
      dockerfile: Dockerfile
    privileged: true
    command:
      - /bin/sh
      - -c
      - |
        "/usr/local/sbin/cf-agent --bootstrap $(ip -4 -o addr show eth0 | awk '{print $4}' | cut -d'/' -f1)"
    networks:
      - control-plane

  cfengine-host:
    image: cfengine:lts
    build:
      context: .
      dockerfile: Dockerfile
    privileged: true
    command:
      - /bin/sh
      - -c
      - |
        "/usr/local/sbin/cf-agent --bootstrap $(dig +short cfengine-hub|tr -d [:space:])"
    networks:
      - control-plane
    depends_on:
      cfengine-hub:
        condition: service_healthy
        required: true

networks:
  control-plane:

Validate the compose.yaml file

command
docker compose -f compose.yaml config 1>/dev/null

Note: No output means valid yaml file.

Start service cfengine-demo

command
docker compose -f compose.yaml up -d

Bootstrap hub and hosts

command
docker exec -it cfengine-hub bash -c "/usr/local/sbin/cf-agent --bootstrap \$(ip -4 -o addr show eth0 | awk '{print \$4}' | cut -d'/' -f1)"
output
R: Bootstrapping from host '192.168.16.2' via built-in policy '/var/cfengine/inputs/failsafe.cf'
R: This host assumes the role of policy server
R: Updated local policy from policy server
R: Triggered an initial run of the policy
R: Restarted systemd unit cfengine3
notice: Bootstrap to '192.168.16.2' completed successfully!
command
docker exec -it cfengine-demo-cfengine-host-1 bash -c "/usr/local/sbin/cf-agent --bootstrap \$(dig +short cfengine-hub|tr -d [:space:])"
output
notice: Bootstrap mode: implicitly trusting server, use --trust-server=no if server trust is already established
notice: Trusting new key: MD5=2f406e11cfd3e08d810d77a186e204e2
R: Bootstrapping from host '192.168.16.2' via built-in policy '/var/cfengine/inputs/failsafe.cf'
R: This autonomous node assumes the role of voluntary client
R: Updated local policy from policy server
R: Triggered an initial run of the policy
R: Restarted systemd unit cfengine3
notice: Bootstrap to '192.168.16.2' completed successfully!

Health-check for hub and host

command
docker exec -it cfengine-hub bash -c "/usr/local/sbin/cf-agent --self-diagnostics"
output
...
[ YES ] Check that agent is bootstrapped: 192.168.16.2
[ YES ] Check if agent is acting as a policy server: Acting as a policy server
[ YES ] Check private key: OK at '/var/cfengine/ppkeys/localhost.priv'
[ YES ] Check public key: OK at '/var/cfengine/ppkeys/localhost.pub'
...
command
docker exec -it cfengine-demo-cfengine-host-1 bash -c "/usr/local/sbin/cf-agent --self-diagnostics"
output
...
[ YES ] Check that agent is bootstrapped: 192.168.16.2
[ NO  ] Check if agent is acting as a policy server: Not acting as a policy server
[ YES ] Check private key: OK at '/var/cfengine/ppkeys/localhost.priv'
[ YES ] Check public key: OK at '/var/cfengine/ppkeys/localhost.pub'
...

Stop services and cleanup

command
docker compose -f compose.yaml down

Glossary

References