你用虚拟化平台的 cloud-init 功能批量建虚拟机,图的就是"一次配好、自动初始化"。可机器建出来后,要么 ping 得通却 SSH 密码登不进去,要么首次启动卡上十几分钟,要么根本起不来。问题不在你的操作,而在那份平台默认生成的 user-data。
现象
三个坑,往往一起出现,也可能只暴露其中一个:
- SSH 密码登不进去——用你设的用户名密码连,提示
Permission denied (publickey),好像密码登录被禁用了。 - 首次启动卡很久——虚拟机开机后长时间没反应,cloud-init 阶段耗时异常长,甚至超时。
- 机器起不来 / 行为诡异——初始化脚本中途报错,或用户的权限、家目录、组归属和你预期不符。
根因
平台的 cloud-init 默认生成的 user-data,是一份"通用"模板,里面有几个不明说的坑:
坑一:没有显式打开"允许密码登录"。
现代云镜像出于安全,默认在 SSH 配置里关闭密码登录(PasswordAuthentication no),只允许密钥。而平台默认的 user-data 往往没有去覆盖这个设置。于是你用密码去连,被服务器直接拒绝——镜像的默认值是"关",你没打开它,它就一直关着。
坑二:带了"开机自动升级软件包"。
有些默认模板里带了一段"首次启动时执行软件包升级"的逻辑(cloud-init 的 package_upgrade: true,或一段 apt upgrade 脚本)。它会去拉取软件源、下载更新。如果源是境外的、网络又不畅,这一步会卡到天荒地老——虚拟机看似起来了,其实还卡在初始化里,表现为"首次启动特别慢/卡死"。
坑三:用户创建语义含糊。
默认模板对"创建哪个用户、属于哪个组、有没有 sudo、密码是什么"的处理往往很模糊。不同平台、不同版本对"默认用户"的定义不一样,导致你以为建的是某个管理员用户,实际可能压根没建,或者建了但没给 sudo。
解决
不要用默认模板部署云镜像,用自定义 snippet 全面接管。
在 Proxmox VE 里,把自定义的 user-data 放到平台的 snippet 存储里(例如 snippets 目录下的 user-data.yaml),然后在虚拟机配置里指向它。一份"该有的都显式写清"的 user-data 大致这样:
#cloud-config
# 显式允许密码登录
ssh_pwauth: true
# 关掉开机自动升级,避免首次启动卡死
package_upgrade: false
# 显式创建用户:名字、组、目录、sudo、密码哈希
users:
- name: deploy
groups: [sudo]
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
lock_passwd: false
passwd: <密码的哈希值>
# 兜底保证 SSH 密码登录这一项一定生效
runcmd:
- sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
- systemctl restart ssh || systemctl restart sshd
chpasswd:
expire: false
几个要点:
ssh_pwauth: true只是让 cloud-init 去改 sshd 配置,但有些镜像的配置写法不同,所以再用runcmd里的sed+restart兜底,保证它一定生效;package_upgrade: false明确关掉开机升级,首次启动不再去够境外源;- 密码要用哈希值(
openssl passwd -6 '你的密码'生成),别直接写明文; - 用户、组、sudo 每一项都显式写出来,别依赖"默认用户"这类含糊概念。
改完重建虚拟机,验证。
延伸与预防
这条坑的核心教训是:默认模板是"能跑就行"的产物,不是"符合你需求"的产物,凡是要紧的配置都要显式接管。
同一思路也适用于别处的 cloud-init(各大云厂商的默认 user-data、其它工具生成的模板等)。养成习惯:拿到一份模板,先逐项问"这一项默认是什么?我需不需要它?"特别是这几类:
- 认证方式(密码 vs 密钥);
- 网络与主机名;
- 用户与权限;
- 首次启动会执行什么(升级、脚本、拉取)。
至于预防"首次启动卡死",最实用的一条是:任何会联网拉东西的初始化步骤,都别放在默认路径里。境外源、可能不通的地址,都是首启超时的元凶。真要装包,就配好内网源,或者把这一步挪到系统起来之后再做。