Часто бывает такое, что нужен к примеру самый новый PHP или другой софт на сервере, плюсом требуется некоторая кастомизация системы и вы хотите чтобы ваше приложение заработало на другом сервере, у другого человека, чтобы он просто развернул контейнер и всё. Иногда нужны пропатченные библиотеки, а на другом компе это может поломать другие приложения.
Уже давно очень популярна контейнеризация на основе Docker, т.к. она решает все эти вопросы, каждое приложение запускается в своей виртуальной системе со своим окружением - системные библиотеки, дополнительный софт, настройки самой системы и многое другое.
Сегодня попробуем немного поэкспериментировать в Docker!
Все опыты будут выполняться на Debian 10, актуально и для убунты и для всего остального...
Для начала установим докер
apt install docker
В докере есть образы, а есть контейнеры, контейнер это так сказать экземпляр системы из образа, т.е. контейнер использует образ.
Если нужного для контейнера образа нет в системе, то он сам скачается...
Для большинства задач достаточно стандартных образов, этот блог на таком же и развернут, была статья раннее.
Но иногда нужно что-то особенное, для этого я решил углубить свои познания.
Самый минимальный образ это alpine. Но иногда стабильнее будет работать к примеру Debian или Ubuntu, т.к. в Alpine нет glibc.
Скачать образ можно так
docker pull ubuntu
А посмотреть список образов в системе вот так
docker images
Удалить образ можно так
docker rmi ubuntu
Создадим контейнер, в который сразу провалимся, команда run создает контейнер и выполняет
docker run -it ubuntu bash
По умолчанию у нас внутри нет ни nano, ни vim и даже apt install не работает, нужно выполнить например apt-get update
но только в докере такая ситуация, как только вы выйдите из консоли и по новой запустите контейнер, все пропадет...
Чтобы хоть что-то сохранять, нужно или образ свой лепить на основе этого со своими модификациями, или пробрасывать свою папку внутрь контейнера, то что будет писаться в виртуальной среде будет и в папке...
docker ps
- выводит запущенные контейнеры, а docker ps --all
все, в том числе и остановленные
Если мы в команде run
не указали никаких параметров, то докер сам придумывает имена контейнерам, я пару раз запустил баш в убунте и у меня два контейнера
root@debian-testing:~# docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f877b8cfec0d ubuntu "bash" 5 minutes ago Exited (0) 4 minutes ago frost
y_engelbart
543a77036c96 ubuntu "bash" 9 minutes ago Exited (0) 5 minutes ago nosta
lgic_shannon
Попробуем не создавать теперь, а просто снова запустить созданный контейнер
Если бы там был демон, то можно было сделать например
docker start f877b8cfec0d
Но у нас там баш, остается наверно только снести контейнеры с пустым башем
docker rm f877b8cfec0d 543a77036c96
Если нам нужно выполнить команду в определенном образе, но не хранить этот контейнер, он у нас не демон, то можно вот так, на пример php-cli
docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp php:7.4-cli php your-script.php
-v
- это прокинуть текущую папку в виртуальную внутрь контейнера /usr/src/myapp
-w
- сразу перейти в эту директорию
Мне захотелось слепить свой образ, который будет компактным, ничего лишнего и с моими настройками, php.ini, еще чтобы там изначально были некоторые библиотеки, как например клиент для RabbinMQ, небольшие патчи локализации и некоторые вещи, которые я настраиваю в приложении, хотелось бы настроить в php.ini... Плюсом ко всему, приложение будет заперто от всех контейнеров. Хочу сделать сразу cli версию для фоновых обработчиков и fpm, а на хостовом компе будет nginx все это проксировать.
Попробуем создать свой образ, для начала возьмем то что есть в официальном образе, немного перелопатим и попробуем...
Желательно создавать из пустой папки, положить в нее только Dockerfile
собираем образ php-mp (со своим префиксом, чтобы не пересекаться с официальными образами)
docker build -t php-mp .
Всё собралось, для php-fpm нет понятия document-root, он просто запускает тот файл, который передан из nginx, минимально тестово можно сделать так
...
root /var/www/html
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass localhost:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
...
И запустить, предварительно положив в /var/www/html index.php
с содержимым типа <?php phpinfo();
docker run -d -p 9000:9000 -v /var/www/html:/var/www/html -w /var/www/html php-mp
Всё получилось...
А теперь самое интересное, посмотрим список образов
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
php-mp latest 5b8c7cf24f1e 58 minutes ago 477MB
php 7.4-cli 285d825479be4 7 days ago 405MB
ubuntu latest 46e2eef94cd6b 3 weeks ago 73.9MB
alpine 3.12 a24bb4013296 3 months ago 5.57MB
Интересная картинка, я собирал на основе alpine, который 5 мегабайт, но у меня получился образ на 477 мегабайт, официальный php образ тоже не маленький, 405 мегабайт... В чем же дело?
Попробуем посмотреть историю изменения нашего образа
# docker history php-mp
IMAGE CREATED BY SIZE
4b8c7cf24f1e /bin/sh -c #(nop) CMD ["php-fpm"] 0B
322041d76f21 /bin/sh -c #(nop) EXPOSE 9000 0B
1a6ac1349fb4 /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
afc59ced57ba /bin/sh -c set -eux; cd /usr/local/etc; cp p… 25.4kB
d4ba7843c37a /bin/sh -c cd /; runDeps="$( scanelf --neede… 51.6kB
6786fe77ed30 /bin/sh -c export CFLAGS="$PHP_CFLAGS" CPPFL… 78.6MB
1104663835d0 /bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -… 0B
7267e5e725a3 /bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-… 0B
78e9c4a80b2a /bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-pr… 0B
3c59c17a324b /bin/sh -c set -eux; apk add --no-cache --vi… 266MB
7a0e31d9f804 /bin/sh -c tar -Jxvf /usr/src/php.tar.xz -C … 114MB
d7cb999958ae /bin/sh -c mkdir -p /usr/src/php; cd /usr/sr… 10.3MB
7d8970c4f4fe /bin/sh -c #(nop) ENV PHP_URL=https://www.p… 0B
9240b0b75cb6 /bin/sh -c set -eux; mkdir -p "$PHP_INI_DIR/… 0B
663500a7b308 /bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/loca… 0B
0396dfa3d284 /bin/sh -c set -eux; addgroup -g 82 -S www-d… 4.68kB
1a21cc439955 /bin/sh -c apk add --no-cache ca-certificate… 2.75MB
a24bb4013296 /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> /bin/sh -c #(nop) ADD file:c92c248239f8c7b9b… 5.57MB
Получается так, что если скачать архив в одной команде RUN
то это сохранится в один слой, если в слудующей команде компиляция, то это еще в один слой, если потом в следующем RUN
подчищаем все, то по факту размер образа не уменьшается.
Какие могут быть решения для уменьшения размера образа?
- Собирать с опцией
--squash
в экспериментальном режиме, где все слои должно склеить, но у меня это отработало не совсем корректно и образ не особо уменьшило, видимо повторно этот же докерфайл собирает слишком поверхностно и надо сперва удалить раннее собранный, не стал копаться тут... - Можно весь скрипт в один RUN, там где скачал и распаковал файлы, в этой же команде RUN и чистить следы, удалять лишние пакеты требуемые для сборки...
- Воспользоваться крутой утилитой Docker-slim
Docker-slim цменьшаем размер образа Docker
Попробуем уменьшить размер образа
wget https://downloads.dockerslim.com/releases/1.32.0/dist_linux.tar.gz
tar -zxvf dist_linux.tar.gz
cd dist_linux/
./docker-slim
Набираем команду в утилите
build --target php-mp
В итоге в списке образов появится такой же, но с префиксом .slim
Было 477 мегабайт, стало 25, круто ведь, проверил, все работает корректно.
Теперь о том, как выгрузить этот образ, ведь он будет для наших кастомных задачек, публиковать на докер-хабе не собираюсь.
docker save php-mp.slim > myphp.tar
А на другом сервере
docker load --input myphp.tar
Получается мы можем забилдить контейнеры со своими конфигами, своими опциями сборки и модулями, скопировать на все сервера 25 мегабайтный архив, развернуть, запустить и все будет работать, никаких десятков пакетов в apt-get install
, никаких монотонных правок php.ini и my.cnf, просто распаковать в докер, запустить и оно работает везде одинаково, везде с одним окружением... Магия!
Эта статья больше ознакомительная и шпаргалка для себя на будущее, скопировать готовый докер и запустить особо ума не надо, моя дальнейшая цель поработать с опциями, кое-что включить, а кое-что выключить, скачать сторонние библиотеки и собрать, больше не будет у меня такого что на новом сервере ставлю другую версию ПО на другой версии системы и потом спустя время ловлю что то тут то там забыл что-то настроить или установить, докер это круто.
Предварительная подготовка докерфайла
У любого человека при виде такого массивного Dockerfile возникнет вопрос - как же это вообще создать, чтобы не было ошибок, все просто, сперва запускаем консоль, предварительно сразу прокинув нужный порт, например 9000
docker run -it --rm -p 9000:9000 -v /var/www/html:/var/www/html alpine:3.12 sh
Начинаем в нем выполнять команды типа apk add
, apk del
, make
и все такое, в конце запускаем например php-fpm и тестируем из браузера, nginx ведь уже настроен, только остановите предварительно прошлый контейнер, который использует 9000 порт...
Немного про apline...
Устанавливать пакеты через apk add
, удалять apk del
Если использовать параметр --no-cache, то кеш не будет создаваться и заполнять место, параметр --virtual это вообще интеерсная штука, после параметра virtual можно указать некое имя и потом по этому имени всё это снести что этой командой ставилось, например
apk add --no-cache --virtual kokoko nano glib zip ...
adk del kokoko
Это очень удобно, можно снести все что ставилось для компиляции и больше не требуется.
В ходе кастомной сборки у вас наверняка будут возникать моменты, когда какой-то библиотеки нет в системе, вы будете доустанавливать пакеты, поэтому во втором окне рядом откройте блокнот и актуализируйте команду установки, добавляя туда новые пакеты...
Когда все получилось, все действия в блокнотике отмечены, все команды и параметры, составляем наш докер-файл, и билдим, с первого раза может не получиться, например в одном RUN команды через ;
, а все переносы экранируются \
, когда многотекстовый документ надо заполнить, то так не получится:
echo 'f
fff
fff
gggg' > file.txt
Докер не понимает открытой строки, можно делать так как в примере выше было
Дальше все зависит от вашей фантазии, помните, эксперименты и опыты наше всё!