持续交付之Jenkins+Ansible+Python搭建自动化部署框架(win版)
前言
无论是为新需求添加的代码,还是静态配置的变更,应用的任何变动都要经过部署这道工序才能最终落地。但通常,新的部署意味着应用重启、服务中断。工程师和测试人员经常在深夜搞得筋疲力尽,甚至焦头烂额。进入持续交付的时代后,这个痛点只会更加突显,因为持续交付意味着持续部署。例如,在测试环境小时级的持续集成场景中,如果没有办法将部署过程流程化、自动化,显然会频繁打断最终的交付过程,大幅降低开发测试效率。
因此,我们想要的应该是:一个易用、快速、稳定、容错力强,必要时有能力迅速回滚的部署系统。
部署的需求
单机部署过程高度抽象后其实就三个步骤:
- 在目标机器上执行命令停掉运行中的服务
- 把提前准备好的变更包传上机器覆盖原来的目录
- 运行命令把服务再跑起来
假设我们实现了一个自动部署程序,简单地顺序执行上面的步骤,让我们一起来检验是否能满足发布的需求:
- 易用:执行脚本就好,填入参数,一键执行。
- 快速:自动化肯定比手工快,并且有提升空间。比如,因为有版本的概念,我们可以跳过相同版本的部署,或是某些步骤。
- 稳定:因为这个程序逻辑比较简单,而且执行步骤并不多,没有交叉和并行,所以稳定性也没什么大的挑战。
- 容错性强:表现一般,脚本碰到异常状况只能停下来,但因为版本间是隔离的,不至于弄坏老的服务,通过人工介入仍能恢复。
- 回滚顺滑:因为每个版本都是完整的可执行产物,所以回滚可以视作使用旧版本重新做一次部署。甚至我们可以在目标机器上缓存旧版本产物,实现超快速回滚。
通过这个程序的简单执行过程,我们可以看到这套流程的简单实现,基本满足了我们部署的需求。而且,可以通过添加更复杂的控制流,获得更大的提升空间。
而如今架构基本上告别了单点世界,面向集群的部署带来了更高维度的问题。当部署的目标是一组机器而不是一台机器时,主要问题就变成了如何协调整个过程。比如,追踪、同步一组机器目前部署进行到了哪一步,编排集群的部署命令就成为了更核心功能。
落地方案
技术架构
主要特点
- 使用 Jenkins 作为一站式部署平台,方便选择参数,自动协调各主机,自动运行部署命令,自动通知等
- 支持快速回滚指定旧版本
- 支持面向集群进行编排、追踪和同步任务
- 实现钉钉自动化通知及跳转功能
技术选型
- 执行引擎:Ansible
- 自动通知:钉钉webhook & python
- Jenkins 插件:
- Shell:执行 shell 脚本
- Active Choices Plugin:动态交互参数
- AnsiColor:彩色输出,非必须
环境配置
- Ansible: 2.9.0
- Python: 2.7.5
- CentOS: 7.6
- Java: 1.8.0_73
- Jenkins: 2.164.3
预备知识
Ansible
Ansible是什么?
Ansible 是一个自动化运维管理工具,支持 Linux/Windows 跨平台的配置管理,任务分发等操作,可以帮我们大大减少在变更环境时所花费的时间。与其他三大主流的配置管理工具 Chef、Puppet、Salt 相比,Ansible 最大的特点在于“agentless”,即无需在目标机器装安装 agent 进程,即可通过 SSH 或者 PowerShell 对一个环境中的集群进行中心化的管理。所以,这个“agentless” 特性,可以大大减少我们配置管理平台的学习成本,尤其适合于第一次尝试使用此类配置管理工具。
Ansible能做什么?
正如其他配置管理工具一样,Ansible 可以帮助我们完成一些批量任务,或者完成一些需要经常重复的工作
- 比如:同时在 100 台服务器上安装 nginx 服务,并在安装后启动它们
- 比如:将某个文件一次性拷贝到 100 台服务器上
- 比如:每当有新服务器加入工作环境时,你都要为新服务器部 redis 服务,也就是说你需要经常重复的完成相同的工作
这些场景中我们都可以使用到 Ansible
Ansible架构
Ansible工作原理
Ansible特性
- 模块化:调用特定的模块,完成特定任务
- 有 Paramiko,PyYAML,Jinja2(模板语言)三个关键模块
- 支持自定义模块
- 基于 Python 语言实现
- 部署简单,基于 python 和 SSH(默认已安装),agentless
- 安全,基于 OpenSSH
- 支持 playbook 编排任务
- 幂等性:一个任务执行1遍和执行n遍效果一样,不因重复执行带来意外情况
- 无需代理不依赖 PKI(无需ssl)
- 可使用任何编程语言写模块
- YAML 格式,编排任务,支持丰富的数据结构
- 较强大的多层解决方案
Ansible主要组成部分
- PLAYBOOKS:任务剧本(任务集),编排定义 Ansible 任务集的配置文件,由 Ansible 顺序依次执行,通常是 JSON 格式的 YML 文件
- INVENTORY:Ansible 管理主机的清单
/etc/anaible/hosts
- MODULES:Ansible 执行命令的功能模块,多数为内置的核心模块,也可自定义,
ansible-doc –l
可查看模块 - PLUGINS:模块功能的补充,如连接类型插件、循环插件、变量插件、过滤插件等,该功能不常用
- API:供第三方程序调用的应用程序编程接口
- ANSIBLE:组合 INVENTORY、 API、 MODULES、PLUGINS 的绿框,可以理解为是 Ansible 命令工具,其为核心执行工具
注意事项
- 执行 Ansible 的主机一般称为主控端,中控,master 或堡垒机
- 主控端 Python 版本需要2.6或以上
- 被控端 Python 版本小于2.4需要安装 python-simplejson
- 被控端如开启 SELinux 需要安装 libselinux-python
- windows 不能做为主控端
具体实现
环境规划
序号 | 名称 | 操作系统 | IP地址 | 用途 |
---|---|---|---|---|
1 | enkins-salve | Centos7 | 172.16.106.122 | Jenkins salve |
2 | win7-app-Develop-Dev | win7专业版 | 172.16.106.199 | 开发环境 |
3 | win7-app-Develop-Release | win7专业版 | 172.16.106.153 | 开发环境 |
4 | win7-app-Test-Dev | win7专业版 | 172.16.106.180 | 功能测试环境 |
5 | win7-app-Test-Release | win7专业版 | 172.16.106.185 | 功能测试环境 |
6 | win7-app-AutoTest-Release | win7专业版 | 172.16.106.191 | 自动化测试环境 |
7 | win7-app-AutoTest-Dev | win7专业版 | 172.16.106.14 | 自动化测试环境 |
搭建 Master 环境(Linux)
这里以 Centos 7.x yum安装为例:
# yum install ansible
查看版本:
# ansible --version
ansible 2.9.0
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Jun 20 2019, 20:27:34) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
配置文件:
配置文件 | 描述 |
---|---|
/etc/ansible/ansible.cfg | 主配置文件,配置ansible工作特性 |
/etc/ansible/hosts | 主机清单 |
/etc/ansible/roles/ | 存放角色的目录 |
/usr/bin/ansible | 主程序,临时命令执行工具 |
/usr/bin/ansible-doc | 查看配置文档,模块功能查看工具 |
/usr/bin/ansible-galaxy | 下载/上传优秀代码或Roles模块的官网平台 |
/usr/bin/ansible-playbook | 定制自动化任务,编排剧本工具 |
/usr/bin/ansible-pull | 远程执行命令的工具 |
/usr/bin/ansible-vault | 文件加密工具 |
/usr/bin/ansible-console | 基于Console界面与用户交互的执行工具 |
搭建受控端环境(window)
主机要求
Ansible 从 1.7+ 版本开始支持 Windows,但前提是管理机必须为 Linux 系统,远程主机的通信方式也由SSH变更为PowerShell,同时管理机必须预安装 Python 的 Winrm 模块,方可和远程 Windows 主机正常通信,但 PowerShell 需4.0+版本且Management Framework 4.0+版本,。Ansible 可以管理包括 Windows 7、8.1和10的桌面操作系统以及包括Windows Server 2008、2008 R2、2012、2012 R2、2016和2019的服务器操作系统。
简单总结如下:
- 管理机必须为 Linux 系统且需预安装 Python 和 Winrm 模块
- 底层通信基于 PowerShell,版本为3.0+,Management Framework 版本为4.0+
- 远程主机开启 Winrm 服务
升级 Upgrading PowerShell 和 .NET Framework
可以使用 Upgrade-PowerShell.ps1
脚本来更新它们
这是如何从PowerShell运行此脚本的示例:
$url = "https://raw.githubusercontent.com/jborean93/ansible-windows/master/scripts/Upgrade-PowerShell.ps1"
$file = "$env:temp\Upgrade-PowerShell.ps1"
$username = "Administrator"
$password = "Password"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force
# Version can be 3.0, 4.0 or 5.1
&$file -Version 5.1 -Username $username -Password $password -Verbose
完成后,将需要删除自动登录并将执行策略重新设置为默认值 Restricted。可以使用以下PowerShell命令执行此操作:
# This isn't needed but is a good security practice to complete
Set-ExecutionPolicy -ExecutionPolicy Restricted -Force
$reg_winlogon_path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
Set-ItemProperty -Path $reg_winlogon_path -Name AutoAdminLogon -Value 0
Remove-ItemProperty -Path $reg_winlogon_path -Name DefaultUserName -ErrorAction SilentlyContinue
Remove-ItemProperty -Path $reg_winlogon_path -Name DefaultPassword -ErrorAction SilentlyContinue
该脚本通过检查是否需要安装哪些程序(例如.NET Framework 4.5.2)以及所需的PowerShell版本来工作。如果需要重新启动 username 并且 password 已设置和参数,则脚本将从重新启动后自动重新启动并登录。该脚本将继续执行,直到不需要其他操作并且PowerShell版本与目标版本匹配为止。如果未设置 usernam 和 password 参数,脚本将提示用户手动重新启动并在需要时登录。下次登录用户时,脚本将从上次停止的地方继续,然后继续该过程,直到不需要其他操作为止。
注意:
- 如果在 Server 2008 上运行,则必须安装SP2。如果在 Server 2008 R2 或 Windows 7 上运行,则必须安装SP1
- Windows Server 2008 只能安装 PowerShell 3.0,指定较新的版本将导致脚本失败
- 在 username 和 password 参数都存储在注册表中的纯文本。确保脚本完成后运行清除命令,以确保主机上仍没有存储凭据。
WinRM 内存修补程序
在 PowerShell v3.0 上运行时,WinRM 服务存在一个错误,该错误会限制 WinRM 可用的内存量。没有安装此修补程序,Ansible 将无法在 Windows 主机上执行某些命令。这些修补程序应作为系统引导或映像过程的一部分进行安装
脚本Install-WMF3Hotfix.ps1
可用于在受影响的主机上安装此修补程序
$url = "https://raw.githubusercontent.com/jborean93/ansible-windows/master/scripts/Install-WMF3Hotfix.ps1"
$file = "$env:temp\Install-WMF3Hotfix.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file -Verbose
WinRM 安装程序
一旦将 Powershell 升级到至少3.0版,最后一步就是配置 WinRM 服务,以便 Ansible 可以连接到它。WinRM 服务的两个主要组件决定着 Ansible 与 Windows 主机的接口方式:listener和和service配置设置。
可以使用脚本 ConfigureRemotingForAnsible.ps1
来设置基础。该脚本使用自签名证书设置HTTP和HTTPS侦听器,并Basic 在服务上启用身份验证选项。
要使用此脚本,请在PowerShell中运行以下命令:
$url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file
WinRM 监听
WinRM 服务在一个或多个端口上侦听请求。这些端口中的每一个都必须具有创建和配置的侦听器。
要查看 WinRM 服务上正在运行的当前侦听器,请运行以下命令:
winrm enumerate winrm/config/Listener
Listener
Address = *
Transport = HTTP
Port = 5985
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint
ListeningOn = 10.0.2.15, 127.0.0.1, 192.168.56.155, ::1, fe80::5efe:10.0.2.15%6, fe80::5efe:192.168.56.155%8, fe80::
ffff:ffff:fffe%2, fe80::203d:7d97:c2ed:ec78%3, fe80::e8ea:d765:2c69:7756%7
Listener
Address = *
Transport = HTTPS
Port = 5986
Hostname = SERVER2016
Enabled = true
URLPrefix = wsman
CertificateThumbprint = E6CDAA82EEAF2ECE8546E05DB7F3E01AA47D76CE
ListeningOn = 10.0.2.15, 127.0.0.1, 192.168.56.155, ::1, fe80::5efe:10.0.2.15%6, fe80::5efe:192.168.56.155%8, fe80::
ffff:ffff:fffe%2, fe80::203d:7d97:c2ed:ec78%3, fe80::e8ea:d765:2c69:7756%7
在上面的示例中,激活了两个侦听器。一种是通过 HTTP 监听端口5985,另一种是通过HTTPS监听端口5986。一些有用的关键选项是:
- Transport:无论侦听器是通过HTTP还是HTTPS运行,建议对HTTPS使用侦听器,因为数据已加密,无需进行任何进一步更改。
- Port:监听器运行的端口,默认情况下是5985用于HTTP和5986 TTPS的端口。该端口可以更改为所需的任何端口,并与主机var对应ansible_port。
- Prefix:要侦听的URL前缀,默认为wsman。如果更改此 ansible_winrm_path 设置,则必须将主机 var 设置为相同的值。
- CertificateThumbprint:如果运行在HTTPS侦听器上,这是连接中使用的 Windows 证书存储中证书的指纹。
要获取证书本身的详细信息,请在PowerShell中使用相关的证书指纹运行以下命令:
$thumbprint = "E6CDAA82EEAF2ECE8546E05DB7F3E01AA47D76CE"
Get-ChildItem -Path cert:\LocalMachine\My -Recurse | Where-Object { $_.Thumbprint -eq $thumbprint } | Select-Object *
设置 WinRM 侦听器
可以通过三种方式设置WinRM侦听器:
- 使用了 HTTP 或 HTTPS的。在域环境之外运行并且需要一个简单的侦听器时,这是最容易使用的选项。与其他选项不同,此过程还具有为所需的端口打开防火墙并启动WinRM服务的额外好处。
winrm quickconfigwinrm quickconfig -transport:https
-
使用组策略对象。当主机是域的成员时,这是创建侦听器的最佳方法,因为配置是自动完成的,无需任何用户输入。有关组策略对象的更多信息,请参阅 组策略对象文档。
-
使用 PowerShell 创建具有特定配置的侦听器。这可以通过运行以下 PowerShell 命令来完成:
$selector_set = @{
Address = "*"
Transport = "HTTPS"
}
$value_set = @{
CertificateThumbprint = "E6CDAA82EEAF2ECE8546E05DB7F3E01AA47D76CE"
}
New-WSManInstance -ResourceURI "winrm/config/Listener" -SelectorSet $selector_set -ValueSet $value_set
设置Windows远端管理
查看 winrm service listener:
winrm e winrm/config/listener
为 winrm service 配置 auth:
winrm set winrm/config/service/auth @{Basic="true"}
为 winrm service 配置加密方式为允许非加密:
winrm set winrm/config/service @{AllowUnencrypted="true"}
好了,远程 Windows 主机配置到此结束,我们验证配置的是否有问题。
Inventory 主机清单
Ansible 必须通过 Inventory 来管理主机。Ansible 可同时操作属于一个组的多台主机,组和主机之间的关系通过 inventory 文件配置。
# vi /etc/ansible/hosts
[Dev_ALL]
172.16.106.14 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
172.16.106.180 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
172.16.106.199 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Dev_AutoTest]
172.16.106.14 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Dev_FunctionTest]
172.16.106.180 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Dev_Develop]
172.16.106.199 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Release_AutoTest]
172.16.106.191 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Release_Develop]
172.16.106.153 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Release_FunctionTest]
172.16.106.185 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
[Release_ALL]
172.16.106.191 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
172.16.106.153 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
172.16.106.185 ansible_ssh_user="xxxx" ansible_ssh_pass="xxxx" ansible_ssh_port=5985 ansible_connection="winrm" ansible_winrm_server_cert_validation=ignore
参数说明:
- ansible_ssh_user:用户名
- ansible_ssh_pass:密码
- ansible_ssh_port:端口号
- ansible_connection:与主机的连接类型
主机说明:
- Dev_ALL:所有dev版本环境
- Dev_AutoTest:dev版本自动化测试环境
- Dev_FunctionTest:dev版本功能测试环境
- Dev_Develop:dev版本开发环境
- Release_ALL:所有release版本环境
- Release_AutoTest:release版本自动化测试环境
- Release_Develop:release版本开发环境
- Release_FunctionTest:release版本功能测试环境
使用 ansible 对 Release_AutoTest 组内的主机进行 ping 模块测试
# ansible Release_AutoTest -m win_ping
172.16.106.191 | SUCCESS => {
"changed": false,
"ping": "pong"
}
PlayBook 任务剧本
PlayBook 是 Ansible 的脚本文件,使用 YAML 语言编写,包含需要远程执行的核心命令、定义任务具体内容,等等。
通常情况下,我们用脚本的方式使用 Ansible,只要使用好 Inventory 和 PlayBook 这两个组件就可以了,即:使用 PlayBook 编写 Ansible 脚本,然后用 Inventory 维护好需要管理的机器列表。这样,就能解决 90% 以上使用 Ansible 的需求。
但如果你有一些更复杂的需求,比如通过代码调用 Ansible,可能还要用到 API 组件。感兴趣的话,你可以参考 Ansible 的官方文档。
剧本、资源路径
/home/ansible/playbooks 剧本存放目录
/home/ansible/python python搅拌
完整剧本
# vi server-deploy.yaml
# ---------------------------------
# 1.各变量赋值
# 2.初始化目录,包括:程序目录,下载目录,资源备份目录(如果不存在)
# 3.结束正在运行的服务进程(等待3秒)
# 4.清空资源目录
# 5.备份 Data/Files 目录
# 6.备份 Data/projects 目录
# 7.清空程序目录
# 8.下载 server 程序文件
# 9.解压文件
# 10.清空&还原 Data/projects 目录
# 11.启动 server 服务
# ---------------------------------
- hosts: "{{target}}"
remote_user: htsd
vars:
package:
root_url: "http://172.16.106.188:8081/repository/app-{{branch}}-info/server/"
deploy:
app_path: "C:\\app\\app-{{branch}}-info\\server"
package_path: C:\app\package
res_path: C:\app\res
PsExec_path: C:\app\tools\PSTools
tasks:
# --------------------初始化目录--------------------
- name: 创建程序目录
win_file:
path: "{{deploy.app_path}}"
state: directory
become: yes
- debug:
msg: "{{deploy.app_path}}"
- name: 创建下载目录
win_file:
path: "{{deploy.package_path}}"
state: directory
become: yes
- debug:
msg: "{{deploy.package_path}}"
- name: 创建资源备份目录
win_file:
path: "{{deploy.res_path}}"
state: directory
become: yes
- debug:
msg: "{{deploy.package_path}}"
# -------------------备份及结束进程------------------
- name: 结束 Server 进程
win_shell: Stop-Process -Name "app.Server" -Force
ignore_errors: true
- name: 等待3秒停止 Server 进程
win_wait_for_process:
process_name_pattern: app.Server
state: absent
timeout: 3
- name: 清空资源目录
win_shell: |
$TargetFolder = "{{deploy.res_path}}"
$Files = get-childitem $TargetFolder -force
Foreach ($File in $Files)
{
$FilePath=$File.FullName
Remove-Item -Path $FilePath -Recurse -Force
}
- name: 备份 Data/Files 目录
win_shell: Copy-Item "{{deploy.app_path}}\Data\Files" -Destination {{deploy.res_path}} -Recurse
ignore_errors: yes
- name: 备份 Data/projects 目录
win_shell: Copy-Item "{{deploy.app_path}}\Data\projects" -Destination {{deploy.res_path}} -Recurse
ignore_errors: yes
- name: 清空程序目录
win_shell: |
$TargetFolder = "{{deploy.app_path}}"
$Files = get-childitem $TargetFolder -force
Foreach ($File in $Files)
{
$FilePath=$File.FullName
Remove-Item -Path $FilePath -Recurse -Force
}
# ----------------下载&更新程序------------------------
- name: 下载 server 程序文件
win_get_url:
url: "{{package.root_url}}{{package_name}}"
dest: "{{deploy.package_path}}"
force: no
- debug:
msg: "{{package_name}}"
- name: 递归解压文件后删除zip包
win_unzip:
src: "{{deploy.package_path}}/{{package_name}}"
dest: "{{deploy.app_path}}"
recurse: yes
delete_archive: yes
- name: 删除原 Data/Files 目录
win_shell: rmdir /s/q "{{deploy.app_path}}\Data\Files"
args:
executable: cmd.exe
ignore_errors: yes
- name: 删除原 Data/projects 目录
win_shell: rmdir /s/q "{{deploy.app_path}}\Data\projects"
args:
executable: cmd.exe
ignore_errors: yes
- name: 还原 Data/Files 目录
win_shell: Copy-Item "{{deploy.res_path}}\Files" -Destination "{{deploy.app_path}}\Data" -Recurse
ignore_errors: yes
- name: 还原 Data/projects 目录
win_shell: Copy-Item "{{deploy.res_path}}\projects" -Destination "{{deploy.app_path}}\Data" -Recurse
ignore_errors: yes
# -------------------------启动-------------------------
- name: 启动 app-server
win_shell: "{{deploy.PsExec_path}}/psexec.exe -accepteula -nobanner -i 1 -s -d {{deploy.app_path}}//app.Server.exe"
register: output
ignore_errors: yes
# - name: 打印日志
# debug: var=output
回滚部署
由于各种各样的原因,部署的版本可能会出现异常,这时候可能需要紧急回滚版本,我们可以手动去回滚版本,但是缺点也很明显,当主机实例过多时,手动回滚明显是不再明智的,所以我们可结合 Jenkins+Ansible 这两者来做到一个通用的服务回滚策略。
Jenkins 执行
#!/usr/bin/env bash
echo '版本类型:'$Branch
echo '环境类型:'$Hosts
echo '文件名称:'$Package_Name
ansible-playbook /home/ansible/playbooks/server-deploy.yaml --extra-vars "package_name=$Package_Name branch=$Branch target=$Hosts"
具体参考上文:持续交付之解决Jenkins自动发布中交互式参数应用
Jenkins 执行日志:
钉钉通知
Jenkins 调用:
python 脚本:
# coding=utf-8
'''
@author: zuozewei
@file: notification.py
@time: 2019/4/25 18:00
@description:dingTalk通知类
'''
import os, jenkins, json
from dingtalkchatbot.chatbot import DingtalkChatbot
from jsonpath import jsonpath
JOB_NAME = str(os.getenv("JOB_NAME"))
BUILD_URL = str(os.getenv("BUILD_URL")) + "console"
BUILD_NUMBER = str(os.getenv("BUILD_NUMBER"))
Package_Name = str(os.getenv("Package_Name"))
VERSION = Package_Name.split('-')[4].replace('.zip','')
Branch = str(os.getenv("Branch"))
Hosts = str(os.getenv("Hosts"))
Branch_Name = ''
Eev = ''
Host_name = ''
if Branch == 'dev':
Branch_Name = '开发版'
if Hosts == 'Dev_ALL':
Eev = 'Dev所有环境'
Host_name = '- 172.16.106.175' + '\n' + \
'- 172.16.106.155' + '\n' + \
'- 172.16.106.115' + '\n'
elif Hosts == 'Dev_AutoTest':
Eev = 'Dev自动化测试环境'
Host_name = '- 172.16.106.175' + '\n'
elif Hosts == 'Dev_FunctionTest':
Eev = 'Dev功能测试环境'
Host_name = '- 172.16.106.155' + '\n'
elif Hosts == 'Dev_Develop':
Eev = 'Dev开发环境'
Host_name = '- 172.16.106.115' + '\n'
elif Branch == 'release':
Branch_Name = '预览版'
if Hosts == 'Release_ALL':
Eev = 'Release所有环境'
Host_name = '- 172.16.106.58' + '\n' + \
'- 172.16.106.168' + '\n' + \
'- 172.16.106.203' + '\n'
elif Hosts == 'Release_AutoTest':
Eev = 'Release自动化测试环境'
Host_name = '- 172.16.106.58' + '\n'
elif Hosts == 'Release_FunctionTest':
Eev = 'Release功能测试环境'
Host_name = '- 172.16.106.203' + '\n'
elif Hosts == 'Release_Develop':
Eev = 'Release开发环境'
Host_name = '- 172.16.106.168' + '\n'
print("【版本类型】:" + Branch_Name)
print("【环境类型】:" + Eev)
print("【主机列表】:" + Host_name)
# 连接jenkins
server = jenkins.Jenkins(url="http://172.16.106.251:8080", username='xxx', password="xxx")
# 获取指定项目编译状态
BUILD_STATUS = server.get_build_info(JOB_NAME, int(BUILD_NUMBER))['result']
print("【BUILD_STATUS】:" + BUILD_STATUS)
build_info = server.get_build_info(JOB_NAME, int(BUILD_NUMBER))
# dict字典转json数据
build_info_json = json.dumps(build_info)
# 把json字符串转json对象
build_info_jsonobj = json.loads(build_info_json)
causes = jsonpath(build_info_jsonobj, '$.actions..shortDescription')
def packagNotification():
title = 'xxx部署通知'
textFail = '#### ' + JOB_NAME + ' # ' + BUILD_NUMBER + ' \n' + \
'##### <font color=#FF0000 size=6 face="黑体">部署状态: ' + BUILD_STATUS + '</font> \n' + \
'##### **版本类型**: ' + Branch_Name + '\n' + \
'##### **当前版本**: ' + VERSION + '\n' + \
'##### **文件名称**: ' + Package_Name + '\n' + \
'##### **触发类型**: ' + str(causes[0]) + '\n' + \
'##### **部署日志**: [查看详情](' + BUILD_URL + ') \n' + \
'##### **关注人**: @186xxxx2487 \n' + \
'##### **部署环境**: ' + Eev + '\n' + \
'##### **执行主机**: \n' + \
Host_name + '\n' + \
'> ###### xxx技术团队 \n '
textSuccess = '#### ' + JOB_NAME + ' # ' + BUILD_NUMBER + ' \n' + \
'##### **部署状态**: ' + BUILD_STATUS + '\n' + \
'##### **版本类型**: ' + Branch_Name + '\n' + \
'##### **当前版本**: ' + VERSION + '\n' + \
'##### **文件名称**: ' + Package_Name + '\n' + \
'##### **触发类型**: ' + str(causes[0]) + '\n' + \
'##### **部署日志**: [查看详情](' + BUILD_URL + ') \n' + \
'##### **部署环境**: ' + Eev + '\n' + \
'##### **执行主机**: \n' + \
Host_name + '\n' + \
'> ###### xxx技术团队 \n '
if BUILD_STATUS == 'SUCCESS':
dingText = textSuccess
else:
dingText = textFail
sendding(title, dingText)
def sendding(title, content):
at_mobiles = ['186xxxx2487']
Dingtalk_access_token_v3c = 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxx'
# 初始化机器人小丁
xiaoding1 = DingtalkChatbot(Dingtalk_access_token_v3c)
# Markdown消息@指定用户
xiaoding1.send_markdown(title=title, text=content, at_mobiles=at_mobiles)
if __name__ == "__main__":
packagNotification()
通知效果:
注意:
如果主机比较多的情况,建议不要使用这种硬编码的方式,可以考虑放到一个配置文件进行读取。
小结
在今天这篇文章中,主要基于 Ansible 系统的能力,和大家分享了搭建一套部署系统的过程。在搭建过程中,你最需要关注的几部分内容是:
- 利用 Inventory 做好部署目标的管理
- 利用 PlayBook 编写部署过程的具体逻辑
- 利用 Jenkins 对主机集群进行编排、追踪和同步任务
- 利用 Python 脚本钉钉自动化通知及跳转功能
至此,我们要搭建的整个自动部署系统,也算是顺利完成了
相关资源:
参考资料:
[1]:https://blog.51cto.com/191226139/2066936
[2]:https://docs.ansible.com/ansible/latest/user_guide/windows.html
[3]:持续交付36讲 王潇俊
- 点赞
- 收藏
- 关注作者
评论(0)