Stacja pogodowa – konfiguracja Docker’a

docker-compose

Tak jak pisałem w poprzednim artykule przyszedł czas na opisanie procesu budowania i uruchamiania środowiska backendowego i forntendowego dla Stacji Pogodowych. Całość mojego rozwiązania opiera się na uruchomieniu odpowiednich kontenerów Docker’owych (jeśli słowo Docker nic ci nie mówi to zajrzyj najpierw tu) na środowisku Raspberry Pi4. Zacznijmy jednak od początku, czyli pobrania źródeł i zbudowania wszystkich niezbędnych części całej aplikacji.

Budowanie frontendu

Po pobraniu repozytorium należy zainstalować wszystkie niezbędne pakiety npm , a następnie zbudować biblioteki wspólne i aplikację.

cd app

npm ci

npm run build:core
npm run build:weather-stations

npm build

Wówczas w katalogu /dist/app powinien być zbudowana nasza aplikacja frontendowa.

Budowanie aplikacji backendowych

Aplikacja backendowa, tak jak już wiesz z poprzedniego artykułu, składa się z dwóch części:

  • serwisu MQTT, który nasłuchuje na różne zdarzenia spływające ze Stacji Pogodowych
  • serwisu WEB, który serwuje aplikację forntendową i odpowiada za wystawienie API, którego ona potrzebuje

Przejdźmy zatem do zbudowania powyższych aplikacji:

cd server

npm ci

npm run build:weather-stations-module

npm run build:weather-stations-subscriber
npm run build:weather-stations-microservice

Te polecenia spowodują zbudowanie wspólnego modułu a następnie zbudowanie serwisu MQTT i serwera WEB. Wynikiem działania tych poleceń będzie utworzenie dwóch katalogów:

  • /server/dist/apps/weather-stations-subscriber – serwer MQTT
  • /server/dist/apps/weather-stations-microservice – serwer WEB

W obu katalogach będą się znajdowały pliki:

  • main.js – plik aplikacji
  • package.json – plik z listą niezbędnych pakietów npm do zainstalowania przed uruchomieniem

Docker Compose

Przyszedł czas na skonfigurowanie naszego dockera, do tego celu użyję docker compose, który pozwoli mi na stowrzenie pliku konfiguracyjnego z trzema kontenerami:

  • bazy danych
  • serwera MQTT
  • serwera WEB

W zależności od tego na jakiej architekturze stawiamy nasze środowisko to trzeba znaleźć odpowiednią wersję obrazu dla każdego kontenera. W moim przypadku konfiguracja jest przeznaczona dla Raspberry Pi4 z zainstalowanym Ubuntu Server na którym zainstalowany jest docker i docker-compose.

Baza danych

version: '2.4'
services:
  database:
    container_name: ws-database
    image: webhippie/mariadb
    environment:
      - TZ=Europe/Warsaw
      - MARIADB_ROOT_PASSWORD=password
      - MARIADB_DATABASE=database_name
      - MARIADB_USERNAME=username
      - MARIADB_PASSWORT=password
    restart: always
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d
      - ./mysql/mysql:/var/lib/mysql
      - ./mysql/backup:/var/lib/backup
    expose:
      - 3306
    ports:
      - 3308:3306
    mem_limit: 512m
    mem_reservation: 256m

Baza danych oparta jest na obrazie webhippie/mariadb należy dla niej skonfigurować:

  • nazwę „database” tak by inne kontenery w obrębie tego projektu mogły się wewnętrznie z nią komunikować
  • środowisko (strefę czasową, nazwę, użytkownika i hasło do bazy danych)
  • wolumeny (podpiąć lokalne katalogi pod odpowiednie katalogi na kontenerze, tak by w przypadku usunięcie kontenera nie stracić danych)
  • wystawić na zewnątrz port 3306 (domyslny dla bazy MySQL) i przekierować go na inny port na maszynie host (w moim przypadku jest to 3308)
  • ustawiłem też parametry pamięci jaką może zużyć kontener, w tym przypadku jest to 256MB – 512MB, niby nie wiele jak na bazę danych, jednak do takiego zastosowania jakim jest zbieranie danych w domowym zaciszu wystarczy w zupełności

Dodatkowy obraz dockera dla aplikacji nodejs

Jak już wiemy z poprzedniej części tego artykułu nasz serwer WEB aby mógł się uruchomić potrzebuje zainstalowania odpowiednich paczek NPM i uruchomienia pliku main.js. Problem polega jednak na tym, że chcemy to zautomatyzować, tak by nasz docker się uruchomił i zrobił te dwie czynności sam, tak byśmy nie musieli przy każdym starcie dockera tego robić sami. W tym celu przygotowujemy swój obraz dockera na bazie już istniejącego.

Ja wybrałem obraz balenalib/raspberrypi4-64-debian-node:latest, który pasował do mojej architektury. W tym celu tworzymy plik Dockerfile o poniższej treści.

FROM balenalib/raspberrypi4-64-debian-node:latest

WORKDIR /usr/src/app

COPY ./start-app.sh /usr/local/bin

RUN npm -v
RUN /bin/bash -c "node -v"

EXPOSE 8080

