前言
當你需要同時維運多台 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
沒有留言: