Кажется, что вокруг Docker сложился ореол некой магической сложности. Всякие там «контейнеры», «образы», «оркестрация» — слова, которые слышишь в разговорах продвинутых DevOps-инженеров, и кажется, что это тема не для простого смертного. Я и сам так думал, пока не сел однажды и не попробовал разобраться. Оказалось, что начать — проще простого. Эта статья — тот самый недостающий ключ, который я сам искал. Мы не будем грузить вас сложной теорией, а сразу перейдем к делу. Моя цель — чтобы уже через десять минут после прочтения вы с гордостью смотрели на работающее в контейнере приложение, которое вы запустили сами.
Не война сред, или Зачем всё это нужно
Представьте самую обыденную и при этом самую раздражающую ситуацию в работе программиста. Вы неделями пишете код, отлаживаете его, и на вашем ноутбуке всё летает. Вы сдаете работу, и коллега на своем компьютере пытается ее запустить. И тут начинается: «У меня не компилируется», «А где эта библиотека?», «У тебя версия Node.js какая-то древняя!». Знакомо? Эта проблема настолько стара, что у нее есть собственное имя — «works on my machine», или «на моей машине работает». Проблема не в вас и не в коллеге, а в бесконечном разнообразии сред: разные операционные системы, версии интерпретаторов, системные настройки.
Вот здесь на сцену и выходит Docker со своей элегантной идеей. По сути, он предлагает паковывать ваше приложение в стандартный «контейнер» — этакий универсальный ящик, внутри которого лежит не только ваш код, но и вся его среда обитания: нужная версия Python, все системные библиотеки, настройки переменных окружения. Если проводить аналогию, то это не просто пересылка файла с кодом, а переезд всей вашей комнаты со всей мебелью, книгами и любимой кружкой. Вы гарантируете, что на новом месте всё будет расставлено точно так же.
Чем же этот подход лучше старых добрых виртуальных машин? Все дело в эффективности. Виртуальная машина эмулирует целый компьютер со своим ядром операционной системы, что требует гигантских ресурсов. Контейнер же, как поясняют в своей документации разработчики Docker, «использует ядро хостовой ОС», что делает его невероятно легковесным и быстрым. Запуск виртуальной машины — это процесс на несколько минут, а контейнер стартует за секунды. На одном сервере можно запустить три-четыре виртуальные машины или несколько десятков, а то и сотен контейнеров. Эта эффективность, помноженная на гарантированную переносимость, и стала топливом для настоящей революции в том, как мы сейчас разрабатываем и развертываем программное обеспечение.
Знакомство с инструментом: установка и первый запуск
Итак, теория ясна, руки чешутся попробовать. Первым делом нам нужно установить Docker. Не пугайтесь, сегодня это максимально упрощено. Проще всего зайти на официальный сайт docker.com и скачать Docker Desktop для вашей операционной системы (Windows или macOS). Это такой комплексный пакет, который устанавливает всё необходимое, включая удобный графический интерфейс, которым при желании можно пользоваться. Процесс установки ничем не отличается от установки любой другой программы — несколько кликов «Далее», и готово. Пользователям Linux повезло еще больше — установка часто заключается в выполнении пары команд в терминале, подробные инструкции для всех популярных дистрибутивов легко гуглятся.
После установки запустите Docker Desktop. Он может попросить вас перезагрузить компьютер — это нормально. Когда он запустится, вы увидите в системном трее его значок (кита с контейнерами). Теперь откройте ваш терминал (командную строку в Windows, Terminal на Mac). Чтобы проверить, что всё в порядке, давайте спросим у Docker, как он себя чувствует. Введите команду docker --version и нажмите Enter. В ответ вы должны увидеть что-то вроде Docker version 24.0.6. Если видите номер версии — поздравляю, клиент Docker готов к работе и общается с демоном.
Теперь совершим наш первый, самый важный ритуал. В мире программирования традиционно начинают с вывода на экран фразы «Hello, World!». Мы не будем нарушать традицию. Введите в командной строке волшебную команду: docker run hello-world. Что же происходит в этот момент? Демон Docker получает приказ, смотрит в свои локальные запасы и не находит там образа с именем hello-world. Без паники! Он тут же лезет в интернет, на центральный реестр образов Docker Hub, находит там нужный образ и скачивает его. После загрузки он на его основе создает и сразу же запускает контейнер. Задача этого контейнера-новичка — просто вывести сообщение, которое начинается со слов: «Hello from Docker! This message shows that your installation appears to be working correctly». Если вы это видите — вы только что успешно запустили свой первый контейнер! Этот простой шаг — квинтэссенция всего рабочего процесса Docker.
Создаем свое детище: упаковываем веб-приложение
Запустить готовый образ — это как собрать мебель из IKEA по инструкции. Полезно, но не так увлекательно, как сделать что-то свое. Давайте теперь станем не пользователем, а творцом и упакуем в контейнер наше собственное простое веб-приложение. Для примера возьмем микрофреймворк Flask для Python. Это идеальный кандидат — просто пишем несколько строк кода, и у нас уже есть работающий веб-сервер. Создайте файл app.py и поместите в него код, который при обращении по корневому адресу возвращает радостное: «Ура! Мое приложение в Docker работает!».
Теперь главный ингредиент — Dockerfile. Это не файл с расширением, а просто текстовый файл с именем Dockerfile (без расширения). Представьте, что это подробный рецепт для повара-демона Docker, в котором мы по шагам расписываем, как приготовить наш образ. Первая строка — FROM python:3.9-alpine. Этой командой мы говорим: «Повар, начни с легковесного базового образа, где уже установлен Python 3.9». Alpine Linux известен своим малюсеньким размером, что делает наши контейнеры компактными.
Далее командой WORKDIR /app мы создаем внутри будущего контейнера директорию /app и делаем ее нашей рабочей папкой. Все последующие команды будут выполняться внутри нее. Теперь нужно перенести наш код внутрь. Команда COPY . . копирует все файлы из текущей папки на вашем компьютере (ту, где лежит Dockerfile) в рабочую директорию контейнера. Поскольку наше приложение использует Flask, его нужно установить. Добавляем строку RUN pip install flask. Она выполнится во время сборки образа и установит нужную зависимость.
Теперь нужно сделать два последних штриха. Строкой EXPOSE 5000 мы сообщаем Docker, что наше приложение будет слушать порт 5000. Это просто метаданные, информационный сигнал. И, наконец, самая важная команда — CMD ["python", "app.py"]. Она говорит контейнеру, что делать, когда его запустят. В нашем случае — запустить наш скрипт. Всё, рецепт готов! Остается собрать образ. В терминале, в папке с нашими файлами, выполняем команду docker build -t my-python-app .. Демон начнет послушно выполнять инструкции из Dockerfile, и через мгновение у вас будет ваш собственный образ с гордым именем my-python-app.
Финальный аккорд — запуск. Но если мы просто выполним docker run my-python-app, приложение заработает, но будет недоступно снаружи контейнера. Нам нужно «пробросить» порт. Делается это флагом -p: выполняем docker run -p 5000:5000 my-python-app. Эта команда связывает порт 5000 на вашем локальном компьютере с портом 5000 внутри контейнера. Теперь смело открывайте браузер и переходите на http://localhost:5000. И вот оно, долгожданное сообщение! Ваше приложение, живущее в своем изолированном мирке-контейнере, отвечает вам. Вы это сделали. Вы не просто запустили чужой образ, а создали свой с нуля. И этот практический опыт, этот восторг от «оно работает!» — самый лучший фундамент для того, чтобы двигаться дальше, в бесконечно интересный мир контейнеризации.