Первоначально
написанаВ Okta мы прошли много итераций использования Jenkins для создания и тестирования нашего программного обеспечения. Мы используем ряд инструментов, чтобы убедиться, что наш код работает правильно, и нам нравится, когда Дженкинс управляет ими. Список будет знаком любому, кто использует среду Java; PMD, Cobertura, модульные и функциональные тесты с JUnit, тесты Selenium с testNG, а также некоторые более экзотические инструменты, такие как сканер BURP Security, MogoTest и SLAMD.
Мы быстро обнаружили, что ручная установка и настройка Jenkins, когда нам требовался новый сервер для конкретной задачи или специального проекта, или перестройка существующего сервера, который был поврежден или съеден Amazon, была невероятно трудоемкой и подверженной ошибкам. Мы реализовали простой способ управления конфигурацией сервера Jenkins и конфигурациями заданий для данного экземпляра с помощью кода. Я собираюсь пройтись по подходу, который мы взяли (немного упростил), как пример того, как это сделать.
Мы используем Python и Jinja2, однако этот подход можно использовать с любым языком шаблонов, который имеет простую замену переменных и логические элементы управления для циклических и условных выражений.
Итак, начнем.
Необходимые условия для библиотеки и пакета
Это предполагает, что мы работаем на CentOS, однако мы можем и действительно запускать те же самые вещи на Windows и Mac.
Python 2.6
Мы стандартизируем эту версию, но другие тоже будут работать. Однако по учебным причинам я буду ссылаться на эти конкретные библиотеки, которые мы используем. Если у вас не установлен Python или у вас другая версия, вы можете получить 2.6 здесь: http://python.org/ftp/python/2.6.6/Python-2.6.6.tgz
wget http://python.org/ftp/python/2.6.6/Python-2.6.6.tgz tar fxz Python-2.6.6.tgz cd Python-2.6.6 ./configure make sudo make altinstall sudo ln -s /usr/local/bin/python2.6 /usr/bin/python2.6
Setuptools
setuptools необходим для установки Jinja2.
wget http://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11-py2.6.egg sudo sh setuptools-0.6c11-py2.6.egg
Jinja2
Этот пакет можно найти здесь: http://pypi.python.org/packages/source/J/Jinja2/Jinja2-2.5.5.tar.gz
wget http://pypi.python.org/packages/source/J/Jinja2/Jinja2-2.5.5.tar.gz tar fxz Jinja2-2.5.5.tar.gz cd Jinja2-2.5.5 sudo python2.6 setup.py install
Дженкинс: сердце этого
Для динамического создания экземпляра Jenkins необходимо понять, какие файлы находятся и где они используются.
Jenkins — это отличное программное обеспечение, и его великолепием является простота использования через веб-интерфейс Jenkins, особенно для тех, кто только начинает. Но в своей основе Jenkins использует файлы конфигурации, определенные с помощью xml, для управления приложением. Редактирование этих файлов конфигурации xml имеет тот же эффект, что и редактирование Jenkins через графический интерфейс. Создавать XML-файлы с помощью кода проще, но обычно они менее сложны, чем сборка в API Jenkins. Так что это подход, который мы выбрали для управления нашими экземплярами Jenkins.
Когда Дженкинс запускается с помощью java -jar jenkins.war или какого-либо более сложного варианта этого, он создает каталог с именем .jenkins, на который ссылается переменная среды с именем JENKINS_HOME. Мы будем работать с файлами, созданными в этом каталоге, чтобы контролировать, как определяются сервер и соответствующие задания.
Загрузите последнюю стабильную войну Дженкинса из проекта с открытым исходным кодом здесь: http://jenkins-ci.org/
Мы пройдемся по содержимому каталога, созданного Дженкинсом. Это ключ к пониманию того, как создать конкретный экземпляр Jenkins и связанные задания с кодом.
После загрузки установите переменную JENKINS_HOME в файл с именем .jenkins в текущем рабочем каталоге:
export JENKINS_HOME=`pwd`/.jenkins
По умолчанию он будет создан в файле с именем .jenkins в домашнем каталоге пользователей, но мы не хотим этого, мы хотим, чтобы он находился в текущем рабочем каталоге в скрытом каталоге с именем .jenkins, рядом с jenkins.war. ,
Запустите веб-приложение Jenkins:
java -jar jenkins.war
Давайте пройдемся по структуре.
Вы должны увидеть что-то вроде этого:
$ cd .jenkins [.jenkins]$ ls -al total 32 drwxr-xr-x 10 dlumma staff 340 Mar 19 15:22 . drwxr-xr-x 4 dlumma staff 136 Mar 19 15:22 .. -rw-r--r-- 1 dlumma staff 159 Mar 19 15:22 hudson.model.UpdateCenter.xml -rw------- 1 dlumma staff 1675 Mar 19 15:22 identity.key drwxr-xr-x 2 dlumma staff 68 Mar 19 15:22 jobs -rw-r--r-- 1 dlumma staff 907 Mar 19 15:22 nodeMonitors.xml drwxr-xr-x 16 dlumma staff 544 Mar 19 15:22 plugins -rw-r--r-- 1 dlumma staff 64 Mar 19 15:22 secret.key drwxr-xr-x 3 dlumma staff 102 Mar 19 15:22 userContent drwxr-xr-x 30 dlumma staff 1020 Mar 19 15:22 war
Это скелетная структура каталогов, которую Jenkins использует для управления собой. Дополнительные файлы и каталоги добавляются по мере того, как указываются дополнительные функции и конфигурации. Каталоги, с которыми мы имеем дело и которые наиболее полезны, это: 1) каталог заданий (это потрясающе определяет все задания) и 2) каталоги плагинов (где определены все плагины, я тоже знаю, что это шокирует). По умолчанию в каталоге jobs ничего не определено, но уже установлено несколько плагинов, таких как ant maven, ssh и subversion.
Помимо вещей в каталогах заданий и плагинов, другой файл, которым мы часто манипулируем, называется config.xml. Этот файл находится на верхнем уровне под .jenkins. Это основной файл конфигурации для сервера Jenkins. Мы можем заставить Дженкинса сгенерировать этот файл, изменив страницу http: // jenkinsinstance / configure через пользовательский интерфейс. Перейдите на страницу конфигурации, добавьте системное сообщение «My Awesome Jenkins Server» и нажмите «Сохранить». Файл config.xml должен теперь существовать в каталоге .jenkins.
$ emacs .jenkins / config.xml показывает:
<?xml version='1.0' encoding='UTF-8'?> <hudson> <disabledAdministrativeMonitors/> <version>1.456</version> <numExecutors>2</numExecutors> <mode>NORMAL</mode> <useSecurity>true</useSecurity> <authorizationStrategy/> <securityRealm/> <projectNamingStrategy/> <workspaceDir>${ITEM_ROOTDIR}/workspace</workspaceDir> <buildsDir>${ITEM_ROOTDIR}/builds</buildsDir> <systemMessage>My Awesome Jenkins Server</systemMessage> <jdks/> <viewsTabBar/> <myViewsTabBar/> <clouds/> <slaves/> <quietPeriod>5</quietPeriod> <scmCheckoutRetryCount>0</scmCheckoutRetryCount> <views> <hudson.model.AllView> <owner reference="../../.."/> <name>All</name> <filterExecutors>false</filterExecutors> <filterQueue>false</filterQueue> <properties/> </hudson.model.AllView> </views> <primaryView>All</primaryView> <slaveAgentPort>0</slaveAgentPort> <label></label> <nodeProperties/> <globalNodeProperties/> </hudson>
После понимания базовой структуры файлов, которые Jenkins использует для определения его конфигурации и функциональности, можно приступить к определению его с помощью исходного кода.
Jenkinizer
Теперь не стесняйтесь сдуть свой экземпляр Jenkins и все связанные с ним файлы и клонировать настройку репозитория для этого сообщения в блоге Okta здесь:
git clone [email protected]: / okta / jenkinizer
В нашем репозитории jenkinizer есть четыре вида файлов: файлы конфигурации python, файлы шаблонов xml, файлы статических ресурсов и сценарии. Структура выглядит следующим образом (это упрощенная версия нашего фактического внутреннего кода и содержит только подмножество кода, связанного с тестированием селена для ясности):
jenkinizer /
— config /
—- серверы /
—— selenium /
——– selenium_server.py
—- шаблоны /
—— selenium /
——– selenium_server_config.xml
——– selenium_setup.xml
——– selenium_suite.xml
— установить /
—- .jenkins /
—— plugins /
——– (набор установленных нами плагинов)
—— hudson.plugins.emailext.ExtendedEmailPublisher.xml
—- jenkins-env.sh
—- jenkins.sh
—- jenkins.war
— mkconfig. ру
Справочники
конфиг
Этот каталог содержит все шаблоны и конфигурации заданий.
устанавливать
Статические ресурсы и сценарии инициализации для запуска Jenkins находятся в стадии установки.
Главный скрипт mkconfig.py находится в главном каталоге. Давайте пройдемся по тому, что это делает.
class ServerOptionParser(OptionParser): def __init__(self): OptionParser.__init__(self) self.add_option("-t", "--jenkins-type", action="store", dest="jenkins_type", default="selenium", help="Type of server to generate." ) self.add_option("-j", "--jenkins-jobset", dest="jenkins_jobset", default="integration", help="Specific host this Jenkins will be deployed to.") self.add_option("-o", "--install-dir", dest="install_dir", default="/ebs/ci-build/tools/jenkins", help="Location to install the newly generated server.") self.add_option("-b", "--jenkins-branch", dest="jenkins_branch", default="release", help="Branch to use in testing.") self.add_option("-e", "--recipient-email", dest="recipient_email", default=" ", help="Email recipient") self.add_option("-d", "--discard-old-builds", action="store_true", dest="discard_old_builds", default=False, help="If set, old builds will be discarded.")
Это простой инструмент командной строки, созданный из прекрасной библиотеки OptionParser. Определим несколько параметров и аргументов. Наиболее важными из них являются jenkins-type и jenkins-jobset. В этом примере я показываю дистиллированную версию наших серверов селена. Таким образом, тип jenkins будет селеном, а набор jenkins-job будет либо интеграцией, либо предварительной интеграцией, либо последовательным. У нас есть много других видов серверов jenkins, которые я здесь не включаю для таких вещей, как сборка и тестирование мобильных устройств, артефакты Windows, модульные и функциональные сборки, конфигурации master / slave и информационные панели. Если вы хотите создавать серверы разных типов, вы можете следовать шаблону здесь, создав новый тип jenkins и назначив ему группу наборов заданий.
Следующие несколько методов используются для сохранения результатов теста при обновлении самого экземпляра Jenkins. Иногда нам все равно, но иногда мы хотим сохранить результаты испытаний, собранные до сих пор, и добавить новую работу или немного изменить настройку. Эти методы справляются с этим.
def make_selected_archive(backup_file_name, base_dir, files, gzip = True): backup_file = tarfile.open(backup_file_name, 'w:gz' if gzip else "w") current_dir = os.getcwd() os.chdir(base_dir) for artifact in files: if os.path.exists(artifact): backup_file.add(artifact) backup_file.close() os.chdir(current_dir) def restore_archive(backup_file_name, base_dir, gzip = True): backup_file = tarfile.open(backup_file_name, "r:gz" if gzip else "r") backup_file.extractall(base_dir) backup_file.close() def backup_old_builds(jobs_dir, job_names): archives = {} for job in job_names: job_dir = jobs_dir + "/" + job if os.path.exists(job_dir): print "Backing up old builds in %s" % (job_dir) backup_file_name = tempfile.gettempdir() + "/" + job + ".tar.gz" make_selected_archive(backup_file_name, job_dir, ["./builds", "./lastStableBuild", "./lastSuccessfulBuild", "./nextBuildNumber"]) archives[job] = backup_file_name return archives
Затем идет основная функциональность, которая генерирует файлы конфигурации для этого сервера Jenkins и его заданий.
Мы получаем тип, jobset, installation_dir и email, которые будут использоваться в этом случае.
def build_server(options, base_path = os.getcwd()): # We categorize Jenkins instances depending on the jobs they run. We have Jenkins instances that run selenium, # run unit and functional tests, build artifacts and deploy artifacts. This defined the kind of Jenkins we are # deploying. jenkins_type = options.jenkins_type # The set of jobs to deploy. jenkins_jobset = options.jenkins_jobset # The location to install this Jenkins instance to on the filesystem. install_dir = options.install_dir # Email. recipient_email = options.recipient_email
Мы загружаем файлы шаблонов, определенные для нашего типа сервера.
# Make our python job and server configuration files reachable. sys.path.extend([base_path + "/config/servers"])
Мы импортируем определенный dict, определяющий указанный набор заданий и тип jenkins.
# Define the template environment. template_env = Environment(loader=FileSystemLoader(base_path + '/config/templates/' + jenkins_type + '/'))
Мы определяем места установки.
# Import the specific server and job data for this jenkins type # and this jobset. server_config = __import__(jenkins_type).__dict__[jenkins_jobset] server_config["env"]["EMAIL"] = recipient_email server_config["env"]["BRANCH"] = options.jenkins_branch
Если указано, мы создаем резервные копии существующих архивов, а затем удаляем текущую установку Jenkins.
jenkins_dir = install_dir + "/.jenkins" jobs_dir = jenkins_dir + "/jobs" # Backup archived files and # remove the old instance if it exists. archives = {} if os.path.exists(install_dir): if not options.discard_old_builds: archives = backup_old_builds(jobs_dir, [job["job_name"] for job in server_config["jobs"]]) shutil.rmtree(install_dir)
Мы копируем все файлы статических ресурсов в каталог установки и распечатываем информационное сообщение.
# Copy all of the static Jenkins files to the installation directory shutil.copytree(base_path + "/install", install_dir) print "Generating configuration for jenkins type %s (writing to %s)" % (jenkins_type, install_dir)
Мы генерируем основной файл конфигурации Jenkins с заданным файлом конфигурации и конфигурацией сервера.
# Create the jenkins main config file. with open(jenkins_dir + "/config.xml", "w") as f: t = template_env.get_template("server-config.xml") f.write(t.render(server_config))
И, наконец, мы генерируем файлы конфигурации заданий, записываем их в каталог установки и восстанавливаем любые заархивированные файлы.
# Create the jobs config files. for job in server_config["jobs"]: job_name = job[ "job_name"] job_template = job["template_file" ] job.update(server_config) print "Creating job %s from %s" % (job_name, job_template) job_dir = jobs_dir + "/" + job_name os.makedirs(job_dir) with open(job_dir + "/config.xml", "w") as f: t = template_env.get_template(job_template) f.write(t.render(job)) if archives.has_key(job_name): print "Restoring old builds for %s" % (job_name) restore_archive(archives[job_name], job_dir)
Теперь давайте посмотрим на используемые файлы. Файлы шаблонов в config / templates / selenium являются XML-файлами с простыми переменными, определенными в файле конфигурации selenium.py. Первоначально они были скопированы из установки Jenkins и отредактированы с помощью переменных для динамического рендеринга на основе данных, определенных в файлах Python.
Основные данные задания определены в config / servers / selenium.py. Каждый набор заданий определяется с помощью dict, созданного из get_default (), который содержит ключи env, os, chained, views и jobs. Ключ jobs ссылается на другой список, который содержит указания, какие конкретные данные задания, такие как job_name, template_file, max_builds_to_keep, repo и другие.
Обновление существующего сервера Jenkins selenium с помощью набора заданий интеграции будет выглядеть следующим образом:
$ python2.6 mkconfig.py -t selenium -j integration Generating configuration for jenkins type selenium (writing to /ebs/ci-build/tools/jenkins) Creating job okta.build from selenium-setup.xml Creating job core.chrome from selenium-suite.xml Creating job apps.chrome from selenium-suite.xml Creating job plugin.apps.chrome from selenium-suite.xml Creating job core.firefox.latest from selenium-suite.xml Creating job apps.firefox.latest from selenium-suite.xml Creating job plugin.apps.firefox.latest from selenium-suite.xml Creating job core.ie.8 from selenium-suite.xml Creating job apps.ie.8 from selenium-suite.xml Creating job plugin.apps.ie.8 from selenium-suite.xml Creating job core.firefox.3.6 from selenium-suite.xml Creating job apps.firefox.3.6 from selenium-suite.xml Creating job plugin.apps.firefox.3.6 from selenium-suite.xml Creating job core.ie.9 from selenium-suite.xml Creating job apps.ie.9 from selenium-suite.xml Creating job plugin.apps.ie.9 from selenium-suite.xml
Теперь перейдите в каталог установки и запустите его.
$ cd /ebs/ci-build/tools/jenkins $ ./jenkins.sh JENKINS_HOME=/ebs/ci-build/tools/jenkins/.jenkins
Хвост журнал, чтобы убедиться, что все работает гладко.
$ tail -f jenkins.log Running from: /ebs/ci-build/tools/jenkins/jenkins.war webroot: EnvVars.masterEnvVars.get("JENKINS_HOME") Apr 1, 2012 10:04:26 PM winstone.Logger logInternal INFO: Beginning extraction from war file Jenkins home directory: /ebs/ci-build/tools/jenkins/.jenkins found at: EnvVars.masterEnvVars.get("JENKINS_HOME") Apr 1, 2012 10:04:29 PM winstone.Logger logInternal INFO: HTTP Listener started: port=8080 Apr 1, 2012 10:04:29 PM winstone.Logger logInternal INFO: AJP13 Listener started: port=8009 Apr 1, 2012 10:04:29 PM winstone.Logger logInternal INFO: Winstone Servlet Engine v0.9.10 running: controlPort=disabled Apr 1, 2012 10:04:30 PM jenkins.InitReactorRunner$1 onAttained INFO: Started initialization Apr 1, 2012 10:04:32 PM jenkins.InitReactorRunner$1 onAttained INFO: Listed all plugins Apr 1, 2012 10:04:32 PM jenkins.InitReactorRunner$1 onAttained INFO: Prepared all plugins Apr 1, 2012 10:04:32 PM jenkins.InitReactorRunner$1 onAttained INFO: Started all plugins Apr 1, 2012 10:04:32 PM jenkins.InitReactorRunner$1 onAttained INFO: Augmented all extensions Apr 1, 2012 10:04:36 PM org.apache.sshd.common.util.SecurityUtils$BouncyCastleRegistration run INFO: Trying to register BouncyCastle as a JCE provider Apr 1, 2012 10:04:36 PM org.apache.sshd.common.util.SecurityUtils$BouncyCastleRegistration run INFO: Registration succeeded Apr 1, 2012 10:04:36 PM jenkins.InitReactorRunner$1 onAttained INFO: Loaded all jobs Apr 1, 2012 10:04:36 PM org.jenkinsci.main.modules.sshd.SSHD start INFO: Started SSHD at port 52810 Apr 1, 2012 10:04:36 PM jenkins.InitReactorRunner$1 onAttained INFO: Completed initialization Apr 1, 2012 10:04:37 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@2be3d80c: display name [Root WebApplicationContext]; startup date [Sun Apr 01 22:04:37 PDT 2012]; root of context hierarchy Apr 1, 2012 10:04:37 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory INFO: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@2be3d80c]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1e94b0ca Apr 1, 2012 10:04:37 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1e94b0ca: defining beans [authenticationManager]; root of factory hierarchy Apr 1, 2012 10:04:37 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@23a65a18: display name [Root WebApplicationContext]; startup date [Sun Apr 01 22:04:37 PDT 2012]; root of context hierarchy Apr 1, 2012 10:04:37 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory INFO: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@23a65a18]: org.springframework.beans.factory.support.DefaultListableBeanFactory@29cc3436 Apr 1, 2012 10:04:37 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@29cc3436: defining beans [filter,legacy]; root of factory hierarchy Apr 1, 2012 10:04:37 PM hudson.TcpSlaveAgentListener INFO: JNLP slave agent listener started on TCP port 7070 Apr 1, 2012 10:04:42 PM hudson.triggers.SCMTrigger$Runner run INFO: SCM changes detected in okta.build. Triggering #1 Apr 1, 2012 10:04:48 PM hudson.WebAppMain$2 run INFO: Jenkins is fully up and running Apr 1, 2012 10:04:58 PM hudson.model.DownloadService$Downloadable doPostBack INFO: Obtained the updated data file for hudson.tasks.Maven.MavenInstaller Apr 1, 2012 10:04:58 PM hudson.model.DownloadService$Downloadable doPostBack INFO: Obtained the updated data file for hudson.tasks.Ant.AntInstaller Apr 1, 2012 10:04:58 PM hudson.model.DownloadService$Downloadable doPostBack INFO: Obtained the updated data file for hudson.tools.JDKInstaller Apr 1, 2012 10:05:01 PM hudson.model.UpdateSite doPostBack INFO: Obtained the latest update center data file for UpdateSource default Apr 1, 2012 10:05:42 PM hudson.triggers.SCMTrigger$Runner run INFO: SCM changes detected in okta.build. Triggering #2 Apr 1, 2012 10:06:42 PM hudson.triggers.SCMTrigger$Runner run INFO: SCM changes detected in okta.build. Triggering #3
Откройте ваш браузер и посмотрите недавно созданный экземпляр Jenkins по адресу: http: // localhost: 8080
Это простой и эффективный способ управления несколькими экземплярами Jenkins, предназначенными для выполнения различных задач. Это более легко обслуживаемо, чем настройка нового экземпляра через графический интерфейс, который очень подвержен ошибкам и занимает много времени. Тем не менее, это просто и не требует какого-либо сложного программирования. Для управления многими экземплярами Jenkins, делающими много разных вещей, этот подход хорошо сработал для нас.