Jedną z części rozmowy rekrutacyjnej jest rozmowa techniczna. Często podczas tej części rozmowy rekruter poprosi Cię o opisanie projektów, w których do tej pory brałeś(aś) udział. Warto wtedy opisać czego się nauczyłeś(aś), jakie trudności napotkałeś(aś) oraz jak udało Ci się z nimi uporać. W wielu przypadkach rekruterowi to wystarczy. Gdy jednak nie masz zbyt wiele doświadczenia, to rekruter będzie chciał sprawdzić Twoją wiedzę poprzez serię kilku pytań technicznych.
Artykuł dotyczący Dockera, w serii artykułów z pytaniami rekrutacyjnymi na stanowisko Web Developera, może wydawać się dość przewrotny. Niemniej jednak Docker jest tak przydatnym i szeroko wykorzystywanym narzędziem w branży IT, że jego znajomość jest bardzo wskazana. Ponadto, nawet jeśli rozwijasz wyłącznie aplikacje frontendowe, to najprawdopodobniej integrujesz je z jakąś usługą backendową (np. jakimś API). Dzięki Dockerowi w prosty sposób możesz uruchomić backend lokalnie i polegać wyłącznie na swojej maszynie. Uruchomienie backendu lokalnie pozwala także na napisanie deterministycznych testów E2E, które da się uruchomić lokalnie oraz na środowiskach ciągłej integracji (CI).
Część pytań pochodzi z mojego e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer, który możesz odebrać, zapisując się na mój mailing. Nie wszystkie pytania zawarte w tym artykule znajdziesz w moim e-booku, więc zachęcam Cię do przeczytania tego wpisu, nawet jeśli planujesz przeczytać e-booka.
Na blogu tematyce pytań technicznych poświęciłem szerszą uwagę w serii wpisów. Zachęcam do sprawdzenia pozostałych artykułów:
- Junior Web Developer – pytania rekrutacyjne cz. 1
- Junior Web Developer – pytania rekrutacyjne cz. 2
- Junior Web Developer – pytania rekrutacyjne cz. 3
- Junior Web Developer – pytania rekrutacyjne cz. 4
- Junior Web Developer – pytania rekrutacyjne cz. 5
- Junior Web Developer – pytania rekrutacyjne – React
- Programista – pytania rekrutacyjne – Git
- Programista – pytania rekrutacyjne – TypeScript
- Programista – pytania rekrutacyjne – bazy danych
Pytania z tej serii możesz również traktować jako trening i sprawdzenie swoich umiejętności przed rozmową rekrutacyjną.
Czym jest Docker i do czego służy?
Aby rozpocząć pracę z Dockerem, warto wiedzieć, czym on jest. Docker jest narzędziem służącym do uruchamiania kontenerów. Dzięki kontenerom można w prosty sposób uruchamiać aplikacje bez fizycznego instalowania ich na wykorzystywanej maszynie. Przykładowo, chcąc uruchomić bazę danych, nie trzeba instalować jej lokalnie. Wystarczy, że uruchomiony zostanie kontener, bazując na obrazie wybranej bazy danych oraz zostanie zdefiniowane, jak można się z tym kontenerem komunikować. W ten sam sposób można dokonać konteneryzacji aplikacji, którą rozwijasz. Istotnym aspektem konteneryzacji z wykorzystaniem Dockera jest to, że kontenery nic o sobie nie wiedzą, dopóki użytkownik jawnie nie zadeklaruje, że ma być inaczej. Czyli posiadając kontener z bazą danych i kontener z rozwijaną aplikacją należy jeszcze zdefiniować, jak te dwa kontenery powinny się ze sobą komunikować.
Jaka jest różnica między obrazem i kontenerem?
W tym pytaniu zawiera się zrozumienie, czym jest obraz a czym kontener, jakie są różnice między nimi oraz jaka występuje między nimi zależność.
Obrazem (image) można nazwać „snapshot” aplikacji, którą potrzebujesz uruchomić. Używając analogii z programowania obiektowego, obraz można przyrównać do klasy. Oznacza to, że mając jeden obraz, można powołać do życia wiele kontenerów. Aby uruchomić własną aplikację z użyciem Dockera niezbędne będzie utworzenie dla niej Dockerfile
. Plik Dockerfile
zawiera w sobie listę kroków niezbędną do zbudowania obrazu aplikacji. Co ważne, tworząc Dockerfile
, nie podaje się żadnego rozszerzenia. Przykładowy, najprostszy, Dockerfile
może wyglądać następująco.
FROM node:16.13.1 WORKDIR /app COPY ["package.json", "./"] RUN npm install --production COPY . . CMD [ "node", "server.js" ]
Ta prosta definicja obrazu wykorzystuje obraz bazowy Node.js. Obraz ten został pobrany z Docker Hub, czyli rejestru obrazów, gotowych do uruchomienia lub rozbudowy. Istnieją również inne rejestry obrazów Dockerowych tj. Google Container Registry, Azure Container Registry od Microsoftu czy Elastic Container Registry od AWS. Po wskazaniu obrazu bazowego następuje kopiowanie niezbędnych plików i instalacja zależności produkcyjnych. Ostatnim krokiem w podanym przykładzie jest zdefiniowanie komendy do uruchomienia aplikacji w kontenerze. Mając stworzony Dockerfile
, należy na jego przykładzie zbudować obraz za pomocą komendy docker build -t image_name .
, gdzie image_name
jest nazwą, pod jaką dostępny będzie obraz. Oczywiście każdy obraz ma swój identyfikator, a podanie nazwy jest opcjonalne. Jednak prościej jest znaleźć zbudowany obraz na podstawie konkretnej nazwy, niż po ciągu losowych znaków, przez co rekomenduję z korzystania z flagi -t
.
Natomiast kontener (container) jest to instancja aplikacji uruchomiona na podstawie obrazu. Przyrównując to do programowania obiektowego, kontener byłby obiektem stworzonym na podstawie klasy (obrazu).
Czym jest Docker Compose?
Docker Compose jest narzędziem służącym do usprawnienia pracy z wieloma kontenerami. Dzięki Docker Compose użytkownik jest w stanie uruchomić środowisko z wieloma kontenerami za pomocą jednej komendy. Aby skorzystać z Docker Compose niezbędne będzie stworzenie pliku z rozszerzeniem .yml
. Domyślną nazwą pliku, którego Docker Compose się spodziewa, jest docker-compose.yml
. Przykładowy plik może wyglądać następująco.
version: "3.7" services: web: build: . ports: - "5000:5000" volumes: - .:/code links: - redis redis: image: redis
Powyższa konfiguracja zawiera deklarację uruchomienia dwóch kontenerów. Jeden z nich to jakaś lokalna aplikacja, której kod źródłowy znajduje się w folderze code
. Aplikacja jest dostępna z zewnątrz na porcie 5000. Należy pamiętać, aby aplikacja miała utworzony Dockerfile
! Natomiast drugi kontener wykorzystuje gotowy obraz, w tym przypadku jest to obraz Redisa. Mając taki plik, jedyne co należy zrobić, to wywołać polecenie docker-compose up
. Spowoduje to uruchomienie kontenerów. Oczywiście Docker Compose oferuje znacznie więcej niż na przedstawionym przykładzi. Dzięki temu narzędziu można na przykład przekazać zmienne środowiskowe do kontenerów, zdefiniować i skonfigurować sieć do komunikacji między kontenerami czy wymusić ich uruchamianie w określonej kolejności. Pełną listę możliwości Docker Compose znajdziesz w dokumentacji Dockera.
Do czego służy Docker volume?
Warto zauważyć, że w poprzednim przykładzie pierwsza z aplikacji wykorzystuje Docker volume. Dzięki wykorzystaniu wolumenów tworzymy tak jakby link między wskazaną lokalizacją w kontenerze a wskazaną lokalizacją na lokalnej maszynie. Taki zabieg pozwala na bieżąco obserwować, jak zmiany w kodzie źródłowym wpływają na działanie aplikacji, bez potrzeby przebudowywania obrazu po każdej zmianie. Innym wykorzystaniem wolumenów może być np. zapis logów z aplikacji do pliku na lokalnej maszynie czy przekazywanie inicjalnego stanu bazy danych do aplikacji z lokalnej maszyny przy starcie aplikacji.
Jak „wejść” do uruchomionego kontenera?
Dodałem to pytanie do tego wpisu, ponieważ jest to czynność, której dokonuję bardzo często. Dzięki „wejściu” do kontenera możemy w prosty sposób dokonywać debugowania aplikacji, szczególnie na środowiskach zdalnych wykorzystując np. połączenie SSH. Gdy występują problemy z poprawną pracą kontenera, często „wchodzę” do kontenera i próbuję znaleźć przyczynę problemu. Aby „wejść” do kontenera potrzebny będzie jego identyfikator. Aby pozyskać id kontenera należy wywołać polecenie docker ps
. Z listy działających kontenerów, należy wybrać pożądany kontener i skopiować jego id. Następnie w CLI należy skorzystać z polecenia docker exec -it container_id /bin/bash
. Polecenie to sprawia, że uruchomiona zostanie powłoka bash we wskazanym kontenerze. Oczywiście można użyć również innej powłoki, jeśli jest ona dostępna we wskazanym kontenerze.
Czym jest Open Container Initiative?
Open Container Initiative (OCI) jest odpowiedzialne za standaryzację w świecie konteneryzacji. Podążając za standardami wytyczanymi przez OCI, w przypadku potrzeby zastąpienia jednego z rozwiązań służących do konteneryzacji innym, migracja odbywa się w zdecydowanie mniej bolesny sposób. Można tu przywołać analogię do kontenerów używanych na statkach. Wymiary kontenerów używanych w transporcie morskim są ustandaryzowane. Dzięki temu doskonale wiadomo, jakie wymiary ma kontener, ile się do niego zmieści oraz ile kontenerów może zostać załadowanych na statek.
Dzięki OCI w prosty sposób możesz podmienić w swoim projekcie Dockera np. na Podmana. Świetny artykuł opisujący czym jest OCI, znajdziesz pod tym linkiem.
Jakie są korzyści wynikające z konteneryzacji?
Tyle zostało już powiedziane o konteneryzacji, ale nie zostało powiedziane, dlaczego warto zainteresować się konteneryzacją. Najważniejsze zalety konteneryzacji to:
- Brak konieczności uruchamiania aplikacji na lokalnej maszynie. Oznacza to też brak konieczności instalacji dodatkowych komponentów tj. serwer HTTP czy bazy danych.
- Jeden standard wynikający z OCI.
- Przenaszalność — kontenery w łatwy sposób można uruchomić na każdej maszynie umożliwiającej uruchomienie Dockera. Co istotne, na każdej maszynie kontener bazujący na tym samym obrazie w tej samej konfiguracji będzie działał tak samo (chyba że na maszynie docelowej zabraknie zasobów, ale uważam to za edge case).
- Izolacja — wystawienie endpointu dostępnego z zewnątrz dla kontenera wymaga jawnego zadeklarowania tego faktu. Domyślnie, kontenery są od siebie odizolowane, jak i w maksymalnym możliwym stopniu od maszyny, na której są uruchamiane.
- Szybkie dostarczanie — gotowe obrazy można w szybki i prosty sposób udostępnić czy to klientom, czy innym programistom. Ich uruchomienie przez odbiorcę jest równie szybkie i proste.
- Konteneryzacja nie wymaga tylu zasobów co korzystanie z maszyn wirtualnych.
Jakie są dobre praktyki z pracą z kontenerami?
Cała masa dobrych praktyk pracy z Dockerem została przedstawiona w dokumentacji Dockera. Przedstawię tutaj małe streszczenie tego, co możesz znaleźć w dokumentacji:
- Nie korzystaj z konta root w kontenerze. Korzystanie z konta root jest złą praktyką z punktu widzenia bezpieczeństwa. Niektóre obrazy, np. obraz dla Node.js oferują wbudowanego użytkownika, a jego użycie sprowadza się do dodania kroku w
Dockerfile
—USER node
. - Nie instaluj zbędnych zależności — np. nie potrzebujesz w obrazie produkcyjnym developerskich zależności.
- Używaj ściśle zdefiniowanej wersji obrazów (z tagiem z konkretną wersją). Ponadto nie korzystaj z obrazów oznaczonych tagiem latest. Pozwoli to na zachowanie determinizmu przy pracy z zewnętrznym obrazami. W innym razie nigdy nie ma pewności, która wersja obrazu zostanie wykorzystana.
- Regularnie skanuj swoje obrazy w poszukiwaniu podatności, na przykład za pomocą narzędzia Snyk.
- Używaj lżejszych wersji zewnętrznych obrazów, jeśli to możliwe. Przykładowo, w dniu publikacji artykułu, obraz dla Node.js z tagiem
erbium-buster
dostępny w Docker Hub ważył ponad 300 MB, gdzie obrazerbium-alpine3.14
ważył zaledwie 30 MB! Przy wyborze obrazu należy zadać sobie pytanie, czy któryś z lżejszych wariantów obrazów jest wystarczający. - Korzystaj z multistage builds. Przede wszystkim pozwala to na zmniejszenie rozmiaru ostatecznego obrazu. Oprócz tego jest to dobra praktyka bezpieczeństwa w momencie, gdy przy budowaniu używasz tokenów bezpieczeństwa. W obrazie zapisane są informacje o każdej jego warstwie, co może spowodować wyciek tokenów bezpieczeństwa, które wykorzystano podczas budowania obrazu. Multistage build pozwala temu zapobiec. Należy jednak pamiętać, aby wrażliwe wartości nie były używane podczas ostatniego stage’a.
- Ignoruj pliki, które nie powinny znaleźć się w obrazie za pomocą
.dockerignore
. - Minimalizuj liczbę warstw w obrazie. Ponadto, Docker potrafi cache’ować poszczególne warstwy, przez co umiejętne konstruowanie definicji obrazów pozwala znacznie skrócić czas przebudowania obrazu.
Podsumowanie
Te kilkanaście pytań to o wiele za mało, by być dobrze przygotowanym do rozmowy rekrutacyjnej. Jeszcze raz gorąco zachęcam do sprawdzenia innych wpisów na blogu oraz pobranie e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer. Zachęcam też do zostawiania komentarzy i udostępnienia tego wpisu znajomym, którym ta wiedza może się przydać.
Źródła i materiały dodatkowe
- Docker dla programistów, co to jest?
- Docker Docs – Overview of the get started guide
- What is the difference between a Docker image and a container?
- Docker Image vs Container: The Major Differences
- Docker Docs – docker build
- Google Cloud – Artifact Registry
- Amazon Elastic Container Registry
- Docker Hub
- Docker Docs – Docker Compose
- Docker Compose Tutorial: advanced Docker made simple
- Docker Docs – volumes
- Docker 'run’ command to start an interactive BaSH session
- Docker Docs – exec
- Open Container Initiative (OCI) – standard dla obrazów i kontenerów
- About the Open Container Initiative
- Benefits of containerization
- 10 best practices to containerize Node.js web applications with Docker
- Best practices for writing Dockerfiles
- Docker and Node.js Best Practices
Zapisz się na mailing i odbierz e-booka
Odbierz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer i realnie zwiększ swoje szanse na rozmowie rekrutacyjnej! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.