Yerel Port Tükenmesi ve Dolan conntrack Tablosu: TIME_WAIT, Bağlantı Yeniden Kullanımı ve Doğru Çekirdek Ayarları
"Cannot assign requested address" ve "nf_conntrack: table full" hatalarının nedeni, işe yaramayan yaygın ayarlar ve sorunu kalıcı çözen sıra.
Yük arttığında bazı servisler hata vermeye başlar ama CPU boştur, bellek yeterlidir, veritabanı rahattır. Loglarda connect: cannot assign requested address ya da istemci tarafında açıklamasız zaman aşımları görünür. Çekirdek kaydında ise nf_conntrack: table full, dropping packet satırı belirir. Bu iki belirti aynı aileden gelir: bağlantı başına tutulan çekirdek kaynakları tükenmiştir. Biri giden bağlantılar için yerel port aralığı, diğeri bağlantı izleme (conntrack) tablosudur. İkisi de sessizce dolar, ikisi de genellikle yanlış ayarla "çözülür".
Yerel port neden biter
Bir TCP bağlantısı dört değerle tanımlanır: kaynak IP, kaynak port, hedef IP, hedef port. Sunucunuz tek bir veritabanına ya da tek bir yukarı akış servisine bağlanıyorsa bu dörtlünün üçü sabittir; değişebilen tek şey kaynak porttur. Linux'ta bu aralık net.ipv4.ip_local_port_range ile belirlenir ve varsayılanı 32768 60999, yani yaklaşık 28 bin porttur.
Bu sayı küçük görünmez, ama asıl maliyet kapanan bağlantılardadır. Bağlantıyı önce kapatan taraf soketi TIME_WAIT durumunda tutar ve Linux'ta bu süre 60 saniyedir, çekirdeğe gömülüdür. Saniyede 500 yeni bağlantı açıp kapatan bir istemci, aynı hedef için 60 saniyede 30 bin portu TIME_WAIT'te bekletir. Aralık bitmiştir, yeni connect() çağrıları EADDRNOTAVAIL döner.
Tipik kaynaklar şunlardır: her istekte yeni HTTP istemcisi oluşturan kod, keepalive kapalı bir HTTP kütüphanesi, bağlantı havuzu olmadan her sorguda veritabanına bağlanan betikler, sağlık denetimini çok sık yapan ve her seferinde bağlantı kapatan araçlar.
Durumu görmek için:
ss -s
ss -tan state time-wait | wc -l
ss -tan state time-wait '( dport = :5432 )' | wc -l
sysctl net.ipv4.ip_local_port_range
Son komut belirli bir hedef porta giden TIME_WAIT soketlerini sayar. Sayı port aralığının büyüklüğüne yaklaşıyorsa sorunu buldunuz.
Çözüm sırası: önce bağlantıyı yeniden kullan
Burada üç yaklaşım vardır ve sıraları önemlidir.
Bağlantı yeniden kullanımı. Gerçek çözüm budur. HTTP istemcisini uygulama ömrü boyunca tek örnek olarak tutun, keepalive'ı açık bırakın, veritabanına havuz üzerinden gidin. Saniyede 500 istek, onlarca kalıcı bağlantı üzerinden akarsa TIME_WAIT sorunu ortadan kalkar; üstelik her istekten TCP ve TLS el sıkışması da düşer, gecikme azalır. Çoğu dilde yaygın hata, istemci nesnesini istek işleyicinin içinde oluşturmaktır. Kod incelemesinde aranacak ilk şey budur.
Aralığı genişletmek ve tcp_tw_reuse. Yeniden kullanım mümkün değilse ya da zaman kazanmanız gerekiyorsa port aralığını genişletebilirsiniz:
sysctl -w net.ipv4.ip_local_port_range="10240 65535"
sysctl -w net.ipv4.tcp_tw_reuse=1
tcp_tw_reuse, giden bağlantılarda TCP zaman damgalarını kullanarak TIME_WAIT'teki bir portun güvenli biçimde yeniden kullanılmasına izin verir. Yalnızca bağlantıyı başlatan taraf için anlamlıdır, gelen bağlantıları etkilemez. Aralığı genişletirken, o portları dinleyen bir servisinizin olmadığından emin olun; aksi halde çekirdek o portu giden bağlantıya verebilir ve servis yeniden başladığında address already in use alır. Gerekirse net.ipv4.ip_local_reserved_ports ile belirli portları ayırın.
Hedefi çoğaltmak. Dörtlüde hedef IP değişirse kaynak port tekrar kullanılabilir. Yukarı akışa birden çok IP üzerinden gitmek ya da istemciye birden çok kaynak IP vermek kapasiteyi katlar. Bu yöntem genellikle yük dengeleyici ve NAT katmanında işe yarar.
Yaygın yanlış ayarlar
net.ipv4.tcp_fin_timeout değerini düşürmek sıkça önerilir ama TIME_WAIT süresini değiştirmez. Bu ayar FIN_WAIT_2 durumunun süresidir. TIME_WAIT Linux'ta sabittir ve sysctl ile kısaltılamaz.
tcp_tw_recycle adlı eski ayar, NAT arkasındaki istemcileri kırdığı için Linux 4.12'de kaldırıldı. Eski bir sertleştirme rehberinde görürseniz uygulamayın; yeni çekirdekte zaten yoktur.
SO_LINGER ile sıfır süre vererek bağlantıyı RST ile kapatmak TIME_WAIT'i atlatır, ama karşı taraf yarım kalmış veriyi kaybedebilir ve hata görür. Bunu performans ayarı olarak kullanmayın.
Bir tuzak daha: uygulama giden bağlantıdan önce belirli bir kaynak IP'ye bind() çağırıyorsa, çekirdek portu hedefi bilmeden ayırmak zorunda kalır ve port, dört değerin tamamına göre değil tek başına benzersiz tutulur. Bu, aralığı çok daha hızlı tüketir. Linux'ta IP_BIND_ADDRESS_NO_PORT soket seçeneği bu ayırmayı connect() anına erteler; birden çok kaynak IP kullanan vekil sunucuların çoğu bu seçeneği destekler.
conntrack tablosu neden dolar
Güvenlik duvarı kuralı, NAT ya da konteyner ağı kullanan her Linux makinesinde netfilter her akışı bir bağlantı izleme tablosunda tutar. Tablonun bir üst sınırı vardır: net.netfilter.nf_conntrack_max. Sınıra ulaşıldığında yeni akışların paketleri düşürülür. Bu, uygulama açısından en kötü belirti türüdür: hata mesajı yoktur, yalnızca SYN paketleri kaybolur ve istemci yeniden dener, saniyeler süren gecikmeler oluşur.
Kontrol etmek için:
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
conntrack -S
dmesg | grep -i conntrack
conntrack -S çıktısındaki drop, early_drop ve insert_failed sayaçları artıyorsa tablo ya dolu ya da yarış koşulu yaşıyordur. conntrack aracı ayrı bir paket olarak gelir (genellikle conntrack ya da conntrack-tools).
Tablonun dolmasının iki tipik nedeni vardır. Birincisi gerçek akış sayısıdır: çok sayıda kısa bağlantı açan bir NAT geçidi ya da ters vekil. İkincisi zaman aşımlarıdır: kurulmuş TCP akışları için nf_conntrack_tcp_timeout_established varsayılan olarak beş gündür. Karşı tarafı düzgün kapatmadan kaybolan istemciler tabloda günlerce yer tutar.
conntrack için doğru ayar
Sınırı artırmak meşrudur, her kayıt birkaç yüz bayt tutar ve modern bir sunucuda yüz binlerce kayıt sorun değildir:
sysctl -w net.netfilter.nf_conntrack_max=524288
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600
Sınırı artırırken karma tablo boyutunu da düşünün. Karma kovası sayısı /sys/module/nf_conntrack/parameters/hashsize dosyasından okunur ve yazılabilir; sınır büyüyüp kova sayısı aynı kalırsa her arama uzun zincirlerde gezinir ve CPU maliyeti artar. Kalıcı olsun diye /etc/sysctl.d/ altına bir dosya yazın; yalnız şuna dikkat edin, nf_conntrack modülü sysctl uygulandıktan sonra yüklenirse ayar sessizce uygulanmaz. Modülü önyüklemede yükletin ya da ayarı modül yüklendikten sonra tekrar uygulayın.
Kurulmuş bağlantı zaman aşımını bir saate indirmek çoğu ortamda güvenlidir. Bir saatten uzun süre hiç paket göndermeyen uzun ömürlü bağlantılarınız varsa (bazı veritabanı ve mesaj kuyruğu istemcileri), uygulama tarafında TCP keepalive'ı açın; böylece akış canlı kalır ve tablodan düşmez.
Çok yüksek hacimli ve durumsuz trafik için izlemeyi tamamen kapatabilirsiniz. raw tablosunda NOTRACK (ya da CT --notrack) hedefiyle belirli bir porta gelen trafik conntrack'e hiç girmez. Bedeli şudur: bu trafik için durumlu güvenlik duvarı kuralları (ESTABLISHED,RELATED eşleşmesi) ve NAT çalışmaz. Dönüş paketleri için de açık kural yazmanız gerekir. Bunu ancak trafiği iyi tanıdığınız bir yerde, örneğin yalnızca iç ağdan gelen bir önbellek sunucusunda yapın.
Ne izlenmeli
Bu iki kaynak dolana kadar görünmez, o yüzden oran olarak izlenmelidir. Prometheus node_exporter kullanıyorsanız node_nf_conntrack_entries / node_nf_conntrack_entries_limit oranı ve node_sockstat_TCP_tw değeri hazır gelir. Conntrack oranı yüzde 70'i geçince uyarı vermek, tablonun dolmasından önce müdahale zamanı bırakır. TIME_WAIT sayısının ani artışı ise neredeyse her zaman bir dağıtımla gelen bağlantı yeniden kullanımı hatasına işaret eder.
Ne zaman ayar yapmamalı
Port aralığını genişletmek ve conntrack sınırını artırmak semptomu erteler. Sorun, her istekte yeni bağlantı açan bir koddan geliyorsa sysctl ile kazandığınız şey birkaç kat fazla trafik payıdır; trafik büyümeye devam ettiğinde aynı yere dönersiniz ve bu kez sınır daha yüksek olduğu için bellek ve CPU maliyeti de eklenir. Önce ss ile hangi hedefe kaç TIME_WAIT olduğunu görün, o hedefe giden kodu bulun, bağlantıyı yeniden kullanır hale getirin. Çekirdek ayarı, ölçülmüş bir ihtiyaç için ikinci adımdır.