...
Markdown |
---|
# Introduction
- Containerization is about packaging an application, libraries and
configuration as an image
- The image can be shared easily with others publicly or in a limited
scope
- Instances of the image (the containers) can be quickly spawned in a
portable way
- Containers are isolated from the host system and from each other
(except when explicitly configured otherwise)
- This allows to design a software system as a set of
communicating microservices
- This also enhances security, because a compromised component
does not grant access to the rest of the system
# Containerization vs Virtualization
- VMs emulate the whole stack (hardware + software), while containers
share some of the resources with the host system
- VMs require more resources to operate and take longer to start
- VMs are usually mutable -- one creates an instance from base image,
then installs `X` and configures `Y`, then after a few weeks one
installs `Z`, etc.
- Containers are immutable -- if you want to update `X` installed, you
rebuild the container image and restart it
# Container types
- A *system container* is one which emulates VM behaviour
- It has a main process (`/sbin/init`) able to spawn other daemons and
foreground applications
- An *application container* has just one application running as its
only process
- The best practice is to design application containers and connect
them using well-defined channels
# Basic commands
- These examples use Docker as the container engine
- To download `ubuntu` image from the public repository:
docker pull ubuntu
- To list available images:
docker images
- To run a container from `ubuntu` image:
docker run -it ubuntu
- The `-it` stands for:
-i, --interactive Keep STDIN open even if not attached
-t, --tty Allocate a pseudo-TTY
- It is required to run interactive commands in a shell
- To list running containers:
docker ps
# Specific images
- The public repository called the [**Docker
Hub**](https://hub.docker.com/) contains a lot of ready-to-use
images
- Some of the images contain a base OS:
- [Ubuntu](https://hub.docker.com/_/ubuntu)
- [CentOS](https://hub.docker.com/_/centos)
- [Arch Linux](https://hub.docker.com/_/archlinux)
- Some contain compilers, interpreters or other useful tools:
- [Python](https://hub.docker.com/_/python)
- [Maven](https://hub.docker.com/_/maven)
- Others contain known servers or DBMS:
- [Apache HTTP Server](https://hub.docker.com/_/httpd)
- [PostgreSQL](https://hub.docker.com/_/postgres)
- It is always a good idea to get acknowledged with the documentation
of each specific image
- Creators of these images follow certain conventions to make the
usage of image easier
# Examples
- To run an HTTP server hosting a set of HTML files:
echo '<h1>Hello World from Docker!</h1>' > index.html
docker run -p 8080:80 -v "$(pwd):/usr/local/apache2/htdocs" httpd
- The `-p` and `-v` flags stand for:
-p, --publish list Publish a container's port(s) to the host
-v, --volume list Bind mount a volume
- The `-p 8080:80` allows to bind `localhost:8080` on the host
system, to `localhost:80` inside the container
- The `-v "$(pwd):/usr/local/apache2/htdocs"` mounts the current
directory on the host as `/usr/local/apache2/htdocs` inside the
container
- To check if your Maven project builds correctly in a clean
environment:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=pl.psnc -DartifactId=docker-training -Dversion=1.0.0 -DinteractiveMode=false
docker run -v "$(pwd)/docker-training:/usr/src/docker-training" -w /usr/src/docker-training maven:3-openjdk-8 mvn package
- The `-v` is used in a similar way as previously
- The `-w /usr/src/docker-training` sets the working directory
when the container launches
- The image is `maven:3-openjdk-8`, to select specific OpenJDK
version
- To start a PostgreSQL instance with a pre-defined schema and initial
data:
$ cat 1-init.sql
CREATE TABLE data (
id SERIAL PRIMARY KEY,
value INTEGER
);
$ cat 2-fill.sql
INSERT INTO data (value) VALUES (100), (200), (300), (400), (500);
docker run -e POSTGRES_PASSWORD=qwerty -v "$(pwd):/docker-entrypoint-initdb.d" -p 5432:5432 postgres
- The `-e POSTGRES_PASSWORD=qwerty` sets value to an environment
variable
- The `-v` mounts the SQL scripts in a path loaded at runtime
- The `-p` is used to publish the default PostgreSQL port from the
container to the host
- In another terminal, you can check if the database works
correctly:
- By executing a query inside the container:
echo 'SELECT * FROM data' | docker exec -i sql-test psql --user postgres
- `docker exec` allows to execute a command inside a
running container
- `-i` means that the standard input from the host (the
SQL query) is passed to the process in the container
- By executing a query in the host OS: (requires that you have
`psql` command line tool available)
echo 'SELECT * FROM data' | psql --host=localhost --username=postgres
# Applications with multiple containers
- Keep the PostgreSQL container running
- Prepare the following `index.php` file:
``` php
<?php
$dbconn = pg_connect("host=localhost port=5432 user=postgres password=qwerty");
$result = pg_query($dbconn, "SELECT id, value FROM data");
echo "<table><thead><tr><th>Id</th><th>Value</th></tr></thead><tbody>";
while ($row = pg_fetch_row($result)) {
echo "<tr><td>$row[0]</td><td>$row[1]</td></tr>";
}
echo "</tbody></table>";
?>
```
- The script will connect to the database, select all rows in the
`data` table and create an HTML table with the result
- To run an Apache HTTP server with PHP:
docker run -it -p 8080:80 -v "$(pwd):/var/www/html" php:apache sh -c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'
- Take note that `php:apache` uses different default path than
`httpd` image used
- The command to be run in the container will (1) update packages
list, (2) install `libpq-dev`, (3) install `pgsql` PHP extension
and finally (4) run the HTTP server
- This is not the optimal way of using the `php:apache` image; it
would be better to build a new image on top of `php:apache`, but
this will be covered in the next tutorial
- You can try opening <http://localhost:8080>, however you will only
see a connection error
- The error appears because the PostgreSQL container is isolated
- It publishes its 5432 port to the host system, but not to the other
containers
- To overcome this, let's create a Docker network:
docker network create docker-training
- Restart the PostgreSQL container, but (1) put it into the
`docker-training` network and (2) name it explicitly
docker run -e POSTGRES_PASSWORD=qwerty -v "$(pwd):/docker-entrypoint-initdb.d" -p 5432:5432 --network docker-training --name db postgres
- Modify the `index.php`
``` diff
@@ -1,5 +1,5 @@
<?php
-$dbconn = pg_connect("host=localhost port=5432 user=postgres password=qwerty");
+$dbconn = pg_connect("host=db port=5432 user=postgres password=qwerty");
$result = pg_query($dbconn, "SELECT id, value FROM data");
```
- Restart the PHP container, but also put it into the
`docker-training` network
docker run -it -p 8080:80 -v "$(pwd):/var/www/html" --network docker-training php:apache sh -c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'
- Now you can see the result in <http://localhost:8080>
- Note that you can omit `-p 5432:5432` when running PostgreSQL
container unless you want to connect to database from the host
# Docker Compose
- Running a multi-container application in the way described so far is
tedious and error-prone
- To address such issues you can use Docker Compose
- The whole application is described in a declarative YAML file
- The file can be source-controlled in git or SVN and easily
shared with others
- To recreate the previous application:
- Create directories `db` and `web`
- Copy `1-init.sql` and `2-fill.sql` to `db/`
- Copy `index.php` to `web/`
- Create the following `docker-compose.yml` file:
``` yaml
version: "3.9"
services:
db:
image: "postgres"
environment:
- POSTGRES_PASSWORD=qwerty
volumes:
- "./db:/docker-entrypoint-initdb.d"
web:
image: "php:apache"
command: "sh -c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'"
ports:
- "8080:80"
volumes:
- "./web:/var/www/html"
```
- In this file you described `db` and `web` services
- By default Docker Compose will create a new Docker network and put
both containers inside under these names
- Take note that in the `volumes` section you can use relative paths
(command line Docker requires absolute paths)
- You can put into a git repository all files i.e. the `db/` and
`web/` directories and the YAML file
- To run the Docker Compose application
docker compose up
- Please note, that unlike `docker run` the containers started by
Docker Compose are not destroyed but just stopped when you exit the
above command
- This has consequences for certain images; for example, `postgres`
image does not read `/docker-entrypoint-initdb.d/` if it detects
that there is a database already; if you start Docker Compose, then
stop it to modify the initial SQL, then you need to run
`docker compose rm` to enforce removal of containers and restart
from scratch the next time |
...