目录
Ansible自动化运维实操:从环境搭建到高可用集群部署全流程复盘
一、实验环境说明
本次实操共使用2台Rocky Linux和1台Centos 7主机,分别承担控制节点、Web后端、高可用备节点角色,具体IP与作用如下:
| 主机名 | IP地址 | 角色与作用 |
|---|---|---|
| server1 | 192.168.48.161 | Ansible控制节点、Keepalived主节点、Haproxy主节点,负责下发配置与管控所有主机 |
| server2 | 192.168.48.162 | Web后端节点,部署Apache服务,作为负载均衡后端池成员 |
| node1 | 192.168.48.171 | Keepalived备节点、Haproxy备节点,同时用于测试批量用户管理等功能 |
这次实操从Ansible基础环境搭建开始,一步步完成了Playbook入门、Jinja2模板配置、Keepalived高可用部署,最后实现了Haproxy+Keepalived负载均衡集群的全自动化部署。中间踩了不少权限、YAML缩进、变量引用的坑,把完整流程和排坑思路整理出来,方便后续复盘。
二、环境准备:Ansible管控端与被控端配置
2.1 被控端统一用户与sudo权限配置
Ansible基于SSH协议工作,为了统一管控权限,所有被控节点都需要创建专用的运维用户,并配置sudo免密提权。
首先在server2节点创建devops用户并设置密码:
useradd devops
passwd devops
一开始图省事设了个短密码,直接被系统弹出BAD PASSWORD警告,还踩了一次两次密码输入不一致的坑,最后设置了符合复杂度要求的密码才通过验证。

用户创建完成后,通过visudo命令配置sudo免密权限,在root配置行下方添加devops用户的权限规则,让该用户可以无需密码执行所有sudo命令:
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
devops ALL=(ALL) NOPASSWD: ALL

同样的操作在server1控制端也执行一遍,创建devops用户并配置sudo免密,为后续SSH免密和Ansible运行做准备。
2.2 控制端SSH免密配置
Ansible默认通过SSH连接被控端,配置密钥免密登录后无需每次输入密码,是自动化运维的基础。
切换到devops用户,生成4096位的RSA密钥对:
su - devops
ssh-keygen -t rsa -b 4096
生成过程一路回车即可,不设置密钥口令,避免后续使用时重复输入密码。
密钥生成完成后,通过ssh-copy-id命令将公钥推送到被控节点server2:
ssh-copy-id devops@192.168.48.162
首次连接会弹出主机指纹确认提示,输入yes后再输入devops用户的密码,即可完成公钥推送。推送完成后可以直接SSH登录验证,无需输入密码就说明配置成功。

2.3 Ansible工作目录与基础配置
我没有使用系统默认的/etc/ansible目录,而是在devops用户家目录下创建独立的ansible工作目录,方便文件管理和权限控制。
mkdir -p ~/ansible
cd ~/ansible

首先编写inventory主机清单文件,定义web主机组和通用连接变量:
[web]
server2 ansible_host=192.168.48.162
[web :vars]
ansible_user=devops
ansible_become=yes
ansible_become_method=sudo
将用户名、sudo提权配置都放在组变量中,后续执行命令时无需重复附加参数。

接着编写ansible.cfg主配置文件,指定主机清单路径,同时关闭主机密钥检查,避免首次连接弹出确认提示:
[defaults]
inventory=/home/devops/ansible/inventory
host_key_checking = False

配置完成后,使用ping模块做连通性测试:
ansible web -m ping
返回"ping": "pong"就说明两端通信正常,Ansible基础环境搭建完成。

三、Playbook入门:Apache服务自动化部署
3.1 基础版:三步完成Apache部署
第一个Playbook实现最基础的Apache部署,包含安装软件、启动服务、写入默认首页三个任务。
创建install-apache.yml文件:
- hosts: web
become: yes
tasks:
- name: Install the Apache
ansible.builtin.yum:
name: httpd
state: present
- name: Start service httpd, if not started
ansible.builtin.service:
name: httpd
state: started
enabled: yes
- name: create index.html
ansible.builtin.copy:
content: "www.westos.org\n"
dest: /var/www/html/index.html

执行Playbook:
ansible-playbook install-apache.yml
首次执行三个任务均为changed状态,说明软件安装、服务启动、首页写入全部执行成功。Ansible的幂等性在这里体现得很明显,重复执行时已完成的任务会显示ok,不会重复操作。

3.2 主机分组:多节点批量管理
后续新增node1节点后,我将主机按角色分组管理:web组存放Web节点,db组存放测试节点,再通过:children关键字定义lamp父组,批量操作所有节点时直接调用lamp组即可。
更新inventory文件:
[web]
192.168.48.162
[db]
192.168.48.171
[lamp:children]
web
db

node1节点需要提前配置好devops用户和sudo免密,操作流程和server2完全一致。

配置完成后测试lamp组的连通性:
ansible lamp -m ping
两个节点均返回pong,分组配置正常。

3.3 配置文件修改:lineinfile模块改端口
需要修改Apache配置文件的监听端口时,使用lineinfile模块最合适,功能类似Linux的sed命令,通过正则匹配行并替换内容。
比如将默认80端口修改为8080,在Playbook中新增任务:
- name: Ensure the default Apache port is 8080
ansible.builtin.lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen'
insertafter: '#Listen'
line: Listen 8080

执行Playbook后,只有端口修改任务为changed状态,其余已配置完成的任务均为ok状态,不会重复执行。

3.4 配置变更触发重启:handlers机制
修改端口后需要重启服务才会生效,但如果端口没有发生变更,就没必要重启服务,这种场景正好适用Ansible的handlers机制:只有任务状态为changed时,才会通过notify触发对应的处理器。
将端口改回80,新增notify触发和handlers配置:
- hosts: web
become: yes
tasks:
- name: Install the Apache
ansible.builtin.yum:
name: httpd
state: present
- name: Start service httpd, if not started
ansible.builtin.service:
name: httpd
state: started
enabled: yes
- name: create index.html
ansible.builtin.copy:
content: "www.westos.org\n"
dest: /var/www/html/index.html
- name: Ensure the default Apache port is 80
ansible.builtin.lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen'
insertafter: '#Listen'
line: Listen 80
notify: restart service httpd
handlers:
- name: restart service httpd
ansible.builtin.service:
name: httpd
state: restarted

执行后可以看到,端口修改任务变更后,触发了RUNNING HANDLER执行服务重启。如果端口已经是80,任务为ok状态,handler就不会执行,避免不必要的服务中断。

3.5 变量引用:facts变量与自定义变量
Ansible的facts组件可以自动采集被控端的主机名、IP、系统版本等信息,这些内置变量可以直接在Playbook中调用。比如将首页内容设置为当前主机名,方便区分不同后端节点:
- name: create index.html
ansible.builtin.copy:
content: "{{ ansible_hostname }}\n"
dest: /var/www/html/index.html


除了内置facts变量,也可以在Playbook中自定义变量,比如将端口号定义为变量,后续修改时只需更改变量值即可:
- hosts: lamp
vars:
http_port: 80
tasks:
...
- name: Ensure the default Apache port is {{ http_port }}
ansible.builtin.lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen'
insertafter: '#Listen'
line: Listen {{ http_port }}
notify: restart service httpd
3.6 自动校验:uri模块做可用性检测
服务部署完成后,可以通过uri模块自动发起HTTP请求,验证服务是否正常返回200状态码,无需手动逐个curl测试。
在Playbook中新增一个针对localhost的play,专门执行检测任务:
- hosts: localhost
gather_facts: false
become: false
tasks:
- name: Check that you can connect (GET) to a page and it returns a status 200
ansible.builtin.uri:
url: http://192.168.48.162
return_content: true
register: result
- name: Print return information from the previous task
ansible.builtin.debug:
var: result


执行完成后可以看到返回状态码为200,响应内容为server2,说明Apache服务运行正常。

3.7 任务标签:tags实现精细化执行
当Playbook任务较多时,有时只需要执行其中某几个任务,不需要全量运行。给任务打上tags标签,就可以通过标签指定执行范围。
给每个任务添加对应的标签:
- name: Install the Apache
ansible.builtin.yum:
name: httpd
state: present
tags: t1
- name: Start service httpd, if not started
ansible.builtin.service:
name: httpd
state: started
enabled: yes
tags: t2
- name: create index.html
ansible.builtin.copy:
content: "{{ ansible_hostname }}\n"
dest: /var/www/html/index.html
tags: t3

可以先查看Playbook中所有的标签:
ansible-playbook install-apache.yml --list-tags

指定单个标签执行,比如只执行安装任务:
ansible-playbook install-apache.yml --tags=t1
也可以指定多个标签,用逗号分隔:
ansible-playbook install-apache.yml --tags=t1,t3

四、进阶实战:Jinja2模板与高可用集群部署
4.1 Keepalived高可用:模板化批量配置
Keepalived主备节点的配置大部分内容一致,只有节点状态、优先级等少数参数不同,非常适合用Jinja2模板+主机变量的方式实现,一份模板适配所有节点。
首先在inventory中新增hacluster组,给每个主机定义独立的状态和优先级变量,组变量定义公共参数:
[hacluster]
192.168.48.161 state=MASTER pri=100
192.168.48.171 state=BACKUP pri=80
[hacluster:vars]
interface=eth0
router_id=61
vip=192.168.48.200/24

编写keepalived.conf.j2模板文件,用变量替换所有差异化配置项:
global_defs {
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
vrrp_garp_interval 0
vrrp_gna_interval 0
}
vrrp_instance VI_1 {
state {{ state }}
interface {{ interface }}
virtual_router_id {{ router_id }}
priority {{ pri }}
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
{{ vip }} dev {{ interface }}
}
}

编写keepalived.yml Playbook,完成软件安装、配置下发、服务启动全流程,配置变更时自动触发服务重启,最后在本地验证VIP连通性:
- hosts: hacluster
become: yes
tasks:
- name: Install keepalived
ansible.builtin.yum:
name: keepalived
state: present
- name: Deploy keepalived config template
ansible.builtin.template:
src: keepalived.conf.j2
dest: /etc/keepalived/keepalived.conf
notify: restart keepalived service
- name: Start & enable keepalived
ansible.builtin.service:
name: keepalived
state: started
enabled: yes
handlers:
- name: restart keepalived service
ansible.builtin.service:
name: keepalived
state: restarted
- hosts: localhost
gather_facts: false
become: false
tasks:
- name: Test ping virtual VIP
ansible.builtin.command: ping -c 3 192.168.48.200
register: ping_res
- debug:
var: ping_res.stdout

执行完成后,在server1上通过ip a查看网卡信息,可以看到虚拟IP 192.168.48.200已经成功绑定在eth0网卡上,本地ping VIP也能正常通。


4.2 批量用户管理:loop循环迭代
需要批量创建多个用户时,不需要重复编写user任务,使用loop循环迭代用户列表即可,配合password_hash过滤器可以对密码进行SHA512加密。
编写user.yml:
- hosts: db
become: yes
tasks:
- ansible.builtin.user:
name: "{{ item.user }}"
password: "{{ item.pass | password_hash('sha512') }}"
state: present
create_home: yes
loop:
- {user: user1, pass: pass1}
- {user: user2, pass: pass2}

执行时会出现一条弃用警告:Python的crypt模块将在后续版本中移除,推荐安装passlib库。测试环境可以忽略该警告,不影响用户创建结果。

登录被控端查看用户家目录,两个用户均已正常创建。

4.3 批量分发hosts文件:模板自动生成映射
集群节点数量较多时,手动维护每台机器的/etc/hosts文件非常繁琐。通过Jinja2模板结合Ansible的groups魔法变量,可以自动生成所有节点的主机名映射,批量下发到所有机器。
基础版hosts.j2模板,固定写入所有节点映射:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.48.161 server1
192.168.48.162 server2
192.168.48.171 node1

编写deploy_hosts.yml,将模板批量分发到所有节点:
- hosts: all
become: yes
tasks:
- ansible.builtin.template:
src: hosts.j2
dest: /etc/hosts
owner: root
group: root
mode: '0644'


后续优化了模板写法,通过for循环遍历指定主机组,自动生成后端节点映射,新增节点时只需修改inventory,无需改动模板:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.48.161 server1
192.168.48.162 server2
192.168.48.171 node1
{% for ip in groups['backend_web'] %}
{{ ip }} web{{ loop.index }}
{% endfor %}

4.4 综合实战:Haproxy+Keepalived负载均衡集群
最后做了一个综合实战案例:两台节点部署Haproxy+Keepalived实现负载层高可用,后端对接Apache服务,整套架构全部通过Ansible自动化部署。
先更新inventory最终分组:
[backend_web]
192.168.48.162
[hacluster]
192.168.48.161 state=MASTER pri=100
192.168.48.171 state=BACKUP pri=80
[hacluster:vars]
interface=eth0
router_id=61
vip=192.168.48.200/24
vip_ip=192.168.48.200
[all:children]
hacluster
backend_web

Haproxy配置模板同样使用for循环遍历backend_web组,自动生成后端服务器列表,扩容时只需在inventory中加IP,无需修改配置模板:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http_front
bind *:80
default_backend http_back
backend http_back
balance roundrobin
{% for srv in groups['backend_web'] %}
server web{{ loop.index }} {{ srv }}:80 check inter 2000 fall 3 rise 2
{% endfor %}
listen stats
bind *:8088
stats enable
stats uri /stats
stats auth admin:123456

完整的Playbook分为三个逻辑段:先批量更新所有节点的hosts解析,再部署Haproxy+Keepalived高可用负载层,最后部署后端Web服务。执行完成后所有节点状态正常,通过VIP即可访问后端Apache服务,Haproxy状态页也能正常打开。


踩坑汇总与实操总结
核心踩坑点汇总
- 密码复杂度校验报错:设置用户密码时长度过短、包含用户名都会触发PAM的密码复杂度警告,测试环境可忽略提示重复输入,生产环境建议遵循密码规范。
- sudo配置格式错误:visudo中单词拼写错误、缩进异常都会导致sudo命令失效,修改后建议保留一个root终端验证,避免普通用户无法提权。
- YAML缩进语法错误:YAML对空格缩进要求极其严格,禁止使用Tab键,层级必须对齐。初期handlers和tasks层级错位,导致反复报语法错误。
- handlers触发条件限制:只有任务状态为changed时才会触发notify,若配置已符合预期,任务为ok状态则handler不会执行,修改配置时需留意该特性。
- 密码哈希弃用警告:
password_hash默认依赖的Python crypt模块在高版本Python中已弃用,生产环境建议安装passlib库以保证兼容性。 - 模板变量未定义:Jinja2模板中引用的变量必须在inventory或Playbook中提前定义,否则渲染时会直接报变量未定义错误。
实操心得
完整走下来最大的感受是,Ansible的核心价值在于幂等性和模板化:同一份脚本重复执行不会产生副作用,配置通过变量+模板解耦后,扩容节点只需要修改主机清单,部署脚本完全不用改动,批量运维的效率提升非常明显。
从单模块Ad-hoc命令,到Playbook任务编排,再到Jinja2模板、分组管理,一步步进阶下来,自动化运维的思路也越来越清晰。后续还可以继续探索Roles角色化管理,把不同服务的部署拆分成独立角色,代码复用性和可维护性会更高。

373

被折叠的 条评论
为什么被折叠?



