Understanding Linux Namespaces
The Bored Dev
·
Feb 21
In this article we will explain what Linux namespaces are and how do we take
advantage of them in our software systems nowadays.
Let’s start!
What Is a Namespace?
A Linux namespace is a feature that Linux kernel provides to allow us to isolate
resources for a set of processes. In some way, they are a sort of implementation of the
bulkhead pattern we frequently see in microservices architectures.
What are the main advantages in the use of namespaces? The main advantages are
two:
•Isolation of resources
One troublesome process won’t be taking down the whole host, it’ll only affect
those processes belonging to a particular namespace.
•Security
The other advantage is that a security flaw in the process or processes running
under a given namespace, won’t give access to the attacked to the whole system.
Whatever he/she could do, will always be contained within the boundaries of that
namespace! This is why it’s also very important to avoid running our processes
using privileged users whenever possible.
Namespaces and Containers
Nowadays, containers make extensive use of Linux namespaces to be able to group
processes and provide resource isolation for them. Some of the most popular and
user-friendly container engines at the moment are Docker or Podman.
Namespaces (and therefore containers) are something unique to the Linux
kernel. You might be wondering, how is possible that we can run Docker or Podman in
our Mac (OSX) or Windows machines then?
Well, the answer is simple: Both Docker and Podman run a virtual machine in your
local machine (OSx or Windows) to be able to make use of namespaces and any other
kernel features required to run containers.
For example, we are running Podman in our local machine, in order to make it work we
had to initialise and start a podman machine first by running the following:
$ podman machine init
$ podman machine start
This initialises and starts a virtual machine in your local machine, which runs a linux
operating system. How can we get the details about this machine to make sure that it’s
actually a virtual machine? We can get this information by running this command:
$ podman machine inspect
This will inspect the default machine we have currently running in podman. The output
will be something similar to this:
[
{
"ConfigPath": {
"Path":
"/Users/theboreddev/.config/containers/podman/machine/qemu/podman-machine-
default.json"
},
"ConnectionInfo": {
"PodmanSocket": {
"Path":
"/Users/theboreddev/.local/share/containers/podman/machine/podman-machine-
default/podman.sock"
}
},
"Created": "2023-02-12T07:50:02.100074Z",
"Image": {
"IgnitionFilePath": {
"Path":
"/Users/theboreddev/.config/containers/podman/machine/qemu/podman-machine-
default.ign"
},
"ImageStream": "testing",
"ImagePath": {
"Path":
"/Users/theboreddev/.local/share/containers/podman/machine/qemu/podman-
machine-default_fedora-coreos-37.20230205.2.0-qemu.aarch64.qcow2"
}
},
"LastUp": "2023-02-12T07:50:02.100074Z",
"Name": "podman-machine-default",
"Resources": {
"CPUs": 1,
"DiskSize": 100,
"Memory": 2048
},
"SSHConfig": {
"IdentityPath": "/Users/theboreddev/.ssh/podman-machine-
default",
"Port": 56324,
"RemoteUsername": "xxxx"
},
"State": "running"
}
]
You can see above how Podman uses QEMU to run a virtual machine, using an image
which runs Fedora Core OS as the operating system. If you want to run a different
Linux version, our article “Run Ubuntu on Max Using QEMU” might be useful.
The way it works in Docker is very similar to what Podman does, although Docker uses
VirtualBox instead. This is the default virtual machine that comes with Docker Desktop,
but there are other alternatives like colima for example.
Now that we know what a Linux namespace is, you could be thinking: How can I create
a new namespace?
Let’s see how!
How to Use Namespaces
In order to create a namespace in Linux, we have to use the unshare command to
run a process in a new namespace.
If we consult unshare command's help section, the first we can see is something like
this:
$ unshare --help
Usage:
unshare [options] [<program> [<argument>...]]
Run a program with some namespaces unshared from the parent.
Options:
-m, --mount[=<file>] unshare mounts namespace
-u, --uts[=<file>] unshare UTS namespace (hostname
etc)
-i, --ipc[=<file>] unshare System V IPC namespace
-n, --net[=<file>] unshare network namespace
-p, --pid[=<file>] unshare pid namespace
-U, --user[=<file>] unshare user namespace
-C, --cgroup[=<file>] unshare cgroup namespace
-T, --time[=<file>] unshare time namespaceshel
You can see how most of the first few options allow us to specify different types of
namespaces, these are the different types of namespaces we have in Linux systems.
We will see cgroups in more detail in a future article, as it takes a very important role
in the containers world.
Types of Namespaces
Each type of namespace is different and it provides isolation for different resources in
our system.
If we check the namespaces in the Linux manual pages, we can see a list of namespace
types:
Namespace Flag Page Isolates
Cgroup CLONE_NEWCGROUP cgroup_namespaces(7) Cgroup root
directory
IPC CLONE_NEWIPC ipc_namespaces(7) System V IPC,
POSIX message
queues
Network CLONE_NEWNET network_namespaces(7) Network
devices,
stacks, ports,
etc.
Mount CLONE_NEWNS mount_namespaces(7) Mount points
PID CLONE_NEWPID pid_namespaces(7) Process IDs
Time CLONE_NEWTIME time_namespaces(7) Boot and
monotonic
CLONE_NEWUSER user_namespaces(7) User and group
IDs
UTS CLONE_NEWUTS uts_namespaces(7) Hostname and
NIS domain
name
clocks
User CLONE_NEWUSER user_namespaces(7) User and group
IDs
UTS CLONE_NEWUTS uts_namespaces(7) Hostname and
NIS domain
name
We won’t get into too much detail, but you can clearly see how every namespace will
isolate a different resource in our system. Therefore, we can see briefly what the
different types of namespaces in a Linux system are:
•User namespace
Contains an independent set of user IDs and group IDs that can be assigned to
processes.
•PID namespace
Contains its own set of process IDs (PIDs). Every time we create a new namespace,
the process will get assigned PID 1. Every child process created in the new
namespace will be assigned subsequent IDs.
•Mount namespace
They allow the management of mount points in our system. Doing unmount in our
new namespace won’t have any effect on the main host, as every new mount will
be private to the current namespace.
•Network namespace
Virtualises the network stack for the new namespace. This means that the new
namespace will have its own virtual interface/s, private IPs, IP route table, sockets,
etc.
•IPC (Inter-Process Communication) namespace
Allows defining shared memory segments between processes within a namespace
for inter-process communications, not interfering with other namespaces.
•UTS (Unix Time-Sharing) namespace
Allows our system to have different host names and domain names for each
namespace.
•Time namespace
This namespace was released quite recently in Linux (2020), it allows having
different system times within our system by specifying different time namespaces.
•CGroup (Control Groups) namespace
Introduced in 2016 as part of Linux release 4.6, limits the resource usage in our
system (cpu, memory, disk, etc) for a particular group of processes (under this
namespace). As we will see in future articles, this is a very important feature of
Linux Kernel in the containerised world we now live in!
Now that we have gone through all the existing types of namespaces in Linux, we’re
going to show a simple example to understand the power that namespaces bring to
us.
Let’s run the following command:
$ unshare --user --pid --map-root-user --mount-proc --fork
bash
What exactly are we doing here? What are we telling the Kernel to do? Let’s go step by
step to try to understand what we are doing.
If we check each of these options in the manual here, we can see each of these options
tell the Kernel to do the following:
•— user: Create a new user namespace.
•— pid: Create a new pid namespace. (Will fail if — fork is not specified)
•— map-root-user: Wait to start the process until the current user (running
unshare command) gets mapped to the superuser in the new namespace. This
allows having root privileges within the namespace, but not outside of that scope.
•— mount-proc: Mount /proc filesystem in the new namespace and create a new
mount, this is to be able to have different processes with same process IDs in both
namespaces.
•— fork: Run command as a child process of the unshare command, instead of
running it directly. It's required when creating a new PID namespace.
Let’s see what this implies. First of all, if we list the processes in the new namespace,
we only see the new processes running in the new namespace and not the processes
running in the host’s default namespace:
$ ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/bash
2 root 0:00 ps -ef
This is because we’ve used both --pid and –fork options.
You will also notice that our processes are running as root within the new
namespace, this is what the –map—root-user does. The current user is root within
this namespace only, this use won't be able to do any harm to any of the external
namespaces! This is great, right?
Lastly, we can see how the IDs of the processes are 1 and 2, this is because we create a
new /proc mount for our namespace using --mount-proc.
Is that clear? We hope so!
It’s also worth mentioning that the unshare command we just ran is equivalent to the
following command using a docker/podman client:
docker exec -it myImage /bin/bash
This will be very familiar to those used to work with containers, as this is normally used
to gain access through a terminal inside the container. Now that we’ve mentioned
containers, let’s see what the namespaces role is inside of Linux containers.
Namespaces Role in Containers
Namespaces have been available in the Linux kernel since 2002, however, the use of
them was restricted to those with a very advanced knowledge of Linux systems. Linux
namespaces and containers were made “popular” by Docker, when it made possible a
wide adoption of containers across the sector for anyone with a minimum (or zero)
understanding of Linux Kernel internals.
The use of namespaces is not transparent to the users, and that’s probably why most
users don’t understand containers in they way they actually are.
There’s a considerable number of people in the sector that see containers as some sort
of virtual machine that does not contain the kernel, but shares it with the host instead.
This is a wrong understanding of what a container is, we’ll see why very soon.
Containers create an illusion that could make you think that you’re actually running a
virtual machine on your host, but this is far from the truth. When you run a virtual
machine on your host , you are actually booting a new OS distribution on your
machine, with the difference being that this OS will make calls to a middleware
provided by the virtualisation engine you use (Virtual Box, QEMU, etc).
This middleware translates every kernel call inside your virtual machine to a system call
that your host can understand. This is what mades possible, for example, to run
different operating systems like Windows on a unix system!
On the other hand, a Linux container makes use of Linux namespaces to provide this
illusion of running a different operating system. When we create an image based a any
known Linux distribution, we are actually mounting its filesystem, giving us the
impression of being on a new operating system. But actually we’re just inside of a new
namespace!
Conclusion
In this article we have seen what Linux namespaces are and the very important role
they play in distributed systems nowadays. We’ve also tried to clarify one of the biggest
misconceptions around Linux containers with regards to Linux namespaces and virtual
machines.
We really hope that every concept is now clearer to you. If you are still confused and
this is unclear, don’t worry, this can happen. Just re-read these concepts and eventually
you will get the point of Linux namespaces.
That’s all from us today! We really hope you’ve enjoyed reading this article as much as
we enjoyed writing it!
Thanks for reading us!