В прошлый раз мы создали механизм, позволяющий разворачивать нужное количество виртуальных машин и объединять их в сеть. Для их подключения в качестве сборочных узлов к CI-серверу осталось только запустить на них агентов Jenkins.
Для этого потребуется:
- Настроить сам сервер Jenkins на хостовой машине.
- Запустить агентов Jenkins в развёрнутых машинах c возможностью их взаимодействия с рабочим столом и запуска через них пользовательских "интерактивных" приложений. В частности - запуска клиентов 1С.
- Обеспечить максимально простое подключение агентов Jenkins к серверу без ключей и паролей, зная только имя хостовой машины.
Конфигурирование Jenkins на хостовой машины - это единственное, что мы не будем пытаться полностью автоматизировать. Дело в том что Jenkins - это своего рода конструктор, состоящий из плагинов. С помощью плагинов реализуется даже такая базовая функциональность, как работа с Git, управление правами доступа, возможность описывать сборочные линии кодом. Настройки плагинов при этом могут меняться от версии к версии и хранятся в виде XML-файлов. Неизменность формата этих файлов не гарантируется, поэтому настройка плагинов через bat- или bash-скрипты может привести к нестабильности системы. Хотя возможно я просто не копал так глубоко, чтобы научиться это делать правильно и Вы сможете найти стабильное решение этой задачи ;)
Выполняемых вручную настроек в Jenkins будет немного. Основной объем данных - настройки задач и узлов - мы всё же будем сохранять в составе репозитория и обеспечим возможность их восстановления на любом сервере.
В первой части этого цикла рассматривалась целесообразность разделения всего используемого кода на два независимых друг от друга репозитория:
- первый - инфраструктурный, обеспечивающий развёртывание среды исполнения для CI
- второй - хранящий исполняемый на CI код, а также настройки задач сборки и тестирования
Именно на данном этапе имеет смысл выполнить такое разделение. Настройки задач и узлов Jenkins, а также код их архивации и развёртывания, сохраним в в отдельном репозитории:
- на Гитхаб: https://github.com/vladimirlitvinenko84/ci-for-1c-based-on-jenkins
- и его зеркале на Гитлаб: https://gitlab.com/vladimirlitvinenko84/ci-for-1c-based-on-jenkins
Данный репозиторий должен позволять переносить все задачи сборки и связанный с ними код на любую инфраструктуру, независимо от того, как она создана - вручную на основе физических машин, или автоматически с помощью Packer и Vagrant, или каким-либо другим способом.
В то же время мы завершим настройку отдельных виртуальных машин для их развёртывания через Vagrant. Обеспечим их подключение к серверу Jenkins. Этот код по прежнему сохраним в "инфраструктурном" репозитории, с которым работали ранее.
Обновление Jenkins и первый вход в систему
Начнем работу с Jenkins с его обновления. Ранее был написан скрипт, осуществляющий конфигурирование хостовой машины. С помощью него мы устанавливали на хостовой машине Jenkins и включали в нём поддержку UTF-8 следующими командами:
choco install jenkins -y --force
sed -i "s/<arguments>-/<arguments>-Dfile.encoding=UTF-8 -Dpermissive-script-security.enabled=true -/" "C:\Program Files (x86)\Jenkins/jenkins.xml"
Если с тех пор Вы не обновляли Jenkins, то можно воспользоваться преимуществами менеджера пакетов Chocolaty и выполнить обновление всего одной командой:
choco update jenkins -y
Главное, что происходит при обновлении - это загрузка и замена на новую версию файла jenkins.war - основного исполняемого файла, который на Windows расположен в каталоге C:\Program Files (x86)\Jenkins. После обновления можно приступить к первому входу в систему и первоначальной настройке.
Работа с Jenkins ведётся через веб-интерфейс. Если служба Jenkins запущена, то в системе работает веб-сервер на порту, который указан в файле jenkins.xml:
Таким образом, чтобы подключиться к нему с локального компьютера в адресной строке браузера достаточно вбить http://localhost:8080.
При первом входе в системе еще нет пользователей и поэтому Jenkins попросит подтвердить, что мы имеем доступ к серверу (к его файловой системе). Для этого нужно ввести в поле пароля содержимое файла, путь к которому будет здесь указан:
Получить содержимое этого файла можно через любой редактор или команду cat:
После создания первого же пользователя в системе этот файл будет удален. Далее нужно будет выбрать вариант установки. Будет предложена установка c самостоятельным выбором плагинов или с установкой рекомендуемых плагинов. Для наших целей подойдет вариант с рекомендуемыми плагинами. Это сократит время на дальнейшую настройку системы, а тюнинг для оптимизации занимаемого на диске места или скорости работы можно будет выполнить когда будет получен базовый опыт работы с этой системой. Кроме того плагины всегда можно не только установить, но и удалить ненужные.
То, что таким образом мы серьёзно экономим время настройки можно увидеть по списку устанавливаемых плагинов из которых нам в дальнейшем пригодятся:
- Folders - для создания папок с задачами сборки для 1С и их логического отделения от других задач
- Build Timeout - для задания максимального времени этапов сборки и предотвращения "вечного" зависания сборок
- Timestamper - для вывода в логи сборок времени выполнения отдельных действий-шагов
- Pipeline - для программирования сборочных линий на языке Groovy вместо накликивания мышкой с ограниченными возможностями
- Git - для получения кода пайплайнов и сценарных тестов из git-репозитория
- Email Extension - для отправки оповещений об упавших сборках на почту администратору
- Matrix Authorization Strategy - для управления доступом. Возможности этого плагина мы будем скорее отключать, чем использовать, но при этом немного познакомимся с управлением доступом к Jenkins
После завершения установки плагинов остаётся только задать имя и пароль первого администратора системы.
Кстати, здесь есть команда-ссылка "Continue as admin" нажав на которую можно пропустить шаг создания администратора, но в этом случае при следующем входе в Jenkins снова придётся указывать текст из файла C:/Program Files (x86)/Jenkins/secrets/initialAdminPassword. Если же этот шаг не пропустить, то данный файл будет больше не нужен и будет автоматически удалён:
Более подробно с процессом установки можно ознакомиться, посмотрев следующие два видео с канала Кирилла Семаева (а заодно рекомендую ознакомиться и с остальным содержимым канала):
Итак, теперь мы зашли в Jenkins и у нас есть настроенные стандартные плагины. Состав плагинов по умолчанию почти полностью покрывает наши потребности в построении CI для 1С. Приступим к конфигурированию системы.
Общие настройки Jenkins
HTTP-адрес сервера
Первое что необходимо сделать - это настроить http-адрес сервера на основе имени хостовой машины.
По умолчанию Jenkins использует порт 8080. Если необходимо изменить порт, то сделать это можно всё в том же файле jenkins.xml в каталоге установки, в котором мы ранее включали поддержку UTF-8. Но если порт не менялся, то адрес, который нужно задать в настройках должен выглядеть следующим образом: http://ИМЯ-КОМЬЮТЕРА:8080
Выполнить эту настройку нужно потому, что многие внутренние гиперссылки внутри самого Jenkins и гиперссылки в плагинах для отчетности о результатах тестирования и e-mail рассылок генерируются относительно этого адреса. Если указать здесь что-то вроде http://localhost:8080, то и ссылка приходящая на почту или добавляемая в отчет будет выглядеть как http://localhost:8080/job/job_group/job/job_name/95. Конечно, такого лучше избегать. Поэтому нужно перейти в конфигурацию системы:
и заполнить поле Jenkins URL. При желании также можно указать e-mail администратора системы. На этот e-mail будут приходить сообщения об исключительных ситуациях и ошибках:
Важный момент. После сохранения этой настройки в адресной строке браузера также стоит указывать именно этот адрес (при доступе в Jenkins через веб-интерфейс). Если продолжить в адресной строке указывать localhost:8080, то после входа в систему в web-интерфейсе будет светиться надпись, о том, что возможно эта настройка задана некорректно. Иными словами адрес заданный в настройках Jenkins URL должен совпадать с тем, через который осуществляется вход в веб-интерфейс сервера.
Настройка оповещений по e-mail
Если вы хотите получать информацию о результатах выполнения задач не только через графический интерфейс Jenkins или Allure, но и другими способами, то есть множество плагинов, которые могут выводить информацию в мессенджеры, прикреплять ссылки к Jira или другим системам учета задач, даже слать СМС на телефон. Самым простым в настройке способом "из коробки" является настройка оповещений на e-mail.
Настройка может быть выполнена через настройки плагина "Extended E-mail Notification" или настройки "Уведомление почтой". Рекомендую делать это через настройки плагина "Extended E-mail Notification", так как далее в коде пайплайнов для отправки почты на указанный в настройках e-mail будет использоваться функция emailext, предоставляемая этим плагином.
Здесь можно настроить рассылку через корпоративную почту. Но в нашем случае для демонстрации возможностей можно задействовать открытый SMTP-сервер smtp.gmail.com. Для него требуется указать использование SSL и порт 465. В качестве имени пользователя обязательно указывать полный e-mail:
Имя исполняемого файла Git для работы с Linux
И еще один штрих на данный момент - настройка имени исполняемого файла git. Git используется в Jenkins на всех узлах. Подчиненные узлы, работающие на Linux, будут вызывать его для получения всех последних изменений из гит-репозитория, хранящего исполняемый на CI код (сейчас этот репозиторий хранит только скрипты архивирования и развертывания задач Jenkins, но позже в нём появится и исполняемый код пайплайнов).
Мастер-узел, работающий на Windows будет обращаться к тому же репозиторию. Но для получения меньшего объема данных - только текста файлов, содержащих код пайплайнов.
Важно здесь то, что происходить обращение к git будет как с Linux , так и с Windows. Но при установке Jenkins на Windows в конфигурации глобальных инструментов именем исполняемого файла будет git.exe. Конечно на стороне Linux расширения "exe" у этого файла не будет. Поэтому его необходимо удалить, оставив в поле только "git ". Полный путь к файлу задавать не имеет смысла - во всех системах путь к нему будет включен в переменную PATH:
К настройке глобальных инструментов еще нужно будет вернуться, когда мы будем говорить про публикацию отчетности о результатах тестирования в Allure и настройку таймаутов для операций в пйплайнах. Но сейчас можно перейти к другой задаче - настройке сборочных узлов Jenkins.
Создание узлов
На главной странице Jenkins есть блок, отображающий состояние сборщиков. После первоначальной установки в нём будет только один узел - master. Он соответствует главному процессу Java на хостовой машине, является управляющим, и именно его настройки мы меняли, когда с помощью sed правили содержимое файла jenkins.xml.
Нам необходимо создать и настроить узлы, соответствующие агентам сервера, которые будут работать на наших виртуальных машинах. Описанным далее способом должно быть создано столько узлов, сколько будет у нас виртуальных машин.
Для добавления нового узла можно перейти по ссылке "Состояния сборщиков" и затем "Новый узел".
В зависимости от состава плагинов, установленных в системе в системе, при создании узла для него можно выбрать различные типы.
Настройку всегда можно скопировать с существующего узла (кроме узла master), но так как сейчас мы рассматриваем процесс создания первого узла и никаких плагинов, добавляющих новые типы узлов, у нас не установлено, то здесь следует выбрать Permanent Agent - это стандартный тип узла.
Название узла - очень важный параметр, исполняющий скорее роль идентификатора и имени, чем просто заголовка.
В нашем случае название должно соответствовать имени хоста в виртуальной машине. Это не требование Jenkins, а требование наших механизмов. При развертывании виртуальной машины через Vagrant мы передаем в него только одно уникальное имя - имя хоста. При подключении агента, запущенного в Linux к серверу Jenkins, необходимо указывать имя узла, с которым будет ассоциирован этот агент. Чтобы не множить количество переменных в нашей системе, удобно сделать так, чтобы имя хоста в виртуальной машины совпадало с именем узла Jenkins. Тогда можно не передавать имя узла через отдельную переменную окружения в Vagrantfile. Именно так и будут реализованы наши механизмы подключения далее.
Нажав на Ok попадем на страницу детальной настройки узла. Здесь много параметров и справку по каждому из них можно получить справку нажав на знак вопроса:
Количеством процессов-исполнителей
Параметр правильнее было бы написать "потоков-исполнителей", так как процесс Java останется один. Смысл параметра в том, что он определяет сколько задач параллельно могут выполняться на данном узле.
Вспомним еще раз схему, приведенную в первой части. У нас будут узлы выполняющие сценарные тесты и подготовку образа тестовой базы. Эти узлы будут запускать тест-клиенты и тест-менеджеры, открывать на выполнение внешние обработки, эмулировать интерактивные действия пользователей. В машинах, соответствующих таким узлам не должно исполняться более одного процесса одновременно и поэтому данный параметр надо установить в 1. В рассматриваемых далее примерах будет два таких узла с именами ubuntu-interactive-2 и ubuntu-interactive-3 .
Узлы, отвечающие за выгрузку изменений из хранилища и запуск основной задачи сборки, могут иметь сколь угодно большое количество потоков-исполнителей. На практике нам потребуется хотя бы два потока. Также имеет смысл ограничить их руководствуясь здравым смыслом. Например мы не хотим, чтобы на узле выполнялось одновременно более трех задач. Один поток будет выполнять выгрузку из хранилища. Второй - запускать основную задачу сборки. Третий может использоваться для каких-то тестовых задач. В рассматриваемых далее примерах будет один такой узел с именем ubuntu-interactive-1.
Корень удаленной файловой системы
Это место, куда агент Jenkins будет писать все необходимые ему данные: выкачивать гит-репозитории, складывать результаты сборок, размещать временные файлы с паролями (да, пароли всегда можно вытянуть из Jenkins). При этом для каждой задачи будет создаваться отдельный подкаталог.
Во всех наших узлах здесь будет один и тот же путь к каталогу, который мы будем создавать в каждой виртуальной машине посредством Vagrantfile.
Метки
Тоже важный параметр. Метка - это по сути способ классификации узлов в Jenkins. С помощью них мы определяем к каким классам узлов относится данный узел. Так как в Jenkins всё построено на строках, а не на каталогах (справочниках) то для классификации узлов также используются строки. Нет необходимости предварительно заполнять какой-то каталог с классами узлов. Просто задаются метки.
При создании задачи Jenkins или при задании кода пайплайна можно указать на каких классах узлов может исполняться эта задача указывая допустимые для исполнения задачи метки узлов. Запуская задачу мастер-узел на хостовой машине будет искать подходящий свободный процесс/поток-исполнитель на узлах, подходящих под указанные метки, и если такой узел находится - передает исполнение на него. Если не находится - то ставит задачу в очередь и ждет.
Чтобы не запутаться в названиях давайте еще раз взглянем на схему задач Jenkins:
В каждой задаче у нас будет указана только одна метка. В задачах, связанных с эмуляцией интерактивных действий (это задача подготовки образа тестовой базы и основная задача сборки и тестирования), будет указана метка ubuntu_interactive и ее мы назначим узлам ubuntu-interactive-2 и ubuntu-interactive-3. В задаче выгрузки хранилища будет указана метка configuration_storage_reader, а в задаче-стартере - метка starter. Обе эти задачи будут исполняться на узле ubuntu-interactive-1 и поэтому у этого узла следует указать обе эти метки через пробел.
Напомню, что на узле, отвечающем за выгрузку версий хранилища 1С, обязательно должна стоять платформа 1С той же версии, на которой работает сервер хранилища конфигураций. А на узлах, где выполняются процессы тестирования может стоять любая версия платформы, работу на которой мы хотели бы проверять тестами.
Последний параметр, который нужно изменить при настройке узла - это способ запуска. Он заслуживает отдельного рассмотрения.
Способы запуска узлов
Jenkins предоставляет несколько способов подключения узлов. Рассмотрим особенности каждого из них, так как эта тема мало где освещается с точки зрения возможностей, которые они дают, а не только механики подключения.
SSH-подключение (Launch agents via SSH)
Особенности подключения с этим типом:
- Выполняется по инициативе управляющего мастер-узла (в нашем случае мастер-узел работает на хостовой машине).
- Иногда приходится включать узел из интерфейса Jenkins вручную, если сначала был запущен мастер-узел, а затем машины, на которых необходимо запустить агентов. Это как раз наш случай, так как виртуальные машины запускаются на хосте и служба Jenkins с мастер-узлом будет очевидно запущена раньше чем они.
- Подходит для запуска агентов без их взаимодействия с графическим окружением.
На самом деле есть способы запустить SSH сервер как "интерактивное" приложение в Linux или в Windows, но это решение нельзя считать подходящим в общем случае. В большинстве случаев SSH-сервер запускается как служба, а не как приложение. Также есть способ взаимодействия с рабочим столом через SSH-соединение при работе с X11 на Linux. Но в данном варианте подключения Jenkins сам формирует команду для установки соединения и указать в ней параметры для доступа SSH к графической системе нет возможности.
Метод подключения работает так:
- Управляющий Java-процесс на мастер-узле запускает ssh-клиента с параметрами подключения к ssh-серверу на той машине, где будет запускаться подчиненный узел. В качестве параметров авторизации используется SSH-ключ заданный в настройках Jenkins или ключ из каталога .ssh в домашнем каталоге пользователя, из под которого запущен мастер-узел. Для того чтобы подключение прошло без проблем стоит также заполнить поле " Host Key Verification Strategy " указав в нем отсутствие необходимости в проверке машины, к которой происходит подключение (значение " Non verifying verification strategy ").
- Н а машину, к которой произошло подключение, загружается jar-файл агента. Конечно это происходит только если подключение выполняется в первый раз и файла агента на машине ещё нет.
- Затем запускается Java-процесс с указанием пути к этому jar-файлу. Этот процесс и представляет из себя слейв-узел.
- SSH-подключение удерживается, что позволяет получать на мастер-узле консольный вывод всех команд, выполняемых на слейв-узле.
- Java-процесс на слейв-узле является родительским для всех процессов, которые им запускаются. При его завершении завершаются и все запущенные им процессы.
Похожий принцип с удержанием установленного соединения применяется и для других типов подключений.
Выполнение произвольной команды со стороны мастер узла (Launch agent via execution of command on the master)
Этот вариант стоит предпочесть в том случае, если необходимо выполнение какой-то специфичной команды или скрипта для подключения. Или особых параметров подключения через SSH. Выбрав такой вариант подключения мы можем вручную сформировать команду подключения через SSH к другому или даже текущему компьютеру со всеми нужными параметрами.
Например для подключения к узлу, работающему на Windows может подойти команда, подобная следующей:
ssh -i C:\ssh-keys\key_without_passphrase -o StrictHostKeyChecking=no username@HOSTNAME "D:/jenkins_remote/jdk/bin/java.exe -Dfile.encoding=UTF-8 -Xmx1048m -jar D:/jenkins_remote/remoting.jar -workDir D:\jenkins_remote"
Здесь мы самостоятельно заботимся об указании нужных ключей для авторизации без пароля, отключении запроса на внесение хеша удаленной машины в список известных хостов ssh-клиента, указывая параметр -o StrictHostKeyChecking=no (это аналог выбора "Non verifying verification strategy" для прошлого варианта подключения).
Преимущества и недостатки у этого варианта всё те же что и у предыдущего. Дополнительный плюс - этот вариант дает больше гибкости в настройках подключения. Дополнительный минус - о наличии jar-файла агента и всех параметров подключения придется заботиться самостоятельно.
Самостоятельное подключение узла к мастер-узлу (Launch agent by connecting it to the master)
В этом случае механизм запуска узла следующий:
- Мастер-узел Jenkins сам не пытается запустить подчиненный узел. Вместо этого он ждет запроса на подключение со стороны подчиненного узла.
- Агент подчиненного узла запускается внешними средствами: при загрузке операционной системы, логоне пользователя, вручную или любым другим способом. Запускается он по прежнему как Java-приложение поэтому необходимо обеспечить наличие на машине JVM/JRE и jar-файла агента.
- При запуске подчиненного узла ему указывается http-адрес, по которому необходимо отправлять запрос на подключение.
- Если запрос не завершается успешно, то подчиненный узел продолжает повторять этот запрос раз в 10 секунд, бесконечно. Аналогично он ведёт себя при разрыве соединения. Это позволяет запускать сначала подчиненные узлы, а затем мастер узел. Или наоборот, сначала мастер-узел, а затем подчиненные узлы. В любой момент времени перезапускать службу Jenkins с мастер-узлом с последующим восстановлением соединения.
- При получении запроса на подключение мастер-узел создает TCP-соединение с подчиненным узлом. Может использоваться как фиксированный порт, указанный в настройках Jenkins , так и "случайный" порт.
- Подчиненный узел передает через установленное соединение информацию о консольном выводе всех запускаемых им команд, позволяя получать тот же результат, что и при ssh-подключении.
Именно этот третий вариант мы будем использовать так как он:
- Позволяет стабильнее и проще остальных способов запускать агента в "интерактивном" режиме (с возможностью взаимодействия с рабочим столом).
- Стабильнее устанавливать связь когда подчиненные узлы (виртуальные машины и агенты на них) запускаются после того как запущен мастер-узел.
С настройками Jenkins по умолчанию такого пункта как "Launch agent by connecting it to the master" в варианте запуска вообще не будет. Для того чтобы получить возможность выбирать такой вариант подключения необходимо явно разрешить его в настройках Manage Jenkins > Configure Global Security > Agents > TCP port for inbound agents или в русифицированном варианте Настроить Jenkins > Глобальные настройки безопасности > Agents > TCP port for inbound agents.
В этом разделе необходимо выбрать либо "Случайный порт" либо указать фиксированный порт. Фиксированный порт имеет смысл указать если у вас работает фаервол и нужно настроить исключения для порта. Других отличий в стабильности связи между этими вариантами за время использования Jenkins я не замечал.
После того как эта настройка выполнена и сохранена в списке выбора варианта подключения появится нужный нам пункт.
Настройка анонимного подключения к Jenkins
Теперь позаботимся об удобствах. Посмотрим как при настройках по умолчанию Jenkins предлагает нам подключать узлы.
Для подключения нам предлагается указать ключ вида 35e2648c23c8aa6e92745b92fdf3d2cf85453be78b1d684825555afffc0e1730, который известен администратору Jenkins, настраивающему подключение, но не будет известен "вражеским" агентам. Этот ключ будет уникальным для каждого узла Jenkins. За такое поведение отвечает один из плагинов, которые устанавливаются по умолчанию - это плагин "Matrix Authorization Strategy" - именно этот плагин включает повышенные настройки безопасности и не позволяет подключать агенты без указания ключей.
Если оставить всё как есть, то нам придется передавать этот ключ в виртуальную машину через Vagrantfile (точнее через переменные окружения). Это не только доставляет сложности с введением еще одной переменной, но и снижает переносимость наших механизмов между серверами, за которую мы с самого начала боремся и ради которой выбрали работу с виртуальными машинами.
При этом защищаться от "вражеских" агентов в нашей локальной сети нет никакой необходимости, им просто неоткуда взяться. Удалять плагин "Matrix Authorization Strategy" не нужно. Можно просто его настроить на возможность анонимного подключения агентов.
При этом можно сразу решить еще один вопрос. Нам нужно будет как то обеспечить первоначальную загрузку файла агента в каждую машину. И затем обновлять его, ведь при обновлении Jenkins может быть обновлена не только серверная часть, но и файлы агентов.
Пока мы залогинены в Jenkins исполняемый файл агента можно загрузить по ссылке http://ИМЯ-ХОСТА:8080//jnlpJars/agent.jar. Для скачивания этого файла другими приложениями им придётся авторизоваться - передать серверу Jenkins логин и пароль. Но хранить имя пользователя и пароль на стороне машин-узлов неправильно и неудобно. Для возможности загружать этот же файл без авторизации нужно разрешить анонимное чтение данных. Сделать это можно в том же разделе, где мы разрешали TCP подключение со стороны агентов. Таким образом настройки в разделе "Глобальные настройки безопасности" должны выглядеть следующим образом:
Теперь, если перейти к настройкам узла, то мы увидим, что ключ для запуска больше не нужен:
И кроме того мы получим возможность из виртуальной машины всегда загружать новую версию агента Jenkins, избавившись от необходимости его ручного обновления после обновления сервера на хосте.
На этом с настройками Jenkins мы заканчиваем и вернемся к ним только когда будем конфигурировать плагин Allure. Далее мы ещё создадим простейшие задачи Jenkins для проверки сделанных настроек. Но сейчас вернемся к Vagrantfile и обеспечим запуск агента Jenkins в виртуальных машинах и их подключение к серверу.
Передача имени/адреса сервера в виртуальные машины
Как видно на скриншотах выше, для подключения подчиненного узла и загрузки jar-файла агента Jenkins потребуется знать имя хоста или IP-адрес машины, на которой работает сервер Jenkins, а также порт, на котором работает этот сервер. Их нужно будет передать в виртуальную машину. Как и в случае всех прочих параметров, сделаем это через Vagrantfile и переменные окружения, которые назовем host_machine_ip_or_hostname и jenkins_port.
Чаще всего удобнее передавать именно имя хоста, как и предлагает Jenkins в настройках узла. Особенно если для хостовой машины не назначен статический IP адрес. Но несмотря на установку Avahi иногда достучаться до хостовой машины по имени может не получиться, поэтому сохраним возможность передавать не только имя хоста, но и IP-адрес.
Для того чтобы упростить задание настроек узлов в командных файлах, можно в общем файле запуска (в рассматриваемом репозитории это vagrant-up-destroy-common.bat) сделать настройку по умолчанию. Если переменная окружения host_machine_ip_or_hostname не определена ранее в файле, специфичном для отдельной машины, то можно автоматически установить её равной имени компьютера:
if NOT DEFINED host_machine_ip_or_hostname (
SET host_machine_ip_or_hostname=%computername%
)
Для того чтобы вместо имени хоста указать IP-адрес достаточно будет в файле настроек отдельного узла node-settings.bat определить значение этой же переменной:
:my_config
SET host_machine_ip_or_hostname=192.168.1.64
SET host_phisical_adapter_name_for_bridge_connection=Realtek PCIe GBE Family Controller
SET ram_memory_size_mb=2048
SET cpu_count=2
goto :end
Теперь настройка заданная в файле node-settings.bat имеет приоритет.
Команды, инициируемые Vagrantfile работают уже на стороне гостевой машины Linux и поэтому надо использовать уже команды bash.
Сервис Avahi работает таким образом, что к имени хоста может потребоваться добавить постфикс ".local". Сделать это необходимо при соблюдении двух условий:
- Переданное значение не является IP адресом
- Без постфикса .local до хоста не получилось достучаться
В ином случае можно использовать переданное значение переменной host_machine_ip_or_hostname внутри виртуальной машины не модифицируя его. Выполнить обе проверки и при необходимости добавить постфикс ".local" можно следующими строками кода:
if [ $(echo $host_machine_ip_or_hostname | grep -P "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" | wc -l) -eq 0 ] \
&& [ $(ping -c 1 $host_machine_ip_or_hostname 2>&1 | head -n 1 | grep 'failure in name resolution' | wc -l) -gt 0 ]
then
host_machine_ip_or_hostname=$host_machine_ip_or_hostname.local
fi
Располагаться эти команды будут в командном файле настройки подчиненного узла jenkins_node_configure.sh. Перейдем к его созданию.
Скрипт для обновления и подключения агента
Выше были описаны способы запуска агента. Наш выбор - отложенный запуск агента, как приложения, способного взаимодействовать с рабочим столом после каждой загрузки гостевой машины c Linux.
Для выполнения команд при каждой загрузке машины предназначены провизионеры Vagrant с типом запуска always. Но в данном случае они не подходят. Дело в том, что Vagrant взаимодействует с гостевой машиной Linux через ssh-соединение, а запуск интерактивных приложений через ssh доставляет слишком много сложностей и вносит нестабильность в систему (хотя такая возможность всё же существует и даже будет рассмотрена далее в этой публикации).
Для решения этой задачи нам подойдут механизмы автозапуска используемого в Linux графического окружения. В нашем случае это механизмы автозапуска Gnome. Конечно, если для своей сборки Вы решите использовать что-то более легкое и подходящее для целей CI, например LXDE или XFCE, то нужно будет адаптировать используемые далее команды.
Итак, возьмем за основу команду запуска агента, которую предлагает сам Jenkins
java -jar agent.jar -jnlpUrl http://HOST:8080/computer/ubuntu-interactive-4/slave-agent.jnlp -workDir "/home/vagrant/jenkins_agent/workdir"
и слегка модифицируем её, чтобы передавать основные составляющие как параметры:
jenkins_server=$host_machine_ip_or_hostname:$jenkins_port
java -Dfile.encoding=UTF-8 -jar agent.jar -jnlpUrl http://$jenkins_server/computer/$node_name/slave-agent.jnlp -workDir $jenkins_agent_dir 2>&1 | tee agent_console.log
Здесь же можно решить вопрос с обновлением агента. У нас включена возможность анонимного чтения с сервера Jenkins, поэтому для обновления агента достаточно выполнить команду загрузки агента с сервера Jenkins:
wget -q -O agent_new.jar http://$jenkins_server/jnlpJars/agent.jar && mv agent_new.jar agent.jar
Но так как нам необходимо сформировать командный файл, который будет выполняться после запуска графической оболочки, то поместим все эти команды в файл .sh (в данном случае он называется start_jenkins_node.sh) и укажем системе, что этот файл является исполняемым выполнив команду chmod u+x имя_sh_файла:
wgetCommand="wget -q -O agent_new.jar http://$jenkins_server/jnlpJars/agent.jar && mv agent_new.jar agent.jar"
startAgentCommand="java -Dfile.encoding=UTF-8 -jar agent.jar -jnlpUrl http://$jenkins_server/computer/$node_name/slave-agent.jnlp -workDir $jenkins_agent_dir 2>&1 | tee agent_console.log"
echo "cd $jenkins_agent_dir" > start_jenkins_node.sh
echo "sleep 15" >> start_jenkins_node.sh
echo "$wgetCommand" >> start_jenkins_node.sh
echo "$startAgentCommand" >> start_jenkins_node.sh
chmod u+x start_jenkins_node.sh
После этого остаётся прописать запуск файла в механизмах автозапуска Gnome, сформировав файл нужного формата с расширением .desktop. Имя файла при этом может быть любым, главное чтобы он располагался в подкаталоге .config/autostart домашнего каталога пользователя и имел указанное расширение:
cd $HOME
mkdir -p .config/autostart
cd .config/autostart
echo [Desktop Entry] > autostart_jenkins_node.desktop
echo Name=AutostartJenkinsNode >> autostart_jenkins_node.desktop
echo Exec=$jenkins_agent_dir/start_jenkins_node.sh >> autostart_jenkins_node.desktop
echo Terminal=false >> autostart_jenkins_node.desktop
echo Type=Application >> autostart_jenkins_node.desktop
echo X-GNOME-Autostart-enabled=true >> autostart_jenkins_node.desktop
Итоговый файл скрипта помещен в репозиторий: jenkins_node_configure.sh
Примеры автоматически генерируемых файлов автозапуска и скрипта для запуска агента в репозитории не хранятся, поэтому посмотрим на них здесь. Файл автозапуска должен получаться таким:
[Desktop Entry]
Name=AutostartJenkinsNode
Exec=/home/vagrant/jenkins_agent/start_jenkins_node.sh
Terminal=false
Type=Application
X-GNOME-Autostart-enabled=true
Файл запуска агента /home/vagrant/jenkins_agent/start_jenkins_node.sh при этом будет иметь такое содержимое:
cd /home/vagrant/jenkins_agent
sleep 15
wget -q -O agent_new.jar http://ИМЯ-СЕРВЕРА:8080/jnlpJars/agent.jar && mv agent_new.jar agent.jar
java -Dfile.encoding=UTF-8 -jar agent.jar \
-jnlpUrl http://ИМЯ-СЕРВЕРА:8080/computer/ubuntu-interactive-1/slave-agent.jnlp \
-workDir /home/vagrant/jenkins_agent 2>&1 | tee agent_console.log
Теперь остаётся только обеспечить запуск командного файла jenkins_node_configure.sh из Vagrantfile при первом развертывании машины. В провизионере с типом shell через параметр path можно указать путь к файлу, который будет скопирован в виртуальную машину и запущен в ней. Используя выражение Dir.pwd можно указать путь к файлу, относительно текущего каталога.
Переменные окружения, действующие в Vagrantfile нельзя передать в виртуальную машину напрямую. Но можно передать их в скрипт как параметры. Для этого существует параметр args. В sh-скрипте jenkins_node_configure.sh, который мы рассматривали выше к ним можно будет обратиться через знак доллара:
host_machine_ip_or_hostname=$1
jenkins_port=$2
node_name=$3
Таким образом код провизионера в Vagrantfile будет следующим:
config.vm.provision "Configuring and starting Jenkins node", type: "shell", run: $initRunType do |s|
s.path = Dir.pwd+"/../jenkins_node_configure.sh"
s.args = "#{ENV['host_machine_ip_or_hostname']} #{ENV['jenkins_port']} #{ENV['node_name']}"
s.privileged = false
end
Также будет удобно иметь возможность выводить диагностическое сообщение об успешном запуске по требованию запускающих развертывание механизмов. Не будем выводить это сообщение каждый раз, но сделаем возможность выводить из запускающего Vagrantfile командного файла по требованию. Для этого укажем тип запуска "never":
config.vm.provision "CheckJenkinsNode", type: "shell", run: "never" do |s|
$checkJenkinsAgentScript =<<-SCRIPT
sleep 30
echo "Output of ps aux | grep agent.jar must contain information about running jenkins agent."
echo "If there is no such information then maybe Jenkins server or Jenkins agent is stopped:"
ps aux | grep agent.jar
SCRIPT
s.inline = $checkJenkinsAgentScript
end
В файл vagrant-up-destroy-common.bat теперь следует добавить строку
vagrant provision --provision-with CheckJenkinsNode
для вывода диагностического сообщения в консоль:
if %vagrant_action%==up (
vagrant up
vagrant provision --provision-with CheckJenkinsNode
) else if %vagrant_action%==destroy (
vagrant destroy -f && rmdir /s /q .vagrant
)
Перезагрузка виртуальной машины после первоначального развертывания
Итак при первом развертывании виртуальной машины мы настроили обновление агента Jenkins с хостовой машины и его запуск при логоне пользователя. Но сразу после первой загрузки (первого развертывания машины) агент не был запущен.
Как обеспечить его запуск, при том, что Vagrant устанавливает с виртуальной машиной только ssh-соединение и не может взаимодействовать с графическим окружением?
Варианта два:
1) Обеспечить ssh-подключению доступ к дисплею. Эта возможность доступна только при использовании X Window System и не может применяться в получающем всё большее распространение Wayland. Для того чтобы обеспечить такую возможность достаточно выполнить команду export DISPLAY=:0. Точнее, более правильной будет являться команда export DISPLAY=:$(echo $DISPLAY | cut -d. -f1 | cut -d: -f2), учитывающая то что номер дисплея может быть не равен нулю. Подробнее про это Вы можете прочитать вбив в поисковики ключевые слова SSH, export и DISPLAY. Тема довольно интересная.
2) Второй вариант - просто перезагрузить операционную систему. Автологон у нас уже настроен, автозапуск скрипта при логоне тоже. После перезагрузки узел сможет сам подключиться к серверу Jenkins.
Оба варианта по своему хороши. Но так как в процессе развертывания машины мы вносили в нее существенные изменения - меняли имя хоста, настройки сервера 1С, а теперь еще и настроили автологон, то более правильным способом кажется именно перезагрузка машины. Это позволит убедиться, что все изменения сделаны корректно. А если нет - то мы об этом узнаем сразу, а не тогда когда нужно будет выполнить какое-то срочное действие в будущем, но окажется, что настройки некорректны и нужно сначала исправить какую-то ошибку.
Для перезагрузки машины существует команда vagrant reload, которую нужно добавить в наш общий командный файл vagrant-up-destroy-common.bat. Но при этом перезагрузка нужна не всегда, а только при первом развертывании.
Будем считать (и соответственно организовывать работу), что после уничтожения узла мы также удаляем каталог .vagrant виртуальной машины. А если каталог существует, то это означает, что машина уже развернута и остается ее только запустить. Это важный момент. Если при развертывании возникнет ошибка, то прежде чем повторять попытку развертывания нам необходимо будет выполнять командный файл vagrant-destroy.bat из каталога узла, и только потом снова выполнять vagrant-up.bat.
В файл vagrant-up-destroy-common.bat внесем следующие изменения:
1) Будем формировать текст команды, выполняемой после развертывания машины, в зависимости от наличия каталога .vagrant. Если каталог есть, то выведем в консоль сообщение о том, что перезагрузка не требуется. Если же каталога нет, то команда будет перезагружать машину:
if not exist .vagrant (
SET vagrant_reload_command=vagrant reload
) else (
SET vagrant_reload_command=echo It is not the first start, no need to reload machine
)
2) Если файл vagrant-up-destroy-common.bat вызывается из командного файла vagrant-up.bat, то затребовано действие "up". В этом случае вставим новую команду сразу после вызова vagrant up. Если же файл вызывается из командного файла vagrant-destroy.bat , то будет затребовано действие "desstroy". В этом случае у нас уже предусмотрено удаление каталога .vagrant после вызова vagrant destroy:
if %vagrant_action%==up (
vagrant up
%vagrant_reload_command%
vagrant provision --provision-with CheckJenkinsNode
) else if %vagrant_action%==destroy (
vagrant destroy -f && rmdir /s /q .vagrant
)
Теперь все файлы для Packer и Vagrant приобрели свой окончательный вариант и можно перейти к проверке результата.
Проверка подключения
Проверим подключение узлов к серверу Jenkins. Завершим работу виртуальных машин. Если ранее они уже были подключены к Jenkins, то через некоторое время Jenkins поймет что узлы отключены и изменит статусы узлов в своем интерфейсе. После этого снова запустим машины и подождем из загрузки.
После загрузки постепенно отрабатывают скрипты запуска и агенты один за другим подключаются к серверу Jenkins:
Пример распределения задач по узлам CI-сервера
Создадим первую тестовую задачу, которая нужна только для того, чтобы проверить подключение к подчиненному узлу Jenkins. Сейчас на смысле выполняемых действий можно не сосредотачиваться - особенностям разработки пайплайнов будет посвящена следующая публикация. В этот раз нам нужно только проверить подключение и возможность запуска интерактивных приложений через агента Jenkins максимально простым способом.
В веб-интерфейсе Jenkins выберем "New Item":
Затем укажем тип "Pipeline". Это тип задач в которых необходимые действия можно задавать программным кодом. И укажем имя задачи connection_test:
В открывшемся окне настройки задачи укажем такой простейший код пайплайна:
pipeline {
agent {label "ubuntu_interactive" }
stages {
stage('Connection test') {
steps { script {
sleep 15
sh "code"
sh "/opt/1C/v8.3/x86_64/1cv8"
}}
}
}
}
Здесь строкой agent {label "ubuntu_interactive"} мы сообщаем Jenkins, что задача должна запускаться только на узлах с меткой ubuntu_interactive.
Сохраним задачу и запустим ее несколько раз. На узлах где один сборщик - запустится только одна задача за раз, следующая будет запущена только после завершения предыдущей, выполняющийся на том же узле:
Если задать другую метку: agent {label "starter"}, то задачи будут назначаться на узел, где у нас несколько потоков исполнителей (так мы настраивали узел ubuntu-interactive-1, предполагающий наличие нескольких потоков-сборщиков). И параллельно будут исполняться несколько задач:
Архивация настроек узлов и задач, включение их в состав репозитория
В случае установки Jenkins на Windows, настройки его задач хранятся в xml файлов в каталоге C:\Program Files (x86)\Jenkins\jobs, а настройки узлов в каталоге каталогах C:\Program Files (x86)\Jenkins\nodes.
Создадим для наших задач отдельную папку через интерфейс Jenkins. Для этого в интерфейсе новой задачи выберем тип Folder, в следующем окне достаточно сохранить данные не внося в них никаких изменений:
Теперь уже в этой папке создадим четыре задачи с типом Pipeline:
- main_build - эта задача будет отвечать за основной процесс тестирования
- dump_storage_version_to_cf_file - будет отвечать за выгрузку хранилища в cf-файлы
- starter_for_main_build - будет запускать основную задачу тестирования
- create_test_database_image - будет осуществлять подготовку образа тестовой базы
Эти задачи будем использовать в дальнейшем для всех остальных примеров. Никаких настроек и кода пайплайнов в них сейчас можно не задавать, сейчас они нам нужны только для настройки архивации задач и узлов и проверки этого механизма архивации:
Когда для задач создается отдельная папка, то для этой папки создается отдельный подкаталог в C:\Program Files (x86)\Jenkins\jobs. В нем помимо конфигурации самой папки (файла config.xml) будет находиться еще один подкаталог jobs и уже в него будут помещаться данные отдельных задач:
Все настройки сохраняется в файлах config.xml. Состав тегов и данных в этом файле определяется составом плагинов. Поэтому например настроив автоматическое архивирование этих файлов, не всегда целесообразно автоматизировать их обратное копирование в каталог установки Jenkins. Лучше выполнять это обдуманно, убедившись, что все нужные плагины были настроены.
Чтобы обеспечить архивацию этих данных и сохранение их в репозитории достаточно скопировать их в отдельный каталог репозитория с сохранением иерархии каталогов. Для этого достаточно очень простого скрипта:
chcp 65001
SET APACHE_PATH=C:/Apache24
SET JENKINS_PATH=C:/Program Files (x86)/jenkins
SET JENKINS_JOBS_FOLDER_NAME=ci_for_1c
SET EXTERNAL_CONF_DIR=%~dp0/external_configuration_files
SET JOBS_FOLDER_SOURCE_PATH=%JENKINS_PATH%/jobs/%JENKINS_JOBS_FOLDER_NAME%
SET JOBS_FOLDER_DESTINATION_PATH=%EXTERNAL_CONF_DIR%/jenkins/jobs/%JENKINS_JOBS_FOLDER_NAME%
rm -rf "%EXTERNAL_CONF_DIR%/jenkins"
mkdir "%EXTERNAL_CONF_DIR%/jenkins/nodes"
mkdir "%JOBS_FOLDER_DESTINATION_PATH%"
cp "%JENKINS_PATH%/jenkins.xml" "%EXTERNAL_CONF_DIR%/jenkins/jenkins.xml"
cp -a "%JENKINS_PATH%/nodes/." "%EXTERNAL_CONF_DIR%/jenkins/nodes"
cp "%JOBS_FOLDER_SOURCE_PATH%/config.xml" "%JOBS_FOLDER_DESTINATION_PATH%/config.xml"
for /F %%i IN ('ls -1 "%JOBS_FOLDER_SOURCE_PATH%/jobs"') DO (
mkdir "%JOBS_FOLDER_DESTINATION_PATH%/jobs/%%i"
cp "%JOBS_FOLDER_SOURCE_PATH%/jobs/%%i/config.xml" "%JOBS_FOLDER_DESTINATION_PATH%/jobs/%%i/config.xml"
)
rm -rf "%EXTERNAL_CONF_DIR%/apache"
mkdir "%EXTERNAL_CONF_DIR%/apache"
cp "%APACHE_PATH%/conf/httpd.conf" "%EXTERNAL_CONF_DIR%/apache/httpd.conf"
Здесь rm и cp - это утилиты из пакета git для Windows. Напоминаю, что путь к этим командам обязательно должен быть указан в переменной PATH в хостовой операционной системы.
За одно здесь выполняется архивирование основного конфигурационного файла Jenkins и файла конфигурации Apache.
Для восстановления узлов и задач на другом сервере достаточно выполнить bat-файл со следующими командами:
chcp 65001
SET EXTERNAL_CONF_DIR=%~dp0\external_configuration_files
SET JENKINS_PATH=C:\Program Files (x86)\jenkins
cp -a "%EXTERNAL_CONF_DIR%\jenkins\nodes\." "%JENKINS_PATH%\nodes"
cp -a "%EXTERNAL_CONF_DIR%\jenkins\jobs\." "%JENKINS_PATH%\jobs"
После чего перезапустить службу Jenkins. Данный командный файл, также входит в состав репозитория, поэтому если Вы не хотите повторять создание узлов и задач вручную, то можете проверить его работу на своей машине.
На этом работа с репозиторием, хранящим код сборки образа и развертывания виртуальных машин, закончена. В него сделан последний коммит:
- на Гитлаб https://gitlab.com/vladimirlitvinenko84/ci-infrastructure-for-1c
- на Гитхаб https://github.com/VladimirLitvinenko84/ci-infrastructure-for-1c
Больше вносить изменения в него не потребуется, так как инфраструктура для нашего CI-контура фактически готова.
В этой части мы начали работать с репозиторием, относящимся непосредственно к Jenkins и механизмам тестирования, а не к инфраструктуре. Далее продолжим работать уже с ним:
- на Гитлаб https://gitlab.com/vladimirlitvinenko84/ci-for-1c-based-on-jenkins
- на Гитхаб https://github.com/vladimirlitvinenko84/ci-for-1c-based-on-jenkins
В следующих раз продолжим рассматривать функционал Jenkins. Поговорим про синтаксис пайплайнов, взаимодействие модулей (скриптов), подходы к хранению настроек, возможности переиспользования кода (за счёт создания общих модулей). Это будет временный переход от практики к теории, чтобы лучше понимать, как именно работает Jenkins и научиться писать пайплайны с минимальным привлечением внешних инструментов. Ведь под капотом у Jenkins полнофункциональная Java-машина!
Если Вы поделились ссылкой на публикацию с коллегами - большое спасибо! ;) Ведь им тоже может быть интересна тема CI и повышения качества разработки на платформе 1С ))