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? 🙂

Mikrus – Alpine jako LAMP

Siema Ubuntu, ooo Debian! Gdzie CentOS? Ooo tu jesteś! Fajnie Was widzieć. Mam złe wieści, Alpine przyjeżdża na plan.

Wszyscy wiedzą że jestem ogromnym fanem Linuxa i że dla mnie nie ma nigdzie miejsca dla Windowsa, a tym bardziej jakbym miał robić swój serwer (windows pfu!). Jak wiecie z poprzednich notek, że ja i Bonn333 staramy się pokazać zalety innych systemów z rodziny Linux – tym razem jest to Alpine 🙂

Najważniejsza zaleta Alpine to minimalizm. Alpine korzysta z OpenRC, zaś taki Debian czy Ubuntu aktualnie ze systemd, a w dodatku na Alpine postawienie pełnego serwera WWW (np. LAMP – Linux Apache2 MySQL PHP) jest o wiele prostsze niż na innych systemach. W tej notce pokażę Wam jak to bardzo prosto można zrobić. Zapraszam do lektury! 🙂

Zaczynamy więc od instalacji potrzebnych nam pakietów (przy okazji proponuję jak zwykle zaaktualizować system 🙂 ):

apk add apache2 apache2-ssl php7-apache2 mysql mysql-client php7-common php7-iconv php7-json php7-gd php7-curl php7-xml php7-mysqli php7-imap php7-cgi fcgi php7-pdo php7-pdo_mysql php7-soap php7-xmlrpc php7-mcrypt php7-gettext php7-ctype php7-dom

Jak już mamy zainstalowane potrzebne nam pakiety to można ruszać z uruchomieniem bazy danych więc wpisujemy:

mysql_install_db --user=mysql --datadir=/var/lib/mysql

No i MariaDB zainicjowała nam bazę danych w katalogu /var/lib/mysql czyli w domyślnym miejscu. Teraz przyda się dodanie procesów apache2 oraz mariadb do autostartu więc wpisujemy:

rc-update add apache2
rc-update add mariadb

Teraz tak, Ci co są chętni zoptymalizować swoją bazę danych mogą skonfigurować MariaDB oraz Apache2 tak aby zyskać na pamięci RAM, Ci co mają dużą ilość ramu to mogą to pominąć całkowicie.
W pliku /etc/my.cnf.d/mariadb-server.cnf dodajemy pod skip-networking takie o to linijki:

innodb=OFF
default-storage-engine=MyISAM

zaś w pliku /etc/apache2/httpd.conf w linii 29 zmieniamy na:

ServerTokens Minimal

a następnie w pliku /etc/apache2/conf.d/mpm.conf w liniach 28-34:

<IfModule mpm_prefork_module>
StartServers 2
MinSpareServers 2
MaxSpareServers 5
MaxRequestWorkers 200
MaxConnectionsPerChild 200
</IfModule>

Jeśli już jest to gotowe to teraz można uruchomić serwer www oraz bazę danych i ustawić hasło do serwera bazy danych 🙂

rc-service apache2 start
rc-service mariadb start
/usr/bin/mysqladmin -u root password 'password'

W miejsce 'password’ wpisać hasło do bazy mysql jakie ma być ustawione.
Od tej pory można robić sobie powolutku własną stronę www w katalogu /var/www/localhost/htdocs/ który jest domyślnym katalogiem dla stron www. WordPress powinien na takiej konfiguracji działać bez żadnych problemów.

Dlaczego akurat w tym poradniku piszę o Alpine? Kto wie ten wie, kto nie wie ten się teraz dowie, że jestem administratorem w projekcie Mikrus i tam system Alpine Linux działa najlepiej i najwydajniej ze wszystkich. Osobiście polecam poeksperymentować z każdym systemem, a później wybrać jeden ze wszystkich, taki z którym najlepiej się współpracuje i w jego kierunku się rozwijać. Jak to powiedział mój dawny nauczyciel ze szkoły średniej, by wybrać jeden system i powoli stawać się specjalistą z zakresu tego systemu 🙂 Miłej zabawy!

PS. Kto zejdzie tak nisko z pamięcią ram?

Tunelowanie SSH – czyli jak udostępnić serwer www w sieci i każdy inny

Jak ma się dużą ilość osób na moim kanale xmpp to zawsze można się czegoś od innych dowiedzieć. Ostatnio Bonn333 pokazał mi coś o czym jeszcze nie wiedziałem, a dokładnie pokazał mi sposób na tunel po protokole SSH.
Zapewne już ktoś powie, że ten sposób jest znany od dawna, no bo faktycznie, sam używam tego do tunelowania ruchu na moim serwerze, choć Bonn333 wykazał się czymś innym.

Za dzisiejszy przykład weźmy sobie serwer WWW nasłuchujący na porcie 80 i uruchomiony na maszynie lokalnej (np. komputer, laptop), oraz serwer vps/shell na którym mamy otwarty port 8881.
Teraz prosimy właściciela serwera shellowego lub vpsa (o ile my nimi nie jesteśmy) o to by w pliku /etc/ssh/sshd_config została odkomentowana linia 88 (u mnie taka właśnie jest) w której znajduje się „GatewayPorts” i wartość no została zmieniona na yes.
Teraz finalnie wystarczy zrestartować serwer ssh i z naszego komputera czy laptopa połączyć się z serwerem za pomocą programu PuTTy dla Windowsa lub poprzez wydanie polecenia ssh dla Linuxa: ssh -NC -R 1.2.3.4:8881:127.0.0.1:80 [email protected]

Dla Windowsa:

Od tego momentu Wasz serwer WWW na laptopie będzie widoczny w internecie pod adresem http://1.2.3.4:8881
Mam nadzieję, że pomogłem zrozumieć jak to działa 🙂

PS. Jeżeli w PuTTy źle coś zrobiłem to napiszcie mi o tym bo ja używam tylko Linuxa 🙂

===== Aktualizacja notki =====

Przez to, że notka nie jest dla niektórych dość przejrzysta to postanowiłem ją z lekka poprawić na przykładzie serwera vps w Aruba Cloud.
Aby zacząć należy zakupić serwer vps w Aruba Cloud (za 4zł + VAT = 4,92zł wystarczy), a następnie zalogować się na konto root.

Teraz na naszym komputerze czy na jakimkolwiek urządzeniu musimy zrobić klucze ssh, więc na Linuxie wydajemy polecenie:

ssh-keygen -b 4096

Kiedy poprosi nas o podanie ścieżki to podajemy katalog w którym chcemy by klucze się znalazły, gdzie w moim przypadku jest to: /home/user/.ssh/
Na Windowsie używamy do tego oczywiście programu puttygen.

Kiedy już na serwerze vps zmienimy odpowiednią linijkę (podaną powyżej) i go zrestartujemy to teraz można zgrać klucz publiczny na serwer i go dopisać do pliku authorized_keys (instrukcja podana dla Linuxa):

Na laptopie/rasberce:

scp /home/user/.ssh/id_rsa.pub [email protected]:/root/.ssh/id_rsa.pub

Na serwerze:

cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys

Dla Windowsa robimy tak, że generujemy klucz w puttygen, następnie kopiujemy to co wyświetli puttygen, logujemy się na serwer za pomocą putty i dopisujemy do wyżej podanego pliku (/root/.ssh/authorized_keys) zawartość z puttygen, a klucze zapisujemy na pulpicie.

Teraz aby się połączyć z serwerem wpisujemy na Linuxie komendę podaną wyżej z dopiskiem -i /sciezka/do/klucza, zaś na Windowsie w programie PuTTy w Connection->SSH->Auth podajemy klucz i zapisujemy połączenie, a następnie logujemy się na serwer z udostępnionym portem na zewnątrz.

Na Linuxie można jeszcze zrobić taki myk, że zrobić plik /usr/bin/remote i w nim zrobić taki wpis:

#!/bin/bash
ssh -i /home/user/.ssh/id_rsa -NC -R 1.2.3.4:8881:127.0.0.1:80 [email protected]

Teraz tylko należy wydać polecenie chmod +x /usr/bin/remote i zawsze po włączeniu laptopa/komputera czy rasberki wydać polecenie remote by z automatu połączyło z serwerem vps.

Reinstalacja Debiana na serwerze VPS w Aruba Cloud

Serwer vps w Aruba Cloud jest jednym z najtańszych vpsów w Polsce. Niestety jednego czego mu brakuje
to „tryb rescue” w razie np. problemów przy aktualizacji. Podczas konfigurowania jednego z serwerów vps nagle zachciało mi się
zmienić system na nim, a także ustawić partycje na nim po swojemu i dzięki pomysłowi Evilusa doszliśmy do pewnego etapu, przy
którym wirtualne napędy mogą zostać uznane za niepotrzebne.

Aby uruchomić instalator Debiana musicie mieć na serwerze każdy dowolny system z którego jesteście w stanie zmienić regułki gruba, a następnie poszukać idealnego katalogu który podczas rozruchu będzie mógł nam podać pliki obrazów do uruchomienia. W katalogu /boot na serwerze vps
mamy sporo miejsca by pobrać te pliki i uruchomić, więc wpisujemy:

cd /boot
mkdir netboot
cd netboot
wget
http://ftp.debian.org/debian/dists/stable/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
wget
http://ftp.debian.org/debian/dists/stable/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux

Teraz jak już mamy te pliki to edytujemy zawartości pliku /etc/grub.d/40_custom odpowiednimi słowami pod
komentarzami:

menuentry 'Debian Netinstall' {
insmod part_msdos
insmod ext2
set root='(hd0,msdos1)'
linux /netboot/linux
initrd /netboot/initrd.gz
}

Teraz tylko wystarczy wydać komendę update-grub, wejśc do panelu admina Aruba Cloud i w konsoli (konsola odzyskiwania)
jak się nam wyświetli logowanie do systemu to na dole wciskamy kilka razy sekwencję CTRL ALT DEL i przy opcji bootowania
wybieramy Debian Netinstall.

Podczas reinstalacji Debiana możecie sobie ustawić partycje według własnej potrzeby, mi się natomiast przydały takie partycje
jak
/boot, /, /home, oraz /var/www/.
Dlaczego /var/www ? Gdyż po prostu w razie reinstalowania vpsa pliki od strony zostaną, zaś w /home zrobiłem sobie jeszcze
katalog mysql z plikami od bazy danych.
Skrypt od kopii bazy danych wygląda następująco:

#!/bin/bash
datautw=`date +"%d-%m-%Y"`
user='uzytkownikbazydanych'
password='haslobazydanych'
baza='nazwabazydanych'
host='localhost'
/usr/bin/mysqldump -u $user -p$password -h $host $baza > /tmp/bazadanych.sql
/bin/tar -czvf /tmp/bazadanych_$datautw.tar.gz /tmp/bazadanych.sql
rm -rf /tmp/bazadanych.sql
mv /tmp/bazadanych_$datautw.tar.gz /home/mysql/

Wyżej pokazana możliwość zadziała wszędzie gdzie jest konsola, gdyż przy rozruchu będzie możliwość wybrania systemu i jego
zainstalowania.
Mam nadzieję, że tym poradnikiem pomogłem wielu osobom i że chociaż troszkę zaskoczyłem tych co we mnie nie wierzyli 😉