company_banner

Что такое Docker: краткий экскурс в историю и основные абстракции

    10 августа в Слёрм стартовал видеокурс по Docker, в котором мы разбираем его полностью — от основных абстракций до параметров сети.


    В этой статье поговорим об истории появления Docker и его основных абстракциях: Image, Cli, Dockerfile. Лекция рассчитана на новичков, поэтому вряд ли будет интересна опытным пользователям. Здесь не будет крови, аппендикса и глубокого погружения. Самые основы.



    Что такое Docker


    Посмотрим на определение Docker из Википедии.


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

    Из этого определения ничего непонятно. Особенно непонятно, что значит «в средах с поддержкой контейнеризации». Чтобы разобраться, вернёмся в прошлое. Начнём с эпохи, которую я условно называю «Монолитной эрой».


    Монолитная эра


    Монолитная эра — это начало 2000-х, когда все приложения были монолитными, с кучей зависимостей. Разработка шла долго. При этом серверов было не так много, мы все их знали по именам и мониторили. Есть такое забавное сравнение:



    Pets — это домашние животные. В монолитной эре мы относились к своим серверам, как к домашним животным, холили и лелеяли, пылинки сдували. А для лучшего управления ресурсами использовали виртуализацию: брали сервер и пилили на несколько виртуальных машин, тем самым обеспечивая изоляцию окружения.


    Системы виртуализации на базе гипервизора


    Про системы виртуализации наверняка все слышали: VMware, VirtualBox, Hyper-V, Qemu KVM и т. д. Они обеспечивают изоляцию приложений и управление ресурсами, но у них есть и минусы. Чтобы сделать виртуализацию, нужен гипервизор. А гипервизор — это оверхед ресурсов. Да и сама виртуальная машина обычно целая махина — тяжелый образ, на нём операционная система, Nginx, Apache, возможно и MySQL. Образ большой, виртуальной машиной неудобно оперировать. Как следствие, работа с виртуалками может быть медленной. Чтобы решить эту проблему, создали системы виртуализации на уровне ядра.


    Системы виртуализации на уровне ядра


    Виртуализацию на уровне ядра поддерживают системы OpenVZ, Systemd-nspawn, LXC. Яркий пример такой виртуализации — LXC (Linux Containers).


    LXC — система виртуализации на уровне операционной системы для запуска нескольких изолированных экземпляров операционной системы Linux на одном узле. LXC не использует виртуальные машины, а создаёт виртуальное окружение с собственным пространством процессов и сетевым стеком.


    По сути LXC создаёт контейнеры. В чём разница между виртуальными машинами и контейнерами?



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


    Различия между виртуализацией и контейнеризацией можно увидеть на схеме.
    Бывают аппаратные гипервизоры, гипервизоры поверх ОС и контейнеры.



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


    Есть гипервизоры как программа, и есть контейнеры, о них мы и будем говорить дальше. В системах контейнеризации гипервизора нет, но есть Container Engine, который создаёт контейнеры и управляет ими. Штука это более легковесная, поэтому за счет работы с ядром оверхед меньше, или его нет совсем.


    Что используется для контейнеризации на уровне ядра



    Основные технологии, которые позволяют создавать изолированный от других процессов контейнер, — это Namespaces и Control Groups.

    Namespaces: PID, Networking, Mount и User. Есть ещё, но для простоты понимания остановимся на этих.

    PID Namespace ограничивает процессы. Когда мы, например, создаём PID Namespace, помещаем туда процесс, то он становится с PID 1. Обычно в системах PID 1 — это systemd или init. Соответственно, когда мы помещаем процесс в новый namespace, он тоже получает PID 1.

    Networking Namespace позволяет ограничить/изолировать сеть и внутри уже размещать свои интерфейсы. Mount — это ограничение по файловой системе. User — ограничение по юзерам.

    Control Groups: Memory, CPU, IOPS, Network — всего около 12 настроек. Иначе их ещё называют Cgroups («Cи-группы»).

    Control Groups управляют ресурсами для контейнера. Через Control Groups мы можем сказать, что контейнер не должен потреблять больше какого-то количества ресурсов.

    Чтобы контейнеризация полноценно работала, используются дополнительные технологии: Capabilities, Copy-on-write и другие.

    Capabilities — это когда мы говорим процессу, что он может делать, а чего не может. На уровне ядра это просто битовые карты со множеством параметров. Например, пользователь root имеет полные привилегии, может делать всё. Сервер времени может изменять системное время: у него есть capabilities на Time Capsule, и всё. С помощью привилегий можно гибко настроить ограничения для процессов, и тем самым обезопасить себя.

    Система Copy-on-write позволяет нам работать с образами Docker, использовать их более эффективно.

    На данный момент Docker имеет проблемы с совместимостью Cgroups v2, поэтому в статье рассматриваются именно Cgroups v1.

    Но вернёмся к истории.


    Когда появились системы виртуализации на уровне ядра, их начали активно применять. Оверхед на гипервизор пропал, но некоторые проблемы остались:


    • большие образы: в ту же OpenVZ толкают операционку, библиотеки, кучу разного софта, и в итоге образ всё равно получается немаленьким;
    • нет нормального стандарта упаковки и доставки, поэтому остаётся проблема зависимостей. Бывают ситуации, когда два куска кода используют одну библиотеку, но с разными версиями. Между ними возможен конфликт.

    Чтобы все эти проблемы решить, пришла следующая эра.


    Эра контейнеров


    Когда наступила Эра контейнеров, сменилась философия работы с ними:


    • Один процесс — один контейнер.
    • Все нужные процессу зависимости доставляем в его контейнер. Это требует распиливать монолиты на микросервисы.
    • Чем меньше образ, тем лучше — меньше возможных уязвимостей, быстрее раскатывается и так далее.
    • Инстансы становятся эфемерными.

    Помните, я говорил про pets vs cattle? Раньше инстансы были подобны домашним животным, а теперь стали как cattle — скот. Раньше был монолит — одно приложение. Теперь это 100 микросервисов, 100 контейнеров. У каких-то контейнеров может быть по 2-3 реплики. Нам становится не столь важно контролировать каждый контейнер. Нам скорее важна доступность самого сервиса: того, что делает этот набор контейнеров. Это меняет подходы в мониторинге.


    В 2014-2015 годах случился расцвет Docker — той технологии, о которой мы и будем сейчас говорить.


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


    В Docker-контейнер мы закладываем всё необходимое, поэтому решается проблема зависимостей. Docker гарантирует воспроизводимость. Я думаю, многие сталкивались с невоспроизводимостью: у тебя всё работает, пушишь на продакшен, там это перестает работать. С Docker эта проблема уходит. Если твой Docker-контейнер запускается и делает то, что требуется делать, то с большой долей вероятности он запустится на продакшене и там сделает то же самое.


    Отступление про оверхед



    По поводу оверхед постоянно идут споры. Кто-то считает, что Docker не несёт дополнительную нагрузку, так как использует ядро Linux и все его процессы, необходимые для контейнеризации. Мол, «если вы говорите, что Docker — это оверхед, то тогда и ядро Linux оверхед».

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

    Первое — это PID namespace. Когда мы в namespace помещаем какой-то процесс, ему присваивается PID 1. В то же время у этого процесса есть ещё один PID, который находится на хостовом namespace, за пределами контейнера. Например, мы запустили в контейнере Nginx, он стал PID 1 (мастер-процесс). А на хосте у него PID 12623. И сложно сказать, насколько это оверхед.

    Вторая штука — это Cgroups. Возьмём Cgroups по памяти, то есть возможность ограничивать контейнеру память. При её включении активируются счётчики, memory accounting: ядру надо понимать, сколько страниц выделено, а сколько ещё свободно для этого контейнера. Это возможно оверхед, но точных исследований о том, как он влияет на производительность, я не встречал. И сам не замечал, что приложение, запущенное в Docker, вдруг резко теряло в производительности.

    И ещё одно замечание о производительности. Некоторые параметры ядра прокидываются с хоста в контейнер. В частности, некоторые сетевые параметры. Поэтому если вы хотите запустить в Docker что-то высокопроизводительное, например то, что будет активно использовать сеть, то вам, как минимум, надо эти параметры подправить. Какой-нибудь nf_conntrack, к примеру.

    О концепции Docker


    Docker состоит из нескольких компонентов:


    1. Docker Daemon — то самое Container Engine; запускает контейнеры.
    2. Docker CLI — утилита по управлению Docker.
    3. Dockerfile — инструкция по тому, как собирать образ.
    4. Image — образ, из которого раскатывается контейнер.
    5. Container.
    6. Docker registry — хранилище образов.

    Схематично это выглядит примерно вот так:



    На Docker_host работает Docker daemon, запускает контейнеры. Есть Client, который передаёт команды: собери образ, скачай образ, запусти контейнер. Docker daemon ходит в registry и выполняет их. Docker-клиент может обращаться и локально (к юникс-сокету), и по TCP с удалённого хоста.


    Пройдёмся по каждому компоненту.


    Docker daemon (демон) — это серверная часть, она работает на хост-машине: скачивает образы и запускает из них контейнеры, создаёт сеть между контейнерами, собирает логи. Когда мы говорим «создай образ», этим тоже занимается демон.


    Docker CLI — клиентская часть Docker, консольная утилита для работы с демоном. Повторю, она может работать не только локально, но и по сети.


    Базовые команды:


    docker ps — показать контейнеры, которые сейчас запущены на Docker-хосте.
    docker images — показать образы, скачанные локально.
    docker search <> — поиск образа в registry.
    docker pull <> — скачать образ из registry на машину.
    docker build <</path/to/dir>> — собрать образ.
    docker run <> — запуск контейнер.
    docker rm <> — удалить контейнер.
    docker logs <> — логи контейнера
    docker start/stop/restart <> — работа с контейнером

    Если вы освоите эти команды и будете уверенно ими пользоваться, то считайте, что на 70% освоили Docker на уровне пользователя.


    Dockerfile — инструкция для создания образа. Почти каждая команда инструкции — новый слой. Посмотрим на примере.



    Примерно так выглядит Dockerfile: слева команды, справа — аргументы. Каждая команда, что здесь есть (и вообще пишется в Dockerfile), создаёт новый слой в Image.


    Даже глядя на левую часть, можно примерно понять, что происходит. Мы говорим: «создай нам папку» — это один слой. «Сделай папку рабочей» — это ещё один слой, и так далее. Слоёный пирог упрощает жизнь. Если я создам ещё один Dockerfile и в последней строчке что-то изменю — запущу не "python" "main.py", а что-нибудь другое, или установлю зависимости из другого файла — то предыдущие слои будут переиспользованы, как кеш.


    Image — это упаковка контейнера, из образа запускаются контейнеры. Если смотреть на Docker с точки зрения пакетного менеджера (как будто мы работаем с deb или rpm-пакетами), то image — это по сути rpm-пакет. Через yum install мы можем поставить приложение, удалить его, найти в репозитории, скачать. Здесь примерно то же самое: из образа запускаются контейнеры, они хранятся в Docker registry (по аналогии с yum, в репозитории), и каждый image имеет хеш SHA-256, имя и тег.


    Image собирается по инструкции из Dockerfile. Каждая инструкция из Dockerfile создаёт новый слой. Слои могут использоваться повторно.


    Docker registry — это репозиторий образов Docker. По аналогии с ОС, у Docker есть общедоступный стандартный реестр — dockerhub. Но можно собрать свой репозиторий, свой Docker registry.


    Container — то, что запускается из образа. По инструкции из Dockerfile собрали образ, затем мы его из этого образа запускаем. Этот контейнер изолирован от остальных контейнеров, он должен содержать в себе всё необходимое для работы приложения. При этом один контейнер — один процесс. Случается, что приходится делать два процесса, но это несколько противоречит идеологии Docker.


    Требование «один контейнер — один процесс» связано с PID Namespace. Когда в Namespace запускается процесс с PID 1, если он вдруг умрёт, то весь контейнер тоже умирает. Если же там запущено два процесса: один живёт, а второй умер, то контейнер всё равно продолжит жить. Но это к вопросу Best Practices, мы про них поговорим в других материалах.


    Более детально изучить особенности и полную программу курса можно по ссылке: «Видеокурс по Docker».


    Автор: Марсель Ибраев, сертифицированный администратор Kubernetes, практикующий инженер в компании Southbridge, спикер и разработчик курсов Слёрм.

    Southbridge
    Обеспечиваем стабильную работу highload-проектов

    Комментарии 9

      0
      «А гипервизор — это оверхед ресурсов.»
      что? даже виндовс 10 с версии 2004 теперь работает поверх гипервизора, замедлений не заметил.
        0
        Чем Hosted Hypervisor отличается от Bare Metal hypervisor?
          0

          Если это не шутка :), то на пальцах:
          Hosted Hypervisor устанавливается в операционную систему. Н-р: VMware Workstation установленный в OS Windows. И уже в нем запускаются виртуальные машины.
          Bare Metal Hypervisor устанавливается на "голое железо". Н-р: VMware ESXi установленный на сервер.

            0
            Если это не шутка, то Bare Metal Hypervisor тоже не висит в воздухе и работает под управлением какой-либо ОС.
              0

              По факту Bare Metal Hypervisor это и есть OS :). Если хотите пойти в частности, то обычно это обрезанная до функционала работы с виртуальным машинами OS. Большинство "лишних" вещей дающих дополнительный оверхед по управлению и производительности вырезаны, в отличии от полноценной OS и установленного в нее Hosted Hypervisor.

                0
                По вашей логике такими свойствами обладает только всфера с её васянским дистрибутивом. Вы вводите какое-то сомнительное новое понятие лишь опираясь на своё ощущение «лишних» вещей, что по сути сосвсем не соотвествует инфографике представленой в статье.
                  0

                  Логика к сожалению не моя, а общепринятая в отрасли. Продукты VMware — это лишь как наглядный пример. Можете почитать англоязычные ресурсы и описание определений Bare Metal Hypervisor и Hosted Hypervisor.


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

          0
          А у вас на курсе текстовые материалы есть или только видео? Спасибо.
            0
            Добрый день! Теория дается в видеоформате, практика описана текстом и выполняется на предоставляемых нами стендах

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

          Самое читаемое