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发行版预装了ufwfirewalld,你手动改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之前,网络模块叫eninetplan;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/目录下保存了每次执行的缓存和结果,学会看这些文件,比问人更高效。