背景
在现代化的软件开发流程中,持续集成和持续部署(CI/CD)已经成为不可或缺的一部分。
Jenkins作为一款开源的自动化运维工具,能够帮助我们实现这一目标。
本文将详细介绍如何在Windows服务器下使用Jenkins来自动化发布C#应用程序。
下载与安装
访问官方网站 https://www.jenkins.io/zh/download/。

点击上图红框中的windows,则会下载windows安装包jenkins.msi,拷贝到服务器,双击进行安装。

安装环节会提示选择JDK,要求版本17或21,下载一个17版本的JDK,放到磁盘目录,填写对应路径即可。

初始化
使用谷歌浏览器,访问http://localhost:38080/,打开初始化界面,如下图:

按照提示找到管理员密码,填充后点击继续按钮,弹出如下界面:

因服务器无法访问公网,所以显示离线,点击跳过插件安装,进入创建管理员账号界面,如下图:

点击"保存并完成"按钮,弹出提示,如下图:

再点击"保存并完成"按钮,提示安装完成,Jenkins就绪,如下图:

此时已完成Jenkins的初始化工作,并创建了管理员账号。
登录
使用ip地址+默认的38080端口访问,进入登录页面,如下图所示:

使用初始化过程中创建的账号及密码,登录,进入系统首页。

插件安装
Jenkins采用了内核+插件的模式来保持系统功能的扩展性,大量功能是通过插件来完成的。
服务器如能直接访问公网环境,直接使用在线插件管理即可,可以方便地下载、安装、更新、卸载等操作。

如服务器无法直接访问公网,则插件需要采用离线安装的方式,以常用的使用SSH协议传输文件到远程服务器的插件Publish Over SSH为例,步骤如下:

- 点击上图中的版本号,下载对应该版本的文件 publish-over-ssh.hpi。
- 通过如下菜单和功能,上传插件后安装。

点击Deploy按钮后,提示该插件安装成功,但是有报错,查看installed plugins,是因为该插件有其他四个依赖插件需要安装,如下图:

按同样方法安装上述依赖的插件,发现还有进一步的依赖,如下图:

继续上述过程,前后一共下载11个插件,且有2个插件需要版本升级,如下图:

将所有依赖的插件都安装后,重启jenkins,进入已安装插件列表,显示所有插件无报错,正常加载状态。

以上方式过于繁琐,安装1个插件耗时长,工作量大,推荐更简便的方式,在能连接公网的电脑上安装同版本的Jenkins,将所需插件都安装完成后,将C:\ProgramData\Jenkins.jenkins\plugins目录下的所有文件,拷贝到内网服务器对应目录下覆盖,然后重启Jenkins生效。
SSH安装
要实现远程文件拷贝,仅仅在Jenkins服务器上安装插件是不够的,还需要在程序运行的远程服务器上安装一个SSH服务端,这里推荐OpenSSH。
OpenSSH 是一款用于安全远程连接和计算机管理的工具套件,它通过加密技术来保障通信安全,其主要功能包括远程登录、命令执行、文件传输和端口转发等。
OpenSSH 包含客户端和服务器两部分,其中:
- 客户端:用于连接远程服务器。
- 服务器:在远程服务器上运行,接受客户端的连接请求。
OpenSSH 最初是为 Linux 系统开发的,现在也支持包括 Windows 和 macOS 在内的多种操作系统。
windows Server 2016需要单独下载OpenSSH-Win64-v9.8.1.0.msi并安装,windows Server2019及以上服务器直接通过添加服务器功能的方式来实现,参考地址:https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_firstuse?utm_source=ld246.com&eqid=ccf6b3510033b2e9000000026577e875&tabs=gui&pivots=windows-server-2019https://github.com/PowerShell/Win32-OpenSSH/releases
远程服务器配置
我们的目的是通过Jenkins实现程序的发布,因此准备工作之一就是维护一下程序运行的远程服务器地址、身份认证等信息。
访问Manage Jenkins>System,拉到最下方的SSH Servers区域,如下图所示:
点击上图中的Add按钮,添加远程服务器信息,如下:

输入名称、ip地址、服务器操作系统用户名和密码,点击右下角的测试按钮,可验证连通性,最后点击保存按钮。
重复上面步骤,添加另一台服务器219。
实战案例------备份
程序发布具有一定风险性,发布过程中出错或者发布后验证环节发现问题,且短时间内无法解决时,需要回滚到先前版本,因此通常在发布之前需要进行备份。
传统手工操作模式是将程序目录拷贝到备份目录,我们使用Jenkins来实现一键备份功能。
任务配置
在Jenkins首页,点击左侧导航中的New Item菜单,如下图所示:

输入名称,创建一个自由风格的任务,如下:

进入配置页面,在构建步骤中选择**Send files or execute commands over SSH**,然后指定批处理脚本的位置,如下图所示:

最后点击保存按钮。
批处理脚本编制
在远程服务器219上,编制批处理脚本如下:
shell
@echo off
:: 设置源文件夹和目标备份文件夹路径
set "sourceFolder=D:\LimsWebApi"
set "backupFolderBase=D:\dev\backup\LimsWebApi"
:: 获取当前日期和时间
for /f "tokens=2 delims==" %%a in ('wmic os get localdatetime /value') do set datetime=%%a
set "year=%datetime:~0,4%"
set "month=%datetime:~4,2%"
set "day=%datetime:~6,2%"
set "hour=%datetime:~8,2%"
set "minute=%datetime:~10,2%"
set "second=%datetime:~12,2%"
:: 创建目标备份文件夹名称
set "backupFolder=%backupFolderBase%\Backup_%year%%month%%day%_%hour%%minute%%second%"
:: 创建目标备份文件夹
if not exist "%backupFolder%" mkdir "%backupFolder%"
:: 复制文件到目标备份文件夹
xcopy "%sourceFolder%\*" "%backupFolder%" /E /I /Y
其主要逻辑是设置要备份的目录,然后根据脚本执行时间生成备份文件夹名称,放到预定义的位置,拷贝所有文件夹和文件。
任务执行
在任务详情页面,点击构建,如下图:

点击上图左下角的构建记录,通过Console Output可以看到执行过程及结果,如下图:

多台备份
上面过程展示了单台服务器应用程序的备份,可以在任务配置中设置多台备份,特别是在集群部署的应用场景下。
我们修改上面的任务配置,增加220服务器的备份,如下:

再次执行任务,控制台输出如下:

实战案例------发布
传统手工操作模式需要通过远程桌面登录到各台服务器,然后分别停止IIS,拷贝覆盖发布内容,启动IIS,我们使用Jenkins来实现一键发布功能。
任务配置
在Jenkins首页,点击左侧导航中的New Item菜单,创建一个自由风格的任务,Jenkins支持复制新增功能,我们可以拷贝上文中的备份任务,在其基础上修改,如下:

修改配置,程序发布涉及到了文件传输了,如下图所示:

这里有个默认设定,源文件路径位于C:\ProgramData\Jenkins.jenkins\workspace下。

我们先执行一次,让其自动生成任务项目文件夹。
然后source files的默认起始路径,实际是C:\ProgramData\Jenkins.jenkins\workspace\lims-webapi-publish-qas。
Remote dictionary,即拷贝到远程服务器的默认起始路径是C:\Users\administrator\
我们把需要发布的程序文件放到该目录下,在Source files中使用"**"通配符代表拷贝所有的文件,包括子文件夹和子文件,在Remote dictionary输入LimsWebApi,如下图:

我们新建一个文本文档测试,执行任务,发现已经成功实现了远程文件传输,如下图:


批处理脚本编制
在远程服务器219上,编制批处理脚本如下:
shell
@echo off
C:\Windows\System32\inetsrv\appcmd.exe stop site "LimsWebApi"
xcopy "C:\Users\administrator\LimsWebApi\*" "D:\LimsWebApi\" /E /I /Y
C:\Windows\System32\inetsrv\appcmd.exe start site "LimsWebApi"
如果该服务器上只发布了一个web站点,则可以使用net stop iisadmin和net start iisadmin 进行停止和启动整个iis服务。
不过,当该服务器上基于IIS,创建了多个web站点时,为了避免影响其他站点,使用上述脚本来实现针对特定站点的启停服务。
任务执行
在任务详情页面,点击构建,使用Console Output可以看到执行过程及结果,如下图:

实战优化------通过视图功能分离测试与生产任务
虽然我们可以通过后缀名的方式,来区分测试还是生产,但是放在同一个列表中,容易误操作,如下图所示:

我们可以使用Jenkins自带的视图功能,将测试与生产环境分离,如下图:

创建测试视图QAS,如下图:

勾选需要放到该视图中的任务,如下图:

保存后,效果如下:

同理,我们创建一个生产视图PRD,将生产相关的两个任务放进去,如下图:

通过该方式,可以方便地分离测试与生产环境的任务,避免误操作。
实战优化------使用FTP解决文件传输问题
Jenkins放在远程服务器,发布程序的时候,读取的是该服务器上的文件,也就是还需要一个步骤,把要更新的文件,从开发电脑上拷贝到Jenkins服务器上,如果这一步采用远程桌面的方式,仍然有点繁琐。
一种解决思路是采用共享文件夹的模式,将Jenkins服务器的C:\ProgramData\Jenkins.jenkins\workspace目录设置为共享。

但是由于之前局域网共享会被病毒利用,域策略默认强行关闭了文件夹共享的445端口,该方式走不通。
另一种解决思路就是通过ftp协议了,直接用IIS配置一个ftp站点出来,指向上面的workspace地址,如下图所示:

然后在开发笔记本上,通过ftp协议,直接粘贴要更新的文件即可。

这样就省去了远程桌面到Jenkins服务器的麻烦,直接复制粘贴文件,然后浏览器访问Jenkins的界面,执行任务处理即可。
常见问题
403报错
进行保存操作时,报错如下:HTTP ERROR 403 No valid crumb was included in the request

这是因为安全设置问题,按照下图,进入安全设置,将默认未勾选的Enable proxy compatibility勾选上,然后点击保存按钮,如下图:

最后,还需要重新加载一下配置使其生效。
