
cloud-init 批量初始化云服务器,解决的是“每台服务器都靠人工登录配置,最后环境不一致”的问题。本文会教你如何把用户、SSH(安全远程登录协议)、软件源、基础安全策略和验证命令写成一份可复用的初始化配置,让新机器首次启动时自动完成标准化设置。对需要频繁创建测试环境、业务节点或客户项目隔离环境的团队来说,这比复制命令到终端更稳定,也更容易审计。
如果你已经在使用 VPS(虚拟专用服务器)主机、云服务器(按需分配资源的服务器)或多台 Linux 实例,最容易遇到的不是某条命令不会执行,而是第 3 台、第 8 台、第 20 台机器配置细节开始偏离。有人忘了关密码登录,有人漏装了 fail2ban,还有机器的默认用户、时区和 SSH(安全远程登录协议)端口不同。cloud-init 的价值,就是把这些“开机后必做项”提前写进声明式配置,减少人工步骤带来的随机性。
为什么要把初始化配置前移到首次启动
传统做法通常是先创建服务器,再用 SSH(安全远程登录协议)登录,逐条执行用户创建、密钥写入、软件更新、防火墙配置等命令。单台服务器这样做问题不大,但批量场景会很快暴露出三个风险:配置不可追溯、执行顺序不稳定、故障排查依赖个人记忆。比如同一批 10 台节点里,有 2 台没有执行系统更新,有 1 台忘记限制 root 登录,后续排查就会从应用层一路追到系统层。
cloud-init 的思路是把初始化动作放到实例首次启动阶段。你创建服务器时注入一段 user-data,系统启动后由 cloud-init 读取并执行。它不是替代配置管理工具,而是负责“机器出生时的第一套标准动作”。如果后续还要长期管理软件版本、服务配置和发布流程,可以再接入 Ansible、SaltStack 或 CI/CD(持续集成与持续交付)流水线;但最初的用户、密钥、安全基线,适合用 cloud-init 先统一。
在 服务器配置 类工作中,我们建议把初始化动作拆成两层:第一层是所有服务器都应该一致的基线,例如时区、普通管理员用户、SSH(安全远程登录协议)密钥、自动安全更新;第二层才是业务角色差异,例如 Web 节点、数据库节点、缓存节点。这样做的好处是,即使业务脚本暂时失败,服务器也已经具备可登录、可审计、基础安全可控的状态。

一份可复用的 cloud-init 基线应包含什么
不要把 cloud-init 写成“什么都装”的万能脚本。初始化配置越复杂,首次启动失败的概率越高;配置越靠近系统基线,复用价值越大。对大多数中小团队来说,一份合理的 cloud-init 基线通常包含用户、登录、安全更新、基础工具和日志验证 5 类内容。
下面是一份可作为起点的 user-data 示例。它创建一个普通管理员用户,写入 SSH(安全远程登录协议)公钥,关闭密码登录,并安装常用基础工具。实际使用时,请把示例公钥替换成你自己的 ed25519 或 rsa 公钥,不要直接复制占位内容。
#cloud-config
timezone: Asia/Shanghai
package_update: true
package_upgrade: true
users:
- name: ops
groups: [sudo]
shell: /bin/bash
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
lock_passwd: true
ssh_authorized_keys:
- ssh-ed25519 AAAA_REPLACE_WITH_YOUR_PUBLIC_KEY ops@example
ssh_pwauth: false
disable_root: true
packages:
- curl
- vim
- git
- ufw
- fail2ban
write_files:
- path: /etc/ssh/sshd_config.d/99-hardening.conf
permissions: '0644'
content: |
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
runcmd:
- systemctl reload ssh || systemctl reload sshd
- ufw allow OpenSSH
- ufw --force enable
- systemctl enable --now fail2ban
- cloud-init status --long > /root/cloud-init-final-status.txt
这段配置有一个明确边界:它只做系统基础初始化,不直接部署业务代码。原因很简单,业务部署通常依赖版本、环境变量、数据库连接和回滚策略,变化频率高;而用户、SSH(安全远程登录协议)和基础安全配置变化较少,适合沉淀成稳定模板。如果你还在规划后续网站运行环境,可以结合 网站性能优化 思路,把系统基线和应用优化分开管理,避免初始化脚本越来越臃肿。
批量使用时,先定义变量,再生成配置
当服务器数量从 1 台变成 20 台,真正需要控制的不是 YAML 文件本身,而是变量来源。比如管理员用户名、SSH(安全远程登录协议)公钥、时区、允许访问的管理 IP、是否启用自动更新,这些值应当从同一份清单生成,而不是由不同成员手工编辑多份文件。
一个实用做法是维护一个环境变量文件,再用模板工具生成 user-data。下面示例用 shell 环境变量表达关键参数,适合小团队起步;如果环境更复杂,可以改用 Terraform、Jinja2 或平台侧模板能力。
export OPS_USER="ops"
export TIMEZONE="Asia/Shanghai"
export SSH_KEY_FILE="$HOME/.ssh/id_ed25519.pub"
export ADMIN_CIDR="203.0.113.10/32"
test -f "$SSH_KEY_FILE" || { echo "missing ssh public key"; exit 1; }
随后把这些变量渲染到 cloud-init 模板里。无论你使用哪种云服务器(按需分配资源的服务器)创建方式,都应确保最终注入的是完整 user-data,而不是部署完成后再补救。这样一来,批量创建出的服务器从第一分钟起就遵循同一套登录和安全规则。

在 WordPress教程 或企业站项目中,测试环境、预发布环境和生产环境常常会临时扩容。我们见过一个典型案例:团队原先每次新建服务器都由运维手工执行 18 条命令,平均耗时 20 分钟,且经常漏掉时区或 fail2ban。改成 cloud-init 模板后,创建 5 台测试节点只需要确认 4 个变量,首次登录后用同一组命令验证,配置差异明显减少。
SSH 与基础安全配置要保守,不要追求花哨
安全配置的目标不是把所有端口都关掉,而是在“不影响后续运维”的前提下减少暴露面。cloud-init 首次执行时,一旦 SSH(安全远程登录协议)配置写错,可能导致新服务器无法登录。因此我们建议分两步处理:先保证密钥登录可用,再关闭密码登录和 root 登录;防火墙只开放明确需要的端口,不要一次性写入复杂规则。
可以把首次验证命令写进交付清单,而不是只相信 cloud-init 日志。新服务器创建完成后,至少执行下面 4 组检查:
cloud-init status --long
id ops
sudo sshd -t
sudo ufw status verbose
如果 cloud-init status --long 不是 status: done,不要继续部署业务;如果 sshd -t 返回配置错误,要先通过控制台或救援模式修复。对外贸站、企业官网或活动页来说,早期忽略这类基础检查,后续可能表现为“应用偶发不可访问”,但根因其实是系统初始化不一致。
这里还要提醒一个常见误区:不要把 SSH(安全远程登录协议)端口改成某个“神秘端口”就当成安全策略。端口变更可以降低部分扫描噪音,但不能替代密钥登录、禁用密码、限制管理来源和及时更新。对于需要稳定运行的网站,虚拟主机(共享式网站托管服务) 与云服务器(按需分配资源的服务器)的运维边界不同;前者很多安全基线由平台托管,后者则需要你自己把初始化和持续维护流程做好。

把验证结果写进交付流程,避免“看起来成功”
cloud-init 最容易被误用的地方,是只看服务器能登录,就认为初始化成功。更稳妥的做法,是把验证结果也纳入交付流程:每台服务器创建后都输出 cloud-init 状态、用户信息、SSH(安全远程登录协议)配置测试、防火墙状态和关键服务状态。这样即使后续发生问题,也能快速判断是初始化阶段的差异,还是应用部署阶段的变化。
你可以准备一段简单的验证脚本,在首次登录后执行:
#!/usr/bin/env bash
set -euo pipefail
echo "[1/5] cloud-init"
cloud-init status --long
echo "[2/5] user"
id ops
echo "[3/5] ssh config"
sudo sshd -t
echo "[4/5] firewall"
sudo ufw status verbose
echo "[5/5] fail2ban"
systemctl is-active fail2ban
这段脚本不追求复杂,只确认 5 个基础事实。它的意义在于统一交付口径:不是“我刚才应该执行过”,而是“这台服务器现在确实处于可验证状态”。如果你使用 Hostease 的 VPS(虚拟专用服务器)主机承载多个项目,我们建议把这类基线脚本放进团队仓库,并在每次新建服务器后保留验证输出,方便后续审计和交接。
常见问题:哪些内容不适合放进 cloud-init
cloud-init 适合做首次启动的基础动作,但不适合承担所有运维职责。比如数据库初始化、证书续期、应用发布、灰度切换和备份策略,都不建议完全塞进 user-data。原因是这些任务需要重复执行、需要回滚,甚至需要依赖外部状态;而 cloud-init 更像一次性启动脚本,失败后重跑和排错成本较高。
我们建议遵循一个简单判断:如果这项配置“每台服务器出生时都应该一样”,适合放进 cloud-init;如果它“会随着业务版本持续变化”,就交给后续配置管理或发布系统。按这个标准,用户、SSH(安全远程登录协议)、时区、基础工具、安全更新属于前者;站点代码、数据库迁移、缓存策略和业务环境变量属于后者。
另一个需要控制的是敏感信息。不要把数据库密码、长期访问令牌、私钥直接写入 user-data。许多平台会在控制台或实例元数据中保留 user-data 记录,一旦权限管理不严,就可能造成泄露。更安全的做法是只写公钥、角色标识和初始化入口,敏感值通过专门的密钥管理或后续受控流程下发。
总结:先统一基线,再谈规模化部署
总结来看,cloud-init 批量初始化云服务器(按需分配资源的服务器)的关键,不是写一份很长的脚本,而是把首次启动阶段的基线动作标准化:统一用户、统一 SSH(安全远程登录协议)密钥、统一基础安全、统一验证方式。这样做可以减少人工配置漂移,也能让后续排障从“猜当时做了什么”变成“检查验证输出是否符合标准”。
如果你需要管理 3 台以上服务器,我们建议先从一份最小 cloud-init 模板开始,只覆盖用户、密钥、防火墙和基础工具;等验证稳定后,再逐步把团队共识沉淀进去。对于正在选择承载环境的站长或中小团队,也可以结合 Hostease 的 VPS(虚拟专用服务器)主机方案,把服务器规格、初始化模板和上线验证清单一起规划,避免上线前后反复补配置。
最后可以考虑建立一个固定流程:新建服务器前确认变量,创建时注入 user-data,首次登录后执行验证脚本,验证通过再部署业务。这个流程并不复杂,却能显著降低批量初始化中的随机错误。真正可靠的规模化运维,往往就是从这些可复用、可验证的小步骤开始。