Kurzfassung: Ein Überblicksartikel und Leitfaden, der verschiedene Umgebungen zum Ausführen von Anwendungen in Containern vergleicht. Es werden die Funktionen von Docker und anderen ähnlichen Systemen behandelt.

Ein bisschen Geschichte, woher alles stammt
Geschichte
Die erste allgemein bekannte Methode zur Isolierung von Anwendungen ist chroot. Der gleichnamige Systemaufruf ermöglicht es, das Wurzelverzeichnis zu ändern – so erhält das aufrufende Programm nur Zugriff auf die Dateien innerhalb dieses Verzeichnisses. Hat das Programm innerhalb von chroot Superuser-Rechte, könnte es theoretisch "entkommen" und auf das zugrunde liegende Betriebssystem zugreifen. Darüber hinaus werden neben dem Ändern des Wurzelverzeichnisses keine weiteren Ressourcen (Arbeitsspeicher, Prozessor) eingeschränkt, und der Netzwerkzugang bleibt ebenfalls unbeschränkt.
Eine weitere Methode besteht darin, ein vollwertiges Betriebssystem innerhalb eines Containers zu starten, wobei die Mechanismen des Betriebssystemkerns genutzt werden. In verschiedenen Betriebssystemen wird diese Methode unterschiedlich bezeichnet, doch das Prinzip bleibt das gleiche: mehrere unabhängige Betriebssysteme zu betreiben, die alle denselben Kern nutzen, auf dem auch das Hauptbetriebssystem läuft. Dazu gehören FreeBSD Jails, Solaris Zones, OpenVZ und LXC für Linux. Hierbei wird nicht nur der Speicherplatz isoliert, sondern auch andere Ressourcen, insbesondere kann jeder Container Einschränkungen in Bezug auf die CPU-Zeit, den Arbeitsspeicher und die Netzbandbreite haben. Im Vergleich zu chroot ist es schwieriger, aus einem Container auszubrechen, da der Superuser im Container nur Zugriff auf die Inhalte des Containers hat. Allerdings gibt es, aufgrund der Notwendigkeit, das Betriebssystem im Container auf dem neuesten Stand zu halten und aufgrund der Verwendung älterer Kernversionen (was für Linux relevant ist, in geringerem Maße für FreeBSD), eine nicht geringe Wahrscheinlichkeit, dass die Isolierung des Kernels durchbrochen wird und Zugang zum Hauptbetriebssystem erlangt wird.
Anstatt ein vollwertiges Betriebssystem in einem Container (mit Init-System, Paketmanager usw.) zu starten, können Sie direkt Anwendungen ausführen, solange die notwendigen Voraussetzungen gegeben sind (wie die erforderlichen Bibliotheken und Dateien). Diese Idee bildet die Grundlage für die Container-Virtualisierung von Anwendungen, deren bekanntestes und popularstes Beispiel Docker ist. Im Vergleich zu früheren Systemen ermöglichen die flexibleren Isolationsmechanismen in Kombination mit der integrierten Unterstützung für virtuelle Netzwerke zwischen Containern und der Überwachung des Anwendungszustands innerhalb des Containers die Bildung einer einheitlichen Umgebung aus vielen physischen Servern für das Ausführen von Containern – ohne manuelle 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 Aufs und andere ähnliche Technologien zur Einsparung von Speicherplatz.

Quelle: wikimedia
Architektur von
Bis zur Version 1.11 arbeitete Docker als ein einziger Dienst, der alle Operationen mit Containern ausführte: das Herunterladen von Images für Container, das Starten von Containern und die Verarbeitung von API-Anfragen. Ab Version 1.11 wurde Docker in mehrere Teile untergliedert, die miteinander interagieren: containerd für die Verwaltung des gesamten Lebenszyklus von Containern (Zuteilung von Speicherplatz, Herunterladen von Images, Netzwerkbetrieb, Start, Installation und Überwachung des Zustands von Containern) und runC, eine Laufzeitumgebung für Container, die auf cgroups und anderen Funktionen des Linux-Kernels basiert. Der Service docker bleibt bestehen, dient jedoch jetzt nur noch zur Verarbeitung von API-Anfragen, die an containerd weitergeleitet werden.

Installation und Konfiguration
Mein bevorzugter Installationsweg für Docker ist docker-machine, das nicht nur die Installation und Konfiguration von Docker auf Remote-Servern (einschließlich verschiedener Clouds) ermöglicht, sondern auch die Arbeit mit Dateisystemen von Remote-Servern unterstützt und verschiedene Befehle ausführen kann.
Seit 2018 entwickelt sich das Projekt jedoch fast nicht mehr weiter, daher werden wir die Installation auf die für die meisten Linux-Distributionen übliche Weise durchführen – durch Hinzufügen des Repositories und die Installation der benötigten Pakete.
Diese Methode wird auch bei automatisierten Installationen verwendet, beispielsweise mit Ansible oder ähnlichen Systemen, aber in diesem Artikel werde ich das nicht behandeln.
Die Installation erfolgt auf CentOS 7; als Server werde ich eine virtuelle Maschine verwenden. Um die Installation durchzuführen, führen Sie einfach die folgenden 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 müssen Sie den Dienst starten und ihn für den automatischen Start einrichten:
# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanentZusätzlich können Sie eine Docker-Gruppe erstellen, deren Benutzer ohne sudo mit Docker arbeiten können. Auch das Logging kann konfiguriert werden, der Zugang zur API von außen sollte aktiviert werden, und der Firewall sollte genauer eingestellt werden (alles, was nicht erlaubt ist, ist verboten; in den obigen und nachfolgenden Beispielen habe ich das der Einfachheit halber ausgelassen), aber ich werde hier nicht näher darauf eingehen.
Weitere Möglichkeiten
Neben der bereits genannten Docker-Maschine gibt es auch ein Docker-Registry, ein Tool zur Speicherung von Images für Container, sowie Docker Compose, ein Werkzeug zur Automatisierung der Bereitstellung von Anwendungen in Containern. Hierbei kommen YAML-Dateien zur Erstellung und Konfiguration von Containern und anderen verwandten Elementen (wie Netzwerken und persistente Dateisysteme zur Datenspeicherung) zum Einsatz.
Damit lassen sich auch CI/CD-Pipelines organisieren. Eine weitere interessante Funktion ist der Betrieb im Cluster-Modus, bekannt als Swarm-Modus (vor Version 1.12 als Docker Swarm bezeichnet). Dies ermöglicht die Bildung einer einheitlichen Infrastruktur aus mehreren Servern zur Ausführung von Containern. Es gibt Unterstützung für virtuelle Netzwerke über alle Server hinweg, einen integrierten Lastenausgleich sowie die Unterstützung für Geheimnisse in Containern.
Modifizierte YAML-Dateien von Docker Compose können für diese Cluster verwendet werden und automatisieren vollständig den Betrieb kleiner und mittlerer Cluster für verschiedene Zwecke. Für große Cluster ist es besser, Kubernetes zu nutzen, da die Wartungskosten im Swarm-Modus die von Kubernetes übersteigen können. Neben runC kann auch beispielsweise
Arbeit mit Docker
Nach der Installation und Konfiguration werden wir versuchen, einen Cluster aufzubauen, in dem wir GitLab und eine Docker-Registry für das Entwicklerteam bereitstellen. Ich werde drei virtuelle Maschinen als Server nutzen, auf denen ich zusätzlich ein verteiltes Dateisystem GlusterFS bereitstelle, welches ich als Speicher für Docker-Volumes verwenden werde, zum Beispiel für die Bereitstellung einer ausfallsicheren Version der Docker-Registry. Die wichtigsten Komponenten für den Start sind: Docker Registry, PostgreSQL, Redis, GitLab mit Unterstützung für GitLab Runner in Swarm. PostgreSQL werden wir mit Clusterbildung betreiben, deshalb sollte GlusterFS nicht für die Datenspeicherung von PostgreSQL verwendet werden. Andere kritische Daten werden auf GlusterFS gespeichert.
Um GlusterFS auf allen Servern (benannt node1, node2, node3) zu installieren, müssen die Pakete installiert, die Firewall aktiviert und die erforderlichen 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 sollte die Konfiguration von GlusterFS von einem Knoten aus fortgesetzt werden, zum Beispiel von 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 gemountet werden (der Befehl muss auf allen Servern ausgeführt werden):
# mount /srv/dockerDie Konfiguration des Swarm-Modus erfolgt auf einem der Server, der als Leader fungiert; die anderen müssen dem Cluster beitreten. Daher muss das Ergebnis des Befehls auf dem ersten Server kopiert und auf den anderen ausgeführt werden.
Die anfängliche Konfiguration des Clusters führe ich 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 Konfiguration der Dienste. Die Befehle zum Ausführen werden von node1 gestartet, es sei denn, es wird etwas anderes 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 erforderlich, 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 node3Erstellen Sie nun die Verzeichnisse für die Speicherung von Daten für etcd, einem KV-Speicher, der für Traefik und Stolon benötigt wird. Ähnlich wie bei PostgreSQL werden dies an die Server gebundene Container sein, daher führen wir diesen Befehl auf allen Servern aus:
# mkdir -p /srv/etcdErstellen Sie anschließend die Datei zur Konfiguration von etcd und wenden Sie 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 einer Weile überprüfen wir, dass das etcd-Cluster gestartet wurde:
# 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 die Verzeichnisse für PostgreSQL, den Befehl führen wir auf allen Servern aus:
# mkdir -p /srv/pgsqlAls Nächstes erstellen wir die 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: trueWir generieren Geheimnisse, indem wir die 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 einer Weile (sehen wir uns die Ausgabe des Befehls docker service ls, um zu überprüfen, ob alle Dienste gestartet wurden) 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 ermöglichen:
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 zum Speichern:
# 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 redisDocker-Registry hinzufügen:
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 gitlabStatus 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 Funktionalität von Containern über HTTPS in Traefik ein und fügen Sie TLS-Verschlüsselung für PostgreSQL und Redis hinzu. Aber insgesamt kann es bereits als Proof of Concept an die Entwickler übergeben werden. Lassen Sie uns nun die Alternativen zu Docker betrachten.
Podman
Ein weiterer recht bekannter Engine zum Starten von Containern, gruppiert in Pods (Pod-Gruppen, in denen Container gemeinsam bereitgestellt werden). Im Gegensatz zu Docker benötigt es keinen Dienst zum Starten von Containern, alle Arbeiten erfolgen über die Bibliothek libpod. Es ist ebenfalls in Go geschrieben und benötigt eine OCI-kompatible Laufzeitumgebung zum Ausführen von Containern, beispielsweise runC.

Die Arbeit mit Podman ähnelt im Großen und Ganzen der mit Docker, bis hin zu der Möglichkeit, dies so zu tun (wie von vielen ausprobiert und berichtet, einschließlich des Autors dieses Artikels):
$ alias docker=podmanEs ist spannend zu sehen, wie sich die Situation mit Podman entwickelt. Während frühere Kubernetes-Versionen mit Docker arbeiteten, begann ab 2015, nach der Standardisierung der Container-Welt (OCI – Open Container Initiative) und der Aufteilung von Docker in containerd und runC, eine Alternative zu Docker zur Einführung in Kubernetes: CRI-O. Podman bietet in diesem Kontext eine Docker-Alternative, die nach den Prinzipien von Kubernetes aufgebaut ist, einschließlich der Gruppierung von Containern. Das Hauptziel des Projekts ist es, Container im Docker-Stil ohne zusätzliche Dienste zu starten. Aus offensichtlichen Gründen gibt es keinen Swarm-Modus, da die Entwickler klar kommunizieren, dass man Kubernetes nutzen sollte, wenn ein Cluster benötigt wird.
Installation von
Um Podman auf CentOS 7 zu installieren, genügt es, das Extras-Repository zu aktivieren und dann alles mit dem folgenden Befehl zu installieren:
# yum -y install podmanWeitere Möglichkeiten
Podman kann Einheiten für systemd erstellen und löst somit das Problem, Container nach einem Serverneustart zu starten. Es wurde zusätzlich bestätigt, dass systemd als PID 1 in einem Container korrekt funktioniert. Für das Erstellen von Containern wird ein separates Werkzeug namens buildah verwendet, und es gibt auch Drittanbieter-Tools — Alternativen zu docker-compose, die unter anderem Konfigurationsdateien generieren, die mit Kubernetes kompatibel sind, wodurch der Übergang von Podman zu Kubernetes so einfach wie möglich gestaltet wird.
Arbeiten mit Podman
Da es keinen Swarm-Modus gibt (man geht von einem Übergang zu Kubernetes aus, falls ein Cluster benötigt wird) — werden wir separate Container erstellen.
Installieren Sie podman-compose:
# yum -y install python3-pip
# pip3 install podman-composeDie resultierende Konfigurationsdatei für Podman unterscheidet sich etwas, da beispielsweise eine separate Sektion für Volumes direkt in die Sektion mit den Diensten verschoben werden musste.
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_1Lassen Sie uns ansehen, was es für systemd und kubernetes generiert; 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 der Container nichts weiter (zum Beispiel keine Bereinigung alter Container beim Neustart eines solchen Dienstes); deshalb müssen solche Dinge selbst hinzugefügt werden.
Im Grunde ist Podman ausreichend, um auszuprobieren, was Container sind, alte Konfigurationen für docker-compose zu migrieren, und danach, falls nötig, in Richtung Kubernetes zu wechseln, um einen Cluster zu nutzen oder eine einfachere Alternative zu Docker zu erhalten.
rkt
Projekt Vor etwa einem halben Jahr wurde es von RedHat übernommen, daher werde ich nicht näher darauf eingehen. Insgesamt hinterließ es einen recht positiven Eindruck, allerdings wirkt es im Vergleich zu Docker und erst recht zu Podman wie ein Multifunktionsgerät. Es gab auch die Distribution CoreOS, die auf rkt basierte (obwohl sie ursprünglich Docker verwendet hatten), deren Unterstützung jedoch ebenfalls nach dem Kauf durch RedHat endete.
Plash
Weitere , dessen Autor einfach Container erstellen und starten wollte. Laut der Dokumentation und dem Code – hat der Autor keine Standards eingehalten, sondern sich entschieden, eine eigene Implementierung zu schreiben, was er im Grunde auch getan hat.
Fazit
Die Situation mit vorhandenem Kubernetes ist sehr interessant: Einerseits kann mit Docker ein Cluster (im Swarm-Modus) gebildet werden, 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 geringer Gesamtlastauslastung oder wenn kein Wunsch besteht, sich mit den Feinheiten der Kubernetes-Konfiguration, insbesondere für hohe Lasten, auseinanderzusetzen.
Podman bietet zwar keine vollständige Kompatibilität, hat jedoch einen entscheidenden Vorteil: die Kompatibilität mit Kubernetes, einschließlich zusätzlicher Werkzeuge (wie buildah und andere). Daher gehe ich bei der Auswahl des Werkzeugs wie folgt vor: für kleine Teams oder bei begrenztem Budget – Docker (eventuell im Swarm-Modus), für die persönliche Entwicklung auf dem eigenen localhost – Podman mit Kollegen, und für alle anderen – Kubernetes.
Ich bin mir nicht sicher, ob sich die Situation mit Docker in Zukunft ändern wird. Immerhin sind sie die Pioniere und standardisieren sich Schritt für Schritt. Dennoch scheint die Zukunft von Podman, trotz seiner Mängel (es läuft nur auf Linux, keine Clusterbildung, und die Erstellung sowie andere Aktionen erfordern externe Lösungen), klarer zu sein. Daher lade ich alle Interessierten ein, diese Schlussfolgerungen in den Kommentaren zu diskutieren.
P.S. Am 3. August starten wir „“, in dem Sie mehr über dessen Funktionalität erfahren können. Wir werden alle Werkzeuge durchgehen: von den grundlegenden Abstraktionen bis zu Netzwerkkonfigurationen, den Feinheiten der Arbeit mit verschiedenen Betriebssystemen und Programmiersprachen. Sie werden mit der Technologie vertraut und lernen, wo und wie Sie Docker am besten einsetzen können. Zudem teilen wir Best Practices und Fallstudien.
Vorbestellungskosten bis zur Veröffentlichung: 5000 R. Informationen zum Kurs "Videokurs zu Docker" finden Sie .
Quelle: habr.com
