在管理現代 IT 基礎架構的過程中,自動化已成為不可或缺的一環。隨著系統規模不斷擴大,手動設定與維護不僅耗時費力,更容易引入人為錯誤。作為一名資深系統架構師,我見證了 Ansible 如何徹底改變系統管理的方式,讓複雜的佈署流程變得簡單與可靠。

Ansible 作為一款強大的自動化工具,以其簡單易用的特性在 DevOps 領域中脫穎而出。不同於其他組態管理工具,Ansible 採用無代理(Agentless)架構,只需要 SSH 連線和 Python 環境,就能輕鬆管理各種系統。這種設計不僅降低了佈署門檻,也大幅減少了維護成本。

在本文中,我將探討 Ansible 的核心概念、實用技巧以及最佳實踐,幫助你建立高效的自動化工作流程。無論你是初學者還是有經驗的系統管理員,都能從中獲得實用的知識與見解。

Ansible 核心架構與設計理念

無代理架構的優勢

Ansible 最顯著的特色是其「無代理」(Agentless) 架構。這意味著不需要在目標主機上安裝任何特殊軟體,只要目標系統支援 SSH 連線和 Python 環境,就能被 Ansible 管理。這種設計帶來了幾個關鍵優勢:

  • 簡化佈署流程:無需在每台伺服器上安裝和維護代理程式
  • 降低資源消耗:不會在被管理主機上持續執行背景程式
  • 提高安全性:減少潛在的安全漏洞攻擊面
  • 增強相容性:幾乎適用於所有支援 SSH 的系統

Ansible 的工作流程相當直觀:控制節點透過 SSH 連線到目標主機,將執行所需的 Python 程式碼傳送過去,在目標主機上執行,然後收集結果回傳。這種設計使得 Ansible 特別適合 Linux 環境,因為幾乎所有 Linux 發行版都預設支援 SSH 和 Python。

Ansible 的三大核心元素

1. 模組 (Modules)

模組是 Ansible 的功能單位,每個模組負責特定任務。例如:

  • ansible.builtin.apt - 在 Debian/Ubuntu 系統上管理套件
  • ansible.builtin.yum - 在 RHEL/CentOS 系統上管理套件
  • ansible.builtin.service - 管理系統服務
  • ansible.builtin.copy - 複製檔案到遠端主機

在我的自動化工作流程中,我常組合使用不同模組來完成複雜任務。例如,先使用 copy 模組佈署設定,再用 service 模組重啟相關服務,最後用 uri 模組驗證服務是否正常執行。

2. 外掛 (Plugins)

外掛擴充套件 Ansible 的核心功能,包括:

  • Callback Plugins - 自訂輸出格式和通知方式
  • Connection Plugins - 定義如何連線到目標主機
  • Inventory Plugins - 從不同來源動態生成主機清單
  • Filter Plugins - 處理和轉換範本中的資料

我特別喜歡使用 Callback Plugins 來自訂輸出格式,讓執行結果更易於閱讀和分析,尤其是在處理大量主機時。

3. 集合 (Collections)

Ansible 2.9 版本後引入的概念,將相關模組、角色和外掛封裝成一個整體。使用集合可以更方便地管理和分享 Ansible 內容。安裝集合的方式:

ansible-galaxy collection install community.general

檢視已安裝的模組列表:

ansible-doc -l

檢視特定模組的詳細說明:

ansible-doc ansible.builtin.copy

這些指令在撰寫複雜 Playbook 時非常有用,可以快速查閱模組的引數和用法,避免反覆查閱檔案。

Ansible 環境建置與設定最佳化

各主要 Linux 發行版的安裝方法

Debian/Ubuntu 系統

sudo apt update
sudo apt install ansible

RHEL/CentOS/Fedora 系統

在 RHEL 8/CentOS 8/Fedora 系統上,使用 dnf 安裝:

sudo dnf install ansible

對於 RHEL 7/CentOS 7,則使用 yum:

sudo yum install epel-release
sudo yum install ansible

使用 pip 安裝 Ansible 的優勢

使用 pip 安裝 Ansible 的主要優勢是可以安裝最新版本,並且能夠在不同的 Python 虛擬環境中安裝不同版本的 Ansible。

# 檢查 Python 版本
python3 --version

# 安裝 pip(如果尚未安裝)
sudo apt install python3-pip  # Debian/Ubuntu
sudo dnf install python3-pip  # RHEL/CentOS 8/Fedora

# 使用 pip 安裝 Ansible
pip3 install ansible

# 建立虛擬環境
python3 -m venv ~/ansible_env

# 啟動虛擬環境
source ~/ansible_env/bin/activate

# 在虛擬環境中安裝 Ansible
pip install ansible

使用虛擬環境的另一個好處是可以為不同專案建立獨立的 Ansible 環境,避免版本衝突。

Ansible 設定的關鍵設定

Ansible 的設定通常位於 /etc/ansible/ansible.cfg,但也可以放在專案目錄中的 ansible.cfg 或使用者主目錄的 .ansible.cfg。Ansible 會按照以下順序尋找設定:

  1. 環境變數 ANSIBLE_CONFIG 指定的檔案
  2. 當前目錄中的 ansible.cfg
  3. 使用者主目錄中的 .ansible.cfg
  4. /etc/ansible/ansible.cfg

以下是一些重要的設定選項:

[defaults]
# 主機清單檔案位置
inventory = ./inventory

# 平行執行的任務數
forks = 20

# SSH 連線超時時間(秒)
timeout = 30

# 是否顯示任務執行時間
callback_whitelist = profile_tasks

# 停用 SSH 主機金鑰檢查(僅用於測試環境)
host_key_checking = False

# 設定模組路徑
library = ./library

# 設定角色路徑
roles_path = ./roles

[privilege_escalation]
# 是否預設使用 sudo
become = True

# sudo 使用的使用者
become_user = root

# 提權方式(sudo, su, pbrun, pfexec, doas, dzdo, ksu, runas)
become_method = sudo

# 是否詢問 sudo 密碼
become_ask_pass = False

在實際工作中,我會根據不同專案的需求調整這些設定。例如,在管理大量伺服器時,會增加 forks 值以提高平行執行效率;在處理敏感環境時,會確保 host_key_checking = True 以增強安全性。

Ansible 指令操作:從基礎到進階

Ansible 指令基本結構與常用選項

Ansible 指令的基本結構如下:

ansible [主機或群組] -m [模組名稱] -a "[模組引數]" [其他選項]

常用選項包括:

  • -i, --inventory:指定主機清單檔案
  • -u, --user:指定連線使用者
  • -k, --ask-pass:提示輸入 SSH 密碼
  • -b, --become:使用許可權提升(如 sudo)
  • -K, --ask-become-pass:提示輸入許可權提升密碼
  • -C, --check:模擬執行,不實際變更系統
  • -D, --diff:顯示檔案變更的差異
  • -v, --verbose:增加輸出詳細程度(-v, -vv, -vvv)

主機清單(Inventory)設定與管理

主機清單是 Ansible 管理的目標系統列表,可以是靜態檔案或動態指令碼。以下是一個靜態主機清單範例:

# 基本主機群組
[webservers]
web1.example.com
web2.example.com ansible_host=192.168.1.102

# 資料函式庫伺服器
[dbservers]
db1.example.com ansible_host=192.168.1.201
db2.example.com ansible_host=192.168.1.202

# 使用範圍表示法
[workers]
worker[01:20].example.com

# 群組的群組
[production:children]
webservers
dbservers

# 群組變數
[webservers:vars]
http_port=80
proxy_env={'http_proxy':'http://proxy.example.com:8080'}

# 主機特定變數
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=/home/deploy/.ssh/id_rsa

在管理大型基礎設施時,我通常會使用動態清單指令碼從雲端平台或 CMDB 系統取得主機資訊。這樣可以確保 Ansible 總是使用最新的主機資訊,避免手動維護主機清單的麻煩。

