欢迎光临

用 Ansible 管理多台 VPS:从手动 SSH 到自动化运维的完整进阶指南

为什么你需要 Ansible 来管理 VPS?

如果你手里只有一两台 VPS,SSH 上去手动敲命令还能应付。但当你的机器数量超过五台,每次系统更新、安全补丁、配置变更都要逐台登录操作,那种痛苦只有经历过的人才懂。更可怕的是,手动操作不可避免地会出现不一致——A 机器装了某个安全补丁,B 机器忘了装,C 机器的配置文件版本和 A、B 都不一样。这种”配置漂移”问题,轻则导致服务异常,重则成为安全漏洞。

Ansible 是目前最轻量级的运维自动化工具。它不需要在被管理机器上安装任何客户端(Agentless),只需要 SSH 通道就能完成所有操作。对于 VPS 玩家来说,这意味着你不需要在每台机器上额外部署任何东西,只要能 SSH 连上去,Ansible 就能干活。

和 Puppet、Chef 这类重量级工具相比,Ansible 的学习曲线极低。你不需要懂 Ruby,不需要搭服务端,写几个 YAML 文件就能跑起来。这正是它成为 VPS 自动化运维首选工具的原因。

服务器机房

环境准备:5 分钟搞定 Ansible

安装 Ansible

Ansible 只需要安装在你的控制机上(可以是你的本地电脑,也可以是一台跳板机)。被管理的 VPS 不需要任何额外安装。推荐使用 pip 安装最新版:


1
2
3
4
5
6
7
8
9
10
11
12
# 推荐方式:pip 安装最新版
pip install ansible

# 或者用系统包管理器(版本可能较旧)
# Ubuntu/Debian
sudo apt update && sudo apt install ansible

# CentOS/RHEL
sudo yum install ansible

# macOS
brew install ansible

安装完成后验证:


1
2
3
4
5
ansible --version
# ansible [core 2.16.x]
# config file = None
# configured module search path = ['/home/user/.ansible/plugins/modules']
# python version = 3.11.x

配置 SSH 免密登录

Ansible 依赖 SSH 连接,所以必须先配置好免密登录。如果你还在用密码登录,赶紧换成密钥:


1
2
3
4
5
6
7
8
9
10
# 生成密钥对(如果还没有)
ssh-keygen -t ed25519 -C "ansible-control"

# 把公钥推送到所有 VPS
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@your-vps-1
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@your-vps-2
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@your-vps-3

# 测试连接
ssh root@your-vps-1 "hostname && uptime"

如果你的 VPS 使用非标准 SSH 端口,可以在

1
~/.ssh/config

中预先配置:


1
2
3
4
5
6
7
8
9
10
11
Host vps-hk
    HostName 103.xx.xx.xx
    Port 2222
    User root
    IdentityFile ~/.ssh/id_ed25519

Host vps-tokyo
    HostName 45.xx.xx.xx
    Port 2222
    User root
    IdentityFile ~/.ssh/id_ed25519

代码编写

Inventory:把你的 VPS 管起来

Inventory 是 Ansible 的核心概念,它定义了你要管理哪些机器,以及如何对它们分组。一个清晰合理的 Inventory 文件是后续所有自动化操作的基础。

基础 Inventory 文件

创建一个

1
hosts.ini

文件:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# hosts.ini

[hk_servers]
vps-hk-1 ansible_host=103.xx.xx.1
vps-hk-2 ansible_host=103.xx.xx.2

[tokyo_servers]
vps-tokyo-1 ansible_host=45.xx.xx.1

[us_servers]
vps-us-1 ansible_host=162.xx.xx.1
vps-us-2 ansible_host=162.xx.xx.2

# 分组嵌套:所有亚太区机器
[asia:children]
hk_servers
tokyo_servers

