Установка Jenkins используя Ansible и плагин Configuration as Code на виртуальной машине


Jenkins Configuration as Code (aka JCasC) призвана быть инструментом, который позволяет вам запускать свой Jenkins в парадигме Infrastructure as Code или инфраструктура как код (IaC).


Этот пост будет состоять из двух частей. Первая часть — быстрый запуск тестового примера. Вторая часть — подробное описание.


Для быстрого запуска тестового примера будем использовать Yandex Cloud. Клонируем репозиторий, указываем креды Yandex cloud и запускаем установку.


Первая часть. Быстрый запуск тестового примера.


Перед установкой необходимо установить необходимые утилиты. Подробнее в readme репозитория.


Скачиваем репозиторий:


git clone https://github.com/patsevanton/infrastructure-as-a-code-example.git
cd jenkins-geerlingguy-jenkins
cp private.auto.tfvars.example private.auto.tfvars

Заполняем указываем креды Yandex cloud в private.auto.tfvars.


Запускаем установку Jenkins:


jenkins_install.sh

Вторая часть. Подробное описание.


В файле private.auto.tfvars указываем ресурсы виртуальной машины. Я использую sslip.io для резолвинга DNS динамического IP. Вы можете использовать другие варианты.


В jenkins_install.sh с помощью Ansible устанавливаются java, jenkins, nginx и генерируется letsencrypt сертификат.


Файл inventory создается из шаблона terraform. Разберем его.


При использовании роль geerlingguy.ansible-role-jenkins нельзя указать точную LTS версию Jenkins. Issue — https://github.com/geerlingguy/ansible-role-jenkins/issues/359:


    jenkins_prefer_lts: true
    #jenkins_version: "2.346"

Параметры letsencrypt ansible роли. Регистрируем letsencrypt без указания email и используем http проверку домена:


    letsencrypt_opts_extra: "--register-unsafely-without-email"
    letsencrypt_cert:
      name: ${hostname}.${public_ip}.${domain}
      domains:
        - ${hostname}.${public_ip}.${domain}
      challenge: http
      http_auth: standalone
      reuse_key: True

После того как сгенерироваться letsencrypt устанавливаем и настраиваем nginx c помощью роли geerlingguy.nginx. В конфигурации удаляем виртуальных хост default, который используется по умолчанию и настраиваем виртуальный хост для jenkins:


    nginx_remove_default_vhost: true
    nginx_vhosts:
      - listen: "80"
        server_name: "${hostname}.${public_ip}.${domain}"
        return: "301 https://${hostname}.${public_ip}.${domain}$request_uri"
        filename: "${hostname}.${public_ip}.${domain}.80.conf"
      - listen: "443 ssl"
        server_name: ${hostname}.${public_ip}.${domain}
        state: "present"
        extra_parameters: |
          location / {
            proxy_set_header        Host $host:$server_port;
            proxy_set_header        X-Real-IP $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        X-Forwarded-Proto $scheme;

            # Fix the "It appears that your reverse proxy set up is broken" error.
            proxy_pass          http://127.0.0.1:8080;
            proxy_read_timeout  90;

          }
          ssl_certificate     /etc/letsencrypt/live/${hostname}.${public_ip}.${domain}/cert.pem;
          ssl_certificate_key /etc/letsencrypt/live/${hostname}.${public_ip}.${domain}/privkey.pem;

Рассмотрим playbook.yml. Jenkins устанавливается как systemd сервис. Установку и настройку Jenkins в Kubernetes думаю протестировать в будущем.


После того как Jenkins установлен небходимо допилить его напильником или донастроить.


Создаем директорию /var/lib/jenkins/jcasc и помещаем туда файл конфигурации плагина Jenkins Configuration as Code (aka JCasC).


Описание jcasc.yaml рассмотрим ниже:


    - name: Create directory /var/lib/jenkins/jcasc
      ansible.builtin.file:
        path: /var/lib/jenkins/jcasc
        state: directory
        owner: jenkins
        group: jenkins

    - name: Copy jcasc.yaml with owner and permissions
      ansible.builtin.copy:
        src: jcasc.yaml
        dest: /var/lib/jenkins/jcasc/jcasc.yaml
        owner: jenkins
        group: jenkins
        mode: '0644'

Далее необходимо сделать override systemd сервиса jenkins и добавить environment переменную чтобы плагин Jenkins Configuration as Code (aka JCasC) заработал. Для этого используем репозиторий https://github.com/MatthewRFennell/ansible-role-jenkins/tree/use-systemd-instead-of-init.


Устанавливаем роль из этого репозитория:


ansible-galaxy install --force git+https://github.com/MatthewRFennell/ansible-role-jenkins.git,use-systemd-instead-of-init

Из этого репозитория в репозиторий geerlingguy/ansible-role-jenkins сделан pull request https://github.com/geerlingguy/ansible-role-jenkins/pull/354.


Добавляем env переменную CASC_JENKINS_CONFIG в inventory. Так как JAVA_OPTS перезатирается, то в нее тоже добавляем -Djenkins.install.runSetupWizard=false


    jenkins_java_options: "-Djava.awt.headless=true -Djenkins.install.runSetupWizard=false"

    jenkins_init_changes:
      - option: "JAVA_OPTS"
        value: "{{ jenkins_java_options }}"
      - option: "CASC_JENKINS_CONFIG"
        value: "/var/lib/jenkins/jcasc/"

Далее идет установка jenkins плагинов через jenkins-plugin-manager. Скрипт установки Jenkins плагинов (https://github.com/jenkinsci/docker/blob/master/install-plugins.sh) устарел и был удален из репозитория. В директории files находимя файл plugins.txt, в котором список плагинов для установки.


Так же заведена issue в community.general.jenkins_plugin. По умолчанию with_dependencies включена, но она не работает — https://github.com/ansible-collections/community.general/issues/4995.


Дальше идет генерация api token для текущего юзера admin:


    - name: Generate generate-jenkins-api-token.sh from template
      template:
        src: generate-jenkins-api-token.sh.j2
        dest: /var/lib/jenkins/generate-jenkins-api-token.sh
        owner: jenkins
        group: jenkins
        mode: 0755

    - name: Create API token from script
      shell: "/var/lib/jenkins/generate-jenkins-api-token.sh"
      register: script_api_token_output
      args:
        creates: /var/lib/jenkins/api_token.txt

    - name: Read api_token from /var/lib/jenkins/api_token.txt
      slurp:
        src: /var/lib/jenkins/api_token.txt
      register: api_token

    - name: Set fact api_token
      set_fact:
        api_token: "{{ api_token.content | b64decode }}"

Содержимое файла generate-jenkins-api-token.sh. Правильный скрипт генерации api token нашел только в одном месте: https://stackoverflow.com/a/72906138/3698532. Остальные скрипты не работали. Переработанный рабочий скрипт:


#!/bin/bash

FILE=/var/lib/jenkins/api_token.txt

if [ -f "$FILE" ]; then
    echo "$FILE exists."
else 
    echo "$FILE does not exist."
    JENKINS_URL="http://localhost:8080"
    JENKINS_USER=admin
    JENKINS_USER_PASS={{ jenkins_admin_password }}

    JENKINS_CRUMB=$(curl -u "$JENKINS_USER:$JENKINS_USER_PASS" -s --cookie-jar /tmp/cookies $JENKINS_URL'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')

    ACCESS_TOKEN=$(curl -u "$JENKINS_USER:$JENKINS_USER_PASS" -H $JENKINS_CRUMB -s \
                        --cookie /tmp/cookies $JENKINS_URL'/me/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken' \
                        --data 'newTokenName=GlobalToken' | jq -r '.data.tokenValue')

    echo $ACCESS_TOKEN > /var/lib/jenkins/api_token.txt
fi

Далее генерируем job с помощью jenkins-job-builder:


    - name: Generate jenkins-jobs-config from template
      template:
        src: jenkins-jobs-config.j2
        dest: /var/lib/jenkins/jenkins-jobs-config
        owner: jenkins
        group: jenkins
        mode: 0644

    - name: Create directory /var/lib/jenkins/jobs
      ansible.builtin.file:
        path: /var/lib/jenkins/jobs
        state: directory
        owner: jenkins
        group: jenkins
        mode: '0755'

    - name: Copy job-template.yaml
      ansible.builtin.copy:
        src: job-template.yaml
        dest: /var/lib/jenkins/jobs/job-template.yaml
        owner: jenkins
        group: jenkins

    - name: Copy defaults.yaml
      ansible.builtin.copy:
        src: defaults.yaml
        dest: /var/lib/jenkins/jobs/defaults.yaml
        owner: jenkins
        group: jenkins

    - name: Copy projects.yaml
      ansible.builtin.copy:
        src: projects.yaml
        dest: /var/lib/jenkins/jobs/projects.yaml
        owner: jenkins
        group: jenkins

    - name: Create Jenkins jobs using Jenkins job builder
      shell: "jenkins-jobs --conf=jenkins-jobs-config update jobs"
      register: jenkins_job_builder_output
      args:
        chdir: /var/lib/jenkins

Описание файлов конфигурации jenkins-job-builder
defaults.yaml:


- defaults:
    name: global
    logrotate:
      daysToKeep: 30
      numToKeep: 5
      artifactDaysToKeep: -1
      artifactNumToKeep: -1

job-template.yaml:


- project:
    name: project-example
    jobs:
      - '{name}_job':
          name: getspace
          command: df -h
      - '{name}_job':
          name: listEtc
          command: ls /etc

Эти файлы конфигурации были взяты из практического видео по jenkins-job-builder: https://www.youtube.com/watch?v=SoP05dLe5kA


В конце playbook подкладываем измененый конфиг jcasc.yaml


- name: Enable googleOAuth2
  hosts: jenkins
  become: true
  tasks:
    - name: Copy jcasc_googleoauth2.yaml
      ansible.builtin.copy:
        src: jcasc_googleoauth2.yaml
        dest: /var/lib/jenkins/jcasc/jcasc.yaml
        owner: jenkins
        group: jenkins
        mode: '0644'
      register: jcasc_googleoauth2_yaml

    - name: Restart service jenkins
      ansible.builtin.systemd:
        state: restarted
        daemon_reload: yes
        name: jenkins
      when: jcasc_googleoauth2_yaml.changed

В конфиге указываем что вход осуществляется с помощью Google учетки. Идем в APIs & serivces вашего проекта в https://console.developers.google.com, создаем новые credentials, выбираем OAuth client ID, выбираем Application Type: Web Application. Выбираем название name. В Authorized JavaScript origins указываем URI ссылку, ведущую на ваш jenkins сайт (я использовать HTTPS в URL). В Authorized redirect URIs указываем ${ваш-jenkins-сайт}/securityRealm/finishLogin. Полученные clientId и clientSecret указываем в конфиге jcasc_googleoauth2.yaml. В domain указываем домен, с которого можно входить в этот Jenkins.


jenkins:
  securityRealm:
    googleOAuth2:
      # https://stackoverflow.com/questions/17699656/how-to-configure-jenkins-login-with-google-apps
      # The Client ID from the https://console.developers.google.com/
      clientId: "xxx-xxx.apps.googleusercontent.com"
      # The Client Secret from the Google Developer Console.
      clientSecret: "xxx-xxx"
      # Optional. If present, access to Jenkins will be restricted to users in the provided Google Apps Domain.
      #           A comma separated list can be used to specify multiple domains.
      domain: ""

Комментарии (0)