摘要
在使用 Scaleway 官方镜像时,Cloud-init 通常已经安装并完成适配。但如果系统是通过 DD、网络重装或其他方式手动安装的 Debian,再将它封装成自定义模板,就需要自行补齐 Cloud-init,并明确决定哪些配置由云平台管理、哪些配置继续由操作系统本身管理。
本文记录一套已经在 Debian 13 + Scaleway + IPv6 网络环境中实际验证的方案,目标是:
- 从 Scaleway datasource 获取 User Data;
- 在首次启动时设置 Root 密码;
- 允许 Root 通过 SSH 密码登录;
- 每个新实例重新生成 SSH 主机密钥;
- 保留手工安装系统原有的 Debian 网络配置;
- 配合 CXT-SystemPrep 重置 Cloud-init 状态并制作可克隆模板。
本文配置允许 Root 密码登录,属于明确的运维选择,并不是通用安全默认值。请使用每台机器唯一的高强度密码,并配合防火墙、来源地址限制、入侵防护和后续密钥登录策略。
最终 Cloud-config
下面是当前采用的完整模板。示例中不包含真实密码或密码哈希:
#cloud-config
disable_root: false
ssh_pwauth: true
ssh_deletekeys: true
# Generate a new password hash interactively with: openssl passwd -6
chpasswd:
expire: false
users:
- name: root
password: '$6$REPLACE_WITH_THE_COMPLETE_GENERATED_HASH'
type: hash
write_files:
- path: /etc/ssh/sshd_config.d/00-cxt-cloud-init.conf
owner: root:root
permissions: '0644'
content: |
PermitRootLogin yes
PasswordAuthentication yes
runcmd:
- [ passwd, -u, root ]
- [ sh, -c, "/usr/sbin/sshd -t && (systemctl reload ssh.service || systemctl restart ssh.service)" ]
在 Scaleway 控制台中,将完整内容作为实例或模板的 Cloud-init User Data。替换密码哈希后再提交,不要保留示例占位符。
生成 Root 密码哈希
不要把明文密码直接写进长期保存的 Cloud-config。可以在可信环境中交互生成 SHA-512 crypt 哈希:
openssl passwd -6
命令会提示输入密码,并输出一个以 $6$ 开头的完整哈希。将完整结果替换到:
password: '$6$REPLACE_WITH_THE_COMPLETE_GENERATED_HASH'
注意:
- 外层单引号应保留,避免 YAML 或 Shell 将
$解释为其他内容; - 哈希仍属于敏感认证材料,不应公开提交到 Git 仓库、博客或公共模板;
- 不同实例最好使用不同密码或不同哈希;
- 如果平台提供更安全的密钥或秘密注入机制,应优先使用平台能力。
配置项说明
disable_root: false
允许 Cloud-init 对 Root 用户执行配置。部分发行版的云镜像会默认限制 Root,因此这里明确关闭该限制。
ssh_pwauth: true
允许 SSH 密码认证。但不同发行版的 OpenSSH 包、默认配置和配置片段优先级可能不同,所以本文同时写入一个独立的 sshd 配置片段。
ssh_deletekeys: true
删除模板中继承的 SSH 服务器主机密钥,让新实例在启动时生成新的密钥。
这是制作可克隆镜像时的正确方向。如果多台克隆机共享同一组主机密钥,客户端无法可靠区分服务器身份,也会扩大私钥泄露的影响范围。
启用后,新实例第一次连接时 SSH 指纹发生变化属于预期行为。若复用同一 IP 地址,客户端可能提示 known_hosts 冲突,需要在确认新实例身份后更新旧记录。
chpasswd
chpasswd:
expire: false
users:
- name: root
password: '...'
type: hash
这里告诉 Cloud-init:
- 为 Root 设置已经哈希过的密码;
- 不强制用户首次登录后立即修改密码;
- 不把哈希误当作明文密码处理。
write_files
配置写入:
/etc/ssh/sshd_config.d/00-cxt-cloud-init.conf
文件内容明确启用:
PermitRootLogin yes
PasswordAuthentication yes
使用独立配置片段比直接用 sed 修改 /etc/ssh/sshd_config 更容易审计,也不依赖主配置文件中某一行是否存在、是否被注释或使用了怎样的空格格式。
文件名使用 00- 前缀是当前模板的明确选择。部署后仍应使用 sshd -T 检查最终生效值,因为 OpenSSH 的 Include 顺序和其他配置片段可能影响结果。
passwd -u root
- [ passwd, -u, root ]
这一行作为额外保险,确保 Root 账户没有处于锁定状态。
它不会设置密码,也不能替代 chpasswd。如果没有有效密码,仅仅执行解锁不能凭空创建一个可用的认证凭据。这里保留它,是因为部分手工安装或经过安全加固的 Debian 模板可能继承被锁定的 Root 状态。
检查并重新加载 SSH
- [ sh, -c, "/usr/sbin/sshd -t && (systemctl reload ssh.service || systemctl restart ssh.service)" ]
首先使用 sshd -t 检查配置语法,只有检查成功才重新加载 SSH。优先使用 reload,失败时再尝试 restart。
Debian 通常使用 ssh.service。如果目标发行版只提供 sshd.service,需要按实际 unit 名称调整。

