VPS环境Cloud-Init自动化配置避坑指南:从user-data格式到网络与安全策略,一次搞定
为什么Cloud-Init会坑哭新手?
你第一次在VPS面板粘贴一段长长的user-data脚本,满怀期待地重启实例,结果SSH连不上、服务没启动、磁盘没扩容…… 这不是个例。Cloud-Init虽然是云初始化的事实标准,但它的模块执行顺序、YAML格式陷阱、网络配置依赖,足以让任何不熟悉其内部机制的人抓狂。本文基于真实的踩坑记录,直击最常翻车的三个场景,附带可复用的代码片段。
避坑一:user-data格式翻车
新手最常见的错误:把Bash脚本直接贴进去,却不加#cloud-config头,或者写了头但是缩进错误。
- 错误写法1(缺少声明头):
runcmd:
- apt update -y
- apt install nginx -y
Cloud-Init会认为这是无效数据,直接跳过,你调试时在/var/log/cloud-init-output.log里会看到failed to parse。
- 正确做法:始终以
#cloud-config开头,且YAML字典的key必须顶格。如果是纯Bash脚本,请改用#!/bin/bash开头的write_files模块或runcmd。
#cloud-config
package_update: true
packages:
- nginx
runcmd:
- systemctl enable --now nginx
针对轻云互联VPS的特别提示
在配置轻云互联的VPS时,如果选择“自定义系统镜像”,务确保镜像中预装了cloud-init且版本≥18.2,否则network-config模块可能无法正确识别。建议用官方云镜像(如Ubuntu Cloud、Debian Cloud),它们已经内置了优化过的cloud-init配置。
避坑二:网络配置“死锁”——静态IP与DHCP冲突
当你用cloud-init配置静态IP时,最常见的翻车是:cloud-init在第一次启动时重写网络配置文件,但你同时又在runcmd里手动调用了systemctl restart networking,导致网络服务在未完全应用新配置时被中断,SSH断连。
- 根本原因:Cloud-Init的
network模块在bootcmd阶段执行,而runcmd在最后阶段。如果你在runcmd里重启网络,可能会覆盖之前的配置或导致竞争条件。 - 解决方案:不要在
runcmd里重启网络服务!Cloud-Init会自动应用网络配置并重启网络(取决于模块配置)。如果你需要确保网络生效,可以在bootcmd里用cloud-init-nonet等待网络就绪。
#cloud-config
bootcmd:
- [ cloud-init-nonet, --wait ] # 等待网络完全配置完成
network:
version: 2
ethernets:
eth0:
dhcp4: false
addresses:
- 192.168.1.100/24
gateway4: 192.168.1.1
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
调试技巧
如果SSH断连无法恢复,使用VNC或救援模式登陆,查看/var/log/cloud-init.log和/etc/cloud/cloud.cfg.d/下的调试文件。运行sudo cloud-init --debug single -n network可单独重跑网络阶段。
避坑三:安全策略被cloud-init悄悄“吞掉”
很多新手在runcmd里写iptables -A INPUT -p tcp --dport 22 -j ACCEPT,以为防火墙生效了,结果重启后就失效。原因是iptables规则默认不持久化,而cloud-init也不会帮你保存。更坑的是,某些VPS发行版预装了ufw或firewalld,你手动改iptables会被它们覆盖。
- 正确做法:使用cloud-init的
write_files模块写入iptables-restore规则文件,并在runcmd中启用服务。
#cloud-config
write_files:
- path: /etc/iptables/rules.v4
content: |
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p tcp --dport 22 -j ACCEPT
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -j DROP
COMMIT
permissions: '0644'
runcmd:
- iptables-restore < /etc/iptables/rules.v4
- apt install -y iptables-persistent # 启用持久化
- netfilter-persistent save
高级坑:cloud-init版本差异导致模块名变化
Cloud-Init 20.2之前,网络模块叫eni或netplan;20.2之后统一为network-config。如果你在旧版镜像里用了新版配置语法,会直接报错。建议在#cloud-config里额外加上network: {config: disabled},手动用write_files写网络配置文件,最稳定。
最后一道防线:完整的调试脚本
在第一次部署后,用以下命令检查cloud-init执行状态:
sudo cloud-init status --long
sudo journalctl -u cloud-init --no-pager
sudo cat /var/log/cloud-init-output.log
如果status显示error,用sudo cloud-init --debug analyze分析每个阶段的耗时和错误。通常80%的失败都是因为YAML语法错误或网络配置模块提前执行。建议先用sudo cloud-init --debug single -n write-files单独测试写入模块,没问题再跑完整流程。
记住:Cloud-Init不是黑盒——/run/cloud-init/目录下保存了每次执行的缓存和结果,学会看这些文件,比问人更高效。