TL;DR: Ein Überblicksartikel-Leitfaden zum Vergleich von Umgebungen für das Starten von Anwendungen in Containern. Es werden die Möglichkeiten von Docker und anderen ähnlichen Systemen behandelt.

Ein bisschen Geschichte, woher das alles kommt
Geschichte
Die erste allgemein bekannte Methode zur Isolation von Anwendungen ist chroot. Der gleichnamige Systemaufruf ermöglicht die Änderung des Wurzelverzeichnisses — so hat das aufrufende Programm nur Zugriff auf die Dateien innerhalb dieses Verzeichnisses. Wenn das Programm jedoch mit Superuser-Rechten ausgestattet wird, kann es potenziell "aus dem chroot entkommen" und Zugriff auf das zugrunde liegende Betriebssystem erhalten. Darüber hinaus sind andere Ressourcen (Arbeitsspeicher, Prozessor) und der Netzwerkzugang nicht beschränkt.
Die nächste Methode besteht darin, ein vollständiges Betriebssystem innerhalb eines Containers zu starten, basierend auf den Mechanismen des Betriebssystemkerns. In verschiedenen Betriebssystemen wird diese Methode unterschiedlich bezeichnet, aber das Prinzip bleibt das gleiche — mehrere unabhängige Betriebssysteme zu starten, die alle mit demselben Kern arbeiten wie das zugrunde liegende Betriebssystem. Dazu gehören FreeBSD Jails, Solaris Zones, OpenVZ und LXC für Linux. Hierbei wird nicht nur die Isolation des Speicherplatzes, sondern auch anderer Ressourcen sichergestellt, sodass jeder Container Einschränkungen hinsichtlich CPU-Zeit, Arbeitsspeicher und Netzwerkbandbreite haben kann. Im Vergleich zu chroot ist es schwieriger, aus dem Container auszubrechen, da der Superuser im Container nur Zugriff auf die Inhalte des Containers hat. Dennoch gibt es eine nicht unerhebliche Wahrscheinlichkeit für ein "Durchbrechen" des Isolationstrenns des Kerns und damit für den Zugriff auf das zugrunde liegende Betriebssystem, insbesondere aufgrund der Notwendigkeit, das Betriebssystem im Container aktuell zu halten und der Verwendung älterer Kernversionen (insbesondere relevant für Linux, weniger für FreeBSD).
Anstatt ein vollwertiges Betriebssystem in einem Container (mit Initialisierungssystem, Paketmanager usw.) zu starten, kann man direkt Anwendungen ausführen, wobei es wichtig ist, den Anwendungen diese Möglichkeit zu geben (Verfügbarkeit der benötigten Bibliotheken und anderer Dateien). Diese Idee diente als Grundlage für die containerbasierte Virtualisierung von Anwendungen, deren bekanntester Vertreter Docker ist. Im Vergleich zu früheren Systemen ermöglichten flexiblere Isolationsmechanismen zusammen mit der integrierten Unterstützung virtueller Netzwerke zwischen Containern und der Überwachung des Anwendungsstatus innerhalb des Containers letztendlich den Aufbau einer einheitlichen Umgebung aus einer großen Anzahl physischer Server für die Ausführung von Containern – ohne die Notwendigkeit einer manuellen Ressourcenverwaltung.
Docker
Docker ist die bekannteste Software zur Containerisierung von Anwendungen. Es ist in der Programmiersprache Go geschrieben und nutzt die Standardfunktionen des Linux-Kernels – cgroups, Namespaces, Capabilities usw. – sowie Dateisysteme wie Auffs und andere ähnliche, um Speicherplatz zu sparen.

Quelle: wikimedia
Architektur
Vor Version 1.11 arbeitete Docker als ein einzelner Dienst, der alle Operationen mit Containern durchführte: Herunterladen von Container-Images, Starten von Containern, Verarbeiten von API-Anfragen. Ab Version 1.11 wurde Docker in mehrere Teile aufgeteilt, die miteinander interagieren: containerd zur Verwaltung des gesamten Lebenszyklus von Containern (Zuteilung von Speicherplatz, Herunterladen von Images, Netzwerkmanagement, Start, Installation und Überwachung des Containerstatus) und runC, eine Ausführungsumgebung für Container, die auf der Nutzung von cgroups und anderen Möglichkeiten des Linux-Kernels basiert. Der Dienst docker selbst blieb bestehen, dient jedoch jetzt nur noch der Verarbeitung von API-Anfragen, die an containerd weitergeleitet werden.

Installation und Konfiguration
Meine bevorzugte Methode zur Installation von Docker ist docker-machine, das nicht nur die Installation und Konfiguration von Docker auf entfernten Servern (einschließlich verschiedener Clouds) ermöglicht, sondern auch die Arbeit mit den Dateisystemen entfernter Server erlaubt und verschiedene Befehle ausführen kann.
Seit 2018 hat sich das Projekt jedoch kaum weiterentwickelt, daher werden wir die Installation auf die gängige Weise für die meisten Linux-Distributionen durchführen – durch Hinzufügen des Repositories und der Installation der erforderlichen Pakete.
Diese Methode wird auch bei automatisierten Installationen angewendet, beispielsweise mit Ansible oder anderen ähnlichen Systemen, aber in diesem Artikel werde ich sie nicht behandeln.
Die Installation erfolgt auf Centos 7. Als Server verwende ich eine virtuelle Maschine; zur Installation reichen die untenstehenden Befehle aus:
# yum install -y yum-utils
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum install docker-ce docker-ce-cli containerd.ioNach der Installation muss der Dienst gestartet und beim Systemstart aktiviert werden:
# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanentZusätzlich kann eine Gruppe namens docker erstellt werden, deren Benutzer mit docker ohne sudo arbeiten können, das Logging konfiguriert werden, der Zugriff auf die API von außen aktiviert und die Firewall präziser eingestellt werden (alles ist verboten, was nicht erlaubt ist; in den obigen und folgenden Beispielen habe ich dies der Einfachheit halber weggelassen), aber ich werde hier nicht näher darauf eingehen.
Weitere Möglichkeiten
Neben der bereits genannten Docker-Maschine gibt es auch eine Docker-Registry, ein Tool zur Speicherung von Container-Images, sowie Docker Compose — ein Tool zur Automatisierung der Bereitstellung von Anwendungen in Containern, bei dem YAML-Dateien zur Erstellung und Konfiguration von Containern und anderen damit verbundenen Aspekten (wie Netzwerke, dauerhafte Dateisysteme zur Datenspeicherung) verwendet werden.
Darüber hinaus können mit dieser Methode Pipeline für CICD organisiert werden. Eine andere interessante Möglichkeit ist der Clusterbetrieb, der sogenannte Swarm-Modus (bis Version 1.12 als Docker Swarm bekannt), der es ermöglicht, aus mehreren Servern eine einheitliche Infrastruktur für den Betrieb von Containern zu schaffen. Es gibt Unterstützung für virtuelle Netzwerke über alle Server hinweg, einen integrierten Lastenausgleich sowie Unterstützung für Geheimnisse für Container.
Die YAML-Dateien von Docker Compose können mit kleinen Änderungen für solche Cluster verwendet werden, wodurch die Verwaltung kleinerer und mittlerer Cluster für verschiedene Zwecke vollständig automatisiert wird. Für große Cluster ist es jedoch besser, Kubernetes zu verwenden, da die Betriebskosten im Swarm-Modus die von Kubernetes überschreiten können. Neben runC kann beispielsweise auch
Arbeiten mit Docker
Nach der Installation und Konfiguration werden wir versuchen, ein Cluster aufzubauen, in dem wir GitLab und Docker Registry für das Entwicklerteam bereitstellen werden. Ich werde dafür drei virtuelle Maschinen als Server verwenden, auf denen ich zusätzlich das verteilte Dateisystem GlusterFS einrichten werde, das ich als Speicher für die Docker-Volumes verwenden werde, beispielsweise für den Start einer ausfallsicheren Version der Docker Registry. Die Schlüsselkomponenten für den Betrieb sind: Docker Registry, Postgresql, Redis, GitLab mit Unterstützung für GitLab Runner über Swarm. Postgresql werden wir mit Clusterbildung starten. , daher sollte GlusterFS nicht zur Speicherung der Postgresql-Daten verwendet werden. Die anderen kritischen Daten werden auf GlusterFS gespeichert.
Um GlusterFS auf allen Servern (die als node1, node2, node3 bezeichnet werden) bereitzustellen, müssen die Pakete installiert, die Firewall konfiguriert und die benötigten Verzeichnisse erstellt werden:
# yum -y install centos-release-gluster7
# yum -y install glusterfs-server
# systemctl enable glusterd
# systemctl start glusterd
# firewall-cmd --add-service=glusterfs --permanent
# firewall-cmd --reload
# mkdir -p /srv/gluster
# mkdir -p /srv/docker
# echo "$(hostname):/docker /srv/docker glusterfs defaults,_netdev 0 0" >> /etc/fstabNach der Installation muss die Konfiguration von GlusterFS von einem Knoten aus fortgesetzt werden, zum Beispiel node1:
# gluster peer probe node2
# gluster peer probe node3
# gluster volume create docker replica 3 node1:/srv/gluster node2:/srv/gluster node3:/srv/gluster force
# gluster volume start dockerDann muss das erhaltene Volume (der Befehl muss auf allen Servern ausgeführt werden) eingebunden werden:
# mount /srv/dockerDie Konfiguration des Swarm-Modus erfolgt auf einem der Server, der der Leader sein wird, die anderen müssen sich dem Cluster anschließen. Daher muss das Ergebnis des Befehls auf dem ersten Server kopiert und auf den anderen ausgeführt werden.
Die erste Konfiguration des Clusters, ich führe den Startbefehl auf node1 aus:
# docker swarm init
Swarm initialized: current node (a5jpfrh5uvo7svzz1ajduokyq) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-0c5mf7mvzc7o7vjk0wngno2dy70xs95tovfxbv4tqt9280toku-863hyosdlzvd76trfptd4xnzd xx.xx.xx.xx:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
# docker swarm join-token managerWir kopieren das Ergebnis des zweiten Befehls und führen es auf node2 und node3 aus:
# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377
This node joined a swarm as a manager.Damit ist die vorläufige Konfiguration der Server abgeschlossen, wir beginnen mit der Einrichtung der Dienste. Die Befehle zur Ausführung werden von node1 ausgeführt, sofern nicht anders angegeben.
Zuerst erstellen wir Netzwerke für die Container:
# docker network create --driver=overlay etcd
# docker network create --driver=overlay pgsql
# docker network create --driver=overlay redis
# docker network create --driver=overlay traefik
# docker network create --driver=overlay gitlabDann kennzeichnen wir die Server, dies ist notwendig, um einige Dienste an die Server zu binden:
# docker node update --label-add nodename=node1 node1
# docker node update --label-add nodename=node2 node2
# docker node update --label-add nodename=node3 node3Danach erstellen wir Verzeichnisse zum Speichern von etcd-Daten, einem KV-Speicher, der für Traefik und Stolon benötigt wird. Ähnlich wie bei Postgresql werden dies Container sein, die an die Server gebunden sind, daher führen wir diesen Befehl auf allen Servern aus:
# mkdir -p /srv/etcdAls nächstes erstellen wir eine Datei zur Konfiguration von etcd und wenden sie an:
00etcd.yml
version: '3.7'
services:
etcd1:
image: quay.io/coreos/etcd:latest
hostname: etcd1
command:
- etcd
- --name=etcd1
- --data-dir=/data.etcd
- --advertise-client-urls=http://etcd1:2379
- --listen-client-urls=http://0.0.0.0:2379
- --initial-advertise-peer-urls=http://etcd1:2380
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- --initial-cluster-state=new
- --initial-cluster-token=etcd-cluster
networks:
- etcd
volumes:
- etcd1vol:/data.etcd
deploy:
replicas: 1
placement:
constraints: [node.labels.nodename == node1]
etcd2:
image: quay.io/coreos/etcd:latest
hostname: etcd2
command:
- etcd
- --name=etcd2
- --data-dir=/data.etcd
- --advertise-client-urls=http://etcd2:2379
- --listen-client-urls=http://0.0.0.0:2379
- --initial-advertise-peer-urls=http://etcd2:2380
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- --initial-cluster-state=new
- --initial-cluster-token=etcd-cluster
networks:
- etcd
volumes:
- etcd2vol:/data.etcd
deploy:
replicas: 1
placement:
constraints: [node.labels.nodename == node2]
etcd3:
image: quay.io/coreos/etcd:latest
hostname: etcd3
command:
- etcd
- --name=etcd3
- --data-dir=/data.etcd
- --advertise-client-urls=http://etcd3:2379
- --listen-client-urls=http://0.0.0.0:2379
- --initial-advertise-peer-urls=http://etcd3:2380
- --listen-peer-urls=http://0.0.0.0:2380
- --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- --initial-cluster-state=new
- --initial-cluster-token=etcd-cluster
networks:
- etcd
volumes:
- etcd3vol:/data.etcd
deploy:
replicas: 1
placement:
constraints: [node.labels.nodename == node3]
volumes:
etcd1vol:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/etcd"
etcd2vol:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/etcd"
etcd3vol:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/etcd"
networks:
etcd:
external: true# docker stack deploy --compose-file 00etcd.yml etcdNach einiger Zeit überprüfen wir, ob der etcd-Cluster gestartet ist:
# docker exec $(docker ps | awk '/etcd/ {print $1}') etcdctl member list
ade526d28b1f92f7: name=etcd1 peerURLs=http://etcd1:2380 clientURLs=http://etcd1:2379 isLeader=false
bd388e7810915853: name=etcd3 peerURLs=http://etcd3:2380 clientURLs=http://etcd3:2379 isLeader=false
d282ac2ce600c1ce: name=etcd2 peerURLs=http://etcd2:2380 clientURLs=http://etcd2:2379 isLeader=true
# docker exec $(docker ps | awk '/etcd/ {print $1}') etcdctl cluster-health
member ade526d28b1f92f7 is healthy: got healthy result from http://etcd1:2379
member bd388e7810915853 is healthy: got healthy result from http://etcd3:2379
member d282ac2ce600c1ce is healthy: got healthy result from http://etcd2:2379
cluster is healthyWir erstellen Verzeichnisse für PostgreSQL; der Befehl wird auf allen Servern ausgeführt:
# mkdir -p /srv/pgsqlAls nächstes erstellen wir eine Datei zur Konfiguration von PostgreSQL:
01pgsql.yml
version: '3.7'
services:
pgsentinel:
image: sorintlab/stolon:master-pg10
command:
- gosu
- stolon
- stolon-sentinel
- --cluster-name=stolon-cluster
- --store-backend=etcdv3
- --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
- --log-level=debug
networks:
- etcd
- pgsql
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 30s
order: stop-first
failure_action: pause
pgkeeper1:
image: sorintlab/stolon:master-pg10
hostname: pgkeeper1
command:
- gosu
- stolon
- stolon-keeper
- --pg-listen-address=pgkeeper1
- --pg-repl-username=replica
- --uid=pgkeeper1
- --pg-su-username=postgres
- --pg-su-passwordfile=/run/secrets/pgsql
- --pg-repl-passwordfile=/run/secrets/pgsql_repl
- --data-dir=/var/lib/postgresql/data
- --cluster-name=stolon-cluster
- --store-backend=etcdv3
- --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
networks:
- etcd
- pgsql
environment:
- PGDATA=/var/lib/postgresql/data
volumes:
- pgkeeper1:/var/lib/postgresql/data
secrets:
- pgsql
- pgsql_repl
deploy:
replicas: 1
placement:
constraints: [node.labels.nodename == node1]
pgkeeper2:
image: sorintlab/stolon:master-pg10
hostname: pgkeeper2
command:
- gosu
- stolon
- stolon-keeper
- --pg-listen-address=pgkeeper2
- --pg-repl-username=replica
- --uid=pgkeeper2
- --pg-su-username=postgres
- --pg-su-passwordfile=/run/secrets/pgsql
- --pg-repl-passwordfile=/run/secrets/pgsql_repl
- --data-dir=/var/lib/postgresql/data
- --cluster-name=stolon-cluster
- --store-backend=etcdv3
- --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
networks:
- etcd
- pgsql
environment:
- PGDATA=/var/lib/postgresql/data
volumes:
- pgkeeper2:/var/lib/postgresql/data
secrets:
- pgsql
- pgsql_repl
deploy:
replicas: 1
placement:
constraints: [node.labels.nodename == node2]
pgkeeper3:
image: sorintlab/stolon:master-pg10
hostname: pgkeeper3
command:
- gosu
- stolon
- stolon-keeper
- --pg-listen-address=pgkeeper3
- --pg-repl-username=replica
- --uid=pgkeeper3
- --pg-su-username=postgres
- --pg-su-passwordfile=/run/secrets/pgsql
- --pg-repl-passwordfile=/run/secrets/pgsql_repl
- --data-dir=/var/lib/postgresql/data
- --cluster-name=stolon-cluster
- --store-backend=etcdv3
- --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
networks:
- etcd
- pgsql
environment:
- PGDATA=/var/lib/postgresql/data
volumes:
- pgkeeper3:/var/lib/postgresql/data
secrets:
- pgsql
- pgsql_repl
deploy:
replicas: 1
placement:
constraints: [node.labels.nodename == node3]
postgresql:
image: sorintlab/stolon:master-pg10
command: gosu stolon stolon-proxy --listen-address 0.0.0.0 --cluster-name stolon-cluster --store-backend=etcdv3 --store-endpoints http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
networks:
- etcd
- pgsql
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 30s
order: stop-first
failure_action: rollback
volumes:
pgkeeper1:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/pgsql"
pgkeeper2:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/pgsql"
pgkeeper3:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/pgsql"
secrets:
pgsql:
file: "/srv/docker/postgres"
pgsql_repl:
file: "/srv/docker/replica"
networks:
etcd:
external: true
pgsql:
external: trueGenerieren von Geheimnissen, Datei anwenden:
# </dev/urandom tr -dc 234567890qwertyuopasdfghjkzxcvbnmQWERTYUPASDFGHKLZXCVBNM | head -c $(((RANDOM%3)+15)) > /srv/docker/replica
# </dev/urandom tr -dc 234567890qwertyuopasdfghjkzxcvbnmQWERTYUPASDFGHKLZXCVBNM | head -c $(((RANDOM%3)+15)) > /srv/docker/postgres
# docker stack deploy --compose-file 01pgsql.yml pgsqlNach einiger Zeit (wir sehen uns die Ausgabe des Befehls an docker service ls, dass alle Dienste hochgefahren sind) initialisieren wir den Postgresql-Cluster:
# docker exec $(docker ps | awk '/pgkeeper/ {print $1}') stolonctl --cluster-name=stolon-cluster --store-backend=etcdv3 --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 initWir überprüfen die Bereitschaft des Postgresql-Clusters:
# docker exec $(docker ps | awk '/pgkeeper/ {print $1}') stolonctl --cluster-name=stolon-cluster --store-backend=etcdv3 --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 status
=== Active sentinels ===
ID LEADER
26baa11d false
74e98768 false
a8cb002b true
=== Active proxies ===
ID
4d233826
9f562f3b
b0c79ff1
=== Keepers ===
UID HEALTHY PG LISTENADDRESS PG HEALTHY PG WANTEDGENERATION PG CURRENTGENERATION
pgkeeper1 true pgkeeper1:5432 true 2 2
pgkeeper2 true pgkeeper2:5432 true 2 2
pgkeeper3 true pgkeeper3:5432 true 3 3
=== Cluster Info ===
Master Keeper: pgkeeper3
===== Keepers/DB tree =====
pgkeeper3 (master)
├─pgkeeper2
└─pgkeeper1
Wir konfigurieren Traefik, um den Zugriff auf die Container von außen zu öffnen:
03traefik.yml
version: '3.7'
services:
traefik:
image: traefik:latest
command: >
--log.level=INFO
--providers.docker=true
--entryPoints.web.address=:80
--providers.providersThrottleDuration=2
--providers.docker.watch=true
--providers.docker.swarmMode=true
--providers.docker.swarmModeRefreshSeconds=15s
--providers.docker.exposedbydefault=false
--accessLog.bufferingSize=0
--api=true
--api.dashboard=true
--api.insecure=true
networks:
- traefik
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
replicas: 3
placement:
constraints:
- node.role == manager
preferences:
- spread: node.id
labels:
- traefik.enable=true
- traefik.http.routers.traefik.rule=Host(`traefik.example.com`)
- traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.docker.network=traefik
networks:
traefik:
external: true# docker stack deploy --compose-file 03traefik.yml traefikWir starten den Redis-Cluster, dazu erstellen wir auf allen Knoten ein Verzeichnis für die Speicherung:
# mkdir -p /srv/redis05redis.yml
version: '3.7'
services:
redis-master:
image: 'bitnami/redis:latest'
networks:
- redis
ports:
- '6379:6379'
environment:
- REDIS_REPLICATION_MODE=master
- REDIS_PASSWORD=xxxxxxxxxxx
deploy:
mode: global
restart_policy:
condition: any
volumes:
- 'redis:/opt/bitnami/redis/etc/'
redis-replica:
image: 'bitnami/redis:latest'
networks:
- redis
ports:
- '6379'
depends_on:
- redis-master
environment:
- REDIS_REPLICATION_MODE=slave
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_MASTER_PASSWORD=xxxxxxxxxxx
- REDIS_PASSWORD=xxxxxxxxxxx
deploy:
mode: replicated
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: any
redis-sentinel:
image: 'bitnami/redis:latest'
networks:
- redis
ports:
- '16379'
depends_on:
- redis-master
- redis-replica
entrypoint: |
bash -c 'bash -s <<EOF
"/bin/bash" -c "cat < /opt/bitnami/redis/etc/sentinel.conf
port 16379
dir /tmp
sentinel monitor master-node redis-master 6379 2
sentinel down-after-milliseconds master-node 5000
sentinel parallel-syncs master-node 1
sentinel failover-timeout master-node 5000
sentinel auth-pass master-node xxxxxxxxxxx
sentinel announce-ip redis-sentinel
sentinel announce-port 16379
EOF"
"/bin/bash" -c "redis-sentinel /opt/bitnami/redis/etc/sentinel.conf"
EOF'
deploy:
mode: global
restart_policy:
condition: any
volumes:
redis:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: "/srv/redis"
networks:
redis:
external: true# docker stack deploy --compose-file 05redis.yml redisWir fügen das Docker-Registry hinzu:
06registry.yml
version: '3.7'
services:
registry:
image: registry:2.6
networks:
- traefik
volumes:
- registry_data:/var/lib/registry
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
restart_policy:
condition: on-failure
labels:
- traefik.enable=true
- traefik.http.routers.registry.rule=Host(`registry.example.com`)
- traefik.http.services.registry.loadbalancer.server.port=5000
- traefik.docker.network=traefik
volumes:
registry_data:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/docker/registry"
networks:
traefik:
external: true# mkdir /srv/docker/registry
# docker stack deploy --compose-file 06registry.yml registryUnd schließlich — GitLab:
08gitlab-runner.yml
version: '3.7'
services:
gitlab:
image: gitlab/gitlab-ce:latest
networks:
- pgsql
- redis
- traefik
- gitlab
ports:
- 22222:22
environment:
GITLAB_OMNIBUS_CONFIG: |
postgresql['enable'] = false
redis['enable'] = false
gitlab_rails['registry_enabled'] = false
gitlab_rails['db_username'] = "gitlab"
gitlab_rails['db_password'] = "XXXXXXXXXXX"
gitlab_rails['db_host'] = "postgresql"
gitlab_rails['db_port'] = "5432"
gitlab_rails['db_database'] = "gitlab"
gitlab_rails['db_adapter'] = 'postgresql'
gitlab_rails['db_encoding'] = 'utf8'
gitlab_rails['redis_host'] = 'redis-master'
gitlab_rails['redis_port'] = '6379'
gitlab_rails['redis_password'] = 'xxxxxxxxxxx'
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.yandex.ru"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "noreply@example.com"
gitlab_rails['smtp_password'] = "xxxxxxxxx"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['gitlab_email_from'] = 'noreply@example.com'
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_tls'] = true
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
external_url 'http://gitlab.example.com/'
gitlab_rails['gitlab_shell_ssh_port'] = 22222
volumes:
- gitlab_conf:/etc/gitlab
- gitlab_logs:/var/log/gitlab
- gitlab_data:/var/opt/gitlab
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == manager
labels:
- traefik.enable=true
- traefik.http.routers.gitlab.rule=Host(`gitlab.example.com`)
- traefik.http.services.gitlab.loadbalancer.server.port=80
- traefik.docker.network=traefik
gitlab-runner:
image: gitlab/gitlab-runner:latest
networks:
- gitlab
volumes:
- gitlab_runner_conf:/etc/gitlab
- /var/run/docker.sock:/var/run/docker.sock
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == manager
volumes:
gitlab_conf:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/docker/gitlab/conf"
gitlab_logs:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/docker/gitlab/logs"
gitlab_data:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/docker/gitlab/data"
gitlab_runner_conf:
driver: local
driver_opts:
type: none
o: bind
device: "/srv/docker/gitlab/runner"
networks:
pgsql:
external: true
redis:
external: true
traefik:
external: true
gitlab:
external: true# mkdir -p /srv/docker/gitlab/conf
# mkdir -p /srv/docker/gitlab/logs
# mkdir -p /srv/docker/gitlab/data
# mkdir -p /srv/docker/gitlab/runner
# docker stack deploy --compose-file 08gitlab-runner.yml gitlabDer aktuelle Status des Clusters und der Dienste:
# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
lef9n3m92buq etcd_etcd1 replicated 1/1 quay.io/coreos/etcd:latest
ij6uyyo792x5 etcd_etcd2 replicated 1/1 quay.io/coreos/etcd:latest
fqttqpjgp6pp etcd_etcd3 replicated 1/1 quay.io/coreos/etcd:latest
hq5iyga28w33 gitlab_gitlab replicated 1/1 gitlab/gitlab-ce:latest *:22222->22/tcp
dt7s6vs0q4qc gitlab_gitlab-runner replicated 1/1 gitlab/gitlab-runner:latest
k7uoezno0h9n pgsql_pgkeeper1 replicated 1/1 sorintlab/stolon:master-pg10
cnrwul4r4nse pgsql_pgkeeper2 replicated 1/1 sorintlab/stolon:master-pg10
frflfnpty7tr pgsql_pgkeeper3 replicated 1/1 sorintlab/stolon:master-pg10
x7pqqchi52kq pgsql_pgsentinel replicated 3/3 sorintlab/stolon:master-pg10
mwu2wl8fti4r pgsql_postgresql replicated 3/3 sorintlab/stolon:master-pg10
9hkbe2vksbzb redis_redis-master global 3/3 bitnami/redis:latest *:6379->6379/tcp
l88zn8cla7dc redis_redis-replica replicated 3/3 bitnami/redis:latest *:30003->6379/tcp
1utp309xfmsy redis_redis-sentinel global 3/3 bitnami/redis:latest *:30002->16379/tcp
oteb824ylhyp registry_registry replicated 1/1 registry:2.6
qovrah8nzzu8 traefik_traefik replicated 3/3 traefik:latest *:80->80/tcp, *:443->443/tcpWas kann noch verbessert werden? Stellen Sie unbedingt die Arbeit der Container in Traefik über https ein, fügen Sie eine tls-Verschlüsselung für Postgresql und Redis hinzu. Aber insgesamt kann es bereits als PoC an die Entwickler übergeben werden. Schauen wir uns jetzt die Alternativen zu Docker an.
Podman
Eine weitere recht bekannte Engine zum Starten von Containern, gruppiert in Pods (Pods, Gruppen von gemeinsam bereitgestellten Containern). Im Gegensatz zu Docker benötigt es keinen Dienst zum Starten von Containern, die gesamte Arbeit erfolgt über die Bibliothek libpod. Sie ist ebenfalls in Go geschrieben und benötigt zur Ausführung von Containern eine OCI-kompatible Runtime, beispielsweise runC.

Die Arbeit mit Podman ähnelt weitgehend der mit Docker, bis hin zu der Möglichkeit, folgendes zu tun (wie viele, einschließlich des Autors dieses Artikels, berichteten):
$ alias docker=podmanund man kann weiterarbeiten. Insgesamt ist die Situation mit Podman sehr interessant, denn während frühere Versionen von Kubernetes mit Docker arbeiteten, entwickelt sich seit etwa 2015, nach der Standardisierung der Containerwelt (OCI - Open Container Initiative) und der Trennung von Docker in containerd und runC, eine Alternative zu Docker für den Einsatz in Kubernetes: CRI-O. In diesem Sinne ist Podman eine Alternative zu Docker, die nach den Prinzipien von Kubernetes aufgebaut ist, einschließlich der Gruppierung von Containern, aber das Hauptziel des Projekts besteht darin, Container im Docker-Stil ohne zusätzliche Dienste zu starten. Aus verständlichen Gründen gibt es keinen Swarm-Modus, da die Entwickler deutlich darauf hinweisen, dass, wenn ein Cluster benötigt wird, Kubernetes verwendet werden sollte.
Installation
Für die Installation auf Centos 7 reicht es aus, das Extras-Repository zu aktivieren und danach alles mit dem Befehl zu installieren:
# yum -y install podmanWeitere Möglichkeiten
Podman kann Einheiten für systemd generieren, was das Problem der Ausführung von Containern nach einem Serverneustart löst. Zusätzlich wurde eine ordnungsgemäße Funktion von systemd als pid 1 im Container bestätigt. Zum Erstellen von Containern gibt es ein separates Werkzeug namens buildah, es gibt auch Drittanbieter-Tools - Alternativen zu docker-compose, die unter anderem Konfigurationsdateien generieren, die mit Kubernetes kompatibel sind, sodass der Übergang von Podman zu Kubernetes so reibungslos wie möglich gestaltet ist.
Arbeiten mit Podman
Da es keinen Swarm-Modus gibt (ein Wechsel zu Kubernetes wird vorausgesetzt, wenn ein Cluster benötigt wird) - werden wir separate Container erstellen.
Wir installieren podman-compose:
# yum -y install python3-pip
# pip3 install podman-composeDie resultierende Konfigurationsdatei für podman unterscheidet sich ein wenig, so musste beispielsweise eine separate Sektion für Volumes direkt in die Sektion mit den Services verschoben werden.
gitlab-podman.yml
version: '3.7'
services:
gitlab:
image: gitlab/gitlab-ce:latest
hostname: gitlab.example.com
restart: unless-stopped
environment:
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['gitlab_shell_ssh_port'] = 22222
ports:
- "80:80"
- "22222:22"
volumes:
- /srv/podman/gitlab/conf:/etc/gitlab
- /srv/podman/gitlab/data:/var/opt/gitlab
- /srv/podman/gitlab/logs:/var/log/gitlab
networks:
- gitlab
gitlab-runner:
image: gitlab/gitlab-runner:alpine
restart: unless-stopped
depends_on:
- gitlab
volumes:
- /srv/podman/gitlab/runner:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
networks:
- gitlab
networks:
gitlab:# podman-compose -f gitlab-runner.yml -d upErgebnis:
# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
da53da946c01 docker.io/gitlab/gitlab-runner:alpine run --user=gitlab... About a minute ago Up About a minute ago 0.0.0.0:22222->22/tcp, 0.0.0.0:80->80/tcp root_gitlab-runner_1
781c0103c94a docker.io/gitlab/gitlab-ce:latest /assets/wrapper About a minute ago Up About a minute ago 0.0.0.0:22222->22/tcp, 0.0.0.0:80->80/tcp root_gitlab_1Lass uns sehen, was es für systemd und kubernetes generieren wird. Dafür müssen wir den Namen oder die ID des Pods herausfinden:
# podman pod ls
POD ID NAME STATUS CREATED # OF CONTAINERS INFRA ID
71fc2b2a5c63 root Running 11 minutes ago 3 db40ab8bf84bKubernetes:
# podman generate kube 71fc2b2a5c63
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.6.4
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2020-07-29T19:22:40Z"
labels:
app: root
name: root
spec:
containers:
- command:
- /assets/wrapper
env:
- name: PATH
value: /opt/gitlab/embedded/bin:/opt/gitlab/bin:/assets:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: HOSTNAME
value: gitlab.example.com
- name: container
value: podman
- name: GITLAB_OMNIBUS_CONFIG
value: |
gitlab_rails['gitlab_shell_ssh_port'] = 22222
- name: LANG
value: C.UTF-8
image: docker.io/gitlab/gitlab-ce:latest
name: rootgitlab1
ports:
- containerPort: 22
hostPort: 22222
protocol: TCP
- containerPort: 80
hostPort: 80
protocol: TCP
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
volumeMounts:
- mountPath: /var/opt/gitlab
name: srv-podman-gitlab-data
- mountPath: /var/log/gitlab
name: srv-podman-gitlab-logs
- mountPath: /etc/gitlab
name: srv-podman-gitlab-conf
workingDir: /
- command:
- run
- --user=gitlab-runner
- --working-directory=/home/gitlab-runner
env:
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: HOSTNAME
- name: container
value: podman
image: docker.io/gitlab/gitlab-runner:alpine
name: rootgitlab-runner1
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
volumeMounts:
- mountPath: /etc/gitlab-runner
name: srv-podman-gitlab-runner
- mountPath: /var/run/docker.sock
name: var-run-docker.sock
workingDir: /
volumes:
- hostPath:
path: /srv/podman/gitlab/runner
type: Directory
name: srv-podman-gitlab-runner
- hostPath:
path: /var/run/docker.sock
type: File
name: var-run-docker.sock
- hostPath:
path: /srv/podman/gitlab/data
type: Directory
name: srv-podman-gitlab-data
- hostPath:
path: /srv/podman/gitlab/logs
type: Directory
name: srv-podman-gitlab-logs
- hostPath:
path: /srv/podman/gitlab/conf
type: Directory
name: srv-podman-gitlab-conf
status: {}Systemd:
# podman generate systemd 71fc2b2a5c63
# pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
# autogenerated by Podman 1.6.4
# Thu Jul 29 15:23:28 EDT 2020
[Unit]
Description=Podman pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
Documentation=man:podman-generate-systemd(1)
Requires=container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
Before=container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start db40ab8bf84bf35141159c26cb6e256b889c7a98c0418eee3c4aa683c14fccaa
ExecStop=/usr/bin/podman stop -t 10 db40ab8bf84bf35141159c26cb6e256b889c7a98c0418eee3c4aa683c14fccaa
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/db40ab8bf84bf35141159c26cb6e256b889c7a98c0418eee3c4aa683c14fccaa/userdata/conmon.pid
[Install]
WantedBy=multi-user.target
# container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
# autogenerated by Podman 1.6.4
# Thu Jul 29 15:23:28 EDT 2020
[Unit]
Description=Podman container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
Documentation=man:podman-generate-systemd(1)
RefuseManualStart=yes
RefuseManualStop=yes
BindsTo=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
After=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864
ExecStop=/usr/bin/podman stop -t 10 da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864/userdata/conmon.pid
[Install]
WantedBy=multi-user.target
# container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service
# autogenerated by Podman 1.6.4
# Thu Jul 29 15:23:28 EDT 2020
[Unit]
Description=Podman container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service
Documentation=man:podman-generate-systemd(1)
RefuseManualStart=yes
RefuseManualStop=yes
BindsTo=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
After=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start 781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3
ExecStop=/usr/bin/podman stop -t 10 781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3/userdata/conmon.pid
[Install]
WantedBy=multi-user.targetLeider macht die generierte Einheit für systemd außer dem Starten von Containern nichts weiter (zum Beispiel keine Bereinigung alter Container bei einem Neustart eines solchen Dienstes), daher müssen solche Dinge manuell hinzugefügt werden.
Prinzipiell ist Podman ausreichend, um zu testen, was Container sind, alte Konfigurationen für docker-compose zu migrieren, und danach auf Kubernetes umzusteigen, falls man einen Cluster benötigt, oder um eine einfachere Alternative zu Docker zu erhalten.
rkt
Projekt vor ungefähr einem halben Jahr, nachdem es von RedHat gekauft wurde, weshalb ich nicht näher darauf eingehen werde. Insgesamt machte es einen ganz guten Eindruck, erschien aber im Vergleich zu Docker und insbesondere Podman wie ein Kombinat. Es gab auch eine Distribution namens CoreOS, die auf rkt basierte (obwohl sie ursprünglich Docker hatten), deren Unterstützung jedoch ebenfalls nach dem Kauf durch RedHat endete.
Plash
Weitere , dessen Autor einfach Container sammeln und starten wollte. Judging by the documentation and the code — der Autor hielt sich nicht an Standards, sondern entschloss sich einfach, seine eigene Implementierung zu schreiben, was er grundsätzlich auch tat.
Das DBMS Tarantool ist ein attraktives, zukunftsträchtiges Produkt zur Erstellung von hochbelasteten Anwendungen.
Die Situation mit Kubernetes gestaltet sich recht interessant: Einerseits kann man mit Docker einen Cluster (im Swarm-Modus) aufbauen, mit dem sogar produktive Umgebungen für Kunden gestartet werden können. Dies ist besonders relevant für kleine Teams (3-5 Personen) oder bei einer geringen Gesamtlast, oder auch wenn es keine Lust gibt, sich mit den Feinheiten der Kubernetes-Konfiguration, auch für hohe Lasten, auseinanderzusetzen.
Podman bietet keine vollständige Kompatibilität, hat jedoch einen wichtigen Vorteil – die Kompatibilität mit Kubernetes, einschließlich zusätzlicher Tools (buildah und andere). Daher werde ich bei der Auswahl des Werkzeugs so verfahren: für kleine Teams oder bei begrenztem Budget – Docker (mit möglichem Swarm-Modus), für die persönliche Entwicklung auf localhost – Podman mit Freunden, und für alle anderen – Kubernetes.
Ich bin mir nicht sicher, ob sich die Situation mit Docker in Zukunft ändern wird; schließlich sind sie Pioniere und standardisieren sich Schritt für Schritt. Dennoch hat Podman mit all seinen Nachteilen (nur auf Linux lauffähig, keine Clusterbildung, Erstellung und andere Aktionen – mit externen Lösungen) eine klarere Zukunft, weshalb ich alle Interessierten einlade, diese Schlussfolgerungen in den Kommentaren zu besprechen.
P.S. Am 3. August starten wir „“, wo Sie mehr über seine Funktionsweise erfahren können. Wir werden alle seine Werkzeuge durchgehen: von den grundlegenden Abstraktionen bis hin zu Netzwerkeinstellungen, Feinheiten der Arbeit mit verschiedenen Betriebssystemen und Programmiersprachen. Sie werden mit der Technologie vertraut gemacht und verstehen, wo und wie man Docker am besten einsetzt. Außerdem teilen wir Best Practices.
Der Preis für die Vorbestellung bis zur Veröffentlichung: 5000 RUR. Das Programm „Videokurs über Docker“ kann eingesehen werden .
Quelle: habr.com
