使用 Ansible 實現資料中心自動化管理
長久以來,IT 運維在企業內部一直是個耗人耗力的事情。隨著虛擬化的大量應用、私有云、容器的不斷普及,資料中心內部的壓力愈發增加。傳統的自動化工具,往往是面向於資料中心特定的一類物件,例如作業系統、虛擬化、網路裝置的自動化運維工具往往是不同的。那麼,有沒有一種資料中心級別的統一的自動化運維工具呢?
答案就是 Ansible 。和傳統的自動化工具 (如 Puppet)相比,Ansible 尤其明顯的優勢:
- 簡單,是一種高階的指令碼類語言,而非標準語言。
- 不需要安裝 agent, 分為管理節點和遠端被管節點通過 SSH 認證。
- 納管範圍廣泛,不僅僅是作業系統,還包括各種虛擬化、公有云,甚至網路裝置。
接下來,本文將針對 Ansible 這一開源 IT 自動化運維工具進行介紹,並通過實驗場景讓您瞭解 Ansible 的實際作用。
Ansible 簡介
Ansible 是一個簡便的 IT 自動化引擎。近期,Ansible 在 Github 上是一個非常熱門的開源專案,可以參見下圖該專案的 Star、Fork 和 commits 數量。
圖 1. Github 上 Ansible Repo

那麼,Ansible 能夠納管(管理)哪些資料中心物件呢?通過檢視 Ansible 的 模組 (Modules,後文將具體介紹)可知,它幾乎支援資料中心的一切自動化,包括(不限於):
免費試用 IBM Cloud
利用IBM Cloud Lite 快速輕鬆地構建您的下一個應用程式。您的免費帳戶從不過期,而且您會獲得 256 MB 的 Cloud Foundry 執行時記憶體和包含 Kubernetes 叢集的 2 GB 儲存空間。瞭解所有細節並確定如何開始。如果您不熟悉 IBM Cloud,請查閱 developerWorks 上的 IBM Cloud Essentials 課程 。
- 作業系統層面:從 Linux(物理機、虛擬機器、雲環境), Unix,到 Windows。
- 虛擬化平臺:VMware、Docker、Cloudstack、LXC、Openstack 等。
- 商業化硬體:F5、ASA、Citrix、Eos 以及各種伺服器裝置的管理。
- 系統應用層:Apache、Zabbix、RabbitMQ、SVN、GIT 等。
- 紅帽解決方案:Openshift、Ceph、GlusterFS 等,支援幾乎所有紅帽解決方案的一鍵部署和配置。
- 雲平臺:IBM Cloud、AWS、Azure、Cloudflare、Red Hat CloudForms、Google、Linode、Digital Ocean 等。
接下來,我們來了解一下 Ansible 的相關元件,看它如何納管資料中心的物件。
Ansible 的元件
Ansible 的核心元件包括:Modules、Inventory、Playbook、Roles 和 Plugins。
Modules
我們在 Linux 上書寫 Shell,需要呼叫 Linux 作業系統命令,如 ls、mv、chmod
等;在書寫 POJO 時,需要呼叫 Java 相關 Pattern。Linux 系統命令對 Shell 而言和 Java Pattern 對於 POJO 而言,都是被呼叫的模組。Modules 就是使用 Ansible 進行自動化任務時呼叫的模組。在工作方時,Ansible 首先連線(預設通過 SSH)被管理節點(可能是伺服器、公有云或、網路裝置等),然後向這些節點推送 Modules、執行這些 Modules,並在完成後刪除 Modules。
Modules 是 Ansible 的核心資產,有了 Modules,我們才能呼叫這些 Modules 來完成我們想要執行的自動化任務。舉個例子:selinux - Change policy and state of SELinux。這個 Module 對的作用是配置配置 SELinux 模式和策略。我們可以通過呼叫這個 Module,來配置 RHEL/CentOS 的 SELinux 模式(eforcing、permissive 或 disabled)。目前 社群中 Modules 數量非常多、涵蓋範圍非常廣,並且以較快的速度進行增長。
Inventory
Inventory 是 Ansible 要管理物件的清單。在清單中,還可以配置分組資訊等。舉例如下:
清單 1. Inventory 示例
[webservers] www1.example.com www2.example.com [dbservers] db0.example.com db1.example.com
如果說 Modules 是我們使用 Ansible 進行自動化任務時呼叫的模組。那麼 Playbook 就是 Ansible 自動化任務的指令碼(YAML 格式)。
Roles
Roles 是將 Playbook 分成多個檔案的主要機制。這簡化了編寫複雜的 Playbook,並使其更易於重用。通過 Roles 可以將 Playbook 分解為可重用的元件。
Plugins
Plugins 是增強 Ansible 核心功能的程式碼。Ansible 附帶了許多方便的外掛,如果這些外掛不夠,我們可以編寫自己的外掛。Ansible 自帶的 Plugins 如下圖所示:
圖 2. Ansible Plugins

Plugins 與 Modules 一起執行 Playbook 任務所需的自動化任務的動作。當我們使用 Modules 的時候如果需要呼叫 Plugins,Action Plugins 預設會被自動執行。
以上文提到的 Selinux Module 舉例。在書寫 Playbook 是要呼叫 Selinux Modules,完成對 RHEL/CentOS 的 SElinux 模式的配置,這就是一個 Action。這需要 Selinux Modules 呼叫 Action Plugins 一起完成。
Plugins 的作用有很多,例如 Cache Plugins 的作用是實現後端快取機制,允許 Ansible 儲存收集到的 inventory 源資料。
Ansible 基本使用場景
在本章中,我們將介紹 Ansible 的基本使用場景,展示如果通過呼叫 Ansible Modules 執安裝 HTTP 並啟動 HTTP 服務。此外,我們還會介紹如何呼叫 Ansible Roles 來執行自動化任務。
呼叫 Ansible Modules 執行自動化任務
在本案例中,我們呼叫兩個 Modules,yum 和 service,它們的作用如下:
在 Linux 系統中檢視 Ansible 的版本,版本號為 2.5.3:
圖 3. 檢視 Ansible 版本

在 Ansible 主機上配置 Inventory。配置兩個 Group:web 和 sql,分別包含一臺 Linux 被管系統。
清單 2. Inventory 內容
[root@workstation-d04e ~]# cat /etc/ansible/hosts [web] servera.example.com [sql] serverb.example.com
配置 Ansible 主機到兩臺被管主機之間的無密碼 SSH 互信,之後,Ansible 可以與兩臺被管主機正常通訊:
圖 4. 檢視 Ansible 與被管節點之間的通訊

通過 Ansible 呼叫 yum Modules,為 Inventory 中的 Web Group 主機安裝 httpd:
圖 5. 執行 Ansible Modules

手工確認 HTTP 成功安裝:
圖 6. 確認 HTTP 安裝成功

Ansible 呼叫 service Module,啟動 httpd:
圖 7. 執行 Ansible Modules

檢查服務是否啟動:
圖 8. 確認 HTTP 啟動成功

通過本案例,我們瞭解了 Modules 和 Inventory 的功能。接下來,我將展示 Roles 的功能。
呼叫 Ansible Galaxy Roles 執行自動化任務
Roles 可以自行書寫,也可以使用 Ansible Galaxy 官網 上大量已經書寫好的 Roles。本案例將通過書寫 Playbook 呼叫 Roles,完成資料庫的安裝和配置。
登入 Ansible Galaxy 網站,搜尋並挑選一個質量評分高的 mysql Roles。
圖 9. 檢視 mysql Roles

在 Ansible 主機上安裝 mysql Roles:
圖 10. 安裝 Ansible mysql Roles

