2024-12-20 почивай в мир, chervarium

December 20th, 2024 by Vasil Kolev

Снощи е починал Атанас Бъчваров (който повечето хора познават като chervarium).

Още нямам информация за кога ще бъде погребението и какво са плановете там, ако е ок, ще пиша тук.

И бих искал да напиша още нещо по темата, но ще изчака…

Update: погребението е в неделя, 22.12, от 13:30 в Ямбол (на гробищата)

2024-11-03 лекция “Екипи” на OpenFest 2024

November 3rd, 2024 by Vasil Kolev


Преди известно време NSA разсекретиха лекция, която (по това време капитан, после адмирал) Grace Hopper изнесла при тях през 1982 година. В тази лекция има много интересни неща и я препоръчвам на всички да я изгледат, но една част от нея ме побутна да направя моята.
(всички, които гледат лекцията почват да искат да имат баба като нея)

(ако някой не знае коя е Grace Hopper, силно препоръчвам да се се зарови)

Явно се сблъска в главата ми с много други неща, свързани с “мениджмънт”, “човешки ресурси”, “модерно робство” (за мен термина “човешки ресурси” е наследство от времената, когато робството е било нормално. Честно казано, даже очаквах да намеря някой римски термин като “Homo subjecto” (човекопредметност), на който да е наследство).

Та поне аз имам нужда да разкажа какво знам за правенето на екипи, поддръжката им, работата с тях. Вижда ми се много по-важно от всякаква техническа тема, на която бих могъл да говоря в момента, и съм зарязал поне три такива полунаписани лекции, за да направя тази.

Нещата толкова се бяха събрали в главата ми, че една вечер, след като спешно трябваше да стана да оправя нещо, вместо пак да си легна отделих един час и написах подробен план на лекцията. Това, което ще разказвам, не се отклонява особено от него.

Тази лекция събира опита ми от последните двайсетина години с някакво количество книги, изчетени по темата. Опитът ми е от различни фирми (последната е StorPool), и доброволчески организации като OpenFest, FOSDEM, IT турнето и разни други.

Този въпрос го има и накрая.

Въпреки, че много повече предпочитам да се занимавам със сървъри, мрежи и машини, никога не съм успявал да се измъкна от човешкия елемент. И колкото да ми е уморителен, с годините съм приел, че е сравнимо важен с всичко останало, което правят.

Едно от нещата, които ми е липсваше в университета (и което се стараех по някакви начини да вкарвам в преподаването си там) бяха екипни проекти. Възможно е това, че аз съм бил три години втори курс да ме е спряло да открия, че всъщност се преподава екипност, но и на разглеждане на конспекта такива неща няма – всички изпити, контролни и други подобни забавления са индивидуални. Единственото отборно нещо, в което съм участвал беше едно състезание по програмиране.

HR отделите това им е част от работата, но в повечето случаи не им се получава. Всички екипи, които съм виждал са супер подозрително настроени към всякакви подобни инициативи.

Също така, много ще се радвам, ако след лекцията хората ми кажат, че това са неща, дето се знаят и съм и загубил времето.

Малко spoiler alert за какво ще говоря – за как се събира екип, за какви хора бихте искали и каква култура, как може да работи, и как се поддържа.

Този слайд го има основно, за да си изяснят хората дали ще им е интересна лекцията, и да ходят да пият/пушат отвън, ако не :)

Малко източници, на които съм се базирал.

Астронавтите и изобщо космическите пътувания са много добър пример за начин на работа на малки и големи екипи с критични системи. Информацията е публична, намира се лесно, и чак е трудно да избягаш от нея.

Военните организации са друг интересен пример. Теорията им съществуват от много отдавна, и при тях доста от лошите идеи са отмрели (понякога съвсем буквално). Доста от техните идеи са неприложими извън тяхната сфера (например принципите им за team building са незаконни), но има и много полезни неща.

За системите от хора (т.е. организации) има също написано много, ще се появява на разни места из лекцията.

И накрая, художествената литература. Правил съм цяла отделна лекция за това колко е полезна, тук само ще отбележа, че популярната такава показва какво хората очакват и би им се харесало в реалния свят. Описанията на добрите лидери, с които сме свикнали от малки идват основно от там – те ни дават очакванията към лидерие и границите, до които могат да се нарушат.

Да започнем с това как се събират правилните хора.

Виждал съм и познавам хора, които са или водещи екип, или важна част от него, но отказват да се занимават с взимането на хора в него. Считат го за скучна, второстепенна или направо ненужна част. Има и фирми, в които самите екипи почти нямат право на глас кой влиза в тях.
И двете не са правилни, съответно силно ги препоръчвам на всичките ни конкуренти.

Хората, с които работите определят дали ще си свършвате работата, дали ще ви харесва, или сутрин ще трябва да си разреждате кафето с нещо по-спиртно, за да може да понесете деня.
(Личен съвет – ако откриете, че започвате да си сипвате алкохол от сутринта, за да може да си вършите работата, е време да напуснете. Както и ако в понеделник сутрин ви става лошо при мисълта, че трябва да ходите на работа)

На повечето места, на които съм работил съм имал участие в процеса на наемане, а в последните няколко и изрично съм държал да участвам. Вече не ми се налага да пия сутрин :)

Ще базирам тази част основно на какво правим в StorPool, но доста неща са подобни и при търсенето на доброволци, и твърдя, че са приложими за повечето IT специалности.

В StorPool ни отне време да изгладим процеса (и сигурно има неща, които допълнително можем да подобрим). Това не стана без да направим едно количество грешки.

Първия път пуснахме обява по нормалния начин, видяхме 20-30 CV-та, от тях избрахме 10 по някакъв начин, и ги поканихме на интервюта.
След като приключихме с тях (отне около седмица), седнахме, поговорихме и си казахме “NEVER AGAIN”. Обърнахме процеса, като сега първо пращаме на кандидата задачи, и след и ако реши някаква част от тях, каним на интервю.

CV-тата на хората дават идея за какво да си говориш с тях. От там нататък стойността им не е особено голяма – хората лъжат, доста често ненарочно, и разликата между двама човека със същото CV може да е от земята до небето. А интервю с грешния вид човек е изпитание и уморително упражнение, което спокойно може да се пропусне.

Единствения човек, който се радваше на този тип интервюта беше жена ми, която много се забавляваше на историите от тях. Или, както е казал народът, на чужд гръб и сто интервюта са малко.

Та, един ден седнах и насъбрах задачи. Спомних си някакво количество проблеми, с които съм се борил и които са ми останали в главата, малкото код или други неща около тях, и сглобих нещо, което лесно да даваме на хората да решават.
Това, че съм ги запомнил има и друго предимство – че вероятно са интересни. Ако карам някого да ми отдели някакво време, е добре да не е с нещо скучно :)

Важно е задачите да са реални. Това има допълнителния момент, че кандидатът може да разпознае неща, с които се е борил и да разбере, че не е сам :)

И разбира се, трябва някакво разнообразие, не може да дадем същата задача 5 пъти с различни параметри. Най-малко никъде работата няма такова нещо.

Задачите са прекрасен филтър.
Има хора, които изобщо отказват да обърнат внимание, защото смятат, че са над такива неща. Има хора, които не намират подобни неща за интересни. Има такива, които не могат да решат нищо от тях. И наскоро дори имаше случай “не смятам, че някога в реалната работа ще ми се наложи да правят A и B (неща от задачите).

Последният не е прав, а всичките други групи са хора, които не бих искал в екипа си.

Кратко отклонение за типа хора, с които искам да работя, понеже идеята със задачите е много дискриминираща спрямо останалите.

Смятам, че човек трябва да се занимава с неща, които му харесват. Може би не всички детайли, но основната част от работата, генералната цел и каквото се постига, трябва да са нещо, което ти харесва. Животът е твърде кратък, за да се занимаваш с глупости.

Това, освен че прави целия процес на работа по-приятен, води и до това, че се работи с по-кадърни хора. Колкото повече ти харесва нещо, толкова повече влагаш усилия и се занимаваш с него и ставаш по-добър (всички сме чували за 10те хиляди часа).

И разбира се, ако хората им харесва, няма да искат да си тръгнат. Достатъчно често попадам на случаи, в които хората искат да напуснат и да идат някъде дори на по-ниска заплата. От друга страна пък, никога няма да забравя как се опитвахме да подмамим при нас Боби Станимиров. Бяхме го завели на обяд в едно много приятно заведение, говорехме си, обяснявахме колко интересни неща правим, и той в един момент каза “Да, вашето е интересно. Ама при нас лекуваме рак.”
(той тогава беше във viewray, те правят комбинация от ядрено-магнитен резонанс и гама-нож, т.е. докторите могат да гледат докато режат с гама ножа)

Да си харесваш работата всъщност не е толкова рядко, а хората харесват какви ли не работи. Може би най-простия пример, който имам е как хора ходят, учат във ФМИ и вместо да след това да работят за много пари в IT-то, стават учители – професия, която (да цитирам един мой любим сериал) комбинира стреса на неврохируг със заплащането на продавач в магазин.
Шапка им свалям на тези хора, аз не бих могъл.

(или всички доктори)

Слайдът, разбира се, може да се чете като “отклонение е да си харесвате работата”. И такива отклонения трябват :)

Благодаря, че ми намериха този графит, който беше написан едно време, мисля на ул. “Париж”, и който е донякъде крайната фаза на харесването на работата и мотивираността. Поне според мен, човек трябва да може сутрин да си отговаря на този въпрос или с “да”, или с “ще направя нещо, че отговорът да е ‘да'”.
(предполагам не е полезно да мислиш за умиране всяка сутрин, та може да бъде разредено кога се преосмисля, но принципът си остава :) )

Чудил съм се понякога колко е наемът на един билборд на Орлов мост, за да сложим този надпис да постои там известно време.

Човек и добре да живее, трябва да си говори с други хора. Процес на наемане без интервюта няма как да се направи.

Виждал съм различни варианти на интервюта. Може би най-любимото ми беше в google, където им бях на гости за един ден, сложиха ме в една стая и различни екипни lead-ове ме разпитваха в продължение на около 5-6 часа.
(може би заради това Дъблин ми стана другия любим град след София)

Това идва тежко за доста хора, и е отделяне на много време с неясно каква полезност. При нас базовото техническо интервю е едно, с двама-трима човека от наша страна. Имаме няколко области, в които задаваме въпроси, като цяло съвсем стандартно.
Различното е, че цялото интервю започва с обясняване кои сме ние, какво правим и каква е работата. През целия процес на интервюто идеята е не само ние да научим нещо (за кандидата, или като цяло), но това да се случи и за отсрещната страна. Всяко интервю трябва да е полезно и за двете страни, и да помогне за отговора дали наистина искат да работят заедно.

После срещаме човека с целия екип, с който ще работи. Всеки има право да знае в какво се забърква, а и помага да видиш, че ще работиш в компания на сродни души (или ужасни изроди и по-добре да бягаш надалеч).
След срещата на целия екип въпросът, който си задаваме е “Кажете нещо лошо за човека”. Ще го спомена по-долу, но да почна от тук, решението за нов човек се взема с консенсус – всички от екипа участват във взимането на решението и трябва да са съгласи за новия член. Тези интервюта са едно от много малкото неща, за които занимаваме хора, излезли в отпуска.

И има едно “човешко” интервю (все ми се карат, като го наричам така, та приетия термин е “не-техническо”). Целта му е да хване проблеми с човека, които ние не сме видели.

Всички преди изпитателния срок е филтър, който да намали хората, провалящи се в него. А самият изпитателен срок е правилният показател дали може да се работи с някого.

Един от любимите ми процеси за интервюиране беше в Automattic – там имаше едно-две интервюта, и после директно едномесечен договор, в рамките на който ти дават достъп, правиш задачи с разни хора за разни цели (истински задачи, има шанс едно от нещата дето правих да са го ползвали прилично време) и двете страни могат да видят как изглежда работата и вписването на новия човек. Завиждам на фирмите с достатъчно ресурс, които могат да го правят.

Учудващо, разликите в доброволческите организации са основно в реда на случващото се. Много често доброволците директно почват с един изпитателен срок, преди да навлязат повече в организацията :)

Практически обаче, има горе-долу същите стъпки и действия, за да стане някой част от доброволческа организация – да свърши нещо, да се срещене с някакви основни хора, и после с всички, и да работи с тях известно време. На практика много от доброволците минават като първа стъпка нещото със “задачите”, понеже идват един ден и някой им казва “свърши това” :)

Това е като продължение на частта с типа хора, който искаш.

За да работи един екип, трябва да съществува в добра среда. Среда, която насърчава правилните неща, не толерира вредните, и дава възможност на хората да се развиват и да работят.

Културата е нещо, което съществува в екипа, но трябва да е подкрепено на всяко едно ниво.

И понеже това е някакво вятърничаво като обяснение, ето конкретни примери.

Един пример, който ще дам, е FOSDEM и open-source решенията. Видеото на FOSDEM винаги е било с open-source решения, доколкото е възможно, и когато е имало възможност да се направи още нещо отворено, като например смяната на един capture device с друг, това винаги е било подкрепяно от цялата организация, въпреки отражението върху бюджета на събитието. Това винаги е важало и за всичко, което се ползва – ако има възможност, се прави с open-source софтуер, ако ще и самата организация да си го напише, без възражения от типа “не можем ли да ползваме онова готовото и да не се занимаваме”.

Друг пример – за нещо, което не осъзнавах, че е добре да се формализира – е т.нар. blameless postmortem. Идеята е следната – ако се случи грешка, която да доведе до сериозни последствия (в нашия случай – downtime), се пише postmortem документ, който описва как се е случило събитието, какво е довело до него, и какво може да се направи, за да не се случва пак.
Blameless частта е, че вместо да кажем “Пешо пипна там и то се счупи, та ще му хвърлим един бой”, се прави по-трудното – изяснява се процеса, грешката и как да не се стигне пак до там. Така се правят по-малко грешки, и не се стига до там, че хората не смеят да правят нищо, за да не пострадат.
(и това е обратно на културата на твърде много места, които съм виждал)

Свързано с това по-горе, културата трябва да приема, че грешките са нещо възможно, и че са някаква част от learning процеса. Щом това успяват да го приемат в области като медицината и воденето на война (USMC имат изрично записано, че “there must be no ‘zero defects’ mentality”), където заради грешки умират хора, не мисля, че и в IT-то не може да се приложи.

Също така, одобено доброволческите организации трябва и осигуряват възможности за развитие, да можеш да смениш екипа, да видиш нещо от друга гледна точка, и да можеш да разнообразиш. [да се допише]

Това си мисля, че е познато на всички. Ще се връщам към него, но просто ще кажа, че всеки в един екип трябва да може да е Бай Иван, без значение позицията.

Съвсем дискриминационно ще кажа, че има правилни и неправилни хора, с които да работиш.

Правилните хора не ги е страх от работата. Както отбелязах, тя трябва да им харесва, да ги забавлява, да не им е лошо сутрин като трябва да ходят на работа и т.н.. И когато има да се свърши нещо, да могат да хванат лопатата.

Има много изписано и изговорено на темата как се мотивират хора. Преди години един колега беше попаднал на статия, която възприехме, и която казваше, че ако хората не се мотивират сами, то насила няма как да се получи добър резултат.
(“насила админ не става”)

Аз не мисля, че мога да мотивирам някой, който не би могъл да се мотивира сам (и определено не искам). Мога да покажа работата, да обясня какво е интересното, какви неща никъде другаде не правят (или в случая на доброволчеството – какво постигаме), но всичко това е безсмислено, ако човекът не е достатъчно self-driven, за да може сам да се мотивира и да се движи напред.

И да натъртя още веднъж идеята, ще дам пример за едно обяснение към студенти, как по партита като се забиват с някой от подходящия пол, преди секс да не търсят “consent”, а “entusiasm”.

При доброволците има важен момент, който следва от предното. За да си харесваш работата и да си достатъчно желаещ да се занимаваш с нея (особено за без пари), трябва да го припознаеш като нещо свое. И това е едно от най-важните неща в подобни организации – да събере хора с обща цел, които приемат целите на организацията за свои.

Това води до друго важно нещо, възможността за изграждане на автономност в хората. Ако всички знаят целта и насоката, ако са запознати с цялостната работа на организацията, то те могат да вършат повечето си работа без да занимават някой друг, и да направят организацията много по-ефективна.

Така и обратната връзка не се приема като нещо лошо, а като нещо, което помага да се движите напред. Екипът не трябва да има страх да каже на някой “моткаш се” или “това е грешно”, какото и всеки в екипа трябва да е ок с подобен feedback. Ако сме се подбрали правилно, нищо от това не е с цел да тормози другия, а с цел да си свършим работата.

Та може би поговоркара за сговорната дружина може да е “дружина с обща осъзната цел планина повдига”, понеже съм го виждал в действие :)

Крис Хадфийлд, който първо ми беше познат от една глава на “How to” (на темата “как да приземим космическата совалка на различни странни места”) има една глава в книгата си “An astornaut’s guide to life on Earth”, в която обяснява какво е правилното нещо, към което да се стремиш да си в един екип. Отговорът му е, че винаги поне трябва да си на нула, т.е. да поддържаш нивото на работа на екипа.

Това е нещо, което е трудно за разбиране. Много хора смятат, че могат от първия ден да направят промени, които да подобрят екипа, работата, да им помогнат да се изтъкнат и т.н. После бързо се обезкуражават след сблъсъка с реалността – понеже за всяко нещо в една организация си има причини, и те трябва да бъдат адресирани, преди да се направи някаква (може би голяма) промяна в начина на работа.
(това не променя факта, че е важно да се проявява инициатива и да се подобрява работата на всички)

Това е и истинската дефиниция на “екипен” човек за мен. Като пример как не се прави, ще дам скорошната случка, в която maintainer-а на Rust за linux kernel-а се отказа да се занимава, защото му било омръзнало да го занимават с “нетехнически” проблеми. Което показва колко заблуден е бил, щом е очаквал, че проблемите му ще са само такива – цялото това нещо, което се опитва да направи е като да накара ламята Спаска да танцува балет. И колкото да обяснява колко гениален е тоя балет, има много други подробности, преди това да има шанс да се случи.

Изобщо по темата за убеждаването на хората и прокарването на идеи, препоръчвам лекцията на Грейс Хопър, която споменах в началото. Тя обяснява за добри начини за постигане на промени в подобни обстоятелства (тя все пак е работела за department of defense и други подобни гигантски структури).

Иначе, имало е доста странни въпроси по интервюта, които са ми помогнали да изчистя моето очакване за какво е екипност и какво всъщност очакват хората. Най-странния въпрос, който са ми задавали беше дали сме повече adversarial или cooperative – което за мен беше невероятно, понеже никога не съм бил/очаквал някой такъв екип да е adversarial и хората да се състезават, вместо да си помагат. Предполагам, някъде има приложение, но поне за мен не е в рамките на една организация, която се опитва да върши работа.

Понеже до тук говорих за общите неща в хората, извън тях, останалото няма значение. В разните екипи, в които съм бил и са били ефективни е имало хора от всякаквите възможни части на обществото, по всякакви признаци, и това определено помага да се избегне groupthink-а (или поне да се намали). Дори в текущия ми екип, който на практика е само системни администратори, има следните под-видове хора: бивши военни, тонрежисьори, готвачи, заварчици и т.н.. Ако попаднем на необитаем остров, няма да ни е скучно, няма да умрем от глад и сигурно ще намерим начин да се измъкнем…

А последния link – ако не сте го чели, отделете му 20 минути. Той обяснява колко бързо свикват хората с глупостите в една организация. Заради него ние правим едно post-onboarding интервю с всеки нов човек, горе-долу на втория месец, в което сядаме и започваме да задаваме въпроси от типа “сега, докато още не си свикнал с нашите глупости, кажи какво не ти се вижда вярно”. Силно препоръчвам и като четиво и като практика.
(в началото имаше доста моменти в тези разговори, в които си казвах “а, ама и аз имах проблем с това, и то все още не е правилно”)

И така, имаме правилните хора, как да работим с тях? Има някои важни неща, особено в подобен екип, които трябва да бъдат взимани предвид, за да може да се работи гладко и най-вече – правилно.

Едно от нещата, които се откриват по трудния начин е какъв е работещия начин за взимане на решения в един екип и каква е ролята на водещия екипа в тях.

Има една идея (срещана и в много вицове), че решенията идват отгоре и толкова. Това е грешно по две причини – губи се полезния опит на членовете на екипа (които сме ги избирали да са умни хора), и защото така има приличен шанс екипът да няма желание да ги изпълнява – понеже не може да ги разбере, не мога да види, че водят до нещо смислено и може би просто от инат.

Това, което работи най-добре е да се изговори даден проблем и да се стигне до консенсус. За някои неща това е единственото правилно решение (например наемането на нови хора в екипа), въпреки времето, което може да отнеме. Понякога има подобен вариант с rough consensus (който го има разписан в IETF документите), който казва, че в общи линии сме съгласни с някакви резервации, но да действаме е по-добрия вариант, отколкото да продължим да го дъвчем.

И ролята на водещия екипа в такива дискусии (освен нормалното участие) е да сумаризира и да отпушва дискусията, когато забие в някоя ненужна посока. Формално накрая може да произнесе решението и да носи отговорност за него, но то на практика трябва да идва от екипа.

Понякога консенсус не се получава – опциите са много, мненията са различни, има нужда да си подредим по предпочитаност някакви опции. В такива случаи може да се прибегне до някакво гласуване, като обаче е важно да сме наясно с изборите, и как точно избираме. Темата за системите за гласуване е много обширна и няма да се спирам на нея, но препоръчвам на всички да се запознаят с модифицирания Condorcet метод, който се ползва в Debian проекта.

А в някои случаи (по-често срещани в доброволческите организации), под-група казва “това ние можем да го случим и поемаме отговорност”, и това е решението. Това пак помага на организацията да се движи напред и да си върши работата, вместо само да отлага и да не прави нищо.

За да може да се случи последното, трябва правилната култура, за която ще говоря надолу.

Между другото, учудващо е колко голяма част от тези два слайда се среща по книгите за военни организации и колко важно е да се изслушат/събере информация от всички (от които е възможно във времевите ограничения). Повечето очаквания от такива организации е, че всичко се спуска отгоре, но практиката (и текущата литература по въпроса) говори друго.

Нещо важно, което се пропуска (и което продължава да ми е проблем при мен самия) е, че понякога има връщане към стар проблем, и опит да разберем защо сме взели дадено решение и дали не трябва сега да вземем различно. Съответно, е добре да има някъде записани старите доводи.

Ако нямате стенографиращ екип, и не сте водили дискусията по mail, то на практика се разчита само на паметта на хората. Паметта на хората е нещо ужасно (който не го вярва, да прочете “Невидимата горила”). За това всяка организация би трябвало има писмена институционна памет (не само хора, дето помнят някакви неща), за да може да се обръща към нея в такива моменти.

Би трябвало всички тук да са имали момента, в който се ровят в стар код, чудят се защо и кой го е написал, и откриват, че са те.

Документирането никак не е лесно, но се изплаща многократно.

И тук някъде идва разговорът за воденето на екипа.

По принцип екипите си имат lead в някакъв вид. Дали се води директор, мениджър или какъвто-там-е-термина, в организацията ролята е да бъде интерфейс между екипа и останалата част от организацията.

Това има различни варианти да се направи. Чувал съм, че често е някой, дето да назначават и да размахва камшика без идея какво правят хората “под” него.

В това има няколко проблема, като тръгнем от това, че тези хора не трябва да са “под”, а “при” него. За мен един lead е част от екипа, може да е смислена част от него и може да е “бай Иван”.

Това е един списък, дето написах още в началото, докато си правех плана на лекцията. За мен е сравнително очевиден, но сам по себе си не е особено интересен. Може да се намери в някакъв вид в много книги, включително в голяма част от всичката военна фантастика :)

За мен по-важно е в екипа всяко едно от тези неща да може да го прави всеки, и да има увереността, че може да го прави – може би не от първия ден, не максимално ефективно, но в един момент всеки трябва да може да поема отговорност, и всички да могат да застанат зад него, когато се наложи.

И ако се върнем към културата, важни за един екип са възможността, способността и желанието да се поеме отговорност. Без тях нищо не може да се случи.

Не е задължително да има един lead. Виждал съм (и съм участвал в такива) екипи, където има трима lead-ове, с различни профили. Те донякъде могат да се покриват, но са специализирани достатъчно, и са достатъчно автономни, че да могат да движат нещата без постоянно да трябва да се разбират за някакви неща. Като един пример, това беше OpenFest през повечето време, през което се занимавах с него.

Да има резервен човек, който може да поеме такъв тип задачи е направо животоспасяващо. Това може да е формализирано, а може и просто когато се наложи, да излизат хора от екипа, които да поемат тези функции. Та е важно (както споменах и по-горе) да има една резервна лидерска скамейка, на която има хора, можещи да поемат отговорност.

А може просто да няма формализиран lead, и ролята да се мести, или просто да се случва сама. Това също е работещ вариант при правилните хора, и е просто една голяма “резервна скамейка”, от която някой става, когато е нужно.

Такива примери имам много, от всякакви организации, но най-лесния за илюстриране и един от от любимите ми (даже се оказа, че съм го ползвал в друга моя лекция :) ). Това се случва в петък вечер преди OpenFest 2018. Аз съм се прибрал да си гледам жената и детето (тогава само едно), и нещо се счупва с интернета и сървъра. И няколко човека, които всъщност не са настройвали тая част, щото са били заети с други неща сядат и го оправят (с малко обаждане по телефона да ме питат нещо).

Да си призная, тези хора ми бяха от любимите екипи :)

Почистването на екип е от нещата, с които съм се занимавал най-малко. Старая се основно да не се налага, понеже това губи време и изтормозва всички.

Виждал съм най-различни случаи на проблемни хора. За тях има различни решения, като тръгнем от корекция на конфликти, разделение на отговорности (ако някакви хора не могат да се траят, но трябва да работят в близки екипи), до директно раздяла с някого. Важното при всички такива решения е да са ясни защо се случват и да се случват.

Това по принцип е една от най-тежките задачи, и във фирмите даже си има хора за тая работа, да играят лошия, когато ни дойде желание да избягаме от отговорност…

А трябва ли да се занимваме с това? Да сме в екип, да го поддържаме, да работим заедно?

За някои хора не. Има достатъчно неща, за които може да си one-man show и да сработят, и познавам хора, които правят точно това – гледат да са единствените, които се занимават с дадено нещо и да си го организират така, че да не им е проблем.

За мен си заслужава и винаги си е заслужавало. Правил съм неща сравнително сам, и съм наясно какво мога да постигна. Постигал съм повече в различни екипи, и определено си е заслужавало. Препоръчвам на всички :) В някои отношения е по-трудно от работата с компютри, в някои е по-лесно, но съм се убедил, че е важно.

И ако се огледаме около себе си, огромна част от света, в който живеем се е случил и развил заради групи от хора с определена цел.

Оставям един slide с малко допълнителни ресурси, има ли някакви въпроси?

2024-10-19 “Self-hosting workshop” в initLab

October 20th, 2024 by Vasil Kolev

В initLab се случват доста събития, но рядко има записи (или не се откриват, трябва да проуча малко).

Вчера с Благо направихме self-hosting workshop, и има запис. Ще се постарая да намерим начин останалите неща, дето се случват там да се записват и да ходят във видео архива.

2024-09-23 гласуване за лекции на OpenFest

September 23rd, 2024 by Vasil Kolev

vote.openfest.org, както винаги. Гласуването помага за подреждането на лекциите и понякога за избора им.

(има и моя там, но понеже аз държах на това да не се виждат кои са лекторите, за да се избират лекции на база материала в тях, няма да кажа коя)
(както и че ще я правя тестово в събота в initLab)

2024-08-31 Лекция на Grace Hopper

August 31st, 2024 by Vasil Kolev

По принцип видеото ми е един от най-бавните и неудобни начини да приемам информация (това въпреки всичкото време, което съм отделил за записване и stream-ване на конференции). Та рядко гледам такива неща, и много често предпочитам да си прочета за нещо, вместо да седя и да слушам.

Тия дни обаче попаднах на запис на лекция на Grace Hopper в NSA, който наскоро са разсекретили (и в който няма нищо особено секретно или тайно). За лекция, която е изнесена преди 30-40 години, нещата са доста интересни и на място, доста от проблемите от тогава съществуват и сега, а самият разказ и начинът на разказване са едни от най-добрите, дето съм виждал.

Та, препоръчвам.

2024-08-11 initLab рожден ден

August 11th, 2024 by Vasil Kolev

initLab стана на 14 години.

Отпразнувахме го подобаващо, с разни занимания, включващи пиене, свирене и бордови игри. Имаше хора, които са членове отдавна, имаше хора, които идваха за пръв път, та си беше весело.

Хубаво е, че лабът съществува толкова години. Това е едно от малкото места, на които хора могат да се съберат и да свършат нещо интересно, в почти произволна посока, без някакви особени ограничения и задължения, просто за удоволствието и знанията. През годините са организирани толкова събития, че ми е трудно да ги изброя (във времената, когато бях в управителния съвет си спомням колко дълъг ставаше отчетът за дейността, просто защото целогодишно се случват някакви неща). Без initLab щеше да е доста по-трудна организацията на OpenFest, на доброволците, които ходим на FOSDEM, study групите за Rails Girls, както и на много знайни и незнайни други проекти.

Около свиренето си припомних, че в лаба има един много забавен ефект (така де, любимо звучащия ми дисторжън на тоя свят), и в събота отидох да подрънча малко. Резултатите са (средно зле), нещо с полка ритъм и частта от нещото без distortion, която за някои хора може да е по-слушаема.
(частта без distortion е от края и е по-зле, понеже вече малко ме боляха ръцете, поотвикнал съм)

(също обмислям да проходя някакви лекции в лаба в следващите месеци, понеже имам три започнати и трите не ми харесват изобщо, и се чудя дали ако ги изговоря с някакви други хора може да се получат)

Празнуването на sysadminday 2024

July 11th, 2024 by Vasil Kolev

Има запазени 2-3 маси в “Дондуковъ” (където се провежда и ИББ), за 26.07 (петък), 19:00, на мое име.

Ще има и във Варна, ще update-на за там като ми кажат къде ще е.

Update: За Варна – “Черната овца” до ЖП гарата, на името на Тодор Петков, пак от 19:00, пак 26.07.

Update: За Велико Търново – “Тихият кът” от 20:00, пак 26.07.

2024-06-16 празнуване на sysadminday

June 16th, 2024 by Vasil Kolev

По традиция деня на системния администратор е последния петък на юли.

Дали за тази година има желаещи да го празнуваме в София? Нещо като Маймунарника, или някоя кръчма по центъра, някой би ли дошъл?:)

2024-04-06 нова marla, нов tyler, миграции

April 6th, 2024 by Vasil Kolev

И дойде време за подмяна на хардуера.

marla.ludost.net последно е подменяна през 2016, и имаше нужда от refresh.

За разлика от предишните, новата е 1U. Сега е AMD EPYC 7303P, 128 GiB RAM, и три броя 7.68TB NVMe-та в RAID5. В сравнение с предишната направо лети.

Хардуерната част беше по-лесната. Машината беше още на Debian 9, с купчина ръчно компилирани неща, с разни стари услуги и какви ли не странности. За миграцията имах 40тина неща за подготвяне, и план за самата миграция план от 45 точки какво има да се пусне/спре/мигрира и т.н.. От по-забавните:

– Не ми се занимаваше да мигрирам до maiman3, така че си build-нах пакет за mailman2, с python2 и всичко останало. Тва ще живее в миналото, докато не събера желание да пренеса миграцията до нещо по-модерно. Също, никак не ме радва идеята разни пакети да мислят, че да се счупи миграцията от версия 2 към версия 3 е добра идея, и обвинявам Python-а, че даде лош пример на света.
– Трябваше да си build-на ircd-то и services с моите patch-ове за UTF-8 support и подобни неща. В момента ircd-ratbox го няма никъде в debian и трябва да помисля за миграция към някой от наследниците.
– Ползвам packetbl за филтриране на пакети на база на RBL, та му направих по-нормален начин за пускане и пакетиране за текущата ситуация.
– И разбира се купчини сайтове, PHP-та, бази и какво ли не още.

Подготовката беше няколко седмици, от които може би 2 дни бяха rsync на всички данни през интернетите, за да пренеса повечето информация, разни дебели видео архиви и подобни работи. После едни колеги пренесоха желязото физически до 3dc, пъхнаха го в rack-а, и аз след ден-два направих самия switch, в рамките на няколко часа. Помогна и monitoring-а, да покаже какво не е светнало и какво има да се досветне.

Имаше няколко fail-а, които се оправяха в последствие, но като цяло нещата минаха добре, и в момента желязото си клати краката, щото предишния load изобщо не може да се сравни с какво може. Следва да се качи още натоварване :)

И това беше лесната част. Като допълнение, исках да махна стария tyler (който е по-предната marla, купувана 2010та), и да го пренеса на старата marla. Което щеше да е доста по-проста задача, ако не беше това:

u0    RAID-5    DEGRADED       -       -       64K     2793.94   RiW    ON    
VPort Status         Unit Size      Type  Phy Encl-Slot    Model
------------------------------------------------------------------------------
p0    OK             u0   1.82 TB   SATA  0   -            ST2000DM008-2UB102 
p1    OK             u0   1.82 TB   SATA  1   -            ST2000DM008-2UB102 
p2    DEGRADED       u0   1.82 TB   SATA  2   -            ST2000DM008-2UB102 
p3    ECC-ERROR      u0   1.82 TB   SATA  3   -            ST2000DM008-2UB102 

Това е RAID5 масив с един изпаднал диск и един, който дава грешки, или в човешка терминология, “life sucks and then you die”. Открих го, когато започнах да планирам миграцията, и се наложи да го планирам малко по-форсмажорно. Файловите системи вече даваха грешки, така че в общи линии една нощ преточвах каквото можеше, и в една ранна сутрин с dd през netcat преточих root partition-а на другото желязо и boot-нах.

Най-голямото упражнение беше да го направя без ходене на място, понеже да се boot-не нещо на желязо с толкова стар IPMI се оказа забавно, и загубих 4-5 часа да се опитвам да намеря java, която да може да направи закачането на block device отсреща (в което се провалих изцяло). Накрая (понеже все пак имах ipmi) написах едно sysrescuecd на един partition на диска и просто boot-нах от него, за да мога спокойно да overwrite-вам root-а.
(след което имаше разни други проблеми с разлики в GRUB версии и т.н., като цяло в GRUB prompt не бях изкарвал толкова време доста отдавна)

Доста неща са пострадали. Спасих си netbox-а, но kenny.ludost.net е със заминала база, там няма спасение, та който е имал account, ще трябва да си прави пак. Jabber сървъра също е пострадал, и ще видя там какво мога да направя в следващите дни.

Цялата работа ми показа основно колко ме е разглезил StorPool с тия виртуални машини, snapshot-и, end-to-end checksum-и, live миграции… Ако събера бюджет, някой ден ще си вдигна нещо подобно. От друга страна, човек определено трябва да си припомня как се правят по-криви неща от време на време :)

2024-03-05 The failed live migration case

March 5th, 2024 by Vasil Kolev

There’s a purpose in everyone’s life. Some days, it seems like mine is to show people what not to do.

I’ve written this explanation to show my mistakes and that, hopefully, other people don’t (there’s no hope for me). What follows is most of my debugging process, and it could be helpful to other people to see it (and point out what is wrong with it).

This happens in a StorPool deployment for a customer who runs a public cloud service. We provide the shared storage via some drivers of our own, and the customer uses OpenNebula to control Linux/KVM hypervisors. It’s all integrated very nicely and is used for our internal clouds.

KVM VMs are a qemu process in the Linux userspace with multiple threads.

The case started with a customer complaining that after an update we did to the storage software, the live migrations of his VMs started failing (the VMs would fail to move but would continue working on the original host). The problem was narrowed down to VMs started before a specific date (when we did the update mentioned above), and initially, it seemed to be contained to VMs that had QEMU CPU set (i.e., the least performant one).

At this point, I asked – can’t they restart all such VMs? This CPU type is mainly contributing to global warming anyway.
The reply was, “Are you insane? These are 900 VMs. We’ll reboot them, but we really don’t want to do that now”.
Fair point.

Then, some further tests showed that any VMs, not just the ones with the QEMU CPU, were failing the migration. Finally, someone looked into the (not easy to find) logs of qemu and found the following:

2024-02-20T09:46:41.938285Z qemu-kvm-one: qemu_savevm_state_complete_precopy_non_iterable: bdrv_inactivate_all() failed (-1)

(seriously. Trying to trace anything via the logs in OpenNebula/libvirt/qemu feels like interrogating prisoners who don’t speak your language.)

Two mistakes above that would’ve saved a day or two – I should’ve looked into the issue immediately myself instead of relying on hearsay and building the wrong picture (based on assumptions) in my head. I did not expect that the (routine) update we did could have any such effect.

I specifically checked if the same problem existed in our labs and test environments, where we had done the same routine update, and it didn’t exist.

Now, this was weird. The only mention of the above error was in https://bugzilla.redhat.com/show_bug.cgi?id=2058982 when the storage device on the originating side was broken. This was not the case here.

My first step was to get permission to use one VM at the customer for tests, and after I had that, I ran strace on it while migrating.

What I was able to see in the thread that performed the migration was:

prctl(PR_SET_NAME, "live_migration")    = 0
…
sendmsg(99, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="..."..., iov_len=32768}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 32768
sendmsg(99, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="..."..., iov_len=32768}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 32768
write(2, "2024-02-15T07:00:23.057678Z ", 28) = 28
write(2, "qemu-kvm-one:", 13)           = 13
write(2, " ", 1)                        = 1
write(2, "qemu_savevm_state_complete_precopy_non_iterable: bdrv_inactivate_all() failed (-1)", 82) = 82
write(2, "\n", 1)                       = 1
futex(0x55586af89640, FUTEX_WAKE_PRIVATE, 1) = 1
sendmsg(20, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="{\"timestamp\": {\"seconds\": 1707980423, \"microseconds\": 57880}, \"event\": \"MIGRATION\", \"data\": {\"status\": \"failed\"}}\r\n", iov_len=115}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 115
futex(0x55586af89640, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN (Resource temporarily unavailable)
sendmsg(20, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="{\"timestamp\": {\"seconds\": 1707980423, \"microseconds\": 58133}, \"event\": \"RESUME\"}\r\n", iov_len=82}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 82

Some digging showed that 99 was the file descriptor with the connection to the remote qemu. This was the memory transfer, followed almost immediately by writing out the error and nothing in between.

So, being somewhat lost, I looked up debug symbols for qemu to see if I could trace this further. The usual tooling told me there weren’t:

[root@somewhere str3]# gdb /usr/libexec/qemu-kvm
GNU gdb (GDB) Rocky Linux 8.2-20.el8.0.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/libexec/qemu-kvm...Reading symbols from .gnu_debugdata for /usr/libexec/qemu-kvm...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Missing separate debuginfos, use: yum debuginfo-install qemu-kvm-core-6.2.0-33.module+el8.8.0+1454+0b2cbfb8.x86_64
(gdb) quit

[root@somewhere str3]# yum debuginfo-install qemu-kvm-core-6.2.0-33.module+el8.8.0+1454+0b2cbfb8.x86_64
Repository storpool-contrib is listed more than once in the configuration
enabling epel-debuginfo repository
Extra Packages for Enterprise Linux 8 - x86_64 - Debug                                                                                                                                                                                            4.0 MB/s | 6.1 MB     00:01    
Last metadata expiration check: 0:00:02 ago on Thu Feb 15 16:26:34 2024.
Could not find debuginfo package for the following installed packages: qemu-kvm-core-15:6.2.0-33.module+el8.8.0+1454+0b2cbfb8.x86_64
Could not find debugsource package for the following installed packages: qemu-kvm-core-15:6.2.0-33.module+el8.8.0+1454+0b2cbfb8.x86_64
Dependencies resolved.
Nothing to do.
Complete!

(I can already hear people saying, “You’re an idiot”; someone had to point that out to me)

Then, the colleague in communication with the customer said, “BTW, do you remember that at that time we did the other update, to migrate from one-type-of-volume-name to the-other-type-of-volume-name”? Of course, I didn’t. We ran the same migration in our lab, and the issue was reproduced immediately.

The mistake was that I did not collect all available information (or even most of it), still relying on assumptions.

I could deploy the latest qemu for AlmaLinux (almost the same as what the customer was using), download debug symbols, and start digging. Yay.

On strace, the issue looked the same – lots of sending to the socket and then just the error. So, I started digging with gdb by attaching, adding a breakpoint, continuing the process, and then running a live migration to see how the situation looked like at different points of the program. I had something like this primed and pasted when I ran gdb -p xxxx (some comments added to explain what these are):

set print pretty			# the usual gdb print is hard to read
set pagination off			# pagination is annoying in this regard, my terminal can scroll
handle SIGUSR1 nostop pass		# qemu uses SIGUSR1 a lot
break block/block-backend.c:253		# stop here
cont					# continue execution
bt full					# print me a full backtrace with local variables when stopped

It took some time to read through the relevant code and ensure there wasn’t something obvious (which took many wrong turns). The issue looked like that bdrv_inactivate_recurse() was trying to remove write permissions from the devices and was failing for some reason.

6523     /* Inactivate this node */
6524     if (bs->drv->bdrv_inactivate) {
6525         ret = bs->drv->bdrv_inactivate(bs);
6526         if (ret < 0) {
6527             return ret;
6528         }
6529     }
6530 
6531     QLIST_FOREACH(parent, &bs->parents, next_parent) {
6532         if (parent->klass->inactivate) {
6533             ret = parent->klass->inactivate(parent);
6534             if (ret < 0) {
6535                 return ret;
6536             }
6537         }
6538     }
6539 
6540     bdrv_get_cumulative_perm(bs, &cumulative_perms,
6541                              &cumulative_shared_perms);
6542     if (cumulative_perms & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED)) {
6543         /* Our inactive parents still need write access. Inactivation failed. */
6544         return -EPERM;
6545     }

This is the code in question, and in bdrv_get_cumulative_perms(), there were some devices that still had r/w set even after the stuff between lines 6531 and 6533 had passed:

bdrv_inactivate_recurse (bs=0x556e00221530) at ../block.c:6534
6534                if (ret < 0) {
(gdb) 
6531        QLIST_FOREACH(parent, &bs->parents, next_parent) {
(gdb) 
6540        bdrv_get_cumulative_perm(bs, &cumulative_perms,
(gdb) 
6542        if (cumulative_perms & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED)) {
(gdb) 
bdrv_inactivate_all () at ../block.c:6591
6591            if (ret < 0) {
(gdb) 
6592                bdrv_next_cleanup(&it);
(gdb) 

The inactivate() function above was in block/block-backend.c and looks like this:

 250 static int blk_root_inactivate(BdrvChild *child)
 251 {
 252     BlockBackend *blk = child->opaque;
 253 
 254     if (blk->disable_perm) {
 255         return 0;
 256     }
 257 
 258     if (!blk_can_inactivate(blk)) {
 259         return -EPERM;
 260     }
 261 
 262     blk->disable_perm = true;
 263     if (blk->root) {
 264         bdrv_child_try_set_perm(blk->root, 0, BLK_PERM_ALL, &error_abort);
 265     }
 266 
 267     return 0;
 268 }

and what was happening was:

258         if (!blk_can_inactivate(blk)) {
(gdb) 
262         blk->disable_perm = true;
(gdb) 
263         if (blk->root) {
(gdb) 
264             bdrv_child_try_set_perm(blk->root, 0, BLK_PERM_ALL, &error_abort);
(gdb) 
bdrv_inactivate_recurse (bs=0x556e00221530) at ../block.c:6534

So qemu is calling it, and that was all OK.

Here, a few times, I had missed what this was trying to do. Here’s bdrv_child_try_set_perm():

2517 
2518 int bdrv_child_try_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
2519                             Error **errp)
2520 {
2521     Error *local_err = NULL;
2522     Transaction *tran = tran_new();
2523     int ret;
2524 
2525     bdrv_child_set_perm(c, perm, shared, tran);
2526 
2527     ret = bdrv_refresh_perms(c->bs, &local_err);
2528 
2529     tran_finalize(tran, ret);
2530 
2531     if (ret < 0) {
2532         if ((perm & ~c->perm) || (c->shared_perm & ~shared)) {
2533             /* tighten permissions */
2534             error_propagate(errp, local_err);
2535         } else {
2536             /*
2537              * Our caller may intend to only loosen restrictions and
2538              * does not expect this function to fail.  Errors are not
2539              * fatal in such a case, so we can just hide them from our
2540              * caller.
2541              */
2542             error_free(local_err);
2543             ret = 0;
2544         }
2545     }
2546 
2547     return ret;
2548 }

So that 0 being passed is “remove all permissions”, but at some time late in the day, you can’t see it…

And now, I break into that one and try to see what happens:

(gdb) n
2197        *s = (BdrvChildSetPermState) {
(gdb) print *(local_err)
No symbol "local_err" in current context.
(gdb) 
No symbol "local_err" in current context.
(gdb) n
2203        c->perm = perm;
(gdb) print *(local_err)
No symbol "local_err" in current context.
(gdb) n
2204        c->shared_perm = shared;
(gdb) print *(local_err)
No symbol "local_err" in current context.
(gdb) n
2206        tran_add(tran, &bdrv_child_set_pem_drv, s);
(gdb) n
bdrv_child_try_set_perm (c=0x556e002e1730, perm=perm@entry=0, shared=shared@entry=31, errp=0x556dfeb99258 <error_abort>) at ../block.c:2527
2527        ret = bdrv_refresh_perms(c->bs, &local_err);
(gdb) print *(local_err)
Cannot access memory at address 0x0
(gdb) n
2529        tran_finalize(tran, ret);
(gdb) print *(local_err)
$1 = {
  msg = 0x7f25a4041b70 "Could not open '/var/lib/one//datastores/0/310/disk.0': No such file or directory", 
  err_class = ERROR_CLASS_GENERIC_ERROR, 
  src = 0x556dfe63ca2a "../util/osdep.c", 
  func = 0x556dfe63cbc0 <__func__.26488> "qemu_open_internal", 
  line = 359, 
  hint = 0x0
}
(gdb) q

Now, I see it going through the function, and I’m trying to look at that local_err if there’s something in it. Finally, I get the actual error; it has happened somewhere and is not propagated – but qemu has tried opening a file, and that doesn’t exist.

Two things here:
– This file does exist (it’s a symlink to a symlink to a block device, and I can access it with the permissions of the process);
– Why isn’t this error logged or propagated? I should probably send a patch for that…
– This was not in what strace showed, so what the hell.

So, I break at util/osdep.c in qemu_open_internal(), to see what happens. I pass through that a few times, trying to see where that error comes from, because I still think there is no syscall there, and glibc is screwing with me. This goes to show that you shouldn’t debug when tired because glibc returning ENOENT on its own is a lot less likely than strace missing one syscall.

I’m going to leave out a lot of unsuccessful attempts and show you how I got to the next clue in one large gdb session (commends with #):

(gdb) break util/osdep.c:312
Breakpoint 1 at 0x556dfe2c5c71: file ../util/osdep.c, line 312.
(gdb) handle SIGUSR1 nostop pass
Signal        Stop      Print   Pass to program Description
SIGUSR1       No        Yes     Yes             User defined signal 1
(gdb) set print pretty
(gdb) set pagination off
(gdb) cont
Continuing.
[New Thread 0x7f259c9ba700 (LWP 1798462)]
[New Thread 0x7f259c1b9700 (LWP 1798463)]

Thread 5 "CPU 0/KVM" received signal SIGUSR1, User defined signal 1.

Thread 5 "CPU 0/KVM" received signal SIGUSR1, User defined signal 1.

Thread 5 "CPU 0/KVM" received signal SIGUSR1, User defined signal 1.
[Switching to Thread 0x7f259c1b9700 (LWP 1798463)]

Thread 8 "live_migration" hit Breakpoint 1, qemu_open_internal (name=name@entry=0x556e0021b241 "/var/lib/one//datastores/0/310/disk.0", flags=16384, mode=mode@entry=0, errp=errp@entry=0x7f259c1b85e0) at ../util/osdep.c:318
318         if (strstart(name, "/dev/fdset/", &fdset_id_str)) {
(gdb) bt full# nothing interesting in the backtrace
(gdb) disassemble 
Dump of assembler code for function qemu_open_internal:
   0x0000556dfe2c5c40 <+0>:     push   %r15
   0x0000556dfe2c5c42 <+2>:     push   %r14
   0x0000556dfe2c5c44 <+4>:     mov    %rcx,%r14
   0x0000556dfe2c5c47 <+7>:     push   %r13
   0x0000556dfe2c5c49 <+9>:     mov    %edx,%r13d
   0x0000556dfe2c5c4c <+12>:    push   %r12
   0x0000556dfe2c5c4e <+14>:    mov    %esi,%r12d
   0x0000556dfe2c5c51 <+17>:    lea    0x376dad(%rip),%rsi        # 0x556dfe63ca05
   0x0000556dfe2c5c58 <+24>:    push   %rbp
   0x0000556dfe2c5c59 <+25>:    mov    %rdi,%rbp
   0x0000556dfe2c5c5c <+28>:    push   %rbx
   0x0000556dfe2c5c5d <+29>:    sub    $0x28,%rsp
   0x0000556dfe2c5c61 <+33>:    mov    %fs:0x28,%rax
   0x0000556dfe2c5c6a <+42>:    mov    %rax,0x18(%rsp)
   0x0000556dfe2c5c6f <+47>:    xor    %eax,%eax
=> 0x0000556dfe2c5c71 <+49>:    lea    0x10(%rsp),%rdx
   0x0000556dfe2c5c76 <+54>:    callq  0x556dfe2c6a30 <strstart>
   0x0000556dfe2c5c7b <+59>:    test   %eax,%eax
   0x0000556dfe2c5c7d <+61>:    je     0x556dfe2c5cd8 <qemu_open_internal+152>
   0x0000556dfe2c5c7f <+63>:    mov    0x10(%rsp),%rdi
   0x0000556dfe2c5c84 <+68>:    callq  0x556dfe2c79b0 <qemu_parse_fd>
   0x0000556dfe2c5c89 <+73>:    movslq %eax,%rdi
   0x0000556dfe2c5c8c <+76>:    mov    %rdi,%rbx
   0x0000556dfe2c5c8f <+79>:    cmp    $0xffffffffffffffff,%rdi
   0x0000556dfe2c5c93 <+83>:    je     0x556dfe2c5d7c <qemu_open_internal+316>
   0x0000556dfe2c5c99 <+89>:    mov    %r12d,%esi
   0x0000556dfe2c5c9c <+92>:    callq  0x556dfe101b60 <monitor_fdset_dup_fd_add>
   0x0000556dfe2c5ca1 <+97>:    mov    %eax,%ebx
   0x0000556dfe2c5ca3 <+99>:    cmp    $0xffffffff,%eax
   0x0000556dfe2c5ca6 <+102>:   je     0x556dfe2c5db3 <qemu_open_internal+371>
   0x0000556dfe2c5cac <+108>:   mov    0x18(%rsp),%rcx
   0x0000556dfe2c5cb1 <+113>:   xor    %fs:0x28,%rcx
   0x0000556dfe2c5cba <+122>:   mov    %ebx,%eax
   0x0000556dfe2c5cbc <+124>:   jne    0x556dfe2c5dd3 <qemu_open_internal+403>
   0x0000556dfe2c5cc2 <+130>:   add    $0x28,%rsp
   0x0000556dfe2c5cc6 <+134>:   pop    %rbx
   0x0000556dfe2c5cc7 <+135>:   pop    %rbp
   0x0000556dfe2c5cc8 <+136>:   pop    %r12
   0x0000556dfe2c5cca <+138>:   pop    %r13
   0x0000556dfe2c5ccc <+140>:   pop    %r14
   0x0000556dfe2c5cce <+142>:   pop    %r15
   0x0000556dfe2c5cd0 <+144>:   retq   
   0x0000556dfe2c5cd1 <+145>:   nopl   0x0(%rax)
   0x0000556dfe2c5cd8 <+152>:   mov    %r12d,%esi
   0x0000556dfe2c5cdb <+155>:   mov    %r13d,%edx
   0x0000556dfe2c5cde <+158>:   mov    %rbp,%rdi
   0x0000556dfe2c5ce1 <+161>:   xor    %eax,%eax
   0x0000556dfe2c5ce3 <+163>:   or     $0x80000,%esi
   0x0000556dfe2c5ce9 <+169>:   callq  0x556dfdf0f190 <open64@plt>

…

I am not good at reading assembly, so if I’m doing this, I’m desperate (the colleague talking to the client was sitting next to me and asking why I was digging in the assembly). I see that it should call open64@plt, which looks like the syscall open to me.

I spend some time stepping instruction by instruction and trying to understand (and failing) what /usr/include/bits/fcntl2.h is doing with the macros there, but instruction by instruction I get to this:

(gdb) si
__libc_open64 (file=file@entry=0x556e0021b241 "/var/lib/one//datastores/0/310/disk.0", oflag=oflag@entry=540672) at ../sysdeps/unix/sysv/linux/open64.c:37
37      {
(gdb) si
0x00007f25d7a401d4      37      {
(gdb) si
0x00007f25d7a401d8      37      {
(gdb) disassemble 
Dump of assembler code for function __libc_open64:
   0x00007f25d7a401d0 <+0>:     endbr64 
   0x00007f25d7a401d4 <+4>:     sub    $0x68,%rsp
=> 0x00007f25d7a401d8 <+8>:     mov    %esi,%r10d
   0x00007f25d7a401db <+11>:    mov    %rdx,0x40(%rsp)
   0x00007f25d7a401e0 <+16>:    mov    %fs:0x28,%rax
   0x00007f25d7a401e9 <+25>:    mov    %rax,0x28(%rsp)
   0x00007f25d7a401ee <+30>:    xor    %eax,%eax
   0x00007f25d7a401f0 <+32>:    and    $0x40,%r10d
   0x00007f25d7a401f4 <+36>:    jne    0x7f25d7a40248 <__libc_open64+120>
   0x00007f25d7a401f6 <+38>:    mov    %esi,%eax
   0x00007f25d7a401f8 <+40>:    and    $0x410000,%eax
   0x00007f25d7a401fd <+45>:    cmp    $0x410000,%eax
   0x00007f25d7a40202 <+50>:    je     0x7f25d7a40248 <__libc_open64+120>
   0x00007f25d7a40204 <+52>:    mov    0x20d236(%rip),%eax        # 0x7f25d7c4d440 <__pthread_multiple_threads>
   0x00007f25d7a4020a <+58>:    test   %eax,%eax
   0x00007f25d7a4020c <+60>:    jne    0x7f25d7a40273 <__libc_open64+163>
   0x00007f25d7a4020e <+62>:    mov    %esi,%edx
   0x00007f25d7a40210 <+64>:    mov    $0x101,%eax
   0x00007f25d7a40215 <+69>:    mov    %rdi,%rsi
   0x00007f25d7a40218 <+72>:    mov    $0xffffff9c,%edi
   0x00007f25d7a4021d <+77>:    syscall 
   0x00007f25d7a4021f <+79>:    cmp    $0xfffffffffffff000,%rax
   0x00007f25d7a40225 <+85>:    ja     0x7f25d7a402c8 <__libc_open64+248>
   0x00007f25d7a4022b <+91>:    mov    0x28(%rsp),%rcx
   0x00007f25d7a40230 <+96>:    xor    %fs:0x28,%rcx
   0x00007f25d7a40239 <+105>:   jne    0x7f25d7a402f1 <__libc_open64+289>
   0x00007f25d7a4023f <+111>:   add    $0x68,%rsp

And that, at 0x00007f25d7a4021d <+77>: is obviously the entry point to the kernel (the syscall). So…

(gdb) si
0x00007f25d7a40289      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a4028d      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a40290      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a40295      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a4029a      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a4029c      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a4029f      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a402a4      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) si
0x00007f25d7a402a6      48        return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | EXTRA_OPEN_FLAGS,
(gdb) disassemble 
Dump of assembler code for function __libc_open64:
… 			# omitted
   0x00007f25d7a40285 <+181>:   mov    0xc(%rsp),%esi
   0x00007f25d7a40289 <+185>:   mov    (%rsp),%rdi
   0x00007f25d7a4028d <+189>:   mov    %eax,%r8d
   0x00007f25d7a40290 <+192>:   mov    0x8(%rsp),%r10d
   0x00007f25d7a40295 <+197>:   mov    $0x101,%eax
   0x00007f25d7a4029a <+202>:   mov    %esi,%edx
   0x00007f25d7a4029c <+204>:   mov    %rdi,%rsi
   0x00007f25d7a4029f <+207>:   mov    $0xffffff9c,%edi
   0x00007f25d7a402a4 <+212>:   syscall 
=> 0x00007f25d7a402a6 <+214>:   cmp    $0xfffffffffffff000,%rax
   0x00007f25d7a402ac <+220>:   ja     0x7f25d7a402de <__libc_open64+270>

Maybe not the one I expected, but by stepping instruction by instruction, I obviously called a syscall here. So qemu asks the kernel about the file, and the kernel says it’s not there.

So for the hell of it I run strace -e trace=open,openat -p PIDFILE, and

[root@a8onekvm1 ~]# strace -f -p 1463232 -o dbg.open -s 255 -e trace=open,openat
strace: Process 1463232 attached with 6 threads
strace: Process 1832488 attached
strace: Process 1832489 attached
^Cstrace: Process 1463232 detached
strace: Process 1463236 detached
strace: Process 1463237 detached
strace: Process 1463240 detached
strace: Process 1463242 detached
strace: Process 1463245 detached

[root@a8onekvm1 ~]# grep open dbg.open 
1832489 openat(AT_FDCWD, "/var/lib/one//datastores/0/310/disk.0", O_RDONLY|O_DIRECT|O_CLOEXEC) = -1 ENOENT (No such file or directory)

So, now strace sees it, but otherwise doesn’t?

Cue 5 minutes of me, yelling about strace betraying me, then checking that the file exists, writing a small C program to execute the exact same syscall with the permissions of the process, and (being late Friday afternoon and being tired) complaining to other people. And someone says, “this is most probably mount namespaces issue”.

And what do you know, it is:

[root@a8onekvm1 1463232]# nsenter -m -t 1463232 ls -l /var/lib/one//datastores/0/310/disk.0
lrwxrwxrwx 1 oneadmin oneadmin 28 Feb 16 11:02 /var/lib/one//datastores/0/310/disk.0 -> /dev/storpool-byid/fir.b.f2g
[root@a8onekvm1 1463232]# nsenter -m -t 1463232 ls -l /dev/storpool-byid
ls: cannot access '/dev/storpool-byid': No such file or directory

Sooo… what we have done is replace the symlink from pointing to /dev/storpool/XXX to /dev/storpool-byid/YYY. The namespace for qemu doesn’t have the second directory, it was not in use when the process was started, and we have just screwed it by moving the symlink.

So, happy with that, on Monday I wrote a lousy script to recreate the links, which one of my colleagues made into a good script that people won’t be ashamed to show, for example, replacing

virsh dumpxml one-316 |grep 'source dev.*datastores' | cut -d ' ' -f 2

with

xmlstarlet sel -t -m '//devices/disk/source' -v '@dev' -n "${DOMXML}" 2>/dev/null

Then, we apply this to the live environment to one VM, and happily tell the customer to try a live migration.

It fails with the same error.
(cut some time for swearing at the world and all broken software)

So I’m back to square one, as I try strace-ing the process in the live environment with -e trace=open,openat, and there is no such call. I also tried ltrace, but the only difference was that it was slower and didn’t show anything more.

Then, a colleague says, “You know, these debuginfo packages should exist, they’re not that old”, goes looking in the mirrors, and FINDS THEM. For some reason, google doesn’t index that part, and I could not find them the lazy way.

Here’s the obvious error that you should look for yourself. It would’ve been even easier if I logged in to the local mirror we have for Rocky Linux and ran a find for myself.

Armed with debug symbols, I get the test VM again, attach to it, add a breakpoint at qemu_internal_open(), and nothing happens. It doesn’t get hit. It looks like strace wasn’t lying here.

I go back to the blk_root_inactivate() function and I notice:

Thread 8 "live_migration" hit Breakpoint 1, blk_root_inactivate (child=0x55586bf47c00) at ../block/block-backend.c:252
252         BlockBackend *blk = child->opaque;
(gdb) bt
#0  blk_root_inactivate (child=0x55586bf47c00) at ../block/block-backend.c:252
#1  0x000055586a5e8162 in bdrv_inactivate_recurse (bs=0x55586b297760) at ../block.c:6533
#2  0x000055586a5e963f in bdrv_inactivate_all () at ../block.c:6590
#3  0x000055586a37b535 in qemu_savevm_state_complete_precopy_non_iterable (f=0x55586c05d500, in_postcopy=<optimized out>, inactivate_disks=</optimized><optimized out>) at ../migration/savevm.c:1441
#4  0x000055586a37b622 in qemu_savevm_state_complete_precopy (f=0x55586c05d500, iterable_only=iterable_only@entry=false, inactivate_disks=inactivate_disks@entry=true) at ../migration/savevm.c:1493
#5  0x000055586a374236 in migration_completion (s=0x55586b207000) at ../migration/migration.c:3260
#6  migration_iteration_run (s=0x55586b207000) at ../migration/migration.c:3672
#7  migration_thread (opaque=0x55586b207000) at ../migration/migration.c:3908
#8  0x000055586a6d9dc4 in qemu_thread_start (args=0x55586c0ee6c0) at ../util/qemu-thread-posix.c:585
#9  0x00007f4a951d11ca in start_thread () from target:/lib64/libpthread.so.0
#10 0x00007f4a94e3de73 in clone () from target:/lib64/libc.so.6
(gdb) n
254         if (blk->disable_perm) {
(gdb) n
bdrv_inactivate_recurse (bs=0x55586b297760) at ../block.c:6534

blk->disable_perm is already set, so it looks like in this version of qemu (a bit older than what I initially used), when the live migration fails, this flag doesn’t get reset. I made a diff between the two versions, and that part has enough changes to warrant this idea. So, VMs that didn’t fail the migration would be fine.

Now, what about this one and a few more? They seem to be in a corrupt state. I read a bit more code, then bite the bullet and change the variable live:

Thread 7 "live_migration" hit Breakpoint 1, blk_root_inactivate (child=0x55586bf47c00) at ../block/block-backend.c:254
254         if (blk->disable_perm) {
(gdb) bt
#0  blk_root_inactivate (child=0x55586bf47c00) at ../block/block-backend.c:254
#1  0x000055586a5e8162 in bdrv_inactivate_recurse (bs=0x55586b297760) at ../block.c:6533
#2  0x000055586a5e963f in bdrv_inactivate_all () at ../block.c:6590
#3  0x000055586a37b535 in qemu_savevm_state_complete_precopy_non_iterable (f=0x55586c05d500, in_postcopy=</optimized><optimized out>, inactivate_disks=</optimized><optimized out>) at ../migration/savevm.c:1441
#4  0x000055586a37b622 in qemu_savevm_state_complete_precopy (f=0x55586c05d500, iterable_only=iterable_only@entry=false, inactivate_disks=inactivate_disks@entry=true) at ../migration/savevm.c:1493
#5  0x000055586a374236 in migration_completion (s=0x55586b207000) at ../migration/migration.c:3260
#6  migration_iteration_run (s=0x55586b207000) at ../migration/migration.c:3672
#7  migration_thread (opaque=0x55586b207000) at ../migration/migration.c:3908
#8  0x000055586a6d9dc4 in qemu_thread_start (args=0x55586c2325e0) at ../util/qemu-thread-posix.c:585
#9  0x00007f4a951d11ca in start_thread () from target:/lib64/libpthread.so.0
#10 0x00007f4a94e3de73 in clone () from target:/lib64/libc.so.6
(gdb) print ( (BlockBackend *) (child->opaque))->disable_perm
$1 = true
(gdb) print ( (BlockBackend *) (child->opaque))->perm
$2 = 3
(gdb) set: variable ( (BlockBackend *) (child->opaque))->disable_perm = false
No symbol "false" in current context.	# arghhhh
(gdb) set variable ( (BlockBackend *) (child->opaque))->disable_perm = 0
(gdb) cont
Continuing.

Thread 7 "live_migration" hit Breakpoint 1, blk_root_inactivate (child=0x55586bf51850) at ../block/block-backend.c:254
254         if (blk->disable_perm) {
(gdb) print ( (BlockBackend *) (child->opaque))->disable_perm
$3 = false
(gdb) print ( (BlockBackend *) (child->opaque))->perm
$4 = 1
(gdb) cont
Continuing.
[Thread 0x7f4999ffb700 (LWP 144024) exited]
[Thread 0x7f499a7fc700 (LWP 144025) exited]

Thread 1 "qemu-kvm-one" received signal SIGTERM, Terminated.	# oops?
[Switching to Thread 0x7f4a985a6e40 (LWP 3460894)]
0x00007f4a94f28036 in ppoll () from target:/lib64/libc.so.6
(gdb) q

(imagine this being done live and as quickly as possible, as the VM is hanging. You also see two devices, OpenNebula adds a context image to all VMs, and it’s a CD-ROM device that the migration didn’t get to corrupt)

Then I see SIGTERM and think, “crap, I killed it”. Then I look at the logs, and it has actually migrated successfully.

So, finally, with some pointers (“the command in gdb is command” :) ) I write the following:

set print pretty
set pagination off
handle SIGUSR1 nostop pass
handle SIGTERM nostop pass
break block/block-backend.c:253
commands
print ( (BlockBackend *) (child->opaque))->disable_perm
print ( (BlockBackend *) (child->opaque))->perm
set variable ( (BlockBackend *) (child->opaque))->disable_perm = 0
cont
end
cont

and it works like a charm on the 10-or-something VMs with a corrupt state.

A list of errors and lessons learned:

– Don’t assume that because there’s something not right, it’s the cause of the problem you’re looking at (QEMU processor for the VMs);
– Collect all relevant information (and maybe even irrelevant) – I keep telling people that it’s easier to work with too much information than with not enough. I should listen to myself one of these days;
– strace can hide syscalls and shouldn’t be fully trusted;
– If you’re searching for debuginfo (or anything like that), do your legwork, and don’t trust a single search engine;
– testing the wrong software version can reveal only a part of the problem (or a different one).

2023-12-25 равносметъчно

December 25th, 2023 by Vasil Kolev

– Децата са добре. Проведохме даже експеримент – големия ходи с майка си за 2 седмици до Ню Йорк, и ние с малкия си изкарахме много добре, типично по ергенски, с печене на пържоли и други такива;
– Успял съм да напиша 7 blog post-а през годината (без този). Има какво да се желае още, определено;
– FOSDEM 2023 се случи, направихме видео за последна година със стария хардуер, и се получи добре;
– Продължаваме да борим новия setup за FOSDEM 2024, пълно е с “интересни” проблеми;
– OpenFest 2023 се случи без мен. Догодина предавам фондацията, и ще съм приключил;
– Опитите ми за баланс на колко се натоварвам не са от най-успешните, въпреки махането на някакви задачи, трябва да се постарая малко повече за догодина.

Мисля да последвам традицията от миналата година и да не си правя планове за идващата.

2023-12-09 OpenFest 2023

December 9th, 2023 by Vasil Kolev

Голяма лудница ми е, та все не стигам да напиша тия няколко реда (и щяха да останат за равносметката), но държа да кажа:

OpenFest 2023 се случи без мен.

Правих някакви дребни неща, но не съм бил в организацията, нещата се случиха някакси, аз бях там само за да си тествам setup-а за FOSDEM. Да съм честен, най-трудното ми беше да не давам нареждания, щото навикът е страшна работа…

Та, по някое време догодина като направим събрание на фондацията да я предам, ще съм приключил с тая си част от живота. Екип се намира да движи нещата, та съм спокоен :)

п.с. по предишната тема за junior администраторите ще пиша отделно.

2023-11-11 откъде започват в наши дни junior администраторите?

November 11th, 2023 by Vasil Kolev

От време на време задавам тоя въпрос (и си мислех, че вече съм го блогвал, ама не откривам къде) – в наши дни, откъде започват junior системните администратори? Говорим за съвсем начинаещи, първа работа, горе-долу знаят как се държи клавиатура, и имат желание…

Едно време се започваше от support в близкото ISP. Там имаше всякакви мрежови и клиентски проблеми, човек научаваше, че user-ите лъжат (особено като ги питаш “нещо променяно ли е”), и малко по малко се попиваше от по-старшите и админите, така че да може да се научи къде да рови, какво да очаква и как да оправя разни по-дълбоки проблеми. Понеже работата никога не беше малко, почваха да се дават на човека по-сложни и по-сложни задачи, и така.
(“наградата за добре свършената работа е още работа” не е нещо лошо :) )

Също имаше достатъчно места (малки фирмички на някой роднина/познат на роднини), дето имаше нужда някой да им поддържа компютрите и където имаше достатъчно потребители, на чиито гръб да се научиш.

В наши дни май не е така, или поне аз не знам. Има от време на време някакви junior обяви, но като съм имал някакви такива интервюта и разговори, не мога да кажа на хората къде е добре за тях да отидат, ако нямат предишен опит.
(support-а в isp-тата в наши дни са хора, дето четат сценарий стъпка по стъпка и ако не беше толкова модерно заменянето на хора с AI, щяха да са ги заменили със shell script)

Една идея, която давам на хората, е да правят неща сами вкъщи. Може би аз съм в някаква изкривена среда, но повечето ми колеги имат нещо вкъщи, с което си играят, едно-две-три съвърчета, малка мрежа, собственоръчно правен rack… Това дава доста възможности да се научат някакви неща, основно на собствен гръб (или на хората, живеещи с човека).
От друга страна, не всички имат възможност, и не всички могат съвсем самостоятелно да започнат. До някакъв момент си мислех, че всички хора са/трябва да са такива, но с годините се поразубедих (и няма как да очаквам, че всички биха били като мен).

Липсва ми и това, че при мен няма такъв вариант. Естеството на работата за момента изглежда такова, че няма как да учим някой от нулата – няма капацитет и подходяща работа. От нещата, които искам да променя е, само дето още не съм измислил как…

Та, откъде в наши дни могат да почнат junior администраторите, дайте идея.

2023-09-25 гласуване за лекции за OpenFest

September 25th, 2023 by Vasil Kolev

Както обикновено, гласуването за лекции за OpenFest може да се намери на vote.openfest.org. Аз лекция тая година нямам, та няма да давам идеи :)

(“за бога братя, гласувайте”)

2023-09-21

September 21st, 2023 by Vasil Kolev

Има дни, които започват с намирането, ремонтирането и report-ването на бъг в мобилното приложение на rocketchat, минават през срещи и гасене на 3 пожара паралелно, и завършват с


MariaDB [opennebula]> update vm_pool set short_body=replace(short_body,'80','33') where oid=39;
Query OK, 1 row affected (0.002 sec)
Rows matched: 1 Changed: 1 Warnings: 0

Един ден трябва да направя лекция за opennebula и за кандидатурата им за най-невъзможна за докарване в консистентно състояние база данни на тоя свят. Онова по-горе (редактиране на XML в базата със string операции, което теорията твърди, че е идиотщина, и аз съм съгласен) се налага, понеже има същите данни в 2 различни полета на същата таблица, с малка редакция, и понякога не са синхронизирани.

Имам чувството, че съм почнал да гледам на целия свят като някакъв проблем за дебъгване.

2023-06-02 Взимане на решения в екип

June 2nd, 2023 by Vasil Kolev

От няколко места виждам вариации на темата, та ми се стори уместно да напиша нещо.

Ще започна с нещо от Камил Галеев, който на моменти има много добри обяснения. Важният момент в това е, че хората, които са на власт/в позиция да взимат решения/вършат нещо, не могат да имат крайни позиции. Вършенето на работа в екип с някого изисква компромиси, и е далеч по-лесно да работиш с хора, които са съгласни с теб и с които може да си сглобиш обща цел, отколкото с такива, които трябва да бъдат насилвани.

От това има няколко посоки на изводите:

Първо, хората/организациите с крайни позиции са такива, които никога не са стигали до свършване на нещо реално. Неспособността да направиш някакъв компромис и да се промениш/пренасочиш, за да се справиш с някаква промяна в обстановката води до това да си по-зле от тези, които намират вариант и да “отпаднеш” по някакъв начин.
(тук има огромна скоба и малко примери, понеже може да се разбира по грешния начин…

Опциите за компромис и промяна на посоката винаги са повече от една. Първи пример, някой на база на горното може да каже “Това значи ли, че Украйна трябва да направи компромис с Русия?” и отговорът там е, че те са направили много компромиси със западния свят/ЕС, за да могат да продължат да се бият. Дали тези ще са им достатъчни, е отделен въпрос.

Друг пример, на скоро обяснявах нуждата от “черна каса” и бях гледан с огромно учудване, че това се налага в наши дни. Моето твърдение е, че на практика няма организация, в която да няма нещо подобно, просто защото има неща, които не могат да се случат без подобен фонд за плащания, които нямат насрещен документ. Имам някакво количество примери от моята практика, за които по очевидни причини няма да разкажа, и само ще отбележа, че всяка достатъчно голяма организация от едно ниво нагоре има нещо подобно, известно като “discretionary фондове”, с които определени хора разполагат по собствено усмотрение за благото на организацията. С тях се злоупотребява, но ползите от тях надминават загубите.
Чувал съм твърдение, че има организации без такива, но тогава тези неща се случват, като определени хора поемат тези разходи от собствения си джоб. В повечето НПО-та е така и за това в счетоводството на доста от тях може да се намери “и за <нещоси> на тоя член му платихме толкова пари”, като нещото е от типа на “да стои и да изглежда добре”
край на скобата)

Второ, съвсем нормално и здравословно в една организация е да има някакво количество спорове и да се достига до решение с дискусия. Мисля си, че сме супер повредени от много години на “един спасител, който знае всичко”, но очакването да има един-единствен диктатор/ръководител/авторитет и той да е прав за всичко е идиотщина и като цяло сбъркано. Смислените лидери (думата не ми харесва, но не ми хрумва по-добра) не взимат решенията ей-така, без да се консултират, и дори в повечето случаи работата им не е да вземат решението, а например да решат кога има някакъв приблизителен консенсус или достатъчно голямо мнозинство за някакво решение, и да прекратят дискусията.
Много полезна е идеята за rough consensus, дошла от IETF. Може да се каже дори, че организации работещи на еквивалента за тях на “rough consensus and running code” (щото не всички живеят на код) са по-здрави и работещи от тези, които не го правят.

За това и аз лично много се зарадвах на записа от заседанието на ПП, на което обсъждат правителството с ГЕРБ – понеже е пример за всичките неща по-горе и за смислен процес по взимане на решение (въпреки че чисто практически изтичането на записа има неприятни последици). Може би след някакво количество години подобни записи ще се използват за обучителни материали :)

(изобщо, може да не съм от ПП, но от време на време Кирил Петков свършва нещо, с което ми се издига в очите, което като цяло е огромна рядкост за български политик)

2023-02-28 video-streaming (“видео-наблюдение”) на изборите

February 28th, 2023 by Vasil Kolev

Тия дни от Дневник ме разпитваха някакви неща за видео streaming-а (“видеонаблюдението”) на изборите и решиш да доразкажа някакви подробности, ако на някой му е интересно.

Моят опит с тези неща идва от няколко места – генералното ми занимаване със сервиране на файлове и видеа, streaming-а на конференции (OpenFest и най-вече FOSDEM), и накрая – инфраструктурата и схемата, която сглобявах за tibroish.bg за излъчване на предишните избори.
(предишните видео-наблюдения бяха саботирани там, където вероятно ще бъдат и тези, на ниво секция, и на практика имаше твърде малко stream-ващи, може би около 1%. Съответно, цялостната схема не е минала пълен тест в production, но не успявам да намеря причина да не работи.)

(код на всичкото на tibroish)

Според мен, подобна система може да се реализира с почти подръчни средства. В нея няма нито един много специализиран, сложен или невиждан досега компонент. От друга страна, точно това не е правено особено много и има много интеграционна и тестова работа, която не намалява с идването на изборите, та най-големият проблем всъщност са сроковете.

Ще тръгна по потока на данните и ще обясня моето виждане (и как в общи линии работеше системата за streaming в tibroish, която за тези избори е спряна).

Самото заснемане се прави с телефон.
– Хардуерът на модерните телефони е доста добър, и като качество на картината, и като звук;
– в тях има хардуерен encoder за видео, който може да го сдъвче доста добре на хубаво качество;
– два варианта за свързаност (мобилна мрежа и wifi, като в този случай само мобилната мрежа вероятно ще се ползва);
– място за събиране на записи – на повечето телефони 6-часовия, 2.7GiB запис на 1mbps ще се събере във вътрешната памет и няма да има нужда от SD карта;
– самия телефон си има поддържаща операционна система и готови библиотеки;
– вграден UPS;
– ако трябва да се осигурят 12000 устройства за нещо и да се изпрограмират, телефоните нямат равни, понеже като цяло имат подобно производство.

Конкретно в tibroish нямахме особен избор, и задачата беше допълнително затруднена от това, че трябваше да поддържаме random телефони, което ако някоя фирма прави цялостното решение, няма да е проблем.

Приложението на tibroish има в себе си и video streaming, и е open-source, съответно може да се използва тази му част като основа.

Първата задача на приложението е да се оторизира пред зададен централен сървър и да разбере къде да си изпраща видео потока. Подобни системи и схеми има в най-различни видове и са нещо, което почти всеки е правил. Това е услуга с малко натоварване, свястна база данни и сравнително защитена от атаки (по темата сигурност ще пиша отделно).

След като получи информацията, приложението започва да записва и ако може, да stream-ва. Ако може, понеже в някакво количество секции internet няма и няма достатъчно мобилно покритие, за да излъчи 1Mbps видео.

Стойността 1Mbps идва от някакъв опит и проби/грешки, за повечето нужди в 1Mbps на 1280×720 (720p) може да се смести картина с четим текст и като цяло всичко да е добре различимо, при подходящ H.264 профил. Не съм успявал да докарам поносимо видео на по-малка разделителна способност, а 2Mbps ще е доста сложно за мобилната мрежа. Като опция, вероятно може да се записва на различен bitrate, но това ще увеличи доста натоварването на телефона.

Stream-ването го бяхме реализирали по RTMP. В момента опциите за подобно нещо са:
– WebRTC базирано – генерира UDP/RTP трафик и като цяло губи пакети, понеже е ориентирано за комуникация с ниска латентност. Голямото му предимство е сериозната поддръжка в телефоните и като цяло, понеже се използва много, но е доста по-сложно и като цяло неподходящо.
– Нещо файлово-базирано – например да генерира на телефона HLS фрагменти и да ги качва директно един по един – това би било страхотно решение и изисква най-малко от инфраструктурата, но не съм виждал такова.
– RTMP(S) – стандартен протокол в/у TCP, от ерата на Flash-а. Все още стандартът за stream-ване (всичките платформи като twitch, youtube и facebook май само него поддържат) и има добра open-source поддръжка. Аз все още ползвам nginx-rtmp за повечето streaming и стига да не се подава странно видео, работи добре.

Тук задачата в крайна сметка е да има видео и да изглежда добре, латентност от 5-10-30 секунди е поносима.

Този поток от данни с всички останали се праща до приемащ сървър. Сметките всеки може да си ги направи, при 1Mbps на поток и като цяло 12Gbps, ако се раздели на 50 виртуалки (както аз го бях направил), натоварването става поносимо и се издържа от и събира на почти всякаква машина в някаква cloud услуга. Единият път за tibroish използвах Hetzner, но има и други, които могат да свършат работа. За конкретния случай дори комбинация от няколко български доставчика ще свърши работа, и вероятно дори самите мобилни оператори могат да предоставят подобна мижава инфраструктура.
(по моя опит, нужната инфраструктура за това не стига размерите дори на среден cloud provider, може би е като един малък такъв. 12 Gbps и 30 TiB данни за записите би трябвало да не са нещо особено за когото и да било)

От гледна точка на сигурност, адресът на приемащия сървър се дава на приложението от централния, малко преди да е започнал streaming-а. Това се прави да се ограничи периодът, в който ще може да се подготви и засили DDoS атака, понеже такава ще има, както всяка година има спрямо сайта на ЦИК. Доколкото знам, ИО са отработили система за защита, и поне според мен може да се направи уговорка с мобилните оператори точно този трафик да не излиза никъде от мрежите им и да върви по трасета, които няма как да се DDoS-нат отвън.

След като трафикът стигне на приемните машини, от там те могат да генерират HLS потоци (за които няма нужда от reencode-ване, т.е. просто се препакетира трафика, което не яде почти никакъв процесор, от моите експерименти – бях пускал няколко-стотин потока към един сървър, докато сгъне) и може да се гледа. Като цяло гледането ще е по-малката част от трафика, и HLS се проксира доста лесно, та в tibroish просто ползвахме CloudFlare за proxy/филтър пред гледането. Мисля, че това е опция и за всеки друг, който го прави.

И на последно място, някъде трябва да има една красива страница, от която да се вижда къде има работещ stream и да може да се избере/гледа stream. Това се сглабя сравнително лесно (и пак го има в tibroish), като цяло начини да показваш видео – бол…

Няколко проблема, които не съм споменал горе:

– Цялото нещо има нужда минимум от DDoS защита. Може да се очаква да бъде блъскано усилено, ако се намери къде е, за това аз бих разчитал на елемента на изненадата (да не се вижда къде е докато не започне събитието), random имена на сървърите (и всичко в dns зоната, което не е тези сървъри, сочи към някакви blackhole-нати адреси, да затруднява допълнително), подходяща DDoS защита с подходящ капацитет и почистване на трафика, и различни, големи доставчици за проксиране, които да могат да издържат.

– Не сме правили запис, и не мога да кажа какви мотики може да се ударят там. Според мен и сметка на салфетка вътрешния storage на телефоните ще се справи, за удобство може да се пише И на SD карта, там е основно въпрос на логистика. Проблемът на SD картата е, че доста по-лесно изчезва…

– ЦИК има изискването да могат да се четат номерата на бюлетините. Това изисква най-вече добро осветление и поставяне на камерата, което не е от най-простите неща за обясняване (камерата има почти второстепенна роля). Според мен 1Mbps ще се справи със задачата.

– Не е ясно дали може да се записва звук или не. В tibroish го бяхме спрели директно, понеже юристите не можаха да се произнесат, според мен би бил страхотна идея да се записва и stream-ва, колкото и да е зле.

Та, технически може да стане. Практически – времето е малко и ще е сложно, и както обикновено, ще бъде саботирано в секциите. Ако няма конкретни глоби и наказания за липса на видео-наблюдение, може да се очаква поне 20% от него да не работи и да няма запис. Честно казано, ако видя да се случи от 50% от секциите, ще го броя за чудо.

Приемам всякакви идеи и корекции, че това ми е brain dump от една ранна сутрин :)

2023-01-21 процес на наемане на хора

January 21st, 2023 by Vasil Kolev

Отдавна се каня да опиша нашия процес на наемане на хора, и защо е такъв – случва се от време на време хора да ме питат “а как си намирате хора”.

(това е бая важно като задача в една фирма, и почти навсякъде, където съм бил съм гледал да имам участие в процеса, за да не се наложи после да работя с хора, които не стават)

Прескачайки “откъде влизат cv-та” (jobs.bg и подобни сайтове), стъпките нататък са:

1) Базов преглед на CV-то. Ако в него няма нещо наистина притеснително, или не е тотално не подходящо, даваме нататък.
Някои хора гледат внимателно CV-та и на база на тях канят кандидатите на интервю. Всичкия ми опит с това е, че се губи много време, и на интервюиращите, и на кандидатите, понеже CV-то не показва много неща, хората лъжат и т.н.. В някои изключителни случаи (при много senior CV), може да направим първо интервю с човека предварително, но иначе, следваща стъпка:

2) Пращаме едни стандартни задачи на човека. Те са събрани от моята и фирмената практика, и като цяло не са от типа на “сферичен кон във вакуум”, и имат няколко цели:
– да видят на кандидата колко му се занимава
– да се види как мисли и за какви неща се усеща
– да дадат теми за разговор на интервюто :)
В предишни фирми това се заместваше от това да напишат един fizzbuzz на лист на интервюто, но понеже няма fizzbuzz еквивалент за админи, и понеже да каниш хората само за да ги изгониш като не решат задачата си е бая загуба на време. Задачите са повечко и е добре хората да могат да си ги решат на спокойствие, а и няма изискване да се решат всички, а е по избор на канидата.
Както и с fizzbuzz-а, случвало се е да се наемат някакви хора, които да не могат да ги минат тия неща, и това винаги е било голям провал.
Има и хора, дето казват “тия какво ще ме занимават, ще ида при някой друг” (това съм го чувал и от първа ръка) и в общи линии това е търсен ефект, понеже почти във всички случаи аз не искам хора, дето това хем им е голямо усилие, хем не им е достатъчно интересно, че да се хванат и да решат едно-две неща.
В много редки случаи сме филтрирали хора на тази стъпка, заради наистина ужасяващи решения.

3) Правим едно техническо интервю. От наша страна гледаме да сме до 3ма човека (повече от това почва да изглежда притеснително).
Първите 5-10 минути обясняваме ние с какво се занимаваме и какво представлява работата (една от първите реплики е “май ти го разказва това предния път, та сега съм аз”). Това е полезно за да си изгради кандидата контекст и да знае защо задаваме някакви въпроси.
(това май е рядко срещано, и аз не го правех преди)
После, имаме набор от теми, по които разпитваме човека. Започваме от задачите, минаваме през някакви мрежови, linux-ки неща, инструменти, но най-интересното е да разкаже нещо, което е правил сам. Особено хората с повече опит почти задължително имат нещо такова, и от там може да се види всъщност доколко способен е човека.
В тази част има нещо, което често се пропуска, че интервюто трябва да е полезно и за двете страни – ако кандидатът не знае нещо, му го обясняваме/разказваме.
Също така, целта на голяма част от въпросите е да изясни доколко работата ще е интересна за кандидата и доколко като цяло му харесва. Хора, които не са мотивирани, не са заинтересувани и това го работят между другото, като цяло не стават за при нас.
(това е доста видимо при почти всички кандидати, които се появяват от СофтУни)
Последните 10-15 минути гледаме да оставим за въпроси от кандидата към нас, всякакви неща свързани с работата.

Към края на интервюто обясняваме, че ще се свържем с кандидата до около седмица, без значение какво ни е решението – т.е. никого не оставяме без отговор и да се чуди какво става. Това важи и за предишната стъпка – който ни е пратил решение на задачите, е получавал отговор от нас.

4) Срещаме кандидата с целия екип. Това е разговор в сравнително свободен формат, двете страни да се запознаят и да решат дали им се работи заедно. Това винаги е доста забавно, и помага на кандидата да види в какво се забърква.

5) Прави се “човешко” интервю с кандидата, което е с CEO-то на фирмата, да се види дали не сме пропуснали, че човекът е психопат или нещо подобно (както се вижда, аз това тотално не го разбирам и вероятно не го описвам правилно :) ).

6) Правим оферта на човека, и ако приеме, почва при нас.

7) Месец след като е започнал, правим post-onboarding интервю. Тази идея дойде от Dan Luu, та хващаме и разпитваме човека какво не ни е наред, преди да е свикнал с нещата при нас.
(Дори файлът, в който водим бележките, се казва “Какво не е наред”)
(разбира се, onboarding-а продължава много повече, те само базовите неща са поне един месец, но е важно да се направи тоя разговор преди човекът да е привикнал с процесите)

Това са нещата по самия процес, в общи линии. Извън него има като цяло изграждането на имидж на фирмата (с появяване по конференции и т.н.), които не са ми по специалността. Само мога да кажа, че са важни (поне двама от екипа ми са дошли през конференции).

2023-01-01 равносметъчно

January 1st, 2023 by Vasil Kolev

> Отказвам да имам очаквания за идващата година. Някои хора казват, че поне supply chain проблема може да почне да се пооправя към края ѝ, а дано.

(аз, в началото на годината)

Явно решението да нямам очаквания е било съвсем правилно.

От по-интересните неща, които се случиха:

– Малките зверове растат – весели, здрави и все толкова опасни :)

– Направихме пак online FOSDEM, и ще е последния такъв. Имам билети, подготвили сме се, идващия ще е на живо, в гадния студен и мокър Брюксел, в кривия университет. И съм сигурен, че много ще му се радваме.

– Направихме един хубав OpenFest на живо, на който дори успях да изнеса една лекция (още ми е трудно да повярвам колко хора гласуваха за нея, въпреки малоумното заглавие). Следващия OpenFest ще се случва без мен, както писах, от всички организрани досега не съм бил в организацията само на 4-5…

– Успяхме да си попълним екипа тая година, изглежда, че ходенето по разни конференции помага. Даже имах време да посъбера нов списък от “мотики”, та със сигурност в момента има материал поне за една лекция. Ако имам възможност, ще драсна една-две тук.

– Започнахме да си организираме (в екипа) сбирка на всички в офиса да работим заедно, за по една седмица на около два месеца. Аз, както не съм особено социален все пак го намирам за много полезно и приятно, и определено ще се запази.

– ИББ изглежда се възстановява.

Имам голямото желание (и) тази година да пиша малко повече, например отдавна се каня да напиша нещо за наемането на хора, да видим докъде ще стигна.

2022-12-29 ИББ

December 29th, 2022 by Vasil Kolev

Public service announcement: разни хора са възродили ИББ – пак така, всяка сряда, само че в бирария “Дондуковъ” (на бул. Дондуков 14, пак на ъгъла с “Будапеща”, ама диаметрално противоположно спрямо старото “Криво”). Масите са запазени на името на “ИББ”.

Мястото не е лошо, яденето става, бирата става, няма от спирта, дето аз харесвам.