Instructor Demo: Process Isolation

In this demo, we'll illustrate:

  • What containerized process IDs look like inside versus outside of a namespace
  • How to impose resource limitations on CPU and memory consumption of a containerized process

Exploring the PID Namespace

  1. Start a simple container we can explore:

    PS: node-0 Administrator> docker container run -d --name pinger `
        microsoft/nanoserver:latest powershell ping -t 8.8.8.8
    
  2. Launch a child process inside this container to display all the processes running inside it:

    PS: node-0 Administrator> docker container exec pinger powershell Get-Process
    
    Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
    -------  ------    -----      -----     ------     --  -- -----------
          0       5      944       4364       0.02   4812  65 CExecSvc
          0       5      636       1872       0.02  14264  65 csrss
          0       0        0          4                 0   0 Idle
          0      18     3568      11296       0.25   5760  65 lsass
          0       5      636       3084       0.03  13476  65 PING
          0      38    43400      66500       1.72   9436  65 powershell
          0      33    22788      44312       1.33  13184  65 powershell
          0       9     1592       5756       0.13  12872  65 services
          0       3      340       1144       0.02  15996   0 smss
          0      15    12448      21192       5.98   4828  65 svchost
          0      12     4996       9472       0.19   6412  65 svchost
          0       8     1548       5788       0.06  11252  65 svchost
          0      29     6000      16224       0.48  11308  65 svchost
          0       9     1936       6428       0.06  13668  65 svchost
          0      13     1756       6336       0.06  15040  65 svchost
          0      15     9852      17896       0.42  15680  65 svchost
          0       0      128        140     898.88      4   0 System
          0       7      840       3916       0.02   8428  65 wininit
    

    In Windows containers, a whole set of system processes need to run in order for the intended application process to be executed successfully. Just like a regular Windows process list, we see the root Idle process at PID 0, and the System process at PID 4.

    Another way to achieve a similar result is to use container top:

    PS: node-0 Administrator> docker container top pinger
    
    Name                PID                 CPU                 Private Working Set
    smss.exe            15996               00:00:00.015        217.1kB
    csrss.exe           14264               00:00:00.015        397.3kB
    wininit.exe         8428                00:00:00.015        618.5kB
    services.exe        12872               00:00:00.125        1.372MB
    lsass.exe           5760                00:00:00.250        2.793MB
    svchost.exe         11252               00:00:00.062        1.176MB
    svchost.exe         15040               00:00:00.062        1.425MB
    svchost.exe         6412                00:00:00.187        3.715MB
    svchost.exe         4828                00:00:05.984        10.93MB
    svchost.exe         13668               00:00:00.062        1.692MB
    svchost.exe         11308               00:00:00.484        4.85MB
    svchost.exe         15680               00:00:00.421        8.266MB
    CExecSvc.exe        4812                00:00:00.015        757.8kB
    powershell.exe      13184               00:00:01.328        18.62MB
    PING.EXE            13476               00:00:00.031        487.4kB
    
  3. Run Get-Process directly on your host. The ping process is visible there, but so are all the other processes on this machine; the container's namespaces isolated what Get-Process returns when executed as a child process within the container.

  4. List your containers to show that the pinger container is still running:

    PS: node-0 Administrator> docker container ls
    

    Kill the ping process by host PID, confirm with Y to stop the process, and show the container has stopped:

    PS: node-0 Administrator>Stop-Process -Id [PID of ping]
    PS: node-0 Administrator>docker container ls
    
    CONTAINER ID        IMAGE                  COMMAND                    CREATED             STATUS          PORTS
             NAMES
    

    Killing the ping process on the host also kills the container. Note using Stop-Process is just for demonstration purposes here; never stop containers this way.

Imposing Resource Limitations

  1. Open the Task Manager, either through the search bar or by typing taskmgr in the command prompt.

  2. Start a container designed to simulate cpu and memory load:

    PS: node-0 Administrator> docker container run -it training/winstress:ee2.1 powershell
    
  3. Execute a script inside your container to allocate memory as fast as possible:

    PS C:\> .\saturate-mem.ps1
    

    You should see the Memory column on the Task Manager increase quickly, even turning red afterwhile. Then, this error message should be thrown (CTRL+c to break the loop):

    Exception of type 'System.OutOfMemoryException' was thrown.
    At C:\saturate-mem.ps1:2 char:37
    + ...  -lt 100000; $i++) { $mem_stress += ("a" * 1023MB) + ("b" * 1023MB) }
    +                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : OperationStopped: (:) [], OutOfMemoryException
        + FullyQualifiedErrorId : System.OutOfMemoryException
    

    Note this may even disrupt your RDP connection to your VM - failing to constrain resource consumption can be catastrophic.

  4. CTRL+C to kill this memory-saturating process. Then, exit and remove the container to release the allocated memory:

    PS: node-0 Administrator>docker container rm -f <container ID>
    

    Immediately, the memory in the Task Manager should drop.

  5. Now, let's start a container with a memory limit:

    PS: node-0 Administrator> docker container run `
        -it -m 4096mb training/winstress:ee2.1 powershell
    
  6. Run the same script to generate memory pressure:

    PS C:\> .\saturate-mem.ps1
    

    While the memory does increase in the Task Manager, allocations get cut off before the system memory is completely consumed. CTRL+C to kill the process, and exit the container again.

  7. Remove this container.

    PS: node-0 win-stress>docker container rm -f <container ID>
    
  8. With the Task Manager still up and running, let's do the same thing but for processor consumption:

    PS: node-0 Administrator> docker container run -it training/winstress:ee2.1 powershell
    PS C:\> .\saturate-cpu.ps1
    

    You should see the CPU column of the Task Manager increase quickly and turn red.

  9. CTRL+C to kill this CPU-burning process, and exit the container. As soon as it exits, the CPU consumption percentage on the Task Manager should drop immediately.

  10. Start another container, but this time with a CPU limit:

    PS: node-0 Administrator> docker container run `
        -it --cpus="1" training/winstress:ee2.1 powershell
    
  11. Execute the script to overload the CPUs:

    PS C:\> .\saturate-cpu.ps1
    

    While you will see the CPU consumption on the Task Manager increase, it will not increase as dramatically as it did before, as it is being limited by Window's control groups.

  12. Exit and remove this container.

Conclusion

In this demo, we explored some of the most important technologies that make containerization possible: namespaces and control groups. The core message here is that containerized processes are just processes running on their host, isolated and constrained by these technologies. All the tools and management strategies you would use for conventional processes apply just as well for containerized processes.