Commit 122cf3da authored by mh's avatar mh
Browse files

add first shot about container hosting

parent caa35ae5
Pipeline #5538 passed with stages
in 1 minute and 41 seconds
---
title: "Containers"
date: 2020-01-21
weight: 5
---
Du kannst bei uns auch Container hosten lassen um dein Projekt laufen zu lassen. Grundsätzlich mappen wir ein Webhosting (d.h. ein Zugang mit Domain) auf einen Container resp. auf ein Set von Containers, die als Pod betrieben werden. Im folgenden sprechen wir grundsätzlich von einem Pod, wobei hierbei ein oder mehrere Container des gleichen Webhostings gemeint sind.
Die Integration in unser Webhosting nimmt dir dabei einiges ab, stellt jedoch auch ein paar Anforderungen an deine Container. Wenn deine Applikation im Container mit diesen Anforderungen und Einschränkungen umgehen kann, wird ein Hosten deiner Container ohne weiteres möglich sein. Weitergehende Anforderungen müssen individuell angesehen werden.
Dein Container Hosting wird nach wie vor durch unseren Webserver ausgeliefert, es ist deshalb nicht zwingend notwendig in deinem Pod einen Webserver zu betreiben.
## Übersicht
Damit die Inhalte deines Containers abrufbar werden, routen wir die Domains zu deinem Hosting auf **einen** Port deines Pods. Wir machen weiterhin die Organisation der Zertifikate (wie auch deren Rotation), terminieren HTTPS vor deinem Container und reichen nur HTTP Traffic in deinen Container.
Als Definition wie dein Pod aussehen und damit soll wird eine Spezifikation deines Pods im [Pod-Yaml Format von Kubernetes](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#pod-v1-core) verwendet, wobei nur ein Subset an Optionen beachtet werden und einige Einstellungen durch uns forciert werden. Das parktischste ist es ein solches Pod-YAML lokal bei dir mit Hilfe von [podman](https://podman.io) zu erstellen und dann mit uns zur initialen Einrichtung zu teilen.
Solltest du nur einen simplen einfachen Container haben, dann reicht es auch, einfach zu wissen auf welchem Port dieser Pod hört.
## Anforderungen
Neben dem Pod-YAML oder der Container Informationen gibt es folgende Anforderungen:
* Images sind in einer Registry verfügbar. Sind die Images nur mit Hilfe von Authentifierung zugänglich, benötigen wir Zugangsinformationen mit Pullrechten. Siehe dazu Registry Authentifizierung.
* Keine Prozesse unter `root`. Die ausgeführten Prozesse dürfen nicht als root laufen. Es gibt kaum Gründe, weshalb ein Prozess in einem Container als root laufen soll.
* Ingress HTTP Traffic. Wir forwarden den Traffic auf euer Webhosting auf einen vordefinierten Port (Bevorzugt 8080). Wir terminieren HTTPS auf dem Host selbst und in den Pod wird nur HTTP weitergeleitet.
* Storage ist relativ zu eurem Webhosting.
* Container laufen grundsätzlich read-only, dies bedeutet sämtliche Ordnerstrukturen, welche veränderbar sein müssen, müssen über ein Volume eingebunden werden.
## Webhosting Struktur
```
www/ # Public Webhosting Verzeichnis -> Siehe Ingress HTTP Traffic
logs/ # Sämtliche Logs zu eurem Hosting > Siehe Logs
scripts/ # User Skripte, die euch bei der Verwaltung helfen
data/ # Vom Webhost zugreifbare Struktur, sieh Storage
private/ # Privater Space des Webhostings, siehe Konfiguration
tmp/ # Temporäre Dateien, die während der Ausführung verwendet werden
```
## Konfiguration
Dein Container Webhosting besteht üblichersweise aus einem Pod, dieser Pod hat den Namen deines Webhostings.
Relevant hierfür sind zwei Dateien:
```
private/container-config/pod-<POD-NAME>.yaml
private/container-config/system-<POD-NAME>.yaml
```
Die Datei `system-<POD-NAME>.yaml` ist durch uns vorgeben uns nicht editierbar. Sie dokumentiert gewisse Einstellungen, welche wir beim starten deines Containers enforcen.
### Pod YAML
Die Datei `private/container-config/pod-<POD-NAME>.yaml` ist eine durch dich editierbare Definition deines Pods. Die Spezfiikation folgt dem [Pod-Yaml Format von Kubernetes](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#pod-v1-core). Wobei jedoch nur ein Subset an Controls erlaubt sind und ansonsten durch uns forciert werden.
Dies sind:
* `spec.volumes` - Wobei nur `Directory` und emptyDir mit medium `tmpfs` unterstützt werden. Siehe Storage für Details.
* `spec.hostname` - Wird per default auf deinen Hostingnamen gesetzt
* `spec.securityContext.runAsUser` - Wobei dies nicht root sein darf
* `spec.securityContext.runAsGroup` - Wobei diese ggf. überschrieben wird. Siehe Storage
* `spec.containers` - Wobei von der ContainerSpec nur folgende Attribute unterstützt werden:
* `name`: notwendig!
* `image`: notwendig!
* `volumeMounts`: Referenziert auf `spec.volumes` - Siehe Einschränkungen Storage
* `env`: Mehr dazu siehe Abschnitt Env
* `ports`: Wobei hier nur Ports erlaubt sind, welche in der Datei `system-<POD-NAME>.yaml` freigeschalten sind. Siehe Ingress HTTP Traffic für mehr.
* `securityContext.runAsUser` - Wobei dies nicht root sein darf
* `securityContext.runAsGroup` - Wobei diese ggf. überschrieben wird. Siehe Storage
Alle anderen Attribute eines Pod Yaml Spec werden ignoriert.
### Applikationscode
Hast du Applikationscode, welcher nicht Teil des Images ist, dann kannst du diesen auch in `private/app` hinterlegen. Dieser Ordner kann dann unter `/app` in deinen Container gemapped werden.
Dies ermöglicht es bspw. im Container selbst nur die Runtime zu haben und die App (bspw. deinen PHP/Python/Ruby/...) code separat zu verwalten.
## Ingress HTTP Traffic
Wie bereits erwähnt regeln wir die SSL Zertifikate und terminieren HTTPS auf dem Host, wo der Pod läuft. Wir routen den Traffic für dein Hosting auf den vordefinierten Port in deinen Pod. Dieser Port muss durch einen Container in deinem Pod definiert worden sein.
Alle anderen Ports werden ignoriert.
Generiert deine Applikation im Container Daten, welche direkt durch unseren Webserver (bspw. Bilder) ausgeliefert werden können, so können wir bspw. den Pfad `/images` vom Routing auf deinen Container aussnehmen und diese Dateien direkt ausliefern. Dies spart resourcen und ist sicher schneller als den Umweg durch deinen Container Prozess zu nehmen.
## Storage
Über `spec.volumes` kannst du Pfade anziehen, welche in die Containers deines Pods gemounted werden. Wichtig hierbei ist, dass die Pfade immer relativ zum Pfad deines Webhostings sein müssen. Wir empfehlen grundsätzlich alle Pfade unter `/data/private/data` anzulegen. Bspw. so:
```yaml
volumes:
- name: db
hostPath:
path: /data/private/data/db
type: Directory
```
Der Ordner muss existieren und darf kein Symlink sein.
Benötigen Container in einem Pod Storage, so wird der Prozess mit einer von uns vorgegeben Gruppe ausgeführt. Der User wird jedoch beibehalten. Dies hat den Effekt, dass Dateien die durch die Prozesse in containern geschrieben werden durch euren SFTP User les- oder gar editierbar sind, wenn die entsprechenden Gruppenberechtigungen vergeben sind.
Benötigst du nur flüchtigen Speicher, so kannst du dies folgendermassen spezifizieren:
```yaml
volumes:
- name: db
emptyDir:
medium: Memory
```
Da grundsätzlich sämtliche `Volumes` Definitionen eines Images gemounted werden, ist es empfohlen alle auch einzubinden. Ansonsten werden Volumes nur auf temporärem Speicher gemounted und verschwinden sobald der Pod neugestartet wird.
## Env
In der Pod Spezifikation kann du Environment Variablen für deinen Container festlegen. Es empfiehlt sich kleinere Konfigurationen wie bspw. Username und Passwort einer Datenbank darüber festzulegen.
Zusätzlich kannst du eine typsiche Env-Datei unter `private/container-config/<POD-NAME>-<CONTAINER_NAME>.env` hinterlegen, welche beim starten auch angezogen wird.
## Registry
Sollten die Images der Container nicht anonym bezogen werden können, so benötigen wir einen Account mit Pull-Berechtigung auf der entsprechenden Registry. Die Authentifizierungsdaten sind in `private/container-config/auth-<POD_NAME>-registry.yaml` hinterlegt und du kannst in dieser Datei Username und Passwort für weitere Registries hinzufügen.
Das Format der Datei ist folgendes:
```yaml
registry.example.com:
user: myreaduser
password: some_password
```
Solltest du die initialen Authentifizierungsdaten nicht mehr benötigen, so teile uns dies mit. Diese müssen zusätzlich von uns von Hand entfernt werden.
## Updates
Wir überprüfen die Images des Pods täglich auf Basis der hinterlegten Image Spezifikationen auf Updates. Ist für ein Image ein Update verfügbar, wird der Pod anschliessend neugestartet.
Grundsätzlich empfehlen wir die Images mit einem Tag zu referenzieren, welches Updates bekommt, jedoch eine stabile API bereitstellt. So dass die automatisierten Updates immer funktionieren.
## Neustarten
Du kannst deinen Pod auch selbst neustarten. Hierfür kannst du das user script [pod_restart](https://code.immerda.ch/immerda/puppet-modules/webhosting/-/blob/master/templates/user_scripts/pod_restart/pod_restart.pods.erb) anstossen. Dieses stoppt den aktuell laufenden Pod, worauf dieser automatisch wieder gestartet wird.
Das Skript wird durch anlegen der leeren Datei: `scripts/pod_restart/pod_restart.run` angestossen.
Beim Starten werden auch die Images neu heruntergeladen. Solltest du also dein Image schneller als das tägliche Update erneuern möchten, dann kannst du dies auch druch einen solchen Neustart erzwingen.
## Logs
Es ist empfohlen, dass die Applikationen in Containern ihre Logs auf stdout/stderr schreiben. Dies hat den Vorteil, dass wir die Logs dann in deinem Webhosting unter `logs/<hostingname>-<podname>.log` hinterlegen können. Wir rotieren diese auch täglich.
## Wiederkehrende Jobs aka. CronJobs
Benötigst du wiederkehrende Jobs, welche in deinem Pod angestossen werden sollen, so können wir Cron Jobs einrichten, die regelmässig angestossen werden. Dafür müssen wir wissen, in welchem Container, welcher Pfad angestossen werden muss.
## Beachte
Ein paar Punke, die es bei unserem Container Hosting zu beachten gibt:
### Email Versand
Musst du Emails aus deinen Containers verschicken, so musst dies über unsere Mailserver direkt machen. Die Auslieferung muss dabei über einen authentifizierten Account gehen. Wir empfehlen für dein Hosting einen dedizierten Account unter deiner Domain zuerstellen und dann für diesen ein Applikationspasswort zu erstellen. Mit diesem Account können Emails dann über `smtp.immerda.ch:587` versendet werden.
### Logische Backups
Grundsätzlich backupen wir wie bei allen anderen Hostings auch, alle Dateien unter deinem Hosting. Wenn du in deinem Pod bspw. jedoch auch eine Datenbank laufen lässt, dann wird ein reines Backup der Dateien nicht ausreichen. Du musst dafür ein Skript in einem deiner Container einrichten, welches das Backup der Datenbank in ein Verzeichnis deines Webhostings erstellt. Dieses Skript kann dann durch einen CronJob angestossen werden.
## Beispiel
Hast du Applikationen als Container gebaut, die du bei uns hosten möchtest. Dann helfen dir evtl. folgende Schritte das ganze einrichten etwas zu beschleunigen und einige Probleme bereits im vorraus auszumerzen.
Du benötigst dafür:
* [Podman](https://podman.io)
* Ruby
* Curl
Mit folgenden Schritte führen wir dies mit einem Simplen WebContainer aus, der illustrativ für ein simples Hosting herhalten soll:
```bash
$ cd /tmp && mkdir mycontainertest
$ curl 'https://code.immerda.ch/immerda/puppet-modules/podman/-/raw/master/files/manage-user-pod.rb?inline=false' -o manage-user-pod.rb
$ chmod +x manager-user-pod.rb
$ mkdir data data/app tmp tmp/run
$ chmod a+w tmp/run
$ cat >data/app/index.html<<EOF
Hello World
EOF
$ cat >pod.yaml<<EOF
apiVersion: v1
kind: Pod
metadata:
name: mycontainertest
spec:
containers:
- env:
- name: ADDR
value: 127.0.0.1:8080
image: registry.git.autistici.org/ai3/docker/static-content:latest
name: mywebserver
volumeMounts:
- mountPath: /var/www
name: data-app
securityContext:
runAsUser: 1000
runAsGroup: 1000
ports:
- containerPort: 8080
hostPort: 8080
protocol: TCP
volumes:
- hostPath:
path: /data/app
type: Directory
name: data-app
EOF
$ cat >system.yaml<<EOF
name: 'mycontainertest'
volumes_base_dir: '$(pwd)'
volumes_containers_gid_share: true
container_env_dir: '$(pwd)'
tmp_dir: '$(pwd)/tmp'
socket_ports:
8080:
dir: $(pwd)/tmp/run
exposed_ports: []
pidfile: '$(pwd)/pod.pid'
EOF
$ ./manage-user-pod.rb parse pod.yaml system.yaml
# alles ok?
$ ./manage-user-pod.rb start pod.yaml system.yaml
# pod wird gestartet, rufe nun den inhalt ab
$ curl --unix-socket tmp/run/8080 http://localhost
Hello World
$ ./manage-user-pod.rb stop pod.yaml system.yaml
# pod wird gestoppt
```
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment