Distroless Docker Images vs Alpine Linux

Distroless Images

Na początku, gdy zaczęto używać kontenerów, większość z nas traktowała je jako bardziej wydajne VM-ki. Jeżeli spojrzymy na obrazy na Docker Hubie, publikowane przez dostawców systemów operacyjnych, możemy dostrzec, że w większości przypadków kopiują oni do obrazów to, co do tej pory dostarczali jako cały system operacyjny. Zwykle, obrazy są okrajane tylko o niektóre, niepotrzebne ich zdaniem paczki / binarki.

Pomimo tego, obrazy nadal są za ciężkie, zajmując na dysku setki megabajtów. To spowodowało wzrost popularności dystrybucji takich jak Alpine. Czy kiedykolwiek wcześniej wykorzystywałeś Alpine, zanim zaczęto go używać w obrazach dockerowych? Ja nie.

Dlaczego zaczęto stosować Alpine Linux do obrazów bazowych?

Głównym powodem, dla którego zaczęto na potęgę wykorzystywać Alpine, jest oczywiście jego rozmiar. 5 MB — tyle waży obraz Alpine. W porównaniu do np. Debiana (125 MB) lub Ubuntu (188MB) jest to spora różnica. W pewnym momencie, z uwagi na rozmiar obrazów, Docker Hub zaczął mieć problemy wydajnościowe. Te problemy z czasem zostały zażegnane, lecz popularność Alpine nadal rosła.

Pojawia się zatem pytanie. Co jest złego w stosowaniu Alpine Linux?

Docker alpine

Wady Alpine Linux

Pomimo że Alpine Linux jest ultralekki, nadal jego zachowanie może być porównane do wirtualnej maszyny. Dlaczego?

Stosując obraz Alpine, nadal możemy korzystać z shell’a a nawet z managera pakietów. No właśnie, manager pakietów…

1. Brakujące pakiety

Zdarzały się przypadki, w których brakowało podstawowych pakietów. Coś, co innych dystrybucjach takich jak Debian mamy pewne. Przykład – brakująca paczka MySQL.

2. Różnice w repozytoriach dla różnych wersji Alpine

Każda wersja Alpine Linux posiada własny manager pakietów i niestety nie przechowuje archiwalnych wersji. Przykładowo w wersji Alpine 3.5, dostępna paczka Node.js może być w wersji 2.0, w Alpine 3.4 będzie już w wersji 1.9. Chcąc używać Node.js w wersji 1.9, musimy opierać się o starszą wersje Alpine.

3.  Brak glibc

Jeśli cokolwiek w życiu kompilowałeś (np. na studiach) to zakładam że w 90% przypadków korzystałeś z glibc. Alpine zastąpił glibc własnym stworem o nazwie musl. Osobiście, natknąłem się na ten problem w momencie w którym aplikacja webowa uruchomiona w kontenerze na bazie Debiana działała poprawnie, a wewnątrz kontenera na bazie Alpine, już nie

Jeżeli chcesz by Twoja aplikacja działała poprawnie, musisz skompilować ją w środowisku wykorzystującym musl.

4. Stabilność

Obecnie obraz dockerowy Alpine Linux jest rozwijany jako projekt open-source.

Poszperałem nieco tu i ówdzie i co znalazłem?

Okazuje się, że aktywnie rozwija go zaledwie kilka osób. A do zatwierdzenia zmiany w repozytorium wystarczą dwie (!), powtarzam, dwie osoby (na dzień pisania tego artykułu). Przykład pull requesta — TUTAJ

Czy w swoich obrazach, chcesz bazować na czymś, czego jakość sprawdza dwie osoby?

Zauważyłem też, że repozytorium z powyższego linka, w którym widnieją osoby rozwijające, zostało przeniesione do https://github.com/alpinelinux/docker-alpine, gdzie kontrybutorem jest tylko jedna osoba. Na Docker Hubie widnieje już adres nowego repozytorium Githuba. Dlaczego?

5. Wydajność

Szukając informacji o wydajności Alpine, natrafiłem na post na Reddicie, w którym mowa o aplikacji Node.js. Jeden z użytkowników twierdzi, że jego aplikacja działała 15% wolniej używając Alpine jako obraz bazowy w porównaniu do Debiana.

Inny przykład to aplikacja w Pythonie. Bardzo ciekawy artykuł opisujący wady Alpine Linux w porównaniu do Ubuntu.

6. Bezpieczeństwo

Nie wiele narzędzi do skanowania obrazów “radzi” sobie z obrazami bazującymi na Alpine. Jeżeli skanowałeś obrazy narzędziami takimi jak Anchore czy Clair, które nie wykryły żadnych podatności – polecałbym porównać wyniki skanowania z wynikami uzyskanymi narzędziem trivy.


Distroless Docker Images

W związku z problemami z rozmiarem obrazów oraz wyżej wymienionymi wadami Alpine Linux nie kto inny jak firma Google, zaprezentowała światu Distroless Images.

Nie muszę chyba przypominać, że to jak zachowuje się nasz kontener, zależy od obrazu, na podstawie którego został utworzony. Co zatem takiego kryje się pod słowem distroless?

Ideą distroless jest zbudowanie minimalnego obrazu zawierającego tylko aplikację i jej zależności.

$ docker pull gcr.io/distroless/base

Rozmiar powyższego obrazu to tylko ~16 MB.

Obraz jest pozbawiony managera pakietów, shell’a (!) oraz innych programów, które możesz znaleźć w tradycyjnej dystrybucji.

UWAGA: brak shella oznacza, że nie mamy możliwości wejścia do kontenera poprzez wykonanie polecenia docker exec -it <container> sh

$ docker run -it gcr.io/distroless/base /bin/sh
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: 
starting container process caused 
"exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown

Brak shell’a został wprowadzony celowo, z uwagi na bezpieczeństwo. Często przekazujemy sekrety do kontenera jako zmienne środowiskowe. Może być to na przykład connection string do bazy danych. W tradycyjnych obrazach atakujący może go wyłuskać, wchodząc do kontenera i podglądnąć zmienne środowiskowe.

Pojawia się zatem problem – Jak można debuggować kontener, jeżeli nie ma shella?

W razie takiej potrzeby, jednym z podejść jest stworzenie kontenera bazującego na obrazie z tagiem :debug. Obrazy z tym tagiem, mają wbudowany shell.


Przykłady obrazów distroless

Obrazy distroless bazują na na Debianie (wersja 9 i 10). Wszystkie gotowe obrazy możemy znaleźć w oficjalnym repozytorium Google – gcr.io.

Oprócz “czystych” obrazów, możemy znaleźć obrazy dedykowane do języków/technologii. Jednak według Google’a, należy je stosować z rozwagą (na czas pisania tego artykułu, nie są rekomendowane na produkcję)

Tworzenie własnych obrazów w oparciu o distroless

Chcąc samemu tworzyć obrazy distroless, możemy użyć dwóch podejść.

Pierwszym podejściem jest wykorzystanie Docker multi-stage builds. Wystarczy zastosować się do dwóch kroków:

  1. Wybierz obraz bazowy odpowiadający Twojej aplikacji
  2. Stwórz Dockerfile w trybie multi-stage build

Poniżej znajdziesz Dockerfile budujący obraz distroless dla aplikacji Node.js.

FROM node:10.17.0 AS build-env
ADD . /app
WORKDIR /app

FROM gcr.io/distroless/nodejs
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.js"]

Budujemy obraz tradycyjnie:

$ docker build . -t myapp:distroless


Druga metoda na tworzenie obrazów distroless to użycie narzędzia Bazel.

Podstawową kwestią jest stworzenie pliku BUILD.  Poniżej znajduję się zawartość pliku BUILD. Cały przykład, wraz z plikami *.js znajdziesz TUTAJ

package(default_visibility = ["//visibility:public"])

load("@io_bazel_rules_docker//container:container.bzl", "container_image")

# These examples are adapted from:
# https://howtonode.org/hello-node
container_image(
    name = "hello",
    base = "//experimental/nodejs:nodejs",
    cmd = ["hello.js"],
    files = [":hello.js"],
)

container_image(
    name = "hello_http",
    base = "//experimental/nodejs:nodejs",
    cmd = ["hello_http.js"],
    files = [":hello_http.js"],
    ports = ["8000"],
)

load("@io_bazel_rules_docker//contrib:test.bzl", "container_test")

container_test(
    name = "hello_test",
    configs = ["testdata/hello.yaml"],
    image = ":hello",


Podsumowanie

Pomimo lekkości obrazu Alpine, okazuje się że nie jest on taki idealny. Licznie opublikowane problemy z wydajnością oraz braki niektórych pakietów mogą dawać do myślenia.

Alternatywą są obrazy distroless, które oprócz lekkości, zapewniają bezpieczeństwo. Ich atutem jest fakt, że są rozwijane przez Google oraz stosowane w rozwiązaniach produkcyjnych wraz z Kubernetes.

Na sam koniec, jeśli zastanawiasz się co wybrać – Distroless vs Alpine, wystarczy odpowiedzieć sobie na pytanie: Czy wolisz stosować coś co jest rozwijane przez firmę Google czy też coś nad czym czuwają dwie osoby?

Dla mnie odpowiedź jest prosta.


.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *