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.
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 verwendet, wobei nur ein Subset an Optionen beachtet werden und einige Einstellungen durch uns forciert werden. Das praktischste ist es ein solches Pod-YAML lokal bei dir mit Hilfe von podman 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.
Neben dem Pod-YAML oder der Container Informationen gibt es folgende Anforderungen:
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.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
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 und nicht editierbar. Sie dokumentiert gewisse Einstellungen, welche wir beim starten deines Containers enforcen.
Die Datei private/container-config/pod-<POD-NAME>.yaml
ist eine durch dich editierbare Definition deines Pods. Die Spezifikation folgt dem Pod-Yaml Format von Kubernetes. Wobei jedoch nur ein Subset an Controls erlaubt sind und ansonsten durch uns forciert werden.
Dies sind:
spec.volumes
- Wobei nur Directory
, File
und emptyDir mit medium tmpfs
unterstützt werden. Siehe Storage für Details.spec.hostname
- Wird per default auf deinen Hostingnamen gesetztspec.securityContext.runAsUser
- Wobei dies nicht root sein darfspec.securityContext.runAsGroup
- Wobei diese ggf. überschrieben wird. Siehe Storagespec.containers
- Wobei von der ContainerSpec nur folgende Attribute unterstützt werden:
name
: notwendig!image
: notwendig!volumeMounts
: Referenziert auf spec.volumes
- Siehe Einschränkungen Storageenv
: Mehr dazu siehe Abschnitt Envports
: 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 darfsecurityContext.runAsGroup
- Wobei diese ggf. überschrieben wird. Siehe StorageAlle anderen Attribute eines Pod Yaml Spec werden ignoriert.
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.
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.
Ü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:
volumes:
- name: db
hostPath:
path: /data/private/data/db
type: Directory
Der Ordner muss existieren und darf kein Symlink sein. Die direkten Ordner der Webhosting Struktur (wie bspw. /www
) einzubinden ist nicht unterstützt.
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. Soll ein Volume schreibbar sein, sollte dies also per SFTP chmod g+w erstellt werden.
Du kannst auch Dateien in deinen Container einbinden. Beispielsweise so:
volumes:
- name: db-dump
hostPath:
path: /data/private/data/db-dump.sh
type: File
Die Datei muss existieren und ein richtige Datei (bspw. kein Symlink) sein.
Benötigst du nur flüchtigen Speicher, so kannst du dies folgendermassen spezifizieren:
volumes:
- name: db
emptyDir:
medium: Memory
Container Images können selbst definieren, welche Pfade in ihrem Image als Volume verwendet werden soll. Das Standardverhalten von Container Runtimes (podman, docker, …) ist, dass diese als anonyme Volumes gemounted werden und quasi semi-persistent sind.
Bei uns werden diese Volume definition explizit ignoriert.
Wird versucht ein Image zu starten, dass Volumes definiert hat, die aber nicht in den Volumes auf lokalen Storage gemapped wird, so wird die Ausführung des Pods abgebrochen. Dies damit auch wirklich alle persistent Daten in einem Image an einem von euch definierten Ort sind und ihr alle anderen Volumes zum ignorieren freigegeben habt.
Ihr könnt diese Pfade entweder als flüchtigen Speicher definieren oder explizit ignorieren.
Letzeres kann folgendermassen passieren:
metadata:
[...]
volumes_to_ignore:
'<container_name>':
- /path/of/a/volume
- /another/volume/path
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.
Sollten die Images der Container nicht anonym bezogen werden können, so wird ein Account mit Pull-Berechtigung auf der entsprechenden Registry benötigt (unter Gitlab ist das z.B. ein Project Access Token
mit der Rolle Reporter
und der Berechtigung read_registry
) Die Authentifizierungsdaten werden aus private/container-config/auth-<POD_NAME>-registry.yaml
gelesen und du kannst in dieser Datei Username und Passwort für mehrere Registries hinzufügen.
Das Format der Datei ist folgendes:
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.
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.
Du kannst deinen Pod auch selbst neustarten. Hierfür kannst du das user script pod_restart 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 durch einen solchen Neustart erzwingen.
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.
Diese Logs solltest du aber nie löschen, ansonsten musst du einen Tag warten, bis wieder Logs da rein geschrieben werden.
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.
Ein paar Punke, die es bei unserem Container Hosting zu beachten gibt:
Musst du E-Mails 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 E-Mails dann über smtp.immerda.ch:587
versendet werden.
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.
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:
Mit folgenden Schritte führen wir dies mit einem Simplen WebContainer aus, der illustrativ für ein simples Hosting herhalten soll:
$ 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 manage-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