Pierwsza linijka tego pliku odpowiada za wskazanie obrazu na bazie którego stworzymy własny kontener. Następnie tworzę na kontenerze katalog /usr/src/app który będzie moim katalogiem roboczym (podlinkuję tu aplikację, ale o tym później). Kolejnym krokiem jest przekopiowanie pliku start-app.sh do kontenera, jego zawartość znajduje się poniżej.

#!/usr/bin/env bash
echo "install npm ..."
npm i
node main.js

Nie ma w nim nic nadzwyczajnego: zainstalowanie pakietów i uruchomienie pliku main.js.

Wrócmy jednak do naszego Dockerfile, kolejne dwa kroki to tylko wyświetlenei informacji o wersji npm i node. Ostatnia linijka wystawia jednak port 8080 z kontenera na świat zewnętrzny.

Właściwie to już wszystko teraz wystarczy tylko zbudować kontener i zarejestrować go lokalnie. Poniższe polecenie buduje obraz kontenera na podstawie Dockerfile znajdującego się w katalogu ./ i rejestruje go pod nazwą node-web-app.

docker build -t node-web-app ./

Od teraz w naszych projektach można używać tego kontenera.

Serwer WEB

Zatem mając już zbudowany odpowiedni obraz node-web-app popatrzmy na konfiguracje kontenera, który będzie to obsługiwał serwer WEB.

api:
    container_name: ws-api
    image: node-web-app:latest
    environment:
      - TZ=Europe/Warsaw
    volumes:
      - ./www/api/:/usr/src/app
    ports:
      - 8080:8080
    links:
      - "database:database"
    command:
      - start-app.sh
    mem_limit: 256m
    mem_reservation: 128m

Tak jak wcześniejszy kontener tak i ten ma swoją konfigurację:

  • środowiska – tym razem jest tylko strefa czasowa
  • wolumeny – chyba najważniejsza wartość, lokalny katalog w którym są pliki main.js, pacakage.json i config.env podlinkowujemy do katalogu roboczego w kontenerze na którym będzie uruchamiany skrypt start-app.sh.
  • porty – wystawiamy port 8080 jako 8080 na maszynie host
  • powiązania – ustawiamy podlinkowanie do kontenera bazy danych
  • polecenie – ustawiamy polecenie jakie ma zostać wywołane na kontenerze jeśli się uruchomi
  • pamięć – podobnie jak w przypadku serwera bazy danych ustawiamy parametry pamięci, w przypadku tego serwera o połowę mniejsze niż poprzednio

Serwer MQTT

Konfiguracja serwera MQTT jest niemal analogiczna jak serwera WEB

  mqtt:
    container_name: ws-mqtt
    image: node-web-app:latest
    environment:
      - TZ=Europe/Warsaw
    volumes:
      - ./www/mqtt/:/usr/src/app
    ports:
      - 8081:8080
    links:
      - "database:database"
    command:
      - start-app.sh
    mem_limit: 256m
    mem_reservation: 128m

Różnica polega wyłącznie w podlinkowanym katalogu w którym znajduje się zbudowana paczka z aplikacją i przekierowywanym porcie.

Podsumowując nasz cały plik konfiguracyjny wygląda tak:

version: '2.4'
services:
  database:
    container_name: ws-database
    image: webhippie/mariadb
    environment:
      - TZ=Europe/Warsaw
      - MARIADB_ROOT_PASSWORD=password
      - MARIADB_DATABASE=database_name
      - MARIADB_USERNAME=username
      - MARIADB_PASSWORT=password
    restart: always
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d
      - ./mysql/mysql:/var/lib/mysql
      - ./mysql/backup:/var/lib/backup
    expose:
      - 3306
    ports:
      - 3308:3306
    mem_limit: 512m
    mem_reservation: 256m

  api:
    container_name: ws-api
    image: node-web-app:latest
    environment:
      - TZ=Europe/Warsaw
    volumes:
      - ./www/api/:/usr/src/app
    ports:
      - 8080:8080
    links:
      - "database:database"
    command:
      - start-app.sh
    mem_limit: 256m
    mem_reservation: 128m

  mqtt:
    container_name: ws-mqtt
    image: node-web-app:latest
    environment:
      - TZ=Europe/Warsaw
    volumes:
      - ./www/mqtt/:/usr/src/app
    ports:
      - 8081:8080
    links:
      - "database:database"
    command:
      - start-app.sh
    mem_limit: 256m
    mem_reservation: 128m

Struktura plików jest następująca:

  • docker-composer.yaml – konfiguracja kontenerów
  • www/mqtt – katalog z aplikacją serwera MQTT
  • www/api – katalog z aplikacją serwera WEB
  • www/api/app – katalog zawierający skompilowaną aplikację frontendową
  • mysql/mysql – katalog na pliki bazy danych

Teraz wystarczy z linii poleceń wywołać

docker-compose up -d

i nasza aplikacja po chwili się uruchomi i będzie dostępna pod portami 8080 i 8081.

Podsumowanie

Dzięki takiemu rozwiązaniu bardzo łatwo wdrażać zmiany w aplikacji i uruchamiać ją ponownie w razie potrzeby. Wszystkie niezbędne pliki znajdują się na maszynie hosta więc nawet rozsypanie się kontenera z jakichś niewyjaśnionych przyczyn jest łatwe do naprawy. Kolejnym plusem całego rozwiązania jest to, że można w łatwy sposób przenieść je na inny serwer.

Stacja pogodowa – konfiguracja Docker’a
Przewiń do góry