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, пак на ъгъла с “Будапеща”, ама диаметрално противоположно спрямо старото “Криво”). Масите са запазени на името на “ИББ”.

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

2022-12-24 нов лаптоп

December 24th, 2022 by Vasil Kolev

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

Преди има-няма 4 години си взех едно ново дъно от китайски оптимисти за T60. Машинката, в която го пъхнах ми служи вярно доста години, но вече взе да и идва много, да не мога да и намирам батерия и самата и пластмаса да сдава багажа. Като добавим и проблемите, които винаги съм имал с видео картата, си стана време за ново желязо.

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

Та, реших да намеря нещо, което да е максимално поносимо, и стигах до thinkpad t14 gen3. Има средно поносима клавиатура, не е 16:9, а 16:10 (което е малко по-поносимо), и твърди, че батерията му държи над няколко часа. Взех го в петък, и сравнително лесно го инсталирах.

След няколко часа точене (да живее гигабитовата мрежа и nvme/ssd-тата, че правеха по 120MB/s) си пренесох данните, и с още половин ден ръчкане си пренесох работната среда (в момента пиша от него и не може да се познае, че е нещо друго). compiz-а си работи като слънце, след един BIOS update и външните монитори почнаха да се виждат, и определено е по-тих от старото желязо.

Най-голямото забавление беше един бъг на xfce. В началото, като го закачах тестово на docking станцията, то я разпозна като 52″ монитор, и нещо в xfce-то запомни, че дисплея е невероятно широк, съответно всичките кутийки на workspace switcher-а бяха станали ужасно широки, дотолкова, че на екрана се събираха само 3-4. Рових, рових, не намерих някой друг със същия проблем (или това, дето намерих се твърдеше, че е оправено), та накрая свалих source на xfce4-panel, намерих къде смята съотношението на височина към ширина и го hardcode-нах на 1 (тъкмо ще пасва на квадратния монитор). Та 20 минути работа по кода (с компилирането) реши проблем, на който бях отделил поне два часа ровене и четене. После как чукчата да не е писател…

Остана да видим как ще се разбере с носенето м/у вкъщи и офиса и смяната на монитори, но това вероятно няма да е голяма болка (famous last words)…

2022-10-28 post-OpenFest 2022

October 28th, 2022 by Vasil Kolev

Мислех да пиша recap на OpenFest 2022, но ми е твърде уморено. Направихме един работещ фест след няколко години online и малки събития, получи се с нормалното количество проблеми, съвсем накратко :)

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

Така като си погледна архивите, като изключим 2003, 2012 и 2020, съм участвал във всички останали, като в последните 10 години – като един от основните координатори. Ако продължа да се занимавам, вероятно ще тръгна да правя побъркани неща като 10gbps wireless и т.н., а си мисля, че моите идеи за какво е хубаво да се прави/случва може вече да не са съвсем правилни :)

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

Все пак, крайно време е да ида един път на OpenFest като обикновен посетител, не ми се е случвало от 2003 :)

2022-10-15 Лекция от OpenFest 2022, “Голям monitoring”

October 15th, 2022 by Vasil Kolev

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


Ще ви разкажа за една от основните ми задачи в последните 5 години.

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

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

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

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

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

Monitoring-ът като цяло е от основните инструменти за постигане на situational
awareness.

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

Та е много важно да сме наясно какво се случва. И по време на нормална работа,
и докато правим проблеми, т.е. промени – важно е да имаме идея какво става.
Много е полезно да гледаме логовете докато правим промени, но monitoring-а може
да ни даде една много по-пълна и ясна картина.

Да правиш нещо и изведнъж да ти светне alert е супер полезно.

