Mastering Container Fundamentals: From Isolation to Control

I'm Srijan, a passionate DevOps enthusiast currently pursuing a Bachelor's degree in Information Technology. With a love for programming in both Golang and Python, I bring two years of valuable tech experience to the table. As an avid advocate for open source technologies, I'm dedicated to fostering collaborative and innovative solutions within the tech community.
Introduction
Picture a world where developers no longer fret over tangled webs of dependencies and scaling nightmares. Welcome to the realm of containers, where life is a whole lot simpler.
You've likely heard of containers, perhaps even incorporated them into your everyday developer's toolkit. In the upcoming sections, we will delve deeper into the world of containers, gaining a comprehensive understanding of how they offer operating-system-level virtualization by abstracting the "user space" and sharing the host system's kernel. Let's understand how containers work from the ground up.
This blog will be structured into three pivotal sections:
File System Isolation (using chroot)
Process Isolation (using namespaces)
Memory Restrictions (using c-groups)
Also, there are many more things in a container but these three are the building blocks, so in this blog, we will cover only these three.
File System Isolation
To begin with creating our container we need to start with File System Isolation. And for this, we will be using a file called Rootfs.
Rootfs stands for "Root file system" used in Unix-like Operating System. The root file system is the top-level directory structure that contains all the files and directories necessary for the operating system to function properly.
Now Let's isolate our file. For this, we will first download rootfs image depending upon our operating system. Here I am downloading an Ubuntu base rootfs file system.
Click here to download the image
After downloading your rootfs image let's untar it. Here I have moved the untared files into a folder named rootfs for easiness.
tar -xvzf ubuntu-base-14.04-core-amd64.tar.gz -C ./rootfs
This will create a folder named rootfs in your present directory. Here is an example.

As you can see there are all the essential files inside our rootfs folder so now we can start with the process of isolating this.
Running chroot for rootfs.
sudo chroot rootfs /bin/bash
By running this we enter into our rootfs file and then run the ls -l command. And your output would be something like this.

Great! We have entered it into our file system. But you must be thinking why chroot, what good does it do?
chroot stands for change-root in a Unix-like operating system. When you use the
chrootcommand, you're effectively creating a confined environment where the specified directory becomes the new root directory for all file path references.
chroot
chroot is to create a restricted environment, often referred to as a "chroot jail."To check whether your file is isolated or not, go back to the main file by exiting from the chroot. Write an exit command to do so. Create a new file named 'test.txt' in the directory where your rootfs file is. Here in my case, it is in the Downloads folder.
touch test.txt

As you can see there was no test.txt file earlier but we created one by running the following command.
Now, let's go back into chrooted file by running the same command of chroot and then try to access our newly created file "test.txt" by changing our directory.

The above figure shows it very clearly, we can not. Even after we try to move to the previous directory by running the cd .. command, we are unable to access our file.
Hence we have isolated our rootfs file system. Congratulations on holding this long, it's only a short way from here.
Process Isolation
Any container has its processes isolated from the host machine's process. In other words, there should be no interaction between the processes of the container and the host. But so far our rootfs folder has not achieved this. This is how we can check it.
Firstly, we will use two terminals for this. On the left side is the host system and on the right is chrooted environment. Now check the process and their IDs on both sides.
ps aux
Run this in both environments.

On the chrooted side you might have to mount the proc by running the following command before you run ps aux.
mount -t proc proc /proc
So we know that the processes are not isolated as on both sides process IDs are the same.
To prevent this we will use namespace. So what are namespaces in Linux?
It provides process and resource isolation. To know in-depth check the namespace dropdown below.
Namespaces
Let's now implement this. On the right side exit out of the chrooted environment and run the following
sudo unshare --pid --fork --mount-proc=$PWD/rootfs/proc chroot $PWD/rootfs /bin/bash
This will apply the unshare and also move back to the chrooted environment
Now run ps and ps aux on the right side as well as on the left side.

It is clearly visible that this time the processes are different and hence we can say that our container is now process isolated.
Memory Restriction
Our rootfs file has now gone through file system isolation and Process Isolation, but still, any other process in the host can consume all the memory or memory that is required by any process in a chrooted environment.
To prevent this we will be using Control Groups(cgroup).
"Cgroups, short for Control Groups, are a Linux kernel feature that manages and regulates the allocation of system resources, like CPU and memory, for processes or groups of processes. They are essential for resource control, isolation, and optimization, especially in containerized environments
Now try out the following command to create a Cgroup for memory Control and Set memory limits.
sudo mkdir /sys/fs/cgroup/memory/my_chroot_memory
echo 536870912 | sudo tee /sys/fs/cgroup/memory/my_chroot_memory/memory.limit_in_bytes
Here, 536870912 represents 512MB in bytes.
Now to move Process to Cgroup and verify that our memory limits have been applied successfully.
sudo echo <PID of the process in the chrooted environment> > /sys/fs/cgroup/memory/my_chroot_memory/cgroup.procs
cat /sys/fs/cgroup/memory/my_chroot_memory/memory.usage_in_bytes
Now that we've completed Memory Restriction, our container is primed and ready. But our exploration doesn't have to end here. You can take your container journey even further by exposing host directories to the container using 'mount --bind' and delving into advanced security capabilities if you so choose.
Conclusion
As we wrap up our exploration, our container has successfully traversed the realms of File System Isolation, Process Isolation, and Memory Restriction. But, as we know, containers like Docker or Podman offer a multitude of features that extend far beyond these fundamental aspects. They encompass Networking, Security, Scaling, and much more. Yet, beneath all these functionalities, there's one fundamental truth: it all comes back to Linux. This is why comprehending the core functionality of a container is paramount.
Thank you for joining us on this journey!