为什么不再使用 bootcmd 设置密码
早期方案曾在 bootcmd 中再次执行:
echo 'root:明文密码' | chpasswd
passwd -u root
这不适合模板长期使用,原因包括:
bootcmd默认每次启动都会执行;- 每次重启都可能把 Root 密码重置回固定值;
- 明文密码会出现在 User Data 和执行参数中;
- 后续人工修改密码可能在下次重启时被覆盖;
- 同一模板产生的多台机器容易共享同一密码。
当前方案使用 Cloud-init 的 chpasswd 模块,并把补充命令放在 runcmd 中。它们按照 once-per-instance 语义执行:同一 Cloud-init 实例不会每次普通重启都重新运行;当模板通过 cloud-init clean 重置实例状态后,新克隆机才会再次执行。
官方说明可参考:
手工安装 Debian 的网络所有权问题
这套模板不是 Scaleway 官方 Cloud-init 镜像,而是手工安装的 Debian 13。系统原本已经通过 /etc/network/interfaces 管理基础网络,例如:
source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback
auto ens2
iface ens2 inet6 auto
首次安装 Cloud-init 并启用 Scaleway datasource 后,Cloud-init 又生成了:
/etc/network/interfaces.d/50-cloud-init
结果是同一块 ens2 接口同时被原生配置和 Cloud-init 生成配置管理,曾出现重复地址、ifup ens2 失败以及 networking.service failed。
对于这类已经有稳定原生网络配置的自制镜像,正确做法是明确禁止 Cloud-init 渲染网络,而不是让两个系统竞争接口所有权。
创建:
/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
内容如下:
network:
config: disabled
并确保旧的生成文件不再参与启动:
rm -f /etc/network/interfaces.d/50-cloud-init
这样分工后:
- Debian
/etc/network/interfaces负责基础网络; - Cloud-init 继续负责 Scaleway datasource、User Data、密码、SSH 和文件写入;
cloud-init clean --logs --seed不会删除本地的网络禁用策略;- 新实例启动时不会再次生成
50-cloud-init。
不要把
network: {config: disabled}无条件复制到所有云镜像。官方云镜像或依赖 Cloud-init 生成网络的环境一旦禁用渲染,可能失去网络。该策略只适用于已经由系统原生网络管理器稳定配置接口的自制模板。
Cloud-init 网络配置文档:
在封装前重置 Cloud-init
确认网络策略和 User Data 都已验证后,可在封装前执行:
cloud-init clean --logs --seed
它会清理实例缓存、运行标记、Cloud-init 日志和 seed,使下一次启动重新探测 Scaleway datasource,并重新执行 once-per-instance 模块。
如果使用 CXT-SystemPrep,可以通过单项参数完成同类处理:
/bin/sh /run/CXT-SystemPrep.sh \
--cloud-state \
--reboot \
--dry-run
预检通过后再正式执行:
HISTFILE=/dev/null
export HISTFILE
history -c 2>/dev/null || :
exec /bin/sh /run/CXT-SystemPrep.sh \
--cloud-state \
--reboot \
--yes
也可以在完整模板封装时使用:
exec /bin/sh /run/CXT-SystemPrep.sh \
--profile seal \
--poweroff \
--yes
需要理解的是,--cloud-state 不只是“删除日志”。它会让下一次启动重新执行平台 User Data,因此密码、SSH 设置、主机密钥、文件和命令都可能再次应用。
不要在普通封装流程中使用 CXT-SystemPrep 的高风险 --cloud-configs,也不要默认运行:
cloud-init clean --configs all
这会删除 Cloud-init 已生成到系统中的配置文件。只有明确确认目标平台会提供兼容 datasource,并且所有相关配置都可以安全再生时才应考虑。
提交前验证 Cloud-config
将配置保存为本地文件后,可以使用 Cloud-init 自带的 schema 检查:
cloud-init schema -c /run/cxt-scaleway-user-data.yaml
预期结果应包含:
Valid schema
检查完成后删除包含密码哈希的临时文件:
rm -f /run/cxt-scaleway-user-data.yaml
在使用命令历史的 Shell 中操作敏感内容前,可以临时关闭历史写入:
HISTFILE=/dev/null
export HISTFILE
history -c 2>/dev/null || :
新实例首次启动后的检查
Cloud-init 状态与 datasource
cloud-init status --wait --long
cloud-id
预期 datasource 为:
scaleway
SSH 最终配置
/usr/sbin/sshd -T |
awk '$1 == "permitrootlogin" || $1 == "passwordauthentication"'
预期输出:
permitrootlogin yes
passwordauthentication yes
检查 Root 是否具有有效密码且未锁定:
passwd -S root
状态字段通常应为 P,而不是表示锁定的 L。
SSH 主机密钥是否再生
for key in /etc/ssh/ssh_host_*_key.pub; do
ssh-keygen -lf "$key"
done
新实例的指纹应与封装前模板不同。
网络和关键服务
systemctl is-active networking.service ssh.service
systemctl --failed --no-pager
test ! -e /etc/network/interfaces.d/50-cloud-init &&
echo 'PASS: Cloud-init did not render network configuration'
ip -brief address
ip -6 route
Scaleway IPv6 环境中的已知警告
本次实测中,Scaleway datasource、User Data、Root 密码、SSH 配置、主机密钥再生和 IPv6 网络均成功,但 cloud-init status --long 仍可能显示:
extended_status: degraded done
No valid DHCP lease configuration found in dhcpcd lease: ''
在这台 IPv6-only、自制 Debian 镜像上,Cloud-init 尝试读取不存在的 DHCPv4/dhcpcd 租约,因此记录 recoverable error,并以状态码 2 表示 degraded。经过验证:
- datasource 仍为 Scaleway;
- User Data 已执行;
- SSH 与 Root 密码设置已生效;
networking.service正常;- IPv6 地址和默认路由正常;
- 系统没有 failed units。
因此,这里应区分“Cloud-init 报告可恢复警告”和“实例初始化失败”。不要为了消除一条不影响功能的状态提示,就随意安装额外 DHCP 客户端或改写已经稳定的网络栈。
判断是否可以接受,应以实际功能和日志证据为准,而不是只看 done 或 degraded done 一个字段。
安全建议
允许 Root 密码 SSH 登录会明显扩大暴力破解风险。至少应同时做到:
- 使用长且唯一的随机密码,不复用博客、面板或其他服务器密码。
- 不在 Git、公开脚本、镜像说明或截图中暴露密码哈希。
- 使用 Scaleway 安全组或主机防火墙限制 SSH 来源。
- 部署 fail2ban、CrowdSec 或等效防护时,确认其规则与业务环境匹配。
- 登录后尽快配置 SSH 公钥;如果业务允许,之后关闭密码认证。
- 首次连接时核对云平台控制台、串口/VNC 或其他可信渠道中的 SSH 指纹。
- 每个克隆实例保留独立 machine-id 和 SSH 主机密钥。
如果没有强制 Root 密码登录的需求,更推荐使用普通管理用户、sudo 和 SSH 公钥认证。
最终职责边界
这套方案最终形成了清晰的配置所有权:
| 配置 | 负责方 |
|---|---|
| Scaleway metadata 与 User Data | Cloud-init DataSourceScaleway |
| Root 密码、解锁与 SSH 登录策略 | Cloud-config |
| SSH 主机密钥再生 | Cloud-init ssh_deletekeys: true |
| ens2 基础网络 | Debian /etc/network/interfaces |
| 禁止 Cloud-init 渲染网络 | 99-disable-network-config.cfg |
| 封装前实例状态清理 | CXT-SystemPrep --cloud-state 或 cloud-init clean --logs --seed |
| 完整系统日志与克隆身份准备 | CXT-SystemPrep seal profile |
对于自制云镜像,最重要的不是让 Cloud-init 接管所有内容,而是让每类配置只有一个明确的所有者。只要 datasource、User Data、网络、SSH 和模板清理各自边界清楚,这套手工安装的 Debian 系统同样可以成为稳定、可重复部署的 Scaleway 自定义镜像。