Gatsby Webhook Service (2) - Das große Containern
In diesem Artikel soll es darum gehen ein kleines Netzwerk aus Containern für den Webhook Service zu erstellen. Am Ende möchte ich zumindest einen kleinen Web Server auf Fastify Basis zum Testen fertig haben.
Im letzten Artikel habe ich einen groben Fahrplan gegeben was hier wie entstehen soll und wie es funktionieren soll. In diesem hier soll es darum gehen ein kleines Netzwerk aus Container zu erstellen. Am Ende möchte ich zumindest einen kleinen Web Server auf Fastify Basis zum Testen fertig haben.
Ein Basis Image
Wir werden aus dem Node:current Image ein Basis Image bauen woraus dann das eigentliche Image entstehen soll. Das hat sich beim Testen als vorteilhaft herausgestellt, da das Installieren der nötigen Compiler Werkzeuge und Node Module sich als zeitraubend erwiesen hat.
FROM node:current as anne-base
RUN apt update && apt upgrade -y
RUN apt install libvips-dev python make build-essential node-gyp \
g++ gcc libc6-dev libpng-dev libjpeg-dev pngquant webp \
fftw-dev tar nginx nano -y
RUN yarn global add gatsby-cli sharp pm2
RUN mkdir /usr/lib/gatsby && mkdir /usr/lib/api
COPY files/node/gatsby /usr/lib/gatsby
COPY files/node/api /usr/lib/api
RUN chmod 4755 -R /usr/lib/api && chmod 4755 -R /usr/lib/gatsby
WORKDIR /usr/lib/api
RUN yarn
WORKDIR /usr/lib/gatsby
RUN yarn
Nach dem update der installierten Linux Pakete installieren wir neben den Build Werkzeugen, Entwicklerbibliotheken für alle üblichen Bildformate. Das brauchen wir damit Sharp kompiliert werden kann. Nginx installieren wir um es als Reverse Proxy nutzen zu können. So ist bei bedarf auch ein Gatsby Development Server zu erreichen.
Sharp ist ein hoch performantes natives Modul für Node.js zum manipulieren von Bilddateien, die wir von Instagram runterladen. Außerdem wird Sharp auch von Gatsby für die im gatsby-plugin-image integrierte Bildoptimierung benötigt.
Danach installieren wir mit Yarn global gatsby-cli
, sharp
und pm2
.
Die nächsten Befehle sind zum Erstellen der Source Verzeichnisse, das initiale Kopieren des Quellcodes für Gatsby und den Webhook Service und das anschließende Setzen der Zugriffsrechte. Alles nur um die Node.js Abhängigkeiten hier installieren zu können und das nicht bei jedem Recreate neu durchlaufen zulassen.
Danach kann das Image mit dem folgenden Befehl erstellt werden:
sudo docker build -f Dockerfile.base.dev -t anne-base
Das Image für den Container
Das zweite Image wird durch die Nutzung Ersteres nun deutlich schneller erzeugt.
FROM anne-base
ADD files/node/nginx.conf /etc/nginx/nginx.conf
ADD files/node/copy.sh /
ADD files/node/run.sh /
RUN chmod +x /run.sh && chmod +x /copy.sh
EXPOSE 80
CMD ["/run.sh"]
Es werden lediglich die Konfiguration für nginx, ein Shell Script für den Start und eins zum Kopieren des Gatsby ./public Verzeichnisses hinzugefügt. Danach werden die Dateirechte der beiden Shell Scripts um das Ausführen erweitert. Als letztes wird der Port 80 der Docker Engine zugänglich gemacht und angewiesen das run.sh
Script beim Start auszuführen.
# Nicht als Daemon (Dienst) starten
daemon off;
pid /var/run/nginx.pid;
worker_processes auto;
events {
worker_connections 4096;
}
http {
sendfile on;
default_type application/octet-stream;
tcp_nopush on;
server {
listen 80;
root /var/www;
index index.html index.htm;
disable_symlinks off;
# Header dem Proxy-Requests anhängen
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
location / {
# Alle nicht weiter definierten Anfragen werden
# an den Gatsby Dev Server weitergeleitet.
proxy_pass http://127.0.0.1:8000$request_uri;
}
location ~ ^/api {
# Alle Anfragen die mit der URI /api beginnen
# werden an unseren Webhook Service weitergeleitet.
proxy_pass http://127.0.0.1:3333$request_uri;
}
}
}
#!/bin/sh
[ -d /var/www/public ] && rm -R /var/www/public
cp -R /usr/lib/gatsby/public /var/www/public
#!/bin/sh
[ -f /run-pre.sh ] && /run-pre.sh
cd /usr/lib/api
pm2 start app.js --name anne-api
cd /usr/lib/gatsby
pm2 start "gatsby develop" --name anne-gatsby
nginx &
pm2 logs
Eine Komposition für Docker
Das Container Netzwerk wird vorerst lediglich aus zwei Container bestehen. Zum einem wird aus der vorab erstellten Dockerfile.dev
unser persönlicher Container erzeugt und zum Anderen laden wir das Image für Redis und lassen docker-compose
auch ein Container Netzwerk zur internen Kommunikation zwischen den Containern erstellen.
version: '2.2'
services:
gatsby:
build:
context: .
dockerfile: Dockerfile.dev
container_name: anne-gatsby
depends_on:
- redis
ports:
- '127.0.0.1:10001:80'
volumes:
- './www:/var/www'
- './src/api:/usr/lib/api'
- './src/gatsby:/usr/lib/gatsby'
environment:
- NODE_ENV=development
- GATSBY_WEBPACK_PUBLICPATH=/
- REDIS=redis
hostname: gatsby
networks:
- app-network
redis:
image: redis:latest
restart: always
hostname: redis
container_name: anne-redis
ports:
- '6379'
networks:
- app-network
networks:
app-network:
driver: bridge
Die in dem Container laufende nginx Instanz ist dann auf dem lokalen Loopback über den Port 10001 zu erreichen. Redis steht hingegen nur für das interne Netzwerk zur Verfügung. Die im Container liegenden Verzeichnisse /var/www, /usr/lib/api und /usr/lib/gatsby werden zu den auf dem Host liegenden Verzeichnisse ./www, ./src/api und ./src/gatsby gemapped.
Nun kann man das ganze mit folgenden Befehl starten:
sudo docker-compose --name anne up -d
Falls es nötig sein sollte eine Shell im Container zu starten um darin selbst rum zuwerkeln, dann kann man das mittels
sudo docker exec -it anne-gatsby /bin/bash
bewerkstelligen.
Fastify nur zum Testen
Damit wir auch was zum Testen haben fangen wir mit einem kleinen Programmabschnitt an, welcher Fastify instanziiert und in den "Lauschmodus" versetzt.
require('dotenv').config();
const path = require('path');
const cluster = require('cluster');
const prettifier = require('@mgcrea/pino-pretty-compact');
const createFastifyInst = require('fastify');
const fp = require('fastify-plugin');
const routes = require('./routes');
/*
Globale Konstanten initialisieren
*/
const external = {
hostname: 'www.instagram.com',
path: (arg) => `/${arg ? arg+'/' : ''}?__a=1`
};
const port = process.env.PORT ?? 3333;
const host = process.env.HOST ?? '0.0.0.0';
const fastify = createFastifyInst({
logger: {
prettyPrint: true,
prettifier,
},
});
const expire = 60 * 60;
/*
Redis Fastify Plugin registrieren
*/
fastify.register(require('fastify-redis'), {
host: process.env.REDIS,
port: 6379,
closeClient: true,
});
/*
Ein Plugin zum effizienten senden von
statischen Dateien registrieren.
*/
fastify.register(require('fastify-static'), {
root: path.join(__dirname, 'static'),
prefix: '/static'
});
/*
Ein Plugin zum Parsen von Requests mit Form URL
kodierten Body registrieren.
*/
fastify.register(require('fastify-formbody'));
/*
Die Route GET /api/ werden wir für den Test
nutzen, da nginx nur Traffic für ^/api an den
Socket des Ports 3333 weiterleitet.
*/
fastify.get('/api/', (req, res) => {
res.send('(@^0^)')
})
/*
Wenn der aktuelle Prozess ist Parent ist
*/
if (cluster.isPrimary) {
const nCpus = (require('os')).cpus().length;
fastify.log.info(`Master ${process.pid} is running`);
/*
Für jeden Kern des CPUs ein Fork vom
aktuellem Prozess erzeugen
*/
for (let i = 0; i < nCpus; i++)
cluster.fork();
/* --- */
cluster.on('online', (worker) =>
fastify.log.info(`Worker ${worker.process.pid} is listening`));
/*
Im Fehlerfall den Prozess nach
einer Verzögerung erneut starten
*/
cluster.on('exit', (worker, code) => {
fastify.log.error(`Worker ${worker.process.pid} died`);
if (code === 1) setTimeout(cluster.fork, 10000);
});
/*
Wenn der aktuelle Prozess ein Child ist
*/
} else {
try {
(async () => {
await fastify.listen(port, host);
})();
}
catch (err) {
fastify.log.error(err);
process.exit(1);
}
}
Weitere Blog Posts aus dieser Serie
- Gatsby Webhook Service
- Gatsby Webhook Service (2) - Das große Containern
- Gatsby Webhook Service (3) - JWT Authentifizierung
- Gatsby Webhook Service (4) - Instagram
- Gatsby Webhook Service (5) - Gatsby