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 psqlexec --host=localhosti sql-test psql --username=user postgres

#  Applications with multiple containers

-   Keep the PostgreSQL container running

-   Prepare`docker theexec` followingallows `index.php` file:

    ``` phpto execute a command inside a
    <?php
    $dbconn = pg_connect("host=localhost port=5432 user=postgres password=qwerty");
   running $resultcontainer
 = 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)) {
  `-i` means that the standard input from the host (the
      echo "<tr><td>$row[0]</td><td>$row[1]</td></tr>";
    }
    echo "</tbody></table>";
    ?>
    ``` SQL query) is passed to the process in the container

    -   The script- will connect toBy theexecuting database,a selectquery all rows in the
 host OS: (requires that you have
  `data` table and create an HTML table with the result

- `psql` command Toline run an Apache HTTP server with PHP:

tool available)

             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"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:
            volumesdb:
            image:  - "./web:/var/www/html"postgres"
        ```

-   In thisenvironment:
 file you described `db` and `web` services

-   By default Docker Compose will create a new Docker network and put
- POSTGRES_PASSWORD=qwerty
          both containers insidevolumes:
 under these names

-   Take note that in the `volumes` section you can use relative paths
- "./db:/docker-entrypoint-initdb.d"
      (command line Docker requires absolute paths)

- web:
   You can put into a git repository all files i.e. the `db/` and image: "php:apache"
    `web/` directories and the YAML file

-   Tocommand: run"sh the Docker Compose application

        docker compose up

-   Please note, that unlike `docker run` the containers started by-c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'"
            ports:
    Docker Compose are not destroyed but just stopped when you exit the- "8080:80"
    above command

-   This has consequences for certain images; for example, `postgres`
   volumes:
          image does not read `/docker-entrypoint-initdb.d/` if it detects
- "./web:/var/www/html"
       that there```

- is a databaseIn already;this iffile you startdescribed Docker`db` Compose,and then
 `web` services

-   stopBy itdefault toDocker modifyCompose thewill initialcreate SQL,a thennew youDocker neednetwork toand runput
    both containers inside `dockerunder compose rm` to enforce removal of containers and restart
    from scratch the next timethese 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

...

This work has been carried out within the framework of the EUROfusion Consortium, funded by the European Union via the Euratom Research and Training Programme (Grant Agreement No. 101052200—EUROfusion). Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Commission. Neither the European Union nor the European Commission can be held responsible for them. The scientific work is published for the realization of the international project co-financed by Polish Ministry of Science and Higher Education in 2021 from financial resources of the program entitled "PMW” 5218/HEU - EURATOM/2022/2