Uruchamiamy aplikację w NodeJS na NanoPi w środowisku Docker

W tym przewodniku uruchomimy aplikację w NodeJS na platformie NanoPI w środowisku Docker.

By rozpocząć, podłączamy NanoPI do komputera i weryfikujemy poprawność połączenia. W tym celu podłączamy płytkę NanoPi NEO (na obrazku powyżej) do komputera poprzez interfejs UART na sposób zilustrowany poniżej.

Połączenie UART realizujemy łącząc piny UART płytki FT232 z pinami płytki NanoPi NEO, odpowiednio: GND z GND, TX0 ("transmit, złącze pierwsze") z RX0 ("receive złącze pierwsze") oraz RX0 z TX0. Ogólna zasada brzmi tutaj: porty łączymy na przemian - port wysyłający łączymy z odbierającym; port odbierający z wysyłającym.

Do płytki NanoPi NEO dołączamy zasilanie poprzez wbudowany port mikro-USB. Płytkę TX232 podłączamy do komputera kablem mini-USB. Po zadbaniu o obecność karty microSD z systemem operacyjnym w porcie płytki NanoPi NEO, mikrokontroler uruchomi się.

Z płytką nawiązujemy kontakt wpisując sudo minicom -b 115200 -o -D /dev/ttyUSB0, a następnie naciskamy klawisz [Enter] aby wyświetlić linię zachęty podobną do pi@NanoPi-NEO-Core:~$. Naciskamy kombinację klawiszy [Ctrl]+[D] aby podejrzeć ekran powitalny płytki. Z programu minicom wychodzimy naciskając kombinację klawiszy: [Ctrl]+[A], a następnie [x].

Dla większej czytelności: W razie potrzeby, konsolę Minicom czyścimy za pomocą kombinacji klawiszy [Ctrl]+[L]. Komenda reset nie daje tu swego efektu czyszczenia okna terminala, typowego dla natywnych terminali linuksowych.

Dla bezpieczeństwa: Możemy dodać swojego użytkownika do grupy dialout aby używać polecenia minicom bez poprzedzania go komendą sudo. Umożliwia to komenda sudo usermod -a -G dialout $USER.

Jeżeli po naciśnięciu klawisza [Enter] w konsoli nie pojawia się linia zachęty, sprawdzamy, czy odpowiednie piny są połączone oraz czy są połączone na przemian zgodnie ze specyfikacją - pin TX złączony z przeciwległym pinem RX i na odwrót.

Podłączenie płytki do sieci Internet

Według dokumentacji, jeżeli płytka podczas swego startu wykryje aktywne połączenie sieciowe poprzez Ethernet, uzyska konfigurację automatycznie przez protokół DHCP. W praktyce jednak nie stwierdzono działania tej funkcjonalności, dlatego połączyć płytkę do Internetu wykonujemy na niej skrypt, którego treść podana jest poniżej.

#!/usr/bin/env bash

# config.sh: Set up networking. These settings are reboot-sensitive.

# Set local IP address
sudo ifconfig eth0 192.168.1.171 netmask 255.255.255.0

# Set default gateway
sudo route add default gw 192.168.1.250 eth0

# Set default DNS (vanishes after reboot)
echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf

Aby zainstalować obsługę DHCP, konfigurujemy dostęp do sieci używając podanego wyżej skryptu, a następnie instalujemy pakiet DHCPCD używając komendy sudo apt install nscd unbound dhcpcd5. Ostatnia nazwa w komendzie to właściwy pakiet, pozostałe odnoszą się do opcjonalnych zależności. Gdy instalacja się zakończy, uruchamiamy komendę sudo dhcpcd. Na koniec by sprawdzić. czy wszystko zakończyło się sukcesem, wykonujemy polecenie ping google.com oczekując prawidłowo powracających do nas pakietów kontrolnych jak pokazano na poniższym obrazku.

Gdy już połączyliśmy naszą płytkę z Internetem, możemy teraz zainstalować pakiet Docker. Wykonujemy polecenie sudo apt install docker.io, a następnie - sudo service docker start. Stan usługi Docker w systemie sprawdzamy za pomocą polecenia service docker status i szukając linii podpisanej Active:.

Aby operować funkcjami pakietu Docker bez potrzeby używania komendy sudo, dodajemy naszego użytkownika do grupy docker, wpisując sudo usermod -a -G docker $USER a następnie wylogowujemy się kombinacją klawiszy [Ctrl]+[D] i sprawdzamy, czy w rezultatach polecenia groups pojawiło się słowo docker jak pokazano poniżej.

Instalujemy rozszerzenia powłoki na NanoPi

Instalujemy powłokę ZSH: sudo apt install zsh -y.

Następnie instalujemy pakiet Oh-My-Zsh [1], pamiętając o wcześniejszym zainstalowaniu pakietu Git jeżeli nie jest zainstalowany: sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)".

Jeżeli połączenie z NanoPi poprzez interfejs UART przestało działać, restartujemy nasze NanoPi komendą sudo reboot now. Po restarcie sytuacja powinna wrócić do normy.

Logujemy się do lokalnego rejestru kontenerów Dockera

Przy wysyłaniu obrazów do rejestru pamiętamy aby podawać pełną cieżkę do rejestru, łącznie z adresem URL jak w przykładzie: docker pull docker.giss.pl/patryk.kocielnik/tcp_echo.

Dbamy o to, aby na serwerze GitLab obecne było repozytorium o zadanej nazwie - w naszym przypadku tcp_echo na profilu użytkownika patryk.kocielnik na maszynie pod adresem docker.giss.pl.

Instalujemy aplikację w Node

Tworzymy na płytce NanoPi NEO ("NEO") folder o nazwie server poleceniem mkdir server, a następnie umieszczamy w tym folderze dwa pliki: Dockerfile oraz tcp_echo_server.js o treści podanej poniżej.

Dockerfile:

# Dockerfile

FROM arm32v7/node

WORKDIR /app
COPY . .

# If you have native dependencies, you'll need extra tools
# RUN apk add --no-cache make gcc g++ python

#RUN npm install --production

EXPOSE 5002
CMD ["node", "tcp_echo_server.js"]

Plik tcp_echo_server.js:

#!/usr/bin/env node

// tcp_echo_server.js

const net = require('net')

const ip = '0.0.0.0'
const port = 5002

const server = net.createServer(socket => {
  socket.pipe(socket)

  socket.on('data', data => {
    if (data == 'exit') process.exit()
    else socket.write(data)
  })
})

server.listen(port, ip)

Budowę obrazu formatu Docker z naszą aplikacją rozpoczynamy wpisując docker build ..

Architekturę bieżącego systemu sprawdzamy w konsoli poleceniem arch.

Widzimy, że Node nie udostępnia oficjalnego obrazu kontenera dla naszej platformy - armv7l [2]. Udostępnia natomiast taki obraz firma ARM [3]. Ten właśnie obraz został włączony do naszego pliku Dockerfile.

Przenosimy obraz kontenera stworzony lokalnie na pokład mikrokontrolera

Przy wpisaniu polecenia docker images na maszynie użytej do skompilowania obrazu kontenera okazuje się, że podczas procesu budowania obrazu powstało wiele śmieciowych obrazów, których wspólną cechą jest brak tagów, jak widać na poniższym obrazku.

Usuniemy te śmieciowe obrazy, uprzednio upewniwszy się, że wszystkie ważne dla nas obrazy posiadają tagi. Do usunięcia niepotrzebnych obrazów użyjemy polecenia docker rmi $(docker images | grep "^<none>" | awk "{print $3}") [4].

Logowanie do Container Registry z poziomu NanoPi

Do zalogowania się użyjemy polecenia docker login docker.giss.pl. Program poprosi nas o nazwę użytkownika i hasło. Jeżeli wystąpi błąd zatytułowany Error response from daemon: (...) getsockopt: connection refused, sprawdź, czy możliwe jest połączenie z ping z adresem podanym w komunikacie błędu. Jeżeli nie, ustaw adres podstawowego serwera DNS płytki NanoPi na 192.168.1.200 albo - co jest drogą mniej skalowalną - dopisz do pliku /etc/hosts następującą linię: 192.168.1.96 docker.giss.pl. Dopisać tę linię możesz prosto komendą echo "192.168.1.96 docker.giss.pl" | sudo tee -a /etc/hosts.

Następnie jeszcze raz używamy komendy docker login w następujący sposób: docker login -u <nazwa_użytkownika> docker.giss.pl.

Kompatybilność obrazów między platformami

Obraz kontenera zbudowany na podstawie obrazu dla architektury x86_64 nie uruchomił się na platformie ARMv7l, na której oparty jest NanoPi NEO. Błąd brzmiał standard_init_linux.go:178: exec user process caused "exec format error" i dotyczył niekompatybilności platform. Potwierdza to, że do budowania obrazów przeznaczonych dla platformy NEO nadają się jedynie obrazy dedykowane tej platformie, a co więcej, finalny obraz również musi być zadedykowany platformie docelowej.

To oznacza, że nie możemy budować obrazów dla minikomputera NanoPi na szybkim komputerze stacjonarnym lub laptopie, tam ich testować, a na końcu wgrywać ich na platformę docelową. Wydaje się to w prosty sposób niemożliwe.

Istnieją kross-kompilatory, które mają pozwalać na kompilację kodu pomiędzy platformami. Rozwiązanie takie wygląda na nieco skomplikowane, ale przez to - tym atrakcyjniejsze z perspektywy rozwoju i eksploracji.

Quick reference

  1. Nawiąż połączenie z NanoPi przez UART: minicom -b 115200 -o -D /dev/ttyUSB0 lub ssh pi@<ip_nanopi>,
  2. Usuń nieotagowane obrazy z rejestru Dockera: docker rmi $(docker images | grep "^<none>" | awk "{print $3}").

Źródła


  1. Oh-My-Zsh ↩︎

  2. RPi-Node ↩︎

  3. arm32v7/node ↩︎

  4. Remove all untagged images ↩︎