使用 ansible 指令測試連線與執行簡單任務

連線測試是使用 Ansible 的第一步。使用 ping 模組可以快速檢查是否能夠連線到目標主機:

ansible all -m ping -i inventory

成功的輸出應該類別似:

web1.example.com | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

如果連線失敗,可能需要調整 SSH 設定或檢查網路連線。

執行簡單的系統指令:

# 檢查系統資訊
ansible webservers -m command -a "uname -a" -i inventory

# 檢查磁碟空間
ansible all -m shell -a "df -h" -i inventory

# 檢查記憶體使用情況
ansible dbservers -m shell -a "free -m" -i inventory

commandshell 模組的主要區別在於,command 模組直接執行指令而不透過 shell,因此不支援 shell 特性如管道、重定向等;而 shell 模組則透過 /bin/sh 執行指令,支援完整的 shell 功能。

檔案管理與許可權控制

Ansible 提供了多種模組來管理檔案和許可權:

# 複製檔案到遠端主機
ansible webservers -m copy -a "src=/local/path/file.conf dest=/etc/service/file.conf owner=root group=root mode=0644" -i inventory

# 建立目錄
ansible all -m file -a "path=/opt/data state=directory mode=0755 owner=app group=app" -i inventory

# 修改檔案許可權
ansible dbservers -m file -a "path=/var/lib/mysql state=directory mode=0700 owner=mysql group=mysql recurse=yes" -i inventory

# 下載檔案
ansible webservers -m get_url -a "url=https://example.com/archive.tar.gz dest=/tmp/archive.tar.gz checksum=sha256:3733cd..." -i inventory

# 解壓縮檔案
ansible webservers -m unarchive -a "src=/tmp/archive.tar.gz dest=/opt/app remote_src=yes" -i inventory

在實際工作中,我發現 template 模組特別有用,它可以根據 Jinja2 範本生成設定:

ansible webservers -m template -a "src=templates/nginx.conf.j2 dest=/etc/nginx/nginx.conf" -i inventory

這使得設定管理變得更加靈活,可以根據不同環境的變數生成相應的設定。

套件管理與服務控制

Ansible 可以輕鬆管理各種 Linux 發行版的套件和服務:

# 在 Debian/Ubuntu 系統上安裝套件
ansible webservers -m apt -a "name=nginx state=present update_cache=yes" -i inventory

# 在 RHEL/CentOS 系統上安裝套件
ansible dbservers -m yum -a "name=mariadb-server state=latest" -i inventory

# 管理服務狀態
ansible webservers -m service -a "name=nginx state=started enabled=yes" -i inventory

# 重啟服務
ansible dbservers -m service -a "name=mariadb state=restarted" -i inventory

使用 Ansible 的套件管理模組,可以確保所有系統上安裝的軟體版本一致,減少因版本差異導致的問題。

Ansible Playbook:自動化工作流程的藝術

Playbook 基本結構與語法

Playbook 由一個或多個 play 組成,每個 play 定義要在特定主機上執行的任務集合。以下是一個基本的 Playbook 結構:

---
- name: 安裝並設定 Web 伺服器
  hosts: webservers
  become: true
  vars:
    http_port: 80
    max_clients: 200
  tasks:
    - name: 安裝 Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
    - name: 佈署 Nginx 設定
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: 重啟 Nginx
    - name: 確保 Nginx 服務啟動
      service:
        name: nginx
        state: started
        enabled: yes
  handlers:
    - name: 重啟 Nginx
      service:
        name: nginx
        state: restarted

這個 Playbook 包含了以下元素:

  • Play 定義:指定目標主機群組和執行環境
  • 變數:定義可在任務中使用的值
  • 任務:按順序執行的操作
  • 處理程式:僅在被通知時執行的特殊任務

變數、條件與迴圈:增強 Playbook 彈性

變數使 Playbook 更具彈性,可以適應不同環境和需求:

---
- name: 安裝資料函式庫伺服器
  hosts: dbservers
  become: true
  vars:
    db_name: myapp
    db_user: appuser
    db_password: "{{ vault_db_password }}" # 使用 Ansible Vault 加密的變數
  tasks:
    - name: 安裝 MariaDB
      yum:
        name:
          - mariadb-server
          - python3-PyMySQL
        state: present
    - name: 啟動 MariaDB 服務
      service:
        name: mariadb
        state: started
        enabled: yes
    - name: 建立資料函式庫
      mysql_db:
        name: "{{ db_name }}"
        state: present
        login_unix_socket: /var/lib/mysql/mysql.sock
    - name: 建立資料函式庫使用者
      mysql_user:
        name: "{{ db_user }}"
        password: "{{ db_password }}"
        priv: "{{ db_name }}.*:ALL"
        host: '%'
        state: present
        login_unix_socket: /var/lib/mysql/mysql.sock

條件陳述式允許根據特定條件執行任務:

- name: 安裝 Web 伺服器
  apt:
    name: apache2
    state: present
  when: ansible_distribution == 'Ubuntu'

- name: 安裝 Web 伺服器
  yum:
    name: httpd
    state: present
  when: ansible_distribution == 'CentOS'

迴圈可以對多個專案執行相同的任務:

- name: 建立多個使用者
  user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
    shell: /bin/bash
  loop:
    - { name: 'john', groups: 'admin' }
    - { name: 'jane', groups: 'developer' }
    - { name: 'bob', groups: 'support' }

在實際工作中,我經常結合使用條件和迴圈來處理不同環境的差異。例如,根據伺服器角色安裝不同的套件,或者根據環境(開發、測試、生產)應用不同的設定。

Handlers:優雅處理設定變更

Handlers 是一種特殊的任務,只有在被通知時才會執行。這對於處理設定變更特別有用,例如只在設定變更時重啟服務:

tasks:
  - name: 佈署 Apache 設定
    template:
      src: templates/httpd.conf.j2
      dest: /etc/httpd/conf/httpd.conf
    notify: 重啟 Apache
  - name: 佈署虛擬主機設定
    template:
      src: templates/vhost.conf.j2
      dest: /etc/httpd/conf.d/vhost.conf
    notify: 重啟 Apache

handlers:
  - name: 重啟 Apache
    service:
      name: httpd
      state: restarted

即使多個任務都通知同一個 handler,該 handler 也只會在 play 結束時執行一次。這避免了不必要的服務重啟,提高了效率和可靠性。

角色 (Roles):模組化 Playbook

隨著 Playbook 變得越來越複雜,將相關任務組織成角色可以提高程式碼的可重用性和可維護性。角色是一種預定義的目錄結構,包含任務、處理程式、變數、檔案和範本:

roles/
  webserver/
    tasks/
      main.yml
    handlers/
      main.yml
    templates/
      nginx.conf.j2
    files/
      index.html
    vars/
      main.yml
    defaults/
      main.yml
    meta/
      main.yml

使用角色的 Playbook 範例:

---
- name: 設定 Web 伺服器
  hosts: webservers
  become: true
  roles:
    - common
    - webserver

- name: 設定資料函式庫伺服器
  hosts: dbservers
  become: true
  roles:
    - common
    - database

在我的自動化專案中,我通常會建立以下角色:

  • common:適用於所有伺服器的基本設定(安全性、監控、日誌等)
  • webserver:Web 伺服器特定設定
  • database:資料函式庫伺服器特定設定
  • application:應用程式佈署和設定

這種模組化方法使得維護和擴充套件自動化工作流程變得更加容易。

Ansible 模組:自動化工具箱

系統管理模組

這些模組用於管理系統基本功能:

ping 模組

測試目標主機的連線狀態:

- name: 測試連線
  ping:

command 和 shell 模組

在遠端主機上執行指令:

- name: 執行簡單指令
  command: ls -la /var/log
  register: command_output

- name: 使用 shell 特性
  shell: find /var/log -name "*.log" | grep error
  register: shell_output

- name: 顯示結果
  debug:
    var: command_output.stdout_lines

command 模組不支援 shell 特性(如管道、重定向),但更安全;shell 模組則支援完整的 shell 功能。

file 模組

管理檔案、目錄和符號連結:

- name: 建立目錄
  file:
    path: /opt/application
    state: directory
    mode: '0755'
    owner: app
    group: app

- name: 建立符號連結
  file:
    src: /opt/application/current
    dest: /var/www/html
    state: link
    force: yes

- name: 刪除檔案
  file:
    path: /tmp/temp_file
    state: absent

copy 和 template 模組

管理檔案內容:

- name: 複製靜態檔案
  copy:
    src: files/ntp.conf
    dest: /etc/ntp.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes

- name: 使用範本生成設定
  template:
    src: templates/nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    validate: 'nginx -t -c %s'

template 模組使用 Jinja2 範本引擎,可以根據變數生成動態設定。這在管理不同環境的設定時特別有用。

套件管理模組

不同 Linux 發行版使用不同的套件管理系統,Ansible 提供了相應的模組:

apt 模組 (Debian/Ubuntu)

- name: 更新套件快取
  apt:
    update_cache: yes
    cache_valid_time: 3600

- name: 安裝多個套件
  apt:
    name:
      - nginx
      - postgresql
      - python3-psycopg2
    state: present

- name: 移除套件
  apt:
    name: apache2
    state: absent
    purge: yes

yum/dnf 模組 (RHEL/CentOS/Fedora)

- name: 安裝最新版本的套件
  yum:
    name: httpd
    state: latest

- name: 從特定儲存函式庫安裝套件
  yum:
    name: nginx
    enablerepo: epel
    state: present

- name: 安裝特定版本的套件
  dnf:
    name: python3-3.9.5
    state: present

服務管理模組

service 模組

管理系統服務的狀態:

- name: 啟動服務
  service:
    name: nginx
    state: started
    enabled: yes

- name: 重啟服務
  service:
    name: postgresql
    state: restarted

- name: 停止服務
  service:
    name: httpd
    state: stopped
    enabled: no

systemd 模組

專門用於管理 systemd 服務:

- name: 啟動並啟用 systemd 服務
  systemd:
    name: mariadb
    state: started
    enabled: yes
    daemon_reload: yes

- name: 重啟服務並等待完成
  systemd:
    name: docker
    state: restarted
    daemon_reload: yes

使用者和群組管理模組

user 模組

管理系統使用者:

- name: 建立使用者
  user:
    name: deploy
    comment: "Deployment User"
    shell: /bin/bash
    groups: sudo
    append: yes
    create_home: yes
    generate_ssh_key: yes
    ssh_key_bits: 4096

- name: 刪除使用者
  user:
    name: temp_user
    state: absent
    remove: yes

group 模組

管理系統群組:

- name: 建立群組
  group:
    name: developers
    state: present
    gid: 1005

- name: 刪除群組
  group:
    name: old_group
    state: absent

資料函式倉管理模組

Ansible 提供了多種資料函式倉管理模組,例如 mysql_dbpostgresql_db 等:

- name: 確保 MySQL 套件已安裝
  apt:
    name:
      - mysql-server
      - python3-mysqldb
    state: present

- name: 建立 MySQL 資料函式庫
  mysql_db:
    name: webapp
    state: present
    login_unix_socket: /var/run/mysqld/mysqld.sock

- name: 建立資料函式庫使用者
  mysql_user:
    name: webapp_user
    password: "{{ db_password }}"
    priv: "webapp.*:ALL"
    host: '%'
    state: present
    login_unix_socket: /var/run/mysqld/mysqld.sock

- name: 匯入資料函式庫結構
  mysql_db:
    name: webapp
    state: import
    target: /tmp/schema.sql
    login_unix_socket: /var/run/mysqld/mysqld.sock

