Powrót do strony głównej

DNS Docker w GitLab CI: poprawka powiadomień MAX

Przypadek debugowania powiadomień MAX z GitLab CI: objawy proto.payload maskowały problem DNS w Docker. Poprawka przez config.toml runnera, działający szablon skryptu z diagnostyką.

GitLab CI + MAX: DNS zepsuł powiadomienia o wdrożeniu
Advertisement 728x90

DNS w Dockerze zepsuł powiadomienia MAX z GitLab CI: debugowanie i naprawa

W GitLab CI zadanie wysłania powiadomienia do MAX Messenger po wdrożeniu skończyło się tygodniem debugowania. Zapytania wychodziły, ale odpowiedź przychodziła z błędem {"code":"proto.payload","message":"Can't deserialize body"}. Log wskazywał na problem z ciałem zapytania, więc skupiono się na formowaniu JSON-payloadu.

Mimo uproszczeń nic nie działało. Zmienne środowiskowe MAX_BOT_TOKEN i MAX_NOTIFY_CHAT_ID były ustawione, curl był w obrazie. Ale wiadomości nie docierały.

Nieudane próby z payloadem

Skrypt przeszedł kilka iteracji:

Google AdInline article slot
  • printf do pliku: printf '{"text":"%s"}' "$MSG" > /tmp/payload.json; curl ... -d @/tmp/payload.json. Odpowiedź: Can't deserialize body.
  • jq w Alpine: Przejście na alpine:3.20 z apk add jq. jq -nc --arg t "$MSG" '{text:$t}' > /tmp/payload.json. Ta sama odpowiedź.
  • jq do zmiennej: PAYLOAD=$(jq ...); curl ... --data "${PAYLOAD}". Teraz Empty request body, plus apk add kończyło się kodem błędu 2 z powodu niedostępnych repozytoriów.
  • Harkod: curl ... -d '{"text":"test ok"}'. Znowu Empty request body.

Postęp był minimalny: ciało zapytania konsekwentnie nie docierało.

Kluczowy test: host vs kontener

Porównanie pokazało różnicę:

Z hosta:

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, wiadomość dostarczona.

Z Dockera:

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"}'

exit code 6: Could not resolve host: platform-api.max.ru.

Google AdInline article slot

Diagnoza: DNS w kontenerach Docker nie działał, choć host rozwiązywał poprawnie.

Test z --dns 8.8.8.8 potwierdził: wiadomość poszła natychmiast.

Dlaczego DNS udawał błąd JSON

Oczekiwany błąd DNS to curl exit code 6 bez odpowiedzi HTTP. Zamiast tego przychodził JSON od MAX z proto.payload. Przyczyna w specyfice GitLab runnera: używa niestandardowej konfiguracji DNS dla kontenerów zadań. To powodowało niestabilne połączenie — ciało było obcinane lub gubione, MAX interpretował pusty input jako błąd deserializacji.

Naprawa: DNS w config.toml runnera

Rozwiązanie — dodać DNS w /etc/gitlab-runner/config.toml:

[[runners]]
  ...
  [runners.docker]
    dns = ["8.8.8.8", "8.8.4.4"]

Restart: sudo gitlab-runner restart.

Alternatywa na poziomie Dockera w /etc/docker/daemon.json:

{
  "dns": ["8.8.8.8", "8.8.4.4"]
}

sudo systemctl restart docker.

Po naprawie harkodowy payload zadziałał przy pierwszym uruchomieniu.

Gotowy szablon skryptu

Ostateczny skrypt z diagnostyką i czyszczeniem zmiennych:

if [ -z "$MAX_BOT_TOKEN" ] || [ -z "$MAX_NOTIFY_CHAT_ID" ]; then
  echo "WARN: MAX_BOT_TOKEN lub MAX_NOTIFY_CHAT_ID nie są skonfigurowane"
  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: nie udało się wysłać. Response: ${RESPONSE}"
  exit 1
fi

Format powiadomienia i wyciąganie danych

Powiadomienie składane z zmiennych GitLab CI:

  • CI_PROJECT_NAME — projekt
  • CI_COMMIT_BRANCH — gałąź
  • CI_COMMIT_SHORT_SHA — wersja
  • CI_COMMIT_TITLE — wiadomość commita (ekranizowana sed 's/\/\\/g; s/\"\"/g')
  • CI_COMMIT_AUTHOR — autor
  • CI_PIPELINE_URL — link do pipeline'u

Przykład:

✅ Udane wdrożenie na DEV

📦 Projekt: u.clinic
🌍 Środowisko: DEV
🌿 Gałąź: dev
🔖 Wersja: f7a249d0
💬 Commit: fix(ci): poprawić obsługę powiadomień MAX
👤 Autor: Anton Kurganskii
⏰ Czas (MSK): 2026-04-08 21:29:07
⏱️ Czas trwania pipeline: 9 min 52 s

🔗 Pipeline: https://git.tverdasoft.ru/...

✓ Wdrożenie zakończone pomyślnie

Wybór obrazu i alternatywy

curlimages/curl:8.7.1 wybrany zamiast alpine + jq: nie wymaga sieci do instalacji pakietów, działa z cache pod pull_policy: if-not-present. apk add w Alpine kończyło się błędem z powodu niedostępnych repozytoriów.

Co ważne

  • Błędy DNS w Dockerze udają problemy z payloadem przez niestabilne połączenie w GitLab runnerze.
  • Najpierw test docker run curl ... platform-api.max.ru na serwerze runnera — exit code 6 wskazuje DNS.
  • Naprawa: dns = ["8.8.8.8", "8.8.4.4"] w [runners.docker] config.toml.
  • Diagnostyka: logować BODY_SIZE, HTTP_CODE, pełny Response.
  • Dla wiadomości commitów: tr -d '\r\n' + ekranizacja sed.

— Editorial Team

Advertisement 728x90

Czytaj dalej