Если вы являетесь программистом или техником, то вы наверняка хотя бы слышали о Docker - полезном инструменте для упаковки, отправки и запуска приложений в «контейнерах». Трудно не знать об этой вещи, ведь в последнее время она получает много внимания от всех, начиная от разработчиков и заканчивая сисадминами. Даже такие крупные шишки, как Google, VMware и Amazon создают сервисы, поддерживающие Docker.
Независимо от того, использовали ли вы уже Docker или нет, я думаю, важно понять некоторые фундаментальные концепции того, что такое «контейнер», и сопоставить его с виртуальной машиной (VM). В то время как Интернет полон отличных руководств по использованию Docker, я не смог найти понятных для начинающих руководств о концепции, особенно о том, из чего состоит контейнер. Итак, надеюсь, этот пост решит эту проблему!
Давайте начнем с понимания того, что такое виртуальные машины и контейнеры.
Что такое «контейнеры» и «VM»?
Контейнеры и виртуальные машины схожи по своим целям. Они должны изолировать приложение и его зависимости в автономную единицу, которая может работать в любом месте.
Кроме того, контейнеры и виртуальные машины устраняют необходимость в физическом оборудовании, что позволяет более эффективно использовать вычислительные ресурсы с точки зрения как потребления энергии, так и затрат.
Основное различие между контейнерами и виртуальными машинами заключается в их архитектуре. Давайте рассмотрим этот аспект подробнее.
Виртуальные машины
Виртуальная машина, по сути, является эмуляцией реального компьютера, которая выполняет те же программы, что и настоящий компьютер. Виртуальные машины запускаются на физической машине с использованием «гипервизора». Гипервизор, в свою очередь, работает либо на машине-хосте, либо на чистом сервере.
Давайте разберемся с жаргоном:
Гипервизор - это часть ПО, прошивки или аппаратного обеспечения, на которой работают виртуальные машины. Гипервизоры запускаются на физических компьютерах, называемых «хост-машинами». Хост-машина обеспечивает виртуальные машины ресурсами, включая оперативную память и процессор. Эти ресурсы делятся между виртуальными машинами и могут быть распределены по вашему усмотрению. Если на одной виртуальной машине работает более ресурсоемкое приложение, вы можете выделить больше ресурсов для нее, чем для других виртуальных машин, запущенных на одном и том же хосте.
Виртуальная машина, работающая на хосте (опять же, с использованием гипервизора), также часто называется «гостевой машиной». Этот гостевой компьютер содержит как приложение, так и все, что ему нужно для его запуска (например, системные двоичные файлы и библиотеки). Он также включает в себя весь собственный виртуализированный аппаратный стек (в него входят виртуализированные сетевые адаптеры, хранилище и процессор). Это означает, что у него также есть своя полноценная гостевая операционная система. Изнутри гостевая машина ведет себя как отдельное устройство с собственными ресурсами. С точки зрения извне, мы знаем, что это VM-ресурсы совместного доступа, предоставляемые хост-машиной.
Как упоминалось выше, гостевая машина может работать либо на гипервизоре в хосте, либо на гипервизоре на чистом сервере. Между ними есть некоторые важные различия.
Во-первых, хостовый гипервизор виртуализации работает на операционной системе хост-машины. Например, компьютер с OSX может иметь виртуальную машину (например, VirtualBox или VMware Workstation 8), установленную «поверх» этой ОС. У VM нет прямого доступа к аппаратным средствам, поэтому она должна проходить через операционную систему хоста (в нашем случае - Mac OSX).
Преимущество хостового гипервизора заключается в том, что базовое оборудование здесь имеет меньшую важность. Операционная система хоста отвечает за аппаратные драйверы вместо самого гипервизора и поэтому считается более «совместимой с оборудованием». С другой стороны, этот дополнительный уровень между аппаратным обеспечением и гипервизором требует больше ресурсов, что снижает производительность виртуальной машины.
Гипервизорная среда на чистом сервере устраняет проблему производительности, так как установка и запуск проходит с аппаратного обеспечения хост-машины. Поскольку взаимодействие происходит напрямую с базовым оборудованием, для его работы не требуется ОС хоста. В этом случае сначала на сервере хост-машины в качестве ОС нужно установить гипервизор. В отличие от хостового гипервизора, гипервизор чистого сервера имеет собственные драйверы устройств и взаимодействует с каждым компонентом напрямую для любых задач ввода-вывода, обработки или специфических команд. Это приводит к повышению производительности, масштабируемости и стабильности. Компромисс заключается в том, что аппаратная совместимость ограничена, потому что в гипервизор может быть встроено ограниченное количество драйверов устройств.
После этого разговора о гипервизорах вам могло стать интересно, почему нам нужен этот дополнительный слой гипервизора между виртуальной машиной и машиной-хостом.
Ну, поскольку VM имеет собственную виртуальную ОС, гипервизор играет важную роль в обеспечении виртуальных машин платформой для управления и выполнения этой гостевой операционной системы. Он позволяет хост-компьютерам делиться своими ресурсами с виртуальными машинами, которые работают в качестве гостей поверх них.
Как вы можете видеть на диаграмме, каждая виртуальная машина содержит виртуальное оборудование, ядро (т. е. ОС) и пользовательское пространство.
Контейнеры
Виртуальные машины обеспечивают аппаратную виртуализацию. В отличие от них, контейнеры обеспечивают виртуализацию на уровне операционной системы путем абстрагирования пользовательского пространства. Вы поймете, что я имею в виду, когда мы разберемся с самим термином.
С точки зрения всех целей и задач контейнеры ничем не отличаются от виртуальных машин. Например, они могут иметь личное пространство для обработки, выполнять команды, такие как root, иметь частный сетевой интерфейс и IP-адрес, разрешать кастомные маршруты и правила iptable, монтировать файловые системы и т. д.
Единственное крупное различие между контейнерами и виртуальными машинами заключается в том, что контейнеры используют ядро хост-системы совместно с другими контейнерами.
На этой диаграмме показано, что контейнеры содержат только пользовательское пространство, а не ядро или виртуальное оборудование, как в случае с VM. Каждый контейнер Docker получает свое изолированное пользовательское пространство, позволяющее нескольким контейнерам запускаться на одном хосте. Мы видим, что вся архитектура ОС делится между контейнерами. С нуля создаются только файлы bin, lib. Именно поэтому контейнеры такие легкие.
Когда же в дело вступает Docker?
Docker - проект с открытым исходным кодом, основанный на контейнерах Linux. Он использует функции ядра Linux, такие как пространства имен и контрольные группы, для создания контейнеров поверх ОС.
Контейнеры - это не новое изобретение. Google использует собственные технологии контейнеров уже многие годы. Другие технологии контейнеров на Linux включают в себя Solaris Zones, BSD jails и LXC, которые существуют уже много лет.
Так почему же Docker внезапно набирает обороты?
Простота использования: Docker упростил для всех (разработчиков, системных администраторов, архитекторов и других) использование контейнеров для быстрой сборки и тестирования портативных приложений. Это позволяет кому угодно передавать приложение на свой ноутбук. Такое приложение, в свою очередь, может работать без изменений в любом общедоступном облаке, в частном облаке или даже на чистом сервере. Девиз таков: «Создайте один раз, запускайте откуда угодно»;
Скорость: Контейнеры в Docker очень легкие и быстрые. Поскольку контейнеры - это помещенные в «песочницу» среды, работающие на ядре, они потребляют меньше ресурсов. На создание и запуск Docker контейнеров тратятся мгновения по сравнению с VM. Работа с ними может занять больше времени, потому что каждый раз они должны загружать полную виртуальную ОС;
Docker Hub: Пользователи Docker также получают выгоду от все больше богатеющей экосистемы Docker Hub, которую вы можете назвать «магазином приложений для образов Docker». Docker Hub содержит десятки тысяч общедоступных образов, созданных сообществом. Они легко доступны для использования. Теперь невероятно легко найти образы, которые отвечают вашим потребностям. Их уже можно взять и использовать практически без изменений;
Модульность и масштабируемость: Docker позволяет легко разбить функциональность вашего приложения на отдельные контейнеры. Например, ваша база данных Postgres может работать в одном контейнере, а сервер Redis - в другом, приложение Node.js - в третьем. С Docker стало проще связать эти контейнеры вместе для создания приложения. В будущем это упростит масштабирование или обновление компонентов независимо друг от друга.
И наконец: кто же не любит кита Docker?
Фундаментальные Docker концепции
Раз уж мы разобрались с основной картиной, можно перейти к фундаментальным частям Docker.
Docker Engine
Docker Engine - это слой, на котором работает Docker. Это легкая среда и набор инструментов, которые позволяют осуществлять управление контейнерами, образами, версиями и многим другим. По умолчанию эта часть работает на системах Linux и состоит из:
- Docker Daemon (работает на хосте);
- Docker Client (связывается с Docker Daemon, чтобы осуществлять команды);
- REST API (для удаленного взаимодействия с Docker Daemon).
Docker Client
Клиент Docker - то, с чем вы, конечный пользователь Docker, взаимодействуете. Вы можете представить его как интерфейс Docker. Например, когда вы задаете:
docker build iampeekay/someImage .
Вы связываетесь с клиентом Docker, который впоследствии передает ваши инструкции Docker Daemon.
Docker Daemon
Демон Docker то, что отвечает за выполнение команд (создание, запуск Docker контейнеров, их распределение), отправленных клиенту Docker. Демон работает на машине-хосте, однако пользователь никогда не взаимодействует с ним напрямую. Клиент Docker тоже может работать на хосте, но необязательно. Он может работать на другой машине и связываться с демоном, который запущен на хосте.
Dockerfile
Dockerfile - то, где вы пишете инструкции для создания образов Docker. Эти инструкции могут быть таковы:
- Для установки пакета ПО: RUN apt-get y install some-package;
- Для раскрытия порта: EXPOSE 8000;
- Для передачи переменной среды: ENV ANT_HOME /usr/local/apache-ant.
Бывают и другие инструкции. Как только вы запустите Dockerfile, то сможете использовать команду docker build
для создания образа. Вот пример Dockerfile:
# Start with ubuntu 14.04
FROM ubuntu:14.04
MAINTAINER preethi kasireddy iam.preethi.k@gmail.com
# For SSH access and port redirection
ENV ROOTPASSWORD sample
# Turn off prompts during installations
ENV DEBIAN_FRONTEND noninteractive
RUN echo "debconf shared/accepted-oracle-license-v1-1 select true" | debconf-set-selections
RUN echo "debconf shared/accepted-oracle-license-v1-1 seen true" | debconf-set-selections
# Update packages
RUN apt-get -y update
# Install system tools / libraries
RUN apt-get -y install python3-software-properties \
software-properties-common \
bzip2 \
ssh \
net-tools \
vim \
curl \
expect \
git \
nano \
wget \
build-essential \
dialog \
make \
build-essential \
checkinstall \
bridge-utils \
virt-viewer \
python-pip \
python-setuptools \
python-dev
# Install Node, npm
RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
RUN apt-get install -y nodejs
# Add oracle-jdk7 to repositories
RUN add-apt-repository ppa:webupd8team/java
# Make sure the package repository is up to date
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
# Update apt
RUN apt-get -y update
# Install oracle-jdk7
RUN apt-get -y install oracle-java7-installer
# Export JAVA_HOME variable
ENV JAVA_HOME /usr/lib/jvm/java-7-oracle
# Run sshd
RUN apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo "root:$ROOTPASSWORD" | chpasswd
RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
# Expose Node.js app port
EXPOSE 8000
# Create tap-to-android app directory
RUN mkdir -p /usr/src/my-app
WORKDIR /usr/src/my-app
# Install app dependencies
COPY . /usr/src/my-app
RUN npm install
# Add entrypoint
ADD entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "start"]
Docker Image
Образы - это шаблоны, доступные только для чтения. Они создаются из набора инструкций, написанных в Dockerfile. Образы определяют 2 вещи: структуру вашего приложения и его зависимостей и процессы, которые будут происходить при его запуске.
Образы Docker создаются с помощью Dockerfile. Каждая инструкция в Dockerfile добавляет к образу новый «слой». Слои представляют собой часть файловой системы, которая либо дополняет слой под собой, либо заменяет его. Слои - ключ к легкой, но мощной структуре Docker. Для этого Docker применяет Union FS.
Union File System
Docker применяет Union FS для создания образов. Думайте о Union FS как о составной файловой системе. Это значит, что файлы и директории отдельных систем (их называют ветками) могут без перекодировки сформировать единую систему.
Содержимое директорий, которые имеют одинаковые пути в ветках, представляется в виде объединенного директория. Из-за этого не требуется создавать копии каждого слоя. Вместо этого они получат указатели к одному и тому же ресурсу. Когда вам понадобится изменить определенные слои, будет создана копия. Изменения будут происходить на копии, а оригинал останется нетронутым. Поэтому вам может казаться, что файловые системы можно переписать, хотя на самом деле они этого не позволяют. (Другими словами, это система «копирование при записи».)
Многоуровневые системы имеют 2 главных преимущества:
- Свободное копирование: слои помогают избежать необходимости дублировать полный набора файлов каждый раз, когда вы используете образ для создания и запуска нового контейнера. Это делает создание контейнеров в Docker очень быстрым и «дешевым»;
- Разделение слоев: внесение изменений происходит намного быстрее - при изменении образа Docker распространяет обновления только на слой, который был изменен.
Volume
Volume - это часть контейнера, ответственная за данные. Ее инициализация происходит при создании контейнера. Volume позволяют хранить и передавать данные контейнера. Volume отделены от обычной Union FS. Они существуют в виде обычных директорий и файлов в хостовой системе файлов. Даже если вы разрушите, обновите или рекомпилируете контейнер Docker, данные Volume останутся нетронутыми. Если вы хотите обновить Volume, нужно изменять его напрямую. (Дополнительное преимущество: данные в Volume можно распределять и использовать в нескольких контейнерах, что достаточно удобно.)
Контейнеры Docker
Контейнер Docker, как говорилось выше, упаковывает ПО приложения в невидимую «коробку», в которой есть все, что необходимо для запуска приложения. Туда входят ОС, код приложения, среда, инструменты, библиотеки и т. д. Контейнеры Docker создаются из образов. Так как образы доступны только для чтения, для создания контейнера Docker добавляет файловую систему «чтение/запись» поверх системы «read-only».
Более того, после создания контейнера Docker добавляет сетевой интерфейс (он позволяет контейнеру общаться с локальным хостом), присоединяет доступный IP-адрес к контейнеру и выполняет процесс, который вы указали для запуска приложения при определении образа.
После успешного создания контейнера можно запустить его в любой среде без необходимости вносить изменения.
Применение двойного клика к контейнерам
Фух! Как много подвижных частей. Меня всегда интересовало: а как контейнеры вообще создаются? Вокруг них ведь нет какой-то абстрактной инфраструктуры. Я много читал, и теперь все стало ясно. Я попытаюсь объяснить это вам!
Термин «контейнер» на самом деле является просто абстрактным понятием, описывающим, как несколько различных функций работают вместе для визуализации «контейнера». Давайте очень быстро пробежимся по ним:
1) Пространства имен
Пространства имен предоставляют контейнерам свое собственное представление о базовой системе Linux. Это ограничивает то, что контейнер может видеть и к чему он может получать доступ. Когда вы запускаете контейнер, Docker создает пространства имен, которые будет использовать конкретный контейнер.
В ядре используется несколько различных типов пространств имен, например:
a. NET: Предоставляет контейнер с собственным представлением сетевого стека системы (например, собственные сетевые устройства, IP-адреса, таблицы IP-маршрутизации, каталог /proc/net, номера портов и т. д.);
b. PID: PID обозначает идентификатор процесса. Если вы когда-нибудь запускали ps aux
в командной строке, чтобы проверить, какие процессы запущены в вашей системе, вы видели столбец с названием «PID». Пространство имен PID дает контейнерам свое собственное видение процессов, которые они могут просматривать и с которыми способны взаимодействовать, включая независимый init (PID 1), который является «предком всех процессов»;
c. MNT: дает контейнеру собственное представление о «монтировании» в системе. Таким образом, процессы в разных пространствах имеют разные представления об иерархии файловой системы;
d. UTS: UTS обозначает UNIX Timesharing System. Это позволяет процессу идентифицировать системные идентификаторы (т. е. имя хоста, имя домена и т. д.). UTS позволяет контейнерам иметь свое собственное имя хоста и имя домена NIS, которое не зависит от других контейнеров и хост-системы;
e. IPC: IPC расшифровывается как InterProcess Communication. Пространство имен IPC отвечает за изоляцию ресурсов IPC между процессами, выполняющимися внутри каждого контейнера;
f. USER: это пространство имен используется для изоляции пользователей в каждом контейнере. Он функционирует, позволяя контейнерам иметь другое представление диапазонов uid (идентификатор пользователя) и gid (идентификатор группы) по сравнению с хост-системой. В результате uid и gid процесса могут различаться внутри и снаружи пространства имен пользователя. Это позволяет процессу иметь непривилегированного пользователя вне контейнера, не жертвуя привилегиями root внутри него.
Docker использует эти пространства имен вместе, чтобы изолировать и начать создание контейнера. Следующий элемент - контрольные группы.
2) Контрольные группы
Контрольные группы (также называются cgroups) - это функция ядра Linux, которая изолирует, расставляет приоритеты и учитывает использование ресурсов (ЦП, память, дисковый ввод-вывод, сеть и т. д.) для набора процессов. Cgroup гарантирует, что контейнеры Docker используют только те ресурсы, которые им необходимы, и при необходимости устанавливает ограничения на то, какие ресурсы контейнер может использовать. Cgroups также гарантирует, что единственный контейнер не исчерпает один из этих ресурсов и не разрушит всю систему.
Наконец, Docker также использует Union FS.
3) Изолированная Union FS
Описана выше в разделе «Docker Image».
Вот и все, что существует в контейнере Docker. Конечно, все трудности начинаются в деталях осуществления: например, как управлять взаимодействиями между различными компонентами?
Будущее Docker: сосуществование Docker и VM
Хотя Docker определенно набирает обороты, я не верю, что он станет реальной угрозой для VM. Контейнеры будут продолжать развиваться, но существует много случаев, когда лучше применять виртуальные машины.
Например, если вам нужно запустить несколько приложений на нескольких серверах, возможно, имеет смысл использовать виртуальные машины. С другой стороны, если вам нужно запустить много копий одного приложения, Docker предлагает некоторые неоспоримые преимущества.
Более того, контейнеры позволяют разбить ваше приложение на более функциональные отдельные части, чтобы создать разделение задач. Это также означает, что появится большое число частей для управления, и они могут быть громоздкими.
Безопасность также вызывает беспокойство в случае контейнеров Docker. Поскольку контейнеры используют одно и то же ядро, барьер между ними становится меньше. В то время как полная виртуальная машина может выдавать только гипервызовы гипервизору хоста, контейнер Docker может делать системные вызовы к ядру хоста, что создает большую площадь поверхности для атаки. Когда безопасность особенно важна, разработчики могут выбирать виртуальные машины. Они изолированы абстрактным оборудованием. Это значительно затрудняет их взаимодействие друг с другом.
Конечно, такие вопросы, как безопасность и управление, будут развиваться по мере того, как контейнеры будут получать больше внимания при производстве и будут подвергнуты дополнительному контролю со стороны пользователей. Пока что оставим дебаты о контейнерах и виртуальных машинах разработчикам, которые живут ими и дышат ими каждый день!
Заключение
Надеюсь, теперь вы вооружились знаниями и пониманием того, что вам нужно больше узнать о Docker и, быть может, когда-нибудь применить его в проекте.