Ansible多机部署虚拟机实践指南与思路引领——Ansible安全基线配置(二)

举报
Prism 发表于 2025/11/13 11:33:33 2025/11/13
【摘要】 引言大家好,欢迎来到我的 《Ansible 安全自动化:从服务器到 K8S 的堡垒之路》 系列教程,我是 Prism。本篇聚焦使用 Ansible 在多台宿主机上批量部署 KVM 虚拟机,并实现基础自动化与网络互联(通过 Tailscale + NFS)。阅读本篇后,你将学会:在宿主机上创建可免密 sudo 的用户并配置 SSH 免密登录;使用 Ansible 在多台 KVM 宿主机上统一...

引言

大家好,欢迎来到我的 《Ansible 安全自动化:从服务器到 K8S 的堡垒之路》 系列教程,我是 Prism。

本篇聚焦使用 Ansible 在多台宿主机上批量部署 KVM 虚拟机,并实现基础自动化与网络互联(通过 Tailscale + NFS)。阅读本篇后,你将学会:

  • 在宿主机上创建可免密 sudo 的用户并配置 SSH 免密登录;
  • 使用 Ansible 在多台 KVM 宿主机上统一安装与配置 KVM/libvirt 环境;
  • 通过 Tailscale 将宿主机纳入同一私有网络以安全共享镜像(NFS);
  • 使用变量驱动的方式动态生成并批量创建 / 删除虚拟机实例。

注意:

  • 示例中宿主机使用 Ubuntu/Debian 系列(playbook 已兼容 RedHat/Rocky,但包名与服务名请核对);
  • 虚拟机默认使用 Rocky Linux 9 通用云镜像(支持 cloud-init);
  • NFS 只能用于受信任内网,示例通过 Tailscale 实现内网穿透以避免直接在公网暴露 NFS 服务。

文档概览

  • 项目结构
  • 脚本设计思路
  • 用户与免密配置(sudo / SSH)
  • inventory / ansible.cfg / group_vars 示例
  • Cloud-Init 模板(templates/user-data.yml.j2)
  • setup_host.yml(初始化宿主机)
  • create_vms.yml(批量创建 VM)
  • delete_vms.yml(销毁与清理)
  • 运行示例与注意事项

项目的文件结构

ansible-kvm/
├── inventory.ini # 1. 主机清单
├── ansible.cfg # 2. Ansible 配置文件
├── group_vars/
│ └── all.yml # 3. 变量文件(核心:动态生成配置)
├── templates/
│ └── user-data.yml.j2 # 4. Cloud-Init 模板
├── setup_host.yml # 5. 剧本:安装 KVM 环境
├── create_vms.yml # 6. 剧本:创建虚拟机(核心:动态循环)
└── delete_vms.yml # 7. 剧本:删除虚拟机(核心:动态循环)

# 一键创建所有目录和文件
mkdir -p ansible-kvm/{group_vars,templates} && touch ansible-kvm/{inventory.ini,ansible.cfg,group_vars/all.yml,templates/user-data.yml.j2,setup_host.yml,create_vms.yml,delete_vms.yml}

脚本设计思路(简要)

多台宿主机都保存完整镜像既浪费带宽又浪费磁盘空间。方案采用:

  • 在控制机(host1)保留一份基础 qcow2 镜像,通过 NFS 只读共享给其他宿主机,避免重复下载与重复存储;
  • 使用 Tailscale 将宿主机加入同一虚拟私有网络,安全地进行 NFS 共享与 Ansible 管理;
  • 通过 group_vars 中的 vm_config 动态指定每台宿主机上的 VM 命名规则与数量,playbook 根据配置自动循环创建或删除 VM,保持幂等性。

风险提示:

  • 切勿在不受信网络直接开放 NFS;Tailscale 或其它受控隧道是必须的;
  • Tailscale auth key 应妥善保存,建议使用短期、受限的 key。

创建用户并配置免密 sudo 权限

在需要使用 Ansible 的机器上创建用户并配置权限

# 使用 adduser 创建一个叫 'testuser' 的用户
# 你需要把 'testuser' 换成你想要的用户名
sudo adduser testuser

# 使用 usermod 命令将 'testuser' 添加到 'sudo' 组
# -aG 的意思是 '追加(append)''指定组(Groups)'
sudo usermod -aG sudo testuser

# 使用 visudo 安全地创建并编辑新配置文件
# 文件名可以是用户名,比如 'testuser-nopasswd'
sudo visudo -f /etc/sudoers.d/testuser-nopasswd

编辑文件

# 将 'testuser' 替换成你自己的用户名
testuser ALL=(ALL) NOPASSWD: ALL

验证免密 sudo 是否生效:

su testuser
sudo apt update

免密 SSH 登录(请在控制节点机器上运行)

# -t (type) 指定算法为 ed25519
# -C (comment) 添加一个注释,通常是你的邮箱或 "user@host",方便识别
ssh-keygen -t ed25519 -C "prism@wwww.com"
# 格式: ssh-copy-id [服务器用户名]@[服务器IP或域名]
# 你也可以用 -i 指定公钥文件,如果不是默认路径的话
ssh-copy-id -i ~/.ssh/id_ed25519.pub remote_user@192.168.1.100

验证:使用 ssh 登录目标宿主机,确认无需密码即可登录。


一、inventory.ini(主机清单)

[kvm_hosts]
# 确保你的Ansible控制机能通过你之前创建(或已有)用户免密SSH登录
# host1为本机(控制机)
host1 ansible_connection=local ansible_python_interpreter={{ ansible_playbook_python }}
host2 ansible_host=192.168.1.101 ansible_user=testuser
host3 ansible_host=192.168.1.101 ansible_user=testuser

提示:inventory 中的主机名需与 group_vars 中 vm_config 的键一致,例如 host1、host2、host3。


二、ansible.cfg(项目配置)

[defaults]
inventory=./inventory.ini

你可以根据需要添加 roles_path、retry_files_enabled、forks 等配置项。


三、group_vars/all.yml(示例与获取 Tailscale auth key)

获取 Tailscale auth key(简要):

  1. 登录taliscale官网;
  2. 进入 Settings -> Keys(或 Keys/Auth keys);
  3. 生成新的 auth key,复制保存(建议使用短期或受限 key)。
# 1、用户自定义路径
kvm_base_path: "/kvm"
temp_config_path: "/tmp/kvm_configs"

# 2、存储池配置
storage_pool_name: "my_kvm_pool"
storage_pool_path: "{{ kvm_base_path }}/images/1"

# 3、基础镜像配置
base_image_dir: "{{ kvm_base_path }}/iso"
base_image_name: "Rocky9-Base.qcow2"
base_image_url: "https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
base_image_full_path: "{{ base_image_dir }}/{{ base_image_name }}"

# 4虚拟机规格 (默认)
vm_vcpus: 2
vm_memory: 1024
os_variant: "rocky9.0"

# 5VM动态生成配置
# 主机名必须匹配 inventory.ini
vm_config:
  host1:
    prefix: "rocky"
    start_index: 1
    count: 3
  host2:
    prefix: "rocky"
    start_index: 4
    count: 1
  host3:
    prefix: "rocky"
    start_index: 5
    count: 1

# 6、虚拟机内用户定义
user_name: "prism"

# 1. 定义控制机的公钥 (VM 唯一信任的公钥)
control_node_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFx9MULLnBirVF5hi9psq2j9ZxGi/vgL7CRcQ3kUTFsa prism@wwww.com"

# 2. Tailscale 认证密钥
tailscale_auth_key: "tskey-auth-xxx"

# 7. 是否设置开机自动启动,推荐开启
set_autostart: true

VM 动态生成配置(示例重复区,保持原样以便参考)

vm_config:
  host1:
    prefix: "rocky"
    start_index: 1
    count: 3

  host2
    prefix: "rocky"
    start_index: 4
    count: 3

  host3
    prefix: "rocky"
    start_index: 4
    count: 3

