Ask HN: I need to learn to love containers – HN please show me the good side?

12 points by andrewstuart ↗ HN
I played with containers when Docker first came out and I hated them with a passion. It seems to me that they were horribly complex and replicated a whole bunch of operating system functionality and just made things hard hard hard, not to mention needing to implement clunky and fat layer file systems as well as constant fiddling with ports and configuration - ugh the whole thing seemed a truly ugly mess that sent complexity through the roof.

But I need to distribute some server software and my understanding is these days one of the expected ways to do that is to deliver some sort of containerised package.

The whole thing will be easier for me to swallow if I can come to love containers.

Can someone who loves containers share the love please? Help me understand why I should love containers instead of seeing them as the ugly pile of unnecessary mess that I currently see.

17 comments

[ 3.0 ms ] story [ 53.0 ms ] thread
I'm looking for words that explain why containers are a great thing, why they are more simple than I thought, how they aren't bound to one specific operating system kernel, or something that tells me why they're good, what the upside is.

I want to hear the passionate beliefs of someone who loves them.

Without a bind mount, their state is explicitly contained. You can run one, delete it, and run it again, and know that your host system is unmodified inbetween, and that the state is coming up brand new the second time.

It's the reproducibility of a vm without the overhead of a whole 'nother OS/boot. You can keep all of the state in a single volume, and can back it up or move it to another machine and relaunch it with a minimum of fuss.

>> move it to another machine

Just any machine at all? I hear people say things like "move it to another machine" but I'm imagining a whole bunch of caveats are left out such as "move it to another machine wherein W,X,Y,Z must all be version foo and this thingy must match that thingy and in fact no you can't really move it to another machine without first replicating the underlying machines your moving from and to". You can hear my cynicism unfortunately.

In practice, this isn't a problem. Any reasonably modern Linux kernel will do.

The container image will usually be based on a Linux distribution (so, Ubuntu if you want it, often something lightweight like Alpine). Most applications don't actually see the kernel: they see the system library (eg. glibc, musl) and it hides differences in system call versions, etc.

This is not a problem in general usage. A container will work on basically any Linux kernel.

A virtual machine runs a kernel on an virtualized CPU. Different CPU versions exist, but so long as they have the basic virtualization extensions, the hyperviser will be fine. From inside the VM, it looks like you own the CPU.

A container runs applications on a virtualized kernel (kinda). Different kernel version exist, but so long as they have the basic namespacing extensions, the container will be fine. From inside the container, it looks like you own the kernel.

It's like a chroot, but for everything. You have your own init, your own daemons, your own applications.

As long as it's got the same vlans (or your sidecar container can hook it up to the appropriate service mesh/router or update a load balancer backend) then yup. The entire userspace is inside the container. The kernel version doesn't need to match, anything not ancient will be fine.

Think of it like a tar of a chroot, although that is an oversimplification.

This presumes you trust the workload in the container (which you might!).

Containers are not an adequate security boundary for untrusted workloads to assume they leave the host unchanged.

I share some of your trepidation but I've learned to like containers for some uses cases. In particular -- and I think this might help you have fun with learning -- I like one-liners. For example, the following downloads a "virtual machine" with a Java development kit in it, compiles a program and runs it:

  docker run --rm adoptopenjdk/openjdk11-openj9:ubi sh -c "printf 'public class main { public static void main(String... args) throws Throwable { System.out.println(\"Hello World\"); } }' > main.java && javac main.java && java main"
This is a trivial example, but I think it gets at the value of containers which is to make things self-contained.

From here on, it does get increasingly complicated and sometimes questionably so. You'll normally create a Dockerfile, add all your commands there, add separate files which you'll copy into the container. Then you might use compose, Kubernetes, nomad, or a million other technologies to actually make something more realistic and there are lots of annoyances of those.

But I think the base value for me is the idea of scripting the creation and deployment of self-contained "virtual machines". Arguably, this was done before with Ansible, Chef, etc., but there is some value to a popular, standardized approach and a huge library of images out on Docker Hub that you can simply run/pull. This is particularly useful for reproducing problems for other people to investigate, sharing toy projects, letting people explore your application, etc.

I'm not a passionate container person, but let me try: the basic upside is isolation/containment.

For a moderately complicated application, there'll generally be several backend processes. A database, perhaps a message queue, several application processes. To install and run the application, you would need to eg. install one or more package repositories, install a bunch of packages, edit various configuration files, create various users, and then arrange for everything to start (and stop) in the right order. That's kinda painful to set up, and even worse to tear down.

There are/were two popular solutions: automation, and containers.

Automation addresses the grunt work: you script the process of installing and configuring (and later removing) all the bits of this application. Where automation falls short is that it doesn't have the isolation/containment: applications still exist in the same namespaces, and can collide.

