自动化运维Ansible
Puppet:基于 Ruby,C/S 架构,可扩展强;远程命令执行偏弱。SaltStack:基于 Python,C/S 架构,较轻量,配置用 YAML;需要在每台被控端安装 agent。Ansible:基于 Python,分布式,无客户端,轻量;远程命令执行强。默认通过 SSH 连接。通俗理解:Ansible 更容易“开箱即用”,无需在被控端安装东西(免 agent),非常适合快速批量操作与配置编
目录
一、介绍
1. 自动化运维工具对比
- Puppet:基于 Ruby,C/S 架构,可扩展强;远程命令执行偏弱。
- SaltStack:基于 Python,C/S 架构,较轻量,配置用 YAML;需要在每台被控端安装 agent。
- Ansible:基于 Python,分布式,无客户端,轻量;YAML + Jinja2;远程命令执行强。默认通过 SSH 连接。
通俗理解:Ansible 更容易“开箱即用”,无需在被控端安装东西(免 agent),非常适合快速批量操作与配置编排。
2. Ansible 简介与特性
- Ansible 是自动化运维工具,基于模块工作(核心是模块,Ansible 负责编排和分发调用)。
- 特性:
- no agents:被控端无需安装客户端;升级只需升级控制端。
- no server:无中心服务端,直接用命令/剧本即可。
- modules in any languages:模块可用任意语言实现。
- yaml, not code:用 YAML 写剧本(Playbook),更易读。
- ssh by default:默认基于 SSH 连接。
- 组件简述:
- connection plugins:连接插件,默认 SSH。
- host inventory:主机清单,定义被控主机与分组。
- modules:模块(command、copy、user…)。
- plugins:插件(连接、回调、邮件等)。
- playbook:编排多任务的 YAML 定义。
- 默认并发数:5(可在 ansible.cfg 中调整 forks)。
3. 变量优先级(从高到低)
- 命令行变量:
-e/--extra-vars(最高)。 - Inventory 中的主机/组变量。
- Playbook 内的
vars与vars_files。 - Facts(setup 收集)。
- 角色的默认变量:
roles/*/defaults/main.yml最低。
注:新写法建议使用 ansible_user/ansible_password/ansible_port/ansible_ssh_private_key_file,不再推荐旧名 ansible_ssh_user/ansible_ssh_pass(兼容但逐步淘汰)。
二、Ansible 安装(CentOS 7)
1. 环境准备
- 关闭防火墙与 SELinux(生产建议按需放行端口而非直接关闭,这里为教学简化)。
- 控制节点 1 台,被控节点若干。示例:
# /etc/hosts(各机均添加,便于主机名解析)
192.168.1.10 ansible-web1
192.168.1.11 ansible-web2
192.168.1.12 ansible-web3
192.168.1.9 ansible-server # 控制端
- SSH 免密:在控制端生成密钥并分发至各被控端。
ssh-keygen -t rsa -b 4096 -C "ansible@server"
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.10
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.11
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.12
提示:无免密也可用密码登录,但不便于自动化;建议配置免密。
2. 安装步骤
CentOS 7 默认 Python 2.7,但新版本 Ansible 更推荐 Python 3。常见两种方式:
- 方式 A(简单):EPEL + yum 安装 Ansible(一般为 2.9.x),可在 Python 2.7 下运行。
yum install -y epel-release
yum install -y ansible
ansible --version
ansible --help
- 方式 B(推荐):安装 Python 3(EPEL 或 IUS),用 pip 安装较新 Ansible。
yum install -y epel-release
yum install -y python36 python3-pip
pip3 install --upgrade pip
pip3 install "ansible<8" # 在 CentOS 7 上兼容较佳的版本区间
ansible --version
说明:CentOS 7 年代较老,新版 Ansible 可能依赖较新 Python;如遇依赖冲突,选择方式 A 或锁定版本。
3. 基础配置与 Inventory
- 查看默认配置文件位置:
rpm -qc ansible
# /etc/ansible/ansible.cfg
# /etc/ansible/hosts
- 主配置:
/etc/ansible/ansible.cfg(日志、forks、模块路径、回调插件等)。 - 主机清单:
/etc/ansible/hosts(建议直接使用 IP,避免 DNS 依赖)。
示例(单主机与分组):
# 直接添加主机
ansible-web1
# 添加主机组
[webservers]
192.168.1.11
ansible-web2
# 组合组(children)
[webservers1]
ansible-web1
[webservers2]
ansible-web2
[weball:children]
webservers1
webservers2
# 组变量(组内主机均生效)
[weball:vars]
ansible_user=root
ansible_port=22
# 使用私钥(如路径非默认时设置)
# ansible_ssh_private_key_file=/root/.ssh/id_rsa
# 若无免密,也可用密码(不安全,演示用)
# ansible_password=your_password
查看组内主机:
ansible weball --list-hosts
使用自定义 Inventory(文件 /opt/hostlist):
[all:vars]
ansible_user=root
ansible_port=22
# ansible_password=your_password
[all]
ansible-web1
ansible-web2
执行时指定:
ansible -i /opt/hostlist all -m ping -o
提示:ping 模块是 SSH 探活(非 ICMP),等价于“能否连上 22 端口并执行模块”。
三、Ad-Hoc 命令与常用模块
命令格式与常用选项
ansible <pattern> -m <module_name> -a <arguments> [其他选项]
- pattern:主机/组名/IP/别名,
all为全部;支持通配与正则。 -m:模块名,默认command。-a:模块参数。-o:单行输出(更紧凑)。
ping 探活
# 单台
ansible ansible-web1 -m ping -o
# 多台
ansible ansible-web1,ansible-web2 -m ping -o
# 组
ansible webservers1 -m ping -o
shell vs command
- shell:支持管道、重定向、通配符;适合复杂命令链。
- command:直接执行命令,不经 shell;更高效/安全。
# 两者等效示例(简单命令)
ansible webservers1 -m shell -a 'uptime'
ansible webservers1 -a 'uptime' # 默认 command
copy 复制与备份
常用参数:src/dest/owner/group/mode/backup。
# 控制端 /root/a.txt -> 远端 /opt/ (保留权限)
ansible weball -m copy -a 'src=/root/a.txt dest=/opt owner=root group=root mode=0644' -o
# 覆盖时备份
ansible weball -m copy -a 'src=/root/a.txt dest=/opt/ owner=root group=root mode=0644 backup=yes' -o
user 用户管理
# 添加/删除用户
ansible ansible-web1 -m user -a "name=qianfeng"
ansible ansible-web1 -m user -a "name=qianfeng state=absent" -o
ansible ansible-web1 -m user -a "name=qianfeng state=absent remove=yes" # 连同家目录/邮件删除
# 设置密码(需哈希值,而非明文)
python - <<'PY'
import crypt
print(crypt.crypt('12345678'))
PY
# 假设输出为 $6$.... 形如 /etc/shadow 的哈希
ansible ansible-web1 -m user -a "name=tom password='$6$...'"
# 生成 SSH 密钥对(在被控端为该用户创建 ~/.ssh/id_rsa*)
ansible ansible-web1 -m user -a "name=tom generate_ssh_key=yes"
yum 包管理
# 安装 httpd(最新)
ansible webservers1 -m yum -a "name=httpd state=latest" -o
# 卸载 httpd
ansible webservers1 -m yum -a "name=httpd state=removed" -o
常见 state:
latest:安装最新present/installed:确保已安装absent/removed:卸载
service 服务管理
ansible webservers1 -m service -a "name=httpd state=started" # 启动
ansible webservers1 -m service -a "name=httpd state=stopped" # 停止
ansible webservers1 -m service -a "name=httpd state=restarted" # 重启
ansible webservers1 -m service -a "name=httpd state=started enabled=yes" # 开机自启
提示:CentOS 7 使用 systemd;某些模块也支持 enabled 单独设置。
file 文件/目录/链接
# 创建空文件
ansible webservers1 -m file -a 'path=/tmp/88.txt mode=0777 state=touch'
# 创建目录
ansible webservers1 -m file -a 'path=/tmp/99 mode=0777 state=directory'
更多 Playbook 写法(更可读):
- name: Ensure testfile exists
file:
path: /tmp/testfile
state: touch
- name: Ensure testdir exists
file:
path: /tmp/testdir
state: directory
owner: myuser
group: mygroup
mode: '0755'
- name: Remove testfile
file:
path: /tmp/testfile
state: absent
- name: Create a symlink
file:
src: /tmp/original
dest: /tmp/link
state: link
- name: Recursively chmod
file:
path: /tmp/testdir
state: directory
mode: '0755'
recurse: yes
script 远程执行本地脚本
适合“控制端有脚本,但被控端没有”的场景。Ansible 会把脚本临时传到远端执行。
# 本地脚本
cat > /root/test.sh <<'SH'
#!/usr/bin/env bash
touch test{1..50}
SH
chmod +x /root/test.sh
# 在远端 /mnt 目录下执行该脚本
ansible webservers1 -m script -a "chdir=/mnt /root/test.sh"
# 条件执行示例(存在某文件才执行/或不存在才执行)
cat > /root/awk.sh <<'SH'
#!/usr/bin/env bash
awk -F: '{print $1,$2}' /etc/passwd
SH
chmod +x /root/awk.sh
ansible webservers1 -m script -a "/root/awk.sh removes=/etc/passwd"
说明:removes=/path 表示当该路径存在时才执行;creates=/path 表示当该路径存在时跳过执行。
setup 收集 Facts
# 收集全部 facts
ansible webservers1 -m setup
# 仅过滤 IPv4 地址
ansible webservers1 -m setup -a 'filter=ansible_all_ipv4_addresses'
常用 facts:
ansible_all_ipv4_addresses、ansible_default_ipv4ansible_distribution/ansible_kernelansible_processor_cores/ansible_mem_totalansible_python_version/ansible_pkg_mgr
Playbook 收集示例:
- hosts: all
tasks:
- name: 收集硬件信息
setup:
gather_subset: hardware
- name: 收集网络信息
setup:
gather_subset: network
archive 打包
ansible webservers1 -m archive -a "path=/etc dest=/mnt/$(date +%F)-etc.tar.gz format=gz"
支持 tar/zip/gz/bz2 等格式,常用参数:path/dest/format/owner/mode。
unarchive 解压
# 从控制端复制并解压到远端
ansible all -m unarchive -a "src=/root/arc.tar.gz dest=/root/" --become
# 远端已有压缩包(不复制,只解压)
ansible all -m unarchive -a "src=/path/file.tar.gz dest=/path/ remote_src=yes"
# 覆盖已有
ansible all -m unarchive -a "src=/root/a.tar.gz dest=/root/ extra_opts=--overwrite"
cron 定时任务
# 新增定时任务
ansible webservers -m cron -a 'name="deletefile" minute="53" hour="20" day="7" month="5" job="rm -rf /mnt/*"'
# 删除定时任务
ansible webservers -m cron -a "name='deletefile' state=absent"
更多 Playbook 写法:
- name: 每日 2 点备份
cron:
name: "daily backup"
minute: "0"
hour: "2"
job: "/usr/local/bin/backup.sh"
get_url 下载文件
ansible webservers -m get_url -a "url=https://download.redis.io/releases/redis-7.0.10.tar.gz dest=/mnt force=yes"
常用参数:url/dest/backup/force/url_username/url_password。
yum_repository 管理仓库源
# 创建本地源
ansible webserver -m yum_repository -a "name='Centos Base' file=base description='test' baseurl=file:///mnt/centos enabled=yes gpgcheck=no"
# 配置阿里 EPEL 源
ansible webserver -m yum_repository -a "name=aliepel baseurl=https://mirrors.aliyun.com/epel/7/x86_64/ enabled=yes gpgcheck=yes gpgcakey=https://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-7 state=present file=AlicloudEpel description=alepel"
# 删除仓库
ansible webserver -m yum_repository -a "file=base name='Centos Base' state=absent"
lineinfile 修改/插入行
关键参数:path/line/state/regexp/insertafter/create/backup。
用法场景:批量改配置文件的某行,如 PermitRootLogin yes。
debug 调试输出
# 输出字符串
ansible webservers -m debug -a "msg=hello,beijing"
# 输出变量(通过 -e 传入)
ansible webservers -m debug -a "var=a" -e "a=1234567"
Playbook 中常配合 register 把任务结果存变量后再 debug 打印。
四、Ansible Playbook 剧本
Playbook 基础与结构
- Playbook 用 YAML 描述,适合多步骤批量操作。
- 一个 Playbook = 若干个 Play;一个 Play = 在一组主机上按序执行多个任务(tasks)。
示例:
---
- name: first play
gather_facts: false # 跳过 facts 可加速
hosts: webservers
remote_user: root # 建议改用 become 提权
tasks:
- name: test connection
ping:
- name: disable selinux
command: '/sbin/setenforce 0'
ignore_errors: true
- name: disable firewalld
service: name=firewalld state=stopped
- name: install httpd
yum: name=httpd state=latest
- name: install configuration file for httpd
copy: src=/opt/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify: "restart httpd" # 若变更则触发 handler
- name: start httpd service
service: enabled=true name=httpd state=started
handlers:
- name: restart httpd
service: name=httpd state=restarted
提示(最佳实践):
- 生产环境更推荐
become: yes以普通用户登录再提权,少用 root 直连。 - 文件路径/模板请用
templates/+template模块,便于变量化。
基础命令/输出颜色含义
ansible-playbook test.yml # 运行
ansible-playbook test.yml --syntax-check # 语法检查
ansible-playbook test.yml --list-tasks # 列出 tasks
ansible-playbook test.yml --list-hosts # 列出主机
ansible-playbook test.yml --start-at-task 'install httpd'
颜色:绿色 success;黄色 changed(状态有变更);红色 failed(需排查)。
Tasks 示例
- hosts: webservers1
user: root
tasks:
- name: create file
file: state=touch mode=0777 path=/tmp/playbook.txt
- name: create dir
file: path=/mnt/dir12 state=directory
Handlers/Notify 触发器
- 只有当某任务状态为 changed 时才触发对应 handler(并且在本 play 的所有普通任务结束后执行)。
- hosts: webservers1
tasks:
- name: test copy
copy: src=/root/a.txt dest=/mnt
notify: test handlers
handlers:
- name: test handlers
shell: echo "abcd" >> /mnt/a.txt
常见用法:配置文件变更后“重启服务”。
循环/迭代(with_items/loop)
- 字符串列表:
- hosts: websrvs
tasks:
- name: install packages
yum:
name: "{{ item }}"
state: latest
with_items:
- httpd
- httpd-tools
- php
- php-mysql
- php-mbstring
- php-gd
- loop(推荐新写法)创建用户/组:
- hosts: test
tasks:
- name: Create Groups
group:
name: "{{ item }}"
loop:
- group1
- group2
- group3
- name: Create Users
user:
name: "{{ item.user }}"
group: "{{ item.group }}"
uid: "{{ item.uid }}"
loop:
- { user: jack, group: group1, uid: 2001 }
- { user: tom, group: group2, uid: 2002 }
- { user: alice,group: group3, uid: 2003 }
- 批量安装/卸载:
- hosts: 192.168.157.129
tasks:
- name: install epel
yum: name=epel-release state=latest
- name: install packages
yum:
name: "{{ item }}"
state: latest
loop:
- httpd
- mysql
- nginx
- redis
自定义变量与 vars_files
- 优势:更清晰、可复用、一处修改全局生效。
- 变量文件示例:
/etc/ansible/vars/file.yml
src_path: /root/test/a.txt
dest_path: /opt/test/
- Playbook 引用:
- hosts: ansible-web1
vars_files:
- /etc/ansible/vars/file.yml
tasks:
- name: create directory
file: path={{ dest_path }} mode=0755 state=directory
- name: copy file
copy: src={{ src_path }} dest={{ dest_path }}
分组与多 Play 示例(group 模块)
- hosts: webserver1
tasks:
- name: create a group
group: name=mygrp system=yes
- name: create a user
user: name=tom group=mygrp system=yes
- hosts: webserver2
tasks:
- name: install apache
yum: name=httpd state=latest
- name: start httpd service
service: name=httpd state=started
gather_facts 与变量示例
- hosts: ansible-web1
gather_facts: false
vars:
user: jack
src_path: /root/a.txt
dest_path: /mnt/
tasks:
- name: create user
user: name={{ user }}
- name: copy file
copy: src={{ src_path }} dest={{ dest_path }}
debug 调试 Playbook
- hosts: webserver
tasks:
- name: create file
file: path=/mnt/debug.txt state=touch
register: create_file
- name: 输出创建过程
debug:
msg: "{{ create_file }}"
- hosts: webserver
vars:
user1: jack
tasks:
- name: 打印变量
debug:
var: user1
条件判断 when
- 作用:按条件决定 task 执行与否。常用比较:
> >= < <= == !=。
- 根据主机名条件创建文件:
- hosts: webserver
tasks:
- name: create file when hostname is localhost
file: path=/mnt/test1.txt state=touch
register: create_file
when: ansible_hostname == "localhost"
- 仅在 CentOS 上安装包:
- hosts: webserver
tasks:
- name: install package
ignore_errors: true
yum:
name: "{{ item }}"
state: latest
loop:
- nginx
- redis
when: ansible_distribution == "CentOS"
- 文件为空则写入内容:
- hosts: webserver
tasks:
- name: ensure file
file: path=/opt/file.txt state=touch
- name: check file content
shell: cat /opt/file.txt
register: check_file
- name: insert hello when empty
shell: echo "hello" >> /opt/file.txt
when: check_file.stdout == ""
- 服务未启动则启动(以 mysqld 为例):
- hosts: webserver
tasks:
- name: check mysql status
shell: systemctl is-active mysqld
register: mysql_status
ignore_errors: true
- name: start mysql when inactive
service: name=mysqld state=started
when: mysql_status.rc != 0
五、项目综合实战:Nginx + PHP Web 部署(CentOS 7)
本章节给出一套可直接运行的“多机 Web 部署”案例,涵盖目录规范、配置、角色拆分与一键部署。
1. 目标与架构
- 目标:在一组
web主机上部署 Nginx + PHP-FPM,发布一个简单 PHP 应用。 - 架构:
- 控制端:Ansible(CentOS 7)
- 被控端:CentOS 7(组名
web) - 组件:Nginx 作为前端,转发到 PHP-FPM。
2. 目录结构
建议项目结构(便于环境区分与复用):
ansible-project/
├─ ansible.cfg
├─ site.yml # 顶层编排
├─ inventories/
│ ├─ prod/
│ │ ├─ hosts.ini
│ │ └─ group_vars/
│ │ └─ web.yml
│ └─ dev/
│ └─ hosts.ini
└─ roles/
├─ common/
│ └─ tasks/main.yml
├─ nginx/
│ ├─ tasks/main.yml
│ ├─ handlers/main.yml
│ └─ templates/
│ ├─ nginx.conf.j2
│ └─ vhost.conf.j2
├─ php/
│ ├─ tasks/main.yml
│ ├─ handlers/main.yml
│ └─ templates/www.conf.j2
└─ app/
├─ tasks/main.yml
└─ templates/index.php.j2
快速初始化(在控制端执行,示意):
mkdir -p ansible-project/inventories/{prod,dev}/group_vars
mkdir -p ansible-project/roles/{common,nginx,php,app}/{tasks,handlers,templates}
touch ansible-project/{ansible.cfg,site.yml}
touch ansible-project/inventories/prod/hosts.ini
touch ansible-project/inventories/prod/group_vars/web.yml
3. 基础配置与清单
ansible-project/ansible.cfg:
[defaults]
inventory = inventories/prod/hosts.ini
forks = 10
host_key_checking = False
timeout = 30
deprecation_warnings = False
log_path = ./ansible.log
inventories/prod/hosts.ini:
[web]
192.168.1.10 ansible_user=root
192.168.1.11 ansible_user=root
[web:vars]
http_port=80
server_name=www.example.com
app_root=/var/www/app
inventories/prod/group_vars/web.yml(组变量):
http_port: 80
server_name: www.example.com
app_root: /var/www/app
php_fpm_listen: 127.0.0.1:9000
nginx_worker_processes: auto
4. 角色实现(common/nginx/php/app)
roles/common/tasks/main.yml:基础环境与常用工具。
---
- name: Ensure EPEL
yum: name=epel-release state=present
- name: Install base tools
yum:
name:
- vim-enhanced
- curl
- unzip
- git
state: present
- name: Set SELinux permissive (runtime)
command: setenforce 0
ignore_errors: true
- name: Disable firewalld (demo)
service: name=firewalld state=stopped enabled=no
ignore_errors: true
roles/nginx/tasks/main.yml:
---
- name: Install nginx
yum: name=nginx state=present
- name: Deploy nginx.conf
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
notify: Restart nginx
- name: Deploy vhost
template: src=vhost.conf.j2 dest=/etc/nginx/conf.d/app.conf
notify: Restart nginx
- name: Enable and start nginx
service: name=nginx state=started enabled=yes
roles/nginx/handlers/main.yml:
---
- name: Restart nginx
service: name=nginx state=restarted
roles/nginx/templates/nginx.conf.j2(最小可用):
user nginx;
worker_processes {{ nginx_worker_processes | default('auto') }};
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
roles/nginx/templates/vhost.conf.j2:
server {
listen {{ http_port }};
server_name {{ server_name }};
root {{ app_root }};
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass {{ php_fpm_listen }};
}
}
roles/php/tasks/main.yml:
---
- name: Install PHP-FPM and extensions
yum:
name:
- php
- php-fpm
- php-cli
- php-json
- php-mbstring
- php-xml
state: present
- name: Configure php-fpm pool
template: src=www.conf.j2 dest=/etc/php-fpm.d/www.conf
notify: Restart php-fpm
- name: Enable and start php-fpm
service: name=php-fpm state=started enabled=yes
roles/php/handlers/main.yml:
---
- name: Restart php-fpm
service: name=php-fpm state=restarted
roles/php/templates/www.conf.j2(关键监听修改为变量):
[www]
user = nginx
group = nginx
listen = {{ php_fpm_listen }}
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
roles/app/tasks/main.yml:应用目录与首页。
---
- name: Ensure app root exists
file: path={{ app_root }} state=directory owner=nginx group=nginx mode=0755
- name: Deploy index.php
template: src=index.php.j2 dest={{ app_root }}/index.php owner=nginx group=nginx mode=0644
roles/app/templates/index.php.j2:
<?php
phpinfo();
5. site.yml 编排与运行
site.yml:
---
- hosts: web
become: yes
roles:
- common
- php
- nginx
- app
运行:
cd ansible-project
ansible-playbook site.yml # 使用默认 prod 清单
# 或指定清单
ansible-playbook -i inventories/prod/hosts.ini site.yml
6. 回滚与验证要点
- 回滚建议:
- 配置文件用
template+backup=yes或copy backup=yes; - 应用发布建议采用版本目录(releases/2025xxxx)+
current符号链接模式; - 使用
unarchive的creates防重复解压,或在任务中加 checksum 校验。
- 配置文件用
- 验证:
- 语法检查:
nginx -t - 端口监听:
ss -lntp | egrep ':80|:9000' - 服务状态:
systemctl status nginx php-fpm - 页面验证:
curl -I http://<web_ip>/或浏览器访问http://server_name/。
- 语法检查:
更多推荐



所有评论(0)