# 虚拟机内用户定义
user_name: "prism"
# 定义控制机的公钥
control_node_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFx9MULLnBirVF5hi9psq2j9ZxGi/vgL7CRcQ3kUTFsa prism@wwww.com"

# 定义 KVM 宿主机 (host2, host3...)(被管节点) 上的公钥文件路径
# 我们假设所有宿主机上的 prism 用户公钥都在这个路径
kvm_host_public_key_path: "/home/{{ ansible_user }}/.ssh/id_ed25519.pub"
# (注意: ansible_user 会被自动替换为 'inventory.ini中定义的用户名')

# 是否设置开机自动启动,推荐开启
set_autostart: true

Cloud-Init 模板(templates/user-data.yml.j2)

#templates/user-data.yml.j2
#cloud-config
user: {{ user_name }}
sudo: ['ALL=(ALL) NOPASSWD:ALL']
system_info:
  default_user:
    name: prism
hostname: {{ vm_name }}

ssh_authorized_keys:
  # VM 需要信任 Ansible 控制机(localhost)的公钥
  - {{ control_node_public_key }}

# 自动安装 curl (Rocky 9 最小镜像可能没有)
packages:
  - curl

# VM 首次启动时,自动安装并加入 Tailscale 网络
runcmd:
  # 1. 下载并运行 Tailscale 安装脚本
  - 'curl -fsSL https://tailscale.com/install.sh | sh'
  
  # 2. 启动 Tailscale 并使用 Auth Key 自动加入网络
  #    --ssh (自动开启 Tailscale SSH 服务, 允许你从 Tailscale 网站登录)
  #    --hostname={{ vm_name }} (自动注册机器名)
  - 'tailscale up --authkey={{ tailscale_auth_key }} --ssh --hostname={{ vm_name }}'

五、setup_host.yml(初始化宿主机环境)

设计思路:

  • 目标:保证所有宿主机具备一致的 KVM/libvirt 运维环境并能通过安全内网(Tailscale)互通与共享基础镜像(NFS)。
  • 幂等性:使用 package、file、systemd 等模块,按操作系统分支(Debian/RedHat)控制任务执行,避免重复安装或重复创建目录。
  • 最小权限与用户管理:将运行 Ansible 的用户添加到 libvirt/kvm 组,避免在 playbook 中滥用 root,并通过 become 控制提权点。
  • Tailscale 与 NFS:将 Tailscale 的安装和 up 操作放在宿主机上以保证内网连通性;仅在控制节点(host1)上配置 NFS server,其他节点挂载为只读,从而降低主机间同步写入风险。
  • 可恢复性与通知(handlers):对 /etc/exports 等关键配置使用 handlers 重启 NFS 服务,保证配置变更能被安全应用。
  • 并发与顺序:NFS server 的刷新需在 host1 上完成后才允许其他节点挂载,使用 when/delegate 控制执行顺序。
