持續交付與滾動升級
介紹
持續交付是頻繁對軟件應用程序持續更新的概念.
這個想法使在大量頻繁的更新面前, 你不必等待在一個指定的特殊時間點, 並且使你的組織在響應過程中變得更好.
一些 Ansible 用戶每小時都在部署更新給他們的最終用戶甚至更加頻繁 – 每時每刻都有代碼修改的批準. 要實現這一點, 你需要工具能在零停機的時間內快速的應用這些更新.
本文檔詳細介紹了如何現實這一目標, 使用 Ansible playbooks 作為一個完整例子的模板: lamp_haproxy. 這個例子使用了大量的 Ansible 特性: roles, templates 和 group variables, 並且它還配備了一個業務流程的 playbook 可以做到零停機滾動升級 web 應用程序棧.
Note
點擊這裏查看最新版本的 playbook 例子.
這個 playbooks 基於 CentOS 部署 Apache, PHP, MySQL, Nagios, 和 HAProxy 這些服務.
在這裏我們不去討論如何運行這些 playbooks. 閱讀包含在 github 項目中關於這個例子的 README 信息. 相反的, 我們將進一步觀察這些 playbook 並且去解釋它們.
部署網站
讓我們首先使用 site.yml
. 這是我們網站部署的 playbook. 它被用來部署我們最初的網站以及推送更新到所有的服務器:
--- # 這個 playbook 在這個網站上部署整個應用程序. # 應用通用的配置到所有的主機上 - hosts: all roles: - common # 配置和部署數據庫服務器. - hosts: dbservers roles: - db # 配置和部署 web 服務器. 註意這裏我們包含了兩個 roles, 這個 ‘base-apache‘ role 用來簡單設置 Apache, 而 ‘web‘ 則包含了我們的 web 應用程序. - hosts: webservers roles: - base-apache - web # 配置和部署 load balancer(s). - hosts: lbservers roles: - haproxy # 配置和部署 Nagios 監控節點(s). - hosts: monitoring roles: - base-apache - nagios
Note
如果你不熟悉 playbooks 和 plays, 你應該回顧 Playbooks.
在這個 palybook 我們有 5 個 plays. 首先第一個目標 all
(所有)主機和適用於所有主機的 common
role. 這是整個網站要做的事像 yum 倉庫的配置, 防火墻的配置, 和其他任何需要適用於所有服務器的配置.
接下來的這四個 plays 將運行於指定的主機組並特定的角色應用於這些服務器. 隨著對 Nagios monitoring, 數據庫角色, 和 web應用程序的應用, 我們可以通過 base-apache
角色安裝和配置一個基本的 Apache. 這是 Nagios 主機和 web 應用程序所需要的.
可重用的: Roles
關於 roles 你現在應該有一點了解以及它們是如何工作的. Roles 是組織: tasks, handlers, templates, 和 files, 到可重用的組件中的方法.
這個例子有 6 個 roles: common
, base-apache
, db
, haproxy
, nagios
, 和 web
. 你如何組織你的 roles 是由你和你的應用程序所決定, 但是大部分網站都將適用一個或多個共同的 roles, 和一些列關於應用程序特定的 roles 來安裝和配置這個網站的特定部分.
Roles 可以用變量和依賴關系, 你可以通過參數來調整它們的行為. 你可以在 Playbook Roles and Include Statements 章節中閱讀更多關於 roles
配置: Group Variables
Group variables 是應用在服務器組上的. 通過設置和修改參數將他們應用在 templates 中來定義 playbooks 的行為. 他們被存儲在和你的 inventory 相同目錄下名為 group_vars
的目錄中. 下面是 lamp_haproxy 的 group_vars/all
文件內容. 正如你所期望的, 這些變量將會應用到 inventory 中的所有服務器上:
--- httpd_port: 80 ntpserver: 192.168.1.2
這是一個 YAML 文件, 並且你可以創建列表和字典等更加復雜的變量結構. 在這種情況下, 我們只設置了兩個變量, 一個做為 web server 的端口, 一個作為我們服務器所使用的時間同步 NTP 服務的地址.
這是另外一個 group variables 文件. 這個 group_vars/dbservers
適用於在 dbservers
組中的主機:
--- mysqlservice: mysqld mysql_port: 3306 dbuser: root dbname: foodb upassword: usersecret
如果你看了這個例子, 你會發現對於 webservers
組合 lbservers
組的 group variables 十分相似.
這些變量可以用於任何地方. 你可以在 playbooks 中使用它們, 像這樣, 在 roles/db/tasks/main.yml
:
- name: Create Application Database mysql_db: name={{ dbname }} state=present - name: Create Application DB User mysql_user: name={{ dbuser }} password={{ upassword }} priv=*.*:ALL host=‘%‘ state=present
你也可以在 templates 中使用這些變量, 想這樣, 在 roles/common/templates/ntp.conf.j2
:
driftfile /var/lib/ntp/drift restrict 127.0.0.1 restrict -6 ::1 server {{ ntpserver }} includefile /etc/ntp/crypto/pw keys /etc/ntp/keys
你可以看到這些變量替換的語法 {{ and }} 和 templates 中的變量是相同的. 這種花括號格式是采用的jinj2語法, 你在對於內部的數據做各種操作及應用不同的過濾器. 在 templates, 你也可以使用循環和 if 語句來處理更加復雜的情況, 想這樣, 在 roles/common/templates/iptables.j2
:
{% if inventory_hostname in groups[‘dbservers‘] %} -A INPUT -p tcp --dport 3306 -j ACCEPT {% endif %}
這是用來判斷, 名為 (inventory_hostname
) 的機器是否存在於組 dbservers
. 如果這樣的話, 該機器將會添加一條 目標端口為 3306 的 iptables 允許規則.
這有一些其他的例子, 來自相同的模板:
{% for host in groups[‘monitoring‘] %} -A INPUT -p tcp -s {{ hostvars[host].ansible_default_ipv4.address }} --dport 5666 -j ACCEPT {% endfor %}
這裏循環了一個組名為 monitoring
中的所有主機, 並且配置了源地址為所有監控主機的 IPV4 地址目標端口為 5666 的 iptables 允許規則到當前主機上, 正因為如此 Nagios 才可以監控這些主機.
你可以學到更多關於 Jinja2 的功能 here, 並且你可以讀到更多關於 Ansible 所有的變量在這個 Variables 章節
滾動升級
現在你有了一個全面的網站包含 web servers, 一個 load balancer, 和 monitoring. 如何更新它? 這就是 Ansible 的特殊功能發揮作用. 盡管一些應用程序使用’業務流程’來編排命令執行的邏輯, Ansible將指揮編排這些機器, 並且擁有一個相當復雜的引擎.
Ansible 有能力在一次操作中協調多種應用程序, 使在進行更新升級我們的 web 應用程序時更加實現零停機時間. 這是一個單獨的 playbook, 叫做 roleing_upgrade.yml
.
看這個 playbook, 你可以看到它是由兩個 plays 組成. 首先第一個看起來十分簡單像這樣:
- hosts: monitoring tasks: []
這裏要做什麽, 為什麽沒有 tasks? 你可能知道 Ansible 在運行之前會從服務上收集 “facts”. 這些 facts 是很多種有用的信息: 網絡信息, OS/發行版本, 配置. 在我們的方案中, 在更新之前我們需要了解關於所有監控服務器的環境信息, 因此這個簡單的 paly 將會在我們的所監控的服務器上強制收集 fact 信息. 你有時會見到這種模式, 這是一個有用的技巧.
接下來的部分是更新 play. 第一部分看起來是這樣:
- hosts: webservers user: root serial: 1
我們僅僅是像通常一樣在 webservers
組中定義了 play. 這個 serial
關鍵字告訴 Ansible 每次操作多少服務器. 如果它沒有被指定, Ansible 默認根據配置文件中 “forks” 限制指定的值進行並發操作. 但是對於零停機時間的更新, 你可能不希望一次操作多個主機. 如果你僅僅有少數的 web 服務器, 你可能希望設置 serial
為 1, 在同一時間只執行一臺主機. 如果你有 100 臺, 你可以設置 serial
為 10, 同一時間執行 10 臺.
下面是更新 play 接下來的部分:
pre_tasks: - name: disable nagios alerts for this host webserver service nagios: action=disable_alerts host={{ inventory_hostname }} services=webserver delegate_to: "{{ item }}" with_items: groups.monitoring - name: disable the server in haproxy shell: echo "disable server myapplb/{{ inventory_hostname }}" | socat stdio /var/lib/haproxy/stats delegate_to: "{{ item }}" with_items: groups.lbservers
這個 pre_tasks
關鍵字僅僅是讓在 roles 調用前列出運行的 tasks. 這段時間將十分有用. 如果你看到這些 tasks 的名稱, 你會發現我們禁用了 Nagios 的報警並且將當前更新的服務器從 HAProxy load balancing pool 中移除.
參數``delegate_to`` 和 with_items
一起來使用, 因為 Ansible 循環每一個 monitoring 服務器和 load balancer, 並且針對循環的值在 monitoring 或 load balancing 上操作(delegate 代表操作). 從編程方面來說, 外部的循環是 web 服務器列表, 內部的循環是 monitoring 服務器列表.
請註意 HAProxy 的步驟看起來有點復雜. 我們使用它作為例子是因為它是免費的, 但如果你有(例如)一個 F5 或 Netscaler 在你的基礎設施上(或者你有一個 AWS 彈性 IP 的設置?), 你可以使用 Ansible 的模塊而不是直接和他們進行交互. 你也可能希望使用其他的 monitoring 模塊來代替 nagios, 但是這僅僅是展示了在任務開始前的部分 – 把服務從監控中移除並且輪換它們.
下一步重新簡單的使正確的角色應用在 web 服務器上. 這將導致一些名為 web
和 base-apache
的配置管理角色應用到 web 服務器上, 包含一個更新 web 應用程序自身代碼. 我們不需要這樣做 – 我們僅需要將其修改為純碎的更新 web 程序, 但是這是一個很好的例子關於如何通過 roles 來重用這些任務:
roles: - common - base-apache - web
最後, 在 post_tasks
部分, 我們反向的改變 Nagios 的配置並且將 web 服務器重新添加到 load balancing pool:
post_tasks: - name: Enable the server in haproxy shell: echo "enable server myapplb/{{ inventory_hostname }}" | socat stdio /var/lib/haproxy/stats delegate_to: "{{ item }}" with_items: groups.lbservers - name: re-enable nagios alerts nagios: action=enable_alerts host={{ inventory_hostname }} services=webserver delegate_to: "{{ item }}" with_items: groups.monitoring
再一次說明, 如果你在使用一個 Netscaler 或 F5 或 Elastic 的負載均衡器, 你僅僅需要替換為適合的模塊對象.
管理其他的負載均衡
在這個例子中, 我們使用了簡單的 HAProxy 負載均衡到後端的 web 服務器. 它是非常容易配置和管理的. 正如我們所提到的, Ansible 已經為其他的負載均衡器像 Citrix NetScaler, F5 BigIP, Amazon Elastic Load Balancers 等提供了內建的支持.閱讀更多信息 模塊相關
對於其他的負載均衡器, 如果公開一個負載均衡時, 你可能需要向它們發送 shell 命令 (像上面我們對 HAProxy 一樣), 或者調用一些 API. 你可以越多更多關於 local actions 在這個 委托,滾動更新,本地動作章節中. 對於一些硬件的開發將更加有趣, 他們沒有一個核心模塊, 所以你可以使用更好的模塊將他們封裝起來!
持續交付結束
現在你有一個自動化的方式來部署更新你的應用程序, 你將如何將他們綁定在一起? 許多組織使用持續集成的工具像 Jenkins 或 Atlassian Bamboo 來完成開發, 測試, 發布, 和部署這樣的流程步驟. 你也可以使用這些工具像 Gerrit 來添加一個 code review 的步驟來審查提交的應用程序的本身或者 Ansible playbooks.
根據你的環境, 你可能會部署到一個測試環境, 在這個環境中運行一些集成測試, 然後自動部署到生產環境. 你可以保持他們的簡單性僅按需來進行滾動升級到測試或者指定的生產環境中. 這些你都隨你決定.
與持續集成工具的結合, 你可以通過 ansible-playbook
命令行工具很容易的觸發 playbook 的運行, 或者, 如果你使用 Ansible Tower, tower-cli
或者內置的 REST API. (這個 tower-cli 命令的 ‘joblaunch’ 將通過 REST API 遠程產生一個 job 這非常棒).
Ansible 對於如何組合多層應用程序在任務編排和持續交付給客戶的最終目標上給了你很好的主意. 你可以使用滾動升級的思路來擴展一些應用程序之間的不同部分; 也許向前端 web 服務器添加後端應用服務, 例如, 使用 MongoDB 或 Riak 來替換 SQL 數據庫. Ansible 可以給你在復雜的環境中輕松完成常見的自動化操作.
See also
- lamp_haproxy example
- The lamp_haproxy example discussed here.
- Playbooks
- An introduction to playbooks
- Playbook Roles and Include Statements
- An introduction to playbook roles
- Variables
- An introduction to Ansible variables
- Ansible.com: Continuous Delivery
- An introduction to Continuous Delivery with Ansible
持續交付與滾動升級