0. 简介

Jenkins 是一个开源的持续集成和持续交付 (CI/CD) 工具。它通过自动化构建测试和部署流程,显著提升软件交付效率。其核心优势在于强大的插件生态可扩展性以及对分布式构建的支持。


1. Jenkins 安装与初始化

1.1 安装方式

方法 A:Debian/Ubuntu 官方源安装(推荐)

# 添加密钥
curl -fsSL https://pkg.jenkins.io/debian/jenkins.io-2023.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
# 添加存储库
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
# 安装
sudo apt-get update
sudo apt-get install fontconfig openjdk-11-jre jenkins

方法 B:WAR 包手动安装(灵活便捷)

  1. 下载:wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/war/latest/jenkins.war
  2. 运行:java -jar jenkins.war --enable-future-java --httpPort=8081

1.2 基础配置

  • 修改端口:编辑 /etc/default/jenkins/usr/lib/systemd/system/jenkins.service 中的 HTTP_PORT
  • 初始密码:首次访问时从日志或 /var/lib/jenkins/secrets/initialAdminPassword 获取。
  • 镜像加速:在 管理 Jenkins -> 插件管理 -> 高级 中,将升级站点改为:http://mirror.esuni.jp/jenkins/updates/update-center.json

2. 权限与环境调优

2.1 切换为 Root 用户运行

默认的 jenkins 用户权限有限。若需以 root 运行:

  1. 编辑 /etc/default/jenkins,修改 JENKINS_USER=root JENKINS_GROUP=root
  2. 编辑 /usr/lib/systemd/system/jenkins.service,修改 User=root Group=root
  3. 执行:systemctl daemon-reload && systemctl restart jenkins

2.2 内存限制与环境变量

  • 内存优化:在配置文件 JAVA_ARGS 中添加 -Xmx2048m
  • 全局变量节点管理 -> 配置从节点 -> 环境变量。例如 PATH 加入 Python 或 Allure 的路径。
  • 并发设置:在系统配置中修改 # of executors,提升任务并行处理能力。

3. 自动化任务构建技巧

3.1 源码管理与凭据

  • Git 凭据:使用 "Username with password" 存储 Git 账号。
  • 子模块 (Submodule):若子模块拉取失败,建议将子仓库设为 Public 或在服务器配置 SSH 公钥。

3.2 定时触发 (Cron 语法)

在“构建触发器”中输入,格式为:分钟 小时 日 月 周

  • H */4 * * *:每 4 小时一次。
  • H 18 * * 1-5:周一至周五 18 点。

3.3 解决 Python 导入错误

如果在 Jenkins 环境下运行 Python 报 ImportError,在脚本头部手动添加路径:

import sys
sys.path.append('/usr/local/lib/python3.8/dist-packages')

4. 核心功能:动态修改构建标题 (无需插件)

场景: 将默认的 #203 标题实时改为 #203-resume-192.168.31.182

Execute shell 任务最后添加如下脚本:

#!/bin/bash
# 1. 业务逻辑
cd /home/yys/mobile && pipenv run python3 control_device.py ${ACTION} ${TARGET_IP}

# 2. 修改标题逻辑 (仅需账号密码,方便迁移)
USER="yys"
PASS="123456"
NEW_NAME="#${BUILD_NUMBER}-${ACTION}-${TARGET_IP}"

# 获取 Crumb 并保持 Session Cookie
CRUMB_DATA=$(curl -s -u "$USER:$PASS" -c cookies.txt "${JENKINS_URL}crumbIssuer/api/xml?xpath=concat(//crumbRequestField\":\"//crumb)")

# 调用 API 修改 (增加 -s 隐藏进度条,> /dev/null 隐藏返回结果)
curl -s -u "$USER:$PASS" -b cookies.txt -H "$CRUMB_DATA" -X POST "${BUILD_URL}configSubmit" \
     --data-urlencode "json={\"displayName\":\"$NEW_NAME\"}" > /dev/null

rm -f cookies.txt
echo "✅ 构建标题已更新: $NEW_NAME"

5. Jenkins API 自动化脚本 (Python 实现)

5.1 批量读取任务配置

import requests
import xml.etree.ElementTree as ET

def get_all_jobs(url user pwd):
    s = requests.Session()
    s.auth = (user pwd)
    jobs = s.get(f"{url}/api/json").json()['jobs']
    result = []
    for j in jobs:
        config = s.get(f"{url}/job/{j['name']}/config.xml").text
        result.append({'name': j['name'] 'config': config})
    return result

5.2 检测任务状态并自动运行

def run_job_if_idle(url user pwd job_name):
    s = requests.Session()
    s.auth = (user pwd)
    info = s.get(f"{url}/job/{job_name}/api/json?tree=builds[building]").json()
    is_building = any(b['building'] for b in info['builds'])
    if not is_building:
        s.post(f"{url}/job/{job_name}/build")
        print(f"任务 {job_name} 已启动")

6. 进阶维护与故障排查

6.1 中文乱码解决 (Docker 环境)

docker-compose.yml 中添加环境变量,确保 UTF-8 编码:

environment:
  - LANG=C.UTF-8
  - JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8

6.2 自动备份

创建脚本定期打包 /var/lib/jenkins 目录:

tar -czvf /backup/jenkins_$(date +%Y%m%d).tar.gz /var/lib/jenkins

6.3 安卓 ADB 授权

Jenkins root 用户和普通用户下的 .android/adbkey 需保持一致。初次连接时需在手机端点击“允许调试”,否则 Jenkins 脚本调用 adb 会报权限错误。


