Ta historia opowiada o tym, jak wykorzystujemy kontenery w produkcyjnym środowisku, zwłaszcza w kontekście Kubernetes. Artykuł poświęcony jest zbieraniu metryk i logów z kontenerów oraz budowaniu obrazów.

Jesteśmy firmą fintech Exness, zajmującą się rozwijaniem usług dla handlu online oraz produktów fintech dla B2B i B2C. W naszym dziale R&D działa wiele zespołów, a w dziale rozwoju pracuje ponad 100 osób.
Reprezentujemy zespół odpowiedzialny za platformę do zbierania i uruchamiania kodu przez naszych programistów. W szczególności jesteśmy odpowiedzialni za zbieranie, przechowywanie i udostępnianie metryk, logów i zdarzeń z aplikacji. Aktualnie operujemy na około trzech tysiącach kontenerów Docker w środowisku produkcyjnym, utrzymujemy nasze magazyny big data o pojemności 50 TB i dostarczamy rozwiązania architektoniczne, które opierają się na naszej infrastrukturze: Kubernetes, Rancher i różnych publicznych dostawcach chmury.
Nasza motywacja
Co się pali? Nikt nie potrafi odpowiedzieć. Gdzie jest ognisko? Trudno to zrozumieć. Kiedy zaczęło się palić? Da się to ustalić, ale nie od razu.

Dlaczego jedne kontenery stoją, a inne zawiodły? Który kontener jest winny? Przecież z zewnątrz kontenery są identyczne, a w środku każdy ma swojego Neo.

Nasi programiści to kompetentni ludzie. Tworzą dobre usługi, które przynoszą firmie zyski. Ale zdarzają się wpadki, kiedy kontenery z aplikacjami idą w rozproszenie. Jeden kontener zużywa zbyt dużo CPU, inny - pasmo, trzeci - operacje wejścia-wyjścia, a czwarty w ogóle nie wiadomo, co robi z gniazdami. Wszystko to się psuje, a statek tonie.
Agenci
Aby zrozumieć, co się dzieje wewnątrz, postanowiliśmy umieścić agentów bezpośrednio w kontenerach.

Ci agenci to programy kontrolne, które utrzymują kontenery w takim stanie, aby nie uszkodziły się nawzajem. Agenci są ustandaryzowani, co pozwala ujednolicić podejście do obsługi kontenerów.
W naszym przypadku agenci muszą dostarczać logi w standardowym formacie, tagowane i z throttlingiem. Muszą również zapewniać nam ustandaryzowane metryki, rozszerzalne pod względem aplikacji biznesowych.
Pod agentami należy również rozumieć narzędzia do eksploatacji i konserwacji, które potrafią pracować w różnych systemach orkiestracji, obsługując różne obrazy (Debian, Alpine, Centos itd.).
W końcu agenci muszą wspierać prosty CI/CD, który obejmuje pliki Docker. W przeciwnym razie statek się rozpadnie, ponieważ kontenery zaczną być dostarczane po „krzywych” torach.
Proces budowy i struktura docelowego obrazu
Aby wszystko było znormalizowane i zarządzalne, należy przestrzegać jakiegoś standardowego procesu budowy. Dlatego postanowiliśmy budować kontenery przy pomocy kontenerów - taka oto rekursja.

Tutaj kontenery przedstawione są jako ciągłe kontury. Przy okazji zdecydowaliśmy się umieścić w nich dystrybucje, aby „życie nie wydawało się zbyt proste”. Dlaczego to zrobiono, opowiemy poniżej.
W rezultacie powstało narzędzie do budowy – kontener określonej wersji, który odwołuje się do określonych wersji dystrybucji i określonych wersji skryptów.
Jak go używamy? Mamy Docker Hub, na którym znajduje się kontener. Lustrzymy go do naszego systemu, aby pozbyć się zewnętrznych zależności. Uzyskaliśmy kontener oznaczony kolorem żółtym. Tworzymy szablon, aby zainstalować w kontenerze wszystkie potrzebne nam dystrybucje i skrypty. Po tym zbieramy gotowy obraz do użytku: programiści wkładają do niego kod i jakieś swoje szczególne zależności.
Czym jest dobry takie podejście?
- Po pierwsze, pełna kontrola wersji narzędzi budowlanych – kontener budowy, wersje skryptów i dystrybucji.
- Po drugie, osiągnęliśmy standaryzację: tworzymy szablony w ten sam sposób, obrazy pośrednie i gotowe do użytku.
- Po trzecie, kontenery zapewniają nam przenośność. Dziś używamy Gitlab, a jutro przejdziemy na TeamCity lub Jenkins i dokładnie tak samo będziemy mogli uruchamiać nasze kontenery.
- Po czwarte, minimalizacja zależności. Nie przypadkowo umieściliśmy dystrybucje w kontenerze, bo pozwala to na niewiązanie ich ponownie z Internetem.
- Po piąte, zwiększyła się prędkość budowy – posiadanie lokalnych kopii obrazów pozwala zaoszczędzić czas na pobieraniu, ponieważ mamy lokalny obraz.
Innymi słowy, osiągnęliśmy kontrolowany i elastyczny proces budowy. Używamy tych samych narzędzi do budowy różnych kontenerów z pełnym wersjonowaniem.
Jak działa nasza procedura budowy

Uruchomienie zbioru odbywa się za pomocą jednej komendy, a proces wykonuje się w obrazie (podświetlony na czerwono). Deweloper ma plik Docker (podświetlony na żółto), renderujemy go, zastępując zmienne wartościami. Dodatkowo dodajemy nagłówki i stopki — to są nasi agenci.
Nagłówek dodaje dystrybucje z odpowiednich obrazów. A stopka instaluje nasze usługi, konfiguruje uruchomienie obciążenia, logowanie i inne agenty, zastępuje entrypoint itp.

Długo zastanawialiśmy się, czy potrzebujemy superwizora. Ostatecznie zdecydowaliśmy, że jest nam potrzebny. Wybraliśmy S6. Superwizor zapewnia zarządzanie kontenerem: pozwala na podłączenie się do niego w przypadku awarii głównego procesu oraz umożliwia ręczne zarządzanie kontenerem bez jego ponownego tworzenia. Logi i metryki to procesy uruchamiane wewnątrz kontenera. Również musimy je jakoś kontrolować, co robimy dzięki superwizorowi. W końcu S6 zajmuje się wykonywaniem housekeeping, przetwarzaniem sygnałów i innymi zadaniami.
Ponieważ stosujemy różne systemy orkiestracji, po zbudowaniu i uruchomieniu kontener musi zrozumieć, w jakim środowisku się znajduje, i działać zgodnie z sytuacją. Na przykład:
Pozwala nam to zbudować jeden obraz i uruchamiać go w różnych systemach orkiestracji, przy czym uruchomienie będzie miało na uwadze specyfikę tego systemu orkiestracji.

Dla tego samego kontenera uzyskujemy różne drzewka procesów w Docker i Kubernetes:

Ładunek wykonuje się pod superwizorem S6. Zwróć uwagę na collector i events — to są nasi agenci odpowiedzialni za logi i metryki. W Kubernetes ich nie ma, a w Docker są. Dlaczego?
Jeśli spojrzymy na specyfikację "poda" (od teraz – Kubernetes pod), zobaczymy, że kontener events wykonuje się w podzie, w którym znajduje się oddzielny kontener collector, odpowiedzialny za zbieranie metryków i logów. Możemy skorzystać z możliwości Kubernetes: uruchamianie kontenerów w jednym podzie, w jednolitych przestrzeniach procesowych i/lub sieciowych. Faktycznie wprowadzać swoich agentów i wykonywać różne funkcje. A jeśli ten sam kontener zostanie uruchomiony w Docker, uzyska dokładnie te same możliwości, to znaczy będzie mógł dostarczać logi i metryki, ponieważ agenci będą uruchomieni wewnątrz.
Metryki i logi
Dostarczanie metryków i logów to złożone zadanie. Z jego rozwiązaniem wiąże się kilka aspektów.
Infrastruktura jest tworzona w celu realizacji przydatnych obciążeń, a nie masowego dostarczania logów. Oznacza to, że proces ten powinien być realizowany z minimalnymi wymaganiami dotyczącymi zasobów kontenerów. Staramy się pomóc naszym programistom: „Weź kontener Docker Hub, uruchom go, a my dostarczymy logi”.
Drugim aspektem jest ograniczenie objętości logów. Jeśli w kilku kontenerach dochodzi do sytuacji nagłego wzrostu objętości logów (aplikacja w pętli wyświetla stack-trace), zwiększa to obciążenie CPU, kanałów komunikacyjnych, systemu przetwarzania logów, co wpływa na działanie hosta jako całości oraz inne kontenery na hoście, co czasami prowadzi do „awarii” hosta.
Trzecim aspektem jest potrzeba wspierania jak największej liczby metod zbierania metryk „prosto z pudełka”. Od odczytywania plików i zapytań do punktu końcowego Prometheus, po używanie specyficznych protokołów aplikacji.
Ostatnim aspektem jest konieczność minimalizacji zużycia zasobów.
Wybraliśmy rozwiązanie open-source w Go o nazwie Telegraf. To uniwersalny konektor, który obsługuje ponad 140 rodzajów kanałów wejściowych (input plugins) i 30 rodzajów wyjściowych (output plugins). Został przez nas dostosowany i teraz opowiemy, jak go używamy na przykładzie Kubernetes.

Załóżmy, że programista wdraża obciążenie, a Kubernetes otrzymuje zapytanie o utworzenie podu. W tym momencie dla każdego podu automatycznie tworzony jest kontener o nazwie Collector (używamy webhooka mutacji). Collector to nasz agent. Na starcie ten kontener konfiguruje się do pracy z Prometheus i systemem zbierania logów.
- W tym celu wykorzystuje on adnotacje podu, a w zależności od jej zawartości, tworzy na przykład punkt końcowy (end-point) Prometheus;
- Na podstawie specyfikacji podu i specyficznych ustawień kontenerów decyduje, jak dostarczać logi.
Logi zbieramy przez API Docker: programiści wystarczy, że umieści je w stdout lub stderr, a dalej Collector się tym zajmie. Logi są zbierane w fragmentach z pewnym opóźnieniem, aby zapobiec ewentualnemu przeciążeniu hosta.
Metryki zbieramy w oparciu o instancje obciążenia (procesy) w kontenerach. Wszystko jest oznaczane tagami: namespace, pod i tak dalej, a następnie konwertowane na format Prometheus – i staje się dostępne do zbierania (oprócz logów). Logi, metryki i zdarzenia wysyłamy również do Kafka i dalej:
- Logi są dostępne w Graylog (do analizy wizualnej);
- Logi, metryki i zdarzenia są wysyłane do Clickhouse w celu długoterminowego przechowywania.
Dokładnie tak samo działa to w AWS, tylko zamieniamy Graylog z Kafka na Cloudwatch. Wysyłamy tam logi, a wszystko staje się bardzo wygodne: od razu wiadomo, do którego klastra i kontenera się odnoszą. To samo dotyczy Google Stackdriver. Nasza architektura działa zarówno on-premise z Kafka, jak i w chmurze.
Jeśli nie mamy Kubernetes z podami, schemat staje się nieco bardziej skomplikowany, ale działa na tych samych zasadach.

Wewnątrz kontenera uruchamiane są te same procesy, które są orkiestrzone za pomocą S6. Wszystkie te same procesy są uruchomione w jednym kontenerze.
W konsekwencji,
Stworzyliśmy kompleksowe rozwiązanie do budowy i uruchamiania obrazów w eksploatacji, z opcjami zbierania i dostarczania logów oraz metryk:
- Opracowaliśmy ustandaryzowane podejście do budowy obrazów, na jego podstawie stworzyliśmy szablony CI;
- Agenci do zbierania danych – to nasze rozszerzenia Telegraf. Dobrze je przetestowaliśmy w produkcji;
- Wykorzystujemy mutation webhook do wdrażania kontenerów z agentami w podach;
- Zintegrowaliśmy się z ekosystemem Kubernetes/Rancher;
- Możemy uruchamiać te same kontenery w różnych systemach orkiestracji i uzyskać oczekiwany wynik;
- Stworzyliśmy w pełni dynamiczną konfigurację zarządzania kontenerami.
Współautor: Ilja Prudnikow
Źródło: habr.com
