Docker și tot, tot, tot

TL;DR: un articol de revizuire-ghid pentru compararea medii pentru rularea aplicațiilor în containere. Vor fi analizate capabilitățile Docker și ale altor sisteme similare.

Docker și tot, tot, tot

Puțin istorie, de unde a pornit totul

Istoria

Primul mod cunoscut de izolare a aplicațiilor este chroot. Apelul de sistem omonim permite modificarea directorului rădăcină, asigurând astfel accesul programului care l-a apelat doar la fișierele din acest director. Dar dacă programul din interior are drepturi de superutilizator, acesta poate 'fugi' din chroot și obține acces la sistemul de operare principal. De asemenea, în afară de schimbarea directorului rădăcină, nu sunt restricționate alte resurse (memorie RAM, procesor) și accesul la rețea.

Următorul mod este rularea unui sistem de operare complet în interiorul unui container, prin intermediul mecanismelor nucleului sistemului de operare. În diverse sisteme de operare, această metodă este cunoscută diferit, dar esența este aceeași: rularea mai multor sisteme de operare independente, fiecare dintre ele funcționând cu același nucleu ca și sistemul de operare principal. Exemple includ FreeBSD Jails, Solaris Zones, OpenVZ și LXC pentru Linux. Aceasta asigură izolare nu doar pe baza spațiului de stocare, ci și a altor resurse; în special, fiecare container poate avea limitări privind timpul procesorului, memoria RAM și lățimea de bandă a rețelei. Comparativ cu chroot, ieșirea din container este mai dificilă, deoarece superutilizatorul din container are acces doar la elementele interne ale containerului; totuși, din cauza necesității de a menține sistemul de operare din interiorul containerului actualizat și utilizării versiunilor mai vechi ale nucleelor (relevant pentru Linux, mai puțin pentru FreeBSD), există o probabilitate non-zero de 'spargere' a sistemului de izolare a nucleului și obținerea accesului la sistemul de operare principal.

În loc să pornești un sistem de operare complet într-un container (cu sistem de inițializare, manager de pachete etc.), poți porni aplicații direct, fiind esențial să le oferi această capacitate (disponibilitatea bibliotecilor necesare și a altor fișiere). Această idee a stat la baza virtualizării containers de aplicații, cel mai notabil și cunoscut reprezentant fiind Docker. Între sistemele anterioare, mecanismele de izolare mai flexibile împreună cu suportul încorporat pentru rețele virtuale între containere și monitorizarea stării aplicației în interiorul containerului au permis crearea unui mediu unificat dintr-un număr mare de servere fizice pentru rularea containerelor — fără necesitatea de a gestiona manual resursele.

Docker

Docker este cel mai cunoscut software pentru containerizarea aplicațiilor. Scris în limbajul Go, utilizează capacitățile de bază ale nucleului Linux — cgroups, namespaces, capabilities etc., precum și sisteme de fișiere Aufs și altele similare pentru economisirea spațiului pe disc.

Docker și tot, tot, tot
Sursă: wikimedia

Arhitectură

Până la versiunea 1.11, Docker funcționa ca un singur serviciu, care efectua toate operațiunile cu containere: descărcarea imaginilor pentru containere, pornirea containerelor, procesarea cererilor prin API. Începând cu versiunea 1.11, Docker a fost împărțit în mai multe părți, interconectate între ele: containerd, pentru gestionarea întregului ciclu de viață al containerelor (alocarea spațiului pe disc, descărcarea imaginilor, lucrul cu rețeaua, pornirea, instalarea și monitorizarea stării containerelor) și runC, mediu de execuție a containerelor, bazat pe utilizarea cgroups și a altor caracteristici ale nucleului Linux. Serviciul Docker a rămas, dar acum servește doar pentru procesarea cererilor prin API, transmise la containerd.

Docker și tot, tot, tot

Instalare și configurare

Metoda mea preferată de instalare a Docker este docker-machine, care, pe lângă instalarea și configurarea Docker pe servere la distanță (inclusiv pe diverse cloud-uri), permite lucrul cu sistemele de fișiere ale serverelor la distanță și poate executa diferite comenzi.

Cu toate acestea, din 2018, proiectul s-a dezvoltat foarte puțin, așa că vom efectua instalarea în modul standard pentru majoritatea distribuțiilor Linux — adăugând repository-ul și instalând pachetele necesare.

Această metodă este aplicată și în cazul instalării automate, de exemplu, folosind Ansible sau alte sisteme similare, dar nu voi discuta despre aceasta în acest articol.

Instalarea va fi efectuată pe CentOS 7, voi folosi o mașină virtuală ca server; pentru instalare, este suficient să executați comenzile de mai jos:

# 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

După instalare, trebuie să porniți serviciul și să-l setați să se pornească automat:

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

De asemenea, puteți crea un grup Docker, utilizatorii căruia vor putea lucra cu Docker fără sudo, să configurați jurnalizarea, să permiteți accesul la API din exterior, să nu uitați să configurați mai exact firewall-ul (toate permisiunile sunt interzise, cu excepția celor permise; în exemplele de mai sus și de mai jos am omis acest lucru pentru simplitate și claritate), dar nu mă voi opri aici pentru a detalia mai mult.

Alte funcționalități

Pe lângă Docker Machine menționat anterior, există și Docker Registry, un instrument pentru stocarea imaginilor containerelor, precum și Docker Compose - un instrument pentru automatizarea desfășurării aplicațiilor în containere, folosind fișiere YAML pentru construirea și configurarea containerelor și a altor lucruri asociate (de exemplu, rețele, sisteme de fișiere persistente pentru stocarea datelor).

De asemenea, acesta permite organizarea de pipeline-uri pentru CICD. O altă posibilitate interesantă este modul de lucru în cluster, numit swarm mode (până la versiunea 1.12 cunoscut ca Docker Swarm), care permite construirea unei infrastructuri unice din mai multe servere pentru executarea containerelor. Există suport pentru rețele virtuale peste toate serverele, un balansor de încărcare încorporat, precum și suport pentru secrete pentru containere.

Fișierele YAML ale Docker Compose, cu mici modificări, pot fi utilizate pentru aceste clustere, automatizând complet întreținerea clusterelor mici și medii pentru diverse scopuri. Pentru clustere mari, este preferabil să folosiți Kubernetes, deoarece costurile de întreținere a swarm mode pot depăși pe cele cu Kubernetes. În plus față de runC, ca mediu de execuție a containerelor, ar putea fi instalat, de exemplu, Kata containers

Lucru cu Docker

După instalare și configurare, vom încerca să construim un cluster în care vom desfășura GitLab și Docker Registry pentru echipa de dezvoltare. Ca servere, voi folosi trei mașini virtuale, pe care voi desfășura, de asemenea, un sistem de fișiere distribuit GlusterFS, pe care îl voi utiliza ca stocare pentru volumele Docker, de exemplu, pentru a lansa o versiune tolerantă la defecțiuni a Docker Registry. Componentele cheie pentru pornire: Docker Registry, Postgresql, Redis, GitLab cu suport pentru GitLab Runner pe Swarm. Vom rula Postgresql cu clusterizare. Stolon, așadar pentru stocarea datelor Postgresql nu este necesar să folosim GlusterFS. Celelalte date critice vor fi stocate pe GlusterFS.

Pentru desfășurarea GlusterFS pe toate serverele (denumite node1, node2, node3) trebuie să instalăm pachetele, să permitem funcționarea firewall-ului, să creăm directoarele necesare:

# 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

După instalare, lucrările de configurare a GlusterFS trebuie continuate de pe un nod, de exemplu 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

Apoi trebuie să montăm volumul obținut (comanda trebuie executată pe toate serverele):

# mount /srv/docker

Configurarea modului swarm se face pe unul dintre servere, care va fi Lider, ceilalți trebuie să se alăture cluster-ului, așa că rezultatul comenzii de pe primul server va trebui copiat și executat pe celelalte.

Configurarea inițială a cluster-ului, comanda o lansez pe 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

Copiem rezultatul celei de-a doua comenzi, executăm pe node2 și node3:

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

Aici, configurarea preliminară a serverelor s-a încheiat, trecem la configurarea serviciilor, comenzile de execuție vor fi lansate de pe node1, dacă nu se specifică altfel.

Mai întâi, vom crea rețele pentru containere:

# 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

Apoi, marcăm serverele, acest lucru este necesar pentru a lega anumite servicii de servere:

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

Apoi, creăm directoare pentru stocarea datelor etcd, un depozit KV necesar pentru Traefik și Stolon. În mod asemănător cu Postgresql, acestea vor fi containere legate de servere, de aceea această comandă o executăm pe toate serverele:

# mkdir -p /srv/etcd

Apoi, creăm un fișier pentru configurarea etcd și îl aplicăm:

00etcd.yml

versiune: '3.7'

servicii:
  etcd1:
    imagine: quay.io/coreos/etcd:latest
    hostname: etcd1
    comandă:
      - 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
    rețele:
      - etcd
    volume:
      - etcd1vol:/data.etcd
    desfășurare:
      replici: 1
      plasare:
        constrângeri: [node.labels.nodename == node1]
  etcd2:
    imagine: quay.io/coreos/etcd:latest
    hostname: etcd2
    comandă:
      - 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
    rețele:
      - etcd
    volume:
      - etcd2vol:/data.etcd
    desfășurare:
      replici: 1
      plasare:
        constrângeri: [node.labels.nodename == node2]
  etcd3:
    imagine: quay.io/coreos/etcd:latest
    hostname: etcd3
    comandă:
      - 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
    rețele:
      - etcd
    volume:
      - etcd3vol:/data.etcd
    desfășurare:
      replici: 1
      plasare:
        constrângeri: [node.labels.nodename == node3]

volume:
  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"

rețele:
  etcd:
    extern: true

# docker stack deploy --compose-file 00etcd.yml etcd

După un timp, verificăm că a fost ridicat clusterul etcd:

# 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

Creăm directoare pentru Postgresql, comanda se execută pe toate serverele:

# mkdir -p /srv/pgsql

Apoi creăm un fișier pentru configurarea Postgresql:

01pgsql.yml

versiune: '3.7'

servicii:
  pgsentinel:
    imagine: sorintlab\/stolon:master-pg10
    comandă:
      - gosu
      - stolon
      - stolon-sentinel
      - --nume-cluster=stolon-cluster
      - --back-end-store=etcdv3
      - --puncte-finalizare-store=http:\/\/etcd1:2379,http:\/\/etcd2:2379,http:\/\/etcd3:2379
      - --nivel-logare=debug
    rețele:
      - etcd
      - pgsql
    desfășurare:
      replici: 3
      configurație_actualizare:
        paralelism: 1
        întârziere: 30s
        ordine: stop-primul
        acțiune-eroare: pauză
  pgkeeper1:
    imagine: sorintlab\/stolon:master-pg10
    nume-gazdă: pgkeeper1
    comandă:
      - gosu
      - stolon
      - stolon-keeper
      - --adresa-listen-pg=pgkeeper1
      - --nume-utilizator-repl-pg=replica
      - --uid=pgkeeper1
      - --nume-utilizator-su-pg=postgres
      - --fișier-parolă-su-pg=\/run\/secrets\/pgsql
      - --fișier-parolă-repl-pg=\/run\/secrets\/pgsql_repl
      - --director-date=\/var\/lib\/postgresql\/data
      - --nume-cluster=stolon-cluster
      - --back-end-store=etcdv3
      - --puncte-finalizare-store=http:\/\/etcd1:2379,http:\/\/etcd2:2379,http:\/\/etcd3:2379
    rețele:
      - etcd
      - pgsql
    mediu:
      - PGDATA=\/var\/lib\/postgresql\/data
    volume:
      - pgkeeper1:\/var\/lib\/postgresql\/data
    secrete:
      - pgsql
      - pgsql_repl
    desfășurare:
      replici: 1
      plasare:
        constrângeri: [node.labels.nodename == node1]
  pgkeeper2:
    imagine: sorintlab\/stolon:master-pg10
    nume-gazdă: pgkeeper2
    comandă:
      - gosu
      - stolon
      - stolon-keeper
      - --adresa-listen-pg=pgkeeper2
      - --nume-utilizator-repl-pg=replica
      - --uid=pgkeeper2
      - --nume-utilizator-su-pg=postgres
      - --fișier-parolă-su-pg=\/run\/secrets\/pgsql
      - --fișier-parolă-repl-pg=\/run\/secrets\/pgsql_repl
      - --director-date=\/var\/lib\/postgresql\/data
      - --nume-cluster=stolon-cluster
      - --back-end-store=etcdv3
      - --puncte-finalizare-store=http:\/\/etcd1:2379,http:\/\/etcd2:2379,http:\/\/etcd3:2379
    rețele:
      - etcd
      - pgsql
    mediu:
      - PGDATA=\/var\/lib\/postgresql\/data
    volume:
      - pgkeeper2:\/var\/lib\/postgresql\/data
    secrete:
      - pgsql
      - pgsql_repl
    desfășurare:
      replici: 1
      plasare:
        constrângeri: [node.labels.nodename == node2]
  pgkeeper3:
    imagine: sorintlab\/stolon:master-pg10
    nume-gazdă: pgkeeper3
    comandă:
      - gosu
      - stolon
      - stolon-keeper
      - --adresa-listen-pg=pgkeeper3
      - --nume-utilizator-repl-pg=replica
      - --uid=pgkeeper3
      - --nume-utilizator-su-pg=postgres
      - --fișier-parolă-su-pg=\/run\/secrets\/pgsql
      - --fișier-parolă-repl-pg=\/run\/secrets\/pgsql_repl
      - --director-date=\/var\/lib\/postgresql\/data
      - --nume-cluster=stolon-cluster
      - --back-end-store=etcdv3
      - --puncte-finalizare-store=http:\/\/etcd1:2379,http:\/\/etcd2:2379,http:\/\/etcd3:2379
    rețele:
      - etcd
      - pgsql
    mediu:
      - PGDATA=\/var\/lib\/postgresql\/data
    volume:
      - pgkeeper3:\/var\/lib\/postgresql\/data
    secrete:
      - pgsql
      - pgsql_repl
    desfășurare:
      replici: 1
      plasare:
        constrângeri: [node.labels.nodename == node3]
  postgresql:
    imagine: sorintlab\/stolon:master-pg10
    comandă: gosu stolon stolon-proxy --adresa-listen 0.0.0.0 --nume-cluster stolon-cluster --back-end-store=etcdv3 --puncte-finalizare-store http:\/\/etcd1:2379,http:\/\/etcd2:2379,http:\/\/etcd3:2379
    rețele:
      - etcd
      - pgsql
    desfășurare:
      replici: 3
      configurație_actualizare:
        paralelism: 1
        întârziere: 30s
        ordine: stop-primul
        acțiune-eroare: rollback

volume:
  pgkeeper1:
    driver: local
    opțiuni_driver:
      tip: none
      o: bind
      dispozitiv: "\/srv\/pgsql"
  pgkeeper2:
    driver: local
    opțiuni_driver:
      tip: none
      o: bind
      dispozitiv: "\/srv\/pgsql"
  pgkeeper3:
    driver: local
    opțiuni_driver:
      tip: none
      o: bind
      dispozitiv: "\/srv\/pgsql"

secrete:
  pgsql:
    fișier: "\/srv\/docker\/postgres"
  pgsql_repl:
    fișier: "\/srv\/docker\/replica"

rețele:
  etcd:
    extern: true
  pgsql:
    extern: true

Generăm secrete, aplicăm fișierul:

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

După un timp (verificăm ieșirea comenzii docker service ls, care să ne arate că toate serviciile au fost pornite) inițializăm clusterul 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

Verificăm disponibilitatea clusterului 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

Configurăm traefik pentru a deschide accesul la containere din exterior:

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

Pornim Redis Cluster, pentru aceasta creăm un director pentru stocare pe toate nodurile:

# 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

Adăugăm Docker Registry:

06registry.yml

versiune: '3.7'

