1 of 49

Deep Dive into Docker

Dr. Noman Islam

2 of 49

What are docker images?

  • A good way to think of a Docker image is as an object that contains an OS filesystem and an application.
  • If you work in operations, it’s like a virtual machine template
  • In the Docker world, an image is effectively a stopped container.
  • If you’re a developer, you can think of an image as a class.
  • Getting images onto your Docker host is called “pulling”
  • An image contains enough of an operating system (OS), as well as all the code and dependencies to run whatever application it’s designed for.

3 of 49

Running a container

  • docker container run -it microsoft/powershell:nanoserver PowerShell.exe
  • Docker container run tells the Docker daemon to start a new container.
  • The -it flags tell the daemon to make the container interactive and to attach our current terminal to the shell of the container
  • Next, the command tells Docker that we want the container to be based on the microsoft/powershell:nanoserver image
  • Finally, we tell Docker which process we want to run inside of the container.
  • For the Windows container were running PowerShell.

4 of 49

Docker engine

  • The Docker engine is modular in design with many swappable components.
  • and based heavily on open-standards from the OCI.
  • Where possible, these are based on open-standards outlined by the Open Container Initiative (OCI).

5 of 49

6 of 49

Runc

  • runc is the reference implementation of the OCI container-runtime-spec
  • It’s effectively a lightweight CLI that wraps around libcontainer.
  • It has a single purpose in life - to create containers.

7 of 49

Containerd

  • In order to use runc, the Docker engine needed something to act as a bridge between the daemon and runc.
  • This is where containerd comes into the picture.
  • containerd as a container supervisor - the component that is responsible for container lifecycle operations such as; starting and stopping containers, pausing and un-pausing them, and destroying them.
  • Like runc, containerd is small, lightweight, and designed for a single task in life - containerd is only interested container lifecycle operation

8 of 49

Benefit of this model

  • All of the logic and code to start and manage containers removed from the daemon means that the entire container runtime is decoupled from the Docker daemon.
  • We sometimes call this “daemonless containers”, and it makes it possible to perform maintenance and upgrades on the Docker daemon without impacting running containers!

9 of 49

Shim

  • containerd uses runc to create new containers.
  • In fact, it forks a new instance of runc for every container it creates
  • Once each container is created, its parent runc process exits
  • Once a container’s parent runc process exits, the associated containerd-shim process becomes the container’s parent process.

10 of 49

Responsibilities of shim

  • Some of the responsibilities the shim performs as a container’s parent include:
    • Keeping any STDIN and STDOUT streams open so that when the daemon is restarted, the container doesn’t terminate due to pipes being closed etc.
    • Reports the container’s exit status back to the daemon.

11 of 49

Docker Daemon

  • image management,
  • image builds,
  • the REST API,
  • authentication,
  • security,
  • core networking,
  • and orchestration

12 of 49

Docker daemon

  • The Docker daemon implements the Docker API which is currently a rich, versioned, HTTP API that has developed alongside the rest of the Docker project.
  • This Docker API is accepted as the industry-standard container API

13 of 49

Images

  • The most popular registry is Docker Hub, but others do exist
  • Images are made up of multiple layers that get stacked on top of each other and represented as a single object.
  • Inside of the image is a cut-down operating system (OS) and all of the files and dependencies required to run an application.
  • Because containers are intended to be fast and lightweight, images tend to be small.
  • The whole purpose of a container is to run an application or service

14 of 49

What does an image contain?

  • Containers are all about being fast and lightweight. This means that the images they’re built from are usually small and stripped of all non-essential parts.
  • For example, Docker images do not ship with 6 different shells for you to choose from - they usually ship with a single minimalist shell, or no shell at all.
  • They also don’t contain a kernel - all containers running on a Docker host share access to the host’s kernel.
  • For these reasons, we sometimes say images contain just enough operating system

15 of 49

Registries

  • Docker images are stored in image registries.
  • Official Vs unofficial repos
  • nginx - https://hub.docker.com/_/nginx/ busybox - https://hub.docker.com/_/busybox/ redis - https://hub.docker.com/_/redis/ mongo - https://hub.docker.com/_/mongo/

16 of 49

Image naming

  • Addressing images from official repositories is as simple as giving the repository name and tag separated by a colon (:).
  • docker image pull <repository>:<tag>

17 of 49

Layers

  • A Docker image is just a bunch of loosely-connected read-only layers.
  • Docker takes care of stacking these layers and representing them as a single unified object.
  • Another way to see the layers of an image is to inspect the image with the docker image inspect command
  • Multiple images can, and do, share layers. This leads to efficiencies in space and performance.
  • Digest

18 of 49

19 of 49

Swarm mode

  • Orchestration is all about automating and simplifying the management of containerized applications at scale.
  • Things like automatically rescheduling containers when nodes break, scaling things up when demand increases, and smoothly pushing updates and fixes into live production environments.
  • Swarm mode brought a load of changes and improvements to the way we manage containers at scale.
  • At the heart of those changes is native clustering of Docker hosts that’s deeply integrated into the Docker platform

20 of 49

21 of 49

What is swarm?

  • A swarm consists of one or more nodes.
  • These can be physical servers, VMs, or cloud instances.
  • The only requirement is that all nodes in a swarm can communicate with each other over reliable networks.
  • Nodes are then configured as managers or workers.
  • Managers look after the state of the cluster and are in charge of dispatching tasks to workers.
  • Workers accept tasks from managers and execute them
  • When talking about tasks in the context of a swarm, we mean containers

22 of 49

Services

  • At the highest level, services are the way to run tasks on a Swarm.
  • To run a task (container) on Swarm we wrap it in a service and deploy the service.
  • Beneath the hood, services are a declarative way of setting the desired state on the cluster.
    • Set the number of tasks (containers) in the service
    • Set the image the containers in the service will use
    • Set the procedure for updating to newer versions of the image

23 of 49

  • The configuration and state of the swarm is held in a distributed etcd database located on all managers in the swarm.
  • It’s kept extremely up-to-date and is hosted in-memory on all manager nodes to make it fast.
  • Swarm mode uses TLS to encrypt communications, authenticate nodes, and authorize roles.
  • Automatic key rotation is also thrown in as the icing on the cake!
  • And it all happens so smoothly that you wouldn’t even know it was there!

24 of 49

Enabling swarm mode

  • docker swarm init
  • swarm join

25 of 49

Node 1

  • docker swarm init \ --advertise-addr 10.0.0.1:2377 \ --listen-addr 10.0.0.1:2377
  • docker swarm init tells Docker to initialize a new Swarm and make this node the first manager. It also enables swarm mode on the node.
  • --advertise-addr is the IP and port that other nodes should use to connect to this manager
  • --listen-addr lets you specify which IP and port you want to listen on for swarm traffic
  • docker node ls

26 of 49

Join swarm

  • Run following commands on manager1:
    • docker swarm join-token worker
    • docker swarm join-token manager
  • To join type following on workers:
    • docker swarm join \ --token SWMTKN-1-0uahebax...c87tu8dx2c \ 10.0.0.1:2377 \ --advertise-addr 10.0.0.4:2377 \ --listen-addr 10.0.0.4:2377

  • docker node ls

27 of 49

High availability of swarm managers

  • Technically speaking, swarm mode implements a form of active-passive multimanager H/A.
  • This means that although you might - and should - have multiple managers, only one of them is ever considered active.
  • And the leader’s the only one that will ever issue live commands against the swarm such as changing the configuration of the swarm or issuing tasks to workers.
  • If a non-active manager receives commands for the swarm it’ll proxy them across to the leade

28 of 49

  • Swarm uses an implementation of the Raft consensus algorithm to power manager HA
  • Deploy an odd number of managers.
  • Don’t deploy too many managers (3 or 5 is recommended)

29 of 49

Services

  • Assume you’ve got an app that has a web front-end.
  • You have an image for the web service, and testing has shown that you will need 5 instances of the web service to handle normal daily traffic.
  • You would translate this requirement into a service declaring the image the containers should use, and that the service should always have 5 running tasks.
  • docker service create --name web-fe -p 8080:8080 --replicas 5 nigelpoulton/pluralsight-docker-ci

30 of 49

  • We used docker service create to tell Docker we are declaring a new service, and we used the --name flag to name the service web-fe.
  • We told Docker to map port 8080 on every node in the swarm to 8080 inside of each container (task) in the service.
  • Next, we used the --replicas flag to tell Docker that there should always be 5 tasks/containers in the service.
  • Finally, we told Docker which image to use for all tasks and containers - it’s important to understand that all tasks in a service use the same image and config!

31 of 49

  • Each worker or manager then pulled the image and started a container from it running on port 8080.
  • The swarm leader also ensured a copy of the service’s desired state was replicated to every manager in the swarm.
  • All services are constantly monitored by the swarm - the swarm runs a reconciliation loop that constantly compares the actual state of the service to the desired state
  • docker service ls
  • docker service ps web-fe

32 of 49

Scaling a service

  • docker service scale web-fe=10
  • docker service ls
  • docker service rm web-fe

33 of 49

Docker compose

  • Docker Compose is a tool for defining and running multi-container applications.
  • Docker Compose allows you to define and manage multi-container applications in a single YAML file
  • Docker Compose configuration files are easy to share, facilitating collaboration among developers, operations teams, and other stakeholders.
  • Compose supports variables in the Compose file. You can use these variables to customize your composition for different environments, or different users.

34 of 49

Common use cases

  • Development environments
  • Automated testing environments
  • Single host deployments

35 of 49

How compose works?

  • Docker Compose relies on a YAML configuration file, usually named compose.yaml.
  • You then interact with your Compose application through the Compose CLI.
  • Commands such as docker compose up are used to start the application, while docker compose down stops and removes the containers.

36 of 49

Example

  • Consider an application split into a frontend web application and a backend service.
  • The frontend is configured at runtime with an HTTP configuration file managed by infrastructure, providing an external domain name, and an HTTPS server certificate injected by the platform's secured secret store.
  • The backend stores data in a persistent volume.
  • Both services communicate with each other on an isolated back-tier network, while the frontend is also connected to a front-tier network and exposes port 443 for external usage.

37 of 49

38 of 49

Parts of the application

  • 2 services, backed by Docker images: webapp and database
  • 1 secret (HTTPS certificate), injected into the frontend
  • 1 configuration (HTTP), injected into the frontend
  • 1 persistent volume, attached to the backend
  • 2 networks

39 of 49

40 of 49

Quick start

  • Create a directory

mkdir composetest

cd composetest

  • Requirements.txt file

flask

redis

41 of 49

app.py

import time

import redis

from flask import Flask

app = Flask(__name__)

cache = redis.Redis(host='redis', port=6379)

def get_hit_count():

retries = 5

while True:

try:

return cache.incr('hits')

except redis.exceptions.ConnectionError as exc:

if retries == 0:

raise exc

retries -= 1

time.sleep(0.5)

@app.route('/')

def hello():

count = get_hit_count()

return 'Hello World! I have been seen {} times.\n'.format(count)

42 of 49

Dockerfile

# syntax=docker/dockerfile:1

FROM python:3.10-alpine

WORKDIR /code

ENV FLASK_APP=app.py

ENV FLASK_RUN_HOST=0.0.0.0

RUN apk add --no-cache gcc musl-dev linux-headers

COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt

EXPOSE 5000

COPY . .

CMD ["flask", "run", "--debug"]

43 of 49

Compose file

services:

web:

build: .

ports:

- "8000:5000"

redis:

image: "redis:alpine"

44 of 49

  • This Compose file defines two services: web and redis.

  • The web service uses an image that's built from the Dockerfile in the current directory. It then binds the container and the host machine to the exposed port, 8000. This example service uses the default port for the Flask web server, 5000.

  • The redis service uses a public Redis image pulled from the Docker Hub registry.

45 of 49

Build and run docker compose

  • docker compose up

  • Enter http://localhost:8000/ in a browser to see the application running.

  • docker image ls

46 of 49

Compose watch

services:

web:

build: .

ports:

- "8000:5000"

develop:

watch:

- action: sync

path: .

target: /code

redis:

image: "redis:alpine"

47 of 49

Run the app with compose watch

  • docker compose watch

  • Changes in app.py
  • return 'Hello from Docker! I have been seen {} times.\n'.format(count)

  • Stop the docker compose
  • docker compose down

48 of 49

Splitting up services

  • Infra.yaml

services:

redis:

image: "redis:alpine"

49 of 49

Compose.yaml

include:

- infra.yaml

services:

web:

build: .

ports:

- "8000:5000"

develop:

watch:

- action: sync

path: .

target: /code