Почему GitLab CI «зависает» на apk add в docker:latest и как это исправить (MTU в Docker-сети)

Docker network

Кейс из практики: на сервере Ubuntu настроен GitLab и GitLab Runner (Docker executor). Пайплайн с образом docker:latest зависает при выполнении apk add внутри job’ового контейнера. При этом сами Docker-образы тянутся нормально.

Симптомы

  • Логи job’а доходят до строки вида:
  apk add --no-cache curl
  fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz

и «висят» без явной ошибки.

  • Образы (docker:latest и helper) подтягиваются без проблем.
  • Вручную в контейнере:
  docker run --rm alpine:3.20 sh -xec 'apk -vv update'

зависает на fetch.

Почему так происходит

  • Docker-образы качает Docker-демон на хосте (в обход проблем внутри контейнера).
  • А apk add внутри контейнера использует сеть docker bridge (NAT через docker0).
  • В облачных средах (например, OpenStack) MTU внешнего интерфейса часто 1450 из‑за инкапсуляции. Docker по умолчанию создаёт bridge с MTU 1500.
  • Итог: PMTU discovery ломается (ICMP «Fragmentation needed» фильтруется), TLS-сессии «замирают» — apk висит на fetch.

Быстрая диагностика
1) Проверить, что проблема именно в docker bridge:

docker run --rm alpine:3.20 sh -xec 'apk -vv update'               # виснет
docker run --rm --network host alpine:3.20 sh -xec 'apk -vv update' # работает

2) Убедиться, что DNS здесь не при чём:

docker run --rm alpine:3.20 sh -xec 'cat /etc/resolv.conf; nslookup dl-cdn.alpinelinux.org; getent ahosts dl-cdn.alpinelinux.org'

Если резолвится и есть A/AAAA-записи, а fetch всё равно висит — почти наверняка MTU.

3) Проверить MTU на интерфейсах:

ip -br link | egrep 'docker0|en|eth'
docker network inspect bridge | grep -i mtu -A2
ip link show ens3 | grep -i mtu     # замените ens3 на ваш внешний интерфейс

Часто видим: docker bridge MTU 1500, внешний интерфейс MTU 1450.

Как исправить (рекомендуется)
1) Выставить корректный MTU для Docker-сетей (равный MTU внешнего интерфейса, обычно 1450):
Создайте/отредактируйте /etc/docker/daemon.json:

{
  "mtu": 1450,
  "ipv6": false
}

Перезапустите сервисы:

systemctl restart docker
systemctl restart gitlab-runner

Проверьте:

docker network inspect bridge | grep -i mtu
ip link show docker0 | grep -i mtu
docker run --rm alpine:3.20 sh -xec 'apk -vv update'  # должно пройти

Как подобрать правильный MTU, если не уверены
Найдите максимальный размер пакета без фрагментации и прибавьте 28:

for s in 1472 1464 1452 1440 1432 1424 1412 1400; do
  if ping -c1 -W1 -M do -s $s 1.1.1.1 >/dev/null; then
    echo "OK $s"; break
  else
    echo "FAIL $s"
  fi
done

MTU = «OK» + 28. Это значение укажите в daemon.json.

Дополнительные и временные решения

  • Включить TCP MSS clamping (если не хотите менять MTU прямо сейчас).
    iptables (legacy):
  iptables -t mangle -C FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu || \
  iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

nftables:

  nft add rule inet filter forward tcp flags syn tcp option maxseg size set clamp to pmtu
  • Временно запустить раннер в host-сети:
    В /etc/gitlab-runner/config.toml:
  [runners.docker]
    network_mode = "host"

Затем:

  systemctl restart gitlab-runner
  • Задать DNS для контейнеров раннера (если есть проблемы с резолвом):
    В /etc/gitlab-runner/config.toml:
  [runners.docker]
    dns = ["1.1.1.1","8.8.8.8"]

Проверка после исправления

docker run --rm alpine:3.20 sh -xec 'apk -vv update'   # должно отработать быстро
gitlab-runner restart
# Перезапустить пайплайн из UI

Небольшой совет для CI
Если вам нужно просто проверить сетевую связность в job’е с образом docker:latest, можно не тянуть curl через apk (именно это и зависало), а использовать встроенный busybox wget:

before_script:
  - wget -S -O- http://ya.ru || true

А для задач, где нужен именно curl, используйте отдельный образ с curl (например, curlimages/curl) в отдельных шагах, либо устанавливайте MTU как описано выше.

Чек-лист на будущее

  • MTU docker bridge должен соответствовать MTU внешнего интерфейса (в OpenStack обычно 1450).
  • Если «виснет» только внутри контейнера, но образы тянутся — проверьте MTU/DNS/IPv6 в Docker.
  • Быстрая диагностика: сравнить поведение с обычной сетью и с —network host.
  • Временный обходной путь: MSS clamping или network_mode=host.

После выставления правильного MTU apk/curl, а также docker build и docker push в пайплайнах работают стабильно.

Просмотров: 11 просмотров

Вам также может понравиться

Автор: Джон Смитов

Coach of the pohuizm. Trasher in the web development.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.