在現代 IT 環境中,定時任務是系統維運的重要組成部分。透過 Ansible,我們可以輕鬆地在多台伺服器上統一管理 Cron 任務,確保關鍵操作如備份、日誌清理和系統更新能夠按時執行。

基本 Cron 任務設定一個基本的每日備份任務相當簡單:

- name: 設定基本備份 Cron 任務
  hosts: webservers
  become: true
  tasks:
    - name: 新增每日備份任務
      ansible.builtin.cron:
        name: "每日備份"
        hour: "2"
        minute: "0"
        job: "/usr/local/bin/backup.sh > /var/log/backup.log 2>&1"

這個 Playbook 會在所有 webservers 群組的主機上建立一個名為「每日備份」的 Cron 任務,設定在每天凌晨 2:00 執行 backup.sh 指令碼,並將輸出導向至日誌檔案。become: true 表示以 root 許可權執行,確保有足夠許可權修改 crontab。

使用特殊時間規格

Ansible 支援 Cron 的特殊時間關鍵字,讓排程更加直覺:

- name: 設定特殊時間規格的 Cron 任務
  hosts: all
  become: true
  tasks:
    - name: 每週執行系統更新
      ansible.builtin.cron:
        name: "每週系統更新"
        special_time: weekly
        job: "apt-get update && apt-get upgrade -y > /var/log/weekly_upgrade.log 2>&1"
        
    - name: 每日清理暫存檔案
      ansible.builtin.cron:
        name: "每日暫存清理"
        special_time: daily
        job: "find /tmp -type f -mtime +7 -delete"

這個 Playbook 使用 special_time 引數設定兩個任務:一個每週執行系統更新,另一個每日清理暫存檔案。Ansible 支援的特殊時間值包括 rebootyearlyannuallymonthlyweeklydailyhourly,讓排程更加人性化。

為特定使用者設定 Cron 任務

有時我們需要以特定使用者身分執行 Cron 任務,特別是對於資料函式庫備份這類別操作:

- name: 設定使用者特定的 Cron 任務
  hosts: all
  become: true
  tasks:
    - name: 為 dbadmin 使用者新增資料函式庫備份任務
      ansible.builtin.cron:
        name: "資料函式庫備份"
        user: dbadmin
        hour: "*/4"
        minute: "0"
        job: "/home/dbadmin/scripts/db_backup.sh"

這個任務會在 dbadmin 使用者的 crontab 中新增一個每 4 小時執行一次的備份任務。透過指定 user 引數,Ansible 會修改該使用者的 crontab,而非 root 使用者的。這對於需要特定許可權或環境的任務非常有用。

設定環境變數

Cron 任務通常需要特定的環境變數才能正確執行:

- name: 設定帶環境變數的 Cron 任務
  hosts: all
  become: true
  tasks:
    - name: 新增帶 PATH 的日誌輪轉任務
      ansible.builtin.cron:
        name: "日誌輪轉"
        hour: "0"
        minute: "0"
        job: "rotate_logs.sh"
        env:
          PATH: "/usr/local/bin:/usr/bin:/bin"
          MAILTO: "admin@example.com"

這個任務設定了兩個環境變數:PATH 確保系統能找到 rotate_logs.sh 指令碼,而 MAILTO 則指定任務執行結果的郵件接收者。在 Cron 環境中設定正確的 PATH 變數尤為重要,因為 Cron 預設的 PATH 通常很有限。

移除 Cron 任務

當某些任務不再需要時,我們可以輕鬆移除它們:

- name: 移除過時的 Cron 任務
  hosts: all
  become: true
  tasks:
    - name: 移除舊的備份任務
      ansible.builtin.cron:
        name: "舊備份任務"
        state: absent

這個任務使用 state: absent 引數移除名為「舊備份任務」的 Cron 專案。Ansible 會根據任務名稱找到對應的 Cron 專案並將其刪除,這比手動編輯 crontab 檔案安全得多。

批次管理 Cron 任務

對於需要管理多個 Cron 任務的情況,使用變數檔案是個好方法:

# cron_jobs.yml
cron_jobs:
  - name: "系統更新"
    special_time: weekly
    job: "apt-get update && apt-get upgrade -y"
  - name: "資料函式庫備份"
    hour: "3"
    minute: "30"
    job: "/usr/local/bin/db_backup.sh"
  - name: "日誌清理"
    hour: "1"
    minute: "0"
    day: "*/3"
    job: "find /var/log -name \"*.gz\" -mtime +30 -delete"

然後在 Playbook 中使用這個變數檔案:

- name: 設定多個 Cron 任務
  hosts: all
  become: true
  vars_files:
    - cron_jobs.yml
  tasks:
    - name: 設定 Cron 任務
      ansible.builtin.cron:
        name: "{{ item.name }}"
        hour: "{{ item.hour | default(omit) }}"
        minute: "{{ item.minute | default(omit) }}"
        day: "{{ item.day | default(omit) }}"
        month: "{{ item.month | default(omit) }}"
        weekday: "{{ item.weekday | default(omit) }}"
        special_time: "{{ item.special_time | default(omit) }}"
        job: "{{ item.job }}"
      loop: "{{ cron_jobs }}"

這個方法將 Cron 任務定義與執行邏輯分離,使管理更加靈活。使用 default(omit) 過濾器確保只傳遞已定義的引數,避免因未定義引數而導致的錯誤。loop 指令讓我們能夠迭代處理每個任務定義。

條件式 Cron 任務設定

根據主機角色設定不同的 Cron 任務是自動化管理的常見需求:

- name: 設定角色特定的 Cron 任務
  hosts: all
  become: true
  tasks:
    - name: 為資料函式庫伺服器新增備份任務
      ansible.builtin.cron:
        name: "資料函式庫備份"
        hour: "2"
        minute: "0"
        job: "/usr/local/bin/db_backup.sh"
      when: "'database_servers' in group_names"
      
    - name: 為網頁伺服器新增日誌輪轉
      ansible.builtin.cron:
        name: "網頁日誌輪轉"
        hour: "1"
        minute: "0"
        job: "/usr/local/bin/rotate_web_logs.sh"
      when: "'web_servers' in group_names"

這個 Playbook 使用 when 條件根據主機所屬群組決定是否執行特定任務。group_names 是 Ansible 的內建變數,包含目標主機所屬的所有群組。這種方法讓我們能夠在單一 Playbook 中處理不同型別的伺服器,大幅提高管理效率。

Ansible 與 Docker 整合:容器化佈署自動化

Docker 已成為現代應用佈署的標準工具,而 Ansible 提供了豐富的模組來管理 Docker 容器、映像和網路。這種組合讓我們能夠實作容器化應用的完整自動化生命週期管理。

安裝 Docker

首先,讓我們使用 Ansible 來安裝 Docker:

- name: 安裝 Docker
  hosts: docker_hosts
  become: true
  tasks:
    - name: 安裝必要套件
      ansible.builtin.apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
          - lsb-release
        state: present
        update_cache: yes
        
    - name: 新增 Docker GPG 金鑰
      ansible.builtin.apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present
        
    - name: 新增 Docker 儲存函式庫
      ansible.builtin.apt_repository:
        repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
        state: present
        
    - name: 安裝 Docker 引擎
      ansible.builtin.apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
        state: present
        update_cache: yes
        
    - name: 啟動並啟用 Docker 服務
      ansible.builtin.service:
        name: docker
        state: started
        enabled: yes

這個 Playbook 執行了安裝 Docker 的完整流程:先安裝必要的依賴套件,然後新增 Docker 的 GPG 金鑰和儲存函式庫,接著安裝 Docker 引擎,最後啟動並設定 Docker 服務為開機自動啟動。ansible_distribution_release 是 Ansible 的內建變數,會自動偵測目標系統的發行版本,確保安裝正確版本的 Docker。

管理 Docker 映像

使用 docker_image 模組可以輕鬆管理 Docker 映像:

- name: 管理 Docker 映像
  hosts: docker_hosts
  become: true
  tasks:
    - name: 提取 Nginx 映像
      community.docker.docker_image:
        name: nginx:latest
        source: pull
        
    - name: 提取 PostgreSQL 映像
      community.docker.docker_image:
        name: postgres:13
        source: pull
        
    - name: 移除舊映像
      community.docker.docker_image:
        name: "{{ item }}"
        state: absent
      loop:
        - "nginx:1.18"
        - "postgres:12"

這個 Playbook 展示了三種常見的 Docker 映像管理操作:提取最新的 Nginx 映像、提取特定版本的 PostgreSQL 映像,以及移除不再需要的舊版映像。使用 loop 指令可以一次處理多個映像,提高效率。

執行 Docker 容器

使用 docker_container 模組可以管理 Docker 容器的生命週期:

- name: 執行 Docker 容器
  hosts: docker_hosts
  become: true
  tasks:
    - name: 執行 Nginx 容器
      community.docker.docker_container:
        name: web
        image: nginx:latest
        state: started
        restart_policy: always
        ports:
          - "80:80"
        volumes:
          - "/data/nginx/html:/usr/share/nginx/html:ro"
          
    - name: 執行 PostgreSQL 容器
      community.docker.docker_container:
        name: db
        image: postgres:13
        state: started
        restart_policy: always
        env:
          POSTGRES_PASSWORD: "{{ db_password }}"
          POSTGRES_USER: "{{ db_user }}"
          POSTGRES_DB: "{{ db_name }}"
        ports:
          - "5432:5432"
        volumes:
          - "/data/postgres:/var/lib/postgresql/data"

這個 Playbook 啟動了兩個容器:一個 Nginx 網頁伺服器和一個 PostgreSQL 資料函式庫。對於每個容器,我們設定了名稱、使用的映像、啟動狀態、重啟策略、連線埠對映和卷掛載。對於 PostgreSQL 容器,我們還設定了環境變數來指定資料函式庫憑證。restart_policy: always 確保容器在發生錯誤或系統重啟後能自動重新啟動。

使用 Docker Compose

對於多容器應用,Docker Compose 是更好的選擇。Ansible 可以輕鬆管理 Docker Compose 專案:

- name: 使用 Docker Compose 佈署
  hosts: docker_hosts
  become: true
  tasks:
    - name: 建立專案目錄
      ansible.builtin.file:
        path: /opt/myapp
        state: directory
        
    - name: 複製 docker-compose.yml
      ansible.builtin.template:
        src: templates/docker-compose.yml.j2
        dest: /opt/myapp/docker-compose.yml
        
    - name: 使用 Docker Compose 佈署
      community.docker.docker_compose:
        project_src: /opt/myapp
        state: present

docker-compose.yml.j2 範本:

version: '3'
services:
  web:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - app
  app:
    image: {{ app_image }}
    environment:
      DB_HOST: db
      DB_USER: {{ db_user }}
      DB_PASSWORD: {{ db_password }}
    depends_on:
      - db
  db:
    image: postgres:13
    environment:
      POSTGRES_USER: {{ db_user }}
      POSTGRES_PASSWORD: {{ db_password }}
      POSTGRES_DB: {{ db_name }}
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:

這個 Playbook 首先建立專案目錄,然後使用 Jinja2 範本生成 docker-compose.yml 檔案,最後使用 docker_compose 模組啟動服務。範本中的變數(如 {{ app_image }}{{ db_user }} 等)會在執行時被實際值替換。這種方法讓我們能夠在不同環境中重複使用相同的 Compose 檔案,只需調整變數即可。

建立自訂 Docker 映像

Ansible 也可以用來建立自訂 Docker 映像:

- name: 建立自訂 Docker 映像
  hosts: docker_hosts
  become: true
  tasks:
    - name: 建立建置目錄
      ansible.builtin.file:
        path: /tmp/docker_build
        state: directory
        
    - name: 複製 Dockerfile
      ansible.builtin.template:
        src: templates/Dockerfile.j2
        dest: /tmp/docker_build/Dockerfile
        
    - name: 複製應用程式檔案
      ansible.builtin.copy:
        src: files/app/
        dest: /tmp/docker_build/app/
        
    - name: 建置自訂映像
      community.docker.docker_image:
        name: myapp:{{ app_version }}
        build:
          path: /tmp/docker_build
        source: build

