Как настроить Jenkins в связке с Ansible

Эта статья — способ заглянуть в курс «Ansible: от первых шагов до большого проекта». Всеволод Севостьянов, Lead Engineer в Vene, отвечающий за пайплайны и deployment, показал, как настраивать Jenkins в связке с Ansible.

Первые шаги

Предположим что у нас есть система с установленным Jenkins и мы хотим привязать к ней IaaC в виде Ansible. После изначальной установки Jenkins, необходимо настроить связку с Ansible. Нужно договориться об условиях: 

  1. Jenkins установлен со стандартными плагинами;

  2. Все пайплайны запускаются на машине, где установлен Jenkins (стандартный build agent), но для remote-агентов будет то же самое.

После установки Jenkins создаём новую build-работу: 

Вы можете создать Freestyle project — шаблон  для Jenkins, в котором есть все этапы «Спулить код» → «Запустить работу», они позволяют с помощью шаблонов выбрать готовые пайплайны. Эта опция была добавлена в Jenkins чтобы повысить дружелюбность интерфейса к начинающим администраторам и автоматизаторам. Но мы выберем Pipeline. 

Окно со свойствами проекта: 

Здесь можно задать описание проекта, настроить как будут вести себя пайплайны. Во вкладке Build Triggers можно поставить параметры для автоматического запуска пайплайна, например запускать раз в день, или запускать, если появился новый коммит в определенной ветке Git.

Обратите внимание на поле ниже — Pipeline: 

Jenkins, как и Gitlab, использует скрипт, который умеет выполнять консольные команды для запуска пайплайна по шагам. В отличие от Gitlab, Jenkins использует Groovy — полноценный язык программирования, который дает возможность тоньше кастомизировать работы и позволяет полноценно использовать программные конструкции для автоматизации. Но его сложнее освоить. 

Итак, с помощью Pipeline достигнем целей:

  1. Запустим Ansible и выведем его версию;

  2. Скачаем Playbook с Github и запустим его;

  3. Постучимся в другую машину изнутри Jenkins;

  4. Используем специальный плагин Ansible для предыдущих трёх пунктов.

Запускаем Ansible и выводим его версию

Начнем с очень простой задачи — запустим Ansible в пайплайне и выведем его версию в консоль билда. 

Пайплайны в Jenkins задаются очень простым путем: пишется Stage, аналогично стейджу в Gitlab, а внутри него перечисляются Steps. Все системы CI/CD основаны на одном принципе, дьявол именно в деталях. 

Напишем свой Stage, назовем его Deploy и вызовем там версию Ansible.

pipeline { // задаем тон groovy, даем понять что здесь у нас шаги пайплайна
    agent any // если у нас есть какие то jenkins агенты специально для Ansible мы можем их указать здесь, у меня только один агент, поэтому я поставлю any
   
    stages { // здесь определяем какие шаги в пайплайне
        stage('Deploy') { // наш деплой
            steps { // определяем шаги уже самого стейджа
                sh 'ansible --version' // выводим версию Ansible, команда sh просто выполнит скрипт из консоли
            }
        }
    }
}

Сохраняем пайплайн, выходим в главное окно проекта и нажимаем Build Now.

Если у вас на машине уже установлен Ansible, все пройдет успешно. Первый пайплайн готов. 

Заглянем ему под капот

Нажмем на #1 внизу в Build history. Каждый пайплайн создает history, которую можно ограничить настройками билда, чтобы предотвратить засорение диска логами и артефактами сборки. По умолчанию ничего не удаляем, я рекомендую на период настройки пайплайнов не удалять билды, чтобы вернуться на любой шаг и посмотреть необходимую информацию.

Жмем на Console Output чтобы посмотреть вывод:

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

Дальше — шаги пайплайна подсвеченные серым, а вывод консоли черным цветом. После отображения папки, где был запущен пайплайн, выводятся  результаты работы нашей sh команды, которая покажет версию Ansible в привычном  виде, как если бы мы запустили ее из консоли. 