Ansible 實戰案例:從開發到生產

案例一:自動化 LAMP 堆積積疊佈署

這個案例展示如何使用 Ansible 自動佈署 LAMP (Linux, Apache, MySQL, PHP) 堆積積疊:

---
- name: 佈署 LAMP 堆積積疊
  hosts: webservers
  become: true
  vars:
    mysql_root_password: "{{ vault_mysql_root_password }}"
    app_db_name: myapp
    app_db_user: myapp_user
    app_db_password: "{{ vault_app_db_password }}"
  tasks:
    - name: 更新套件快取
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: 安裝 LAMP 套件
      apt:
        name:
          - apache2
          - mysql-server
          - php
          - php-mysql
          - libapache2-mod-php
          - python3-pymysql
        state: present

    - name: 啟動並啟用 Apache 服務
      service:
        name: apache2
        state: started
        enabled: yes

    - name: 啟動並啟用 MySQL 服務
      service:
        name: mysql
        state: started
        enabled: yes

    - name: 設定 MySQL root 密碼
      mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
        state: present

    - name: 建立應用程式資料函式庫
      mysql_db:
        name: "{{ app_db_name }}"
        state: present
        login_unix_socket: /var/run/mysqld/mysqld.sock

    - name: 建立應用程式資料函式庫使用者
      mysql_user:
        name: "{{ app_db_user }}"
        password: "{{ app_db_password }}"
        priv: "{{ app_db_name }}.*:ALL"
        host: localhost
        state: present
        login_unix_socket: /var/run/mysqld/mysqld.sock

    - name: 佈署 PHP 測試頁面
      copy:
        content: |
          <?php
          phpinfo();
          ?>
        dest: /var/www/html/info.php
        owner: www-data
        group: www-data
        mode: '0644'

    - name: 設定 Apache 虛擬主機
      template:
        src: templates/vhost.conf.j2
        dest: /etc/apache2/sites-available/000-default.conf
      notify: 重啟 Apache

  handlers:
    - name: 重啟 Apache
      service:
        name: apache2
        state: restarted

這個 Playbook 完成了以下任務:

  1. 安裝所有必要的 LAMP 套件
  2. 設定 MySQL 資料函式庫和使用者
  3. 佈署測試頁面
  4. 設定 Apache 虛擬主機

案例二:系統安全強化

這個案例展示如何使用 Ansible 自動化系統安全強化:

---
- name: 系統安全強化
  hosts: all
  become: true
  vars:
    ssh_port: 22022
    allowed_users:
      - admin
      - deploy
  tasks:
    - name: 更新所有套件
      apt:
        upgrade: dist
        update_cache: yes
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'

    - name: 安裝安全相關套件
      apt:
        name:
          - fail2ban
          - ufw
          - unattended-upgrades
        state: present
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'

    - name: 設定自動更新
      template:
        src: templates/20auto-upgrades.j2
        dest: /etc/apt/apt.conf.d/20auto-upgrades
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'

    - name: 設定 SSH 伺服器
      template:
        src: templates/sshd_config.j2
        dest: /etc/ssh/sshd_config
      notify: 重啟 SSH 服務

    - name: 設定防火牆規則
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - "{{ ssh_port }}"
        - 80
        - 443
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'

    - name: 啟用防火牆
      ufw:
        state: enabled
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'

    - name: 設定 fail2ban
      template:
        src: templates/jail.local.j2
        dest: /etc/fail2ban/jail.local
      notify: 重啟 fail2ban

    - name: 停用 root 登入
      user:
        name: root
        password_lock: yes

    - name: 設定密碼策略
      lineinfile:
        path: /etc/pam.d/common-password
        regexp: '^password\s+requisite\s+pam_pwquality.so'
        line: 'password requisite pam_pwquality.so retry=3 minlen=12 difok=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root'
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'

  handlers:
    - name: 重啟 SSH 服務
      service:
        name: sshd
        state: restarted

    - name: 重啟 fail2ban
      service:
        name: fail2ban
        state: restarted

