Trochę o dockerze #1

Cześć! Wreszcie po długich przemyśleniach postanowiłem opowiedzieć Wam trochę o technologii konteneryzacji znanej też jako „Docker”.

Dlaczego warto używać dockera?

Na wstępie zacznijmy sobie od zrozumienia czym jest konteneryzacja.

Konteneryzacja to metoda umożliwiająca łatwiejsze zarządzanie aplikacjami i ich zależnościami w środowisku deweloperskim.

Aby zobrazować to w prosty sposób, możemy sobie wyobrazić, że aplikacja to paczka prezentów, a kontener to pudełko na te prezenty. Kontenerizacja pozwala na zapakowanie aplikacji w takie pudełko razem z jej zależnościami (czyli innymi prezentami), które są potrzebne do jej działania.

Docker zaś to narzędzie, które umożliwia izolowanie aplikacji w kontenerach, co sprawia że nie ma żadnej potrzeby instalowania aplikacji lokalnie, a także pozwala uniknąć problemów z zależnościami, gdyby na przykład dwie aplikacje potrzebowały różnych wersji bibliotek. Największymi plusami stosowania konteneryzacji jest to, że to użytkownik decyduje o wersji danej aplikacji i nie musi być ona zawsze najnowsza (jak to jest w repozytorium systemu), oraz to, że użytkownik zostawia zawsze porządek na swoim głównym serwerze produkcyjnym, gdyż chcąc usunąć daną aplikację kasuje kontener i po problemie.

Jak uruchomić kontener w Dockerze?

Aby uruchomić kontener w Dockerze powinniśmy:

  • ustalić jaką aplikację chcemy uruchomić
  • czy chcemy wystawiać aplikację na zewnątrz
  • czy kontener ma sobie działać w tle

Takim podstawowym poleceniem aby uruchomić kontener z aplikacją NGINX jest:

docker run -d nginx

Czym różni się uruchomienie Dockera z parametrem -d, a uruchomienie go bez tego parametru?

Uruchomienie kontenera z parametrem -d oznacza, że kontener będzie działał w tle (w trybie „detach”), a proces kontenera będzie uruchomiony w tle i nie będzie wyświetlał się na standardowym wyjściu, takim jak konsola lub terminal.

Uruchomienie kontenera bez parametru -d spowoduje, że kontener będzie działał w trybie interaktywnym i będzie wyświetlał logi w terminalu, w którym został uruchomiony. Uruchomienie kontenera bez parametru -d jest używane do debugowania i testowania kontenerów, a także do interaktywnego dostępu do procesu kontenera.

W jaki sposób można ustawić nazwę dla kontenera?

W dockerze aby ustawić nazwę dla kontenera wykorzystuje się parametr --name lub w jego krótszej wersji -n. W Docker Compose domyślnie nazwa bloku kontenera to jego nazwa, na przykład:

services:
    nginx: # ta nazwa bloku kontenera domyślnie będzie także nazwą kontenera
        container_name: nginx # można jeszcze podać nazwę kontenera, ale nie jest to niezbędne
        image: nginx

Dobra, a co jeżeli chcę dodać do jakiegoś obrazu kilka rzeczy bo mi ich w nim brakuje?

Tu z pomocą przychodzi Docker Build. Pozwala on zbudować nowy obraz dla kontenera w oparciu o plik Dockerfile i zamieszczone tam taski (zadania). Przykładem pliku dockerfile, który buduje mi serwer SSH z kilkoma narzędziami do wykorzystania w aplikacji n8n, jest poniższy kod:

FROM ubuntu:20.04
RUN apt update && apt install openssh-server sudo nmap telnet mariadb-client net-tools -y
RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu
RUN echo 'ubuntu:ubuntu' | chpasswd
RUN service ssh start
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]

gdzie:

  • FROM – definiuje wersję obrazu na bazie którego ma powstać nowy obraz
  • RUN – definiuje polecenie, które ma zostać wykonane podczas budowania kontenera
  • EXPOSE – definiuje port bądź porty, które są informacją dla użytkownika jakie porty będą nasłuchiwane gdy uruchomimy ten obraz w kontenerze
  • CMD – definiuje polecenie, które ma zostać uruchomione za każdym startem kontenera

Jak utworzyć kontener ze zmodyfikowanym obrazem?

