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

Docs as Code. Часть 1: автоматизируем обновление

Docs as Code. Часть 1: автоматизируем обновление - 1

В больших проектах, состоящих из десятков и сотен взаимодействующих сервисов, всё чаще становится обязательным подход к документации как к коду — docs as code [1].

Я покажу, как можно применять эту философию в реалиях classified-сервиса, а точнее, начну с первого этапа её внедрения: автоматизации обновления данных в документации.

Набор инструментов

Принцип «документация как код» подразумевает использование при написании документации того же инструментария, что и при создании кода: языков разметки текста, систем контроля версий, code review и авто-тестов. Главная цель: создать условия для совместной работы всей команды над итоговым результатом — полноценной базой знаний и инструкций по использованию отдельных сервисов продукта. Далее я расскажу о конкретных инструментах, выбранных нами для решения этой задачи.

В качестве языка разметки текста мы решили использовать наиболее универсальный — reStructuredText [2]. Помимо большого количества директив, которые предоставляют все основные функции для структурирования текста, этот язык поддерживает ключевые конечные форматы, в том числе необходимый для нашего проекта HTML.

Файлы конвертируются из .rst в .html посредством генератора документации Sphinx [3]. Он позволяет создавать статические сайты, для которых можно делать собственные или использовать уже готовые темы оформления [4]. В нашем проекте используются две готовые темы — stanford-theme [5] и bootstrap-theme [6]. Вторая содержит подтемы, позволяющие задавать разные цветовые схемы для ключевых элементов интерфейса.

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

Исходные файлы проекта хранятся в Bitbucket-репозитории, причём сайт генерируется только из файлов, содержащихся в ветке master. Обновить в ней данные можно только через pull-request, что позволяет проверять все новые разделы документации перед тем, как они будут опубликованы в общем доступе.

Поскольку между завершением нового раздела документации и его отправкой на сайт нужно проверять его содержание, ключевым во всей цепочке становится сам процесс сборки сайта и обновления данных на хосте. Эта процедура должна повторяться каждый раз после того, как pull-request с обновлением сливается с главной веткой проекта.

Docs as Code. Часть 1: автоматизируем обновление - 2

Реализовать подобную логику позволяет Jenkins [7] — система непрерывной интеграции разработки, в нашем случае — документации. Подробнее о настройке я расскажу в разделах:

  1. Добавление нового узла в Jenkins [8]
  2. Описание Jenkinsfile [9]
  3. Интеграция Jenkins и Bitbucket [10]

Добавление нового узла в Jenkins

Для сборки и обновления документации на сайте необходимо зарегистрировать в качестве агента Jenkins [7] заранее подготовленную для этого машину.

Подготовка машины

Согласно требованиям Jenkins [11], на всех компонентах, входящих в систему, включая мастер-машину и все зарегистрированные агентские узлы, должна быть установлена JDK или JRE. В нашем случае будет использоваться JDK 8 [12], для установки которой достаточно выполнить команду:

sudo apt-get -y install java-1.8.0-openjdk git

Мастер-машина будет подключаться к агенту для выполнения на нём назначенных задач. Для этого на агенте необходимо создать пользователя, под которым будут выполняться все операции, и в домашней папке которого будут храниться все генерируемые Jenkins [7]-файлы. В Linux-системах достаточно выполнить команду:

sudo adduser jenkins --shell /bin/bash
su jenkins

Для установки соединения между мастер-машиной и агентом необходимо настроить SSH и добавить необходимые ключи авторизации. Сгенерируем ключи на агенте, после чего для пользователя jenkins добавим публичный ключ в файл authorized_keys.

Собирать сайт с документацией будем в Docker-контейнере, использующем готовый образ python:3.7 [13]. Для установки Docker на агенте следуйте инструкциям официальной документации [14]. Чтобы завершить процесс установки, необходимо переподключиться к агенту. Проверим корректность установки, выполнив команду, которая загружает тестовый образ:

docker run hello-world

Чтобы не приходилось запускать команды Docker от имени суперпользователя (sudo), достаточно добавить в созданную на этапе установки группу docker пользователя, от имени которого будут выполняться команды.

sudo usermod -aG docker $USER

Конфигурация нового узла в Jenkins

Поскольку подключение к агенту требует авторизации, в настройках Jenkins необходимо добавить соответствующие учётные данные. Подробная инструкция о том, как это делать на Windows-машинах, представлена в официальной документации Jenkins [15].

ВАЖНО: Идентификатор, который задаётся в разделе Настроить Jenkins -> Управление средами сборки -> Имя узла -> Настроить в параметре Метки, в дальнейшем используется в Jenkinsfile [16] для указания агента, на котором будут выполнены все операции.

Описание Jenkinsfile

В корне репозитория проекта хранится Jenkinsfile [16], который содержит инструкции по:

  • подготовке среды сборки и установке зависимостей;
  • сборке сайта с помощью Sphinx [3];
  • обновлению информации на хосте.

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

Указание агента

В начале Jenkinsfile [16] укажем метку агента в Jenkins [7], на котором будут выполняться все операции. Для этого необходимо использовать директиву agent [17]:

agent {
  label 'название-метки'
}

Подготовка среды

Для выполнения команды сборки сайта sphinx-build необходимо задать переменные среды, в которых будут храниться актуальные пути к данным. Также для обновления информации на хосте необходимо заранее указать пути, где хранятся данные сайта с документацией. Присвоить эти значения переменным позволяет директива environment [18]:

environment {
  SPHINX_DIR = '.' //папка, в которой установлен Sphinx
  BUILD_DIR = 'project_home_built' //папка с собранным сайтом
  SOURCE_DIR = 'project_home_source' //папка с исходными .rst и .md файлами
  DEPLOY_HOST = 'username@127.1.1.0:/var/www/html/' //пользоатель@IP_адрес_хоста:папка_с_сайтом
}

Основные действия

Основные инструкции, которые будут выполняться в Jenkinsfile [16], содержатся внутри директивы stages [19], которая состоит из разных шагов, описываемых директивами stage [20]. Простой пример трёхэтапного CI-pipeline:

pipeline {
  agent any
  stages {
    stage('Build') {  
      steps {
        echo 'Building..'
      }
    }
    stage('Test') {
      steps {
        echo 'Testing..'
      }
    }
    stage('Deploy') {
      steps {
        echo 'Deploying....'
      }
    }
  }
}

Запуск контейнера Docker и установка зависимостей

Сначала запустим Docker-контейнер с готовым образом python:3.7 [13]. Для этого воспользуемся командой docker run [21] с флагами --rm [22] и -i [23]. Затем последовательно сделаем следующее:

  • установим python virtualenv [24];
  • создадим и активируем новую виртуальную среду;
  • установим в ней все необходимые зависимости, перечисленные в файле
    requirements.txt, который хранится в корне репозитория проекта.

stage('Install Dependencies') {
  steps {
    sh '''
      docker run --rm -i python:3.7
      python3 -m pip install --user --upgrade pip
      python3 -m pip install --user virtualenv
      python3 -m virtualenv pyenv
      . pyenv/bin/activate
      pip install -r ${SPHINX_DIR}/requirements.txt
    '''
  }  
}

Сборка сайта с документацией

Теперь соберём сайт. Для этого необходимо выполнить команду sphinx-build [25] со следующими флагами:

-q: записывать в лог только предупреждения и ошибки;
-w: записывать лог в указанный после флага файл;
-b: имя сборщика сайта;
-d: указать директорию для хранения кешированных файлов — doctree pickles.

Перед запуском сборки, с помощью команды rm -rf удалим предыдущую сборку сайта и логи. В случае ошибки на одном из этапов в консоли Jenkins [7] появится лог выполнения sphinx-build [25].

stage('Build') {
  steps {
    // clear out old files
    sh 'rm -rf ${BUILD_DIR}'
    sh 'rm -f ${SPHINX_DIR}/sphinx-build.log'
    sh '''
      ${WORKSPACE}/pyenv/bin/sphinx-build -q -w ${SPHINX_DIR}/sphinx-build.log 
      -b html 
      -d ${BUILD_DIR}/doctrees ${SOURCE_DIR} ${BUILD_DIR}
    '''
  }
  post {
    failure {
      sh 'cat ${SPHINX_DIR}sphinx-build.log'
    }
  }
}

Обновление сайта на хосте

И в завершение обновим информацию на хосте, обслуживающем доступный в локальной среде сайт с документацией продукта. В текущей реализации хостом служит та же виртуальная машина, которая зарегистрирована в качестве Jenkins-агента для выполнения задач по сборке и обновлению документации.

В качестве инструмента синхронизации используем утилиту rsync [26]. Для её корректной работы необходимо настроить подключение по SSH между Docker-контейнером, в котором собирался сайт с документацией, и хостом.

Чтобы можно было с помощью Jenkinsfile [16] настраивать подключение по SSH, в Jenkins [7] нужно установить следующие плагины [27]:

  1. SSH Agent Plugin [28] — позволяет использовать в скриптах шаг sshagent для предоставления учётных данных вида имя_пользователя/ключ.
  2. SSH Credentials Plugin [29] — позволяет сохранять в настройках Jenkins [7] учётные данные вида имя_пользователя/ключ.

После установки плагинов необходимо указать актуальные учётные данные для подключения к хосту, заполнив форму в разделе Credentials:

  • ID: идентификатор, который будет использоваться в Jenkinsfile [16] на шаге sshagent для указания конкретных учётных данных (docs-deployer);
  • Username: имя пользователя, под которым будут выполняться операции обновления данных сайта (пользователь должен иметь доступ на запись в папку /var/html хоста);
  • Private Key: приватный ключ для доступа к хосту;
  • Passphrase: пароль для ключа, если он задавался на этапе генерирования.

Ниже представлен код скрипта, который подключается по SSH и обновляет информацию на хосте, используя заданные на этапе подготовки среды системные переменные с путями к необходимым данным. Результат выполнения команды rsync [26] записывается в лог, который будет выводиться в консоли Jenkins [7] в случае ошибок синхронизации.

stage('Deploy') {
  steps {
    sshagent(credentials: ['docs-deployer']) {
    sh '''
      #!/bin/bash
      rm -f ${SPHINX_DIR}/rsync.log
      RSYNCOPT=(-aze 'ssh -o StrictHostKeyChecking=no')
      rsync "${RSYNCOPT[@]}" 
      --delete 
      ${BUILD_DIR_CI} ${DEPLOY_HOST}/
    '''
    }
  }

  post {
    failure {
      sh 'cat ${SPHINX_DIR}/rsync.log'
    }
  }
}

Интеграция Jenkins и Bitbucket

Существует много способов организовать взаимодействие Jenkins [7] и Bitbucket [30], но в нашем проекте мы решили использовать плагин Parameterized Builds for Jenkins [31]. В официальной документации есть подробная инструкция по установке [32] плагина, а также приведены настройки, которые должны быть заданы для обеих систем. Для работы с этим плагином необходимо создать пользователя Jenkins [7] и сгенерировать для него специальный токен, который позволит этому пользователю авторизоваться в системе.

Создание пользователя и API-токена

Для создания нового пользователя в Jenkins [7] необходимо перейти в раздел Настройки Jenkins -> Управление пользователями -> Создать пользователя, и в форме заполнить все необходимые учётные данные.

Механизм аутентификации, позволяющий сторонним скриптам или приложениям использовать API Jenkins без фактической передачи пароля пользователя, представляет собой специальный API-токен, который можно сгенерировать для каждого пользователя Jenkins [7]. Для этого:

  • авторизуйтесь в консоли управления, используя реквизиты пользователя, созданного ранее;
  • перейдите в раздел Настроить Jenkins -> Управление пользователями;
  • нажмите на значок шестерёнки справа от имени пользователя, под которым авторизовались в системе;
  • в списке параметров найдите API Token и нажмите на кнопку Add new Token;
  • в появившемся поле укажите идентификатор API-токена и нажмите кнопку Generate;
  • следуя подсказке на экране, скопируйте и сохраните сгенерированный API-токен.

Теперь в настройках сервера Bitbucket [30] можно указывать пользователя по умолчанию для подключения к Jenkins [7].

Заключение

Если раньше процесс состоял из нескольких шагов:

  • загрузить обновление в репозиторий;
  • дождаться подтверждения корректности;
  • собрать сайт с документацией;
  • обновить информацию на хосте;

то теперь достаточно нажать одну кнопку Merge в Bitbucket. Если после проверки не требуется вносить изменения в исходные файлы, то актуальная версия документации обновляется сразу же после подтверждения корректности данных.

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

Автоматизация этого процесса — первый шаг в построении инфраструктуры по управлению документацией. В дальнейшем мы планируем добавить автоматические тесты, которые будут проверять корректность внешних ссылок, используемых в документации, а также хотим создать интерактивные объекты интерфейса, встраиваемые в готовые темы для Sphinx [3].

Спасибо дочитавшим за внимание, скоро мы продолжим делиться подробностями создания документации в нашем проекте!

Автор: Алексей Солнцев

Источник [33]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/323950

Ссылки в тексте:

[1] docs as code: https://www.writethedocs.org/guide/docs-as-code/

[2] reStructuredText: http://docutils.sourceforge.net/rst.html

[3] Sphinx: https://www.sphinx-doc.org/en/master/index.html

[4] темы оформления: https://sphinx-themes.org/

[5] stanford-theme: https://pypi.org/project/stanford-theme/

[6] bootstrap-theme: https://pypi.org/project/sphinx-bootstrap-theme/

[7] Jenkins: https://jenkins.io/

[8] Добавление нового узла в Jenkins: #dobavlenie-novogo-uzla-v-jenkins

[9] Описание Jenkinsfile: #opisanie-jenkinsfile

[10] Интеграция Jenkins и Bitbucket: #integraciya-jenkins-i-bitbucket

[11] требованиям Jenkins: https://jenkins.io/doc/administration/requirements/java/

[12] JDK 8: https://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html

[13] python:3.7: https://hub.docker.com/_/python

[14] официальной документации: https://docs.docker.com/install/linux/docker-ce/ubuntu/

[15] официальной документации Jenkins: https://wiki.jenkins.io/display/JENKINS/Step+by+step+guide+to+set+up+master+and+agent+machines+on+Windows

[16] Jenkinsfile: https://jenkins.io/doc/book/pipeline/jenkinsfile/

[17] директиву agent: https://jenkins.io/doc/book/pipeline/syntax/#agent

[18] директива environment: https://jenkins.io/doc/book/pipeline/syntax/#environment

[19] директивы stages: https://jenkins.io/doc/book/pipeline/syntax/#stages

[20] директивами stage: https://jenkins.io/doc/book/pipeline/syntax/#stage

[21] docker run: https://docs.docker.com/engine/reference/run/

[22] --rm: https://docs.docker.com/engine/reference/run/#clean-up---rm

[23] -i: https://docs.docker.com/engine/reference/run/#foreground

[24] python virtualenv: https://virtualenv.pypa.io/en/latest/

[25] sphinx-build: https://www.sphinx-doc.org/en/master/man/sphinx-build.html

[26] rsync: https://rsync.samba.org/

[27] установить следующие плагины: https://jenkins.io/doc/book/managing/plugins/

[28] SSH Agent Plugin: https://plugins.jenkins.io/ssh-agent

[29] SSH Credentials Plugin: https://wiki.jenkins.io/display/JENKINS/SSH+Agent+Plugin

[30] Bitbucket: https://bitbucket.org/product/

[31] Parameterized Builds for Jenkins: https://marketplace.atlassian.com/apps/1213179/parameterized-builds-for-jenkins?hosting=server&tab=overview

[32] инструкция по установке: https://github.com/KyleLNicholls/parameterized-builds

[33] Источник: https://habr.com/ru/post/459640/?utm_source=habrahabr&utm_medium=rss&utm_campaign=459640