Linux Namespaces
Linux Namespace goes very technical, but I am presenting this in plain way — everyone should understand “tech” but it can be in “less-tech” way too. Did you know Docker and containers may not have existed if Linux namespaces hadn’t existed?
What are Linux Namespaces?
To put it simple, linux namespaces make some of the global system resources (network interfaces, hostname, mounts, etc.) isolated beween independent processes. This means any two independent processes on the host can have different view of these global system resources just because they are in different namespaces.
Resources exist in the host but kernel makes them visible or invisible depending on which namespace the process is on. When a process enters a namespace, the namespace makes sure that what view of system components the process should have.
flowchart LR
Processes -- Enter --> Namespaces -- decide-process'-view-of --> Resources["System Components"]
One thing not to get confused regarding namespaces is that they doesn’t restrict access to physical resources like: CPU, RAM or Disk. That’s actually metered and restricted by Cgroups.
Type of Namespaces
The level of isolation depends on the namespaces a process is associated with. The table below lists the types of namespaces available at the time of writing. Some examples show how containers takes advantage of different namespaces.
| Namespace Type | Isolates | Short example |
|---|---|---|
| Cgroup | Cgroup root directory | Limit CPU/memory for a container |
| IPC | System V IPC, POSIX message queues | Isolate shared memory between containers |
| Network | Network devices, stacks, ports, etc. | Give a container its own IP and network stack |
| Mount | Mount points | Give a container its own filesystem view |
| PID | Process IDs | Make a container’s process appear as PID 1 |
| Time | Boot and monotonic clocks | Give processes a different view of system time |
| User | User and group IDs | Map container root to an unprivileged host user |
| UTS | Hostname and NIS domain name | Give a container its own hostname |
Listing Namespaces
Important
Before starting, I want to make sure that most of the commands you use requires root access. Keep that on mind.
The lsns (the full form is list namespaces) utility allows us to list the namespaces available on a system or the namespaces associated with a particular process.
Per User View of Namespaces
By default lsns shows the list of namespaces current user can see. This means two user can have different namespace view.
# Who is this?
$ whoami
alice
# Namespaces visible to alice
$ lsns
NS TYPE NPROCS PID USER COMMAND
4026531833 net 21 571 alice -bash
4026531834 time 21 571 alice -bash
4026531835 cgroup 21 571 alice -bash
4026531837 user 21 571 alice -bash
4026532208 ipc 21 571 alice -bash
4026532219 mnt 1 642 alice -bash
4026532220 uts 21 571 alice -bash
4026532221 pid 21 571 alice -bash
4026532240 mnt 20 571 alice -bash
# Switch to root
$ sudo su
$ whoami
root
# Namespaces visible to root
$ lsns
NS TYPE NPROCS PID USER COMMAND
4026531833 net 56 1 root /sbin/init
4026531834 time 56 1 root /sbin/init
4026531835 cgroup 56 1 root /sbin/init
4026531837 user 56 1 root /sbin/init
4026532208 ipc 56 1 root /sbin/init
4026532219 mnt 22 1 root /sbin/init
4026532220 uts 51 1 root /sbin/init
4026532221 pid 56 1 root /sbin/init
4026532231 mnt 1 90 root ├─/usr/lib/systemd/systemd-udevd
4026532232 uts 1 90 root ├─/usr/lib/systemd/systemd-udevd
4026532233 mnt 1 166 systemd-resolve ├─/usr/lib/systemd/systemd-resolved
4026532234 mnt 1 167 systemd-timesync ├─/usr/lib/systemd/systemd-timesyncd
4026532235 uts 1 167 systemd-timesync ├─/usr/lib/systemd/systemd-timesyncd
4026532256 uts 1 189 root ├─/usr/libexec/wsl-pro-service -vv
4026532294 uts 1 186 root ├─/usr/lib/systemd/systemd-logind
4026532295 uts 1 191 syslog ├─/usr/sbin/rsyslogd -n -iNONE
4026532296 mnt 1 189 root ├─/usr/libexec/wsl-pro-service -vv
4026532297 mnt 1 186 root └─/usr/lib/systemd/systemd-logind
4026532240 mnt 29 570 root /init- NS is the ID of namespace
- TYPE is the type of namespace
- NPROS is the number of processes in the namespace
- PID is the lowest processID in the namespace
- UESR is the username of the PID
- COMMAND is the command of the PID
Per Process View of Namespaces
init is the first process after linux system boots which gets the PID 1. So the view of namespaces as per PID 1 will be:
lsns -p 1Note
As PID 1 user is root, you need to be root in order to view the namespaces of PID 1.
The -p <PID> option lets you view the namespaces per process.
First you need to find pid of service you want to view namespace of. For example, if dockerd is running:
pid=$(pidof dockerd)gives the pid of dockerd. In my case, I get the pid of dockerd in $pid variable. Now list namespaces for that $pid:
lsns -p $pidNS TYPE NPROCS PID USER COMMAND
4026531833 net 54 1 root /sbin/init
4026531834 time 54 1 root /sbin/init
4026531835 cgroup 54 1 root /sbin/init
4026531837 user 54 1 root /sbin/init
4026532208 ipc 54 1 root /sbin/init
4026532219 mnt 20 1 root /sbin/init
4026532220 uts 49 1 root /sbin/init
4026532221 pid 54 1 root /sbin/initMy Process Namespace
List all the namespaces related to my current process:
ls -l /proc/$$/nsAll Process Inside A Namespace
Use lsns <NAMESPACE_ID> to view all the processes inside a particular namespace (I use the uts namespace ID from above):
lsns 4026532220PID PPID USER COMMAND
571 570 alice -bash
714 571 alice └─myserver --serve-without-reload
642 572 alice -bash
37152 37151 alice -bash
40149 37152 alice └─lsns 4026532220Create Linux Namespace And Run Programs Inside New Namespaces
unshare (un share parent namespace) utility provides the way to create new linux namespace and run programs/commands insde it.
As an example:
unshare --uts yourprogramThe command creates brand new UTS Namespace and execute yourprogram program. If yourprogram is not daemon or doesn’t create any other child process than the namespace is destroyed as soon as yourprogram finishes it’s task.
This is important to know:
New Namespace persist only as long as it has child or member process running. It gets destroyed automatically as soon as last member process finishes it’s job.
If you want to run more commands inside new namespace, you can open interactive shell like sh or bash:
unshare --pid /bin/bashYou decide the type of namespace you want by --<TYPE>, I choose --pid namespace.
Entering Inside Existing Namespace
While you can create new namespace using unshare and run commands inside them, nsenter (namespace enter) provides you a way to enter inside the existing namespace of a particular process and execute particular command. The general syntax is:
nsenter -t <PID> --<NAMESPACE_TYPE> [CMD]You specify the type of namespace you want to enter of particular pid with the command you want to execute. As CMD is optional, if you don’t provide, the default is $SHELL which is /bin/bash in my case. Also you can provide --all if you want to enter inside all namespaces of given PID.
Let’s say I wanted to enter inside --time namespace of the same dockerd pid -t 284 with default $SHELL:
nsenter -t 284 --timeYou are now time namespace of PID 284 and have interactive shell to interact with it.
Example1: Docker Network Isolation
All the concepts and commands you learnt so far can be understood properly by simulating similar to docker behaviour of how Docker isolates network from containers to containers or containers to host.
Create a new network namespace (--net) with unshare and specify $SHELL to go inside it
unshare --net $SHELLYou are now inside newly created network namespace. From the namespace types table we know that net namespace is responsible for providing netwrok level isolation. This means:
This newly created network namespace has been networkly isolated i.e. it has different view of network compared to it’s parent.
This can be proved. List the interfaces from inside the namespace:
ip linkYou see only the loopback lo interface:
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00Now exit the namespace. It’s easy — use the exit command.
Now list the interfaces again after exiting:
ip linkYou have completely different view of network interfaces:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000You can think the first view is the view of Docker containers and the second one is your actual host. I know it’s oversimplified but yes Docker does it all the time — more programatically and more effictively tho.
Example2: Hostname Isolation
If you remember the table, it’s uts namespace that provides the Hostname Isolation. You guessed it right — yes we are entering uts namespace now. But before that, let’s take a note of our current uts namespace id.
readlink /proc/$$/ns/utsThis shows current process uts namespace id is:
uts:[4026532220]Now let’s enter new uts namespace. The command we all know:
unshare --utsYou will get your default $SHELL now, but inside newly created uts namespace. Verify with the same command:
readlink /proc/$$/ns/utsYou will get different uts namespase id this time:
uts:[4026532237]Note your hostname too before changing:
hostnameFor me, it’s:
Ansible-ControllerNow let’s change the hostname to linuxbox:
hostname linuxboxVerify with hostname command, you will get:
linuxboxAlso note the current process id:
echo $$For me, it was:
56146Great! Keep this terminal as it is and open new terminal. I get my initial bash as such:
root@Ansible-Controller:~#This is not unexpected because it’s not the namespace we poke with the hostname. It was the uts namespace of pid 56146 we noted above. Now that, we know the PID, we can enter the uts namespace with nsenter we see a little while ago:
nsenter -t 56146 --utsNow, I am greeted with this bash:
root@linuxbox:~#Yep! linuxbox was the same hostname we changed in uts namespace (uts:[4026532237]) belonging to pid 56146.
What else should I say? Good bye!