- name: 安装 KVM 环境
  hosts: kvm_hosts
  become: yes
  gather_facts: yes 
  vars:
    target_user: "{{ ansible_user }}" 

  tasks:
    - name: 1. 更新 apt 缓存并安装 KVM 软件包 (Debian/Ubuntu)
      ansible.builtin.apt:
        name: [qemu-kvm, qemu-utils, libvirt-daemon-system, libvirt-clients, bridge-utils, virtinst, wget, python3-libvirt, python3-lxml] 
        state: present
        update_cache: yes
      when: ansible_os_family == "Debian"

    - name: 1b. 安装 KVM 软件包 (Rocky/RHEL)
      ansible.builtin.dnf:
        name: [qemu-kvm, qemu-utils, libvirt-daemon-kvm, libvirt-clients, virt-install, wget]
        state: present
      when: ansible_os_family == "RedHat"

    - name: 2. 启动并设置 libvirtd 服务自启
      ansible.builtin.systemd:
        name: libvirtd
        state: started
        enabled: yes

    - name: 3.'{{ target_user }}' 添加到 libvirt 和 kvm 组
      ansible.builtin.user:
        name: "{{ target_user }}"
        groups: [libvirt, kvm]
        append: yes

    - name: 4a. (全局) 创建 KVM 根目录和存储目录
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: "{{ target_user }}"
        group: "{{ target_user }}"
        mode: '0755'
      loop:
        - "{{ kvm_base_path }}"
        - "{{ storage_pool_path }}"
        - "{{ temp_config_path }}"

    - name: 4b. (全局) 创建 NFS 挂载点目录 (不修改权限)
      ansible.builtin.file:
        path: "{{ base_image_dir }}"
        state: directory
        mode: '0755'

    - name: 5.KVM 宿主机上安装和启动 Tailscale
      block:
        - name: 下载 Tailscale 安装脚本
          ansible.builtin.get_url:
            url: "https://tailscale.com/install.sh"
            dest: "/tmp/install-tailscale.sh"
            mode: '0755'
        - name: 运行 Tailscale 安装脚本
          ansible.builtin.command: /tmp/install-tailscale.sh
          changed_when: true
        - name: 启动 Tailscale 并加入网络
          ansible.builtin.command: "tailscale up --authkey={{ tailscale_auth_key }} --hostname={{ inventory_hostname }}"
          changed_when: true
          
    - name: 6. 配置 NFS 服务端 (仅在 host1/控制机 上)
      when: inventory_hostname == 'host1'
      block:
        - name: 6a. 安装 NFS Server
          ansible.builtin.apt:
            name: nfs-kernel-server
            state: present
            update_cache: yes
          when: ansible_os_family == "Debian"
          
        - name: 6b. [安全] 配置 /etc/exports
          ansible.builtin.lineinfile:
            path: /etc/exports
            line: "{{ base_image_dir }} *(ro,sync,no_subtree_check)"
            state: present
          notify: Restart nfs-kernel-server

    - name: 6c. 立即刷新 handlers (重启 NFS server)
      ansible.builtin.meta: flush_handlers
      when: inventory_hostname == 'host1'

    - name: 7. 配置 NFS 客户端 (仅在 host2, host3... 远程主机上)
      when: inventory_hostname != 'host1'
      block:
        - name: 7a. 安装 NFS Client
          ansible.builtin.apt:
            name: nfs-common
            state: present
            update_cache: yes
          when: ansible_os_family == "Debian"

        - name: 7b. 挂载来自 host1 的共享目录
          ansible.posix.mount:
            src: "host1:{{ base_image_dir }}" 
            path: "{{ base_image_dir }}"
            fstype: nfs
            opts: "ro,soft,timeo=10,noauto,x-systemd.automount"
            state: mounted
          
  handlers:
    - name: Restart nfs-kernel-server
      ansible.builtin.service:
        name: nfs-kernel-server
        state: restarted

六、create_vms.yml(批量创建虚拟机)

设计思路:

  • 幂等性检查:先调用 list_vms 获取已存在虚拟机列表,只有缺失的 VM 才创建;使用 qemu-img 的 creates 参数避免重复创建磁盘。
  • 存储池管理:集中管理 storage pool(定义/激活/autostart),保证磁盘文件位于受控目录,便于备份与清理。
  • 基础镜像下载优化:只在 host1 上下载基础 qcow2 镜像并通过 NFS 只读共享,减少带宽与重复存储。
  • 链接克隆(qemu-img create -b):采用基于 base image 的 qcow2 链接克隆以节省磁盘空间和加速创建;注意 base image 的只读属性与备份策略。
  • Cloud-Init 自动化:通过模板渲染 user-data 注入公钥与 tailscale auth key,实现 VM 首次启动自动加入内网并可远程管理。
  • 错误与回滚:对易失败的步骤(virt-install)通过 register/changed_when 判断结果,并在失败时保留调试信息;对磁盘、临时文件使用清理任务保证环境整洁。《Ansible 剧本导演手册》 (实战增补版)
    章节一:变量 (Variables) - 动态的剧本
    变量是让剧本从“写死”的指令变成“智能”的模板的核心。
- name: 多机部署 KVM 虚拟机
  hosts: kvm_hosts
  become: yes 
  gather_facts: yes

  pre_tasks:
    - name: 0. 检查 'libvirt' 组权限
      ansible.builtin.command: "groups {{ ansible_user }}"
      register: user_groups
      changed_when: false
      failed_when: "'libvirt' not in user_groups.stdout"
      tags: [checks]
      become: no 

    - name: 1. (全局) 确保 KVM 根目录和子目录存在
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        mode: '0755'
      loop:
        - "{{ storage_pool_path }}"
        - "{{ base_image_dir }}"
        - "{{ temp_config_path }}"
      become: yes 

    - name: 2a. [在 host1] 检查基础镜像是否已存在
      ansible.builtin.stat:
        path: "{{ base_image_full_path }}"
      register: file_stat
      when: inventory_hostname == 'host1'
      delegate_to: host1
      become: no 

    - name: 2b. [在 host1] 下载基础镜像 (如果不存在)
      ansible.builtin.get_url:
        url: "{{ base_image_url }}"
        dest: "{{ base_image_full_path }}"
        mode: '0644'
      when:
        - inventory_hostname == 'host1'
        - not file_stat.stat.exists
      delegate_to: host1
      become: no 

    - name: 3a. (全局) 步骤一:确保 KVM 存储池被定义 (Present)
      community.libvirt.virt_pool:
        name: "{{ storage_pool_name }}"
        state: present
        xml: |
          <pool type='dir'>
            <name>{{ storage_pool_name }}</name>
            <target>
              <path>{{ storage_pool_path }}</path>
            </target>
          </pool>
      become: yes 

    - name: 3b-1. 确保存储池开机自启
      community.libvirt.virt_pool:
        name: "{{ storage_pool_name }}"
        autostart: yes
        state: present
      become: yes

    - name: 3b-2. 停用存储池以强制重载配置
      community.libvirt.virt_pool:
        name: "{{ storage_pool_name }}"
        state: inactive
      become: yes
      ignore_errors: yes

    - name: 3b-3. (全局) 步骤二:确保 KVM 存储池已激活 
      community.libvirt.virt_pool:
        name: "{{ storage_pool_name }}"
        state: active
      become: yes 

    - name: 4. (全局) 获取所有已定义的 KVM 虚拟机 (用于幂等性检查)
      community.libvirt.virt:
        command: list_vms
      register: existing_vms
      changed_when: false
      become: yes 

  tasks:
    - name: 1. 为当前主机获取配置
      ansible.builtin.set_fact:
        my_config: "{{ vm_config[inventory_hostname] | default({}) }}"

    - name: 2. 动态生成要创建的 VM 列表 (如果配置存在)
      ansible.builtin.set_fact:
        my_vms_to_create: >
          {{ range(my_config.start_index, my_config.start_index + my_config.count) | map('string') | map('regex_replace', '^(.*)$', my_config.prefix + '\1') | list }}
      when: my_config.prefix is defined and my_config.count is defined

    - name: 3. 调试信息
      ansible.builtin.debug:
        msg: "虚拟机 {{ vm_name }} (磁盘: {{ vm_disk }})"
      when: vm_name not in existing_vms.list_vms
      loop: "{{ my_vms_to_create | default([]) }}"
      loop_control:
        loop_var: vm_name
      vars:
        vm_disk: "{{ storage_pool_path }}/{{ vm_name }}.qcow2"
      become: no 

    - name: 4. 创建链接克隆硬盘
      ansible.builtin.command: >
        qemu-img create -f qcow2 -F qcow2
        -b {{ base_image_full_path }}
        {{ vm_disk }}
      args:
        creates: "{{ vm_disk }}"
      when: vm_name not in existing_vms.list_vms
      loop: "{{ my_vms_to_create | default([]) }}"
      loop_control:
        loop_var: vm_name
      vars:
        vm_disk: "{{ storage_pool_path }}/{{ vm_name }}.qcow2"
      become: yes 

    - name: 5. 生成 Cloud-Init 配置文件
      ansible.builtin.template:
        src: "templates/user-data.yml.j2"
        dest: "{{ user_data_file }}"
        mode: '0644'
      when: vm_name not in existing_vms.list_vms
      loop: "{{ my_vms_to_create | default([]) }}"
      loop_control:
        loop_var: vm_name
      vars:
        user_data_file: "{{ temp_config_path }}/user-data-{{ vm_name }}.yaml" 
      become: yes 

    - name: 6. 定义和安装虚拟机
      ansible.builtin.command: >
        virt-install
        --name {{ vm_name }}
        --vcpus {{ vm_vcpus }}
        --memory {{ vm_memory }}
        --disk path={{ vm_disk }},device=disk
        --network network=default
        --os-variant {{ os_variant }}
        --import
        --noautoconsole
        --cloud-init "user-data={{ user_data_file }}"
      register: install_cmd
      changed_when: "'Domain creation completed' in install_cmd.stdout"
      when: vm_name not in existing_vms.list_vms
      loop: "{{ my_vms_to_create | default([]) }}"
      loop_control:
        loop_var: vm_name
      vars:
        vm_disk: "{{ storage_pool_path }}/{{ vm_name }}.qcow2"
        user_data_file: "{{ temp_config_path }}/user-data-{{ vm_name }}.yaml" 
      become: yes 

    - name: 7. 清理临时的 Cloud-Init 文件
      ansible.builtin.file:
        path: "{{ user_data_file }}"
        state: absent
      when: vm_name not in existing_vms.list_vms
      loop: "{{ my_vms_to_create | default([]) }}"
      loop_control:
        loop_var: vm_name
      vars: 
        user_data_file: "{{ temp_config_path }}/user-data-{{ vm_name }}.yaml"
      become: yes 

    - name: 8. 设置开机自启动
      community.libvirt.virt:
        name: "{{ vm_name }}"
        autostart: yes
      when: set_autostart | bool and vm_name in existing_vms.list_vms
      loop: "{{ my_vms_to_create | default([]) }}"
      loop_control:
        loop_var: vm_name
      become: yes 

七、delete_vms.yml(销毁并清理)

设计思路:

  • 安全删除顺序:先 destroy(强制关机),再 undefine(取消定义),最后从存储池删除磁盘文件;该顺序可避免磁盘被删除但域仍挂载导致资源泄露或 libvirt 状态异常。
  • 可重复与容错:对每步操作使用 ignore_errors 合理容错,保证即使部分 VM 状态异常(已不存在或已损坏)也能继续清理其余资源。
  • delete_all 模式:提供风险更高的全盘清理选项(delete_all=true),并在 playbook 中显式以 when 控制,避免误删。建议在执行前备份重要数据或先在非生产环境验证。
  • NFS 卸载:在被管节点先卸载 NFS 挂载点(lazy umount 或 systemd automount 卸载),保证后续删除/移除存储池时不会出现占用。
  • 日志与审计:在删除操作中记录每个已处理的 VM 名称与状态,便于事后审计和回滚(如需要基于快照恢复的流程)。
  • 逐步回收资源:删除虚拟机磁盘后不立即删除 base image(位于 host1),以便在误删情况下能快速恢复测试 VM;仅在 delete_all 且确认无恢复需求下才删除基础目录。
- name: 销毁并清理 KVM 虚拟机
  hosts: kvm_hosts
  become: yes 
  gather_facts: yes

  pre_tasks:
    - name: 1. [在 host2, host3 上] 卸载 NFS 共享
      when: inventory_hostname != 'host1'
      block:
        - name: 1a. 确保 NFS 共享被卸载
          ansible.builtin.command:
            cmd: "umount -l {{ base_image_dir }}" # -l 或 --lazy 选项尝试断开连接
          changed_when: true
          ignore_errors: yes # 忽略文件系统未挂载导致的错误
          become: yes

        - name: 1a-alt. 确保 NFS 共享被卸载
          ansible.posix.mount:
            path: "{{ base_image_dir }}"
            state: absent
          ignore_errors: yes
          become: yes

    - name: 2. (全局) 获取所有已定义的 KVM 虚拟机
      community.libvirt.virt:
        command: list_vms
      register: existing_vms
      changed_when: false

  tasks:
    - name: 1. 为当前主机获取配置
      ansible.builtin.set_fact:
        my_config: "{{ vm_config[inventory_hostname] | default({}) }}"

    - name: 2. 动态生成要删除的 VM 列表
      ansible.builtin.set_fact:
        my_vms_to_delete: >
          {{ range(my_config.start_index, my_config.start_index + my_config.count) | map('string') | map('regex_replace', '^(.*)$', my_config.prefix + '\1') | list }}
      when: my_config.prefix is defined

    - name: 3a. 强制关机:{{ vm_name }}
      community.libvirt.virt:
        name: "{{ vm_name }}"
        state: destroyed
      when: vm_name in existing_vms.list_vms
      ignore_errors: yes
      loop: "{{ my_vms_to_delete | default([]) }}"
      loop_control:
        loop_var: vm_name

    - name: 3b. 取消定义:{{ vm_name }}
      community.libvirt.virt:
        name: "{{ vm_name }}"
        command: undefine
      when: vm_name in existing_vms.list_vms
      loop: "{{ my_vms_to_delete | default([]) }}"
      loop_control:
        loop_var: vm_name

    - name: 3c. 删除硬盘:{{ vm_name }}——Ansible安全基线配置(二).qcow2
      community.libvirt.virt_pool: 
        pool: "{{ storage_pool_name }}"
        name: "{{ vm_name }}.qcow2"
        state: absent
      ignore_errors: yes
      loop: "{{ my_vms_to_delete | default([]) }}"
      loop_control:
        loop_var: vm_name

    - name: 4. [可选] 彻底清理存储池和目录
      when: delete_all | default(false) | bool
      block:
        - name: 4a. 正在停用存储池:{{ storage_pool_name }}
          community.libvirt.virt_pool:
            name: "{{ storage_pool_name }}"
            state: inactive
          ignore_errors: yes

        - name: 4b. 正在取消定义存储池:{{ storage_pool_name }}
          community.libvirt.virt_pool:
            name: "{{ storage_pool_name }}"
            state: undefined
          ignore_errors: yes

        - name: 4c. 正在删除临时配置目录
          ansible.builtin.file:
            path: "{{ temp_config_path }}"
            state: absent

        - name: 4d. 删除被管节点的目录
          when: inventory_hostname != 'host1'
          block:
            - name: 4d-1. 删除目录中
              ansible.builtin.file: 
              path: "{{ kvm_base_path }}"
              state: absent
              ignore_errors: yes

