还记得那个凌晨3点被电话叫醒的夜晚吗?生产环境的20台服务器需要紧急更新配置,你不得不一台一台手动SSH登录,重复执行相同的命令。两个小时后,当你拖着疲惫的身躯完成任务时,心里暗暗发誓:"一定要找个自动化工具!"
如果你有过类似的经历,那么恭喜你,今天这篇文章将彻底改变你的运维生涯。我将带你从零开始掌握Ansible,通过一个实际的Web服务器批量部署项目,让你体验自动化运维的魅力。读完这篇文章,你将能够:
10分钟内完成50台服务器的Nginx部署
一键实现应用的滚动更新和回滚
构建可复用的自动化部署流程
将重复性工作时间缩短90%以上
1.1 传统运维的痛点
在深入Ansible之前,让我们先看看传统运维面临的挑战:
场景一:配置漂移问题你管理着100台服务器,理论上它们的配置应该完全一致。但随着时间推移,因为各种临时修改、紧急补丁,服务器配置开始出现差异。某天一个看似简单的更新,却因为配置不一致导致部分服务器故障。
场景二:规模化挑战公司业务快速增长,服务器数量从10台增长到100台。原本30分钟能完成的部署任务,现在需要5个小时。而且随着操作复杂度增加,人为错误的概率也在上升。
场景三:知识传承困难资深运维离职了,留下的只有一堆零散的Shell脚本和简单的文档。新人接手后发现,每个脚本的执行顺序、参数含义都需要猜测和试错。
1.2 Ansible 的优势
Ansible 是一个开源的IT自动化工具,它通过简单的YAML语法描述系统配置,实现:
无代理架构(Agentless):不需要在被管理节点安装任何客户端,通过SSH即可管理
声明式配置:描述"想要达到的状态",而不是"如何达到"
幂等性保证:多次执行产生相同结果,避免重复操作带来的问题
易学易用:YAML语法简单直观,降低学习门槛
强大的模块库:3000+内置模块,覆盖各种运维场景
2.1 环境准备
我们将搭建一个实验环境,包含1台控制节点和3台被管理节点:
# 控制节点(安装Ansible的机器)
control-node: 192.168.1.10
# 被管理节点(目标服务器)
web-01: 192.168.1.11
web-02: 192.168.1.12
web-03: 192.168.1.13
2.2 安装 Ansible
在控制节点上执行:
# CentOS/RHEL 系统
sudo yum install -y epel-release
sudo yum install -y ansible
# Ubuntu/Debian 系统
sudo apt update
sudo apt install -y ansible
# 使用 pip 安装(推荐,获取最新版本)
sudo pip3 install ansible
# 验证安装
ansible --version
2.3 配置 SSH 免密登录
自动化的前提是控制节点能够无密码访问被管理节点:
# 生成SSH密钥对(如果还没有)
ssh-keygen -t rsa -b 2048
# 将公钥复制到所有被管理节点
for ip in 192.168.1.11 192.168.1.12 192.168.1.13; do
ssh-copy-id -i ~/.ssh/id_rsa.pub root@$ip
done
# 测试连接
ssh root@192.168.1.11 'hostname'
2.4 创建 Inventory 文件
Inventory文件定义了Ansible要管理的主机清单:
# 创建 inventory.ini 文件
[webservers]
web-01 ansible_host=192.168.1.11
web-02 ansible_host=192.168.1.12
web-03 ansible_host=192.168.1.13
[webservers:vars]
ansible_user=root
ansible_python_interpreter=/usr/bin/python3
[all:vars]
ansible_connection=ssh
测试连接所有主机:
ounter(line
ansible -i inventory.ini all -m ping
如果看到所有主机返回 "pong",恭喜你,环境搭建成功!
现在让我们通过一个实际项目,深入理解Ansible的强大功能。我们将实现:
批量安装Nginx
部署自定义配置
部署静态网站
实现滚动更新
3.1 项目结构设计
nginx-deployment/
├── inventory.ini # 主机清单
├── ansible.cfg # Ansible配置文件
├── site.yml # 主Playbook
├── roles/ # 角色目录
│ └── nginx/
│ ├── tasks/ # 任务定义
│ │ └── main.yml
│ ├── templates/ # 模板文件
│ │ ├── nginx.conf.j2
│ │ └── index.html.j2
│ ├── handlers/ # 触发器
│ │ └── main.yml
│ └── vars/ # 变量定义
│ └── main.yml
└── group_vars/ # 组变量
└── webservers.yml
3.2 编写 Playbook
创建主Playbook site.yml:
---
- name: Deploy Nginx Web Servers
hosts: webservers
become: yes
gather_facts: yes
vars:
nginx_port: 80
nginx_worker_processes: "{{ ansible_processor_vcpus }}"
nginx_worker_connections: 1024
website_title: "Ansible自动化部署演示"
tasks:
- name: 更新系统包缓存
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: 安装Nginx
package:
name: nginx
state: present
- name: 创建网站目录
file:
path: /var/www/html
state: directory
mode: '0755'
- name: 部署Nginx配置文件
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: restart nginx
- name: 部署网站首页
template:
src: index.html.j2
dest: /var/www/html/index.html
mode: '0644'
- name: 确保Nginx服务运行
service:
name: nginx
state: started
enabled: yes
- name: 等待端口就绪
wait_for:
port: "{{ nginx_port }}"
host: "{{ ansible_default_ipv4.address }}"
delay: 5
timeout: 30
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
3.3 创建配置模板
创建 templates/nginx.conf.j2:
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
multi_accept on;
use epoll;
}
http {
# 基础配置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript;
# 虚拟主机配置
server {
listen {{ nginx_port }} default_server;
listen [::]:{{ nginx_port }} default_server;
root /var/www/html;
index index.html index.htm;
server_name {{ ansible_hostname }}.example.com;
location / {
try_files $uri $uri/ =404;
}
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
创建 templates/index.html.j2:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ website_title }}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
text-align: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
backdrop-filter: blur(10px);
}
h1 { font-size: 3rem; margin-bottom: 1rem; }
.info {
background: rgba(255, 255, 255, 0.2);
padding: 1rem;
border-radius: 10px;
margin-top: 2rem;
}
.info p { margin: 0.5rem 0; }
</style>
</head>
<body>
<div class="container">
<h1> {{ website_title }}</h1>
<p>恭喜!您已成功使用 Ansible 部署了这个页面</p>
<div class="info">
<p><strong>服务器名称:</strong> {{ ansible_hostname }}</p>
<p><strong>IP地址:</strong> {{ ansible_default_ipv4.address }}</p>
<p><strong>操作系统:</strong> {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
<p><strong>部署时间:</strong> {{ ansible_date_time.iso8601 }}</p>
</div>
</div>
</body>
</html>
3.4 执行部署
# 语法检查
ansible-playbook -i inventory.ini site.yml --syntax-check
# 模拟执行(Dry Run)
ansible-playbook -i inventory.ini site.yml --check
# 正式部署
ansible-playbook -i inventory.ini site.yml
# 查看详细输出
ansible-playbook -i inventory.ini site.yml -vvv
4.1 滚动更新策略
在生产环境中,我们需要确保服务的持续可用性。Ansible支持滚动更新:
---
- name: 滚动更新Web服务器
hosts: webservers
become: yes
serial: 1 # 每次更新1台服务器
max_fail_percentage: 30 # 允许30%的失败率
pre_tasks:
- name: 从负载均衡器移除
uri:
url: "http://lb.example.com/api/remove"
method: POST
body_format: json
body:
server: "{{ ansible_hostname }}"
delegate_to: localhost
tasks:
- name: 更新应用代码
git:
repo: https://github.com/yourapp/webapp.git
dest: /var/www/html
version: "{{ app_version | default('master') }}"
- name: 重启服务
service:
name: nginx
state: restarted
post_tasks:
- name: 健康检查
uri:
url: "http://{{ ansible_default_ipv4.address }}/health"
status_code: 200
retries: 5
delay: 10
- name: 重新加入负载均衡器
uri:
url: "http://lb.example.com/api/add"
method: POST
body_format: json
body:
server: "{{ ansible_hostname }}"
delegate_to: localhost
4.2 使用 Ansible Vault 保护敏感信息
生产环境中,密码和密钥需要加密存储:
# 创建加密文件
ansible-vault create secrets.yml
# 编辑加密文件
ansible-vault edit secrets.yml
# 在secrets.yml中添加:
db_password: "SuperSecret123!"
api_key: "sk-1234567890abcdef"
# 使用加密变量运行playbook
ansible-playbook -i inventory.ini site.yml --ask-vault-pass
4.3 动态 Inventory
当服务器数量众多或经常变化时,可以使用动态Inventory:
#!/usr/bin/env python3
# dynamic_inventory.py
import json
import boto3
def get_inventory():
ec2 = boto3.client('ec2', region_name='us-west-2')
response = ec2.describe_instances(
Filters=[
{'Name': 'tag:Environment', 'Values': ['production']},
{'Name': 'instance-state-name', 'Values': ['running']}
]
)
inventory = {
'webservers': {
'hosts': [],
'vars': {
'ansible_user': 'ubuntu',
'ansible_ssh_private_key_file': '~/.ssh/aws-key.pem'
}
}
}
for reservation in response['Reservations']:
for instance in reservation['Instances']:
inventory['webservers']['hosts'].append(instance['PublicIpAddress'])
return inventory
if __name__ == '__main__':
print(json.dumps(get_inventory()))
4.4 性能优化技巧
当管理大规模基础设施时,性能优化至关重要:
# ansible.cfg
[defaults]
host_key_checking = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_cache
fact_caching_timeout = 86400
pipelining = True
forks = 50
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
control_path = /tmp/ansible-%%h-%%p-%%r
让我们通过一个完整的案例,展示如何将Ansible集成到CI/CD流程中:
---
# deploy_pipeline.yml
- name: 完整的部署流程
hosts: webservers
become: yes
vars:
app_name: mywebapp
app_version: "{{ lookup('env', 'BUILD_NUMBER') | default('latest') }}"
deploy_user: webapp
deploy_dir: /opt/{{ app_name }}
backup_dir: /opt/backups/{{ app_name }}
tasks:
- name: 创建部署用户
user:
name: "{{ deploy_user }}"
shell: /bin/bash
groups: www-data
append: yes
- name: 创建必要的目录
file:
path: "{{ item }}"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0755'
loop:
- "{{ deploy_dir }}"
- "{{ backup_dir }}"
- /var/log/{{ app_name }}
- name: 备份当前版本
archive:
path: "{{ deploy_dir }}"
dest: "{{ backup_dir }}/backup-{{ ansible_date_time.epoch }}.tar.gz"
when: deploy_dir is directory
- name: 拉取最新代码
git:
repo: "https://github.com/company/{{ app_name }}.git"
dest: "{{ deploy_dir }}"
version: "{{ app_version }}"
force: yes
become_user: "{{ deploy_user }}"
- name: 安装应用依赖
pip:
requirements: "{{ deploy_dir }}/requirements.txt"
virtualenv: "{{ deploy_dir }}/venv"
virtualenv_python: python3
become_user: "{{ deploy_user }}"
- name: 运行数据库迁移
command: |
{{ deploy_dir }}/venv/bin/python manage.py migrate
args:
chdir: "{{ deploy_dir }}"
become_user: "{{ deploy_user }}"
run_once: true
- name: 收集静态文件
command: |
{{ deploy_dir }}/venv/bin/python manage.py collectstatic --noinput
args:
chdir: "{{ deploy_dir }}"
become_user: "{{ deploy_user }}"
- name: 配置Systemd服务
template:
src: app.service.j2
dest: /etc/systemd/system/{{ app_name }}.service
notify:
- reload systemd
- restart app
- name: 配置Nginx反向代理
template:
src: nginx_app.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: reload nginx
- name: 启用站点
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: reload nginx
- name: 运行冒烟测试
uri:
url: "http://localhost/api/health"
status_code: 200
retries: 5
delay: 10
handlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart app
systemd:
name: "{{ app_name }}"
state: restarted
enabled: yes
- name: reload nginx
service:
name: nginx
state: reloaded
自动化不是"一劳永逸",我们需要持续监控:
---
# monitoring.yml
- name: 配置监控和日志收集
hosts: webservers
become: yes
tasks:
- name: 安装监控代理
package:
name:
- prometheus-node-exporter
- filebeat
state: present
- name: 配置Prometheus Node Exporter
lineinfile:
path: /etc/default/prometheus-node-exporter
regexp: '^ARGS='
line: 'ARGS="--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|run)($|/)"'
notify: restart node-exporter
- name: 配置Filebeat
template:
src: filebeat.yml.j2
dest: /etc/filebeat/filebeat.yml
mode: '0600'
notify: restart filebeat
- name: 配置自定义指标收集脚本
copy:
content: |
#!/bin/bash
# 收集应用自定义指标
echo "app_requests_total $(curl -s localhost/metrics | grep requests_total | awk '{print $2}')"
echo "app_errors_total $(grep ERROR /var/log/{{ app_name }}/app.log | wc -l)"
echo "app_response_time_seconds $(tail -n 100 /var/log/nginx/access.log | awk '{sum+=$10} END {print sum/NR}')"
dest: /usr/local/bin/collect_metrics.sh
mode: '0755'
- name: 添加指标收集定时任务
cron:
name: "收集应用指标"
minute: "*/5"
job: "/usr/local/bin/collect_metrics.sh > /var/lib/node_exporter/textfile_collector/app_metrics.prom"
handlers:
- name: restart node-exporter
service:
name: prometheus-node-exporter
state: restarted
- name: restart filebeat
service:
name: filebeat
state: restarted
即使是最完善的自动化,也可能出现问题。让我们准备一个快速回滚方案:
---
# rollback.yml
- name: 紧急回滚程序
hosts: webservers
become: yes
serial: 1
vars_prompt:
- name: confirm_rollback
prompt: "确认要回滚到上一个版本吗?(yes/no)"
private: no
tasks:
- name: 验证确认
fail:
msg: "回滚操作已取消"
when: confirm_rollback != "yes"
- name: 查找最新的备份
find:
paths: "{{ backup_dir }}"
patterns: "backup-*.tar.gz"
register: backup_files
- name: 确保有可用备份
fail:
msg: "没有找到可用的备份文件"
when: backup_files.files | length == 0
- name: 获取最新备份
set_fact:
latest_backup: "{{ (backup_files.files | sort(attribute='mtime') | last).path }}"
- name: 停止应用服务
systemd:
name: "{{ app_name }}"
state: stopped
- name: 清理当前版本
file:
path: "{{ deploy_dir }}"
state: absent
- name: 恢复备份
unarchive:
src: "{{ latest_backup }}"
dest: /opt/
remote_src: yes
- name: 启动应用服务
systemd:
name: "{{ app_name }}"
state: started
- name: 验证服务状态
uri:
url: "http://localhost/api/health"
status_code: 200
retries: 3
delay: 5
- name: 发送回滚通知
mail:
to: ops-team@example.com
subject: "紧急回滚完成 - {{ ansible_hostname }}"
body: "服务器 {{ ansible_hostname }} 已成功回滚到备份版本:{{ latest_backup }}"
delegate_to: localhost
总结:从手动到自动的蜕变
通过这篇文章,我们一起经历了从传统手动运维到Ansible自动化的完整旅程。让我们回顾一下关键收获:
效率提升:原本需要数小时的部署任务,现在只需要几分钟
一致性保证:通过代码化的配置管理,消除了环境差异
可追溯性:每次变更都有记录,便于审计和问题排查
知识沉淀:运维经验转化为可复用的Playbook
降低风险:自动化减少人为错误,回滚机制保障业务连续性
但这仅仅是开始。Ansible的生态系统远比我们今天探索的要丰富:
Ansible Tower/AWX 提供企业级的管理界面
Ansible Galaxy 社区分享了数千个现成的角色
与Kubernetes、Docker、云平台的深度集成
网络设备、数据库、中间件的自动化配置
下一步行动建议
立即实践:选择一个简单的重复性任务,尝试用Ansible自动化
逐步推广:从开发环境开始,逐步扩展到生产环境
持续学习:关注Ansible官方文档和社区最佳实践
分享交流:将你的自动化经验分享给团队,共同成长
记住,自动化不是目的,而是让我们能够专注于更有价值工作的手段。当你不再被重复性任务束缚,你就有更多时间去思考架构优化、性能调优、安全加固这些真正体现运维价值的工作。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!