Containers Under The Hood
In order to deploy our application, we can use the actual physical machines but the problem with that one application can interfere with another one’s and can cause downtime, resource starvation, security issues for another microservice running on the same machine.
In order to overcome this problem, we can create individual VM’s for each application/microservice and can allocate resources according to their needs. VM’s provide us many benefits by having more control on our resources getting shared, and if one application/microservice goes bad it will affect only itself keeping other ones safe. All those features of VM come at a cost of performance. Running an entire guest operating system on top of the host operating system just to run our code. Although, in the time where we have very high computing power these days, it won’t be that much of a difference., but spawning a new container can be less time-consuming than spawning a new VM and can help in reducing the deployment time
What if? We can reduce all this performance overhead and directly run our code with all those VM features and save some deployment time. That’s where containers come into play.
What are Containers?
In a true sense, containers are nothing but some features of Linux combined together. These are
- Linux Jail, aka chroot
- namespaces aka unshare
- control group aka cgroup
Let’s understand their purpose one by one :
- It’s a Linux command that allows us to set the root directory of any new process you created. Let’s create a new custom container named “my-container” and set its root directory
In the above screenshot, I’ve created a directory named my-container. Now I am trying to set the new root within the my-container directory and running the command “bash”, but it got failed. The reason for its failure is “bash” command will try to look for its binary file within the /bin directory which is currently not there. So in order to successfully run this command let me copy bash binary and its library files manually to this folder.
- In the above screenshot, I've copied all the binary files for the bash shell and all its required dependencies in our my-container directory and re-run the chroot command. See our container is locked down at this file system level and doesn’t know anything about its outer space. This whole container is mounted at a new root. This is critical for containers if you want to isolate their file system and don’t want them to look into each of their files.
- chroot help us to isolate the file system, but there are still some issues with our custom my-container. If we copy some other important binaries such as ps to list down all the processes, we can see not only process running within the container itself but also all those processes running on the parent i.e host and we have the power to sabotage the other containers and important processes by sending SIGKIll signal using “sudo killl -9 <pid>” command. If I copy ifconfig binary we would be able to see the network details of the host too. We want these important functionalities but want to unshare all parent-level processes or sibling processes critical information from the process running in a container itself. That’s where unshare command comes into play.
In the above screenshot, I’ve created one more container named my-container-2. And in both the container, I’ve started a background process of tail command polling a.txt and b.txt files in container-1 and container-2 respectively. And if you check the processes using the ps command in both of the containers you would be able to get the process id of the other process running in a separate container, and you would be able to easily kill the process running on the different containers with ease. This is the problem we want to handle which can be solved using unshare command.
In order to unshare all the processes running on the host we can use the PID namespace and the children processes will have a distinct set of PID-to-process mappings from their parent.
unshare --pid chroot my-container-1
unshare --pid chroot my-container-2
There are different namespaces to control the capabilities and set an upper bound of many other things such as network namespace, mounting namespace, user namespace. unshare help host to become more responsible for our other processes as “With great power, comes great responsibility. :)”
Note: using unshare child processes can’t see the host processes but the host can.
- Ok, so we are done with file system isolation and bounding the limits of some very important and useful commands. The only thing remaining is to control how much hardware resource an individual process can take be it CPU, Ram, Network bandwidth. We need to take care of this one last thing because a badly written code or hungry microservice can take all the containers down by consuming all the hardware resources to itself.
- Until c-group comes to rescue us, c-group was invented by Google and got released in 2007. Below I’ve attached two screenshots one for my host machine mac and one for my docker container. For my docker container, I’ve set the cpu usage for the docker process to use only one core and memory usage of 1000MB.
#create a control group named container-group
cgcreate -g cpu,memory:/container-group# add our unshared env to our cgroup
cgclassify -g cpu,memory <container-PID>#now we can use cgset command to set cpu and memory upper bound
cgset -r cpu.cfs_period_us=100000 -r cpu.cfs_quota_us=$[ 100000 * $(getconf _NPROCESSORS_ONLN) ] container-groupcgset -r memory.limit_in_bytes=1000M container-group
So, now think about docker as a wrapper on these critical functionalities of containers helping us to automate these things with ease.
Thank you for reading this long. Hope you enjoyed the article, if yes do hit that applause button 😜