Запускаем Playbook с Github

Для изначального тестирования я создам очень простой плейбук:

- name: "Test playbook run"

hosts: localhost

tasks:

  - name: "Debug"

    ansible.builtin.debug:

      msg: "Test debug"

Он располагается в github по адресу https://github.com/Nortsx/jenkinsansiblebook. Важно: перед началом работы склонируйте его в свой репозиторий, чтобы не зависеть от оригинала.

Модифицируем пайплайн:

pipeline {
    agent any

    stages {
        stage(‘Checkout’) { // добавим новый Stage
            steps {
                git branch: ‘main’, url: «git@github.com:Nortsx/jenkinsansiblebook.git» // используем встроенный в Jenkins плагин Git для скачивания проекта из бранча main
            }
        }
        stage(‘Deploy’) {
            steps {
                sh ‘ansible-playbook playbook.yml’ // поскольку скрипты работают в одной папке, на втором шаге мы можем просто запустить плейбук
            }
        }
    }
}

Я скачиваю плейбук его через ssh, а не http.

Запускаем Pipeline и смотрим на результаты билда:

Jenkins провалился на первом этапе, не смог скачать исходники с Git, потому что не смог установить соединение с Github. Ответ на вопрос  «как это пофиксить?» в тексте ошибки. Мы увидим эту ошибку, если соединимся по ssh с локальным ssh-agent, но при этом сервера нет в known_hosts. Добавим сервер Github для Jenkins.

Логинимся на машину с Jenkins, от лица пользователя Jenkins, если вы не переставляли пользователя для Jenkins и запускаем ssh github.com. На вопрос «Do you want to add gihutb.com to known hosts» жмем Y. Первая ошибка больше не будет мешать. 

Запускаем пайплайн второй раз:

Ошибка в stderr поменялась на Permission Denied — для подключения по ssh к Github нужен ключ, который Github знает. Добавим этот ключ. 

Убеждаемся, что плагин Credentials установлен: Dashboard → Manage Jenkins → Manage Plugins, если нет, то установим его. 

Генерируем ключ для Github и добавляем его в аккаунт. 

Запустим команду на машине с linux 

Ssh-keygen -t rsa.

После ответа на вопросы об имени ключа (укажите другое имя кроме id_rsa, если ваш стандартный ключ уже используется, поскольку его можно случайно перезаписать), защита паролем (выбираем «no») вы получите пару приватный/публичный ключ. Публичный ключ имеет расширение .pub после имени, приватный ключ его не имеет. 

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

После генерации ключей идем на github.com, кликаем на иконку профиля справа, Settings→SSH and GPG keys и попадаем на страницу добавления ssh ключей профиля.

Жмякаем New SSH Key

Копируем ПУБЛИЧНУЮ часть ключа и называем его произвольным именем 

В Dashboard → Manage Jenkins → Credentials нажимаем на Global и Add Credentials.

В открывшемся окне выбираем SSH Username with private key и заполняем данные. 

ID  — идентификатор, уникальный для каждого credential, по которому вызывают их из пайплайна. Username — имя для вашего профиля Github. Private key → нажмите Enter Directly → Add и скопируйте значение из сгенерированного Приватного ключа. 

После добавления ключ в списке Credentials.

Время использовать ключ в пайплайне:

pipeline {
    agent any

    stages {
        stage(‘Checkout’) {
            steps {
                git branch: ‘main’, url: «git@github.com:Nortsx/jenkinsansiblebook.git», credentialsId: ‘github_key’ // здесь добавляем credentialsId и указываем наш ID.
            }
        }
        stage(‘Deploy’) {
            steps {
                sh ‘ansible-playbook playbook.yml’
            }
        }
    }
}

Запускаем пайплайн и смотрим на вывод:

Сработало!

Посмотреть результаты запусков по шагам можно, нажав кнопку Pipeline Steps.

Она покажет аргументы запуска и позволит по клику на мониторчике изолированно посмотреть вывод по конкретному шагу, что может быть удобно для отладки.

Например вывод только запуска нашего Ansible playbook:

Переходим к следующей части. 

Стучимся в другую машину изнутри Jenkins

Воспользуемся Ansible, чтобы постучаться в другие хосты.

В плейбук добавим файлы hosts и ansible.cfg

Hosts.ini

testmachine ansible_host=192.168.60.55 ansible_user=vagrant

Я использую Vagrant, поэтому мои адреса принадлежат локальной сети. 

Ansible.cfg
[defaults]
host_key_checking=false

Playbook.yml


— name: «Test playbook run»
  hosts: all # теперь запускаем скрипты на удаленных хостах
  tasks:
    — name: «Debug»
      ansible.builtin.debug:
        msg: «Test debug»

После изменений в своем гите, нам необходимо изменить пайплайн, для использования файла hosts.ini.
pipeline {
    agent any

    stages {
        stage(‘Checkout’) { // добавим новый Stage
            steps {
                git branch: ‘main’, url: «git@github.com:Nortsx/jenkinsansiblebook.git», credentialsId: ‘github_key’
            }
        }
        stage(‘Deploy’) {
            steps {
                sh ‘ansible-playbook playbook.yml -i hosts.ini’ // добавляем новый аргумент
            }
        }
    }
}

После запуска видим провал на втором шаге:

Ключи не авторизованы на удалённой машине, поэтому с ней нельзя связаться. 

Добавим ключи для авторизации на удаленной машине:

Я буду использовать тот же ключ, что для github. Возьмем публичную часть ключа и добавим в ~/.ssh/authorized_keys машины, к которой подключаемся через ansible. 

Изменим пайплайн, чтобы Ansible мог использовать ключи авторизации. 

pipeline {
    agent any

    stages {
        stage(‘Checkout’) {
            steps {
                git branch: ‘main’, url: «git@github.com:Nortsx/jenkinsansiblebook.git», credentialsId: ‘github_key’
            }
        }
        stage(‘Deploy’) {
            steps {
                withCredentials([sshUserPrivateKey(credentialsId: ‘github_key’, keyFileVariable: ‘PRIVATE’)]) { // используем credentials и записываем их содержание во временный файл доступный по пути PRIVATE
                  sh ‘ansible-playbook playbook.yml -i hosts.ini —private-key $PRIVATE’ //используем переменную с помощью Ansible
                }
            }
        }
    }
}

И вуаля:

Для других переменных, которые не надо скрывать, мы можем использовать секцию variables. 

Например:

pipeline {
    agent any
   
    environment {
        GIT_BRANCH = 'main'
    }

    stages {
        stage(‘Checkout’) {
            steps {
                git branch: $GIT_BRANCH, url: «git@github.com:Nortsx/jenkinsansiblebook.git», credentialsId: ‘github_key’
            }
        }

Используем специальный плагин Ansible для всего сделанного выше

Снова идём в список плагинов в Manage Jenkins и ищем Ansible в списке Available плагинов. 

Устанавливаем его. 

При установке ставим галочку «Restart Jenkins», после установки Jenkins перезагрузится. 

Теперь можно переписать пайплайн:

pipeline {
    agent any

    stages {
        stage(‘Checkout’) {
            steps {
                git branch: ‘main’, url: «git@github.com:Nortsx/jenkinsansiblebook.git», credentialsId: ‘github_key’
            }
        }
        stage(‘Deploy’) {
            steps {
                ansiblePlaybook playbook: ‘playbook.yml’, inventory: ‘hosts.ini’, credentialsId: ‘github_key’
            }
        }
    }
}

Вместо оборачивания в credentials, мы передали строчку в Id и вызвали специальный модуль, делающий за нас работу. Это сравнимо с использованием, например command или shell в Ansible, когда можно использовать специальный модуль, упрощающий и контролирующий работу, вместо «голых» вызовов консоли. 

Плагин умеет больше, чем простой запуск плейбука, сильно упрощает пайплайны и саму жизнь. 

Читайте так же: