Docker — основы контейнеризации
Docker — платформа контейнеризации, которая решает одну из самых старых проблем в разработке: «у меня на машине работает». Она упаковывает приложение вместе со всеми его зависимостями — системными библиотеками, средой исполнения, конфигурациями — в изолированный контейнер. Этот контейнер ведёт себя одинаково на ноутбуке разработчика, CI-сервере и продакшн-кластере.
Для Go-разработчика Docker — это не абстрактный инструмент из мира DevOps, а повседневная реальность. Вы будете собирать Go-бинарники в Docker, поднимать базы и зависимости через Compose, описывать деплой в Kubernetes. Поэтому начнём с фундамента: что на самом деле происходит, когда вы запускаете docker run.
Что такое Docker и зачем он нужен
На первый взгляд Docker решает простую задачу — воспроизводимость окружения. Но за этим стоит целый набор практических проблем, с которыми сталкивается любая команда:
Проблема окружения. Приложение зависит не только от своего кода. Ему нужна конкретная версия Go (или другого рантайма), определённые системные библиотеки, переменные окружения, файлы конфигурации. На одной машине стоит PostgreSQL 14, на другой — 15. На одной — Ubuntu, на другой — Alpine. Docker фиксирует всё окружение целиком.
Проблема изоляции. На одном сервере могут работать несколько приложений, и они не должны мешать друг другу. Один сервис слушает порт 8080, другому тоже нужен 8080. Один требует определённую версию glibc, другой — другую. Docker изолирует каждое приложение в собственном пространстве.
Проблема масштабирования. Когда нагрузка растёт, нужно быстро поднимать новые экземпляры сервиса. Docker-контейнеры запускаются за секунды, в отличие от виртуальных машин, которым нужны минуты на загрузку полноценной ОС.
Проблема CI/CD. Сборка, тестирование и деплой должны происходить в одинаковом окружении. Docker-образ — это артефакт, который один раз собрали, протестировали и развернули в продакшн без изменений.
Контейнеры vs виртуальные машины
Контейнеры и виртуальные машины решают схожую задачу — изоляцию приложений — но принципиально разными способами. Понимание этой разницы важно не только для собеседований, но и для принятия архитектурных решений.
Виртуальные машины
VM виртуализирует железо. Гипервизор (VMware, KVM, Hyper-V) создаёт виртуальное оборудование — процессор, память, диск, сетевой адаптер — и внутри него запускается полноценная гостевая ОС со своим ядром:
┌──────────────────────────────────────────────────┐
│ Хостовая ОС / Гипервизор │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Гостевая ОС │ │ Гостевая ОС │ │
│ │ (ядро Linux) │ │ (ядро Windows)│ │
│ │ ┌──────────┐│ │ ┌──────────┐│ │
│ │ │ App A ││ │ │ App B ││ │
│ │ └──────────┘│ │ └──────────┘│ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────┘
Каждая VM — это гигабайты данных (ОС + приложение) и минуты на старт.
Контейнеры
Контейнер виртуализирует операционную систему. Все контейнеры используют одно и то же ядро хоста, но каждый видит собственное изолированное пространство процессов, сети и файловой системы:
┌──────────────────────────────────────────────────┐
│ Хостовая ОС (Linux kernel) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Контейнер A │ │ Контейнер B │ │
│ │ (userspace) │ │ (userspace) │ │
│ │ ┌──────────┐│ │ ┌──────────┐│ │
│ │ │ App A ││ │ │ App B ││ │
│ │ └──────────┘│ │ └──────────┘│ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────┘
Сравнение
| Характеристика | Контейнер | Виртуальная машина |
|---|---|---|
| Уровень виртуализации | ОС (userspace) | Железо (hardware) |
| Ядро | Общее с хостом | Собственное |
| Размер | Мегабайты | Гигабайты |
| Время старта | Секунды | Минуты |
| Накладные расходы | Минимальные | Значительные |
| Изоляция | Процессная (namespaces) | Полная (гипервизор) |
| Разные ОС на одном хосте | Нет | Да |
Контейнеры выигрывают в скорости и эффективности, а виртуальные машины — в безопасности и гибкости. Но это не значит, что одно вытесняет другое — они часто работают вместе. Kubernetes-кластер, например, обычно работает на виртуальных машинах, внутри которых бегут контейнеры.
Linux under the hood: namespaces, cgroups, overlay fs
Docker — это не магия. Под капотом он использует три механизма ядра Linux, которые существовали задолго до Docker. Docker просто сделал их удобными.
Namespaces — изоляция
Namespaces создают иллюзию того, что контейнер — отдельная машина. Каждый namespace изолирует определённый аспект системы:
| Namespace | Что изолирует | Эффект |
|---|---|---|
| PID | Процессы | Контейнер видит только свои процессы, PID 1 — его init |
| NET | Сеть | Собственный сетевой стек, IP-адрес, порты |
| MNT | Файловая система | Собственная корневая ФС, точки монтирования |
| UTS | Hostname | Собственное имя хоста |
| IPC | Межпроцессное общение | Изолированные очереди сообщений, shared memory |
| USER | Пользователи | Маппинг UID/GID — root в контейнере != root на хосте |
Когда вы запускаете docker run, Docker создаёт набор namespaces для контейнера. Процесс внутри контейнера думает, что он единственный в системе — у него PID 1, свой IP-адрес, своя файловая система. Но на самом деле это обычный процесс хоста, просто с ограниченной видимостью.
Cgroups — лимиты ресурсов
Namespaces изолируют видимость, но не ограничивают потребление. Если контейнер начнёт есть всю память хоста — другие контейнеры упадут. Здесь вступают cgroups (control groups).
Cgroups позволяют задать лимиты:
- CPU — сколько процессорного времени может использовать контейнер
- Memory — максимальный объём оперативной памяти
- Disk I/O — скорость чтения/записи на диск
- Network — пропускная способность сети
# Запуск контейнера с ограничением: максимум 512 МБ RAM и 1 ядро CPU
docker run --memory=512m --cpus=1.0 my-app
Если контейнер превышает лимит памяти, ядро Linux отправляет ему OOM-сигнал (Out of Memory) и завершает процесс. Это поведение критично для Go-приложений: рантайм Go должен знать о лимитах cgroups, иначе GC будет работать неэффективно. Начиная с Go 1.19, рантайм автоматически читает лимиты cgroups через GOMEMLIMIT.
Overlay FS — слоистая файловая система
Overlay FS — это механизм, который позволяет «наложить» несколько файловых систем друг на друга. Docker использует его для создания слоёв образа:
- Нижние слои — read-only (базовый образ, зависимости)
- Верхний слой — writable (изменения контейнера)
Если контейнер хочет изменить файл из нижнего слоя, overlay fs создаёт копию файла в верхнем слое (copy-on-write). Оригинал остаётся нетронутым. Это позволяет множеству контейнеров делить одни и те же базовые слои, экономя место на диске.
Docker на Mac и Windows — почему нужна VM
Это один из самых частых вопросов: «если контейнер использует ядро хоста, а на Mac нет Linux-ядра — как Docker вообще работает?»
Ответ прост: на Mac и Windows Docker запускает скрытую Linux VM.
Linux-хост:
Приложение --> контейнер --> Linux kernel хоста (напрямую)
Mac/Windows:
Приложение --> контейнер --> Linux kernel --> LinuxKit VM --> хост ОС
Docker Desktop устанавливает легковесную виртуальную машину LinuxKit, внутри которой работает настоящее Linux-ядро. Все контейнеры бегут внутри этой VM. На Mac для виртуализации используется встроенный фреймворк Apple Hypervisor (или ранее xhyve), на Windows — WSL 2 или Hyper-V.
Из-за этого на Mac/Windows есть дополнительные накладные расходы по сравнению с нативным Linux:
- Потребление памяти (VM забирает фиксированный объём)
- Скорость файловой системы (bind mounts работают через виртуальный FS)
- Networking (порты пробрасываются через VM)
Почему VM не вымерли
Раз контейнеры такие лёгкие и удобные, зачем нужны VM? Потому что у контейнеров есть фундаментальное ограничение — они разделяют ядро хоста:
| Сценарий | Контейнер | VM |
|---|---|---|
| Разные ОС на одном хосте | Нет | Да |
| Полная изоляция ядра (безопасность) | Нет | Да |
| Мультитенантность (чужой код) | Рискованно | Да |
| Legacy-приложения (Windows Server) | Нет | Да |
| Микросервисы, CI/CD, dev-окружение | Да | Избыточно |
Если эксплойт в ядре Linux — все контейнеры на хосте уязвимы. VM обеспечивает полную изоляцию на уровне гипервизора. Поэтому в облаках (AWS, GCP) каждый клиент получает свои VM, а внутри VM уже бегут контейнеры.
Также существуют Windows-контейнеры — они работают только на Windows-хосте. Linux-контейнер на Windows запускается через VM (WSL 2). А вот Windows-контейнер на Linux — невозможно в принципе, потому что ему нужно Windows-ядро.
Docker Image — неизменяемый шаблон
Docker Image (образ) — это read-only шаблон, который содержит всё необходимое для запуска приложения:
- Легковесную операционную систему (обычно урезанный Linux)
- Среду исполнения (если нужна)
- Код приложения
- Зависимости
- Конфигурации
- Инструкции по запуску
Ключевые характеристики образа
Неизменяемость (immutability). После сборки образ невозможно изменить. Любое изменение — это новый образ с новым хешем. Это фундаментальное свойство, которое обеспечивает воспроизводимость: если образ работает на staging — он будет работать точно так же на production.
Легковесность. Образ содержит только минимальный набор компонентов. Go-приложение в multi-stage build может уместиться в 10-20 МБ (alpine + бинарник), в отличие от VM-образа на несколько гигабайт.
Портативность. Один раз собранный образ будет работать одинаково на любом сервере, где установлен Docker. Не важно — Ubuntu, CentOS, или Amazon Linux.
Основные команды для работы с образами
# Сборка образа из Dockerfile (точка — контекст сборки, текущая директория)
docker build -t my-backend-app:v1.0 .
# Получение готового образа из реестра (Docker Hub, GitLab Registry, etc.)
docker pull postgres:15-alpine
# Список всех образов на локальной машине
docker images
# Удаление образа
docker rmi my-backend-app:v1.0
# Просмотр истории слоёв образа
docker history my-backend-app:v1.0
Теги и реестры
Каждый образ имеет имя и тег: postgres:15-alpine, golang:1.26-bookworm, my-app:v2.3.1. Тег latest используется по умолчанию, если тег не указан явно — но полагаться на latest в production опасно, потому что он указывает на разные версии в разное время.
Версии базовых образов в примерах нужно регулярно ревизовать: Go поддерживает только две последние major/minor ветки, а Alpine release branches имеют ограниченный срок security support. Старый фиксированный тег лучше latest, но он всё равно устаревает.
Образы хранятся в реестрах (registries). Docker Hub — публичный реестр по умолчанию. В компаниях обычно используют приватные реестры: GitLab Container Registry, AWS ECR, Google Artifact Registry.
Слои Docker — механизм кэширования
Каждая инструкция в Dockerfile (RUN, COPY, ADD) создаёт новый read-only слой. Слои — это то, что делает Docker быстрым и экономным.
Как устроены слои
Образ (read-only слои):
┌─────────────────────┐
│ ENTRYPOINT ["/app"] │ слой 4
├─────────────────────┤
│ RUN go build │ слой 3
├─────────────────────┤
│ COPY . . │ слой 2
├─────────────────────┤
│ FROM golang:1.26 │ слой 1 (базовый)
└─────────────────────┘
Контейнер:
┌─────────────────────┐
│ writable layer │ <-- логи, tmp файлы (удаляется с контейнером)
├─────────────────────┤
│ образ (read-only) │
└─────────────────────┘
Когда из образа создаётся контейнер, сверху добавляется тонкий writable layer. Все изменения, которые происходят в контейнере (создание файлов, запись логов, модификация конфигов), попадают в этот слой. Когда контейнер удаляется — writable layer удаляется вместе с ним. Образ остаётся нетронутым.
Кэширование слоёв
Docker кэширует каждый слой по его содержимому. Если инструкция и её входные данные не изменились — слой берётся из кэша. Это критически важно для скорости сборки.
Но есть правило: если слой изменился — все последующие слои пересобираются. Именно поэтому порядок инструкций в Dockerfile имеет огромное значение.
# Плохо: любое изменение кода инвалидирует кэш зависимостей
COPY . .
RUN go mod download
RUN go build
# Хорошо: зависимости кэшируются отдельно
COPY go.mod go.sum ./
RUN go mod download # кэшируется, пока go.mod не изменился
COPY . .
RUN go build
В первом варианте каждое изменение в любом .go файле инвалидирует слой COPY . ., а значит — и go mod download будет выполняться заново. Во втором варианте зависимости скачиваются только когда изменился go.mod или go.sum.
Группировка команд RUN
Каждый RUN создаёт отдельный слой. Связанные команды стоит объединять, чтобы уменьшить количество слоёв и размер образа:
# Плохо: 3 слоя, промежуточные данные (apt cache) остаются в слоях
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Хорошо: 1 слой, чище и меньше по размеру
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Важно понимать: удаление файла в следующем слое не уменьшает размер образа. Файл всё ещё существует в предыдущем слое (слои неизменяемы). Поэтому создание и удаление временных файлов нужно делать в одном RUN.
Переиспользование слоёв между образами
Если два образа используют один и тот же базовый образ (например, alpine:3.23), Docker хранит его слои только один раз. Десять сервисов на базе alpine:3.23 не занимают 10x места — базовые слои разделяются. Это справедливо и для docker pull — скачиваются только те слои, которых ещё нет локально.
Docker Container — запущенный экземпляр образа
Docker Container (контейнер) — это запущенный и работающий экземпляр образа. Если образ — это пассивный шаблон, «архив» с файлами и инструкциями, то контейнер — это активный процесс, изолированный от хостовой ОС и других контейнеров.
Ключевые особенности контейнера
Изоляция. Контейнер работает внутри собственного набора namespaces. У него свой PID-namespace (процессы), NET-namespace (сетевой стек), MNT-namespace (файловая система). Он не видит процессы других контейнеров и не конфликтует с ними.
Эффективность. В отличие от VM, контейнеру не нужна полная гостевая ОС. Он использует ядро хоста и содержит только минимальный userspace. Типичный контейнер с Go-приложением весит десятки мегабайт и запускается за секунды.
Контроль ресурсов. При запуске можно явно ограничить, сколько CPU и памяти может использовать контейнер. Это защищает другие контейнеры и хост от «прожорливых» процессов.
Эфемерность. Контейнеры проектируются как временные сущности. Их можно остановить, удалить, пересоздать из того же образа — и получить идентичный результат. Данные, которые должны пережить контейнер, хранятся в volumes (о них ниже).
Жизненный цикл контейнера
docker create --> Created
docker start --> Running
docker pause --> Paused
docker unpause --> Running
docker stop --> Stopped (Exited)
docker rm --> Removed
Когда контейнер останавливается, его writable layer сохраняется до явного удаления (docker rm). Это позволяет посмотреть логи или скопировать файлы из остановленного контейнера.
Основные команды для работы с контейнерами
# Запуск контейнера из образа с пробросом порта
docker run -p 8088:8088 my-backend-app
# Запуск в фоновом режиме (detached)
docker run -d -p 8088:8088 --name my-app my-backend-app
# Список запущенных контейнеров
docker ps
# Список всех контейнеров (включая остановленные)
docker ps -a
# Просмотр логов контейнера
docker logs my-app
docker logs -f my-app # в режиме follow (аналог tail -f)
# Выполнение команды внутри работающего контейнера
docker exec -it my-app /bin/sh
# Остановка контейнера (SIGTERM, затем SIGKILL через 10 секунд)
docker stop my-app
# Удаление остановленного контейнера
docker rm my-app
Флаг -it в docker exec — это комбинация -i (interactive, stdin остаётся открытым) и -t (tty, выделяется псевдотерминал). Без них вы не сможете интерактивно работать в shell контейнера.
Docker Volumes — персистентное хранилище
Контейнеры по своей природе эфемерны — когда контейнер удаляется, его writable layer исчезает вместе со всеми данными. Но базе данных нужно хранить файлы между перезапусками. Volumes решают эту проблему.
Существует три способа прокинуть данные в контейнер:
Bind mount
Bind mount монтирует конкретную папку хоста внутрь контейнера. Контейнер видит содержимое этой папки как своё собственное:
# Через командную строку
docker run -v /host/path:/container/path myapp
# Монтирование конфига и данных
docker run -v ./config:/app/config -v ./data:/var/lib/postgresql myapp
Bind mount привязывает контейнер к конкретной структуре файловой системы хоста. Это удобно для разработки — можно монтировать каталог с исходниками и видеть изменения в реальном времени (hot reload). Но на production это анти-паттерн: пути на разных серверах могут отличаться.
Named volume
Named volume — Docker сам управляет хранилищем. Данные хранятся в специальной директории Docker (/var/lib/docker/volumes/) и не зависят от файловой системы хоста:
# Создание именованного тома
docker volume create pgdata
# Использование при запуске контейнера
docker run -v pgdata:/var/lib/postgresql/data postgres:15-alpine
# Список томов
docker volume ls
# Удаление тома
docker volume rm pgdata
Named volumes — предпочтительный способ для production. Они портативны, Docker управляет их жизненным циклом, и они могут быть подключены к разным контейнерам.
tmpfs
tmpfs — данные хранятся в оперативной памяти и исчезают при остановке контейнера:
docker run --tmpfs /tmp myapp
Подходит для временных файлов и секретов, которые не должны попадать на диск.
Сравнение типов
| Тип | Персистентность | Кто управляет | Когда использовать |
|---|---|---|---|
| Bind mount | Да, на хосте | Разработчик | Dev: конфиги, код, hot reload |
| Named volume | Да, в Docker | Docker | Prod: данные БД, файлы |
| tmpfs | Нет | -- | Секреты, временные файлы |
Пример: PostgreSQL с named volume
# docker-compose.yaml
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata: # Docker создаёт и хранит в /var/lib/docker/volumes/
Даже если контейнер postgres удалить и создать заново — данные в pgdata сохранятся. Это фундаментальный паттерн для stateful-сервисов в Docker.
Основные команды Docker
Сводная таблица команд, которые вы будете использовать ежедневно:
Образы
# Сборка образа из Dockerfile в текущей директории
docker build -t my-app:v1.0 .
# Сборка с указанием конкретного Dockerfile
docker build -f Dockerfile.prod -t my-app:prod .
# Скачать образ из реестра
docker pull postgres:15-alpine
# Список локальных образов
docker images
# Удалить образ
docker rmi my-app:v1.0
# Удалить неиспользуемые образы
docker image prune
Контейнеры
# Запуск с пробросом порта и именем
docker run -d -p 8080:8080 --name api my-app:v1.0
# Запуск с переменными окружения
docker run -d -e DB_HOST=localhost -e DB_PORT=5432 my-app:v1.0
# Запуск с ограничением ресурсов
docker run -d --memory=256m --cpus=0.5 my-app:v1.0
# Список запущенных контейнеров
docker ps
# Логи
docker logs -f api
# Зайти внутрь контейнера
docker exec -it api /bin/sh
# Остановить и удалить
docker stop api && docker rm api
# Остановить все контейнеры
docker stop $(docker ps -q)
Volumes
# Создать том
docker volume create mydata
# Список томов
docker volume ls
# Информация о томе
docker volume inspect mydata
# Удалить том
docker volume rm mydata
# Удалить неиспользуемые тома
docker volume prune
Общая очистка
# Удалить все остановленные контейнеры, неиспользуемые сети и образы
docker system prune
# То же + volumes (осторожно!)
docker system prune --volumes
# Показать использование дискового пространства
docker system df
Как всё связано вместе
Подведём итог, чтобы собрать полную картину:
- Dockerfile описывает шаги сборки образа
- docker build выполняет эти шаги и создаёт Image — набор read-only слоёв
- docker run берёт Image, добавляет writable layer и создаёт Container — изолированный процесс
- Контейнер изолирован через namespaces, ограничен через cgroups, его файловая система построена на overlay fs
- Данные, которые должны пережить контейнер, хранятся в Volumes
- На Mac/Windows всё это работает внутри скрытой Linux VM (LinuxKit)
Dockerfile --build--> Image (read-only слои)
|
run |
v
Container (image + writable layer)
|
┌------┼------┐
| | |
namespaces cgroups overlay fs
| | |
└------┼------┘
|
Linux Kernel
Вопросы на собеседовании
1. Чем контейнер отличается от виртуальной машины?
Контейнер виртуализирует ОС (userspace), VM виртуализирует железо. Контейнер использует ядро хоста через namespaces и cgroups, а VM имеет собственное ядро. Контейнеры легче (мегабайты vs гигабайты), быстрее запускаются (секунды vs минуты), но обеспечивают менее строгую изоляцию — уязвимость в ядре затрагивает все контейнеры.
2. Что такое namespaces и cgroups? Какую роль они играют в Docker?
Namespaces обеспечивают изоляцию: PID (процессы), NET (сеть), MNT (файловая система), UTS (hostname), IPC (межпроцессное общение), USER (пользователи). Cgroups ограничивают потребление ресурсов: CPU, память, disk I/O. Docker использует оба механизма: namespaces создают иллюзию отдельной машины, cgroups не дают контейнеру «съесть» все ресурсы хоста.
3. Что произойдёт с данными при удалении контейнера?
Writable layer контейнера удаляется вместе с ним — все данные, записанные внутри контейнера, теряются. Чтобы данные сохранялись, нужно использовать volumes: bind mount (монтирование папки хоста), named volume (управляемый Docker), или tmpfs (в памяти, для временных данных).
4. Чем Docker Image отличается от Docker Container?
Image — неизменяемый (immutable) шаблон, набор read-only слоёв. Container — запущенный экземпляр образа с добавленным writable layer. Из одного образа можно создать множество контейнеров. Образ нельзя изменить после сборки, контейнер — можно (но изменения живут только в его writable layer).
5. Как Docker работает на macOS?
На macOS нет Linux-ядра, а контейнерам оно необходимо. Docker Desktop запускает легковесную Linux VM (LinuxKit) через Apple Hypervisor framework. Все контейнеры работают внутри этой VM. Из-за этого есть дополнительные накладные расходы: медленнее файловая система (bind mounts через виртуальный FS), больше потребление памяти (VM занимает фиксированный объём).
6. Как работает кэширование слоёв Docker? Почему порядок инструкций в Dockerfile важен?
Каждая инструкция (RUN, COPY, ADD) создаёт слой. Docker кэширует слои по содержимому. Если инструкция и входные данные не изменились — слой берётся из кэша. Но если один слой инвалидируется — все последующие слои тоже пересобираются. Поэтому редко меняющиеся инструкции (установка зависимостей) ставят раньше, а часто меняющиеся (копирование кода) — позже.
7. В чём разница между bind mount и named volume?
Bind mount привязывает конкретную директорию хоста к контейнеру — разработчик сам управляет путями и содержимым. Named volume управляется Docker, хранится в /var/lib/docker/volumes/. Bind mount удобен для разработки (монтирование исходников, hot reload), named volume — для production (портативность, управление жизненным циклом).
Задачи
Задача 1: Запуск PostgreSQL с персистентными данными
Запустите контейнер PostgreSQL 15 (образ postgres:15-alpine) со следующими требованиями:
- Пароль
mysecret - Порт проброшен на хост
5432:5432 - Данные хранятся в named volume
pg-data - Контейнер называется
my-postgres - Ограничение памяти: 512 МБ
Убедитесь, что после остановки и удаления контейнера, а затем повторного запуска с тем же volume — данные сохраняются.
Задача 2: Исследование слоёв образа
Возьмите любой Docker-образ (например, golang:1.26-alpine3.23) и выполните:
- Посмотрите его слои через
docker history - Определите, какой слой занимает больше всего места
- Сравните размер
golang:1.26-alpine3.23иalpine:3.23черезdocker images - Объясните, почему разница именно такая
Задача 3: Namespaces на практике
Запустите контейнер с alpine в интерактивном режиме (docker run -it alpine /bin/sh) и выполните внутри:
ps aux— какие процессы видит контейнер? Какой PID у вашего shell?hostname— какое имя хоста у контейнера?ip addr— какой IP-адрес у контейнера?
Сравните результаты с выводом тех же команд на хосте. Объясните, какие namespaces обеспечивают каждое из наблюдаемых отличий.
Интерактивная практика
Что точнее всего описывает разницу между Docker image и container?
Что выведет этот код?
package main
import "fmt"
func dockerObject(running bool) string {
if running {
return "container"
}
return "image"
}
func main() {
fmt.Println(dockerObject(false))
fmt.Println(dockerObject(true))
}
Реализуй StorageKind: для host path нужен bind, для постоянного managed-хранилища — volume, для временного in-memory хранилища — tmpfs.