- PVSM.RU - https://www.pvsm.ru -

В статье рассматривается пошаговое создание образов «с нуля» для контейнерного движка Podman. Внутрь контейнера «упакована» база данных Oracle Database 21c Express Edition. И всё это отечественной операционной системе РЕД ОС.
*2 контейнера
Так всё-таки зачем собирать собственный образ, когда на docker.io уже существует образ gvenzl/oracle-xe [1] с несколькими миллионами скачиваний?
Это моё первое знакомство с контейнерами, хотелось разобраться, как работает контейнерный движок Podman в rootless-режиме и среду выполнения контейнеров в целом, а не просто бездумно копировать нагугленные команды;
Получше узнать устройство ядра Linux в контексте пространств имён и изоляции работающих контейнеров;
Просто было весело попробовать реализовать специфичные требования (сборка образа «с нуля», распространение образов без регистров и реестров).
Начнём.
Чтобы работать с контейнерами, очевидно, что нужно сначала установить контейнерный движок. К счастью, в репозитории ОС уже есть нужный пакет, устанавливаем:
dnf search podman
podman.x86_64 : Manage Pods, Containers and Container Images
dnf install podman
В дальнейшем будет использоваться следующее окружение:
uname -r; cat /etc/redos-release; podman --version
6.1.52-1.el7.3.x86_64
RED OS release MUROM (7.3.5) DESKTOP
podman version 4.9.5
Для запуска контейнера нам потребуются так называемые subuid:
usermod --add-subuids 200000-265535 --add-subgids 200000-265535 user
где user — имя пользователя, под которым будут запускаться контейнеры.
Так как на хосте включён и используется selinux, то необходимо посматривать его логи. Экспериментальным путём было выяснено, что достаточно этих двух команд, чтобы из логов исчезли все ошибки доступа:
setsebool -P virt_sandbox_use_netlink 1
setsebool -P domain_kernel_load_modules 1
Первая команда, как я подозреваю, разрешает контейнерам использовать сеть, вторая убирает ошибку при установке Oracle Database 21c Express Edition (далее — XE).
На этом права рута на хосте больше не потребуются.
Один из файлов конфигурации Podman’а в rootless-режиме хранится в ~/.config/containers/containers.conf. Может потребоваться явно указать часовой пояс:
[containers]
tz = "local" # значение по умолчанию
Хранилище Podman’а в rootless-режиме, на мой взгляд, находится не в очень удачном месте (в скрытой директории ~/.local/), так это поведение можно изменить через файл конфигурации ~/.config/containers/storage.conf:
[storage]
driver = "overlay"
graphroot = "/home/user/.local/share/containers/storage"
#runroot = ...
NOTE: потребуется перемаркировать файлы метками selinux после перемещения хранилища в другое место. Подробнее: в man containers-storage.conf.
graphroot=""
container storage graph dir (default: "/var/lib/containers/storage")
Default directory to store all writable content created by container
storage programs. The rootless graphroot path supports environment
variable substitutions (ie. $HOME/containers/storage). When changing
the graphroot location on an SELINUX system, ensure the labeling
matches the default locations labels with the following commands:
# semanage fcontext -a -e /var/lib/containers/storage /NEWSTORAGEPATH
# restorecon -R -v /NEWSTORAGEPATH
In rootless mode you would set
# semanage fcontext -a -e $HOME/.local/share/containers NEWSTORAGEPATH
$ restorecon -R -v /NEWSTORAGEPATH
С занудным введением покончено, начинается веселье. Хоть это моё первое знакомство с контейнерами, но у меня было до этого некоторое представление, как собираются образ. Для этого программистами или мейнтейнерами описывается Dockerfile и приложение дальше распространяется уже вместе с этим файлом. Podman поддерживает Dockerfile’ы, но и предоставляет похожий формат описания контейнеров с именем Containerfile.
Создадим наш первый файл с именем Containerfile.ol8:
FROM scratch
Немногословно, но нам для запуска XE необходимо системное окружение.
Oracle Linux 8.2 with the Unbreakable Enterprise Kernel 6: 5.4.17-2011.1.2.el8uek.x86_64 or later
Oracle Linux 8.2 with the Red Hat Compatible Kernel: 4.18.0-193.19.1.el8_2.x86_64 or later
Oracle Linux 7.6 with the Unbreakable Enterprise Kernel 5: 4.14.35-2025.404.1.el7uek.x86_64 or later
Oracle Linux 7.4 with the Unbreakable Enterprise Kernel 4: 4.1.12-124.53.1.el7uek.x86_64 or later
Red Hat Enterprise Linux 8.2: 4.18.0-193.19.1.el8_2.x86_64 or later
SUSE Linux Enterprise Server 15 SP1: 4.12.14-197.29-default or later
Review the system requirements section for a list of minimum package requirements.
Итак, нам нужен Oracle Linux 8. И на docker.io есть в этот раз официальные образы [2] от самого Оракла. Но мы пойдём, более интересным путём, эти же образы кто-то собирает, а значит, где-то уже есть готовый Dockerfile. Недолгие поиски привели на гитхаб репозиторий с уже готовой корневой файловой системной oraclelinux-8-amd64-rootfs.tar.xz [3].
Итоговое содержимое файла Containerfile.ol8:
FROM scratch
ADD oraclelinux-8-amd64-rootfs.tar.xz /
RUN --network=host --mount=type=bind,source=.,target=/host,ro,z
dnf install mc nano /host/htop-3.2.1-1.el8.x86_64.rpm;
rm -rf /var/cache/*;
rm -rf /var/log/*;
rm -rf /usr/lib/locale/en_*;
rm -rf /usr/share/doc/*;
rm -rf /usr/share/locale/*/;
rm -rf /usr/share/man/*;
rm -rf /usr/share/licenses/*
CMD /usr/bin/bash
Устанавливаем дополнительные пакеты, без которых жить не можем (здесь это mc, nano и htop). Ещё нам потребуется пакет htop, которого нет в репозитории, так что находим на https://pkgs.org/download/htop [4] подходящий пакет и скачиваем локально. Из хитростей:
--network=host — для работы пакетного менеджера внутри контейнера требуется сеть. Из приятного: переменные окружения, связанные с настройками прокси, автоматически передаются внутрь контейнера из хоста;
--mount=type=bind,source=.,target=/host,ro,z — чтобы не копировать файлы пакетов и удалять их из образа, монтируем текущую директорию внутрь контейнера только на чтение (z — чтобы selinux учитывал свои метки);
обязательно удаляем языковой пакет французского языка.
А чтобы нам не заскучать, уже напишем Makefile с именем Makefile.ol8 (необязательно):
ol8.tar: Containerfile.ol8
podman image build --squash -t localhost/ol8:latest -f Containerfile.ol8
touch ol8.tar
Containerfile.ol8: oraclelinux-8-amd64-rootfs.tar.xz htop-3.2.1-1.el8.x86_64.rpm
oraclelinux-8-amd64-rootfs.tar.xz: oraclelinux-8-amd64-rootfs.tar.xz.md5sum
wget -c https://raw.githubusercontent.com/oracle/container-images/refs/heads/dist-amd64/8/oraclelinux-8-amd64-rootfs.tar.xz
md5sum -c oraclelinux-8-amd64-rootfs.tar.xz.md5sum || exit 1
oraclelinux-8-amd64-rootfs.tar.xz.md5sum:
wget -c https://raw.githubusercontent.com/oracle/container-images/refs/heads/dist-amd64/8/oraclelinux-8-amd64-rootfs.tar.xz.md5sum
htop-3.2.1-1.el8.x86_64.rpm:
wget -c https://dl.fedoraproject.org/pub/epel/8/Everything/x86_64/Packages/h/htop-3.2.1-1.el8.x86_64.rpm
Внимательный читатель может заметить, что мы скачиваем два файла oraclelinux-8-amd64-rootfs.tar.xz и oraclelinux-8-amd64-rootfs.tar.xz.md5sum, проверяем правильность контрольной суммы и собираем образ с меткой localhost/ol8:latest.
TLNR:
podman image build --squash -t localhost/ol8:latest -f Containerfile.ol8
--squash — «склеивает» все новые изменения в один слой.
Результат:
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/ol8 latest 96f0371642fc 2 days ago 269 MB
269 МБ для целой операционной системы, на мой взгляд, звучит довольно неплохо.
Если на прошлом шаге мы просто распаковали уже готовый архив в образ и доставили некоторые пакеты, то теперь пришло время установки XE, Предполагается, что у вас есть в наличии скачанные пакеты oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm и oracle-database-xe-21c-1.0-1.ol8.x86_64.rpm.
План простой:
установить oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm;
установить oracle-database-xe-21c-1.0-1.ol8.x86_64.rpm;
прописать переменные окружения;
сохранить образ.
Если preinstall действительно устанавливается без проблем, то со вторым возникли сложности. К счастью, специалисты Оракла проделали большую работу, чтобы их XE смогла запуститься на самых различных ОС, в том числе debian/ubuntu (после конвертации rmp-пакета в deb- и использованием alien) и в контейнере. Текст ошибки подсказывает передать переменную окружения ORACLE_DOCKER_INSTALL=true.
Итоговое содержимое файла Containerfile.ol8:
FROM localhost/ol8
RUN --network=host --mount=type=bind,source=.,target=/host,ro,z
dnf --setopt=install_weak_deps=False install hostname /host/oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm;
ORACLE_DOCKER_INSTALL=true dnf install /host/oracle-database-xe-21c-1.0-1.ol8.x86_64.rpm;
echo "# oracle-database-xe-21c" >> /root/.bashrc;
echo "export ORACLE_HOME=/opt/oracle/product/21c/dbhomeXE" >> /root/.bashrc;
echo "export ORACLE_BASE=/opt/oracle" >> /root/.bashrc;
echo "export ORACLE_SID=XE" >> /root/.bashrc;
echo "export ORAENV_ASK=NO " >> /root/.bashrc;
echo ". /opt/oracle/product/21c/dbhomeXE/bin/oraenv" >> /root/.bashrc;
rm -rf /var/cache/*;
rm -rf /var/log/*;
rm -rf /usr/lib/locale/en_*;
rm -rf /usr/share/doc/*;
rm -rf /usr/share/locale/*/;
rm -rf /usr/share/man/*;
rm -rf /usr/share/licenses/*
# CMD /etc/init.d/oracle-xe-21c start
CMD /usr/bin/bash
Из новых хитростей:
не устанавливаем «слабые» зависимости;
передаём переменную окружения только одной команде;
и дописываем в .bashrc новые строки.
NOTE: можно попробовать писать не в .bashrc, а в /etc/profile
NOTE: из-за удаления кеша на прошлом слое дважды скачивается статус репозитория. Возможно, во время сборки стоит подключить директорию /var/cache/, чтобы можно было переиспользовать кеш для разных образов.
xe21.tar: Containerfile.xe21
podman image build -t localhost/xe21:latest -f Containerfile.xe21
touch xe21.tar
Containerfile.xe21: ol8.tar oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm oracle-database-xe-21c-1.0-1.ol8.x86_64.rpm
Результат:
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/xe21 latest a1453fecff67 2 days ago 6.59 GB
localhost/ol8 latest 96f0371642fc 2 days ago 269 M
Что ж, никто не говорил, что размер образа будет маленьким.
На прошлом шаге мы только установили XE, теперь осталось инициализировать базу данных. И тут начались настоящие проблемы. Команда /etc/init.d/oracle-xe-21c configure инициализирует не только файлы в oradata, но и что-то изменяет в директории /opt/. И содержимое oradata должно переживать изменение контейнера. То есть наша цель становится нечёткой, нам нужно одновременно проинициализировать базу данных и вынести датафайлы наружу контейнера.
Чистое решение с использованием Containerfile не получилось.
NOTE: возможно, следует попробовать примонтировать директорию в /opt/oracle/oradata/XE/ вместо тома и получить похожий воспроизводимый результат.
Нам потребуется том для хранения датафайлов. Чтобы XE могла писать в примонтированную директорию, необходимо либо при создании тома выдать нужные uid/gid тому: podman volume create --opt "o=uid=54321,gid=54321" w00, где 54321 — oracle/oinstall внутри контейнера.
Но указание дополнительно опции показалось мне сложным, так что проще установить нужного владельца вручную.
Файлы конфигурации взял такие:
# listener.ora Network Configuration File: /opt/oracle/homes/OraDBHome21cXE/network/admin/listener.ora
# Generated by Oracle configuration tools.
DEFAULT_SERVICE_LISTENER = XE
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = 0.0.0.0)(PORT = 1521))
# (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))
)
)
# tnsnames.ora Network Configuration File: /opt/oracle/homes/OraDBHome21cXE/network/admin/tnsnames.ora
# Generated by Oracle configuration tools.
XE =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = 0.0.0.0)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = XE)
)
)
LISTENER_XE =
(ADDRESS = (PROTOCOL = TCP)(HOST = 0.0.0.0)(PORT = 1521))
Создаём из образа контейнер и сразу же запускаем. Чтобы контейнер не остановился, в качестве команды внутри запускаем sh.
podman container run
--hostname w00
--name w00
--replace
--network host
--volume w00:/opt/oracle/oradata/XE/:rw,Z
--volume ./w00-tnsnames.ora:/opt/oracle/homes/OraDBHome21cXE/network/admin/tnsnames.ora:ro,Z
--volume ./w00-listener.ora:/opt/oracle/homes/OraDBHome21cXE/network/admin/listener.ora:ro,Z
-it -d
localhost/xe21:latest sh
Задано имя машины, имя контейнера, разрешена сеть и подцеплены три тома. Том с именем w00 будет создан автоматически.
С настройкой сети возникла интересная проблема. Если просто указать аргумент -p 1521:1521, то в netstat на хосте показывается, что слушается ipv6 порт. В интернете пишут, что это якобы баг отображения в netstat и что на самом деле используется двойной стек (вроде, Podman слушает одновременно и IPv4, и IPv6 порты). Советуют использовать ss вместо netstat. ss мне тоже не помог. При этом соединение с базой данных не устанавливается, пишется ошибка вида «удалённый узел разорвал соединение». В wireshark видно, что tcp-соединение успешно установлено, СУБД даже ответило на несколько запросов, а дальше проходит время и соединение разрывается по таймауту. Чтение alert-логов XE вносит больше подробностей: зачем-то XE нужно держать другие открытые порты для входящих соединений. В качестве решения было использовано --network host, Почему работает образ gvenzl/oracle-xe из введения с одним проброшенным портом — отличный вопрос. Возможно, ещё была бы проблема с отладкой pl/sql кода, но полное разрешение использовать сеть хоста должно решить и её.
В запущенном контейнере запускаем начальную инициализацию базы данных:
podman container exec
-e ORACLE_PASSWORD=password
-it
w00 sh -c '
rm -rf /opt/oracle/oradata/XE/*;
chown 54321:54321 -R /opt/oracle/oradata/XE/;
/etc/init.d/oracle-xe-21c configure'
Переменная окружения ORACLE_PASSWORD является незадокументированной, была обнаружена в скрипте /etc/init.d/oracle-xe-21c.
Потребуется минут 10-15, чтобы СУБД инициализировалась. На этом шаге можно проверить подключение к XE снаружи контейнера, и дальше остановить её и сохранить контейнер как образ, и сохранить том для дальнейшего использования.
Но пока только проверим работоспособность СУБД и переходим к следующему шагу.
Предположим, что у нас уже есть pluggable database (далее — PDB), готовая к клонированию.
[root@w00 /]# mkdir /opt/xepdbxml
[root@w00 /]# chown -R oracle:oinstall /opt/xepdbxml
[root@w00 /]# chmod -R 777 /opt/xepdbxml
[root@w00 /]# su oracle
[oracle@w00 /]$ sqlplus / as sysdba
SQL> ALTER SESSION SET CONTAINER=CDB$ROOT;
SQL> show pdbs;
SQL> alter pluggable database XEPDB1 close immediate;
SQL> show pdbs;
SQL> ALTER PLUGGABLE DATABASE XEPDB1 UNPLUG INTO '/opt/xepdbxml/XEPDB1.xml';
[root@w00 /]# cp /opt/oracle/oradata/XE/XEPDB1/* /opt/xepdbxml/
SQL> DROP pluggable database XEPDB1 KEEP DATAFILES;
SQL> CREATE PLUGGABLE DATABASE XEPDB1 AS CLONE USING '/opt/xepdbxml/XEPDB1.xml' SOURCE_FILE_NAME_CONVERT=('XEPDB1', 'XEPDB1') NOCOPY TEMPFILE REUSE;
SQL> alter pluggable database XEPDB1 open;
SQL> alter pluggable database all save state;
Теперь у нас есть PDB, нужно положить её внутрь контейнера. Копируем и заходим внутрь работающего контейнера:
podman container cp ./XEPDB1/ w00:/tmp/
podman container exec -it w00 bash
Делаем уже знакомые шаги и уничтожаем только созданную PDB и создаём нужную нам:
SQL> alter pluggable database XEPDB1 close immediate;
SQL> DROP pluggable database XEPDB1 INCLUDING DATAFILES;
mv /tmp/XEPDB1/ /opt/oracle/oradata/XE/XEPDB1/
SQL> CREATE PLUGGABLE DATABASE XEPDB1 AS CLONE USING '/opt/oracle/oradata/XE/XEPDB1/XEPDB1.xml' SOURCE_FILE_NAME_CONVERT=('XEPDB1', 'XEPDB1') NOCOPY TEMPFILE REUSE;
SQL> alter pluggable database XEPDB1 open;
SQL> alter pluggable database all save state;
Теперь точно всё останавливаем и сохраняем и контейнер, и том:
podman container exec
-it
w00 /etc/init.d/oracle-xe-21c stop
podman container commit
--pause
--change CMD='/etc/init.d/oracle-xe-21c start'
w00 localhost/w00:latest
podman container stop w00
podman volume export w00 --output w00.tar
podman image push localhost/w00:latest oci-archive:xe21s.tar
Результат:
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/w00 latest fafd040c01df 2 days ago 7.12 GB
localhost/xe21 latest a1453fecff67 2 days ago 6.59 GB
localhost/ol8 latest 96f0371642fc 2 days ago 269 MB
Чтобы запустить новый контейнер нужно:
скопировать tnsnames.ora и listener.ora, указать новые порты;
создать новый том и заполнить его данными:
podman volume create w01;
podman volume import w01 w00.tar
создать контейнер:
podman container create
--hostname w01
--name w01
--replace
--network host
--volume w01:/opt/oracle/oradata/XE/:rw,Z
--volume ./w01-tnsnames.ora:/opt/oracle/homes/OraDBHome21cXE/network/admin/tnsnames.ora:rw,Z
--volume ./w01-listener.ora:/opt/oracle/homes/OraDBHome21cXE/network/admin/listener.ora:rw,Z
-it
localhost/w00:latest
sh -c '/etc/init.d/oracle-xe-21c start; sh'
Запуск тривиален:
podman container start w00 w01
При просмотре списка файлов или списка процессов вместо имени пользователя и (или) группы показывается числовое значение. Я посчитал, что вполне безопасно создать пользователя и группу, чтобы дать имена вместо uid/gid.
cat /etc/subuid | grep user
user:200000:65535
cat /etc/subgid | grep user
user:200000:65535
tail -n1 /etc/passwd
w00-oracle:x:254320:254320::/home/w00-oracle:/bin/false
tail -n1 /etc/group
w00-oinstall:x:254320:
54321 (uid внутри контейнера) + 200000 (subuid пользователя user) - 1 = 254320
NOTE: нерешённая проблема: необходимо безопасно стартовать и останавливать XE, а не как сейчас при остановке контейнера все процессы убиваются через 10 секунд.
NOTE: нерешённая проблема: желательно запускать контейнеры на хосте через unit-systemd.
Автор: redfox0
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/oracle/406444
Ссылки в тексте:
[1] gvenzl/oracle-xe: https://hub.docker.com/r/gvenzl/oracle-xe
[2] официальные образы: https://hub.docker.com/_/oraclelinux
[3] oraclelinux-8-amd64-rootfs.tar.xz: https://raw.githubusercontent.com/oracle/container-images/refs/heads/dist-amd64/8/oraclelinux-8-amd64-rootfs.tar.xz
[4] https://pkgs.org/download/htop: https://pkgs.org/download/htop
[5] Источник: https://habr.com/ru/articles/870766/?utm_source=habrahabr&utm_medium=rss&utm_campaign=870766
Нажмите здесь для печати.