W dokerze aby zbudować obraz należy wydać polecenie docker build -t nazwa_obrazu /ścieżka/do/pliku/Dockerfile, gdzie gdyby nie została podana ścieżka do pliku Dockerfile to wówczas docker by szukał pliku o nazwie Dockerfile w bieżącym katalogu.

Następnie aby uruchomić kontener ze zbudowanego obrazu należy wydać polecenie docker run -d nazwa_obrazu.

W Docker Compose wystarczy dodać poniższy kod do docker-compose.yml:

services:
    ssh_server:
        build:
            context: .
            dockerfile: ssh.dockerfile

gdzie

  • context – docker będzie szukał pliku w katalogu w którym znajduje się plik docker-compose.yml (domyślnie)
  • dockerfile – nazwa pliku dockerfile na podstawie którego ma zostać zbudowany nowy obraz (domyślna nazwa pliku: Dockerfile)

W jaki sposób mógłbym wystawić swoją aplikację na zewnątrz?

Jasne, można to zrobić 🙂 W dockerze można wykorzystać opcję –publish albo w skrócie -p co pozwoli wystawić port aplikacji na zewnątrz np.

docker run -d --name my_nginx -p '80:80' -p '443:443' -v /var/www/html:/var/www/html nginx

albo w Docker Compose:

version: '3'
services:
    my_nginx:
        image: nginx
        restart: always
        volumes:
            - '/var/www/html:/var/www/html'
        ports:
            - '80:80'
            - '443:443'

Jak wygląda zmiana wersji aplikacji w dockerze?

Odpalając kontenery za pomocą polecenie docker run należy:
1. Odszukać kontenery poleceniem docker ps
2. Zatrzymać kontenery poleceniem docker stop <nazwa_kontenera>
3. Skasować kontenery poleceniem docker rm <nazwa_kontenera>
4. Utworzyć kontenery na nowo poleceniem docker run -d ...... z nowszą wersją obrazu.

Jak wygląda zmiana wersji aplikacji w Docker Compose?

Odpalając kontenery za pomocą docker compose należy:
1. Otworzyć plik docker-compose.yml
2. Zmienić wersję obrazu w kontenerach
3. Wykonać polecenie docker-compose up -d

Czy możesz opowiedzieć czym są te volumeny?

Są trzy typy volumenów:

  1. Bind mount
  2. Volume
  3. Tmpfs

Bind mount umożliwia podmontowanie folderu z hosta bezpośrednio do kontenera. Zmiany dokonywane wewnątrz kontenera będą widoczne na hostingu i odwrotnie.

Wolumin to specjalny typ katalogu, który jest zarządzany przez Docker. Wolumin może być współdzielony przez wiele kontenerów, co ułatwia udostępnianie danych pomiędzy aplikacjami.

Tmpfs to rodzaj woluminu, który przechowuje pliki tymczasowe w pamięci RAM. Po wyłączeniu kontenera pliki te zostaną skasowane.

Przykłady:

1. Bind mount:

services:
    nginx:
        image: nginx
        volumes:
            - /var/www/html:/var/www/html

Katalog /var/www/html na głównej maszynie jest odzwierciedleniem katalogu /var/www/html w kontenerze.

2. Volume:

services:
    nginx:
        image: nginx
        volumes:
            - nginx_files:/var/www/html
    tools:
        build:
            context: .
            dockerfile: tools.dockerfile
        volumes:
            - nginx_files:/var/www/html
volumes:
    nginx_files:

Dostęp do tych samych plików mają oba kontenery.

3. Tmpfs

services:
    nginx:
        image: nginx
        volumes:
            - type: tmpfs
              target: /var/cache/nginx

Katalog /var/cache/nginx w kontenerze jest wrzucony do pamięci ram, więc dane tam będą zapisywane i odczytywane znacznie szybciej niż na dysku. Przydatne jest to jeżeli chcemy przyspieszyć działanie serwera WWW. Gdy kontener zostanie wyłączony to dane te zostaną usunięte.

Jeżeli nie zostaną utworzone żadne volumeny to wówczas gdy kontener zostanie skasowany to wszelkie dane na tym kontenerze zostaną skasowane wraz z nim!

Czy da się połączyć kontenery w jedną sieć?

Tak. Domyślnie wszystkie nowo utworzone kontenery znajdują się w podsieci 172.17.0.0/16. Docker jednak pozwala utworzyć nowe podsieci dla kontenerów, gdzie to użytkownik decyduje o tym, który kontener będzie znajdował się w danej sieci. Jest to bardzo pomocne gdy mamy chęć odseparować od siebie kontenery bądź konkretne aplikacje np. jeżeli są dwie bazy danych do dwóch różnych aplikacji i jedna aplikacja ma być w jednej sieci z jedną bazą, a druga baza ma być w drugiej sieci z drugą aplikacją, dzięki czemu obie aplikacje nie zobaczą się wzajemnie.

Przykładem w docker-compose.yml utworzenia nowej sieci dla aplikacji jest:

version: '3'
networks:
  example_network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1

services:
    app:
        build:
            context: .
            dockerfile: app.dockerfile
        restart: always
        volumes:
            - '/var/www/app:/var/www/app'
        networks:
          example_network:
            ipv4_address: 172.20.0.2
    redis:
        image: redis:alpine
        restart: always
        networks:
          example_network:
            ipv4_address: 172.20.0.3

Opis sekcji networks:

networks:
  example_network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1

  • example_network – nazwa sieci, którą definiujemy.
  • driver: bridge – określa typ sieci, w tym przypadku jest to sieć typu bridge, czyli domyślny typ sieci w Dockerze. Bridge to po prostu sieć wewnętrzna, która pozwala na komunikację między kontenerami.
  • ipam – definiuje, jakie adresy IP będą przydzielane kontenerom w tej sieci.
  • config – definiuje konfigurację sieci.
  • subnet – określa pulę adresów IP, jakie będą dostępne w tej sieci.
  • gateway – adres IP, który będzie bramą domyślną dla kontenerów w tej sieci.
        networks:
          example_network:
            ipv4_address: 172.20.0.3

Kontener będzie podłączony do sieci example_network i otrzyma stały adres IP 172.20.0.3

Przykładowy plik docker-compose.yml wraz z opisem:

version: '3' # korzystaj z trzeciej wersji Docker Compose
services: # serwisy
    n8n: # nazwa serwisu: n8n
        image: n8nio/n8n:0.226.2 # obraz to n8n w wersji 0.226.2
        restart: unless-stopped # w razie awarii restartuj tak długo dopóki nie zostanie zatrzymany
        environment: # zmienne środowiskowe
            - N8N_BASIC_AUTH_ACTIVE=true
            - N8N_BASIC_AUTH_USER=admin
            - N8N_BASIC_AUTH_PASSWORD=super_tajne_haslo
            - TZ="Europe/Warsaw"
        volumes: # wolumeny
            - /var/run/docker.sock:/var/run/docker.sock # plik /var/run/docker.sock ma odpowiadać plikowi /var/run/docker.sock w kontenerze
            - '~/.n8n:/home/node/.n8n' # katalog ~/.n8n ma odpowiadać katalogi /home/node/.n8n w kontenerze
        ports: # porty
            - '5678:5678' # na głównej maszynie port 5678 ma kierować do portu 5678 w kontenerze

Na tą chwilę to tyle z podstawowych informacji. Mam nadzieję, że choć trochę przybliżyłem Wam temat tak fajnego narzędzia jak Docker Compose. W następnej notce pokażę Wam jak zrobić deploy niektórych aplikacji (w tym WordPressa) korzystając z Docker Compose 🙂

Dzięki za uwagę,
Ferexio

PS. Czy zauważyłeś że u mnie w plikach YAML każde następne wcięcie to +4 spacje? 🙂

Subscribe
Powiadom o
guest

5 komentarzy
najstarszy
najnowszy oceniany
Inline Feedbacks
View all comments
Dominik
1 rok temu

Dobra robota 👏 czekam na więcej!

Od siebie mogę dorzucić polecenie Dockera, z którego korzystam bardzo często. Jeśli chcemy „zajrzeć” do kontenera można wykorzystać polecenie docker exec -it container_id /bin/sh gdzie container_id należy zastąpić id kontenera (polecenie docker ps -a). Polecenie uruchomi shella dla kontenera. Można również skorzystać z /bin/bash ale nie wszędzie bash będzie dostępny.

Cipexon
Cipexon
1 rok temu
Reply to  Dominik

Można zrobić to w jeszcze prostszy sposób, tylko trzeba być w cmd tam gdzie jest plik docker-compose.yml

docker compose exec -it <nazwa-serwisu-zdefiniowanego-w-docker-compose> bash

Nie musisz dzięki temu znać container_id.

Krzysiek
1 rok temu

Wspaniała robota🔥

KasparHauser
KasparHauser
1 rok temu

Dobry wpis!

5
0
Would love your thoughts, please comment.x