# 全局变量:所有机器共用
[all:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3

# 非标准端口
[hk_servers:vars]
ansible_port=2222

验证 Inventory 连通性


1
2
3
4
5
6
7
8
9
10
11
12
13
# 测试所有机器的连通性
ansible all -i hosts.ini -m ping

# 只测试香港机器
ansible hk_servers -i hosts.ini -m ping

# 输出示例:
# vps-hk-1 | SUCCESS => {
#     "ping": "pong"
# }
# vps-hk-2 | SUCCESS => {
#     "ping": "pong"
# }

如果看到

1
SUCCESS

,说明 Ansible 已经能正常连接你的所有 VPS 了。如果某台机器报

1
UNREACHABLE

,先检查 SSH 连接和防火墙规则。

Playbook:让 VPS 自动干活

Ad-hoc 命令(就是上面

1
ansible all -m ping

这种)适合临时操作,但真正有威力的是 Playbook。Playbook 是用 YAML 编写的自动化剧本,把一系列操作按顺序编排在一起,可重复执行、可版本控制。

实战:系统初始化 Playbook

新开的 VPS 总要做一堆初始化工作——更新系统、装常用工具、配置时区、设置 Swap。这些操作写成 Playbook,一键搞定:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# playbook-init.yml
---
- name: VPS 初始化配置
  hosts: all
  become: yes
  serial: 2  # 每次只操作 2 台,防止全部同时出问题

  tasks:
    - name: 更新系统软件包
      apt:
        update_cache: yes
        upgrade: dist
      when: ansible_os_family == "Debian"

    - name: 更新系统软件包 (CentOS)
      yum:
        name: "*"
        state: latest
      when: ansible_os_family == "RedHat"

    - name: 安装常用工具
      package:
        name:
          - curl
          - wget
          - vim
          - htop
          - tmux
          - git
          - unzip
          - net-tools
          - rsync
        state: present

    - name: 设置时区为上海
      timezone:
        name: Asia/Shanghai

    - name: 配置 2G Swap
      shell: |
        if [ ! -f /swapfile ]; then
          fallocate -l 2G /swapfile
          chmod 600 /swapfile
          mkswap /swapfile
          swapon /swapfile
          echo '/swapfile none swap sw 0 0' >> /etc/fstab
        fi
      args:
        creates: /swapfile

    - name: 设置 Swap 优先级
      sysctl:
        name: vm.swappiness
        value: '10'
        state: present
        reload: yes

    - name: 创建普通用户
      user:
        name: deploy
        shell: /bin/bash
        groups: sudo
        append: yes

    - name: 部署 SSH 公钥
      authorized_key:
        user: deploy
        key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
        state: present

执行这个 Playbook:


1
2
3
4
5
6
7
8
ansible-playbook -i hosts.ini playbook-init.yml

# 输出会显示每台机器每个任务的执行结果
# PLAY [VPS 初始化配置] ****************************************
# TASK [更新系统软件包] ****************************************
# changed: [vps-hk-1]
# changed: [vps-hk-2]
# ...

注意

1
serial: 2

这个参数——它控制每次只对 2 台机器执行,避免所有 VPS 同时重启服务导致业务中断。这是生产环境中的最佳实践。

技术架构

安全加固:自动化防火墙与 SSH 配置

VPS 安全加固是每次开新机器的必做事项,但手动配置费时又容易遗漏。用 Ansible 可以确保每台机器的安全策略完全一致。

SSH 安全加固 Playbook


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# playbook-security.yml
---
- name: VPS 安全加固
  hosts: all
  become: yes

  tasks:
    - name: 禁用 SSH 密码登录
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PasswordAuthentication"
        line: "PasswordAuthentication no"
        state: present
      notify: Restart SSH

    - name: 禁用 root 密码登录
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PermitRootLogin"
        line: "PermitRootLogin prohibit-password"
        state: present
      notify: Restart SSH

    - name: 禁用空密码登录
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PermitEmptyPasswords"
        line: "PermitEmptyPasswords no"
        state: present
      notify: Restart SSH

    - name: 设置 SSH 最大认证尝试次数
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?MaxAuthTries"
        line: "MaxAuthTries 3"
        state: present
      notify: Restart SSH

    - name: 安装 UFW 防火墙
      package:
        name: ufw
        state: present

    - name: 配置 UFW 默认规则
      ufw:
        policy: deny
        direction: incoming

    - name: 放行 SSH
      ufw:
        rule: allow
        port: "{{ ansible_port | default('22') }}"
        proto: tcp

    - name: 放行 HTTP/HTTPS
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - '80'
        - '443'

    - name: 启用 UFW
      ufw:
        state: enabled

    - name: 安装 Fail2ban
      package:
        name: fail2ban
        state: present

    - name: 配置 Fail2ban
      copy:
        dest: /etc/fail2ban/jail.local
        content: |
          [DEFAULT]
          bantime = 3600
          findtime = 600
          maxretry = 3

          [sshd]
          enabled = true
          port = {{ ansible_port | default('22') }}
          filter = sshd
          logpath = /var/log/auth.log
          maxretry = 3
      notify: Restart Fail2ban

  handlers:
    - name: Restart SSH
      service:
        name: sshd
        state: restarted

    - name: Restart Fail2ban
      service:
        name: fail2ban
        state: restarted

这里有个关键细节:Playbook 中的

1
notify

1
handlers

机制。当

1
lineinfile

修改了 SSH 配置文件,它不会立即重启 SSH,而是等到所有任务执行完毕后,由 handler 统一重启。这样避免了多次修改配置文件导致多次重启服务的问题。

Role:组织复杂项目的最佳实践

当你的 Playbook 越来越长,把所有东西塞在一个文件里会很混乱。Ansible Role 提供了一套标准化的目录结构,让你把配置、任务、变量、模板分门别类地组织起来。

Role 目录结构


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
project/
├── hosts.ini
├── site.yml              # 主入口
├── group_vars/
│   ├── all.yml           # 全局变量
│   └── hk_servers.yml    # 香港组变量
└── roles/
    ├── common/
    │   ├── tasks/main.yml
    │   ├── handlers/main.yml
    │   ├── templates/
    │   └── defaults/main.yml
    ├── nginx/
    │   ├── tasks/main.yml
    │   ├── handlers/main.yml
    │   ├── templates/nginx.conf.j2
    │   └── defaults/main.yml
    └── monitoring/
        ├── tasks/main.yml
        ├── handlers/main.yml
        └── defaults/main.yml

主入口 Playbook


1
2
3
4
5
6
7
8
9
# site.yml
---
- name: 全站部署
  hosts: all
  become: yes
  roles:
    - common
    - nginx
    - monitoring

Role 示例:Nginx 部署


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# roles/nginx/defaults/main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_client_max_body_size: "64m"
nginx_keepalive_timeout: 65

# roles/nginx/tasks/main.yml
---
- name: 安装 Nginx
  package:
    name: nginx
    state: present

- name: 部署 Nginx 配置
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: "nginx -t -c %s"
  notify: Reload Nginx

- name: 确保 Nginx 启动
  service:
    name: nginx
    state: started
    enabled: yes

# roles/nginx/handlers/main.yml
---
- name: Reload Nginx
  service:
    name: nginx
    state: reloaded

注意

1
validate: "nginx -t -c %s"

这一行——它会先验证配置文件语法是否正确,只有通过验证才会实际部署。如果配置文件有语法错误,Ansible 会拒绝部署,不会把你线上跑得好好的 Nginx 搞崩。这是 Ansible 的一个重要安全特性。

团队协作

变量与模板:让配置灵活可复用

Ansible 的 Jinja2 模板系统是它的杀手级特性。你可以用变量和条件逻辑生成配置文件,同一套模板适配不同机器的差异化需求。

Group Variables:按组区分配置


1
2
3
4
5
6
7
8
9
10
11
12
13
14
# group_vars/hk_servers.yml
---
nginx_upstream_servers:
  - 127.0.0.1:8080
  - 127.0.0.1:8081
domain_name: hk.example.com
ssl_cert_path: /etc/letsencrypt/live/hk.example.com

# group_vars/us_servers.yml
---
nginx_upstream_servers:
  - 127.0.0.1:3000
domain_name: us.example.com
ssl_cert_path: /etc/letsencrypt/live/us.example.com

模板文件示例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# roles/nginx/templates/site.conf.j2
server {
    listen 80;
    server_name {{ domain_name }};
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name {{ domain_name }};

    ssl_certificate {{ ssl_cert_path }}/fullchain.pem;
    ssl_certificate_key {{ ssl_cert_path }}/privkey.pem;

    upstream backend {
    {% for upstream in nginx_upstream_servers %}
        server {{ upstream }};
    {% endfor %}
    }

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    client_max_body_size {{ nginx_client_max_body_size }};
    keepalive_timeout {{ nginx_keepalive_timeout }};
}

这样一套模板,香港机器自动生成香港的配置,美国机器自动生成美国的配置,完全不需要手动干预。而且当你要修改所有站点的某个通用参数(比如

1
client_max_body_size

),只需要改变量文件,重新执行 Playbook 即可。

实战进阶:批量更新与灰度发布

当你有 10 台 VPS 跑着同样的服务,更新部署时最怕的就是一把梭全更新,结果新版本有 bug 导致全线挂掉。Ansible 提供了几种策略来安全地执行批量更新。

滚动更新(Rolling Update)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# playbook-deploy.yml
---
- name: 滚动部署应用
  hosts: web_servers
  become: yes
  serial: 1         # 每次只更新一台
  any_errors_fatal: true  # 任何一台失败就停止

  pre_tasks:
    - name: 从负载均衡移除
      shell: "curl -X POST http://lb/remove/{{ inventory_hostname }}"

  tasks:
    - name: 拉取最新代码
      git:
        repo: "https://github.com/yourorg/app.git"
        dest: /opt/app
        version: "{{ deploy_version }}"

    - name: 安装依赖
      shell: "cd /opt/app && pip install -r requirements.txt"

    - name: 重启服务
      systemd:
        name: app
        state: restarted

    - name: 等待服务就绪
      wait_for:
        port: 8080
        delay: 5
        timeout: 60

    - name: 健康检查
      uri:
        url: "http://localhost:8080/health"
        status_code: 200
      register: health
      until: health.status == 200
      retries: 5
      delay: 3

  post_tasks:
    - name: 加回负载均衡
      shell: "curl -X POST http://lb/add/{{ inventory_hostname }}"
      when: health.status == 200

这个 Playbook 的执行流程是:先从负载均衡摘除一台机器,更新代码和重启服务,健康检查通过后加回负载均衡,然后再处理下一台。如果任何一台的健康检查失败,整个部署会立即停止,避免更多机器被更新到有问题的版本。

Canary 发布:先拿一台试水


1
2
3
4
5
6
7
8
9
10
11
# 先只部署到 canary 组
ansible-playbook -i hosts.ini playbook-deploy.yml \
  --limit "canary" \
  -e "deploy_version=v2.1.0"

# 观察 10 分钟,确认没问题
# sleep 600

# 确认没问题后,部署到所有机器
ansible-playbook -i hosts.ini playbook-deploy.yml \
  -e "deploy_version=v2.1.0"
1
--limit

参数让你只对特定机器执行,这是最简单的灰度发布方式。配合

1
-e

传入版本号,可以灵活控制每次部署的版本。

日常运维:常用命令速查

不是所有操作都需要写 Playbook,很多日常运维用 Ad-hoc 命令就能搞定。以下是一些高频场景:

场景 命令
查看所有机器系统负载
1
ansible all -i hosts.ini -m shell -a "uptime"
查看磁盘使用率
1
ansible all -i hosts.ini -m shell -a "df -h"
检查内存使用
1
ansible all -i hosts.ini -m shell -a "free -h"
批量重启某服务
1
ansible all -i hosts.ini -m systemd -a "name=nginx state=restarted"
快速部署文件
1
ansible all -i hosts.ini -m copy -a "src=local.conf dest=/etc/app/app.conf"
收集机器信息
1
ansible all -i hosts.ini -m setup | grep ansible_distribution
批量创建目录
1
ansible all -i hosts.ini -m file -a "path=/opt/data state=directory mode=0755"
检查服务状态
1
ansible all -i hosts.ini -m systemd -a "name=nginx state=started"

收集到的机器信息(Facts)非常丰富,包括操作系统版本、CPU 型号、内存大小、网络接口、磁盘分区等。你可以用这些信息做条件判断,比如只对 Ubuntu 机器执行 apt 操作,只对内存大于 2G 的机器部署某个服务。

与 CI/CD 集成:GitOps 实践

将 Ansible Playbook 和 Inventory 纳入 Git 管理,配合 CI/CD 流水线,就能实现 GitOps 风格的基础设施管理——所有变更通过 Git 提交触发,自动执行,有完整的审计记录。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# .github/workflows/deploy.yml
name: Deploy Infrastructure

on:
  push:
    branches: [main]
    paths:
      - 'ansible/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Ansible
        run: pip install ansible

      - name: Run Playbook
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          cd ansible
          ansible-playbook -i hosts.ini site.yml

      - name: Notify on Failure
        if: failure()
        run: |
          curl -X POST "${{ secrets.WEBHOOK_URL }}" \
            -H 'Content-Type: application/json' \
            -d '{"text": "Ansible 部署失败,请检查!"}'

这种模式的好处是:谁在什么时候改了什么配置,Git 记录清清楚楚。出了问题可以

1
git revert

回滚,比手动改配置文件安全得多。

常见坑与排错技巧

用 Ansible 管理 VPS 的过程中,有几个常见的坑值得提前了解:

  • SSH 连接超时:跨国 VPS 的 SSH 连接不稳定,建议在
    1
    ansible.cfg

    中设置

    1
    ssh_args = -o ConnectTimeout=10 -o ServerAliveInterval=30

    ,避免长时间挂起。

  • Python 解释器路径:部分老系统默认 Python 2,而 Ansible 2.12+ 已不再支持 Python 2。在 Inventory 中显式设置
    1
    ansible_python_interpreter=/usr/bin/python3

  • 幂等性陷阱
    1
    shell

    1
    command

    模块不是幂等的,每次执行都会报告

    1
    changed

    。尽量用对应的专用模块(如用

    1
    apt

    而非

    1
    shell: apt-get install

    ),专用模块会检查当前状态,只有真正需要变更时才执行。

  • 大文件传输:Ansible 默认用 SFTP 传文件,大文件传输较慢。可以改用 SCP:
    1
    transfer_method = scp

    ,或用

    1
    synchronize

    模块(基于 rsync,增量传输)。

  • Ansible 事实收集慢:每次执行前收集 Facts 需要几秒,如果 Playbook 不需要 Facts,加
    1
    gather_facts: no

    可以显著加速。

推荐 ansible.cfg 配置


1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ansible.cfg
[defaults]
inventory = hosts.ini
host_key_checking = False
retry_files_enabled = False
timeout = 30
# 并发数,根据机器数量调整
forks = 10
# 禁用 SSH 主机密钥检查(仅限内网环境)

[ssh_connection]
ssh_args = -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
transfer_method = scp

其中

1
pipelining = True

能显著提升执行速度——它不再为每个任务创建临时文件再 SCP 过去,而是直接通过 SSH 管道传输,减少了大量文件 I/O 操作。配合

1
ControlMaster

实现 SSH 连接复用,多任务执行时不需要反复建立连接。

总结

从手动 SSH 到 Ansible 自动化运维,不是什么高大上的转型,而是当你的 VPS 数量超过 5 台时的必然选择。Ansible 的核心优势在于:Agentless 架构零侵入、YAML 语法低门槛、幂等执行保证一致性、丰富的模块覆盖几乎所有运维场景。

建议的渐进式落地路径:先从 Ad-hoc 命令开始,把日常操作脚本化;然后写 Playbook 把重复性工作自动化;最后用 Role 组织复杂项目,配合 Git 管理实现完整的 GitOps 流程。不要一上来就搞复杂的 Role 架构——先解决最痛的问题,再逐步完善。

记住:自动化运维的目标不是炫技,而是让你晚上能安心睡觉,不用担心哪台机器忘了打补丁、哪个配置文件忘了同步。工具只是手段,稳定和省心才是目的。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 用 Ansible 管理多台 VPS:从手动 SSH 到自动化运维的完整进阶指南
分享到: 更多 (0)