文章目录
  1. 1. 前言
  2. 2. Jenkins
    1. 2.1. Jenkins安装
    2. 2.2. 修改Jenkins主目录
    3. 2.3. Managing Jenkins
      1. 2.3.1. System Configuration
        1. 2.3.1.1. 系统配置
        2. 2.3.1.2. 全局工具配置
        3. 2.3.1.3. 插件管理
        4. 2.3.1.4. 节点和云管理
          1. 2.3.1.4.1. Controller
          2. 2.3.1.4.2. Nodes
          3. 2.3.1.4.3. Agents
          4. 2.3.1.4.4. Executors
        5. 2.3.1.5. Clouds
        6. 2.3.1.6. Appearance
      2. 2.3.2. Security
        1. 2.3.2.1. Security
        2. 2.3.2.2. Credentials
        3. 2.3.2.3. Credential Providers
        4. 2.3.2.4. Users
      3. 2.3.3. Status Information
        1. 2.3.3.1. 系统信息
        2. 2.3.3.2. 系统日志
        3. 2.3.3.3. 负载统计
        4. 2.3.3.4. About Jenkins
    4. 2.4. Jenkins Pipeline
      1. 2.4.1. 什么是Pipeline
      2. 2.4.2. 为什么要Pipeline
      3. 2.4.3. 如何使用Pipeline
      4. 2.4.4. Pipeline工具
    5. 2.5. Unity打包CI/CD+Pipeline实战
      1. 2.5.1. 系统配置
      2. 2.5.2. 全局工具配置
      3. 2.5.3. Security
      4. 2.5.4. Credentials
        1. 2.5.4.1. 添加SSH Credential
      5. 2.5.5. 创建Agent
      6. 2.5.6. 启动Agent
      7. 2.5.7. 搭建打包流程
        1. 2.5.7.1. 创建Pipeline
        2. 2.5.7.2. Pipeline打包
          1. 2.5.7.2.1. No Jenkinsfile打包报错
          2. 2.5.7.2.2. Jenkinsfile编写
          3. 2.5.7.2.3. 打包脚本编写
          4. 2.5.7.2.4. Git SSH访问报错
          5. 2.5.7.2.5. 打包CS脚本传漏报错
          6. 2.5.7.2.6. Jenkins没输出Unity日志问题
          7. 2.5.7.2.7. Unity中文日志乱码问题
          8. 2.5.7.2.8. 打包成功
        3. 2.5.7.3. Pipeline打包归档
        4. 2.5.7.4. 多Agent多Pipeline打包
  3. 3. Reference

前言

游戏开发过程中打包是一个很频繁和复杂的事。打包除了涉及资源打包流程,还涉及到APK,IPA,小游戏打包等平台分类。不同平台接入第三方SDK等库又不一样,不同渠道又有不同的SDK要接入。每次打包如果都是打开Unity的打包窗口去设置这些详细打包参数,那对于打包维护成本来说就很高。本章节就是详细深入**CI/CD(持续集成和持续交付/部署)**的实战,目标是打造易用性,易扩展维护性,支持快速持续打包部署交付的自动化打包流程。

Jenkins

The leading open source automation server, Jenkins provides hundreds of plugins to support building, deploying and automating any project.

Jenkins是一个用Java编写的开源的持续集成工具。

从介绍可以看出Jenkins是一个免费开源的支持数百个插件用于打包,部署,自动化的自动化服务器。

以前学习了解过Jenkins的打包流程,这一次准备从0到1搭建Unity游戏开发的全自动化打包流程(包含不同打包需求,不同平台,不同渠道,不同SDK等)。

Jenkins官网文档

Jenkins安装

因为Jenkins是基于Java编写的,所以我们要先安装Java环境:

Java下载地址

关于Java环境变量设置参考网上说明,最终配置好后:

JavaHomeEnvSettting

JavaBinEnvSetting

后续会将Path里的全路径修改成访问JAVA_HOME的相对路径方便统一修改Java版本。

验证是否设置Java环境变量成功可以在PowerShell里输入”java -version”查看java版本信息:

JavaVersionInPowerShell

搭建Jenkins自动化打包第一步安装Jenkins:

Jenkins下载地址

考虑到C盘硬盘容量有限,我安装到了其他盘:

JenkinsInstallLocation

为了确保后续Jenkins权限服务正常,优先选择Logon Type为Run service as local or domain user:,选择这个后需要输入本机的用户名和密码(这里的用户名和密码我尝试了各种用户名和密码好像都不对),考虑到为了避免使用现有用户名可能因为现有用户名的一些权限修改影响Jenkins运行,最终我决定新建一个用户名作为Jenkins独立使用的用户名,Win10家庭版添加用户名流程:

设置->账户->家庭和其他用户->将其他人添加到这台电脑->输入新用户名和密码

Win10AddNewUserForJenkins

然后我们使用这个用户名和密码作为Jenkins安装的用户名,如果确认用户名密码都数对了,但还是提示Test Credential失败如下:

TestCredentialsFailed

按照官网的说法是指定的用户名没有Logon as a service的权限解决办法

如果是家庭版Win10好像没有secpol.msc这个时候我们要使用以下方案:

How To Enable Group Policy Editor (gpedit.msc) In Windows 10 Home

经过测试上述方案命令行有报错,无法正确安装gpedit.msc,也就无法给用户名添加Logon as a service权限,最后AI的建议是先通过LocalSystem模式安装,安装完成后就能有services.msc工具,同时也能从LocalSystem指定切换用户,相当于最终又回到Run service as local or domain user的方式!

  1. 以LocalSystem安装Jenkins

    ChooseRunServiceAsLocalSystem

  2. 设置默认Jenkins端口号到8080

    SetJenkinsPort

  3. 设置Java SDK位置

    ChooseJavaJDKLocaltion

    这一步Jenkins对Java版本有要求,需要下对应版本的Java JDK

    JenkinsJavaVersionRequired

    这里本人安装一个Java 21,安装完成后要将JAVA_HOME重新指向最新的Java 21目录,这样本机默认使用的才是Java 21,为了保持Java Path配置一致性顺带将Path里的路修改成%JAVA_HOME%\bin的方式:

    JavaHomeAndPathConfiguration

    打开PowerShell输入”java -version”测试是否配置成功:

    TestJava21Configuration

  4. Jenkins安装成功后我们就能在**本地服务(Ctrl+R然后输入services.msc运行)**看到Jenkins服务了,属于自动启动也就意味着开机Jenkins服务就会自动运行起来

    JenkinsServiceInfo

  5. 本地服务里选中Jenkins右键->属性->登录,输入要切换的用户名和密码进行用户名切换,从而切换到前面我们无法使用的Run service as local or domain的方式:

    ChangeJenkinsUserAccountInfo

    这一步貌似直接使用现成的用户名和密码好像无法正确启动Jenkins,AI说因为我使用的账户是MicrosoftAccount账户,需要单独创建一个用户作为Jenkins的用户

  6. 创建新用户,设置->账号->家庭和其他用户->将其他人添加到这台电脑

    CreateJenkinsBuildUser

  7. 然后我们再次去设置Jenkins服务的用户到jenkins-build这个用户并启动Jenkins服务

    ChangeJenkinsUserAndLaunch

    可以看到经历千辛万苦我们终于给Jenkins指定新用户且启动起来了。

  8. 本地测试下Jenkins是否运作正常,在浏览器输入localhost:8080进行测试

    RunJenkinsWithPassword

    默认密码存在于C:\ProgramData\Jenkins.jenkins\secrets\initialAdminPassword我们复制输入后进入Jenkins初始化设置界面

  9. 由于第一次安装也不确定安装哪些Jenkins插件,所以这里我默认安装推荐的插件模式(安装好后进入Jenkins后续我们还能根据自己需求去自行删除和安装插件)

    InstallRecomendedPlugins

    安装过程中会发现有大量插件安装失败:

    JenkinsRecomendPluginsInstallFailed

    这个我们可以后续再自行安装。

  10. Jenkins插件安装完成后,我们需要创建我们第一个Jenkins管理员账号

    CreateJenkinsAdminAccount

  11. 接下来配置Jenkins的访问URL

    JenkinsURLConfig

  12. 完成Jenkins设置可以开始使用Jenkins了

    CompleteJenkinsSetting

  13. 考虑到刚才有很多插件安装失败了,所以开始Jenkins使用的第一件事就是打开Manage Jenkins进行插件安装

    ReinstallJenkinsPlugin

    关于到底哪些插件安装失败了,可以打开Download progress查看到:

    JenkinsPluginDownloadProgress

    部分插件貌似需要重启Jenkins,但重启Jenkins的我发现又报错了:

    ReLaunchJenkinsFailed

    核心原因是我们前面设置的用户jenkins-build没有对C盘jenkins目录和安装Jenkins目录(含jenkins.exe)的读写权限:

    JenkinsFolderAuthSettingProblem

    所以我们需要对这两个目录右键->属性->安全->编辑->添加jenkins-build用户(并设置对应权限)

    SetJenkinBuildUserAuth

    上面为了确保jenkins-build用户权限足够,我把对应两个目录的所有权限都勾上了。

    然后再次重启Jenkins会发现成功启动:

    ReLaunchJenkinsSuccess

  14. 然后再次打开Jenkins Manage Plugins惊奇的发现起始之前大部分插件已经安装成功了!

在真正开始使用Jenkins之前我们先了解下Managing Jenkins和Jenkins Pipeline。

Note:

  1. Jenkins作为Service启动在Windows上,必须确保Jenkins服务账户拥有Log on as a service 权限
  2. Jenkins的安装对Windows的.NET Framwork版本也有要求,Jenkins 2.238起要求.Net Framework 4.0,之前要求.Net Framework 2.0

修改Jenkins主目录

在了解Manageing Jenkins和Jenkins Pipeline之前,我先修改Jenkins的默认工作目录(因为我的C盘硬盘不够了),默认是在**%ProgramData%\Jenkins.jenkins**路径。

迁移流程如下:

  1. 打开PowerShell(必须以管理员身份运行)输入”net stop Jenkins”将Jenkins服务停止

    StopJenkinsService

  2. 复制所有%ProgramData%\Jenkins.jenkins目录下的文件到新的Jenkins目录

  3. 修改Jenkins目录下的jenkins.xml里的的value到新路径

    1
    <env name="JENKINS_HOME" value="G:\Software\JenkinsHome"/>
  4. 打开PowerShell(必须以管理员身份运行)输入”net start Jenkins”将Jenkins服务开启

    StartJenkinsService

  5. 然后再次在页面上打开jenkins的Manage Jenkins配置可以看到Jenkins的工作目录已经切换到我们的新目录了

    ChangeJenkinsFolderSuccess

    注意给新的Jenkins目录添加jenkins-build等使用用户的操作权限(这一步请先停止jenkins服务先,如果有部分文件权限修改报错如果能正常启起来Jenkins可以先不管,AI说是有写保护文件无法添加完全控制权限)

至此我们成功切换了Jenkins的工作目录,再也不用担心Jenkins的使用让我的C盘不够用了。

Managing Jenkins

打开Jenkins,点击Manage Jenkins我们会看到Jenkins的管理界面:

ManageJenkinsPage

接下来分别理解下这个页面上的各个入口是做什么的。

System Configuration

系统配置

给Jenkins Controller配置全局设置和路径

比如配置Jenkins主目录,Jenkins URL,系统管理员邮箱等

目前我只添加了一个系统管理员邮件地址

全局工具配置

工具配置,包括它们的位置和自动化安装器

插件管理

添加,删除,禁用或启用Jenkins功能扩展插件

推荐把Jenkins推荐安装的插件都先安了,方便后续直接开工搭建自动化打包流程

节点和云管理

添加,删除,控制和监视系统运行任务的节点

这里我们需要深入理解下Jenkins的几个概念Controller,Nodes,Agents,Executors

Controller

The Jenkins controller is the Jenkins service itself and where Jenkins is installed. It is also a web server that also acts as a “brain” for deciding how, when, and where to run tasks. Management tasks such as configuration, authorization, and authentication are executed on the controller, which serves HTTP requests. Files written when a Pipeline executes are written to the filesystem on the controller, unless they are off-loaded to an artifact repository such as Nexus or Artifactory.

从介绍来看,Jenkins Controller是Jenkins本身,是作为一个总管理者和调度中心的角色存在,比如管理任务调度,任务配置,任务权限等。

Nodes

Nodes are the “machines” on which build agents run. Jenkins monitors each attached node for disk space, free temp space, free swap, clock time/sync, and response time. A node is taken offline if any of these values go outside the configured threshold. Jenkins supports two types of nodes:

从介绍来看,Jenkins Nodes是构建机器,Jenkins负责管理这些构建机器的磁盘空间,系统时间,磁盘释放等。

Jenkins Nodes分为一下两种:

  1. agents

  2. build-in node

    build-in node好像是Controller里默认的一个节点,从安全,性能,可扩展性等方面来说官方不建议直接使用build-in node

    我想这就是为什么我在Manage Jenkins页面看到以下警告的原因:

    BuildinNodeWarning

Agents

Agents manage the task execution on behalf of the Jenkins controller by using executors.

从介绍看,Agents是通过使用Executors帮Controllers管理任务执行的

Executors

An executor is a slot for the execution of tasks. Effectively, it is a thread in the agent. The number of executors on a node defines the number of concurrent tasks that can run. In other words, this determines the number of concurrent Pipeline stages that can execute at the same time.

从介绍看,Excecutor是真正执行任务的执行者,agent有多少个executor就表示能同时执行多少个任务。

官方建议如下:

通过上面的介绍,可以看出当我们搭建CI/CD工作流的时候,为了安全,性能和可扩展性最好不要直接使用Build-in node,我们应该去创建我们自己的Agent去针对性配置。

Clouds

添加,移除,配置远端提供Agent能力的实例

Appearance

配置Jenkins外观样子

Security

Security

保护Jenkins,配置谁被允许访问和使用Jenkins系统

这里有一项关于Git验证方式的配置Host Key Verification Strategy有以下几个选项:

  1. Know hosts file(default)

    使用.ssh\known_hosts文件进行验证(我们单独给Jenkins配置了jenkins-build用户,所以这个文件可能压根没有,也就是后续我们添加了SSH Credential后依然无法正常访问Git SSH地址的原因)

  2. Accept first connection

    第一次连接时接受并记录GitHub提供的Host Key,后续Host Key变化时拒绝连接。

  3. Manually provided keys

    利用手动添加到这里的Host key进行验证(后续为了避免不同WIndows用户名的SSH配置问题,这里会采用这个方案)

  4. No verification(not recommended)

    不验证Host Key

Credentials

配置证书相关

这里用于全局存放打包相关证书的地方,Agent Pipeline里通过全局变量访问,不要把证书信息直接写进Agent打包流程里!

Credential Providers

配置证书提供者和类型

Users

创建,删除,修改可以登录到Jenkins的用户信息

Status Information

系统信息

显示系统环境信息

系统日志

所有Jenkins通过java.util.loggins捕获到的日志信息

负载统计

显示Jenkins的资源使用情况

About Jenkins

查看Jenkins的版本和许可证信息

Jenkins Pipeline

在真正开始使用Jenkins之前我们先了解下Jenkins Pipeline,。

这里直接搬运下以前Jenkins Pipeline的学习记录。

什么是Pipeline

Jenkins Pipeline is a suite of plugins which supports implementing and integrating continuous delivery pipelines into Jenkins. Pipeline provides an extensible set of tools for modeling simple-to-complex delivery pipelines “as code”.(这里的Pipeline是一套插件,用于支持持续自动化集成编写。好比脚本文件定义一系列的自动化流程,但并不等价于脚本。)

那么Pipeline是用什么语言编写了?
Jenkins是基于Java的,Pipeline的编写是基于Groovy。
Groovy是Java平台上设计的面向对象编程语言。这门动态语言拥有类似Python、Ruby和Smalltalk中的一些特性,可以作为Java平台的脚本语言使用。

这里了解几个Pipeline的概念Node,Stage,Step

  1. Node

    这个就是前面讲到的Node概念,用于运行Pipeline的Node

  2. Stage

    A stage block defines a conceptually distinct subset of tasks performed through the entire Pipeline (e.g. “Build”, “Test” and “Deploy” stages), which is used by many plugins to visualize or present Jenkins Pipeline status/progress.

    Stage是将Pipeline细分成几个任务流程(比如Build,Test,Deploy)

  3. Step

    A single task. Fundamentally, a step tells Jenkins what to do at a particular point in time (or “step” in the process).

    Step是运行任务的最小单位,是告诉Jenkins在什么时间点执行什么的任务步骤,比如执行指定shell命令或者python脚本

为什么要Pipeline

为什么需要Pipeline?

  1. Code – 可管理可编辑可视化的自动化流程脚本
  2. Durable – 可持续化开发
  3. Pausable – 可以自动也可以停止等待输入
  4. Versatile – 支持复杂的持续交付需求
  5. Extensible – “The Pipeline plugin supports custom extensions to its DSL [5: Domain-Specific
    Language] and multiple options for integration with other plugins.”(支持DSL语言的自定义扩展)

如何使用Pipeline

接下来让我们看看如何创建一个Pipeline。

首先我们必须安装Pipeline插件(这一步在安装推荐插件的时候完成了,没安装的可以去Manage Plugins的插件管理界面自行安装)

关于创建Pipeline有两种方式:

  1. Through the classic UI(Jenkins提供的UI操作)
  2. In SCM(可以直接手动编写Jenkinsfile文件)

前者生成的Jenkinsfile文件在Jenkins的目录,后者的好处是可以打包流水线流程可以直接进入Git版本管理,就算电脑出问题了Jenkins的流水线也不会丢。

这里我们先了解几个Pipeline里编写的几个大的概念:

  1. Node(这个就是我们创建Agent对应的Node概念,在Jenkinsfile里表示指定流程可动态申请一个Executor执行,并在node流程块执行完后释放)
  2. environment(表示当前Pipeline流程里可全局访问的环境变量)
  3. parameters(表示Pipeline流程里可以通过页面配置显示的参数数据)
  4. Stage(定义一个流程块,包含一系列相关任务,比如Build,Test,Deploy等流程块
  5. Step(单个任务步骤,包含具体的任务执行)
  6. post (支持在特定step后指定运行状态下执行特定操作,比如将我们打包产物归档)

详细Pipeline语法关键词介绍参考

Pipeline工具

Snippet Generator
PipelineSnippetGenerator
这是一个很有用帮助快速编写Pipeline的工具,他可以帮助我们把我们熟悉的自动化脚本转换成Pipeline格式的语句,也可以快速生成一些特定功能的脚本(比如邮件功能等)。(Jenkins提供)

脚本命令行(Jenkins->Manage Jenkins->脚本命令行)

可以进行Jenkins代码编写和快速测试!

比如访问所有node并打印node名字:

1
2
3
Jenkins.get().nodes.each { node ->
println("节点名称:${node.nodeName}")
}

JenkinsConsoleCommandRun

Pipiline全局环境变量

Unity打包CI/CD+Pipeline实战

系统配置

有些配置(比如磁盘监控警告限制配置,管理员通知邮箱)不归属单个Agent而是全局生效的

ConfigGlobalSystemManageEmailAdress

全局工具配置

有些东西是属于所有Agent都要统一使用的工具(比如Git,JDK,Gradle等),这些东西应该配置在全局而非但Agent里。

为了明确给Jenkins指定Git使用还是配置上路径避免用默认的。

比如配置Git:

Manage Plugins->全局工具配置->Git installations

ConfigureGlobalGitInstallations

如果不确定本地安装Git位置,可以打开cmd输入where git查看

如果不配置Git将会使用系统默认能找到的Git

如果发现配置了全局Git,但打包日志里依然有”Selected Git installation does not exist. Using Default. The recommended git tool is:None”日志,但Git.exe工具拉去地址后续又显示正确可以先不管,具体原因不明但功能看起来正常。

配置全局Git后发现打包Git拉取报错如下:

GlobalGitConfigurationBuildFailed

这是因为我们给Jenkins创建的jenkins-build用于没有配置过Git相关的.ssh配置,所以默认访问了SSH 22端口。

那为什么用默认的Git没报错?

AI给出的回答是我们创建的Agent是在TonyTang用户名下通过bat运行的,所以能读取到TonyTang\.ssh\config的配置,所以读取了443端口。(不确定是否是这个原因)

TonyTang\.ssh\config内容如下:

1
2
3
Host github.com
Hostname ssh.github.com
Port 443

总之我们给Pipeline配置Git地址时,准确写明是使用443端口即可:

原本Git地址:

1
git@github.com:TonyTang1990/UnityNativeFramework.git

修改到SSH 443端口后Git地址:

1
ssh://git@ssh.github.com:443/TonyTang1990/UnityNativeFramework.git

主动配置SSH 443的Git地址时,全局Security的git的Host Keys也要配置成对应的包含ssh和443端口的地址:

GitHostKeyWithSSHAnd443

Note:

  1. Unity打包自带下载了JDK,NDK,Gradle,所以一般Agent要使用对应的JDK,NDK而非全局配置的。

Security

这里重点讲一下前面提到的关于Git Host Key Verification Stratefy(Git Host Key验证策略)配置,这里为了避免不同Windows用户带来的SSH访问验证问题,这里选择Manually provided keys策略:

ConfigHostKeyVerificationStrategy

这里我们将我们访问Git的**Host Key(注意不是SSH的公钥)**配置上即可。

具体Git的Host Key值是哪一个在这里查看:

GitHubs SSH key fingerprints

选择符合加密方式的那个key fingerprints即可,这里我用的是ED25519算法,所以Host key为:

1
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl

Credentials

像重要的一些比如打包Keystore,Apple证书,Git SSH用户名秘钥等都不要直接配置在Agent里,避免泄露,这些东西都统一配置在全局Credentials里,然后Agent里指向使用。

这里简单了解下Jenkins添加Credentials的方式类型

  1. Secret Text(比如API token或者GitHub个人访问token)
  2. Username and password(比如配置用户名和密码,格式”用户名:密码”)
  3. Secret file(包含Secret内容的文件)
  4. SSH Username with private key(SSH链接包含用户名和私有Key,适用于Github SSH链接时的授权)
  5. Certificate(某些证书文件,暂时不清楚具体用处)
  6. Docker Host Certificate Authentication(Docker的证书授权)

每一个在Jenkins Credential里配置的Credential都有一个Credential ID,我们通过Credential ID可以在Jenkinsfile里访问

添加SSH Credential

这里我首先添加我的Git证书,考虑到用SSH访问,这里添加SSH username with private key的方式:

Jenkins->Manage Jenkins->Credentials->Add Credentials

AddSSHUsernameWithPrivateKey

给SSH username with private key配置生效范围和访问ID:

这里考虑到为了方便范围就配成Global,ID根据自己需求取一个唯一值后续会在Jenkinsfile里使用访问Credential,关于Github如何生成SSH Key这个自行搜索了解下。

ConfigSSHUsernameWithPrivateKey

SSH Key默认是在C:\\User\\用户名\.ssh\目录下的,要求填的密码是创建私钥时的一个密码,如果忘记密码可以在改目录下输入以下命令”ssh-keygen -y -f 私钥文件”进行尝试。

添加Github SSH的时候注意Username要填写git(AI说的具体不确定是不是必须)

添加完成后就能看到Jenkins->Manage Jenkins->Credentials下方出现我们配置的Credential了:

AfterSSHConfigComplete

在Jenkinsfile里访问Credential:

1
credentials('***')

上面的配置是配置了SSH Credential的相关信息,但并不保证我们新建Windows的jenkins-build用户就能正确使用,因为我之前是在TonyTang这个用户下工作,所以所有的git SSH key相关都是操作保存在C:\\User\\TonyTang\\.ssh目录下的,现在Jenkins以jenkins-build用户在工作,默认全局的Security的Host Key Verification Stratety(Git的验证策略配置)是Known hosts file,也就是使用C:\\User\\USER_NAME\.ssh目录下的known_hosts文件进行验证。所以我们前面Credentials需要将Security的Host Key Verification Stratety修改成SSH Username with private key。

配置Git Host Key Verification Configuration的Manually provideed keys时注意要把http和ssh访问git的Host Keys都添加上。

GitHttpAndSSHHostKeyConfiguration

创建Agent

结合前面提到的官方不建议使用默认的build-in Node,所以我们首先要先创建我们自己的Agent,打开Jenkins主页->Set up an agent:

CreateNewAgentEntrance

接下来分别讲讲创建Agent里的各个参数的含义。

  1. 名字(Agent的唯一Id)

    比如我的项目名称是UnityNativeFramework,所以我创建的Agent名称叫UnityNativeFrameworkBuild

    CreateUnityNativeFrameworkBuildAgent

  2. 描述(Agent的一个文本描述)

  3. Number of executors(Agent拥有多少的Executors,官方建议1个Agent对应1个Executor最佳)

  4. 远程工作目录(Agent所在工作目录,不要放到JENKINS_HOME下)

  5. 标签(用于Pipeline指定Agent Lable时的识别标签,符合相同标签的可以被公用,比如多个工程同一个项目建了多个Agent,指定相同的标签能自动利用空闲Agent触发打包)

  6. 用法(限定节点的使用方式)

  7. 启动方式(限定Agent的启动方式)

  8. 可用性(限定Agent的可用状态)

关于节点属性选项:

  1. Disable deferred wipeout on this node

    来至Workspace Cleanup插件,用于删除整个Workspace时采用延迟擦除的方式优化速度(不推荐勾选,避免一些奇怪的问题)

  2. Disk Space Monitoring Thresholds

    用于设置该Agent的磁盘空间阈值,避免特定硬盘空间情况可以发出警告或直接终止打包(推荐勾选,设置合理阈值)

  3. Enviroment variables

    配置只运行在当前Agent的一些环境变量数据,比如Unity.exe路径,后续Pipeline里编写时可以通过%*%访问环境变量值,这里不要放证书相关数据,证书相关放到配置Security的Credentials里(推荐勾选,按需设置必要的环境变量)

  4. Tool Locations

    用于覆盖 Jenkins全局工具在这个Node上的实际安装位置。(不推荐勾选,,当前Agent和全局Controller配置工具没有特殊情况都使用统一的,比如Git使用统一配置)

最终设置结果:

FinalAgentSetting1

FinalAgentSetting2.PNG

Note:

  1. Agent的远程工作目录不要放到JENKINS_HOME
  2. 一个Agent推荐1个Executor,这是官方建议最稳妥的方式,避免一些I/O,CPU或内存的负载问题
  3. 新建一个统一放Jenkins Agent的目录,记着把该目录的修改权限添加给jenkins-build用户
  4. 标签里的名字默认不能与Agent名同名,因为默认Jenkinsfile里就能用Agent名字指定特定Agent

启动Agent

创建完成后会发现我们新建的Unity NativeFrameworkBuild还处于离线状态:

AfterCreateAgentButDisplayOffline

这是因为我们只是保存了Agent配置,Agent进程还没启动。

点击UnityNativeFrameworkBuild的Agent名字,点击状态,我们会看到启动Agent的办法:

RunAgentWithCommandLine

多行命令复制粘贴到PowerShell会有问题,需要自己一行一行的复制执行:

LaunchAgentFromPowerShell

可以看到我们成功启动了我们新建的UnityNativeFrameworkBuild的Agent:

LaunchAgentSuccess

我们新建的Agent的启动方式之前设置的是Launch agent by connecting it to the controller,为了避免每次重启电脑我们自己创建的Agent都要重新执行命令启动,我们需要利用WinSW将长期Unity构建的Jenkins Agent注册为Windows服务,考虑到注册服务也挺麻烦的,这里暂时将启动命令做成start-UnityNavitveFrameworkBuild-agent.bat运行:

1
2
3
4
@echo off
curl.exe -sO http://localhost:8080/jnlpJars/agent.jar
java -jar agent.jar -url http://localhost:8080/ -secret 00577401b16f73577726ed1cc37efdd02c39a31f1a9f5dec1c94dfcb426351a2 -name UnityNavitveFrameworkBuild -webSocket -workDir "G:\Software\JenkinsAgent\UnityNavitveFrameworkBuild"
pause

Note:

  1. 如果不想每个Agent都独立注册Windows服务,可以选择Luanch agent via SSH的方式,配置好SSH Server和SSH Key让所有Agent一次性自动全启动
  2. bat启动这个方式目前的bat代码是窗口关闭Agent就断开连接离线

搭建打包流程

搭建打包流程前,我们需要深入理解一个概念Pipeline,详情参考前面的介绍。

Jenkins搭建打包流程只要通过定义Pipeline,Pipeline有两种方式定义:

  1. 通过网页可视化界面

    这种方式生成的Jenkinsfile默认在JENKINS_HOME文件夹下,不方便可视化+版本管理

  2. 手写Jenkinsfile文件

    这种方式好处是可以提交版本管理,同一个项目多个Agent可以快速重用同一个Agent,不同考虑Jenkinsfile流程同步问题

创建Pipeline

In SCM Jenkinsfile官方文档

创建Jenkinsfile流程如何:

  1. 确保Pipeline插件安装好
    InstallPipelinePlugins

  2. New Item – 新建Pipeline(选择Pipeline类型)
    CreateNewItemEntrance

    SelectPipelineItemType

  3. Configure Pipeline

    这里简单讲几个重要的基础配置含义:

    1. Discard old builds(丢弃旧的构建,用于自动丢弃时长过久的构建,确保硬盘空间够)

      为了避免构建的包被意外清除,不推荐勾选

    2. Do not allow concurrent builds(多个构建发起时的响应策略,用于确保不会同时并发执行多个构建或者后一个覆盖前一个构建等设置)

      一般来说单个Unity工程只允许启动一个Batch mode打包,推荐勾选

    3. Do not allow the pipeline to resume if the controller restarts(是否不允许Pipeline在controller重启后继续)

      一般来说打包流水线必须要完整执行才不容易出问题,推荐勾选

    4. GitHub project(用于关联一个Github项目URL,为部分插件提供上下文)

      一般来说项目打包都是走自己的Git操作流程,不推荐勾选

    5. This project is parameterized(这个项目是否允许参数配置化打包)

      参数配置化打包也就是Jenkins里常见的页面可视化参数配置,然后打包时传入Unity进行对应打包参数控制,推荐勾上

    这里简单讲几个重要触发时机配置介绍:

    1. Build after other projects are build(是否在其他工程打包完成后自动触发打包)

      适用于多个打包流程顺序合作构建的情况,目前Untiy打包暂时不需要,不推荐勾选

    2. Build periodically(固定间隔时间或者固定时间自动构建)

      主要用于每隔一段时间自动触发构建进行连续打包测试跟进

      Unity开发过程中提交情况比较复杂,每隔一段时间打包很可能出现提交不完全导致打包失败等情况,建议还是自己主动找合适时机触发打包,不推荐勾选

    关于Pipeline的详细配置介绍:

    1. 定义选择Pipeline script form SCM而非Pipeline script(因为我们打算走本地jenkinsfile的方式而非网页可视化配置界面)
    2. SCM类型选择Git,目前我使用的是Git,Jenkins显示支持的选项也只有Git,其他方式估计要安装对应插件
    3. Repository URL(jenkinsfile文件所在git地址,一般来说jenkinsfile文件都是放到Git项目根目录)
    4. Credentials(Git地址访问权限证书,这个也就是我们需要配置Git证书之后才能选择的了,证书添加参考前面的Credentials)

    最终Pipeline创建配置如下:

    PipelineConfiguration1

    PipelineConfiguration2

    PipelineConfiguration3

    PipelineConfiguration4

Pipeline打包
No Jenkinsfile打包报错

我们直接点击Build Now会发现没有打包参数界面直接就开始打包并报错:

NoJenkinsfileBuildError

这是因为我们选择了SCM Jenkinsfile的方式,但我们还没有自己在仓库根目录创建Jenkinsfile文件上传导致的,所以我们需要在Git仓库下方自己创建Jenkinsfile并编写内容上传。

Jenkinsfile编写

Jenkinsfile初版内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
pipeline {
agent {
// 指定用UnityNavitveFrameworkBuild的Agent的标签打包
// 方便后续创建多个符合的Agent做Agent池
label 'windows && unity && android'
}

parameters {
// bool值的是否生成开发版本参数
booleanParam(
name: 'DEVELOPMENT_BUILD',
defaultValue: true,
description: '是否生成Development Build'
)

// 版本号
string(
name: 'VERSION_CODE',
defaultValue: '1',
description: '版本号(格式:*.**)'
)

// 资源版本号
string(
name: 'RESOURCE_CODE',
defaultValue: '1',
description: '资源版本号(格式:*)'
)
}

options {
// 让Pipeline打包输出Log带时间戳信息
timestamps()

// 禁止同时并发多个Build
disableConcurrentBuilds()

// 配置Pipeline打包超时时长
timeout(
time: 120,
unit: 'MINUTES'
)
}

environment {
// 环境变量配置
UNITY_PROJECT_DIR = 'UnityNativeFramework'
}
stages {
// 验证Pipeline相关环境变量数据流程
stage('Verify Pipeline') {
steps {
// 打印打包相关变量信息
bat '''
echo NODE_NAME=%NODE_NAME%
echo NODE_LABELS=%NODE_LABELS%
echo WORKSPACE=%WORKSPACE%
echo UNITY_PROJECT_DIR=%UNITY_PROJECT_DIR%
echo DEVELOPMENT_BUILD=%DEVELOPMENT_BUILD%
echo VERSION_CODE=%VERSION_CODE%
echo RESOURCE_CODE=%RESOURCE_CODE%
'''
}
}

// 打包流程
stage('Build') {
steps {
dir("${env.UNITY_PROJECT_DIR}") {
bat '''
@echo off
rem 将Windows CMD代码页切换为UTF-8
chcp 65001 >nul

rem 强制Python标准输入输出使用UTF-8
set PYTHONIOENCODING=utf-8

echo '进入项目工程目录!'
echo Current directory: %CD%

python -u "BuildScripts\\BatchBuildAndroid.py" ^
-isDevelopment %DEVELOPMENT_BUILD% ^
-versionCode %VERSION_CODE% ^
-resourceVersionCode %RESOURCE_CODE%
echo '打包结束!'
'''
}
}
}
}
}

Jenkinsfile负责编写打包流程,将所有打包流程串联起来。

Jenkins将可视化配置的打包参数一个一个传递给BatchBuildAndroid.py打包脚本打包。

Note:

  1. 详细的Pipeline参数语法参考
  2. Pipiline全局环境变量查看(需要启动Jenkins服务)
打包脚本编写

BatchBuildAndroid.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3
"""将 Jenkins 参数逐个转交给 Unity Batch Mode。"""

import argparse
import subprocess
import sys
from pathlib import Path


# 构建机固定配置:更换 Unity 版本或静态打包入口时只修改这里。
UNITY_EDITOR = Path(
r"G:\Software\UnityAllVersions\2022.3.62f3\2022.3.62f3\Editor\Unity.exe"
)
UNITY_EXECUTE_METHOD = "BuildTool.DoBuildFromCommands"

def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("-isDevelopment", required=True)
parser.add_argument("-versionCode", required=True)
parser.add_argument("-resourceVersionCode", required=True)
return parser.parse_args()

def run() -> int:
args = parse_args()
project_path = Path.cwd().resolve()
unity_editor = UNITY_EDITOR.resolve()
print(f'project_path:{project_path}')
print(f'unity_editor:{unity_editor}')
if not unity_editor.is_file():
print(f"Unity Editor executable was not found: {unity_editor}", file=sys.stderr)
return 1

required_directories = ("Assets", "Packages", "ProjectSettings")
missing = [name for name in required_directories if not (project_path / name).is_dir()]
if missing:
print(
f"Not a valid Unity project: {project_path}. "
f"Missing directories: {', '.join(missing)}",
file=sys.stderr,
)
return 1

command = [
str(unity_editor),
"-batchmode",
"-nographics",
"-quit",
"-logFile",
"-",
"-projectPath",
str(project_path),
"-executeMethod",
UNITY_EXECUTE_METHOD,
"-buildTarget",
"Android",
"-isDevelopment",
args.isDevelopment,
"-versionCode",
args.versionCode,
"-resourceVersionCode",
args.resourceVersionCode,
]

print(f"Unity Command: {subprocess.list2cmdline(command)}", flush=True)

# 将Unity打包日志获取输出到控制台(以便Jenkins可以获取到Unity Batch Mode打包的日志)
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding="utf-8",
errors="replace",
bufsize=1,
)

