Docker a všechno, všechno, všechno

TL;DR: průvodce porovnáváním prostředí pro spouštění aplikací v kontejnerech. Budou zhodnoceny možnosti Dockeru a dalších podobných systémů.

Docker a všechno, všechno, všechno

Trocha historie, odkud to všechno pochází

Historie

Prvním známým způsobem izolace aplikací je chroot. Tento systémový příkaz umožňuje změnit kořenový adresář — tímto způsobem poskytuje programu, který ho zavolal, přístup pouze k souborům uvnitř tohoto adresáře. Pokud však programu uvnitř dají práva superuživatele, může potenciálně "utéci" z chrootu a získat přístup k základnímu operačnímu systému. Kromě změny kořenového adresáře nejsou omezeny další prostředky (operační paměť, procesor) ani přístup k síti.

Další metodou je spuštění plnohodnotného operačního systému uvnitř kontejneru pomocí mechanismů jádra operačního systému. V různých operačních systémech se tento způsob nazývá různě, ale podstata je stejná — spuštění několika nezávislých operačních systémů, z nichž každý používá stejné jádro jako základní operační systém. Sem patří FreeBSD Jails, Solaris Zones, OpenVZ a LXC pro Linux. Zajišťuje se izolace nejen podle diskového prostoru, ale i dalších prostředků, konkrétně každý kontejner může mít omezení na procesorový čas, operační paměť a šířku pásma sítě. Ve srovnání s chrootem je těžší dostat se z kontejneru, protože superuživatel v kontejneru má přístup pouze k obsahu kontejneru, nicméně kvůli nutnosti udržovat operační systém uvnitř kontejneru aktuální a používání starších verzí jader (to je aktuální pro Linux, v menší míře pro FreeBSD) je zde nezanedbatelná pravděpodobnost "průniku" systému izolace jádra a získání přístupu k základnímu operačnímu systému.

Místo spuštění plnohodnotného operačního systému v kontejneru (se systémem inicializace, správcem balíků atd.) lze okamžitě spouštět aplikace, hlavní je zajistit aplikacím tuto možnost (přítomnost potřebných knihoven a dalších souborů). Tato myšlenka se stala základem pro kontejnerovou virtualizaci aplikací, jejímž nejjasnějším a nejznámějším zástupcem je Docker. Ve srovnání s předchozími systémy poskytly flexibilnější izolační mechanismy spolu se zabudovanou podporou virtuálních sítí mezi kontejnery a sledováním stavu aplikace uvnitř kontejneru možnost vytvořit jednotné a celistvé prostředí z velkého počtu fyzických serverů pro spuštění kontejnerů — bez nutnosti ručního řízení zdrojů.

Docker

Docker je nejznámější software pro kontejnerizaci aplikací. Napsán v jazyce Go, využívá standardní funkce jádra Linux — cgroups, namespaces, capabilities atd., a také souborové systémy Aufs a další podobné pro úsporu místa na disku.

Docker a všechno, všechno, všechno
Zdroj: wikimedia

Architektura

Do verze 1.11 Docker fungoval jako jediná služba, která prováděla všechny operace s kontejnery: stahování obrazů pro kontejnery, spouštění kontejnerů, zpracování API požadavků. S počátkem verze 1.11 byl Docker rozdělen na několik částí, které spolu komunikují: containerd pro správu celého životního cyklu kontejnerů (přidělování diskového prostoru, stahování obrazů, práce se sítí, spouštění, instalace a monitorování stavu kontejnerů) a runC, prostředí pro provádění kontejnerů založené na používání cgroups a dalších funkcí jádra Linux. Samotná služba docker zůstala, ale nyní slouží pouze k zpracování API požadavků, které se přenášejí do containerd.

Docker a všechno, všechno, všechno

Instalace a konfigurace

Mým oblíbeným způsobem instalace dockeru je docker-machine, která kromě samotné instalace a nastavení dockeru na vzdálené servery (včetně různých cloudů) umožňuje práci se souborovými systémy vzdálených serverů a také může provádět spouštění různých příkazů.

Od roku 2018 však projekt téměř nevyvíjí, a proto instalaci provedeme běžným způsobem pro většinu distribucí Linux — přidáním repozitáře a instalací potřebných balíků.

Tento způsob se také používá při automatizované instalaci, například pomocí Ansible nebo jiných podobných systémů, ale v tomto článku se tomu nebudu věnovat.

Instalace bude probíhat na Centos 7, jako server použiji virtuální stroj, pro instalaci stačí spustit následující příkazy:

# 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

Po instalaci je nutné spustit službu a přidat ji do automatického spouštění:

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

Dále je možné vytvořit skupinu docker, jejíž uživatelé budou moci pracovat s docker bez sudo, nastavit logování, povolit přístup k API zvenčí a nezapomenout podrobněji nastavit firewall (zakázat vše, co není povoleno, v uvedených příkladech jsem to zjednodušil pro přehlednost), ale tomuto se podrobněji věnovat nebudu.

Další možnosti

Kromě výše zmíněné docker machine existuje také docker registry, nástroj pro ukládání obrazů pro kontejnery, a docker compose — nástroj pro automatizaci nasazení aplikací v kontejnerech, používají se soubory YAML pro sestavení a konfiguraci kontejnerů a dalších souvisejících věcí (například sítě, trvalé souborové systémy pro ukládání dat).

Také pomocí něj lze organizovat pipeline pro CICD. Další zajímavou možností je provoz v clusterovém režimu, takzvaném swarm mode (do verze 1.12 známém jako docker swarm), umožňující vytvořit z několika serverů jednotnou infrastrukturu pro spuštění kontejnerů. Existuje podpora virtuální sítě nad všemi servery, je k dispozici vestavěný vyrovnávač zátěže a podpora tajemství pro kontejnery.

Soubory YAML z docker compose s drobnými úpravami mohou být použity pro takové clustery, čímž se plně automatizuje údržba malých a středních clusterů pro různé účely. Pro velké clustry je výhodnější použít Kubernetes, protože náklady na údržbu swarm mode mohou překročit ty s Kubernetes. Kromě runC jako prostředí pro spuštění kontejnerů lze nainstalovat například Kata containers

Práce s Dockerem

Po instalaci a nastavení se pokusíme vytvořit klastr, ve kterém nasadíme GitLab a Docker Registry pro tým vývojářů. Jako servery použiji tři virtuální stroje, na kterých také nasadím distribuovaný FS GlusterFS, který budu používat jako úložiště docker volumes, například pro spuštění vysoce dostupné verze docker registry. Klíčové komponenty pro spuštění: Docker Registry, Postgresql, Redis, GitLab s podporou GitLab Runner v režimu Swarm. Postgresql spustíme s možností clusterování. Stolon, proto pro ukládání dat Postgresql není potřeba používat GlusterFS. Ostatní kritické údaje budou uloženy na GlusterFS.

Pro nasazení GlusterFS na všech serverech (označovaných jako node1, node2, node3) je potřeba nainstalovat balíčky, povolit práci firewallu, vytvořit potřebné adresáře:

# 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

Po instalaci je třeba pokračovat v konfiguraci GlusterFS z jednoho uzlu, například 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

Poté je třeba připojit získaný volume (příkaz je třeba provést na všech serverech):

# mount /srv/docker

Nastavení režimu swarm se provádí na jednom ze serverů, který bude Leader, ostatní se budou muset připojit ke klastru, proto je nutné výsledek provedení příkazu na prvním serveru zkopírovat a provést na ostatních.

První nastavení klastru, příkaz spouštím na 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

Zkopírujeme výsledek druhého příkazu, provedeme na node2 a node3:

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

Na tomto místě je předběžné nastavení serverů ukončeno, přecházíme k nastavování služeb, příkazy budou vykonávány z node1, pokud není uvedeno jinak.

Nejdříve vytvoříme sítě pro kontejnerové aplikace:

# 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

Poté označíme servery, což je potřeba pro přiřazení některých služeb k serverům:

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

Dále vytváříme adresáře pro ukládání dat etcd, KV úložiště, které je potřeba pro Traefik a Stolon. Podobně jako u Postgresql budou to kontejnery přiřazené k serverům, proto tento příkaz vykonáváme na všech serverech:

# mkdir -p /srv/etcd

Dále vytvoříme soubor pro nastavení etcd a aplikujeme ho:

00etcd.yml

verze: '3.7'

služby:
  etcd1:
    obraz: quay.io/coreos/etcd:latest
    hostname: etcd1
    příkaz:
      - 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
    sítě:
      - etcd
    svazky:
      - etcd1vol:/data.etcd
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.labels.nodename == node1]
  etcd2:
    obraz: quay.io/coreos/etcd:latest
    hostname: etcd2
    příkaz:
      - 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
    sítě:
      - etcd
    svazky:
      - etcd2vol:/data.etcd
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.labels.nodename == node2]
  etcd3:
    obraz: quay.io/coreos/etcd:latest
    hostname: etcd3
    příkaz:
      - 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
    sítě:
      - etcd
    svazky:
      - etcd3vol:/data.etcd
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.labels.nodename == node3]

svazky:
  etcd1vol:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/etcd"
  etcd2vol:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/etcd"
  etcd3vol:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/etcd"

sítě:
  etcd:
    externí: true

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

Po chvíli zkontrolujeme, že se cluster etcd spustil:

# 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

Vytvoříme adresáře pro PostgreSQL, příkaz provedeme na všech serverech:

# mkdir -p /srv/pgsql

Dále vytvoříme soubor pro konfiguraci PostgreSQL:

01pgsql.yml

verze: '3.7'

služby:
  pgsentinel:
    image: sorintlab/stolon:master-pg10
    příkaz:
      - 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
    sítě:
      - etcd
      - pgsql
    nasazení:
      repliky: 3
      aktualizační_config:
        paralelismus: 1
        zpoždění: 30s
        pořadí: stop-first
        akcí při chybě: pozastavit
  pgkeeper1:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper1
    příkaz:
      - 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
    sítě:
      - etcd
      - pgsql
    prostředí:
      - PGDATA=/var/lib/postgresql/data
    svazky:
      - pgkeeper1:/var/lib/postgresql/data
    tajemství:
      - pgsql
      - pgsql_repl
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.labels.nodename == node1]
  pgkeeper2:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper2
    příkaz:
      - 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
    sítě:
      - etcd
      - pgsql
    prostředí:
      - PGDATA=/var/lib/postgresql/data
    svazky:
      - pgkeeper2:/var/lib/postgresql/data
    tajemství:
      - pgsql
      - pgsql_repl
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.labels.nodename == node2]
  pgkeeper3:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper3
    příkaz:
      - 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
    sítě:
      - etcd
      - pgsql
    prostředí:
      - PGDATA=/var/lib/postgresql/data
    svazky:
      - pgkeeper3:/var/lib/postgresql/data
    tajemství:
      - pgsql
      - pgsql_repl
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.labels.nodename == node3]
  postgresql:
    image: sorintlab/stolon:master-pg10
    příkaz: 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
    sítě:
      - etcd
      - pgsql
    nasazení:
      repliky: 3
      aktualizační_config:
        paralelismus: 1
        zpoždění: 30s
        pořadí: stop-first
        akcí při chybě: rollback

svazky:
  pgkeeper1:
    řidič: local
    řidičové_opts:
      typ: none
      o: bind
      zařízení: "/srv/pgsql"
  pgkeeper2:
    řidič: local
    řidičové_opts:
      typ: none
      o: bind
      zařízení: "/srv/pgsql"
  pgkeeper3:
    řidič: local
    řidičové_opts:
      typ: none
      o: bind
      zařízení: "/srv/pgsql"

tajemství:
  pgsql:
    soubor: "/srv/docker/postgres"
  pgsql_repl:
    soubor: "/srv/docker/replica"

sítě:
  etcd:
    externí: true
  pgsql:
    externí: true

Generujeme tajemství, použijeme soubor:

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

Po nějaké době (sledujeme výstup příkazu docker service ls, co byly všechny služby spuštěny) inicializujeme 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

Kontrolujeme připravenost clusteru 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

Nastavujeme traefik pro otevření přístupu k kontejnerům zvenčí:

03traefik.yml

verze: '3.7'

služby:
  traefik:
    obraz: traefik:latest
    příkaz: >
      --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
    sítě:
      - traefik
    porty:
      - 80:80
    objemy:
      - /var/run/docker.sock:/var/run/docker.sock
    nasazení:
      repliky: 3
      umístění:
        omezení:
          - node.role == manager
        preference:
          - rozptýlení: node.id
      štítky:
        - traefik.enable=true
        - traefik.http.routers.traefik.rule=Host(`traefik.example.com`)
        - traefik.http.services.traefik.loadbalancer.server.port=8080
        - traefik.docker.network=traefik

sítě:
  traefik:
    externí: true

# docker stack deploy --compose-file 03traefik.yml traefik

Spouštíme Redis Cluster, proto na všech uzlech vytváříme adresář pro úschovu:

# mkdir -p /srv/redis

05redis.yml

verze: '3.7'

služby:
  redis-master:
    obraz: 'bitnami/redis:latest'
    sítě:
      - redis
    porty:
      - '6379:6379'
    proměnné prostředí:
      - REDIS_REPLICATION_MODE=master
      - REDIS_PASSWORD=xxxxxxxxxxx
    nasazení:
      režim: globální
      restart_politika:
        podmínka: jakýkoli
    objemy:
      - 'redis:/opt/bitnami/redis/etc/'

  redis-replica:
    obraz: 'bitnami/redis:latest'
    sítě:
      - redis
    porty:
      - '6379'
    závisí_na:
      - redis-master
    proměnné prostředí:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master
      - REDIS_MASTER_PORT_NUMBER=6379
      - REDIS_MASTER_PASSWORD=xxxxxxxxxxx
      - REDIS_PASSWORD=xxxxxxxxxxx
    nasazení:
      režim: replikovaný
      repliky: 3
      update_config:
        paralelismus: 1
        zpoždění: 10s
      restart_politika:
        podmínka: jakýkoli

  redis-sentinel:
    obraz: 'bitnami/redis:latest'
    sítě:
      - redis
    porty:
      - '16379'
    závisí_na:
      - redis-master
      - redis-replica
    vstupní_bod: |
      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'
    nasazení:
      režim: globální
      restart_politika:
        podmínka: jakýkoli

objemy:
  redis:
    ovladač: místní
    ovladač_opce:
      typ: 'none'
      o: 'bind'
      zařízení: "/srv/redis"

sítě:
  redis:
    externí: true

# docker stack deploy --compose-file 05redis.yml redis

Přidáváme Docker Registry:

06registry.yml

verze: '3.7'

služby:
  registry:
    obraz: registry:2.6
    sítě:
      - traefik
    svazky:
      - registry_data: /var/lib/registry
    nasazení:
      repliky: 1
      umístění:
        omezení: [node.role == manager]
      restart_policy:
        podmínka: on-failure
      štítky:
        - traefik.enable=true
        - traefik.http.routers.registry.rule=Host(`registry.example.com`)
        - traefik.http.services.registry.loadbalancer.server.port=5000
        - traefik.docker.network=traefik

svazky:
  registry_data:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/docker/registry"

sítě:
  traefik:
    externí: true

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

A nakonec — GitLab:

08gitlab-runner.yml

verze: '3.7'

služby:
  gitlab:
    obraz: gitlab/gitlab-ce:latest
    sítě:
      - pgsql
      - redis
      - traefik
      - gitlab
    porty:
      - 22222:22
    prostředí:
      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
    svazky:
      - gitlab_conf:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    nasazení:
      mód: replikovaný
      repliky: 1
      umístění:
        omezení:
        - node.role == manager
      štítky:
        - 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:
    obraz: gitlab/gitlab-runner:latest
    sítě:
      - gitlab
    svazky:
      - gitlab_runner_conf:/etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    nasazení:
      mód: replikovaný
      repliky: 1
      umístění:
        omezení:
        - node.role == manager

svazky:
  gitlab_conf:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/docker/gitlab/conf"
  gitlab_logs:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/docker/gitlab/logs"
  gitlab_data:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/docker/gitlab/data"
  gitlab_runner_conf:
    ovladač: local
    ovládací možnosti:
      typ: none
      o: bind
      zařízení: "/srv/docker/gitlab/runner"

sítě:
  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

Konečný stav klastru a služeb:

# 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

Co lze ještě zlepšit? Určitě je potřeba nastavit v Traefiku fungování kontejnerů přes https, přidat šifrování tls pro PostgreSQL a Redis. Ale celkově už lze předat vývojářům jako PoC. Podíváme se nyní na alternativy Dockeru.

Podman

Další známý engine pro spouštění kontejnerů, uspořádaných do podů (pods, skupiny kontejnerů nasazených společně). Na rozdíl od Dockeru nevyžaduje žádnou službu pro spouštění kontejnerů, veškerá práce probíhá prostřednictvím knihovny libpod. Také je napsán v jazyce Go a potřebuje runtime kompatibilní s OCI pro spuštění kontejnerů, například runC.

Docker a všechno, všechno, všechno

Práce s Podmanem celkově připomíná práci s Dockerem, až tolik, že lze udělat takto (tvrdí mnozí, kteří to zkusili, včetně autora tohoto článku):

$ alias docker=podman

a lze pokračovat v práci. Celkově je situace s Podmanem velmi zajímavá, neboť pokud dřívější verze Kubernetes pracovaly s Dockerem, od přibližně roku 2015, po standardizaci kontejnérů (OCI - Open Container Initiative) a rozdělení Dockeru na containerd a runC, se vyvíjí alternativa Dockeru pro spuštění v Kubernetes: CRI-O. Podman je v tomto ohledu alternativou Dockeru, založenou na principech Kubernetes, včetně seskupování kontejnerů, ale hlavním cílem existence projektu je spouštění kontejnerů v stylu Docker bez dalších služeb. Z pochopitelných důvodů zde není swarm mode, protože vývojáři jasně říkají, že pokud potřebujete klastr — vezměte Kubernetes.

Instalace

Pro instalaci v CentOS 7 stačí aktivovat repozitář Extras, poté nainstalovat vše příkazem:

# yum -y install podman

Další možnosti

Podman může generovat jednotky pro systemd, tímto způsobem řeší úkol spouštění kontejnerů po restartu serveru. Dále je uvedena správná funkce systemd jako pid 1 v kontejneru. Pro sestavování kontejnerů existuje samostatný nástroj buildah, jsou zde také obdobné nástroje — jako docker-compose, generující mimo jiné konfigurační soubory kompatibilní s Kubernetes, což usnadňuje přechod z Podman na Kubernetes, jak jen to je možné.

Práce s Podmanem

Protože zde není swarm mode (předpokládá se přechod na Kubernetes, pokud je potřeba klastr) — budeme sestavovat jednotlivé kontejnery.

Instalujeme podman-compose:

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

Výsledný konfigurační soubor pro podman se mírně liší, například bylo nutné přesunout jednotlivou sekci volumes přímo do sekce se službami.

gitlab-podman.yml

verze: '3.7'

služby:
  gitlab:
    obrázek: 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:
    obrázek: 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

sítě:
  gitlab:

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

Výsledek práce:

# 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

Podívejme se, co vygeneruje pro systemd a kubernetes, k tomu je potřeba zjistit název nebo ID podu:

# 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

Bohužel kromě spuštění kontejnerů generovaný unit pro systemd nic dalšího nedělá (například úklid starých kontejnerů při restartu takové služby), takže takové věci bude nutné doprogramovat samostatně.

V podstatě je Podman dostatečný k tomu, abyste si vyzkoušeli, co jsou kontejnery, přenést staré konfigurace pro docker-compose, po čemž můžete přejít na Kubernetes, pokud potřebujete na cluster, nebo získat jednodušší alternativu k Dockeru.

rkt

Projekt odešel do archivu přibližně před šesti měsíci, protože ho koupil RedHat, proto se na něm nebudu podrobněji zastavovat. Celkově zanechával docela dobrý dojem, ovšem v porovnání s Dockerem a tím více s Podmanem vypadá jako kombajn. Také existoval distribuce CoreOS, postavená na základě rkt (ačkoli původně měli Docker), jehož podpora však také skončila po koupi RedHat.

Plash

Další jeden projekt, jehož autor chtěl prostě sbírat a spouštět kontejnery. Soudě podle dokumentace a kódu – autor nesledoval standardy, ale rozhodl se napsat svou vlastní implementaci, což v zásadě udělal.

Závěry

Situace v přítomnosti Kubernetes je velmi zajímavá: na jedné straně lze s Dockerem sestavit cluster (v módu swarm), se kterým lze dokonce spouštět produktové prostředí pro klienty, což je zejména актуální pro malé týmy (3-5 osob), nebo při malé celkové zátěži, či při absenci chuť se zabývat nuancemi nastavení Kubernetes, včetně vysoké zátěže.

Podman nenabízí plnou kompatibilitu, ale má jednu důležitou výhodu — kompatibilitu s Kubernetes, včetně doplňkových nástrojů (buildah a další). Proto k výběru nástroje pro práci přistoupím takto: pro malé týmy nebo při omezeném rozpočtu — Docker (s možným režimem swarm), pro vývoj na osobním localhost — Podman s kolegy, a pro všechny ostatní — Kubernetes.

Nejsem si jistý, zda se situace s Dockerem v budoucnu nezmění, přeci jen jsou pionýři a také se krok za krokem pomalu standardizují, ale u Podmanu, navzdory všem jeho nedostatkům (funguje pouze na Linuxu, žádná klasterizace, sestavování a další operace — prostřednictvím externích řešení), je budoucnost jasnější, proto zvu všechny zúčastněné k diskuzi těchto závěrů v komentářích.

P.S. 3. srpna spouštíme „Videokurz o Dockeru“, kde se můžete dozvědět více o jeho fungování. Probereme všechny jeho nástroje: od základních abstrakcí po parametry sítě, nuance práce s různými operačními systémy a programovacími jazyky. Seznámíte se s technologií a pochopíte, kde a jak nejlépe používat Docker. Také se podělíme o best practice případy.

Cena předobjednávky před vydáním: 5000 Kč. S programem „Video kurzu o Dockeru“ se můžete seznámit na stránce kurzu.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster