Прим. перев.: это перевод статьи из инженерного блога компании Preply о том, как можно использовать конфигурацию как код для такого популярного CI/CD инструмента как Jenkins.
В нашей компании, мы стараемся следовать практикам «Все как код», это касается не только инфраструктурных ресурсов, но и мониторинга, Jenkins джоб и т.д. В статье я расскажу, о том, как мы используем эту практику при разворачивании и поддержке Jenkins. Причем это касается не только инфраструктуры для сервера и агентов, но и плагинов, доступов, джоб и множества других вещей.
Кроме того, в данной статье мы попробуем найти ответы на такие вопросы как:

Обычно, первое что приходит в голову при упоминании словосочетания «инструменты DevOps» — это CI/CD система. К примеру, мы используем Jenkins, потому что мы запускаем сотни задач каждый день, а это десятки тысяч билдов. Некоторые возможности, которые мы используем в Jenkins либо отсутствуют в других CI/CD системах, либо имеют ограниченный функционал.

Нам бы хотелось управлять Jenkins полностью из кода, включая инфраструктуру, конфигурации, джобы и плагины. Мы пробовали запускать Jenkins в Kubernetes, однако под наши нужды он не вписался, плюс непросто было масштабироваться из-за его архитектуры.
Вот об этом пойдет речь
Мы используем AWS и конфигурируем всю инфраструктуру с помощью Terraform и других инструментов из хашистека, таких как Packer и Vault.
Как упоминалось ранее, мы пробовали использовать Jenkins в Kubernetes и столкнулись с некоторыми проблемами масштабирования PVC, ресурсов, а также не очень продуманной архитектуры.
Здесь же, мы используем обычные ресурсы AWS: EC2 инстансы, SSL-сертификаты, балансировщики, Cloudfront и т.д. Образ ОС (AMI) сконфигурирован с помощью Packer, который отлично интегрируется с Terraform и Vault.
В свою очередь, файл
Вот пример возможной инфраструктуры на AWS для Jenkins
Пользователи заходят на Jenkins через внутренний балансер, а Github-хуки попадают на сервер через внешний. Мы используем интеграцию Jenkins с GitHub, поэтому некоторые ссылки сервера должны быть доступны из интернета. Здесь есть множество различных решений (к примеру белый список для IP-адресов, урлов или токенов, и т.д.), в нашем случае мы используем комбинацию разрешенных урлов и валидации токена.
Следующее чем мы займемся, это установкой Jenkins и его плагинов. У нас постоянно возгникали проблемы с обновлением плагинов, поэтому основной целью было иметь четкий слепок установленных плагинов и их версий в коде.
И здесь нам поможет Docker, ведь мы можем взять уже готовый предустановленный Docker-образ и использовать его как базовый, для нашей конфигурации.
Внутрь Docker-образа устанавливаются некоторые пакеты как Job Builder, о котором я расскажу позже, также регистрируются хранилища и устанавливаются плагины, указанные в файле
И наконец, конфигурация для docker-compose, которая будет запускать Jenkins в Docker.
Обратите внимание на некоторые параметры Java, которые помогли нам со сборкой мусора и ограничением ресурсов. Вот в этой статье очень классно расписано о тюнинге Jenkins.
Ну и конечно теперь мы можем локально развернуть копию Jenkins и экспериментировать с новыми версиями сервера и плагинов. Это очень удобно.
В общем, есть такой плагин под названием Jenkins Configuration as Code (JCasC), который позволяет хранить конфигурацию сервера в человекочитаемом текстовом формате.
С помощью этого плагина можно описывать конфигурации безопасности, доступы, настройки плагинов, агентов, вкладок и многое другое.
Конфигурация представлена в формате YAML и разделена на 5 блоков:

Плагин поддерживаем импорт конфигурации из уже существующей инсталляции Jenkins
Помимо этого, плагин предоставляет поддержку различных провайдеров секретов, однако, в данном примере будем просто использовать переменные окружения.
Мы также используем плагин Amazon EC2 для поднятия агентов в AWS, и его конфигурация также может быть описана с помощью этого плагина. Матричная авторизация позволяет нам конфигурировать доступы пользователей с помощью кода.
Плагин поддерживает и некоторые другие штуки, которые мы используем. С правильно организованным процессом локального тестирования Jenkins, можно эффективно находить и исправлять баги перед тем как они потенциально могут попасть в прод Jenkins.
Существует несколько способов создания freestyle-джоб в Jenkins:
Jenkins Job Builder (JJB) позволяет конфигурировать джобы с помощью YAML или JSON. И это довольно удобно, ведь можно конфигурировать все джобы и хранить их состояние в условном git. То есть, по факту, мы можем построить CI/CD процесс для нашего CI/CD инструмента с помощью JJB.
Конфигурационный файл JJB также выглядит просто.
Само собой, пользователь, для которого создавался токен, должен иметь соответствующие привилегии для создания и настройки джоб. Нам всего лишь необходимо применить одну инициализационную джобу (seed job), которая будет применять изменения с помощью JJB в Jenkins.
Стоит упомянуть, что JJB не является «серебрянной пулей», так как некоторые не очень популярные плагины не поддерживаются. Однако, это очень гибкий инструмент для хранения джоб в коде, в том числе с поддержкой макросов.
Теперь, когда мы дошли до конца этой статьи, хотелось бы вернуться в начало и ответить на вопросы заданные в начале. На каждый из поставленных вопросов мы можем ответить — «да».
В этой статье мы не углублялись в тонкости настройки тех или иных технологий или в то, как правильно настраивать Jenkins, мы всего лишь делимся своим опытом, который, возможно, пригодится и вам.
P.S. В статье я часто использую слово «джоба» (с англ. «job», «задача»), для меня оно звучит привычнее чем «задача» в контексте CI/CD в целом или Jenkins.
В нашей компании, мы стараемся следовать практикам «Все как код», это касается не только инфраструктурных ресурсов, но и мониторинга, Jenkins джоб и т.д. В статье я расскажу, о том, как мы используем эту практику при разворачивании и поддержке Jenkins. Причем это касается не только инфраструктуры для сервера и агентов, но и плагинов, доступов, джоб и множества других вещей.
Кроме того, в данной статье мы попробуем найти ответы на такие вопросы как:
- Стал ли наш Jenkins стабильнее?
- Можем ли мы делать частые изменения в конфигурацию сервера и джоб?
- Является ли обновление Jenkins для нас все еще болью?
- Можем ли мы контролировать все наши изменения?
- Можем ли мы быстро восстановить Jenkins в случае факапа?

Введение
Обычно, первое что приходит в голову при упоминании словосочетания «инструменты DevOps» — это CI/CD система. К примеру, мы используем Jenkins, потому что мы запускаем сотни задач каждый день, а это десятки тысяч билдов. Некоторые возможности, которые мы используем в Jenkins либо отсутствуют в других CI/CD системах, либо имеют ограниченный функционал.

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

Инфраструктура для Jenkins
Мы используем AWS и конфигурируем всю инфраструктуру с помощью Terraform и других инструментов из хашистека, таких как Packer и Vault.Как упоминалось ранее, мы пробовали использовать Jenkins в Kubernetes и столкнулись с некоторыми проблемами масштабирования PVC, ресурсов, а также не очень продуманной архитектуры.
Здесь же, мы используем обычные ресурсы AWS: EC2 инстансы, SSL-сертификаты, балансировщики, Cloudfront и т.д. Образ ОС (AMI) сконфигурирован с помощью Packer, который отлично интегрируется с Terraform и Vault.
Пример того, как выглядит конфигурация образа ОС в Packer{ "variables": { "aws_access_key": "{{vault `packer/aws_access_key_id` `key`}}", "aws_secret_key": "{{vault `packer/aws_secret_access_key` `key`}}", "aws_region": "{{vault `packer/aws_region` `key`}}", "vault_token": "{{env `VAULT_TOKEN`}}" }, "builders": [{ "access_key": "{{ user `aws_access_key` }}", "secret_key": "{{ user `aws_secret_key` }}", "region": "{{ user `aws_region` }}", "type": "amazon-ebs", "communicator": "ssh", "ssh_username": "ubuntu", "instance_type": "c5.xlarge", "security_group_id": "sg-12345", "iam_instance_profile": "packer-role-profile", "ami_name": "packer-jenkins-master-{{timestamp}}", "ami_description": "Jenkins master image", "launch_block_device_mappings": [{ "device_name": "/dev/sda1", "volume_size": 50, "volume_type": "gp2", "delete_on_termination": true }], "source_ami_filter": { "filters": { "virtualization-type": "hvm", "name": "ubuntu/images/*ubuntu-bionic-18.04-amd64-server-*", "root-device-type": "ebs" }, "owners": ["099720109477"], "most_recent": true } }], "provisioners": [{ "type": "shell", "environment_vars": ["VAULT_TOKEN={{ user `vault_token` }}"], "scripts": ["packer_bootstrap.sh"] }] }
В свою очередь, файл
packer_bootstrap.sh содержит в себе набор команд, с помощью которых устанавливается софт внутрь образа. К примеру, мы можем установить Docker, docker-compose и vaultenv или Datadog-агент для мониторинга. Касаемо инфраструктуры под этот образ, мы можем использовать Terraform, Cloudformation, Pulumi или даже Ansible.
Пользователи заходят на Jenkins через внутренний балансер, а Github-хуки попадают на сервер через внешний. Мы используем интеграцию Jenkins с GitHub, поэтому некоторые ссылки сервера должны быть доступны из интернета. Здесь есть множество различных решений (к примеру белый список для IP-адресов, урлов или токенов, и т.д.), в нашем случае мы используем комбинацию разрешенных урлов и валидации токена.
Итак, после проделанных нами манипуляций, у нас уже есть готовая инфраструктура с собранным образом ОС, возможностью мониторинга и доступом к корпоративному хранилищу секретов.
Используем Docker для установки Jenkins и его плагинов
Следующее чем мы займемся, это установкой Jenkins и его плагинов. У нас постоянно возгникали проблемы с обновлением плагинов, поэтому основной целью было иметь четкий слепок установленных плагинов и их версий в коде.И здесь нам поможет Docker, ведь мы можем взять уже готовый предустановленный Docker-образ и использовать его как базовый, для нашей конфигурации.
DockerfileFROM jenkins/jenkins:2.215 ENV CASC_JENKINS_CONFIG /jenkins_configs USER root # Установка дополнительных пакетов RUN apt update && \ apt install -y python3 python3-pip && \ pip3 install awscli jenkins-job-builder jjb-reactive-choice-param --no-cache-dir USER jenkins VOLUME /jenkins_configs VOLUME /var/jenkins_home # Установка плагинов COPY plugins.txt /usr/share/jenkins/ref/ RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt
Внутрь Docker-образа устанавливаются некоторые пакеты как Job Builder, о котором я расскажу позже, также регистрируются хранилища и устанавливаются плагины, указанные в файле
plugins.txt.Получить список установленных плагинов в Jenkins можно по ссылкеJenkins.instance.pluginManager.plugins.each{ plugin -> println ("${plugin.getShortName()}:${plugin.getVersion()}") }
https://our-jenkins-url/script и сохранив вывод в файл plugins.txtИ наконец, конфигурация для docker-compose, которая будет запускать Jenkins в Docker.
Мы также используем vaultenv для проброса секретов из Vaultversion: "3" services: jenkins: build: . container_name: jenkins restart: always ports: - "50000:50000" - "8080:8080" volumes: - ./configs/:/jenkins_configs/:ro - ./jenkins_home/:/var/jenkins_home/:rw environment: - VAULT_TOKEN - GITHUB_TOKEN - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - JAVA_OPTS=-Xms4G -Xmx8G -Xloggc:/var/jenkins_home/gc-%t.log -XX:NumberOfGCLogFiles=5 -XX:+UseGCLogFileRotation -XX:GCLogFileSize=20m -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCCause -XX:+PrintTenuringDistribution -XX:+PrintReferenceGC -XX:+PrintAdaptiveSizePolicy -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:+UnlockDiagnosticVMOptions -XX:G1SummarizeRSetStatsPeriod=1 volumes: configs: driver: local jenkins_home: driver: local
Обратите внимание на некоторые параметры Java, которые помогли нам со сборкой мусора и ограничением ресурсов. Вот в этой статье очень классно расписано о тюнинге Jenkins.
Ну и конечно теперь мы можем локально развернуть копию Jenkins и экспериментировать с новыми версиями сервера и плагинов. Это очень удобно.
Теперь у нас есть чистая инсталляция Jenkins и плагинов, которая легко может запускаться в проде. Давайте добавим больше конфигурации для нее.
Настройка плагина Jenkins as a Code (JCaSC) для конфигурации сервера
В общем, есть такой плагин под названием Jenkins Configuration as Code (JCasC), который позволяет хранить конфигурацию сервера в человекочитаемом текстовом формате.С помощью этого плагина можно описывать конфигурации безопасности, доступы, настройки плагинов, агентов, вкладок и многое другое.
Конфигурация представлена в формате YAML и разделена на 5 блоков:
- credentials (описание системных секретов)
- jenkins (настройки авторизации и облака, глобальные настройки, описание агентов, некоторые настройки безопасности и вкладки)
- security (глобальные настройки безопасности, такие как разрешенные скрипты)
- tool (конфигурация для внешних инструментов, таких как git, allure, и т.д.)
- unclassified (другие настройки, такие как интеграция со Slack)