(По темата за situational awareness и колко е важно да знаеш какво се случва и
какви са последствията от действията ти мога да направя отделна лекция…

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

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

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

Друго нещо, за което са много полезни (понякога заедно с малко статистически
софтуер) е да се изследват промени в поведението или как определена промяна е
повлияла. Това до колкото знам му казват да сте “data driven”, т.е. като
правите нещо, да гледате дали данните казват, че сте постигнали това, което
искате.

Метриките, които аз събирам са веднъж в секунда, за много различни компоненти
на системата – дискове, процесори, наши процеси, мрежови интерфейси. В момента
са няколко-стотин хиляди точки в секунда, и тук съм показал една примерна –
показва данните за една секунда за един процесор на една машина, заедно с малко
тагове (че например процесорът не се използва от storage системата, та за това
там пише “nosp”) и с timestamp.

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

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

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

Имам любим пример, с cron, който работеше на една секунда и питаше един RAID
контролер “работи ли ти батерията?”. Това караше контролера да блокира всички
операции, докато провери и отговори, и на минутните графики имаше повишение на
латентността на операциите (примерно) от 200 на 270 микросекунди, и никой не
можеше да си обясни защо. На секундните си личеше, точно на началото на всяка
минута, един голям пик, който много бързо ни обясни какво става.

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

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

Също така, не знам дали мога да обясня колко е готино да пускате тестове, да
сте оставили една grafana да refresh-ва веднъж в секунда и почти в реално време
да може да виждате и да показвате на хората как влияят (или не) разлини
действия на системата, от типа на “тук заклахме половината мрежа и почти не се
усети”.

Другото нещо, което се следи е текущото състояние на системата. Това е нещото,
от което се смята статуса на системата в nagios, icinga и други, и от което се
казва “тук това не работи”. Системите, които получават тази информация решават
какви проблеми има, кого и дали да известят и всякакви такива неща.

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

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

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

А какъв е проблема с цялата тази работа, не е ли отдавна решен, може да се питате…

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

Данните са доста. Написал съм едни приблизителни числа, колкото да си проличи
генералния обем на данните, налагат се някакви трикове, така че да не ви удави,
и определено да имате достатъчно място и bandwidth за тях.

Дори като bandwidth не са малко, и хубавото на това, че имаме контрол в/у двете
страни, e че можем да си ги компресираме, и да смъкнем около 5 пъти трафика.
За това ни помага и че изпращаните данни са текстови, CSV или JSON – и двете
доста се поддават на компресия.

Няма някой друг service при нас, който да ползва сравнимо количество internet
bandwidth, дори някакви mirror-и :)

Идващите данни трябва да се съхраняват за някакво време. Добре, че фирмата
се занимава със storage, та ми е по-лесно да си поискам хардуер за целта…

За да можем все пак да съберем всичко, пазим само 2 дни секундни данни и 1
година минутни. InfluxDB се справя прилично добре да ги компресира – мисля, че
в момента пазим общо около двайсетина TB данни само за метрики. За
monitoring данните за debug цели държим пращаните от последните 10 часа, но
това като цяло е лесно разширимо, ако реша да му дам още място, за момента
се събират в около 1.6TiB.
(Това е и една от малкото ми употреби на btrfs, защото изглежда като
единствената достъпна файлова система с компресия… )

Ако погледнем още един път числата обаче, тук няма нищо толкова голямо, че да
не е постижимо в почти “домашни” условия. Гигабит интернет има откъде да си
намерите, терабайтите storage дори на ssd/nvme вече не са толкова
скъпи, и скорошните сървърни процесори на AMD/Intel като цяло могат да вършат
спокойно тая работа. Та това е към обясненията “всъщност, всеки може да си го
направи” :)

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

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

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

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

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

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

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

От какво е сглобена текущата ни система?

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

Беше upgrade-ната от icinga1, която на около 40% от текущото натоварване умря и
се наложи спешна миграция.

InfluxDB, по времето, в което започвах да пиша системата беше най-свястната от
всичките съществуващи time series бази.

Като изключим доста осакатения език за заявки (който се прави на SQL, но е
много далеч и е пълен със странни ограничения), и че не трябва да се приема за
истинска база (много глезотии на mysql/pgsql ги няма), InfluxDB се справя добре
в последните си версии и не може да бъде crash-ната с някоя заявка от
документацията. Основното нещо, което ми се наложи да правя беше да прескоча
вътрешния механизъм за агрегиране на данни и да си напиша мой (който не помня
дали release-нахме), който да може да върши същото нещо паралелно и с още малко
логика, която ми трябваше.

От някакво време има и версия 2, която обаче е променила достатъчно неща, че да
не мога още да планирам миграция до нея.

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

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


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

Дори и да не ползвате стандартните методи, пак по-старите версии на icinga не
се справят с толкова натоварване. При нас преди няколко години надминахме
лимита на icinga1 и изборът беше м/у миграция или клъстериране. Миграцията се
оказа по-лесна (и даде някакви нови хубави функционалност, да не говорим, че се
ползваше вече софтуер от тоя век).

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

Изобщо, технически това един бивш колега го описваше като “десет литра лайна
в пет литра кофа”.

Новата например reload-ва в отделен процес, който не пречи на главния.

Признавам си, не обичам Prometheus. Работи само на pull, т.е. трябва да може да
се свързва до всичките си крайни станции, и като го пуснахме за вътрешни цели с
около 100-200 машини, почна да му става сравнително тежко при проверка на 3
секунди. Също така изобщо няма идеята да се справя с проблеми на internet-а,
като просто интерполира какво се е случило в периода, в който не е имал
свързаност.

Върши работа за някакви неща, но определен е далеч от нашия случай. Според мен
е и далеч от реалността и единствената причина да се ползва толкова е многото
натрупани готови неща под формата на grafana dashboard-и и exporter-и.


Компонентите са в общи лини ясни:

Агентите събират информация и се стараят да ни я пратят;

Получателите я взимат, анализират, записват и т.н.;

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

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

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

Понеже това е писано на perl по исторически причини, беше малко странно да се
направи (понеже select/poll на perl, non-blocking връзки и подобни неща не са
съвсем стандартни), но сега мога да го похваля, че работи много добре и се
справя вкл. и с IPv6 свързаност.

И всичкия код, който получава/прави анализи си е тип ETL
(extract-transform-load). Взима едни данни, дъвче, качва обратно.

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

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

Има и по-интересни такива неща (анализи на латентности от метриките, например),
но нямаше да са добър пример за тук :)

“Липсата на сигнал е сигнал” – можем да знаем, че нещо цялостно или част от
него не ни говори, понеже имаме контрол в/у агентите и кода им, и знаем
кога/как трябва да се държат.

Някой би казал, че това се постига по-добре с активна проверка (“тия хора
пингват ли се?”), но това се оказва много сложно да се прави надеждно, през
интернета и хилядите firewall-и по пътя. Също така, не може да се очаква
интернета на всички да работи добре през цялото време (заради което и всичките
агенти са направени да се борят с това).

Както казах по-рано, Интернета не работи :)

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

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

Системата се грижи всичко останало да се случва съвсем само.

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

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

Самото php се оказва много удобно за подобни цели, основно защото успява да се
справи и с объркани данни (което се случва при разни ситуации) и не убива
всичко, а си се оплаква и свършва повечето работа. Разбира се, това не е
единствения подходящ език, аз лично писах на него, защото ми беше лесен – но
ако искате да сглобите нещо, в наши дни може да го направите почти на каквото и
си искате и да работи. Python и js са някакви основни кандидати (като езици,
дето хората знаят по принцип), само не препоръчвам shell и C :)

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

Monitoring системата се налага да се следи, разбира се. Всичкият софтуер се
чупи, този, който аз съм правил – още повече. По случая други колеги направиха
нещо, което да следи всичките ни вътрешни физически и виртуални машини, и това
между другото следи и големия monitoring :)

Изглежда по коренно различен начин – използва Prometheus, Alertmanager и за
база VictoriaMetrics, което в общи линии значи, че двете системи рядко имат
същите видове проблеми. Също така и допълнителния опит с Prometheus ми показа,
че определено е не е бил верния избор за другата система :)

Може би най-големият проблем, който съм имал е да обясня какво значи текста на
дадена аларма на други колеги. Тук например съм казал “има малък проблем (за
това е WARNING), в услугата bridge, има един счупен, нула игнорирани (по разни
причини) и 2 общо, и този на машина 4 е down” – и това поне трима различни
човека са ми казвали, че не се разбира. Последното ми решение на проблема е да
карам да ми дадат как очакват да изглежда и да го пренаписвам. От една страна,
съобщенията станаха по-дълги, от друга – не само аз ги разбирам :)

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

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

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

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

Решението тук е да се вземат алармите за един ден, и да се види кое от тях е
безсмислено и кое може да се махне за сметка на нещо друго, след което мноооого
внимателно да се провери, че това няма да скрие истински проблем, вероятно с
гледане на всички останали задействания на неприемливото съобщение. Което е
бавна, неблагодарна и тежка работа :)

Не исках да слагам този slide в началото, че сигурно нямаше да остане никой да
слуша лекцията. Оставям го да говори сам за себе си:)