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:
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 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'
Other resources
List of other useful resources related to the debops.lxc
Ansible role:
Manual pages: lxc(7), lxc.conf(5), lxc.system.conf(5), lxc.container.conf(5)
LXC page in Debian Wiki
Linux Containers page in Arch Linux Wiki
LXC 1.0 blog post series written by Stéphane Graber