Skip to content

Latest commit

 

History

History
616 lines (479 loc) · 30.9 KB

Docker.md

File metadata and controls

616 lines (479 loc) · 30.9 KB

Контейнеры и контейнеризация

Контейнер (Container) — стандартная единица ПО, в которую упаковывается приложение со всеми необходимыми для его полноценной работы зависимостями (кодом, средой запуска, библиотеками и настройками).

Сравнение контейнера и виртуальной машины

Будем называть хост-машиной (host machine) компьютер (сервер), ресурсы которого выделяются под контейнер или виртуальную машину.

Контейнерпроцесс или сервис, который напрямую запущен на хост-машине.

Docker-демон следит за тем, чтобы контейнер запускался в полной изоляции от операционной системы хост-машины. Ничего подобного виртуальной машине при этом не создаётся.

Виртуальная машина (Virtual Machine) — изолированная операционная подсистема на хост-машине.

С помощью виртуальной машины можно внутри Windows ОС запустить Linux и наоборот. Существует множество инструментов, чтобы работать с виртуальными машинами (например, Virtual Box).

Преимущества контейнеров

  • Возможность упаковать приложение вместе с его средой запуска. Это позволяет контейнеру запускаться одинаково в разных окружениях (операционных системах) и решает пооблему их настройки (подготовки к запуску приложения), а значит на каждом компьютере запуск контейнера происходит одинаково.
  • Поскольку в контейнерах содержится только самое необходимое (ничего лишнего), им свойственны легковесность, быстродействие и простота настройки.

Docker

Архитектура Docker

Docker использует клиент-серверную архитектуру.

С клиента, который называется Docker-клиент (Docker client), поступают CLI-команды.

/* примеры CLI-команд */
docker build
docker ps
docker run

Клиент при помощи REST API передаёт команды серверу, который называется Docker-демон (Docker daemon).

Docker-демон собирает, запускает и раздаёт (distribute) контейнеры.

Этапы докеризации приложения

Создание Dockerfile

Dockerfile содержит инструкции (instructions) — последовательность действий, которые нужно выполнить, чтобы построить образ.

Простой пример Dockerfile для NodeJS-приложения.

# Dockerfile
FROM node:latest
EXPOSE 3001

WORKDIR /app

COPY ./package.json .
COPY ./src ./src

RUN npm install
RUN npm run build

CMD npm run start

Конкретные инструкции Dockerfile разобраны здесь.

Построение образа

Образ (Image) — доступный только для чтения шаблон с инструкциями о том, как запустить какой-то Docker-контейнер, содержащий внутри себя всё необходимое для выполнения этих инструкций.

Один образ может расширять другой.

Базовый образ (Base Image) — образ, который не имеет родительского образа.

Для построения образа используется команда docker build, которая принимает контекст (context) — путь к папке, с которой будет происходить работа в Dockerfile.

/* узнать текущую папку консоли */
ls

/* "." означает, что текущая папка консоли взята в качестве контекста */
docker build . 

Образ строится на основании Dockerfile, который по умолчанию берётся из корня контекста.

Каждая инструкция в Dockerfile создаёт новый слой (layer) в образе.

Если Dockerfile лежит не в корне контекста, то можно явно указать путь к файлу.

docker build -f /path/to/a/Dockerfile .

Можно задать явное название образа.

docker build -t your_image_name .

Найти созданный образ можно среди других образов при помощи команды docker images.

docker build -t test .
docker images

/*
  REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
  test                   latest              9468e6677939        22 seconds ago      730MB
  mongo                  3.4                 aeaac14e1ffb        5 months ago        429MB
  redis                  4.0                 04c446bf216f        5 months ago        89.2MB
  node                   10.15.3             5a401340b79f        10 months ago       899MB
*/

Образы хранятся в Docker-реестре (Docker registry).

Одним из публичных реестров является Docker Hub. Он используется по умолчанию.

// загрузить image из реестра
docker pull <image>
// загрузить image в реестр
docker push <image>

Создание и запуск контейнера

Контейнер (Container) — запускаемый экземпляр образа.

Для создания и последующего запуска контейнера по образу можно спользовать команду docker run.

docker run -d image_name

Флаг -d используется для запуска контейнера в фоновом режиме (in background), таким текущая консоль не будет занята контейнером и можно будет вводить в неё другие команды.

Можно также задать явное имя контейнеру при создании.

docker run -d --name container_name image_name

Команда docker run объединяет в себе две команды: docker create и docker start.

/* создание контейнера */
docker create image_name
/* запуск ещё не запущенного контейнера */
docker start container_id

Для просмотра списка всех запущенных контейнеров и информации о них используется команда docker ps.

docker ps

/*
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
b56a528cbe78        test                "docker-entrypoint.s…"   2 days ago          Up About a minute   3001/tcp           fervent_brattain
*/

Для просмотра всех контейнеров (в том числе и незапущенных) используется флаг -a.

docker ps -a

Если есть необходимость посмотреть, что лежит внутри запущенного контейнера, можно зайти в него при помощи команды docker exec.

docker exec -i -t container_id bash
/* осуществляется переход в интерактивный режим */

Пример работы в интерактивном режиме.

/* вывод названий файлов и папок в текущей директории "app" */
root:/app# ls

/* вывод файла "package.json" в консоль */
root:/app# cat package.json

/* переход в папку "src" */
root:/app# cd ./src

/* выход из интерактивного режима */
root:/app/src# exit 

Флаг -i отвечает за переход в интерактивный режим, флаг -t позволяет эмулировать терминал.

Для остановки контейнера используется команда docker stop.

docker stop container_id

Композиция нескольких контейнеров

Для написания приложения чаще всего не достаточно одного контейнера.

Если есть необходимость иметь несколько контейнеров в одном приложении, то нужно каждый из них настроить, как указано в этапах выше.

Обычно контейнеры зависят друг от друга, поэтому их запуск должен осуществляться в строгой последовательности. Такой запуск называется композицией контейнеров.

Чтобы было проще запускать композицию контейнеров, её шаги описывается в отдельном файле при помощи Docker Compose.

Хранение данных в Volume

Volume — предпочитительный механизм для хранения данных (persisting data), используемых в Docker-контейнере.

Volume даёт контейнеру доступ к какой-то локальной папке на хост-машине, на которой этот контейнер запущен. Файлы из Volume нельзя использовать на этапе сборки (build-time), то есть в Dockerfile нельзя использовать файлы из Volume, они доступны лишь во время выполнения (run-time).

Почему следует использовать Volume

  • Volume хранится вне контейнера, поэтому он не увеличивает размер контейнера и не подвержен влиянию жизненного цикла контейнера.

Dockerfile

Использование образов с помощью FROM

Инструкция FROM инициализирует новый этап сборки и устанавливает базовый образ, функциональность которого может быть использована в последующих инструкциях.

# Dockerfile
FROM ubuntu:latest
# Dockerfile

# образ для NodeJS
FROM node:latest 

# ...

# npm доступен благодаря образу node
RUN npm i 

Валидный Dockerfile должен содержать как минимум одну инструкцию FROM и она должна быть первой инструкцией в файле.

Для создания базового образа используется инструкция FROM scratch.

Копирование файлов с помощью ADD И COPY

Инструкция COPY позволяет скопировать локальный файл или папку с хост-машины в образ. Она принимает два параметра: относительный путь на хост-машине, откуда копировать, и абсолютный путь, по которому данные будут доступны в контейнере.

# Dockerfile
COPY ./package.json /app/
COPY ./src /app/src

Инструкция ADD может делать то же самое, но помимо этого она может принять URL как источник для копирования или разархивировать локальный .tar файл, а затем поместить в образ.

# Dockerfile
ADD ./package.json /app/
ADD ./src /app/src

ADD archive.tar.gz /
ADD http://some_url.here /

Если нет явной необходимости в ADD, лучше использовать COPY.

Запуск команд с помощью RUN и CMD

Инструкция RUN позволяет запускать команды внутри образа (image). Эти команды запускаются один раз во время сборки (build) и записываются в образ как новый слой (layer).

# Dockerfile
RUN echo "Install modules"
RUN npm install

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

# Dockerfile
CMD npm start

Таким образом, несмотря на то, что CMD является инструкцией Dockerfile, он запускается не во время сборки, а уже в запущенном контейнере. Чаще всего командой в CMD выступает запуск сервера.

Рабочая директория WORKDIR

Инструкция WORKDIR устанавливает рабочую директорию для инструкций RUN, CMD, COPY, ADD. Все действия, связанные с перечисленными инструкциями, будут происходить в заданной при помощи WORKDIR директории.

# Dockerfile
WORKDIR /app

По умолчанию используется WORKDIR /.

Инструкция WORKDIR может быть использована несколько раз.

WORKDIR /
COPY ./package.json /app/

WORKDIR /app
COPY ./src ./src

Аргументы ARG

Инструкция ARG определяет переменную, которую можно передать во время сборки (build-time) контейнера.

  • Объявление аргументов в Dockerfile.
# Dockerfile
ARG argument_name
ARG another_argument_name=default_value # со значением по умолчанию
  • Использование аргументов в Dockerfile.
# Dockerfile
RUN echo ${argument_name}
RUN echo ${another_argument_name}
  • Передача аргументов в команду сборки контейнера.
docker-compose build --build-arg port=3000 --build-arg env="local"
# Dockerfile
ARG port
ARG env

Переменные окружения ENV

Инструкция ENV сохраняет переменную внутри контейнера. Таким образом переменная в контейнере доступна во время выполнения (run-time).

  • Объявление переменных окружения в Dockerfile.
# Dockerfile
ENV env=production
  • Передача в команду запуска контейнера.
docker run -e env=production

Если есть необходимость передать аргумент как переменную окружения, то можно сделать это следующим образом.

# Dockerfile
ARG port
ENV port=${port}

Порты и инструкция EXPOSE

Ранее уже отмечалось, контейнеры достаточно изолированы от окружающего мира, но иногда это можно контролировать.

Если внутри контейнера запущено приложение на каком-то порте (например, 3000), то оно будет доступно только внутри контейнера. Проверить, что оно действительно запущено в контейнере можно, сделав запрос на URL из консоли контейнера.

docker exec -it container_id bash
curl "http://localhost:3000"

При этом у хост-машины нет доступа к приложению, запущенному в контейнере.

Инструкция EXPOSE используется в целях документации, позволяя явно указать, какие порты используются внутри контейнера.

# Dockerfile
FROM node:12.13.1

EXPOSE 3000

Если порт контейнера выставляется наружу, то он называется выставленным (exposed).

Можно также вместо инструкции EXPOSE выставить порт при помощи флага --expose.

docker run --expose 3000 your_image

Выставление порта не является обязательным, поскольку оно не предоставляет хост-машине доступ к приложению.

Чтобы предоставить доступ хост-машине, необходимо опубликовать (publish) порт. В таком случае порт называют опубликованным (published).

При создании контейнера у него по умолчанию нет опубликованных портов.

Для публикации порта используется флаг --publish, -p. Флаг принимает порт хост-машины и порт контейнера в формате hostPort:containerPort.

docker run --publish 4000:3000 your_image

В примере выше приложение, которое запущено внутри контейнера на порте 3000, также доступно и на хост-машине на порте 4000 (http://localhost:4000).

Можно также опубликовать сразу все выставленные порты контейнера на случайные порты хост-машины при помощи флага --publish-all, -P.

docker run --publish-all your_image

Публикация на случайные порты хост-машины не удобна из-за трудности конфигурации приложений, которые от этих портов зависят.

Несколько портов лучше публиковать следующим образом.

docker run -p 3000:3000 -p 3001:3001 your_image

Пример Dockerfile для NodeJS

# Dockerfile
FROM node:12.13.1

ARG port
ARG env

EXPOSE ${port}
COPY ./package.json /app/
COPY ./src /app/src

WORKDIR /app

RUN npm install
RUN NODE_ENV=${env} npm run build

CMD npm run start
docker build --build-arg port=3001 --build-arg env=staging .

Docker Compose

Композиция контейнеров

Чаще всего приложения можно разделить на несколько контейнеров, которые зависят друг от друга. Понятно, что, чтобы приложение заработало, эти контейнеры нужно запускать вместе, причём в определённом порядке. Такой запуск называется композицией контейнеров.

Docker Compose — инструмент, позволяющий составлять композицию контейнеров (запускать приложения, состоящие из нескольких контейнеров).

Docker Compose использует файлы формала YAML (.yml).

Сервисы

Docker Compose файл состоит из сервисов (services). Сервис содержит в себе все данные, необходимые для запуска конкретного контейнера (с предварительным созданием образа для него при необходимости).

Есть два способа запустить сервис.

  • Можно указать готовый образ в поле image (можно скачать его с Docker Hub или создать самому).
# docker-compose.yml
version: '3.7'
services:
  foo:
    image: image_name
  • Можно настроить этап построения в поле build, указав там путь к Dockerfile, по которому должен быть построен образ.
# docker-compose.yml
version: '3.7'
services:
  bar:
    build:
      context: ./bar
      dockerfile: Dockerfile

Порядок запуска сервисов

Docker Compose запускает и останавливает контейнеры в порядке их зависимостей.

Если зависимости между контейнерами не указаны, то контейнеры запускаются последовательно.

Настроить зависимость можно при помощи поля depends_on.

В примере ниже запуск сервиса server произойдёт раньше, чем запуск client.

docker-compose up
# docker-compose.yml
version: '3.7'
services:
  client:
    image: image_name
    depends_on: server
  server:
    image: another_image_name

В случае остановки происходит обратная ситуация: client останавливается раньше, чем server.

docker-compose stop

Здесь важно отметить, что Docker Compose дожидается лишь окончания запуска контейнера, но не полной готовности того, что лежит внутри него. К примеру, база данных в контейнере может быть не готова к соединениям в тот момент, когда контейнер только запустился. В таких случаях нужно конфигурировать приложение таким образом, чтобы подключение к базе данных повторялось через некоторое время после каждой неудачной попытки.

Порты

Можно указать порты на хост-машине и внутри контейнера, по которым будет доступно приложение.

Сервис foo будет доступен на порте 3000 внутри контейнера.

# docker-compose.yml
version: '3.7'
services:
  foo:
    image: image_name
    ports:
     - "3000"

Сервис bar будет доступен на порте 4000 внутри контейнера и на порте 3001 на хост-машине.

# docker-compose.yml
version: '3.7'
services:
  bar:
    image: image_name
    ports:
     - "3000:4000"

Конфигурация сервиса baz эквивалетна конфигурации сервиса foo.

# docker-compose.yml
version: '3.7'
services:
  baz:
    image: image_name
    ports:
     - target: 4000 # порт, выставленный внутри контейнера
       published: 3000 # опубликованный порт (на хост-машине)

Пример композиции контейнеров трёхуровнего приложения

Трёхуровневое (3-tier) приложение состоит из клиента, сервера и базы данных. Для каждого уровня необходим отдельный контейнер, а поскольку они связаны друг с другом, создаётся их композиция.

Первым подключается база данных (сервис db), поскольку её может использовать сервер. Вторым подключается сервер (сервис server), поскольку его может использовать клиент. Последним подключается клиент (сервис client).

В docker-compose может указываться уже собранный образ (builded image) вместе с командой, которая должна быть запущена в контейнере; или Dockerfile, по котому образ будет создаваться.

# docker-compose.yml
version: '3.7'
services:
  db:
    command: mongod
    image: mongo:3.6.3
    ports:
      - "27017:27017"
  server:
    build:
      context: "./server"
      dockerfile: Dockerfile
    ports:
      - "4001:4001"
  client:
    build:
      context: "./client"
      dockerfile: Dockerfile
    ports:
      - "4000:4000"

Переменные ENVIRONMENT и ARGS

Перееменные окружения ENVIRONMENT передаются в уже запущенные контейнеры.

# docker-compose.yml
client:
  environment:
    - NODE_ENV: production
    - SERVER_URL: xxx

Переменные ARGS доступны во время построения образа (build image).

# docker-compose.yml
client:
  build:
    args:
      - port: 3000
      - env: production

Передача и использование аргументов в Docker Compose

  • Передача любых аргументов осуществляется при запуске Docker Compose в формате argument=value.
CLIENT_PORT=3000 ENV=production docker-compose up --build
  • Переданные аргументы доступны для использования в YAML-файле в формате${argument}. Есть возможность задать значение по умолчанию: ${argument:-defaultValue}. Без некоторых значений по умолчанию (например, для портов) может возникать ошибка приведения типов.
# docker-compose.yml
version: '3.7'
services: 
  client:
    build:
      context: "./client"
      dockerfile: Dockerfile
      args:
        port: ${CLIENT_PORT}
        env: ${ENV}
    environment:
      NODE_ENV: ${ENV}
    ports:
      - "${CLIENT_PORT:-3000}:${CLIENT_PORT:-3000}" # по умолчанию 3000:3000

Проверка конфигурации

Можно проверить правильность настройки, а также посмотреть все установленные переменные при помощи следующей команды.

docker-compose config