Как я запустил 99* контейнеров с Oracle Database 21c Express Edition внутри Podman на отечественной ОС

в 7:16, , рубрики: oracle, oracle database, podman, никто не читает теги
Как я запустил 99* контейнеров с Oracle Database 21c Express Edition внутри Podman на отечественной ОС - 1

В статье рассматривается пошаговое создание образов «с нуля» для контейнерного движка Podman. Внутрь контейнера «упакована» база данных Oracle Database 21c Express Edition. И всё это отечественной операционной системе РЕД ОС.

*2 контейнера


Так всё-таки зачем собирать собственный образ, когда на docker.io уже существует образ gvenzl/oracle-xe с несколькими миллионами скачиваний?

  1. Это моё первое знакомство с контейнерами, хотелось разобраться, как работает контейнерный движок Podman в rootless-режиме и среду выполнения контейнеров в целом, а не просто бездумно копировать нагугленные команды;

  2. Получше узнать устройство ядра Linux в контексте пространств имён и изоляции работающих контейнеров;

  3. Просто было весело попробовать реализовать специфичные требования (сборка образа «с нуля», распространение образов без регистров и реестров).

Начнём.

0. Настраиваем хост

Чтобы работать с контейнерами, очевидно, что нужно сначала установить контейнерный движок. К счастью, в репозитории ОС уже есть нужный пакет, устанавливаем:

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

1. Собираем первый слой. Установка ОС

С занудным введением покончено, начинается веселье. Хоть это моё первое знакомство с контейнерами, но у меня было до этого некоторое представление, как собираются образ. Для этого программистами или мейнтейнерами описывается Dockerfile и приложение дальше распространяется уже вместе с этим файлом. Podman поддерживает Dockerfile’ы, но и предоставляет похожий формат описания контейнеров с именем Containerfile.

Создадим наш первый файл с именем Containerfile.ol8:

FROM scratch

Немногословно, но нам для запуска XE необходимо системное окружение.

Системные требования к 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 есть в этот раз официальные образы от самого Оракла. Но мы пойдём, более интересным путём, эти же образы кто-то собирает, а значит, где-то уже есть готовый Dockerfile. Недолгие поиски привели на гитхаб репозиторий с уже готовой корневой файловой системной oraclelinux-8-amd64-rootfs.tar.xz.

Итоговое содержимое файла 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 подходящий пакет и скачиваем локально. Из хитростей:

  • --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 МБ для целой операционной системы, на мой взгляд, звучит довольно неплохо.

2. Собираем второй слой. Установка XE

Если на прошлом шаге мы просто распаковали уже готовый архив в образ и доставили некоторые пакеты, то теперь пришло время установки XE, Предполагается, что у вас есть в наличии скачанные пакеты oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm и oracle-database-xe-21c-1.0-1.ol8.x86_64.rpm.

План простой:

  1. установить oracle-database-preinstall-21c-1.0-1.el8.x86_64.rpm;

  2. установить oracle-database-xe-21c-1.0-1.ol8.x86_64.rpm;

  3. прописать переменные окружения;

  4. сохранить образ.

Если 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

Что ж, никто не говорил, что размер образа будет маленьким.

3. Собираем третий слой. Погружаемся глубже. Инициализация базы данных и тома

На прошлом шаге мы только установили 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 снаружи контейнера, и дальше остановить её и сохранить контейнер как образ, и сохранить том для дальнейшего использования.

Но пока только проверим работоспособность СУБД и переходим к следующему шагу.

4. Подключаем pluggable database

Рисунок из документации к Oracle DB

Рисунок из документации к Oracle DB

Предположим, что у нас уже есть 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

5. Запуск нескольких контейнеров

Чтобы запустить новый контейнер нужно:

  1. скопировать tnsnames.ora и listener.ora, указать новые порты;

  2. создать новый том и заполнить его данными:

    podman volume create w01;

    podman volume import w01 w00.tar

  3. создать контейнер:

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

6. Вместо заключения

При просмотре списка файлов или списка процессов вместо имени пользователя и (или) группы показывается числовое значение. Я посчитал, что вполне безопасно создать пользователя и группу, чтобы дать имена вместо 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js