Docker und alles, alles, alles

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.

Docker und alles, alles, alles

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.

Docker und alles, alles, alles
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.

Docker und alles, alles, alles

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 Kata-Behälter

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 Stolon, sodass Sie GlusterFS nicht zum Speichern von Postgresql-Daten verwenden müssen. Die verbleibenden kritischen Daten werden auf GlusterFS gespeichert.

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.

Docker und alles, alles, alles

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 ins Archiv gegangen vor etwa sechs Monaten aufgrund der Tatsache, dass RedHat es gekauft hat, daher werde ich nicht näher darauf eingehen. Im Allgemeinen hat es einen sehr guten Eindruck hinterlassen, aber im Vergleich zu Docker und noch mehr zu Podman sieht es aus wie ein Mähdrescher. Es gab auch eine CoreOS-Distribution, die auf rkt aufbaute (obwohl sie ursprünglich Docker hatte), aber auch diese endete nach dem Kauf von RedHat.

Plätschern

Noch ein Projekt, dessen Autor lediglich Container erstellen und ausführen wollte. Der Dokumentation und dem Code nach zu urteilen, folgte der Autor nicht den Standards, sondern beschloss einfach, seine eigene Implementierung zu schreiben, was er im Prinzip auch tat.

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 „Docker-Videokurswo Sie mehr über seine Arbeit erfahren können. Wir werden alle seine Werkzeuge analysieren: von grundlegenden Abstraktionen über Netzwerkparameter bis hin zu Nuancen der Arbeit mit verschiedenen Betriebssystemen und Programmiersprachen. Sie lernen die Technologie kennen und verstehen, wo und wie Sie Docker am besten einsetzen. Wir werden auch Best-Practice-Fälle teilen.

Vorbestellungskosten vor Veröffentlichung: 5000 Rubel. Das Programm „Docker Video Course“ finden Sie hier auf der Kursseite.

Source: habr.com

Kommentar hinzufügen