Skip to content

Proposal: Restructure cmdeploy to facilitate container image builds #627

@cliffmccarthy

Description

@cliffmccarthy

To build container images for deployment under runtime environments such as Docker, we need an image, which contains the filesystem content that will be visible to processes running in the containers. Use of containers will facilitate swapping out individual components of the chatmail software environment in the future, but for an initial baseline implementation, we can aim to match the "software stack" of a server with host-based services under systemd. We already have a well-defined process for creating this stack, in the pyinfra-based cmdeploy code. Reusing this code to build an image provides the benefit of having a single process that produces matching results under both deployment methods, and that will stay aligned over time as the processes are changed.

A common practice in container development is to run one specific application in each container, and to make each image as small as possible, with only the content that container absolutely needs. For now, we do not have to aspire to that practice here. Instead, we can build a single image with many applications installed in it, that looks basically like the software stack of a host-based deploy. We can still run multiple containers with that image, using different entrypoint and command arguments to differentiate the containers into specific applications.

The current process implemented by cmdeploy performs three primary types of operation:

  1. Installation of software, universal across all deployments.
  2. Configuration of software, with deploy-specific variations.
  3. Activation of services.

We can further divide the second item into:

  • 2a. Configuration that is essential to the definition of a chatmail server and the same across all deployments.
  • 2b. Configuration of "preferences" that vary among some deployments, but may be common across groups of deployments.
  • 2c. Configuration of information that is completely unique to a single deployment, including anything with the server name baked in, and "secrets" such as private keys.

For a general-purpose container image, it is desirable to do as much as possible in the image build stage, incorporating everything that would be common across all deploys of that image. This would clearly include everything from item 1. Ideally, it would also include everything from item 2a, while the information for items 2b and 2c would be injected at runtime through arguments, environment variables, or file mounts. However, the applications in the stack do not generally divide their configurations along those boundaries so cleanly for us. This means that we probably will always need some step in deployment where application configuration files are derived from source-controlled templates, applying deploy-specific parameters to generate files like dovecot.conf and main.cf.

The current implementation of cmdeploy is not explicitly divided along these categories of operation. For example, postfix installation is implemented in deploy_chatmail(), with configuration in a separate _configure_postfix() routine, while the installation and configuration of mtail is all handled in a single deploy_mtail() routine. The steps are also not grouped into a clear "installation phase" and "configuration phase" in the main routine, with the order of installation, configuration, and activation somewhat interleaved across different applications. It is also not immediately evident whether there are steps where the operations must occur in the current order -- for example, whether there are any steps where configuration of one component must occur before installation of another.

To build images, it would help to be able to call everything in item 1, the basic installation steps, and nothing else. This could be achieved by sprinkling 'if' statements throughout the code, but that is not the best approach for maintainability or understandability. Instead, I propose we consider refactoring the installation process explicitly around the three main phases of installation, configuration, and activation, while maintaining functional equivalence to the current process, to ensure compatibility with existing host-based deploys.

Once we have this explicitly represented in code, it should be straightforward to provide image builds a way to call only the installation steps, while allowing host-based deploys to continue calling all the operations for standing up the whole server.

I have some work in progress towards this goal, and can put it in a pull request soon.

How to handle the configuration operations in a container environment is another whole topic to consider, and probably worth its own issue. The refactoring proposed here will at least make it easier to examine the configuration phase on its own, as we evaluate options for how it should best be handled in a container-based deploy.

The third phase, activation, is fairly straightforward once the other details have been worked out. It can be achieved with a compose file, the equivalent docker run commands, or something similar. The exact method will likely be subject to the preferences of the administrator of the server. If we build phases 1 and 2 with clear interfaces for deployment, this step will fall into place fairly easily.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions