1. 为什么在2024年还要用Ubuntu 16.04做Packer镜像构建?一个被低估的兼容性锚点
你点开这篇内容,大概率不是因为想怀旧——而是手头正卡在一个真实场景里:某套遗留系统依赖Ubuntu 16.04的内核ABI、特定版本的glibc或某个已下线的PPA源;运维团队刚收到安全通告,要求对这批服务器做快照级备份与可复现重建;而DigitalOcean控制台里的“Snapshot”按钮,只支持手动触发、无法API化、更不带任何配置审计日志。这时候,Packer就不是“高级玩具”,而是唯一能打通CI/CD流水线与基础设施快照生命周期的生产级工具。
我去年帮一家做工业IoT网关固件分发的客户重构镜像体系时,就撞上这个典型矛盾:他们的边缘设备驱动模块必须运行在4.4.0-190-generic内核上,而该内核仅官方支持到Ubuntu 16.04 LTS(EOL已于2021年4月结束)。客户拒绝升级操作系统,但又要求所有新部署节点必须通过自动化流程生成、带完整软件签名、且每次构建都能回溯到精确的快照ID。最终方案就是用Packer在Ubuntu 16.04宿主机上构建DigitalOcean快照——不是为了“用老系统”,而是把老系统变成可验证、可审计、可灰度发布的原子单元。
这里需要划重点: Packer本身不关心你用什么OS做构建机,它只关心构建机能否执行shell命令、调用API、写入临时文件 。Ubuntu 16.04作为构建机,优势在于其极简的systemd环境(无snapd干扰)、稳定的curl/wget版本(避免TLS握手失败)、以及对老旧DigitalOcean API v2的完美兼容(新版Packer 1.10+已默认启用v2,但某些企业防火墙仍拦截v2的User-Agent头)。这不是技术倒退,而是工程妥协下的精准选型。
提示:别被“Ubuntu 16.04已EOL”吓退。我们不是把它当生产系统用,而是当一个轻量、确定、隔离的构建沙盒。就像你不会因为GCC 4.8过时就不用它编译嵌入式固件一样——稳定性比时髦更重要。
关键词“Packer”“DigitalOcean”“Ubuntu 16.04”“snapshots”在此场景中形成强耦合链:Packer是编排引擎,DigitalOcean是目标云平台,Ubuntu 16.04是构建环境载体,snapshots是最终交付物形态。而热搜词“misconf redis is configured to save rdb snapshots”看似无关,实则暴露了行业通病—— 快照(snapshot)一词在不同语境下承载完全不同的语义负荷 :Redis的RDB snapshot是内存数据的二进制快照,DigitalOcean的snapshot是块存储的全量磁盘镜像,Packer的snapshot构建过程则是将“配置即代码”编译为“镜像即制品”的编译过程。混淆这三者,是踩坑的第一步。
所以本文不讲“如何安装Packer”,也不罗列DigitalOcean API文档——那些官网都有。我要带你走一遍从零开始,在一台真实的Ubuntu 16.04机器上,用Packer生成一个可立即部署、带预装服务、含安全加固的DigitalOcean快照的完整闭环。每一步都标注清楚“为什么非得这样”,包括那些官网绝不会写的细节:比如DigitalOcean API Token权限粒度怎么切、Packer变量注入的时机陷阱、Ubuntu 16.04特有的apt-get update超时对策,以及——最关键的一点——如何让生成的快照在DO控制台里显示为“Active”而非“Pending”状态。
2. 构建环境准备:在Ubuntu 16.04上搭建Packer黄金三角
在Ubuntu 16.04上部署Packer,表面看只是
wget + chmod + mv
三步,但实际要解决四个隐藏层问题:二进制兼容性、网络代理穿透、API凭据安全注入、以及构建缓存隔离。我见过太多团队卡在第一步——下载下来的Packer二进制在Ubuntu 16.04上直接报
./packer: No such file or directory
,结果折腾半天才发现是glibc版本不匹配。
2.1 Packer二进制的精准匹配策略
Packer官方提供的Linux AMD64二进制,从1.5.x开始已全面转向musl libc静态链接,按理说应该“开箱即用”。但Ubuntu 16.04的
/lib64/ld-linux-x86-64.so.2
路径与musl预期存在微小差异,尤其当系统启用了SELinux或AppArmor时。我的实测结论是:
必须使用Packer 1.7.10(最后支持Ubuntu 16.04的稳定版)的动态链接版本
,而非最新版。
操作步骤如下:
# 创建专用构建目录,避免污染系统PATH
mkdir -p ~/packer-build/{bin,templates,cache}
cd ~/packer-build
# 下载Packer 1.7.10动态链接版(注意不是.github.io上的musl版)
wget https://releases.hashicorp.com/packer/1.7.10/packer_1.7.10_linux_amd64.zip
unzip packer_1.7.10_linux_amd64.zip -d bin/
chmod +x bin/packer
# 验证glibc兼容性(关键!)
ldd bin/packer | grep "not found"
# 正常输出应为空;若出现"not found",说明需降级到1.6.6
注意:Packer 1.8.0起强制要求glibc 2.27+,而Ubuntu 16.04自带glibc 2.23。强行运行会触发段错误(Segmentation fault),且core dump极难调试。这是90%新手第一次构建失败的根源。
2.2 DigitalOcean API Token的最小权限设计
DigitalOcean控制台生成的Personal Access Token默认拥有
read
和
write
全权限,但生产环境必须遵循最小权限原则。Packer创建快照只需以下三个scope:
-
droplets:read -
droplets:write(用于创建临时Droplet) -
images:write(用于上传快照)
创建Token的正确路径:
Control Panel → API → Tokens → Generate New Token → 勾选上述三项 → 复制Token值
然后, 绝对不要 把Token明文写进Packer模板。正确做法是通过环境变量注入:
# 在构建机上设置(建议写入~/.profile,避免泄露到ps命令)
echo 'export DIGITALOCEAN_TOKEN="your_actual_token_here"' >> ~/.profile
source ~/.profile
# 验证是否生效
env | grep DIGITALOCEAN_TOKEN
提示:如果你的构建机在企业内网,且出口需走HTTP代理,请务必设置
HTTP_PROXY和HTTPS_PROXY环境变量。Packer 1.7.x对代理的支持不稳定,建议在~/.profile中追加:export HTTP_PROXY="http://proxy.internal:3128" export HTTPS_PROXY="http://proxy.internal:3128" export NO_PROXY="127.0.0.1,localhost,api.digitalocean.com"
2.3 Ubuntu 16.04专属的APT源与超时修复
Ubuntu 16.04官方源已归档,
archive.ubuntu.com
重定向到
old-releases.ubuntu.com
,但部分镜像站未同步。更致命的是,
apt-get update
默认超时仅30秒,而DigitalOcean新加坡机房到
old-releases.ubuntu.com
的RTT常达200ms以上,导致构建中途失败。
解决方案是双管齐下:
-
替换为可靠镜像源 (推荐清华源):
sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list -
全局延长APT超时 (写入
/etc/apt/apt.conf.d/99timeout):echo 'Acquire::http::Timeout "120";' | sudo tee /etc/apt/apt.conf.d/99timeout echo 'Acquire::https::Timeout "120";' | sudo tee -a /etc/apt/apt.conf.d/99timeout echo 'Acquire::Retries "3";' | sudo tee -a /etc/apt/apt.conf.d/99timeout
执行
sudo apt-get update && sudo apt-get install -y curl jq python-pip
后,务必验证
curl -I https://api.digitalocean.com/v2
返回200,否则后续所有API调用都会静默失败。
2.4 构建缓存目录的硬链接优化
Packer在构建过程中会下载大量基础镜像(如Ubuntu 16.04的cloud-init镜像),默认缓存路径为
~/.packer.d/cache
。但在Ubuntu 16.04上,ext4文件系统对小文件写入性能较差,频繁的cache读写会导致构建时间增加40%以上。
我的优化方案是:将cache目录挂载到tmpfs内存文件系统,并启用硬链接去重:
# 创建内存缓存目录
sudo mkdir -p /mnt/packer-cache
sudo mount -t tmpfs -o size=2G tmpfs /mnt/packer-cache
sudo chown $USER:$USER /mnt/packer-cache
# 创建符号链接(Packer会自动识别)
ln -sf /mnt/packer-cache ~/.packer.d/cache
# 启用硬链接(需安装hardlink工具)
sudo apt-get install -y hardlink
# 在Packer构建完成后自动去重(加入构建脚本末尾)
echo 'hardlink -c ~/.packer.d/cache' >> ~/packer-build/build.sh
这个细节让单次构建时间从平均8分23秒降至5分07秒,对于需要高频迭代的镜像开发,每年节省的工程师等待时间以人天计。
3. Packer模板深度解析:从JSON结构到快照语义落地
Packer模板不是配置文件,而是 基础设施的编译器输入规范 。它把“我要一个装了Nginx、开了SSH、禁用了密码登录的Ubuntu 16.04镜像”这种模糊需求,翻译成DigitalOcean API可执行的精确指令流。很多人以为写个JSON就完事,结果构建出的快照根本无法启动——问题往往出在模板的语义断层上。
3.1 builders区块:DigitalOcean插件的七层参数解构
DigitalOcean builder的
type
必须为
"digitalocean"
,但真正决定快照质量的是以下七个关键参数,它们构成一个不可分割的约束组:
| 参数名 | 必填 | 典型值 | 为什么必须这样设 |
|---|---|---|---|
api_token
| 是 |
"{{user
do_token
}}"
| 必须用user变量注入,禁止硬编码 |
image
| 是 |
"ubuntu-16-04-x64"
| 必须用DO官方镜像slug,不能写"ubuntu-16.04" |
region
| 是 |
"sgp1"
| 影响快照地域合规性,且sgp1延迟最低 |
size
| 是 |
"s-1vcpu-1gb"
| 小于此规格无法通过DO健康检查 |
ssh_username
| 是 |
"root"
| Ubuntu 16.04默认只有root用户,无ubuntu用户 |
ssh_timeout
| 是 |
"30m"
| DO新创建Droplet的SSH服务启动慢,需延长 |
snapshot_name
| 是 |
"prod-web-{{timestamp}}"
|
必须含
{{timestamp}}
,否则同名快照会覆盖
|
特别强调
ssh_username
:Ubuntu 16.04的cloud-init默认创建
ubuntu
用户,但DigitalOcean的16.04基础镜像
禁用了该用户
,只保留root且root密码为空。因此
ssh_username
只能是
"root"
,且必须配合
"ssh_password": ""
(空字符串,非null)。
{
"type": "digitalocean",
"api_token": "{{user `do_token`}}",
"image": "ubuntu-16-04-x64",
"region": "sgp1",
"size": "s-1vcpu-1gb",
"ssh_username": "root",
"ssh_timeout": "30m",
"snapshot_name": "web-prod-1604-{{timestamp}}"
}
注意:
snapshot_name中的{{timestamp}}是Packer内置函数,格式为YYYYMMDDHHMMSS。DO控制台对快照名长度限制为64字符,因此前缀务必精简。我曾因写"production-web-server-ubuntu-16-04-lts-2024-q3"导致API返回400错误,调试半小时才发现是命名超长。
3.2 provisioners区块:Shell脚本的幂等性生死线
provisioners是Packer的灵魂,它把裸机变成可用镜像。但90%的失败案例源于Shell provisioner的非幂等设计。例如:
// ❌ 危险写法:每次执行都apt-get install,可能因网络中断失败
{
"type": "shell",
"inline": ["apt-get update && apt-get install -y nginx"]
}
正确做法是分三层防御:
- 前置检查 :确认包管理器状态
-
幂等安装
:用
apt-get install --reinstall确保状态一致 - 服务验证 :检查进程与端口
{
"type": "shell",
"inline": [
"set -eux", // 关键!开启严格模式,任一命令失败即终止
"apt-get update || true", // 允许update失败,但后续必须成功
"DEBIAN_FRONTEND=noninteractive apt-get install -y nginx",
"systemctl is-active --quiet nginx || systemctl start nginx",
"ss -tln | grep ':80' || exit 1" // 端口验证,失败则整个构建失败
]
}
提示:
set -eux是Shell脚本的“安全带”。-e使任一命令失败即退出,-u禁止未定义变量,-x打印执行命令。在Packer中,这比任何try-catch都可靠。
3.3 variables区块:用户变量的注入时序陷阱
Packer变量分三类:
user
(用户定义)、
build
(构建时生成)、
template
(模板内硬编码)。新手常犯的错误是把
do_token
写在variables里:
// ❌ 错误:token明文写入模板
"variables": {
"do_token": "abc123..."
}
正确结构必须是:
{
"variables": {
"do_token": "{{env `DIGITALOCEAN_TOKEN`}}", // 从环境变量读取
"region": "sgp1",
"snapshot_prefix": "web-prod"
},
"builders": [{
"type": "digitalocean",
"api_token": "{{user `do_token`}}", // 这里引用
"region": "{{user `region`}}",
"snapshot_name": "{{user `snapshot_prefix`}}-{{timestamp}}"
}]
}
关键原理
:Packer变量解析是单向传递的。
{{env ...}}
在模板加载时解析,
{{user ...}}
在builder执行前解析,
{{timestamp}}
在builder启动瞬间解析。如果把
{{timestamp}}
写在variables里,它会在模板加载时就固化,导致所有快照同名。
3.4 一个生产级模板的完整骨架
以下是我在客户项目中实际使用的模板(
ubuntu1604-web.json
),已去除敏感信息,保留全部工程细节:
{
"variables": {
"do_token": "{{env `DIGITALOCEAN_TOKEN`}}",
"region": "{{env `DO_REGION`}}",
"size": "s-1vcpu-1gb",
"snapshot_prefix": "prod-web-1604"
},
"builders": [{
"type": "digitalocean",
"api_token": "{{user `do_token`}}",
"image": "ubuntu-16-04-x64",
"region": "{{user `region`}}",
"size": "{{user `size`}}",
"ssh_username": "root",
"ssh_timeout": "30m",
"snapshot_name": "{{user `snapshot_prefix`}}-{{timestamp}}"
}],
"provisioners": [
{
"type": "shell",
"inline": [
"set -eux",
"apt-get update",
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget gnupg2 ca-certificates",
"curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -",
"add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"",
"apt-get update",
"DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce=18.06.3~ce~3-0~ubuntu"
]
},
{
"type": "shell",
"inline": [
"set -eux",
"useradd -m -s /bin/bash deploy",
"echo 'deploy:deploy123' | chpasswd",
"usermod -aG sudo deploy",
"sed -i 's/^# %sudo/%sudo/' /etc/sudoers"
]
},
{
"type": "shell",
"inline": [
"set -eux",
"systemctl disable apt-daily.service apt-daily.timer",
"systemctl disable systemd-timesyncd.service",
"echo 'UTC' > /etc/timezone",
"dpkg-reconfigure -f noninteractive tzdata"
]
}
]
}
这个模板的价值在于:它构建的快照不仅装了Docker,还创建了标准部署用户、禁用了系统自动更新(避免生产环境意外重启)、并统一了时区。每一行都是线上事故换来的教训。
4. 构建过程全链路追踪:从packer build到DO控制台可见
执行
packer build ubuntu1604-web.json
不是按下回车就完事。Packer的构建过程分为五个明确阶段,每个阶段都有可观测指标和失败特征。掌握这些,才能把“构建失败”从玄学变成可诊断事件。
4.1 阶段一:Template Validation(模板校验)
Packer首先解析JSON语法并验证必填字段。失败表现:
Failed to parse template: Error parsing...
。常见原因:
-
JSON末尾多逗号(
,) -
变量引用格式错误(如
{{user do_token}}漏掉反引号) -
builder type拼写错误(
"digitalocean"写成"digital ocean")
诊断命令:
packer validate -var-file=variables.json ubuntu1604-web.json
# 或检查语法
python -m json.tool ubuntu1604-web.json >/dev/null
4.2 阶段二:Builder Initialization(构建器初始化)
Packer调用DigitalOcean API创建临时Droplet。这是最常失败的阶段,错误码集中在400/401/422:
| HTTP状态码 | 典型错误信息 | 根本原因 | 修复动作 |
|---|---|---|---|
| 401 |
Unauthorized
|
DIGITALOCEAN_TOKEN
无效或过期
| 重新生成Token,检查环境变量 |
| 400 |
size_slug is required
|
size
参数值不在DO有效规格列表中
|
改为
s-1vcpu-1gb
或
512mb
|
| 422 |
You specified an invalid image slug
|
image
值错误,如
ubuntu-16.04
应为
ubuntu-16-04-x64
| 查DO官方镜像列表修正 |
关键观测点:执行
packer build
后,立即登录DO控制台,查看Droplets列表是否有新实例(状态为
New
)。若有,说明API调用成功;若无,问题出在认证或参数。
4.3 阶段三:Provisioning(配置注入)
Packer通过SSH连接Droplet并执行provisioners。失败特征:卡在
Waiting for SSH...
或
Timeout waiting for SSH.
。根本原因有三:
-
SSH服务未启动
:Ubuntu 16.04的cloud-init有时延迟启动SSH,需延长
ssh_timeout - 防火墙拦截 :DO默认开放22端口,但若Droplet启用了UFW,需在provisioner中关闭
- 密钥认证失败 :Packer默认用随机密钥,但若Droplet镜像禁用了密码登录且未预置密钥,会失败
我的标准修复provisioner(加在第一个shell前面):
{
"type": "shell",
"inline": [
"set -eux",
"ufw status | grep -q inactive || ufw disable",
"systemctl is-active --quiet sshd || systemctl start sshd",
"ss -tln | grep ':22' || exit 1"
]
}
4.4 阶段四:Snapshot Creation(快照创建)
Packer调用
/v2/images
API创建快照。成功标志是控制台中快照状态从
Creating
变为
Available
。但这里有个严重陷阱:
Packer默认不等待快照完成就退出
,导致你看到
Build 'digitalocean' finished.
,但DO控制台里快照仍是
Pending
。
解决方案:在模板中添加
"snapshot_wait_timeout": "60m"
(单位是分钟,不是秒!):
{
"type": "digitalocean",
"snapshot_wait_timeout": "60m", // 必须显式设置
...
}
实测数据:Ubuntu 16.04的1GB快照创建耗时约22-38分钟,取决于磁盘IO负载。60分钟足够覆盖99.9%场景。
4.5 阶段五:Artifact Export(制品导出)
构建成功后,Packer输出类似:
==> Builds finished. The artifacts of successful builds are:
--> digitalocean: A snapshot was created: 'web-prod-1604-20240520143022'
此时,快照已存在于DO账户中,但 尚未关联到任何Droplet 。你需要手动在控制台中:
- 进入Images → Snapshots
- 找到对应快照(按名称或创建时间)
-
点击
...→Assign to Droplet(可选,非必须)
注意:Packer生成的快照默认是私有(Private),仅你账户可见。若需共享给其他团队,必须在DO控制台中点击
Make Public,并接受DO的公开镜像审核条款。
5. 快照验证与生产就绪检查清单
构建成功不等于可用。一个合格的DigitalOcean快照必须通过五层验证,缺一不可。我设计了一套自动化验证脚本(
validate-snapshot.sh
),每次构建后自动运行,覆盖所有关键维度。
5.1 基础连通性验证
创建一个临时Droplet,用快照启动,测试SSH可达性:
#!/bin/bash
# validate-snapshot.sh
SNAPSHOT_ID=$(doctl compute image list --format ID,Name | grep "web-prod-1604" | head -1 | awk '{print $1}')
echo "Using snapshot ID: $SNAPSHOT_ID"
# 创建临时Droplet
DROPLET_ID=$(doctl compute droplet create \
--image "$SNAPSHOT_ID" \
--region sgp1 \
--size s-1vcpu-1gb \
--ssh-keys "$(doctl compute ssh-key list --format ID --no-header | head -1)" \
--wait \
--format ID \
--no-header)
echo "Created droplet: $DROPLET_ID"
# 获取IP
IP=$(doctl compute droplet get "$DROPLET_ID" --format PublicIPv4 --no-header)
# 测试SSH(超时30秒)
if timeout 30 ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"$IP" 'echo "SSH OK"'; then
echo "✅ SSH connectivity: PASS"
else
echo "❌ SSH connectivity: FAIL"
exit 1
fi
5.2 服务功能验证
登录后检查关键服务状态:
# 继续上面的脚本...
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"$IP" << 'EOF'
set -eux
# 检查Docker
docker version --format '{{.Server.Version}}' | grep -q "18.06" || exit 1
# 检查部署用户
id deploy || exit 1
su - deploy -c 'whoami' || exit 1
# 检查时区
date | grep -q "UTC" || exit 1
# 检查自动更新已禁用
systemctl is-enabled apt-daily.timer | grep -q "disabled" || exit 1
EOF
5.3 安全基线验证
快照必须满足最低安全要求,否则上线即高危:
| 检查项 | 命令 | 合格标准 |
|---|---|---|
| 密码登录禁用 |
grep "^PasswordAuthentication" /etc/ssh/sshd_config
|
输出
PasswordAuthentication no
|
| Root登录禁用 |
grep "^PermitRootLogin" /etc/ssh/sshd_config
|
输出
PermitRootLogin no
(注意:Packer构建时需先用root登录,构建后必须禁用)
|
| 未授权端口关闭 | `ss -tln | grep -E ":(22 | 80 |
| 内核参数加固 |
sysctl net.ipv4.conf.all.send_redirects
|
返回
net.ipv4.conf.all.send_redirects = 0
|
提示:
PermitRootLogin no必须在最后一个provisioner中设置,否则快照里root仍可SSH登录。这是重大安全漏洞。
5.4 快照元数据审计
DigitalOcean快照附带元数据,必须符合内部审计要求:
# 获取快照详细信息
doctl compute image get "$SNAPSHOT_ID" --format "ID,Name,Type,Public,MinDiskSize,CreatedAt"
# 标准应为:
# ID: 数字ID
# Name: 包含时间戳的唯一名称
# Type: snapshot
# Public: false
# MinDiskSize: 1 (GB)
# CreatedAt: ISO8601时间
5.5 构建产物归档与版本追溯
Packer不保存构建日志,必须主动归档:
# 构建时重定向日志
packer build -machine-readable ubuntu1604-web.json 2>&1 | tee "build-$(date +%Y%m%d-%H%M%S).log"
# 日志中提取关键信息
grep "artifact,0,id" "build-*.log" | tail -1 | cut -d',' -f6 # 提取快照ID
grep "artifact,0,string" "build-*.log" | tail -1 | cut -d',' -f6 | sed 's/\"//g' # 提取快照名
最终,一个生产就绪的快照必须同时满足:
-
✅ 控制台显示状态为
Available -
✅ 可通过
doctl compute droplet create --image <ID>成功部署 - ✅ 部署后SSH登录、服务启动、安全配置全部达标
-
✅ 日志中包含
Build 'digitalocean' finished.且无error字样 - ✅ 归档日志包含快照ID、构建时间、Packer版本、构建机IP
这套验证流程,是我为客户交付的23个Ubuntu 16.04快照模板的统一标准。它把“构建成功”从一句口号,变成可审计、可回滚、可度量的工程事实。
6. 常见故障排查实战:从报错日志定位根因
Packer构建失败时,错误信息往往藏在层层嵌套的日志里。与其盲目谷歌,不如建立一套标准化排查路径。以下是我在过去三年处理的TOP 5故障及其根因分析。
6.1 故障一:“Error uploading snapshot: 401 Unauthorized”
现象
:构建卡在
Creating snapshot...
阶段,日志末尾显示
Error uploading snapshot: 401 Unauthorized
。
排查链路 :
-
检查
packer build命令执行时的环境变量:env | grep DIGITALOCEAN_TOKEN -
若变量存在,用
curl -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" https://api.digitalocean.com/v2/account验证Token有效性 -
若返回401,登录DO控制台检查Token状态(是否被撤销、是否过期、scope是否缺失
images:write)
根因
:Token权限不足。DigitalOcean的
images:write
scope在Token创建页面位于底部,极易被忽略。且该scope不包含在默认勾选中。
修复
:删除旧Token,新建Token时
手动滚动到底部,勾选
images:write
,再重新执行构建。
6.2 故障二:“Timeout waiting for SSH”
现象
:日志停在
Waiting for SSH to become available...
,30分钟后超时退出。
排查链路 :
-
登录DO控制台,找到正在构建的Droplet(名称含
packer-前缀) -
点击
Console Access,观察启动日志 -
若看到
cloud-init[123]: Cloud-init v. 18.2-45-g45153e1a-0ubuntu1~16.04.1 running 'init-local' at ...,说明cloud-init已启动 -
若看到
Starting OpenBSD Secure Shell server...后无响应,检查/var/log/cloud-init-output.log
根因
:Ubuntu 16.04的cloud-init在某些区域镜像中,SSH密钥注入逻辑存在竞态条件,导致
/root/.ssh/authorized_keys
未写入。
修复 :在第一个provisioner中强制重写密钥:
{
"type": "shell",
"inline": [
"set -eux",
"mkdir -p /root/.ssh",
"echo 'ssh-rsa AAAAB3NzaC1yc2E... packer' > /root/.ssh/authorized_keys",
"chmod 700 /root/.ssh && chmod 600 /root/.ssh/authorized_keys"
]
}
6.3 故障三:“Error creating droplet: 422 You specified an invalid region”
现象
:
packer validate
通过,但
packer build
报
422
错误,提示region无效。
排查链路 :
-
运行
doctl compute region list获取当前账户支持的region列表 -
检查模板中
region值是否在列表中(注意大小写,如sgp1不是SGP1) -
若使用
nyc1等老region,检查DO是否已将其标记为available:false
根因
:DigitalOcean定期下线老旧region。
nyc1
、
ams2
等region已在2023年Q4停用,但Packer模板未更新。
修复
:改用
nyc3
、
ams3
或
sgp1
。其中
sgp1
(新加坡)对亚太用户延迟最低,且资源充足。
6.4 故障四:“Build 'digitalocean' errored: Failed to create snapshot”
现象
:构建完成,但日志末尾显示
Failed to create snapshot
,控制台中无新快照。
排查链路 :
-
检查Droplet是否仍在运行:
doctl compute droplet list | grep packer - 若Droplet存在,手动在控制台中对其创建快照(Actions → Snapshots)
-
若手动创建也失败,检查Droplet磁盘使用率:
doctl compute droplet get <ID> --format "ID,Status,Memory,Disk,Region,Image",然后SSH进去执行df -h -
若
/分区使用率>95%,Packer快照创建会失败(DO要求至少5%空闲空间)
根因
:Ubuntu 16.04的
/var/log/journal
日志占满磁盘。cloud-init默认启用journald,且不轮转。
修复 :在provisioner中清理日志:
{
"type": "shell",
"inline": [
"set -eux",
"journalctl --disk-usage",
"journalctl --vacuum-size=100M",
"rm -rf /var/log/*.gz /var/log/*.1"
]
}
6.5 故障五:“The artifact ID is not a valid DigitalOcean image ID”
现象
:构建成功,但后续用
doctl compute droplet create --image <ID>
失败,报ID无效。
排查链路 :
-
运行
doctl compute image list --public=false --format ID,Name,Type | grep snapshot -
检查输出中ID是否为纯数字(如
123456789),还是带前缀(如ubuntu-16-04-x64) -
若ID含字母,说明你误用了
imageslug而非快照ID
根因
:混淆了
image
(基础镜像标识符)和
snapshot

491

被折叠的 条评论
为什么被折叠?



