Docker – creating your own local CentOS image and Docker container from scratch : создание собственного локального CentOS образа и Docker контейнера с нуля.

Для создания образа будем использовать один из популярных инструментов старых админов – supermin. Те кто на ты с man могу сразу там почитать что к чему)

свободно от авторского текста

supermin [-o OUTPUTDIR] - Имена СПИСОК PKGS ...
supermin [-o OUTPUTDIR] Имена файлов PKG ...

Supermin – это инструмент для создания “программных сущностей или устройств или наборов файлов” – читай как хочешь.
Они вообщем-то крошечные и похожи на виртуальные машины, обычно около 100 КБ в размере, которые создаются полностью на лету за доли секунды когда нужно. Начиная с версии 4 этот инструмент является независимым от дистрибутива и может создавать “супер-сущности” для нескольких популярных дистрибутивов Linux, а добавить поддержку других достаточно просто.

и пару слов о том как он работает ))

Есть два режима использования супермина:

С параметром –names supermin берет список имен пакетов и создает supermin сущность, содержащюю эти пакеты и все зависимости, которые требуются для этих пакетов. В этом режиме supermin обычно требуется доступ к сети потому что, возможно, потребуется проконсультироваться с репозиториями пакетов, чтобы определить зависимости и загрузить пакеты.

И без –names supermin принимает список самих пакетов (т.е. имена файлов уже локально доступных пакетов). Этот пакет должен быть полным и с отсутствием зависимостей за границей набора пакетов, которые вы предоставляете. В этом режиме супермин не требует доступа к сети. Он видит все файлы пакетов сами. Под «пакетом» мы понимаем пакет RPM, DEB и т. д. Имя пакета может быть полным именем (например, “coreutils-8.22-24.el7.x86_64”) или некоторое сокращение (например, “coreutils”). Точный формат имени и допустимые сокращения зависят от менеджера пакетов.

Файловая программная сущность supermin, которая создается в результате, состоит из двух файлов, называемых «hostfiles» и «base.img».
По умолчанию они записываются в текущий рабочий каталог , но если указать опцию -o OUTPUTDIR, то эти файлы будут записаны в этот каталог (обычно этот каталог “supermin.d“, но вы можете называть его как хотите).

Исходя из обобщенного выше, для примера, при создание своего образа с нуля нам уже будет нужна целевая ОС с установленными пакетами.
Итак, устанавливаем 5 версию (если его еще нет в ОС) и создаем рабочий каталог:

# yum install supermin5
$ cd ; mkdir supermin ; cd supermin

Далее готовим минимальное дерево среды с запросом явной установки только одного пакета с зависимостями – yum.
В говорливом режиме мы видим много интересного под капотом ))

$ supermin5 --verbose --prepare yum -o centos8s.
supermin: version: 5.2.1
supermin: rpm: detected RPM version 4.14
supermin: rpm: detected RPM architecture x86_64
supermin: package handler: fedora/rpm
supermin: prepare: yum
supermin: packages specified on the command line:
- yum-4.7.0-18.el8.noarch
supermin: writing centos8s.supermin.d.ovrhqye7/packages
supermin: after resolving dependencies there are 131 packages:
...
Loaded plugins: builddep, changelog, config-manager, copr, debug, debuginfo-install, download, generate_completion_cache, groups-manager, needs-restarting, playground, repoclosure, repodiff, repograph, repomanage, reposync, system-upgrade
DNF version: 4.7.0
...
supermin: renaming centos8s.supermin.d.ovrhqye7 to centos8s.supermin.d

в результате мы получили:

$ tree centos8s.supermin.d
centos8s.supermin.d
├── base.tar.gz
└── packages

0 directories, 2 files

А что же было сделано? Да очень просто! Это команда просто вызывает yum на хосте, получает все зависимости этого пакета из текущих репозиториев и сохраняет в кэш менеджера пакетов на хосте. Затем ищет конфигурационные файлы в пакетах и на основе этого создает дерево директорий с этими файлами конфигурации и упаковывает в архив в указанную директорию. Вот такой индийский стиль редхата для “базового набора” файлов. Файл packages – просто текст с именем упомянутого в выхове пакета.

Как ни странно, по логике, дальше нам нужно все это разархивировать в отдельный каталог в так называемом режиме “build в chroot” и туда все установить. Мы же помним как это) Тихий смех в зале среди старых админов из-за странных действий.

–verbose – просто выводить больше информации;
–build – собственно сам глагол действия;
–include-packagelist – включать ли текстовый файлик в корень файлового дерева установленных пакетов;
–format – в каком режиме или каким путем установить (определить /);
–centos8s.appliance.d – где это все будет установлено как финальная среда/дерево через chroot

$ supermin --verbose --build --include-packagelist --format chroot centos8s.supermin.d -o centos8s.appliance.d
supermin: version: 5.2.1
supermin: rpm: detected RPM version 4.14
supermin: rpm: detected RPM architecture x86_64
supermin: package handler: fedora/rpm
supermin: build: centos8s.supermin.d
supermin: reading the supermin appliance
supermin: build: visiting centos8s.supermin.d/base.tar.gz type gzip base image (tar)
supermin: build: visiting centos8s.supermin.d/packages type uncompressed packages
supermin: mapping package names to installed packages
supermin: resolving full list of package dependencies
supermin: build: 131 packages, including dependencies
supermin: build: 29592 files
supermin: build: 29592 files, after matching excludefiles
supermin: build: 29592 files, after adding hostfiles
supermin: build: 14005 files, after removing unreadable files
supermin: build: 14037 files, after munging
supermin: chroot: creating /packagelist
supermin: renaming centos8s.appliance.d.8ncgbjln to centos8s.appliance.d

О! Теперь в новом каталоге все базовое дерево каталогов Linux в которые уже установлены все новые пакеты с зависимостью, а не только каркас с файлами конфигов. Обратим внимание на размер и идем дальше.

Кстати, для тех, кому не нужно всех локалей, установленных по умолчанию, могжно почистить и оставить только английскую локализацию, тем самым выиграв около 50 мегабайт

$ du -hs *
334M centos8s.appliance.d
176K centos8s.supermin.d

$ ( cd centos8s.appliance.d/usr/share/locale ; find . -type d -not -regex 'en_US' -not -regex '.' -not -regex '..' -print | xargs /bin/rm -rf )
$ ( cd centos8s.appliance.d/usr/share/i18n/locales ; find . -type d -not -regex 'en_US' -not -regex '.' -not -regex '..' -print | xargs /bin/rm -rf )

И надо добавить переменную для yum (иначе потом просто не будет работать yum в контейнере)

$ mkdir -p centos8s.appliance.d/etc/yum/vars
$ echo 8-stream > centos8s.appliance.d/etc/yum/vars/stream
$ echo 8 > centos8s.appliance.d/etc/yum/vars/releasever

Теперь все готово упаковать созданное дерево каталогов в tar архив пригодный для экспорта в docker как базовый слой:

$ tar --numeric-owner -cpf centos8s_zero.tar -C centos8s.appliance.d .
$  ls -l
total 314788
drwxr-xr-x. 17 U4710S U4710S      4096 Aug 11 23:04 centos8s.appliance.d
drwxr-xr-x.  2 U4710S U4710S      4096 Aug 11 20:38 centos8s.supermin.d
-rw-rw-r--.  1 U4710S U4710S 322334720 Aug 11 23:40 centos8s_zero.tar

И теперь экспортируем tar архив как образ для контейнеров docker в локальной системе (пользователь должен быть в группе docker для доступа к сокету docker демона):

$ sudo cat centos8s_zero.tar | docker import - local/centos8s-zero
sha256:4f3a0bfdbf5fed5193b9e7c8b51bdc9f495ae918381fa8ee9a8eb6b679b79191

$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
local/centos8s-zero latest 9a0d60008825 14 seconds ago 312MB

В данный момент мы получили в локальной системе минимальный образ файловой сиcтемы для Centos 8 Stream.
Теперь можно запустить интерактивный контейнер на основе этого образа и выполнить остальную установку пакетов до минимального набора.
Мы помним, что контейнеры это не виртуальные системы, а просто изоляции отдельных процессов с изменными namespace для файлового и сетевого пространства. В остальном они равноправно живут под управлением одного ядра как и все остальные процессы не заключенные в изоляцию докером. Поэтому, даже этот контейнер можно не убивать при выходе, а просто отключить стандартные дескрипторы ввода/вывода Вашего терминала от изолированного процесса (Ctrl P+Q). Потом, при необходимости, можно снова “подцепиться” через docker container attach.

$ sudo docker run -it local/centos8s-zero /bin/bash
bash-4.4# yum clean all
0 files removed
bash-4.4# yum makecache
CentOS Stream 8 - AppStream 7.9 MB/s | 31 MB 00:03
CentOS Stream 8 - BaseOS 3.3 MB/s | 41 MB 00:12
CentOS Stream 8 - Extras 64 kB/s | 18 kB 00:00
CentOS Stream 8 - Extras common packages 12 kB/s | 6.8 kB 00:00
Metadata cache created.
bash-4.2#
bash-4.2# yum -y install rpm rpm-build-libs rpm-libs python3-rpm
bash-4.2# yum -y install cracklib cracklib-dicts binutils
bash-4.2# yum -y install dbus-libs device-mapper device-mapper-libs file
bash-4.2# yum -y install iproute iptables iptables-services iputils groff-base
bash-4.2# yum -y install less libcroco libgomp libunistring
bash-4.2# yum -y install vim rootfiles
bash-4.2# yum -y install openssh-server
bash-4.2#

Теперь когда я вышел из контейнера – он остановился или нет?

Так или иначе Вы можете снова запустить контейнер, если он остановился и подключиться.

На следующем шаге я создаю образ “обновленной” файловой системы в новом архиве для создания будущих образов на его основе. Я просто экспортирую текущее состоние файловой системы контейнера в tar архив.

$ docker ps --all
CONTAINER ID   IMAGE                 COMMAND       CREATED          STATUS          PORTS     NAMES
6ea89e7d69c3   local/centos8s-zero   "/bin/bash"   31 minutes ago   Up 31 minutes             adoring_jemison

$ docker export 6ea89e7d69c3 > centos8s_minimal.tar

Сейчас я готов создавать новые образы на базе данного архива/образа и использовать их как базу для новых прикладных образов (с новыми слоями) для новых контейнеров на базе CentOS 8 Stream.
Наиболее правильным шагом будет создание контенейра с доступом из сети, так как это возможно основной смысл использования таких любых сетевых процессов/микросервисов в изоляции, как атомарные сущности, так и в рамках минимальной среды ОС.
Для этого подойдет официальный пример для получения командной оболочки в контейнере с sshd процессом НО! с нашими изменениями

Импортируем новый образ.

$ cat centos8s_minimal.tar | docker import - local/centos8s-minimal
sha256:6dac80d36138f1176ce2f6232375dcdd1a69880a7e8b034e1abf75f9cddf79a3

$ docker images 
REPOSITORY               TAG       IMAGE ID       CREATED              SIZE
local/centos8s-minimal   latest    6dac80d36138   51 seconds ago       877MB
local/centos8s-zero      latest    9a0d60008825   4 days ago           312MB

Создаем докер файл для образа где будет запускаться ssh демон и берем за основу наш подготовленный minimal в отдельной директории

$ mkdir centos8s_with_ssh && cd centos8s_with_ssh
$ cat dockerfile-centos8s-minimal-with-sshd
FROM local/centos8s-minimal
RUN mkdir /var/run/sshd
RUN ssh-keygen -A
RUN echo 'root:12345678' | chpasswd
EXPOSE 22
CMD ["/sbin/sshd", "-D"]
$ docker build --force-rm=true --file=dockerfile-centos8s-minimal-with-sshd --tag=local/centos8s-minimal-with-sshd .
[+] Building 9.1s (8/8) FINISHED                                                                                                                                                   
 => [internal] load build definition from dockerfile-centos8s-minimal-with-sshd                                                                                               1.0s
 => => transferring dockerfile: 266B                                                                                                                                          0.0s
 => [internal] load .dockerignore                                                                                                                                             1.1s
 => => transferring context: 2B                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/local/centos8s-minimal:latest                                                                                                      0.0s
 => [1/4] FROM docker.io/local/centos8s-minimal                                                                                                                               0.3s
 => [2/4] RUN mkdir /var/run/sshd                                                                                                                                             1.8s
 => [3/4] RUN ssh-keygen -A                                                                                                                                                   2.3s
 => [4/4] RUN echo 'root:12345678' | chpasswd                                                                                                                                 2.1s 
 => exporting to image                                                                                                                                                        1.1s
 => => exporting layers                                                                                                                                                       1.1s
 => => writing image sha256:5927624d3cdf7db302f6cf770e12bec4bb5fbfd67514b3b2d4dc1a236b533078                                                                                  0.0s
 => => naming to docker.io/local/centos8s-minimal-with-sshd