servicii:
  registry:
    imagine: registry:2.6
    rețele:
      - traefik
    volume:
      - registry_data: /var/lib/registry
    deploy:
      replici: 1
      plasare:
        constrângeri: [node.role == manager]
      politica_restart:
        condiție: on-failure
      etichete:
        - traefik.enable=true
        - traefik.http.routers.registry.rule=Host(`registry.example.com`)
        - traefik.http.services.registry.loadbalancer.server.port=5000
        - traefik.docker.network=traefik

volume:
  registry_data:
    driver: local
    opts_driver:
      tip: none
      o: bind
      dispozitiv: "/srv/docker/registry"

rețele:
  traefik:
    extern: true

# mkdir /srv/docker/registry
# docker stack deploy --compose-file 06registry.yml registry

Și în final — GitLab:

08gitlab-runner.yml

versiune: '3.7'

servicii:
  gitlab:
    imagine: gitlab/gitlab-ce:latest
    rețele:
      - pgsql
      - redis
      - traefik
      - gitlab
    porturi:
      - 22222:22
    mediu:
      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
    volume:
      - gitlab_conf:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    deploy:
      mod: replicated
      replici: 1
      plasare:
        constrângeri:
        - node.role == manager
      etichete:
        - 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:
    imagine: gitlab/gitlab-runner:latest
    rețele:
      - gitlab
    volume:
      - gitlab_runner_conf:/etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mod: replicated
      replici: 1
      plasare:
        constrângeri:
        - node.role == manager

volume:
  gitlab_conf:
    driver: local
    opts_driver:
      tip: none
      o: bind
      dispozitiv: "/srv/docker/gitlab/conf"
  gitlab_logs:
    driver: local
    opts_driver:
      tip: none
      o: bind
      dispozitiv: "/srv/docker/gitlab/logs"
  gitlab_data:
    driver: local
    opts_driver:
      tip: none
      o: bind
      dispozitiv: "/srv/docker/gitlab/data"
  gitlab_runner_conf:
    driver: local
    opts_driver:
      tip: none
      o: bind
      dispozitiv: "/srv/docker/gitlab/runner"

rețele:
  pgsql:
    extern: true
  redis:
    extern: true
  traefik:
    extern: true
  gitlab:
    extern: 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

Starea finală a clusterului și serviciilor:

# 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

Ce altceva poate fi îmbunătățit? Este esențial să configurăm în Traefik funcționarea containerelor prin https, să adăugăm criptarea tls pentru Postgresql și Redis. În general, însă, poate fi oferit deja dezvoltatorilor ca PoC. Să vedem acum alternativele pentru Docker.

Podman

Un alt engine destul de cunoscut pentru rularea containerelor, grupate în poduri (pods, grupuri de containere desfășurate împreună). Spre deosebire de Docker, nu necesită niciun serviciu pentru a rula containere, toată munca se face prin biblioteca libpod. De asemenea, este scris în Go, necesită un runtime compatibil OCI pentru a rula containere, de exemplu runC.

Docker și tot, tot, tot

Lucrul cu Podman este în general similar cu cel pentru Docker, până la punctul în care se poate face așa (declarat de mulți dintre cei care au încercat, inclusiv autorul acestui articol):

$ alias docker=podman

și se poate continua să lucrăm. În general, situația cu Podman este foarte interesantă, deoarece, dacă versiunile timpurii ale Kubernetes au lucrat cu Docker, începând cu 2015, după standardizarea lumii containerelor (OCI — Open Container Initiative) și separarea Docker în containerd și runC, se dezvoltă o alternativă la Docker pentru rularea în Kubernetes: CRI-O. În acest sens, Podman este o alternativă la Docker, construită pe principiile Kubernetes, inclusiv în gruparea containerelor, dar scopul principal al existenței proiectului este de a rula containere în stil Docker fără servicii suplimentare. Din motive evidente, nu există modul swarm, deoarece dezvoltatorii spun clar că, dacă este necesar un cluster, folosiți Kubernetes.

Instalare

Pentru instalare în Centos 7, este suficient să activăm repository-ul Extras, după care instalăm totul cu comanda:

# yum -y install podman

Alte funcționalități

