Кейс из практики: на сервере 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 в пайплайнах работают стабильно.