返回首页

GitLab CI 中的 DNS Docker:MAX 通知修复

来自 GitLab CI 的 MAX 通知调试案例:proto.payload 症状掩盖了 Docker 中的 DNS 问题。通过 runner 的 config.toml 修复,附带诊断的脚本模板。

GitLab CI + MAX:DNS 破坏了部署通知
Advertisement 728x90

Docker DNS故障导致GitLab CI MAX通知失败:调试与修复

一个本该在部署后向MAX Messenger发送通知的GitLab CI任务,演变成一周的调试噩梦。请求已发送,但响应返回{"code":"proto.payload","message":"Can't deserialize body"}。日志指向请求体问题,我们于是专注于构建JSON负载。

即使简化后仍无效。环境变量MAX_BOT_TOKENMAX_NOTIFY_CHAT_ID已设置,镜像中也有curl。但消息始终未送达。

失败的负载尝试

团队尝试了多次迭代:

Google AdInline article slot
  • printf写入文件: printf '{"text":"%s"}' "$MSG" > /tmp/payload.json; curl ... -d @/tmp/payload.json。响应:Can't deserialize body
  • Alpine中使用jq: 切换到alpine:3.20,运行apk add jqjq -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 容器

并行对比揭示了罪魁祸首:

从宿主机:

Google AdInline article slot
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

Google AdInline article slot

诊断: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_SIZEHTTP_CODE、完整Response
  • 提交消息:tr -d '\r\n' + sed转义。

— Editorial Team

Advertisement 728x90

继续阅读