Установка 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/yandex-iac-ansible-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: ""