Dockerfile.j2 範本:

FROM python:3.9-slim
WORKDIR /app
COPY app/ .
RUN pip install -r requirements.txt
ENV PORT={{ app_port }}
EXPOSE {{ app_port }}
CMD ["python", "app.py"]

這個 Playbook 展示瞭如何使用 Ansible 建立自訂 Docker 映像。它首先建立一個臨時建置目錄,然後複製 Dockerfile 範本和應用程式檔案,最後使用 docker_image 模組的 build 引數建置映像。Dockerfile 範本中的變數(如 {{ app_port }})會在執行時被替換,讓我們能夠動態調整映像設定。

Docker 網路管理

管理 Docker 網路對於多容器應用至關重要:

- name: 管理 Docker 網路
  hosts: docker_hosts
  become: true
  tasks:
    - name: 建立自訂橋接網路
      community.docker.docker_network:
        name: app_network
        driver: bridge
        
    - name: 在自訂網路上執行容器
      community.docker.docker_container:
        name: "{{ item.name }}"
        image: "{{ item.image }}"
        networks:
          - name: app_network
        state: started
      loop:
        - { name: "web", image: "nginx:latest" }
        - { name: "app", image: "myapp:latest" }
        - { name: "db", image: "postgres:13" }

這個 Playbook 首先建立一個名為 app_network 的自訂橋接網路,然後啟動三個容器並將它們連線到這個網路。使用自訂網路可以讓容器之間透過容器名稱互相通訊,簡化了設定。loop 指令讓我們能夠一次處理多個容器,提高效率。

Docker Swarm 管理

對於需要高用性和負載平衡的應用,Docker Swarm 是個不錯的選擇:

- name: 初始化 Docker Swarm
  hosts: swarm_manager
  become: true
  tasks:
    - name: 初始化 Swarm
      community.docker.docker_swarm:
        state: present
      register: swarm_info
      
    - name: 儲存加入令牌
      ansible.builtin.set_fact:
        worker_token: "{{ swarm_info.swarm_facts.JoinTokens.Worker }}"

- name: 加入 Swarm 作為工作節點
  hosts: swarm_workers
  become: true
  tasks:
    - name: 加入 Swarm 叢集
      community.docker.docker_swarm:
        state: join
        join_token: "{{ hostvars['swarm_manager']['worker_token'] }}"
        remote_addrs: [ "{{ hostvars['swarm_manager']['ansible_default_ipv4']['address'] }}:2377" ]

這個 Playbook 包含兩個 play:第一個在管理節點上初始化 Swarm 並儲存工作節點的加入令牌;第二個讓工作節點使用這個令牌加入 Swarm。register 指令將初始化結果儲存在 swarm_info 變數中,然後我們使用 set_fact 提取工作節點令牌。在第二個 play 中,我們使用 hostvars 變數存取管理節點上的令牌和 IP 地址。

佈署 Docker Swarm 服務

在 Swarm 叢集上佈署服務:

- name: 在 Swarm 上佈署服務
  hosts: swarm_manager
  become: true
  tasks:
    - name: 佈署網頁服務
      community.docker.docker_swarm_service:
        name: web
        image: nginx:latest
        mode: replicated
        replicas: 3
        publish:
          - published_port: 80
            target_port: 80
        networks:
          - app_network
          
    - name: 佈署資料函式庫服務
      community.docker.docker_swarm_service:
        name: db
        image: postgres:13
        env:
          POSTGRES_PASSWORD: "{{ db_password }}"
          POSTGRES_USER: "{{ db_user }}"
        mounts:
          - source: db_data
            target: /var/lib/postgresql/data
            type: volume
        networks:
          - app_network

這個 Playbook 在 Swarm 叢集上佈署了兩個服務:一個具有三個副本的 Nginx 服務和一個 PostgreSQL 資料函式庫服務。對於網頁服務,我們設定了複製模式並指定了三個副本,這意味著 Swarm 會在叢集中維護三個容器例項,提供高用性和負載平衡。對於資料函式庫服務,我們使用了卷掛載來確保資料永續性。兩個服務都連線到同一個網路,使它們能夠互相通訊。

密碼安全性:Ansible 的最佳實踐

在系統管理中,密碼安全一直是個關鍵議題。在 Ansible 環境中,我們絕對不該在 Playbook 中直接使用明文密碼,而應該採用加密技術來保護敏感資訊。

使用 password_hash 過濾器加強安全性

Ansible 提供了 password_hash 過濾器,能將明文密碼轉換為加密雜湊值:

- name: 產生雜湊密碼
  hosts: localhost
  tasks:
    - name: 計算密碼雜湊值
      ansible.builtin.debug:
        msg: "{{ '安全密碼範例' | password_hash('sha512') }}"

執行此 Playbook 後,你會得到一個 SHA-512 加密的密碼雜湊值:

ok: [localhost] => {
  "msg": "$6$rounds=50000$UJWtdpxDHQbwG8DB$hG0SRgRpArdp9QOGJ3c4o7fVfW0Eq4N5QFh7tlShV3VVG48sTDwT.vOgJAEoPBINMuDzoE2rg00JMj4jDlb.l/"
}

這個雜湊值可以直接用於使用者建立或密碼更新任務中:

- name: 更新使用者密碼
  hosts: all
  become: true
  tasks:
    - name: 變更使用者密碼
      ansible.builtin.user:
        name: johndoe
        password: "$6$rounds=50000$UJWtdpxDHQbwG8DB$hG0SRgRpArdp9QOGJ3c4o7fVfW0Eq4N5QFh7tlShV3VVG48sTDwT.vOgJAEoPBINMuDzoE2rg00JMj4jDlb.l/"

這種方法不僅提高了安全性,還讓密碼管理變得更加系統化。即使有人取得了你的 Playbook,也無法輕易還原出原始密碼。password_hash 過濾器支援多種雜湊演算法,包括 SHA-512、SHA-256 和 MD5,但出於安全考量,建議使用 SHA-512。

開發完整的自動化使用者管理系統

在管理大型基礎設施時,手動建立和維護使用者帳號既耗時又容易出錯。以下是一個完整的解決方案,整合了前面討論的技術。

使用者資料集中管理

首先,我們需要一個集中存放使用者資訊的 YAML 檔案:

---
users:
  - name: johndoe
    password: "{{ 'Password123' | password_hash('sha512') }}"
    home: /home/johndoe
    shell: /bin/bash
    groups: developers
    state: present
    ssh_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq5X0..."
  - name: janedoe
    password: "{{ 'Password456' | password_hash('sha512') }}"
    home: /home/janedoe
    shell: /bin/bash
    groups: developers
    state: present
    ssh_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvG1l..."
  - name: alice
    password: "{{ 'Password789' | password_hash('sha512') }}"
    home: /home/alice
    shell: /bin/zsh
    groups: admins
    state: present
    ssh_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAp7lR..."
deleted_users:
  - name: bob
  - name: charlie

全功能使用者管理 Playbook

接下來,我們建立一個功能完整的 Playbook,處理使用者的建立、修改和刪除:

---
- name: 全方位 Linux 使用者管理
  hosts: all
  become: true
  vars_files:
    - users.yml
  tasks:
    - name: 確保群組存在
      ansible.builtin.group:
        name: "{{ item.groups }}"
        state: present
      loop: "{{ users | map(attribute='groups') | unique | list }}"
      ignore_errors: yes
      
    - name: 建立或修改使用者
      ansible.builtin.user:
        name: "{{ item.name }}"
        password: "{{ item.password }}"
        home: "{{ item.home }}"
        shell: "{{ item.shell }}"
        groups: "{{ item.groups }}"
        state: "{{ item.state }}"
      loop: "{{ users }}"
      
    - name: 確保 .ssh 目錄存在
      ansible.builtin.file:
        path: "{{ item.home }}/.ssh"
        state: directory
        mode: '0700'
        owner: "{{ item.name }}"
        group: "{{ item.name }}"
      loop: "{{ users }}"
      
    - name: 佈署 SSH 公鑰
      ansible.builtin.copy:
        content: "{{ item.ssh_key }}"
        dest: "{{ item.home }}/.ssh/authorized_keys"
        mode: '0600'
        owner: "{{ item.name }}"
        group: "{{ item.name }}"
      loop: "{{ users }}"
      
    - name: 移除指定使用者
      ansible.builtin.user:
        name: "{{ item.name }}"
        state: absent
        remove: yes
      loop: "{{ deleted_users }}"

