准备工作
Jenkins和gogs的安装配置可以看前两篇:Jenkins安装、配置与说明 和 gogs安装与说明(docker)
此外,因为还要安装.net core的SDK和Git工具:
安装.net core(本文使用的3.1) : linux 部署.net core 环境
安装Git:
# Ubuntu sudo apt-get install git # CentOS sudo yum install -y git # 配置,暂时也可以不配置 git config --global user.name "your name" git config --global user.email "your email"
一、创建项目及Git仓库
首先,我们需要一个.net core项目及git仓库,以.net core的WebApi为例,创建一个WebApi项目(我使用的.net core 3.1),项目名称是DemoWebApi,添加一个简单的控制器(后面会修改这个接口来进行演示):
[ApiController] [Route("[controller]")] public class HomeController : ControllerBase { [HttpGet] public string Get() { return "Hello,当前时间:" + DateTime.Now; } }
在gogs上创建一个仓库,然后创建的WebApi项目推送到gogs中的这个仓库,最后仓库大致是这样子的:
二、创建本地构建的Jenkins的任务
接下来是任务构建,如果发现自己找不到对应的设置,可能是相应的插件未安装,比如,如果没有安装Git插件,下面的源码管理中可能就没有Git源码管理,建议插件安装是推荐使用Jenkins首次启动时的推荐插件安装。
然后在【源码管理】中配置git仓库所在位置:
【构建触发器】和【构建环境】可按需要自行配置,我这里暂时不配置:
在【构建】选项卡选择自己的构建方式,比如我使用的是Ubuntu,所以我选择的是【执行shell】,也就是说构建使用执行shell脚本的方式来执行:
接下来就是一个重点了:脚本如何执行?
一般的,我们不会使用命令行直接运行项目,也就是说我们不会直接使用 dotnet XXXX.dll 的形式运行项目,理由有二:
1、没有守护进程机制,项目挂了也就挂了,不会重启
2、如果项目构建运行时阻塞了Jenkins构建进程,构建过程会一直等待,直到完成或超时,当然,我们也可以直接使用命令行在后台运行项目而不阻塞构建进程,但等Jenkins构建结束后,会kill掉构建进程及其子进程,也就是说项目所在的子进程也会被kill掉,虽然我们可以配置让项目的这个子进程不被kill,但是下次构建可能就会出现端口冲突而导致构建失败。
所以,我们可以采用守护进程来实现,比如Supervisor,服务单元,我们也可以采用docker来实现,这里以服务单元和docker来举例,Supervisor的方式类似。
首先,我们添加一个目录用来存放项目文件,同时设置它的权限:
# 创建一个目录,存放项目文件,目录可自定义,比如我这里是/opt/demo sudo mkdir /opt/demo # 修改权限,把这个目录所有者给jenkins用户 sudo chown -R jenkins. /opt/demo
1、如果采用服务单元,我们需要先创建一个服务单元:
# 打开一个service文件 sudo vim /etc/systemd/system/DemoWebApi.service
输入以下内容(根据实际项目情况填写):
[Unit] Description=DemoWebApi(dotnet) [Install] WantedBy=multi-user.target [Service] User=root WorkingDirectory=/opt/demo ExecStart=/opt/dotnet-sdk-3.1.302-linux-x64/dotnet DemoWebApi.dll --urls=http://0.0.0.0:5050 ExecReload=/bin/kill -HUP $MAINPID KillMode=process Restart=always RestartSec=30s
其中主要三个地方注意:Description是描述,ExecStart是启动命令,WorkingDirectory是命令执行的工作目录,命令执行前会切换到这个目录
保存退出之后,启用服务单元
# 启用 sudo systemctl enable DemoWebApi.service
那么我们执行的shell脚本是这样子的(根据实际项目填写):
#!/bin/bash # 这里执行的shell的脚本将使用git pull下来的代码根路径作为当前工作路径来执行脚本,这个路径也就是Jenkins所指的workspace路径,这里切换到.csproj文件所在目录 cd DemoWebApi # 停止服务单元 sudo systemctl stop DemoWebApi.service # 使用的是dotnet 3.1,-c Release表示以Release的形式发布项目,发布失败直接退出 /opt/dotnet-sdk-3.1.302-linux-x64/dotnet publish -c Release || exit 1 # 删除旧发布文件 rm -rf /opt/demo/* # 移动新发布文件到项目文件存放目录 mv bin/Release/netcoreapp3.1/publish/* /opt/demo/ # 启动服务单元 sudo systemctl start DemoWebApi.service
2、如果使用docker容器化,我们需要安装docker,参考:docker简单安装 ,还需要.net core 运行的镜像,参考:docker构建.net core运行的镜像
使用docker构建容器化,我们可以直接使用docker命令行,也可以使用docker-compose。
如果使用docker命令行,那么只需要直接在构建脚本中实现:
#!/bin/bash # 这里执行的shell的脚本将使用git pull下来的代码根路径作为当前工作路径来执行脚本,这个路径也就是Jenkins所指的workspace路径,这里切换到.csproj文件所在目录 cd DemoWebApi # 停止并删除容器 sudo docker stop DemoWebApi && sudo docker rm DemoWebApi # 使用的是dotnet 3.1,-c Release表示以Release的形式发布项目,发布失败直接退出 /opt/dotnet-sdk-3.1.302-linux-x64/dotnet publish -c Release || exit 1 # 删除旧发布文件 rm -rf /opt/demo/* # 移动新发布文件到项目存放目录 mv bin/Release/netcoreapp3.1/publish/* /opt/demo/ # 创建并启动容器,注意,这里项目在容器中启动端口是5000,使用-p在宿主机和容器做了一个端口映射 sudo docker run -d --name DemoWebApi -p 5050:5000 -w /opt/demo -v /opt/demo:/opt/demo dotnetcore:v3.1 dotnet DemoWebApi.dll --urls=http://0.0.0.0:5000
如果采用docker-compose,需要创建一个docker-compose.yml文件,比如我在/opt/demo目录下创建一个docker-compose.yml:
# 创建docker-compose.yml sudo vim /opt/demo/docker-compose.yml # 修改权限,把所有者给jenkins用户 sudo chown -R jenkins. /opt/demo/docker-compose.yml
docker-compose.yml内容如下:
version: '2' services: api: image: dotnetcore:v3.1 container_name: DemoWebApi volumes: - /opt/demo:/opt/demo expose: - 5000 restart: always
working_dir: /opt/demo ports: - 5050:5000 command: dotnet DemoWebApi.dll --urls=http://0.0.0.0:5000
构建的脚本就是:
#!/bin/bash # 这里执行的shell的脚本将使用git pull下来的代码根路径作为当前工作路径来执行脚本,这个路径也就是Jenkins所指的workspace路径,这里切换到.csproj文件所在目录 cd DemoWebApi # down,--project-directory是docker-compose项目生成目录,-f是指定docker-compose.yml sudo docker-compose --project-directory /opt/demo -f /opt/demo/docker-compose.yml down # 使用的是dotnet 3.1,-c Release表示以Release的形式发布项目,发布失败直接退出 /opt/dotnet-sdk-3.1.302-linux-x64/dotnet publish -c Release || exit 1 # 删除旧发布文件 find /opt/demo/* | grep -v docker-compose.yml | xargs sudo rm -rf # 移动新发布文件到项目存放目录 mv bin/Release/netcoreapp3.1/publish/* /opt/demo/ # up,--project-directory是docker-compose项目生成目录,-f是指定docker-compose.yml sudo docker-compose --project-directory /opt/demo -f /opt/demo/docker-compose.yml up -d
按照自己的方式选择构建方式后,我们还可以选择构建完成之后的操作,比如构建完成后发送邮件等等,不过这里我这边暂不设置:
保存后回到控制面板,可以看到我们创建的任务:
任务创建好了,就可以开始构建了,点击我们创建的任务最右边的图标立即构建一次。
构建成功后,我们可以访问我们的项目,比如我这里是一个WebApi项目,而服务器地址是192.168.209.128,那么在浏览器上输入 http://192.168.209.128:5050/Home 即可访问
接下来,我们修改上面的接口代码:
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public string Get()
{
return "Hello,这是修改后的当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
}
然后使用git更新上传代码到gogs,再次点击上面的构建按钮再构建一次,成功后再访问这个接口,得到结果:
可以发现,我们的代码已经在服务器更新并发布运行了,不需要我们手动干预,这样一来,大大方便了项目的部署。
此外我们还可以查看任务状态:
任务状态大概是这样的
如果构建失败,我们可以点击构建历史记录,进入查看构建详情:
注:如果构建失败,但是我们的脚本及构建方式都没问题,可以尝试清理工作空间后重试
三、跨服务器构建部署
上面虽然完成了一个项目的构建,但是存在一个问题,那就是我们项目的构建和运行是在同一台服务器上,但是现实中往往他们是分开的,构建过程可能在本地,而程序是在远程服务器上运行。
为实现构建与部署的分离,我们可以像上面一样,在shell脚本中操作,构建完成之后,将文件打包,然后发送到远程服务器,再执行远程服务器的命令或者脚本来部署项目,事实上,我们很少这么做,因为这个写脚本的过程繁琐且不易维护,这违背了Jenkins的宗旨,可喜的是,Jenkins以插件的形式,提供了上述构建与部署分离的一个实现。
首先,我们要安装插件:Publish Over SSH
点击【系统管理】=》【插件管理】=》【可选插件】,搜索Publish Over SSH
安装完成之后,执行下面的重启一下jenkins,如果是使用服务单元启动的jenkins,则执行下面的命令重启jenkins:
# 重启 sudo systemctl restart jenkins.service
重启之后,在【系统管理】=》【系统配置】中就会有SSH的相关设置:
说明:
Passphrase:私钥的秘钥,在生成SSH Key的时候可以设置一个秘钥,就是Passphrase,如果不设置,此项可以置为空
Path to key:私钥文件的路径,可以是绝对路径,也可以是基于$JENKINS_HOME
的相对路径
Key:私钥,也就是私钥文件的内容,Key和Path to key应该至少设置一个,如果都设置,Key将优先使用
私钥生成参考:SSH公/私秘钥的生成及使用,这里我采用的是Path to Key,生成的私钥文件默认在用户主目录下的.ssh目录中,我将它copy一份到/var/lib/jenkins/目录下,所以上面我的私钥文件路径是 /var/lib/jenkins/id_rsa 。
点击【新增】添加一个服务器连接配置:
说明:
Name:连接名称,随便取 Hostname:SSH连接使用的地址,可以是Hostname,也可以是IP Username:SSH连接使用的用户,要求将上面配置的私钥对应的公钥信息追加到这个用户所在主目录下的.ssh目录下的authorized_keys文件中,为避免出现不必要的权限问题,建议使用root用户 Remote Directory:远程服务器上的一个目录地址,必须是已存在的,SSH连接只会将会将文件发送保存到这个目录下,同时保证上面的Username用户对这个目录有操作权限 高级:像端口等其它设置在高级里面
Test Configuration:测试SSH Server配置是否正确,建议配置完成后测试一遍
到这里,SSH相关配置就完成了,下面就是构建的配置了,以服务单元的形式为例。
首先,我们在远程服务器上再做一个上面介绍的服务单元。
接着,我们的项目同样采用shell脚本的形式构建:
#!/bin/bash # 这里执行的shell的脚本将使用git pull下来的代码根路径作为当前工作路径来执行脚本,这个路径也就是Jenkins所指的workspace路径,这里切换到.csproj文件所在目录 cd DemoWebApi # 使用的是dotnet 3.1,-c Release表示以Release的形式发布项目,发布失败直接退出 /opt/dotnet-sdk-3.1.302-linux-x64/dotnet publish -c Release || exit 1 # 如果上次包文件存在,则删除 if [ -f ./publish.tar.gz ];then rm -f ./publish.tar.gz fi # 将发布文件打包 tar -zcf publish.tar.gz -C bin/Release/netcoreapp3.1 publish
在安装Publish Over SSH插件之后,在【构建后操作】处就有SSH相关的操作了:
选择【构建后操作】:Send build artifacts over SSH
其中Exec command的脚本内容如下:
#!/bin/bash # 注意,命令执行的目录是用户根目录,所以建议切换目录或者使用绝对路径 cd /opt/jenkins/DemoWebApi # 文件解压 tar -zxf publish.tar.gz # 停止服务单元 systemctl stop DemoWebApi.service # 删除旧发布文件 rm -rf /opt/demo/* # 移动新发布文件到项目文件存放目录 mv /opt/jenkins/DemoWebApi/publish/* /opt/demo/ # 启动服务单元 systemctl start DemoWebApi.service # 清空当前目录文件 rm -rf ./*
说明:
SSH Server:即上面我们创建的服务器连接配置 Transfer:即往SSH Server发送文件或者执行命令的设置集合set,每个set设置如下: Source file:要往SSH Server发送的文件,多个文件之间默认使用逗号(,)分隔,注意,这是文件不是目录,如果有多个文件及目录,可以使用tar打包,发送过去后再解压,比如我这里打包publish.tar.gz Remote directory:SSH Server上存放这个文件的目录,如果不存在会自动创建,注意,这个目录是相对上面【系统管理】=》【系统配置】中配置的SSH Server的Remote directory目录的,比如我这边Source files会保存在/opt/jenkins/DemoWebApi/目录下 Remove prefix:移除前缀,一般是Source files的目录部分,比如我这里如果不设置前缀,那么我上面的Source files将会放到/opt/jenkins/DemoWebApi/DemoWebApi/目录下,因为我的Source files前面还有一节路径DemoWebApi,如果我这里设置了前缀DemoWebApi,因此会将Source files放到/opt/jenkins/DemoWebApi目录下 Exec command:发送SSH Server执行的命令,注意,命令执行的目录是用户主目录,不是Remote directory,所以建议命令使用到的路径采用绝对路径
这样基本上就设置完成了,但是,还有一点需要补充一下,在安装Publish Over SSH插件之后,在【构建后操作】处就有SSH相关的操作:
在构建环境中可以选择在构建之前或之后往SSH Server发送文件或者执行脚本的操作,不过这里构建后往SSH Server发送文件或者执行脚本的操作类似上面【构造后操作】,不过它先于上面【构造后操作】执行。
保存退出后重新构建一次,就会发现程序在远程服务器上运行了,上面整个构建流程大致如下:
1、点击立即构建开始构建 2、Jenkins从gogs获取代码更新 3、执行【构建环境】中的【Send files or execute commands over SSH before the build starts】操作(这里没使用到) 4、执行【构建】,即执行shell脚本,我这里是发布项目,并将发布文件打包成tar包 5、执行【构建环境】中的【Send files or execute commands over SSH after the build runs】操作(这里没使用到) 6、执行【构建后操作】,我使用的是【Send build artifacts over SSH】,先发送Source files,然后执行Command脚本,脚本内容就是解压,并将发布文件更新,再重新启动服务 7、构建完成
四、使用WebHook自动构建部署
上面都是我们手动去点击立即构建才开始一次构建,我们也可以在【构建触发器】中使用其它的方式去自动构建,比如使用定时构建:
不过这里要说的是使用WebHook来自动构建,也就是说当提交代码后,自动触发构建一次。这里使用的是gogs的WebHook,当然也可以是Gitee等其他平台。
首先,我们需要安装Generic Webhook Trigger插件:
安装之后,在【构建触发器】中就会有Generic Webhook Trigger选项
勾选后保存,然后在Token栏中输入认证(随便写):
另外,Generic Webhook Trigger还有很多其他的选项,像Parameter、过滤器等等,感兴趣的可以自己操作试一下,这里就不单独介绍了。
保存之后,我们打开gogs中项目的【仓库设置】=》【管理Web钩子】,添加钩子时选择【Gogs】
推送地址格式:http(s)://<你的Jenkins地址>/generic-webhook-trigger/invoke?token=<你的token>
添加完成之后保存。接着修改接口:
[ApiController] [Route("[controller]")] public class HomeController : ControllerBase { [HttpGet] public string Get() { return "Hello,这是本次修改后将自动构建,当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } }
接着使用git将代码push之后,在查看Jenkins构建记录,会发现自动生成构建了一次,如果访问接口,显示:
这下子都不用自己去点击构建按钮了!!!
五、总结
虽然这里是根据Jenkins和Gogs对.net core进行自动化部署来解释说明,但是重点还是Jenkins,能理解活用就可以了,比如上面自动构建过程,gogs的钩子推送消息到Jenkins,而Jenkins又从gogs获取代码更新,那是不是说gogs和Jenkins要部署在同一网络下,让他们可以互通呢?其实不是的,我们可以让Jenkins可以直接访问gogs,但是gogs推送消息到Jenkins仅仅只是一个http请求,我们完全可以在它们之间做一层反向代理来实现。Jenkins还有很多功能,要在使用过程中慢慢了解了。