熱門分類
載入中…
目錄0%

🛠 Ansible 實戰:批次管理多台 Linux 的 Netplan 與靜態路由設定

    前言

    當你需要同時維運多台 Linux 主機時,網路設定(IP、DNS、Gateway、靜態路由)若仍靠人工逐台調整, 不只耗時,也容易在變更時造成連線中斷。本文以 Ansible 實戰角度,示範如何用「角色化 + 模板化 + 分批套用」 集中管理 Netplan 與靜態路由,並加入驗證與回滾策略,降低大規模網路變更風險。

    適用範圍與前置條件

    • 適用:使用 Netplan 的 Linux(常見於 Ubuntu Server / Ubuntu Desktop)。
    • 控制端:已安裝 ansible(建議 2.14+ 以上)。
    • 受控端:可 SSH 登入、具 sudo 權限,且 /etc/netplan/ 存在。
    • 語彙偏好:技術內容中使用 Gateway

    🧭 行動清單

    ✅ 建立 Ansible 專案結構(roles + templates + group_vars/host_vars)
    ✅ 用 Jinja2 模板統一輸出 /etc/netplan/*.yaml(含 IP / DNS / Gateway / 靜態路由)
    ✅ 套用前先 netplan generate 驗證語法
    ✅ 以 serial 分批套用,套用後做路由與連通性驗證
    ✅ 失敗可回滾至備份設定,降低大規模斷線風險

    專案結構(建議)

    ansible-netplan/
    ├─ inventory/
    │  ├─ hosts.ini
    ├─ group_vars/
    │  ├─ netplan_hosts.yml
    ├─ host_vars/
    │  ├─ host01.yml
    │  ├─ host02.yml
    ├─ playbooks/
    │  ├─ netplan.yml
    ├─ roles/
    │  ├─ netplan/
    │     ├─ tasks/
    │     │  └─ main.yml
    │     ├─ handlers/
    │     │  └─ main.yml
    │     ├─ templates/
    │     │  └─ 01-netcfg.yaml.j2
    │     └─ defaults/
    │        └─ main.yml

    Inventory(示例)

    [netplan_hosts]
    host01 ansible_host=10.10.10.11
    host02 ansible_host=10.10.10.12
    
    [netplan_hosts:vars]
    ansible_user=ubuntu
    ansible_become=true
    ansible_become_method=sudo

    變數設計:把「設定」變成資料

    最佳實務是把 Netplan 內容拆成「資料(vars)」與「輸出(template)」,讓你只調整變數就能全站一致下發。 以下示範在 group_vars/netplan_hosts.yml 放共用設定,再用 host_vars 覆寫差異。

    group_vars/netplan_hosts.yml(共用)

    netplan_filename: "01-netcfg.yaml"
    netplan_renderer: "networkd"   # 可選 networkd / NetworkManager
    netplan_apply: true
    
    netplan_ethernets:
      - name: "ens160"
        dhcp4: false
        addresses: []              # 每台主機用 host_vars 覆寫
        gateway4: ""               # 每台主機用 host_vars 覆寫
        nameservers:
          addresses: ["1.1.1.1", "8.8.8.8"]
        routes:
          - to: "10.20.0.0/16"
            via: "10.10.10.1"
            metric: 100
          - to: "172.16.0.0/12"
            via: "10.10.10.254"
            metric: 110

    host_vars/host01.yml(個別覆寫)

    netplan_ethernets:
      - name: "ens160"
        dhcp4: false
        addresses: ["10.10.10.11/24"]
        gateway4: "10.10.10.1"
        nameservers:
          addresses: ["1.1.1.1", "8.8.8.8"]
        routes:
          - to: "10.20.0.0/16"
            via: "10.10.10.1"
            metric: 100
          - to: "172.16.0.0/12"
            via: "10.10.10.254"
            metric: 110

    host_vars/host02.yml(個別覆寫)

    netplan_ethernets:
      - name: "ens160"
        dhcp4: false
        addresses: ["10.10.10.12/24"]
        gateway4: "10.10.10.1"
        nameservers:
          addresses: ["1.1.1.1", "8.8.8.8"]
        routes:
          - to: "10.30.0.0/16"
            via: "10.10.10.1"
            metric: 100

    Netplan 模板:roles/netplan/templates/01-netcfg.yaml.j2

    # This file is managed by Ansible. DO NOT EDIT MANUALLY.
    network:
      version: 2
      renderer: {{ netplan_renderer }}
      ethernets:
    {% for nic in netplan_ethernets %}
        {{ nic.name }}:
          dhcp4: {{ nic.dhcp4 | default(false) | bool | lower }}
    {% if nic.addresses is defined and (nic.addresses | length) > 0 %}
          addresses:
    {% for addr in nic.addresses %}
            - {{ addr }}
    {% endfor %}
    {% endif %}
    {% if nic.gateway4 is defined and nic.gateway4 %}
          gateway4: {{ nic.gateway4 }}
    {% endif %}
    {% if nic.nameservers is defined and nic.nameservers.addresses is defined and (nic.nameservers.addresses | length) > 0 %}
          nameservers:
            addresses:
    {% for dns in nic.nameservers.addresses %}
              - {{ dns }}
    {% endfor %}
    {% endif %}
    {% if nic.routes is defined and (nic.routes | length) > 0 %}
          routes:
    {% for r in nic.routes %}
            - to: {{ r.to }}
              via: {{ r.via }}
    {% if r.metric is defined %}
              metric: {{ r.metric }}
    {% endif %}
    {% endfor %}
    {% endif %}
    {% endfor %}

    Role:defaults / handlers / tasks(套用、驗證、回滾)

    roles/netplan/defaults/main.yml

    netplan_filename: "01-netcfg.yaml"
    netplan_renderer: "networkd"
    netplan_apply: true
    netplan_ethernets: []

    roles/netplan/handlers/main.yml

    - name: netplan_generate
      become: true
      ansible.builtin.command: "netplan generate"
    
    - name: netplan_apply
      become: true
      ansible.builtin.command: "netplan apply"

    roles/netplan/tasks/main.yml(含驗證與回滾骨架)

    - name: Ensure netplan directory exists
      become: true
      ansible.builtin.file:
        path: /etc/netplan
        state: directory
        mode: "0755"
    
    - name: Set paths
      ansible.builtin.set_fact:
        netplan_path: "/etc/netplan/{{ netplan_filename }}"
        netplan_backup_path: "/etc/netplan/{{ netplan_filename }}.bak.ansible"
    
    - name: Backup current netplan file if exists
      become: true
      ansible.builtin.copy:
        src: "{{ netplan_path }}"
        dest: "{{ netplan_backup_path }}"
        remote_src: true
        mode: "0644"
      ignore_errors: true
    
    - name: Deploy netplan config from template
      become: true
      ansible.builtin.template:
        src: "{{ netplan_filename }}.j2"
        dest: "{{ netplan_path }}"
        mode: "0644"
    
    - name: Validate netplan syntax (generate)
      become: true
      ansible.builtin.command: "netplan generate"
      register: netplan_generate_result
      changed_when: false
    
    - name: Apply netplan
      become: true
      ansible.builtin.command: "netplan apply"
      when: netplan_apply | bool
      register: netplan_apply_result
      changed_when: false
    
    - name: Show routes (post)
      become: true
      ansible.builtin.command: "ip route show"
      register: ip_route_post
      changed_when: false

    Playbook:playbooks/netplan.yml(分批套用)

    - name: Batch manage netplan and static routes
      hosts: netplan_hosts
      become: true
      serial: "10%"
      any_errors_fatal: true
    
      roles:
        - role: netplan

    執行方式與驗證建議

    1) Check Mode 看差異

    ansible-playbook -i inventory/hosts.ini playbooks/netplan.yml --check --diff

    2) 正式套用(分批)

    ansible-playbook -i inventory/hosts.ini playbooks/netplan.yml

    3) 套用後驗證(主機端)

    ip addr
    ip route show
    systemctl status systemd-networkd --no-pager || true
    systemctl status NetworkManager --no-pager || true

    📘 結語

    把 Netplan 與靜態路由「資料化、模板化、角色化」,再配合分批套用與基本驗證, 就能以 Infrastructure as Code 的方式管理大量主機網路設定,讓變更更可控、可追溯且更安全。

    💬 留言互動:你的環境與需求是什麼?

    如果你在套用 Netplan / 靜態路由時遇到斷線、不同 renderer 行為差異、或想支援 VLAN / 多 NIC / policy routing, 歡迎在留言提供以下資訊,我可以協助你把 Playbook 調整成更貼近實際場景的版本。

    • 作業系統版本(例如 Ubuntu 22.04/24.04)與 renderer(networkd / NetworkManager)
    • 介面名稱(例如 ens160、bond0、vlan10)與 IP 規劃
    • Gateway 與需要的靜態路由(to/via/metric)
    • 是否需分流(多出口)、或需 policy routing / routing table

    延伸閱讀

    🔗 分享這篇 LINE Facebook X

    沒有留言:

    字級