TL;DR: Ein Übersichtsartikel – eine Anleitung zum Vergleich von Umgebungen für die Ausführung von Anwendungen in Containern. Dabei werden die Möglichkeiten von Docker und anderen ähnlichen Systemen berücksichtigt.
Eine kleine Geschichte darüber, woher alles kam
Geschichte
Die erste bekannte Methode zum Isolieren einer Anwendung ist Chroot. Der gleichnamige Systemaufruf ermöglicht eine Änderung des Stammverzeichnisses und ermöglicht so den Zugriff auf das Programm, das ihn aufgerufen hat, und nur auf Dateien in diesem Verzeichnis. Wenn dem Programm jedoch Superuser-Rechte gewährt werden, kann es möglicherweise aus der Chroot-Umgebung „entkommen“ und Zugriff auf das Hauptbetriebssystem erhalten. Zusätzlich zur Änderung des Stammverzeichnisses sind auch andere Ressourcen (RAM, Prozessor) sowie der Zugriff auf das Netzwerk nicht eingeschränkt.
Die nächste Möglichkeit besteht darin, ein vollwertiges Betriebssystem innerhalb des Containers zu starten und dabei die Mechanismen des Betriebssystemkernels zu nutzen. Diese Methode wird in verschiedenen Betriebssystemen unterschiedlich aufgerufen, aber das Wesentliche ist dasselbe – die Ausführung mehrerer unabhängiger Betriebssysteme, von denen jedes auf demselben Kernel läuft, auf dem das Hauptbetriebssystem ausgeführt wird. Dazu gehören FreeBSD Jails, Solaris Zones, OpenVZ und LXC für Linux. Die Isolierung erfolgt nicht nur für den Speicherplatz, sondern auch für andere Ressourcen. Insbesondere kann jeder Container Einschränkungen hinsichtlich Prozessorzeit, RAM und Netzwerkbandbreite unterliegen. Im Vergleich zu Chroot ist das Verlassen des Containers schwieriger, da der Superuser im Container jedoch aufgrund der Notwendigkeit, das Betriebssystem im Container auf dem neuesten Stand zu halten und den alten Kernel zu verwenden, nur Zugriff auf das Innere des Containers hat Versionen (relevant für Linux, in geringerem Maße FreeBSD) besteht eine Wahrscheinlichkeit ungleich Null, das Kernel-Isolationssystem zu „durchbrechen“ und Zugriff auf das Hauptbetriebssystem zu erhalten.
Anstatt ein vollwertiges Betriebssystem in einem Container zu starten (mit einem Initialisierungssystem, einem Paketmanager usw.), können Anwendungen sofort gestartet werden. Die Hauptsache besteht darin, Anwendungen diese Möglichkeit zu bieten (das Vorhandensein der erforderlichen Bibliotheken usw.). andere Dateien). Diese Idee diente als Grundlage für die containerisierte Anwendungsvirtualisierung, deren prominentester und bekanntester Vertreter Docker ist. Im Vergleich zu früheren Systemen führten flexiblere Isolationsmechanismen zusammen mit der integrierten Unterstützung für virtuelle Netzwerke zwischen Containern und Anwendungsstatus innerhalb eines Containers dazu, dass eine einzige ganzheitliche Umgebung aus einer großen Anzahl physischer Server zum Ausführen von Containern aufgebaut werden konnte – ohne die Notwendigkeit einer manuellen Ressourcenverwaltung.
Docker
Docker ist die bekannteste Anwendungscontainerisierungssoftware. Es ist in der Go-Sprache geschrieben und nutzt die regulären Funktionen des Linux-Kernels – cgroups, Namespaces, Funktionen usw. – sowie Aufs-Dateisysteme und ähnliches, um Speicherplatz zu sparen.
Quelle: Wikimedia
Architektur
Vor Version 1.11 arbeitete Docker als einzelner Dienst, der alle Vorgänge mit Containern ausführte: Bilder für Container herunterladen, Container starten, API-Anfragen verarbeiten. Ab Version 1.11 war Docker in mehrere Teile unterteilt, die miteinander interagieren: Containerd, zur Verarbeitung des gesamten Lebenszyklus von Containern (Zuweisen von Speicherplatz, Herunterladen von Bildern, Arbeiten mit dem Netzwerk, Starten, Installieren und Überwachen des Status von Containern). und runC, die Container-Ausführungsumgebung, basierend auf der Verwendung von cgroups und anderen Funktionen des Linux-Kernels. Der Docker-Dienst selbst bleibt bestehen, dient jedoch nur noch der Verarbeitung von in Containerd übersetzten API-Anfragen.
Installation und Konfiguration
Meine bevorzugte Art, Docker zu installieren, ist Docker-Machine, die neben der direkten Installation und Konfiguration von Docker auf Remote-Servern (einschließlich verschiedener Clouds) auch die Arbeit mit Dateisystemen von Remote-Servern ermöglicht und auch die Ausführung verschiedener Befehle ermöglicht.
Da das Projekt jedoch seit 2018 kaum weiterentwickelt wurde, werden wir es auf die für die meisten Linux-Distributionen übliche Weise installieren – durch das Hinzufügen eines Repositorys und die Installation der notwendigen Pakete.
Diese Methode wird auch für die automatisierte Installation verwendet, beispielsweise mit Ansible oder anderen ähnlichen Systemen, ich werde sie in diesem Artikel jedoch nicht berücksichtigen.
Die Installation erfolgt auf Centos 7. Ich werde eine virtuelle Maschine als Server verwenden. Zur Installation 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.io
Nach der Installation müssen Sie den Dienst starten und in den Startmodus versetzen:
# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanent
Darüber hinaus können Sie eine Docker-Gruppe erstellen, deren Benutzer mit Docker ohne Sudo arbeiten, die Protokollierung einrichten, den Zugriff auf die API von außen ermöglichen und nicht vergessen können, die Firewall genauer zu konfigurieren (alles, was nicht erlaubt ist). ist in den Beispielen oben und unten verboten (ich habe dies der Einfachheit und Klarheit halber weggelassen), aber ich werde hier nicht näher darauf eingehen.
Andere Eigenschaften
Neben der oben genannten Docker-Maschine gibt es auch Docker Registry, ein Tool zum Speichern von Bildern für Container, sowie Docker Compose, ein Tool zur Automatisierung der Bereitstellung von Anwendungen in Containern. YAML-Dateien werden zum Erstellen und Konfigurieren von Containern verwendet und andere verwandte Dinge (z. B. Netzwerke, persistente Dateisysteme zur Speicherung von Daten).
Es kann auch zur Organisation von Förderbändern für CICD verwendet werden. Ein weiteres interessantes Feature ist das Arbeiten im Cluster-Modus, dem sogenannten Swarm-Modus (vor Version 1.12 hieß er Docker Swarm), der es Ihnen ermöglicht, eine einzige Infrastruktur aus mehreren Servern für den Betrieb von Containern zusammenzustellen. Es gibt Unterstützung für ein virtuelles Netzwerk auf allen Servern, es gibt einen integrierten Load Balancer sowie Unterstützung für Geheimnisse für Container.
Die YAML-Dateien von Docker Compose können mit geringfügigen Modifikationen für solche Cluster verwendet werden, wodurch die Wartung kleiner und mittlerer Cluster für verschiedene Zwecke vollständig automatisiert wird. Für große Cluster ist Kubernetes vorzuziehen, da die Wartungskosten für den Schwarmmodus die von Kubernetes übersteigen können. Zusätzlich zu runC können Sie beispielsweise als Ausführungsumgebung für Container installieren
Arbeiten mit Docker
Nach der Installation und Konfiguration werden wir versuchen, einen Cluster aufzubauen, in dem wir GitLab und Docker Registry für das Entwicklungsteam bereitstellen. Als Server werde ich drei virtuelle Maschinen verwenden, auf denen ich zusätzlich das verteilte GlusterFS-FS bereitstellen werde, ich werde es als Docker-Volumes-Speicher verwenden, um beispielsweise eine ausfallsichere Version der Docker-Registrierung auszuführen. Wichtige auszuführende Komponenten: Docker Registry, Postgresql, Redis, GitLab mit Unterstützung für GitLab Runner zusätzlich zu Swarm. Postgresql wird mit Clustering gestartet
Um GlusterFS auf allen Servern bereitzustellen (sie heißen Knoten1, Knoten2, Knoten3), müssen Sie Pakete installieren, die Firewall aktivieren und die erforderlichen Verzeichnisse erstellen:
# 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/fstab
Nach der Installation muss die Arbeit an der Konfiguration von GlusterFS von einem Knoten aus fortgesetzt werden, zum Beispiel Knoten1:
# 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 docker
Dann müssen Sie das resultierende Volume mounten (der Befehl muss auf allen Servern ausgeführt werden):
# mount /srv/docker
Der Schwarmmodus ist auf einem der Server konfiguriert, der der Anführer sein wird, der Rest muss dem Cluster beitreten, sodass das Ergebnis der Ausführung des Befehls auf dem ersten Server kopiert und auf den anderen ausgeführt werden muss.
Beim ersten Cluster-Setup führe ich den Befehl auf Knoten1 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 manager
Wir kopieren das Ergebnis des zweiten Befehls und führen es auf Knoten2 und Knoten3 aus:
# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377
This node joined a swarm as a manager.
An diesem Punkt ist die vorläufige Konfiguration der Server abgeschlossen. Fahren wir mit der Einrichtung der Dienste fort; die auszuführenden Befehle werden von Knoten1 aus gestartet, sofern nicht anders angegeben.
Lassen Sie uns zunächst Netzwerke für Container erstellen:
# 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 gitlab
Anschließend markieren 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 node3
Als nächstes erstellen wir Verzeichnisse zum Speichern von etcd-Daten, dem KV-Speicher, den Traefik und Stolon benötigen. Ähnlich wie bei Postgresql handelt es sich hierbei um an Server gebundene Container, daher führen wir diesen Befehl auf allen Servern aus:
# mkdir -p /srv/etcd
Erstellen Sie als Nächstes eine Datei zum Konfigurieren von etcd und verwenden Sie sie:
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 etcd
Nach einer Weile überprüfen wir, ob der etcd-Cluster aufgestiegen 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 healthy
Erstellen Sie Verzeichnisse für Postgresql und führen Sie den Befehl auf allen Servern aus:
# mkdir -p /srv/pgsql
Erstellen Sie als Nächstes eine Datei zum Konfigurieren 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: true
Wir generieren Geheimnisse, wenden die Datei an:
# </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 pgsql
Einige Zeit später (schauen Sie sich die Ausgabe des Befehls an Docker-Dienst lsdass alle Dienste aktiv 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 init
Überprüfen der 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 so, dass der Zugriff auf Container von außen möglich ist:
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 traefik
Wir starten Redis Cluster, dazu erstellen wir auf allen Knoten ein Speicherverzeichnis:
# mkdir -p /srv/redis
05redis.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 <<EOF > /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 redis
Docker-Registrierung 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 registry
Und 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'] = "[email protected]"
gitlab_rails['smtp_password'] = "xxxxxxxxx"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['gitlab_email_from'] = '[email protected]'
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 gitlab
Der Endzustand 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/tcp
Was kann noch verbessert werden? Stellen Sie sicher, dass Sie Traefik für die Arbeit mit https-Containern konfigurieren und TLS-Verschlüsselung für Postgresql und Redis hinzufügen. Aber im Allgemeinen kann man es bereits als PoC an Entwickler weitergeben. Schauen wir uns nun Alternativen zu Docker an.
Podman
Eine weitere ziemlich bekannte Engine zum Ausführen von Containern, die nach Pods gruppiert sind (Pods, Gruppen von gemeinsam bereitgestellten Containern). Im Gegensatz zu Docker ist zum Ausführen von Containern kein Dienst erforderlich, die gesamte Arbeit wird über die libpod-Bibliothek erledigt. Ebenfalls in Go geschrieben, benötigt eine OCI-kompatible Laufzeit, um Container wie runC auszuführen.
Die Arbeit mit Podman ähnelt im Allgemeinen der von Docker, soweit Sie es auf diese Weise tun können (behauptet von vielen, die es ausprobiert haben, einschließlich des Autors dieses Artikels):
$ alias docker=podman
und Sie können weiterarbeiten. Im Allgemeinen ist die Situation mit Podman sehr interessant, denn wenn die frühen Versionen von Kubernetes mit Docker funktionierten, gibt es seit etwa 2015, nach der Standardisierung der Containerwelt (OCI – Open Container Initiative) und der Aufteilung von Docker in Containerd und RunC, eine Alternative zu Docker wird für die Ausführung in Kubernetes entwickelt: CRI-O. Podman ist in dieser Hinsicht eine Alternative zu Docker, die auf den Prinzipien von Kubernetes basiert, einschließlich der Containergruppierung, aber das Hauptziel des Projekts besteht darin, Container im Docker-Stil ohne zusätzliche Dienste auszuführen. Aus offensichtlichen Gründen gibt es keinen Schwarmmodus, da die Entwickler klar sagen: Wenn Sie einen Cluster benötigen, nehmen Sie Kubernetes.
Einstellung
Um auf Centos 7 zu installieren, aktivieren Sie einfach das Extras-Repository und installieren Sie dann alles mit dem Befehl:
# yum -y install podman
Andere Eigenschaften
Podman kann Einheiten für systemd generieren und so das Problem lösen, Container nach einem Server-Neustart zu starten. Darüber hinaus wird systemd als ordnungsgemäß als PID 1 im Container deklariert. Zum Erstellen von Containern gibt es ein separates Buildah-Tool, es gibt auch Tools von Drittanbietern – Analoga von Docker-Compose, das auch Kubernetes-kompatible Konfigurationsdateien generiert, sodass der Übergang von Podman zu Kubernetes so einfach wie möglich ist.
Zusammenarbeit mit Podman
Da es keinen Schwarmmodus gibt (es soll auf Kubernetes umgestellt werden, wenn ein Cluster benötigt wird), werden wir ihn in separaten Containern zusammenstellen.
Podman-compose installieren:
# yum -y install python3-pip
# pip3 install podman-compose
Die resultierende Konfigurationsdatei für Podman unterscheidet sich geringfügig, sodass wir beispielsweise einen separaten Volume-Abschnitt direkt in den Abschnitt mit Diensten verschieben mussten.
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 up
Ergebnis der Arbeit:
# 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_1
Mal sehen, was es für systemd und kubernetes generiert. Dazu 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 db40ab8bf84b
Kubernetes:
# 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.target
Leider führt die generierte Einheit für systemd außer dem Starten von Containern nichts anderes aus (z. B. das Bereinigen alter Container, wenn ein solcher Dienst neu gestartet wird), sodass Sie solche Dinge selbst hinzufügen müssen.
Im Prinzip reicht Podman aus, um auszuprobieren, was Container sind, alte Konfigurationen für Docker-Compose zu übertragen und dann auf Kubernetes umzusteigen, wenn Sie einen Cluster benötigen, oder eine benutzerfreundlichere Alternative zu Docker zu erhalten.
rkt
Projekt
Plätschern
Noch
Befund
Die Situation bei Kubernetes ist sehr interessant: Einerseits kann man mit Docker einen Cluster (im Schwarmmodus) zusammenstellen, mit dem man sogar Produktionsumgebungen für Kunden betreiben kann, dies gilt insbesondere für kleine Teams (3-5 Personen). ) oder mit einer geringen Gesamtlast oder dem fehlenden Wunsch, die Feinheiten der Einrichtung von Kubernetes zu verstehen, auch für hohe Lasten.
Podman bietet keine vollständige Kompatibilität, hat aber einen wichtigen Vorteil – Kompatibilität mit Kubernetes, einschließlich zusätzlicher Tools (Buildah und andere). Daher werde ich die Wahl eines Tools für die Arbeit wie folgt angehen: für kleine Teams oder mit begrenztem Budget - Docker (mit einem möglichen Schwarmmodus), für die Entwicklung für mich selbst auf einem persönlichen lokalen Host - Podman-Kameraden, und für alle anderen - Kubernetes.
Ich bin mir nicht sicher, ob sich die Situation mit Docker in Zukunft nicht ändern wird, schließlich sind sie Pioniere und standardisieren auch langsam Schritt für Schritt, aber Podman mit all seinen Mängeln (funktioniert nur unter Linux, kein Clustering, Assembly und andere Maßnahmen sind Entscheidungen Dritter) ist die Zukunft klarer, daher lade ich alle ein, diese Ergebnisse in den Kommentaren zu diskutieren.
PS Am 3. August starten wir „
Vorbestellungskosten vor Veröffentlichung: 5000 Rubel. Das Programm „Docker Video Course“ finden Sie hier
Source: habr.com