Docker

Для чего нужен Docker?

Зачастую наиболее распространенные биоинформатические алгоритмы доступны только пользователям одной операционной системы (тот же bowtie2 отсутствует на Windows). Чтобы решить эту проблему, некоторые разработчики стали предоставлять приложения в форме контейнеров (conteiners) - полностью изолированных от операционной системы самодостаточных наборов программ, включающие полный каталог скриптов и все зависимости. Разработчики приложения упаковывают в контейнер все необходимые компоненты операционной системы, и проверяют зависимости на совместимость, благодаря чему приложение из контейнера будет работать без ошибок на любой операционной системе. Запуск приложения в контейнере означает, что программа никак не зависит от окружающих процессов и операционной системы, в которой запущен контейнер. Самым распространенным менеджером контейнеров является Docker. С его помощью на ОС Windows можно запускать такие программы как bowtie2, hisat2, higlass и любые другие программы, которые были написаны специально под Linux или любую другую ОС. Вообще, разработчики все чаще предоставляют свои программы именно в форме docker-контейнеров, поэтому всем заинтересованным в биоинофрматике необходимо иметь хотя бы минимальные навыки работы с Docker.

Пояснения

Как контейнеры попадают на компьютер?

Для того, чтобы запустить контейнер, docker требуется скачать все необходимые файлы. Исходные файлы организованы в виде так называемых образов. Образ (image) является пакетом данных, который содержит нужное для запуска контейнера программное обеспечение.

Образ - это неизменяемый файл, содержащий исходный код, библиотеки, зависимости, инструменты и другие файлы, необходимые для запуска приложения в контейнере. Из-за того, что образы предназначены только для чтения их иногда называют снимками (snapshot). Образ используется как шаблон для построения контейнера. При создании контейнера поверх образа добавляет слой, доступный для записи, что позволяет менять его по своему усмотрению.

Перед запуском контейнера Docker должен скачать его образ (или несколько образов). В зависимости от программы размер образа может сильно отличаться (от нескольких мегабайт до десятков гигабайт). Скаченный образ будет храниться в файловой системе компьютера и будет использоваться при каждом запуске контейнера. Если вы удалите контейнер, то образ останется в системе. Однако, если вы запустили контейнер и сделали в нем какие-то изменения (добавили новые файлы, например), то внутри образа никаких изменений не будет. Так что при новом запуске контейнера все изменения будут утеряны, программа будет работать “с нуля”.

Пользователь может создавать образы самостоятельно либо получать их из репозитория Docker Hub. Удобно то, что пользователю docker не обязательно скачивать образ перед запуском контейнера. Команда docker run, которая запускает контейнер, при каждом вызове будет искать нужный образ в системе, и если такой отсутствует, то скачает его из хаба самостоятельно.

Архитектура платформы Docker

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

  • Docker Daemon

Основной движущей силой Docker является так называемый демон Docker Daemon. По определению демон (daemon) - это служебная программа, которая запускается UNIX-системой в фоновом режиме и напрямую не взаимодействует с пользователем (в Windows такие программы назваются службами). Функцией демона Docker ялвяется запуск, поддержка и хранение образов и контейнеров. По своей сути Docker Daemon является сервером, только при обращении к нему вы запускаете на компьютере не HTML-страницы или базу данных, а контейнеры. Запуск Docker Daemon производится при помощи программы dockerd.

  • Docker-клиент

Работа с контейнерами, включая их запуск, обращение к ним или останувку, происходит через Docker-клиент. По сути это набор программ и подпрограмм, которые позволяют пользователю обращаться к демону. Взаимодействие демона и клиента происходит при помощи REST API (интерфейс клиент-сервер). Существует два вид клиентов:

** Docker Client используется для работы с программами, размещенными внутри одного контейнера, и вызывается программой docker. Собственно, с этим клиентом и мы и будем знакомиться ниже.

** Docker Compose используется для работы с большими и сложными программами, для которых требуется запускать и обрабатывать множество контейнеров. Этот клиент вызывается программой docker-compose.

  • Движок Docker Engine

Программы Docker и Dockerd образуют клиент-серверное приложение, а общее название технологии, с помощью которой это приложение работает с контенерами, называется движок Docker (Docker Engine).

  • Интерфейс командной строки Docker CLI

Работа с Docker большую часть времени проходит в командной строке. Большинство инструкций, которые пользователь может дать движку Docker представляют собой текстовые команды. Такой стиль управления программами называется Command Line Interface. Набор текстовых инструкций, с помощью которых происходит управление Docker называют Docker command line interface, или Docker CLI.

  • Реестр Docker registry

Реестр Docker registry - это сервис, который хранит образы Docker. Наиболее известным реестром является Docker Hub - это бесплатный сервис, который может использовать каждый пользователь, и Docker по умолчанию настроен на поиск образов в Docker Hub. Но можно также запустить свой собственный частный реестр.

  • Репозитории Docker

Репозиторием Docker Repository называют коллекцию образов, обладающих одним названием, но представляющих собой разные версии одного и того же приложения. Например, в репозитории Python хранятся разные образы программы Python, в каждом из которых лежат разные версии интерпретатора. Идентификатор, указывающий на версию образа называется Тэгом (ярлыком, Tag). Тэг указывают после названия образа через двоеточие как дополнительный указатель на версию. По умолчанию Docker скачивает образы, с тэгом latest. Таким образом, если образ называется python, то это то же самое что python:latest. В указанном выше репозитории есть также версии python:3.8-bullseye, python:3.10-bullseye и куча других. По сути, репозиторий Docker - это раздел внутри реестра.

  • Приложение Docker Desktop

Docker Desktop — это приложение с графическим интерфейсом для ОС Mac, Linux или Windows, которое позволяет создавать и совместно использовать контейнерные приложения и микросервисы Docker. Оно позволяет вам управлять своими контейнерами, приложениями и образами без необходимости использовать терминал. Однако интерфейс Docker Desktop очень минималистичен и врд ли избавит вас от работы в командной строке.

  • Расширения Docker Extensions

Расширения Docker представляют собой инструменты, созданные сторонними разработчиками для увеличения фнукционала Docker Desktop.

  • Dockerfile

Существует несколько стратегий для созднания образов. Самая интуитивно понятная - это создать образ напрямую из контейнера. Например, если вы запустили контейнер и внесли в него изменения, добавив или удалив программы, копировав внутрь новые файлы, то из видоизмененного контейнера можно сделать образ. Однако это делает затруднительным распространение контейнеров между устройствами, а для экономии пространства на компьютере требуется загружать образ в реестр.

Вторая возможность - создать текстовый файл, который позволит Docker сконструировать контейнер по заданному внутри файлу сценарию. Такие текстовые файлы назвают “Dockerfile” (без расширения), они почти ничего не весят и могут свободно переноситься между устройствами.

Установка Docker Desktop

Сторонние руководства по установке можно найти по ссылкам ниже:

Установка Docker Desktop происходит в несколько этапов.

  1. В BIOS включить виртуализацию. Для этого нужно перезагрузить компьютер, войти в BIOS, затем в одном из окон найти опцию, которая включает слова “Virtualization” (Intel Virtualization Technology, VT, Virtualization и пр.) и “Hyper-V”. Затем перевести опцию в режим Enabled.

Куда смотреть в BIOS?

  1. Скачать установщик Docker Desktop на сайте docker.com. Во время установки следовать инструкциям.

Официальный сайт

Если все работает, то после установки вы должны увидеть вот такое окно:

Когда все нормально

Но программа может выдать ошибки из-за неполной установки зависимостей. В таком случае будут видны такие ошибки:

WSL2 is not installed

WSL2 installation is incomplete

Вообще в сообщении об ошибке уже написано, что делать, но если есть затруднения, то нужно сделать как указано ниже.

Решением будет установка подсистемы Linux для Windows, как это описано в руководстве здесь и здесь. На первый взгляд это кажется очень сложным, но на самом деле потребует только несколько раз войти в PowerShell и MicrosoftStore.

Как открыть PowerShell

Далее нажать Пуск и в строке поиска ввести «Дополнительные компоненты». В появившемся окне выбрать «Другие компоненты Windows». Далее поставить галочки напротив «Платформа виртуальной машины» и «Подсистема Linux для Windows».

Как найти Другие компоненты Windows

Пример работы с Docker и Docker Desktop

Для быстрого знакомства создатели docker разработали контейнер getting-started. Правильный запуск контейнера сделает возможным просмотр ознакомительной документации через HTML-страницы в браузере. По сути, после запуска этого контейнера, у вас появится ссылка на статический вебсайт, на который вы сможете зайти через браузер. Ниже показана последовательность действий, которая позволит запустить этот контейнер.

  1. Зайти в PowerShell или CommandLine
Способ 1
Способ 2
  1. Cкачать образ контейнера с помощью docker pull

В появившемся окне консоли ввести команду:

> docker pull docker/getting-started

Эту команда написана в документации образа docker/getting-started в правой части экрана.

В результате в окне приложения Docker Desktop в разделе images появится информация о скачанном образе. В данном случае это docker/getting-started. Нужно обратить внимание, что название образа совпадает с названием, которое было введен в команде выше.

Image

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

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

  1. Запустить контейнер.

Запустить контейнер можно через консоль (при помощи команды docker run) либо при помощи кнопки Run в раделе Images. Во время запуска нужно будет заставить Docker связать контейнер с IP-адресом. Это позволит контейнеру вывести результат работы программы в браузер.

Запуск через консоль
Запуск через Docker Desktop

В разделе Container появится название контейнера. В данном случае это condescending_bhaskara. Само название может отличаться от названия образа, поскольку мы его не задавали изначально.

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

Кроме того, у контейнера есть индентификатор. В данном случае это “f5a36b69f508”. Это кодовое название контейнера, которое может быть использовано в служебных командах Docker. Есть еще параметр port, к которому мы обратимся позже.

Container

Запущеный таким образом контейнер содержит программу, к которой мы можем обратиться при помощи браузера. Самый простой способ сделать это - нажать на троеточие напротив названия контейнера и в сплывающем окне вырбрать Open with browser. Либо можно открыть на компьютере браузер и ввести в адресную строку localhost:80 или, что то же самое, http://localhost:80. Нужно понимать, что параметр Open with browser и ссылка в браузере заработают только при правильной настройке контейнера.

Container

Обратите внимание, что после того, как вы введете нужный адрес в адресную строку браузера, адрес поменяется в соответствием с URL-адресами, запрограммированными внутри контейнера.

Как с помощью Docker запускать программы

Как было показано, запуск контейнера производится при помощи команды docker run. Показанный пример демонстрировал, каким образом при помощи нее запускаются серверные приложения. Однако в общем случае Docker позволяет путем минимальных модификаций, запускать в командной строке Windows любые программы, написанные под Linux (или под любую другую ОС).

Синтаксис команды docker run следующий:

> docker run [Параметры] название_образа [название_программы] [аргументы...]

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

Приведем элементарный пример. В операционной системе Linux команда echo "Hello World!" выводит на экран терминала строку “Hello World!”. C помощью Docker мы можем запустить эту команду, загрузив образ Ubuntu (Debian или любой другой ОС Linux), и запустив внутри соответсвующего контейнера упомянутую команду следующим образом:

> docker pull ubuntu

...Несколько служебных сообщений...

> docker run ubuntu echo "Hello World!"

Hello World!

Представленный пример по сути является бессмысленным, поскольку програма echo спокойно запустится на современных вариантах ОС Windows. Однако строго говоря, программы echo под Windows не существует, так как само слово “echo” является псевдонимом (alias) программы Write-Output.

Данный пример только демонстрирует, что запуск программ под, написанных под Unix-системы, на компьютере с ОС Windows с помощью Docker по сути не отличается от работы в нативном терминале Linux, только добавлением приставки, состоящей из служебных команд docker run. По сути, запуск программы начинается с вызова контейнера, после чего в нем запускается программа. В разделе ниже процесс создания контейнеров будет описан более подробно.

Работа с контейнерами

Как дать контейнеру название?

Параметр --name позволит присвоить контейнеру нужное имя. Например, после команды

> docker run --name StaticSite -d -p 80:80 docker/getting-started

название контейнера будет “StaticSite” и можно будет использовать это название при обращении к контейнеру другими командами docker.

Как найти все запущеные контейнеры и их параметры?

Команда

> docker ps

предоставить таблицу со списком всех установленных контейнеров. Ту же самую информацию можно увидеть в разделе Containers программы Docker Desktop. Дополнительные подробности можно рассмотреть в разделе Inspect, куда можно попасть если нажать на название контейнера. Стоит отметить, что по умолчанию docker ps (а также Containers) отображает только запущенные контейнеры. Можно заставить показать Docker все контейнеры, написав docker ps --all или, что то же самое, docker ps -a

> docker ps

Настройка доступа к контейнеру через браузер

Зачастую Docker используется для запуска на комьютере сервера. Как было показано выше, после включения контейнера docker/getting-started на компьютере запускается статичный HTML-сервер, к которому можно обращаться всегда, пока запущен контейнер. Правильная настройка доступа к серверу может быть проблематичной, если не понимать, как это работает (что затруднительно, особенно если не иметь представления о компьютерных сетях).

При запуске контейнера был указан параметр -p 80:80. Этот параметр заставляет привязать контейнер к IP-адресу локальной машины. А значит, чтобы открыть сайт, нам нужно ввести адрес на локальной машине.

Чтобы узнать, по какому адресу располагается сервер, который мы сейчас запустили, нужно обратить внимание на параметр port, который можно подробно рассмотреть в разделе Inspect, куда можно попасть если нажать на название контейнера.

Container

Как альтернатива, можно ввести в PowerShell или cmd команду

> docker ps

В таком случае в консоли появится следующая таблица:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f5a36b69f508 docker/getting-started “/docker-entrypoint….” 2 minutes ago Up 5 seconds 0.0.0.0:80->80/tcp condescending_bhaskara

Легко заметить, что информация в этой таблице почти полностью отображена в разделе Inspect. Здесь мы снова видим название контейнера, название образа, время создания, а также адрес порта.

Если мы знаем название контейнера, мы можем быстро узнать порт при помощи команды PowerShell

> docker port название_контейнера

Данные в ячейке PORTS позволяют понять, как можно обращаться к серверу через браузер. В данном случае для нашего контейнера port обозначен как 0.0.0.0:80 ->80/tcp. Строка 0.0.0.0 перед двоеточием - это IP-адрес, по которому браузер будет искать запущеную нами программу. В компьютерных сетях этим адресом обозначают все возможные IP-адреса, закрепленные за локальной машиной localhost, то есть закрепленные за компьютером, на котором запущен docker. Число 80 после двоеточия означает, что чтобы найти данные, принадлежащие именно запущеному на локальной машине контейнеру, браузер должен будет искать пакеты данных с индексом 80. Это так называемый порт контейнера.

Фрагмент ->80/tcp говорит о том, что браузер будет общаться с программой по протоколу TCP/IP через порт хоста c номером 80. Выбор порта и протокола передачи данных зависит от структуры сети и типа сервера, который запрограммирован в контейнере. В данном случае указан порт хоста 80, поскольку сервер передает HTML-страницы. Почтовые сервера обычно связываются с портом 25 или 110 и общаются по протоколу SMTP. Но вообще, это важно только для программистов. Поэтому, чтобы ничего не ломать, я предлагаю писать в параметре -p только тот адрес и протокол, которые упоминаются в документации к контейнеру.

Почему после запуска программы появился именно такой IP-адрес? В команде docker run мы вводили параметр -p 80:80. Перед двойточием указан порт контейнера, после двоеточия указан tcp-порт локальной машины под номером 80, по которому осуществляется передача данных по протоколу TCP/IP. Если команда выглядит “-p 8989:80”, то порт контейнера равен 8989, страницу нужно искать по IP-адресу локальной машины localhost, а передача данных будет производиться по протоколу TCP/IP. В таком случае, чтобы вызвать программу из браузера, нужно ввести адрес localhost:8989.

В документации docker есть много других примеров, как можно привязать контейнер к IP-адресам. Например, в команде -p 8989:80 мы не пишем конкретный IP-адрес, к которому будет привязан контейнер, поэтому docker по умолчанию использует адрес 0.0.0.0. Но так как это общее название для всех адресов на локальной машине, мы не знаем, к какому IP-адресу локальной машины все-таки будет привязан контейнер. Если docker принимает в качестве IP-адреса 0.0.0.0, то к контейнеру смогут подключиться пользователи на всех сетевых интерфейсах (как публичных, так и частных), что обычно считается небезопасным. Чтобы попасть на созданный нами сервер, мы должны вписать в адресной строке “localhost:8989”. В то же время, обращение 8989:80 является сокращенной формой, и полностью команда выглядит как -p 0.0.0.0:8989:80/tcp. Зная это, мы можем менять IP-адрес, порт контейнера, порт локальной машины, а также протокол передачи данных. Например, ввод -p 127.0.0.1:8000:80/tcp заставит docker привязать контейнер к адресу http:/127.0.0.1:8000.

Как приостановить и запустить снова выполнение контейнера?

Остановить запущеный контейнер можно из окна docker в разделе Containers, нажатием на значок Stop (серый квадратик) напротив названия контейнера. Либо нужно в PowerShell ввести команду:

> docker stop название_контейнера

Обратите внимание, что здесь нужно вводить не название образа, а название контейнера.

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

Чтобы заново запустить выполнение контейнера нужно нажать на значок Start (серый треугольник) напротив остановленного контейнера. Либо нужно в PowerShell ввести команду:

> docker restart название_контейнера

Режимы работы с контейнером

При запуске контейнера при помощи docker run в терминале вы должны сначала решить, хотите ли вы запускать контейнер в фоновом режиме (detached mode) или на переднем плане (foreground mode).

  • Фоновый режим

В примере выше мы запускали статический вебсайт следующим образом:

> docker run -d -p 80:80 docker/getting-started

А сразу после ввода команды терминал выводил идентификатор образа и приглашал ввести следующую команду:

> docker run -d -p 80:80 docker/getting-started

0e1d96e727a0d5b855b559689a91ca6e0bcf7149ce2492fa4525935336133950

>

Даже после закрытия терминала контейнер продолжает работать, и статический вебсайт остается доступен до тех пор, пока контейнер не будет остановлен. Это так называемый режим Detach, или фоновый режим.

В режиме Detach, после ввода команды docker run ... в терминале, Docker запустит программу внутри контейнера в фоновом режиме и вы сможете вводить другие команды оболочки Windows в терминал. В таком случае можно будет спокойно закрыть терминал, а контейнер продолжит работу. Контейнеры, запущенные в фоновом режиме, останавливаются, когда завершается корневой процесс, используемый для запуска контейнера.

Запустить фоновый режим можно с помощью параметра -d или --detach, или -d=true (это одно и то же). В примере выше мы именно его и вводили. Если вы укажете вместе с параметром -d еще параметр --rm, то после выхода из контейнера или закрытия Docker контейнер будет полностью удален.

  • Передний план

В примере выше мы запускали контейнер docker/getting-started с параметром -d в фоновом режиме, что делало возможным пользоваться терминалом после открытия контейнера. Если убрать этот параметр, то в окно консоли начнут выводиться журнал сообщений (logs, логи) контейнера.

> docker run -p 80:80 docker/getting-started

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
...[много всего остального]...
2022/10/13 12:08:41 [notice] 1#1: start worker process 39

Теперь, требуется открыть новое окно терминала, чтобы вводить команды в консоль, так как терминал привязан к процессу, запущенному Docker’ом. Это и есть запуск на переднем плане. Если закрыть терминал, то контейнер будет остановлен. Если в данном случае добавить параметр --rm, то после завершения программы или после закрытия терминала контейнер будет удален.

Те же логи можно просматривать и в Docker Desktop во вкладке Logs раздела Containers. Эту вкладку можно найти, если нажать на название контейнера. Логи несут служебную информацию и вряд ли пригодятся рядовому пользователю Docker.

Логи представляют собой текстовые сообщения, которые выводятся программами внутри контейнера. Есть стандатные сообщения (например, о том, что программа запущена или выполнена) и сообщения об ошибках (например, ошибка из-за неверного формата файлов). Вывод стандартных сообщений и ошибок в окно консоли производится через разные потоки вывода, а именно поток стандартного вывода (STDOUT) и поток стандартных ошибок (STDERR), соответственно. На самом деле, сообщения, которые выводятся через поток ошибок не обязательно содержат ошибки, и два потока вывода нужны для только для удобства. По умолчанию режим переднего плана подразумевает, что в консольвыводятся сообщения из обоих потоков вывода.В зависимости от цели, которую вы преследуете, вы можете заставить Docker выводить только сообщения об ошибках или только стандартные сообщения, а также получать сообщения из стандартного ввода. Для этого требуется использовать параметр -a:

> docker run ... -a stdout -a stderr ...

Вы можете вписать один или несколько потоков, повторив параметр -a несколько раз.

Легко обнаружить, что при запуске docker/getting-started c выводом сообщений по STDOUT, выводятся только сообщения о запуске скриптов:

> docker run -a stdout -p 80:80 docker/getting-started

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration

А при выводе только из STDERR, только сообщения о запуске процессов:

> docker run -a stderr -p 80:80 docker/getting-started

2022/10/13 12:08:41 [notice] 1#1: start worker process 32
...[аналогичные сообщения]...

Эти сообщения посылаются программами, запущенными внутри контейнера. В зависимости от того, что за программа работает внутри контейнера, вы увидите разные сообщения.

Передача данных в контейнер через поток стандартного ввода

Для работы некоторых приложения может потребоваться подать на вход текст, полученный из другой программы (через поток стандартного ввода STDIN) при помощи символа | (pipe). Чтобы сделать возможным получение данных из STDIN, необходимо упомянуть поток стандартного ввода в параметре -a stdin. В большинстве случаев, чтобы передать в контейнер данные из стандартного ввода придется также ввести параметр -i, который заставляет держать канал STDIN открытым, даже если контейнер не подключен к вводу (это задается создателем образа).

