Maszyny wirtualne i Docker: ciężki dom i lekki namiot

„Zainstaluj linuxową maszynę wirtualną i odtwórz dokładnie to samo środowisko.”

Trzy miesiące po moim dołączeniu do firmy atmosfera w biurze była niepokojąca. Jedyny senior w zespole miał zostać przeniesiony do innego projektu i był tak zajęty pakowaniem własnych rzeczy, że o sensownym przekazaniu wiedzy nie było mowy, a team leader od dawna nie zajmował się już praktycznym developmentem i był dość daleko od aktualnych trendów technologicznych.

Pewnego dnia team leader zawołał mnie do siebie. „Kiedy twój senior odejdzie, jedyną osobą, która będzie mogła ogarniać serwery, będziesz ty. Nawet jeśli serwer padnie, musisz umieć sam go postawić. Zainstaluj Linuxa jako maszynę wirtualną, VM, na swoim obecnym laptopie deweloperskim, Windows, i spróbuj odtworzyć dokładnie to samo środowisko co na serwerze produkcyjnym.”

Od tego dnia zaczęła się moja udręka. Zainstalowałem VMware, pobrałem plik ISO Ubuntu, a samo postawienie systemu zajęło mi pół dnia. Przekopywałem firmową wiki, żeby zainstalować Javę, Node.js i PostgreSQL. Pomyślałem: „Nowsze będzie lepsze”, więc wrzuciłem Javę 17, po czym okazało się, że legacy code opiera się na Javie 8 i nawet się nie buduje. Musiałem wszystko usuwać i instalować od nowa. Za każdym razem, gdy zmieniałem jedną linijkę kodu, musiałem odpalać mvn build, przenosić plik jar do VM i uruchamiać go tam. To była czysta męka.

W pewnym momencie naszła mnie myśl. „Chwileczkę. Ostatnim razem, kiedy wdrażaliśmy na serwer produkcyjny, czy to nie skończyło się po wpisaniu jednej komendy docker service update?”

Moja lokalna maszyna wirtualna była tak ciężka i wymagała góry konfiguracji, więc czym właściwie był ten cały „Docker” na serwerze produkcyjnym, skoro pozwalał wszystko zaktualizować jedną komendą?

Docker to najlżejszy sposób na przeniesienie całego środowiska.

Ciężkie rozwiązanie: maszyna wirtualna

Metoda, którą kazał mi zastosować team leader, czyli zainstalowanie Linuxa na Windowsie, to dokładnie to, czym jest maszyna wirtualna, VM. To tak, jakby na fizycznym komputerze, host, zbudować całkowicie osobny „wirtualny dom”, czyli guest OS.

  • Sposób działania: na jednym komputerze uruchamia się kilka systemów operacyjnych. (Linux na Windowsie, a na nim kolejny Linux…)
  • Problem: żeby zbudować dom, trzeba od nowa postawić wszystko, łącznie z filarami, ścianami i łazienką (kernelem).
    • Żeby uruchomić jedną aplikację, trzeba w całości zainstalować system operacyjny ważący kilka gigabajtów.
    • Uruchamianie trwa wieki. (To właśnie dlatego mój laptop tak zwolnił)
    • Zasoby są wykorzystywane nieefektywnie. (Nawet gdy nic nie robi, zjada pamięć na samo utrzymanie systemu operacyjnego)

Ostatecznie VM rzeczywiście dobrze izoluje środowisko, ale do developmentu czy deploymentu jest po prostu zbyt ciężka i zbyt wolna, żeby za każdym razem ją przerzucać tam i z powrotem.

Lekka rewolucja: Docker

Dlatego pojawił się Docker, czyli technologia kontenerów. Docker nie buduje całego domu tak jak VM. Zamiast tego rozstawia namiot.

  • Sposób działania: współdzieli podwórko (kernel) już zbudowanego domu (Host OS – Linux). Rozstawia tylko odizolowany namiot (Container).
  • Cechy:
    • Współdzieli system operacyjny (kernel): w namiocie nie ma ciężkiego OS. Są w nim tylko moja aplikacja i jej biblioteki.
    • Jest lekki: rozmiar liczy się w megabajtach (MB).
    • Jest szybki: rozruch systemu? Nie ma czegoś takiego. Startuje tak szybko, jak włącza się pojedynczy proces (w milisekundach).

Polecenie update, które uruchomiłem na serwerze produkcyjnym, nie instalowało od nowa ciężkiego systemu operacyjnego. Ono tylko „zwijało stary namiot i rozstawiało nowy z nową wersją kodu w środku”. Nic dziwnego, że kończyło się niemal natychmiast.

[Code Verification] Czy Docker naprawdę jest taki lekki?

Nie poprzestawajmy na samym mówieniu, że Docker jest lekki. Sprawdźmy to naprawdę. Jeśli celem jest uruchomienie środowiska Linux, Ubuntu, różnica między VM a Dockerem jest uderzająca.

  1. Uruchomienie Ubuntu na VM: pobranie pliku ISO (2GB) -> instalacja (20 minut) -> rozruch (1 minuta).
  2. Uruchomienie Ubuntu w Dockerze:
# Uruchom Ubuntu komenda Docker (pobiera obraz, jesli brak)
$ docker run -it ubuntu:latest /bin/bash

Wynik:

  • Pobieranie: około 30MB (okrojony obraz zawierający tylko rdzeń Ubuntu)
  • Czas uruchomienia: około 0,5 sekundy

W chwili, gdy nacisnąłem Enter, byłem już w środowisku Ubuntu. To jest możliwe, ponieważ kontener Docker nie jest prawdziwym systemem operacyjnym. To jedynie „izolowana przestrzeń, która pożycza kernel host OS, czyli mojego komputera, jednocześnie udając osobny OS”.

Praktyczna rada: prawdziwy powód, żeby używać Dockera

Odkąd wdrożyliśmy Dockera w pracy, moje życie zmieniło się całkowicie.

  1. Zniknęło „Proszę skonfigurować środowisko”: kiedyś, gdy przychodził nowy pracownik, dostawał dziesięciostronicowy dokument A4 w stylu „Zainstaluj Javę, ustaw zmienne środowiskowe, postaw bazę danych…”. Teraz mówię po prostu: „Proszę zainstalować Dockera i wpisać docker-compose up.”
  2. Ucieczka z piekła wersji: co, jeśli projekt A wymaga Javy 8, a projekt B Javy 17? Jeśli zainstalujesz obie lokalnie, skończy się to konfliktami i bałaganem. Z Dockerem możesz włożyć Javę 8 do kontenera A, a Javę 17 do kontenera B i uruchomić je jednocześnie bez żadnych problemów. Wszystko dlatego, że są one idealnie „odizolowane” (Isolation).

Na koniec: nie dostarczamy już pliku wykonywalnego, tylko „środowisko”

Pojawienie się Dockera zmieniło paradygmat developmentu. Nie wysyłamy już na serwer samego kodu źródłowego, .java. Zamrażamy też konfigurację systemu operacyjnego, biblioteki i zmienne środowiskowe potrzebne temu kodowi w postaci „obrazu” i wysyłamy cały pakiet.

To jak właściwie powstaje ten magiczny „obraz Dockera”? Czy to po prostu skompresowany plik? Co zaskakujące, mówi się, że obraz Dockera składa się z wielu warstw, jak tort.

Następnym razem przyjrzyjmy się sekretowi wydajności Dockera: obrazom i ich warstwowej strukturze.

Dodaj komentarz