Categories
AWS Development Linux

Docker Anwendung in AWS (EC2)

In meinem letzten Blogeintrag habe ich eine geDockerte Anwendung auf einem Server mit Ubuntu 18 zum laufen gebracht. Aus verschiedenen Gründen war das aber nur ein Zwischenschritt, um zu testen, ob die Anwendung grundsätzlich in solch einer Umgebung lauffähig ist. Neben den beschriebenen Problemen gab es noch viele weitere, die gelöst werden mussten.

Als nächsten Schritt möchte ich die Anwendung in die AWS umziehen, immerhin bin ich ja inzwischen ein zertifizierter Cloud Practitioner.

AWS User

Mit dem Stammbenutzer einen neuen IAM Nutzer für die Anwendung anlegen. Dieser bekommt erstmal umfangreiche Rechte, was nicht best Practice ist und später sollte ich diese Rechte auf das unbedingt benötigte zurücksetzen.

EC2 Server

Die Anwendung soll erstmal mit dem Docker Setup auf einem EC2 Server laufen.

Mit dem neuen IAM Nutzer wechsele ich zuerst auf die Europa Zone ec-central-1.

Ich lege eine neue EC2 Server Instanz an, wobei ich als Sparfuchs nach “nur kostenloses Kontingent” filtere und ein AMI für Ubuntu Server 20.04 LTS (x64) und Typ t2.micro auswähle.
Es wird ein neues Schlüsselpaar erzeugt und ich speichere den privaten Schlüssel.

Über EC2 > Instances > Server-Instanz auswählen.

Über Verbinden lässt sich im Browser ein Terminal öffnen. Hier lässt sich aber auch am einfachsten die öffentliche IP und vor allem der Benutzername finden:

Ich habe allerdings nicht die Web Shell verwendet, sondern die Daten, sowie den privaten Schlüssel genommen, um eine Verbindung in WinSCP einzurichten. So kann ich später leicht die Daten auf den Server kopieren und per Klick eine PuTTY-Shell öffnen.

Port Freigabe

Standardmäßig ist für den Server nur Port 22 für SSH frei gegeben.

Weitere Ports, wie zB der benötigte HTTP Port 80 oder HTTPS 443, lassen sich über die AWS Management Console frei geben.

Die EC2-Server-Instanz auswählen und unter Sicherheit findet sich die Sicherheitsgruppe:

In der Sicherheitsgruppe können die Regeln für den eingehenden Datenverkehr erweitert werden.
Dabei ist zu beachten, dass man weitere Regeln hinzufügen muss und nicht den bestehenden Typ SSH auf zB HTTP ändert und speichert, weil das diesen nur ändert und nicht als neue, weitere Regel hinzufügt. Dann kann man zwar die Seiten des Webservers bewundern, aber sich nicht mehr per SSH einloggen.

Server einrichten

Auf der Linux Konsole des EC2-Servers wird dieser eingerichtet, dazu wird Docker Compose installiert, was als Abhängigkeit Docker mitbringt.

apt list --upgradable
sudo apt update
sudo apt upgrade -y
sudo apt install docker-compose -y

sudo docker version         # -> 20.10.7
sudo docker-compose version # -> 1.25.0
sudo service docker status  # -> running

sudo docker run hello-world

Docker läuft und es werden die Daten der Anwendung auf den Server kopiert und anschließend über Docker Compose gestartet.

sudo docker-compose up 

Leider führte das zu einem Fehler, wie er schon bei der Ubuntu 18 Installation aufgetreten ist. Das zuvor gewonnene Wissen kann ich jetzt zur schnellen Fehlerbehebung anwenden:

sudo apt-get remove docker-compose -y
sudo curl -L https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
# Output:
-bash: /usr/bin/docker-compose: No such file or directory
# Lösung: neue Shell, zb per tmux, starten
# und dann nochmals testen
docker-compose --version
# Output:
Docker Compose version v2.2.3

Anschließend ließ sich die Anwendung per Docker Compose starten und per cURL, bzw. HTTPie, über localhost:80 und <öffentlicheIP>:80 aufrufen. Der Aufruf <öffentlicheIP>:80 vom Entwickler Laptop funktioniert auch.

Der Start dauerte etwas länger, die Webanwendung selbst ließ sich anschließend aber angenehm schnell bedienen. Zumindest als Test-Server scheint der “Gratis”-EC2-Server völlig auszureichen.

Ausblick

Auf dem kostenfreien Server laufen ein Tomcat Webserver, eine PostgreSQL Datenbank und PGAdmin und das, zumindest den ersten Tests nach, mit völlig ausreichender Performance.

Als nächstes möchte ich dem Docker Compose Konstrukt noch um einen Reverse Proxy erweitern, der eine (vermutlich nur selbstsignierte) verschlüsselte Verbindung per HTTPS anbietet und über Port 80 und 443 die Anwendung und den PGAdmin erreichbar macht. Außerdem soll es einen einfachen Authentifizierungs- und ggf. Authorisierungsmechanismus geben. Das wird mit einem Apache HTTP Server realisiert werden und sollte keinen besonderen Ressourcenbedarf haben.

Falls sich die Zeit findet, möchte ich das um Keycloak erweitern und den Zugriff auf Anwendung und PGAdmin erst nach erfolgreicher Authentifizierung und Authorisierung erlauben. Vielleicht ist das noch mit dem Apache HTTP Server realisierbar, ggf. werde ich aber auf zB Traefik umstellen.
Bei dem Setup kann ich mir schon vorstellen, dass die Ressourcen des kleinen Server nicht mehr ausreichen und es zu spürbaren Performanceeinbrüchen kommen wird.

Eine ansprechendere URL, anstelle der generierten AWS URL, wäre wünschenswert.

Categories
Development Linux

Docker Compose und Ubuntu 18

Docker Compose Datei vom Entwicklungsrechner auf den Server kopieren, kleinere Anpassungen vornehmen und ausführen. So einfach habe ich es mir vorgestellt, aber es gab dann leider doch noch Herausforderungen zu bewältigen:

Docker Compose Updaten

Ich habe einen Server mit dem nicht mehr ganz taufrischen Ubuntu 18 am laufen und wollte dort ein Docker Compose Skript ausführen.

Das Skript läuft auf meinem Entwicklungsrechner, aber auf dem Server wurde lediglich eine Fehlermeldung ausgegeben:

dockeruser@myServer:~/myproject$ docker-compose up
ERROR: Version in "./docker-compose.yml" is unsupported. You might be seeing this error because you're using the wrong Compose file version. Either specify a supported version (e.g "2.2" or "3.3") and place your service definitions under the `services` key, or omit the `version` key and place your service definitions at the root of the file to use version 1.
For more on the Compose file format versions, see https://docs.docker.com/compose/compose-file/

Wie sich herausstellte, war für Ubuntu 18 bei Docker Compose 1.17.1 Schluss und ich muss händisch upgraden:

Docker Compose entfernen:

sudo apt-get remove docker-compose

Die aktuelle Docker Compose Version ermitteln (heute: 2.2.3): https://github.com/docker/compose/releases

Auf dieser Seite kann man auch den direkten Link zum Download finden, falls es beim ausführen des nächsten Befehls zu Problemen kommt.

Beispielsweise ist die Versionsnummer v2.2.3, also mit einem kleinen “v” am Anfang und wenn das fehlt, schlägt der Download fehl.

So lautet der Link für mein Ubuntu:
https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64

Der Befehl zum Download:

sudo curl -L https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

Rechte setzen:

sudo chmod +x /usr/local/bin/docker-compose

Installation und Version checken:

docker-compose --version
# Output:
Docker Compose version v2.2.3

Docker Compose ohne GUI

Mit der neuesten Docker Compose Version gibt es einen neuen Fehler:

error getting credentials - err: exit status 1, out: `Cannot autolaunch D-Bus without X11 $DISPLAY`

Die Erklärung lautet:

Looks like this is because it defaults to use the secretservice executable which seems to have some sort of X11 dependency for some reason. If you install and configure pass docker will use that instead which seems to solve the problem.

Zu erst muss die aktuelle Version des Docker Credential Helpers ermittelt werden: v0.6.4

Install the Docker Credential Helper for pass:

# substitute with the latest version
url=https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-pass-v0.6.4-amd64.tar.gz

# download and untar the binary
wget $url
tar -xzvf $(basename $url)

# move the binary to a dir in your $PATH
sudo mv docker-credential-pass /usr/local/bin

# verify it works
docker-credential-pass list

# cleanup
rm docker-credential-pass-v0.6.4-amd64.tar.gz

Install and configure pass:

sudo apt install pass

# create a gpg2 key
gpg2 --gen-key

Und der nächste Fehler:

gpg: agent_genkey failed: Keine Berechtigung
Schlüsselerzeugung fehlgeschlagen: Keine Berechtigung

Und eine Erklärung mit Lösungsvorschlag findet sich hier:

Expected behavior. Here’s why.
At the point of failure, gen-key is about to ask the user for a passphrase. For security purposes, rather than using stdin/stdout, it wants to directly open the controlling terminal for the session and use that handle to write the prompt and receive the passphrase. When you use su to switch to some other user, the owner of the controlling terminal device file does not change; it remains associated with the user who actually logged in (i.e. received a real terminal from getty or got a pty from telnet or ssh or whatever). That device file is protected mode 600, so it can’t be opened by anyone else.

The solution is to sudo-chown the device file to the user-who-needs-to-gen-the-key before su’ing to that user. Create the key within the su’d environment, then exit back to the original environment. Then, finally, sudo-chown the terminal back to yourself.

Glücklicherweise geht es auch einfacher, indem man einfach das Programm tmux verwendet. 🙂

tmux

# create a gpg2 key
gpg2 --gen-key

# list key information
gpg2 -k

# Copy the key id (from the line labelled [uid]) and do
pass init "whatever key id you have"

Jetzt sollte der Docker Login funktionieren, aber:

docker login
# Output:
Error saving credentials: error storing credentials - err: fork/exec /usr/local/bin/docker-credential-pass: permission denied, out: ``

Auch wieder kein neues Problem, dass zB bereits hier und hier diskutiert wurde.

mkdir ~/.docker
touch ~/.docker/config.json
# brachte jeweils keine Änderung


/usr/local/bin/docker-credential-pass
# Output:
-bash: /usr/local/bin/docker-credential-pass: Keine Berechtigung


# Erfolg kam mit diesem Befehl:
sudo chmod +x /usr/local/bin/docker-credential-pass

#Zumindest funktioniert dieser Aufruf:
docker-credential-pass list


# Ein weiterer Fehler ließ sich beheben durch:
export GPG_TTY=$(tty)

Ich musste die einzelnen Images per docker pull imagename ziehen, erst danach konnte ich docker-compose ausführen.

Categories
Database Development

PostgreSQL Docker-Compose Setup

Im vorletzten Post: PostgreSQL hatte ich beschrieben, wie ich zwei Bestandsdatenbanken analysiert und in eine PostgreSQL-DB in einem Docker Container gebracht habe, samt PGAdmin.

Jetzt möchte ich einen Schritt weiter gehen und das komplette Setup über ein Script starten können: DB, PGAdmin & SQL-Scripte. Dazu verwende ich Docker-Compose.

Ausgangslage

In PostgreSQL hatte ich bereits die zugrundeliegenden Docker-Images ermittelt: postgres:13.4-buster für die DB und dpage/pgadmin4 für PGAdmin. Inzwischen gibt es aber ein aktuelleres Image für die DB, das ich verwenden werde: postgres:13.5-bullseye

docker pull postgres:13.5-bullseye
docker pull dpage/pgadmin4

Für die SQL-Daten werde ich auf den Artikel PostgreSQL IDs zurückgreifen und daraus zwei Scripte machen, eines für das Schema der DB und eines mit den “Masterdaten” mit denen das Schema initial befüllt werden soll.

CREATE SEQUENCE object_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

CREATE TABLE person (
    object_id integer NOT NULL DEFAULT nextval('object_id_seq'::regclass),
    vorname varchar(255),
    nachname varchar(255),
    CONSTRAINT person_pkey PRIMARY KEY (object_id)
);

CREATE TABLE adresse (
    object_id integer NOT NULL DEFAULT nextval('object_id_seq'::regclass),
    strasse varchar(255),
    ort varchar(255),
    CONSTRAINT adresse_pkey PRIMARY KEY (object_id)
);
INSERT INTO person (vorname, nachname) VALUES ('Max', 'Mustermann');
INSERT INTO person (vorname, nachname) VALUES ('Peter', 'Person');
INSERT INTO person (vorname, nachname) VALUES ('Donald', 'Demo');

INSERT INTO adresse (strasse, ort) VALUES ('Beispielstrasse', 'Beispielstadt');
INSERT INTO adresse (strasse, ort) VALUES ('Erpelweg', 'Entenhausen');
INSERT INTO adresse (strasse, ort) VALUES ('Bruchstrasse', 'Berlin');

Auf der Seite von Docker Compose bringe ich in Erfahrung, dass die aktuelle Version von Docker Compose 3.9 ist und erstelle schon mal die Datei:

version: "3.9"  # optional since v1.27.0

Ordernstrucktur:

Images starten

Bisher habe ich PostgreSQL und PGAdmin über folgende Kommandos gestartet:

docker run --name myapp-db -p 5432:5432 -e POSTGRES_PASSWORD=PASSWORD -d postgres:13.4-buster
docker run --name myapp-pgadmin -p 80:80 -e PGADMIN_DEFAULT_EMAIL=admin@admin.com -e PGADMIN_DEFAULT_PASSWORD=admin -d dpage/pgadmin4

Diese Kommandos werden in ein Docker-Compose Script transferiert:

version: "3.9"  # optional since v1.27.0
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin

Jetzt können beide Images mit einem einfachen Befehl gestartet werden:

\myapp> docker-compose up
Creating network "myapp_default" with the default driver
Creating myapp_myapp-db_1      ... done
Creating myapp_myapp-pgadmin_1 ... done
Attaching to myapp_myapp-db_1, myapp_myapp-pgadmin_1

PGAdmin im Browser starten: http://localhost:80
Login wie bisher mit admin@admin.com / admin

Im nächsten Schritt gibt es bereits eine entscheidende Änderung:
Während bisher beide Container isoliert nebeneinander liefen und nur über den Host-Rechner kommunizieren konnten, wurde durch Docker-Compose automatisch beim Start ein Netzwerk (“myapp_default”) angelegt, in dem beide Container laufen. Außerdem sind beide Container über ihren Servicenamen (“myapp-db” & “myapp-pgadmin”) erreichbar.

Dadurch muss nicht mehr die IP des Host-Rechners ermittelt werden (die sich manchmal ändert), sondern es kann der Name genommen werden:

Datenbank erstellen

In der PostgreSQL Instanz muss jetzt eine Datenbank erzeugt werden, in der die Anwendungsdaten gespeichert werden.

Hierzu gehen wir in den DB Container.
Allerdings ist der Name anders als bisher: Es wurde der Verzeichnisname als Präfix davor und eine 1 (für die 1. und in unserem Fall einzige Instanz) als Postfix dahinter gehangen und so lautet der Name : myapp_myapp-db_1

docker exec -it myapp_myapp-db_1 bash

Im Container erzeugen wir die DB:

su postgres
createdb myappdb
exit

So war es zumindest bisher, einfacher geht es mit Docker-Compose und dem Setzten der Environment-Variablen POSTGRES_DB wodurch die DB automatisch angelegt und verwendet wird.
Sicherlich hätte ich das auch bisher im Docker Kommando so nehmen können, aber im letzten Post hatte ich es zum einen mit zwei DBs zu tun und zum anderen musste ich eh auf die Kommandozeile um die DBs einzuspielen.

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myappdb
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin

Datenbank befüllen

Bisher war der Weg, die SQL-Dateien in den Container zu kopieren und von dort einzuspielen:

# Dateien in Container kopieren
docker cp database/Masterdata.sql myapp_myapp-db_1:/tmp
docker cp database/Schema.sql myapp_myapp-db_1:/tmp
# In Container wechseln
docker exec -it myapp_myapp-db_1 bash

Im Container SQLs einspielen:

su postgres
psql myappdb < /tmp/Schema.sql
psql myappdb < /tmp/Masterdata.sql
exit

Einfacher geht es über Docker-Compose und den Mechanismus, dass PostgreSQL automatisch die Dateien importiert, die im Verzeichnis /docker-entrypoint-initdb.d/ liegen. Und zwar in alphabetischer Reihenfolge.

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myappdb
    volumes:
      - ./database/Schema.sql:/docker-entrypoint-initdb.d/1-Schema.sql
      - ./database/Masterdata.sql:/docker-entrypoint-initdb.d/2-Masterdata.sql
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin

PGAdmin Einstellungen persistieren

Während der Entwicklung öfters mal die DB platt macht und komplett neu aufsetzt: Mit Docker ist das schnell gemacht. Mit Docker-Compose sind es jetzt nur noch zwei Befehle:

# stop and remove stopped containers
docker-compose down
# start containers
docker-compose up

Einen Nachteil gibt es allerdings: Die Einstellungen im PGAdmin gehen ebenfalls flöten und müssen neu eingegeben werden.
Die Lösung: Der PGAdmin Container bekommt ein persistentes Volume, das ein docker-compose down übersteht. Und wenn es doch mal neu aufgesetzt werden muss, ist das einfach über das -v Flag umsetzbar: docker-compose down -v

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myappdb
    volumes:
      - ./database/Schema.sql:/docker-entrypoint-initdb.d/1-Schema.sql
      - ./database/Masterdata.sql:/docker-entrypoint-initdb.d/2-Masterdata.sql
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    volumes:
      - pgadminvolume:/var/lib/pgadmin
volumes:
  pgadminvolume: {}

DB Image mit Schema und Masterdata

An Schema und Stammdaten wird sich erstmal nichts ändern. Daher wäre es gut, wenn beim Neubau der Container die DB bereits mit Schema und Stammdaten gestartet wird und nicht diese erst aufbauen muss.

Das wird dadurch erreicht, dass ein Image gebaut wird, dass die PostgreSQL DB sowie Schema und Stammdaten enthält.

Im Verzeichnis database wird eine Datei Dockerfile angelegt. Dieses Dockerfile enthält die Informationen zum Bau des DB Images.

FROM postgres:13.5-bullseye
 ENV POSTGRES_PASSWORD PASSWORD
 ENV POSTGRES_DB myappdb
COPY ./Schema.sql /docker-entrypoint-initdb.d/1-Schema.sql
COPY ./Masterdata.sql /docker-entrypoint-initdb.d/2-Masterdata.sql
version: "3.9"
services:
  myapp-db:
    build: ./database
    ports:
      - "5432:5432"
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    volumes:
      - pgadminvolume:/var/lib/pgadmin
volumes:
  pgadminvolume: {}

Gestartet wird wie gewohnt:

docker-compose up

Sollte es erforderlich sein, das Image neu zu bauen, zB wenn ich das Schema verändert hat:

docker-compose up --build
# or
docker-compose build

UPDATE: Das war leider nix mit dem vorgefüllten Image

Das Dockerfile enthält zwar den Schritt die SQL Dateien in das Image zu kopieren. Es fehlt aber der Schritt, bei dem diese Dateien in die Datenbank hineinmigriert werden. Das geschieht wie zuvor auch erst beim Starten des Containers. Mein Ziel, einen Image zu haben, dass diese Daten bereits enthält, habe ich damit also leider nicht erreicht. Das Dockerfile enthält keine Informationen, die nicht zuvor auch schon im Docker Compose enthalten waren.

Da mich das herumkaspern mit diesem Problem heute den ganzen Tag gekostet und abgesehen vom Erkenntnisgewinn leider nichts gebracht hat, kehre ich zur Docker Compose Variante zurück, bei der die DB beim Starten des Containers gebaut wird. Für das Beispiel auf dieser Seite macht das praktisch gesehen keinen Unterschied, da die Scripte winzig sind. Für mein Projekt leider schon, da benötigt der Aufbau der DB ein paar Minuten. Für die Anwendungsentwicklung, bei der ich die DB alle paar Tage mal neu aufsetze, ist das durchaus OK. Für Tests, die mit einer frischen DB starten sollen, die am Ende weggeschmissen wird, ist das schon ein Problem.

Also Rückkehr zu Docker Compose, diesmal mit einem extra Volume für die DB. Das kann ich dann gezielt löschen.

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myapp
    volumes:
      - postgresvolume:/var/lib/postgresql/data
      - ./database/Schema.sql:/docker-entrypoint-initdb.d/1-Schema.sql
      - ./database/Masterdata.sql:/docker-entrypoint-initdb.d/2-Masterdata.sql
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    volumes:
      - pgadminvolume:/var/lib/pgadmin
volumes:
  postgresvolume: {}
  pgadminvolume: {}

Docker Befehle

# Create and start containers
docker-compose up
# Image neu bauen
docker-compose up --build
# or
docker-compose build

# Stop containers
docker-compose stop
# Start containers
docker-compose start

# Stop containers and remove them
docker-compose down
# Stop containers, remove them and remove volumes
docker-compose down -v

Categories
Database Development

PostgreSQL

Für die Neu- und Weiterentwicklung einer Anwendung habe ich zur Analyse die Bestandsanwendung samt Datenbanken bekommen.
Für die Analyse musste ich zunächst die Datenbanken zum laufen bekommen und uA mit einem DB-Client einsehen.

Ich habe zum einen eine Datei dump.backup bekommen. Zunächst musste ich herausfinden, um was für eine Datei es sich dabei handelt, dazu nutzte ich das Linux Tool file:

apt update
apt install file
file /tmp/dump.backup
# /tmp/dump.backup: PostgreSQL custom database dump - v1.14-0

Es handelt sich also um einen Dump einer PostgreSQL Datenbank. Und im Dump konnte ich eine Versionsnummer 13.0 finden.

Die andere Datei mydb.sql.gz beinhaltet eine gezippte Version eines SQL Exports einer PostgreSQL DB Version 13.2 von einem Debian 13.2 Server.

Im Laufe der weiteren Analyse der DB Exporte stellte sich heraus, dass der dump.backup die PostGIS Erweiterung der PostgreSQL DB benötigt, welche mit installiert werden muss.

PostgreSQL Datenbank Docker Image

Ich werde beide Datenbanken in einer Docker Version installieren, dazu werde ich eine DB Instanz starten, in der beide DBs installiert werden.

Die Docker Seite für Postgres: Postgres – Official Image | Docker Hub

Da es sich um die Versionsnummer 13.0 und 13.2 handelt, werde ich ein aktuelles Image von Version 13 verwenden, was zum Projektzeitpunkt Version 13.4 war.

Da zumindest eine der beiden DBs auf einem Debian System gehostet ist, werde ich ein Debian Image wählen.

Die Wahl des Images fällt also auf: postgres:13.4-buster

Datenbank installieren

Zuerst das Docker Image ziehen:

docker pull postgres:13.4-buster

Datenbank starten:

docker run --name myapp-db -p 5432:5432 -e POSTGRES_PASSWORD=PASSWORD -d postgres:13.4-buster

DB-Dateien in den laufenden Docker Container kopieren:

docker cp mydb.sql.gz myapp-db:/tmp
docker cp dump.backup myapp-db:/tmp

In den laufenden Container wechseln:

docker exec -it myapp-db bash

PostGIS installieren:

apt update
apt install -y postgresql-13-postgis-3-scripts

DB von Dump erstellen:

su postgres
createdb mydb_dump
pg_restore -d mydb_dump -v /tmp/dump.backup
exit

DB von SQL erstellen:

su postgres
cd /tmp
gunzip mydb.sql.gz
createdb mydb_sql
pg_restore -d mydb_sql -v /tmp/mydb.sql
exit

Die Datenbanken sind installiert und es kann mittels eines letzten exit der Container verlassen werden.

DB Client PGAdmin installieren

Um in die Datenbanken hinein sehen zu können wird ein Client Programm benötigt. Sicherlich es gibt da bereits etwas auf der CommandLine Ebene:

su postgres
psql mydb_sql

Übersichtlich oder komfortabel ist das aber nicht. Daher möchte ich ein Tool mit einer grafischen Oberfläche verwenden. Die Wahl fiel auf pgAdmin, welches sich leicht von einem Docker Image installieren und anschließend über den Browser bedienen lässt.

Zuerst das Docker Image ziehen:

docker pull dpage/pgadmin4

pgAdmin parametrisiert starten:

docker run --name myapp-pgadmin -p 80:80 -e PGADMIN_DEFAULT_EMAIL=admin@admin.com -e PGADMIN_DEFAULT_PASSWORD=admin -d dpage/pgadmin4

Login:
* Email: admin@admin.com
* Passwort: admin

Port: 80

Der pgAdmin ist über den Browser aufrufbar: http://localhost/

Für die Konfiguration für die Verbindung zur zuvor gestarteten PostgreSQL Datenbank benötige ich die IP meines Rechners, die ich mittels ipconfig herausfinden kann.

Categories
Database Development

MS-SQL DB in Docker Container

Ich möchte eine bestehende MS-SQL Datenbank in einen lokalen Docker Container kopieren.
Dazu werde ich zuerst die Struktur der Datenbank exportieren und in einem Docker Container neu aufbauen. Anschließend werden die Daten in CSV Dateien exportiert und in die containerisierte Datenbank importiert.

Datenbank Script exportieren

Den Export der Datenbankstruktur geschieht über das MS-SQL Management Studio. In der DB einloggen und dann im Objekt-Explorer über Tasks -> Skripts generieren aufrufen:

Datenbankscript generieren lassen(Symbolphoto)

Die Einstellungen wie vorgegeben belassen und die Datei script.sql speichern.

Das Script beginnt ungefähr so:

USE [master]
GO
CREATE DATABASE [DEMO]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'DEMO', FILENAME = N'C:/Pfad/DEMO.mdf' , SIZE = 102400KB , MAXSIZE = UNLIMITED, FILEGROWTH = 102400KB ), 
 FILEGROUP [DEMO_DAT] 
( NAME = N'DEMO_DAT', FILENAME = N'C:/Pfad/DEMO.ndf' , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 102400KB ), 
 FILEGROUP [DEMO_IDX] 
( NAME = N'DEMO_IDX', FILENAME = N'C:/Pfad/DEMO_IDX.ndf' , SIZE = 3552960KB , MAXSIZE = UNLIMITED, FILEGROWTH = 102400KB )
 LOG ON 
( NAME = N'DEMO_log', FILENAME = N'C:/Pfad/DEMO_Log.ldf' , SIZE = 2234880KB , MAXSIZE = 2048GB , FILEGROWTH = 102400KB )
GO

ALTER DATABASE [DEMO] ADD FILEGROUP [DEMO_DAT]
GO
ALTER DATABASE [DEMO] ADD FILEGROUP [DEMO_IDX]
GO

USE [DEMO]
GO
CREATE USER [DONALDDEMO] FOR LOGIN [DonaldDemo] WITH DEFAULT_SCHEMA=[DEMO_SCHEMA]
GO
GO
ALTER ROLE [db_owner] ADD MEMBER [DONALDDEMO]
GO

CREATE SCHEMA [DEMO_SCHEMA]
GO

Datenbank Script anpassen

Damit das Script im Container ausgeführt werden kann, müssen ein paar Anpassungen erfolgen.

Der Speicherort auf dem Quell-Server lautet: “C:/Pfad/”. Im Container lautet der Pfad: “/var/opt/mssql/data/”. Dies ist bei den Filenamen anzupassen.

Das Script definiert einen Benutzer für die Datenbank an und weist diesem ein Login zu:

CREATE USER [DONALDDEMO] FOR LOGIN [DonaldDemo]

User und Login sind also zwei verschiedene Sachen. Der User gehört zur Datenbank, der Login zur übergeordneten Datenbankinstanz “master”. Bevor über das Script die DB und der User angelegt werden, wird ein entsprechender Login angelegt:

USE [master]
GO
CREATE LOGIN [DonaldDemo] WITH PASSWORD = 'DonaldDemo12345678'

Eventuell vorhandenen AD-Accounts fliegen raus, die benötige ich nicht für die lokale Entwicklung.

Das angepasste Demo-Script:

USE [master]
GO
CREATE LOGIN [DonaldDemo] WITH PASSWORD = 'DonaldDemo12345678'

USE [master]
GO
CREATE DATABASE [DEMO]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'DEMO', FILENAME = N'/var/opt/mssql/data/DEMO.mdf' , SIZE = 102400KB , MAXSIZE = UNLIMITED, FILEGROWTH = 102400KB ), 
 FILEGROUP [DEMO_DAT] 
( NAME = N'DEMO_DAT', FILENAME = N'/var/opt/mssql/data/DEMO.ndf' , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 102400KB ), 
 FILEGROUP [DEMO_IDX] 
( NAME = N'DEMO_IDX', FILENAME = N'/var/opt/mssql/data/DEMO_IDX.ndf' , SIZE = 3552960KB , MAXSIZE = UNLIMITED, FILEGROWTH = 102400KB )
 LOG ON 
( NAME = N'DEMO_log', FILENAME = N'/var/opt/mssql/data/DEMO_Log.ldf' , SIZE = 2234880KB , MAXSIZE = 2048GB , FILEGROWTH = 102400KB )
GO

ALTER DATABASE [DEMO] ADD FILEGROUP [DEMO_DAT]
GO
ALTER DATABASE [DEMO] ADD FILEGROUP [DEMO_IDX]
GO

USE [DEMO]
GO
CREATE USER [DONALDDEMO] FOR LOGIN [DonaldDemo] WITH DEFAULT_SCHEMA=[DEMO_SCHEMA]
GO
GO
ALTER ROLE [db_owner] ADD MEMBER [DONALDDEMO]
GO

CREATE SCHEMA [DEMO_SCHEMA]
GO

Der Docker Container

Die Quell DB ist ein Microsoft SQL Server Version 11, was dem dem Release Namen “SQL Server 2012” entspricht. Das älteste Docker Image ist ein SQL Server 2017, was der Version 14 entspricht. Bei meinen Tests war es aber kein Problem, dass die DB in eine höhere Version migriert wird.
Um die Datenbank zu persistieren wird ein Docker Volume verwendet und das Image über Docker Compose gestartet.

version: "3.8"
services:

  sql-server-db:
    container_name: sql-server-db
    image: microsoft/mssql-server-linux:2017-latest
    ports:
      - "1433:1433"
    environment:
      SA_PASSWORD: "change_This_Password"
      ACCEPT_EULA: "Y"
    volumes:
      - mssql_vol:/var/opt/mssql

volumes:
  mssql_vol:

Start des MS-SQL Servers:

docker-compose up -d

Stoppen des MS-SQL Servers und bei Bedarf anschließendes Löschen des Volumes, um danach wieder frisch anfangen zu können:

docker-compose down
docker volume rm ms-sql_mssql_vol

Sobald der MS-SQL Server gestartet wurde, kann das SQL-Script in den Container kopiert und dort ausgeführt werden:

docker cp demo_script.sql sql-server-db:/var/opt/mssql/demo_script.sql
docker exec -it sql-server-db bash
root@dockerContainer: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -i /var/opt/mssql/demo_script.sql # -o /var/opt/mssql/demo_script.out

Mit der Datenbank verbinden

Mit dem Microsoft SQL Server Management Studio kann man sich nun gegen die Datenbank verbinden:

  • Servername: localhost
  • Port: 1433
  • Anmeldename: DonaldDemo
  • Kennwort: DonaldDemo12345678

Daten Export

Für den Daten ex- und anschließenden import verwende ich ein Tool auf das ich hier nicht weiter eingehen werde (vgl. DB Export & Import) und beschreibe lediglich die logischen Schritte und die benötigten SQLs.

In einem ersten Schritt werden die zu exportierenden Tabellen der benötigten Schemata der Datenbank ermittelt und gespeichert:

  SELECT table_catalog, table_schema, table_name, table_type
    FROM DEMO.INFORMATION_SCHEMA.TABLES
   WHERE TABLE_TYPE = 'BASE TABLE'
     AND TABLE_SCHEMA IN ('DEMO_SCHEMA')--, 'DEMO_SCHEMA_2')
ORDER BY TABLE_SCHEMA, TABLE_NAME

Als nächstes wird über die Tabellen iteriert (IDX), die Daten selektiert und gespeichert:

  SELECT *
    FROM [IDX:table_schema].[IDX:table_name]

Daten Import

Der Datenimport ist nicht ganz so einfach.

Eine Tabelle hat keinen Primärschlüssel und konnte nicht importiert werden. Da diese Tabelle auch keine Daten enthält, war das aber kein Problem und der Import dieser Tabelle konnte einfach ausgelassen werden. Später kann geprüft werden, ob diese Tabelle überhaupt noch verwendet wird oder final gelöscht werden kann.

Eine andere Tabelle hat eine Spalte mit der IDENTITY Eigenschaft und die Daten können nicht einfach so eingefügt werden, dazu muss zuerst das IDENTITY_INSERT für diese Tabelle eingeschaltet werden.

Allgemein besteht das Problem, dass die Tabellen über gewisse Constraints verfügen, die das naive importieren der Daten verhindern. Beispielsweise Foreign Keys, so dass die Daten in einer bestimmten Reihenfolge importiert werden müssten. Oder man deaktiviert für die Dauer des Imports alle Constraints und spart sich so die Sortiererei!

Das Import Script sieht ungefährt so aus:

-- disable all constraints
EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"

--
SET IDENTITY_INSERT demo_schema.mydemotable ON

#IMPORT_CSV_FILES

-- enable all constraints
exec sp_MSforeachtable @command1="print '?'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"

Ein Problem mit Case Sensitiven Daten

Eine Tabelle bereitet mir noch Probleme:

In dieser Tabelle befinden sich Datensätze, deren Primärschlüssel sich lediglich in der Groß/Kleinschreibung unterscheiden, zB: “EinTollerDatensatz” und “eintollerdatensatz”. In der alten DB waren das zwei unterschiedliche Schlüssel, in der neuen DB leider nicht und so können einige Datensätze nicht importiert werden.

Das Problem könnte mit der Collation, bzw. im Deutschen: Serversortierung, zusammenhängen. In der Servereigenschaften ist diese immer standardmäßig “SQL_LATIN1_General_CP1_CI_AS”, wobei das “CI” für Case Insensitive steht. In den einzelnen Datenbanken des Servers kann man diese anpassen und eine Überprüfung der alten Datenbank ergab, dass diese “Latin1_General_CS_AS” ist. Daher habe ich der neuen Datenbank im Script nach dem CREATE DATABASE Befehl auch diese Eigenschaft zugewiesen:

CREATE DATABASE [DEMO]
# [...]

ALTER DATABASE [DEMO] COLLATE Latin1_General_CS_AS
GO

Leider führte das zu weiteren, multiplen Fehlern. Daher habe ich mich an dieser Stelle erstmal dazu entschlossen, die Collation nicht zu ändern und mit fehlenden Datensätzen weiter zu arbeiten.

Categories
Development

TV-Browser

In der Vergangenheit habe ich gerne den TV-Browser als freie Programmzeitschrift verwendet.
Leider funktioniert das Programm auf meinem neuen Laptop nicht mehr, irgendwie ist das Java bei mir nicht so installiert, wie der TV-Browser es gerne hätte und bei Versuch eines Programmstarts sehe ich lediglich dieses Fenster:

JAVA_HOME ist gesetzt, Java ist im PATH aber anscheinend fehlen dem TV-Browser die notwendigen Registry Einträge. Oder irgendwas anderes.
Also habe ich mir überlegt, ich könnte doch mal testen, das Programm in einem Docker Container laufen zu lassen.

TV-Browser im Docker Container

Das Docker Image

TV-Browser gibt es für Mac, Windows und Linux. Die Mac Version scheidet völlig aus, denn ich habe keinen Mac. Windows im Container? Nee hab ich noch nicht gemacht, gibt bestimmt Probleme. Also eine Linux Version. Ich habe die meiste Erfahrung mit Debian und Ubuntu, der Münzwurf entscheidet zur GNU/Linux Variante. Daher wird der Container auf einem Debian Linux mit Java 11 Image aufgebaut und das Dockerfile startet mit:

FROM adoptopenjdk/openjdk11:debianslim-jre

Wie ich beim Testen feststellen musste, reicht das reine Image nicht aus, es muss noch das Package “default-jre” installiert werden. Was aber leider auch nicht auf Anhieb funktioniert, zuerst muss manuell ein Verzeichnis angelegt werden. Außerdem wird noch “wget” benötigt, um die Installationsdatei herunterzuziehen.

 RUN mkdir -p /usr/share/man/man1 \ 
  && apt-get update && apt-get -y install \
     default-jre \
     wget \
  && rm -rf /var/lib/apt/lists/*

Installation von TV-Browser im Container:

 RUN wget --output-document=tvbrowser_4.2.3-1_all.deb https://sourceforge.net/projects/tvbrowser/files/TV-Browser%20Releases%20%28Java%2011%20and%20higher%29/4.2.3/tvbrowser_4.2.3-1_all.deb/download \
  && dpkg -i tvbrowser_4.2.3-1_all.deb \
  && rm  -rf tvbrowser_4.2.3-1_all.deb

Außerdem muss noch die Timezone gesetzt werden. Diese wird mittels einer Environment Variablen gesetzt, so dass sie beim Starten des Containers ggf. auch überschrieben werden kann:

 ENV TZ=Europe/Berlin
 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

Abschließend muss der TV-Browser natürlich noch gestartet werden:

  CMD tvbrowser 

Persistenz

Ich möchte nicht bei jedem Start alle Einstellungen wieder aufs Neue vornehmen müssen, also legen wir ein Volume an:

  docker volume create tvbrowser_vol

Im Container werden die Daten in /root/.config/tvbrowser gespeichert, daher lautet das mount Fragment:

  docker [...] --mount source=tvbrowser_vol,destination
=/root/.config/tvbrowser [...]

Todo

Leider werden so zwar einige, aber leider eben nicht alle Daten gespeichert. Damit der TV-Browser rund läuft muss hier noch nachgearbeitet werden.

Die GUI

Der TV-Browser kann jetzt im Container laufen, aber sehen tun wir erstmal nix. Dafür brauchen wir einen X Server, beispielsweise den VcXsrv Windows X Server. Downloaden, installieren und starten.

Beim Start zuerst die “Display settings” und dann den “Client startup” unverändert weiter klicken. Bei den “Extra settings” muss der access control disabled werden, andernfalls wird der Request aus dem Docker Container abgewiesen:

Important: Disable access control in Extra settings

Es wird die IP Adresse des Windows Rechners benötig, kann man beispielsweise über den ipconfig Befehl herausfinden, bei mir war es zB 192.168.56.1, somit lautet das entsprechende Fragment für den docker Befehl:

  docker [...] --rm -e DISPLAY=192.168.56.1:0.0 [...]

Die Option –rm sorgt dafür, dass der Container nach Schließen des X-Server Fensters geschlossen und removed wird.

Hätte ich statt Windows ein Linux Betriebssystem, wäre der Befehl wie folgt:

  docker [...] --rm -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix [...]

Der Start

Der X-Server muss wie oben beschrieben gestartet sein und dann kann der TV-Browser Container über Docker gestartet werden:

  docker run --rm -e DISPLAY=192.168.56.1:0.0 --name tvbrowser --mount source=tvbrowser_vol,destination
=/root/.config/tvbrowser tv-browser

Falls gewünscht kann auch eine andere Zeitzone gesetzt werden:

  docker [...] -e TZ=America/Los_Angeles [...]

Man kann den Container auch im Hintergrund laufen lassen, verpasst so aber die schönen Logging Ausgaben:

  docker [...] --detach [...]

Resultat

Der TV-Browser läuft prinzipiell:

Der gedockerte TV-Browser im X-Server

Allerdings kommt bei jedem Start der Konfigurations Assistent, der sich auch nicht komplett durcharbeiten lässt und abgebrochen werden muss. Die Ursache für den Fehler ist mir nicht klar, geloggt wird:

  10:13:50 PM SEVERE: UNCAUGHT EXCEPTION IN THREAD 'Thread-9'
java.lang.IndexOutOfBoundsException: The row index 15 must be less than or equal to 11.
        at jgoodies.forms/com.jgoodies.forms.layout.CellConstraints.ensureValidGridBounds(CellConstraints.java:949)
        at jgoodies.forms/com.jgoodies.forms.layout.FormLayout.setConstraints(FormLayout.java:821)
        at jgoodies.forms/com.jgoodies.forms.layout.FormLayout.addLayoutComponent(FormLayout.java:1106)
        at java.desktop/java.awt.Container.addImpl(Unknown Source)
        at java.desktop/java.awt.Container.add(Unknown Source)
        at jgoodies.forms/com.jgoodies.forms.builder.AbstractFormBuilder.add(AbstractFormBuilder.java:491)
        at jgoodies.forms/com.jgoodies.forms.builder.PanelBuilder.add(PanelBuilder.java:879)
        at schedulesdirectdataservice.SchedulesDirectSettingsPanel.createGui(SchedulesDirectSettingsPanel.java:216)
        at schedulesdirectdataservice.SchedulesDirectSettingsPanel.<init>(SchedulesDirectSettingsPanel.java:96)
        at schedulesdirectdataservice.SchedulesDirectDataService.getAuthenticationPanel(SchedulesDirectDataService.java:1470)
        at tvbrowser/tvbrowser.core.tvdataservice.DefaultTvDataServiceProxy.getAuthenticationPanel(DefaultTvDataServiceProxy.java:302)
        at tvbrowser/tvbrowser.ui.configassistant.AuthenticationChannelCardPanel.createPanel(AuthenticationChannelCardPanel.java:74)
        at tvbrowser/tvbrowser.ui.configassistant.NetworkSuccessPanel.onNext(NetworkSuccessPanel.java:85)
        at tvbrowser/tvbrowser.ui.configassistant.ConfigAssistant.lambda$actionPerformed$0(ConfigAssistant.java:203)
        at java.base/java.lang.Thread.run(Unknown Source)

Die Senderauswahl wird gespeichert, nicht aber das heruntergeladene Programm, dieses muss also auch nach jedem Start erneut heruntergeladen werden.

Ausblick

Vermutlich durch die Verwendung des X-Servers kommt es zu der IndexOutOfBounds-Exception. Ich könnte mir den Code aus dem SVN ziehen: https://svn.code.sf.net/p/tvbrowser/code/ und dann versuchen, den Fehler zu debuggen. Also wenn ich irgendwann mal zu viel Zeit habe…
Oder ich schreibe einen Bugreport an die Entwickler, kostet aber auch Zeit, da das vermutlich ein sehr spezieller Spezialfall ist, den ich dann spezifisch beschreiben müsste, damit das überhaupt was bringt.
Alternativ kann man vermutlich auch mit dem Abbruch des Konfigurations Assistenten leben. Allerdings muss man den dann aber bei jedem Neustart wieder wegklicken, was auch irgendwie lästig ist.

Das bereits heruntergeladenes Programm “vergessen” wird ist da schon ärgerlicher. Hier müsste man den Speicherort im Container orten und über ein weiteres Volume persistieren.

In den laufenden Container kann man folgendermaßen einsteigen:

  docker exec -it tvbrowser bash

Der Start ließe sich über Docker-Compose etwas vereinfachen, in das Script (YML-File) kann man alle Environment Variablen, Volumes etc. eintragen.

Sobald die Probleme behoben sind, könnte ich den Dockerfile/Docker-Compose Code in ein GIT Repository, und das Image in die Docker Registry hochladen.

Die Lösung

Nachdem ich mit dem TV-Browser im Docker Container herumgespielt hatte, habe ich nochmal einen Versuch über den “herkömmlichen” Weg gewagt und das Sorglospaket (Java enthalten) heruntergezogen und installiert. Und siehe da: Der TV-Browser läuft auf meinem Windows Rechner ganz ohne Docker!

Conclusio

Interessant war das ganze natürlich trotzdem! Aber ob ich nochmal an dem Projekt weiter arbeiten werde glaube ich eher nicht. Dafür gibt es noch zu viele andere spannende Projekte 😉