I have been digging into LXD again and wanted to figure out how to manage and configure containers using LXD profiles. I touched briefly on managing container resources at the end of my last post Using LXD but there is so much more you can do with profiles.
LXD has a default profile pre-installed and it looks like this:
config: {}
description: Default LXD profile
devices:
eth0:
name: eth0
network: lxdbr0
type: nic
root:
path: /
pool: default
type: disk
name: default
used_by:
- /1.0/instances/ubuntu2004
Yours should look similar though it may not be exact. It is pretty generic. If you do not specify a profile when creating a container default is the profile that is used.
To get a list of the profiles that currently exist use the list command:
$ lxc profile list
+---------+---------------------+---------+
| NAME | DESCRIPTION | USED BY |
+---------+---------------------+---------+
| default | Default LXD profile | 1 |
+---------+---------------------+---------+
| demo | | 0 |
+---------+---------------------+---------+
In this case I have two profiles: default and demo. The “USED BY” column is a quick way to see how many containers use a particular profile. In my case, I have one container using the default profile and no containers using the demo profile. Since I have no containers using the demo profile let’s delete it so we can create and configure it later.
$ lxc profile delete demo
Profile demo deleted
$ lxc profile list
+---------+---------------------+---------+
| NAME | DESCRIPTION | USED BY |
+---------+---------------------+---------+
| default | Default LXD profile | 1 |
+---------+---------------------+---------+
One of the common uses of a custom profile is for constraining resources. Let’s create a profile to limit the number of CPU cores and memory available to a container. First, let’s determine how many cores and memory is available to the system using the nproc and free commands.
$ nproc
4
$ free -m
total used free shared buff/cache available
Mem: 7649 1737 156 472 5754 5142
Swap: 975 6 969
In the case of my demo machine I have 4 total cores and 8GiB of RAM. No matter what the container is doing I never want it to be able to use all of the CPU and RAM of my machine so I will constrain a container to one CPU and 1GiB of RAM. To do this I will have to add constraints to a profile. I could add them to the default profile, but it is conceivable that I may want some containers without these constraints. Instead, I will recreate the demo profile to hold the constraints. To get started I have to either create a new profile or copy an existing one. I will show both methods.
To create a new profile named demo:
$ lxc profile create demo
Profile demo created
$ lxc profile list
+---------+---------------------+---------+
| NAME | DESCRIPTION | USED BY |
+---------+---------------------+---------+
| default | Default LXD profile | 1 |
+---------+---------------------+---------+
| demo | | 0 |
+---------+---------------------+---------+
To create a new profile by copying an existing profile:
$ lxc profile copy demo demo2
$ lxc profile list
+---------+---------------------+---------+
| NAME | DESCRIPTION | USED BY |
+---------+---------------------+---------+
| default | Default LXD profile | 1 |
+---------+---------------------+---------+
| demo | | 0 |
+---------+---------------------+---------+
| demo2 | | 0 |
+---------+---------------------+---------+
No method is better than the other so use which method is best for your situation. Now it is time to add our CPU and memory constraints. Just like creating the profile, several options exist to add constraints. Constraints can be added one at a time using the profile set command or the profile can be edited in using your editor. They both accomplish the same goal. I will first create the CPU constraint using the profile set method.
$ lxc profile set demo limits.cpu 1
$ lxc profile show demo
config:
limits.cpu: "1"
description: ""
devices: {}
name: demo
used_by: []
I used profile show to verify that the profile set command worked, but you can verify just the constraint using the profile get command.
$ lxc profile get demo limits.cpu
1
With the CPU limit set, let’s set our memory constraint. It works the same as setting the CPU constraint. A matter of fact, setting constraints all work this way and there are too many items that you can configure for this blog post, but take a look at the listing of configuration settings at linuxcontainers.org for more.
$ lxc profile set demo limits.memory "1GiB"
$ lxc profile show demo
config:
limits.cpu: "1"
limits.memory: 1GiB
description: ""
devices: {}
name: demo
used_by: []
I mentioned earlier that it is possible to edit the whole profile in order to add all of the configuration at one time:
$ lxc profile edit demo
### This is a YAML representation of the profile.
### Any line starting with a '# will be ignored.
###
### A profile consists of a set of configuration items followed by a set of
### devices.
###
### An example would look like:
### name: onenic
### config:
### raw.lxc: lxc.aa_profile=unconfined
### devices:
### eth0:
### nictype: bridged
### parent: lxdbr0
### type: nic
###
### Note that the name is shown but cannot be changed
config:
limits.cpu: "1"
limits.memory: 1GiB
description: ""
devices: {}
name: demo
used_by: []
It is possible to remove configuration items as well. Using profile edit like the command above will work. Another way is to remove items individually using the unset command:
$ lxc profile unset demo limits.cpu
$ lxc profile show demo
config:
limits.memory: 1GiB
description: ""
devices: {}
name: demo
used_by: []
At this point I want the demo profile to look like this:
$ lxc profile show demo
config:
limits.cpu: "1"
limits.memory: 1GiB
description: ""
devices: {}
name: demo
used_by: []
I have limited any container using the demo profile to 1 CPU and 1 GiB of RAM. But if I examine my profiles I see that the demo profile is not being used by any containers.
$ lxc profile list
+---------+---------------------+---------+
| NAME | DESCRIPTION | USED BY |
+---------+---------------------+---------+
| default | Default LXD profile | 1 |
+---------+---------------------+---------+
| demo | | 0 |
+---------+---------------------+---------+
| demo2 | | 0 |
+---------+---------------------+---------+
Applying a profile to a container can be done either at the time we create the container or later to an already running container. If we check we will see that I already have an Ubuntu 20.04 container running so I will apply the demo profile to that.
$ lxc list
+------------+---------+---------------------+-----------------------------------------------+-----------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------------+---------+---------------------+-----------------------------------------------+-----------+-----------+
| ubuntu2004 | RUNNING | 10.7.249.161 (eth0) | fd42:db50:ff3d:1bfa:216:3eff:feeb:cc92 (eth0) | CONTAINER | 0 |
+------------+---------+---------------------+-----------------------------------------------+-----------+-----------+
$ lxc profile add ubuntu2004 demo
Profile demo added to ubuntu2004
lxc profile list
+---------+---------------------+---------+
| NAME | DESCRIPTION | USED BY |
+---------+---------------------+---------+
| default | Default LXD profile | 1 |
+---------+---------------------+---------+
| demo | | 1 |
+---------+---------------------+---------+
| demo2 | | 0 |
+---------+---------------------+---------+
Let’s check if the profile has been applied by running the nproc command in the container:
$ lxc exec ubuntu2004 nproc
4
That isn’t right! It should be 1. So what happened? It appears that you have to restart the container for the new configuration to take effect. Here is the command to restart the container:
$ lxc restart ubuntu2004
$ lxc exec ubuntu2004 nproc
1
Now when we re-run the nproc command it returns one CPU as we expected. If we check the RAM, we should see it is now set to 1GiB:
$ lxc exec ubuntu2004 free
total used free shared buff/cache available
Mem: 1048576 70232 925016 132 53328 978344
Swap: 999420 0 999420
If we look at what profiles are assigned to the container we see that the ubuntu2004 container actually has two profiles applied to it – both default and demo:
$ lxc list --fast
+------------+---------+--------------+----------------------+----------+-----------+
| NAME | STATE | ARCHITECTURE | CREATED AT | PROFILES | TYPE |
+------------+---------+--------------+----------------------+----------+-----------+
| ubuntu2004 | RUNNING | x86_64 | 2021/03/31 14:40 UTC | default | CONTAINER |
| | | | | demo | |
+------------+---------+--------------+----------------------+----------+-----------+
It turns out that you can stack profiles on a container with the last profile applied overriding the previous settings.
In this case this is fine, but if you only want one profile applied you can remove the unwanted profile from the container:
$ lxc profile remove ubuntu2004 demo
Profile demo removed from ubuntu2004
$ lxc list --fast
+------------+---------+--------------+----------------------+----------+-----------+
| NAME | STATE | ARCHITECTURE | CREATED AT | PROFILES | TYPE |
+------------+---------+--------------+----------------------+----------+-----------+
| ubuntu2004 | RUNNING | x86_64 | 2021/03/31 14:40 UTC | default | CONTAINER |
+------------+---------+--------------+----------------------+----------+-----------+
Finally, it is possible to specify the profile to use at container creation:
$ lxc launch ubuntu:20.04 ubuntu --profile demo
Containers can be configured in many ways not just resource constraints. We could also have applied network settings, firewall settings, and much more. But the process would be the same.