這個 Playbook 實作了多項安全強化措施:

  1. 更新系統套件
  2. 安裝和設定安全工具(fail2ban、防火牆)
  3. 強化 SSH 設定
  4. 設定密碼策略
  5. 停用 root 登入

案例三:自動化備份與還原

這個案例展示如何使用 Ansible 自動化資料函式庫備份與還原:

---
- name: 資料函式庫備份
  hosts: dbservers
  become: true
  vars:
    backup_dir: /var/backups/mysql
    remote_backup_server: backup.example.com
    remote_backup_user: backup
    remote_backup_path: /data/backups
    databases:
      - name: webapp
        tables: all
      - name: analytics
        tables:
          - users
          - events
  tasks:
    - name: 確保備份目錄存在
      file:
        path: "{{ backup_dir }}"
        state: directory
        mode: '0750'
        owner: mysql
        group: mysql

    - name: 備份完整資料函式庫
      mysql_db:
        state: dump
        name: "{{ item.name }}"
        target: "{{ backup_dir }}/{{ item.name }}_{{ ansible_date_time.date }}.sql"
        login_unix_socket: /var/run/mysqld/mysqld.sock
      loop: "{{ databases | selectattr('tables', 'equalto', 'all') | list }}"

    - name: 備份特定資料表
      mysql_db:
        state: dump
        name: "{{ item.0.name }}"
        target: "{{ backup_dir }}/{{ item.0.name }}_{{ item.1 }}_{{ ansible_date_time.date }}.sql"
        tables: "{{ item.1 }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
      with_subelements:
        - "{{ databases | rejectattr('tables', 'equalto', 'all') | list }}"
        - tables

    - name: 壓縮備份檔案
      archive:
        path: "{{ backup_dir }}/*.sql"
        dest: "{{ backup_dir }}/mysql_backup_{{ ansible_date_time.date }}.tar.gz"
        format: gz
        remove: yes

    - name: 將備份傳輸到遠端伺服器
      synchronize:
        src: "{{ backup_dir }}/mysql_backup_{{ ansible_date_time.date }}.tar.gz"
        dest: "{{ remote_backup_path }}/"
        mode: push
      delegate_to: "{{ inventory_hostname }}"

    - name: 清理舊備份檔案
      find:
        paths: "{{ backup_dir }}"
        patterns: "mysql_backup_*.tar.gz"
        age: 7d
      register: old_backups

    - name: 刪除舊備份檔案
      file:
        path: "{{ item.path }}"
        state: absent
      loop: "{{ old_backups.files }}"

這個 Playbook 實作了完整的資料函式庫備份流程:

  1. 備份指定的資料函式庫或資料表
  2. 壓縮備份檔案
  3. 將備份傳輸到遠端備份伺服器
  4. 自動清理過期的備份檔案

Ansible 進階技巧與最佳實踐

Ansible Vault:安全管理敏感資料

在自動化過程中,我們經常需要處理敏感資料,如密碼、API 金鑰等。Ansible Vault 提供了一種安全的方式來加密這些敏感資訊:

# 建立加密檔案
ansible-vault create secrets.yml

# 編輯加密檔案
ansible-vault edit secrets.yml

# 加密現有檔案
ansible-vault encrypt vars/credentials.yml

# 解密檔案
ansible-vault decrypt vars/credentials.yml

# 檢視加密檔案內容
ansible-vault view secrets.yml

# 修改密碼
ansible-vault rekey secrets.yml

在 Playbook 中使用加密變數:

---
- name: 使用加密變數的範例
  hosts: webservers
  vars_files:
    - vars/secrets.yml
  tasks:
    - name: 設定資料函式庫連線
      template:
        src: templates/db_config.j2
        dest: /etc/app/db_config.php
      vars:
        db_host: localhost
        db_name: myapp
        db_user: "{{ vault_db_user }}"
        db_password: "{{ vault_db_password }}"

