持續交付之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 執行日誌:
(http://doc.techstar.work:8181/uploads/v3c-cxjf/images/m_95c6e3d6e0fe81b7b126d7f2967d31c2_r.png)]

釘釘通知

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()

通知效果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-B6iBDmUE-1574214236202)(http://doc.techstar.work:8181/uploads/v3c-cxjf/images/m_ff40fa062cf6df2b46f191a2be63432f_r.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UOe7fFJi-1574214236202)(http://doc.techstar.work:8181/uploads/v3c-cxjf/images/m_15404d38300826db504e7052e3b19bec_r.png)]

注意:
如果主機比較多的情況,建議不要使用這種硬編碼的方式,可以考慮放到一個配置文件進行讀取。

小結

在今天這篇文章中,主要基於 Ansible 系統的能力,和大家分享了搭建一套部署系統的過程。在搭建過程中,你最需要關注的幾部分內容是:

  • 利用 Inventory 做好部署目標的管理
  • 利用 PlayBook 編寫部署過程的具體邏輯
  • 利用 Jenkins 對主機集羣進行編排、追蹤和同步任務
  • 利用 Python 腳本釘釘自動化通知及跳轉功能

至此,我們要搭建的整個自動部署系統,也算是順利完成了

相關資源:
https://github.com/zuozewei/Jenkins-CI/tree/master/jenkins-ansible-python

參考資料:
[1]:https://blog.51cto.com/191226139/2066936
[2]:https://docs.ansible.com/ansible/latest/user_guide/windows.html
[3]:持續交付36講 王瀟俊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章