📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Docker за 20 минут

suchkov tech21:42

Transcription

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

В описании к видео будет ссылка на Telegram-канал, где вы найдёте все использованные команды, а также краткий конспект этого видео и ссылку на репозиторий с кодом, чтобы вы могли повторить всё то, что мы рассмотрим здесь самостоятельно. Не забудьте ставить лайки и задавать вопросы в комментариях. Ну а мы начинаем.

Что такое Docker? Давайте начнём, собственно, с того, что такое Docker. Если совсем просто и кратко, Docker — это инструмент, по сути, обычная программа, которая упрощает процесс разработки, запуска и распространения программного обеспечения. Но неужели для всех этих операций нужен отдельный инструмент? Ведь можно установить, запустить и удалить программу, не прибегая к помощи никакого Docker. Давайте разбираться.

Проблемы, которые решает Docker. Давайте рассмотрим подробнее, какие проблемы решает Docker и почему это действительно такой важный и полезный инструмент. Представьте, что мы ведём разработку веб-приложения, скажем, для AI-генерации изображений, вроде DaVinci. Оно состоит из веб-сервера, веб-клиента, сервиса генерации изображений, сервиса обработки изображений и ещё множество других систем. Каждый из компонентов требует наличие определённых библиотек и может зависеть от других. А зависимости эти могут переплетаться. Разные части нашего сервиса могут зависеть от одних и тех же библиотек и компонентов. Что делать, если одно приложение требует обновления зависимости, а другое приложение работает на более старой версии? С помощью Docker и контейнеризации каждого приложения легко решить эту проблему. Каждый контейнер изолирует своё окружение, и в каждом таком контейнере находятся свои версии библиотек, никак не конфликтуя между собой.

Благодаря изоляции этих контейнеров, другой серьёзный промо при разработке программ является переносимость между различными операционными системами. Если вы используете macOS или Windows для работы, то при деплое на удалённый сервер под Linux часто то, что работало локально, не запускается на сервере. Особенно явно это заметно при использовании компилируемых языков программирования. Если скомпилировать программу Go на локальной машине под Mac, то эта же версия не будет работать на Linux или Windows. Шагом в сторону большей переносимости программ между системами своё время был язык программирования Java со своей виртуальной машиной и девизом "Напиши один раз, запускай везде". Скомпилированный в байт-код, программа запускается поверх виртуальной машины и работает одинаково на любых платформах, где присутствует виртуальная машина Java. Docker и контейнеры по сути позволяют делать то же самое, притом не только для программ, написанных на других языках программирования, но и запускать целые сервисы в этих контейнерах на различных операционных системах, достаточно иметь лишь установленный Docker на этих системах.

Ну и, конечно, во всей красе Docker проявляет себя при разработке и развёртывании микросервисов. Совместно с Kubernetes управлять десятками и сотнями сервисов и производить непрерывное развёртывание становится простой задачей и описывается обычными конфигурационными файлами.

Давайте теперь приступим к изучению непосредственно Docker и начнём мы с обзора основных понятий и концепций.

Основные понятия Docker. Для начала работы с Docker важно понимать основные компоненты. И первое — это Docker Engine. По сути своей является клиент-сервер приложением, которое состоит из непосредственно сервера, который создаёт образы, контейнеры, тома и управляет ими, а для взаимодействия с этим демоном и инструмент командной строки для взаимодействия с сервером через предоставляемый API.

Давайте теперь поговорим про основные компоненты, с которыми работает Docker. Это, в первую очередь, Dockerfile — файл с инструкциями о том, как создать образ. Образ — это шаблон, на основании которого создаются и запускаются контейнеры, которые представляют из себя просто набор файлов, директорий, символических ссылок и необходимых инструментов. И непосредственно Docker-контейнер — приложение, построенное на основе Docker Image, которое исполняется в закрытой файловой системе со своим пространством процессов.

Проще всего понять это на примере из повседневной жизни. Представьте, что мы хотим приготовить печенье определённой формы и размера. Первое — сделать это, нарисовать чертёж того, как будет выглядеть форма для запекания этих печений. Дальше, на основании этого чертежа, мы должны изготовить саму форму для запекания. И уже с помощью этой формы для запекания мы можем изготовить столько печенья, сколько пожелаем. В рассмотренном примере чертёж — это Dockerfile, форма для запекания — это Image, печенье — это контейнер. Изготовление формы для запекания на основе чертежа — это билд образа, а процесс запекания — это создание и запуск контейнера. Рассмотренная последовательность действий является типичной при работе с Docker, но об этом поговорим подробнее далее.

А пока займёмся установкой Docker.

Установка Docker. Проще и удобнее всего установить себе Docker Desktop. Docker Desktop включает в себя всё необходимое для работы с Docker. Помимо стандартных Docker Engine и CLI, в Docker Desktop входят и другие полезные инструменты, включая удобный графический интерфейс. Устанавливается Docker одинаково просто на Mac, Windows или Linux. Достаточно просто скачать файл и проследовать стандартные установки. Проверить, что Docker установлен, можно с помощью команды `docker version`. В ответ видим версию Docker, значит, всё ок. Запускается Docker Desktop как обычная программа.

При установке Docker Desktop на своём компьютере у вас будет также доступен удобный графический интерфейс для работы с Docker. Мы в первую очередь будем работать с Docker из командной строки, но для наглядности также будем заглядывать и сюда. Тут можно увидеть работающие и остановленные контейнеры. Пока у нас пусто. Образы как локально доступны, так и в вашем удалённом репозитории. Тома, истории сборок и многое другое.

Сейчас же перейдём в консоль и запустим наш самый первый контейнер.

Базовые команды для работы с Docker. Все команды Docker выполняются инструментом командной строки. А для запуска контейнера нужно просто выполнить команду `docker run` и указать метку образа. Давайте выполним команду `docker run ubuntu`. Эта команда запустит контейнер на основе самой последней метки образа Ubuntu `latest`. А так как это наш первый запуск и образ Ubuntu у нас на компьютере нет, то она к тому же и загрузит файлы образа из хранилища Docker Hub. Правда, этот контейнер завершит свою работу сразу же после запуска, так как внутри контейнера нет работающего приложения. Такова логика работы контейнеров.

Чтобы этого избежать, можно запустить контейнер в интерактивном режиме. Для этого передадим ключ `-i`. В таком случае к контейнеру присоединяется консоль ввода-вывода, и контейнер будет продолжать работать. Для эмуляции стандартного терминала нужно также передать ключ `-t`. Давайте напишем `docker run -it ubuntu`. Теперь в командной строке можно работать с терминалом так же, как будто это реальная система Ubuntu. Например, можем вывести список директорий командой `ls`. Пока интерактивный режим активен, контейнер будет работать, но как только мы выйдем из этого режима, контейнер остановится. Выйти из контейнера можно с помощью команды `exit`. Давайте напишем `exit`. Всё, мы вышли из контейнера.

Посмотреть работающие контейнеры можно командой `docker ps`. Вот сейчас мы видим, что никаких работающих контейнеров нет. Посмотреть все контейнеры, то есть, в том числе и неработающие, можно с помощью этой же команды, но передать флаг `-a`. Давайте напишем `docker ps -a`. И сейчас мы видим два контейнера со статусом `exited`. Самый первый контейнер завершил свою работу сразу после запуска, и второй, когда мы запустили Ubuntu в интерактивном режиме. В этом выводе отображается также образ, на основе которого был создан контейнер, время запуска и работы, и уникальный идентификатор.

Посмотреть все образы можно с помощью команды `docker images`. Сейчас мы видим Ubuntu с тегом `latest`. Ту же самую информацию можно получить и через графический интерфейс. Сейчас во вкладке "Контейнеры" мы видим эти два контейнера, а во вкладке "Images" видим сам образ Ubuntu.

Вернёмся в консоль. Контейнер можно запустить в так называемом detached режиме, и обычно так и делается. Для этого необходимо передать дополнительный флаг `-d`. Давайте запустим контейнер Ubuntu в таком режиме. Напишем `docker run -d ubuntu`. В ответ мы видим ID созданного контейнера. В этом случае терминал не открывается сразу, а контейнер запускается и работает в фоновом режиме, вне зависимости от наличия в нём работающих процессов. Теперь, введя команду `docker ps`, мы видим этот контейнер. В графическом интерфейсе работающий контейнер отображается зелёным цветом, а остановленный — серым.

Теперь, если мы захотим присоединиться к работающему контейнеру, воспользуемся командой `attach` и передадим идентификатор контейнера. Отсоединиться от работающего контейнера без остановки можно с помощью команды `Ctrl+P` и `Ctrl+Q`. Так мы выйдем из интерактивного режима, не останавливая контейнер. Остановить контейнер можно с помощью команды `stop`. Давайте напишем `docker stop` и идентификатор контейнера. Введём команду `docker ps`, видим, что сейчас у нас нет работающих контейнеров. А вновь запустить можно с помощью команды `start`. Давайте напишем `docker start` идентификатор контейнера. Всё, теперь этот контейнер работает.

Ой, мы научились запускать и останавливать контейнеры на основе готовых, то есть уже созданных до нас образов. Этих знаний уже достаточно для того, чтобы начать пользоваться Docker и экспериментировать с различными образами на своём компьютере, будь то базы данных или веб-сервера, и не опасаться за основную операционную систему. Docker надёжно изолирует всё, что происходит внутри. На сайте Docker Hub можно найти доступные образы операционных систем, баз данных, языков программирования и всего, что может пригодиться вам в работе.

Работа с портами. Окей, предположим, мы разрабатываем веб-сайт, и нам для работы нужен сервер Nginx. Давайте запустим его в Docker-контейнере и сделаем это сразу в detach режиме. Напишем `docker run -d nginx`. Тут также мы не находим образ локально, и происходит скачивание. Окей, контейнер запущен. Давайте проверим, что он действительно работает, с помощью команды `docker ps`. Помимо запущенного ранее контейнера Ubuntu, видим также новый контейнер с Nginx. Видно, что использует восьмидесятый порт. Давайте попробуем обратиться к нему, постучав на этот самый порт в браузере. Видим, что сервер недоступен. Сервер сейчас работает в контейнере, однако доступа до него извне нет, так как требуемый порт не открыт. Открытые или exposed порты — это основной путь взаимодействия между контейнерами.

Давайте остановим текущий контейнер и запустим новый с открытым портом. Для этого напишем `docker stop` и передадим ID нашего контейнера. Окей. И чтобы запустить контейнер с открытым портом, нам нужно передать дополнительно флаг `-p` и указать, какой порт операционной системы мы переадресуем. В том числе и об открытых портах и их переадресации. Теперь, перейдя в браузер по адресу `localhost:8888`, мы увидим приветственную страничку Nginx. То, что мы наблюдали сейчас, является одной из главных особенностей контейнеров. Можно запустить столько разных контейнеров, сколько необходимо. Все они будут надёжно изолированы друг от друга и будут изолировать собственные зависимости, а взаимодействовать между собой и с окружающим миром только через заранее оговоренные открытые порты.

Создание Docker-образов. Запускать готовые образы может быть полезным, но нам, как разработчикам, прежде всего интересно, как создавать новые образы контейнеров, в которых мы будем размещать свои приложения, а затем запускать контейнеры этих образов. Видеть приветственное сервера Nginx не очень полезно. Чаще мы хотим размещать собственное содержимое на веб-сервере, скажем, выводить информацию о продуктах и прочее. Давайте это исправим путём создания собственного образа на базе уже использованного образа.

Создадим папку проекта `docker-nginx` и перейдём в неё. Откроем эту папку в редакторе кода. А давайте создадим собственную статичную HTML-страничку, которую мы хотим видеть как стартовую при обращении к нашему серверу. Для этого создадим файл `index.html` и напишем код. Первое — это поменяем тайтл на "Hello from Docker". Дальше, давайте добавим заголовок `<h1>`. В теле нашего документа напишем "Hello world from". И давайте ещё добавим картинку Docker. Давайте ещё чуть добавим кастомных стилей, сделаем выравнивание по центру и уберём засечки. Окей, всё готово.

Теперь давайте упакуем это в Docker-контейнер. Для этого создадим `Dockerfile`. Это файл, который прямо так и называется `Dockerfile` без всяких расширений. Структура у Dockerfile очень простая. Обычно он начинается с инструкции `FROM`, за которой следует базовый образ. А дальше набор команд с аргументами. В нашем случае базовый образ будет `nginx`. Двоеточие после имени можно указать конкретный тег. Если этого не сделать, то будет выбран `latest` — последний образ. Следующей нашей инструкцией будет установление рабочей директории в контейнере с помощью команды `WORKDIR`. Статичные файлы нам нужно будет поместить в папку `html` в директории `/usr/share/nginx/html` в контейнере. Соответственно, нам нужно написать `WORKDIR /usr/share/nginx/html`. Ну и дальше нам остаётся только скопировать файл `index.html` в эту директорию. Для этого воспользуемся командой `COPY`, указателем на рабочую директорию служит точка, поэтому пишем `COPY index.html .`. Наш Dockerfile готов.

Теперь перейдём в терминал и создадим образ на основании этого файла. Для этого воспользуемся командой `build`. Пишем `docker build . -t nginx-hello-world`. Точка, так как Dockerfile находится в той же директории, откуда мы выполняем команды. И дальше вводим тег или название нашего образа. Назовём его `nginx-hello-world`. В логах построения образа видны шаги, которые были произведены. Сначала был использован базовый образ `nginx` версии `latest`. Дальше была проставлена рабочая директория, и в конце был скопирован файл `index.html` в эту рабочую директорию. Давайте посмотрим на наши образы. Сейчас введём `docker images`. Видим, что помимо скачанных ранее образов Ubuntu и Nginx, теперь присутствует также только что созданный нами образ `nginx-hello-world`. Тег у него `latest`, так как при сборке мы не указали конкретную версию.

Давайте теперь запустим контейнер на основании этого нашего образа и переадресуем порт. Нам хватило буквально пары строк кода, чтобы упаковать нашу веб-страницу. Обратите внимание, что у нас сейчас запущено параллельно два сервера Nginx на нашей системе: один на порту `9999`, а другой на порту `8888`, и они никак не конфликтуют между собой благодаря изоляции Docker-контейнеров.

Давайте теперь рассмотрим чуть более сложный пример. Напишем собственное веб-приложение на Go, которое также будет приветствовать нас. Для этого создадим новую директорию, напишем `docker-go` и перейдём в эту директорию. И также откроем её в VS Code. Давайте напишем само приложение. Для этого создадим файл `main.go`. Пишем `package main`. Нам понадобится пакет `fmt` и `net/http`, поэтому напишем `import ("fmt", "net/http")`. И напишем функцию `main`, то есть входную точку нашего приложения. В ней добавим функцию-обработчик запросов по корневому пути. Это будет функция `helloWorld`. И запустим наш сервер на порту `8080`. И опишем теперь саму функцию-обработчик. Пишем `func helloWorld(w http.ResponseWriter, r *http.Request)`. Сигнатура: первым аргументом мы принимаем `respWriter` и сам `request`, но сам `request` нам не нужен. И внутри напишем `fmt.Fprintf(w, "Hello from Go in Docker!\n")`. И добавим смайлик. Код готов.

Давайте упакуем теперь это приложение в Docker. Также создадим `Dockerfile`. В нём укажем базовый образ. Базовым образом у нас будет `golang:1.21.2`. А дальше, давайте установим рабочей директории папку `app` в контейнере. И в неё будем копировать исходный код и из неё будем запускать наше приложение. Скопирую файл `main.go` уже известной нам командой `COPY`. Как видим, до этого этапа Dockerfile по структуре очень похож на прошлый Dockerfile. Но теперь нам недостаточно просто скопировать исходный код программы, необходимо скомпилировать его в исполняемый файл и запустить этот самый исполняемый файл. Для этого нам понадобятся две новые команды. Первая из них — это команда `RUN`, в ней мы можем прописать, какие действия нам необходимо сделать при сборке образа. В нашем случае нам нужно скомпилировать программу. Давайте сделаем это. Напишем `RUN go build -o hello-go main.go`. Ну и после компиляции нам нужно запустить эту программу. Делается это с помощью команды `CMD`. Дальше за этой командой следует массив аргументов командной строки для выполнения. Нам нужно просто запустить файл `hello-go` без дополнительных аргументов, поэтому пишем `CMD ["./hello-go"]`. Вот и всё. На этом наш Dockerfile готов.

Давайте теперь уже привычными нам командами соберём образ и запустим контейнер. Давайте проставим тег нашему образу, скажем, `1.0`. `docker build -t go-hello:1.0 .` В логах видим также шаги по построению нашего образа. Билд завершён. Давайте посмотрим на наши образы с помощью команды `docker images`. Видим образ `go-hello` с тегом у него `1.0`. Давайте запустим контейнер на базе этого образа. `-p 5555:8080` переадресуем на порт `8080` внутри контейнера. `go-hello:1.0`. Видим контейнер с программой на Go среди других работающих контейнеров. И перейдём в браузер. Видим, что всё работает, как и ожидалось. То есть, больше не нужно задумываться о переносимости бинарных файлов между платформами и сборки приложения под каждую необходимую архитектуру. Обо всём этом позаботится Docker.

Резюме. Мы рассмотрели базовые понятия и команды для работы с Docker. Но для тех, кто только знакомится с этим инструментом, и это могло показаться сложным и запутанным, рекомендую самостоятельно повторить всё то, что мы проделали с вами здесь. Краткий конспект, базовые команды и ссылку на GitHub-проект вы найдёте в описании к этому видео. Попробуйте также поэкспериментировать и создать что-то новое самостоятельно. А в следующем видео мы рассмотрим более подробно работу с Docker Compose, удалёнными репозиториями, томами и многоступенчатую сборку на различных языках программирования, а также альтернативы Dockerfile. Подписывайся, чтобы ничего не пропустить. Как всегда, спасибо за просмотр и до встречи в следующем видео.