Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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

...