執行使用加密檔案的 Playbook:

ansible-playbook playbook.yml --ask-vault-pass
# 或使用密碼檔案
ansible-playbook playbook.yml --vault-password-file ~/.vault_pass.txt

在團隊協作中,我通常會將 Vault 密碼儲存在安全的密碼管理系統中,並確保所有團隊成員都能安全地存取它。

動態庫存:管理雲端環境

在雲端環境中,伺服器可能會動態建立和銷毀,手動維護庫存檔案變得不切實際。Ansible 提供了動態庫存功能,可以從雲端提供商或 CMDB 系統取得最新的主機資訊。

以 AWS 為例,首先安裝 boto3 函式庫:

pip install boto3

然後設定 AWS 動態庫存指令碼:

# 下載 AWS 動態庫存指令碼
wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/aws_ec2.py
chmod +x aws_ec2.py

# 建立設定
cat > aws_ec2.yml << EOF
---
plugin: aws_ec2
regions:
  - ap-northeast-1
keyed_groups:
  - key: tags.Role
    prefix: role
  - key: tags.Environment
    prefix: env
filters:
  instance-state-name: running
EOF

使用動態庫存執行 Playbook:

ansible-playbook -i aws_ec2.yml playbook.yml

這樣,Ansible 會自動取得 AWS 中所有執行中的 EC2 例項,並根據標籤將它們分組。例如,標記為 Role: webserver 的例項會被分到 role_webserver 組中。

使用 Ansible Galaxy 分享和重用角色

Ansible Galaxy 是一個分享、下載和評價 Ansible 角色的平台。使用 Galaxy 可以避免重複造輪子,利用社群的力量提高自動化效率。

搜尋角色:

ansible-galaxy search nginx

安裝角色:

ansible-galaxy install geerlingguy.nginx

在 Playbook 中使用安裝的角色:

---
- name: 設定 Web 伺服器
  hosts: webservers
  become: true
  roles:
    - geerlingguy.nginx

建立自己的角色:

ansible-galaxy init my_custom_role

這會建立一個標準的角色目錄結構,可以根據需要進行自定義。

使用標籤 (Tags) 提高靈活性

標籤允許選擇性地執行 Playbook 中的特定任務,這在處理大型 Playbook 時特別有用:

---
- name: 設定 Web 應用程式
  hosts: webservers
  become: true
  tasks:
    - name: 安裝套件
      apt:
        name:
          - nginx
          - php-fpm
        state: present
      tags:
        - packages
        - nginx

    - name: 設定 Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: 重啟 Nginx
      tags:
        - config
        - nginx

    - name: 佈署應用程式
      git:
        repo: https://github.com/example/webapp.git
        dest: /var/www/webapp
        version: master
      tags:
        - deploy
        - webapp

  handlers:
    - name: 重啟 Nginx
      service:
        name: nginx
        state: restarted
      tags: nginx

執行特定標記的任務:

# 只執行標記為 nginx 的任務
ansible-playbook playbook.yml --tags nginx

# 執行多個標籤
ansible-playbook playbook.yml --tags "config,deploy"

# 跳過特定標籤
ansible-playbook playbook.yml --skip-tags packages

在大型專案中,我通常會為不同型別的任務設定標籤,如 setupconfigdeploybackup 等,這樣可以根據需要選擇性地執行特定部分。

使用 Ansible 回呼外掛自訂輸出

Ansible 回呼外掛可以自訂任務執行的輸出格式,使結果更易於閱讀和分析:

# ansible.cfg
[defaults]
callback_whitelist = profile_tasks, timer, mail

常用的回呼外掛包括:

  • profile_tasks:顯示每個任務的執行時間
  • timer:顯示 Playbook 總執行時間
  • mail:透過電子郵件傳送執行結果
  • slack:將執行結果傳送到 Slack 頻道

在大型佈署中,我經常使用 profile_tasks 外掛來識別耗時的任務,然後針對這些任務進行最佳化。