一、背景与场景
说实话,很多团队在虚拟化环境里折腾手工建虚拟机,鼠标点来点去,配置IP、装软件,一个两个还行,一旦要批量创建几十台,或者每次部署环境都要重复同样操作,那真是又慢又容易出错。尤其是一些做测试环境、开发环境自动化的同学,经常被“环境不一致”搞得头大。这么一看,把虚拟机部署这件事做成自动化流程,就特别有必要了。
这个场景常见的地方比如:
- 开发团队要快速拉起一套和线上一样的测试环境;
- CI/CD流水线需要临时创建虚拟机跑测试任务;
- 运维给新项目分配资源,希望一键生成虚拟机并配置好应用。
大家通常会用两种工具:Terraform负责“基础设施即代码”,比如定义网络、虚拟机规格、磁盘这些;Ansible负责“配置管理”,比如装软件、改配置文件、启动服务。两者结合起来,正是自动化部署KVM虚拟机的经典套路。
二、技术栈说明
本文所有示例会分别使用Terraform和Ansible两种技术栈,但每个代码片段都会明确标注使用的技术栈名称。Terraform版本采用1.5以上,Ansible采用core 2.14以上,操作系统为CentOS 7/8或Ubuntu 20.04,KVM宿主机已经配置好libvirt和qemu。
三、用Terraform定义KVM虚拟机
3.1 为什么先提Terraform
Terraform能让你用代码描述“想要什么样子的虚拟机”,比如几核CPU、多大内存、用什么镜像、网络怎么连。写完代码后执行terraform apply,它就会去调用libvirt的API,把虚拟机创建出来。这样做的好处是:过程可重复、配置版本化、不易出错。
3.2 安装Provider与配置
首先,需要安装Terraform的libvirt provider。在项目目录创建main.tf文件,内容如下:
# 技术栈:Terraform HCL
# 定义provider,source指向官方libvirt插件
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.7.1"
}
}
}
# 配置连接本地的libvirt daemon(默认qemu:///system)
provider "libvirt" {
uri = "qemu:///system"
}
3.3 定义虚拟机资源
接下来定义一个KVM虚拟机,包括CPU、内存、磁盘、网络和cloud-init配置(用于自动设置IP和主机名)。以下是一个完整示例:
# 技术栈:Terraform HCL
# 创建一个存储池,存放镜像文件
resource "libvirt_pool" "default" {
name = "default"
type = "dir"
path = "/var/lib/libvirt/images"
}
# 上传一个Ubuntu 20.04的qcow2镜像到存储池
resource "libvirt_volume" "ubuntu_qcow2" {
name = "ubuntu-focal.qcow2"
pool = libvirt_pool.default.name
source = "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
format = "qcow2"
}
# 创建虚拟机的磁盘,使用上面的镜像作为后端,并设置大小
resource "libvirt_volume" "vm_disk" {
name = "vm-disk.qcow2"
base_volume_id = libvirt_volume.ubuntu_qcow2.id
pool = libvirt_pool.default.name
size = 21474836480 # 20GB
}
# 定义cloud-init的配置数据(网络、主机名、用户密码等)
resource "libvirt_cloudinit_disk" "commoninit" {
name = "commoninit.iso"
pool = libvirt_pool.default.name
network_config = data.template_file.network_config.rendered
user_data = data.template_file.user_data.rendered
}
# 使用template_file渲染cloud-init模板(需要先定义data模板)
data "template_file" "user_data" {
template = file("${path.module}/cloud_init.cfg")
vars = {
hostname = "myvm01"
user = "ubuntu"
password = "ubuntu123"
}
}
data "template_file" "network_config" {
template = file("${path.module}/network_config.cfg")
vars = {
ip_address = "192.168.122.100/24"
gateway = "192.168.122.1"
dns = "8.8.8.8"
}
}
# 最后创建虚拟机
resource "libvirt_domain" "vm" {
name = "myvm01"
memory = "2048"
vcpu = 2
disk {
volume_id = libvirt_volume.vm_disk.id
}
network_interface {
network_name = "default" # 使用libvirt默认NAT网络
hostname = "myvm01"
addresses = ["192.168.122.100"]
}
cloudinit = libvirt_cloudinit_disk.commoninit.id
# 保证ssh能通过密钥登录,这里只是演示,生产建议使用密钥
console {
type = "pty"
target_port = "0"
target_type = "serial"
}
}
注意,上面示例中引用了两个模板文件cloud_init.cfg和network_config.cfg,需要放在同目录下。cloud_init.cfg示例:
#cloud-config
# 技术栈:cloud-init配置
users:
- name: ${user}
lock_passwd: false
passwd: ${password}
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
hostname: ${hostname}
manage_etc_hosts: true
network_config.cfg:
#cloud-config
# 技术栈:cloud-init网络配置
network:
version: 2
ethernets:
ens3:
addresses:
- ${ip_address}
gateway4: ${gateway}
nameservers:
addresses:
- ${dns}
执行terraform init和terraform apply后,Terraform就会自动创建虚拟机。如果中途出错,用terraform destroy一键清理,非常干净。
3.4 Terraform小结
通过Terraform,我们把虚拟机的基础设施(CPU、内存、网络、存储)全部代码化了。这避免了手工点击libvirt管理界面,也方便后续用版本控制管理这些配置。
四、用Ansible进行配置管理
虚拟机建好了,里面只装了一个纯净的Ubuntu系统,接下来需要安装Nginx、配置防火墙、部署代码等。这些任务交给Ansible再合适不过。
4.1 准备工作:配置主机清单
Ansible需要知道要管理哪些机器,所以先写一个inventory.ini文件:
# 技术栈:Ansible Inventory
[webservers]
myvm01 ansible_host=192.168.122.100 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa
测试连通性:
ansible webservers -m ping -i inventory.ini
4.2 编写Playbook:安装Nginx并配置
创建一个playbookdeploy_nginx.yml,内容如下:
---
# 技术栈:Ansible Playbook (YAML)
- name: 部署Nginx到Web服务器
hosts: webservers
become: yes # 需要sudo权限
vars:
nginx_port: 8080 # 自定义端口
custom_index: "<h1>Hello from Ansible!</h1>"
tasks:
- name: 更新apt缓存
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: 安装Nginx
apt:
name: nginx
state: present
- name: 修改Nginx监听端口
lineinfile:
path: /etc/nginx/sites-available/default
regexp: 'listen\s+80;'
line: ' listen {{ nginx_port }} default_server;'
notify: restart nginx
- name: 创建自定义index.html
copy:
content: "{{ custom_index }}"
dest: /var/www/html/index.html
mode: '0644'
- name: 确保Nginx服务启动并开机自启
service:
name: nginx
state: started
enabled: yes
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
执行命令:
ansible-playbook -i inventory.ini deploy_nginx.yml
Ansible会自动连接到虚拟机,依次执行每个任务。如果某任务失败,它会停在出错的地方,方便排查。
4.3 Ansible的扩展能力
除了装软件,你还可用Ansible去添加用户、同步文件、管理系统服务、甚至执行SQL脚本。所有的操作都是幂等的,即多次运行结果一致,不会重复造成问题。
五、从Terraform到Ansible:整合持续交付流程
上面两步是分开做的:先Terraform建虚拟机,再Ansible配置。在实际的持续交付流水线里,我们希望一条命令完成全部。可以写一个Shell脚本或Makefile来串联:
#!/bin/bash
# 技术栈:Shell脚本
set -e
echo "=== 第一步:使用Terraform创建KVM虚拟机 ==="
cd terraform/
terraform init
terraform apply -auto-approve
# 获取虚拟机的IP地址,假设从Terraform output获取
VM_IP=$(terraform output -raw vm_ip)
echo "虚拟机IP地址: $VM_IP"
echo "=== 第二步:使用Ansible配置虚拟机 ==="
cd ../ansible/
# 动态生成inventory文件,或者直接使用变量
ansible-playbook -i "${VM_IP}," -u ubuntu --private-key ~/.ssh/id_rsa deploy_nginx.yml
这样,将Terraform和Ansible脚本放在同一个git仓库里,持续集成系统(比如Jenkins、GitLab CI)一触发,就能自动拉起虚拟机并部署应用,实现“基础设施+配置管理”的全自动化。
六、技术优缺点分析
优点
- 一致性强:代码定义的环境,每次创建结果都一样,减少“我的环境可以你的不行”的扯皮。
- 可版本控制:所有配置文件放入git,变更可追溯,回滚也方便。
- 效率高:批量创建、并行配置,几分钟完成以前半天的工作。
- 易于集成:Terraform和Ansible都是主流DevOps工具,能无缝接入CI/CD流水线。
缺点
- 学习曲线:需要同时掌握HCL和YAML两种语言,对新手有一定门槛。
- 依赖外部插件:Terraform的libvirt provider不是官方维护,有些版本可能不兼容。
- 调试复杂:全自动化出问题时,不如手工操作容易定位原因,需要熟悉日志和状态文件。
- 资源消耗:Terraform的状态文件管理不当可能造成资源泄露;Ansible在规模大时执行速度变慢。
七、注意事项
- 权限问题:KVM宿主机上运行Terraform需要用户有libvirt组权限(通常添加到
libvirt组)。 - 网络规划:确保虚拟机IP不与宿主机或其他虚拟机冲突,建议使用独立的NAT网络或桥接网络。
- 密钥管理:cloud-init中设置密码虽然方便,但生产环境应该使用SSH密钥,并且把私钥妥善保管。
- 状态文件安全:Terraform的
terraform.tfstate包含敏感信息(如IP、密码),最好远程存储在加密的后端(如S3、Consul)。 - Ansible幂等性:写playbook时注意每个任务最好是幂等的,避免重复运行导致错误。
- 镜像缓存:大镜像多次上传会浪费带宽和时间,建议在Terraform中设置
base_volume_id复用已有镜像,或者预先下载到宿主机。
八、总结
通过本文,我们从零开始,用Terraform定义并创建了KVM虚拟机,再用Ansible完成了软件配置,最后整合成一条自动化流水线。整个过程不复杂,但真正落地后,能极大提升环境交付的速度和可靠性。不管你是刚接触虚拟化的小白,还是资深运维,这套思路都值得一试。以后只需要改改代码,跑一下脚本,一个完整的虚拟机环境就诞生了。这就是“基础设施即代码”的魅力所在。
Comments