3 March

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

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

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

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

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

image


Введение


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


Нам бы хотелось управлять 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.

Вот пример возможной инфраструктуры на 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.
Tags:jenkinsawsci/cdjcasciaac
Hubs: System administration DevOps
+15
3.9k 67
Comments 1
Ads