Продемонстрировать это можно, например, запуская программу cat, которая в Linux является стандартным средством просмотра текста через консоль, но в Windows эта программа отсутствует. Если ввести в консоль PowerShell команду

> echo "Hello World!" | docker run --rm -i -a stdin -a stdout ubuntu cat

то Docker передаст тестовую строку “Hello World!” программе cat, запущеной в контейнере из образа ubuntu (содержит минимальный набор компонентов ОС Linux), через STDIN. В свою очередь, cat передаст полученный текст в окно консоли через STDOUT.

В терминале мы увидим сообщение

Hello World!

> 

Когда программа cat завершит выполнение, то контейнер будет удален благодаря параметру --rm.

Запуск контейнера в интерактивном режиме

Контейнер может содержать компоненты ядра операционной системы, а значит, в ряде случаев внутри контейнера можно будет запускать собственный терминал. Это напоминает работу в виртуальной машине. Чтобы запустить терминал, нужно запустить docker run с параметром -t и -i одновременно (часто пишут одним словом -ti или -it). Для примера, следующая команда открывает в PowerShell терминал Ubuntu:

> docker run --rm -it ubuntu

В появившемся терминале можно вводить команды bash. Ниже я проверяю версию Ubuntu, обратившись к стандартному файлу os-ubuntu:

> docker run --rm -it ubuntu

root@df7701d1e063:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
root@df7701d1e063:/#

По сути, docker run --rm -it ubuntu запустила образ операционной системы Ubuntu в терминале Windows. По идее, это полнофункциональная ОС. Все равно, что запустить виртуальную машину с OC Linux, войти на сервер через или войти в терминал на компьютере с этой ОС.

Запрещено вводить параметр -t, если контейнер получает данные через конвейер pipe (знак |).

Запуск команд внутри конвейера, запущенного в фоновом режиме

Если внутри конвейера есть компоненты ядра операционной системы, то можно запустить их при помощи терминала внутри ковейера. Запустить терминал можно из Docker Desktop, если в разделе Containers. Для этого нужно либо нажать на троеточие напротив названия терминала и выбрать Open in terminal, либо нажать на имя контейнера и открыть вкладку CLI (правый верхний угол экрана).

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

> docker exec название_контейнера название_программы [аргументы]

Например, после запуска контейнера ubuntu можно запустить команду cat /etc/os-release, которая в Linux покажет версию операционной системы (то же самое, что показано в примере с запуском контейнера в интерактивном режиме:

> docker run -d -i --name Ubuntu_container ubuntu

3586325f911cd4a83600b08aff7e8e00d1c2ccf5bef5ce77b22a52fe38ccde1d

> docker exec Ubuntu_container cat /etc/os-release

PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

Первая команда запускает контейнер в фоновом режиме, при этом команда -i не позволит контейнеру остановиться, поскольку он будет ожитать ввода из STDIN.

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

Как удалить контейнер?

Удалить контейнер можно из окна docker в разделе Containers, нажатием на значок Delete (мусорный ящик) напротив названия контейнера. Либо в PowerShell можно ввести команду:

> docker rm название_контейнера(или идентификатор)

После удаления контейнера все данные внутри контейнера будут удалены.

Как удалить образ контейнера?

После остановки контейнера, его можно удалить. Это можно сделать из окна docker в разделе Images. Для этого нужно нажать на значок троеточия напротив названия контейнера, затем выбрать Remove. Либо в PowerShell нужно ввести команду:

> docker image rm название_образа(или идентификатор) 

Как переносить файлы между компьютером и контейнером?

Как уже говорилось, контейнер является полностью изолированной от операционной системы компьютера программой. При запуске контейнера docker создает отдельную файловую систему, и к файлам внутри контейнера невозможно обратиться при помощи стандартного файлового менеджера. Что нужно сделать, чтобы быстро и легко переносить данные из локальной машины в файловую систему компьютера?

Самый простой способ - привязать (bind) к контейнеру какую-нибудь папку на локальной машине. Принцип действия очень простой: при запуске контейнера внутри его файловой системы создается директория, которая привязывается к папке на компьютере. Любые изменения, любые новые файлы, внесенные в этой директории контейнера, автоматически отобразатся в папке на компьютере (и наоборот).

Чтобы привязать папку на компьютере к директории в контейнере (смонтировать Том), нужно добавить к команде docker run параметр -v [путь к папке на компьютере]:[путь к директории контейнера]. Обратите внимание, что путь к папке и путь к директории разделены двоеточием. Однако двоеточие есть в путях к папкам в ОС Windows. Поэтому чтобы вводить в параметр путь к папкам Windows, нужно стандартный адрес папок модифицировать как показано ниже:

Адрес папки в Windows Адрес, который нужно ввести
C:\Docker\myContainer\data //c/Docker/myContainer/data

Обратите внимание:

  1. Все обратные слэши (\) были заменены на прямые (/)
  2. Название диска (С) написано строчной буквой (с)
  3. Вместо двоеточия после названия диска (C:) пишется двойной слеш перед названием диска (//c)

Допустим, вы хотите, чтобы на компьютере с Windows Том располагался в папке “C:\Docker\myContainer\data”. В контейнере, содержащем ядро Linux, этому тому будет соответствовать директория “/data”. команда, монтирующая том будет выглядеть следующим образом:

> docker run ... -v //c/Docker/myContainer/data:/data ...

Если вы хотите смонтировать несколько папок, то можно воспользоваться параметром -v несколько раз:

> docker run ... -v //c/Docker/myContainer/data:/data -v //e/Docker/second_dir:/secondDir ...

Обратите внимание, что название папки на компьютере и директории в контейнере может не совпадать.

Более продвинутый способ - создать на компьютере Том (Volume). Тома - это файлы или каталоги, которые смонтированы непосредственно на хосте и не являются частью файловой системы контейнера. По определению, том — это файловая система, которая расположена на хост-машине за пределами контейнеров. Путь к Тому записан в памяти Docker в виде переменной, а раздел внутри контейнер может быть привязан к этой переменной, так что

Создать том можно двумя способами:

  1. В разделе Volumes программы Docker Desktop нажать кнопку Create (правый верхний угол) и ввести какое-нибудь название.

  2. В терминале (Cmd или PowerShell) ввести команду

> docker volume create название_тома

Вы можете найти эту папку при помощи файлового менеджера. Путь к папке, в которой лежат Томы в Windows 11 находится по адресу \\wsl.localhost\docker-desktop-data\data\docker\volumes. Его можно ввести в адресную строку проводника:

Volume

На скриншоте выше показно, как это может выглядеть. Обратите внимание, в проводнике Explorer в левой части экрана располагается панель быстрого доступа, где можно увидеть смонтированные при помощи WSL2 разделы docker-desktop и docker-desktop-data, которые появляются после установки Docker Desktop. В разделе docker-desktop-data - в указанной выше директории - и хранятся тома. На скриншоте показано, что помимо служебных файлов в этой диркетории лежит папка my_vol. Это название универсального тома, который был создан командой docker volume create my_vol. В этой папке лежит несколько служебных папок, а также папка _data. Собственно, содержимое последней папки и будет синхронизироваться с разделами контейнеров.

Для примера, в эту папку были скопированы несколько файлов: текстовый файл 1.txt, HiC карта dixon.mcool, а также изображение program.png. После копирования, появилось еще несколько служебных файлов, которые для нас не важны. Просмотреть содержимое тома my_vol можено в разделе Volumes в Docker Desktop, если нажать на название тома и выбрать вкладку DATA.

Volume1 Volume2 Volume3

После создания тома его можно привязывать к нескольким контейнерам, просто указав название тома перед названием директории в контейнере в параметре -v

> docker run ... -v my_vol:/data ...

Удалить том можно в разделе Volumes в Docker Desktop (значок корзины) либо в терминале при помощи команды

> docker volume rm название_тома

Узнать список всех томов на компьютере можно в разделе Volumes в Docker Desktop либо в терминале при помощи команды

> docker volume ls

Как копировать файлы в контейнер и из него?

Если вы хотите напрямую скопировать файлы из вашей хост-системы в контейнер, вы должны использовать команду docker cp к терминале (Cmd или PowerShell), например:

> docker cp путь_к_файлу_на_компьютере название_контейнера:путь_назначения_в_контейнере

Это работает и в обратную сторону:

> docker cp название_контейнера:путь_к_файлу_в_контейнере путь_назначения_на_компьютере

Использование контейнера на примере bowtie2

Как уже говорилось, программа bowtie2 написана под ОС Linux. Но при помощи Docker ее можно очень быстро запустить на компьютере.

Для данного примера из github-репозитория Rbowtie2 были загружены экземпляры fastq-файлов (reads_1.fastq + reads_2.fastq) и fasta-референс генома фага лямбда (lambda_virus.fa). Создатели библитеки Rbowtie2 (для языка программирования R) предоставили эти данные для тестирования своей библиотеки на компьютере пользователей. Мы могли бы взять совершенно другие данные, так как это не принципиально. Эти файлы были загружены в директорию C:\\Docker_control\\Bowtie2_example со следующей структурой:

C:\Docker_control\Bowtie2_example\
    refs\
        lambda_virus.fa
    reads\
        reads_1.fastq
        reads_2.fastq

Первое, что мы должны сделать - это выбрать образ для контейнера. В Docker Hub я выбрал репозиторий biocontainers/bowtie2, поскольку из него было больше всего скачиваний. После просмотра тэгов в репозитории я обнаружил, что тэг “latest” отсутствует. Поэтому указание biocontainers/bowtie2 для скачивания образа приведет к ошибке. Поэтому я вручную выбрал тег v2.3.4.3-1-deb_cv2, поскольку у него был небольшой объем.

Образ можно загрузить заранее (но не обязательно, так как это делается автоматически при первом обращении в команде docker run):

docker pull biocontainers/bowtie2:v2.3.4.3-1-deb_cv2

Теперь нужно проиндексировать FASTA-файл с референсным геномом вируса при помощи инструмента bowtie2-build.

docker run --rm -v //c/Docker_control/Bowtie2_example/refs/:/data/ -w /data biocontainers/bowtie2:v2.3.4.3-1-deb_cv2 bowtie2-build lambda_virus.fa lv

С помощью последней команды мы сделали следующее:

  1. Запустили оболочку Linux внутри контейнера и привязали там директорию /data к папке C:\\Docker_control\\Bowtie2_example\\
  2. Установили рабочую директорию /data/refs/ при помощи параметра -w /data/. Это сделано для простоты.
  3. Запустили внутри контейнера программу bowtie2-build с fasta-файлом на входе и оутпутом в папке /data/.
  4. Удалили контейнер сразу после завершения bowtie2-build.

Индексы получились.

    refs\
        lambda_virus.fa
        lv.1.bt2"
        lv.2.bt2"
        lv.3.bt2"
        lv.4.bt2"
        lv.rev.1.bt2"
        lv.rev.2.bt2"

Теперь выравниваю

docker run --rm -v //c/Docker_control/Bowtie2_example/:/data/ -w /data biocontainers/bowtie2:v2.3.4.3-1-deb_cv2 bowtie2 -x refs/lv -1 reads/reads_1.fastq -2 reads/reads_2.fastq -S lv_align.sam "2>align.summary.txt"

Обратите внимание, чтобы вывести логи выравнивания из канала STDERR (см. документацию Bowtei2) в файл align.summary.txt, потребовалось оградить запись 2>align.summary.txt двойными скобками, так как командная строка PowerShell не понимает знак “>”.

Мы можем убедиться, что выравнивание прошло успешно, открыв логи выравнивания, которые появились в файле C:\Docker_control\Bowtie2_example\align.summary.txt

1000 reads; of these:
  1000 (100.00%) were paired; of these:
    82 (8.20%) aligned concordantly 0 times
    918 (91.80%) aligned concordantly exactly 1 time
    0 (0.00%) aligned concordantly >1 times
    ----
    82 pairs aligned concordantly 0 times; of these:
      5 (6.10%) aligned discordantly 1 time
    ----
    77 pairs aligned 0 times concordantly or discordantly; of these:
      154 mates make up the pairs; of these:
        100 (64.94%) aligned 0 times
        54 (35.06%) aligned exactly 1 time
        0 (0.00%) aligned >1 times
95.00% overall alignment rate

Sam-файл с выравниванием располагается в по адресу C:\Docker_control\Bowtie2_example\lv-align.sam

Стоит обратить внимание, что то же самое можно сделать, запустив интерактивный терминал Linux внутри контейнера с программой bowtie2:

>docker run -it --rm -v //c/Docker_control/Bowtie2_example/:/data/ biocontainers/bowtie2:v2.3.4.3-1-deb_cv2

biodocker@f2e8661dab5d# cd /data/refs
biodocker@f2e8661dab5d# $bowtie2-build lambda_virus.fa lv
biodocker@f2e8661dab5d# $cd /data
biodocker@f2e8661dab5d# $bowtie2 -x refs/lv -1 reads/reads_1.fastq -2 reads/reads_2.fastq -S lv_align.sam 2>align.summary.txt
biodocker@f2e8661dab5d# exit

>

Последняя команда, введенная в терминал была exit. Она позволяет покинуть терминал в Linux-системе.

Создание собственных контейнеров и образов

Зачастую доступные в реестрах контейнеры имеют ограниченный функционал. Например, стандартный контейнер iocontainers/bowtie2, который был показан в предыдущем разделе, содержит только программу bowtie2, хотя анализ данных секвенирования не ограничивается выравниванием. В самом деле, после выравнивания sam-файлы требуется сортировать, конвертировать в формат bam и т.д. Для этого уже потребуется загрузить образ, содержащий программу samtools или еще больше. Так что число образов, с которыми придется работать может расти в геометрической прогрессии. Однако можно избежать размножения сущностей, если создать свой собственнй контейнер с необходимым функционалом.

В следующем примере мы создадим собственный контейнер. Полученный контейнер будет содержать программы fastqc, cutadapt, bowtie2, samtools и macs3, что теоретически позволит осуществлять полный цикл анализа экспериментов ChIP-seq от проверки качества секвенированных библиотек до построения геномных профилей ChIP-seq.

Перечисленные программы работают на ОС Linux, и для их работы нужны компоненты ядра данной операционной системы. Поэтому для создания своего контейнера мы просто возьмем образ этой ОС и установим внутри него все перечисленные программы. Удобнее всего это делать из терминала. Поэтому для начала загрузим контейнер с дистрибутивом Linux, например ubuntu в режиме терминала.

Чтобы не возникло проблем с загрузкой программ из интернета, нужно запустить командную строку в режиме администратора. Самый простой способ - нажать кнопку Пуск, в строке поиска ввести “PowerShell”, затем нажать правой кнопкой мыши на появившемся ярлыке и выбрать Запуск от имени администратора.

В окне терминала PowerShell запустим терминал ubuntu:

>docker run -it ubuntu

root@84fe65afe070:/#

Чтобы скачать программы в Ubuntu есть программа apt-get. После первого запуска контейнера ее нужно обновить.

# apt-get update

Теперь установим fastqc. Во время установки apt-get запросит рарешение на заполнение дискового пространства. Нужно ввести заглавную Y и нажать ввод, либо ввести параметр -y.

# apt-get install -y fastqc
#

Во время установки в контейнер были загружены десятки разных зависимостей, включая язык программирования Python, Perl, Java. Все это зависимости, без которых fastqc откажется работать.

Можем проверить, что fastqc работает:

# fastqc --version
FastQC v0.11.9

Таким же образом установим bowte2

# apt-get install -y bowtie2
# bowtie2 --version
/usr/bin/bowtie2-align-s version 2.4.4
64-bit
# apt-get install -y samtools
# apt-get install -y cutadapt

Для успешного запуска программ нужно убедиться, что бинарные исходники каждой программы лежат в директории, которая содержится в переменной $PATH. Если это не так, то нужно добавить расположение исходников в эту переменную, после чего нужно добавить команду export PATH=$PATH:путь_к_директории_с_исходником в файл .profile

Установка macs3 происходит через менеджер пакетов Python pip. Сначала его нужно установить.

# apt-get install pip
# pip --version
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

Все, можно установить macs3. Это займет некоторое время.

# pip install -y macs3
# macs3 --version
macs3 3.0.0b1

Теперь можно покинуть контенер.

# exit
>

Мы не дали контейнеру название. Узнать имя контейнера можно в Docker Desktop либо при помощи docker ps -a

> docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS                     PORTS     NAMES
c1db3012df97   ubuntu    "bash"    15 minutes ago   Exited (0) 7 seconds ago             stupefied_kilby

Далее мы можем сделать образ этого контейнера, чтобы впоследствии запускать его в виде отдельных команд. Делается это инструкцией docker commit. Синтаксис следующий:

docker commit [Параметры] название_контейнера [название_образа:тег]

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

> docker commit stupefied_kilby chipseq_image:latest
sha256:479c242b3d361024febdde132014b224c145dabd7b32f4cd6b9fe593eb85ccde

После завершения docker commit вывел идентификатор образа. Можем убедиться, что образ сохранился, заглянув в раздел Images программы Docker Desktop, либо с помощью команды docker image ls

> docker image ls
REPOSITORY      TAG       IMAGE ID       CREATED              SIZE
chipseq_image   latest    479c242b3d36   About a minute ago   1.35GB
ubuntu          latest    216c552ea5ba   12 days ago          77.8MB

Стоит обратить внимание, что полученный таким образом образ тяжелее в 20 раз, чем изначальный образ ubuntu. Попробуем запустить контейнер для проверки:

> docker run --rm chipseq_image bowtie2 --version

/usr/bin/bowtie2-align-s version 2.4.4
64-bit

> docker run --rm chipseq_image macs3

usage: macs3 [-h] [--version]
             {callpeak,bdgpeakcall,bdgbroadcall,bdgcmp,bdgopt,cmbreps,bdgdiff,filterdup,predictd,pileup,randsample,refinepeak,callvar,hmmratac}
             ...
macs3: error: the following arguments are required: subcommand

>

Создание образов при помощи Dockerfile

Создание образов напрямую из контейнера является достаточно простой процедурой. Однако автоматизировать этот процесс достаточно сложно, так как чтобы повторить эту процедуру на другом компьютере потребуется потратить ровно столько же времени (если не больше).

Для того, чтобы сократить процедуру создания образов, создатели Docker заработали собственный язык сценариев, которые позволяет скомпилировать контейнер для образа в один шаг, просто предоставив Docker текстовый файл с набором инструкций. Этот файл сценария называют Dockerfile, без расширения, преффиксов и суффиксов.

Здесь не будет обсуждаться сам язык сценариев для Dockerfile. Стоит сказать, что он интуитивно-понятный. Но вы можете обратиться к нескольким источникам, перечисленным в разделе Документация.

В предыдущем разделе был показан интерактивный способ создания контейнера. Ниже будет показан пример, как создать аналогичный контейнер (точнее обарз), при помощи Dockerfile.

  • Содержимое Dockerfile

Чтобы сделать Dockerfile просто откроем текстовый редактор и введем следующий текст

FROM ubuntu:latest

RUN apt-get -y update && apt-get install -y fastqc bowtie2 samtools cutadapt pip && pip install macs3

Сохраним Dockerfile в отдельной пустой папке. В моем случае это C:\Docker_control\DockerfileExample. Название текстового файла должно быть “Dockerfile” БЕЗ РАСШИРЕНИЯ

Обратите внимание, что после указателя FROM мы ввели название образа, который использовался для запуска контейнера ubuntu. После указателя RUN мы записали все команды, которые вводили в терминал для установки нужных программ. Между командами мы поставили двойной амперсанд (&&), как указание на то, что команды выполняются одна за другой последовательно. Мы также сократили число команд, воспользовавшись способностью apt-get install принимать название нескольких программ сразу.

В терминале, открытом с правами администратора, нужно войти в директорию, где лежит Dockerfile. Это делается при помощи команды cd.

> cd C:\Docker_control\DockerfileExample

Теперь запускаем команду docker build

> docker build -t chipseq_image_dockerfile .

Параметр -t указывает имя (обязательный аргумент) и тег (необязательный аргумент) образа, который мы создаем.

Не забудьте поставить точку в конце. Это указатель на “конекст” (context) - адрес директории или URL, где находятся файлы, необходимые для сборки образа. Обычно контекст - это содержимое директории, в которой лежит Dockerfile. Точка означает, что контекстом является рабочая директория, в которой запущен docker build.

Компиляция образа занимает несколько минут. После завершения мы можем убедиться, что образ существует:

>docker image ls

REPOSITORY                 TAG       IMAGE ID       CREATED         SIZE
chipseq_image_dockerfile   latest    e36113c4d94d   6 minutes ago   1.35GB
chipseq_image              latest    479c242b3d36   2 hours ago     1.35GB
ubuntu                     latest    216c552ea5ba   12 days ago     77.8MB

По сути мы создали клон образа chipseq_image. Преимуществом Dockerfile является возможность автоматизировать процесс создания образов. Кроме того, его удобно хранить и распространять. Вы можете копировать Dockerfile на другой компьютер, чтобы использовать для создания образа. Каждый созданный образ будет занимать много места на жестком диске, но вы можете безопасно удалить образ и восстановить его в будущем при помощи записанных вами инструкций.

Интеграция образов в реестры

Созданные вами образы можно хранить в реестре Docker Hub. Познакомиться с тем, как образы загружаются в реестр, можно по ссылкам ниже:

Видео урок

Мануал на русском языке

Документация от разработчиков

Можно также создавать персональный реестр на устройстве с достаточным количеством постоянно памяти. Иронично, что для этого вам всего лишь потребуется запустить на устройстве контейнер Docker Registry.

Создание персонального реестра

Еще одна статья про персональные реестры

Мануал от разработчиков

Литература

Милл Иан, Сейерс Эйдан Хобсон - Docker на практике (2020)

Эдриен Моуэт - Использование Docker (2017)

Документация

Официальная документация Docker

Русская версия документации Docker

Видеоуроки

Инструкция по установке на Windows с WSL2

Еще одна инструкция по установке на Windows с WSL2

Русскоязычное руководство по Dockerfile

Оригинальная документация по Dockerfile