ftops.space
84 subscribers
555 photos
98 videos
33 files
278 links
ftops.space
Сервера на linux, FreeBSD, сети на микротик и сиськах.
Download Telegram
03:00, сценарий планового вывода узла K3s. Дежурный выставил Longhorn allowScheduling\=false и выключил сервер. Тома стали degraded, восстановление реплик нагрузило соседние диски. Поверхностный диагноз: «хранилище тормозит». Настоящий: запрет новых реплик приняли за перенос существующих. Kubernetes не отменяет физику дисков, зато прекрасно размножает зелёные статусы. До отключения смотрим размещение: kubectl -n longhorn-system get replicas.longhorn.io -o custom-columns\=NAME:.metadata.name,NODE:.spec.nodeID,VOLUME:.spec.volumeName. На оставшихся узлах: cat /proc/pressure/io и iostat -xz 1. Рост PSI io some/full показывает время задержек задач из-за I/O; await — задержки блочных запросов. Это следы нагрузки, а не доказательство поломки диска. Сопоставляем их с началом rebuild. Для эвакуации нужны allowScheduling\=false и evictionRequested\=true в Longhorn Node CR. Longhorn переносит реплику после успешного восстановления замены — это описано в документации
03:00, сценарий планового вывода узла K3s. Дежурный выставил Longhorn allowScheduling\=false и выключил сервер. Тома стали degraded, восстановление реплик нагрузило соседние диски. Поверхностный диагноз: «хранилище тормозит». Настоящий: запрет новых реплик приняли за перенос существующих. Kubernetes не отменяет физику дисков, зато прекрасно размножает зелёные статусы. До отключения смотрим размещение: kubectl -n longhorn-system get replicas.longhorn.io -o custom-columns\=NAME:.metadata.name,NODE:.spec.nodeID,VOLUME:.spec.volumeName. На оставшихся узлах: cat /proc/pressure/io и iostat -xz 1. Рост PSI io some/full показывает время задержек задач из-за I/O; await — задержки блочных запросов. Это следы нагрузки, а не доказательство поломки диска. Сопоставляем их с началом rebuild. Для эвакуации нужны allowScheduling\=false и evictionRequested\=true в Longhorn Node CR. Longhorn переносит реплику после успешного восстановления замены — это описано в документации. Заранее проверяем ёмкость и ограничения размещения на принимающих узлах. Если замене негде жить, флаг не создаст ей диск. Перед выводом проверяем завершение эвакуации, отсутствие реплик на узле и восстановление требуемой избыточности томов. Затем — штатный drain с учётом политики Longhorn и отключение. Для краткого обслуживания стратегия может отличаться, но сам allowScheduling\=false разрешением на shutdown не становится. Эксплуатация начинается с проверки результата: https://ftops.space.
03:17. Учебный постмортем: потерян quorum etcd, начинаем восстановление K3s. Последняя выгрузка в S3 зелёная, API недоступен, restore с прежним S3 Secret не работает. Первый диагноз — отвалился маршрут до бакета. Покупка объектного хранилища уже состоялась. Покупка работоспособного DR — только в презентации. Проверяем журнал: journalctl -u k3s -b -n 200 --no-pager. Снимаем nstat -az TcpRetransSegs до и после попытки, сравниваем прирост; при включённом conntrack смотрим sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax. Эти показатели проверяют сетевую гипотезу, но не доказывают доступность S3. Улика здесь в конфигурации: доступ к бакету задан через etcd-s3-config-secret. Во время restore API недоступен и выдать Secret не может. До скачивания дело не дошло. Выход: заранее вынести S3 credentials и server token соответствующего снепшота в защищённый аварийный комплект вне кластера. Остановить K3s на всех server-узлах. Восстанавливать один узел, передав S3-параметры яв...
03:17. Учебный постмортем: потерян quorum etcd, начинаем восстановление K3s. Последняя выгрузка в S3 зелёная, API недоступен, restore с прежним S3 Secret не работает. Первый диагноз — отвалился маршрут до бакета. Покупка объектного хранилища уже состоялась. Покупка работоспособного DR — только в презентации. Проверяем журнал: journalctl -u k3s -b -n 200 --no-pager. Снимаем nstat -az TcpRetransSegs до и после попытки, сравниваем прирост; при включённом conntrack смотрим sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax. Эти показатели проверяют сетевую гипотезу, но не доказывают доступность S3. Улика здесь в конфигурации: доступ к бакету задан через etcd-s3-config-secret. Во время restore API недоступен и выдать Secret не может. До скачивания дело не дошло. Выход: заранее вынести S3 credentials и server token соответствующего снепшота в защищённый аварийный комплект вне кластера. Остановить K3s на всех server-узлах. Восстанавливать один узел, передав S3-параметры явно; для S3 в --cluster-reset-restore-path указывается имя файла. Затем запустить его без --cluster-reset, остальные etcd-узлы присоединять после сохранения и удаления их старых БД. Порядок и ограничения: документация K3s. Приёмка — восстановление на чистом хосте при выключенном исходном control plane, с проверкой API и состояния ресурсов. Фиксируем фактическое время восстановления. Зелёная выгрузка подтверждает доставку объекта, а эксплуатацию оплачивают за возвращённый сервис. https://ftops.space
Инцидент, 03:17. WireGuard over wstunnel поднялся, latest handshake обновлялся, но SSH зависал, а HTTP пропускал только короткие ответы. Дежурный объявил, что DPI научился распознавать WebSocket. Поверхностный мониторинг видел живой TLS-сеанс и растущий packet loss. Ядро сообщало другое: крупные внутренние пакеты исчезали после инкапсуляции, retransmits внешнего TCP росли, а один потерянный сегмент блокировал весь поток. Проверка: wg show all latest-handshakes transfer; ss -tin; nstat -az \| egrep 'TcpRetransSegs\|IpFragFails\|IpOutDiscards'; tcpdump -ni any 'tcp port 443 or udp port 51820'. Диагноз подтвердил бинарный поиск: ping -M do -s 1200 <peer> проходил, больший размер — нет. Уменьшили MTU интерфейса WireGuard с запасом под WireGuard, WebSocket, TLS и внешний IP: ip link set dev wg0 mtu 1280. Для транзитного TCP добавили MSS clamping в nftables, но не стали выдавать его за лечение TCP-over-TCP. Если DPI вынуждает прятать UDP в TCP, закладывайте head-of-line blocking, контролируй...
Инцидент, 03:17. WireGuard over wstunnel поднялся, latest handshake обновлялся, но SSH зависал, а HTTP пропускал только короткие ответы. Дежурный объявил, что DPI научился распознавать WebSocket. Поверхностный мониторинг видел живой TLS-сеанс и растущий packet loss. Ядро сообщало другое: крупные внутренние пакеты исчезали после инкапсуляции, retransmits внешнего TCP росли, а один потерянный сегмент блокировал весь поток. Проверка: wg show all latest-handshakes transfer; ss -tin; nstat -az \| egrep 'TcpRetransSegs\|IpFragFails\|IpOutDiscards'; tcpdump -ni any 'tcp port 443 or udp port 51820'. Диагноз подтвердил бинарный поиск: ping -M do -s 1200 <peer> проходил, больший размер — нет. Уменьшили MTU интерфейса WireGuard с запасом под WireGuard, WebSocket, TLS и внешний IP: ip link set dev wg0 mtu 1280. Для транзитного TCP добавили MSS clamping в nftables, но не стали выдавать его за лечение TCP-over-TCP. Если DPI вынуждает прятать UDP в TCP, закладывайте head-of-line blocking, контролируйте TcpRetransSegs и измеряйте PMTU с каждого маршрута. Иначе получите зелёный handshake и чёрную дыру для данных. Разборы эксплуатации без облачного фольклора: https://ftops.space
В 03:00 мониторинг показывал systemd unit active, PID существовал, рестартов не было. Поверхностный диагноз: демон исправен. Фактически после обновления пакета drop-in сначала очистил ExecStart\=, затем восстановил устаревшую команду без добавленного в vendor unit защитного параметра. Вскрытие: systemctl cat daemon.service; systemctl show daemon.service -p FragmentPath -p DropInPaths -p ExecStart; systemd-delta. Смотрите не файл из /usr/lib/systemd/system, а итоговую конфигурацию после слияния unit и drop-in из /etc/systemd/system/daemon.service.d/. Исправление хранится в /etc, создаётся через systemctl edit daemon.service и применяется через systemctl daemon-reload && systemctl restart daemon.service. Для списочных директив пустое присваивание сбрасывает прежние значения: сначала ExecStart\=, затем полный новый ExecStart\=. После обновления пакета сравнивайте эффективную команду и проверяйте /proc/$(systemctl show -p MainPID --value daemon.service)/cmdline. Зелёный active — дешёвая де...
В 03:00 мониторинг показывал systemd unit active, PID существовал, рестартов не было. Поверхностный диагноз: демон исправен. Фактически после обновления пакета drop-in сначала очистил ExecStart\=, затем восстановил устаревшую команду без добавленного в vendor unit защитного параметра. Вскрытие: systemctl cat daemon.service; systemctl show daemon.service -p FragmentPath -p DropInPaths -p ExecStart; systemd-delta. Смотрите не файл из /usr/lib/systemd/system, а итоговую конфигурацию после слияния unit и drop-in из /etc/systemd/system/daemon.service.d/. Исправление хранится в /etc, создаётся через systemctl edit daemon.service и применяется через systemctl daemon-reload && systemctl restart daemon.service. Для списочных директив пустое присваивание сбрасывает прежние значения: сначала ExecStart\=, затем полный новый ExecStart\=. После обновления пакета сравнивайте эффективную команду и проверяйте /proc/$(systemctl show -p MainPID --value daemon.service)/cmdline. Зелёный active — дешёвая декорация. Контролировать надо фактический argv процесса, DropInPaths и расхождение с vendor unit. Практика эксплуатации без облачного театра: https://ftops.space
Разбор типового отказа: в 03:00 после сетевой сегментации распределённого K3s межплощадочные запросы уходят в таймаут. Мониторинг обвиняет приложение, WireGuard показывает свежий handshake. Дежурный расширяет разрешения между сегментами. Связь не возвращается, зато изоляция уже ослаблена. На принимающем узле tcpdump -ni wg0 'tcptcpflags & tcp-syn !\= 0' показывает входящие SYN. Проверяем sysctl net.ipv4.conf.all.rpfilter net.ipv4.conf.wg0.rpfilter, затем ip rule show и ip route show table all. В этом сценарии strict reverse-path validation отвергает источник: лучший обратный путь проходит через другой интерфейс. Это поведение описано в документации ядра. Снимаем nstat -az IPReversePathFilter до и после контрольного запроса в том же network namespace. Рост счётчика вместе с захватом SYN и разбором маршрутов подтверждает диагноз. Для policy routing дополнительно проверяем net.ipv4.conf.wg0.srcvalidmark: участвует ли fwmark в обра...
Разбор типового отказа: в 03:00 после сетевой сегментации распределённого K3s межплощадочные запросы уходят в таймаут. Мониторинг обвиняет приложение, WireGuard показывает свежий handshake. Дежурный расширяет разрешения между сегментами. Связь не возвращается, зато изоляция уже ослаблена. На принимающем узле tcpdump -ni wg0 'tcptcpflags & tcp-syn !\= 0' показывает входящие SYN. Проверяем sysctl net.ipv4.conf.all.rpfilter net.ipv4.conf.wg0.rpfilter, затем ip rule show и ip route show table all. В этом сценарии strict reverse-path validation отвергает источник: лучший обратный путь проходит через другой интерфейс. Это поведение описано в документации ядра. Снимаем nstat -az IPReversePathFilter до и после контрольного запроса в том же network namespace. Рост счётчика вместе с захватом SYN и разбором маршрутов подтверждает диагноз. Для policy routing дополнительно проверяем net.ipv4.conf.wg0.srcvalidmark: участвует ли fwmark в обратном поиске. Одна таблица main всей картины не показывает. Исправление: согласовать маршруты и правила; при намеренной асимметрии обоснованно выбрать loose-проверку источника и сохранить явные ограничения доступа. Вернуть узкие разрешения, проверить разрешённые и запрещённые потоки после переключения площадки. Очередной security-оператор не исправит несовместимые настройки ядра. Эксплуатация bare-metal: https://ftops.space.
Сценарий постмортема, 03:00. Узел K3s вернули после первого этапа обслуживания через uncordon, проверка диска ещё идёт. Мониторинг кричит о задержках SSD, приложение тормозит. Дежурный уже готов менять железо. Но в Longhorn оставили allowScheduling\=true: узел снова доступен для размещения реплик, и начавшийся rebuild конкурирует с диагностикой за I/O. Проверяем две независимые ручки: kubectl get node worker-3 -o yaml и kubectl -n longhorn-system get nodes.longhorn.io worker-3 -o yaml. Сопоставляем время uncordon с размещением реплик и началом rebuild. Настройка disable-scheduling-on-cordoned-node по умолчанию запрещает размещение на cordoned-узлах; после uncordon этот барьер исчезает. Механика описана в настройках Longhorn. На хосте смотрим iostat -xz 1 и cat /proc/pressure/io: await, aqu-sz, прирост total и avg10 у some/full. PSI показывает время задержек задач из-за I/O; сам по себе он не док...
Сценарий постмортема, 03:00. Узел K3s вернули после первого этапа обслуживания через uncordon, проверка диска ещё идёт. Мониторинг кричит о задержках SSD, приложение тормозит. Дежурный уже готов менять железо. Но в Longhorn оставили allowScheduling\=true: узел снова доступен для размещения реплик, и начавшийся rebuild конкурирует с диагностикой за I/O. Проверяем две независимые ручки: kubectl get node worker-3 -o yaml и kubectl -n longhorn-system get nodes.longhorn.io worker-3 -o yaml. Сопоставляем время uncordon с размещением реплик и началом rebuild. Настройка disable-scheduling-on-cordoned-node по умолчанию запрещает размещение на cordoned-узлах; после uncordon этот барьер исчезает. Механика описана в настройках Longhorn. На хосте смотрим iostat -xz 1 и cat /proc/pressure/io: await, aqu-sz, прирост total и avg10 у some/full. PSI показывает время задержек задач из-за I/O; сам по себе он не доказывает поломку SSD. Причину устанавливаем по совпадению нагрузки с rebuild и её изменению после завершения восстановления. Покупать IOPS до этого — платить за непрочитанный таймлайн. До начала работ фиксируем allowScheduling\=false в Longhorn Node CR и сохраняем запрет до приёмки диска, даже если вычисления уже разрешены. Это отдельный барьер для новых размещений; завершение эвакуации проверяется отдельно. Возврат storage в работу — отдельный шаг регламента с проверкой состояния томов. Kubernetes не прочитает заявку на обслуживание за вас. Эксплуатация bare metal: https://ftops.space.
03:00. Учебный постмортем: три storage-узла K3s, том с тремя репликами, жёсткое разнесение по узлам. Перед обслуживанием одному узлу выставили allowScheduling\=false и затем выключили его. Алерт сообщает degraded, дежурный подозревает медленный rebuild. На оставшихся дисках свободно. Только третьей реплике негде размещаться по правилам. Проверка: kubectl -n longhorn-system get nodes.longhorn.io -o yaml показывает spec.allowScheduling и состояние дисков. Команда kubectl -n longhorn-system get volumes.longhorn.io -o yaml — spec.numberOfReplicas, status.robustness и status.conditions. Проверяем также переопределения anti-affinity у тома. При запрещённом совместном размещении две доступные машины не дают трёх независимых мест для реплик. Это следует из правил планировщика Longhorn. Ядро проверяем отдельно: cat /proc/pressure/io покажет some/full — долю времени с задержками из-за I/O; iostat -xz 1 — await и aqu-sz блочны...
03:00. Учебный постмортем: три storage-узла K3s, том с тремя репликами, жёсткое разнесение по узлам. Перед обслуживанием одному узлу выставили allowScheduling\=false и затем выключили его. Алерт сообщает degraded, дежурный подозревает медленный rebuild. На оставшихся дисках свободно. Только третьей реплике негде размещаться по правилам. Проверка: kubectl -n longhorn-system get nodes.longhorn.io -o yaml показывает spec.allowScheduling и состояние дисков. Команда kubectl -n longhorn-system get volumes.longhorn.io -o yaml — spec.numberOfReplicas, status.robustness и status.conditions. Проверяем также переопределения anti-affinity у тома. При запрещённом совместном размещении две доступные машины не дают трёх независимых мест для реплик. Это следует из правил планировщика Longhorn. Ядро проверяем отдельно: cat /proc/pressure/io покажет some/full — долю времени с задержками из-за I/O; iostat -xz 1 — await и aqu-sz блочных устройств. Низкие значения совместимы с отсутствием работы для rebuild: планировщик ещё не выделил место. Они сами по себе не доказывают исправность дисков. Крутить очередной sysctl до проверки размещения — инженерный шаманизм. Исправление: до вывода узла подготовить дополнительный storage-узел с подходящими тегами, дисками и ёмкостью, проверить размещение и завершение переноса реплик. Следующий узел обслуживать после восстановления требуемой избыточности. Разрешить две копии на одной машине — изменить модель отказа. Эксплуатация bare metal: https://ftops.space. Свободные байты становятся резервом только там, где планировщик разрешает их использовать.
03:00. Модель аварии: небольшой VPS, rollout, DiskPressure. CPU свободен, мониторинг показывает остаток диска, дежурный обвиняет containerd. Но при imageGCHighThresholdPercent\=85 и evictionHard imagefs.available<15% между стартом уборки и порогом давления практически нет запаса. Загрузка и распаковка нового образа пересекают черту. Порог давления — ещё не гарантия немедленного выселения: kubelet сначала пытается вернуть ресурс. Механика eviction. Снимаем показания: df -h /var/lib/rancher/k3s/agent/containerd и df -i /var/lib/rancher/k3s/agent/containerd. Проверяем байты и свободные inode файловой системы: ядро учитывает их независимо. kubectl describe node NODE покажет DiskPressure и события, sudo journalctl -u k3s --since '30 min ago' — ход уборки; на агенте смотрим k3s-agent. Метрики nodefilesystemavailbytes и nodefilesystemfilesfree сопоставляем с нужной точкой монтирования, а не со средним сво...
03:00. Модель аварии: небольшой VPS, rollout, DiskPressure. CPU свободен, мониторинг показывает остаток диска, дежурный обвиняет containerd. Но при imageGCHighThresholdPercent\=85 и evictionHard imagefs.available<15% между стартом уборки и порогом давления практически нет запаса. Загрузка и распаковка нового образа пересекают черту. Порог давления — ещё не гарантия немедленного выселения: kubelet сначала пытается вернуть ресурс. Механика eviction. Снимаем показания: df -h /var/lib/rancher/k3s/agent/containerd и df -i /var/lib/rancher/k3s/agent/containerd. Проверяем байты и свободные inode файловой системы: ядро учитывает их независимо. kubectl describe node NODE покажет DiskPressure и события, sudo journalctl -u k3s --since '30 min ago' — ход уборки; на агенте смотрим k3s-agent. Метрики nodefilesystemavailbytes и nodefilesystemfilesfree сопоставляем с нужной точкой монтирования, а не со средним свободным местом по серверу. Развязка: сборщик удаляет неиспользуемые образы, а текущие контейнеры ещё держат старую версию. Снижать high/low, например до 70/60, имеет смысл после замера пика rollout: нужны место под загрузку, распаковку и одновременное существование версий. Это пример настройки, не универсальный рецепт. Если удаляемых образов нет, никакой порог не создаст свободные блоки. Правила ImageGC. Профилактика: алерт на остаток байтов, inode и скорость заполнения до порога eviction; проверка фактической конфигурации kubelet; бюджет диска под пик обновления. Kubernetes на дешёвом VPS не отменяет арифметику ёмкости. Автоматизация без запаса превращает экономию в ночное дежурство. https://ftops.space
Инцидент в 03:00: часть pod ходила в сервис, часть получала NXDOMAIN или старый ClusterIP. Дашборд показывал «CoreDNS доступен», readiness был зелёным. Разумеется: процесс жив, а содержимое независимых node-local cache уже разошлось после rollout. Сравниваем ответы на разных нодах, минуя догадки: for ip in 169.254.20.10 10.43.0.10; do dig @$ip service.ns.svc.cluster.local A \+noall \+answer \+comments; done kubectl -n kube-system logs -l k8s-app\=node-local-dns --since\=10m Смотрим corednscachehitstotal, corednscachemissestotal и corednsdnsresponsestotal по rcode, обязательно с разрезом instance/node. Проверяем, не маскирует ли кэш второй отказ в ядре: conntrack -S tcpdump -ni any 'udp port 53 or tcp port 53' nstat -az \| egrep 'Udp(InErrors\|RcvbufErrors)\|IpReasmFails' Если растут insertfailed или drop в conntrack, перезапуск DNS лишь ненадолго очищает декорации. Отдельно проверяем MTU и TCP fallback для крупных ответов. Лечение: ограничить отрицательный TTL, согласовать ro...
Инцидент в 03:00: часть pod ходила в сервис, часть получала NXDOMAIN или старый ClusterIP. Дашборд показывал «CoreDNS доступен», readiness был зелёным. Разумеется: процесс жив, а содержимое независимых node-local cache уже разошлось после rollout. Сравниваем ответы на разных нодах, минуя догадки: for ip in 169.254.20.10 10.43.0.10; do dig @$ip service.ns.svc.cluster.local A \+noall \+answer \+comments; done kubectl -n kube-system logs -l k8s-app\=node-local-dns --since\=10m Смотрим corednscachehitstotal, corednscachemissestotal и corednsdnsresponsestotal по rcode, обязательно с разрезом instance/node. Проверяем, не маскирует ли кэш второй отказ в ядре: conntrack -S tcpdump -ni any 'udp port 53 or tcp port 53' nstat -az \| egrep 'Udp(InErrors\|RcvbufErrors)\|IpReasmFails' Если растут insertfailed или drop в conntrack, перезапуск DNS лишь ненадолго очищает декорации. Отдельно проверяем MTU и TCP fallback для крупных ответов. Лечение: ограничить отрицательный TTL, согласовать rollout DNS и endpoint-изменений, алертить расхождение ответов между нодами и проверять кэш синтетическими запросами. Архитектура эксплуатации без облачного шаманства: https://ftops.space
Мониторинг кричал: NodeLocal DNSCache отвечает быстро, cache hit растёт, CoreDNS не перегружен. Приложения тем временем получали i/o timeout короткими сериями после всплесков параллельных UDP-запросов. В ядре два одинаковых DNS-потока успевали пересечься до подтверждения conntrack-записи. Один ответ становился INVALID и исчезал в netfilter. Проверка: conntrack -S, nstat -az \| grep -E 'Udp\|IpExt', tcpdump -ni any 'udp port 53'; отдельно смотрим insertfailed, drop, earlydrop и nfconntrackcount относительно nfconntrackmax. Подтверждение даёт трассировка: nft monitor trace и conntrack -E -p udp --dport 53. Если запрос виден на локальном DNS, ответ выходит, но сокет приложения его не получает, лечить Deployment CoreDNS бессмысленно. Проверяйте правила NOTRACK для локального DNS, переход NodeLocal DNSCache на TCP к upstream и корректность link-local bind. Локальный кэш не является обходом сетевого стека: он лишь переносит аварию ближе к процессу. Разбор bare-metal отказов без облачно...
Мониторинг кричал: NodeLocal DNSCache отвечает быстро, cache hit растёт, CoreDNS не перегружен. Приложения тем временем получали i/o timeout короткими сериями после всплесков параллельных UDP-запросов. В ядре два одинаковых DNS-потока успевали пересечься до подтверждения conntrack-записи. Один ответ становился INVALID и исчезал в netfilter. Проверка: conntrack -S, nstat -az \| grep -E 'Udp\|IpExt', tcpdump -ni any 'udp port 53'; отдельно смотрим insertfailed, drop, earlydrop и nfconntrackcount относительно nfconntrackmax. Подтверждение даёт трассировка: nft monitor trace и conntrack -E -p udp --dport 53. Если запрос виден на локальном DNS, ответ выходит, но сокет приложения его не получает, лечить Deployment CoreDNS бессмысленно. Проверяйте правила NOTRACK для локального DNS, переход NodeLocal DNSCache на TCP к upstream и корректность link-local bind. Локальный кэш не является обходом сетевого стека: он лишь переносит аварию ближе к процессу. Разбор bare-metal отказов без облачного шаманства: https://ftops.space
Инцидент в 03:00 выглядел как деградация WireGuard/AmneziaWG: handshake обновлялся, ping проходил, HTTP healthcheck был зелёным. Но крупные TLS-ответы зависали. Поверхностный диагноз — потери или перегрузка туннеля. Реальность — PMTU black hole: инкапсуляция съела MTU, ICMP Fragmentation Needed или Packet Too Big отрезал firewall. Проверка без шаманства: tracepath -n <peer> и ping -M do -s 1372 <peer> для IPv4. На обоих концах: tcpdump -ni any 'icmp or icmp6 or (tcptcpflags & tcp-syn !\= 0)'. Затем сверяем ip -s link show wg0, nstat -az \| grep -E 'IpReasmFails\|IpFragFails\|TcpRetransSegs' и /proc/net/snmp. Растущий TcpRetransSegs при живом handshake — не проблема ключей. Исправление на маршрутизаторе: iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu. Для нестандартной обфускации и вложенных туннелей MTU измеряем по реальному underlay, а не копируем магическое 1420. После изменения снова снимаем SYN и проверяем объявленный MSS. Если m...