Backup for Jenkins

30 декабря 2019

Резервное копирование для Jenkins

Все, кто занимается задачами CI/CD, так или иначе знают про Jenkins, даже если им посчастливилось не иметь с ним дела.

Этот непотопляемый кадавр продолжает жить и процветать по следующим причинам:

  • гибкость настроек (от настраиваемых диалоговых окон до встроенного Groovy);
  • архитектура с поддержкой плагинов и обширный набор готовых плагинов на все случаи жизни;
  • и самое главное — большой накопленный объём пользовательских проектов, которые проще продолжать развивать и поддерживать в среде Дженкинса, чем пытаться мигрировать на более современные и приятные платформы.

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

Однако здесь возникают два небольших препятствия:

  • настройки, временные данные и файлы проектов смешаны внутри рабочего каталога в одну кучу;
  • штатного механизма не существует, вместо него есть энное число посторонних костылей, распространяемых в виде плагинов, описаний и копипаст.

Поэтому, взяв за основу один из таких костылей, мы написали свой собственный.

Список предварительных пожеланий к нему был примерно таким:

  • резервная копия должна иметь минимальный размер и максимальную скорость создания (например, дамп виртуальной машины или снимок ZFS с сотнями гигабайт проектов не годится);
  • после восстановления нам достаточно иметь полностью настроенный сервер без предыдущего состояния — который знает, как выполнять новые job'ы, но ничего не помнящий про старые;
  • поскольку настройки имеют текстовый вид, пусть они сохраняются в Git-репозиторий;
  • если Jenkins предназначен для автоматического выполнения заданий, пусть самостоятельно выполняет всю работу по своему обслуживанию.

Общая схема:

  • в $JENKINS_HOME создаётся Git-репозиторий;
  • на Git-сервере для него создаётся origin (далее приводятся настройки для Gitlab);
  • в Дженкинсе создаётся периодическое задание, которое сохраняет в Git ключевые файлы;
  • для уменьшения размера от плагинов сохраняются только манифесты, при восстановлении плагины скачиваются заново.

Сначала создайте пользователя и репозиторий в Gitlab'e:

  • пользователь = jenkins-backup-robot;
  • репозиторий = jenkins-configs;
  • URL репозитория скопируйте в буфер обмена;
  • откройте Repository => Settings => Members;
  • назначьте jenkins-backup-robot мантейнером (иначе Gitlab не даст сделать ему первый push в пустой репозиторий).

Теперь идите в командную строку сервера, на котором работает Jenkins:

  • нам требуется создать SSH-ключ и Git-репозиторий:
  • sudo -Hiu jenkins
    cd ~
    git init
    git config --global user.name Jenkins
    git config --global user.email "jenkins@$(hostname -f)"
    git remote add origin ВСТАВЬТЕ_ЗДЕСЬ_URL_GIT_РЕПОЗИТОРИЯ
    test -s ~/.ssh/id_rsa.pub || ssh-keygen
    cat ~/.ssh/id_rsa.pub

Созданный SSH-ключ надо импортировать в Gitlab:

  • Это делается в разделе Admin => Users => jenkins-backup-robot => Impersonate => Personal Settings => SSH keys.

Создайте в Дженкинсе новое задание:

  • Name: Backup Jenkins configs to Git
  • Type: Free job
  • Label: master
  • SCM: None
  • Build: Build Periodically
  • Schedule: 20 04 * * *
  • Build step: Execute Shell
  • Command:
  • #!/bin/sh -e
    
    cd "$JENKINS_HOME"
    
    # Add general configurations, secrets, job configurations, nodes, user content, users and plugins info:
    ls -1d *.xml secrets/ jobs/*/*.xml nodes/*/*.xml userContent/* users/*/config.xml \
        plugins/*/META-INF/MANIFEST.MF 2>/dev/null | grep -v '^queue.xml$' | xargs -r -d '\n' git add --
    
    # Track deleted files:
    LANG=C git status | awk '$1 == "deleted:" { print $2; }' | xargs -r git rm --ignore-unmatch
    
    LANG=C git status | grep -q '^nothing to commit' || {
        git commit -m "Automated Jenkins commit at $(date '+%Y-%m-%d %H:%M')"
        git push -q -u origin master
    }
  • SAVE

Пояснения:

  • Метка “master” нужна, если у Дженкинса есть slave-узлы. Если их нет, метку в задании можете не указывать. Если они есть, то в списке узлов отредактируйте свойства мастера и в поле “Labels” добавьте “master”. Если вы этого не сделаете, Дженкинс попытается выполнять задание через агентов на всех узлах, а это явно не то, что нам требуется.
  • Если для подключения к Git-серверу используется SSH, перед выполнением задания не забудьте подключиться к нему вручную, чтобы git push не завершался с ошибкой из-за StrictHostKeyChecking.

Заключительный шаг в Gitlab'e после успешного git push:

  • в свойствах репозитория откройте раздел Members и понизьте уровень доступа для Дженкинса с Maintainer до Developer;
  • в Settings => Repository => Protected branches поменяйте для ветки “master” разрешение “Allow to push” с Maintainers на Maintainers+Developers.

Восстановление плагинов:

  • Т.к. мы сохраняем только манифесты плагинов, после восстановления из резервной копии нам потребуется просканировать каталог с манифестами и составить список команд для загрузки дистрибутивов:
  • sudo -Hiu jenkins
    cd ~/plugins/
    gawk 'BEGIN     { RS = "\r\n" }
          BEGINFILE { n = v = "" }
          ENDFILE   { printf "curl -sS -L -O http://updates.jenkins-ci.org/download/plugins/%s/%s/%s.hpi\n", n, v, n }
          $1 == "Short-Name:"     { n = $2 }
          $1 == "Plugin-Version:" { v = $2 }
        ' ./*/META-INF/MANIFEST.MF > ./download_all_plugins.sh
  • Обратите внимание, что вместо awk используется gawk (GNU awk), т.к. классический awk не понимает BEGINFILE и ENDFILE. В некоторых дистрибутивах gawk устанавливается по умолчанию, в некоторых его потребуется установить вручную.
  • Если gawk отработает без ошибок, запустите сгенерированный им файл:
  • sh download_all_plugins.sh
  • Самостоятельно распаковывать и устанавливать скачанные hpi-файлы не требуется — перезапустите Дженкинс и он сделает это сам.


← Назад в Блог

Подпишитесь на новые статьи: