Pages
Monday, January 10, 2022
Docker - Running Container in privileged mode
Friday, January 7, 2022
Docker Networking - Macvlan
While working with Docker Containers, we have seen how we can use network types provided by Docker from NONE to BRIDGE. But did we ever try to configure a Docker container or run a container connected directly to the underlying physical network?
Why do we need to connect an application or Container directly to the Underlying network?. There can be legacy applications where they have to connect to the underlying physical network for faster routing of requests. If we want the same legacy application to be moved to a container, we need a way to let the container connect to the physical network rather than a Vlan provided by Docker dockerO bridge network.
Docker provides a macvlan network type which satisfies the need. When we use the macvlan network driver it assigns a MAC address to each container virtual network interface making it appear to be a physical network interface directly connected to the physical network. The advantage is that the latency in macvlan networks is low since packets are routed directly from the docker host network interface to containers. In this article we will have how we can use a Macvlan network driver and attach to a container.
Check if Macvlan is available on the host machine or not?
[root@ip-172-31-35-163 foo]# lsmod | grep macv
Check the existing docker networks available
[root@ip-172-31-35-163 foo]# docker network ls
NETWORK ID NAME DRIVER SCOPE
48909e557f1d bridge bridge local
fdf372ca609e host host local
09e36b50cb20 none null local
There is no macvlan network interface available. Check the network interfaces available using the ifconfig command,
[root@ip-172-31-35-163 foo]# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:ccff:fe5d:434a prefixlen 64 scopeid 0x20<link>
ether 02:42:cc:5d:43:4a txqueuelen 0 (Ethernet)
RX packets 3727 bytes 201737 (197.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4947 bytes 39337043 (37.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9001
inet 172.31.35.163 netmask 255.255.240.0 broadcast 172.31.47.255
inet6 fe80::894:27ff:fe0e:7f73 prefixlen 64 scopeid 0x20<link>
ether 0a:94:27:0e:7f:73 txqueuelen 1000 (Ethernet)
RX packets 442113 bytes 446230278 (425.5 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 191497 bytes 25180642 (24.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Create a macvlan network driver using the docker network command as below,
[root@ip-172-31-35-163 foo]# docker network create -d macvlan --subnet=172.31.35.163/24 --gateway=172.31.35.1 -o parent=eth0 mac_net
7baebe9803f4a8ccd87f1a5dfeb783305e5247eaa811865a245bb7f953eb93fc
Check the docker network available now?
One important thing about this network mode is that we will be able to connect to other systems on the local network without any issues but the container will not be able to connect to the host and vice versa. This is a limitation of the macvlan interface and without any special support from the network switch the host will not be able to send packets to the macvlan interfaces. The workaround is to create another macvlan interface on your host and use that to communicate with containers.
Run the below commands to provide special support for the macvlan driver we created on the physical network switch ( basically creating a copy of the physical network interface )
[root@ip-172-31-35-163 foo]# ip link add mac0 link eth0 type macvlan mode bridge
[root@ip-172-31-35-163 foo]# ip addr add 172.31.35.163/24 dev mac0
[root@ip-172-31-35-163 foo]# ifconfig mac0 up
Now check the ifconfig command to see a new physical interface as below,
[root@ip-172-31-35-163 foo]# ifconfig
mac0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9001
inet 172.31.35.163 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::60a2:39ff:fe53:d37e prefixlen 64 scopeid 0x20<link>
ether 62:a2:39:53:d3:7e txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 430 (430.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Now lets run a container with the macvlan driver created as below,
[root@ip-172-31-35-163 foo]# docker run --net=mac_net -d --ip=172.31.35.49 -p 81:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
eff15d958d66: Pull complete
1e5351450a59: Pull complete
2df63e6ce2be: Pull complete
9171c7ae368c: Pull complete
020f975acd28: Pull complete
266f639b35ad: Pull complete
Digest: sha256:097c3a0913d7e3a5b01b6c685a60c03632fc7a2b50bc8e35bcaa3691d788226
Status: Downloaded newer image for nginx:latest
c75b50c4370c013be1ffa8ebc97175dee7c06fa0e49d499ad3e306e555e76b7c
We have created a container with the macvlan driver created above (mac_net) with ip address 172.31.35.49. Now once the container is up and running, we can run the curl command directly on the ip address to see the nginx page as below
[root@ip-172-31-35-163 foo]# curl 172.31.35.49
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Hope this Helps in Understanding the Macvlan Driver in Docker. More to Come
Thursday, May 27, 2021
Understanding Docker Resource Management
[root@ip-172-31-32-147 centos]#docker run --rm --memory 50mb alpine free -m
total used free shared buff/cache available
Mem: 989 565 141 0 281 296
Swap: 0 0 0
Now when we run the “free -m” on the host machine, we can see the same output as above,
[root@ip-172-31-32-147 centos]# free -m
total used free shared buff/cache available
Mem: 989 499 175 19 314 329
Swap: 0 0 0
If we observe, the container memory is set with 50mb but still we see a different memory value. This is a very important thing to understand, docker shows all available memory as container memory. The default memory for a container in Mac OSX is 2gb and it will show all available memory of the host as container memory in linux. If we want to see the amount of memory allocated to the container , we need to check the allowed memory of the container from /sys/fs/cgroup/memory/memory.limit_in_bytes file from inside the container as below,
[root@ip-172-31-32-147 centos]# docker run --rm --memory 50mb alpine cat /sys/fs/cgroup/memory/memory.limit_in_bytes
52428800
The important thing we need to understand is that a container has no resource constraints and can use as much of a given resource as the host kernel scheduler allows. It is the responsibility of the developer to control how much memory, or CPU a container can use. This can be set using the runtime configuration flags for the docker run command. In this article, we will see how resources like memory and CPU are managed. Docker allocates resources from Host with 3 resources,
RAM
CPU
I/O Bandwidth
Docker Memory
Docker provides us various ways in setting memory and swap to the container using the run command. The memory flags available for setting in the Docker run command are,
--memory : Hard limit of memory
--memory-reservation : soft limit of memory
--memory-swap : Swap setting
--oom-kill-disable : OOM kill disable
Soft and Hard limits - Docker memory can be set in either soft and hard settings. The hard and soft limits can be set using the “--memory-reservation” and “--memory” flags to the run command. For example, if we set the hard limit as 250mb and the soft limit as 230mb, this means the memory consumption of the process running inside the container can use upto 250mb of memory and can rise from that. This limit can be considered as a warning limit. If we set a soft limit as 230mb and hard limit as 250mb , the process inside the container can upto 230mb and cross that but it cannot cross 250mb.
An example to set hard limit and soft limit looks as,
docker run -d -p 8081:80 --memory-reservation="256m" --memory="256m" nginx
Now if we run the above command,
[root@ip-172-31-11-133]# docker run --memory 50m --rm -it progrium/stress --vm 1 --vm-bytes 62914560 --timeout 2s
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] setting timeout to 55s
stress: dbug: [1] --> hogvm worker 1 [6] forked
stress: dbug: [6] allocating 62914560 bytes ...
stress: dbug: [6] touching bytes in strides of 4096 bytes ...
stress: dbug: [6] freed 62914560 bytes
stress: dbug: [6] touching bytes in strides of 4096 bytes
stress: dbug: [1] (416) <-- worker 6 signalled normally
stress: info: [1] successfully run completed in 2s
We can see the through the container is allocated with 50mb, the stress command still is able to use 62mb of memory. This should not be the case, if the container is set with 50mb and once the 50mb is allocated, it should throw a OOM error. But it did not happen, this is where swap comes into picture.
If we run a container with a memory limitation and no swap definition, then the container uses a hard limit of 50mb and swap of 100mb which means the container has 150mb of memory. The swap is set to the container using the “--memory-swap” flag to container run command. The swap will always be set with the double the amount of memory set. If we set the memory as 10mb , the swap will automatically set to 20mb.
Now lets run the same above container run by adding the swap command as below,
[root@ip-172-31-11-133]# docker run --memory 50m --memory-swap 50m --rm -it progrium/stress --vm 1 --vm-bytes 62914560 --timeout 2s
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] setting timeout to 2s
stress: dbug: [1] --> hogvm worker 1 [6] forked
stress: dbug: [6] allocating 62914560 bytes ...
stress: dbug: [6] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (416) <-- worker 6 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 0s
Now though the memory and swap is set, the container should not be killed but should run until 150mb ( 50mb hard limit and 100mb swap, since the swap will be set with double of the memory). So the container has 150mb, but still the container exits. There are few limitations with how docker allocates memory and swap to a container,
Note : If both memory and memory-swap are set to the same value, the hard limit is the amount set for memory but swap will never be used. In the above case, the hard limit is set to 50mb and swap is set to 50mb but when the container runs the hard limit of 50mb is set and swap is ignored.
Note : If memory and memory-swap are given different values then only swap will be used.
For example,
memory=20mb & memory-swap=20mb : swap will never be used since the hard limit is set to 20mb and swap will be ignored in this case.
memory=20mb & memory-swap=30mb : in this case, the hard limit for memory is 20mb and swap is 10mb.
If --memory-swap is explicitly set to -1, the container is allowed to use unlimited swap, up to the amount available on the host system.
OOM Errors - By default, if an out-of-memory (OOM) error occurs, the kernel kills processes in a container. We can use the flag “--oom-kill-disable” to change this behavior. If we set the memory and memory-swap, and when the OOM error occurs the kernel kills the processes inside the container.
If we set the parameter “--oom-kill-disable” along with container run, if the hard limit is reached, the process running inside the container will be killed and also the container will get hung. Care full in using the --oom-kill-disable as it can hang the containers and also our host linux prompts.
CPU Shares
CPU is also another important resource that needs to be used effectively with containers. By default each container can have unlimited CPU cycles from the host machine. We need to set various constraints to limit a given container access to the host machines CPU cycles. The default CFS ( Completely fair processing ) scheduler is the default scheduler in linux now and will be used in setting and assigning CPU to the containers. The flags available for setting the CPU for the docker run command is,
--cpus : how much of the available cpu resources can a container use. if the host machine has 2 cpus and if we set the --cpus = "1.5", it means the container can use one and half of the cpus available on host.
--cpu-period and --cpu-quota : these 2 flags need to be used alongside. These values define the CPU CFS scheduler period. if we are using docker version > 1.13, the --cpus is used.
--cpusets-cpus : limits the CPU cores that a container can use. if we set the value as [0-3], which means the container can use first,second, third and fourth CPU. The first CPU starts with Zero(0). If we set the value as 0,1 this means the container can use CPUs 0 and 1.
--cpu-shares : the value specifies the container weight to use the CPU. the default value if 1024, so a greater value than this gives greater priority and lesser values gives less priority.
CPU limits are based on shares. These shares are a weight between how much processing time one process should get compared to another. If a CPU is idle, then the process will use all the available resources. If a second process requires the CPU then the available CPU time will be shared based on the weighting.
Below is an example of starting a container with different shares. The --cpu-shares parameter defines a share between 0-768.
If a container defines a share of 768, while another defines a share of 256, the first container will have 75% share with the other having 25% of the available share total. These numbers are due to the weighting approach for CPU sharing instead of a fixed capacity.
docker run -d --name c768 --cpuset-cpus 0 --cpu-shares 768 benhall/stress
docker run -d --name c256 --cpuset-cpus 0 --cpu-shares 256 benhall/stress
sleep 5
docker stats --no-stream
docker rm -f c768 c256
It's important to note that a process can have 100% of the share, no matter defined weight, if no other processes are running.
--cpu-shares
--cpuset-cpus
--memory-reservation
--kernel-memory
--blkio-weight (block IO)
--device-read-iops
--device-write-iops
Disk I/O
Disk Read/Write is another resource that can be handled by the Container.By default Running container will have no restrictions on how many disk read/writes can be onde.We need to set various constraints to limit a given container access to the host machine Disk.
Docker provides us with the following parameters that we can pass with the run command to control the way Disk read/write and Disk IOPS can be handled.
--blkio-weight (block IO)
--device-read-iops
--device-write-iops
--device-write-bps
--device-read-bps
Lets run a container with disk read/write limitation set. First grab the disk available on your machine using the,
[root@ip-172-31-9-137 centos]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 30G 0 disk
└─xvda1 202:1 0 30G 0 part /
Now run a container as below,
[root@ip-172-31-9-137 centos]# docker run -it --device-write-bps /dev/xvda:1mb centos
[root@c5a5a6651ca2 /]#
In the above container run command we specified a device-read-bps option to limit the read rate to 1mb per second for /dev/xvda1 device. Now run a dd command to create a file from inside the container as below,
dd if=/dev/zero of=afile bs=1M count=100
[root@ip-172-31-9-137 centos]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f165ae292cfe centos "/bin/bash" 9 minutes ago Up 9 minutes charming_davinci
[root@ip-172-31-9-137 centos]# docker exec f165ae292cfe dd if=/afile of=/dev/null
20480+0 records in
20480+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.0429057 s, 244 MB/s
Limiting CPU
CPU is a very important resource other than memory and Disk. Allowing one container to use more CPU or container using less cpu can lead to changing performance behavior of the application running. Lets see how we can manage out docker container by using CPU efficiently
Limit Cores : Docker provides us a way to limit the number of cores available to containers by using the --cpus flag
Lock Container to Specific Core : Docker provides us a way to limit the number of cores available to the container. By default a docker container will use all the available cores for its use if available but if we want to lock our containers to a specific core , docker provides us the way.
Limit CPU time : Docker provides a way to limit the cpu time to ensure how often a process is able to interrupt the processor or a set of cores
Shares and Weights : Rather than assigning or limiting cpus and cores, we can apply shares to the containers. This allows more critical containers to have priority over the cpu when needed.
For example, if our host has 2 CPUs and wants to give a container access to one of them, we can run the container setting the --cpus=”1.0”. A container can run using
Docker run -it --cpus=”1.0” centos /bin/bash
As we already discussed, rather than giving a whole CPU to the container we can assign a share to increase or reduce the container weight. Using the “--cpu-shares” flag we can assign a value to a container greater or lesser than 1024 (default) to increase or decrease the container weight. This will give the container access to greater or lesser proportion of the host machine CPU cycles.
docker run -d --cpu-shares=1024 centos
Similar to the memory reservation, CPU shares play the main role when computing power is scarce and needs to be divided between competing processes.Hope this helps you in understanding the basic concepts of Docker resource utilization and management.
Wednesday, May 19, 2021
Grabbing Pid’s for Docker Container from Docker Host
It is known that in the Linux world, Every system has just one root process with PID 1 and PID 0 which is the root of the complete process tree of that system. Docker Cleverly uses the Namespaces to spin a new process tree, causing the process running inside the container to have no access to the parent process of the Docker Host. But the Host where Docker is running has a complete view of the Child PID namespace started by the Docker Engine.
Namespace allow us to create restricted view of system like the process tree, network interface, mount etc. So chroot restricts file system, namespace restricts other important system resources like network, process tree etc. So, a kernel namespace call wraps a global system resource in abstraction and isolation so that processes within that namespace think they have their own isolated instance of the global resource. Modifications done to that resource inside the namespace are not visible to the original resource being used by host machine or other namespaces.
The PID Namespace provides a consistent and unique resource name in place of host dependent resource name. This way pids inside the container are assigned a unique naming manner that are localised to the container. The naming can be the same as the way a traditional host machine provides its pids but the ones that are provided by containers are unique to processes running inside the container. This way the resource naming conflicts are removed.As a result, processes are created inside of a container and spend their entire lifetimes in the context of that container; they are not allowed to leave one container and join another.
Lets see how docker uses some sort of translation hash table between the pids of container and how they are viewed by the host
[root@ansible tmp]# docker run -d alpine sleep 200
6ffcbdba3fab640b0fb8f71626f558382ddb6816fd974c2153f18b2dcede3c08
[root@ansible tmp]# docker ps
ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6ffcb** alpine "sleep 200" 2 minutes Up kind_mestorf
Check the PID of the Running Docker Container using,
[root@ansible tmp]# docker inspect --format "{{ .State.Pid }}" 6ffcbdba3fab
10522
Check the Pid details on the Host machine using
[root@ansible tmp]# ps -fp 10522
UID PID PPID C STIME TTY TIME CMD
root 10522 10503 0 14:39 ? 00:00:00 sleep 200
Get the environ details of the PID from the /proc using [root@ansible tmp]# cat /proc/10522/environ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=6ffcbdba3fabHOME=/root
This way we can grab details regarding the Running Process details in a container on the Host machine where this container is running. More to come. Happy Learning