if process.stdout is not None:
for line in process.stdout:
print(line, end="", flush=True)

return_code = process.wait()

if return_code != 0:
print(
f"Unity build failed with exit code {return_code}.",
file=sys.stderr,
flush=True,
)

return return_code


if __name__ == "__main__":
raise SystemExit(run())

BatchBuildAndroid.py脚本负责接收Jenkins打包传过来的参数并解析,然后利用Unity Batch Build模式将Unity启动起来触发打包并将打包参数传递进打包代码。

Git SSH访问报错

上传Jenkinsfile后再次打包会发现依然打包报错:

BuildFailedAfterConfigureSSHWithPrivateKey

这是因为我们配置Git Host Key的Host Keys只添加了支持http访问的Host Key而没有添加SSH访问的Host Key,按照前面添加SSH Credential的方式添加上后,我们再次打开新建的Agent会看到Jenkins的参数配置界面显示出来了:

JenkinsBuildWithParametersPage

打包CS脚本传漏报错

修改SSH访问的Host Key后我们再次触发打包:

BuildFailedAfterFixGitProblem

发现依然打包失败,但我们打包流程里无论是Jenkins的打印输出还是BatchBuildAndroid.py里的打印输出都成功打印了,从日志可以看到我们触发了Unity Batch Mode打包,但过了一会就打包失败了,这里是因为我忘记上传打包相关脚本了,Git仓库上根本没有BuildTool.DoBuiuldFromCommands的代码文件。

Jenkins没输出Unity日志问题

原因如下:

  1. Unity Batch Mode运行模式需要添加**-logFile -**参数,表示将将日志输出到控制台。

  2. 通过Jenkins->cmd.exe->Python->Untiy这个打包流程后,Unity没能正确得到能写入的stdout

方案:

  1. 我们需要再Python里显示去获取Unity日志并转发打印到Jenkins控制台

通过在打包脚本BatchBuildAndroid.py里添加以下代码,将

1
2
3
4
5
6
7
8
9
10
11
12
13
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding="utf-8",
errors="replace",
bufsize=1,
)

if process.stdout is not None:
for line in process.stdout:
print(line, end="", flush=True)
Unity中文日志乱码问题

原因如下:

  1. Windows控制台,Python,Jenkins使用的字符编码不一致造成的。

方案:

  1. 在打包流程脚本里统一字符编码格式

Jenkinsfile(统一Jenkins bat的字符编码到UTF-8,并将Python输入输出指定为UTF-8)

1
2
3
4
5
6
7
8
9
10
bat '''
@echo off
rem 将Windows CMD代码页切换为UTF-8
chcp 65001 >nul

rem 强制Python标准输入输出使用UTF-8(确保Python向Jenkins输出使用UTF-8)
set PYTHONIOENCODING=utf-8

******
'''

BatchBuildAndroid.py(确保Python保持UTF-8解码)

1
2
3
4
5
6
7
8
9
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding="utf-8",
errors="replace",
bufsize=1,
)
打包成功

上传打包文件后再次触发Jenkins打包:

RunBatchModeBuildSuccessLog

可以看到我们终于正确触发Unity Batch Mode打包并打包成功:

JenkinsPipelineBuildSuccess

最后在JenkinsAgent对应Agent和Pipeline目录下找到了打包输出的APK文件:

JenkinsBuildAPKLocation

安装运行成功:

JenkinsBuildInstallSuccess

上述打包流程相关配置和代码暂时只处理了三个打包参数配置(IsDevelopment,VersionCode,ResourceVersionCode)后续更多参数配置扩展Jenkinsfile和打包相关脚本即可。

Pipeline打包归档

Pipeline打包归档是指我们打包生成的APK等产物,默认生成在JenkinsAgent对应Agent对应Pipeline项目工程下的特定位置,这种位置会受项目Git拉取,Jenkins清理工作区等操作影响。所以最好的方式就是Jenkins打包的最终产物统一放到一个独立长期保存的位置,这个就是Pipeline打包归档。

设置好打包归档后我们能在Jenkins对应Pipeline页面点击Build Artifacts里找到所有打包产物。

归档后的存储位置在%JENKINS_HOME%\jobs\任务名\builds\构件编号\archive路径下。

SuccessArchivingArtifacts

可以看到打包成功后Jenkins成功将打包产物符合的文件全部归档到了%JENKINS_HOME%目录对应目录下。

Note:

  1. 归档成功后没在Jenkins页面看到相关入口,原因不明
多Agent多Pipeline打包

之前我们只创建了一个UnityNativeFrameworkBuild Agent(只有1个Executor),但我们却创建了2个指定Agent Lable是UnityNativeFrameworkBuild的Pipeline,如果我们同时触发这2个Pipeline打包会发生什么?

答案是排队等待。

PipelineWaitForExector

还记着我们之前给Pipeline设置的禁止并发打包吗?

创建的Agent只有1个Executor且禁止并发打包,这就是为什么2个Pipeline触发打包会排队等待的原因。

解决方案:

创建多个可用的Agent并启起来,2个Pipeline指向的Agent Lable多个Agent都符合,那么就会选空闲的Agent的executor进行打包。

为了快速创建相似的Agent,我们可以通过Jenkins->Manage Jenkins->Nodes->New Node->复制现有节点:

CreateAgentFromAgent

BuildAgent2Create

取名UnityNativeFrameworkBuild2

编写启动UnityNativeFrameworkBuild2的Agent的start-UnityNavitveFrameworkBuild2-agent.bat:

1
2
3
4
5
@echo off
curl.exe -sO http://localhost:8080/jnlpJars/agent.jar
java -jar agent.jar -url http://localhost:8080/ -secret ****** -name UnityNavitveFrameworkBuild2 -webSocket -workDir "G:\Software\JenkinsAgent\UnityNavitveFrameworkBuild"
pause

启动后新Agent就可用了。

这是我们再次同时触发2个Pipeline打包会发现,2个Agent都同时运作起来,2个Pipeline也同时开始了打包流程:

MultiplePipelineRunConcurrently

Note:

  1. 复制Agent时远程目录记着要配套修改,不然就指向同一个远程目录了。

Reference

文章目录
  1. 1. 前言
  2. 2. Jenkins
    1. 2.1. Jenkins安装
    2. 2.2. 修改Jenkins主目录
    3. 2.3. Managing Jenkins
      1. 2.3.1. System Configuration
        1. 2.3.1.1. 系统配置
        2. 2.3.1.2. 全局工具配置
        3. 2.3.1.3. 插件管理
        4. 2.3.1.4. 节点和云管理
          1. 2.3.1.4.1. Controller
          2. 2.3.1.4.2. Nodes
          3. 2.3.1.4.3. Agents
          4. 2.3.1.4.4. Executors
        5. 2.3.1.5. Clouds
        6. 2.3.1.6. Appearance
      2. 2.3.2. Security
        1. 2.3.2.1. Security
        2. 2.3.2.2. Credentials
        3. 2.3.2.3. Credential Providers
        4. 2.3.2.4. Users
      3. 2.3.3. Status Information
        1. 2.3.3.1. 系统信息
        2. 2.3.3.2. 系统日志
        3. 2.3.3.3. 负载统计
        4. 2.3.3.4. About Jenkins
    4. 2.4. Jenkins Pipeline
      1. 2.4.1. 什么是Pipeline
      2. 2.4.2. 为什么要Pipeline
      3. 2.4.3. 如何使用Pipeline
      4. 2.4.4. Pipeline工具
    5. 2.5. Unity打包CI/CD+Pipeline实战
      1. 2.5.1. 系统配置
      2. 2.5.2. 全局工具配置
      3. 2.5.3. Security
      4. 2.5.4. Credentials
        1. 2.5.4.1. 添加SSH Credential
      5. 2.5.5. 创建Agent
      6. 2.5.6. 启动Agent
      7. 2.5.7. 搭建打包流程
        1. 2.5.7.1. 创建Pipeline
        2. 2.5.7.2. Pipeline打包
          1. 2.5.7.2.1. No Jenkinsfile打包报错
          2. 2.5.7.2.2. Jenkinsfile编写
          3. 2.5.7.2.3. 打包脚本编写
          4. 2.5.7.2.4. Git SSH访问报错
          5. 2.5.7.2.5. 打包CS脚本传漏报错
          6. 2.5.7.2.6. Jenkins没输出Unity日志问题
          7. 2.5.7.2.7. Unity中文日志乱码问题
          8. 2.5.7.2.8. 打包成功
        3. 2.5.7.3. Pipeline打包归档
        4. 2.5.7.4. 多Agent多Pipeline打包
  3. 3. Reference