Когда студенты или люди с не самым большим опытом слышат это слово, обычно в голове возникает страх, потому что Вы точно слышали отдалённо словосочетания "виртуальная машина" или "docker", вам надо этим пользоваться, а что это означает, достаточно мало людей могут сразу объяснить.
Если в общем виде, то виртуализация это добавление какого-то уровня абстракции для того, чтобы управлять вашими ресурсами. Скажем, указатель — это часть виртуализации памяти компьютера, регистр — часть виртуализации исполнения на процессоре. Хорошее свойство виртуализации в том, что она подразумевает независимость уровней. Вы не думаете, а что там внутри, и пользуйтесь только тем, что Вам выдали и с какими гарантиями.
Например, вы работаете с указателями и не думаете о том, как устроена память внутри. По факту, если вы зааллоцировали очень большой кусок памяти, то он может быть на многих кусках RAM. Это нормально и за это ответственно ядро, оно склеивает понятия указателей и физической памяти через абстракцию под названием "Виртуальная память".
Одна из виртуализаций, которая появилась в ядре, называют контейнерами. Представьте, что на машине у вас есть CPU, RAM, Network, Disk, GPU и т. д. Вы что-то на ней запускаете, всё хорошо работает, вы получаете свои результаты.
Со временем у людей появилась потребность (где-то в 2004-2006 годах на Linux) класть несколько приложений на одну машину -- так можно меньше тратить денег, лучше утилизировать железо.
В целом нет ничего такого, чтобы запускать несколько приложений на одной машине, но представьте ситуацию.
- Первый контейнер с поисковым шардом кушает 40% CPU
- Второй контейнер с обработкой email кушает 40% CPU
- Третий контейнер с новостным сервисом кушает 20% CPU
А потом происходит какая-нибудь беда, наплыв ботов или просто происходит большое событие, в итоге новостному агрегатору надо уже 40% CPU и в итоге кто-то должен чем-то пожертвовать. Ответ не очень понятен, потому что совершенно не хочется, чтобы поиск и email страдал в этот момент.
Но скорее уж правильнее зааллоцировать больше инстансов с новостным сервисом, чем делать так, чтобы поиск страдал даже несколько минут. Или получать сигнал о том, что надо расширяться. Также это позволило сделать облака в роде AWS, Google Cloud, чтобы правильно гарантировать те или иные ресурсы.
Так появились контейнеры — хочется, чтобы интерфейс у приложений был тот же как и нативный с операционной системой, но количество CPU, памяти или других ресурсов было ограничено.
То есть контейнеры привнесли изоляцию по ресурсам, чтобы починить данные проблемы. После этого контейнеры стали ещё более правильным решением для безопасности приложений и так далее, но это стало появляться позже в головах у разработчиков.
Linux со временем принял эту парадигму и назвал ещё cgroups
(control groups,
не container groups, потому что containers были созданы для чего-то другого
тогда).
Чтобы поиграться с чистыми cgroups, можно почитать этот гайд от Red Hat. Сейчас очень редко кто пользуется прям чистыми cgroups, они не самые лёгкие и очевидные в настройке.
Виртуальные машины похожи по своей логике и мотивации на контейнеры, единственное их отличие в том, что они приносят всё ядро, скажем, эмулировать Linux на MacOS стоит достаточно немалых ресурсов (а вот в Windows с появлением WSL 1,2 легче, потому что ядро уже есть).
В итоге контейнеры используют одно ядро, и в конфигурации контейнеров должно быть как минимум похожее ядро, а виртуальные машины приносят ядро с собой и полностью эмулируют его, оставляя только инструкции исполнения и память под нативное исполнение. В каком-то смысле контейнерам всё ещё нужна конфигурация, а виртуальные машины должны работать везде.
На самом деле эмуляция инструкций процессора тоже возможна -- смотрите на Rosetta 2 от Apple или QEMU для всех платформ.
Тема виртуализации достаточно сильно развивается до сих пор, интересные идеи в роде JIT компиляции появляются и кажется пока не собирают останавливаться.
Docker - это инфраструктура над контейнерами в системах Linux/MacOS/Windows и других операционных систем.
Вы скорее всего уже сталкивались с тем, что одни бинарные файлы работают на одном Linux, но не работают на MacOS или слегка другой версии того же Linux. Это нормально, какие-то стандартные библиотеки могут быть несовместимы и в этом нет ничего такого плохого, просто нет бинарной совместимости между версиями.
Чтобы этого избежать, достаточно "привезти" вам всё, что находится под /
и
запустить контейнер над этим с нужными настройками файловой системы. Docker это
и делает.
Из-за этого результаты могут быть воспроизводимыми — кто-то работает на Ubuntu 16, в итоге вы на MacOS можете запустить программу, чтобы воспроизвести все результаты, не скачивая тонну зависимостей, которые к тому же могут работать только на Linux.
- В итоге вы можете воспроизвести результаты других, это один из философских признаков научности. Статьи любят выкладывать docker контейнеры
- Вы спокойно можете себе принести базу данных, которая точно работает в контейнере, не задумываясь о своих зависимостях.
То есть docker контейнер это готовое решение уравнения всех, воспроизводимости результатов и доказательство работоспособности того или иного приложения.
У Docker есть оверхед, чтобы чему-то запуститься, ему надо загрузить свои библиотеки в память, использовать многослойную файловую систему и тд. Можете прочитать статью. Я в среднем ожидаю от docker 5-15%, если он CPU интенсивный, и 20-30% если много работы с файловой системой, и достаточно большую дисперсию результатов — проверять перформанс на докер контейнере очень сложно, поэтому, например, контесты по алгоритмам не используют докер, а курс по C++ — да.
Основные понятия докера:
- Образ — это скомпилированный в специальный формат бинарный файл, который докер знает как загрузить в контейнер и исполнить (через boot loader). Там хранятся все нужные библиотеки.
Чтобы скачать какой-нибудь образ, надо запустить команду (обратите внимание на
#
, это означает sudo, хоть docker и может исполняться без него, поиграться
рекомендую с sudo):
# docker pull image_name
Например, в Docker есть простейший hello-world
.
Чтобы посмотреть все образы, можно запустить следующую команду
# docker images
Чтобы удалить какой-то образ, нужно запустить
# docker rmi image_name
Некоторые полезные опции типа фильтрации можно посмотреть через
tldr docker images
и tldr docker rmi
.
- Контейнер — один инстанс образа.
Запустить контейнер:
# docker run hello-world
Запустить bash в интерактивном -i
моде, создав при этом псевдо терминал -t
,
где можно поковыряться:
# docker run -it ubuntu bash
Запустить команду в новом контейнере с локальной директорией /home/danlark/mydir
,
которая будет директорией /data
в контейнере, и отвести контейнеру 150m памяти
максимум:
# docker run -v /home/danlark/mydir:/data --memory 150m -it {{image_name}} {{command}}
Запустить контейнер в бекграунде и сделать map порта 3000 на 3000 у локальной машины:
# docker run -dp 3000:3000 {{image}}
Присоединиться к контейнеру, который уже запущен и открыть шелл:
# docker exec -it {{container_name}} bash
Стартовать/остановить контейнер
# docker start/stop {{container_name}}
Удалить остановленный контейнер
# docker rm {{container_name}}
Посмотреть все контейнеры
# docker ps -a
Вы также можете делать push обновлённых образов, например, так я обновлял курс по C++
# docker push eu.gcr.io/hse-ts/cpp-course-build:latest
Обычно берут с DockerHub, там есть всё, ubuntu, arch, на всех архитектурах с любыми версиями. Любые серверы, базы данных. Вы даже сами можете создать контейнер.
Сам Docker так и зарабатывает, продавая эту инфраструктуру закрытым компаниям.
Для этого есть Dockerfile
. Пример Dockerfile
FROM debian:10
RUN apt-get update && apt-get install --yes --no-install-recommends \
build-essential \
cmake \
libboost-locale-dev \
libboost-regex-dev \
libprotobuf-dev \
libprotobuf-lite17 \
protobuf-compiler \
locales
COPY src /code
COPY resources /code/resources
COPY lib.so /code/lib.so
COPY input.txt /code/input.txt
RUN make /code
CMD [ "bash" ]
В целом синтаксис такой: DOCKER_COMMAND description
, например, такой Dockerfile
означает взять как основной контейнер debian:10
(Debian 10 версии), далее
установить всё из APT нужные библиотеки, скопировать нужные файлы и папки,
собрать код, и далее запустить команду при входе в контейнер.
Стоит отметить, что RUN это конфигурация образа, когда как CMD это то, что должно выполниться при входе в контейнер, например.
Чтобы собрать образ в директории с Dockerfile, нужно сделать:
# docker build .
Если хочется собрать docker из публичного репозитория, можно сделать
# docker build {{github.com/owner/reponame}}
Можно ещё делать тэги для версионирования (тэг по умолчанию это latest)
# docker build --tag {{name:tag}}
Пусть до файла тоже можно указать
# docker build --file {{Dockerfile}} .
Docker compose это иерархичный запуск Dockerfiles в директориях, пример:
version: "2"
services: # указываем что хотим собрать
builder: # собираем одно
image: clickhouse/clickhouse-builder
build: docker/builder
client: # собираем другое
image: clickhouse/clickhouse-client
build: docker/client
command: ['--host', 'server']
server: # третье
image: clickhouse/clickhouse-server
build: docker/server
ports: # делает соответствие портов
- 8123:8123
Далее в директориях вы можете найти докер файлы, которые будут собираться.
Собрать в background всё с файлом docker-compose.yml
:
# docker-compose up -d
Остановить:
# docker-compose stop
Удалить и почистить всё
# docker-compose down --rmi all --volumes
Filesystem in userspace. В целом это виртуализация файловой системы по любой логике, которую вы хотите — хотите смотреть файлы чужой машины, есть FUSE клиент sshfs, хотите смотреть Drive, есть Google Drive FUSE и так далее. Оно позволяет абстрагировать файловую систему от диска, что в целом, логично.
Смотрите презентацию по тому как файловые системы удалённых машинок клонировать себе.
Пример команды sshfs
, чтобы получить у себя файлы у remote машины:
$ sshfs user@host:/path/to/remote_dir /path/to/local_dir