From 221ab4b432dfcd10d4dc9adcdd21f5b94ef2b626 Mon Sep 17 00:00:00 2001 From: "oleg.vodyanov91@gmail.com" Date: Sun, 23 Nov 2025 03:59:42 +0400 Subject: [PATCH] add jenkins --- ansible/group_vars/jenkins.yml | 6 ++ ansible/hosts | 2 + ansible/install.yml | 1 + ansible/jenkins.yml | 33 +++++++++ ansible/roles/dns/files/manual/db.192.168 | 1 + .../roles/dns/files/manual/db.home.lab.local | 1 + ansible/roles/docker/tasks/main.yml | 58 +++++++++------ ansible/roles/jenkins/handlers/main.yml | 4 + ansible/roles/jenkins/tasks/main.yml | 63 ++++++++++++++++ ansible/roles/jenkins/templates/Dockerfile.j2 | 6 ++ .../jenkins/templates/docker-compose.yaml.j2 | 23 ++++++ .../jenkins/templates/jenkins-casc.yaml.j2 | 62 ++++++++++++++++ .../templates/nginx-jenkins-https.conf.j2 | 73 +++++++++++++++++++ .../roles/jenkins/templates/plugins.txt.j2 | 3 + ansible/roles/jenkins/vars/main.yml | 7 ++ 15 files changed, 322 insertions(+), 21 deletions(-) create mode 100644 ansible/group_vars/jenkins.yml create mode 100644 ansible/jenkins.yml create mode 100644 ansible/roles/jenkins/handlers/main.yml create mode 100644 ansible/roles/jenkins/tasks/main.yml create mode 100644 ansible/roles/jenkins/templates/Dockerfile.j2 create mode 100644 ansible/roles/jenkins/templates/docker-compose.yaml.j2 create mode 100644 ansible/roles/jenkins/templates/jenkins-casc.yaml.j2 create mode 100644 ansible/roles/jenkins/templates/nginx-jenkins-https.conf.j2 create mode 100644 ansible/roles/jenkins/templates/plugins.txt.j2 create mode 100644 ansible/roles/jenkins/vars/main.yml diff --git a/ansible/group_vars/jenkins.yml b/ansible/group_vars/jenkins.yml new file mode 100644 index 0000000..0dd6406 --- /dev/null +++ b/ansible/group_vars/jenkins.yml @@ -0,0 +1,6 @@ +nginx_server_name: jenkins.home.lab.local +jenkins_backend: "http://127.0.0.1:8080" # контейнер проброшен наружу на 8080 +tls_mode: "letsencrypt" # "letsencrypt" | "custom" +tls_cert_path: "/etc/ssl/jenkins/fullchain.pem" # для custom +tls_key_path: "/etc/ssl/jenkins/privkey.pem" # для custom +client_max_body_size: "1g" \ No newline at end of file diff --git a/ansible/hosts b/ansible/hosts index 69ce6d7..dcadcd9 100644 --- a/ansible/hosts +++ b/ansible/hosts @@ -18,6 +18,8 @@ homelab: ansible_host: 192.168.0.101 vpn: ansible_host: vpn.home.lab.local + jenkins: + ansible_host: jenkins.home.lab.local children: dns: hosts: diff --git a/ansible/install.yml b/ansible/install.yml index 085120a..7957eb4 100644 --- a/ansible/install.yml +++ b/ansible/install.yml @@ -4,3 +4,4 @@ become: true roles: - packages + - docker diff --git a/ansible/jenkins.yml b/ansible/jenkins.yml new file mode 100644 index 0000000..326240a --- /dev/null +++ b/ansible/jenkins.yml @@ -0,0 +1,33 @@ +--- +- name: Jenkins in Docker (with JCasC & baked plugins) + hosts: jenkins + become: true + vars: + nginx_conf_path: /etc/nginx/sites-available/jenkins.conf + nginx_conf_link: /etc/nginx/sites-enabled/jenkins.conf + jenkins_version: "lts-jdk17" # можно weekly, но lts стабильнее + jenkins_image_name: "jenkins-custom-casc:1.0.0" + jenkins_root: "/srv/jenkins" + jenkins_data_dir: "{{ jenkins_root }}/data" + jenkins_build_dir: "{{ jenkins_root }}/build" + jenkins_http_port: 8080 + jenkins_agent_port: 50000 + jenkins_admin_user: "admin" + # jenkins_admin_password: "ChangeMe_UseVault!" # Хранить в Ansible Vault! - поставил на хосте + jenkins_url: "http://{{ ansible_default_ipv4.address }}:{{ jenkins_http_port }}" + jenkins_plugins: + - configuration-as-code + - job-dsl + - workflow-aggregator + - credentials + - credentials-binding + - git + - github + - ssh-credentials + - matrix-auth + - timestamper + - email-ext + - ws-cleanup + - pipeline-utility-steps + roles: + - jenkins diff --git a/ansible/roles/dns/files/manual/db.192.168 b/ansible/roles/dns/files/manual/db.192.168 index 3501a3f..167cf25 100644 --- a/ansible/roles/dns/files/manual/db.192.168 +++ b/ansible/roles/dns/files/manual/db.192.168 @@ -25,3 +25,4 @@ $TTL 604800 229.0 IN PTR libre.home.lab.local. ; 192.168.0.229 235.0 IN PTR monitoring.home.lab.local. ; 192.168.0.235 226.0 IN PTR wg.home.lab.local. ; 192.168.0.226 +145.0 IN PTR jenkins.home.lab.local. ; 192.168.0.145 diff --git a/ansible/roles/dns/files/manual/db.home.lab.local b/ansible/roles/dns/files/manual/db.home.lab.local index 3229458..98a53ec 100644 --- a/ansible/roles/dns/files/manual/db.home.lab.local +++ b/ansible/roles/dns/files/manual/db.home.lab.local @@ -28,3 +28,4 @@ nas.home.lab.local. IN A 192.168.0.100 libre.home.lab.local. IN A 192.168.0.229 monitoring.home.lab.local. IN A 192.168.0.235 wg.home.lab.local. IN A 192.168.0.226 +jenkins.home.lab.local. IN A 192.168.0.145 diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml index 9e64b91..e601b6a 100644 --- a/ansible/roles/docker/tasks/main.yml +++ b/ansible/roles/docker/tasks/main.yml @@ -8,34 +8,50 @@ - lsb-release state: present -- name: Add Docker official GPG key (Debian/Ubuntu) - ansible.builtin.apt_key: - url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg - state: present - when: ansible_os_family == 'Debian' +- name: Ensure docker keyring dir + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: "0755" -- name: Add Docker repo (Debian/Ubuntu) - ansible.builtin.apt_repository: - repo: "deb [arch={{ ansible_architecture }}] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable" - state: present - when: ansible_os_family == 'Debian' +- name: Add Docker GPG (dearmored) + ansible.builtin.get_url: + url: https://download.docker.com/linux/ubuntu/gpg + dest: /etc/apt/keyrings/docker.gpg + mode: "0644" + register: docker_gpg_raw + +- name: Dearmor Docker GPG if needed + ansible.builtin.shell: | + gpg --dearmor < /etc/apt/keyrings/docker.gpg > /etc/apt/keyrings/docker.gpg.tmp + mv /etc/apt/keyrings/docker.gpg.tmp /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + args: + creates: /etc/apt/keyrings/docker.gpg.tmp + when: docker_gpg_raw is changed + +- name: Map architecture to repo arch + ansible.builtin.set_fact: + repo_arch: "{{ 'arm64' if ansible_architecture in ['aarch64','arm64'] else 'amd64' }}" + +- name: Add Docker repo (correct arch + signed-by) + ansible.builtin.copy: + dest: /etc/apt/sources.list.d/docker.list + mode: "0644" + content: | + deb [arch={{ repo_arch }} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable + +- name: apt update + ansible.builtin.apt: + update_cache: true - name: Install Docker Engine & Compose plugin - ansible.builtin.package: + ansible.builtin.apt: name: - docker-ce - docker-ce-cli - containerd.io - docker-buildx-plugin - docker-compose-plugin + update_cache: true state: present - -- name: Install Docker Python SDK for Ansible modules - ansible.builtin.pip: - name: docker - -- name: Ensure docker service running - ansible.builtin.service: - name: docker - state: started - enabled: true diff --git a/ansible/roles/jenkins/handlers/main.yml b/ansible/roles/jenkins/handlers/main.yml new file mode 100644 index 0000000..fac6132 --- /dev/null +++ b/ansible/roles/jenkins/handlers/main.yml @@ -0,0 +1,4 @@ +- name: reload nginx + systemd: + name: nginx + state: reloaded \ No newline at end of file diff --git a/ansible/roles/jenkins/tasks/main.yml b/ansible/roles/jenkins/tasks/main.yml new file mode 100644 index 0000000..5fad5cc --- /dev/null +++ b/ansible/roles/jenkins/tasks/main.yml @@ -0,0 +1,63 @@ +- name: Create directories + file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "{{ jenkins_root }}" + - "{{ jenkins_build_dir }}" + - "{{ jenkins_data_dir }}" + +- name: Template plugins list + template: + src: templates/plugins.txt.j2 + dest: "{{ jenkins_build_dir }}/plugins.txt" + mode: "0644" + +- name: Template Dockerfile + template: + src: templates/Dockerfile.j2 + dest: "{{ jenkins_build_dir }}/Dockerfile" + mode: "0644" + +- name: Build custom Jenkins image (plugins baked in) + community.docker.docker_image: + name: "{{ jenkins_image_name }}" + build: + path: "{{ jenkins_build_dir }}" + source: build + register: build_img + +- name: Template JCasC config + template: + src: templates/jenkins-casc.yaml.j2 + dest: "{{ jenkins_data_dir }}/casc.yaml" + mode: "0640" + +- name: Ensure ownership for Jenkins data (uid/gid 1000) + file: + path: "{{ jenkins_data_dir }}" + state: directory + owner: "1000" + group: "1000" + recurse: true + +- name: Template docker-compose.yaml + template: + src: templates/docker-compose.yaml.j2 + dest: "{{ jenkins_root }}/docker-compose.yaml" + mode: "0644" + +- name: Up jenkins stack + community.docker.docker_compose_v2: + project_src: "{{ jenkins_root }}" + files: ["docker-compose.yaml"] + state: present + register: dc + +- name: Wait for Jenkins HTTP + wait_for: + host: 127.0.0.1 + port: "{{ jenkins_http_port }}" + delay: 3 + timeout: 180 \ No newline at end of file diff --git a/ansible/roles/jenkins/templates/Dockerfile.j2 b/ansible/roles/jenkins/templates/Dockerfile.j2 new file mode 100644 index 0000000..4df8f1e --- /dev/null +++ b/ansible/roles/jenkins/templates/Dockerfile.j2 @@ -0,0 +1,6 @@ +FROM jenkins/jenkins:{{ jenkins_version }} +USER root +# Ускоряем установку плагинов и делаем образ детерминированным +COPY plugins.txt /usr/share/jenkins/ref/plugins.txt +RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt +USER jenkins \ No newline at end of file diff --git a/ansible/roles/jenkins/templates/docker-compose.yaml.j2 b/ansible/roles/jenkins/templates/docker-compose.yaml.j2 new file mode 100644 index 0000000..93fc265 --- /dev/null +++ b/ansible/roles/jenkins/templates/docker-compose.yaml.j2 @@ -0,0 +1,23 @@ +services: + jenkins: + image: "{{ jenkins_image_name }}" + container_name: jenkins + restart: unless-stopped + ports: + - "{{ jenkins_http_port }}:8080" + - "{{ jenkins_agent_port }}:50000" + environment: + JAVA_OPTS: "-Djenkins.install.runSetupWizard=false -Dcasc.jenkins.config=/var/jenkins_home/casc.yaml" + JENKINS_ADMIN_ID: "{{ jenkins_admin_user }}" + JENKINS_ADMIN_PASSWORD: ${JENKINS_ADMIN_PASSWORD} + volumes: + - "{{ jenkins_data_dir }}:/var/jenkins_home" + # Если нужно, чтобы pipeline-ы запускали Docker на хосте — расскомментируйте: + - /var/run/docker.sock:/var/run/docker.sock + # healthcheck, чтобы Ansible/оркестратор видел состояние + healthcheck: + test: ["CMD", "bash", "-lc", "curl -fsSL http://127.0.0.1:8080/login || exit 1"] + interval: 30s + timeout: 5s + retries: 10 + start_period: 30s diff --git a/ansible/roles/jenkins/templates/jenkins-casc.yaml.j2 b/ansible/roles/jenkins/templates/jenkins-casc.yaml.j2 new file mode 100644 index 0000000..e154d89 --- /dev/null +++ b/ansible/roles/jenkins/templates/jenkins-casc.yaml.j2 @@ -0,0 +1,62 @@ +jenkins: + systemMessage: "Managed by Ansible + JCasC. Do not edit via UI." + numExecutors: 2 + mode: NORMAL + + # ✅ Перенесено в jenkins.* + crumbIssuer: + standard: + excludeClientIPFromCrumb: false + + # ✅ Перенесено в jenkins.* + securityRealm: + local: + allowsSignup: false + users: + - id: "${JENKINS_ADMIN_ID}" + password: "${JENKINS_ADMIN_PASSWORD}" + + # ✅ Современный matrix-auth + authorizationStrategy: + globalMatrix: + grantedPermissions: + - "Overall/Administer:${JENKINS_ADMIN_ID}" + - "Overall/Read:authenticated" + +unclassified: + # ✅ Разрешённый ключ + location: + url: "{{ jenkins_url }}" + # ✅ У плагина timestamper имя узла — 'timestamper' + timestamper: + systemTimeFormat: "yyyy-MM-dd HH:mm:ss" + elapsedTimeFormat: "'+'HH:mm:ss" + +tool: + git: + installations: + - name: "Default" + home: "/usr/bin/git" + +jobs: + - script: | + pipelineJob('hello-pipeline') { + definition { + cps { + script('''\ + pipeline { + agent any + stages { + stage('Hello') { + steps { + echo "It works on ${env.NODE_NAME}" + } + } + } + } + '''.stripIndent()) + sandbox(true) + } + } + } + diff --git a/ansible/roles/jenkins/templates/nginx-jenkins-https.conf.j2 b/ansible/roles/jenkins/templates/nginx-jenkins-https.conf.j2 new file mode 100644 index 0000000..02d5737 --- /dev/null +++ b/ansible/roles/jenkins/templates/nginx-jenkins-https.conf.j2 @@ -0,0 +1,73 @@ +# HTTP → HTTPS редирект +server { + listen 80; + listen [::]:80; + server_name {{ nginx_server_name }}; + + # ACME challenge (если включен LE) + location ^~ /.well-known/acme-challenge/ { + root /var/www/jenkins; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS ↔ Jenkins (reverse proxy) +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ nginx_server_name }}; + + {% if tls_mode == 'letsencrypt' %} + ssl_certificate /etc/letsencrypt/live/{{ nginx_server_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ nginx_server_name }}/privkey.pem; + {% else %} + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + {% endif %} + + # Базовая TLS-настройка + ssl_session_timeout 1d; + ssl_session_cache shared:MozSSL:10m; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + + # (Опционально) HSTS — включай, если домен всегда по HTTPS + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # Размер артефактов, long-running шаги + client_max_body_size {{ client_max_body_size }}; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + + # Проксирование в Jenkins-контейнер + location / { + proxy_pass {{ jenkins_backend }}; + proxy_http_version 1.1; + + # WebSocket/HTTP/1.1 + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Проброс хостов/протоколов/адресов + proxy_set_header Host $host; + 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 https; + proxy_set_header X-Forwarded-Port 443; + + # Убираем буферизацию, Jenkins любит стримить логи билда + proxy_buffering off; + } + + # Вспомогательное: корректная переменная для upgrade + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + access_log /var/log/nginx/jenkins.access.log; + error_log /var/log/nginx/jenkins.error.log; +} diff --git a/ansible/roles/jenkins/templates/plugins.txt.j2 b/ansible/roles/jenkins/templates/plugins.txt.j2 new file mode 100644 index 0000000..6af738e --- /dev/null +++ b/ansible/roles/jenkins/templates/plugins.txt.j2 @@ -0,0 +1,3 @@ +{% for p in jenkins_plugins -%} +{{ p }} +{% endfor -%} \ No newline at end of file diff --git a/ansible/roles/jenkins/vars/main.yml b/ansible/roles/jenkins/vars/main.yml new file mode 100644 index 0000000..682997e --- /dev/null +++ b/ansible/roles/jenkins/vars/main.yml @@ -0,0 +1,7 @@ +nginx_server_name: jenkins.home.lab.local +jenkins_backend: "http://127.0.0.1:8080" # контейнер проброшен наружу на 8080 +tls_mode: "letsencrypt" # "letsencrypt" | "custom" +tls_cert_path: "/etc/ssl/jenkins/fullchain.pem" # для custom +tls_key_path: "/etc/ssl/jenkins/privkey.pem" # для custom +client_max_body_size: "1g" +email: oleg.vodyanov91@gmail.com \ No newline at end of file