结语: 本手册涵盖了 Jenkins 从 0 到 1 的全过程。建议在迁移项目时,优先检查权限设置(Section 2.1)和编码配置(Section 6.1)。

 

 

6.4. 最佳实践:基于 Python 的 Job 备份与迁移

相比于传统的物理打包 /var/lib/jenkins,使用 Python 脚本备份 Job 更加轻量且易于管理。

6.4.1 备份 Job(提取配置为字典列表)

使用 get_jenkins_jobs 将所有任务的描述参数定时器Shell 脚本提取出来。

import requests
import xml.etree.ElementTree as ET

def get_jenkins_jobs(jenkins_url username password):
    s = requests.Session()
    s.auth = (username password)
    jobs_data = s.get(f"{jenkins_url}/api/json").json()
    result = []

    for job in jobs_data['jobs']:
        config_xml = s.get(f"{jenkins_url}/job/{job['name']}/config.xml").text
        root = ET.fromstring(config_xml)
        
        # 提取核心配置
        job_description = root.find("description").text if root.find("description") is not None else ""
        build_periodically = [bp.text.strip() for bp in root.findall(".//hudson.triggers.TimerTrigger/spec")]
        execute_shell = [es.text.strip() for es in root.findall(".//hudson.tasks.Shell/command")]
        
        # 提取参数
        params = []
        for p in root.findall(".//hudson.model.ParametersDefinitionProperty/parameterDefinitions/*"):
            params.append({
                'name': p.find("name").text if p.find("name") is not None else ""
                'description': p.find("description").text if p.find("description") is not None else ""
                'type': p.tag.split('.')[-1]
                'default_value': p.find("defaultValue").text if p.find("defaultValue") is not None else ""
            })

        result.append({
            'job_name': job['name']
            'job_description': job_description
            'build_periodically': build_periodically
            'execute_shell': execute_shell
            'parameters': params
        })
    return result

6.4.2 还原/创建 Job(利用备份数据迁移)

使用 create_jenkins_jobs 在新环境中批量重建任务。通过 <![CDATA[]]> 完美解决 Shell 中 && 等特殊字符导致的创建失败问题。

from requests.auth import HTTPBasicAuth

def create_jenkins_jobs(jenkins_url user passwd jobs_list):
    print("⚠️ 注意:description 和 execute_shell 中的中文字符不宜过多,以免触发 500 错误。")
    
    for job in jobs_list:
        # 1. 组装参数 XML
        param_xml = "<properties/>"
        if job.get("parameters"):
            inner_xml = "".join([f"""
                <hudson.model.StringParameterDefinition>
                    <name>{p['name']}</name>
                    <description>{p['description']}</description>
                    <defaultValue>{p['default_value']}</defaultValue>
                    <trim>false</trim>
                </hudson.model.StringParameterDefinition>""" for p in job["parameters"] if p['type'] == "StringParameterDefinition"])
            param_xml = f"<properties><hudson.model.ParametersDefinitionProperty><parameterDefinitions>{inner_xml}</parameterDefinitions></hudson.model.ParametersDefinitionProperty></properties>"

        # 2. 组装 Shell XML (使用 CDATA 保护特殊符号)
        shell_cmd = "\n".join(job.get("execute_shell" []))
        builder_xml = f"<builders><hudson.tasks.Shell><command><![CDATA[{shell_cmd}]]></command></hudson.tasks.Shell></builders>" if shell_cmd else "<builders/>"

        # 3. 组装定时器 XML
        trigger_cmd = "\n".join(job.get("build_periodically" []))
        trigger_xml = f"<triggers><hudson.triggers.TimerTrigger><spec>{trigger_cmd}</spec></hudson.triggers.TimerTrigger></triggers>" if trigger_cmd else "<triggers/>"

        config_xml = f"""<project><actions/><description>{job.get('job_description' '')}</description>
          <keepDependencies>false</keepDependencies>{param_xml}
          <scm class="hudson.scm.NullSCM"/><canRoam>true</canRoam><disabled>false</disabled>
          {trigger_xml}<concurrentBuild>false</concurrentBuild>{builder_xml}<publishers/><buildWrappers/>
        </project>"""

        # 4. 发送请求
        s = requests.Session()
        s.auth = HTTPBasicAuth(user passwd)
        crumb = s.get(f"{jenkins_url}/crumbIssuer/api/json").json()
        headers = {'Content-Type': 'application/xml' crumb['crumbRequestField']: crumb['crumb']}
        
        r = s.post(f"{jenkins_url}/createItem?name={job['job_name']}" data=config_xml.encode('utf-8') headers=headers)
        print(f"{job['job_name']} 创建状态: {r.status_code}")

6.4.3. 维护心得与避坑指南

  1. 迁移时的“4个汉字”限制
    在通过 API 创建 Job 时,如果 descriptionshell 中的中文过多(尤其是没有指定 XML header 为 UTF-8 时),Jenkins 可能会解析报错。
    • 对策:尽量保持描述精简,或在生成的 config_xml 字符串最前面加上 <?xml version=Ƈ.1' encoding='UTF-8'?>
  2. 特殊符号问题
    Shell 脚本中的 & < > 是 XML 的敏感字符。在 Python 拼接时,必须像上面代码中那样使用 <![CDATA[ 脚本内容 ]]> 包裹,否则 Job 配置会损坏。
  3. 定时任务检测
    可以使用 run_jenkins_job_if_not_running 脚本(见笔记第 20 节)定期检测核心任务,确保“全任务”等监控脚本始终在运行。