Nowadays, most software is built using a microservices architecture. The easiest way to build microservices is by using containers. But technology and architecture are only half of the equation.
Processes, company culture, and methodologies also play a significant role in the software development process. For this part, the most popular approach is to follow DevOps practices. Containers and DevOps complement each other. In this post, you’ll learn how one relates to the other, and what containerization and DevOps are all about.
What Is Container(ization)?
Containerization is a process of packaging your application together with its dependencies into one package (a container). Such a package can then be run almost anywhere, regardless of whether it’s an on-premises server, a virtual machine in the cloud, or a developer’s laptop. By abstracting the infrastructure, containerization allows you to make your application truly portable and flexible.
Before we dive into explaining containers in the context of DevOps, let’s first talk about containers themselves.
Problems of Traditional Applications
Traditionally, to install and run software on a server, several requirements must be met. The software must support your operating system, and you will likely need to have a few libraries and tools installed to run it. But that’s not all.
All these requirements probably need to be in a specific version and accessible at a particular path. Also, you may need to have proper permissions for specific directories and files. Overall, there are several key steps you need to complete before you can successfully run the software.
These requirements create specific problems. First, what if you already have some of the tools or libraries installed, but in an unsupported version? You’d have to upgrade or downgrade them, hoping it won’t break any existing software. The problems don’t end once requirements are met and the application is running correctly, though.
What if you want to run the application in another cloud environment? You’ll have to start the process all over again. Containers are designed to address these issues.
Containers to the Rescue
The idea of a container is to package these libraries, tools, and any other things your application requires into one “executable package”—a container. Then you can run these containers without worrying about the libraries, as everything is already included in the container. Containers isolate software from the cloud environment, ensuring it functions consistently regardless of potential environmental differences.
Containers vs. Virtual Machines
This all might sound like a concept of a virtual machine deployed from a prebuilt image, but containers work much differently. The main difference between a VM and a container is that a container virtualizes the operating system, whereas a virtual machine is an abstraction of physical hardware. This difference results in containers being more efficient and portable than VMs.
So, how do containers achieve isolation and portability? For a Linux system, the answer is cgroups and namespace isolation. You can also run containers on Windows. The Windows kernel uses different mechanisms to achieve the same results. Instead of cgroups, it has job objects, and instead of namespaces, it has server silo objects. These features of the Windows and Linux kernels enable the building of containers, which encapsulate everything your application may need without creating conflicts with the host operating system or other containers.
Because they achieve the same functionality as VMs (isolation and the ability to package all necessary components into a single executable), some people refer to containers as “lightweight VMs.” They’re called “lightweight” because a traditional VM includes a full copy of an operating system, the application, and binaries and libraries—which can take up space and be slow to boot. Containers share the operating systems and infrastructure with other containers, with each container running as an isolated process in the user space. Since containers are fundamentally different than virtual machines in how they’re constructed, it’s best to avoid calling them “lightweight VMs.”
What Makes Containers Possible?
Let’s go back to the working principle of a container. As we mentioned earlier, both Windows and Linux kernels have certain features—namely, cgroups and namespace isolation—that enable the creation of containers. But what are they, and how do they work?
Linux: cgroups and Namespaces
On the Linux kernel, it’s straightforward. cgroups is a kernel feature that allows limiting hardware resources per process. This allows us to limit CPU, RAM, disk I/O, or network usage for one or more specific processes.
Namespaces, on the other hand, give you the ability to isolate the “scope” of the process. Several namespaces are available, and you can apply one or more of them simultaneously to the process. Let’s take the ID namespace, for example. Typically, any process in a Linux machine can view all other processes running on the same machine. If you apply the process ID namespace isolation to any process, however, that process will no longer be able to see the other processes running on the machine. It will only see the processes running within the namespace.
Windows: Virtual File System, Job Objects, and Server Silos
There are no cgroups and namespaces in the Windows kernel; however, equivalent mechanisms are available to limit processes. A virtual file system effectively abstracts the real devices and “translates” every call from a process to the file system.
Job objects are the equivalent of cgroups—they allow limiting resources per process (or a group of processes).
Server silos would be a Windows equivalent of Linux kernel namespaces. Anything a process can do (access files, read and change registry entries, create links, connect to other processes) is usually orchestrated within a so-called root scope. However, you can make a server silo to separate what’s happening in a limited scope.
cgroups
A cgroup (short for “control group”) is a kernel feature that allows for limiting and isolating access to hardware resources (like CPU, RAM, disk I/O, or networking) by a process. In other words, a cgroup allows you to say, “process with this, and that ID can only use 10% of a CPU and 256 MB of RAM.”
Namespace Isolation
Namespaces, on the other hand, let you isolate one or more processes from another in a way that a process will see only what’s inside the namespace. There are different namespaces for different purposes—for example, network, mount, process ID, or user namespace. Each has its purpose (as you may guess by their names), and you can apply all or only some of them to a process.
To illustrate how it works in practice, let’s take the example of a process ID namespace. Usually, any process in a Linux machine can see all other processes running on the same machine. If you apply process ID namespace isolation to any process, however, that process will no longer be able to see the other processes running on the machine. It’ll only see processes running within the namespace.
Let’s look at mount namespace as another example. If you apply mount namespace isolation to a process, that process will have a list of mount points that is independent of other processes and the host operating system. Therefore, any file system you mount within the namespace will be visible to that process, but not to other processes.
Containers Demystified
If you apply all types of namespace isolation and restrict the process usage of hardware resources with cgroups, you’ll end up with a container. At this point, it’s important to understand there’s no such thing as a container from a Linux kernel perspective. For the kernel, it’s just an ordinary process that has been isolated using cgroups and namespaces.
But don’t get me wrong—namespace isolation and cgroups let you create proper isolation, so whatever you do inside a container will only affect the container. For example, you can have an NGINX server running on your machine and listening on port 80, and you can have a container running on the machine with NGINX running inside and listening on port 80, and they won’t fight with each other. Also, the content of /etc
(and any other directory for that matter) on your host system will be different and independent from the content of /etc
inside the container.
Container Runtime
Creating these namespace isolations, cgroups, job objects, or server silos rules manually is a tedious and complicated task. However, a tool called a “container runtime” can do this for you under the hood. You’ve probably heard about Docker, but it’s not the only container runtime on the market. There’s also containerd, podman, and cri-o. They all work slightly differently, but the general idea remains the same. These tools do the complicated job of managing cgroups and namespace isolation for you, so you can say, “I want a container with this application, these libraries, and these extra files and mount points.”
Containerization
Now that you have some understanding of what it takes to create a container, let’s discuss containerization. Simply put, it’s a process of implementing containers. We mentioned earlier that you need a container runtime, such as Docker, for that. In the simplest scenario, that’s all you need. However, in reality, there are several layers of containerization.
Infrastructure
Starting with the infrastructure, you need one or more servers, either on-premises or in the cloud, to run containers on.
Operating System
When it comes to the operating system, the only absolute requirement is that it will support containers. Most modern operating systems do support containers, but some exotic ones don’t, so you need to double-check beforehand. If you’ll only run containerized applications on your server, you can go to the next level and use one of a few container-optimized operating systems. These are operating systems specifically designed to run containers; therefore, they only include the libraries necessary to run containers. As a result, they’re much smaller than a traditional operating system and more secure.
Orchestrator
In addition to an operating system, you’ll need the previously mentioned container runtime (unless you opt for container-optimized operating systems, which include a built-in container runtime). We could end here, but you’ll probably need a container orchestrator to top off the stack. Managing a few containers is tolerable without it, but in real life, you’ll probably have more than a few containers. And the more you do, the harder it becomes to control them all. A container orchestrator will do the job for you. The most popular these days is Kubernetes.
Containerization vs. DevOps
When discussing containers, you’ll often hear about DevOps as well. We need to understand why this is the case. Containers are a technology, while DevOps is a set of practices, culture, and principles. The reason you often see them together is that containers, as a technology, make implementing DevOps easier. We’ll explain why in a moment, but it’s essential to understand that they can exist independently. You can have containers without DevOps, and vice versa.
Let’s Talk About the Benefits
Containers help with everything. Thanks to containers, different environments (e.g., development, test, production) can be the same, as you no longer rely on operations teams to make sure different servers are running identical software versions. What’s more, applications will be in the same “environment” (container), even on a developer’s laptop. You simply deploy the same containers on different environments. This removes the common problem of “it works on my machine but not on the test server.”
Continuous Deployments
Continuous deployment becomes easier with containers as well. That’s because containers in general are small (or at least they should be), so it just takes seconds to deploy a new version of a container. Additionally, if you’re using containers, you’ve likely architected your application as a microservices-based system. This means you can update different parts of your application independently.
The key point to remember is that one without the other would be more difficult and less effective. Containers are a natural fit for DevOps. There are several reasons for this, but the primary point is that DevOps enables faster software delivery through closer collaboration between developers and operations teams, providing developers with more freedom and a “fail fast” approach.
Flexibility
Another benefit of containers is that the parts of your application can be written in different languages. Therefore, developers aren’t limited to one programming language but can use languages they’re most comfortable with. This contributes to DevOps by giving you more freedom in arranging teams.
Fail Fast
When it comes to the “fail fast” approach, containers limit the scope of application code that developers need to understand. To fix a bug, a developer (in most cases) only needs to know how one container works, not the whole application (unless, of course, the issue spans many containers). So, it’s usually far easier to narrow down the potential problems and find the root cause.
And once it’s fixed, you can quickly deploy a new version of one container, and you’re done. No need for multiple teams to align to find the issue, or for an end-to-end test of the entire application when a change to a single piece of the application was made. No need for approvals and alignment across multiple departments to redeploy the whole application, either.
DevSecOps
But containers’ contribution to DevOps doesn’t stop here. Containerization can help you upscale your DevOps practices to the increasingly popular DevSecOps. Since different parts of your application are packaged into small pieces, it’s easy to implement network security policies to, for example, keep traffic from flowing where it shouldn’t.
Testing also becomes easier because you only need to focus on a small part of an application when writing tests, and that directly decreases the chances of deploying buggy code.
Another feature of containers worth mentioning is the ability to perform runtime security scanning. Traditionally, when your application is running directly on the operating system of your host server, dozens or hundreds of processes will be running alongside it. It’s hard to determine if one of them contains malware. However, suppose your application is running inside containers and you have a good understanding of what should be running inside each of them. In that case, you can block other binaries from running in the container.
The Downside
With all the benefits of containers comes a downside: the cost. Networking becomes significantly more complicated since containers need to communicate with each other. This is usually done using a REST API. Therefore, instead of having only front-end to back-end and back-end to database connectivity, you’ll have dozens of connections, creating a complicated networking mesh.
The same applies to logging. You’ll no longer have a single place to read logs; each container will create its own logs. You’ll have to aggregate them, which may make it more challenging to maintain a general overview of the entire application. There are, however, tools like SolarWinds Papertrail to help you with that. It aggregates logs from various sources, including applications, infrastructure, networking, and orchestration platforms, and provides a centralized, easy-to-understand overview of the application state. Managing container logs with SolarWinds Papertail allows you to enjoy the benefits of containerization while maintaining the ability to identify and troubleshoot issues quickly. Sign up for a free SolarWinds Papertrail trial today.
Summary
As you can see, containers and DevOps are often grouped for good reason. They complement each other well. Containers make implementing DevOps easier, and DevOps helps extract the most value from containers. No one will stop you from packaging a poorly designed monolithic application into a container. However, in such a scenario, you won’t benefit much from containerization. Implementing DevOps at the same time will allow you to break down the monolith and implement microservices, revealing the benefits of containers.
And if you don’t want to lose visibility into your application and get overwhelmed by log files, try SolarWinds Papertrail to simplify the complexity.
This post was written by Dawid Ziolkowski. Dawid has 10 years of experience as a Network/System Engineer, DevOps, and Cloud Native Engineer. He’s worked for an IT outsourcing company, a research institute, telco, a hosting company, and a consultancy company, so he’s gathered a lot of knowledge from different perspectives. Nowadays, he’s helping companies move to cloud and redesign their infrastructure for a more cloud-native approach.
You might also want to check out the SolarWinds Docker monitoring