Docker et tout, tout, tout

TL;DR : Un article de présentation - un guide pour comparer les environnements pour exécuter des applications dans des conteneurs. Les possibilités de Docker et d'autres systèmes similaires seront considérées.

Docker et tout, tout, tout

Une petite histoire d'où tout cela vient

histoire

Le premier moyen bien connu d’isoler une application est le chroot. L'appel système du même nom permet de modifier le répertoire racine - donnant ainsi accès au programme qui l'a appelé, accès uniquement aux fichiers contenus dans ce répertoire. Mais si le programme dispose des droits de superutilisateur, il peut potentiellement « s'échapper » du chroot et accéder au système d'exploitation principal. De plus, en plus du changement de répertoire racine, les autres ressources (RAM, processeur), ainsi que l'accès au réseau, ne sont pas limités.

La méthode suivante consiste à lancer un système d'exploitation à part entière à l'intérieur du conteneur, en utilisant les mécanismes du noyau du système d'exploitation. Cette méthode est appelée différemment selon les systèmes d'exploitation, mais l'essence est la même : exécuter plusieurs systèmes d'exploitation indépendants, chacun fonctionnant sur le même noyau qui exécute le système d'exploitation principal. Cela inclut les prisons FreeBSD, les zones Solaris, OpenVZ et LXC pour Linux. L'isolation est fournie non seulement pour l'espace disque, mais également pour d'autres ressources, en particulier, chaque conteneur peut avoir des restrictions sur le temps processeur, la RAM et la bande passante du réseau. Par rapport au chroot, quitter le conteneur est plus difficile, car le superutilisateur dans le conteneur n'a accès qu'à l'intérieur du conteneur, cependant, en raison de la nécessité de maintenir à jour le système d'exploitation à l'intérieur du conteneur et de l'utilisation de l'ancien noyau. versions (pertinentes pour Linux, dans une moindre mesure FreeBSD), il existe une probabilité non nulle de « percer » le système d'isolation du noyau et d'accéder au système d'exploitation principal.

Au lieu de lancer un système d'exploitation à part entière dans un conteneur (avec un système d'initialisation, un gestionnaire de packages, etc.), les applications peuvent être lancées immédiatement, l'essentiel est d'offrir cette opportunité aux applications (présence des bibliothèques et autres fichiers). Cette idée a servi de base à la virtualisation d'applications conteneurisées, dont le représentant le plus important et le plus connu est Docker. Par rapport aux systèmes précédents, des mécanismes d'isolation plus flexibles, associés à la prise en charge intégrée des réseaux virtuels entre conteneurs et de l'état des applications à l'intérieur d'un conteneur, ont permis de créer un environnement holistique unique à partir d'un grand nombre de serveurs physiques pour exécuter des conteneurs - sans la nécessité d’une gestion manuelle des ressources.

Docker

Docker est le logiciel de conteneurisation d'applications le plus connu. Écrit en langage Go, il utilise les capacités habituelles du noyau Linux - groupes de contrôle, espaces de noms, capacités, etc., ainsi que les systèmes de fichiers Aufs et autres similaires pour économiser de l'espace disque.

Docker et tout, tout, tout
Source : wikimédia

Architecture

Avant la version 1.11, Docker fonctionnait comme un service unique qui effectuait toutes les opérations avec les conteneurs : téléchargement d'images pour les conteneurs, lancement de conteneurs, traitement des requêtes API. Depuis la version 1.11, Docker a été scindé en plusieurs parties qui interagissent entre elles : containersd, pour gérer tout le cycle de vie des conteneurs (allocation de l'espace disque, téléchargement des images, mise en réseau, lancement, installation et suivi de l'état des conteneurs) et runC , environnements d'exécution de conteneurs, basés sur l'utilisation de groupes de contrôle et d'autres fonctionnalités du noyau Linux. Le service Docker lui-même demeure, mais il ne sert désormais qu'à traiter les requêtes API diffusées vers containers.

Docker et tout, tout, tout

Installation et configuration

Ma façon préférée d'installer Docker est Docker-machine, qui, en plus d'installer et de configurer directement Docker sur des serveurs distants (y compris divers cloud), vous permet de travailler avec les systèmes de fichiers de serveurs distants et peut également exécuter diverses commandes.

Cependant, depuis 2018, le projet n'a pratiquement pas été développé, nous l'installerons donc de la manière habituelle pour la plupart des distributions Linux - en ajoutant un référentiel et en installant les packages nécessaires.

Cette méthode est également utilisée pour l'installation automatisée, par exemple à l'aide d'Ansible ou d'autres systèmes similaires, mais je ne la considérerai pas dans cet article.

L'installation s'effectuera sur Centos 7, j'utiliserai une machine virtuelle comme serveur, pour installer, il suffit d'exécuter les commandes ci-dessous :

# 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

Après l'installation, vous devez démarrer le service, le mettre en chargement automatique :

# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanent

De plus, vous pouvez créer un groupe Docker, dont les utilisateurs pourront travailler avec Docker sans sudo, configurer la journalisation, activer l'accès à l'API de l'extérieur, n'oubliez pas d'affiner le pare-feu (tout ce qui n'est pas autorisé est interdit dans les exemples ci-dessus et ci-dessous (j'ai omis cela pour des raisons de simplicité et de visualisation), mais je n'entrerai pas dans les détails ici.

Autres fonctionnalités

En plus de la machine Docker ci-dessus, il existe également un registre Docker, un outil de stockage d'images pour les conteneurs, ainsi que Docker Compose - un outil pour automatiser le déploiement d'applications dans des conteneurs, les fichiers YAML sont utilisés pour créer et configurer des conteneurs et d'autres éléments connexes (par exemple, les réseaux, les systèmes de fichiers persistants pour le stockage des données).

Il peut également être utilisé pour organiser des pipelines pour CICD. Une autre fonctionnalité intéressante est le travail en mode cluster, appelé mode swarm (avant la version 1.12, il était connu sous le nom de docker swarm), qui vous permet d'assembler une seule infrastructure à partir de plusieurs serveurs pour exécuter des conteneurs. Il existe une prise en charge d'un réseau virtuel au-dessus de tous les serveurs, un équilibreur de charge intégré, ainsi qu'une prise en charge des secrets pour les conteneurs.

Les fichiers YAML de docker compose peuvent être utilisés pour de tels clusters avec des modifications mineures, automatisant entièrement la maintenance des clusters de petite et moyenne taille à diverses fins. Pour les grands clusters, Kubernetes est préférable car les coûts de maintenance en mode essaim peuvent dépasser ceux de Kubernetes. En plus de runC, comme environnement d'exécution pour les conteneurs, vous pouvez installer, par exemple Conteneurs de katas

Travailler avec Docker

Après l'installation et la configuration, nous essaierons de construire un cluster dans lequel nous déploierons GitLab et Docker Registry pour l'équipe de développement. En tant que serveurs, j'utiliserai trois machines virtuelles, sur lesquelles je déploierai en plus le FS distribué GlusterFS, je l'utiliserai comme stockage de volumes Docker, par exemple, pour exécuter une version sécurisée du registre Docker. Composants clés à exécuter : Docker Registry, Postgresql, Redis, GitLab avec prise en charge de GitLab Runner au-dessus de Swarm. Postgresql sera lancé avec le clustering stolon, vous n'avez donc pas besoin d'utiliser GlusterFS pour stocker les données Postgresql. Le reste des données critiques sera stocké sur GlusterFS.

Pour déployer GlusterFS sur tous les serveurs (ils s'appellent node1, node2, node3), vous devez installer les packages, activer le pare-feu, créer les répertoires nécessaires :

# 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

Après l'installation, le travail de configuration de GlusterFS doit être poursuivi à partir d'un nœud, par exemple le nœud 1 :

# 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

Ensuite, vous devez monter le volume résultant (la commande doit être exécutée sur tous les serveurs) :

# mount /srv/docker

Le mode Swarm est configuré sur l'un des serveurs, qui sera Leader, les autres devront rejoindre le cluster, donc le résultat de l'exécution de la commande sur le premier serveur devra être copié et exécuté sur le reste.

Configuration initiale du cluster, j'exécute la commande sur node1 :

# 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

Copiez le résultat de la deuxième commande, exécutez sur node2 et node3 :

# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377
This node joined a swarm as a manager.

Ceci termine la configuration préliminaire des serveurs, commençons la configuration des services, les commandes à exécuter seront lancées depuis le nœud1, sauf indication contraire.

Tout d'abord, créons des réseaux pour les conteneurs :

# 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

Ensuite on marque les serveurs, cela est nécessaire pour lier certains services aux serveurs :

# docker node update --label-add nodename=node1 node1
# docker node update --label-add nodename=node2 node2
# docker node update --label-add nodename=node3 node3

Ensuite, nous créons des répertoires pour stocker les données etcd, le stockage KV dont Traefik et Stolon ont besoin. Semblable à Postgresql, ce seront des conteneurs liés aux serveurs, nous exécutons donc cette commande sur tous les serveurs :

# mkdir -p /srv/etcd

Ensuite, créez un fichier pour configurer etcd et appliquez-le :

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

Au bout d'un moment, on vérifie que le cluster etcd est monté :

# 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

Créez des répertoires pour Postgresql, exécutez la commande sur tous les serveurs :

# mkdir -p /srv/pgsql

Ensuite, créez un fichier pour configurer 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

Nous générons des secrets, appliquons le fichier :

# </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

Quelque temps plus tard (regardez le résultat de la commande service docker lsque tous les services ont augmenté) initialisez le cluster Postgresql :

# 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

Vérification de l'état de préparation du cluster Postgresql :

# 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

Nous configurons traefik pour ouvrir l'accès aux conteneurs depuis l'extérieur :

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

On démarre Redis Cluster, pour cela on crée un répertoire de stockage sur tous les nœuds :

# 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

Ajouter le registre Docker :

06registre.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

Et enfin - 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

L'état final du cluster et des services :

# 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

Que peut-on améliorer d’autre ? Assurez-vous de configurer Traefik pour qu'il fonctionne avec les conteneurs https, ajoutez le cryptage tls pour Postgresql et Redis. Mais en général, vous pouvez déjà le donner aux développeurs sous forme de PoC. Voyons maintenant les alternatives à Docker.

Podman

Autre moteur assez connu pour faire tourner des conteneurs regroupés par pods (pods, groupes de conteneurs déployés ensemble). Contrairement à Docker, il ne nécessite aucun service pour exécuter les conteneurs, tout le travail est effectué via la bibliothèque libpod. Également écrit en Go, nécessite un runtime compatible OCI pour exécuter des conteneurs comme runC.

Docker et tout, tout, tout

Travailler avec Podman ressemble en général à celui de Docker, dans la mesure où vous pouvez le faire comme ceci (affirmé par beaucoup de ceux qui l'ont essayé, y compris l'auteur de cet article) :

$ alias docker=podman

et vous pouvez continuer à travailler. En général, la situation avec Podman est très intéressante, car si les premières versions de Kubernetes fonctionnaient avec Docker, alors depuis 2015 environ, après avoir standardisé le monde des conteneurs (OCI - Open Container Initiative) et divisé Docker en containersd et runC, une alternative à Docker est en cours de développement pour fonctionner dans Kubernetes : CRI-O. Podman à cet égard est une alternative à Docker, construite sur les principes de Kubernetes, y compris le regroupement de conteneurs, mais l'objectif principal du projet est d'exécuter des conteneurs de style Docker sans services supplémentaires. Pour des raisons évidentes, il n'y a pas de mode essaim, puisque les développeurs disent clairement que si vous avez besoin d'un cluster, prenez Kubernetes.

Installation

Pour installer sur Centos 7, activez simplement le référentiel Extras, puis installez le tout avec la commande :

# yum -y install podman

Autres fonctionnalités

Podman peut générer des unités pour systemd, résolvant ainsi le problème du démarrage des conteneurs après un redémarrage du serveur. De plus, systemd est déclaré fonctionner correctement en tant que pid 1 dans le conteneur. Pour créer des conteneurs, il existe un outil buildah distinct, il existe également des outils tiers - analogues de docker-compose, qui génère également des fichiers de configuration compatibles Kubernetes, de sorte que la transition de Podman à Kubernetes est aussi simple que possible.

Travailler avec Podman

Puisqu'il n'y a pas de mode essaim (il est censé passer à Kubernetes si un cluster est nécessaire), nous l'assemblerons dans des conteneurs séparés.

Installez podman-compose :

# yum -y install python3-pip
# pip3 install podman-compose

Le fichier de configuration résultant pour podman est légèrement différent, car par exemple nous avons dû déplacer une section de volumes distincte directement vers la section services.

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

Résultat des travaux :

# 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

Voyons ce que cela générera pour systemd et kubernetes, pour cela nous devons connaître le nom ou l'identifiant du pod :

# 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: {}

système :

# 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

Malheureusement, à part exécuter des conteneurs, l'unité générée pour systemd ne fait rien d'autre (par exemple, nettoyer les anciens conteneurs lorsqu'un tel service est redémarré), vous devrez donc ajouter de telles choses vous-même.

En principe, Podman suffit pour essayer ce que sont les conteneurs, transférer d'anciennes configurations pour docker-compose, puis se diriger vers Kubernetes, si nécessaire, sur un cluster, ou obtenir une alternative plus simple à utiliser à Docker.

rkt

Projet je suis allé aux archives il y a environ six mois en raison du fait que RedHat l'a acheté, je ne m'y attarderai donc pas plus en détail. En général, cela a laissé une très bonne impression, mais comparé à Docker, et encore plus à Podman, cela ressemble à une moissonneuse-batteuse. Il existait également une distribution CoreOS construite sur rkt (même si à l'origine Docker était disponible), mais elle a également pris fin après l'achat de RedHat.

Clapotis

Plus un projet, dont l'auteur voulait simplement créer et exécuter des conteneurs. À en juger par la documentation et le code, l'auteur n'a pas suivi les normes, mais a simplement décidé d'écrire sa propre implémentation, ce qu'il a fait en principe.

résultats

La situation avec Kubernetes est très intéressante : d'une part, avec Docker, vous pouvez assembler un cluster (en mode essaim), avec lequel vous pouvez même exécuter des environnements de production pour les clients, c'est particulièrement vrai pour les petites équipes (3-5 personnes ), ou avec une petite charge globale, ou le manque de désir de comprendre les subtilités de la mise en place de Kubernetes, y compris pour des charges élevées.

Podman n'offre pas une compatibilité totale, mais il présente un avantage important : la compatibilité avec Kubernetes, y compris des outils supplémentaires (buildah et autres). J'aborderai donc le choix d'un outil de travail comme suit : pour les petites équipes, ou avec un budget limité - Docker (avec un éventuel mode essaim), pour développer pour moi sur un hôte local personnel - camarades Podman, et pour tout le monde -Kubernetes.

Je ne suis pas sûr que la situation avec Docker ne changera pas à l'avenir, après tout, ils sont des pionniers, et standardisent aussi lentement étape par étape, mais Podman, avec toutes ses lacunes (fonctionne uniquement sous Linux, il n'y a pas de clustering , l'assemblage et d'autres actions sont des décisions de tiers), l'avenir est plus clair, j'invite donc tout le monde à discuter de ces résultats dans les commentaires.

PS Le 3 août, nous lançons "Cours vidéo Dockeroù vous pourrez en apprendre davantage sur son travail. Nous analyserons tous ses outils : des abstractions de base aux paramètres réseau, en passant par les nuances du travail avec divers systèmes d'exploitation et langages de programmation. Vous vous familiariserez avec la technologie et comprendrez où et comment utiliser au mieux Docker. Nous partagerons également des cas de bonnes pratiques.

Coût de précommande avant sortie : 5000 roubles. Le programme "Docker Video Course" peut être trouvé sur la page du cours.

Source: habr.com

Ajouter un commentaire