
Pierwszym krokiem w rozmieszczaniu w Kubernetes jest umieszczenie aplikacji w kontenerze. W tej serii omówimy, jak stworzyć obraz małego i bezpiecznego kontenera.
Dzięki Dockerowi, tworzenie obrazów kontenerów nigdy nie było tak proste. Wskaźnik obraz podstawowy, dodaj swoje zmiany i stwórz kontener.

Chociaż ta technika świetnie nadaje się na początek, używanie domyślnych obrazów podstawowych może prowadzić do niebezpiecznej pracy z dużymi obrazami, pełnymi luk w zabezpieczeniach.
Ponadto, większość obrazów w Dockerze korzysta jako obraz podstawowy z Debiana lub Ubuntu, co zapewnia doskonałą kompatybilność i łatwą adaptację (plik Docker zajmuje zaledwie dwie linijki kodu), ale podstawowe obrazy potrafią dodać setki megabajtów dodatkowego obciążenia do Twojego kontenera. Na przykład, prosty plik aplikacji node.js Go 'hello-world' zajmuje około 700 megabajtów, podczas gdy rozmiar samej aplikacji to tylko kilka megabajtów.

W ten sposób całe to dodatkowe obciążenie to pusta strata miejsca cyfrowego i doskonałe schronienie dla luk i błędów w systemie zabezpieczeń. Dlatego przyjrzyjmy się dwóm sposobom na zmniejszenie rozmiaru obrazu kontenera.
Pierwszy to użycie małych obrazów podstawowych, a drugi to zastosowanie wzoru projektowego Builder Pattern. Użycie mniejszych obrazów podstawowych to prawdopodobnie najprostszy sposób na zmniejszenie rozmiaru swojego kontenera. Prawdopodobnie Twój język programowania lub stos, którego używasz, zapewnia oryginalny obraz aplikacji znacznie mniejszy niż obraz domyślny. Przyjrzyjmy się naszemu kontenerowi node.js.

Domyślnie rozmiar podstawowego obrazu node:8 w Dockerze wynosi 670 MB, a rozmiar node:8-alpine to zaledwie 65 MB, czyli 10 razy mniej. Korzystając z mniejszego podstawowego obrazu Alpine, znacznie zmniejszysz rozmiar swojego kontenera. Alpine to mała i lekka dystrybucja Linuxa, która cieszy się dużą popularnością wśród użytkowników Dockera, ponieważ jest kompatybilna z wieloma aplikacjami, zachowując przy tym niewielki rozmiar kontenerów. W przeciwieństwie do standardowego obrazu Docker „node”, „node:alpine” usuwa wiele plików pomocniczych i programów, pozostawiając tylko te, które są wystarczające do uruchomienia twojej aplikacji.
Aby przejść do mniejszego podstawowego obrazu, po prostu zaktualizuj plik Docker, aby rozpocząć pracę z nowym obrazem podstawowym:

Teraz, w przeciwieństwie do starego obrazu onbuild, musisz skopiować swój kod do kontenera i zainstalować wszystkie zależności. W nowym pliku Docker kontener zaczyna się od obrazu node:alpine, następnie tworzy katalog dla kodu, instaluje zależności za pomocą menedżera pakietów NPM i w końcu uruchamia server.js.

Dzięki tej aktualizacji otrzymujesz kontener o 10 razy mniejszym rozmiarze. Jeśli twój język programowania lub stos nie ma funkcji zmniejszenia podstawowego obrazu, użyj Alpine Linux. Daje to również możliwość pełnej kontroli nad zawartością kontenera. Użycie podstawowych obrazów o małym rozmiarze to doskonały sposób na szybkie tworzenie małych kontenerów. Jednak można osiągnąć jeszcze większe zmniejszenie, korzystając ze Wzoru Budowniczego.

W językach interpretowanych kod źródłowy najpierw jest przekazywany do interpretera, a następnie bezpośrednio wykonywany. W językach kompilowanych kod źródłowy jest wcześniej przetwarzany na kod skompilowany. W tym przypadku kompilacja często wykorzystuje narzędzia, które tak naprawdę nie są potrzebne do uruchomienia kodu. Oznacza to, że możesz całkowicie usunąć te narzędzia z finalnego kontenera. Do tego można wykorzystać Wzór Budowniczy.

Kod jest tworzony w pierwszym kontenerze i kompilowany. Następnie skompilowany kod jest pakowany do finalnego kontenera bez kompilatorów i narzędzi potrzebnych do kompilacji tego kodu. Przejdźmy przez ten proces na przykładzie aplikacji Go. Najpierw przejdziemy od obrazu onbuild do Alpine Linux.

W nowym pliku Docker kontener zaczyna się od obrazu golang:alpine. Następnie tworzy katalog dla kodu, kopiuje go do źródła, kompiluje ten kod źródłowy i uruchamia aplikację. Ten kontener jest znacznie mniejszy niż kontener onbuild, ale nadal zawiera kompilator i inne narzędzia Go, które tak naprawdę nie są nam potrzebne. Dlatego po prostu wydobądźmy skompilowany program i umieśćmy go w własnym kontenerze.

Możesz zauważyć coś dziwnego w tym pliku Docker: zawiera on dwie linie FROM. Pierwsza część z 4 linii wygląda dokładnie tak samo jak poprzedni plik Docker, z wyjątkiem tego, że używa słowa kluczowego AS, aby nadać nazwę temu etapowi. W następnej sekcji znajduje się nowa linia FROM, która pozwala rozpocząć nowy obraz, przy czym zamiast obrazu golang:alpine jako bazowego obrazu użyjemy Raw alpine.
Raw Alpine Linux nie ma żadnych zainstalowanych certyfikatów SSL, co spowoduje awarię większości wywołań API przez protokół HTTPS, dlatego zainstalujmy kilka podstawowych certyfikatów CA.
A teraz najciekawsze: aby skopiować skompilowany kod z pierwszego kontenera do drugiego, możemy po prostu użyć komendy COPY, umieszczonej w 5. linii drugiej sekcji. Skopiuje ona tylko jeden plik aplikacji i nie naruszy narzędzi pomocniczych Go. Nowy wieloetapowy plik Docker będzie miał obraz kontenera o wielkości zaledwie 12 megabajtów, podczas gdy pierwotny obraz kontenera miał 700 megabajtów, co stanowi dużą różnicę!
Tak więc, używanie małych bazowych obrazów i wzorca Builder to doskonałe sposoby na tworzenie kontenerów o znacznie mniejszych rozmiarach bez dużego nakładu pracy.
Możliwe, że w zależności od stosu aplikacji istnieją dodatkowe sposoby na zmniejszenie rozmiaru obrazu i kontenera, ale czy naprawdę małe kontenery mają wymierną przewagę? Rozważmy dwa aspekty, w których małe kontenery są niezwykle skuteczne – to wydajność i bezpieczeństwo.
Aby ocenić wzrost wydajności, przyjrzyjmy się czasowi trwania procesu tworzenia kontenera, umieszczania go w rejestrze (push) i następnego pobierania z niego (pull). Możesz zauważyć, że kontener mniejszego rozmiaru ma niezaprzeczalną przewagę w porównaniu do kontenera większego.

Docker będzie buforować warstwy, więc kolejne kompilacje będą wykonywane bardzo szybko. Jednak w wielu systemach CI, które są używane do kompilacji i testowania kontenerów, warstwy nie są buforowane, więc tutaj istnieje znaczna oszczędność czasu. Jak widać, czas budowy kontenera dużego rozmiaru w zależności od mocy twojego komputera wynosi od 34 do 54 sekund, a przy użyciu kontenera zmniejszonego za pomocą wzorca Builder – od 23 do 28 sekund. Dla takich operacji wzrost wydajności wynosi 40-50%. Dlatego po prostu pomyśl, ile razy tworzysz i testujesz swój kod.
Po tym, jak kontener zostanie zbudowany, musisz umieścić jego obraz (push container image) w rejestrze kontenerów, aby następnie użyć go w swoim klastrze Kubernetes. Zalecam użycie rejestru kontenerów Google.

Korzystając z Google Container Registry (GCR), płacisz tylko za "surowe" przechowywanie i sieć, a dodatkowe opłaty za zarządzanie kontenerami nie są pobierane. Jest to poufne, bezpieczne i bardzo szybkie. GCR wykorzystuje wiele trików, aby przyspieszyć operację pull. Jak widać, umieszczenie obrazu kontenera Docker Container Image przy użyciu go:onbuild w zależności od wydajności komputera zajmie od 15 do 48 sekund, a ta sama operacja z kontenerem mniejszego rozmiaru – od 14 do 16 sekund, przy czym dla mniej wydajnych maszyn przewaga w szybkości operacji wzrasta trzykrotnie. Dla dużych maszyn czas jest mniej więcej taki sam, ponieważ GCR wykorzystuje globalny cache dla wspólnej bazy obrazów, co oznacza, że nie musisz ich w ogóle pobierać. W komputerach o małej mocy CPU jest wąskim gardłem, dlatego przewaga używania małych kontenerów jest tutaj znacznie bardziej odczuwalna.
Jeśli korzystasz z GCR, zdecydowanie zalecam zastosowanie Google Container Builder (GCB) jako części twojego systemu budowy.

Jak widać, jego zastosowanie pozwala osiągnąć znacznie lepsze wyniki w skracaniu czasu operacji Build+Push niż nawet w przypadku wydajnej maszyny – w tym przypadku proces budowania i przesyłania kontenerów na hosta przyspiesza prawie dwukrotnie. Co więcej, każdego dnia otrzymujesz 120 minut budowy za darmo, co w większości przypadków zaspokaja potrzeby tworzenia kontenerów.
Następnie przechodzimy do najważniejszej metryki wydajności – prędkości pobierania kontenerów Pull. I jeśli nie zależy Ci specjalnie na czasie potrzebnym na operację push, to długość procesu pull poważnie wpływa na ogólną wydajność systemu. Załóżmy, że masz klaster z trzema węzłami i jeden z nich ulega awarii. Jeśli korzystasz z systemu zarządzania, na przykład Google Kubernetes Engine, automatycznie zastąpi on uszkodzony węzeł nowym. Jednak ten nowy węzeł będzie całkowicie pusty, a Ty będziesz musiał przenieść do niego wszystkie swoje kontenery, aby mógł rozpocząć pracę. Jeśli operacja pull będzie wystarczająco długa, przez cały ten czas Twój klaster będzie pracować z mniejszą wydajnością.
Istnieje wiele przypadków, kiedy może się to zdarzyć: dodanie nowego węzła do klastra, aktualizacja węzłów czy nawet przełączenie na nowy kontener do wdrożenia. W ten sposób minimalizacja czasu pobierania pull staje się kluczowym czynnikiem. Niezaprzeczalne jest to, że mały kontener pobiera się znacznie szybciej niż duży. Jeśli używasz kilku kontenerów w klastrze Kubernetes, oszczędność czasu może być bardzo znaczna.

Zobacz porównanie: operacja pull przy pracy z małymi kontenerami zajmuje od 4 do 9 razy mniej czasu w zależności od mocy maszyny, niż ta sama operacja z wykorzystaniem go:onbuild. Korzystanie z ogólnych podstawowych obrazów kontenerów małych rozmiarów znacząco przyspiesza czas i prędkość, z jakimi nowe węzły Kubernetes mogą być wdrażane i łączyć się z internetem.
Zastanówmy się nad kwestią bezpieczeństwa. Uważa się, że mniejsze kontenery są znacznie bezpieczniejsze niż dużе, ponieważ mają mniejszą powierzchnię ataku. Czy to prawda? Jedną z najbardziej użytecznych funkcji Google Container Registry jest możliwość automatycznego skanowania twoich kontenerów pod kątem podatności. Kilka miesięcy temu stworzyłem zarówno kontenery onbuild, jak i wieloetapowe, więc przyjrzyjmy się, czy są tam jakieś słabe miejsca.

Wynik jest zdumiewający: w małym kontenerze znaleziono tylko 3 średnie podatności, natomiast w dużym – 16 krytycznych i 376 innych podatności. Przyglądając się zawartości dużego kontenera, widać, że większość problemów bezpieczeństwa nie ma nic wspólnego z naszą aplikacją, a dotyczy programów, których nawet nie używamy. Dlatego gdy ludzie mówią o dużej powierzchni ataku, mają na myśli właśnie to.

Wniosek jest oczywisty: twórz małe kontenery, ponieważ dają one realne korzyści w zakresie wydajności i bezpieczeństwa twojego systemu.

Trochę reklamy 🙂
Dziękujemy, że jesteś z nami. Podobają Ci się nasze artykuły? Chcesz zobaczyć więcej interesujących materiałów? Wspieraj nas składając zamówienie lub polecając nas znajomym, , unikatowy odpowiednik serwerów entry-level, który został stworzony przez nas dla Ciebie: (dostępne opcje z RAID1 i RAID10, do 24 rdzeni i do 40GB DDR4).
Dell R730xd dwa razy tańszy w centrum danych Equinix Tier IV w Amsterdamie? Tylko u nas w Holandii! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 dolarów! Czytaj o tym
Źródło: habr.com
