Привет, Хабр! Представляю вашему вниманию перевод статьи Deep dive into Windows Server Containers and Docker – Part 2 – Underlying implementation of Windows Server Containers от автора Cornell Knulst.


В данной статье рассказывается об особенностях реализации Docker в Windows, а также об отличиях в реализации контейнеров между Windows и Linux.


Перед этим даётся общее представление, что такое контейнеры, чем они похожи и чем отличаются от виртуальных машин.


Вступление


Представив 3 августа 2015 года Windows Server 2016 Technical Preview 3, Microsoft внедрила технологию контейнеров в платформу Windows. В то время, как на Linux технология контейнеров появилась в августе 2008 года, подобная функциональность прежде не поддерживалась операционными системами Microsoft. Благодаря успеху Docker на Linux, Microsoft решила почти 3 года назад (оригинальная статья опубликована 6 мая 2017 — прим. перев.) начать работу над реализацией контейнеров для Windows. С сентября 2016 года мы смогли поработать с публично доступной версией этой новой технологии контейнеров в Windows Server 2016 и Windows 10. Но в чём разница между контейнерами и виртуальными машинами? И как внутренне реализованы контейнеры Windows? В этой статье мы погрузимся в реализацию контейнеров на Windows.


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


Часто с контейнерами начинают знакомить с фразы “Контейнеры — это облегчённые виртуальные машины”. Хотя это может помочь людям дать фундаментальное понимание, что такое контейнеры, важно отметить, что это утверждение на 100% ошибочно и может сильно запутать. Контейнеры отличаются от виртуальных машин, и поэтому я всегда представляю контейнеры как “что-то, отличное от виртуальных машин” или даже говорю “контейнеры — это НЕ виртуальные машины”. Но в чём же разница? И почему она так важна?


Что общего между контейнерами и виртуальными машинами


Хотя контейнеры НЕ виртуальные машины, у них обоих есть три важные характеристики:



(Изображение Albund | Dreamstime.com)


Что общего между контейнерами и виртуальными машинами:


  • Изолированное окружение: как и виртуальные машины, контейнеры гарантируют изоляцию файловой системы, переменных окружения, реестра и процессов между приложениями. Это значит, что, как и виртуальная машина, каждый контейнер создаёт изолированное окружение для всех приложений внутри себя. При миграции и контейнеры, и виртуальные машины сохраняют не только приложения внутри, но также и контекст этих приложений.
  • Миграция между хостами: большое преимущество работы с виртуальными машинами в том, что можно перемещать слепки виртуальных машин между гипервизорами, при этом не нужно изменять их содержимое. Это справедливо и для контейнеров. Там, где виртуальные машины можно “перемещать” между разными гипервизорами, контейнеры можно “перемещать” между разными хостами контейнеров. При “перемещении” обоих видов артефактов между разными хостами содержимое виртуальной машины/контейнера остаётся точно таким же, как и на предыдущих хостах.
  • Управление ресурсами: другая общая черта — это то, что доступные ресурсы (ЦП, ОЗУ, пропускная способность сети) как контейнеров, так и виртуальных машин могут быть ограничены до заданных значений. В обоих случаях это управление ресурсами может осуществляться только на стороне хоста контейнера или гипервизора. Управление ресурсами гарантирует, что контейнер получает ограниченные ресурсы, чтобы свести к минимуму риск того, что он повлияет на производительность других контейнеров, запущенных на том же самом хосте. К примеру, контейнеру можно задать ограничение, что он не может использовать больше 10% ЦП.

Разница между контейнерами и виртуальными машинами


Хотя между контейнерами и виртуальными машинами есть общие черты, также между ними есть некоторые важные различия.



Разница между контейнерами и виртуальными машинами:


  • Уровень виртуализации: контейнеры — это новый уровень виртуализации. Если взглянуть на историю виртуализации, то она началась с таких понятий, как виртуальная память и виртуальные машины. Контейнеры — это новый уровень этой тенденции виртуализации. Там, где виртуальные машины обеспечивают виртуализацию аппаратного обеспечения, контейнеры обеспечивают виртуализацию ОС. Это значит, что если виртуализация аппаратного обеспечения позволяет виртуальной машине верить, что её аппаратные ресурсы принадлежат только ей, виртуализация ОС позволяет контейнеру верить, что вся ОС принадлежит только ему. Важно отметить эту разницу в виртуализации. У контейнеров, к примеру, нет собственного режима ядра. По этой причине контейнеры не видны как виртуальные машины и они также не распознаются, как виртуальные машины внутри операционной системы (можете попробовать самостоятельно выполнить команду PowerShell Get-VM). Хорошая аналогия для того, чтобы объяснить эту разницу — это дома (виртуальные машины) и квартиры (контейнеры). Дома (виртуальные машины) полностью самодостаточны и предоставляют защиту от непрошенных гостей. У каждого дома также есть собственная инфраструктура — водопровод, отопление, электричество и т. д. Квартиры (контейнеры) тоже предоставляют защиту от непрошенных гостей, но они строятся на основе общей инфраструктуры. В многоквартирном доме (Docker Host) водопровод, отопление, электричество и т. д являются общими. Хотя у них обоих могут быть общие характеристики, это разные сущности.
  • Взаимодействие с ОС: другое важное отличие между контейнерами и виртуальными машинами заключается в способе, которым они взаимодействуют с режимом ядра. Тогда как у виртуальных машин есть полноценная ОС (и выделенный режим ядра), контейнеры разделяют “ОС (точнее, режим ядра)” с другими контейнерами и с хостом контейнеров. В результате контейнеры должны ориентироваться на ОС хоста контейнеров, в то время, как виртуальная машина может выбрать любую ОС (версию и тип), какую пожелает. Там, где виртуальные машины могут запускать ОС Linux на гипервизоре Windows, с технологией контейнеров невозможно запустить контейнер Linux на хосте контейнеров Windows, и наоборот. (В Windows 10 можно запускать контейнеры Linux, но они запускаются внутри виртуальной машины Linux — прим. перев.)
  • Модель роста: контейнеры разделяют ресурсы хоста контейнера, и создаются на основе образа, который содержит ровно то, что нужно для запуска вашего приложения. Вы начинаете с основ и добавляете то, что необходимо. Виртуальные машины создаются в обратном порядке. Чаще всего мы начинаем с полной операционной системы и, в зависимости от приложения, убираем вещи, которые не нужны.

Контейнеры Windows Server


Теперь, когда мы знаем о различиях между виртуальными машинами и контейнерами, давайте нырнём глубже в архитектуру контейнеров Windows Server. Чтобы объяснить, как контейнеры внутренне реализованы в операционной системе Windows, нужно знать о двух важных понятиях: режиме пользователя и режиме ядра. Это два разных режима, между которыми постоянно переключается процессор, в зависимости от типа кода, который нужно выполнить.


Режим ядра


Режим ядра операционной системы был создан для драйверов, которым нужен неограниченный доступ к аппаратному обеспечению. Обычным программам (режима пользователя) приходится вызывать API операционной системы, чтобы получить доступ к аппаратному обеспечению или памяти. У кода, выполняющегося в режиме ядра, есть прямой доступ к этим ресурсам, и он разделяет те же области памяти/виртуальное адресное пространство, что и операционная система и другие драйверы в ядре. Поэтому выполнять код в режиме ядра очень рискованно, так как данные, принадлежащие операционной системе или другому драйверу могут быть повреждены, если ваш код режима ядра случайно запишет данные по неверному виртуальному адресу. Если драйвер режима ядра падает, то падает вся операционная система. Поэтому в режиме ядра нужно выполнять как можно меньше кода. По этой самой причине был создан режим пользователя.


Режим пользователя


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


Техническая реализация контейнеров Windows


Но что всем этим режимам процессора делать с контейнерами? Каждый контейнер — это всего лишь “режим пользователя” процессора с парой дополнительных возможностей, таких, как изоляция пространства имён, управление ресурсами и понятием каскадно-объединённой файловой системы. Это значит, что Microsoft нужно было адаптировать операционную систему Windows, чтобы она позволяла поддерживать множественные режимы пользователя. Что-то, что было очень трудно сделать, принимая во внимание высокий уровень интеграции между обоими режимами в предыдущих версиях Windows.


До выпуска Windows Server 2016 каждая операционная система Windows, которой мы пользовались, состояла из единственного “режима пользователя” и “режима ядра”. С появлением Windows Server 2016 стало возможным иметь несколько режимов пользователя, выполняющихся в одной операционной системе. Следующая диаграмма даёт общее представление об этой новой архитектуре мульти-режимов пользователя.



Взглянув на режимы пользователя в Windows Server 2016, можно выявить два различных типа: режим пользователя хоста и режимы пользователя контейнера (зелёные блоки на диаграмме). Режим пользователя хоста идентичен обычному режиму пользователя, с которым мы знакомы по предыдущим версиям Windows. Цель этого режима пользователя — размещать основные службы Windows и процессы, такие, как Session Manager, Event Manager и сеть. Более того, этот режим пользователя облегчает, в случае реализации Windows Server Core, взаимодействие пользователя с Windows Server 2016 при помощи пользовательского интерфейса.


Новая возможность Windows Server 2016 заключается в том, что как только вы включили компонент “Контейнеры”, этот режим пользователя хоста будет содержать некоторые дополнительные технологии управления контейнерами, которые гарантируют, что контейнеры будут работать в Windows. Основа этой технологии контейнеров — абстракция Compute Services (оранжевый блок), которая даёт доступ через публичный API к низкоуровневым возможностям контейнера, предоставляемым ядром. На самом деле, эти службы содержат лишь функциональность, чтобы запускать контейнеры Windows, отслеживать, запущены ли они, и управлять функциональностью, необходимой для их перезапуска. Остальные функции управления контейнером, такие, как отслеживание всех контейнеров, хранение образов контейнеров, томов и т. д., реализованы в Docker Engine. Этот движок напрямую общается с API контейнеров Compute Services и использует для этого “биндинг языка Go”, предложенный Microsoft.


Разница между контейнерами Windows и Linux



Одинаковые утилиты и команды Docker в контейнерах Windows и Linux


Хотя те же самые клиентские утилиты Docker (Docker Compose, Docker Swarm) могут управлять как контейнерами Windows, так и Linux, существуют некоторые важные отличия между реализациями контейнеров в Windows и в Linux.


Системные процессы


Там, где Linux предоставляет свою функциональность уровня ядра через системные вызовы, Microsoft решила контролировать доступную функциональность ядра при помощи DLL (это также является причиной того, почему Microsoft на самом деле не документировала свои системные вызовы). Хотя этот способ абстракции системных вызовов имеет свои преимущества, он привёл к сильно интегрированной операционной системе Windows со множеством взаимозависимостей между разными DLL Win32 и ожиданию от многих DLL, что будут запущены некоторые системные службы, на которые они (не)явно ссылаются. В результате запускать только процессы приложений, указанных в Dockerfile, внутри контейнера Windows не очень реалистично. Поэтому внутри контейнера Windows вы увидите кучу запущенных дополнительных системных процессов, в то время, как контейнерам Linux нужно запускать только указанные процессы приложений. Чтобы гарантировать, что необходимые системные процессы и службы выполняются внутри контейнера Windows, внутри каждого контейнера запускается так называемый процесс smss. Цель процесса smss — запустить нужные системные процессы и службы.



Процесс SMSS


Архитектура ОС


Не только в способе предоставления функциональности уровня ядра, но также и на уровне архитектуры существует важная разница в том, как обе операционные системы предоставляют функциональность контейнера клиентским утилитам Docker. В текущей реализации контейнеров в Windows Server 2016 представлен слой абстракции, называемый Compute Services, который абстрагирует низкоуровневые возможности контейнера извне. Причина этого в том, что Microsoft может менять низкоуровневый API контейнера без необходимости менять публичный API, вызываемый Docker Engine и другими клиентскими утилитами контейнеров. Кроме этого API Compute Services, вы можете написать свой собственный инструментарий управления контейнерами, используя биндинг языков C# или Go, которые доступны по адресам https://github.com/Microsoft/dotnet-computevirtualization и https://github.com/Microsoft/hcsshim.




Каскадно-объединённая файловая система?


Третье важное отличие между реализациями контейнеров Linux и Windows заключается в способе, которым обе операционные системы используют технологию Docker “копирование-при-записи”. Так как множество приложений Windows полагается на семантику NTFS, для команды Microsoft было сложно создать полноценную реализацию каскадно-объединённой файловой системы в Windows. Для таких функций, как журналы USN и транзакции, это, к примеру, потребовало бы совершенно новой реализации. Поэтому в текущей версии контейнеров в Windows Server 2016 нет настоящей каскадно-объединённой файловой системы. Вместо этого текущая реализация создаёт новый виртуальный диск NTFS для каждого контейнера. Чтобы эти виртуальные диски оставались небольшими, различные объекты файловой системы на этом виртуальном NTFS диске на самом деле являются символическими ссылками на настоящие файлы файловой системы хоста. Как только вы изменяете файлы внутри контейнера, эти файлы сохраняются на виртуальное блочное устройство, а в момент, когда вы хотите зафиксировать этот слой изменений, изменения забираются из виртуального блочного устройства и сохраняются в нужном месте файловой системы хоста контейнера.


Контейнеры Hyper-V


Последнее различие в реализации контейнеров Linux и Windows состоит в понятии контейнеров Hyper-V. Я напишу об этом интересном типе контейнеров в следующей статье этой серии. Оставайтесь с нами…

Комментарии (12)


  1. dtkbrbq
    29.08.2017 16:00

    Скажите, можно ли в Windows среде «контейнеризировать» приложения с GUI, несколько приложений в одном контейнере в то время как одно должно взаимодействовать с GUI другого (небольшая автоматизация на AutoIT)?


    1. Skybladev2 Автор
      29.08.2017 16:06

      1. Microsoft пишет, что GUI пока не поддерживается: blog.docker.com/2016/09/dockerforws2016/#comment-369058.
      2. Да, можно запускать несколько приложений в контейнере, но из-за п.1 это вам не сильно поможет :)


  1. April777
    29.08.2017 17:50

    Существует ли возможность вирусов которые бы выходили за границу контейнеров как сейчас в VM?


    1. Skybladev2 Автор
      29.08.2017 18:06
      +1

      Конечно.

      Microsoft говорит, что для «параноидального уровня безопасности» можно использовать изоляцию Hyper-V, то есть, фактически, запускать контейнер как виртуальную машину.

      В целом, по фразе «escape from docker container» можно найти много информации по теме.


  1. torinvoin
    29.08.2017 18:47

    нечего не понятно( примеров нету.


    1. Skybladev2 Автор
      29.08.2017 18:49

      Можно посмотреть видео с DockerCon 16, на основе которого, скорее всего, была написана оригинальная статья.


      Там всё по-английски, зато с живыми примерами.


  1. demas1252
    29.08.2017 20:38

    На Windows 10 при запуске Linux docker-контейнеров поднимается виртуальная машина Hyper-V c Linux. А уже в ней контейнеры.

    Не смог найти информацию — в Windows Server в этом плане что-то поменялось?


    1. Victor_koly
      29.08.2017 22:40

      Возможно, что были какие-то проблемы:

      Проблема в том, что Windows и Linux контейнера обслуживаются разными Docker-демонами, которые, тем не менее, используют один и тот же канал для общения с командной строкой. То есть в каждый момент времени только один демон может быть активным.

      http://dotsandbrackets.com/quick-guide-to-windows-containers-ru/


    1. Kobalt_x
      30.08.2017 12:24

      Странным было бы ожидать что Linux контейнеры выполнялись бы с nt ядром, afaik, нет ничего не поменялось. Причем режим работы докера нужно выбирать (win-контейнеры/Linux-контейнеры)[Я буду читать комментарии до постигшая своего]


      1. demas1252
        30.08.2017 12:28

        WSL позволяет запускать часть Linux программ на ядре Windows. Я надеюсь, что в их число рано или поздно войдет и docker.


        1. Kobalt_x
          31.08.2017 15:13

          для этого нужно пилить управление namespace isolation для wsl внутри ntoskrnl, учитывая общую недопиленность wsl это случится очень нескоро


  1. Skybladev2 Автор
    29.08.2017 20:48

    demas1252
    Да, это более точное описание, спасибо за уточнение.


    Я тоже не встречал информации о том, что можно запускать Linux-контейнеры под Windows Server (если не считать этот хак).