[Перевод] Разворачиваем Jenkins как код

Прим. перев.: это перевод статьи из инженерного блога компании Preply о том, как можно использовать конфигурацию как код для такого популярного CI/CD инструмента как Jenkins.

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

Кроме того, в данной статье мы попробуем найти ответы на такие вопросы как:

  • Стал ли наш Jenkins стабильнее?
  • Можем ли мы делать частые изменения в конфигурацию сервера и джоб?
  • Является ли обновление Jenkins для нас все еще болью?
  • Можем ли мы контролировать все наши изменения?
  • Можем ли мы быстро восстановить Jenkins в случае факапа?


image

Введение


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

v2z_9tswoizsogrjajqpzt3exig.png

Источник: Инженерный блог компании Altexsoft

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

image

Вот об этом пойдет речь

Инфраструктура для Jenkins


imageМы используем AWS и конфигурируем всю инфраструктуру с помощью Terraform и других инструментов из хашистека, таких как Packer и Vault.

Как упоминалось ранее, мы пробовали использовать Jenkins в Kubernetes и столкнулись с некоторыми проблемами масштабирования PVC, ресурсов, а также не очень продуманной архитектуры.
Здесь же, мы используем обычные ресурсы AWS: EC2 инстансы, SSL-сертификаты, балансировщики, Cloudfront и т.д. Образ ОС (AMI) сконфигурирован с помощью Packer, который отлично интегрируется с Terraform и Vault.

{
    "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

В свою очередь, файл packer_bootstrap.sh содержит в себе набор команд, с помощью которых устанавливается софт внутрь образа. К примеру, мы можем установить Docker, docker-compose и vaultenv или Datadog-агент для мониторинга. Касаемо инфраструктуры под этот образ, мы можем использовать Terraform, Cloudformation, Pulumi или даже Ansible.

db8re5y3_y58pzevkiyxtcpkpow.png

Вот пример возможной инфраструктуры на AWS для Jenkins

Пользователи заходят на Jenkins через внутренний балансер, а Github-хуки попадают на сервер через внешний. Мы используем интеграцию Jenkins с GitHub, поэтому некоторые ссылки сервера должны быть доступны из интернета. Здесь есть множество различных решений (к примеру белый список для IP-адресов, урлов или токенов, и т.д.), в нашем случае мы используем комбинацию разрешенных урлов и валидации токена.

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


Используем Docker для установки Jenkins и его плагинов


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

FROM 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

Dockerfile

Внутрь Docker-образа устанавливаются некоторые пакеты как Job Builder, о котором я расскажу позже, также регистрируются хранилища и устанавливаются плагины, указанные в файле plugins.txt.

Jenkins.instance.pluginManager.plugins.each{
  plugin ->
    println ("${plugin.getShortName()}:${plugin.getVersion()}")
}

Получить список установленных плагинов в Jenkins можно по ссылке https://our-jenkins-url/script и сохранив вывод в файл plugins.txt

И наконец, конфигурация для docker-compose, которая будет запускать Jenkins в Docker.

version: "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

Мы также используем vaultenv для проброса секретов из Vault

Обратите внимание на некоторые параметры Java, которые помогли нам со сборкой мусора и ограничением ресурсов. Вот в этой статье очень классно расписано о тюнинге Jenkins.
Ну и конечно теперь мы можем локально развернуть копию Jenkins и экспериментировать с новыми версиями сервера и плагинов. Это очень удобно.

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


Настройка плагина Jenkins as a Code (JCaSC) для конфигурации сервера


imageВ общем, есть такой плагин под названием Jenkins Configuration as Code (JCasC), который позволяет хранить конфигурацию сервера в человекочитаемом текстовом формате.

С помощью этого плагина можно описывать конфигурации безопасности, доступы, настройки плагинов, агентов, вкладок и многое другое.

Конфигурация представлена в формате YAML и разделена на 5 блоков:

  • credentials (описание системных секретов)
  • jenkins (настройки авторизации и облака, глобальные настройки, описание агентов, некоторые настройки безопасности и вкладки)
  • security (глобальные настройки безопасности, такие как разрешенные скрипты)
  • tool (конфигурация для внешних инструментов, таких как git, allure, и т.д.)
  • unclassified (другие настройки, такие как интеграция со Slack)


imageimageПлагин поддерживаем импорт конфигурации из уже существующей инсталляции 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-проектов


imageСуществует несколько способов создания 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.

© Habrahabr.ru