W sytuacji, gdy potrzebujemy uruchomić określoną publicznie dostępną usługę (np. serwer WWW hostujący naszą witrynę czy serwer FTP do współdzielenia plików), zwykle korzystamy z rozwiązań firm hostingowych czy różnych ofert serwerów VPS bądź dedykowanych. Jest to poprawne podejście, ponieważ w domowych warunkach często nie mamy możliwości zapewnienia przyzwoitego poziomu stabilności danej usługi (chociaż wiadomo, iż nie każdy serwis wymaga poziomu SLA wynoszącego 99%). Nie zawsze możemy używać publicznego adresu IP wymaganego w takich przypadkach. Pod względem ekonomicznym często nie ma już żadnego uzasadnienia.
Natomiast nie wszystkie usługi muszą być dostępne publicznie. Przykładem urządzenia mogącego pełnić rolę skromnego lokalnego serwera będzie Raspberry Pi. Ta seria minikomputerów jest powszechnie używana w zastosowaniach IoT oraz automatyki domowej (polecam zapoznać się z systemem Domoticz).
W tym tekście chciałbym przedstawić mniej oczywiste, ale dzięki temu pewnie ciekawsze użycie Raspberry Pi. Wszystkie opisane aplikacje uruchamiam w formie kontenerów Docker i mocno sugeruję właśnie ten sposób. Dzięki konteneryzacji nie musimy przygotowywać środowiska pod konkretne oprogramowanie (różne zależności mogą nie być ze sobą zgodne), co zdecydowanie redukuje czas wdrożenia danej usługi, jak również ułatwia zarządzanie nimi.
Znajomość Docker’a to także ważna umiejętność w wielu specjalizacjach IT, co można łatwo zauważyć w ofertach pracy zamieszczonych m.in. na portalu theprotocol.it. Konteneryzacja znajduje zastosowanie zarówno podczas tworzenia systemu (łatwość „przełączania” wersji języka programowania przy pracy z wieloma projektami), jak i podczas wdrożeń (warto przeczytać mój poprzedni tekst dotyczący GitLab CI/CD).
Konfiguracja Raspberry Pi i instalacja Docker
Do zapisu obrazu wybranego systemu (w moim przypadku Ubuntu Server) na karcie SD możemy użyć narzędzia Raspberry Pi Imager. Po wyborze systemu i nośnika należy kliknąć ikonę koła zębatego i ustawić hostname oraz włączyć obsługę SSH. jeżeli zamierzamy korzystać z WiFi, to zaznaczamy dodatkowo opcję Configure wireless LAN (nazwa sieci i hasło powinny zostać pozyskane z naszego systemu).
Po zakończeniu operacji formatowania karty i zapisu obrazu automatycznie uruchomi się weryfikacja poprawności tego zapisu. Można kliknąć CANCEL VERIFY, aby trochę przyspieszyć cały proces.
Przygotowaną kartę SD należy umieścić w slocie urządzenia. jeżeli korzystamy z obudowy do Raspberry Pi, to zwykle najwygodniejszym sposobem jest chwilowe odmontowanie płytki, wsunięcie karty i ponowne zamontowanie płytki w obudowie.
Adres IP zostanie prawdopodobnie pobrany z serwera DHCP. Proponuję jednak ustawić statyczny adres IP w konfiguracji Netplan (kompletne przykłady znajdziemy w dokumentacji) lub dodać rezerwację adresu na poziomie serwera DHCP.
Użytkownik, który został utworzony, ma pełne uprawnienia do uruchamiania poleceń jako root. Z jego konta możemy wykonywać wszelkie czynności administracyjne. Polecenie docker nie wymaga jednak uprawnień root, dlatego warto utworzyć drugiego użytkownika standardowego.
Docker’a najlepiej zainstalować z repozytorium projektu. Kroki są standardowe:
Aby użytkownik mógł wykonywać polecenie docker bez uprawnień sudo, musimy przypisać go do adekwatnej grupy.
Portainer
Portainer umożliwia zarządzanie naszymi kontenerami poprzez panel w przeglądarce. Nie jest absolutnie wymagany, ale przy większej ilości utworzonych kontenerów graficzny widok może być przydatny. W celu uruchomienia Portainer wystarczy wykonać:
Wchodzimy na port 9443 protokołem HTTPS i ustawiamy hasło dla naszego użytkownika. W wersji Community możemy posiadać tylko jednego użytkownika. Następnie wystarczy kliknąć widoczny przycisk Get started, a środowisko zostanie automatyczne skonfigurowane i będzie gotowe do pracy.
AdGuard
AdGuard to bardzo interesujące rozwiązanie pozwalające na uruchomienie własnego serwera DNS i DHCP. Dzięki temu zablokujemy różne niechciane domeny, a ochrona obejmie wszystkie urządzenia w naszej sieci (zakładając użycie DHCP z AdGuard bądź ustawienie serwera DNS na poziomie routera).
W przypadku Docker’a korzystanie z funkcjonalności serwera DHCP wymaga uruchomienia kontenera z parametrem –net host. Mapowane są wszystkie porty udostępniane przez kontener, więc nie ma potrzeby ich podawania poprzez parametr -p (i tak zostaną zignorowane).
AdGuard będzie przechowywał dane w określonym bind mount (opcje -v), a więc w katalogu z lokalnego systemu plików. Dzięki temu nie utracimy danych (dla AdGuard to logi zapytań i konfiguracja) po usunięciu kontenera. Będziemy mogli także odtworzyć naszą konfigurację na innym urządzeniu lub po zwykłej reinstalacji systemu.
Ustawiamy ponadto nazwę naszego kontenera, aby nie została przypisana losowa. Jako ciekawostkę dodam, iż te nazwy (para przymiotnik + nazwisko znanej osoby) są generowane na podstawie list z pliku names-generator.go. Został dodany jeden wyjątek — zabezpieczenie przed użyciem nazwy boring_wozniak oznaczone komentarzem
/* Steve Wozniak is not boring */
Znajomość podobnych „zaskakujących” faktów o różnych technologiach czasami się przydaje, ale głównie w zakresie pisania artykułów czy prowadzenia prezentacji, aby przyciągnąć uwagę odbiorcy. Natomiast w codziennej pracy postawiłbym raczej na praktyczną wiedzę, którą możemy zdobywać z różnych źródeł, np. blog.theprotocol.it.
Bardzo istotne jest też dodanie parametru –restart unless-stopped. Zapewnia to, iż kontener zostanie automatycznie uruchomiony po restarcie systemu lub daemon’a Docker, chyba iż wcześniej został zastopowany.
Z kolei parametr -d odpowiada za uruchomienie kontenera w tle.
Pełne polecenie uruchamiające kontener z AdGuard ma postać:
Po pierwszym uruchomieniu (bez zapisanych ustawień) na porcie 3000 będzie tymczasowo działał panel konfiguracyjny. W drugim kroku należy ustawić zewnętrzne interfejsy dla panelu administratora i serwera DNS.
Następnie ustawiamy nazwę i hasło dla naszego użytkownika. Przechodzimy przez kolejne dwa kroki konfiguracji i logujemy się do panelu.
Nie chciałbym może doradzać konkretnej konfiguracji, bo tę każdy musi dostosować do własnych potrzeb, ale wskażę najważniejsze funkcje. W zakładce Ustawienia -> Ustawienia główne mamy możliwość aktywować usługi Bezpieczne Przeglądanie, Kontrola Rodzicielska i bezpieczne wyszukiwanie.
Dalej w zakładce Filtry -> Listy zablokowanych DNS polecam aktywować drugą listę, czyli AdAway Default Blocklist. Można też dodać własne reguły, dostępne są gotowe przygotowane dla AdGuard na podstawie zewnętrznych list, w tym listy złośliwych domen od CERT Polska.
Na koniec warto uruchomić wspomniany serwer DHCP. Nie jest to szczególnie skomplikowane, bo ogranicza się do uzupełnienia pięciu pól formularza znajdującego się w zakładce Ustawienia -> Ustawienia DHCP. Wszystkie wartości z wyjątkiem zakresu adresów są nam proponowane.
changedetection.io
To narzędzie służy do monitorowania zmian na wskazanych stronach. Można skonfigurować wysyłkę powiadomień na wybrane przez nas usługi (changedetection.io wykorzystuje do tego zewnętrzne rozwiązanie Apprise, a lista obsługiwanych platform jest spora). Domyślnie wczytywany jest wyłącznie kod strony (czyli technicznie nazywamy to HTTP message body). jeżeli pojawi się potrzeba wykonania pewnych akcji na danej stronie (zalogowanie się, wcześniejsze zamknięcie komunikatu o cookies, itp.) możemy zintegrować changedetection.io z Playwright, a następnie „wyklikać” odpowiednie działanie.
Najlepszym sposobem uruchomienia changedetection.io wraz z Playwright jest użycie docker-compose. Tworzymy więc nowy folder (np. monitoring) i zapisujemy w nim plik docker-compose.yml.
docker-compose służy do pewnej automatyzacji, ale też standaryzacji uruchamiania wybranych kontenerów. Nie ma potrzeby wykonywania docker run dla wszystkich kontenera podając za każdym razem inne parametry. Wszystkie potrzebne informacje zapisujemy w jednym pliku YML. Uruchomienie kontenerów obejmuje jedynie wykonanie polecenia docker-compose up -d (jeśli nazwa pliku nie jest standardowa — docker-compose.yml — wskazujemy plik parametrem -f).
Kontener z changedetection.io będzie nasłuchiwał na porcie 5000. Najbardziej najważniejsze ustawienia to określenie czasu kolejnych weryfikacji zawartości dodanych stron w zakładce SETTINGS -> General. Dodatkowo w karcie Fetching można wskazać, aby używany był Playwright zamiast zwykłych żądań HTTP, a w karcie Notifications konfigurujemy sposoby otrzymywania powiadomień o zmianach zawartości.
Kroki, które mają zostać wykonane po załadowaniu strony, definiujemy w edycji danej witryny po wejściu w kartę Browser Steps.
Przykładowe powiadomienie wysyłane przez opisywane narzędzie wygląda następująco.
Zabbix
Zabbix to chyba najbardziej rozpoznawalne narzędzie do monitoringu systemów. Potrafię to zrozumieć — ze wszystkich innych znaczących rozwiązań Zabbix wyróżnia się wygodą użytkowania i czytelnym interfejsem. Wstępna konfiguracja jest nieunikniona, natomiast późniejsze działanie jest niemal „bezobsługowe”.
Kwestia monitoringu naszych środowisk jest ważnym aspektem pracy administratora IT czy specjalisty od cyberbezpieczeństwa. Wczesne otrzymanie alertu informującego przykładowo o kończącym się miejscu na dysku pozwoli podjąć odpowiednie działania, a ostatecznie może zapobiec całkowitej niedostępności środowiska. Znajomość zagadnień monitoringu zdecydowanie okaże się przydatna na stanowisku związanym z security.
Zabbix może być uruchomiony w kontenerach, dostępne są choćby oficjalne pliki dla docker-compose. U siebie używam docker-compose_v3_alpine_mysql_latest.yaml, ale należy do tego pliku wprowadzić kilka znaczących usprawnień. Na początek klonujemy jednak całe repozytorium (zawarty w nim katalog env_vars będzie potrzebny do działania Zabbix).
Domyślnie interfejs Zabbix będzie dostępny na porcie 80. Wcześniej ten port został zajęty przez AdGuard, dlatego zmieniam port lokalny na 8080 dla kontenera z NGINX.
Łatwo zauważyć, iż żaden z kontenerów nie ma przypisanej nazwy, więc jeżeli nie chcemy zobaczyć losowych, warto ustawić jednoznaczną nazwę dla wszystkich z nich. Służy do tego opcja container_name. Nie trzeba poświęcać może czasu w wymyślanie tych nazw, a skorzystać z nazw poszczególnych obrazów.
Przydane będzie ponadto ustawienie statycznego adresu IP sieci dla kontenera zabbix-server. Zapis powinien wyglądać w ten sposób.
Polecam również dodać restart: unless-stopped dla kontenerów mysql-server i zabbix-web-nginx-mysql.
Z doświadczenia wiem, iż pojawia się problem ze startem niektórych kontenerów Zabbix (w konsekwencji narzędzie nie jest dostępne) po restarcie systemu. Rozwiązaniem jest dodanie poniższego cron’a:
Ostatnią zmianą będzie modyfikacja wartości ZBX_SERVER_NAME w pliku env_vars/.env_web. Domyślnie jest ustawione Composed installation, co nie musi nam koniecznie odpowiadać. Ta nazwa jest używana w tagu <title> przy generowaniu zawartości strony (backend Zabbix jest napisany w języku PHP) oraz pod logiem Zabbix w interfejsie webowym.
Możemy w końcu uruchomić kontenery.
Widoczny kontener db_data_mysql z obrazem busybox nie będzie nam już potrzebny, więc oba możemy usunąć. Dotyczy to też service db_data_mysql w naszym pliku YML.
Przejdziemy teraz do podstawowej konfiguracji Zabbix, ale należy poczekać, aż do bazy danych zostanie wykonany import, a sam serwer Zabbix będzie gotowy do pracy. W logach kontenera musimy po prostu zobaczyć komunikat Starting Zabbix Server. Zabbix 6.4.7 (revision 2ac2006).
Zabbix działa na wcześniej ustawionym porcie 8080, a domyślne dane logowania to użytkownik Admin i hasło zabbix. Hasło można zmienić przechodząc do zakładki Users -> Users i wybierając użytkownika Admin.
Ten użytkownik będzie służył do zarządzania naszą instalacją Zabbix, natomiast raczej nie warto korzystać z pełnych uprawnień w celu zwykłego sprawdzenia monitoringu. Powinniśmy dodać nowego użytkownika; ta możliwość znajduje się w tej samej zakładce pod opcją Create user. Przypisujemy go do grupy Internal, a w karcie Permissions wskazujmy rolę User role.
Oprócz tego musimy nadać prawo do odczytu danych z monitoringu dodanych hostów dla całej grupy Internal. Przechodzimy do zakładki Users -> User groups, dalej wybieramy Internal i w karcie Host permissions zaznaczamy wszystkie grupy hostów, nadając im uprawnienie Read.
Ważne jest również ustawienie strefy czasowej w zakładce Administration -> General -> GUI. To adekwatnie wszystkie kroki z zakresu wstępnej konfiguracji, więc na zakończenie można zmienić układ elementów naszego dashboard, ponieważ domyślny może nie być idealny. Zastosowane w moim przypadku ustawienie pokazane jest poniżej.
To także czas na skonfigurowanie powiadomień. Nie jest to najbardziej intuicyjny proces. W tym celu w zakładce Administration -> Macros dodajemy nowe makro {$ZABBIX.URL}, którego wartością powinien być pełny adres URL naszej instancji Zabbix.
Następnie w zakładce Alerts -> Media types klikamy Disabled (zmieni się na Enabled) przy nazwie wybranej usługi do obsługi powiadomień. Dalej w Alerts -> Actions -> Trigger actions ponownie klikamy Disabled przy akcji Report problems to Zabbix administrators.
Należy jeszcze ustawić samo powiadomienie. Wykonujemy to, przechodząc do edycji użytkownika (np. Admin) w karcie Media.
Ostatnim krokiem będzie dodanie hosta do monitoringu. Aktualnie Zabbix na liście monitorowanych obiektów ma zawarty jedynie Zabbix server, czyli adekwatnie sam kontener. Proponuję usunąć ten host, zainstalować agenta na Raspberry Pi i dodać ten host do monitoringu.
Proces instalacji agenta ogranicza się do pobrania odpowiedniego pliku ze strony https://repo.zabbix.com/zabbix/6.4/ (wersja 6.4, bo serwer Zabbix mamy zainstalowany w tej wersji). Zgadzać się musi ponadto wersja naszej dystrybucji. Dla Ubuntu 22.04 działającego na Raspberry Pi (więc architektura ARM) odpowiedni plik znajduje się tutaj: https://repo.zabbix.com/zabbix/6.4/ubuntu-arm64/pool/main/z/zabbix-release/zabbix-release_latest%2Bubuntu22.04_all.deb.
Użycie polecenia sudo dpkg -i zabbix-release_latest+ubuntu22.04_all.deb doda do naszego systemu repozytorium, z którego już bez trudności zainstalujemy agenta.
Następnie w pliku /etc/zabbix/zabbix_agentd.conf ustawiamy wartości Server (adres IP serwer Zabbix, podany wcześniej w pliku YML) i Hostname (nazwa naszego hosta). Restartujemy usługę agenta.
W panelu Zabbix przechodzimy z kolei do zakładki Monitoring -> Hosts, gdzie z użyciem opcji Create host dodajemy nowego klienta do monitoringu. Uzupełniamy wartości zgodnie ze zrzutem ekranu.
Aktywność systemu zainstalowanego na Raspberry Pi będzie od dzisiaj gromadzona. Zabbix wyśle alert po naruszeniu określonych parametrów, odpowiedni komunikat pojawi się także na dashboard. Ponadto istnieje możliwość sprawdzenia generowanych wykresów z działania różnych elementów naszego środowiska.
Kasm Workspaces
Intrygujący projekt umożliwiający dostęp do skonteneryzowanych systemów operacyjnych lub aplikacji poprzez przeglądarkę. Autorzy udostępniają na GitHub’ie adekwatnie kompletnie gotowe pliki Dockerfile, więc po naszej stronie wystarczy z użyciem docker build zbudować lokalne obrazy i je uruchomić w razie potrzeby. Pliki można też dostosować do własnych wymagań — Dockerfile ma wyjątkowo czytelny format.
I właśnie dla przykładu utworzymy nasz obraz Ubuntu 22.04 w wersji desktop budowany na podstawie pliku dockerfile-kasm-ubuntu-jammy-desktop. W zmiennej INST_SCRIPTS można zauważyć nazwy narzędzi, z których nie będziemy raczej korzystać na co dzień, a już na pewno nie w postaci kontenera. Przyjmijmy, iż skrypty zawarte poniżej będą wystarczające (oczywiście nie jest to przykład wzorcowy — można wskazać własne pakiety, których zwyczajnie potrzebujemy).
Chromium został wybrany ze względu na architekturę procesora Raspberry Pi, czyli arm64, której Chrome niestety nie obsługuje. Zresztą można się tego dowiedzieć ze skryptu instalacyjnego.
Z kolei składnia polecenia docker build będzie wyglądać następująco.
Parametr -t określa nazwę obrazu. Wybrałem ubuntu-light, bo znacznie ograniczyłem ilość instalowanego systemu w stosunku do oryginału. Poprzez -f wskazujemy nazwę używanego pliku. Ważna jest też kropka na końcu, ponieważ dzięki temu Docker będzie wiedział, iż powinien korzystać z danych w katalogu bieżącym (wszystkie skrypty SH znajdziemy w katalogu src/ubuntu/install).
Uruchomienie tak utworzonego obrazu również nie jest złożonym zadaniem.
W zmiennej VNC_PW przekazujemy hasło do Basic Auth. Loginem zawsze jest kasm_user. Dostęp do działającego kontenera uzyskamy protokołem HTTPS na porcie 6901.
Jeśli nie mamy powodów, aby modyfikować obrazy we własnym zakresie, możemy skorzystać z gotowych oficjalnych obrazów z hub.docker.com. Jednego z takich obrazów użyłem do uruchomienia Kali Linux.
Niezależnie czy obraz zbudowaliśmy samodzielnie, czy też skorzystaliśmy z gotowego, nie mamy dostępu do root wewnątrz kontenera. Ciężko określić to mianem „problemu”, tym bardziej iż twórcy rozwiązanie podają w dokumentacji. Wystarczy w Dockerfile, np. zaraz pod zmienną przechowującą ścieżki do skryptów instalacyjnych, dopisać:
Dodanie wpisu z polecenia echo do pliku /etc/sudoers umożliwi użytkownikowi kasm-user (domyślny) wykonywanie wszystkich poleceń z użyciem sudo bez podawania hasła. Po zbudowaniu takiego obrazu dostęp do root będzie możliwy.
Watchtower
Watchtower nie jest kolejnym przykładem usługi do postawienia na Raspberry Pi, tylko narzędziem do łatwej aktualizacji obrazów działających kontenerów. Obrazy w zasadzie wszystkich znaczących projektów są często i regularnie aktualizowane. Nie zawsze są to oczywiście zmiany, które jakkolwiek zauważymy, natomiast co pewien czas rzeczywiście powinniśmy wykonywać aktualizacje.
To narzędzie potrafi zaktualizować każdy kontener bez przestojów w jego działaniu. Obsługa nie musi wymagać żadnych dodatkowych akcji, z użyciem argumentu –cleanup zostaną usunięte poprzednie wersje obrazów. Wystarczy uruchomić kontener z watchtower.
Na poniższym zrzucie ekranu z Portainer widać wszystkie uruchomione kontenery opisane w tym artykule. Kontener z changedetection.io jak właśnie aktualizowany przez watchtower.
Podsumowanie
Używane w tym tekście Raspberry Pi 4 posiada 4 GB pamięci RAM i 4-rdzeniowy procesor ARM Cortex-A72. Nie jest to pod żadnym względem środowisko przystosowane do utrzymywania ogromnych serwisów, ale doskonale sprawdza się w podobnych jak opisane zastosowaniach. Polecam też próbować konfigurować od podstaw własne usługi (niekoniecznie z użyciem Docker), ponieważ to chyba najlepszy praktyczny sposób zdobywania umiejętności z zakresu Linux’a.