Pages

Thursday, May 27, 2021

Understanding Docker Resource Management

Docker allows you to set the limit of memory, CPU and disk on the container.On a linux machine lets run a container by setting the memory to 50MB as below,

[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.

Read More

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

Read More

Understanding Container Runtime : CRI-O

 With different Container Runtimes available in the market, many tech giants came together to form a group called OCI. With this, there are standards defined on how container runtimes should work. Now there are many runtimes available of which CRI-O is one of them. This is an OCI based Implementation of Kubernetes Container Runtime Interface. This provides an Integration between the OCI Container Runtime and the Kubernetes Kubelet. 

In this article, we will see how we can install and configure the CRI-O and perform the basic steps. Below are some of the following functionalities that the CRI-O provides us

Support multiple image formats including the existing Docker image format

Support for multiple means to download images including trust & image verification

Container image management (managing image layers, overlay filesystems, etc)

Container process lifecycle management

Monitoring and logging required to satisfy the CRI

Resource isolation as required by the CRI

 

We can call CRI-O as a best breed libraries as below,

Runtime: runc (or any OCI runtime-spec implementation) and oci runtime tools

Images: Image management using containers/image

Storage: Storage and management of image layers using containers/storage

Networking: Networking support through use of CNI


Install the crictl tool

[root@ip-172-31-30-121]: VERSION="v1.17.0"

[root@ip-172-31-30-121]: wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz

[root@ip-172-31-30-121]: sudo tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin

[root@ip-172-31-30-121]: rm -f crictl-$VERSION-linux-amd64.tar.gz


Run the image list

[root@ip-172-31-89-64]: /usr/local/bin/crictl images

FATA[0002] failed to connect: failed to connect, make sure you are running as root and the runtime has been started: context deadline exceeded 


crictl by default connects to Unix: unix:///var/run/dockershim.sock or Windows: tcp://localhost:3735. For other runtimes, use:

  • containerd: unix:///run/containerd/containerd.sock

  • cri-o: unix:///var/run/crio/crio.sock

  • frakti: unix:///var/run/frakti.sock

I just started the Docker service here, and things started working fine for me. 

Create a Container Image Spec

[root@ip-172-31-89-64]: cat pod.json

{

    "metadata": {

        "name": "nginx-sandbox",

        "namespace": "default",

        "attempt": 1,

        "uid": "hdishd83djaidwnduwk28bcsb"

    },

    "log_directory": "/tmp",

    "linux": {

    }

}


Run the json File

[root@ip-172-31-89-64]: crictl runp pod.json

e4cdd5cfd7daf91e4f6c136f7f9d704ba6c3e587ed6e4a8f354cf4a37ec58076


Check the Running Pods using

[root@ip-172-31-42-175 centos]# crictl pods

POD ID        CREATED       STATE     NAME                NAMESPACE      ATTEMPT

e4cdd5cf*   2 minutes      Ready      nginx-sandbox   default              1


Inspect the Running Pod as below

[root@ip-172-31-42-175 centos]# crictl inspectp e4cdd5cfd7daf

{

  "status": {

    "id": "e4cdd5cfd7daf91e4f6c136f7f9d704ba6c3e587ed6e4a8f354cf4a37ec58076",

    "metadata": {

      "attempt": 1,

      "name": "nginx-sandbox",

      "namespace": "default",

      "uid": "hdishd83djaidwnduwk28bcsb"

    },

    "state": "SANDBOX_READY",

    "createdAt": "2020-09-02T04:16:42.627919195Z",

    "network": {

      "ip": "10.88.0.2"

    },

    "linux": {

      "namespaces": {

        "options": {

          "ipc": "POD",

          "network": "POD",

          "pid": "POD"

        }

      }

    },

    "labels": {},

    "annotations": {}

  }

}


Pull an Image using

[root@ip-172-31-42-175 centos]# crictl pull busybox

Image is up to date for docker.io/library/busybox@sha256:c2d41d2ba6d8b7b4a3ffec621578eb4d9a0909df29dfa2f6fd8a2e5fd0836aed


List the Existing Images using

[root@ip-172-31-42-175 centos]# crictl images

IMAGE                                TAG             IMAGE ID                SIZE

docker.io/library/busybox     latest           edabd795951a0       1.45MB


More to Come, happy learning :-)


Read More

Friday, May 7, 2021

Ansible With Windows

Ansible as we know is an excellent automation tool for *nix based machines. But with recent implementations of ansible, it starts supporting windows based systems too. Using the Linux system as our Ansible Control machine and having windows machines as our remote machine, we can manage the systems. In this document we will see how we can manage a windows machine from a Linux Based Ansible control machine.


As we know that python is a mandatory requirement for ansible to work in linux machines but in windows, we need to have windows modules installed on the windows machine. There are certain requirements that windows machines to satisfy if we need to get ansible to work,


  • Ansible Can generally manage windows versions under current and extended support from Microsoft. Ansible can manage desktop OSs including Windows 7, 8.1, and 10, and server OSs including Windows Server 2008, 2008 R2, 2012, 2012 R2, 2016, and 2019.


  • Ansible requires Powershell 3.0 or newer and at least .NET 4.0 to be installed on the windows Host.


  • A WinRM listener should be created and activated on the remote window machines.


  • A Python package Pywinrm should be installed on the Linux Ansible Control machine.


Configuration on the Window Remote Machine


Check Powershell version : Get the Powershell Version by running the command in the powershell prompt,


PS C:\Windows\system32> (Get-Host).Version

Major  Minor  Build    Revision

-----   -----    -----     --------

5       1         14393  4350


Ensure WinRM ports are open

We also need to make sure both ports 5985 and 5986 are open in the firewall On Both Operating system and also on the network side. That is, now in order to access our windows machine over winRM and ansible will be able to execute playbook and tasks on your windows machine, we need to have these ports open. Check if ports are open are not using the below command on Powershell command line,

PS C:\Windows\system32> Get-Process -Id (Get-NetTCPConnection -LocalPort 5985).OwningProcess


Run the Powershell Script for configuring the Remoting For ansible

Run the below powershell script provided by ansible to configure Remoting for ansible as below,


PS C:\Users\Administrator> $url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/Confi

gureRemotingForAnsible.ps1"


PS C:\Users\Administrator> $file = "$env:temp\ConfigureRemotingForAnsible.ps1"


PS C:\Users\Administrator> (New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)


PS C:\Users\Administrator> powershell.exe -ExecutionPolicy ByPass -File $file

Self-signed SSL certificate generated; thumbprint: DD2BFCC45E7503BC9C05BA9174326B593614C733


wxf                   : http://schemas.xmlsoap.org/ws/2004/09/transfer

a                      : http://schemas.xmlsoap.org/ws/2004/08/addressing

w                      : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd

lang                  : en-US

Address             : http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous

ReferenceParameters : ReferenceParameters


Ok.


The script is available at the location here.


Configuration on the Ansible Control machine

The only one thing other than installing and configuring ansible, we also need to install the pywinrm python module. To install the package we need to run the command,


[root@ip-172-31-30-121 ansible]# pip install pywinrm


Configure the ansible.cfg and Hosts file

Once all the necessary configurations are done, we can now configure the ansible.cfg and hosts file as below,


Hosts file looks as,

[root@ip-172-31-30-121 ansible]# cat hosts 

[test]

172.31.23.13

 

[test:vars]

ansible_user="Administrator"

ansible_password="P@wDt3tLDAdUcV6UKx(.fw(Z7X35(@=Z"

ansible_port="5986"

ansible_connection="winrm"

ansible_winrm_transport="basic"

ansible_winrm_server_cert_validation=ignore


In the above hosts file, we can see the ip address of the machine under the label test. We also defined variables for all machines under the label test. We have the ansible user, password, port , connection and transport. When we are running ansible will use the variables defined in the hosts file.


Ansible.cfg looks as,

[defaults]

inventory=hosts


Execute our first ansible command to ping the windows machine as below,

[root@ip-172-31-30-121 ansible]# ansible test -m win_ping

172.31.23.13 | SUCCESS => {

    "changed": false, 

    "ping": "pong"

}


Write a simple playbook for creating a new user by the name of John in the windows remote machine

[root@ip-172-31-30-121 ansible]# cat win_play.yml 

---

 - hosts: test

   tasks:

    - name: Create a new User 

      win_user:

       name: john

       password: MyP4ssw0rd

       state: present 

       groups:

        - Users

      when: ansible_os_family == 'Windows'


Run the playbook and see the output as below,

[root@ip-172-31-30-121 ansible]# ansible-playbook win_play.yml 

 

PLAY [test] ***************************************************************** 


TASK [Gathering Facts] *****************************************************************ok: [172.31.23.13]

 

TASK [Create a new User] *****************************************************************changed: [172.31.23.13]

 

PLAY RECAP *****************************************************************172.31.23.13               : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

 

Hope this article helps in managing windows hosts using ansible. More to Come. Happy learning :-)

Read More