Podman poate genera unități pentru systemd, rezolvând astfel problema rulării containerelor după repornirea serverului. De asemenea, este declarat că systemd funcționează corect ca pid 1 în container. Pentru construirea containerelor există un instrument separat, buildah; există, de asemenea, instrumente terțe — echivalente cu docker-compose, care generează în special fișiere de configurare compatibile cu Kubernetes, astfel încât trecerea de la Podman la Kubernetes este simplificată cât mai mult posibil.

Lucrul cu Podman

Deoarece nu există modul swarm (se presupune tranziția la Kubernetes, dacă este necesar un cluster) — vom aduna containere separate.

Instalăm podman-compose:

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

Fișierul de configurare rezultat pentru podman diferă puțin, având în vedere că, de exemplu, a fost necesar să mutăm o secțiune separată volumes direct în secțiunea cu servicii.

gitlab-podman.yml

versiune: '3.7'

servicii:
  gitlab:
    imagine: 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:
    imagine: 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

rețele:
  gitlab:

# podman-compose -f gitlab-runner.yml -d up

Rezultatul execuției:

# 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

Să vedem ce va genera pentru systemd și kubernetes, pentru aceasta trebuie să aflăm numele sau ID-ul podului:

# 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

Din păcate, în afară de lansarea containerelor, unitatea generată pentru systemd nu face nimic altceva (de exemplu, să curețe containerele vechi la relansarea unui astfel de serviciu), așadar va trebui să adăugați aceste lucruri manual.

În principiu, Podman este suficient pentru a experimenta cu containere, pentru a migra configurațiile vechi pentru docker-compose, după care să treceți la Kubernetes, dacă aveți nevoie de un cluster, sau pentru a obține o alternativă mai simplă la Docker.

rkt

Proiect a fost arhivat aproximativ acum șase luni deoarece a fost achiziționat de RedHat, așa că nu voi insista asupra lui. În general, a lăsat o impresie destul de bună, dar comparativ cu Docker și cu atât mai mult cu Podman, pare un ansamblu. De asemenea, a existat o distribuție CoreOS, construită pe baza rkt (deși inițial aveau Docker), dar suportul său a încetat de asemenea după achiziția RedHat.

Plash

Altele un proiect, ale cărui autor a vrut pur și simplu să construiască și să ruleze containere. Judecând după documentație și cod — autorul nu a urmat standardele, ci a decis pur și simplu să scrie propria implementare, ceea ce, în principiu, și-a realizat.

Conclusions

Situația în prezența Kubernetes se dovedește a fi foarte interesantă: pe de o parte, cu Docker se poate construi un cluster (în modul swarm), cu care se pot rula medii productive pentru clienți, ceea ce este deosebit de relevant pentru echipe mici (3-5 persoane), sau în cazul unei sarcini generale reduse, sau în absența dorinței de a înțelege nuanțele configurării Kubernetes, inclusiv pentru sarcini mari.

Podman nu oferă o compatibilitate completă, dar are un avantaj important — compatibilitatea cu Kubernetes, inclusiv prin uneltele suplimentare (buildah și altele). De aceea, voi aborda alegerea instrumentului de lucru în felul următor: pentru echipe mici, sau cu bugete reduse — Docker (posibil în modul swarm), pentru dezvoltare personală pe localhost — Podman cu colegii, iar pentru ceilalți — Kubernetes.

Nu sunt sigur că situația cu Docker nu se va schimba în viitor, până la urmă ei sunt pionieri și, pas cu pas, devin standardizați, dar din toate neajunsurile lui Podman (funcționează numai pe Linux, nu are clustering, construcția și alte acțiuni — cu soluții externe) viitorul pare mai clar, așa că invit pe toți cei dornici să discutăm aceste concluzii în comentarii.

P.S. 3 august lansăm „Curs video despre Docker”, unde veți putea afla mai multe despre funcționarea acestuia. Vom analiza toate uneltele sale: de la abtractele de bază la parametrii rețelei, nuanțele lucrului cu diferite sisteme de operare și limbaje de programare. Veți face cunoștință cu tehnologia și veți înțelege unde și cum să folosiți mai bine Docker. De asemenea, vom împărtăși cele mai bune practici.

Prețul precomenzii până la lansare: 5000 RON. Puteți consulta programul „Cursului video despre Docker” pe pagina cursului.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster