Docker und vieles mehr

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.

Docker und vieles mehr

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.

Docker und vieles mehr
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.

Docker und vieles mehr

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.io

Nach 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 --permanent

Zusä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 Kata-Container

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, Stolondeshalb 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/fstab

Nach 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 docker

Dann muss das erhaltene Volume gemountet werden (der Befehl muss auf allen Servern ausgeführt werden):

# mount /srv/docker

Die 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 manager

Wir 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 gitlab

Dann 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 node3

Erstellen 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/etcd

Erstellen 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 etcd

Nach 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 healthy

Wir erstellen die Verzeichnisse für PostgreSQL, den Befehl führen wir auf allen Servern aus:

# mkdir -p /srv/pgsql

Als 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: true

Wir 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 pgsql

Nach 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 init

Wir ü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 traefik

Wir starten den Redis-Cluster. Dazu erstellen wir auf allen Knoten ein Verzeichnis zum Speichern:

# 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 < /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-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 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'] = "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 gitlab

Status des Clusters und der Dienste:

# docker service ls
ID                  NAME                   MODE                REPLICAS            IMAGE                          PORTS
lef9n3m92buq        etcd_etcd1             replicated          1/1                 quay.io/coreos/etcd:latest
ij6uyyo792x5        etcd_etcd2             replicated          1/1                 quay.io/coreos/etcd:latest
fqttqpjgp6pp        etcd_etcd3             replicated          1/1                 quay.io/coreos/etcd:latest
hq5iyga28w33        gitlab_gitlab          replicated          1/1                 gitlab/gitlab-ce:latest        *:22222->22/tcp
dt7s6vs0q4qc        gitlab_gitlab-runner   replicated          1/1                 gitlab/gitlab-runner:latest
k7uoezno0h9n        pgsql_pgkeeper1        replicated          1/1                 sorintlab/stolon:master-pg10
cnrwul4r4nse        pgsql_pgkeeper2        replicated          1/1                 sorintlab/stolon:master-pg10
frflfnpty7tr        pgsql_pgkeeper3        replicated          1/1                 sorintlab/stolon:master-pg10
x7pqqchi52kq        pgsql_pgsentinel       replicated          3/3                 sorintlab/stolon:master-pg10
mwu2wl8fti4r        pgsql_postgresql       replicated          3/3                 sorintlab/stolon:master-pg10
9hkbe2vksbzb        redis_redis-master     global              3/3                 bitnami/redis:latest           *:6379->6379/tcp
l88zn8cla7dc        redis_redis-replica    replicated          3/3                 bitnami/redis:latest           *:30003->6379/tcp
1utp309xfmsy        redis_redis-sentinel   global              3/3                 bitnami/redis:latest           *:30002->16379/tcp
oteb824ylhyp        registry_registry      replicated          1/1                 registry:2.6
qovrah8nzzu8        traefik_traefik        replicated          3/3                 traefik:latest                 *:80->80/tcp, *:443->443/tcp

Was 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.

Docker und vieles mehr

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=podman

Es 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 podman

Weitere 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-compose

Die 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 up

Ergebnis:

# 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

Lassen 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                 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 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 wurde archiviert 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 ein Projekt, 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 „Videokurs zu Docker“, 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 auf der Kursseite.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster