UMGUM.COM (лучше) 

DSpace + GitLab Pipelines ( Автоматизация процедур доставки кода, сборки Java-проекта и развёртывания web-приложений между серверами разработки, тестирования и публикации на примере "DSpace". )

4 апреля 2019

OS: "Linux Debian 9 (Stretch)", "Linux Ubuntu 18.04.1 LTS (Bionic Beaver)".
Apps: "RSync", "Netcat", SSH, "GitLab Runner", Bash.

Задача: наладить автоматизированные процедуры доставки кода, сборки Java-проекта и развёртывания web-приложений между серверами разработки, тестирования и публикации на примере поддержки сервиса репозитория цифровых документов "DSpace", считая последний установленным строго следуя инструкции по сборке и развёртыванию "DSpace v6" на этом же сайте.

В качестве Git-репозитория хранения и организатора процедур CI/CD (Continuous Integration/Deployment) для этой простейшей задачи подходит "GitLab CE (Community Edition)" - функционал его "pipelines" с описываемой в ".gitlab-ci.yml" логикой вполне достаточен.

Исходим из того, что процесс разработки ведётся по классической простейшей схеме перехода от ветке к ветке: "feature" -> "develop" -> "testing" -> "master". В нашем случае с использованием в качестве основы стороннего репозитория (практически замороженного) добавляется исходная ветвь "dspace-6_x".


Предварительная подготовка структуры Git-репозитория.

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

Клонируем актуальную ветвь Git-репозитория, создаём от неё ответвление, вносим в неё отдельными коммитами изменения конфигурационных файлов, вливаем изменения новой ветви в действующую и выгружаем изменения в центральный репозиторий хранения и автоматизации:

# mkdir -p /var/tmp/build && chown -R dspace /var/tmp/build && cd /var/tmp/build
# sudo -u dspace git clone --branch develop ssh://git@gitlab.example.net/group/dspace.git
# cd ./dspace

Если ветвь "custom" отсутствует, то создаём её, клонируя текущую "develop":

# sudo -u dspace git checkout -b custom

Если ветвь "custom" уже имеется, то загружаем её с Git-репозитория хранения и переключаемся на неё:

# sudo -u dspace git fetch ssh://git@gitlab.example.net/group/dspace.git custom
# sudo -u dspace git checkout custom

Вносим изменения в конфигурационные файлы web-проекта (затрагиваемые файлы и параметры подробно расписаны в инструкции по сборке и развёртыванию "DSpace") и фиксируем изменения коммитом:

# sudo -u dspace git diff
# sudo -u dspace git commit -a -m "Init Custom configuration."

Переключаемся в действующую ветвь разработки, в которую предстоит влить изменения от "custom":

# sudo -u dspace git checkout develop

Вливаем изменения "как есть", по возможности с использованием методики смещения указателя "fast-forward", без дополнительной отметки слияния и перестроения (слишком простая операция для фиксирования ветвлений отдельным коммитом):

# sudo -u dspace git merge --ff custom

Fast-forward
dspace/config/dspace.cfg                     | 31 ++...--
dspace/config/log4j-handle-plugin.properties |  5 +++--
dspace/config/log4j-solr.properties          |  7 +++−−−−
dspace/config/log4j.properties               |  6 +++---
4 files changed, 25 insertions(+), 24 deletions(-)

Разумеется, изменённую ветвь "develop" и новую ветвь "custom" нужно будет ещё загрузить в репозиторий хранения:

# cd /var/tmp/build/dspace
# sudo -u dspace git push ssh://git@gitlab.example.net/group/dspace.git custom
# sudo -u dspace git push ssh://git@gitlab.example.net/group/dspace.git develop

Предварительная подготовка способа аутентификации.

На стороне сервера (серверов, получаемых в дальнейшем клонированием), от которого будут исходить обращения к Git-репозиторию хранения, генерируем набор SSH-ключей для аутентификации:

# mkdir -p /var/lib/dspace/.ssh
# ssh-keygen -t rsa -b 2048 -f /var/lib/dspace/.ssh/id_rsa -P "" -C "dspace"

Проверяем корректность созданного SSH-ключа:

# ssh-keygen -l -f /var/lib/dspace/.ssh/id_rsa

2048 SHA256:... dspace (RSA)

У "GitLab" есть замечательный способ обеспечения доступа к проектам и репозиториям без заведения специального пользователя, довольствуясь проверкой SSH-ключа.

Получаем содержимое открытого (публичного) ключа пользователя "dspace" командой "cat /var/lib/dspace/.ssh/id_rsa.pub" добавляем его в перечень ключей доступа на "GitLab" с Git-репозиториями, примерно так:

Group -> Repository -> Repository Settings -> Deploy Keys:
  Create a new deploy key for this project:
    Title: dspace
    Key: ssh-rsa ... dspace
    Write access allowed: false

Для первичной загрузки репозитория вышеуказанному виртуальному пользователю возможно разрешение и на запись (Read/Write), но в дальнейшем его роль лишь в получении данных.

Возможно это слегка небезопасно, но в этой схеме я разрешаю подключаться SSH-пользователю "dspace" к удалённым серверам без запроса на подтверждение приёма "fingerprint" их SSH-ключей - проще будет автоматизировать добавление и перемещение Git-репозитариев:

# vi /var/lib/dspace/.ssh/config

StrictHostKeyChecking no
....

Обращаю внимание, что файлы SSH-ключей должны быть доступны только какому-то определённому пользователю, но не группе таковых - иначе в некоторых строгих конфигурациях OpenSSH-клиент откажется с ними работать (заявив при этом что-то вроде "Permissions are too open" или, как ни странно, "Permission denied"):

# chown -R dspace:root /var/lib/dspace/.ssh
# chmod -R go-rwx /var/lib/dspace/.ssh

Можно сразу проверить простейшим клонированием, получилось ли обеспечить доступ к Git-репозиторию на "GitLab" нашему специализированному пользователю без запроса пароля для аутентификации:

# sudo -u dspace git clone ssh://git@gitlab.example.net/group/dspace.git

Предварительная подготовка способа синхронизации данных.

В процессе автоматизации далее нам понадобится возможность обращаться с серверов разработки и тестирования к ресурсам файловой системы сервера публикации, в частности для получения и актуализации большого объёма файлов в директории "./dspace/assetstore/". Безопаснее и оптимальнее с точки зрения производительности это будет сделать синхронизацией утилитой "RSync" поверх туннеля "Netcat" внутри SSH, с аутентификацией посредством тех же SSH-ключей, которые используются для доступа к Git-репозиториям.

Обычно "RSync" и "Netcat" уже имеются на типовом Linux-сервере, но если нет - то устанавливаем их:

# aptitude install rsync nc

Разрешим сервису "rsync" автоматически запускаться фоновым процессом:

# vi /etc/default/rsync

....
RSYNC_ENABLE=true
....

Создаём конфигурационный файл, из соображений безопасности разрешая серверу "rsync" обслуживать подключения только на интерфейсе "локальной петли":

# vi /etc/rsyncd.conf

log file = /var/log/rsyncd.log
pid file=/var/run/rsyncd.pid
lock file = /var/lock/rsyncd.lock

address = 127.0.0.1
max connections = 5
timeout = 600
dont compress = *

[assetstore]
  comment = DSpace "Asset Store"
  path = /var/lib/dspace/assetstore
  use chroot = yes
  read only = yes
  list = no
  uid = dspace
  gid = dspace
  hosts allow = 127.0.0.1

Запускаем сервер "rsync" и проверяем его состояние:

# systemctl start rsync
# systemctl status rsync

Проверяем, получится ли обратится к файловым ресурсам через RSync-сервер локально:

# cd /tmp
# sudo -u dspace rsync -avn 127.0.0.1::assetstore ./assetstore

receiving incremental file list
created directory ./assetstore
./
....
sent ... received ... bytes ... bytes/sec
... (DRY RUN)

Теперь наладим аутентификацию для транспортного SSH-туннеля и ограничим подключающегося извне пользователя "dspace" только функционалом обращения в RSync-серверу.

Забираем публичную (открытую) часть SSH-ключей со стороны серверов разработки и тестирования (пока что это сам сервер публикации web-сервиса "DSpace" - далее мы его склонируем, так что сейчас можно разрешить ему подключаться к самому себе) и добавляем открытую часть SSH-key в список разрешённых:

# cat /var/lib/dspace/.ssh/id_rsa.pub >> /var/lib/dspace/.ssh/authorized_keys

Обязательно профилактически поправляем права доступа к файлам SSH-ключей:

# chown -R dspace:root /var/lib/dspace/.ssh
# chmod -R go-rwx /var/lib/dspace/.ssh

Функционально ограничиваем пользователя "dspace", подключившегося посредством SSH, после успешной аутентификации любым методом (паролем, либо SSH-ключём) запуская команду открытия сетевого сокета к порту работающего на "локальной петле" RSync-сервера (замещая типовую командную оболочку вроде "/bin/bash" произвольной, в нашем случае утилитой "Netcat"), тем самым обеспечивая обслуживание только RSync-запросов:

# vi /etc/ssh/sshd_config

....
Match user dspace
  AllowTcpForwarding no
  AllowAgentForwarding no
  X11Forwarding no
  PermitTTY no
  ForceCommand /bin/nc 127.0.0.1 873
  AuthenticationMethods publickey
  PasswordAuthentication no

Учитывая специфичность роли пользователя "dspace", предназначенного только для автоматизации и запуска сервисов, где ручных действий не подразумевается, считаю полезным сразу профилактически запретить ему возможность аутентификации посредством логина и пароля (опции "AuthenticationMethods" и "PasswordAuthentication" в примере выше).

Как вариант, если пользователю "dspace" всё-таки потребуется нормальный вход в систему с аутентификацией паролем, автоматизацию можно наладить только на методе аутентификации посредством SSH-ключа, дополнив строку конфигурационного файла ".ssh/authorized_keys" с SSH-ключём указанием запускать команду открытия сетевого сокета к порту RSync-сервера (замещая типовую командную оболочку вроде "/bin/bash" произвольной, в нашем случае утилитой "Netcat"), тем самым обеспечивая обслуживание только RSync-запросов:

# vi /var/lib/dspace/.ssh/authorized_keys

command="/bin/nc 127.0.0.1 873",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-rsa ...<public ssh-key>...
....

Даём SSH-серверу указание принять новую конфигурацию:

# systemctl reload ssh

Разрешаем прошедшему аутентификацию пользователю "dspace" запускать командную оболочку (которую мы выше перекрыли немедленным вызовом "netcat") - в инструкции с ручным развёртыванием "DSpace" этому пользователю доступ к "shell" был запрещён:

# usermod --shell /bin/bash dspace

Теперь попробуем обратится извне к работающему на "локальной петле" серверу RSync по его "нативному" протоколу (вызывается указанием "::" между именем сервера и файловым ресурсом) в туннеле SSH через команду-обёртку, описываемую в специальной переменной "RSYNC_CONNECT_PROG":

# cd /tmp
# sudo -u dspace RSYNC_CONNECT_PROG="ssh dspace.example.net nc 127.0.0.1 873" rsync -v -avn 127.0.0.1::assetstore ./assetstore

Using RSYNC_CONNECT_PROG instead of opening tcp connection to 127.0.0.1 port 873
Running socket program: "ssh dspace.example.net nc 127.0.0.1 873"
sending daemon args: --server --sender -vvnlogDtpre.iLsfxC . assetstore  (5 args)
receiving incremental file list
Setting --timeout=600 to match server
delta-transmission enabled
created directory ./assetstore
./
....
sent ... received ... bytes ... bytes/sec
... (DRY RUN)

Реальная выгрузка или синхронизация файлов данных "DSpace" осуществляется вызовом приведённой выше команды без ключа "-n". Первая выгрузка файлов будет длится долго (в моём случае 34GB копировались минут десять), но при последующих запросах будут передаваться только изменения и дополнения.

Предварительная подготовка способа доставки кода и логики.

В начале публикации я указал на выбор локального сервера "GitLab" в качестве сервиса хранения Git-репозиториев, а так же инструмента автоматизации процедур CI/CD (сборки и развёртывания) нашего демонстрационного web-проекта "DSpace". Для доставки на целевые серверы кода и реализации процедур используем специально для этого написанных программных агентов "GitLab Runner".

В крупных разнородных средах разработки и автоматизации обычно применяют выделенные сервисы автоматизации вроде "Jenkins" или "Bamboo", но для нашей простой задачи написанные на "Go" лёгкие и быстрые агенты отлично подходят.

Для установки "раннера" ("GitLab Runner") на странице официального руководства по умолчанию предлагается скачать и запустить с правами суперпользователя Bash-скрипт, который пошуршит и сформирует ссылку на актуальный APT-репозиторий. Мне такой подход очень не нравится и более углубленный поиск вывел на руководство по ручной настройке репозиториев, которым далее воспользуемся.

Прежде всего добавляем GPG-ключ подписи APT-репозитория "GitLab":

# aptitude install debian-archive-keyring curl gnupg apt-transport-https
# curl -L https://packages.gitlab.com/runner/gitlab-ci-multi-runner/gpgkey | sudo apt-key add -

Уточняем дистрибутив и версию используемой системы:

# lsb_release -a

Вручную формируем конфигурационный файл для менеджера пакетов.

Пример для "Linux Ubuntu 18 LTS (Bionic Beaver)":

# vi /etc/apt/sources.list.d/runner_gitlab-ci-multi-runner.list

# APT-repo "GitLab Runner" for "Ubuntu 18.04 Bionic"
deb https://packages.gitlab.com/runner/gitlab-ci-multi-runner/ubuntu/ bionic main
deb-src https://packages.gitlab.com/runner/gitlab-ci-multi-runner/ubuntu/ bionic main

Пример для "Linux Debian 9 (Stretch)":

# vi /etc/apt/sources.list.d/runner_gitlab-ci-multi-runner.list

# APT-repo "GitLab Runner" for "Debian 9 Stretch"
deb https://packages.gitlab.com/runner/gitlab-ci-multi-runner/debian/ stretch main
deb-src https://packages.gitlab.com/runner/gitlab-ci-multi-runner/debian/ stretch main

Обновляем список доступного ПО и запускаем установку "GitLab Runner":

# aptitude update
# aptitude install gitlab-ci-multi-runner

Как вариант, можно скачать DEB-пакет и установить его в обход менеджера пакетов - потеряв при этом возможность комплексного обновления, конечно.

По умолчанию после установки из DEB-пакета сервис "GitLab Runner" запускается от имени суперпользователя и в целом имеет доступ ко всему в операционной системе, но задачи запускает в контексте того пользователя, который указан в параметре "--user" команды запуска сервиса - по умолчанию это автоматически создаваемый рядовой "gitlab-runner":

# ps waux | grep -i gitlab-runner

root ... /usr/bin/gitlab-runner run --working-directory /var/lib/gitlab-runner --config /etc/gitlab-runner/config.toml --service gitlab-runner --syslog --user gitlab-runner

Для решения нашей задачи сборки и развёртывания единичного специфичного web-приложения я предпочитаю переопределить некоторые заданные по умолчанию параметры сервиса "GitLab Runner" своими, более ориентированными на ресурсы, которыми он будет манипулировать (в частности, рабочую директорию и пользователя, от имени которого будут исполняться команды).

# mkdir -p /etc/systemd/system/gitlab-runner.service.d
# vi /etc/systemd/system/gitlab-runner.service.d/dspace.conf

[Service]
Environment="DAEMON_ARGS=run --working-directory /var/lib/dspace --config /etc/gitlab-runner/config.toml --service gitlab-runner --syslog --user dspace"

Применяем изменения конфигурации "Systemd" и перезапускаем "GitLab Runner":

# systemctl daemon-reload
# systemctl restart gitlab-runner

Просматривая статус сервиса можно заметить, что часть его конфигурации переопределена (строка "drop-in"):

# systemctl status gitlab-runner

gitlab-runner.service - GitLab Runner
  Loaded: loaded (/lib/systemd/system/gitlab-runner.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/gitlab-runner.service.d
           └─dspace.conf
  ....
  CGroup: /system.slice/gitlab-runner.service
          └─... /usr/bin/gitlab-runner run --working-directory /var/lib/dspace --config /etc/gitlab-runner/config.toml
....

Удаляем лишнего, нигде не задействованного пользователя, автоматически созданного при инсталляции "GitLab Runner":

# userdel gitlab-runner

Создание площадок разработки и тестирования.

Проще всего площадки разработки и тестирования для нашего web-сервиса создать путём клонирования действующего сервера публикации.

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

# aptitude install virtinst

Просматриваем перечень уже имеющихся виртуальных машин и выбираем из них нужную:

# virsh list --all

Пример команды клонирования сервера публикации "DSpace" в инстанс для разработки (аналогично для тестирования):

# virt-clone --original dspace.example.net --name dev-dspace.example.net --file /var/lib/libvirt/images/dev-dspace.example.net.qcow2

После клонирования в конфигурационные файлы копий достаточно применить следующие изменения, отличающие их от оригинала (пример для сервера тестирования):

/etc/netplan/01-netcfg.yaml:
  IP: x.x.x.x (netplan try)

/etc/hostname:
  test-dspace

/etc/hosts:
  127.0.0.1  test-dspace.example.net test-dspace

/etc/nginx/sites-available/dspace.example.net.conf:
  server_name test-dspace.example.net

/var/lib/dspace/conf/server.xml:
  proxyName="test-dspace.example.net"

Регистрация CI/CD-агентов в "GitLab".

Следуя инструкции поэтапно мы получили три функционально идентичных сервера: разработки, тестирования и публикации. Подключим запущенные на них CI/CD-агенты к хранящему Git-репозиторий проекта "DSpace" серверу "GitLab".

В web-интерфейсе "GitLab" следуем в раздел настроек автоматизации, к перечню (вначале пустому) "GitLab Runner"-ов, где узнаём специфичный для этого репозитория "ключ регистрации":

Group -> Repository -> CI/CD Setting -> Runner settings:
  Specific Runners:
    registration token: Lz7...5JF
    ....

На каждом из серверов запускаем процедуру регистрации "GitLab Runner"-а, где некоторые параметры определяют принадлежность к конкретному серверу. Например, таким образом регистрируем "runner" сервера публикации:

# gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.example.net/" \
  --registration-token "Lz7...5JF" \
  --builds-dir ".builds" \
  --executor "shell" \
  --shell "bash" \
  --description "dspace.example.net" \
  --tag-list "shell,dspace,master" \
  --run-untagged \
  --locked="true"

Для "runner"-а сервера тестирования наименование и перечень "тегов" будут соответствующие только ему:

# gitlab-runner register \
  ....
  --description "test-dspace.example.net" \
  --tag-list "shell,dspace,testing" \
  ....

Аналогично, у сервера разработки свои идентификационные параметры:

# gitlab-runner register \
  ....
  --description "dev-dspace.example.net" \
  --tag-list "shell,dspace,develop" \
  ....

Обращаю внимание на то, что в процессе отработки "задач", описываемых далее в ".gitlab-ci.yml" именно набор ключевых слов в параметре "tag-list" послужит определяющим, какому из "runner"-ов отправить задание.

Регистрацию агента на "GitLab"-сервере можно отозвать:

# gitlab-runner unregister --name test-dspace.example.net

Разумеется, есть команда проверки текущего состояния "runner"-а:

# gitlab-runner verify

Список и параметры уже зарегистрированных в "GitLab" CI/CD-агентов можно посмотреть на специальной странице администрирования "https://gitlab.example.net/admin/runners".

Автоматизация обработки событий изменения в репозитории.

В нашем случае сборка и развёртывание web-приложений "DSpace" для серверов разработки, тестирования и публикации производится совершенно единообразно, а набор правил автоматизации предназначен для того, чтобы определить, на какой из серверов (определяемых набором "tags") отправлять задачу на исполнение при изменении в соответствующей ветви (определяемой параметром "only:refs"):

В корневой директории исходного кода проекта "DSpace" создаём текстовый файл с правилами автоматизации для "GitLab", составленный в формате YAML:

$ vi .gitlab-ci.yml

variables:
  # Simplest bypass bug: "problem with the SSL CA cert path"
  GIT_SSL_NO_VERIFY: "1"
  # Resource points
  DSPACE_FQDN_DEVELOP: dev-dspace.example.net
  DSPACE_FQDN_TESTING: test-dspace.example.net
  DSPACE_FQDN_MASTER: dspace.example.net
  DSPACE_DIR: /var/lib/dspace

# (jobs execution steps)
stages:
  - dbget
  - dbapply
  - filesync
  - deploy
  - after

# (getting the contents of the main/master database)
db_getting:
  stage: dbget
  only:
    refs:
      - develop
      - testing
  tags:
    - shell
    - dspace
    - master
  script:
    # (cutting commands requires superuser privileges)
    - pg_dump --no-owner --clean --if-exists --verbose --dbname=dspace | sed -E "s#^(DROP\ EXTENSION|CREATE\ EXTENSION|COMMENT\ ON\ EXTENSION)#-- \1#gI" | sed -E "s#^(DROP\ SCHEMA|CREATE\ SCHEMA|COMMENT\ ON\ SCHEMA|GRANT)#-- \1#gI" > dspace-fresh-dump.sql
  artifacts:
    paths:
    - dspace-fresh-dump.sql
    expire_in: 30 minutes

# (replacing the contents of the database development server)
db_applying_develop:
  stage: dbapply
  when: manual
  only:
    refs:
      - develop
  tags:
    - shell
    - dspace
    - develop
  script:
    - psql -v ON_ERROR_STOP=1 --dbname=dspace --file=dspace-fresh-dump.sql

# (replacing the contents of the database testing server)
db_applying_testing:
  stage: dbapply
  only:
    refs:
      - testing
  tags:
    - shell
    - dspace
    - testing
  script:
    - psql -v ON_ERROR_STOP=1 --dbname=dspace --file=dspace-fresh-dump.sql

# (getting/synchronization of files from the main/master server to the development server)
filesyncing_develop:
  stage: filesync
  when: manual
  only:
    refs:
      - develop
  tags:
    - shell
    - dspace
    - develop
  script:
    # (RSync via Netcat in SSH-tunnel)
    - RSYNC_CONNECT_PROG="ssh $DSPACE_FQDN_MASTER nc 127.0.0.1 873" rsync -av --delete 127.0.0.1::assetstore $DSPACE_DIR/assetstore

# (getting/synchronization of files from the main/master server to the testing server)
filesyncing_testing:
  stage: filesync
  only:
    refs:
      - testing
  tags:
    - shell
    - dspace
    - testing
  script:
    # (RSync via Netcat in SSH-tunnel)
    - RSYNC_CONNECT_PROG="ssh $DSPACE_FQDN_MASTER nc 127.0.0.1 873" rsync -av --delete 127.0.0.1::assetstore $DSPACE_DIR/assetstore

# (build from source code and deploy web-services on development server)
deploy_develop:
  stage: deploy
  when: manual
  only:
    refs:
      - develop
  tags:
    - shell
    - dspace
    - develop
  script:
    - /usr/local/etc/devops/dspace-build-deploy.sh $HOME/$CI_PROJECT_DIR $DSPACE_DIR

# (build from source code and deploy web-services on testing server)
deploy_testing:
  stage: deploy
  only:
    refs:
      - testing
  tags:
    - shell
    - dspace
    - testing
  script:
    - /usr/local/etc/devops/dspace-build-deploy.sh $HOME/$CI_PROJECT_DIR $DSPACE_DIR

# (build from source code and deploy web-services on main/master server)
deploy_master:
  stage: deploy
  only:
    refs:
    - master
  tags:
    - shell
    - dspace
    - master
  script:
    - /usr/local/etc/devops/dspace-build-deploy.sh $HOME/$CI_PROJECT_DIR $DSPACE_DIR

# (procedures after deploying web-services on development server)
after_deploy_develop:
  stage: after
  when: manual
  only:
    refs:
      - develop
  tags:
    - shell
    - dspace
    - develop
  script:
    - /usr/local/etc/devops/dspace-post-processing.sh $DSPACE_DIR

# (procedures after deploying web-services on testing server)
after_deploy_testing:
  stage: after
  only:
    refs:
      - testing
  tags:
    - shell
    - dspace
    - testing
  script:
    - /usr/local/etc/devops/dspace-post-processing.sh $DSPACE_DIR

Фиксируем добавленный файл автоматизации коммитом и выгружаем его на сервер хранения репозиториев:

# sudo -u dspace git add .gitlab-ci.yml
# sudo -u dspace git commit -m "Add '.gitlab-ci.yml'."
# sudo -u dspace git push ssh://git@gitlab.example.net/group/dspace.git custom

Имейте в виду, что добавление файла ".gitlab-ci.yml" в ветви, реакция на события в которых в нём же и описана, побудит "GitLab" отработать соответствующую логику, так что не стоит это делать раньше готовности всей цепочки "деплоя" - ничего страшного не случится, но в истории запуска "pipelines" останется отметка о сбое процедуры.

Автоматизация сборки и развёртывания web-проекта "DSpace".

Как я уже упоминал выше, сборка и принципы развёртывания для серверов разработки, тестирования и публикации "DSpace" единообразна - потому скрипт одинаков для всех трёх серверов (основан на инструкции ручной процедуры сборки и развёртывания):

# mkdir -p /usr/local/etc/devops
# cd /usr/local/etc/devops
# vi ./dspace-build-deploy.sh && chmod g-w,+x ./dspace-build-deploy.sh && chown root:dspace ./dspace-build-deploy.sh

#!/bin/bash

# Получаем от "GitLab Runner" месторасположение исходных кодов
WORKING_DIRECTORY=${1}

# Получаем от "GitLab Runner" месторасположение целевой директории web-проекта
# (в нашем случае: "/var/lib/dspace")
DSPACE_DIR=${2}

# Фиксируем дату и время запуска скрипта
DATE=$(date +"%Y-%m-%d.%H:%M:%S")

# Активируем перехват и направление всего STDOUT-вывода в журнальный файл
exec &> >(tee -a "/var/log/dspace/dspace-build.log")

# Фиксируем начало блока журналирования датой и временем запуска процедуры
echo; echo ${DATE}

# Проверяем наличие ожидаемых приложений и утилит
[ -x "/usr/bin/java" ] && [ -x "/opt/apache-maven/bin/mvn" ] && [ -x "/opt/apache-ant/bin/ant" ] || { echo "Не обнаружен необходимый для работы набор приложений и утилит. Операция сборки \"DSpace\" прервана."; exit 1; }

# Проверяем наличие директории данных проекта и прерываем работу при отсутствии таковой
[ ! -d "${WORKING_DIRECTORY}" ] && { echo "Директория \"${WORKING_DIRECTORY}\" данных проекта, для которого запрошена сборка и развёртывание, отсутствует. Операция прервана."; exit 1; }

# Переходим в директорию проекта
cd "${WORKING_DIRECTORY}"

# Накладываем исправление некорректного SQL-скрипта подсистемы обновления "базы данных" до формата "DSpace v6.3"
if ! patch --dry-run --quiet --force --reverse -d ./ -p1 < /usr/local/etc/devops/dspace-6-ds-2701-hibernate-migration.fix-key-and-cascade.patch > /dev/null ; then
  echo "Source patching..."
  patch --forward -d ./ -p1 < /usr/local/etc/devops/dspace-6-ds-2701-hibernate-migration.fix-key-and-cascade.patch
  [ "${?}" -ne "0" ] && { echo "Внесение исправлений в исходный код завершено с ошибкой! Операция прервана."; exit 1; }
else
  echo "Source already patched."
fi

# Запускаем сборку Java-приложений проекта посредством "Maven"
echo "Assembling by \"Apache Maven\" running..."
/opt/apache-maven/bin/mvn clean package >> /var/log/dspace/dspace-build-maven.log

# Проверяем успешность сборки по коду завершения, поступившему от "Maven"
[ "${?}" -ne "0" ] && { echo "Сборка проекта посредством \"Maven\" завершена с ошибкой! Операция прервана."; exit 1; }

# Отмечаем успешное завершение первого этапа сборки
echo "Assembly by \"Apache Maven\" completed successfully."

# Подкладываем отсутствующие нужные "Solr Webapp" Java-библиотеки, копируя их из сборки "DSpace XML-UI"
find ${WORKING_DIRECTORY}/dspace/target/dspace-installer/webapps/xmlui/WEB-INF/lib -iname 'elasticsearch-*.jar' -exec cp -v {} ${WORKING_DIRECTORY}/dspace/target/dspace-installer/webapps/solr/WEB-INF/lib/ \;
find ${WORKING_DIRECTORY}/dspace/target/dspace-installer/webapps/xmlui/WEB-INF/lib -iname 'lucene-sandbox-*.jar' -exec cp -v {} ${WORKING_DIRECTORY}/dspace/target/dspace-installer/webapps/solr/WEB-INF/lib/ \;

# Удаляем и пересоздаём подставную директорию для "инсталляции" посредством "Ant"
rm -rf "${WORKING_DIRECTORY}/ant-dspace-target" && mkdir -p "${WORKING_DIRECTORY}/ant-dspace-target"

# Переходим в директорию с предварительно собранным "Maven" проектом
cd "${WORKING_DIRECTORY}/dspace/target/dspace-installer"

# Посредством "Apache Ant" производим финальную обработку настроек компонентов "DSpace"
echo "Building by \"Apache Ant\" running..."
/opt/apache-ant/bin/ant -Ddspace.dir=${WORKING_DIRECTORY}/ant-dspace-target fresh_install >> /var/log/dspace/dspace-build-ant.log

# Проверяем успешность обработки по коду завершения, поступившему от "Ant"
[ "${?}" -ne "0" ] && { echo "Подготовка проекта посредством \"Ant\" завершена с ошибкой! Операция прервана."; exit 1; }

# Отмечаем успешное завершение второго этапа сборки
echo "Build by \"Apache Ant\" completed successfully."

# Переходим в директорию с обработанным "Ant" проектом
cd "${WORKING_DIRECTORY}/ant-dspace-target"

# Заменяем в конфигурационных файлах имя подставной инсталляционной директории на реальное месторасположение файлов web-проекта
# (ради использования в "sed" переменных со "слешами" заменяем разделитель "/" на "#")
grep -ril -e "${WORKING_DIRECTORY}/ant-dspace-target" ./ | xargs sed -i -e "s#${WORKING_DIRECTORY}\/ant-dspace-target#${DSPACE_DIR}#gI"

# Удаляем устаревшую библиотеку, несовместимую с современным "Tomcat"
[ -f "${WORKING_DIRECTORY}/ant-dspace-target/lib/servlet-api-2.5.jar" ] && { rm -fv "${WORKING_DIRECTORY}/ant-dspace-target/lib/servlet-api-2.5.jar"; }

# Создаём пустой конфигурационный файл, который хочет видеть "DSpace" на этапе запуска
[ ! -f "${WORKING_DIRECTORY}/ant-dspace-target/config/local.cfg" ] && { touch "${WORKING_DIRECTORY}/ant-dspace-target/config/local.cfg"; }

# Отмечаем завершение сборки
echo "Build finished."

# Останавливаем web-сервис "Tomcat"
# (необходимы разрешительные правила в SUDO и "PolicyKit")
echo "Apache Tomcat stoping..."
systemctl stop dspace-tomcat

# Перемещаем замещаемые новыми ресурсы в резервную копию
echo "Backup..."
BACKUP_DIR=".backup_${DATE}"
mkdir -p "${DSPACE_DIR}/${BACKUP_DIR}"
[ -d "${DSPACE_DIR}/bin" ] && { mv -f "${DSPACE_DIR}/bin" "${DSPACE_DIR}/${BACKUP_DIR}"; }
[ -d "${DSPACE_DIR}/config" ] && { mv -f "${DSPACE_DIR}/config" "${DSPACE_DIR}/${BACKUP_DIR}"; }
[ -d "${DSPACE_DIR}/etc" ] && { mv -f "${DSPACE_DIR}/etc" "${DSPACE_DIR}/${BACKUP_DIR}"; }
[ -d "${DSPACE_DIR}/lib" ] && { mv -f "${DSPACE_DIR}/lib" "${DSPACE_DIR}/${BACKUP_DIR}"; }
[ -d "${DSPACE_DIR}/webapps" ] && { mv -f "${DSPACE_DIR}/webapps" "${DSPACE_DIR}/${BACKUP_DIR}"; }
# (Apache Solr)
if [ -d "${DSPACE_DIR}/solr" ] ; then
  cd "${DSPACE_DIR}"
  find ./solr \( -type d -iname "conf" -o -iname "solr.xml" \) -print0 | xargs --null --no-run-if-empty -I {} cp -r --parents {} "${DSPACE_DIR}/${BACKUP_DIR}"
  find ./solr \( -type d -iname "conf" -o -iname "solr.xml" \) -print0 | xargs --null --no-run-if-empty -I {} rm -rf {}
fi

# Удаляем старые резервные копии, усекая их количество до трёх штук
if [ "$(find "${DSPACE_DIR}" -maxdepth 1 -type d -iname ".backup_*" -print | wc -l)" -gt "3" ]; then
  # Удаляем все старее последних трёх (естественная сортировка по имени файла)
  echo "Truncation of backups..."
  find "${DSPACE_DIR}" -maxdepth 1 -type d -iname ".backup_*" -print | sort | head -n -3 | xargs --no-run-if-empty -I {} rm -rf {}
fi

# Избирательно развёртываем компоненты проекта в директорию web-сервиса
echo "Deploy..."
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/bin" "${DSPACE_DIR}"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/config" "${DSPACE_DIR}"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/etc" "${DSPACE_DIR}"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/lib" "${DSPACE_DIR}"
# (Tomcat Webapps)
mkdir -p "${DSPACE_DIR}/webapps"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/webapps/xmlui" "${DSPACE_DIR}/webapps"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/webapps/rest" "${DSPACE_DIR}/webapps"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/webapps/solr" "${DSPACE_DIR}/webapps"
cp -rf "${WORKING_DIRECTORY}/ant-dspace-target/webapps/oai" "${DSPACE_DIR}/webapps"
# (Apache Solr)
mkdir -p "${DSPACE_DIR}/solr"
cd "${WORKING_DIRECTORY}/ant-dspace-target"
find ./solr \( -type d -iname "conf" -o -iname "solr.xml" \) -print0 | xargs --null --no-run-if-empty -I {} cp -r --parents {} "${DSPACE_DIR}"

# Отмечаем завершение развёртывания
echo "Deploy finished."

# Запускаем web-сервис "Tomcat"
echo "Apache Tomcat running..."
systemctl start dspace-tomcat

exit ${?}

Перед развёртыванием web-приложений на площадках разработки и тестирования мы полностью заменяем содержимое "базы данных", отчего индексы полнотекстового поиска могут стать неактуальными - потому заведём скрипт, запускаемый после успешного "деплоя", задачей которого будет переиндексация БД:

# cd /usr/local/etc/devops
# vi ./dspace-post-processing.sh && chmod g-w,+x ./dspace-post-processing.sh && chown root:dspace ./dspace-post-processing.sh

#!/bin/bash

# Получаем от "GitLab Runner" месторасположение целевой директории web-проекта
DSPACE_DIR=${1}

# Запускаем удаление индексов полнотекстового поиска, сопоставленных с документами, удалёнными из основной "баз данных"
CLASSPATH=/opt/dspace/tomcat/lib/* "${DSPACE_DIR}/bin/dspace" index-discovery -c

# Запускаем профилактическое обновление индексов полнотекстового поиска (в фоновом режиме)
CLASSPATH=/opt/dspace/tomcat/lib/* setsid nohup "${DSPACE_DIR}/bin/dspace" index-discovery > /dev/null 2>&1 &

exit ${?}

Скрипты сборки и развёртывания написаны таким образом, что их можно вызывать как из "GitLab Runner" в автоматизированных процедурах, так и вручную, заранее заполучив исходные коды проекта. Например, так проводится предварительное тестирование:

# mkdir -p /var/tmp/build && chown -R dspace /var/tmp/build && cd /var/tmp/build
# sudo -u dspace git clone --branch testing ssh://git@gitlab.example.net/group/dspace.git
# sudo -u dspace /usr/local/etc/devops/dspace-build-deploy.sh /var/tmp/build/dspace /var/lib/dspace
# sudo -u dspace /usr/local/etc/devops/dspace-post-processing.sh /var/lib/dspace

Заводим аккаунты пользователей-разработчиков.

Учитывая то, что все ресурсы web-сервиса "DSpace" находятся в собственности пользователя "dspace" и взаимосвязи с внешними сервисами также налажены от его имени, наверняка разработчикам на выделенном для них сервере "dev-dspace.example.net" понадобится возможность исполнять команды от имени такового:

# useradd -m -s /bin/bash -g dspace developer-one && passwd developer-one

Очень важно установить для пользователей, создающих файлы, которые должны быть доступны для одногруппников вроде сервиса "DSpace", маску разрешений записи для группы и запрета чтения (не не открытия директорий) для всех остальных:

# sed -i -e "s/^#\?umask.*/umask 006/gI" /home/developer-one/.profile

Используя функционал расширения конфигурации SUDO добавляем правило разрешения разработчикам запуска команд от имени пользователя "dspace" в отдельном файле конфигурации (обращаю внимание на то, что для вызова через SUDO маску создаваемых файлов нужно задавать явно):

# vi /etc/sudoers.d/dspace-users

Defaults umask=006
....
developer-one ALL=(dspace:dsapce) NOPASSWD: ALL
developer-two ALL=(dspace:dsapce) NOPASSWD: ALL

Если переопределение "umask" для целевых пользователей не сработает, то возможно это запрещено опцией "Defaults umask_override" в конфигурационном файле "/etc/sudoers".

Обязательно проверяем синтаксическую корректность вносимых изменений:

# visudo -cf /etc/sudoers.d/dspace-users

Ограничиваем доступ к файлу расширенной конфигурации SUDO:

# chown root:root /etc/sudoers.d/dspace-users
# chmod go-rwx /etc/sudoers.d/dspace-users

Предоставляем разработчикам подсказку-инструкцию при входе в систему.

Полезно проинформировать разработчиков об имеющихся у них в распоряжении инструментах, сформировав простейшую инструкцию в текстовом виде:

# vi /home/developer-one/README

Для всех трёх серверов DSpace: "dev-dspace.example.net", "test-dspace.example.net" и "dspace.example.net" - налажены автоматизированные процедуры сборки и развёртывания web-сервиса. Для сервера разработки "dev-dspace.example.net" имеется возможность вести работу локально, не засоряя репозиторий хранения промежуточными "коммитами".

Рекомендуем придерживаться нижеследующего сценария загрузки исходного кода, его модификации, сборки, развёртывания и финальной выгрузки в репозиторий хранения.

Загрузка исходного кода с репозитория хранения:

$ mkdir -p $HOME/build && cd $HOME/build
$ sudo -u dspace git clone --branch develop ssh://git@gitlab.example.net/group/dspace.git

Запуск сборки проекта "Maven", подготовки его посредством "Ant" и развёртывания с промежуточными "патчами" и добавлением библиотек (на вход подаются две переменные: директория с исходным кодом и директория с рабочим проектом):

$ sudo -u dspace /usr/local/etc/devops/dspace-build-deploy.sh $HOME/build/dspace /var/lib/dspace

Вышеприведённый скрипт можно запускать многократно, после каждого изменения в исходном коде.

Выгрузка наработок в репозиторий хранения:

$ cd $HOME/build
$ sudo -u dspace git push ssh://git@gitlab.example.net/group/dspace.git develop

При замене содержимого "базы данных" или повреждении индексов полнотекстового поиска может понадобится принудительная переиндексация:

$ sudo -u dspace CLASSPATH=/opt/dspace/tomcat/lib/* /var/lib/dspace/bin/dspace index-discovery -b

Рекомендации и сообщения о проблемах шлите на почтовый ящик "admin@example.net".

Чтобы разработчики не забывали о регламентах, можно наладить вывод содержимого файла "README" при каждом входе в систему, а не особо нужные им системные сообщения отключить:

# FORUSER="developer-one" && echo -e "\n# Show README file on login\necho; cat \"/home/${FORUSER}/README\" 2>/dev/null" >> /home/${FORUSER}/.profile && touch /home/${FORUSER}/.hushlogin && chmod -w /home/${FORUSER}/.hushlogin


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


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