接下來,書寫一個 Playbook,呼叫 mysql Role,為 Inventory 中定義的 Web 主機安裝 mysql。
清單 3. 書寫安裝 mysql 的 Playbook
[root@workstation-d04e ansible]# cat install-database.yml - hosts: sql name: Install the database server from an Ansible Galaxy role roles: - geerlingguy.mysql
執行 Playbook。至此,通過書寫 Playbook 呼叫 Roles,完成了資料庫的安裝和配置。
圖 11. 檢視 Playbook 執行結果

使用 Ansible Playbook 執行自動化任務
在本小節中,我們將書寫 Playbook,完成如下任務:
- 在 Web 主機上安裝 Web Server(httpd 和 mod_wsgi 這兩個元件)並啟動它。
- 將書寫好的 jinja2 配置載入到 Web Sever 中。關於 jinja2 的配置,不是本文介紹的重點。
首先建立 templates 目錄,在目錄中新增 httpd.conf.j2 模板:
清單 4. 建立 httpd.conf.j2 模板
[root@workstation-d04e ansible]#mkdir templates [root@workstation-d04e ansible]# cathttpd.conf.j2 ServerRoot "/etc/httpd" Listen 80 Include conf.modules.d/*.conf User apache Group apache ServerAdmin root@localhost <Directory /> AllowOverride none Require all denied </Directory> DocumentRoot "/var/www/html" <Directory "/var/www"> AllowOverride None Require all granted </Directory> <Directory "/var/www/html"> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> <IfModule dir_module> DirectoryIndex index.html </IfModule> <Files ".ht*"> Require all denied </Files> ErrorLog "logs/error_log" MaxKeepAliveRequests {{ apache_max_keep_alive_requests }} LogLevel warn <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common <IfModule logio_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio </IfModule> CustomLog "logs/access_log" combined </IfModule> <IfModule alias_module> ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" </IfModule> <Directory "/var/www/cgi-bin"> AllowOverride None Options None Require all granted </Directory> <IfModule mime_module> TypesConfig /etc/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz AddType text/html .shtml AddOutputFilter INCLUDES .shtml </IfModule> AddDefaultCharset UTF-8 <IfModule mime_magic_module> MIMEMagicFile conf/magic </IfModule> EnableSendfile on IncludeOptional conf.d/*.conf
然後,在目錄中新增 index.html.j2 模板:
清單 5. 建立 index.html.j2 模板
[root@workstation-d04e templates]# cat index.html.j2 {{ apache_test_message }} {{ ansible_distribution }} {{ ansible_distribution_version }} Current Host: {{ ansible_hostname }} Server list: {% for host in groups['web'] %} {{ host }} {% endfor %}
書寫 Playbook 如下:
清單 6. Playbook 內容
[root@workstation-d04e ansible]# cat site.yml --- - hosts: web name: Install the web server and start it become: yes vars: httpd_packages: - httpd - mod_wsgi apache_test_message: This is a test message apache_max_keep_alive_requests: 115 tasks: - name: Install the apache web server yum: name: "{{ item }}" state: present with_items: "{{ httpd_packages }}" notify: restart apache service - name: Generate apache's configuration file from jinga2 template template: src: templates/httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart apache service - name: Generate a basic homepage from jinga2 template template: src: templates/index.html.j2 dest: /var/www/html/index.html - name: Start the apache web server service: name: httpd state: started enabled: yes handlers: - name: restart apache service service: name: httpd state: restarted enabled: yes
我們對 Playbook 做簡單的分析:
- 第一段(3-11 行):定義了 httpd_packages 變數,並進行賦值:httpd 和 mod_wsgi。
- 第二段(13-19 行):呼叫 yum 模組,安裝 httpd 和 mod_wsgi。
- 第三段(21-25 行)和第四段(27-30 行):根據事先定義好的模板,生成 Apache 配置檔案和 Homepage。
- 第五段(32-36 行):呼叫 Modules service,啟動 httpd。
在上面的 Playbook 中,還用到了 Handlers 語法,以在變更時執行操作。Playbooks 有一個可用於響應變化的事件系統。當任務執行結束,notify(在 Playbook 中的每個任務塊結束時以事件的方式通知 Handlers,從而觸發 handler 中定義的任務。)會觸發名字為 restart apache service 的 Handlers。在 Ansible 中,即使多個任務都有 notify 的定義,但一個 Playbook 中,handler 只被觸發一次。這個 handler 的作用是呼叫 Module service 重啟 httpd 服務。
接下來,執行寫好的 Playbook,並觀察執行過程。輸出如下:
清單 7. 執行 Playbook
[root@workstation-d04e ansible]# ansible-playbook site.yml PLAY [Install the web server and start it] ******************************************************** TASK [Gathering Facts] **************************************************************************** ok: [servera.example.com] TASK [Install the apache web server] ************************************************************** changed: [servera.example.com] => (item=[u'httpd', u'mod_wsgi']) TASK [Generate apache's configuration file from jinga2 template] ********************************** changed: [servera.example.com] TASK [Generate a basic homepage from jinga2 template] ********************************************* changed: [servera.example.com] TASK [Start the apache web server] **************************************************************** changed: [servera.example.com] RUNNING HANDLER [restart apache service] ********************************************************** changed: [servera.example.com] PLAY RECAP **************************************************************************************** servera.example.com: ok=6changed=5unreachable=0failed=0
Playbook 執行成功以後,通過 curl 驗證 Apache 的配置。
圖 12. 驗證 Apache 的配置

通過本章,相信您已經瞭解瞭如何通過 Modules、Roles 來執行簡單的自動化任務。接下來,我們將介紹如何通過 Ansible 執行較為複雜的自動化任務。
使用 Ansible 部署三層架構應用
在自動化的場景中,我們通常會遇到較為複雜的場景,而不是簡單在一個系統上部署或配置一個服務元件。接下來,我們通過 Ansible 執行一個較為複雜的自動化任務——部署一個三層應用,其中包括:一個前端(HAproxy)、兩個 app 伺服器(Tomcat)、一個數據庫(Postgresql)。架構圖如下:
圖 13. 三層應用架構圖

第一步,編寫 Playbook
由於本案例較為複雜,為了增加 Playbook 的可讀性、可重複利用性,我用三個 Roles 完成安裝配置 HAproxy、Tomcat 和 Postgres,然後用一個主 Playbook 呼叫這個三個 Roles。
清單 8 中,我們先定義整個自動化任務的主 playbook:main.yml:
清單 8. 書寫主 Playbook
[root@bastion 3tier-good]# cat main.yml --- # Setup front-end load balancer tier - name: setup load-balancer tier hosts: frontends become: yes roles: - {name: base-config, tags: base-config} - {name: lb-tier, tags: [lbs, haproxy]} # Setup application servers tier - name: setup app tier hosts: apps become: yes gather_facts: false roles: - {name: base-config, tags: base-config} - {name: app-tier, tags: [apps, tomcat]} # Setup database tier - name: setup database tier become: yes hosts: appdbs roles: - {name: base-config, tags: base-config} - {name: geerlingguy.postgresql, tags: [dbs, postgres]}
接下來,書寫三個 Roles。由於篇幅有限,僅以配置 load-balancer tier Roles 為例進行分析。
在 frontends group 的主機上,執行兩個 Roles:base-config 和 lb-tier。我們可以檢視這兩個 Roles 的具體內容。先檢視 base-config 的目錄結構:
圖 14. base-config 的目錄結構

檢視 tasks 下的 main.yaml:
清單 9. 檢視 base-config 的 main.yml
--- # Initial, common, system setup steps - name: enable sudo without tty for some ansible commands replace: path: /etc/sudoers regexp: '^Defaults\s*requiretty' replace: 'Defaults!requiretty' backup: yes - name: enable repos template: src: repos_template.j2 dest: /etc/yum.repos.d/open_three-tier-app.repo mode: 0644 #- name: setup hostname #hostname: #name: "{{ inventory_hostname }}" - name: install base tools and packages yum: name: "{{ item }}" state: latest with_items: - httpie - python-pip
上面 Playbook 執行的操作如下:
- 替換檔案內容:將 /etc/sudoers 檔案中的
^Defaults\s*requiretty
替換為Defaults !requiretty
。 - enable repos:將./templates/repos_template.j2 檔案拷貝到 /etc/yum.repos.d/open_three-tier-app.repo。
- 設定主機名:將主機名設定成
inventory_hostname
變數的賦值。 - 安裝 HTTP 和 python-pip。
接下來,我們檢視 lb-tier 的目錄結構:
圖 15. lb-tier 的目錄結構

檢視 tasks 目錄下的 main.yml:
清單 10. 檢視 lb-tier 的 main.yml
--- - name: install {{ payload }} yum: name: "{{ payload }}" state: latest - name: enable {{ payload }} at boot service: name: "{{ payload }}" enabled: yes - name: configure haproxy to load balance over app servers template: src: haproxy.cfg.j2 dest: /etc/haproxy/haproxy.cfg mode: 0644 - name: start {{ payload }} service: name: "{{ payload }}" state: restarted
上面的 Playbook 完成的任務:
{{ payload }} {{ payload }}
檢視 ./vars/main.yml
的內容,可以看到變數 {{ payload }}
的賦值為 haproxy
。
清單 11. 檢視 lb-tier 的環境變數
[root@bastion vars]# cat main.yml payload: haproxy
所以,lb-tier Roles 執行的任務就是安裝、配置、啟動 HAproxy 服務。
第二步,執行 Playbook
接下來,我們執行 Playbook,首先確認相關節點可以和 Ansible 正常通訊:
圖 16. 確認 Ansible 與被管節點之間正常通訊

接下來,執行 Playbook,由於篇幅有限我們只列出前端配置部分的關鍵日誌:
清單 12. 執行主 Playbook
[root@bastion 3tier-good]# ansible-playbook -i /etc/ansible/hosts main.yml PLAY [setup load-balancer tier] ************************************************ TASK [Gathering Facts] ********************************************************* ok: [frontend1.cefe.internal] TASK [base-config : enable sudo without tty for some ansible commands] ********* ok: [frontend1.cefe.internal] TASK [base-config : enable repos] ********************************************** changed: [frontend1.cefe.internal] TASK [base-config : install base tools and packages] *************************** [DEPRECATION WARNING]: Invoking "yum" only once while using a loop via squash_actions is deprecated. Instead of using a loop to supply multiple items and specifying `name: "{{ item }}"`, please use `name: ['httpie', 'python- pip']` and remove the loop. This feature will be removed in version 2.11. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. changed: [frontend1.cefe.internal] => (item=[u'httpie', u'python-pip']) PLAY RECAP ********************************************************************* app1.cefe.internal: ok=8changed=7unreachable=0failed=0 app2.cefe.internal: ok=8changed=7unreachable=0failed=0 appdb1.cefe.internal: ok=26changed=13unreachable=0failed=0 frontend1.cefe.internal: ok=8changed=6unreachable=0failed=0
可以看到任務的執行邏輯與我們前文介紹的內容是一致的。
接下來,驗證部署的應用。通過RESTClient向前端發起 POST 請求,可以看到負載均衡的效果。
第一次請求,響應返回為 Tomcat app1:
圖 17. 客戶端發起請求

第二次請求,響應返回為 Tomcat app2:
圖 18. 客戶端發起請求

至此,我們實現了通過 Ansible 部署一個三層應用的工作。
結束語
通過本文,相信您對 Ansible 及其核心元件的使用有了一些瞭解。隨著 Ansible 社群越來越受重視、 Modules 數量的迅速增加,Ansible 對整個資料中心的自動化管理能力越來越強。