A container can package all of this stuff into a single blob, and you can run it by giving it a simple port mapping, and a simple storage mount, and ... it just works. It doesn't matter if you have a different application with a different version of the database server running on the same host. Or a different version of, eg. Python. You don't have to mess with unique users or groups, and it's trivial to map the externally visible ports where it suits you.

That was the first-phase pitch. And it made sense to people whose life was spent dealing with the complexities of making things work together.

Things then got less clear cut. So far, this was just a per-machine thing. The obvious next step was to scale "containers" out to get their benefits, but using a whole fleet of hosts, not just one. Welcome to orchestration.

The benefits are still the same: you spin up a bunch of containers, and they are all self-contained, isolated, it-just-works bundles. The orchestration framework deals with how many of each of them you need, putting them in the right places, and hooking things up together. It's *way* less work than doing it by hand, and especially when you do dynamic scaling or failover or config changes.

But ... then containers and orchestration frameworks became kinda the expected way to do things, even when it wasn't really necessary. A simple, self-contained app doesn't need a container, and many things don't need full-blown Kubernetes.

Containers are good, but only if they address a problem you have.

So far as software distribution goes: if your installation instructions are more than a couple of lines, then you might as well put together a Dockerfile, and make your customers' lives easy. And be quietly thankful that we're not distributing VMware images any more :-)

So is the container somehow tightly bound to the configuration / kernel of the machine it was built on? I seem to have some recollection that you have to run the container on a host with the same kernel or libraries or something like that which seemed like a real downside to me.
The container shares the actual kernel, but has independent namespaces (including mounted filesystems).

In general, the precise kernel version doesn't matter to applications: libc will hide a wide range of stuff for you, and you bring your own libc in the container image (assuming you need it. This is not true for eg. Golang).

don't try to love em. mostly just try not to get so wound up. resist the urge to form stromg opinions or to crave what you know. "scout mindset". just observe, try, learn. but here's some things about containers:

it's better than vm's. easier to manage, better extends the host rather than replacing it with a bunch of very non-uniform vm's.

it's nice having an abstraction to run & manage stuff, without having to learn to manage a bunch of particular languages or runtimes specifically. each container makes it's own tools true but there's still some general operations in common.

containers go nicely with "12 Factor" development (http://12factor.net). shared so standard baseline assumotions that should fit most jobs.

being able to snapshot & re-spaen new instances of containers is cool but almost never used.

I'm pretty not excited by containers. but we were doing a pretty weird hodgepodge of things before & a lot of it was kind of bogus but more so, just that there were so many competing styles. it was amateur. containers aren't super awesome but they're ok. they work pretty decently, and they make good use of a lot of really solid powerful kernel features that you should want to be using (croups).

Easy, stable, predictable and maintainable distribution and installation that leads the a more stable execution (in theory).
1) I love containers when I’m researching/prototyping potential solutions for a problem. Using containers I can quickly spin up an isolated version of the software without having to mess with build systems, package managers, dependencies etc. Usually a “docker run” with some flags to set environment variables and expose ports is enough. And I can easily remove it from my system after the experiment is done.

2) I love docker-compose for describing dependencies during development. Things like databases, caches, message queues. Just one docker-compose.yml file in the project root describing the containers and a “docker-compose up” and I have a full environment for that project up and running.

3) Containers can help reproduce complex build environments. Let’s say an Android app with specific versions of Android Sdk, Ndk, cmake, swig. Package the necessary toolchain versions in a container and any team member can reproduce the same build regardless of what OS they are running natively. And if you have the build env in a container then CI tools can use it as well.

You're right: containers and the container ecosystem are complicated and a bit messy.

However, containers solve real problems; real problems that you are going to want to solve one way or another, whether you choose to use containers or not.

For example:

1. making sure your application is run with the correct dependencies

2. keeping your application (somewhat) isolated from other applications on the same host

3. packaging your application in a way that is widely compatible

You don't have to use containers, but you will gain a lot of insight from understanding the problems they solve well, and where their rough edges are.

Additionally, some of the things that seem ugly are, in some cases, necessary, or design decisions that do make sense in context.

For example, the use of a layered filesystem. If I am building my application on a given base image, then when I deploy a new version of my application, the host only needs to pull the top layers that contain my application. If I have many applications on the same base layers, that amplifies the savings on pulls and storage. Furthermore, a running container can perform all of its writes on a layer, which can just be discarded when the container exits. There is no need to make a copy of the image FS for the container, which saves on startup time. The layered filesystem approach has a lot of benefits. The cost (downside) is having to understand the additional complexity of layers, and having to structure the layers in such a way as to avoid things like excessively large images.

As for love, don't place too much importance on it... in the context of technology, that is. To quote the ever-relevant Choose Boring Technology:

"...you should probably be using the tool that you hate the most. You hate it because you know the most about it."

http://boringtechnology.club/