Ansible 批量初始化服务器:新购 VPS 的自动化准备流程

用 Ansible 批量初始化新购 VPS 的自动化准备流程

为什么新购 VPS 需要批量初始化

当你一次性接入五台、十台新购的 VPS(虚拟专用服务器),最先遇到的问题不是性能,而是重复劳动:改主机名、配 SSH 密钥、关闭密码登录、调整内核参数、统一时区与软件源,每一台都要手工敲一遍。按每台 20 分钟计算,十台服务器就是三个多小时的机械操作,而且第 7 台和第 3 台的配置很可能悄悄不一致——这种不一致会在半年后的排障中让你付出成倍的代价。

Ansible(一种基于 SSH 的自动化配置工具)正是为解决这类问题而生。你在控制机上写好一份 Playbook(任务剧本),一条 ansible-playbook 命令下去,所有目标服务器按同样的顺序完成同样的配置;下次新机器到位,重跑一次即可,已完成的步骤自动跳过。本文将以一次真实的新购 VPS 批量准备流程为例,从主机清单写到安全加固,帮你把重复劳动压缩成一条命令。

一台控制机通过 SSH 同时管理多台 VPS

准备控制机与主机清单

整个流程只需要一台普通的 Linux 控制机,不需要在被管理的服务器上安装任何 Agent。先在控制机上安装 Ansible 并生成专用密钥:

# 控制机安装 Ansible(以 pip 方式为例,版本以官方文档为准)
python3 -m pip install --user ansible
ansible --version

# 生成专用 SSH 密钥,用于批量免密登录
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ansible -N ""

接着把公钥分发到每台新购 VPS。ssh-copy-id 会自动完成追加 authorized_keys 和权限设置:

ssh-copy-id -i ~/.ssh/id_ed25519_ansible.pub root@203.0.113.11

然后创建主机清单 inventory/hosts.ini,把新机器按用途分组。按组管理意味着后续 Web 组加机器只需在文件里加一行:

[web]
203.0.113.11 ansible_host=203.0.113.11
203.0.113.12 ansible_host=203.0.113.12

[db]
203.0.113.21 ansible_host=203.0.113.21

[new_vps:children]
web
db

