Docker DNS故障导致GitLab CI MAX通知失败:调试与修复
一个本该在部署后向MAX Messenger发送通知的GitLab CI任务,演变成一周的调试噩梦。请求已发送,但响应返回{"code":"proto.payload","message":"Can't deserialize body"}。日志指向请求体问题,我们于是专注于构建JSON负载。
即使简化后仍无效。环境变量MAX_BOT_TOKEN和MAX_NOTIFY_CHAT_ID已设置,镜像中也有curl。但消息始终未送达。
失败的负载尝试
团队尝试了多次迭代:
- printf写入文件:
printf '{"text":"%s"}' "$MSG" > /tmp/payload.json; curl ... -d @/tmp/payload.json。响应:Can't deserialize body。
- Alpine中使用jq: 切换到
alpine:3.20,运行apk add jq。jq -nc --arg t "$MSG" '{text:$t}' > /tmp/payload.json。相同响应。
- jq到变量:
PAYLOAD=$(jq ...); curl ... --data "${PAYLOAD}"。现在变为Empty request body,此外apk add因仓库不可达而退出码2失败。
- 硬编码:
curl ... -d '{"text":"test ok"}'。仍是Empty request body。
进展甚微:请求体始终无法完整到达服务器。
关键测试:宿主机 vs 容器
并行对比揭示了罪魁祸首:
从宿主机:
ssh ... "curl -s -X POST 'https://platform-api.max.ru/messages?chat_id=...' -H 'Authorization: ...' -H 'Content-Type: application/json' -d '{"text":"test from server"}'"
HTTP 200,消息送达。
从Docker:
docker run --rm curlimages/curl:8.7.1 curl -s -X POST 'https://platform-api.max.ru/messages?chat_id=...' -H 'Authorization: ...' -d '{"text":"test from docker"}'
退出码6:Could not resolve host: platform-api.max.ru。
诊断:Docker容器内DNS解析失败,尽管宿主机正常。
使用--dns 8.8.8.8测试确认:消息瞬间送达。
为什么DNS故障伪装成JSON错误
我们预期DNS错误如curl退出码6且无HTTP响应。但MAX返回了带proto.payload的JSON。问题源于GitLab Runner为任务容器设置的自定义DNS。这导致连接不稳——请求体被截断或完全丢弃。MAX将空输入视为反序列化错误。
修复:Runner的config.toml中配置DNS
解决方案:在/etc/gitlab-runner/config.toml添加DNS服务器:
[[runners]]
...
[runners.docker]
dns = ["8.8.8.8", "8.8.4.4"]
重启:sudo gitlab-runner restart。
Docker守护进程备选方案,在/etc/docker/daemon.json:
{
"dns": ["8.8.8.8", "8.8.4.4"]
}
sudo systemctl restart docker。
修复后,硬编码负载首次运行即成功。
可用脚本模板
最终脚本带诊断和变量清理:
if [ -z "$MAX_BOT_TOKEN" ] || [ -z "$MAX_NOTIFY_CHAT_ID" ]; then
echo "WARN: MAX_BOT_TOKEN or MAX_NOTIFY_CHAT_ID not set"
exit 0
fi
TOKEN="$(printf '%s' "$MAX_BOT_TOKEN" | tr -d '\r\n')"
CHAT_ID="$(printf '%s' "$MAX_NOTIFY_CHAT_ID" | tr -d '\r\n[:space:]')"
BODY_FILE="$(mktemp)"
RESPONSE_FILE="$(mktemp)"
printf '{"text":"%s"}' "${MSG}" > "${BODY_FILE}"
BODY_SIZE="$(wc -c < "${BODY_FILE}" | tr -d '[:space:]')"
echo "DEBUG: CHAT_ID=${CHAT_ID}"
echo "DEBUG: BODY_SIZE=${BODY_SIZE}"
echo "DEBUG: BODY_TEXT=$(cat "${BODY_FILE}")"
CURL_STATUS=0
HTTP_CODE=$(curl -sS --max-time 15 \
-o "${RESPONSE_FILE}" \
-w "%{http_code}" \
-X POST "https://platform-api.max.ru/messages?chat_id=${CHAT_ID}" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
--data @"${BODY_FILE}") || CURL_STATUS=$?
RESPONSE="$(cat "${RESPONSE_FILE}" 2>/dev/null || true)"
rm -f "${BODY_FILE}" "${RESPONSE_FILE}"
echo "DEBUG: HTTP_CODE=${HTTP_CODE} CURL_STATUS=${CURL_STATUS}"
echo "DEBUG: Response: ${RESPONSE}"
if [ "${CURL_STATUS}" -ne 0 ] || [ "${HTTP_CODE}" != "200" ]; then
echo "ERROR: Failed to send. Response: ${RESPONSE}"
exit 1
fi
通知格式与数据提取
通知从GitLab CI变量拉取:
CI_PROJECT_NAME— 项目CI_COMMIT_BRANCH— 分支CI_COMMIT_SHORT_SHA— 提交哈希CI_COMMIT_TITLE— 提交消息(用sed 's/\/\\/g; s/\"\"/g'转义)CI_COMMIT_AUTHOR— 作者CI_PIPELINE_URL— 流水线链接
示例:
✅ DEV环境部署成功
📦 项目:u.clinic
🌍 环境:DEV
🌿 分支:dev
🔖 版本:f7a249d0
💬 提交:fix(ci): improve MAX notification handling
👤 作者:Anton Kurganskii
⏰ 时间 (UTC):2026-04-08 21:29:07
⏱️ 流水线耗时:9分52秒
🔗 流水线:https://git.tverdasoft.ru/...
✓ 部署完成
镜像选择与备选
curlimages/curl:8.7.1优于alpine + jq:无需网络安装包,使用pull_policy: if-not-present从缓存运行。Alpine的apk add因仓库不可达失败。
关键要点
- GitLab Runner中Docker DNS故障会模拟不稳定连接引起的负载错误。
- 先测试:Runner宿主机上
docker run curl ... platform-api.max.ru——退出码6提示DNS问题。 - 修复:在config.toml的
[runners.docker]中设置dns = ["8.8.8.8", "8.8.4.4"]。 - 调试:记录
BODY_SIZE、HTTP_CODE、完整Response。 - 提交消息:
tr -d '\r\n'+sed转义。
— Editorial Team
暂无评论。