UMGUM.COM 

XWiki ( Развёртывание в среде исполнения "Docker" предназначенного для ведения документации web-приложения "XWiki". )

6 мая 2020  (обновлено 6 июля 2020)

OS: "Linux Debian 9/10", "Linux Ubuntu Server 16/18 LTS".
Apps: "XWiki", "Nginx", "Docker", "Docker Compose", LDAP.

Задача: развернуть в среде исполнения "Docker" написанное на "Java" web-приложение "XWiki", предназначенное для ведения документации и публикации структурированных текстовых данных.


Почему "XWiki"? На протяжении последних пяти лет я активно пользовался "DokuWiki" и "Atlassian Confluence". Первое web-приложение привлекает крайней внутренней простотой, но непривычно для пользователей, избалованных современными ajax-овыми интерфейсами. Второй вариант с точки зрения "usability" на высоте, но стоимость лицензии чрезвычайно велика.

Пришло время пройтись по списку актуальных wiki-приложений и сравнить их возможности. Для таких задач даже специальный web-сервис "WikiMatrix" существует.

Уже на первом этапе изучения было выявлено, что известная всем "MediaWiki" не предоставляет механизмов простого и гарантированного ограничения доступа к документам. Для нас это означает, что применять её в корпоративной среде нежелательно.

Создатели "MediaWiki" рекомендуют как альтернативу использовать "Foswiki", "MoinMoin" или "Confluence". Ранее я отмечал, что последняя сильно платная. Первые две системы хранят данные в "flat-file" с самописным форматом.

В сравнении неплохо смотрелась "PmWiki", давно разрабатываемая и обновлённая в Феврале 2020, но данные тоже в "flat-file", что мне не нравится. Таким образом из всего списка осталась буквально одна "XWiki", которую мы и задействуем.

Последовательность дальнейших действий такова:

1. Подготовка системного окружения (отдельная инструкция);
2. Установка системы контейнеризации "Docker" (отдельная инструкция);
3. Установка сопутствующего ПО и подготовка конфигурации;
4. Установка и настройка СУБД "MySQL";
5. Установка фронтального web-сервера "Nginx";
6. Наладка запуска посредством "Docker Compose" и "Systemd";
7. Первоначальная настройка web-приложения "XWiki";
8. Настройка подключения к внешнему LDAP/AD для аутентификации.


Подготовка несущей файловой структуры для приложения "XWiki".

Распределение файлов приложения внутри docker-контейнера оставим разработчикам "XWiki", а конфигурационные файлы, загружаемые пользователями данные и журналы событий вынесем в файловую систему несущей системы:

# mkdir -p /var/opt/xwiki/data
# mkdir -p /var/opt/xwiki/log/tomcat

Создание пользователей и условий для усечения привилегий приложения "XWiki".

Надо отметить, что docker-контейнер для "XWiki" собран ужасно. Любопытно, что творилось в головах тех, кто накручивал все эти костыли в скриптах инициализации. Одним из следствий стало то, что приложение запускается в контексте суперпользователя и без основательной переделки контейнера этого не изменить. На перспективу я завёл выделенных пользователя и группу "xwiki" и передал им во владение файловые ресурсы приложения, но пока это бессмысленно:

# groupadd --system xwiki
# useradd --system --home-dir /var/opt/xwiki --shell /bin/false --gid xwiki xwiki

Передаём директории файловых ресурсов приложения выделенному для этого пользователю:

# chown -R xwiki:xwiki /var/opt/xwiki
# chmod -R go-rwx /var/opt/xwiki

Установка и настройка СУБД "MySQL".

Развёртываемая "XWiki" работает с "базой данных" через прослойку абстракции JDBC, так что теоретически большинство известных реляционных СУБД поддерживаются. Гарантируется работа с "Oracle", "PostgreSQL" и "MySQL". На "энтерпрайз" у нас нет денег, "слона" в последнее время у меня многовато, и для "wiki" мне захотелось простоты - так что ставим "MySQL":

# apt-get install --no-install-recommends mysql-server

Ничего особого в конфигурации СУБД вносить не требуется - лишь немного опций оптимизации "InnoDB" и разрешение подключений отовсюду, чтобы обеспечить возможность соединений из docker-контейнера:

# vi /etc/mysql/mysql.conf.d/mysql.cnf

....
[mysqld]
....
# Разрешаем подключаться отовсюду
bind-address = 0.0.0.0
....
# Выносим временные файлы в удобное нам место
tmpdir = /var/lib/mysql/tmp
innodb_tmpdir = /var/lib/mysql/tmp
....
# Увеличиваем лимит размера временной таблицы, обрабатываемой в ОЗУ, до сброса её в файловую систему
tmp_table_size = 512M
....
# Увеличиваем лимит размера создаваемых пользователем MEMORY-таблицы, обрабатываемой в ОЗУ
max_heap_table_size = 512M
....
# Увеличиваем размер буфера запросов и журнала транзакций
innodb_buffer_pool_size = 2G # (default: 128MB; optimal: 60% RAM)
innodb_log_file_size = 512M # (default: 5MB)
innodb_log_buffer_size = 256M # (default: 8MB)
....
# Указываем записывать журнал транзакций примерно один раз в секунду, а не немедленно после каждого изменения данных (default: 1)
innodb_flush_log_at_trx_commit = 2
....
# Включаем поддержку нового формата файлов InnoDB и длинных индексных ключей
innodb_file_format = barracuda
innodb_large_prefix = 1

# Явно указываем создавать для каждой таблицы отдельные файлы описаний (.frm) и данных (.ibd) на диске, а не сваливать всё в один (ibdataX)
innodb_file_per_table = 1
....

Проверяем синтаксическую корректность конфигурации и применяем её перезагрузкой сервиса:

# mysqld --verbose --help 1>/dev/null
# /etc/init.d/mysql restart

Оптимизация работы "MySQL" с дисковой подсистемой.

Для СУБД, активно создающей и уничтожающей файлы временных таблиц, выгодно вынести (параметром "tmpdir") эту работу в файловую систему, смонтированную в область памяти ОЗУ:

# mkdir -p -m 750 /var/lib/mysql/tmp
# chown -R mysql:mysql /var/lib/mysql/tmp

# vi /etc/fstab

....
# Tuning the location of MySQL temporary files
tmpfs /var/lib/mysql/tmp tmpfs rw,nosuid,nodev,size=2G,uid=mysql,gid=mysql,mode=0750 0 0
....

# mount /var/lib/mysql/tmp

Я выделяю под эту файловую систему до 20% от всей ОЗУ (она не заблокирует весь заявленный объём, а будет выбирать блоки памяти по мере появления необходимости).

Ограничение сетевого доступа к СУБД извне несущего сервера.

В конфигурации выше мы были вынуждены разрешить сетевые подключения к СУБД отовсюду, чтобы обеспечить простой провод трафика из docker-контейнера, запущенного рядом. Нам достаточно, чтобы сервис "MySQL" был доступен на уровне несущего сервера, так что будет полезным запретить подключения извне совсем:

# iptables -t filter -L INPUT -n -v --line-numbers
# iptables -A INPUT -i eth0 -p TCP --dport 3306 -j REJECT -m comment --comment "Forbidding external connections to MySQL"

Для сохранения правил защитного экрана и активации их при запуске операционной системы применим специализированную утилиту:

# aptitude install iptables-persistent
# netfilter-persistent save

Создаём пользователя и регистрируем "базу данных" для "XWiki".

Всё просто, отдаём команды создания БД, пользователя и наделения последнего неограниченными полномочиями доступа к выделенной "баз данных", в соответствии с рекомендациями разработчиков:

# mysql
MySQL> DROP DATABASE `xwiki`;
MySQL> CREATE DATABASE `xwiki` CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
MySQL> CREATE USER 'xwiki'@'%' IDENTIFIED BY 'xwiki_db_password';
MySQL> GRANT ALL PRIVILEGES ON xwiki.* TO 'xwiki'@'%';
MySQL> FLUSH PRIVILEGES;
MySQL> QUIT;

Установка фронтального web-сервера "Nginx".

В развёртываемое приложение встроен web-сервер "Tomcat". Однако на практике для приёма и первичной обработки клиентских подключений удобнее использовать более легковесный web-сервер. Воспользуемся "Nginx":

# aptitude install nginx-light

Для последующего включения в "Nginx" современного SSL/TLS и HTTPv2 генерируем DH-файл:

# mkdir -p /etc/ssl/nginx && chown -R root:root /etc/ssl/nginx
# openssl dhparam -out /etc/ssl/nginx/dhparam.2048.pem 2048

Слегка преднастроим web-сервер:

# vi /etc/nginx/nginx.conf

user www-data www-data;
worker_processes auto;
....
http {
  ....
  server_tokens off;
  client_max_body_size 0;
....

Конфигурация принимающего подключения пользователей web-сервера "Nginx" проста и сводится к описанию параметров "проксирования" всех запросов нижележащему web-сервису, запущенному внутри docker-контейнера:

# vi /etc/nginx/sites-available/xwiki.example.net.conf

server {
  listen 80;
  server_name xwiki.example.net;
  location / { rewrite ^(.+)$ https://$host$1 permanent; }
  #include /etc/nginx/snippets/letsencrypt.conf;
}

server {
  listen 443 ssl http2;
  server_name xwiki.example.net;

  access_log /var/log/nginx/xwiki.example.net_access.log;
  error_log /var/log/nginx/xwiki.example.net_error.log;

  ssl_dhparam /etc/ssl/nginx/dhparam.2048.pem;
  ssl_certificate /etc/ssl/nginx/wildcard.example.net.crt;
  ssl_certificate_key /etc/ssl/nginx/wildcard.example.net.key.decrypt;
  ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers on;

  location / {
    proxy_pass http://localhost:8080;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme https;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-NginX-Proxy true;
    # (HTTPv1.1 & WebSocket)
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_connect_timeout 240;
    proxy_send_timeout 240;
    proxy_read_timeout 240;
  }
}

Удаляем конфигурацию "по умолчанию", активируем новую, проверяем и запускаем в работу:

# rm /etc/nginx/sites-enabled/default
# ln -s /etc/nginx/sites-available/xwiki.example.net.conf /etc/nginx/sites-enabled/xwiki.example.net.conf
# nginx -t && nginx -s reload

Об установке web-приложения "XWiki".

Команда разработчиков сама собирает docker-контейнер с "XWiki" и публикует их на общеизвестном сервисе "Docker Hub" в соответствующем разделе https://hub.docker.com/_/xwiki/. Не вижу причин не воспользоваться этим, хотя качество скриптов конфигурации и ужасающе (просто будем надеяться, что это со временем будет исправлено). Некоторые аспекты конфигурирования docker-контейнеров "XWiki" описываются в документации, прикреплённой к исходному коду в git-репозитории проекта на "github.com".

В наличии несколько вариантов сборок: "latest", "stable" и "lts". Для долгоиграющих корпоративных сервисов лучше выбирать последнюю.

(опционально) Адаптация "Tomcat" для работы в спарке с "Nginx".

На практике выяснилось, что в этом нет необходимости - по умолчанию "XWiki" настроен так, что исходящие перенаправления осуществляет с относительным путём (без указания протокола и доменного имени).

С учётом того, что запросы пользователей принимаются web-сервером "Nginx", а обрабатываются скрытым за ним "Tomcat", желательно согласовать адреса FQDN и номера сетевых портов терминирования, которые должны сохранятся неизменными на протяжении всей цепочки обработки запроса, при передачи от сервера к серверу.

В самом простом случае достаточно уведомить web-сервис "Tomcat" о параметрах терминирования клиентских запросов вышестоящим проксирующим web-сервисом, чтобы в ответах таковому вместо внутренних имени сайта "localhost", порта "8080" и протокола "http" было указано то, что видится клиентам снаружи. Для этого достаточно модифицировать всего одну секцию в XML-файле конфигурации "Tomcat". В нашем случае этот конфигурационный файл внутри docker-образа, что несколько осложняет дело.

Предварительно добываем дистрибутивный файл конфигурации из docker-образа:

# docker create --name tmp-xwiki xwiki:lts-mysql-tomcat
# docker cp tmp-xwiki:/usr/local/tomcat/conf/* /var/opt/xwiki/conf/tomcat
# docker rm tmp-xwiki

Вносим необходимые изменения:

# vi /var/opt/xwiki/tomcat/conf/server.xml

<Server ... >
  ....
  <Service name="Catalina">
    ....
    <!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
    <Connector port="8080" protocol="HTTP/1.1"
      connectionTimeout="20000"
      enableLookups="false"
      URIEncoding="UTF-8"

      <!-- Невостребованный в нашей схеме обслуживания SSL/TLS вышестоящим Nginx параметр редиректа для обслуживания HTTPS-запросов. -->
      <!-- redirectPort="8443" -->

      <!-- Дополнительный набор параметров описания точки терминирования клиентских запросов вышестоящим проксирующим web-сервисом. -->
      proxyName="xwiki.example.net" proxyPort="443" scheme="https"
    />
....

Очевидно, что файлы конфигурации директории "/var/opt/xwiki/tomcat/conf/" впоследствии нужно будет подать внутрь docker-контейнера, запускаемого с web-сервисом "XWiki".

Наладка запуска посредством "Docker Compose".

Создаём директорию для размещения конфигурационных файлов "Docker Compose":

# mkdir /usr/local/etc/compose
# cd /usr/local/etc/compose

Воспользуемся для единственного в нашей схеме конфигурационного файла именем "по умолчанию":

# vi ./docker-compose.yml

version: "3"
services:
  xwiki:
    container_name: "xwiki-web"
    image: "xwiki:lts-mysql-tomcat"
    networks:
      xwiki:
        aliases:
          - "xwiki"
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      JAVA_OPTS: "-Xms2G -Xmx4G -Dfile.encoding=utf-8 -Djava.awt.headless=true -XX:+UseConcMarkSweepGC -XX:+UseNUMA -XX:+UseCompressedOops"
      XWIKI_VERSION: "xwiki"
      DB_HOST: "10.20.30.40"
      DB_USER: "xwiki"
      DB_PASSWORD: "xwiki_db_password"
      #INDEX_HOST: "localhost"
    working_dir: "/var/opt/xwiki"
    volumes:
      - "/etc/localtime:/etc/localtime:ro"
      - "/etc/timezone:/etc/timezone:ro"
      - "/var/opt/xwiki/data:/usr/local/xwiki/data:rw"
      - "/var/opt/xwiki/log/tomcat:/usr/local/tomcat/logs:rw"
    tmpfs:
      - /tmp
      - /usr/local/tomcat/temp
      - /usr/local/tomcat/work/Catalina/localhost/ROOT/xwiki-temp

networks:
  xwiki:
    driver: bridge
    internal: false
    ipam:
      driver: default
      config:
        - subnet: 100.127.255.0/24

Запускаем контейнер, указывая явно имя конфигурационного файла:

# docker-compose up --remove-orphans --build --force-recreate -d

Останавливаем docker-контейнер, описанный в конфигурации "Docker Compose":

# docker-compose down

Настройка автозагрузки "Docker Compose" посредством "Systemd".

Создаём файл описания параметров запуска и остановки docker-контейнера с "XWiki" посредством "Docker Compose" в роли короткоживущего сервиса "Systemd":

# vi /etc/systemd/system/xwiki-docker.service

[Unit]
Description=XWiki in Docker Compose Service
Requires=network.target docker.service containerd.service
After=docker.service mysql.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/usr/local/etc/compose

ExecStartPre=-/bin/bash -c 'chown -R xwiki:xwiki /var/opt/xwiki'
ExecStartPre=/usr/local/bin/docker-compose -f docker-compose.yml down --remove-orphans
ExecStart=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml up --remove-orphans --build --force-recreate --detach
#
ExecStop=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml down

[Install]
WantedBy=multi-user.target

Указываем "Systemd" перечитать и принять новую конфигурацию, а также явно активируем и запускаем новый сервис:

# systemctl daemon-reload
# systemctl enable xwiki-docker.service
# systemctl start xwiki-docker

Смотрим журнал событий "Systemd" если "что-то пошло не так":

# systemctl status xwiki-docker.service
# journalctl -xe

Первоначальная настройка web-приложения "XWiki".

После всех вышеописанных этапов подготовки окружения можно приступать к настройке "XWiki" посредством его web-интерфейса.

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

"XWiki" сложная, многокомпонентная система, и её настройка "с нуля" может занять массу времени. Гораздо эффективнее воспользоваться заготовленным разработчиками набором конфигураций, называемым "Flavor" - в нашем случае следует установить "XWiki Standard Flavor".

В составе выбранного (безальтернативно, впрочем) flavor-а будет установлено несколько десятков расширений, практически ненужную часть из которые можно удалить при первом входе в уже готовую к работе систему:

Global Administration -> Extensions:
  XWiki Platform - Annotations - UI;
  Help Application;
  Help Center Application;
  XWiki Platform - Invitation - UI;
  App Within Minutes Application;
  Dashboard Application;
  Tour Application;
  Menu Application;
  Wiki Application;
  Application Index Application;
  User Directory Application.
    Uninstall from farm -> Uninstall -> Continue.

Сложность "XWiki" отчасти компенсируется богатством возможностей её настройки, детально описываемых в разделе "Configuration" документации проекта.

Также "XWiki" хорошо поддаётся оптимизации, расписанной в выделенном для этого разделе "Performances" на сайте разработчиков.

Заменяем логотип "XWiki" произвольным.

У предприятия "не одного дня" наверняка имеется свой корпоративный стиль. Естественно, нам захочется как-то применить его к внедряемому web-сервису. В "XWiki" встроены возможности создания новых шаблонов отображения, а также модификации имеющихся. Например, так заменяется дистрибутивный логотип на любое другое изображение:

Global Administration -> Look & Feel -> Themes -> Color Theme -> Customize:
  Logos:
    @logo -> Choose an attachment -> Upload and select.

Настраиваем интеграцию с потовым сервисом.

Для уведомлений пользователей о событиях, а также отслеживания изменений в документах, обязательно настраиваем связь с почтовым сервисом:

Global Administration -> Mail Sending:
  EMAIL ADDRESS TO SEND FROM: xwiki@example.net
  EMAIL SERVER: mail.example.net
  EMAIL SERVER PORT: 25
  EMAIL SERVER USERNAME: xwiki@example.net
  EMAIL SERVER PASSWORD: xwiki_mail_password
  ADDITIONAL PROPERTIES: mail.smtp.starttls.enable=true

На любой существующей странице "XWiki" проверяем возможность отправки почтового уведомления:

Home -> More Actions -> Share by email:
  SEND TO: test@example.net
  Send.

Настройка подключения к внешнему LDAP/AD для аутентификации.

Для интеграции с LDAP/AD в "XWiki" используется плагин (или расширение, как его принято здесь называть) "LDAP Authenticator". Устанавливаем его:

Global Administration -> Extensions:
  LDAP Authenticator:
    Install on farm -> Continue.

Полная настройка аутентификации посредством LDAP в "XWiki" через web-интерфейс невозможна - вначале придётся задать как минимум три параметра в основном конфигурационном файле - ну, а раз так, то и всё остальное сделаем так же. Кроме того, при тестировании выяснилось, что предназначенное для настройки через web-интерфейс расширение "LDAP Application" не считывает фактическую конфигурацию из основого файла "xwiki.cfg", что странно - не особо разбираясь в сути проблемы, я предпочёл обойтись без него.

Редактируем основной конфигурационный файл "XWiki":

# vi /var/opt/xwiki/data/xwiki.cfg

....
#-# The authentication management class.
# xwiki.authentication.authclass=com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl

#-# The authentication management LDAP class.
xwiki.authentication.authclass=org.xwiki.contrib.ldap.XWikiLDAPAuthServiceImpl

#-# Turn LDAP authentication on
xwiki.authentication.ldap=1

#-# Enable local accounts in addition to LDAP.
xwiki.authentication.ldap.trylocal=1

#-# LDAP Server connection
xwiki.authentication.ldap.server=ldap0.example.net
xwiki.authentication.ldap.port=636
xwiki.authentication.ldap.ssl=1
#xwiki.authentication.ldap.ssl.keystore=

#-# LDAP credentials, empty = anonymous access, otherwise specify full dn
#-# {0} is replaced with the user name, {1} with the password
xwiki.authentication.ldap.bind_DN=uid=xwiki,ou=Accounts,ou=Services,dc=example,dc=net
xwiki.authentication.ldap.bind_pass=xwiki_ldap_password

#-# The Base DN used in LDAP searches
xwiki.authentication.ldap.base_DN=dc=example,dc=net

#-# LDAP query to search the user in the LDAP database
#-# {0} is replaced with the user uid field name and {1} with the user name
xwiki.authentication.ldap.user_search_fmt=(&(uid={1})(objectclass=person)(memberOf=cn=xwikiusers,ou=groups,dc=example,dc=net))

#-# Specifies the LDAP attribute containing the identifier to be used as the XWiki name
# xwiki.authentication.ldap.UID_attr=uid

#-# Retrieve the following fields from LDAP and store them in the XWiki user object
xwiki.authentication.ldap.fields_mapping=last_name=sn,first_name=givenName,email=mail,phone=telephoneNumber

#-# On every authentication update the mapped attributes from LDAP to XWiki otherwise this happens only once when the XWiki
xwiki.authentication.ldap.update_user=1
....

Для активации новой подсистемы аутентификации придётся перезапустить docker-контейнер с web-сервисом:

# systemctl stop xwiki-docker && systemctl start xwiki-docker

Обращаю внимание, что опцией "xwiki.authentication.ldap.trylocal" в конфигурации выше мы обеспечили поддержку аутентификации как через локальную "базу данных" (вроде созданного на этапе развёртывания web-сервиса пользователя "admin"), так и через внешний сервис LDAP/AD. Это удобно: в обыденной работе можно аутентифицироваться через LDAP, а в случае сбоя связи с таковым остаётся возможность залогиниться без применения LDAP.

При настройке интеграции с LDAP-сервисом, трафик которого шифруется "самоподписанным" SSL-сертификатом, соединение не получится установить без дополнительных процедур установления доверительных отношений. Реализация "Java" для "Linux" не позволяет запросто отключить проверку валидности SSL-сертификата. Но отлично работает добавление условно корневого сертификата в общий перечень доверенных, размещаемый обычно в файле "$JAVA_HOME/jre/lib/security/cacerts". Ранее я неоднократно рассказывал, как это делается.


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )