Proof of Concept: Dynamic Runtime Configuration with s6 Overlay Using Environment Variables

Dynamic Runtime with s6 Overlay

In the world of containerized applications, managing runtime configurations efficiently is crucial for ensuring flexibility, scalability, and maintainability. One of the challenges developers face is how to dynamically configure services based on the environment in which the container is running. This article explores a proof of concept (PoC) that demonstrates how to define runtime configurations for the s6 overlay system using environment variables. By leveraging environment variables, we can create a single Docker image that adapts its behavior based on the runtime context, reducing the need for multiple images or complex configuration management.

What is s6 Overlay?

s6 is a lightweight process supervision suite designed for Unix systems. It is often used in containerized environments to manage the lifecycle of services. The s6 overlay is a popular implementation that provides a way to manage multiple services within a single container, ensuring that they start, stop, and restart correctly.

The s6 overlay works by defining services in a directory structure under /etc/s6-overlay/s6-rc.d. Each service has its own directory containing configuration files that define how the service should behave. These configurations include scripts for starting (up), stopping (down), and finishing (finish) the service, as well as dependencies and timeouts.

The Challenge: Dynamic Runtime Configuration

In a typical containerized application, different environments (e.g., development, staging, production) may require different configurations. For example, an API service might need to run with different settings or dependencies depending on whether it’s deployed as part of a larger system or as a standalone service. Traditionally, this would require creating multiple Docker images or using complex configuration management tools.

The goal of this PoC is to demonstrate that it’s possible to define the runtime configuration of s6 services dynamically, based on an environment variable. This approach allows us to use a single Docker image that adapts its behavior based on the runtime context, simplifying the deployment process and reducing the overhead of managing multiple images.

The Proof of Concept

Overview

The PoC consists of a Docker image that contains multiple s6 service configurations. The image is designed to run in different modes (e.g., api, worker-alpha, worker-beta) based on the value of an environment variable (S6_RC_ID). When the container starts, a custom entrypoint script copies the appropriate configuration files from a predefined directory structure into the /etc/s6-overlay/s6-rc.d directory, effectively configuring the s6 overlay to run the desired services.

Key Components

  1. Dockerfile: The Dockerfile defines the base image, installs necessary dependencies (e.g., nginx), and copies the s6 configuration files and entrypoint script into the image.
  2. Entrypoint Script: The entrypoint script (docker-entrypoint.sh) is responsible for dynamically configuring the s6 overlay based on the S6_RC_ID environment variable. It copies the appropriate configuration files from /s6-config/$S6_RC_ID to /etc/s6-overlay/s6-rc.d.
  3. s6 Configuration Files: The s6 configuration files are organized in a directory structure under /s6-config. Each directory corresponds to a different runtime mode (e.g., api, worker-alpha, worker-beta) and contains the necessary configuration files for the services that should run in that mode.
  4. Docker Compose File: The Docker Compose file defines three services (api, worker-alpha, worker-beta) that use the same Docker image but are configured differently based on the S6_RC_ID environment variable.

Detailed Walkthrough

Dockerfile

The Dockerfile starts with a base image (php80-ls2-worker) and installs nginx as a dependency. It then copies the s6 configuration files and the entrypoint script into the image.

FROM php:8.4-alpine

LABEL io.motork.poc.s6-runtime-config.authors="Alexandru Busuioc <alexandru.busuioc@motork.io>"

ENV S6_VERBOSITY=0

# Copy s6 configuration
COPY ./s6-config /s6-config
COPY ./build/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
RUN chmod +x /usr/local/bin/docker-entrypoint && apk add --no-cache nginx

Entrypoint Script

The entrypoint script (docker-entrypoint.sh) is responsible for dynamically configuring the s6 overlay based on the S6_RC_ID environment variable. It first cleans up the /etc/s6-overlay/s6-rc.d directory and then copies the appropriate configuration files from /s6-config/$S6_RC_ID to /etc/s6-overlay/s6-rc.d.

#!/bin/sh
set -e

# Copy the files from /s6-config/$S6_RC_ID to /etc/s6-overlay/s6-rc.d
rm -fr /etc/s6-overlay/s6-rc.d # cleanup the s6-rc.d directory first
: ${S6_RC_ID:=api} # the default is "api" if S6_RC_ID is not set or it is empty string
cp -r /s6-config/"$S6_RC_ID" /etc/s6-overlay/s6-rc.d

exec docker-php-entrypoint "$@"

s6 Configuration Files

The s6 configuration files are organized in a directory structure under /s6-config. Each directory corresponds to a different runtime mode (e.g., api, worker-alpha, worker-beta) and contains the necessary configuration files for the services that should run in that mode.

For example, the api mode includes configurations for ls2-php-fpm and ls2-nginx services, while the worker-alpha and worker-beta modes include configurations for ls2-workers-watcher services.

/s6-config
  /api
    /ls2-php-fpm
      /type
      /run
      /finish
    /ls2-nginx
      /type
      /run
      /finish
      /dependencies.d
  /worker-alpha
    /ls2-workers-watcher
      /type
      /up
      /scripts
        /when-down
      /down
      /timeout-finish
  /worker-beta
    /ls2-workers-watcher
      /type
      /up
      /scripts
        /when-down
      /down
      /timeout-finish

Docker Compose File

The Docker Compose file defines three services (api, worker-alpha, worker-beta) that use the same Docker image but are configured differently based on the S6_RC_ID environment variable.

services:
  api:
    image: io.motork.poc.s6-runtime-config:dev
    environment:
      S6_RC_ID: api
  worker-alpha:
    image: io.motork.poc.s6-runtime-config:dev
    environment:
      S6_RC_ID: worker-alpha
  worker-beta:
    image: io.motork.poc.s6-runtime-config:dev
    environment:
      S6_RC_ID: worker-beta

Running the PoC

To run the PoC, follow these steps:

  1. Build the Docker Image: Use the provided Makefile to build the Docker image.
   make build
  1. Start the Services: Use Docker Compose to start the services.
   make up
  1. View Logs: Monitor the logs to see the services starting up and running.
   make logs
  1. Stop the Services: When done, stop the services.
   make down

Conclusion

This proof of concept demonstrates that it is possible to define the runtime configuration of s6 services dynamically based on an environment variable. By using a single Docker image and dynamically configuring the s6 overlay at runtime, we can simplify the deployment process and reduce the overhead of managing multiple images. This approach is particularly useful in environments where the same image needs to be deployed in different contexts with varying configurations.

The key takeaway is that environment variables can be a powerful tool for managing runtime configurations in containerized applications. By leveraging them effectively, we can create more flexible, scalable, and maintainable systems. This PoC serves as a foundation for further exploration and refinement, potentially leading to more advanced configuration management strategies in the future.

Leave a Reply

Your email address will not be published. Required fields are marked *