Pierwsze kroki z Dockerem: uproszczenie DevOps
Opublikowany: 2022-03-11Jeśli lubisz wieloryby lub po prostu interesuje Cię szybkie i bezbolesne ciągłe dostarczanie Twojego oprogramowania do produkcji, to zapraszam do zapoznania się z tym wstępnym Samouczkiem Dockera. Wszystko wskazuje na to, że kontenery oprogramowania to przyszłość IT, więc chodźmy na szybką kąpiel z wielorybami kontenerowymi Moby Dock i Molly.
Docker, reprezentowany przez logo z sympatycznie wyglądającym wielorybem, to projekt open source, który ułatwia wdrażanie aplikacji wewnątrz kontenerów oprogramowania. Jego podstawową funkcjonalność umożliwiają funkcje izolacji zasobów jądra Linuksa, ale dodatkowo zapewnia przyjazny dla użytkownika interfejs API. Pierwsza wersja została wydana w 2013 roku i od tego czasu stała się niezwykle popularna i jest powszechnie używana przez wielu dużych graczy, takich jak eBay, Spotify, Baidu i nie tylko. W ostatniej rundzie finansowania Docker zdobył ogromne 95 milionów dolarów i jest na dobrej drodze, aby stać się podstawą usług DevOps.
Analogia do transportu towarów
Filozofię Dockera można zilustrować za pomocą następującej prostej analogii. W branży transportu międzynarodowego towary muszą być transportowane różnymi środkami, takimi jak wózki widłowe, ciężarówki, pociągi, dźwigi i statki. Towary te mają różne kształty i rozmiary i mają różne wymagania dotyczące przechowywania: worki z cukrem, puszki po mleku, rośliny itp. Historycznie był to bolesny proces polegający na ręcznej interwencji w każdym punkcie tranzytowym w celu załadunku i rozładunku.
Wszystko zmieniło się wraz z pojawieniem się kontenerów intermodalnych. Ponieważ są one dostępne w standardowych rozmiarach i są produkowane z myślą o transporcie, wszystkie odpowiednie maszyny można zaprojektować tak, aby obsługiwały je przy minimalnej interwencji człowieka. Dodatkową zaletą szczelnych pojemników jest to, że mogą one chronić środowisko wewnętrzne, takie jak temperatura i wilgotność w przypadku towarów wrażliwych. W rezultacie branża transportowa może przestać martwić się o same towary i skupić się na dostarczeniu ich z punktu A do punktu B.
I tu właśnie pojawia się Docker i przynosi podobne korzyści branży oprogramowania.
Czym różni się od maszyn wirtualnych?
Na pierwszy rzut oka maszyny wirtualne i kontenery Dockera mogą wyglądać podobnie. Jednak ich główne różnice staną się widoczne, gdy spojrzysz na poniższy diagram:
Aplikacje działające na maszynach wirtualnych, poza hiperwizorem, wymagają pełnej instancji systemu operacyjnego oraz dowolnych bibliotek pomocniczych. Z drugiej strony kontenery współdzielą system operacyjny z hostem. Hypervisor jest porównywalny z silnikiem kontenerów (reprezentowanym jako Docker na obrazie) w tym sensie, że zarządza cyklem życia kontenerów. Ważną różnicą jest to, że procesy działające w kontenerach są takie same, jak procesy natywne na hoście i nie wprowadzają żadnych kosztów ogólnych związanych z wykonywaniem hiperwizora. Dodatkowo aplikacje mogą ponownie wykorzystywać biblioteki i udostępniać dane między kontenerami.
Ponieważ obie technologie mają różne mocne strony, często można znaleźć systemy łączące maszyny wirtualne i kontenery. Doskonałym przykładem jest narzędzie o nazwie Boot2Docker opisane w sekcji Instalacja Dockera.
Architektura Dockera
Na górze diagramu architektury znajdują się rejestry. Domyślnie głównym rejestrem jest Docker Hub, w którym znajdują się obrazy publiczne i oficjalne. Organizacje mogą również hostować swoje prywatne rejestry, jeśli chcą.
Po prawej stronie mamy obrazy i kontenery. Obrazy można pobierać z rejestrów jawnie ( docker pull imageName
) lub niejawnie podczas uruchamiania kontenera. Po pobraniu obrazu jest on buforowany lokalnie.
Kontenery są instancjami obrazów - są żywą istotą. Na tym samym obrazie może działać wiele kontenerów.
W centrum znajduje się demon Docker odpowiedzialny za tworzenie, uruchamianie i monitorowanie kontenerów. Dba również o budowanie i przechowywanie obrazów. Wreszcie po lewej stronie znajduje się klient Dockera. Rozmawia z demonem przez HTTP. Gniazda uniksowe są używane na tej samej maszynie, ale zdalne zarządzanie jest możliwe za pośrednictwem interfejsu API opartego na HTTP.
Instalowanie Dockera
Aby uzyskać najnowsze instrukcje, zawsze należy zapoznać się z oficjalną dokumentacją.
Docker działa natywnie w systemie Linux, więc w zależności od docelowej dystrybucji może to być tak proste, jak sudo apt-get install docker.io
. Szczegółowe informacje można znaleźć w dokumentacji. Zwykle w Linuksie polecenia Dockera poprzedza się sudo
, ale dla jasności pominiemy to w tym artykule.
Ponieważ demon platformy Docker korzysta z funkcji jądra specyficznych dla systemu Linux, nie można uruchomić platformy Docker natywnie w systemie Mac OS lub Windows. Zamiast tego powinieneś zainstalować aplikację o nazwie Boot2Docker. Aplikacja składa się z maszyny wirtualnej VirtualBox, samego Dockera i narzędzi do zarządzania Boot2Docker. Możesz postępować zgodnie z oficjalnymi instrukcjami instalacji dla systemów MacOS i Windows, aby zainstalować Docker na tych platformach.
Korzystanie z Dockera
Zacznijmy tę sekcję od krótkiego przykładu:
docker run phusion/baseimage echo "Hello Moby Dock. Hello Molly."
Powinniśmy zobaczyć ten wynik:
Hello Moby Dock. Hello Molly.
Jednak za kulisami wydarzyło się o wiele więcej, niż mogłoby się wydawać:
- Obraz „phusion/baseimage” został pobrany z Docker Hub (jeśli nie był już w lokalnej pamięci podręcznej)
- Kontener oparty na tym obrazie został uruchomiony
- Echo polecenia zostało wykonane w kontenerze
- Kontener został zatrzymany, gdy polecenie zostało zakończone
Przy pierwszym uruchomieniu możesz zauważyć opóźnienie przed wydrukowaniem tekstu na ekranie. Gdyby obraz był buforowany lokalnie, wszystko zajęłoby ułamek sekundy. Szczegóły dotyczące ostatniego kontenera można pobrać, uruchamiając docker ps -l
:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af14bec37930 phusion/baseimage:latest "echo 'Hello Moby Do 2 minutes ago Exited (0) 3 seconds ago stoic_bardeen
Kolejne nurkowanie
Jak widać, uruchomienie prostego polecenia w Dockerze jest tak proste, jak uruchomienie go bezpośrednio na standardowym terminalu. Aby zilustrować bardziej praktyczny przypadek użycia, w dalszej części tego artykułu zobaczymy, jak możemy wykorzystać Docker do wdrożenia prostej aplikacji serwera WWW. Aby uprościć sprawę, napiszemy program w Javie, który obsługuje żądania HTTP GET do „/ping” i odpowiada ciągiem „pong\n”.
import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class PingPong { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/ping", new MyHandler()); server.setExecutor(null); server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "pong\n"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }
Plik dockera
Przed przystąpieniem do tworzenia własnego obrazu Docker dobrze jest najpierw sprawdzić, czy w Docker Hub lub w dowolnych prywatnych rejestrach, do których masz dostęp, istnieje już taki obraz. Na przykład zamiast samodzielnie instalować Javę, użyjemy oficjalnego obrazu: java:8
.

Aby zbudować obraz, najpierw musimy wybrać obraz bazowy, którego będziemy używać. Jest to oznaczone instrukcją FROM . Tutaj jest to oficjalny obraz Javy 8 z Docker Hub. Skopiujemy go do naszego pliku Java, wydając instrukcję COPY . Następnie skompilujemy go za pomocą RUN . Instrukcja EXPOSE oznacza, że obraz będzie świadczył usługę na określonym porcie. ENTRYPOINT to instrukcja, którą chcemy wykonać, gdy zostanie uruchomiony kontener oparty na tym obrazie, a CMD wskazuje domyślne parametry, które zamierzamy do niego przekazać.
FROM java:8 COPY PingPong.java / RUN javac PingPong.java EXPOSE 8080 ENTRYPOINT ["java"] CMD ["PingPong"]
Po zapisaniu tych instrukcji w pliku o nazwie „Dockerfile”, możemy zbudować odpowiedni obraz Docker, wykonując:
docker build -t toptal/pingpong .
Oficjalna dokumentacja Dockera zawiera sekcję poświęconą najlepszym praktykom dotyczącym pisania pliku Dockerfile.
Uruchamianie kontenerów
Po zbudowaniu obrazu możemy go ożywić jako pojemnik. Istnieje kilka sposobów uruchamiania kontenerów, ale zacznijmy od prostego:
docker run -d -p 8080:8080 toptal/pingpong
gdzie -p [port-on-the-host]:[port-in-the-container] oznacza mapowanie portów odpowiednio na hoście i kontenerze. Ponadto mówimy Dockerowi, aby uruchamiał kontener jako proces demona w tle, podając -d . Możesz sprawdzić, czy aplikacja serwera WWW jest uruchomiona, próbując uzyskać dostęp do „http://localhost:8080/ping”. Zauważ, że na platformach, na których używany jest Boot2docker, musisz zastąpić „localhost” adresem IP maszyny wirtualnej, na której działa Docker.
W systemie Linux:
curl http://localhost:8080/ping
Na platformach wymagających Boot2Docker:
curl $(boot2docker ip):8080/ping
Jeśli wszystko pójdzie dobrze, powinieneś zobaczyć odpowiedź:
pong
Hurra, nasz pierwszy niestandardowy kontener Docker żyje i pływa! Kontener moglibyśmy również uruchomić w trybie interaktywnym -i -t . W naszym przypadku zastąpimy polecenie entrypoint , więc zostanie nam przedstawiony terminal bash. Teraz możemy wykonać dowolne polecenia, ale wyjście z kontenera zatrzyma to:
docker run -i -t --entrypoint="bash" toptal/pingpong
Dostępnych jest znacznie więcej opcji uruchamiania kontenerów. Omówmy jeszcze kilka. Na przykład, jeśli chcemy zachować dane poza kontenerem, możemy współdzielić system plików hosta z kontenerem za pomocą -v . Domyślnie tryb dostępu to odczyt-zapis, ale można go zmienić na tryb tylko do odczytu, dołączając :ro
do ścieżki woluminu wewnątrz kontenera. Woluminy są szczególnie ważne, gdy musimy użyć jakichkolwiek informacji bezpieczeństwa, takich jak poświadczenia i klucze prywatne wewnątrz kontenerów, które nie powinny być przechowywane w obrazie. Dodatkowo może również zapobiec duplikowaniu danych, na przykład poprzez mapowanie lokalnego repozytorium Maven do kontenera, aby uniknąć dwukrotnego pobierania Internetu.
Docker ma również możliwość łączenia kontenerów ze sobą. Połączone kontenery mogą komunikować się ze sobą, nawet jeśli żaden z portów nie jest narażony. Można to osiągnąć za pomocą –link nazwa-innego-kontenera . Poniżej przykład łączący powyższe parametry:
docker run -p 9999:8080 --link otherContainerA --link otherContainerB -v /Users/$USER/.m2/repository:/home/user/.m2/repository toptal/pingpong
Inne operacje na kontenerach i obrazach
Nic dziwnego, że lista operacji, które można zastosować na kontenerach i obrazach, jest dość długa. Dla zwięzłości przyjrzyjmy się tylko kilku z nich:
- stop — zatrzymuje działający kontener.
- start — uruchamia zatrzymany kontener.
- commit — Tworzy nowy obraz ze zmian w kontenerze.
- rm — usuwa jeden lub więcej kontenerów.
- rmi — usuwa jeden lub więcej obrazów.
- ps — wyświetla listę kontenerów.
- obrazy — wyświetla listę obrazów.
- exec — uruchamia polecenie w uruchomionym kontenerze.
Ostatnie polecenie może być szczególnie przydatne do celów debugowania, ponieważ pozwala połączyć się z terminalem działającego kontenera:
docker exec -i -t <container-id> bash
Docker Compose dla świata mikroserwisów
Jeśli masz więcej niż tylko kilka połączonych kontenerów, sensowne jest użycie narzędzia takiego jak docker-compose. W pliku konfiguracyjnym opisujesz, jak uruchomić kontenery i jak powinny być ze sobą połączone. Niezależnie od liczby zaangażowanych kontenerów i ich zależności, możesz mieć je wszystkie za pomocą jednego polecenia: docker-compose up
.
Doker na wolności
Przyjrzyjmy się trzem etapom cyklu życia projektu i zobaczmy, jak nasz przyjazny wieloryb może być pomocny.
Rozwój
Docker pomaga w utrzymaniu czystości lokalnego środowiska programistycznego. Zamiast instalować wiele wersji różnych usług, takich jak Java, Kafka, Spark, Cassandra itp., W razie potrzeby możesz po prostu uruchomić i zatrzymać wymagany kontener. Możesz pójść o krok dalej i uruchamiać wiele stosów oprogramowania obok siebie, unikając pomieszania wersji zależności.
Dzięki Dockerowi możesz zaoszczędzić czas, wysiłek i pieniądze. Jeśli Twój projekt jest bardzo złożony w konfiguracji, „dokeryzuj” go. Wystarczy raz przejść przez trud tworzenia obrazu Dockera, a od tego momentu każdy może po prostu uruchomić kontener w mgnieniu oka.
Możesz także mieć „środowisko integracyjne” działające lokalnie (lub w CI) i zastąpić stuby rzeczywistymi usługami działającymi w kontenerach Dockera.
Testowanie / Ciągła integracja
Dzięki Dockerfile łatwo jest uzyskać odtwarzalne kompilacje. Jenkins lub inne rozwiązania CI można skonfigurować tak, aby tworzyły obraz platformy Docker dla każdej kompilacji. Możesz przechowywać niektóre lub wszystkie obrazy w prywatnym rejestrze platformy Docker do wykorzystania w przyszłości.
Dzięki Dockerowi testujesz tylko to, co należy przetestować i usuwasz środowisko z równania. Wykonywanie testów na uruchomionym kontenerze może pomóc w utrzymaniu większej przewidywalności.
Inną interesującą cechą kontenerów oprogramowania jest łatwość tworzenia maszyn podrzędnych z identyczną konfiguracją programistyczną. Może być szczególnie przydatny do testowania obciążenia wdrożeń klastrowych.
Produkcja
Docker może być wspólnym interfejsem między programistami a personelem operacyjnym, eliminując źródło tarcia. Zachęca również do używania tego samego obrazu/plików binarnych na każdym etapie potoku. Co więcej, możliwość wdrożenia w pełni przetestowanego kontenera bez różnic w środowisku pomaga zapewnić, że w procesie kompilacji nie zostaną wprowadzone żadne błędy.
Możesz bezproblemowo migrować aplikacje do środowiska produkcyjnego. Coś, co kiedyś było żmudnym i niestabilnym procesem, teraz może być tak proste, jak:
docker stop container-id; docker run new-image
A jeśli coś pójdzie nie tak podczas wdrażania nowej wersji, zawsze możesz szybko przywrócić lub przejść do innego kontenera:
docker stop container-id; docker start other-container-id
… gwarantuje, że nie zostawisz żadnego bałaganu ani nie zostawisz rzeczy w niespójnym stanie.
Streszczenie
Dobre podsumowanie tego, co robi Docker, zawiera jego własne motto: buduj, wysyłaj, uruchamiaj.
- Kompilacja — Docker umożliwia komponowanie aplikacji z mikrousług, bez martwienia się o niespójności między środowiskami programistycznymi i produkcyjnymi oraz bez blokowania na dowolnej platformie lub języku.
- Ship - Docker umożliwia zaprojektowanie całego cyklu tworzenia, testowania i dystrybucji aplikacji oraz zarządzanie nimi za pomocą spójnego interfejsu użytkownika.
- Uruchom — Docker oferuje możliwość bezpiecznego i niezawodnego wdrażania skalowalnych usług na wielu różnych platformach.
Baw się pływać z wielorybami!
Część tej pracy jest inspirowana znakomitą książką Using Docker autorstwa Adriana Mouata.