Ansible 自动化运维与 Terraform IaC
概述
Ansible 是一款开源的自动化运维工具,专注于配置管理、应用部署和任务编排。Terraform 是一款基础设施即代码(Infrastructure as Code, IaC)工具,用于声明式地管理和编排云基础设施。本文档系统性地介绍两者的核心概念、实践用法及组合方案,帮助团队实现从基础设施到应用配置的全链路自动化。
一、Ansible 基础
1.1 架构总览
Ansible 采用无 Agent 的分布式架构,通过 SSH(Linux)或 WinRM(Windows)协议远程执行任务,无需在目标节点上安装任何代理程序。
控制节点 (Control Node)
│
├── Inventory → 定义受管节点列表
├── Modules → 执行具体操作的任务单元
├── Playbook → YAML 格式的任务编排文件
├── Plugins → 扩展 Ansible 功能的插件
└── ansible.cfg → 全局配置文件核心组件说明:
| 组件 | 说明 |
|---|---|
| 控制节点 (Control Node) | 安装 Ansible 的主机,负责下发任务。任何安装了 Python 和 Ansible 的机器均可作为控制节点 |
| 受管节点 (Managed Node) | 被 Ansible 管理的主机,仅需 Python 2.6+ 或 Python 3.5+,无需安装额外 Agent |
| Inventory | 受管节点清单,支持静态文件(INI/YAML)和动态来源(云 API、CMDB) |
| Module | 执行具体操作的可执行单元,如 copy、file、yum、systemd 等,幂等性是模块设计的核心原则 |
| Playbook | 以 YAML 编写的配置、部署、编排方案,是 Ansible 的核心使用方式 |
1.2 无 Agent 模式
Ansible 区别于 SaltStack、Puppet 等工具的核心特征是无 Agent 架构:
- 控制节点通过 SSH 或 WinRM 直接与受管节点通信
- 控制节点将模块(Python 脚本)推送到受管节点执行,执行完成后自动清理
- 无需在受管节点上维护常驻进程或守候服务
- 大大降低了受管节点的资源消耗和安全暴露面
- 网络要求:控制节点必须能够通过 SSH/WinRM 访问受管节点
1.3 安装与配置
安装 Ansible
# RHEL/CentOS 通过 EPEL 安装
sudo yum install -y epel-release
sudo yum install -y ansible
# Ubuntu/Debian 通过 PPA 安装
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# 使用 pip 安装(推荐,可指定版本)
pip install ansible # 最新稳定版
pip install ansible==9.0.0 # 指定版本
# 验证安装
ansible --versionansible.cfg 配置文件
Ansible 按以下优先级加载配置文件:环境变量 ANSIBLE_CONFIG > 当前目录 ansible.cfg > 用户目录 ~/.ansible.cfg > 系统级 /etc/ansible/ansible.cfg。
[defaults]
# Inventory 文件路径
inventory = ./inventory/hosts
# 远程执行用户
remote_user = root
# 是否检查 SSH 主机密钥(生产环境建议设为 True)
host_key_checking = False
# 并发执行线程数
forks = 20
# 默认模块的并行连接超时时间(秒)
timeout = 30
# 日志文件路径
log_path = ./ansible.log
# Playbook 中角色的查找路径
roles_path = ./roles
# 是否收集受管节点 facts(首次连接时收集系统信息)
gathering = smart
[ssh_connection]
# SSH 连接复用,大幅提升批量执行速度
pipelining = True
control_path = /tmp/ansible-%%h-%%p-%%r1.4 Inventory 主机清单
Inventory 定义 Ansible 管理的所有主机和主机组,支持静态和动态两种方式。
静态 Inventory(INI 格式)
# inventory/hosts
# 单台主机
web-server-01 ansible_host=192.168.1.10 ansible_user=root
# 主机组定义
[webservers]
web-server-01 ansible_host=192.168.1.10
web-server-02 ansible_host=192.168.1.11
web-server-03 ansible_host=192.168.1.12
[dbservers]
db-master ansible_host=192.168.1.20 ansible_port=22
db-slave-01 ansible_host=192.168.1.21
db-slave-02 ansible_host=192.168.1.22
# 子组:组名格式为 [父组:children]
[production:children]
webservers
dbservers
# 组变量:组名格式为 [组名:vars]
[production:vars]
env=production
ansible_user=deploy静态 Inventory(YAML 格式)
# inventory/hosts.yml
all:
children:
webservers:
hosts:
web-server-01:
ansible_host: 192.168.1.10
web-server-02:
ansible_host: 192.168.1.11
vars:
http_port: 8080
dbservers:
hosts:
db-master:
ansible_host: 192.168.1.20
db-slave-01:
ansible_host: 192.168.1.21
production:
children:
webservers:
dbservers:
vars:
env: production动态 Inventory
动态 Inventory 从外部系统(如 AWS EC2、阿里云 ECS、OpenStack、CMDB)动态获取主机列表。Ansible 官方和社区提供了大量动态 Inventory 脚本/插件。
# 使用 AWS EC2 动态 Inventory(需安装 boto3 并配置 AWS 凭证)
ansible-inventory -i aws_ec2.yml --list
# aws_ec2.yml 示例
# plugin: aws_ec2
# regions:
# - us-east-1
# - ap-southeast-1
# filters:
# tag:Environment: production
# instance-state-name: running
# keyed_groups:
# - key: tags.Role
# prefix: role
# compose:
# ansible_host: public_ip_address常用 ad-hoc 命令测试连接:
# ping 测试所有主机
ansible all -m ping
# 仅测试 webservers 组
ansible webservers -m ping
# 执行 shell 命令查看主机名和内核版本
ansible all -m shell -a "uname -a"
# 使用模块查看主机磁盘使用
ansible all -m command -a "df -h"二、Ansible Playbook
2.1 YAML 语法基础
Playbook 使用 YAML 格式编写,YAML 的核心语法规则:
---
# 列表(数组)
packages:
- nginx
- git
- curl
# 字典(键值对)
config:
port: 8080
host: 0.0.0.0
ssl: true
# 列表嵌套字典
users:
- name: alice
uid: 1001
- name: bob
uid: 1002
# 多行字符串(使用 | 保留换行)
multiline: |
第一行
第二行
第三行
# 多行字符串(使用 > 折叠换行为空格)
folded: >
这是一个很长的
字符串将会被
合并为一行2.2 Playbook 基本结构
Playbook 由一个或多个 Play 组成,每个 Play 对应在一组主机上执行的一系列任务。
---
# playbook.yml
- name: 部署 Nginx Web 服务
hosts: webservers # 在 webservers 组上执行
become: yes # 使用 sudo 提权
become_user: root # 提权到 root 用户
gather_facts: yes # 收集受管节点系统信息
vars:
http_port: 80
server_name: example.com
tasks:
- name: 确保 Nginx 已安装
yum:
name: nginx
state: present
- name: 复制 Nginx 配置文件
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: 确保 Nginx 服务启动并启用开机自启
systemd:
name: nginx
state: started
enabled: yes
handlers:
- name: restart nginx
systemd:
name: nginx
state: restarted2.3 Tasks 任务
任务是 Playbook 的最小执行单元,按从上到下的顺序依次执行。每个任务必须包含一个 name(描述性名称)和一个模块调用。
tasks:
- name: 安装依赖包
yum:
name:
- epel-release
- wget
- curl
- git
state: present
- name: 创建应用目录
file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: '0755'
- name: 从 Git 仓库拉取代码
git:
repo: https://github.com/example/myapp.git
dest: /opt/myapp
version: main
force: yes
- name: 设置定时任务清理日志
cron:
name: "clean app logs"
minute: 0
hour: 3
job: "find /opt/myapp/logs -mtime +7 -delete"2.4 Handlers 处理器
Handler 是一种特殊任务,仅在某个 Task 通知它时执行,且无论被通知多少次,每个 Handler 只在 Playbook 所有 Task 执行完后执行一次。
tasks:
- name: 更新 Nginx 配置
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: reload nginx # 发送通知
- name: 更新 SSL 证书
copy:
src: ssl/example.crt
dest: /etc/nginx/ssl/
notify: reload nginx # 同一个 Handler 只触发一次
- name: 更新应用代码
synchronize:
src: ./app/
dest: /var/www/html/
notify: restart app # 发送不同通知
handlers:
- name: reload nginx # 被配置变更通知
systemd:
name: nginx
state: reloaded
- name: restart app # 被代码更新通知
systemd:
name: myapp
state: restarted注意:Handler 的执行顺序与其在 handlers 中定义的顺序一致,而非被通知的顺序。可使用 meta: flush_handlers 在 Playbook 中途强制执行所有待处理的 Handler。
2.5 Variables 变量
Ansible 支持多种变量定义方式,变量优先级从高到低为:extra vars(-e 参数) > play vars > host vars > group vars > role defaults。
---
# 方式一:在 Playbook 中定义 vars
- hosts: webservers
vars:
app_name: myapp
app_version: 1.2.0
app_port: 8080
config:
debug: false
max_connections: 100
# 方式二:从外部文件加载变量
vars_files:
- vars/common.yml
- "vars/{{ env }}.yml"
# 方式三:使用 vars_prompt 交互式输入
vars_prompt:
- name: db_password
prompt: "请输入数据库密码"
private: yes
tasks:
- name: 使用变量的方式
debug:
msg: "应用 {{ app_name }} 版本 {{ app_version }} 将在端口 {{ app_port }} 运行"
- name: 写入配置文件
template:
src: app.conf.j2
dest: "/etc/{{ app_name }}/app.conf"变量引用规则:
# 使用 {{ }} 引用变量
- name: 创建应用用户
user:
name: "{{ app_user | default('app') }}"
shell: /sbin/nologin
create_home: yes
# 使用 | 过滤器处理变量
- name: 显示大写应用名
debug:
msg: "{{ app_name | upper }}"
# 嵌套变量引用(使用 [] 或 .)
- name: 使用嵌套变量
debug:
msg: "{{ config['max_connections'] }}" # 方式一
# msg: "{{ config.max_connections }}" # 方式二(等价)变量文件示例 (vars/development.yml):
---
env: development
debug: true
db_host: localhost
db_port: 33062.6 Templates 模板
Ansible 使用 Jinja2 模板引擎,模板文件以 .j2 结尾,支持变量插值、条件判断、循环等。
模板文件示例 (templates/nginx.conf.j2):
server {
listen {{ http_port }};
server_name {{ server_name }};
location / {
root {{ web_root }};
index index.html index.htm;
}
{% if enable_ssl %}
listen 443 ssl;
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
{% endif %}
location /api/ {
proxy_pass http://{{ api_upstream }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Playbook 中的模板任务:
- name: 从模板生成 Nginx 配置
template:
src: nginx.conf.j2
dest: /etc/nginx/conf.d/{{ server_name }}.conf
owner: root
group: root
mode: '0644'
notify: reload nginx2.7 常用模块详解
文件操作模块
# copy - 将本地文件复制到远程主机
- name: 复制文件
copy:
src: files/app.jar
dest: /opt/myapp/app.jar
owner: appuser
group: appuser
mode: '0755'
backup: yes # 覆盖前备份原文件
# file - 管理文件、目录和链接
- name: 创建目录
file:
path: /opt/myapp/logs
state: directory
owner: appuser
group: appuser
mode: '0755'
recurse: yes # 递归创建
- name: 创建符号链接
file:
src: /opt/myapp/app.jar
dest: /opt/myapp/current.jar
state: link
# stat - 获取文件状态信息
- name: 检查 JAR 文件是否存在
stat:
path: /opt/myapp/app.jar
register: jar_file
- name: 打印文件信息
debug:
msg: "文件大小: {{ jar_file.stat.size }} 字节,修改时间: {{ jar_file.stat.mtime }}"服务管理模块
# systemd - 管理 systemd 服务
- name: 启动服务并设置开机自启
systemd:
name: myapp
state: started
enabled: yes
daemon_reload: yes # 重载 systemd 配置(新增 service 文件时使用)
- name: 重启服务
systemd:
name: nginx
state: restarted
# service - 通用服务管理(兼容 SysV init 和 systemd)
- name: 确保服务运行
service:
name: crond
state: running
enabled: yes包管理模块
# yum - RHEL/CentOS 包管理
- name: 安装多个包
yum:
name:
- java-11-openjdk
- nginx
- git
- supervisor
state: present
update_cache: yes
- name: 卸载不需要的包
yum:
name: httpd
state: absent
# apt - Ubuntu/Debian 包管理
- name: 安装依赖
apt:
name:
- openjdk-11-jdk
- nginx
- python3-pip
state: present
update_cache: yes
cache_valid_time: 3600命令执行模块
# command - 执行命令(不支持 shell 特性如管道、重定向)
- name: 检查 Java 版本
command: java -version
register: java_version
changed_when: false
- name: 打印 Java 版本
debug:
msg: "{{ java_version.stderr }}"
# shell - 执行命令(支持 shell 特性)
- name: 查找并删除过期日志
shell: |
find /opt/myapp/logs -name "*.log" -mtime +30 -delete
args:
warn: false # 禁用 shell 警告
# 使用 changed_when 控制变更状态
- name: 检查是否需要重启
shell: "test -f /var/run/reboot-required"
register: reboot_required
failed_when: false
changed_when: reboot_required.rc == 0Docker 相关模块
# docker_image - 管理 Docker 镜像
- name: 拉取 Docker 镜像
docker_image:
name: "{{ image_name }}"
tag: "{{ image_tag }}"
source: pull
force_source: yes
- name: 构建 Docker 镜像
docker_image:
name: myapp
tag: latest
build:
path: /opt/myapp
dockerfile: Dockerfile
source: build
# docker_container - 管理 Docker 容器
- name: 启动应用容器
docker_container:
name: myapp
image: "{{ image_name }}:{{ image_tag }}"
state: started
restart_policy: always
ports:
- "8080:8080"
env:
SPRING_PROFILES_ACTIVE: "{{ env }}"
DB_HOST: "{{ db_host }}"
volumes:
- /opt/myapp/logs:/app/logs
networks:
- name: app-network
# docker_compose - 管理 Docker Compose
- name: 通过 Compose 启动服务
docker_compose:
project_src: /opt/myapp
state: present
restarted: yes2.8 条件判断(when)
when 语句用于根据条件决定是否执行某个任务,支持丰富的条件表达式。
- name: 仅在 CentOS 上执行
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: 仅在 Ubuntu 20.04 上执行
apt:
name: apache2
state: present
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version == "20.04"
- name: 根据变量值决定
template:
src: app.prod.conf.j2
dest: /etc/myapp/app.conf
when: env == "production"
- name: 根据注册变量结果
command: /opt/myapp/bin/upgrade.sh
when: upgrade_needed.stat.exists # 前一个任务注册的变量
- name: 复杂条件组合
systemd:
name: myapp
state: restarted
when:
- (ansible_os_family == "RedHat" or ansible_os_family == "Debian")
- app_version is version("2.0", ">=")
- not rolling_restart | default(false)
- name: 条件执行(基于布尔变量)
debug:
msg: "调试信息已开启"
when: debug_mode | bool2.9 循环(loop / with_items)
Ansible 2.5 起推荐使用统一的 loop 关键字替代传统的 with_* 风格。
# loop - 简单列表循环
- name: 创建多个用户
user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- charlie
# loop - 字典列表循环
- name: 创建带属性的用户
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
group: "{{ item.group }}"
loop:
- { name: alice, uid: 1001, group: devops }
- { name: bob, uid: 1002, group: devops }
- { name: charlie, uid: 1003, group: admin }
# loop - 结合 when 条件
- name: 安装指定服务
systemd:
name: "{{ item }}"
state: started
enabled: yes
loop: "{{ services }}"
when: item != "firewalld"
# 使用 with_items(兼容旧版)
- name: 复制多个文件
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: '0644'
with_items:
- { src: config/app.yml, dest: /opt/myapp/config/ }
- { src: config/logback.xml, dest: /opt/myapp/config/ }
# nested loops - 嵌套循环
- name: 创建多个环境的多个主机目录
file:
path: "/opt/env/{{ item[0] }}/{{ item[1] }}"
state: directory
loop: "{{ ['dev', 'test', 'prod'] | product(['web', 'api', 'worker']) | list }}"
# loop over dict - 循环字典
- name: 输出字典内容
debug:
msg: "{{ item.key }} => {{ item.value }}"
loop: "{{ config | dict2items }}"
# until - 重试循环
- name: 等待服务可用
uri:
url: http://localhost:8080/actuator/health
status_code: 200
register: result
until: result.status == 200
retries: 10
delay: 5三、Ansible Roles
3.1 角色目录结构
Role 是 Ansible 中组织和复用代码的标准方式,它将 tasks、handlers、templates、variables 等按约定目录结构组织。
roles/
└── <role_name>/
├── tasks/ # 主要任务文件,main.yml 为入口
│ └── main.yml
├── handlers/ # Handler 定义,main.yml 为入口
│ └── main.yml
├── templates/ # Jinja2 模板文件
│ └── *.j2
├── files/ # 需要复制到远程主机的静态文件
│ └── *
├── vars/ # 角色变量(优先级高)
│ └── main.yml
├── defaults/ # 默认变量(优先级最低,可被任何其他变量覆盖)
│ └── main.yml
├── meta/ # 角色依赖和元数据
│ └── main.yml
└── tests/ # 测试文件(可选)
├── inventory
└── test.yml3.2 使用 ansible-galaxy 初始化角色
# 创建角色骨架目录
ansible-galaxy init roles/java-app
# 查看生成的目录结构
tree roles/java-app/
# roles/java-app/
# ├── README.md
# ├── defaults
# │ └── main.yml
# ├── files
# ├── handlers
# │ └── main.yml
# ├── meta
# │ └── main.yml
# ├── tasks
# │ └── main.yml
# ├── templates
# ├── tests
# │ ├── inventory
# │ └── test.yml
# └── vars
# └── main.yml
# 从 Ansible Galaxy 下载社区角色
ansible-galaxy install geerlingguy.nginx
ansible-galaxy install geerlingguy.java3.3 Role 依赖管理
在角色 meta/main.yml 中定义依赖关系:
---
dependencies:
- role: common
vars:
packages:
- wget
- curl
- unzip
- role: geerlingguy.java
vars:
java_packages:
- java-11-openjdk3.4 完整示例:部署 Java 应用 Role
本示例演示一个完整的 Ansible Role,实现 Java 应用从环境准备到服务启动的全流程。
角色默认变量 (roles/java-app/defaults/main.yml)
---
# 应用配置
app_name: myapp
app_version: 1.0.0
app_user: "{{ app_name }}"
app_group: "{{ app_name }}"
app_port: 8080
app_java_opts: "-Xms512m -Xmx1024m"
# JDK 配置
java_version: 11
java_home: "/usr/lib/jvm/java-11-openjdk"
# 应用路径
app_install_dir: "/opt/{{ app_name }}"
app_log_dir: "/var/log/{{ app_name }}"
app_config_dir: "/etc/{{ app_name }}"
# JAR 包源路径(控制节点上的路径)
app_jar_source: "{{ playbook_dir }}/files/{{ app_name }}-{{ app_version }}.jar"
# 服务配置
app_service_port: 8080
app_health_check_url: "http://localhost:{{ app_service_port }}/actuator/health"角色主任务 (roles/java-app/tasks/main.yml)
---
- name: 包含特定操作系统的变量
include_vars: "{{ ansible_os_family }}.yml"
- name: 安装 JDK
include_tasks: install-jdk.yml
- name: 创建应用用户和组
include_tasks: create-user.yml
- name: 部署应用
include_tasks: deploy-app.yml
- name: 配置应用
include_tasks: configure-app.yml
- name: 配置 systemd 服务
include_tasks: setup-systemd.yml
- name: 启动服务
include_tasks: start-service.yml安装 JDK (roles/java-app/tasks/install-jdk.yml)
---
- name: 安装 OpenJDK
package:
name: "{{ java_package_name }}"
state: present
register: java_install
- name: 验证 Java 安装
command: "{{ java_home }}/bin/java -version"
register: java_version_check
changed_when: false
- name: 设置 JAVA_HOME 环境变量
lineinfile:
path: /etc/profile.d/java.sh
line: "export JAVA_HOME={{ java_home }}"
create: yes
mode: '0644'
when: java_install.changed部署 JAR 包 (roles/java-app/tasks/deploy-app.yml)
---
- name: 创建应用目录
file:
path: "{{ item }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
loop:
- "{{ app_install_dir }}"
- "{{ app_log_dir }}"
- "{{ app_config_dir }}"
- name: 复制 JAR 包
copy:
src: "{{ app_jar_source }}"
dest: "{{ app_install_dir }}/{{ app_name }}.jar"
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
notify: restart {{ app_name }}配置 systemd 服务 (roles/java-app/tasks/setup-systemd.yml)
---
- name: 写入 systemd service 文件
template:
src: app.service.j2
dest: /etc/systemd/system/{{ app_name }}.service
owner: root
group: root
mode: '0644'
notify:
- daemon-reload {{ app_name }}
- restart {{ app_name }}启动服务 (roles/java-app/tasks/start-service.yml)
---
- name: 确保服务启动并设置开机自启
systemd:
name: "{{ app_name }}"
state: started
enabled: yes
daemon_reload: yes
- name: 等待服务健康检查通过
uri:
url: "{{ app_health_check_url }}"
status_code: 200
timeout: 5
register: health_check
until: health_check.status == 200
retries: 15
delay: 3
changed_when: falsesystemd 模板 (roles/java-app/templates/app.service.j2)
[Unit]
Description={{ app_name }} Java Application
After=network.target
[Service]
Type=simple
User={{ app_user }}
Group={{ app_group }}
WorkingDirectory={{ app_install_dir }}
Environment=JAVA_HOME={{ java_home }}
Environment=SPRING_PROFILES_ACTIVE={{ env | default('production') }}
ExecStart={{ java_home }}/bin/java {{ app_java_opts }} \
-jar {{ app_install_dir }}/{{ app_name }}.jar \
--server.port={{ app_port }} \
--spring.config.additional-location=file:{{ app_config_dir }}/
ExecStop=/bin/kill -15 $MAINPID
SuccessExitStatus=143
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetHandler (roles/java-app/handlers/main.yml)
---
- name: "daemon-reload {{ app_name }}"
systemd:
daemon_reload: yes
- name: "restart {{ app_name }}"
systemd:
name: "{{ app_name }}"
state: restarted使用 Role 的 Playbook
---
# deploy-java-app.yml
- name: 部署 Java 应用到生产环境
hosts: app_servers
become: yes
gather_facts: yes
vars:
env: production
app_version: 1.2.0
db_host: "{{ hostvars['db-master']['ansible_default_ipv4']['address'] }}"
roles:
- role: java-app
vars:
app_port: 8080
app_java_opts: "-Xms1024m -Xmx2048m -Dspring.profiles.active={{ env }}"
- role: geerlingguy.nginx
vars:
nginx_vhosts:
- listen: "80"
server_name: "api.example.com"
locations:
- path: /
proxy_pass: "http://127.0.0.1:8080"部署命令
# 使用 Role 部署应用
ansible-playbook -i inventory/production deploy-java-app.yml --check # 预检模式
ansible-playbook -i inventory/production deploy-java-app.yml # 正式执行
ansible-playbook -i inventory/production deploy-java-app.yml --tags=deploy # 仅执行 deploy 标签的任务
ansible-playbook -i inventory/production deploy-java-app.yml --skip-tags=restart # 跳过重启四、Terraform 基础
4.1 IaC 概念
基础设施即代码(Infrastructure as Code, IaC)是通过代码而非手动流程来管理和配置基础设施的实践方法。其核心价值包括:
- 自动化:消除手动操作,降低人为错误
- 可重现:相同的配置始终产生相同的基础设施
- 版本化:基础设施代码可纳入 Git 等版本控制系统
- 审查与协作:通过代码评审流程确保变更安全
- 一致性:开发、测试、生产环境保持相同的配置
IaC 工具分为两类:
| 分类 | 特点 | 代表工具 |
|---|---|---|
| 声明式(Declarative) | 描述期望的最终状态,工具自动处理如何达到该状态 | Terraform、CloudFormation、Pulumi |
| 命令式(Imperative) | 描述执行步骤,按顺序逐个执行 | Ansible、SaltStack、Puppet |
4.2 HCL 语法
Terraform 使用 HashiCorp Configuration Language (HCL) 编写配置,HCL 是声明式语言。
# 基本语法结构
<BLOCK_TYPE> "<LABEL>" "<LABEL>" {
# 参数
<IDENTIFIER> = <EXPRESSION>
# 嵌套块
<BLOCK_TYPE> {
<IDENTIFIER> = <EXPRESSION>
}
}Resource(资源)
资源是 Terraform 中最核心的块类型,代表一个基础设施对象(如云服务器、安全组、DNS 记录)。
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "web-server-01"
}
}
# resource 块格式:resource "<PROVIDER>_<TYPE>" "<LOCAL_NAME>" { ... }
# LOCAL_NAME 是 Terraform 状态中的资源标识符,在同一模块中必须唯一Data Source(数据源)
数据源用于查询或读取已有的基础设施信息,Terraform 不会创建或管理数据源指向的资源。
# 查询已有 VPC
data "aws_vpc" "default" {
default = true
}
# 查询最新 Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
}
}
# 使用数据源引用
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
subnet_id = data.aws_vpc.default.main_route_table_id
}Variable(变量)
变量使配置更具复用性和灵活性。
# 变量定义
variable "region" {
description = "AWS 部署区域"
type = string
default = "ap-southeast-1"
}
variable "instance_type" {
description = "实例规格"
type = string
default = "t2.micro"
}
variable "instance_count" {
description = "实例数量"
type = number
default = 2
}
variable "tags" {
description = "资源标签"
type = map(string)
default = {
Environment = "production"
ManagedBy = "Terraform"
}
}
variable "allowed_ports" {
description = "允许的端口列表"
type = list(number)
default = [22, 80, 443]
}
# 变量验证(Terraform 0.13+)
variable "env" {
description = "部署环境"
type = string
validation {
condition = contains(["dev", "test", "staging", "production"], var.env)
error_message = "环境必须是 dev、test、staging 或 production。"
}
}Output(输出值)
输出值用于在 apply 后展示关键信息,或供其他 Terraform 配置引用。
# 输出资源属性
output "instance_id" {
description = "ECS 实例 ID"
value = aws_instance.web.id
}
output "public_ip" {
description = "公网 IP 地址"
value = aws_instance.web.public_ip
}
output "private_ip" {
description = "内网 IP 地址"
value = aws_instance.web.private_ip
}
# 敏感输出(apply 后隐藏实际值)
output "db_password" {
description = "数据库密码"
value = random_password.db.result
sensitive = true
}Provider(提供者)
Provider 是 Terraform 与基础设施平台交互的插件,每种云服务商或平台对应一个 Provider。
# AWS Provider
provider "aws" {
region = var.region
# 凭证配置方式(按优先级):
# 1. 环境变量 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
# 2. ~/.aws/credentials 文件
# 3. IAM 实例角色(EC2 上运行时)
# 不推荐在代码中硬编码凭证
}
# 阿里云 Provider
provider "alicloud" {
region = var.region
}
# Docker Provider(本地开发测试)
provider "docker" {
host = "unix:///var/run/docker.sock"
}
# 多 Region/Provider 配置
provider "aws" {
alias = "us_east"
region = "us-east-1"
}
provider "aws" {
alias = "ap_southeast"
region = "ap-southeast-1"
}
# 使用别名 Provider 创建资源
resource "aws_instance" "web_us" {
provider = aws.us_east
ami = "ami-xxx"
instance_type = "t2.micro"
}4.3 Terraform 工作流
Terraform 的核心工作流遵循 init -> plan -> apply -> destroy 四个阶段。
# 1. init - 初始化工作目录,下载 Provider 插件和模块
terraform init
# terraform init -upgrade # 升级 Provider 到最新兼容版本
# 2. plan - 预览变更(不实际创建资源)
terraform plan
terraform plan -out=tfplan # 将计划保存到文件
terraform plan -var="env=production" # 传递变量
terraform plan -var-file=prod.tfvars # 使用变量文件
# 3. apply - 执行变更创建或修改资源
terraform apply
terraform apply tfplan # 应用已保存的计划
terraform apply -auto-approve # 跳过确认提示(CI/CD 中使用)
# 4. destroy - 销毁所有已被管理的资源
terraform destroy
terraform destroy -target=aws_instance.web # 仅销毁特定资源工作流图示:
┌──────┐ ┌──────┐ ┌──────┐ ┌─────────┐
│ init │────>│ plan │────>│ apply│────>│ destroy │
└──────┘ └──────┘ └──────┘ └─────────┘
│ │ │
▼ ▼ ▼
Provider .tfplan state file
下载与 变更预览 (terraform.tfstate)
模块初始化4.4 状态管理
Terraform 使用状态文件(terraform.tfstate)跟踪已创建资源的元数据、属性和依赖关系。状态管理是 Terraform 使用的关键环节。
本地状态
# 默认使用本地文件存储状态
# terraform.tfstate 文件生成在当前工作目录
# backend 配置
terraform {
backend "local" {
path = "terraform.tfstate"
}
}远程状态(Backend)
远程状态存储是团队协作的必要条件,它确保多个成员共享同一份状态文件,避免冲突。
# AWS S3 + DynamoDB 远程状态
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "production/network/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-state-lock" # DynamoDB 表用于状态锁定
}
}
# 阿里云 OSS + Tablestore 远程状态
terraform {
backend "oss" {
bucket = "my-terraform-state"
prefix = "production/network"
region = "cn-hangzhou"
encrypt = true
tablestore_table = "terraform-state-lock"
}
}
# 注意:backend 配置在 init 后不可更改
# 如需迁移,需在本地重新 init 或使用 terraform state mv状态管理最佳实践:
- 始终使用远程后端存储状态
- 启用状态锁定(DynamoDB / Tablestore)防止并发冲突
- 定期备份状态文件
- 敏感数据(如数据库密码)存储在状态中时,启用 S3/OSS 加密
- 按环境(dev/test/prod)和组件(network/app/db)拆分状态文件
五、Terraform 完整示例
5.1 示例一:部署云服务器 + 安全组 + 域名解析(AWS)
以下示例完整演示使用 Terraform 在 AWS 上创建基础设施。
目录结构
terraform-aws-demo/
├── main.tf # 主资源配置
├── variables.tf # 变量定义
├── outputs.tf # 输出值
├── terraform.tfvars # 变量值赋值
├── provider.tf # Provider 配置
└── versions.tf # 版本约束Provider 配置 (provider.tf)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-company-terraform-state"
key = "production/web-service/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
provider "aws" {
region = var.aws_region
}变量定义 (variables.tf)
variable "aws_region" {
description = "AWS 部署区域"
type = string
}
variable "environment" {
description = "部署环境"
type = string
}
variable "project_name" {
description = "项目名称"
type = string
}
variable "vpc_cidr" {
description = "VPC CIDR 地址段"
type = string
default = "10.0.0.0/16"
}
variable "instance_type" {
description = "EC2 实例规格"
type = string
default = "t3.medium"
}
variable "instance_count" {
description = "EC2 实例数量"
type = number
default = 2
}
variable "domain_name" {
description = "域名"
type = string
}主资源配置 (main.tf)
# 创建 VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-${var.environment}-vpc"
Environment = var.environment
}
}
# 创建子网
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-${count.index + 1}"
Environment = var.environment
}
}
# 互联网网关
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
Environment = var.environment
}
}
# 路由表
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-${var.environment}-rt"
Environment = var.environment
}
}
# 路由表关联
resource "aws_route_table_association" "public" {
count = 2
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# 安全组 - Web 服务器
resource "aws_security_group" "web" {
name = "${var.project_name}-${var.environment}-web-sg"
description = "Web 服务器安全组"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.ssh_allowed_cidrs
}
ingress {
description = "应用端口"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-web-sg"
Environment = var.environment
}
}
# 安全组 - 数据库
resource "aws_security_group" "db" {
name = "${var.project_name}-${var.environment}-db-sg"
description = "数据库安全组,仅允许 Web 服务器访问"
vpc_id = aws_vpc.main.id
ingress {
description = "MySQL"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-db-sg"
Environment = var.environment
}
}
# 查找最新 Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# 可用区数据源
data "aws_availability_zones" "available" {
state = "available"
}
# SSH 密钥对(假设已有密钥对)
resource "aws_key_pair" "deploy" {
key_name = "${var.project_name}-${var.environment}-key"
public_key = file("~/.ssh/id_rsa.pub")
}
# EC2 实例
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[count.index % 2].id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.deploy.key_name
root_block_device {
volume_size = 50
volume_type = "gp3"
encrypted = true
}
tags = {
Name = "${var.project_name}-${var.environment}-web-${count.index + 1}"
Environment = var.environment
Role = "web"
}
}
# Elastic IP
resource "aws_eip" "web" {
count = var.instance_count
domain = "vpc"
instance = aws_instance.web[count.index].id
associate_with_private_ip = aws_instance.web[count.index].private_ip
tags = {
Name = "${var.project_name}-${var.environment}-eip-${count.index + 1}"
Environment = var.environment
}
}
# Route53 域名解析
data "aws_route53_zone" "primary" {
name = var.domain_name
private_zone = false
}
resource "aws_route53_record" "web" {
count = var.instance_count
zone_id = data.aws_route53_zone.primary.zone_id
name = "web-${count.index + 1}.${var.domain_name}"
type = "A"
ttl = 300
records = [aws_eip.web[count.index].public_ip]
}
# 负载均衡记录(如果使用 ALB)
resource "aws_route53_record" "api" {
zone_id = data.aws_route53_zone.primary.zone_id
name = "api.${var.domain_name}"
type = "A"
alias {
name = aws_lb.web.dns_name
zone_id = aws_lb.web.zone_id
evaluate_target_health = true
}
}
# Application Load Balancer
resource "aws_lb" "web" {
name = "${var.project_name}-${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.web.id]
subnets = aws_subnet.public[*].id
tags = {
Environment = var.environment
}
}
resource "aws_lb_target_group" "web" {
name = "${var.project_name}-${var.environment}-tg"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/actuator/health"
port = 8080
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 5
interval = 30
}
}
resource "aws_lb_target_group_attachment" "web" {
count = var.instance_count
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web[count.index].id
port = 8080
}
resource "aws_lb_listener" "web" {
load_balancer_arn = aws_lb.web.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.ssl_certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}输出值 (outputs.tf)
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "web_public_ips" {
description = "Web 服务器公网 IP 地址"
value = aws_eip.web[*].public_ip
}
output "web_private_ips" {
description = "Web 服务器内网 IP 地址"
value = aws_instance.web[*].private_ip
}
output "alb_dns_name" {
description = "负载均衡 DNS 名称"
value = aws_lb.web.dns_name
}
output "security_group_ids" {
description = "安全组 ID"
value = {
web = aws_security_group.web.id
db = aws_security_group.db.id
}
}
output "connect_instructions" {
description = "连接说明"
value = <<-EOT
服务器 IP 地址:
${join("\n ", aws_eip.web[*].public_ip)}
SSH 连接示例:
ssh -i ~/.ssh/id_rsa ubuntu@${aws_eip.web[0].public_ip}
API 访问地址:
https://api.${var.domain_name}
EOT
}变量文件 (terraform.tfvars)
aws_region = "ap-southeast-1"
environment = "production"
project_name = "myapp"
vpc_cidr = "10.0.0.0/16"
instance_type = "t3.medium"
instance_count = 2
domain_name = "example.com"
ssh_allowed_cidrs = ["你的办公公网IP/32"]
ssl_certificate_arn = "arn:aws:acm:ap-southeast-1:123456789:certificate/xxxx-xxxx"执行流程
# 1. 初始化
cd terraform-aws-demo
terraform init
# 2. 格式化代码
terraform fmt
# 3. 验证语法
terraform validate
# 4. 预览
terraform plan -var-file=terraform.tfvars
# 5. 部署
terraform apply -var-file=terraform.tfvars -auto-approve
# 6. 查看输出
terraform output
# 7. 清理资源
terraform destroy -var-file=terraform.tfvars -auto-approve5.2 示例二:Docker 容器部署(本地开发测试)
使用 Terraform Docker Provider 在本地部署容器,适合学习和测试。
目录结构
terraform-docker-demo/
├── main.tf
├── variables.tf
└── outputs.tf配置代码 (main.tf)
terraform {
required_version = ">= 1.5.0"
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
# 拉取 Nginx 镜像
resource "docker_image" "nginx" {
name = "nginx:1.25-alpine"
keep_locally = true
}
# 创建自定义网络
resource "docker_network" "app_network" {
name = "app-network"
}
# Nginx 容器
resource "docker_container" "nginx" {
name = "nginx-proxy"
image = docker_image.nginx.image_id
networks_advanced {
name = docker_network.app_network.name
}
ports {
internal = 80
external = 80
}
volumes {
host_path = abspath("${path.module}/html")
container_path = "/usr/share/nginx/html"
}
}
# 拉取应用镜像(假设已有)
resource "docker_image" "app" {
name = "myapp:latest"
keep_locally = true
}
# 应用容器
resource "docker_container" "app" {
name = "myapp-backend"
image = docker_image.app.image_id
networks_advanced {
name = docker_network.app_network.name
}
ports {
internal = 8080
external = 8080
}
env = [
"SPRING_PROFILES_ACTIVE=production",
"DB_HOST=database",
"DB_PORT=3306"
]
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval = "30s"
timeout = "10s"
retries = 3
}
}变量和输出
# variables.tf
variable "app_version" {
description = "应用版本号"
type = string
default = "latest"
}
# outputs.tf
output "nginx_url" {
value = "http://localhost:80"
}
output "app_url" {
value = "http://localhost:8080"
}六、Ansible + Terraform 组合
Terraform 擅长创建和管理基础设施资源,Ansible 擅长配置管理和应用部署。将两者组合使用可以实现从基础设施到应用的端到端自动化。
6.1 组合模式总览
┌─────────────────────────────────────────────────────┐
│ CI/CD Pipeline │
│ (Jenkins / GitLab CI / GitHub Actions) │
└─────────────────────────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Terraform │ │ Ansible │
│ 基础设施层 │ │ 配置部署层 │
├──────────────────┤ ├──────────────────┤
│ VPC / 网络 │ ──────> │ 主机名设置 │
│ 安全组 │ 传递 │ 系统参数调优 │
│ 云服务器 EC2 │ 输出 │ JDK/Nginx 安装 │
│ 负载均衡 ALB │ IP │ 应用部署 │
│ 数据库 RDS │ 地址 │ 服务注册 │
│ DNS 解析 │ │ 监控 Agent 配置 │
└──────────────────┘ └──────────────────┘6.2 详细组合流程
第一步:Terraform 创建基础设施
# terraform/main.tf(示例片段)
resource "aws_instance" "web" {
count = 3
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
tags = {
Name = "web-${count.index + 1}"
Role = "web-server"
}
}
# 输出 IP 地址供 Ansible 使用
output "web_ips" {
value = {
# 输出 IP 并生成 Ansible Inventory 格式
hosts = {
for i, instance in aws_instance.web :
"web-${i + 1}" => {
ansible_host = instance.public_ip
private_ip = instance.private_ip
}
}
}
}第二步:生成 Ansible Inventory
#!/bin/bash
# generate-inventory.sh
# 从 Terraform 输出生成 Ansible Inventory 文件
cd terraform/
terraform output -json web_ips > /tmp/web_ips.json
# 使用 jq 生成 INI 格式 Inventory
cat <<'INVENTORY' > ../ansible/inventory/production
[webservers]
INVENTORY
cat /tmp/web_ips.json | jq -r '.hosts | to_entries[] | "\(.key) ansible_host=\(.value.ansible_host) private_ip=\(.value.private_ip)"' \
>> ../ansible/inventory/production
echo "" >> ../ansible/inventory/production
echo "[webservers:vars]" >> ../ansible/inventory/production
echo "ansible_user=ubuntu" >> ../ansible/inventory/production
echo "ansible_ssh_private_key_file=~/.ssh/id_rsa" >> ../ansible/inventory/production
echo "Inventory 文件已生成。"第三步:使用 Terraform Inventory 插件
Terraform 社区提供了专门的 Ansible Inventory 插件,可以直接使用 Terraform 状态文件动态生成 Inventory。
# ansible/inventory/terraform.yml
# 使用 community.general.terraform_inventory 插件
plugin: community.general.terraform_inventory
project_path: /path/to/terraform/
hostnames:
- name或者使用 terraform-inventory 工具:
# 安装 terraform-inventory
pip install terraform-inventory
# 使用 Terraform 状态作为动态 Inventory
ansible-playbook -i terraform/ deploy.yml第四步:Ansible 执行配置部署
---
# deploy.yml
- name: 部署应用到 Terraform 创建的基础设施
hosts: webservers
become: yes
gather_facts: yes
vars:
app_version: 1.2.0
app_port: 8080
roles:
- role: common
- role: java-app
- role: geerlingguy.nginx第五步:完整的 CI/CD 组合
# .gitlab-ci.yml 示例
stages:
- terraform-plan
- terraform-apply
- ansible-deploy
- smoke-test
terraform-plan:
stage: terraform-plan
script:
- cd terraform/
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- terraform/tfplan
terraform-apply:
stage: terraform-apply
script:
- cd terraform/
- terraform apply tfplan
- terraform output -json > ../inventory/terraform_output.json
dependencies:
- terraform-plan
generate-inventory:
stage: ansible-deploy
script:
- cd inventory/
- ./generate-inventory.sh
ansible-deploy:
stage: ansible-deploy
script:
- cd ansible/
- ansible-playbook -i inventory/production deploy.yml
dependencies:
- generate-inventory6.3 使用 Terraform Provisioner(替代方案)
Terraform 也内置了 Provisioner(如 file、remote-exec),可用于在资源创建后执行配置任务。Ansible 组合方案通常更优。
# Terraform 内置 Provisioner 示例(简单场景适用)
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
# 复制文件
provisioner "file" {
source = "app.conf"
destination = "/tmp/app.conf"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
# 远程执行命令
provisioner "remote-exec" {
inline = [
"sudo apt update",
"sudo apt install -y nginx",
"sudo systemctl enable nginx",
"sudo systemctl start nginx",
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
}注意:Provisioner 是 Terraform 的"最后手段",官方文档建议仅在无法使用其他工具时才使用 Provisioner。Ansible 组合方案在可维护性、幂等性和功能丰富度上远超 Provisioner。
七、对比选型
7.1 Ansible vs Terraform vs SaltStack vs Puppet
| 维度 | Ansible | Terraform | SaltStack | Puppet |
|---|---|---|---|---|
| 定位 | 配置管理与应用部署 | 基础设施编排(IaC) | 配置管理与远程执行 | 配置管理 |
| 架构 | 无 Agent(Push模式) | 无 Agent(声明式编排) | Master/Minion(可无Agent) | Master/Agent(Pull模式) |
| 通信协议 | SSH / WinRM | 云 API / Docker API | ZeroMQ / TLS | HTTPS(REST API) |
| 配置语言 | YAML | HCL(声明式) | YAML / Jinja2 / Python | 自定义 DSL(Puppet Language) |
| 幂等性 | 是(模块保证) | 是(声明式保证) | 是(状态管理) | 是(资源保证) |
| 学习曲线 | 低 | 中 | 中 | 高(DSL 独特) |
| 状态管理 | 无中心化状态 | 有状态文件(.tfstate) | 有(Master 维护) | 有(PuppetDB) |
| 资源编排顺序 | 线性执行(自上而下) | 依赖图自动解析 | 可配置 | 自动依赖解析 |
| 操作系统兼容 | Linux/Windows/macOS | 跨平台(主要编排层) | Linux(主)/Windows | Linux/Windows |
| 社区活跃度 | 高(Red Hat 维护) | 极高(HashiCorp 维护) | 中(VMware 维护) | 中(Perforce 维护) |
| 云平台支持 | 通过模块(有限) | 原生支持(涵盖主要云商) | 通过模块 | 通过模块 |
| 编排层级 | OS 配置层 | 基础设施层(IaaS/PaaS) | OS 配置层 | OS 配置层 |
| 实时执行 | 支持(ad-hoc) | 不支持(仅编排) | 支持(远程执行) | 不支持 |
| 回滚 | 手动(重新运行 Playbook) | 通过状态文件回滚 | 手动 | 通过报告回滚 |
| 企业版 | Ansible Automation Platform | Terraform Enterprise / Cloud | SaltStack Enterprise | Puppet Enterprise |
| 适用场景 | 配置管理、应用部署、任务编排 | 多云基础设施创建与管理 | 大规模服务器配置、实时管理 | 标准化配置管理、合规 |
7.2 选择建议
选择 Ansible 的场景
- 已有大量服务器需要做配置标准化和自动化运维
- 需要跨多种操作系统(Linux + Windows)统一管理
- 团队规模小,希望快速上手且 Agentless 降低复杂度
- 需要结合 CI/CD 流水线做应用部署和滚动更新
- 临时任务批量执行(ad-hoc 命令)
选择 Terraform 的场景
- 多云架构(AWS + 阿里云 + GCP)统一基础设施管理
- 基础设施需要版本控制和代码审查流程
- 需要声明式管理云资源(VPC、安全组、负载均衡等)
- 团队需要可视化的基础设施变更预览(terraform plan)
- 基础设施状态需可靠存储和并发锁定
组合使用(Ansible + Terraform)
- 需要同时管理基础设施和服务器配置(推荐模式)
- Terraform 负责"创建",Ansible 负责"配置"
- 大规模生产环境的最佳实践方案
选择 SaltStack 的场景
- 需要极大规模(数千+节点)的实时管理
- 需要事件驱动自动化(Event-Driven)
- 团队已有 Python 技术栈积累
- 需要比 Ansible 更细粒度的远程执行控制
选择 Puppet 的场景
- 需要严格的配置合规和审计能力
- 企业已有成熟 CMDB 或 ITIL 流程
- 需要声明式策略强制执行(而非 Playbook 执行一次)
- 团队有能力学习和维护 Puppet DSL
7.3 技术栈总体评价
快速上手
│
│ Ansible
│
│
│ ●
│ SaltStack │││ Terraform
复 │ ● │ │ ●
杂 │ │││ │ │ │││
部 │ │ │ │ │ │ │
署 │ │ │ │ │ │ │
能 ││ │ │ │ │ │
力 ●─────┴──────┴──────┴────●──────▶ 基础设施管理能力
│ │
│ Puppet │
│ ● │
│ │││ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│
│
学习成本在 DevOps 实践中,Ansible 和 Terraform 是目前社区最广泛采用的工具组合,二者在能力上互补而非重叠:
- Terraform 专注于基础设施的声明式定义和生命周期管理,将"硬件"(云资源)版本化、可审计
- Ansible 专注于基础设施之上的配置管理和应用部署,将"软件"(OS 配置、中间件、应用)标准化、自动化
二者结合使用,形成了一个完整的 DevOps 自动化解决方案:Terraform 负责"创建",Ansible 负责"配置",覆盖了从云资源到应用服务的全生命周期。