Jakiś czas temu byłem gościem Damiana Naprawy ze szkoladockera.pl w jego podcaście Więcej niż konteneryzacja, gdzie opowiadałem o możliwościach uruchamiania kontenerów w chmurze Azure. Zapraszam do słuchania.
Jakiś czas temu byłem gościem Damiana Naprawy ze szkoladockera.pl w jego podcaście Więcej niż konteneryzacja, gdzie opowiadałem o możliwościach uruchamiania kontenerów w chmurze Azure. Zapraszam do słuchania.
Dziś w kilku słowach o tym czym jest FinOps, a w kolejnym wpisie jakie narzędzia do tego dostarcza nam Azure.
FinOps to elastyczne podejście do finansów polegające na współpracy działów finansowych z produktowymi, IT oraz z partnerami biznesowymi w celu:
Podobieństwo nazwy do DevOps nie jest przypadkowe, gdyż tak jak DevOps, podejście FinOps łączy najlepsze praktyki i kulturę pracy w celu zwiększenia świadomości organizacji w zakresie kosztów generowanych przez chmurę, efektywniejszego zarządzania oraz generowania zysków poprzez umiejętne zarządzanie finansami.
FinOps wyróżnia 6 pryncypiów (zasad) jakimi należy się kierować wdrażając ten proces:
W podejściu FinOps nie ma już jednego centralnego działu (Procurement), który identyfikuje koszty i je zatwierdza. Zamiast tego mamy wielofunkcyjny zespół tzw. Cloud Cost Center of Excellence łączący świat IT, biznesu i finansów w celu nie tylko optymalizacji kosztów, ale również negocjowania z dostawcami cen i rabatów.
Aby poradzić sobie ze skomplikowanym światem kosztów w chmurze fundacja FinOps zaleca iteracyjne podejście do zarządzania kosztami. To iteracyjne podejście do implementacji FinOps obejmuje trzy główne etapy: informowanie, optymalizacja i eksploatacja.
Mam nadzieję, że ten krótki artykuł przybliżył Wam temat tego czym jest FinOps i dlaczego jest taki ważny, gdy decydujemy się na użycie chmury obliczeniowej. Po szczegóły zapraszam do świetnej książki Cloud FinOps – Collaborative, Real-Time Cloud Financial Management dostępnej na stronie wydawnictwa O’Reilly.
A w kolejnym artykule pokażę, jakie usługi w Azure możemy użyć, aby lepiej kontrolować koszty i nimi odpowiednio zarządzać.
[1] https://www.finops.org/what-is-finops
[2] https://research.aimultiple.com/finops
[3] https://www.cloudability.com/finops
[4] J.R. Storment, Mike Fuller – Cloud FinOps. Collaborative, Real-Time Cloud Financial Management (O’Reilly, 2020)
W poprzednim artykule pokazałem w jaki sposób możemy użyć szablonów ARM do tworzenia infrastruktury w Azure, jako część podejścia Infrastructure as Code (IaC).
W tym artykule chcę przedstawić kolejne narzędzie, które możemy w tym celu wykorzystać – Azure CLI.
Ponieważ celem artykułu jest przybliżenie możliwości narzędzia, a nie kurs jego nauki, po szczegółowe informacje i samouczki zapraszam do oficjalnej dokumentacji.(https://docs.microsoft.com/en-us/cli/azure/get-started-with-azure-cli?view=azure-cli-latest).
Jeżeli prowadzisz firmę i chciałbyś wykorzystać podejście Infrastructure as Code w swoich projektach, a Tobie i Twojemu zespołowi brakuje wiedzy z tego obszaru to mam w swojej ofercie szkoleniowej 2-dniowe warsztaty Infrastruktura jako kod.
Azure CLI (Azure Command Line Interface), jak sama nazwa wskazuje to narzędzie linii poleceń, więc nie spodziewajmy się tutaj udogodnień graficznych, podobnych do tego co oferuje Visual Studio dla szablonów ARM. W przeciwieństwie do deklaratywnego sposobu tworzenia infrastruktury oferowanego przez ARM, Azure CLI wykorzystuje podejście imperatywne, czyli polegające na wydawaniu poleceń. Na przykład:
az group create –l westeurope –n MyResourceGroup
Polecenie to nakazuje utworzenie grupy zasobów o nazwie MyResourceGroup w lokalizacji westeurope.
Ta sama operacja z użyciem szablonów ARM wyglądałaby następująco:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"functions": [],
"variables": {},
"resources": [
{
"name": "MyResourceGroup",
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2019-10-01",
"location": "westeurope",
"dependsOn": [
],
"tags": {
}
}
],
"outputs": {}
}
Składnia poleceń Azure CLI jest typowa dla tego typu narzędzi i składa się z ciągu poleceń i argumentów. Wszystkie instrukcje zaczynamy od polecenia az <grupa_poleceń>
po którym następuje właściwe polecenie, takie jak create
czy list
i przekazanie argumentów do polecenia.
Porównując tworzenie tak prostego zasobu jak Resource Group za pomocą szablonów ARM do tej samej operacji wykonanej za pomocą Azure CLI widzimy, że w Azure CLI jest to jedna komenda, a w szablonie ARM cały zestaw atrybutów i ich wartości. Na pierwszy rzut oka może się wydawać, że szablony ARM dają większą kontrolę i możliwości, co nie do końca jest prawdą, gdyż w Azure CLI mamy do dyspozycji wszystkie udogodnienia znane z konsoli bash, takie jak:
az vm list --query "[?powerState=='VM running'].name" | grep my_vm
running_vm_ids=$(az vm list -d -g my_rg --query "[?powerState=='VM running'].id" -o tsv)
Jeżeli po przeczytaniu tego artykułu masz wątpliwości, które narzędzie jest lepsze, ARM Templates czy Azure CLI, to niestety nie mam dla Ciebie dobrej wiadomości, gdyż nie ma jednoznacznej odpowiedzi na to pytanie. Tak jak z każdym narzędziem, powinniśmy używać tego co jest najlepsze do rozwiązania danego problemu. Przykładowo jedne usługi łatwiej będzie utworzyć za pomocą Azure CLI aniżeli przy pomocy szablonów ARM. Tak samo w drugą stronę. Ja osobiście używam obydwu tych podejść, i tak samo robi większość osób z mojego otoczenia, a trzeba pamiętać, ze nie są to jedyne narzędzia tego typu bo mamy do dyspozycji jeszcze Azure PowerShell i Azure SDKs, którym niedługo poświęcę osobne artykuły.
Czasy w których infrastrukturę IT tworzyło się ręcznie odchodzą do lamusa. Wraz z rozpowszechnieniem się wirtualizacji i praktyki DevOps specjaliści IT dostali szereg narzędzi pozwalających na optymalizację ich pracy. Jednym z nich jest Infrastructure as Code (IaC).
IaC to podejście w którym infrastrukturę IT oraz jej konfigurację opisujemy za pomocą kodu. Daje nam to wiele zalet:
*Oszczędność wynikająca z wdrożenia podejścia IaC nie jest widoczna od razu. IaC to nie tylko filozofia pracy, ale przede wszystkim zestaw narzędzi i bibliotek, które wymagają czasu potrzebnego na ich opanowanie.
Pomimo niewątpliwych zalet wynikających z użycia IaC spotykam się z oporem niektórych programistów i architektów oprogramowania do wykorzystania tego podejścia w swoich projektach? Dlaczego? Nie do końca wiem. Wydawać by się mogło, że takie podejście będzie naturalne dla programistów, ponieważ mają do czynienia z kodem na co dzień, więc ten sposób pracy z infrastrukturą powinien być dla nich chlebem powszednim, ale nie zawsze jest i z jakiegoś powodu tworząc kolejne środowisko wolą klikać next, next, next…
Jeden z powodów upatruję w przeciążeniu mentalnym. Programiści i tak mają już tyle technologii do opanowania, że na samą myśl o nauce kolejnego narzędzia (lub narzędzi!) do automatyzacji infrastruktury (Terraform, Vargant, Puppet, Chef, Ansible, CloudFormation, ARM Templates), żeby wymienić pierwsze z brzegu, zwyczajnie brakuje im chęci. A w kolejce do nauki stoi kolejny framework JS. W końcu większość z nas ma rodziny, hobby i zainteresowania. Żeby nie było, rozwój w branży IT jest ważny i jeżeli chce się być dobrym to nie da się go całkowicie wyeliminować z życia, niemniej trzeba zachować umiar i nie podążać ślepo za nowinkami, rozsądnie dobierając narzędzia i biblioteki do nauki, by znaleźć równowagę pomiędzy rozwojem zawodowym, a życiem osobistym.
Tym wpisem chcę rozpocząć serię artykułów pokazujących podejście IaC do tworzenia środowisk w Azure, bez obaw o przeciążenie mentalne naszych umysłów w najgorszym wypadku skutkujące wypaleniem zawodowym.
Na początek na warsztat weźmiemy szablony ARM (ang. ARM Templates).
ARM (Azure Resource Manager) Template to plik zawierający opis infrastruktury Azure w postaci JSON. Przykład takiego pliku możemy znaleźć np. tutaj: https://github.com/Azure/azure-quickstart-templates/blob/master/101-webapp-linux-managed-postgresql/azuredeploy.json. Ten szablon utworzy aplikację NodeJS hostowaną za pomocą usługi AppService oraz bazę danych PostgreSQL. Opisywanie infrastruktury za pomocą składni JSON może wydawać się nieco skomplikowane, w końcu trzeba pamiętać te wszystkie atrybuty i ich składnię, a jest co bo to co widzimy w podlinkowanym szablonie to tylko wycinek atrybutów jakimi możemy opisać AppService. Pełna składnia i specyfikacja zasobu aplikacji dostępna jest tutaj: https://docs.microsoft.com/en-us/azure/templates/microsoft.web/sites a gdzie jeszcze specyfikacja bazy danych?
Na szczęście Visual Studio dostarcza nam zestaw szablonów projektów, które możemy wykorzystać do pracy z Azure. VS Code ma podobne udogodnienia, niemniej VS Code jest nastawiony na pracę z edytorem tekstowym i terminalem, dlatego temat VS Code i ARM Templates poruszę w jednym z kolejnych wpisów. Ponieważ chcemy zbudować infrastrukturę wybieramy szablon projektu Azure Resource Group.
Po podaniu informacji o projekcie takich jak nazwa i lokalizacja zostaniemy poproszeni o wybór szablonu ARM. Możemy zacząć od gotowca lub zacząć od zera i wybrać pusty.
W szablonie ARM wyróżniamy następujące sekcje:
W sekcji parameters możemy zdefiniować parametry, z których będziemy korzystać w dalszej części szablonu, takie jak nazwa aplikacji, lokalizacja, konto administratora, rozmiar bazy danych, pricing plan (darmowy, płatny), itp.
Jak widzimy do utworzenia wartości parametru, możemy wykorzystać wbudowane funkcje takie jak concat()
czy uniqueString()
. Pełną listę funkcji możemy znaleźć w dokumentacji https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions. Do parametrów możemy następnie odwoływać z pozostałych miejsc szablonu.
Sekcja variables służy do definiowana zmiennych, czyli wartości do których możemy odwołać się w takich sekcjach jak resources czy outputs. W sekcji variables, podobnie jak w parameters możemy korzystać z wbudowanych funkcji. Co bardziej doświadczeni czytelnicy zauważą, że obydwie sekcje mogą realizować te same funkcjonalności i zadać pytanie jaka jest różnica i kiedy używać jednego lub drugiego? Odpowiedź nie jest prosta. W skrócie moglibyśmy powiedzieć, że sekcja parameters może przyjmować wartości spoza szablonu. To znaczy, że jeżeli szablon używa wartości przyjmowanych z zewnątrz, takie których nie chcemy umieszczać w szablonie bo zależą od konfiguracji czy środowiska: url-e, nazwy użytkowników, hasła, klucze dostępowe, itp, to powinniśmy je zdefiniować w sekcji parameters, gdyż wartości te są dostarczanie dynamicznie w trakcie wdrażania szablonu.
Z kolei sekcji variables powinniśmy używać do przechowywania wartości generowanych na podstawie parametrów i gdy chcemy skorzystać z funkcji, aby wykonać jakąś logikę. Spójrzmy na przykładowy szablon, żeby rozwiać wszelkie wątpliwości:
{
"parameters": {
"application": { ... },
"businessUnit": { ... },
"environment": { ... }
},
"variables": {
"webApp": {
"name": "[concat('app-', parameters('application'), '-', parameters(businessUnit), '-', parameters('environment'))]"
}
},
"resources": [
{
"type": "Microsoft.Web/sites",
"name": "[variables('webApp').name]",
...
}
]
}
W powyższym szablonie wywołanie funkcji [variables('webApp').name
spowoduje utworzenie nazwy aplikacji o następującym formacie:
app-application-businessUnit-environment
Przykładowa nazwa aplikacji mogłaby wyglądać wtedy tak:
app-wordpress-marketing-dev
Ostatnią sekcją szablonów ARM którą chciałbym pokrótce omówić to resources, czyli serce naszego szablonu. Jak sama nazwa wskazuje to w tym miejscu definiujemy zasoby Azure do stworzenia oraz ich pożądaną konfigurację (ang. Desired State Configuration).
Jak zauważyłem wcześniej ilość atrybutów, którymi możemy opisać zasoby Azure może przyprawiać o ból głowy i powodować trudności w odpowiedzi na pytanie, czy w swoim szablonie powinienem użyć wszystkich atrybutów, czy może wystarczy tylko kilka z nich? Czy muszę mieć pod ręką link do dokumentacji każdego zasobu, żeby wiedzieć jakie atrybuty posiada? Otóż nie do końca. Visual Studio dostarcza okno o nazwie JSON Outline. Jak możemy zobaczyć poniżej wyświetla ono strukturę szablonu w nieco bardziej przystępny sposób.
Dzięki temu oknu nie tylko możemy w łatwy sposób nawigować po szablonie czy widzieć z jakich elementów się składa, ale również dodawać nowe zasoby. W tym celu klikamy prawym klawiszem myszki na węzeł resources i wybieramy Add New Resource.
Wybieramy zasób z listy, np Redis Cache i klikamy Add.
Po kliknięciu Add zobaczymy, że w szablonie pojawił się nowy zasób:
Co bardziej dociekliwi czytelnicy zauważą, że wraz z pojawieniem się nowego zasobu, pojawiły się również odniesienia do parametrów reprezentujących wartości dla niektórych atrybutów. Jak możemy się domyśleć Visual Studio dodał odpowiednie wpisy w sekcji parameters za nas:
Nam jedynie pozostaje przekazać odpowiednie wartości do parametrów i voila – infrastruktura potrzebna do zbudowania cache aplikacji opartego o Redis gotowa.
Po przygotowaniu szablonu nie pozostaje nam nic innego jak wdrożyć go na środowisko w chmurze. W tym celu w Solution Explorer klikamy prawym klawiszem myszki i wybieramy Deploy > New. W kolejnym oknie wybieramy subskrypcję, resource group oraz szablon i klikamy Deploy.
W kolejnym oknie mamy możliwość przekazania parametrów do szablonu:
Po kliknięciu Save rozpocznie się proces deploymentu, czyli tworzenia zdefiniowanej infrastruktury w Azure wraz z pożądaną konfiguracją.
Mam nadzieję, że tym wpisem udało mi się pokazać, że mając pod ręką odpowiednie narzędzia, tworzenie szablonów ARM nie jest takie trudne jak może się z początku wydawać. Oczywiście zaprezentowane przykłady stanowią niewielki wycinek możliwości jakie dają szablony ARM (pełna specyfikacja znajduje się pod adresem https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/) niemniej chciałem Cię zachęcić Drogi Czytelniku do spróbowania tego podejścia i poeksperymentowania we własnych zakresie.
Jeżeli prowadzisz firmę i chciałbyś wykorzystać podejście Infrastructure as Code w swoich projektach, a Tobie i Twojemu zespołowi brakuje wiedzy z tego obszaru to mam w swojej ofercie szkoleniowej 2-dniowe warsztaty Infrastruktura jako kod w których uczestnicy poznają teorię, narzędzia wspierające IaC oraz samodzielnie piszą kod wdrażający przykładowe rozwiązania w Azure – od małych środowisk obejmujących aplikację i bazę danych do średniej wielkości systemów składających się z takich usług jak: AzureSQL, kontenery, Azure Search, App Service, CDN, Computer Vision, Storage Account, Azure Front Door, Azure Functions).
Ze względu na obecną sytuację z COVID-19 szkolenia prowadzone są online.
Coraz więcej firm decyduje się na implementację bądź migrację swoich systemów do chmury, często w oparciu o architektury rozproszone jak popularne mikroserwisy. Czy zasadnie czy nie i z jakim skutkiem to opowieść na inny wpis. Niemniej jednak, takie sytuacje mają miejsce i jako konsultant oraz architekt, gdy rozmawiam z innymi programistami czy osobami odpowiedzialnymi za biznesy w firmach dla których pracuję, zauważam, że o ile większość moich rozmówców ma świadomość tego jak zbudować taki system z kilku mniejszych usług biznesowych, będących odzwierciedleniem kontekstów ograniczonych w organizacji (ang. Bounded Contexts) i jak połączyć je w całość, o tyle gdy przychodzi temat raportów i szeroko pojętego Business Intelligence to sprawy mają się różnie. W tym wpisie chciałbym pokazać z jakimi najczęściej wdrażanymi rozwiązaniami się spotykam, jakie mają wady/zalety oraz jak można to zrobić lepiej używając oczywiście chmury 😊.
Jak sama wskazuje to rozwiązanie polega na wprowadzeniu do systemu nowej usługi raportowej, której zadaniem jest wyciąganie danych z pozostałych usług i łączenie ich (agregacja) w jeden model danych, który zostanie użyty do wygenerowania raportu. Przykładowy proces został przedstawiony na poniższym diagramie.
Pomimo prostoty tego rozwiązania, największą jego wadą jest komunikacja synchroniczna. Serwis raportowy musi odpytać API każdej usługi, której dane są potrzebne do wygenerowania raportu. W naszym przypadku są to dwie usługi: Orders i Customers, ale w rzeczywistym systemie z pewnością będzie ich więcej. Ponieważ są to żądania synchroniczne (tutaj HTTP, ale może to też być gRPC lub SOAP zwłaszcza gdy organizacja używa systemów legacy), to musimy czekać na zakończenie każdego z nich i następnie poskładać wszystkie dane w jedną całość co będzie miało duży wpływ na wydajność i responsywność systemu. Raz, że będziemy tracić czas na oczekiwanie na pobranie danych, transformację do modelu raportowego i generowanie wynikowego raportu, a dwa, że będziemy niepotrzebnie obciążać inne usługi poprzez każdorazowe odpytywanie ich API. Oczywiście w niedużych systemach, gdzie raporty nie są złożone i nie są generowane zbyt często to rozwiązanie się sprawdzi bardzo dobrze. Niestety wraz z rozwojem systemu, przyrostem użytkowników i wzrostem złożoności raportów będziemy coraz bardziej obserwować spadek wydajności. Jedni w takiej sytuacji radzą sobie poprzez skalowanie systemu w górę lub wszerz, inni decydują się na zmianę architektury i wdrożenie kolejnych usług, tym razem odpowiedzialnych za wyciąganie danych i zapisywanie ich w dedykowanej bazie raportowej.
To podejście zmienia sposób komunikacji usługi raportowej z pozostałymi poprzez wprowadzenie dodatkowych komponentów, tak zwanych data workerów, których zadaniem jest wyciąganie danych z innych usług i zapisywanie ich w dedykowanej bazie, skąd są później odczytywane przez usługę raportową przy generowaniu raportu. Dzięki takiemu rozluźnieniu komunikacji, usługa raportowa nie musi tracić czasu na odpytywanie innych usług i agregację danych tylko może się skupić na generowaniu raportu na podstawie jednego źródła, zawierającego wszystkie potrzebne informacje.
Data workery pozwalają również na filtrowanie, oczyszczanie i normalizację danych, przez co usługa raportowa nie musi robić później skomplikowanych zapytań. Jeżeli komuś skojarzyło się to z procesem ETL (Extract, Transform, Load) to bardzo dobrze bo koncepcyjnie jest to uproszczona implementacja tego procesu.
Innym podejściem do tematu raportów jest architektura oparta o zdarzenia. Tutaj nie mamy usług, które odpytują inne usługi o dane, tylko usługi same powiadamiają siebie nawzajem o tym, że coś się wydarzyło. Koncepcyjnie rozwiązanie możemy zaprezentować następująco:
Zacznijmy od tego czym jest zdarzenie. Zdarzenie modeluje sytuację która miała miejsce w przeszłości i się zakończyła – jest faktem. Przykładowymi zdarzeniami w systemie są sytuacje gdy:
Takie zdarzenia niosą ze sobą ogrom informacji. Odpowiednio zaprojektowane usługi zorientowane na zdarzenia pozwalają na analizowanie historii działania usługi (event log), śledzenie aktywności, czy nawet przywracanie stanu usługi do momentu z przeszłości. Architektura oparta o zdarzenia (ang. Event Driven Architecture) jest szeroko stosowana w wysokowydajnych systemach o architekturze mikrousługowej do komunikacji usług między sobą. Usługi zamiast komunikować się synchronicznie poprzez HTTP, SOAP lub gRPC, publikują i subskrybują zdarzenia (Publish/Subscribe Pattern). W tym celu wymagany jest komponent, który będzie obsługiwał wiadomości, zwykle jest to message broker (RabbitMQ, ActiveMQ, Azure Service Bus itd.), ale może też to być magazyn zdarzeń (ang. Event Store) taki jak Kafka.
Implementacja architektury opartej o zdarzenia nie jest łatwa. Wymaga bezpiecznej i niezawodnej infrastruktury – awaria Message Brokera lub Event Store uziemia cały system. Bardzo łatwo o duplikację zdarzeń np. w wyniku awarii którejś z usług. Przykładowo usługa reagująca na zdarzenie obciążające użytkownika finansowo, może otrzymać to samo zdarzenie dwa razy i naliczyć opłaty podwójnie. Do tego dochodzą problemy związane ze spójnością danych (Eventual Consistency), transakcyjnością, czy orkiestracją lub choreografią zdarzeń w procesy biznesowe.
Oczywiście nikt z nas nie zdejmie odpowiedzialności za nieprawidłowe zaprojektowanie systemu w wyniku którego może dojść do błędów w systemie, niemniej jednak możemy część obowiązków związanych z utrzymaniem i zarządzaniem infrastrukturą przerzucić na kogoś innego, a mianowicie dostawcę chmury. Obecnie każda wiodąca chmura posiada usługi pozwalające na implementację rozwiązań w architekturze opartej o zdarzenia przy użyciu bezpiecznej, odpornej na awarie i skalowalnej infrastruktury. My w tym czasie zamiast skupiać się na zapewnieniu ciągłości działania i niezawodności infrastruktury możemy skupić się na właściwym modelowaniu i implementacji procesów biznesowych, wykorzystując takie narzędzia jak Event Storming, Domain Driven Design czy Test Driven Development.
Przykładowe wdrożenie usługi raportowej do systemu uruchomionego w MS Azure i opartego o architekturę mikrousługową mogłoby wyglądać następująco:
W tym rozwiązaniu usługi komunikują się ze sobą za pomocą usługi Event Grid w modelu Publish/Subscribe. Dzięki temu, wykorzystujemy istniejącą infrastrukturę komunikacyjną i podpinamy kolejną usługę – Reporting Service, której głównym zadaniem będzie nasłuchiwanie na zdarzenia z pozostałych usług i aktualizowanie danych raportowych na podstawie przychodzących zdarzeń z pozostałych usług. Dane te mogą potem zostać wykorzystane przez narzędzia do wizualizacji danych jak SQL Server Reporting Services czy PowerBI. Dzięki zastosowaniu komunikacji asynchronicznej w modelu Publish/Subscribe pomiędzy usługami, jest to stosunkowo mało inwazyjne rozwiązanie, które w niewielkim stopniu wymaga modyfikacji pozostałych usług.
Rozwiązanie to, mimo, że relatywnie proste w implementacji ma swoje ograniczenia, mianowicie usługa raportująca jest odpowiedzialna za przetwarzanie napływających zdarzeń, to znaczy filtrowanie, walidację, transformację i zapis danych. W jednym z kolejnym wpisów zobaczymy w jaki sposób możemy wykorzystać usługi analityczne i big data MS Azure do budowy wysokowydajnego silnika raportowego wykorzystującego strumienie zdarzeń, analizę i wizualizację danych w trybie rzeczywistym.
Do usłyszenia!