Docker - DmitryGontarenko/usefultricks GitHub Wiki

About Docker

Docker — это программа, которая даёт возможность упаковывать приложения со всеми их зависимостями в стандартизированный модуль (контейнер) и запускать их. Как результат, системы, основанные на контейнерах, легко масштабируются, переносятся и воспроизводятся.
Image (образ) — это некоторый набор слоёв. Каждый слой — результат работы команды в Dockerfile. Все что запускается на основе этого образа является контейнером, или инстансом. т.е. с одного образа можно запустить несколько одинаковых копий этого образа — контейнеров.
Контейнер - это компонент докера, объединяющий код и зависимости. Несколько контейнеров могут работать на одном компьютере и совместно использовать ядро ОС с другими контейнерами, каждый из которых работает как изолированный процесс.
Dockerfile - файл, который содержит скрипт (инструкции) по созданию docker-образа.

Docker и виртуализация

Виртуализация - метод разделения вычислительных процессов выполняемых на одном физическом ресурсе. Виртаулизация означает создания виртуальной версии чего-либо. Чаще всего это относится к процессу запуска виртуального экземпляра компьютерной системы.
Виртуальная машина - виртуализация (эмуляция) реального компьютера. Виртуальная машина работает поверх физической машины, используя гипервизор (hypervisor).
Гипервизор (или Virtual Machine Monitors, VMM) - это часть программного или аппаратного обеспечения, позволяющая одновременное, параллельное выполнение нескольких операционных систем на одном и том же хост-компьютере. Именно гипервизор обеспечивает создание и запуск виртуальных машин, а также изоляцию ОС друг от друга и разделение ресурсов между ними.
Компьютер, на котором гипервизор запускает одну или несколько виртуальных машин, называется хост-машиной, а каждая виртуальная машина - гостевой.
Существует два типа гипервизора:
ТИП 1 - "автономные гипервизоры", запускаются непосредственно на аппаратном обеспечении ("голом железе"), то есть при установке не требуют наличия на машине установленной ОС. Из-за того, что гипервизоры типа 1 имеют прямой доступ к физическому оборудованию, этот тип считается наиболее эффективным.
Такой тип гипервизора требует поддержки технологий аппаратной виртуализации. Такую поддержку для своего оборудования оказывают технологии производителей Intel и AMD.
К гипервизорам 1 типа можно отнести: Xen, Oracle VM Server, Microsoft Hyper-V и VMware ESX / ESXi.
ТИП 2 - "хостовые гипервизоры", запускаются на обычной хостовой ОС, как и другие приложения в системе. Это так называемая программная виртуализация. В этом случае не требуется никакого специализированного аппаратного обеспечения, как в случае с аппаратной виртуализацией. Достаточно установить одну из программ виртуализации.
К гипервизорам 2 типа можно отнести: VMWare Workstation, VirtualBox.

Типы виртуализации:
Эмуляция - полная виртуализация, включая ядро ОС. Недостатком такого типа виртуализации является дополнительная нагрузка на системные ресурсы. Эмуляцию можно отнести ко 2-му типу гипервизоров.
Паравиртуализация - также известная как гипервизор 1-ого типа, выполняется непосредственно на аппаратном уровне и предоставляет услуги виртуализации для виртуальных машин, работающих на нем.
Контейнерная виртуализация - виртуализация на основе контейнеров, она позволяет выполнять несколько процессов, которые совместно используют базовое ядро ОС. Примеры инструментов управления: Docker, LXC.

Отличия контейнеров от VM

  • Контейнеры совместно используют (разделяют) ядро ОС для выполнения операций.
  • Виртуализация на основе контейнеров на так сильно влияет на производительность хост-машины.
  • Докер-образы имеют небольшой размер по сравнению с образами виртуальных машин.

Кратко: Docker - это инструмент управления контейнерами. Образ — это шаблон для запуска контейнера. Контейнер - это компонент докера, объединяющий код и зависимости.

Установка

Установка Docker и работа с ним проводилась в ОС - Ubuntu Mate 18.04.4 x64
ОС была установлена с помощью средств виртуализации - VMware Workstation 12 Pro (12.5.9 build-7535481)

Установка docker:

  1. sudo apt-get update
  2. sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common
  3. curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  4. sudo apt-key fingerprint 0EBFCD88
  5. echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" | sudo tee /etc/apt/sources.list.d/docker.list
  6. sudo apt update
  7. sudo apt install docker.ce --install-recommends
  8. Проверяем - docker -v

Установка docker-compose:

  1. sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  2. sudo chmod +x /usr/local/bin/docker-compose
  3. Проверяем - docker-compose -v

Пробуем запустить контейнер - sudo docker run hello-world

Добавим нашего пользователя в группу docker (что бы каждый раз не писать sudo):

  1. sudo usermod -aG docker <username>
  2. sudo service docker restart
  3. Перезагружаем ОС
  4. Проверяем - docker images, команда должна выполниться без ошибок

Список основных команд:

Описание Команда
Добавить пользователя в группу docker sudo usermod -aG docker
Перезапустить docker sudo service docker restart
Указать свою учетную запись в docker-hub docker login
Проверить версию docker docker -v, docker-compose -v
Зафиксировать изменения контейнера в новый образ docker commit [exist_container] [image_name]
Получить методанные контейнера или образа docker inspect [container_id] / [image_id]
Вывести сведения о занимаемом месте докер сервисов docker system df -v
Удалиь неиспользуемые образы, контейнеры, тома docker system prune

Образы:

Описание Команда
Посмотреть список существующих образов docker images
Собрать образ (на основе Dockerfile) docker build -t [image_name] [path/to/dockerfile]
Собрать образ (на основе Dockerfile) docker build -t [image_name] [path/to/dockerfile]
Удалить образ с локальной машины docker rmi [IMAGE ID]

Контейнеры:

Описание Команда
Запустить контейнер docker run [image]
Запустить контейнер и удалить после завершения docker run --rm [image]
Запустить контейнер в фоновом режиме docker run -d [image]
Запустить контейнер с интерактивным терминалом docker run -it [image]
Выполнить команду в запущеном контейнере docker exec [container] [command]
Просмотреть ВСЕ контейнеры docker ps -a
Просмотреть все запущенные контейнеры docker ps
Переименовать контейнер docker rename <old_name> <new_name>
Просмотр детальной информации о контейнере docker inspect [NAME]
Просмотр логов контейнера docker logs [container_id]
Просмотр изменений в контейнере docker diff <name>
Перезапустить контейнер docker start [NAME/CONTAINER ID]
Остановить запущенный контейнер docker stop [NAME/CONTAINER ID]
Удалить контейнер docker rm [NAME]
Удалить ВСЕ контейнеры docker rm $(docker ps -aq)
Удалить все остановленные контейнеры docker rm -v $(docker ps -aq -f status=exited)

Команды Docker 1.13+:

Описание Команда
Посмотреть список существующих образов docker container list
Удалить образ с локальной машины docker container start
Удалить образ с локальной машины docker image history

Примеры

Command Line Application

Попробуем собрать собственный образ на основе простой программы.

Для начала создадим файл HelloApplication.java, в котором будет выводиться сообщение "Hello!" на консоль:

public class HelloApplication {
    public static void main(String[] args) {
        System.out.println("Hello!");
    }
}

Теперь создадим Dockerfile, что бы "проинструктировать" докер о том, что мы хотим сделать:

FROM java:8

WORKDIR /usr/src/app/
COPY . .

RUN javac HelloApplication.java
CMD ["java","HelloApplication"]

Пройдемся по каждому шагу данного скрипта:
FROM - указываем базовый образ сборки;
WORKDIR - указываем рабочий каталог;
COPY - копируем файл с хост машины в рабочий каталог. Точка . указывает на текущую директорию;
Еще раз RUN - комплируем наш класс для возможности дальнейшего запуска;
CMD - указываем команду для запуска нашего класса.

Класс и скрипт созданы, теперь остается написать команду в консоли для создания самого образа - docker build -t helloapp .
С помощью флага -t устанавливаем имя образа (можно указывать только в нижнем регистре, по умолчанию <none>), сборку образа проводим с текущего каталога.

После успешного создания образа выполним команду docker images, что бы посмотреть список всех образов:

REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
helloapp             latest              a59ecff4511a        2 days ago          643MB
java                 8                   d23bdf5b1b1b        3 years ago         643MB

Запустим наш контейнер с помощью команды docker run helloapp и если все было сделано верно, то на консоли мы увидим сообщение:

Hello!

После этого контейнер закончит свою работу.

Команды Dockerfile

FROM

FROM - задает базовый образ

WORKDIR

WORKDIR - позволяет указать рабочую директорию, после чего инструкции RUN, CMD, ADD, COPY, или ENTRYPOINT будут выполняться в контексте этого каталога. Можно сказать, что эта инструкция выполняет mkdir и cd неявно.
Пример Dockerfile без WORKDIR:

...
RUN mkdir -p /usr/src/app/
COPY . /usr/src/app/
RUN cd /usr/src/app/ && do-something

Тег -p позволяет создать все дерево каталогов целиком.

Теперь используем WORKDIR:

WORKDIR /usr/src/app/
COPY . .
RUN do-something

Ключевым моментом здесь является указание инструкции WORKDIR /usr/src/app/ - все следующие шаги будут выполняться в каталоге /usr/src/app/.

WORKDIR в Dockerfile может использоваться несколько раз. Если указан относительный путь, он будет относительно пути предыдущей инструкции WORKDIR, например:

WORKDIR /foo/bar
WORKDIR baz

RUN pwd

В данном случае команда pwd выведет на экран каталог /foo/bar/baz.

COPY

COPY <src> <dest> - добавляет файлы или папки из нашего билд-окружения (хоста) в образ.

COPY . /usr/src/app/

ADD

ADD <src> <dest> - добавляет файлы или папки из нашего билд-окружения (хоста) в образ. Отличается от COPY поддержкой двух функций:
Во-первых, возможность использовать URL вместо локального файла/каталога:

ADD http://wordpress.org/latest.zip /root/wordpress

Во-вторых, возможность извлечь tar-файл из источника непосредственно в место назначения:

ADD latest.tar.gz /var/www/wordpress/

EXPOSE

EXPOSE - указывает на необходимость открыть порт (является метоинформацией).
Пример использования:

EXPOSE 8085

Что бы пробросить порт из контейнера на хост машину, используется флаг -p при запуске контейнера:
docker run --rm -p 8081:8085 <image>

MAINTAINER (deprecated)

MAINTAINER <name> - с помощью этой инструкции можно указать автора образа. Но в документации MAINTAINER считается устаревшей и рекомендуется использовать инструкцию LABEL.
Просмотреть информацию об авторе можно с помощью команды docker inspect <images>
Пример использования:

LABEL

LABEL <key>=<value> - эта инструкция используется для добавления методанных в образ. LABEL является более гибкой версией MAINTAINER, т.к. LABEL позволяет устанавливать любые методанные, которые вам требуются.
Пример использования:

LABEL com.helloapp.version="0.0.1"
LABEL com.helloapp.release-date="2020-06-07"
LABEL maintainer="[email protected]"

Просмотреть все установленные метки можно с помощью команды docker inspect <images>

RUN

Инструкция RUN позволяет создать новый слой и зафиксировать его во время сборки образа. Эта инструкция часто используется для установки дополнительных пакетов или создания папок.
RUN может быть использована в exec-форме, либо в shell-форме:

# shell
RUN apk update && apk upgrade
# exec
RUN ["mkdir", "/dir"] 

CMD

CMD - описывает команду с аргументами, которая выполнится при запуске контейнера.
Аргументы могут быть переопределены при запуске контейнера в командной строке. При запуске контейнера с аргументами, аргументы по умолчанию будут проигнорированы.
В файле может присутствовать лишь одна инструкция CMD, если инструкций несколько, будет использована только последняя.
Инструкция CMD может быть использована в exec и shell-форме.

ENTRYPOINT

ENTRYPOINT - описывает команду с аргументами, которую нужно выполнить когда контейнер будет запущен.
Она схожа с CMD, но аргументы, заданные в ENTRYPOINT, не перезаписываются в случае запуска контейнера с аргументами командной строки. Вместо этого, передаваемый аргумент запишется в конец списка аргументов ENTRYPOINT.
Инструкция ENTRYPOINT может быть использована в exec и shell-форме.
С помощью связки ENTRUPOINT и CMD можно установить параметры по умолчанию, а затем использовать CMD для установки дополнительных параметров, которые могут быть перезаписаны, например:

FROM ubuntu
ENTRYPOINT ["echo", "Hello"]
CMD ["world"]

Создадим образ hello и запустим контейнер - docker run --rm hello
В консоли получим сообщение Hello world
Теперь запустим контейнер с параметрами - docker run --rm hello John Wick
В консоли получим сообщение Hello John Wick

RUN, CMD and ENTRYPOINT

Все эти инструкции могут быть использованы в exec, либо в shell-форме.
Exec-форма использует синтаксис, напоминающий JSON-формат: <instruction> ["executable", "param1", "param2"], например CMD ["java", "-jar", "Application.java"]
Sell-форма это привычная консольная команда <instruction> <command>, например CMD java -jar Application.java

Использование:
RUN используется для создания и фиксирования нового слоя в образе.
При сборке исполняемого Docker-образа предпочтительней использовать инструкцию ENTRYPOINT.
CMD можно использовать дополнительно, если нужно предоставить дополнительные аргументы, которые могут быть перезаписаны из командной строки.

ENV

ENV - позволяет задавать переменные в Dockerfile.
Пример использования:

...
ENV path /usr/src/app/
RUN mkdir -p $path
WORKDIR $path
CMD ["pwd"]

Результатом выполнения контейнера будет строка /usr/src/app

С помощью инструкции ENV можно определить часовой пояс в контейнере, например - ENV TZ=Europe/Moscow.

ARG

Инструкция ARG позволяет задавать переменные в Dockerfile
Но в отличии от ENV-переменных, ARG-переменные недоступны во время выполнения контейнера, например:

FROM ubuntu
ENV w=world
ARG h=hello
CMD echo $h $w

На основе данной конфигурации создадим образ и запустим контейнер, в результате чего на консоли увидим только world.

ARG-переменные можно использовать для заданий значений по умолчанию для ENV-переменных из командной строки во время сборки образа, например:

FROM ubuntu
ARG a=hello
ENV e=$a
CMD echo $e

На основе данной конфигурации создадим образ - docker build -t hello --build-arg a=bye .
И запустим контейнер, в результате чего на консоли увидим bye.

VOLUME

VOLUME - позволяет указать директорию, которую контейнер будет использовать для постоянного хранилища.
Это поможет сохранить все созданные/измененные данные в результате работы даже после удаления контейнера.

Для работы с VOLUME существует несколько способов:
Способ 1. Определить том в Dockerfile

VOLUME /src/fold

В этом случае для каждого контейнера при запуске будет создан свой том, который по умолчанию расположен в /var/lib/docker/volumes/<VOLUME NAME>/_data
Просмотреть содержимое этой папки можно командой - sudo ls /var/lib/docker/volumes/<VOLUME NAME>/_data

Способ 2. С помощью тега -v "примонтировать" папку к контейнеру
Создадим папку val на рабочем столе и поместим туда какие-нибудь файлы.
Далее запустим контейнер с образом ubuntu и зайдем в его терминал:
docker run -it --rm -v /home/dmitrii/Desktop/val:/home/dmitrii/new_Desktop ubuntu
В терминале выполним:
cd /home/dmitrii/new_Desktop && touch another_file && ls
Это позволит нам увидеть все содержимое папки и наш только что созданный файл.
Любые операции с содержимым папки val будут отображаться в контейнере и на хост-машине в реальном времени.

Способ 3. Создать том
Создать том вручную с помощью команды - docker volume create --name storage
При создании контейнера указываем том - docker run -it --mount source=storage,destination=/path/in/container --name mycont1 helloapp
Находясь в контейнере, зайдем в папку с томом и создадим файл - /path/in/container# touch somefile
Теперь, если мы запустим новый контейнер и укажем ему в качестве тома уже наш существующий том - --mount source=storage, то он также будет иметь доступ ко всем ранее созданным файлам и папкам.
Флаг --mount более гибкий вариант --value, он может принимать различные параметры, представленные в виде key=value.

Команды для volume

Описание Команда
Создать том docker volume create --name <volume name>
Просмотреть все тома docker volume ls
Исследовать конкретный том docker volume inspect <volume name>
Удалить том docker volume rm <volume name>
Удалить все тома docker volume rm $(docker volume ls -q)
Удалить не используемые тома docker volume prune

Флаги для mount
Пример со множеством параметров: docker run --mount type=volume,source=volume_name,destination=/path/in/container,readonly my_image

Описание Команда
Тип монтирования type=volume/bind/tmpfs
Указания тома source/src=<VOLUME NAME>
Путь в контейнере, к которому будет монтироваться том destination/dst/target=</path/in/container>
Монтирует том только для чтения readonly

docker-compose

Инструмент docker-compose предназначен для быстрой настройки и запуска множества контейнеров.
Что бы им воспользоваться, необходимо создать файл docker-compose с расширением .yml или .yaml.

Команды docker-compose:

Описание Команда
собрать образы docker-compose build
собрать образы и запустить контейнеры docker-compose up
собрать и запустить в фоновом режиме docker-compose up -d
пересобрать и запустить docker-compose up -build
остановить и удалить контейнеры docker-compose down
вывести список запущенных в compose контейнеров docker-compose ps
выполнить команду в запущеном контейнере docker-compose exec [service] [command]
частичный запуск docker-compose up [service]
просмотреть логи конкретного контейнера *docker-compose logs -f [service name]
вывести список образов *docker-compose images
просмотреть логи конкретного контейнера *docker-compose logs -f [service name]

По умолчанию, docker-compose up не будет перестраивать контейнеры, если они уже есть на хосте. Что бы заставить докер делать это, нужно использовать аргумент --build. Так что при запуске контейнеров лучше всего использовать docker-compose up --build, это поможет не волноваться о том, что вы забыли пересобирать контейнеры после внесенных в них изменений.

Пример реализации и синтаксиса:

# Файл docker-compose всегда должен начинаться с тега версии, 
# каждая версия поддерживает определенные инструкции
version: '3.1'

# Секция с сервисами (сервис == контейнер), которые будут запущены
services:
  # Имя сервиса (не путать с именем контейнера --name somename)
  db:
    # Указывается образ для создания сервиса/контейнера
    image: postgres
    # Позволяет задать путь к Dockerfile, который будет использован для создания образа
    # ВАЖНО! Сервис не может одновременно иметь ключи image и build
    build: ./src
    # Можно задать имя контейнера по умолчанию
    container_name: restweb
    # Аргументы restart определяет стратегию повторного запуска сервиси: no (никогда),
    # on-failure (после критической сбоя), always (всегда)
    restart: always
    # Проброс портов из контейнера на хост-машину. Работает аналогично флагу -p
    ports:
      - 5432:5432
    # Задает переменные для контейнера. Работает аналогично флагу -e
    environment:
      POSTGRES_PASSWORD: example
    # Позволяет монтировать общую папку для хоста и контейнера
    volumes:
      - data-volume:/var/lib/postgresql/data
    # Указывает, должен ли сервис перед запуском ждать запуска других сервисов
    depends_on:
      - some_service

RESTful application + docker-compose example