這個 Playbook 實作了幾個關鍵功能:

  1. 群組預處理:在建立使用者前,確保所需的群組已經存在,避免因群組不存在而導致的錯誤。
  2. 使用者建立與更新:根據定義的使用者資訊,統一建立或更新使用者帳號。
  3. SSH 金鑰佈署:自動設定 SSH 公鑰,實作無密碼安全登入。
  4. 使用者移除:自動刪除不再需要的使用者帳號及其主目錄。

map(attribute='groups') 過濾器從使用者列表中提取所有群組名稱,unique 過濾器則移除重複項,確保每個群組只建立一次。

執行與測試

在實際執行前,建議先使用 --check 模式進行測試:

ansible-playbook manage_users.yml -i hosts --check

確認無誤後再實際執行:

ansible-playbook manage_users.yml -i hosts

進階技巧與最佳實踐

使用者資料分層管理

對於大型組織,可以考慮將使用者資料分層管理:

inventory/
├── group_vars/
   ├── all/
      └── users.yml # 全域使用者
   ├── developers/
      └── users.yml # 開發團隊使用者
   └── production/
       └── users.yml # 生產環境使用者
└── host_vars/
    ├── server1/
       └── users.yml # 伺服器專屬使用者
    └── server2/
        └── users.yml

這種結構讓使用者管理更加靈活,可以根據不同環境和伺服器角色分配使用者許可權。

使用 Ansible Vault 加密敏感資料

雖然我們已經使用 password_hash 加密碼,但使用者資料檔案中仍可能包含敏感資訊。Ansible Vault 提供了額外的保護層:

# 加密整個使用者資料檔案
ansible-vault encrypt users.yml

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

# 執行時提供密碼
ansible-playbook manage_users.yml -i hosts --ask-vault-pass

整合 CI/CD 流程

將使用者管理 Playbook 整合到 CI/CD 流程中,可以實作使用者管理的自動化:

# .gitlab-ci.yml
stages:
  - validate
  - deploy

validate_playbook:
  stage: validate
  script:
    - ansible-playbook manage_users.yml -i hosts --syntax-check
    - ansible-playbook manage_users.yml -i hosts --check

deploy_users:
  stage: deploy
  script:
    - ansible-playbook manage_users.yml -i hosts
  only:
    - master

使用者活動稽核

為了滿足合規要求,我們可以加入使用者活動稽核功能:

- name: 記錄使用者變更
  ansible.builtin.shell: |
    echo "$(date) - User {{ item.name }} {{ item.state }} by Ansible" >> /var/log/user_management.log
  loop: "{{ users }}"
  changed_when: false

自動化管理

隨著雲端技術和容器化的普及,使用者管理自動化也在不斷演進。未來,我們可能會看到更多 AI 輔助的自動化管理工具,能夠預測使用者行為、自動調整許可權,甚至主動識別潛在的安全風險。但無論技術如何發展,自動化的核心價值始終不變:提高效率、減少錯誤、增強安全性。

透過 Ansible 的使用者管理技術,我們可以建立現代化、安全與高效的使用者管理系統。這不僅能大幅減輕系統管理的負擔,還能為組織建立更加安全可靠的 IT 環境。在自動化的道路上,每一步最佳化都是對未來的投資。

Ansible 提供了強大而靈活的工具,讓我們能夠自動化管理從 Cron 任務到 Docker 容器再到使用者安全的各個方面。透過這些技術,我們可以建立更加高效、安全與可靠的 IT 基礎設施,為組織的數位轉型提供堅實基礎。