TCP Resets in Docker
Een aantal maanden geleden is binnen de afdeling waar ik werk, namelijk het PDC, een probleem opgetreden met TCP verbindingen binnen onze Docker containers. Op het issue is een flinke deep-dive gedaan en er zijn wat interessante resultaten uit gekomen. In dit artikel zal uitgelegd worden wat het probleem was, hoe we de oorzaak hebben gevonden, en de oplossing die we hebben gevonden.
Het probleem
Binnen het PDC kwamen we al eerder wat rare verbindingsproblemen vanuit Docker containers tegen. Dit was echter altijd eenmalig en zeer sporadisch. Tot op een ochtend ineens een van onze repositories niet meer gecloned kon worden vanuit onze jenkins instance. Nadat de clone begon, werd er na een aantal seconden plots geen data meer ontvangen van GitLab. Jenkins dacht echter dat er nog data aan zou komen, en hield dus de connectie open totdat na 10 minuten een timeout optrad. Gezien het feit dat het plots consistent fout ging, moest er gezocht worden naar een oplossing.
De zoektocht
Als er plots connectieproblemen optreden, vermoed je al snel dat de volledige verbinding problemen heeft. Het rare aan de situatie was echter dat het alleen bij deze ene repository voorkwam. We wisten dus niet goed hoe we dit probleem verder gingen onderzoeken. We besloten om de jenkins instance en GitLab opnieuw op te starten, om te kijken of dat het probleem zou oplossen. Dit was inderdaad het geval. Dit was natuurlijk geen permanente oplossing, dus gingen we verder op zoek naar een oplossing. Na een tijd begonnen de problemen zich weer voor te doen. Nu waren er meerdere repositories die problemen hadden. Het viel ons op dat de grootte van de repository meespeelde. De allereerste repository die problemen kreeg was de grootste die we hadden, maar niet dusdanig groot (12 MB) dat we dachten dat verkleinen van de repository een oplossing was.
Omdat de problemen zich begonnen te verspreiden en we in de verbinding an sich geen problemen zagen (de jenkins en gitlab instanties konden elkaar zonder problemen vinden in het netwerk en een verbinding opzetten), wilden we het probleem reproduceerbaar te maken buiten jenkins. We besloten een checkout te doen van de repository op de host van de Jenkins docker container. Toen viel ons ineens op dat buiten de containers de git clone nog wel werkte. Binnen de container faalde het echter.
Op dat punt besloten we om het netwerkverkeer te analyseren. Zoals verwacht zagen we buiten de container niks geks gebeuren bij een clone, omdat de checkout hier succesvol was. Toen we echter de clone binnen de container deden, zagen we na een aantal seconden geen data meer binnenkomen. Toen we buiten de container het verkeer analyseerden zagen we het probleem. Er werd een RST packet gestuurd vanuit GitLab.
Waarom dit precies gebeurt is nog helaas nog steeds niet 100% duidelijk. Gezien de werkende oplossing die hieronder beschreven staat kunnen we in ieder geval zeker zijn dat het komt doordat er ongeldige TCP packets ontvangen worden bij GitLab, waardoor deze van slag raakt en de verbinding wilt resetten.
De oplossing
Na flink rondneuzen op het internet kwamen we uit bij een issue wat enigszins wat weghad van het probleem waar we tegenaan liepen*. In dit issue word gesproken over een retransmit door AWS machines waardoor een RST packet werd verstuurd op de verbinding. De oorzaak is mogelijk niet hetzelfde als in ons geval, maar de workaround die genoemd word werkt gelukkig ook voor ons. De workaround is het volgende commando:
iptables -I INPUT -m conntrack –ctstate INVALID -j DROP
Wat dit commando doet is de server vertellen dat hij invalid packets moet negeren, in plaats van een RST terug te sturen. Dit commando voeren we uit op de host van onze GitLab instantie. Nadat we dit gedaan hadden waren onze problemen verdwenen. Hierdoor durven we met enige zekerheid te zeggen dat de client ongeldige TCP packets stuurt naar onze GitLab instantie.
Eindstand
Er zijn nog een aantal losse eindes aan dit probleem helaas.
Zo weten we nog steeds niet precies waarom vanuit onze docker container ongeldige TCP packets gestuurd worden. Ik vermoed zelf dat het te maken heeft met TCP window scaling**.
Ook viel het ons op dat we van binnen onze container geen RST packet ontvingen. De connectie wordt dus op host niveau getermineerd, maar deze reset wordt niet naar binnen de docker container gestuurd. Hierdoor krijgt hij uiteindelijk een timeout.
Als laatste hebben we de problemen ook al een aantal keer op andere plekken teruggezien waar grotere downloads gedaan worden tussen docker containers op 2 verschillende hosts. De workaround heeft in alle gevallen de problemen verholpen, maar het duidt wel op een terugkerend probleem.
Mocht je nog ideeën of vragen hebben over het probleem, laat het ons ook vooral weten.
Luc Klaassen
PDC – Info Support
* https://github.com/docker/libnetwork/issues/1090
** https://www.pitt-pladdy.com/blog/_20091125-185551_0000_Linux_Netfilter_and_Window_Scaling/
https://github.com/docker/distribution/issues/785