Categories
Linux

Lokaler virtueller Server

Motivation

Ich habe schon seit langer Zeit einen virtuellen Server gemietet, auf dem ich verschiedene Dienste, wie zB Webseite und Mail, laufen lasse. Die Anwendungen laufen direkt auf dem Server und ich will sie schon seit langem in Container stecken. Das Betriebssystem ist mit Ubuntu 18.04 LTS hoffnungslos veraltet, aktuell ist 24.04 LTS.
Ein Upgrade von Ubuntu 18 auf 24 ist mit Risiko und vermutlich hohem Aufwand verbunden, da erfahrungsgemäß irgendetwas irgendwie anders funktioniert und mühevoll angepasst werden muss. Grade bei einer Upgradekette 18->20->22->24 kann so einiges schief gehen.
Den Server miete ich seit ein paar Jahren und inzwischen gibt es für das gleiche Geld bessere (virtuelle) Hardware.
Aus diesen Gründen plane ich, einen neuen virtuellen Server zu mieten und auf diesen umzuziehen.

Wie es auf dem Neuen laufen soll

Auf dem neuen Server soll Ubuntu 24.04 LTS laufen.
Auf dem Linux System soll Docker installiert werden.
Die Docker Container sollen mit Portainer verwaltet werden.
Die Webseite der Portainer Verwaltung und die weiteren Dienste sollen über einen Reverse Proxy, beispielsweise Traefik, erreichbar sein.
Der Reverse Proxy soll den Zugriff ausschließlich verschlüsselt über HTTPS erlauben und entsprechend konfiguriert sein.
Das HTTPS Zertifikat soll von Let's Encrypt kommen.

lokaler virtueller Server

Zuerst möchte ich einen lokalen virtuellen Server einrichten um auf diesem die Migration vorbereiten zu können. Dazu werde ich Virtual Box von Oracle verwenden.

graph TD G[Portainer Webinterface] --> F H[Website: Nginx] --> F I[Mailserver: Postfix/Dovecot] --> F F[Traefik] --> E E[Portainer] --> D D[Docker] --> C C[Ubuntu 24.04 LTS] --> B B[VirtualBox] --> A A[Host System: PC]

Vom Host System (also mein PC, auf dem Virtual Box läuft) aus soll der Zugriff auf die Webseite per Domain Name funktionieren, nicht nur über die IP. Idealerweise sollte die Verschlüsselung mit Let's Encrypt vorgenommen werden, um so realistisch wie möglich das spätere System vorzubauen.
Leider ist das nicht (mit vertretbarem Aufwand) möglich, daher werde ich alles ohne HTTPS aufsetzen.

virtuellen Server aufsetzen

  1. Oracle VirtualBox installieren
  2. Neue virtuelle Maschine erzeugen
    • 8 GB RAM
    • 4 CPUs
    • ISO: Ubuntu 24.04.1 LTS
    • Unbeaufsichtigte Installation:
      • Benutzername und Passwort
      • Hostname: kaulbach
      • Domain Name: local
  3. In den Netzwerkadapter-Einstellungen der virtuellen Maschine auf "Bridged Adapter" (Brückenadapter) umstellen
  4. nach der Installation einloggen und mittels ip a die IP-Adresse der VM identifizieren (192.168.178.47)
  5. Auf dem Host System in der Datei C:\Windows\System32\drivers\etc\hosts hinzufügen: 192.168.178.47 kaulbach.local 192.168.178.47 traefik.kaulbach.local 192.168.178.47 portainer.kaulbach.local
  6. Test auf dem Host System:
    • ping 192.168.178.47
    • ping kaulbach.local

virtuellen Server einrichten

sudo apt update
sudo apt upgrade -y

# Spracheinstellungen ändern
sudo locale-gen de_DE.UTF-8
sudo update-locale LANG=de_DE.UTF-8

# Tastaturbelegung ändern
sudo dpkg-reconfigure keyboard-configuration

# SSH Server installieren
sudo apt install -y openssh-server
sudo systemctl start ssh
sudo systemctl enable ssh

Installation von Docker, Traefik, Portainer

1. Docker installieren

  1. Paketliste aktualisieren:

    sudo apt update
    sudo apt upgrade -y
  2. Abhängigkeiten installieren:

    sudo apt install -y ca-certificates curl gnupg
  3. Docker-Repository hinzufügen:

    sudo mkdir -p /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  4. Docker installieren:

    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  5. Docker-Dienst aktivieren:

    sudo systemctl enable docker
    sudo systemctl start docker
  6. Rechte für den aktuellen Benutzer konfigurieren:

    sudo usermod -aG docker $USER
    newgrp docker

2. Docker Compose installieren

Docker Compose ist bei neueren Docker-Versionen bereits als Plugin enthalten. Es kann direkt über den Docker-CLI-Befehl docker compose genutzt werden.

Installation testen:

docker compose version

3.0 Traefik Netzwerk anlegen

Alle mit dem Traefik Dienst verbundenen Container sollen im selben Netzwerk liegen:

docker network create traefik-net

3. Traefik installieren

  1. Arbeitsverzeichnis erstellen:

    mkdir ~/traefik && cd ~/traefik
  2. docker-compose.yml für Traefik erstellen:

    vim docker-compose.yml

    Inhalt:

    
    services:
    traefik:
    image: traefik:v3.2
    container_name: traefik
    restart: always
    ports:
      - "80:80"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik.yml:/etc/traefik/traefik.yml:ro"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.kaulbach.local`)"
      - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.services.traefik.loadbalancer.server.port=8080"

networks: default: name: traefik-net external: true


3. **Traefik-Konfigurationsdatei erstellen**:
```bash
vim traefik.yml

Inhalt:

entryPoints:
  web:
    address: ":80"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

api:
  dashboard: true
  insecure: true
  1. Container starten:

    docker compose up -d
    # Log Files checken
    docker logs traefik
  2. Subdomain eintragen Auf dem Host System in der Datei C:\Windows\System32\drivers\etc\hosts den Eintrag für die Subdomain "traefik.kaulbach.local" hinzufügen: 192.168.178.47 traefik.kaulbach.local

  3. Dashboard aufrufen: Das Traefik-Dashboard ist unter http://traefik.kaulbach.local erreichbar.

4. Webserver installieren

  1. Arbeitsverzeichnis erstellen:

    mkdir ~/web && cd ~/web
  2. Beispielseite erstellen:

    mkdir ~/web/html
    echo "<h1>Hello, World! (c) DerIngo</h1>" > ~/web/html/index.html
  3. docker-compose.yml für den Webserver erstellen:

    vim docker-compose.yml

    Inhalt:

    
    services:
    nginx:
    image: nginx
    container_name: nginx
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx.rule=Host(`kaulbach.local`)"
      - "traefik.http.routers.nginx.entrypoints=web"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./html:/usr/share/nginx/html:ro"

networks: default: name: traefik-net external: true


4. **Traefik-Labels nutzen**:
- Die Labels im obigen Beispiel sorgen dafür, dass Traefik den Webserver unter `http://kaulbach.local` bereitstellt.

5. **Container starten**:
```bash
docker compose up -d
# Log Files checken
docker logs web
  1. Webseite aufrufen: Die Webseite ist unter http://kaulbach.local erreichbar.

5. Portainer installieren

  1. Arbeitsverzeichnis erstellen:

    mkdir ~/portainer && cd ~/portainer
  2. docker-compose.yml für Portainer erstellen:

    vim docker-compose.yml

    Inhalt:

    
    services:
    portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: always
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
            - "./data:/data"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.kaulbach.local`)"
      - "traefik.http.routers.portainer.entrypoints=web"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"

networks: default: name: traefik-net external: true


3. **Traefik-Labels nutzen**:
- Die Labels im obigen Beispiel sorgen dafür, dass Traefik das Portainer Dashboard unter `http://portainer.kaulbach.local` bereitstellt.

4. **Container starten**:
```bash
docker compose up -d
# Log Files checken
docker logs portainer
  1. Subdomain eintragen Auf dem Host System in der Datei C:\Windows\System32\drivers\etc\hosts den Eintrag für die Subdomain "portainer.kaulbach.local" hinzufügen: 192.168.178.47 portainer.kaulbach.local

  2. Dashboard aufrufen: Das Traefik-Dashboard ist unter http://portainer.kaulbach.local erreichbar.

  3. Portainer konfigurieren

    • Passwort für den User "admin" festlegen: "adminpassword".
    • Environment "local" ist bereits angelegt

6. ein weiterer Webserver

  1. Webserver anlegen und starten:

    mkdir -p ~/web2/html
    echo "<h1>Hello vom geheimnisvollen 2. Server</h1>" > ~/web2/html/index.html
    touch ~/web2/docker-compose.yml # mit Inhalt s.u. befüllen
    cd ~/web2
    docker compose up -d
  2. docker-compose.yml für den Webserver erstellen:

    
    services:
    nginx:
    image: nginx
    container_name: nginx2
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx2.rule=PathPrefix(`/derzweite`)"
      - "traefik.http.routers.nginx2.entrypoints=web"
    volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./html:/usr/share/nginx/html:ro"

networks: default: name: traefik-net external: true



3. **`docker-compose.yml` des ersten Webservers anpassen**:
Der Router des ersten Webservers mit dem Host(`kaulbach.local`) wird auch auf /derzweite matchen.
Daher müssen wir die Regel für den Router des ersten Servers anpassen: ```- "traefik.http.routers.nginx.rule=PathPrefix(`/`) && !PathPrefix(`/derzweite`)"``` 4. **Webseite aufrufen**: Die Webseite ist unter `http://kaulbach.local/derzweite` erreichbar. ## Abschluß Das Fundament ist gelegt, darauf aufbauend kann ich die Migration der einzelnen Anwendungen erarbeiten.
Der Code-Editor, bzw. die Anzeige des Codes, des "Markup Markdown"-Editors ist ziehmlich kaputt. Und das was man sieht, sieht hässlich aus. Ich habe auch schon ein Ticket erstellt, ob und wie man andere Code-Editoren wie CodeMirror Blocks einbinden kann.
Die Dateien habe ich in [GitHub](https://github.com/DerIngo/localvirtualserver) eingecheckt.
Categories
Linux

SSH Key-Based Authentication on Linux Servers

I want to login from one Linux server to another Linux server without the need to enter a password.

Create SSH keys

# Login to server 1
ssh-keygen

Output:

Generating public/private rsa key pair.
Enter file in which to save the key (/home/'USERNAME'/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/'USERNAME'/.ssh/id_rsa
Your public key has been saved in /home/'USERNAME'/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:27U2nApZQLSwS1UVh2Lw4aDM/B9/gR0Uum1ppRrqjpg 'USERNAME'@server1
The key\'s randomart image is:
+---[RSA 3072]----+
|    +o+ .o       |
| + . Bo+o .      |
|  = o.*...       |
|   o + o .       |
|    + o S   .    |
|     . X * +     |
| .    * @ O      |
|E . .. B * .     |
| ..+..o o        |
+----[SHA256]-----+

The utility will prompt to select a location for the keys that will be generated. By default, the keys will be stored in the ~/.ssh directory within user’s home directory. The private key will be called id_rsa and the associated public key will be called id_rsa.pub.

Now we have a private and public key that we can use to authenticate.

Copy keys to server 2

We will use the same keys on server 2:

scp ~/.ssh/id_rsa     USERNAME@server2ip:~/.ssh
scp ~/.ssh/id_rsa.pub USERNAME@server2ip:~/.ssh

Create authorized_keys on both servers:

ssh-copy-id USERNAME@localhost
ssh-copy-id USERNAME@server2ip

Connect to server 2

ssh USERNAME@server2ip
# or just
ssh server2ip

Now we SHOULD connect to server2 without the need to enter a password.

Failing - But why?

Unluckily in my case I still have to enter a password. But why?

# Login to server 2
ssh server2ip
Password:

# check auth.log
less /var/log/auth.log
...
rexec line 15: Deprecated option RSAAuthentication
...

# check sshd_config
less /etc/ssh/sshd_config
...
RSAAuthentication no
...

So in my case the RSA-Authentication has been disabled.
As the default is enabled by purpose, this has been done by purpose.
Unfortunatly I am not the admin or manager of the server, so I can not change this settings.

Categories
Development Linux

Docker Logs lesen

Problem

Der Server wird gestartet mit:

sudo docker-compose up --detach

Dadurch werden die Logfiles mit dem User root geschrieben und ich kann sie mit meinem User ingo nicht lesen.

Auf der Console kann ich das leicht mit einem vorangestellten sudo lösen, aber um mal eben schnell in die Logfiles rein zu schauen würde ich gerne mein graphisches Tool WinSCP verwenden

Lösung

Man kann Docker / Docker Compose mit einem User starten und mit dem würden dann vermutlich auch die Logfiles geschrieben werden.
Als ich das mit einem Tomcat Image getestet hatte, ist es daran gescheitert, dass mit meinem User ingo auf bestimmte Verzeichnisse im Container nicht zugegriffen werden konnte.

Gelöst habe ich es dann so, dass ich nicht den User, oder User und Gruppe, gesetzt habe, sondern nur die Gruppe.
So wird mit dem root User gearbeitet, die Dateien gehören dem User root und für die gesetzte Gruppe sind sie lesbar.
Mein User muss natürlich ebenfalls in der Gruppe sein.

Gruppe anlegen:

sudo groupadd -g 1001 logfilegroup

Die Group ID ist relativ willkürlich gesetzt. Eigentlich hatte ich groupadd ohne das -g Flag aufgerufen und dann mit cat /etc/group die Group ID rausgesucht. Hier wollte ich das Statement mit explizitem setzen der Group ID hinschreiben, da ich es auch im Projekt verwendet hatte, um auf jedem Server die selbe Group ID zu haben.

User der Gruppe hinzufügen:

sudo usermod --append --groups logfilegroup ingo

Mit den Befehlen groups oder id kann man die Gruppen seines Users sehen, die neue logfilegroup wird aber erst in einer neuen Shell hinzugefügt. Also entweder die Shell schließen und neu öffnen, oder mit su ingo weiter arbeiten.

ingo$ sudo usermod --append --groups logfilegroup ingo
ingo$ groups
ingo adm
ingo$ su ingo
Password:
ingo$ groups
ingo adm logfilegroup

Docker Compose File:

Im Docker Compose File muss die Group ID gesetzt werden, mit dem Namen der Gruppe geht es nicht.

version: "3.2"
services:
  melba-web:
    image: tomcat:10.1.18-jre21 # https://hub.docker.com/_/tomcat
    restart: always
    container_name: myapp-tomcat
    user: :1001
    ports: 
      - "8080:8080"
    environment:
      - _JAVA_OPTIONS=-Duser.language=de -Duser.country=DE
    volumes:
      - ./log/tomcat:/usr/local/tomcat/logs
sudo docker-compose up --detach

ls -lisah log/tomcat/*
4211764 4.0K drwxr-xr-x 2 ingo ingo  4.0K Feb  8 16:52 .
4211762 4.0K drwxr-xr-x 4 ingo ingo  4.0K Feb  5 16:42 ..
4205212  24K -rw-r----- 1 root logfilegroup  24K Feb  8 16:21 catalina.2024-02-08.log
4211758  28K -rw-r----- 1 root logfilegroup  24K Feb  8 16:58 localhost_access_log.2024-02-08.txt
Categories
Development Java Linux

SAPJCO 2

Problem

We have a very, very old application that needs to be migrated into AWS. So we copied all files into AWS EC2 instance and tried to start the application. After fixing a lot of minor problems we faced a tough challenge with a SAPJCO RFC Call.

The Exception message was something like this:

Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [sapjcorfc (Not found in java.library.path)]. java.library.path [/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64/default:/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64:/usr/lib]
        at com.sap.mw.jco.JCO.(JCO.java:871)
        at java.lang.J9VMInternals.initializeImpl(Native Method)
        at java.lang.J9VMInternals.initialize(J9VMInternals.java:199)

I guess, with a JCO Version 3 we would not have much trouble, but in this ancient application JCO Version 2 is used and we cannot update to Version 3 without a huge efford. In other projects I had the luck that I could migrate to Version.

The application is running on a Linux system. But belive me: it would have been much harder on a Windows machine.

Analysis

To find the cause of the problem I wrote the simpliest JCO Test Programm I can image:

import com.sap.mw.jco.JCO;

public class TestMain {

    public static void main(String[] args) {
        System.out.println(JCO.getVersion());
    }

}

My analysis programm structure:

app
├─ JCo/
│  ├─ librfccm.so
│  ├─ libsapjcorfc.so
│  ├─ sapjco.jar
├─ TestMain.java

Compile from command line:

javac -cp ".:/app/JCo/sapjco.jar" TestMain.java

Run from command line:

java -cp ".:/app/JCo/sapjco.jar" TestMain

That gave me another error:

Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: librfccm.so: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo]
        at com.sap.mw.jco.JCO.(JCO.java:871)
        at TestMain.main(TestMain.java:11)

Need to set an environment property first:

export LD_LIBRARY_PATH=/app/JCo

Run command line to start programm again and got another error:

Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo]
        at com.sap.mw.jco.JCO.(JCO.java:871)
        at TestMain.main(TestMain.java:11)

The interesting part of the error message:

Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5

Solution

We need the libstdc++.so.5 library, but installed is libstdc++.so.6

To get libstdc++.so.5 we installed package compat-libstdc++-33-3.2.3-66.x86_64:

yum install compat-libstdc++-33-3.2.3-66.x86_64

## to be honest, I am not exactly 100% sure, what I did in my investigations, so the command may be a little differend, ex:
# yum install compat-libstdc++-33-3
# yum install compat-libstdc++-33
# yum install compat-libstdc++-33 libstdc++.so.5

Test

Run from command line:

java -cp ".:/app/JCo/sapjco.jar" TestMain

That gave me no error, but SAPJCo Version number.:

2.1.10 (2011-05-10)

Finally it worked 😎

Anekdotum

Quote from an StackOverflow post from 2010:

libstdc++.so.5 is a very old version of the standard c++ library.

Some Analysis Details

Writing this article is giving me the feeling, that this was all super easy. But in reality it was a real pain in the allerwertesten.

To isolate the source of the problem, I did not only write the small Java (JCO.getVersion) application, I also set up a Docker environment.

One challenge was to find a useful Docker image to start from. I started with an OpenJDK Image that was already deprecated. Deprecated was not the problem, but I could not install libstdc++.so.5.

Next I tried to use the newer, undeprecated Eclipse-Temurin Image. But still could not install libstdc++.so.5

So I finally ended in a Debian Image and self installed Java where I was able to install libstdc++5.

FROM debian:bookworm-slim
 RUN apt-get update && apt-get install -y locales openjdk-11-jdk libstdc++5 \
     && rm -rf /var/lib/apt/lists/* \
     && localedef -i de_DE -c -f UTF-8 -A /usr/share/locale/locale.alias de_DE.UTF-8
 ENV LANG de_DE.utf8

COPY ./JCo/* /app/JCo/
COPY TestMain.java /app
WORKDIR /app
 ENV LD_LIBRARY_PATH=/app/JCo

 RUN javac -cp ".:/app/JCo/sapjco.jar" TestMain.java 

 CMD ["java", "-cp", ".:/app/JCo/sapjco.jar", "-Djava.library.path=/app/JCo", "TestMain"]

Note:

The locale might not be neccessary.
The "-Djava.library.path=/app/JCo" Parameter is not neccessary. But I leave it as an example, how one can use it.

Build and start the app:

docker build -t my-java-app .
docker run -it --rm --name my-running-app my-java-app
2.1.10 (2011-05-10)

Work in container

To work in the running container:

docker exec -it my-running-app /bin/sh

But there is one problem: You can only interact with a running container. But the TestMain-Programm is executed and immediately closed.

So I wrote another Test Programm, that keeps running, so I can enter the running container and test stuff (install packages, compile Java programm, etc.):

import java.io.BufferedReader;
import java.io.InputStreamReader;

import com.sap.mw.jco.JCO;

public class TestMain {

    public static void main(String[] args) {
        System.out.println("Hello World");
        //System.out.println("JCO Version: " + JCO.getVersion());

        while (true) {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Enter Input : ");
            try {
                String s = br.readLine();
                System.out.println(s);
            }catch(Exception e) {
                System.out.println(e);
            }
        }

    }

}