Смотрим что вышло

 docker images -a
REPOSITORY                         TAG       IMAGE ID       CREATED          SIZE
local/centos8s-minimal-with-sshd   latest    5927624d3cdf   2 minutes ago    877MB
local/centos8s-minimal             latest    6dac80d36138   27 minutes ago   877MB
<none>                             <none>    3dfb6c2979e5   27 minutes ago   0B
local/centos8s-zero                latest    9a0d60008825   4 days ago       312MB


docker images -a --digests
REPOSITORY                         TAG       DIGEST    IMAGE ID       CREATED          SIZE
local/centos8s-minimal-with-sshd   latest    <none>    5927624d3cdf   2 minutes ago    877MB
local/centos8s-minimal             latest    <none>    6dac80d36138   27 minutes ago   877MB
<none>                             <none>    <none>    3dfb6c2979e5   27 minutes ago   0B
local/centos8s-zero                latest    <none>    9a0d60008825   4 days ago       312MB

Запускаю контейнер на базе этого образа. Следует отметить, что docker вообще “не дружит с безопасностью” и без каких-либо ограничений может вмешаться в работу уже настроенного файервола и создать не мало проблем, если Вы запустите его на боевом сервере, пытаясь совместить эту реализацию контейнеров с уже работающей продуктовой системой.
И снова смех в зале у старых админов.
Поэтому если Вы еще не завербованы в юные убунтоиды, то стоит в приказном порядке указать демону о недопустимости такого поведения.
А еще лучше выделить для docker отдельный зоопарк виртуальных машин-песочниц для изоляции процессов, что и убережет Вас от сюрпризов.
Пока же просто добавим параметр
–iptables=false в файл конфигурации /etc/sysconfig/docker и перезапустим демона docker.

docker run -d --name centos8s-sshd-empty local/centos8s-minimal-with-sshd
3f14b88438ba34a4ab0a121a5f38465824ee38fe0e565598bf31f5e1c455848d
docker ps -a
CONTAINER ID   IMAGE                              COMMAND           CREATED          STATUS                      PORTS     NAMES
3f14b88438ba   local/centos8s-minimal-with-sshd   "/sbin/sshd -D"   35 seconds ago   Up 32 seconds               22/tcp    centos8s-sshd-empty
 docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 3f14b88438ba
172.17.0.2

Попробуем найти ssh порт на этом IP на мосту docker0 на узле где запущен контейнер с sshd

# nmap 172.17.0.2
Starting Nmap 7.92 ( https://nmap.org ) at 2023-08-16 01:45 +03
Nmap scan report for 172.17.0.2
Host is up (0.000027s latency).
Not shown: 999 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
MAC Address: 02:42:AC:11:00:02 (Unknown)

Nmap done: 1 IP address (1 host up) scanned in 0.39 seconds

И пробуем подключиться к этому демону sshd в его изоляции

# ssh 172.17.0.2
The authenticity of host '172.17.0.2 (172.17.0.2)' can't be established.
ECDSA key fingerprint is SHA256:B7v5o7snKYMPseZMw55QX/L25S6PR3fHlo6gn6izi4M.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '172.17.0.2' (ECDSA) to the list of known hosts.
root@172.17.0.2's password: 
[root@3f14b88438ba ~]# 

О том как пробросить сетевые порты или добавить иные правила в файервол для сетевой доступности приложений в изолированных в контейнере namespace думаю уже обьяснять не надо старым админам. Дерзайте на работе!

Scroll to top