Ansible自动化运维:从入门到批量管理100台服务器

手动SSH登录100台服务器改配置?我试过,改到第30台的时候已经不知道哪些改过了。这篇文章分享我用Ansible实现批量管理的完整过程。

背景:运维的噩梦

去年公司扩张,服务器从10台涨到100+台。以前的运维方式:

bash 复制代码
# 登录服务器1
ssh root@192.168.1.1
vim /etc/nginx/nginx.conf
systemctl restart nginx

# 登录服务器2
ssh root@192.168.1.2
vim /etc/nginx/nginx.conf
systemctl restart nginx

# 重复100次...

问题来了:

  • 改到一半忘了改到哪了
  • 有几台配置漏了没发现
  • 凌晨3点被叫起来改配置,困得要死还要一台台登录

后来用了Ansible,同样的操作:

bash 复制代码
ansible all -m shell -a "systemctl restart nginx"
# 1秒搞定100台

效率提升不是一点半点。


一、Ansible是什么?

简单说:批量在多台服务器上执行命令的工具

和其他工具对比:

工具 需要Agent 学习成本 适合场景
Ansible ❌ 不需要 中小规模,快速上手
SaltStack ✅ 需要 大规模,实时性要求高
Puppet ✅ 需要 大规模,配置复杂
Chef ✅ 需要 大规模,Ruby生态

为什么选Ansible:

  • 不用在目标服务器装Agent(只要有SSH就行)
  • YAML语法,看一眼就懂
  • 模块丰富,常见操作都有现成的

二、5分钟快速上手

2.1 安装Ansible

bash 复制代码
# CentOS/RHEL
yum install -y epel-release
yum install -y ansible

# Ubuntu/Debian
apt update
apt install -y ansible

# macOS
brew install ansible

# 验证安装
ansible --version
# ansible 2.9.27

2.2 配置主机清单

创建 /etc/ansible/hosts

ini 复制代码
# 定义主机组
[webservers]
192.168.1.10
192.168.1.11
192.168.1.12

[dbservers]
192.168.1.20
192.168.1.21

[all:vars]
ansible_user=root
ansible_ssh_private_key_file=/root/.ssh/id_rsa

2.3 配置SSH免密登录

bash 复制代码
# 生成密钥(如果没有)
ssh-keygen -t rsa -b 4096 -N ""

# 批量分发公钥(第一次需要输密码)
for ip in 192.168.1.{10..12} 192.168.1.{20..21}; do
    ssh-copy-id root@$ip
done

2.4 测试连通性

bash 复制代码
# 测试所有主机
ansible all -m ping

# 输出
192.168.1.10 | SUCCESS => {
    "ping": "pong"
}
192.168.1.11 | SUCCESS => {
    "ping": "pong"
}
...

看到SUCCESS就说明配置成功了。


三、常用命令速查

3.1 Ad-hoc命令(临时执行)

bash 复制代码
# 查看所有服务器的磁盘使用
ansible all -m shell -a "df -h"

# 查看内存使用
ansible all -m shell -a "free -m"

# 重启nginx
ansible webservers -m service -a "name=nginx state=restarted"

# 复制文件到所有服务器
ansible all -m copy -a "src=/tmp/test.txt dest=/tmp/test.txt"

# 安装软件包
ansible all -m yum -a "name=htop state=present"

# 创建用户
ansible all -m user -a "name=deploy state=present"

3.2 常用模块

模块 用途 示例
ping 测试连通 ansible all -m ping
shell 执行命令 ansible all -m shell -a "uptime"
copy 复制文件 ansible all -m copy -a "src=a dest=b"
file 文件操作 ansible all -m file -a "path=/tmp/test state=directory"
yum/apt 包管理 ansible all -m yum -a "name=nginx state=present"
service 服务管理 ansible all -m service -a "name=nginx state=started"
user 用户管理 ansible all -m user -a "name=www state=present"
cron 定时任务 ansible all -m cron -a "name='backup' job='/backup.sh' hour=2"

四、Playbook:把操作写成剧本

Ad-hoc命令适合临时操作,但复杂任务还是要写Playbook。

4.1 第一个Playbook

创建 nginx-install.yml

yaml 复制代码
---
- name: 安装并配置Nginx
  hosts: webservers
  become: yes  # 使用sudo
  
  tasks:
    - name: 安装nginx
      yum:
        name: nginx
        state: present
    
    - name: 复制配置文件
      copy:
        src: ./nginx.conf
        dest: /etc/nginx/nginx.conf
        backup: yes  # 备份原文件
      notify: restart nginx  # 配置变更时触发handler
    
    - name: 确保nginx运行
      service:
        name: nginx
        state: started
        enabled: yes
  
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

执行:

bash 复制代码
ansible-playbook nginx-install.yml

# 输出
PLAY [安装并配置Nginx] ****************************

TASK [安装nginx] *********************************
changed: [192.168.1.10]
changed: [192.168.1.11]
changed: [192.168.1.12]

TASK [复制配置文件] *******************************
changed: [192.168.1.10]
changed: [192.168.1.11]
changed: [192.168.1.12]

TASK [确保nginx运行] *****************************
changed: [192.168.1.10]
changed: [192.168.1.11]
changed: [192.168.1.12]

PLAY RECAP ***************************************
192.168.1.10 : ok=3  changed=3  failed=0
192.168.1.11 : ok=3  changed=3  failed=0
192.168.1.12 : ok=3  changed=3  failed=0

3台服务器,一条命令全搞定。

4.2 带变量的Playbook

yaml 复制代码
---
- name: 部署应用
  hosts: webservers
  vars:
    app_version: "1.2.3"
    app_port: 8080
    app_user: "www"
  
  tasks:
    - name: 创建应用用户
      user:
        name: "{{ app_user }}"
        state: present
    
    - name: 下载应用包
      get_url:
        url: "https://releases.example.com/app-{{ app_version }}.tar.gz"
        dest: "/tmp/app-{{ app_version }}.tar.gz"
    
    - name: 解压应用
      unarchive:
        src: "/tmp/app-{{ app_version }}.tar.gz"
        dest: "/opt/app"
        remote_src: yes
    
    - name: 生成配置文件
      template:
        src: ./app.conf.j2
        dest: /opt/app/config/app.conf

配置模板 app.conf.j2

jinja2 复制代码
# 应用配置
server.port={{ app_port }}
server.host=0.0.0.0
app.version={{ app_version }}
app.env={{ ansible_hostname }}

4.3 条件判断和循环

yaml 复制代码
---
- name: 条件和循环示例
  hosts: all
  
  tasks:
    # 条件判断
    - name: 只在CentOS上执行
      yum:
        name: epel-release
        state: present
      when: ansible_distribution == "CentOS"
    
    - name: 只在Ubuntu上执行
      apt:
        name: software-properties-common
        state: present
      when: ansible_distribution == "Ubuntu"
    
    # 循环安装多个包
    - name: 安装常用工具
      yum:
        name: "{{ item }}"
        state: present
      loop:
        - vim
        - htop
        - wget
        - curl
        - net-tools
      when: ansible_distribution == "CentOS"

五、实战:批量初始化100台服务器

这是我实际用的服务器初始化Playbook:

5.1 目录结构

csharp 复制代码
ansible-init/
├── inventory/
│   ├── hosts           # 主机清单
│   └── group_vars/
│       └── all.yml     # 全局变量
├── roles/
│   ├── common/         # 基础配置
│   ├── security/       # 安全加固
│   ├── monitor/        # 监控Agent
│   └── docker/         # Docker安装
├── site.yml            # 主Playbook
└── ansible.cfg         # Ansible配置

5.2 主机清单

inventory/hosts

ini 复制代码
[webservers]
web[01:20].example.com  # web01到web20

[dbservers]
db[01:05].example.com

[cacheservers]
cache[01:10].example.com

[all:vars]
ansible_user=root
ansible_port=22

5.3 全局变量

inventory/group_vars/all.yml

yaml 复制代码
---
# 时区
timezone: "Asia/Shanghai"

# NTP服务器
ntp_servers:
  - ntp.aliyun.com
  - ntp1.aliyun.com

# 运维用户
ops_users:
  - name: deploy
    ssh_key: "ssh-rsa AAAAB3..."
  - name: monitor
    ssh_key: "ssh-rsa AAAAB3..."

# 安全配置
ssh_port: 22
ssh_permit_root: "no"
ssh_password_auth: "no"

# 监控配置
node_exporter_version: "1.6.1"

5.4 Common Role

roles/common/tasks/main.yml

yaml 复制代码
---
- name: 设置主机名
  hostname:
    name: "{{ inventory_hostname }}"

- name: 配置时区
  timezone:
    name: "{{ timezone }}"

- name: 安装基础软件包
  yum:
    name:
      - vim
      - wget
      - curl
      - htop
      - iotop
      - net-tools
      - telnet
      - lsof
      - tree
      - rsync
      - chrony
    state: present

- name: 配置NTP
  template:
    src: chrony.conf.j2
    dest: /etc/chrony.conf
  notify: restart chrony

- name: 启动chrony服务
  service:
    name: chronyd
    state: started
    enabled: yes

- name: 配置内核参数
  sysctl:
    name: "{{ item.name }}"
    value: "{{ item.value }}"
    state: present
    reload: yes
  loop:
    - { name: 'net.ipv4.tcp_tw_reuse', value: '1' }
    - { name: 'net.ipv4.tcp_fin_timeout', value: '30' }
    - { name: 'net.core.somaxconn', value: '65535' }
    - { name: 'vm.swappiness', value: '10' }

- name: 配置文件描述符限制
  pam_limits:
    domain: '*'
    limit_type: "{{ item.type }}"
    limit_item: nofile
    value: "{{ item.value }}"
  loop:
    - { type: 'soft', value: '65535' }
    - { type: 'hard', value: '65535' }

5.5 Security Role

roles/security/tasks/main.yml

yaml 复制代码
---
- name: 创建运维用户
  user:
    name: "{{ item.name }}"
    shell: /bin/bash
    groups: wheel
    append: yes
  loop: "{{ ops_users }}"

- name: 配置SSH公钥
  authorized_key:
    user: "{{ item.name }}"
    key: "{{ item.ssh_key }}"
    state: present
  loop: "{{ ops_users }}"

- name: 配置sudo免密
  lineinfile:
    path: /etc/sudoers.d/ops
    line: "{{ item.name }} ALL=(ALL) NOPASSWD: ALL"
    create: yes
    mode: '0440'
  loop: "{{ ops_users }}"

- name: 加固SSH配置
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  loop:
    - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin {{ ssh_permit_root }}' }
    - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication {{ ssh_password_auth }}' }
    - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
    - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 60' }
  notify: restart sshd

- name: 配置防火墙
  firewalld:
    port: "{{ item }}"
    permanent: yes
    state: enabled
  loop:
    - "{{ ssh_port }}/tcp"
    - "80/tcp"
    - "443/tcp"
  notify: reload firewalld

5.6 主Playbook

site.yml

yaml 复制代码
---
- name: 服务器初始化
  hosts: all
  become: yes
  
  roles:
    - common
    - security
    - monitor

- name: Web服务器配置
  hosts: webservers
  become: yes
  
  roles:
    - docker
    - nginx

- name: 数据库服务器配置
  hosts: dbservers
  become: yes
  
  roles:
    - docker
    - mysql

5.7 执行

bash 复制代码
# 检查语法
ansible-playbook site.yml --syntax-check

# 模拟执行(不实际改变)
ansible-playbook site.yml --check

# 正式执行
ansible-playbook site.yml

# 只执行特定tag
ansible-playbook site.yml --tags "security"

# 限定特定主机
ansible-playbook site.yml --limit "web01.example.com"

六、进阶:异地服务器怎么管?

公司在北京、上海、深圳都有机房,网络不通怎么办?

方案1:跳板机

ini 复制代码
[beijing]
bj-web01 ansible_host=192.168.1.10

[shanghai]
sh-web01 ansible_host=10.0.1.10

[beijing:vars]
ansible_ssh_common_args='-o ProxyJump=jump@jump.bj.example.com'

[shanghai:vars]
ansible_ssh_common_args='-o ProxyJump=jump@jump.sh.example.com'

缺点:

  • 跳板机挂了就完蛋
  • 速度慢(经过两跳)
  • 配置复杂

方案2:VPN

在每个机房部署VPN客户端,打通网络。

缺点:

  • VPN经常断
  • 带宽受限
  • 需要专人维护

方案3:SD-WAN组网(推荐)

我现在用的方案是星空组网,在每个机房的管理节点装一个客户端:

bash 复制代码
# 北京机房
curl -sSL https://down.starvpn.cn/linux.sh | bash
xkcli login your_token
xkcli up

# 上海机房
# 深圳机房
# 同样操作...

组网后,所有机房的服务器都在一个虚拟局域网里:

复制代码
北京机房 192.168.188.x
上海机房 192.168.188.y
深圳机房 192.168.188.z

主机清单直接用虚拟IP:

ini 复制代码
[beijing]
192.168.188.10
192.168.188.11

[shanghai]
192.168.188.20
192.168.188.21

[shenzhen]
192.168.188.30
192.168.188.31

效果:

  • 延迟:北京到上海 30ms(VPN要80ms+)
  • 稳定性:用了3个月没断过
  • 配置:比VPN简单多了

七、常见问题排查

7.1 SSH连接超时

bash 复制代码
# 检查SSH连通性
ssh -vvv root@192.168.1.10

# 常见原因
1. 防火墙没开22端口
2. SSH服务没启动
3. 密钥权限问题(应该是600)

7.2 权限不足

yaml 复制代码
# 在Playbook中加become
- name: 需要root权限的操作
  hosts: all
  become: yes  # 使用sudo
  become_user: root

7.3 模块执行失败

bash 复制代码
# 增加详细输出
ansible all -m yum -a "name=nginx state=present" -vvv

# 查看模块文档
ansible-doc yum

7.4 Playbook执行太慢

yaml 复制代码
# ansible.cfg 优化
[defaults]
forks = 50  # 并发数,默认5
host_key_checking = False
timeout = 30

[ssh_connection]
pipelining = True  # 开启pipelining,减少SSH连接次数
control_path = /tmp/ansible-%%h-%%r

八、总结

用Ansible之前:

  • 100台服务器改配置要半天
  • 经常漏改、错改
  • 凌晨被叫起来手动操作

用Ansible之后:

  • 100台服务器1分钟搞定
  • 操作可审计、可回滚
  • 写好Playbook,新人也能操作

我的建议:

  1. 从简单的Ad-hoc命令开始
  2. 把重复操作写成Playbook
  3. 逐步积累自己的Role库
  4. 异地服务器用组网工具打通

如果你也在管理

相关推荐
踏浪无痕31 分钟前
准备手写Simple Raft(四):日志终于能"生效"了
分布式·后端
程序员西西34 分钟前
SpringBoot 隐式参数注入:告别重复代码,让 Controller 更优雅
java·后端
用户3458482850535 分钟前
dict.fromkeys()和OrderedDict.fromkeys()的底层实现原理是什么?
后端
Cache技术分享37 分钟前
258. Java 集合 - 深入探究 NavigableMap:新增方法助力高效数据处理
前端·后端
做cv的小昊42 分钟前
在NanoPC-T6开发板上通过USB串口通信实现光源控制功能
java·后端·嵌入式硬件·边缘计算·安卓·信息与通信·开发
用户693717500138443 分钟前
21.Kotlin 接口:接口 (Interface):抽象方法、属性与默认实现
android·后端·kotlin
溪饱鱼43 分钟前
主动与被动AI交互范式
前端·后端·aigc
写代码的皮筏艇44 分钟前
Sequelize 详细指南
前端·后端
用户294655509191 小时前
游戏开发中的向量魔法
后端