README.md

Chapter 3

Technical requirements

In this chapter we will learn Docker containers concepts. We provide some labs at the end of the chapter that will help you understand and learn shown concepts. These labs can be run on your laptop or PC using the provided vagrant standalone environment or any already deployed Docker host at your own.

You will need at least (all labs were tested on Linux and Windows):

  • Internet connection.

  • Some Linux, MacOS or Windows basic skills to edit files (using Notepad, Vim, Emacs or any other editor).

  • Git command-line, Vagrant and Virtualbox installed on your PC or laptop.

  • Already cloned book's repository https://github.com/PacktPublishing/Docker-Certified-Associate-DCA-Exam-Guide.git.

  • Enough hardware resources: 1vCPU, 3GB of RAM and 10 GB of available disk space on your hard drive for virtual nodes.

Extended instructions can be found on Github book's repository. These labs will use "environments/standalone-environment" folder for the creation of the virtual environment and "chapter3" folder.

NOTE: To clone book's repository https://github.com/PacktPublishing/Docker-Certified-Associate-DCA-Exam-Guide.git, prepare a directory on your laptop or PC and execute git clone https://github.com/PacktPublishing/Docker-Certified-Associate-DCA-Exam-Guide.git. This will dowload all required files on your current folder.

All labs will start executing vagrant up using your command-line from the environment directory "environments/standalone-environment". This command will start all the required nodes for you. If you are using your own Docker host, you can move directly to "chapter3" folder.

Once all environment nodes are up and running, go to "chapter3" folder and follow each lab instructions.

This environment will be used for labs from chapter 1 to chapter 6. You can keep it on your host stopped, not cosumming RAM and CPU resources. You can execute vagrant halt to stop running virtual node. This will not remove your environment and you will be able to continue using it on next chapter's labs.

After completed the labs, you can use vagrant destroy -f from "environments/standalone-environment" directory to completely remove all the lab-deployed nodes and free your disk.


Previous requirements

  • Working Vagrant and VirtualBox installation.
  • Running "Standalone" environemnt.

NOTE:

You can use your own host (laptop or server), we provide you mock environments to avoid any change in your system.


Following labs can be found under chapter3 directory.


Lab1: Review Docker Command-line Object Options

In this simple lab we will just review docker command-line output.

1 - This isn't even a real lab. We will just review the ouput of docker --help.

vagrant@standalone:~$ docker --help

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
      --config string      Location of client config files (default "/home/zero/.docker")
  -c, --context string     Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with "docker context use")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket(s) to connect to
  -l, --log-level string   Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default "/home/zero/.docker/ca.pem")
      --tlscert string     Path to TLS certificate file (default "/home/zero/.docker/cert.pem")
      --tlskey string      Path to TLS key file (default "/home/zero/.docker/key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

Management Commands:
  builder     Manage builds
  config      Manage Docker configs
  container   Manage containers
  context     Manage contexts
  engine      Manage the docker engine
  image       Manage images
  network     Manage networks
  node        Manage Swarm nodes
  plugin      Manage plugins
  secret      Manage Docker secrets
  service     Manage services
  stack       Manage Docker stacks
  swarm       Manage Swarm
  system      Manage Docker
  trust       Manage trust on Docker images
  volume      Manage volumes

Commands:
  attach      Attach local standard input, output, and error streams to a running container
  build       Build an image from a Dockerfile
  commit      Create a new image from a container's changes
  cp          Copy files/folders between a container and the local filesystem
  create      Create a new container
  diff        Inspect changes to files or directories on a container's filesystem
  events      Get real time events from the server
  exec        Run a command in a running container
  export      Export a container's filesystem as a tar archive
  history     Show the history of an image
  images      List images
  import      Import the contents from a tarball to create a filesystem image
  info        Display system-wide information
  inspect     Return low-level information on Docker objects
  kill        Kill one or more running containers
  load        Load an image from a tar archive or STDIN
  login       Log in to a Docker registry
  logout      Log out from a Docker registry
  logs        Fetch the logs of a container
  pause       Pause all processes within one or more containers
  port        List port mappings or a specific mapping for the container
  ps          List containers
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rename      Rename a container
  restart     Restart one or more containers
  rm          Remove one or more containers
  rmi         Remove one or more images
  run         Run a command in a new container
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  search      Search the Docker Hub for images
  start       Start one or more stopped containers
  stats       Display a live stream of container(s) resource usage statistics
  stop        Stop one or more running containers
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
  top         Display the running processes of a container
  unpause     Unpause all processes within one or more containers
  update      Update configuration of one or more containers
  version     Show the Docker version information
  wait        Block until one or more containers stop, then print their exit codes

Run 'docker COMMAND --help' for more information on a command.

We can split this output in two pieces:

Docker Help

Objects will appear on first part, after common options, and on bottom we will have options allowed. As we mentioned on this chapter, not all objects have same actions.

We should use --help with each kind of object to review what actions are available for them. We can deep dive using any action and --help to obtain information about usage of that specified action. As we have not set any _DOCKERHOST variable neither used -H for connecting to remote daemon, we will use local Docker daemon.

Lab2: Executing Containers

This is a long lab in which we are going to review many actions and options available to containers.

1 - Execute an interactive alpine image based container in the background.

vagrant@standalone:~$ docker container run -ti -d alpine
aa73504ba37299aa7686a1c5d8023933b09a0ff13845a66be0aa69203eea8de7

2 - Now we review and rename container as "myalpineshell".

vagrant@standalone:~$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa73504ba372 alpine "/bin/sh" About a minute ago Up About a minute elastic_curran

We now rename reusing previous execution, obtaining only containers id.

vagrant@standalone:~$ docker container rename $(docker container ls -ql) myalpineshell

If we review latest container again we have different name. Notice that container is running (output probably will show different times for you):

vagrant@standalone:~$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa73504ba372 alpine "/bin/sh" 11 minutes ago Up 11 minutes myalpineshell

3 - We attach to "myalpineshell" container and create an empty file named "TESTFILE" on /tmp and then we exit:

vagrant@standalone:~$ docker container attach myalpineshell
/ # touch /tmp/TESTFILE
/ # exit

4 - If we review container status again we find that it is exited.

vagrant@standalone:~$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa73504ba372 alpine "/bin/sh" 14 minutes ago Exited (0) 46 seconds ago myalpineshell

Container now shows a "Exited (0)" status. Alpine image main process is a shell and CMD is "/bin/sh". We exited issuing "exit" command, therefore exit status was 0. No problem was found during execution.

5 - Now we are going to force a failure status, executing for example a command doesn't exist on image. We will execute "curl" command on a new container.

vagrant@standalone:~$ docker container run alpine curl www.google.com
docker: Error response from daemon: OCI runtime create failed:
container_linux.go:345: starting container process caused "exec: \"curl\": executable file not found in $PATH": unknown.
ERRO[0001] error waiting for container: context canceled

As command does not exist, we can not even execute desired command and as a result, container was created but not executed.

vagrant@standalone:~$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
466cc346e5d3 alpine "curl www.google.com" 17 seconds ago Created fervent_tharp

6 - Now we will execute "ls -l /tmp/TESTFILE" on a new container.

vagrant@standalone:~$ docker container run alpine ls -l /tmp/TESTFILE
ls: /tmp/TESTFILE: No such file or directory

vagrant@standalone:~$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c328b9a0609 alpine "ls -l /tmp/TESTFILE" 8 seconds ago Exited (1) 6 seconds ago priceless_austin

As expected, file does not exist on new container. We created only on "myalpineshell" container. In fact, file is still there. Notice that this time, container was executed and exit status reflects an error code. It is the exit code of the execution of ls command against an inexistent file.

7 - Let's rename last executed

vagrant@standalone:~$ docker container rename $(docker container ls -ql) secondshell

8 - Now we create /tmp/TESTFILE file on our and we copy it to "secondshell" container.

vagrant@standalone:~$ touch /tmp/TESTFILE
vagrant@standalone:~$ docker container cp /tmp/TESTFILE secondshell:/tmp/TESTFILE

9 - Let's now start again "secondshell" container and observe results.

vagrant@standalone:~$ docker container start secondshell
secondshell

vagrant@standalone:~$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c328b9a0609 alpine "ls -l /tmp/TESTFILE" 32 minutes ago Exited (0) 4 seconds ago secondshell

The file now exists on "secondshell" container and as a result execution exited correctly as we can notice on status column ("Exited (0)"). We have manipulated a "dead" container, copying a file inside. Therefore, containers are still present in our host system until we remove them.

10 - Now we remove "secondshell" container. We will try to filter container list output to search for removed and "myalpineshell" containers.

vagrant@standalone:~$ docker container rm secondshell
secondshell

vagrant@standalone:~$ docker container ls --all --filter name=myalpineshell --filter name=secondshell

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa73504ba372 alpine "/bin/sh" 59 minutes ago Exited (0) 45 minutes ago myalpineshell

As expected we only get "myalpineshell" container.

11 - To finnish this lab we will start again "myalpineshell" using docker container start -a -i to attach our command line interactively to container. Then we will get it running background using "Ctrl+p+q" escape sequence and finally we will attach a second shell to container using docker container exec.

vagrant@standalone:~$ docker container start -a -i myalpineshell
/ # read escape sequence

$ docker container exec -ti myalpineshell sh
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh
6 root 0:00 sh
11 root 0:00 ps -ef
/ # exit

We can notice that exiting from new executed shell process does not kill myalpineshell container. It is a process executed using same namespaces, but it is not attached the main process inside container.

vagrant@standalone:~$ docker container ls --all --filter name=myalpineshell
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa73504ba372 alpine "/bin/sh" About an hour ago Up 4 minutes myalpineshell

Lab3: Limiting Containers Resources

In this lab we are going to use "frjaraur/stress-ng:alpine" image from Docker Hub. It is an alpine based image built just with stress-ng and required alpine packages. It is small and will help us to stress our containers.

We will start stressing memory. On this labs, we will use two terminals on same host to launch docker container stats on one of them. Keep it in execution during all these labs because it is where we are going to observe different behaviors. We will launch 2 containers that will try to consume 2GB of memory. We will use --vm 2 --vm-bytes 1024M to create 2 processes with 1024MB of memory each.

1 - We are going to launch a container with a memory reservation. This means that Docker Engine will reserve at least that amount of memory to container. Remember that this is not a limit, it is a reservation.

vagrant@standalone:~$ docker container run --memory-reservation=250m --name 2GBreserved -d frjaraur/stress-ng:alpine --vm 2 --vm-bytes 1024M
b07f6319b4f9da3149d41bbe9a4b1440782c8203e125bd08fd433df8bac91ba7

2 - Now we will launch a limited container. Only 250MB of memory will be allowed, although container wants to consume 2GB:

vagrant@standalone:~$ docker container run --memory=250m --name 2GBlimited -d frjaraur/stress-ng:alpine --vm 2 --vm-bytes 1024M
e98fbdd5896d1d182608ea35df39a7a768c0c4b843cc3b425892bee3e394eb81

3 - On second terminal we will launch docker stats to review our containers resources consumption. We will show something like this (remember, IDs and usage will vary):

vagrant@standalone:~$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
b07f6319b4f9 2GBreserved 203.05% 1.004GiB / 11.6GiB 8.65% 6.94kB / 0B 0B / 0B 5
e98fbdd5896d 2GBlimited 42.31% 249.8MiB / 250MiB 99.94% 4.13kB / 0B 1.22GB / 2.85GB 5

We can notice that non-limited container is taking more than specified memory. It is not a limit in that case. On the other case, container is limited to 250MB although process could consume more. It is really limited and will not get more than that memory. It is confined to 250MB as we can observe on "MEM USAGE/LIMIT MEM" column. It could reach 100% of confined memory, but it can not overpass it.

4 - Remove "2GBreserved" and "2GBlimited" containers.

vagrant@standalone:~$ docker container rm -f 2GBlimited 2GBreserved
2GBlimited
2GBreserved

5 -In this case we will launch 3 containers. With different CPU limitations and processes requirements

First Container - Limited to 1 CPU but with 2 CPUs requirement. It is not a real requirement, but process will try to use 2 CPUs if they exist on system.

vagrant@standalone:~$ docker container run -d --cpus=1 --name CPU2vs1 frjaraur/stress-ng:alpine --cpu 2 --timeout 120

Second Container - Limited to 2 CPUs with 2 CPUs requirement. It will try to use both during execution.

vagrant@standalone:~$ docker container run -d --cpus=2 --name CPU2vs2 frjaraur/stress-ng:alpine --cpu 2 --timeout 120

Third Container - Limited to 4 CPUs with 2 CPUs required. In this case processes could consume 4 CPUs, but as they will just use 2 CPUs they will not have a real limitation unless we try to use more than 4 CPUs.

vagrant@standalone:~$ docker container run -d --cpus=4 --name CPU2vs4 frjaraur/stress-ng:alpine --cpu 2 --timeout 120

6 - If we observe docker stats output we can confirm expected results.

vagrant@standalone:~$ docker stats
CONTAINER ID NAME        CPU % MEM USAGE / LIMIT  MEM %  NET I/O  BLOCK I/O  PIDS
0dc652ed28b0  CPU2vs4  132.47%  7.379MiB / 11.6GiB  0.06%  4.46kB / 0B  0B / 0B  3
ec62ee9ed812  CPU2vs2  135.41%  7.391MiB / 11.6GiB  0.06%  5.71kB / 0B  0B / 0B  3
bb1034c8b588  CPU2vs1  98.90%   7.301MiB / 11.6GiB  0.06%  7.98kB / 0B  262kB / 0B  3

Lab4: Formatting and Filtering Container list Output

In this lab we will review docker container ls output.

1 - Launch some containers, for this example we will run 3 nginx:alpine instances with sequence names:

vagrant@standalone:~$ docker run -d --name web1 --label stage=production nginx:alpine
bb5c63ec7427b6cdae19f9172f5b0770f763847c699ff2dc9076e60623771da3

vagrant@standalone:~$ docker run -d --name web2 --label stage=development nginx:alpine
4e7607f3264c52c9c14b38412c95dfc8c286835fd1ffab1d7898c5cfab47c9b8

vagrant@standalone:~$ docker run -d --name web3 --label stage=development nginx:alpine
fcef82c80ed0b049705609885bc9c518bf062a39bbe2b6d68b7017bcc6dcaa14

2 - Let's list running containers using docker container ls default output:

vagrant@standalone:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fcef82c80ed0 nginx:alpine "nginx -g 'daemon of..." About a minute ago Up 59 seconds 80/tcp web3
4e7607f3264c nginx:alpine "nginx -g 'daemon of..." About a minute ago Up About a minute 80/tcp web2
bb5c63ec7427 nginx:alpine "nginx -g 'daemon of..." About a minute ago Up About a minute 80/tcp web1

3 - As we want to be able to review current stage of containers, we can format output to include labels information:

vagrant@standalone:~$ docker container ls --format "table {{.Names}} {{.Command}}\\t{{.Labels}}"
NAMES COMMAND LABELS
web3 "nginx -g 'daemon of..."  maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>,stage=development
web2 "nginx -g 'daemon of..."  stage=development,maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>
web1 "nginx -g 'daemon of..."  stage=production,maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>

4 - Let's filter now only development containers:

vagrant@standalone:~$ docker container ls --format "table {{.Names}} {{.Command}}\\t{{.Labels}}" --filter label=stage=development
NAMES COMMAND LABELS
web3 "nginx -g 'daemon of..." maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>,stage=development
web2 "nginx -g 'daemon of..." maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>,stage=development

5 - Let's kill now only develpoment containers:

vagrant@standalone:~$ docker container kill $(docker container ls --format "{{.ID}}" --filter label=stage=development)

vagrant@standalone:~$ docker container ls --format "table {{.Names}}\\t{{.Labels}}"
NAMES LABELS
web1  maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>,stage=production

Only web1, labelled as "production", is still running as we expected.