Плагин поддерживаем импорт конфигурации из уже существующей инсталляции JenkinsПомимо этого, плагин предоставляет поддержку различных провайдеров секретов, однако, в данном примере будем просто использовать переменные окружения.
Вот так можно описывать секретыcredentials: system: domainCredentials: - credentials: - usernamePassword: description: "AWS credentials" id: "aws-creds" password: ${AWS_SECRET_ACCESS_KEY} scope: GLOBAL username: ${AWS_ACCESS_KEY_ID} - string: description: "Vault token" id: "vault-token" scope: GLOBAL secret: ${VAULT_TOKEN} ...
Мы также используем плагин Amazon EC2 для поднятия агентов в AWS, и его конфигурация также может быть описана с помощью этого плагина. Матричная авторизация позволяет нам конфигурировать доступы пользователей с помощью кода.
Описание агентов и доступовjenkins: authorizationStrategy: projectMatrix: permissions: - "Overall/Administer:ivan.a@example.org" - "Credentials/View:petr.d@example.org" ... clouds: - amazonEC2: cloudName: "AWS" privateKey: ${EC2_PRIVATE_KEY} region: "${AWS_REGION}" templates: - ami: "ami-12345678" amiType: unixData: sshPort: "22" connectionStrategy: PRIVATE_IP deleteRootOnTermination: true description: "jenkins_agent" idleTerminationMinutes: "20" instanceCapStr: "100" minimumNumberOfInstances: 0 mode: EXCLUSIVE numExecutors: 1 remoteAdmin: "jenkins" remoteFS: "/home/jenkins" securityGroups: "sg-12345678" subnetId: "subnet-12345678" type: C52xlarge ...
Плагин поддерживает и некоторые другие штуки, которые мы используем. С правильно организованным процессом локального тестирования Jenkins, можно эффективно находить и исправлять баги перед тем как они потенциально могут попасть в прод Jenkins.
Теперь у нас есть воспроизводимая конфигурация для сервера, осталось дело за малым, а именно — джобы.
Используем Job Builder для freestyle-проектов
Существует несколько способов создания freestyle-джоб в Jenkins:- с помощью веб-интерфейса (самый простой способ, наклацал и пошел дальше)
- напрямую с помощью REST API
- с помощью плагинов таких как Job DSL или враппера JJB
Jenkins Job Builder (JJB) позволяет конфигурировать джобы с помощью YAML или JSON. И это довольно удобно, ведь можно конфигурировать все джобы и хранить их состояние в условном git. То есть, по факту, мы можем построить CI/CD процесс для нашего CI/CD инструмента с помощью JJB.
Вот так (упрощенно) выглядит структура конфигурации джобы на ФС. ├── config.ini ├── jobs │ ├── Job1.yaml │ | ... │ └── Job2.yaml └── scripts ├── job1.sh | ... └── job2.sh
А так выглядит джоба в файле- job: name: Job1 project-type: freestyle auth-token: mytoken disabled: false concurrent: false node: jenkins_agent triggers: - timed: '0 3 * * *' builders: - shell: !include-raw: ../scripts/job1.sh
Job1.yaml, шаги джобы в скрипте job1.shКонфигурационный файл JJB также выглядит просто.
Применение новых изменений легко может быть запущено с помощью команды$ cat config.ini [job_builder] ignore_cache=True exclude=jobs/Job2 [jenkins] url=https://jenkins.example.org user=some_user password=some_password $ jenkins-jobs --conf config.ini test -r jobs/ $ jenkins-jobs --conf config.ini update -r jobs/
jenkins-jobs updateСамо собой, пользователь, для которого создавался токен, должен иметь соответствующие привилегии для создания и настройки джоб. Нам всего лишь необходимо применить одну инициализационную джобу (seed job), которая будет применять изменения с помощью JJB в Jenkins.
Стоит упомянуть, что JJB не является «серебрянной пулей», так как некоторые не очень популярные плагины не поддерживаются. Однако, это очень гибкий инструмент для хранения джоб в коде, в том числе с поддержкой макросов.
Итоги
Теперь, когда мы дошли до конца этой статьи, хотелось бы вернуться в начало и ответить на вопросы заданные в начале. На каждый из поставленных вопросов мы можем ответить — «да».
В этой статье мы не углублялись в тонкости настройки тех или иных технологий или в то, как правильно настраивать Jenkins, мы всего лишь делимся своим опытом, который, возможно, пригодится и вам.
P.S. В статье я часто использую слово «джоба» (с англ. «job», «задача»), для меня оно звучит привычнее чем «задача» в контексте CI/CD в целом или Jenkins.
