У сучасных дата-цэнтрах устаноўлены сотні актыўных прылад, пакрытых рознымі відамі маніторынгаў. Але нават ідэальны інжынер з ідэальным маніторынгам у руках зможа правільна адрэагаваць на сеткавы збой толькі за некалькі хвілін. У дакладзе на канферэнцыі Next Hop 2020 я прадставіў метадалогію дызайну сеткі ДЦ, у якой ёсць унікальная асаблівасць – дата-цэнтр лечыць сябе сам за мілісекунды. Дакладней, інжынер спакойна чыніць праблему, у той час як сэрвісы яе проста не заўважаюць.
Для шматлікіх сеткавых інжынераў сетка дата-цэнтра пачынаецца, вядома, з ToR, са світаку ў стойцы. ToR звычайна мае два тыпы лінкоў. Маленькія ідуць да сервераў, іншыя – іх у N разоў больш – ідуць у бок спайнаў першага ўзроўня, гэта значыць да яго аплінак. Аплінкі звычайна лічацца раўназначнымі, і трафік паміж аплінкамі балансуецца на аснове хеша ад 5-tuple, у які ўваходзяць proto, src_ip, dst_ip, src_port, dst_port. Тут ніякіх сюрпрызаў.
Далей, як выглядае архітэктура плэйнаў? Спайны першага ўзроўня паміж сабой не злучаныя, а злучаюцца пасродкам суперспайнаў. За суперспайны ў нас будзе адказваць літара X, яна практычна як красканнект.
І зразумелая справа, што з іншага боку да ўсіх спайнаў першага ўзроўня падлучаныя торы. Што важна на гэтым малюнку? Калі ў нас ідзе ўзаемадзеянне ўсярэдзіне стойкі, то ўзаемадзеянне, зразумелая справа, ідзе праз ToR. Калі ўзаемадзеянне ідзе ўнутры модуля, то ўзаемадзеянне ідзе праз спайны першага ўзроўню. Калі ўзаемадзеянне міжмодульнае - як тут, ToR 1 і ToR 2, - то ўзаемадзеянне пойдзе праз спайны як першага, так і другога ўзроўню.
Тэарэтычна такая архітэктура лёгка маштабуецца. Калі ў нас ёсць партовая ёмістасць, запас месца ў дата-цэнтры і загадзя пракладзенае валакно, то заўсёды колькасць плэйнаў можна нарасціць, тым самым павялічваючы агульную ёмістасць сістэмы. На паперы зрабіць такое вельмi лёгка. Было б так у жыцці. Але сённяшняе апавяданне не пра гэта.
Я хачу, каб былі зроблены правільныя высновы. У нас унутры дата-цэнтра шмат шляхоў. Яны ўмоўна незалежныя. Адзін шлях усярэдзіне дата-цэнтра магчымы толькі ўсярэдзіне ToR. Унутры модуля ў нас колькасць шляхоў роўна колькасці плэйнаў. Колькасць шляхоў паміж модулямі роўна здабытку ліку плэйнаў на лік суперспайн ў кожным плейне. Каб было больш зразумела, каб адчуць маштаб, я дам лічбы, якія справядлівыя для аднаго з дата-цэнтраў Яндэкса.
Плейны восем, у кожным плейне 32 суперспайны. У выніку атрымліваецца, што ўсярэдзіне модуля восем шляхоў, а пры міжмодульным узаемадзеянні іх ужо 256.
Гэта значыць калі мы распрацоўваем Cookbook, спрабуем навучыцца таму, як будаваць адмоваўстойлівыя дата-цэнтры, якія лечаць сябе самастойна, то плейновая архітэктура - правільны выбар. Яна дазваляе рашыць задачу маштабавання, і тэарэтычна гэта лёгка. Ёсць шмат незалежных шляхоў. Застаецца пытанне: як такая архітэктура перажывае збоі? Бываюць розныя збоі. І мы зараз гэта абмяркуем.
Хай у нас адзін з суперспайнаў "захварэў". Я тут вярнуўся да архітэктуры двух плэйнаў. У якасці прыкладу мы спынімся на іх, таму што тут папросту будзе лягчэй бачыць, што адбываецца, з меншай колькасцю частак, якія рухаюцца. Няхай X11 захварэў. Як гэта паўплывае на сэрвісы, якія жывуць усярэдзіне дата-цэнтраў? Вельмі шмат залежыць ад таго, як збой выглядае на самой справе.
Калі збой добры, ловіцца на ўзроўні аўтаматыкі таго ж BFD, аўтаматыка радасна кладзе праблемныя стыкі і ізалюе праблему, тое ўсё добра. У нас мноства шляхоў, трафік маментальна перамаршрутызуецца на альтэрнатыўныя маршруты, і сэрвісы нічога не заўважаць. Гэта добры сцэнар.
Дрэнны сцэнар - калі ў нас узнікаюць пастаянныя страты, і аўтаматыка праблемы не заўважае. Каб зразумець, як гэта ўплывае на дадатак, нам давядзецца выдаткаваць крыху часу на абмеркаванне таго, як працуе пратакол TCP.
Я спадзяюся, што нікога не шакую гэтую інфармацыяй: TCP – пратакол з пацверджаннем перадачы. Гэта значыць, у найпростым выпадку ў нас адпраўнік адпраўляе два пакеты, і атрымлівае на іх кумулятыўны ack: «Я атрымаў два пакеты».
Пасля гэтага ён адправіць яшчэ два пакеты, і сітуацыя паўторыцца. Я загадзя прашу прабачэння за некаторае спрашчэнне. Такі сцэнар дакладны, калі акно (лік пакетаў у палёце) роўна двум. Канешне, у агульным выпадку гэта неабавязкова так. Але на кантэкст перапасылкі пакетаў памер акна не ўплывае.
Што будзе, калі мы страцім пакет 3? У гэтым выпадку атрымальнік атрымае пакеты 1, 2 і 4. І ён у відавочным выглядзе з дапамогай опцыі SACK паведаміць адпраўніку: "Ты ведаеш, тры дайшло, а сярэдзіна згубілася". Ён кажа: "Ack 2, SACK 4".
Адпраўнік у гэты момант без праблем паўтарае менавіта той пакет, які згубіўся.
Але калі згубіцца апошні пакет у акне, сітуацыя будзе выглядаць зусім інакш.
Атрымальнік атрымлівае першыя тры пакеты і перш за ўсё пачынае чакаць. Дзякуючы некаторым аптымізацыям у TCP-стэка ядра Linux ён будзе чакаць парнага пакета, калі няма відавочнага ўказання ў сцягах, што гэта апошні пакет або нешта падобнае. Ён пачакае, пакуль скончыцца Delayed ACK таймаўт, і пасля гэтага адправіць пацверджанне на першыя тры пакеты. Але зараз ужо адпраўнік будзе чакаць. Ён жа не ведае, згубіўся чацвёрты пакет ці вось-вось дойдзе. А каб не перагружаць сетку, ён будзе спрабаваць дачакацца моманту відавочнага ўказання, што пакет страчаны, ці заканчэння RTO timeout.
Што такое RTO timeout? Гэта максімум ад вылічанага TCP-стэкам RTT і некаторай канстанты. Што гэта за канстанта, мы зараз абмяркуем.
Але важна, што калі нам зноў не шанцуе і чацвёрты пакет зноў губляецца, то RTO падвойваецца. Гэта значыць кожная няўдалая спроба - гэта падваенне таймаўту.
Цяпер паглядзім, чаму роўная гэтая база. Па дэфолце мінімальны RTO роўны 200 мс. Гэта мінімальны RTO для дата-пакетаў. Для SYN-пакетаў ён іншы, 1 секунда. Як можна бачыць, нават першая спроба пераслаць пакеты будзе па часе займаць у 100 разоў больш, чым RTT усярэдзіне дата-цэнтра.
Цяпер вернемся да нашага сцэнара. Што адбываецца ў сервісу? Сэрвіс пачынае губляць пакеты. Няхай сэрвісу спачатку ўмоўна вязе і губляецца нешта ў сярэдзіне акна, тады ён атрымлівае SACK, перасылае пакеты, якія згубіліся.
Але калі нешанцаванне паўтараецца, то ў нас здараецца RTO. Што тут важна? Так, у нас у сетцы вельмі шмат шляхоў. Але TCP-трафік аднаго канкрэтнага TCP-злучэння будзе працягваць ісці праз адзін і той жа біты стэк. Страты пакетаў пры ўмове, што гэты наш чароўны X11 не згасае самастойна, не прыводзяць да таго, што трафік перацякае ва ўчасткі, якія не з'яўляюцца праблемнымі. Мы спрабуем даставіць пакет праз той жа самы біты стэк. Гэта прыводзіць да каскаднай адмовы: дата-цэнтр – гэта мноства якія ўзаемадзейнічаюць прыкладанняў, і частка TCP-злучэнняў усіх гэтых прыкладанняў пачынаюць дэградаваць – таму што суперспайн закранае наогул усе прыкладанні, якія ёсць усярэдзіне ДЦ. Як у прымаўцы: не падкавалі каня - конь закульгаў; конь закульгаў - данясенне не даставілі; данясенне не даставілі - прайгралі вайну. Толькі тут рахунак ідзе на секунды з моманту ўзнікнення праблемы да стадыі дэградацыі, якую пачынаюць адчуваць сэрвісы. А значыць нешта дзесьці могуць недаатрымаць карыстальнікі.
Ёсць два класічныя рашэнні, якія адзін аднаго дапаўняюць. Першае - гэта сэрвісы, якія спрабуюць падкласці саломкі і вырашыць праблему так: «А давайце мы падкруцім што-небудзь у TCP-стэку. А давайце мы зробім таймаўты на ўзроўні прыкладання або доўга якія жывуць TCP-сесіі з унутранымі health checks». Праблема ў тым, што такія рашэнні: а) увогуле не маштабуюцца; б) вельмі дрэнна правяраюцца. Гэта значыць нават калі сэрвіс выпадкова наладзіць TCP-стэк так, каб яму стала лепш, па-першае, гэта ці наўрад будзе дастасавальна для ўсіх прыкладанняў і ўсіх дата-цэнтраў, а па-другое, хутчэй за ўсё, ён не зразумее, што зроблена правільна , а што не. Гэта значыць, яно працуе, але працуе дрэнна і не маштабуецца. І калі ўзнікае сеткавая праблема, хто вінаваты? Канешне, NOC. Што робіць NOC?
Многія сэрвісы лічаць, што ў NOC праца адбываецца прыкладна так. Але калі казаць сапраўды, не толькі.
NOC у класічнай схеме займаецца распрацоўкай мноства маніторынгаў. Гэта як black box-маніторынгі, так і white box. Аб прыкладзе black box-маніторынгу спайнаў
Што вельмі хацелася б атрымаць? У нас жа столькі шляхоў. А праблемы ўзнікаюць роўна таму, што TCP-струмені, якім не шанцуе, працягваюць выкарыстоўваць адзін і той жа маршрут. Трэба нешта, што дазволіць нам выкарыстоўваць мноства маршрутаў у рамках аднаго TCP-злучэнні. Здавалася б, у нас ёсць рашэнне. Ёсць TCP, які так і называецца – multipath TCP, гэта значыць TCP для мноства шляхоў. Праўда, распрацоўваўся ён для зусім іншай задачы – для смартфонаў, якія маюць некалькі сеткавых прылад. Каб максымізаваць перадачу або зрабіць рэжым primary/backup, быў распрацаваны механізм, які празрыста для прыкладання стварае некалькі патокаў (сеансаў) і дазваляе ў выпадку ўзнікнення збою перамыкацца паміж імі. Ці, як я сказаў, максымізаваць паласу.
Але тут ёсць нюанс. Каб зразумець, у чым ён, нам давядзецца паглядзець, як устанаўліваюцца патокі.
Струмені ўсталёўваюцца паслядоўна. Спачатку ўстанаўліваецца першы паток. Потым з выкарыстаннем печыва, якая ўжо ўзгоднена ў рамках гэтага патоку, устанаўліваюцца наступныя патокі. І тут праблема.
Праблема ў тым, што калі першы паток не ўсталюецца, другія і трэція патокі ніколі і не ўзнікнуць. Гэта значыць multipath TCP ніяк не вырашае страту SYN пакета ў першага струменя. І калі SYN губляецца, multipath TCP ператвараецца ў звычайны TCP. А значыць, у асяроддзі дата-цэнтра не дапаможа нам вырашыць праблему страт у фабрыцы і навучыцца выкарыстоўваць мноства шляхоў у выпадку збою.
Што нам можа дапамагчы? Некаторыя з вас ужо здагадаліся з назову, што важным полем у нашым наступным апавяданні стане поле загалоўка IPv6 flow label. І праўда, гэтае поле, якое з'яўляецца ў v6, яго няма ў v4, яно займае 20 біт, і з нагоды яго выкарыстанні доўгі час ішлі спрэчкі. Гэта вельмі цікава – спрэчкі ішлі, нешта фіксавалася ў рамках RFC, а ў Linux-ядры ў той жа час з'явілася рэалізацыя, якая так нідзе і не задакументаваная.
Я прапаную вам разам са мной адправіцца на невялікае расследаванне. Давайце паглядзім, што адбывалася ў ядры Linux за апошнія некалькі гадоў.
2014 год. Інжынер з адной буйной і паважанай кампаніі дадае ў функцыянальнасць ядра Linux залежнасць значэння flow label ад хеша сокета. Што тут спрабавалі паправіць? Гэта злучана з RFC 6438, у якім абмяркоўвалася наступная праблема. Усярэдзіне дата-цэнтра часцяком инкапсулируется IPv4 у пакеты IPv6, таму што сама фабрыка - IPv6, але вонкі неяк трэба аддаць IPv4. Доўгі час былі праблемы са свічкамі, якія не маглі зазірнуць пад два IP-загалоўка, каб дабрацца да TCP ці UDP і знайсці там src_ports, dst_ports. Атрымлівалася, што хэш, калі глядзець на два першыя IP-загалоўка, апыняўся ці ледзь не фіксаваным. Каб гэтага пазбегнуць, каб балансаванне гэтага інкапсуляванага трафіку працавала карэктна, прапанавалі ў значэнне поля flow label дадаць хэш ад 5-tuple інкапсуляванага пакета. Прыкладна тое ж самае было зроблена і для іншых схем інкапсуляцыі, для UDP, для GRE, у апошнім выкарыстоўвалася поле GRE Key. Так ці інакш, тут мэты зразумелыя. І прынамсі, на той момант часу яны былі карысныя.
У 2015 годзе ад гэтага ж паважанага інжынера прыходзіць новы патч. Ён вельмі цікавы. У ім гаворыцца наступнае — мы будзем рандамізаваць хэш у выпадку негатыўнай падзеі маршрутызацыі. Што такое негатыўная падзея маршрутызацыі? Гэта RTO, якое мы з вамі раней абмяркоўвалі, гэта значыць страта хваста акна - падзея, якое сапраўды негатыўнае. Праўда адносна складана здагадацца, што гэта менавіта яно.
2016 год, іншая паважаная кампанія, таксама вялікая. Яна разбірае апошнія мыліцы і робіць так, што хэш, які мы раней зрабілі рандомізірованным, зараз змяняецца на кожны рэтрансміт SYN і пасля кожнага RTO таймаўту. І ў гэтым лісце ў першы і апошні раз гучыць канчатковая мэта - зрабіць так, каб трафік у выпадку ўзнікнення страт або перагрузкі каналаў меў магчымасць мяккай перамаршрутызацыі, выкарыстання мноства шляхоў. Вядома, пасля гэтага была маса публікацый, вы лёгка іх зможаце знайсці.
Хаця не, не зможаце, таму што ніводнай публікацыі на гэтую тэму не было. Але ж мы ведаем!
І калі вы не да канца зразумелі, што ж было зроблена, я вам зараз раскажу.
Што ж было зроблена, якую функцыянальнасць дадалі ў ядро Linux? txhash мяняецца на рандомнае значэнне пасля кожнай падзеі RTO. Гэты той самы негатыўны вынік маршрутызацыі. Хэш залежыць ад гэтага txhash, а flow label залежыць ад skb hash. Тут ёсць некаторыя выкладкі па функцый, на адзін слайд усе дэталі не змясціць. Калі камусьці цікава, можна прайсці па кодзе ядра і праверыць.
Што тут важна? Значэнне поля flow label мяняецца на выпадковы лік пасля кожнага RTO. Як гэта ўплывае на наш нешчаслівы TCP-струмень?
У выпадку ўзнікнення SACK нічога не змянілася, таму што мы спрабуем пераслаць вядомы страчаны пакет. Пакуль усё адносна добра.
Але ў выпадку RTO, пры ўмове, што мы дадалі flow label у хэш-функцыю на ToR, трафік можа пайсці іншым маршрутам. І чым больш плэйнаў, тым больш шанцаў, што ён знойдзе шлях, які не закрануць збоем на канкрэтнай прыладзе.
Застаецца адна праблема - RTO. Іншы маршрут, вядома, знаходзіцца, але вельмі шмат на гэта траціцца часу. 200 мс - гэта шмат. Секунда - гэта наогул дзікасць. Раней я расказваў пра таймаўты, якія настройваюць сэрвісы. Дык вось, секунда - гэта таймаўт, які звычайна наладжвае сэрвіс на ўзроўні прыкладання, і ў гэтым сэрвіс будзе нават адносна правоў. Прытым, што, паўтаруся, сапраўдны RTT усярэдзіне сучаснага дата-цэнтра – у раёне 1 мілісекунды.
Што можна зрабіць з RTO-таймаўтамі? Таймаўт, які адказвае за RTO у выпадку страты пакетаў з дадзенымі, адносна лёгка можна наладзіць з user space: ёсць утыліта IP, і ў адным з яе параметраў ёсць той самы rto_min. Улічваючы, што круціць RTO, безумоўна, трэба не глабальна, а для зададзеных прэфіксаў, такі механізм выглядае суцэль працоўным.
Праўда, з SYN_RTO усё крыху горш. Ён натуральна прыбіты цвікамі. У ядры зафіксавана значэнне - 1 секунда, і ўсё. З user space дацягнуцца туды нельга. Ёсць толькі адзін спосаб.
На дапамогу прыходзіць eBPF. Калі казаць спрошчана, гэта невялікія праграмы на C. Іх можна ўставіць у хуки ў розных месцах выканання стэка ядра і TCP-стэка, з дапамогай якога можна змяняць вельмі вялікую колькасць налад. Наогул, eBPF – гэта доўгатэрміновы трэнд. Замест таго каб пілаваць дзясяткі новых параметраў sysctl і пашыраць утыліту IP, рух ідзе менавіта ў бок eBPF і пашырэнні яго функцыянальнасці. З дапамогай eBPF можна дынамічна змяняць congestion controls і іншыя разнастайныя налады TCP.
Але нам важна, што з дапамогай яго можна круціць значэнні SYN_RTO. Прычым ёсць публічна выкладзены прыклад:
Што мы ведаем? Што плейновая архітэктура дазваляе маштабавацца, яна аказваецца нам надзвычай карыснай, калі мы ўключаем flow label на ToR і атрымліваем магчымасць абцякаць праблемныя ўчасткі. Самы лепшы спосаб знізіць значэння RTO і SYN-RTO - выкарыстоўваць eBPF-праграмы. Застаецца пытанне: а ці бяспечна выкарыстоўваць flow label для балансавання? І тут ёсць нюанс.
Няхай у вас у сетцы ёсць сэрвіс, які жыве ў anycast. Нажаль, у мяне няма часу падрабязна распавядаць, што такое anycast, але гэта размеркаваны сэрвіс, розныя фізічныя серверы якога даступныя па адным і тым жа IP-адрасу. І тут магчымая праблема: падзея RTO можа ўзнікнуць не толькі пры праходжанні трафіку праз фабрыку. Яно можа ўзнікнуць і на ўзроўні буфера ToR: калі здараецца incast-падзея, яна можа ўзнікнуць нават на хасце, калі хост нешта пралівае. Калі адбываецца падзея RTO, і яна мяняе flow label. У гэтым выпадку трафік можа патрапіць на іншы anycast instance. Выкажам здагадку, гэта stateful anycast, ён трымае ў сабе connection state - гэта можа быць L3 Balancer ці яшчэ нейкі сэрвіс. Тады ўзнікае праблема, таму што пасля RTO TCP-злучэнне прылятае на сервер, які пра гэтае TCP-злучэнні нічога не ведае. І калі ў нас няма шэрагу стейтаў паміж anycast-серверамі, то такі трафік будзе скінуты і TCP-злучэнне парвецца.
Што тут можна зрабіць? Унутры вашага кантраляванага асяроддзя, дзе вы ўключаеце балансаванне flow label, неабходна фіксаваць значэнне flow label пры звароце да anycast-сервераў. Самы просты спосаб - зрабіць гэта праз тую ж eBPF-праграму. Але тут вельмі важны момант - што рабіць, калі вы аперуеце не сеткай дата-цэнтра, а з'яўляецеся аператарам сувязі? Гэта і ваша праблема таксама: пачынальна з вызначаных версій Juniper і Arista уключаюць flow label у хэш-функцыі па дэфолце шчыра кажучы, па незразумелай мне чынніку. Гэта можа прыводзіць да таго, што вы будзеце ірваць TCP-злучэнні карыстальнікаў, якія ідуць праз вашу сетку. Таму я настойліва рэкамендую праверыць наладкі вашых маршрутызатараў у гэтым месцы.
Так ці інакш, мне падаецца, што мы гатовы перайсці да эксперыментаў.
Калі мы ўключылі flow label на ToR, падрыхтавалі eBPF агента, які зараз жыве на хастах, мы вырашылі не чакаць наступнага вялікага збою, а правесці кантраляваныя выбухі. Мы ўзялі ToR, у якога чатыры аплінкі, і на адным з іх задаволілі дропы. Намалявалі правіла, сказалі - зараз ты губляеш усе пакеты. Як можна бачыць злева, у нас per-packet monitoring, які асеў да значэння 75%, гэта значыць 25% пакетаў губляюцца. Справа графікі сэрвісаў, якія жывуць за гэтым ToR. Па сутнасці гэта графікі трафіку стыкаў з серверамі ўнутры стойкі. Як можна бачыць, яны аселі нават ніжэй. Чаму яны аселі ніжэй - не на 25%, а ў некаторых выпадках у 3-4 разы? Калі TCP-злучэнні не шанцуе, яно працягвае спрабаваць дагрукацца праз біты стык. Гэта пагаршаецца тыпавымі паводзінамі сэрвісу ўнутры ДЦ – на адзін запыт карыстальніка генеруецца N запытаў да ўнутраных сэрвісаў, і адказ сыдзе да карыстача, альбо калі адкажуць усе крыніцы дадзеных, альбо калі спрацуе таймаўт на ўзроўні прыкладання, які яшчэ павінен быць наладжаны. Гэта значыць, усё вельмі і вельмі дрэнна.
Цяпер той жа самы эксперымент, але з уключаным значэннем flow label. Як мага бачыць, злева наш пакетны маніторынг асеў на тыя ж самыя 25%. Гэта абсалютна карэктна, таму што ён нічога не ведае аб рэтрансмітах, ён адпраўляе пакеты і проста лічыць стаўленне колькасці дастаўленых і страчаных пакетаў.
А справа знаходзіцца графік сервісаў. Вы тут не знойдзеце эфекту ад праблемнага стыку. Трафік за тыя самыя мілісекунды перацёк з праблемнага ўчастка ў тры аплінкі, якія не закранулі праблему. Мы атрымалі сетку, якая лечыць сябе сама.
Гэта мой апошні слайд, час падвесці вынікі. Цяпер, я спадзяюся, вы ведаеце, як будаваць сетку дата-цэнтра, здольную да самалячэння. Вам не трэба будзе хадзіць па архіве ядра Linux і вышукваць тамака адмысловыя патчы, вы ведаеце, што Flow label у дадзеным выпадку вырашае праблему, але падыходзіць да гэтага механізму трэба асцярожна. І я яшчэ раз падкрэсліваю, што калі вы аператар сувязі, вы не павінны выкарыстоўваць flow label у якасці хэш-функцыі, інакш вы будзеце рваць сесіі вашых карыстальнікаў.
У сеткавых інжынераў павінен адбыцца канцэптуальны зрух: сетка пачынаецца не з ToR, не з сеткавай прылады, а з хаста. Досыць яркі прыклад - тое, як мы выкарыстоўваем eBPF і для змены RTO, і для фіксацыі flow label у бок anycast-сэрвісаў.
Механіка flow label, безумоўна, падыходзіць і для іншых ужыванняў усярэдзіне кантраляванага адміністрацыйнага сегмента. Гэта можа быць трафік паміж дата-цэнтрамі, а можна адмысловым спосабам выкарыстоўваць такую механіку і для кіравання выходным трафікам. Але пра гэта я раскажу, спадзяюся, наступным разам. Дзякуй вялікі за ўвагу.
Крыніца: habr.com