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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


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


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


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


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


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


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


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


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


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



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


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

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


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


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


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


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

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


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

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


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


docker run hello-world

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


sudo usermod -aG docker $USER

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


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


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

Описание Jenkinsfile


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


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

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


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


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


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

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


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


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, содержатся внутри директивы stages, которая состоит из разных шагов, описываемых директивами stage. Простой пример трёхэтапного 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. Для этого воспользуемся командой docker run с флагами --rm и -i. Затем последовательно сделаем следующее:


  • установим python virtualenv;
  • создадим и активируем новую виртуальную среду;
  • установим в ней все необходимые зависимости, перечисленные в файле
    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 со следующими флагами:


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


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


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. Для её корректной работы необходимо настроить подключение по SSH между Docker-контейнером, в котором собирался сайт с документацией, и хостом.


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


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

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


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

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


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 и Bitbucket, но в нашем проекте мы решили использовать плагин Parameterized Builds for Jenkins. В официальной документации есть подробная инструкция по установке плагина, а также приведены настройки, которые должны быть заданы для обеих систем. Для работы с этим плагином необходимо создать пользователя Jenkins и сгенерировать для него специальный токен, который позволит этому пользователю авторизоваться в системе.


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


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


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


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

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


Заключение


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


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

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


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


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


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

Источник: https://habr.com/ru/company/youla/blog/459640/


Интересные статьи

Интересные статьи

Когда-то я проходил серию собеседований на Backend-Java-разработчика и записывал вопросы себе на будущее, чтобы потом можно было пробежаться и освежить память. Подумалось, что, вероятно...
Без раскачки. Самыми значимыми изменениями в проекте, по сравнению с предыдущими версиями сонара (один и два), являются упрощение аналоговой части и эволюция процесса сборки модулей в сто...
Вы наверняка слышали про игровую приставку Xbox 360, и что она «прошивается». Под «прошивкой» здесь имеется в виду обход встроенных механизмов защиты для запуска копий игр и самописного софта. И ...
Кинетическая энергия «Сапсана» на максимальной скорости — свыше 1500 мегаджоулей. Для полной остановки вся она должна быть рассеяна на тормозных устройствах Было дело, просили меня поподробн...
В последнее время JIRA активно используют организации, не имеющие прямой связи с IT. Специалистам, не знакомым ранее с JIRA, бывает сложно понять структуру JQL-запросов, если не привести примеры....