Docker DNS 문제로 GitLab CI MAX 알림 실패: 디버깅과 해결
배포 후 MAX 메신저로 알림을 보내는 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 테스트로 확인: 메시지 즉시 전송.
JSON 오류로 위장된 DNS 문제
curl 종료 코드 6 같은 DNS 오류를 기대했는데, MAX가 proto.payload JSON으로 응답했어요. GitLab 러너의 작업 컨테이너 커스텀 DNS 설정 때문이었죠. 이로 연결이 불안정해져 요청 본문이 잘리거나 사라졌고, MAX가 빈 입력을 역직렬화 오류로 봤습니다.
해결: 러너 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 러너 Docker DNS 실패가 불안정 연결으로 페이로드 오류처럼 보임.
- 먼저 테스트: 러너 호스트에서
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
아직 댓글이 없습니다.