DEBOPS.LXC(5)

NAME
debops.lxc - Configure and manage LXC hosts using Ansible

SYNOPSIS

debops run service/lxc [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

DESCRIPTION

Linux Containers <https://en.wikipedia.org/wiki/LXC> or LXC provide a way to partition existing Linux hosts into separate environments using Linux cgroups, namespace isolation, POSIX capabilities and chrooted filesystems.

The debops.lxc Ansible role can be used to configure LXC support on a Debian/Ubuntu host. It can manage configuration files in /etc/lxc/ directory and provide custom scripts that allow, for example, initial bootstrapping of the user's SSH public keys inside of the container so that it can be managed remotely with Ansible.

GETTING STARTED

  LXC support in DebOps

This role focuses only on the LXC configuration itself. You will need to use other DebOps roles to manage additional required subsystems.

The role will configure an internal lxcbr0 bridge for the local Linux Containers, using the lxc-net service. The internal network will have its own DHCP/DNS server with lxc.{{ ansible_domain }} DNS domain by default.

You can configure a DNS proxy on the LXC host to be able to access the LXC containers by their DNS names instead of their IP addresses. The debops.dnsmasq and debops.unbound Ansible roles will automatically integrate with the LXC host configuration using Ansible local facts and will generate the configuration necessary to access the lxc.{{ ansible_domain }} DNS domain. The role will detect presence of the resolvconf or the systemd-resolved service on the host using Ansible local facts provided by their respective DebOps roles and will integrate the LXC network with the available resolver.

If the LXD support is also configured on the host (detected by the inventory host being included in the [debops_service_lxd] Ansible inventory group), the role will disable and deconfigure any existing lxcbr0 bridge, and will not configure it initially. You should refer to the LXD documentation for details about network bridge management.

Additional bridge network interfaces can be maintained using the debops.ifupdown role. By default the ifupdown role creates the br0 network bridge attached to the external network, which is defined in the LXC configuration as the default network interface to attach the containers, if the internal bridge is disabled.

The containers will use DHCP to get their network interface configuration. You can use debops.dnsmasq or debops.dhcpd Ansible roles to manage a DHCP service on the LXC host or elsewhere in the network.

  Unprivileged containers

You can create unprivileged LXC container owned by the root account by using the command:

lxc-create -n <container> -f /etc/lxc/unprivileged.conf \
           -t download -- --dist debian --release stretch --arch amd64

The role will install the lxc-new-unprivileged script which provides an equivalent functionality to the above command. With it, you can create new LXC unprivileged containers by running:

lxc-new-unprivileged <container>

The container will be configured to use subordinate UID/GID range defined by the debops.root_account Ansible role in the /etc/subuid and /etc/subgid databases. Since it's a container owned by root, it will be automatically started on the boot of the host.

Multiple LXC containers that use the same set of subUIDs/subGIDs might be able to access each others' resources in the case of a breakout, since from the perspective of the host their UIDs/GIDs are the same. You might want to consider this in the planning of your environment and use multiple subUID/subGID ranges for different LXC containers or groups of them.

Currently unprivileged LXC containers managed in the DebOps environment should be fairly secure, however you might want to consider enabling AppArmor for increased security against attacks directed at the LXC host.

  Privileged containers

You can create a privileged LXC container (default) by using the command as the root account:

lxc-create -n <container> -t debian

To select a different release, use this command:

lxc-create -n <container> -t debian -- -r jessie

You can also specify a different default configuration file, to for example connect the container to specific network bridge:

lxc-create -n <container> -t debian -f /etc/lxc/privileged.conf

Remember that privileged LXC containers are not secure and can modify the LXC host configuration. Don't use privileged containers in production environments, and don't allow untrusted users access to the root account inside of these containers.

The default configuration defined by the role is pretty simple, you can find the configuration of each created LXC container in the /var/lib/lxc/<container>/config configuration file.

  LXC containers are managed by systemd

The LXC containers created by the role tasks or by the lxc-new-unprivileged script are managed by systemd, specifically by the lxc@.service unit. This is done because on Debian Stretch using the lxc-stop command directly to stop a container results in a timeout and container processes being forcibly killed by the system. On Debian Stretch and Ubuntu Xenial distributions, the lxc@.service systemd unit is modified by the debops.lxc role to shutdown the container "from the inside" via the lxc-attach command, which results in a properly shut down container.

The containers are configured to not start automatically by the lxc.service unit. Instead, each LXC container has its corresponding lxc@.service instance that will be started by systemd on system boot. On container destruction, either by the debops.lxc role or by the lxc-destroy command, the instance will be disabled automatically.

To start a LXC container using systemd instances, you can issue the command:

systemctl start lxc@<container>.service

To stop a running LXC container started by systemd, you can execute the command:

systemctl stop lxc@<container>.service

With this setup, you should avoid using the lxc-* commands that affect the containers directly, unless the container started by the systemd is stopped first. Otherwise the state of the container managed by the systemd instance might get desynchronized.

References and more details about the issues:

•   Debian Bug #831691: Please use lxc.haltsignal = SIGRTMIN+3 for systemd containers <https://bugs.debian.org/831691>

•   [lxc-users] Graceful Shutdown/Reboot <https://lists.linuxcontainers.org/pipermail/lxc-users/2017-February/012827.html>

•   GitHub issue: Please use lxc.haltsignal = SIGRTMIN+3 for systemd containers <https://github.com/lxc/lxc/issues/1085>

•   GitHub issue: lxc stop does not stop my container <https://github.com/lxc/lxd/issues/2947>

•   Forum post: 'lxc-stop -n <container>' takes too long <https://forum.turris.cz/t/lxc-stop-n-container-takes-too-long/6358>

  SSH access to containers

You can use the command below to start an existing LXC container and prepare SSH access to the root account:

lxc-prepare-ssh <container> [authorized_keys file]

The lxc-prepare-ssh is a custom script installed by the debops.lxc role. It will start the specified container, make sure that OpenSSH service is installed inside, and add the current user's ~/.ssh/authorized_keys contents on the root account inside of the container. The script will check if the ${SUDO_USER} variable is defined in the environment and use that user's ~/.ssh/authorized_keys file as source of SSH public keys. Alternatively, you can specify a custom file with authorized SSH keys to add in the container's /root/.ssh/authorized_keys file.

The lxc__default_container_ssh_root_sshkeys list can be used to specify a list of SSH identities which should be added to new LXC containers by default. The identities will be stored in the /etc/lxc/root_authorized_keys file on the LXC host; the lxc-prepare-ssh script will add them in specified LXC containers. This can be useful to define a set of default system administrators that should have access to all containers.

If the LDAP support is configured on a host and SSH key lookup in LDAP is enabled by the debops.sshd role, the script will look up the current user keys in LDAP directory as well - this ensures that the SSH access is configured even when the SSH public keys are not explicitly defined in the current user's ~/.ssh/authorized_keys file.

After that, the LXC container should be ready to be used remotely, at which point you can use normal DebOps bootstrap playbook and other playbooks to configure it.

  Predictable MAC addresses

The lxc-hwaddr-static script can be used to generate predictable, randomized MAC addresses for LXC containers, based on the container name. The script will automatically save the generated MAC addresses in the container configuration files. Multiple network interfaces defined by the lxc.network.type configuration option are supported.

The script can also be used as a "pre-start" LXC hook, to configure static MAC addresses at container start. This requires the container to be restarted for the new static MAC addresses to be used in network interface setup. This usage is enabled by default in DebOps via the common LXC container configuration.

  Example inventory

To enable LXC support on a host, it needs to be added to the [debops_service_lxc] Ansible inventory group:

[debops_all_hosts:children]
lxc_hosts
lxc_containers
[debops_service_lxc:children]
lxc_hosts
[lxc_hosts]
lxc-host    ansible_host=lxc-host.example.org
[lxc_containers]
webserver   ansible_host=webserver.example.org

By default, containers will use the lxcbr0 bridge managed by the role, with their own internal subdomain. You can use the debops.ifupdown Ansible role to configure additional network bridges on the LXC host, if you want to attach the containers to the public network.

  Remote LXC management without SSH access

Remote LXC containers without SSH access can be accessed indirectly using the lxc_ssh <https://github.com/andreasscherbaum/ansible-lxc-ssh> Ansible connection plugin included with DebOps. This requires direct access to the root account on the LXC host and LXC container (even with unprivileged LXC containers), due to the connection plugin limitations.

Example configuration of that connection in the Ansible inventory (variables specified in multiple lines for readability):

[debops_all_hosts:children]
lxc_hosts
lxc_containers
[debops_service_lxc:children]
lxc_hosts
[lxc_hosts]
lxc-host    ansible_host=lxc-host.example.org
[lxc_containers]
webserver    ansible_connection=lxc_ssh ansible_user=root
webserver    ansible_host=lxc-host.example.org
webserver    ansible_ssh_extra_args=webserver

The lxc_ssh connection plugin is unofficial and may not work correctly. Please report any issues, and if you know fixes for them, provide that as well!

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.lxc role:

---
- name: Manage LXC hosts
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_lxc' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: root_account
      tags: [ 'role::root_account', 'skip::root_account' ]
    - role: apt_preferences
      tags: [ 'role::apt_preferences', 'skip::apt_preferences' ]
      apt_preferences__dependent_list:
        - '{{ lxc__apt_preferences__dependent_list }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules:
        - '{{ lxc__ferm__dependent_rules }}'
    - role: python
      tags: [ 'role::python', 'skip::python', 'role::lxc' ]
      python__dependent_packages3:
        - '{{ lxc__python__dependent_packages3 }}'
      python__dependent_packages2:
        - '{{ lxc__python__dependent_packages2 }}'
    - role: sysctl
      tags: [ 'role::sysctl', 'skip::sysctl' ]
      sysctl__dependent_parameters:
        - '{{ lxc__sysctl__dependent_parameters }}'
    - role: lxc
      tags: [ 'role::lxc', 'skip::lxc' ]
# If a host has 'debops.dnsmasq' or 'debops.unbound' roles configured, execute
# its playbook in case that configuration applied by the 'lxc' role needs to be
# applied to 'dnsmasq' or 'unbound' services. This should ensure that the
# '*.lxc' subdomain for internal LXC containers is resolvable on the LXC host.
#
# If the host is not in the Ansible inventory groups required by the
# 'dnsmasq.yml' or the 'unbound.yml' playbooks, this should not impact
# anything.
- name: Configure dnsmasq service
  import_playbook: 'dnsmasq.yml'
- name: Configure unbound service
  import_playbook: 'unbound.yml'

  Ansible tags

You can use Ansible --tags or --skip-tags parameters to limit what tasks are performed during Ansible run. This can be used after a host was first configured to speed up playbook execution, when you are sure that most of the configuration is already in the desired state.

Available role tags:

role::lxc
    Main role tag, should be used in the playbook to execute all of the role tasks as well as role dependencies.

role::lxc:containers
    Execute tasks that manage LXC containers.

role::lxc:net
    Manage internal LXC network configuration.

role::lxc:dnsmasq
    Manage the dnsmasq instance of the internal LXC network.

  Other resources

List of other useful resources related to the debops.lxc Ansible role:

•   Manual pages: lxc(7) <https://manpages.debian.org/lxc(7)>, lxc.conf(5) <https://manpages.debian.org/lxc.conf(5)>, lxc.system.conf(5) <https://manpages.debian.org/lxc.system.conf(5)>, lxc.container.conf(5) <https://manpages.debian.org/lxc.container.conf(5)>

•   LXC <https://wiki.debian.org/LXC> page in Debian Wiki

•   Linux Containers <https://wiki.archlinux.org/index.php/Linux_Containers> page in Arch Linux Wiki

•   LXC 1.0 blog post series <https://stgraber.org/2013/12/20/lxc-1-0-blog-post-series/> written by Stéphane Graber

DEFAULT VARIABLE DETAILS

Some of debops.lxc default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  lxc__configuration

The lxc__*_configuration variables define the contents of the configuration files in the /etc/lxc/ directory.

  Examples

Select the default bridge interface used by new unprivileged LXC containers:

lxc__configuration:
  - name: 'unprivileged'
    options:
      - 'lxc.network.link': 'br0'

Change the default LXC configuration file used to generate LXC containers to unprivileged:

lxc__configuration:
  - name: 'lxc'
    options:
      - name: 'lxc.default_config'
        value: '/etc/lxc/unprivileged.conf'

The same change, written as a simple YAML dictionary:

lxc__configuration:
  - name: 'lxc'
    options:
      - 'lxc.default_config': '/etc/lxc/unprivileged.conf'

  Syntax

Each variable is a list of YAML dictionaries, each dictionary can contain specific parameters:

name
    Required. Name of the configuration file, saved as /etc/lxc/<name>.conf. This parameter is also used as a key while merging multiple configuration entries.

    Please note that the lxc package creates the /etc/lxc/default.conf configuration file. This file is not managed by the debops.lxc role and it's best not to overwrite it so that package upgrades don't have issues. In other words, don't use the default as the name of the configuration file.

filename
    Optional. Custom filename of the specified configuration. You need to include the .conf prefix in the filename.

state
    Optional. Specify desired state of a given configuration file. Possible states:

•   present or not specified: the file will be generated.

•   absent: the file will be removed if it exists, otherwise it won't be generated.

•   ignore: A given configuration entry will be ignored by the role.

comment
    Optional. A string or YAML text block with a comment added at the top of the configuration file.

raw Optional. A string or YAML text block with LXC configuration, which will be added as-is at the end of the configuration file.

options
    Optional. A YAML list of LXC configuration options defined as YAML dictionaries. Each dictionary key is an lxc.* configuration key, and the dictionary is the configuration value, defined as a string. The options lists from multiple configuration entries with the same name will be merged together.

    If the dictionary has name and value keys, a given dictionary is interpreted with specific parameters:

name
    The LXC configuration option. It's used as a key to merge configuration options. If your configuration uses the same configuration options multiple times, you need to differentiate each one, for example with a prefix or suffix.

alias
    Optional. An alternative option name which will be used in the configuration file. This can be used to allow multiple LXC options with the same name.

value
    The value of an LXC configuration option, a string or a YAML list of strings which will joined with spaces.

comment
    Option. a string or a YAML text block with a comment added to a given LXC configuration option.

separator
    Optional, boolean. If True, a blank line will be added before the option. It can be used to separate configuration into sections for better readability.

state
    Optional. Set a custom state for a given LXC configuration option. Known states:

•   present or not specified: the option will be present in the generated configuration file.

•   absent: the option will not be present in the generated configuration file.

•   comment: the option will be present, but commented out.

•   ignore: a given entry will be ignored during configuration file generation.

  lxc__containers

The lxc__containers variable can be used to define and manage LXC containers on a given LXC host. By default, DebOps configures LXC containers with static MAC addresses based on the container name, therefore the names used for LXC containers should be unique on a given subnet, even between different LXC hosts.

  Examples

Create a few LXC containers using defaults - unprivileged LXC container based on the LXC host OS distribution, release and architecture, with SSH support enabled:

lxc__containers:
  - 'smtp'
  - 'database'
  - 'webserver'

Remove some of the existing LXC containers from a host:

lxc__containers:
  - name: 'smtp'
    state: 'absent'
  - name: 'webserver'
    state: 'absent'

Create an LXC container using specific OS distribution and release, without SSH access configured inside the container:

lxc__containers:
  - name: 'mail-server'
    distribution: 'ubuntu'
    release: 'bionic'
    ssh: False

Create a privileged LXC container using lxc-debian LXC template with overridden template options:

lxc__containers:
  - name: 'privileged'
    config: '/etc/lxc/privileged.conf'
    template: 'debian'
    template_options: ''

Create custom directory on LXC host and share it between two unprivileged LXC containers using the debops.resources and debops.lxc roles, mounted at /opt directory inside of the containers:

resources__host_paths:
  - name: '/srv/shared/lxc-opt'
    state: 'directory'
    owner: '100000'
    group: '100000'
    mode: '0755'
lxc__containers:
  - name: 'container1'
    fstab: |
      /srv/shared/lxc-opt opt none bind 0 0
    state: 'started'
  - name: 'container2'
    fstab: |
      /srv/shared/lxc-opt opt none bind 0 0
    state: 'started'

  Syntax

The variable contains a list of LXC container names, or (as the extended format) YAML dictionaries, each dictionary defines a aprticular LXC container using specific parameters.

The parameters listed below correspond to the Ansible community.general.lxc_container module <https://docs.ansible.com/ansible/latest/collections/community/general/lxc_container_module.html> parameters. See its documentation for details. Most common parameters used to manage LXC containers are:

name
    Required. Name of an LXC container to manage. The names should be unique across all LXC hosts connected to the same subnet. The lxc-hwaddr-static LXC hook configured by DebOps will generate random, but predictable MAC addresses based on the container name.

state
    Optional. If not specified or started, the LXC container will be created and started. On initial creation, if started is specified explicitly, the role will restart the LXC container to use the static MAC addresses generated by the "pre-start" LXC hook.

    If absent, the role will remove an existing LXC container.

    If stopped, the existing LXC container will be stopped, if already running.

    If restarted, the container will be restarted on the next execution of the role.

    If frozen, the LXC container will be frozen on the next execution of the role.

config
    Optional. Absolute path to the LXC system configuration file which will be used to create the LXC container. If not specified, the configuration file defined in lxc__default_container_config variable will be used.

container_command
    Optional. A String or YAML text block with a command or a shell script to execute inside of the LXC container after it's started.

template
    Optional. Name of the LXC template to use for creating a given LXC container, for example download, debian, ubuntu. If not specified, the value of lxc__default_container_template variable will be used. You can find available LXC templates in the /usr/share/lxc/templates/ directory on the LXC host.

template_options
    Optional. A string with shell arguments passed to the template script. If not specified, arguments suitable for the lxc-download LXC template will be automatically generated based on the LXC host OS distribution, release and architecture. To override the automatic creation of arguments, specify an empty string.

    The parameters below can be used to configure additional aspects of the LXC containers when managed by the debops.lxc Ansible role:

fstab
    Optional. YAML text block with fstab(5) <https://manpages.debian.org/fstab(5)> configuration to mount filesystems inside of the LXC containers. If this parameter is specified, the role will create the /var/lib/lxc/<container>/fstab file with the contents of this parameter and configure the container to mount the filesystems specified in this file. Existing LXC containers are not modified.

    See the lxc.container.conf(5) <https://manpages.debian.org/lxc.container.conf(5)> lxc.mount option documentation for more details.

ssh Optional, boolean. If True, the role will use the lxc-prepare-ssh script to configure SSH access and authorized keys in a given LXC container. This will be done only at container creation time.

    If False, the role will not configure SSH access inside of the container. It can still be accessed via lxc-attach command; Ansible can use the lxc connection plugin locally, or lxc_ssh connection plugin remotely to configure the container without SSH access.

    If not specified, the value of lxc__default_container_ssh will determine the SSH status.

systemd_override
    Optional. YAML text block that contains systemd unit configuration for a particular LXC container instance. If specified, the configuration will be added or removed depending on the LXC container state. When the systemd configuration is changed, the LXC container will be restarted.

distribution
    Optional. Specify the name of the OS distribution to use with the lxc-download LXC template. If not specified, the lxc__default_container_distribution value will be used.

release
    Optional. Specify the name of the OS release to use with the lxc-download LXC template. If not specified, the lxc__default_container_release value will be used.

architecture
    Optional. Specify the name of the host architecture to use with the lxc-download LXC template. If not specified, the lxc__default_container_architecture value will be used.

    You can run the command:

    lxc-create -n container -t download -- -l

    to see the list of available unprivileged LXC container images, with distribution, release and architecture combinations.

AUTHOR
Maciej Delmanowski, Robin Schneider

COPYRIGHT
2014-2024, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others