LXC and LXD: a different container story
lwn.net
lwn.net
When LXD was running, I found that it feels like something between a VM and OCI container. In the whole pets vs cattle analogy, LXD containers definitely feel more like pets.
On the host, concepts are similar to OCI containers (networking, macvlan, port mapping, etc). I like its approach to building up containers with several commands instead of one long command. You can add and remove ports, volumes, and even GPUs to running containers. It doesn't have compose files, I just used bash scripts to create reproducible containers.
Inside the LXD container is where things get different. You're containerizing a whole OS, not an application. So you end up administering your container like a VM. You jump inside it, update its packages, install whatever application and dependencies you need. It doesn't work like a glorified package manager (which is how most homelabbers use Docker). As a long time VM user I actually prefer installing things myself, but I can see this turning away many people in the selfhosting community.
I liked LXD and would've kept using it if it weren't so intimately linked to Snap. Last month it failed to start my containers after a system update...again. So I moved that box over to Rocky with Podman containers running as SystemD units. I do kinda miss having fast, throwaway OSes on that server for experiments (sometimes its nice to have pets).
However, I once wanted to rename my zpool. Boy was I up for a world of hurt. In the end, I just reinitialized everything (the VMs themselves were easy to get back up).
Enabling LXD is a one-liner on NixOS:
virtualisation.lxd.enable = true;
LXC and LXD are a somewhat common approach for NixOS users who want to quickly try something out in the environment of a traditional distro. :)The Debian package for LXD just entered the archive about 2 weeks ago:
https://tracker.debian.org/news/1361535/accepted-lxd-500-1-s...
This means the next Debian release will make for stable LXD hosts without needing snap.
Literally all that is installed is Alpine Linux and the SSH server. There's a symbolic link to get to the shared files.
I've since switched to podman because my installation on Alpine Edge kept breaking on updates and I don't want to use Snap. Someone mentioned OpenSuse had it in their package manager and its been stable. Maybe I'll check that out in the future.
I guess you could technically replace what we built with pure Docker but the Proxmox UI is a godsend for our on the ground support engineers who aren't the most technically savvy types.
We started going down the Kubernetes route initially but it quickly turned into an absolute nightmare for us and we gave up quickly and we're all pretty strong with Kubernetes. I guess though when all you need is a few stateful services running the "dynamic scaling" of kubernetes is kind of pointless.
Why can't you do this without BTRFS/LXD and just with Docker/OCI?
How is this different than a Docker/OCI container/cgroup thingie?
I also use lxd on vps hosts, often nesting docker to give more granular control over networking.
It's too bad no one appears to be working on that in OCI/Kubernetes world.
EDIT: very interesting, thanks TIL! Here's at least someone from RH playing with it in the context of K8s: https://youtu.be/DDJxqV98b4U?t=1932
EDIT2: and apparently, at least its minimal implementation has been merged: https://github.com/kubernetes/kubernetes/pull/104907
on my own servers i use plain LXC without proxmox, but created and managed through saltstack for similar reasons. and also to allow me to use different linux distributions for different use cases, and to seperate various development environments
A single i7 with 32GB of RAM can have at least 3 people working on it without hassle, like an old time-sharing system. Myself, I stuck a Raspberry Pi to a monitor (https://taoofmac.com/space/blog/2022/08/14/2030) and use it to access both GNOME and Windows desktops remotely.
All of this works solely with Ubuntu system packages, so I have no third party software to install (yes, ok, snap is a pain, but the host only runs KVM and LXD, nothing else, and all distros have xorgxrdp these days).
It’s like a lightweight Proxmox.
In the past, I used LXC to host several of my services on a single beefy Linode box, and I would go that way again today - I already have a similar setup inside Parallels on my M1 Mac, with one LXC per project.
Though I'm an old fashioned pets-not-cattle type. Docker and friends have the disadvantage of being invented (or becoming practical/popular) in/after my mid 30s!
I also use it to host a Jitsi instance. Jitsi is rather picky about the underlying distribution.
All this without the overhead of an actual virtual machine of course.
The only pain I have with LXC is its behavior when errors occur. It’s not easy to tell what’s wrong, the error messages are worthless. Sometimes the debug log can help, sometimes not so much.
I'm interested to see how you bypassed the Mac address problem, any blogs, guides ?
Me I could not get it to work reliably so I added another bridge on my servers which link via wireguard. It works like a lan for both the physical servers and VMs.
Note that Hetzner added support for multiple MAC addresses in the meantime. So at least for IPv4, you don’t need this.
There’s good info in the Linux kernel docs: https://www.kernel.org/doc/html/latest/networking/ipvlan.htm...
I have a Debian host. In my interfaces file, I have the following code for the Jitsi ipvlan link:
auto ipvl-meet
iface ipvl-meet inet manual
pre-up ip link add link eth0 name ipvl-meet type ipvlan mode l2
post-down ip link delete ipvl-meet
Then, in my LXC guest config, I have this: lxc.net.0.type = phys
lxc.net.0.link = ipvl-meet
lxc.net.0.ipv4.address = 192.0.2.46/26
lxc.net.0.ipv4.gateway = 192.0.2.1
lxc.net.0.ipv6.address = 2001:0DB8::4/128
lxc.net.0.ipv6.gateway = fe80::1
A word of advice though: LXC likes to eat network interfaces when it fails to start a container. If you experiment, keep in mind that you may have to recreate the ipvlan interface after errors.Does this apply to the IP supplied with the server? Because I can't find any Mac address option for it.
Are we talking about something else?
This sort of mediation makes sense and works great for ephemeral containers in Kubernetes. For full virtual servers, LXD is the worst of both worlds.
LXD is a treat to work with, and I feel its container model is perfect for home/small business servers that need to run lots of third-party software, much of which would work poorly in a "proper" stateless container.
First one you supply the image you want (like ubuntu:20.04) and the rest are self explanatory.
I still use lxd on an Ubuntu 18.04 host that still has the deb instal but looking forward I’m trying to get things migrated to systemd-nspawn.
Still Dockerfiles and all the magic that goes with them was just so much easier to pick up and convince others on the team to try.
The justification was that we needed our SaaS to also work on-prem for financial companies and government entities, and thus we could not count on kubernetes or any specific cloud vendor to be available, so we rolled our own orchestration built on ubuntu hosts and (for some stupid reasons) centos LXC containers running on top of those.
LXC is a good system and all, but what we were doing with it was a total nightmare, we were basically trying to reinvent kubernetes using traditional configuration management and LXC with two flavors of linux and a million dependencies.
....? kubernetes is not a cloud vendor, it runs on any Linux distribution, and it's FOSS.... ?!
To be fair, the situation ended up being: bunch of clients running minikube, 1 major client with their own kubernetes cluster, and a lot of other clients using the multi tenant, cloud hosted, offering. Made it a bit of a pain to support that one client with a cluster we could not control, but it was one of our biggest clients, so, them the breaks.
I think that feature combined with overlayfs2 is quite useful, despite my many criticisms of Docker.
It is sort of a middle ground between Nix like granularity which requires rewriting upstream, and big LXC blobs created with shell scripts.
Although I also think we need some kind of middle ground between docker and nix :)
Never used LXD, but LXC does not have layers per-se.
I usually run LXC containers on a btrfs filesystem, which easily supports snapshots and sending containers to other container hosts via "btrfs send | ssh otherhost btrfs receive".
If you are treating your servers like pets (and not cattle) LXC is a very convenient means to consolidate servers onto fewer hardware systems.
It's kind of a similar situation in LXD, where that sort of functionality being available depends on the storage backend/driver in use, for example LVM thin provisioning, various options via ZFS, or btrfs like you mentioned.
On the other hand I think storage and networking should be more unified, and the differential compression and caching you get from layers is important
I suspect that doing the layers at the logical level (files) , like overlayfs, rather than the physical level (blocks) is also better for containers, but I'd be interested to read any comparisons
Also, at least Btrfs snapshots are more logical, like files; than physical, like blocks. Whereas LVM snapshots are separate (logical) block devices, Btrfs snapshots are file b-trees on the same file system.
Maybe the overlay2 graph driver (the driver used in Docker or Podman to interface with the kernel) is getting more usage and thus maturing more quickly, compared to the btrfs graph driver? I think upstream Docker/Moby are defaulting to the btrfs graph driver if /var/lib/docker is on Btrfs. Meanwhile on Fedora where Btrfs is the default for desktops, and Cloud edition, Podman defaults to overlay2 graph driver no matter the underlying file system.
Storage with LXC is just a block device, so you could roll your own using overlayfs. Probably a fair amount of work though. And in the other direction, I have used btrfs instead of overlayfs with Docker.
One trend with docker that I personally don't like is that a lot of projects prefer docker-compose over regular Dockerfiles (though some of them support both), and this leads to a lot of bloat with how many containers it takes to run one application and duplication where each app will need its own database, redis, webserver, etc. containers.
That's not a problem when you are at the scale to need that kind of orchestration and have the resources to run that many containers, but personally I would rather have one database host that all my containers can talk to and one reverse proxy/webserver to make them accessible to make better use of resources and get better density on one host.
One downside with lxc is that there's been some fragmentation with the image creation side of it, I had been used to using lxc-templates for years, which are just shell scripts for bootstrapping and configuring your container and pretty common between distros. I found a bug with creating containers for the latest version of Alpine that I fixed, and only then did I find out that lxc-templates are deprecated and essentially unmaintained, and that now distrobuilder is the preferred way to build lxc containers, at least per Canonical. That seems to be another Canonical-ism, since the distrobuilder docs say that snap is the only way to install it unless you build from source, so that's a huge no from me, and I also didn't feel like learning the yaml config for it.
I was actually considering moving my docker daemon into an unprivileged lxc container like the article mentions, but haven't gotten around to it.
* Afaik, this only works on Proxmox, not LXC in bare Debian, because Proxmox uses a modified Debian Kernel with some things taken from Ubuntu.
[1]: https://du.nkel.dev/blog/2021-03-25_proxmox_docker/
[2]: https://nixos.org/I find it a big advantage that this builds the containers basically from scratch and you only have to trust the distro and not any other parties. I'm always feeling uneasy with images which are hard to inspect and whose provenance is opaque and potentially involved multiple parties.
That, combined with running them unprivileged, should make a fairly secure system. Unfortunately I had great difficulty to create an unprivileged Debian LXC container with this method. If I remember correctly you have to create the privileged container first and then use a tool to fix-up uid and gid. If anyone knows an easier way to do it, I would be grateful to know.
EDIT: I think I used the following to create the container:
lxc-create -n <name> -t debian -- -r stretch
It uses deboostrap for the build.To create the container, I did this:
lxc-create -t download -n <name> -- -d debian -r bullseye -a amd64
Note that this runs the 'download' template, which (IIRC) is better suited to unprivileged containers than the 'debian' template is. The 'download' template will list its available distros if you do this:
lxc-create -t download -n <name> -- --list
Note that some versions of the 'download' template may fail with a keyserver error because sks-keyservers.net died somewhat recently. Workaround: DOWNLOAD_KEYSERVER=hkp://keyserver.ubuntu.com lxc-create ...
Have a few apps running in plain LXC containers (like AdguardHome) but maintenance is non-free (unlike a docker-compose stack with Watchtower keeping everything nice and fresh).
That said, LXD is good for containerizing OS's, not single applications. So when you say "defining reproducible environments" you can get reproducible containers, but not so much environments. In that sense, it behaves more like a VM.
Linux folks love saying: use the right tool for the right job when it comes to telling people why Kubernetes is a strong enough reason for any company to switch to linux, however when it comes to things such as lightweight containers (or ZFS-on-root for the matter) for some reason the same reasoning isn't applied.
LXC and LXD look like a patchwork compared to FreeBSD jails. If you are on the desktop and want to run graphical apps, maybe it can make some kind of sense if you are already using Linux for everything else. But if you want to have a lightweight container setup on a headless server, FreeBSD is by far the best option.
In FreeBSD land, no one boasts of running 5 virtualized environments on a single machine because this is just a common thing for us and part of every workfow. You want to work on some github repository? You create a new jail in 1 minute using a tool such as cbsd.
A server with 32gb of RAM can easily run 1000s of jails, even a small VPS with 1-2gb of RAM can run a dozen jails or more without problems. It is intriguing that so many people dismiss the power of that.
And it is intriguing that people easily accept: "there is no equivalent to dockerfiles but it's fine because things can be scripted easily with basic shell commands" when it comes to LXC/LXD but people dismiss this very argument when FreeBSD folks try to explain why there is no need for Kubernetes on FreeBSD.
Anyway, if you run a startup not operating a resource intensive products (like ML requiring spinning up massive amounts of cores and RAM in an irregular basis) FreeBSD and jails can really save massive amounts of time and allow you to focus on the right thing. just run everything from a single Root VPS (usually hosting companies already handle things such as RAID replication and hardware redundancy transparently) and before you reach scalability problems, you'll already be making millions of dollars in revenue.
Even after, running thousands of jails on a single hosy essentially means doing what AWS and other cloud providers are doing. This doesn't prevent you from builing a small high-availability layer on top to ensure that critical apps run from at least two different physical hosts. This isn't very difficult to build with things like Consul, HA proxy, built-in system utilities and a small custom web interface.
Even my mail server runs in LXC, and it is a direct descendant of a Red Hat 5 server from ~20 years ago!
I've often seen LXD vs Docker described as LXD being better suited to running distros and distro-like environments, with Docker being better suited to single-application environments.
So that's what I do. For many of dev and testing environments, there's an LXD container, which I use over SSH/rsync daily. Compiles, long-running tests, bandwidth heavy network actions and so on are done in those containers, on a few fast servers in data centres. It's much faster than my laptop.
One motivation for running Linux in LXD containers instead of on bare metal was to allows me to decouple changes to host OS version and networking from the containers in which I do most my work. Previously I used bare metal, i.e. ran Linux on servers, but found it annoying that I couldn't update the host OS and especially the kernel or disto major version, without shutting down everything in Screen sessions and long-lived networking tests that are not so easy to shut down and restart (without a rewrite anyway).
Being containers, they can use host filesystems and devices almost directly. So they are great for things like I/O performance tests, with confidence that it's basically testing host performance and behaviour.
Unfortunately, after getting deep into both those things, I found LXD wasn't quite as run-a-distro friendly as it first appeared, and it wasn't as reliable at replicating host I/O performance either.
Doing file I/O with "shift=true" host-filesystem mounts in LXD (the most sensible mode) turns out to be have very much slower O_DIRECT performance than it should, so that screwed up my database storage tests unexpctedly until I realised. Now I run low-level storage performance tests outside LXD, because I don't trust it.
As for distro-like environments, I eventually found stateful snapshot+restore or migation of dev and test environments is permanently broken. No LXD container I've used over many years has ever successfully been able to live-snapshot/migrate, without hitting an error which prevents it. This isn't some obscure bug, either. It's never worked, and my browsing of forums and issue trackers leads to the view that it's not actually expected to work for almost any distro-like environment, despite being one of the headline features.
As a result, one of the main factors motivating using LXD instead of bare metal for me turned out not to work. It's always possible to shut down a container, but that loses so much state, long-lived Screen sessions, running processes and so on that it's about as disruptive as updating bare metal. I.e. no real advantage.
I could switch back to VMs, which are excellent for snapshotting and migration, as I used to use (with libvirt+kvm) but they have a different problem for much of my work: Host filesystem/blockdev sharing is relatively slow. Not only could I not use them to measure performance against various kernels on real hardware (it would be measuring the VM as much as anything), I also run many data-intensive jobs, and the closer I can get to those host storage devices, the better.
The last reason it's felt buggy and rough is when removing a host-container filesystem mount from a container, it has often deleted the mount point on the host as well, disrupting other processes using it. Operations on the container are not supposed to change the host itself. You get used to working around this, but it's annoying.
Ah well, nothing's perfect. It's still a very useful tool, with some rough edges. I'd look into fixing the I/O performance and host-unmounting bug if I thought the snapshot/migration feature would be made to work someday, but because that looks unlikely, plus issues that came with Snap, I'm not as motivated and will live with hackish workarounds.
For things like my mail, web, SpamAssassin and other services, LXD has been great, and the issues I encountered aren't really a problem. This is the sort of thing which Docker is pretty good for as well. However, due to history I've tended to keep servers going for a long time (with my mail server winning the crown as it's an image that's been gradually modified and upgraded for about 20 years, across many different ways of running Linux in container-like environments, starting with chroot). LXC/LXD is better than Docker for this use case, though either can be made to work.
Does anyone have a use case where they couldn't use Docker? I'm sure they exist but the list must be tiny. Listed in the article is:
- run systemd in a container
- why?? does your system not have systemd or equivalent?
- bad design; how will you monitor what's running and what's not, or upgrade an individual service in this container? restart everything?
- apparently systemd can run in docker?? https://medium.com/swlh/docker-and-systemd-381dfd7e4628
- run lxc/docker in a container
- docker-in-dockerAs far as I know you cannot load kernel modules in a container. You need an actual VM for that.
That said, you're not supposed to be able to load kernel modules from a container.
Which is exactly the sort of idea an enterprising rootkit author might think to take advantage of ;-)
You might consider asking your students if they can figure out how they might go about getting a kernel module installed from in a container despite it not being allowed - and what advantages does that bring to the rootkit author. If necessary, you can test it using an LXD container in a VM.
I run different Docker Compose stacks inside different LXC containers to avoid collisions between some containers that are in both stacks.