[new_vps:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/id_ed25519_ansible

清单写完后先用 ping 模块验证连通性,全部返回 pong 才继续往下走:

ansible new_vps -i inventory/hosts.ini -m ping

第一次批量连接新机器时,还有两个小配置能明显改善体验。一是 SSH 首次连接会要求确认主机指纹,十台机器就要敲十次 yes,在 ansible.cfg 里设置 host_key_checking = False 可以跳过这一步(内网或可控环境适用,公网生产环境建议保持默认并提前分发 known_hosts)。二是 Ansible 默认的并发数是 5,也就是说十台机器会分成两批依次执行;把 forks 调到 10 之后,一轮任务会在所有机器上同时展开,基线配置这种无依赖任务的总耗时可接近减半。

编写初始化 Playbook:系统基线一步到位

连通性验证通过后,把所有”每台新机器都要做”的动作写进 init.yml。这份 Playbook 覆盖了系统更新、时区、内核参数和常用工具四类基线配置,其中 chrony 负责时间同步——时钟偏差对日志排查和证书校验的影响,可以参考这篇 chrony 时钟校准教程

---
- name: 新购 VPS 初始化基线
  hosts: new_vps
  become: true
  tasks:
    - name: 更新软件包缓存并升级
      ansible.builtin.apt:
        update_cache: true
        upgrade: dist
      when: ansible_facts['os_family'] == 'Debian'

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

    - name: 安装常用运维工具
      ansible.builtin.package:
        name:
          - curl
          - vim
          - htop
          - chrony
        state: present

    - name: 调整内核连接参数
      ansible.posix.sysctl:
        name: "{{ item.name }}"
        value: "{{ item.value }}"
        sysctl_set: true
      loop:
        - { name: net.core.somaxconn, value: '4096' }
        - { name: net.ipv4.tcp_max_syn_backlog, value: '8192' }

这份 Playbook 有两个细节值得注意。其一,所有任务都声明了 become: true,以 root 权限执行系统级修改;其二,apt 任务用 when 条件限定了 Debian 系,如果清单里混入了 CentOS Stream 机器,ansible.builtin.packagechrony 的组合也能正常工作,不会整批中断。执行时加上 --diff 参数,每台机器改了什么一目了然:

ansible-playbook -i inventory/hosts.ini init.yml --diff

从系统基线到安全加固的批量初始化分层结构

安全加固与防火墙:把风险挡在上线之前

基线配置只解决”统一”,安全加固解决”能用”。生产环境的新机器默认开放密码登录和 root 直接 SSH,这在公网上等于把门钥匙放在门口。用下面这份 hardening.yml 一次性收口,再对照我们整理过的 SSH 服务加固清单 逐项核对,上线第一天就具备基本防线:

---
- name: SSH 与防火墙加固
  hosts: new_vps
  become: true
  tasks:
    - name: 关闭 SSH 密码登录
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication no'
      notify: restart sshd

    - name: 放行 SSH 与 HTTP/HTTPS
      community.general.ufw:
        rule: allow
        port: '{{ item }}'
        proto: tcp
      loop: ['22', '80', '443']

    - name: 启用防火墙并设为默认拒绝
      community.general.ufw:
        state: enabled
        default: deny

  handlers:
    - name: restart sshd
      ansible.builtin.service:
        name: sshd
        state: restarted

这里有一个容易踩坑的顺序问题:notify 触发的 restart sshd 会在所有任务结束后才执行,因此即使当前会话就是通过 SSH 建立的,也不会在中途把自己锁在门外。但为了保险,第一次跑加固剧本前,务必另开一个终端保持一条已登录的 SSH 连接——如果密钥配置有误,这条连接就是你的救援通道。验证方式很简单,加固完成后用密码尝试登录,应当直接被拒绝:

ssh -o PreferredAuthentications=password root@203.0.113.11
# 预期输出:Permission denied (publickey)

如果你的业务以 WordPress 建站 为主,还可以在加固阶段顺手安装 Nginx 与 PHP-FPM 的基础角色,让新机器初始化完成后直接具备承接站点的环境,这部分可以参考我们之前整理的 WordPress 服务器环境准备内容。

常见坑与排查思路

批量初始化第一次跑通之前,多半会撞上几类典型问题,提前知道排查方向能省下大量时间:

  • 连接超时(UNREACHABLE):先用 ssh -i ~/.ssh/id_ed25519_ansible root@<IP> 手工登录验证密钥;能登录再检查清单里的 ansible_user 是否写对,多数 UNREACHABLE 都是这两处之一。
  • 模块报错找不到(couldn’t resolve module):说明控制机的 Ansible 版本过老,不认识 ansible.posix 这类命名空间集合,执行 ansible-galaxy collection install ansible.posix community.general 即可补齐。
  • 改了 sshd_config 后连不上:保留旧 SSH 会话的同时,用 sshd -T | grep passwordauthentication 在服务器上核对生效配置,确认是 off 再退出旧会话。
  • Playbook 第二次运行仍显示大量 changed:通常是 lineinfileregexp 写得太宽导致每次都改写,把正则收紧到精确匹配行即可恢复幂等。
  • 时间同步未生效导致证书报错:初始化后如果 chronyc tracking 显示偏差超过数百毫秒,先检查 udp 123 端口是否被防火墙放行,再确认 chrony 服务已启动,否则后续 HTTPS 证书校验会间歇性失败。

手工逐台配置与 Ansible 批量初始化的效率对比

总结与下一步行动

回到最初的问题:十台新购 VPS 的准备工作,从三个多小时的手工操作收敛为两条命令——ansible-playbook init.yml 打基线,ansible-playbook hardening.yml 做加固,且每一台的配置严格一致、可重复执行。我们建议把 inventory/ 与这两份 Playbook 纳入 Git 仓库管理,新机器上线流程就从”个人经验”变成了团队资产。

下一步可以考虑两个方向:一是把初始化拆成角色(Roles),按 web、db 分组定制 Nginx、MySQL 等不同栈;二是把 Playbook 里的敏感变量改用 Ansible Vault 加密管理,具体做法可以参考这篇 Ansible Vault 实战,再逐步把定时备份与监控 Agent 纳入基线,让”初始化”覆盖服务器的完整生命周期。如果你正在规划多台服务器的批量部署,建议先从 Hostease 的 VPS 方案 选好规格,再按本文流程把环境准备压缩到几分钟;如果你需要先小规模验证,也可以只挑两台机器跑通全流程,确认无误后再全量推开。

发表评论