Рассмотрим использование docker-compose на примере простого веб-приложения.

  1. Создадим веб-приложение, которое по адресу http://localhost:8085/all будет выводить список всех пользователей из таблицы базы данных.
    Для создания используем Spring Web, JPA, PostgreSQL и упакуем все в .jar архив с помощью сборщика Maven.
    В рамках данного примера код приложения показан не будет.
  2. После написания приложения, необходимо создать Dockerfile:
FROM openjdk:8-jdk-alpine
COPY target/docker-spring-boot.jar .
EXPOSE 8085
CMD ["java", "-jar", "docker-spring-boot.jar"]

Используем alpine-образ openjdk:8-jdk-alpine заместо java:8 для экономии места.

  1. Теперь создадим docker-compose.
    В качестве сервисов там будут выступать: наше приложение, база данных postgresql и adminer, который с помощью веб-интерфейса позволяет подключиться к базе данных для более удобного управления ею.
version: '3.3'

services:
  db:
    image: postgres
    restart: always
    container_name: db
    environment:
      POSTGRES_PASSWORD: 1
      POSTGRES_DB: rest_db
    volumes:
      - /home/dmitrii/Desktop/db_temp:/var/lib/postgresql/data

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

  restweb:
    build: .
    container_name: restweb
    ports:
      - 8081:8085
    depends_on:
      - db

Важно! Для того, что бы наше приложение смогло соединиться с базой данных, в конфигурационном файле application.properties приложения необходимо указать в качестве адреса имя сервиса с базой данных - spring.datasource.url=jdbc.postgresql://db:5432/postgres.

  1. Собираем образы и запускаем все наши сервисы/контейнеры с помощью команды docker-compose -up.
    После успешного запуска заходим по адресу http://localhost:8081/all и проверяем, что получаем корректные данные из БД.

DockerHub

Для того, что бы найти и скачать какой-то определенный образ в репозитории DockerHub, необходимо:

  1. Произвести поиск образов в репозитории (опционально) - docker search [image]
  2. Скачать требуемый образ - docker pull [image]:[teg]

Созданый образ можно загрузить в репозиторий DockerHub:

  1. Авторизоваться на https://hub.docker.com/
  2. Создать репозиторий
  3. Авторизоваться в командной строке docker login --username=[username] --email=[email]
  4. Отправляем существующий образ в репозиторий docker push [username]/[exist_image]

Источники

About Docker:
Docker Docs: Docker run options
Habr: Погружаемся в Docker: Dockerfile и коммуникация между контейнерами
The Difference between COPY and ADD in a Dockerfile
Habr: Шпаргалка с командами Docker
Используем Docker и не волнуемся о vendor-lock
Habr: Руководство по Docker: с нуля до кластера на AWS
Docker import/export vs. load/save
Habr: Шпаргалка с командами Docker
Виртаулизация:
Stack: How is Docker different from a virtual machine?
Introduction to Containers, VMs and Docker/Сравнение VM и Docker
Type 1 and Type 2 Hypervisors: What Makes Them Different
Виртуализация – теория и практика
Habr: Автоматизация Для Самых Маленьких. Часть 1.1. Основы виртуализации
Технологии аппаратной виртуализации
Habr: VM или Docker? What is virtualization? (Кратко о виртуализации)
WORKDIR:
Docker совет №20: Используйте WORKDIR
What is the WORKDIR command in Docker?
Stackoverflow: What is the point of WORKDIR on Dockerfile?
VOLUME:
Habr: Изучаем Docker, часть 6: работа с данными
Docker Docs: Use volumes
RUN, CMD, ENTRYPOINT:
goinbigdata: Docker RUN vs CMD vs ENTRYPOINT
Stack: Difference between CMD and ENTRYPOINT
Docker-compose:
Habr: Руководство по Docker Compose для начинающих
Habr: Полная автоматизация с помощью docker-compose
DockerDocks: Overview of Docker Compose
GitHub: FastApli Postgresql/множество примернов docker-compose
DockerHub:
Pushing and Pulling to and from Docker Hub
Размещение образов на Docker Hub

⚠️ **GitHub.com Fallback** ⚠️