运行脚本(示例)

# 运行快速部署脚本
ansible-playbook setup_host.yml
# 运行创建虚拟机脚本
ansible-playbook create_vms.yml
# 运行删除脚本(只删除虚拟机)
ansible-playbook delete_vms.yml
# 运行删除脚本(删除存储池和物理目录)
ansible-playbook delete_vms.yml -e 'delete_all=true'

常见问题与建议

  • NFS 挂载失败时,先检查 Tailscale 是否运行且宿主机间可达(tailscale status / tailscale ip);
  • 若 virt-install 报错或卡住,先在目标宿主机上手动运行相应命令并查看 libvirt 日志(/var/log/libvirt/);
  • Tailscale auth key 建议使用短期或权限受限的密钥以降低泄露影响;
  • 若目标为生产级大规模虚拟化管理,建议采用成熟平台(Proxmox、OpenStack、oVirt 等)替代手工 KVM + NFS 方案。

结语

本文示范如何使用 Ansible 在多宿主机环境下自动化安装 KVM、共享镜像并批量部署 VM,重点在于演示自动化思路与实现方法。实际生产场景请充分考虑安全与运维可控性(镜像源、凭证管理、网络拓扑、权限分离等)。后续会分享如何构建“黄金镜像”以进一步简化 VM 部署流程。

感谢阅读,欢迎根据自身环境与策略调整配置与流程。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。