【TeamCity】使用TeamCity搭建ASP.NET Core + SVN 的 CICD环境

引言:

最近公司让我使用TeamCity搭建公司的CICD环境,但是国内对于使用TeamCity搭建CICD环境的教程或者博客很少,在搭建的过程中遇到的坑颇多,查找解决方案时很是费力。最终在多番折腾下终于搭建完成,写此博客记录 一番,也希望能为后续使用TeamCity的朋友提供些许帮助。

环境描述:ASP.NET Core + SVN + Windowes Server + IIS 

使用TeamCity搭建CICD环境,实现ASP.NET Core 的自动构建、自动发布到测试环境

这里TeamCity和TeamCityBuildAgent部署在同一台服务器(A)上,站点部署在另外一台服务器(B)上,两台均为Windows Server 2016服务器。

目标:

  1. CICD策略是开发分支有提交立刻进行集成,构建、测试。
  2. 每天凌晨进行代码的构建、发布,保证每天都将最近代码发布到测试环境
  3. (后期待做)加入静态代码检测和单元测试覆盖率检测
  4. (后期待做)固定版本迭代周期,定期发布代码至生产环境


本文只讲1.2前两个目标的配置,3、4两点待后期完成后再开一篇。

实现重点:

  1.为保证发布能顺利完成,需要在发布前停止站点,发布完成后重新启动站点,否则会出现文件被IIS进程占用的情况导致发布失败

  2.对发布前的站点进行备份,如果发布期间出现问题导致发布失败,要对代码进行回滚操作

正文开始

 一、安装TeamCity和TeamCityAgent

  这一步直接在官网选择对应的版本进行安装即可,也可以选择docker安装,官网提供了相应的脚本,这里是我的脚本,增加持久化映射:

docker run -d --name teamcity-server-instance -v E:/DockerVolume/teamcity/datadir:/data/teamcity_server/datadir -v E:/DockerVolume/teamcity/logs:/opt/teamcity/logs -p 8111:8111 jetbrains/teamcity-server

  安装完成之后,可通过访问ip:8111,我这里是localhost:8111,进入后等待初始化完成,进行相关配置,我这里都是默认配置,此处省略。

二、项目初始化配置

  1.通过点击站点右上角的Administration进入到管理菜单,点击Create project进行项目的创建

   2.进入配置页面,配置VCS,进入下一步,配置项目相关信息

 

   3.配置完成后,TeamCity会自动检测项目,并给出推荐构建步骤,可以根据自己项目的具体情况进行选择和修改

三、配置构建步骤-Build阶段

  1.配置构建步骤

本项目build阶段的构建步骤共三步,restore、build、test,TeamCity在配置是提供了一定程度的只能提示,可以通过点击输入框后面的方便的选择环境变量、路径和其他提示,配置如下:

  1.restore 注意配置nuget路径

   2.build 注意配置nuget路径和选择对应的Configuration,这里我选择Release

  3.test

   2.配置构建的触发器,使每次提交均会出发构建

 

四、配置构建步骤-Deploy阶段

  发布阶段的构建步骤如下:

  1. build 项目
  2. 停止站点并备份
  3. 发布到CI服务器的临时文件夹
  4. 通过FTP上传到web服务器
  5. 标记发布状态
  6. 检查发布状态,如果失败则进行回滚操作
  7. 重新启动站点
  8. 设置发布结果(如果发布失败则报告CI发布错误),清理备份文件夹

  接下来描述一下每个构建步骤设置的脚本

  1.build项目,该步骤同三、配置构建步骤-Build阶段中的build配置相同,此处不再赘述

  2.停止站点并备份,为何在此时便停止站点呢?因为如果先进行发布到CI服务器再进行停止,仍然会有一定的几率出现文件被占用的问题,所以将此步骤提前,此步骤中使用Powershell脚本对站点进行管理和备份,并使用配置参数优化脚本,脚本如下:

 1 $password='%Deployment.RemoteServer.Password%'
 2 $userName='%Deployment.RemoteServer.UserName%'
 3 $server='%Deployment.RemoteServer.IP%'
 4 $pass=ConvertTo-SecureString -String $password -AsPlainText -Force
 5 $cre=New-Object pscredential($userName, $pass)
 6 $session=New-PSSession -ComputerName $server -Credential $cre -Authentication  Basic
 7 
 8 Invoke-Command -Session $session -ScriptBlock{
 9     import-module webadministration
10     set-location IIS:
11     $site=Get-Item 'IIS:Sites{websiteName}'
12     # 停止站点
13     $site.Stop()
14     # 开始对站点备份
15     $websitePath = $site.physicalPath
16     write-host "检测到站点路径:" $websitePath
17     $bakPath = $websitePath+'.Bak'
18     if(Test-Path $bakPath)
19     {
20         write-host "备份站点路径已存在" $bakPath 
21         Remove-Item $bakPath -Recurse
22         write-host  "已删除备份站点" 
23     }
24     write-host "开始备份站点到" $bakPath
25     Copy-Item $websitePath  $bakPath -Recurse
26     write-host "备份完成"
27     #备份结束
28 }

  

   3.发布到CI服务器的临时文件夹

  

   4.通过FTP上传到web服务器,此步骤需要在web服务器搭建Ftp站点,并指向web站点所在的文件夹

  

   

  5.标记发布状态,当前面所有步骤均构建成功时,则执行此脚本标记发布状态为SUCCESS,否则不执行,这里使用的时TeamCity的特性,详情可参考官方文档。此步骤的其他环境配置同步骤2的配置相同,此处省略,仅描述powershell脚本内容:

1 echo "##teamcity[setParameter name='env.BUILD_STATUS' value='SUCCESS']"
2 Write-Host "发布成功,设置环境变量:" env.BUILD_STATUS: $env.BUILD_STATUS
3 #SUCCESS

  6.检查发布状态,如果失败则进行回滚操作,此步骤的其他环境配置同步骤2的配置相同,此处省略,仅描述powershell脚本内容:

 1 $password='%Deployment.RemoteServer.Password%'
 2 $userName='%Deployment.RemoteServer.UserName%'
 3 $server='%Deployment.RemoteServer.IP%'
 4 $pass=ConvertTo-SecureString -String $password -AsPlainText -Force
 5 $cre=New-Object pscredential($userName, $pass)
 6 $session=New-PSSession -ComputerName $server -Credential $cre -Authentication  Basic
 7 if($env:BUILD_STATUS -ne "SUCCESS")
 8 {
 9     Write-Host "发布失败,准备回滚"
10     Invoke-Command -Session $session -ScriptBlock{
11         import-module webadministration
12         set-location IIS:
13         $site=Get-Item 'IIS:SitesXMGISPlatformNETCore'
14         $websitePath = $site.physicalPath
15         write-host "检测到站点路径:" $websitePath
16         $bakPath = $websitePath+'.Bak'
17         if(Test-Path $bakPath)
18         {
19             write-host "备份站点路径存在,可以回滚" $bakPath 
20             write-host "删除当前站点文件"  
21             Remove-Item $websitePath/* -Recurse -Force
22             write-host "删除完成"  
23             write-host  "开始回滚"
24             Copy-Item  $bakPath/* $websitePath -Recurse
25             write-host  "回滚完成"
26         }
27     }
28 }
29 else
30 {
31     Write-Host '发布成功,无需回滚'
32 }

  7.重新启动站点,仅描述powershell脚本内容:

 1 $password='%Deployment.RemoteServer.Password%'
 2 $userName='%Deployment.RemoteServer.UserName%'
 3 $server='%Deployment.RemoteServer.IP%'
 4 $pass=ConvertTo-SecureString -String $password -AsPlainText -Force
 5 $cre=New-Object pscredential($userName, $pass)
 6 $session=New-PSSession -ComputerName $server -Credential $cre -Authentication  Basic
 7 
 8 Invoke-Command -Session $session -ScriptBlock{
 9     import-module webadministration
10     set-location IIS:
11     $site=Get-Item 'IIS:Sites{websiteName}'
12     $site.Start()
13 }

  8.标记发布结果(如果发布失败则报告CI发布错误),清理备份文件夹,此步骤根据步骤5设置的发布结果来判断此次发布是否成功,如果成功,则清理备份站点,否则标记发布结果为失败。仅描述powershell脚本内容:

 1 if($env:BUILD_STATUS -ne "SUCCESS")
 2 {
 3     write-host "发布失败"
 4     exit(-1)
 5 }
 6 else
 7 {
 8     write-host "发布完成,清理备份文件夹"
 9     $password='%Deployment.RemoteServer.Password%'
10     $userName='%Deployment.RemoteServer.UserName%'
11     $server='%Deployment.RemoteServer.IP%'
12     $pass=ConvertTo-SecureString -String $password -AsPlainText -Force
13     $cre=New-Object pscredential($userName, $pass)
14     $session=New-PSSession -ComputerName $server -Credential $cre -Authentication  Basic
15 
16     Invoke-Command -Session $session -ScriptBlock{
17         import-module webadministration
18         set-location IIS:
19         $site=Get-Item 'IIS:Sites{websiteName}'
20        
21         $websitePath = $site.physicalPath
22         $bakPath = $websitePath+'.Bak'
23         if(Test-Path $bakPath)
24         {
25             write-host "备份站点路径已存在" $bakPath  "准备删除"
26             Remove-Item $bakPath -Recurse
27             write-host  "已删除备份站点" 
28         }
29         write-host  "清理完毕"
30     }
31     
32 }

  配置参数的设置如下:

  

   在Deploy结果,我加入了重试触发器,当构建失败时,将在10秒后重试,连续重试3次,配置如下:

  

踩坑记录

  关于发布前停止站点问题和Powershell远程连接服务器问题

为保证在部署过程中不因为IIS进程占用文件导致部署失败,要在部署前停止IIS站点,在部署成功后重新启动IIS站点。

根据官方文档

When ASP.Net detects that a file by the name of "App_Offline.htm" exists, it will automatically bring down the app domain hosting the application. When the publish process is completed, the App_Offline.htm file will be removed and the site will be online again.

即当IIS检测到文件中包含App_Offline.htm文件时会自动停止进程,当发布完成,检测到该文件被删除后,IIS会自动启动进程。

但是在我使用TeamCity使用FTP进行发布时,虽然TeamCity会在发布时帮我我们发送App_Offline.htm文件,但是仍然会因为未知原因(初步怀疑:该文件提前被删除,站点提前被启动)导致会出现文件被占用问题,错误如下:

[Step 4/4] Deployment problem: Failed to upload artifacts via FTP. Reply was: 550 The process cannot access the file because it is being used by another process.

所以决定在发布站点之前使用PowerShell脚本来停止IIS站点,但在执行脚本时出现错误:

New-PSSession : [ip] 连接到远程服务器 ip 失败,并显示以下错误消息: WinRM 客户端无法处理该请求。如果身份验证方案与 Kerberos 不同,或者客户端计算机未加入到域中, 则必须使用 HTTPS 传输或者必须将目标计算机添加到 TrustedHosts 配置设置。 使用 winrm.cmd 配置 TrustedHosts。请注意,Trus
tedHosts 列表中的计算机可能未经过身份验证。 通过运行以下命令可获得有关此内容的更多信息: winrm help config。 有关详细信息,请参阅 about_Remote_Troubleshooting 帮助主题。
所在位置 行:4 字符: 10
+ $session=New-PSSession -ComputerName $server -Credential $cre
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotingTransportException
+ FullyQualifiedErrorId : ServerNotTrusted,PSSessionOpenFailed
Invoke-Command : 无法对参数“Session”执行参数验证。参数为 Null 或空。请提供一个不为 Null 或空的参数,然后重试该命令。
所在位置 行:6 字符: 25
+ Invoke-Command -Session $session -ScriptBlock{
+ ~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Invoke-Command],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand

可以通过以下命令将ip加入信任列表,注意,此命令要在客户端执行,而不是在要连接的服务端执行

Set-Item wsman:localhostClientTrustedHosts -value 120.26.6.*

配置完成后,可以在本地实现远程关闭启动IIS站点,但是在TeamCity中仍然无法启动,错误如下:

[15:25:46][Step 4/7] New-PSSession : [10.26.6.193] 连接到远程服务器 10.26.6.193 失败,并显示以下错误
[15:25:46][Step 4/7] 消息: WinRM 无法处理该请求。使用 Negotiate 身份验证时发生错误代码为 0x8009030d
[15:25:46][Step 4/7] 的以下错误: 指定的登录会话不存在。可能已被终止。
[15:25:46][Step 4/7] 可能的原因为:
[15:25:46][Step 4/7] -指定的用户名或密码无效。
[15:25:46][Step 4/7] -未指定身份验证方法和用户名时,使用了 Kerberos。
[15:25:46][Step 4/7] -Kerberos 接受域用户名,但不接受本地用户名。
[15:25:46][Step 4/7] -远程计算机名和端口的服务主体名称(SPN)不存在。
[15:25:46][Step 4/7] -客户端和远程计算机位于不同的域中,并且两个域之间没有信任关系。
[15:25:46][Step 4/7] 检查上述问题之后,尝试以下操作:
[15:25:46][Step 4/7] -检查事件查看器中与身份验证有关的事件。
[15:25:46][Step 4/7] -更改身份验证方法;将目标计算机添加到 WinRM TrustedHosts 配置设置中或 使用 HT
[15:25:46][Step 4/7] TPS 传输。
[15:25:46][Step 4/7] 请注意,TrustedHosts 列表中的计算机可能未经过身份验证。
[15:25:46][Step 4/7] -有关 WinRM 配置的详细信息,请运行以下命令: winrm help config。 有关详细信息,
[15:25:46][Step 4/7] 请参阅 about_Remote_Troubleshooting 帮助主题。
[15:25:46][Step 4/7] Other Possible Cause:
[15:25:46][Step 4/7] -The domain or computer name was not included with the specified credential,
[15:25:46][Step 4/7] for example: DOMAINUserName or COMPUTERUserName.
[15:25:46][Step 4/7] 所在位置 行:1 字符: 10
[15:25:46][Step 4/7] + $session=New-PSSession -ComputerName $server -Credential $cre
[15:25:46][Step 4/7] + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[15:25:46][Step 4/7] + CategoryInfo : OpenError: (System.Manageme....RemoteRunspace:Re
[15:25:46][Step 4/7] moteRunspace) [New-PSSession], PSRemotingTransportException
[15:25:46][Step 4/7] + FullyQualifiedErrorId : 1312,PSSessionOpenFailed
[15:25:46][Step 4/7] Process exited with code 1

根据提示,对两台服务进行了配置,并在使用远程登陆时指定认证方式为Basic

配置如下(此配置修改须在管理员模式下执行,否则无法修改):

 1 //快速在服务端运行winrm
 2 c:> winrm quickconfig                  
 3 
 4 //查看winrm的运行情况
 5 c:> winrm e winrm/config/listener      
 6 
 7 //查看winrm的配置
 8 c:> winrm get winrm/config           
 9 
10 //将service中的基本身份验证设置为true,允许
11 c:> winrm set winrm/config/service/auth @{Basic="true"}    
12 
13 // 将service中的allowUnencrypted设置为true,允许未加密的通讯
14 c:> winrm set winrm/config/service @{AllowUnencrypted="true"}    
15 
16 //将client中的基本身份验证设置为true,允许
17 c:> winrm set winrm/config/client/auth @{Basic="true"}           
18 
19 // 将client中的allowUnencrypted设置为true,允许未加密的通讯
20 c:> winrm set winrm/config/client @{AllowUnencrypted="true"}    

最终配置如下图:

客户端:

 

 服务端:

 最终的停止IIS站点的脚本如下,启动站点的脚本只要修改最后的函数即可:

 1 $server='server ip'
 2 $pass=ConvertTo-SecureString -String 'serverpassword' -AsPlainText -Force
 3 $cre=New-Object pscredential('serverUser', $pass)
 4 $session=New-PSSession -ComputerName $server -Credential $cre -Authentication Basic
 5 
 6 Invoke-Command -Session $session -ScriptBlock{
 7     import-module webadministration
 8     set-location IIS:
 9     $site=Get-Item 'IIS:Sites{websiteName}'
10     $site.Stop()
11 }

参考:

https://www.cnblogs.com/gamewyd/p/6805595.html

https://www.cnblogs.com/weloveshare/p/5569719.html

 
原文地址:https://www.cnblogs.com/c-supreme/p/12856168.html