Docker a všetko, všetko, všetko

TL;DR: prehľadový článok-vedenie na porovnanie prostredí pre spúšťanie aplikácií v kontajneroch. Budú preskúmané možnosti Dockeru a iných podobných systémov.

Docker a všetko, všetko, všetko

Trocha histórie, odkiaľ to všetko pochádza

História

Prvým verejne známym spôsobom izolácie aplikácie je chroot. Rovnomenný systémový volanie zabezpečuje zmenu koreňového adresára — týmto spôsobom zabezpečuje prístup programu, ktorý ho vyvolal, len k súborom vo vnútri tohto adresára. Ak však programu vo vnútri poskytneme práva superužívateľa, potenciálne môže 'uteč' z chrootu a získať prístup k základnému operačnému systému. Okrem zmeny koreňového adresára nie sú obmedzené ani iné zdroje (operačná pamäť, procesor), ako aj prístup k sieti.

Ďalším spôsobom je spustenie celého operačného systému vo vnútri kontajnera, vďaka mechanizmom jadra operačného systému. V rôznych operačných systémoch sa tento spôsob nazýva rôzne, ale podstata je rovnaká — spustenie niekoľkých nezávislých operačných systémov, z ktorých každý pracuje s tým istým jadrom, na ktorom pracuje aj základný operačný systém. Toto zahŕňa FreeBSD Jails, Solaris Zones, OpenVZ a LXC pre Linux. Zabezpečuje sa izolácia nielen po diskovom priestore, ale aj ostatných zdrojoch, pričom každý kontajner môže mať obmedzenia na procesorový čas, operačnú pamäť, priepustnosť siete. V porovnaní s chrootom je ťažšie uniknúť z kontajnera, keďže superužívateľ v kontajneri má prístup iba k obsahu kontajnera, avšak z dôvodu potreby udržiavať operačný systém vo vnútri kontajnera aktuálny a používania starých verzií jadier (aktuálne pre Linux, menej pre FreeBSD) existuje nenulová pravdepodobnosť 'prerazenia' systému izolácie jadra a získania prístupu k základnému operačnému systému.

Namiesto spustenia plnohodnotného operačného systému v kontajneri (so systémom inicializácie, správcom balíčkov a pod.) je možné okamžite spustiť aplikácie, pričom je dôležité zabezpečiť aplikáciám potrebné podmienky (prítomnosť potrebných knižníc a iných súborov). Táto myšlienka slúžila ako základ pre kontajnerovú virtualizáciu aplikácií, z ktorých najvýraznejším a najznámejším predstaviteľom je Docker. V porovnaní s predchádzajúcimi systémami flexibilnejšie mechanizmy izolácie spolu so vstavanou podporou virtuálnych sietí medzi kontajnermi a sledovaním stavu aplikácie vo vnútri kontajnera umožnili vytvoriť komplexné prostredie z veľkého počtu fyzických serverov na spustenie kontajnerov — bez potreby manuálneho riadenia zdrojov.

Docker

Docker je najznámejší softvér na kontajnerizáciu aplikácií. Napísaný v programovacom jazyku Go, využíva štandardné funkcie jadra Linux — cgroups, namespaces, capabilities atď., ako aj súborové systémy Aufs a ďalšie podobné pre úsporu diskového priestoru.

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

Architektúra

Až do verzie 1.11 Docker fungoval ako jednotná služba, ktorá vykonávala všetky operácie s kontajnermi: sťahovanie obrazov pre kontajnery, spustenie kontajnerov, spracovanie API požiadaviek. Od verzie 1.11 bol Docker rozdelený na niekoľko častí, ktoré si navzájom spolupracujú: containerd, ktorý spracováva celý životný cyklus kontajnerov (alokácia diskového priestoru, sťahovanie obrazov, práca so sieťou, spustenie, inštalácia a sledovanie stavu kontajnerov) a runC, prostredie vykonávania kontajnerov založené na využívaní cgroups a iných funkcií jadra Linux. Samotná služba Docker zostala, ale teraz slúži iba na spracovanie API požiadaviek, ktoré sú prenášané do containerd.

Docker a všetko, všetko, všetko

Inštalácia a nastavenie

Mojím obľúbeným spôsobom inštalácie Docker je docker-machine, ktorá okrem samotnej inštalácie a nastavenia Docker na vzdialených serveroch (vrátane rôznych cloudových služieb) umožňuje pracovať so súborovými systémami vzdialených serverov a môže vykonávať rôzne príkazy.

Avšak od roku 2018 projekt takmer nevyvíja, preto inštaláciu vykonáme štandardným spôsobom pre väčšinu distribúcií Linux — pridaním repozitára a inštaláciou potrebných balíkov.

Tento spôsob sa používa aj pri automatizovanej inštalácii, napríklad pomocou Ansible alebo iných podobných systémov, ale v tomto článku sa mu nebudem podrobne venovať.

Inštalácia sa bude vykonávať na Centos 7, ako server použijem virtuálny stroj, na inštaláciu stačí vykonať nasledujúce prí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 inštalácii treba spustiť službu a pridať ju do automatického spúšťania:

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

Okrem toho môžete vytvoriť skupinu docker, ktorej používatelia môžu pracovať s docker bez sudo, nastaviť logovanie, povoliť prístup k API z vonku, nezabudnúť presnejšie nastaviť firewall (zakázané je všetko, čo nie je povolené, v príkladoch vyššie a nižšie to opúšťam pre jednoduchosť a prehľadnosť), ale tu sa nebudem podrobnejšie zaoberať.

Ďalšie možnosti

Okrem uvedenej docker machine existuje aj docker registry, nástroj na ukladanie obrazov pre kontejnery, ako aj docker compose - nástroj na automatizáciu nasadzovania aplikácií v kontejnere, využívajúci súbory YAML na zostavenie a konfiguráciu kontajnerov a ďalších súvisiacich vecí (napríklad siete, trvalé súborové systémy na uchovávanie údajov).

Taktiež s jeho pomocou môžete vytvoriť potrubia pre CICD. Ďalšou zaujímavou možnosťou je prevádzka v režime klasteru, nazývanom swarm mode (do verzie 1.12 bol známy ako docker swarm), ktorý umožňuje vytvoriť z viacerých serverov jednotnú infraštruktúru na spúšťanie kontajnerov. Existuje podpora virtuálnej siete nad všetkými servermi, prítomnosť zabudovaného vyvažovača záťaže, ako aj podpora tajomstiev pre kontajnery.

Súbory YAML z docker compose s malými zmenami môžu byť použité pre takéto klastre, úplne automatizujúc údržbu malých a stredných klastrov pre rôzne účely. Pre veľké klastre je lepšie používať Kubernetes, pretože náklady na údržbu swarm mode môžu prekročiť náklady s Kubernetes. Okrem runC ako prostredie vykonávania kontajnerov môžete nainštalovať napríklad Kata containers

Práca s Docker

Po inštalácii a nastavení skúsime zriadiť klaster, v ktorom rozbehneme GitLab a Docker Registry pre vývojársky tím. Ako servery použijem tri virtuálne stroje, na ktorých navyše nasadím distribuovaný súborový systém GlusterFS, ktorý použijem ako úložisko docker volumes, napríklad na spustenie vysoko dostupnej verzie docker registry. Kľúčové komponenty na spustenie: Docker Registry, Postgresql, Redis, GitLab s podporou GitLab Runner nad Swarm. Postgresql spustíme s klastrovaním. Stolon, preto na uchovávanie dát Postgresql netreba používať GlusterFS. Ostatné kritické dáta budú uložené na GlusterFS.

Na zriadenie GlusterFS na všetkých serveroch (tie sú pomenované node1, node2, node3) je potrebné nainštalovať balíky, povoliť pracovný firewall a vytvoriť potrebné adresáre:

# 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 inštalácii je potrebné pokračovať v nastavení GlusterFS z jedného uzla, naprí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

Potom je potrebné pripojiť získaný volume (príkaz treba vykonať na všetkých serveroch):

# mount /srv/docker

Nastavenie swarm mode sa vykonáva na jednom zo serverov, ktorý bude Leader, ostatné sa musia pripojiť ku klastru, preto výsledok vykonania príkazu na prvom serveri je potrebné skopírovať a vykonať na ostatných.

Počiatočné nastavenie klastru, príkaz spúšťam 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

Kopírujeme výsledok druhého príkazu, vykonávame 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 mieste je predbežné nastavenie serverov ukončené, prechádzame k nastaveniu služieb, príkazy na vykonanie budú spúšťané z node1, ak nie je uvedené inak.

Ako prvé vytvoríme siete pre kontajnery:

# 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

Potom označíme servery, to je potrebné na priradenie niektorých služieb k serverom:

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

Následne vytvárame adresáre na uchovávanie dát etcd, KV úložiska, ktoré je potrebné pre Traefik a Stolon. Podobne ako Postgresql to budú priradené k serverom kontajnery, preto tento príkaz vykonávame na všetkých serveroch:

# mkdir -p /srv/etcd

Potom vytvárame súbor na nastavenie etcd a aplikujeme ho:

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

Po chvíli skontrolujeme, že sa spustil cluster 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

Vytvárame adresáre pre Postgresql, príkaz vykonáme na všetkých serveroch:

# mkdir -p /srv/pgsql

Následne vytvoríme súbor na konfiguráciu Postgresql:

01pgsql.yml

verzia: '3.7'

služby:
  pgsentinel:
    obraz: sorintlab/stolon:master-pg10
    prí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
    siete:
      - etcd
      - pgsql
    nasadenie:
      repliky: 3
      update_config:
        paralelizmus: 1
        oneskorenie: 30s
        poradie: stop-first
        failure_action: pause
  pgkeeper1:
    obraz: sorintlab/stolon:master-pg10
    hostname: pgkeeper1
    prí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
    siete:
      - etcd
      - pgsql
    prostredie:
      - PGDATA=/var/lib/postgresql/data
    objemy:
      - pgkeeper1:/var/lib/postgresql/data
    tajomstvá:
      - pgsql
      - pgsql_repl
    nasadenie:
      repliky: 1
      umiestnenie:
        obmedzenia: [node.labels.nodename == node1]
  pgkeeper2:
    obraz: sorintlab/stolon:master-pg10
    hostname: pgkeeper2
    prí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
    siete:
      - etcd
      - pgsql
    prostredie:
      - PGDATA=/var/lib/postgresql/data
    objemy:
      - pgkeeper2:/var/lib/postgresql/data
    tajomstvá:
      - pgsql
      - pgsql_repl
    nasadenie:
      repliky: 1
      umiestnenie:
        obmedzenia: [node.labels.nodename == node2]
  pgkeeper3:
    obraz: sorintlab/stolon:master-pg10
    hostname: pgkeeper3
    prí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
    siete:
      - etcd
      - pgsql
    prostredie:
      - PGDATA=/var/lib/postgresql/data
    objemy:
      - pgkeeper3:/var/lib/postgresql/data
    tajomstvá:
      - pgsql
      - pgsql_repl
    nasadenie:
      repliky: 1
      umiestnenie:
        obmedzenia: [node.labels.nodename == node3]
  postgresql:
    obraz: sorintlab/stolon:master-pg10
    prí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
    siete:
      - etcd
      - pgsql
    nasadenie:
      repliky: 3
      update_config:
        paralelizmus: 1
        oneskorenie: 30s
        poradie: stop-first
        failure_action: rollback

objemy:
  pgkeeper1:
    ovládač: local
    ovládacie_ponuky:
      typ: none
      o: bind
      zariadenie: "/srv/pgsql"
  pgkeeper2:
    ovládač: local
    ovládacie_ponuky:
      typ: none
      o: bind
      zariadenie: "/srv/pgsql"
  pgkeeper3:
    ovládač: local
    ovládacie_ponuky:
      typ: none
      o: bind
      zariadenie: "/srv/pgsql"

tajomstvá:
  pgsql:
    súbor: "/srv/docker/postgres"
  pgsql_repl:
    súbor: "/srv/docker/replica"

siete:
  etcd:
    externé: true
  pgsql:
    externé: true

Generujeme tajomstvá, používame súbor:

# </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 chvíli (pozrite si výstup príkazu docker service ls, čo potvrdzuje, že všetky služby sú spustené) inicializujeme klaster 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 pripravenosť klastera 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

Konfigurujeme traefik na sprístupnenie kontajnerov zvonka:

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

Spúšťame Redis Cluster, na to vytvoríme na všetkých uzloch adresár na ukladanie:

# 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

Pridávame Docker Registry:

06registry.yml

verzia: '3.7'

služby:
  registry:
    image: registry:2.6
    siete:
      - traefik
    objemy:
      - registry_data: /var/lib/registry
    nasadenie:
      repliky: 1
      umiestnenie:
        obmedzenia: [node.role == manager]
      politika_reštartu:
        podmienka: 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

objemy:
  registry_data:
    ovládač: local
    ovládač_opts:
      typ: none
      o: bind
      zariadenie: "/srv/docker/registry"

siete:
  traefik:
    externé: true

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

A nakoniec — GitLab:

08gitlab-runner.yml

verzia: '3.7'

služby:
  gitlab:
    image: gitlab/gitlab-ce:latest
    siete:
      - pgsql
      - redis
      - traefik
      - gitlab
    porty:
      - 22222:22
    prostredie:
      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
    objemy:
      - gitlab_conf:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    nasadenie:
      mód: replicated
      repliky: 1
      umiestnenie:
        obmedzenia:
        - 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:
    image: gitlab/gitlab-runner:latest
    siete:
      - gitlab
    objemy:
      - gitlab_runner_conf:/etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    nasadenie:
      mód: replicated
      repliky: 1
      umiestnenie:
        obmedzenia:
        - node.role == manager

objemy:
  gitlab_conf:
    ovládač: local
    ovládač_opts:
      typ: none
      o: bind
      zariadenie: "/srv/docker/gitlab/conf"
  gitlab_logs:
    ovládač: local
    ovládač_opts:
      typ: none
      o: bind
      zariadenie: "/srv/docker/gitlab/logs"
  gitlab_data:
    ovládač: local
    ovládač_opts:
      typ: none
      o: bind
      zariadenie: "/srv/docker/gitlab/data"
  gitlab_runner_conf:
    ovládač: local
    ovládač_opts:
      typ: none
      o: bind
      zariadenie: "/srv/docker/gitlab/runner"

siete:
  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 klastra a služieb:

# 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

Čo ešte možno vylepšiť? Určite nastaviť v Traefik prácu kontajnerov cez https, pridať šifrovanie tls pre Postgresql a Redis. Ale celkovo už to možno odovzdať vývojárom ako PoC. Teraz sa pozrime na alternatívy Dockeru.

Podman

Ďalší dosť známy engine na spúšťanie kontajnerov, skupinových po pods (pods, skupiny kontajnerov nasadených spoločne). Na rozdiel od Dockeru nevyžaduje žiadny servis na spúšťanie kontajnerov, všetka práca sa vykonáva cez knižnicu libpod. Tiež napísaný v Go, vyžaduje OCI-kompatibilný runtime na spúšťanie kontajnerov, napríklad runC.

Docker a všetko, všetko, všetko

Práca s Podmanom vo všeobecnosti pripomína prácu s Dockerom, až po to, že môžete spraviť takto (tvrdené mnohými, ktorí to vyskúšali, vrátane autora tohto článku):

$ alias docker=podman

a môžete pokračovať v práci. Všeobecne situácia s Podmanom je veľmi zaujímavá, pretože ak rané verzie Kubernetes pracovali s Dockerom, od približne roku 2015, po standardizácii sveta kontajnerov (OCI — Open Container Initiative) a rozdelení Dockeru na containerd a runC, sa vyvíja alternatíva Dockeru na spúšťanie v Kubernetes: CRI-O. Podman v tomto ohľade je alternatívou Dockeru postavenou na princípoch Kubernetes, vrátane skupinovania kontajnerov, ale hlavný cieľ existencie projektu — spúšťanie kontajnerov v štýle Docker bez dodatočných služieb. Z zrozumiteľných dôvodov nie je prítomný swarm mode, pretože vývojári jasne hovoria, že ak potrebujete klaster — berte Kubernetes.

Inštalácia

Na inštaláciu v Centos 7 stačí aktivovať repozitár Extras, po čom nainštalujeme všetko príkazom:

# yum -y install podman

Ďalšie možnosti

Podman môže generovať jednotky pre systemd, čím rieši úlohu spúšťania kontajnerov po reštartovaní servera. Navyše bola deklarovaná správna práca systemd ako pid 1 v kontajneri. Na zostavenie kontajnerov existuje samostatný nástroj buildah, sú tiež k dispozícii externé nástroje — analógy docker-compose, ktorý generuje aj konfiguračné súbory kompatibilné s Kubernetes, takže prechod z Podman na Kubernetes je zjednodušený, ak je to možné.

Práca s Podmanom

Keďže nie je prítomný swarm mode (predpokladá sa prechod na Kubernetes, ak potrebujete klaster) — budeme zbierať jednotlivé kontajnery.

Inštalujeme podman-compose:

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

Výsledný konfiguračný súbor pre podman sa trochu líši, napríklad bolo potrebné presunúť samostatnú sekciu volumes priamo do sekcie so službami.

gitlab-podman.yml

verzia: '3.7'

služby:
  gitlab:
    obraz: gitlab/gitlab-ce:latest
    hostname: gitlab.example.com
    restart: unless-stopped
    prostredie:
      GITLAB_OMNIBUS_CONFIG: |
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    porty:
      - "80:80"
      - "22222:22"
    objemy:
      - /srv/podman/gitlab/conf:/etc/gitlab
      - /srv/podman/gitlab/data:/var/opt/gitlab
      - /srv/podman/gitlab/logs:/var/log/gitlab
    siete:
      - gitlab

  gitlab-runner:
    obraz: gitlab/gitlab-runner:alpine
    restart: unless-stopped
    závisí_od:
      - gitlab
    objemy:
      - /srv/podman/gitlab/runner:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
    siete:
      - gitlab

siete:
  gitlab:

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

Výsledok 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

Pozrime sa, čo vygeneruje pre systemd a kubernetes, na to je potrebné zistiť meno alebo 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

Žiaľ, okrem spustenia kontajnerov generovaný jednotkový súbor pre systemd nič viac nerobí (napríklad vyčistenie starých kontajnerov pri reštarte takejto služby), takže tieto veci bude treba dopisovať samostatne.

V zásade Podman je dostatočný na to, aby ste vyskúšali, čo sú kontajnery, preniesli staré konfigurácie pre docker-compose, po čom môžete prejsť na Kubernetes, ak potrebujete na klaster, alebo získať jednoduchšiu alternatívu k Dockeru.

rkt

Projekt odišiel do archívu približne pred pol rokom, pretože ho kúpil RedHat, takže sa nebudem na ňom podrobne zastavovať. Celkovo zanechával celkom dobrý dojem, avšak v porovnaní s Dockerom a už vôbec s Podmanom vyzerá ako kombajn. Existoval tiež distribúcia CoreOS, postavená na základe rkt (aj keď pôvodne mali Docker), avšak jeho podpora tiež skončila po kúpe RedHat.

Plash

Ďalšie jeden projekt, autor ktorého chcel jednoducho zbierať a spúšťať kontajnery. Súdiac podľa dokumentácie a kódu - autor nedodržiaval štandardy, ale jednoducho sa rozhodol napísať svoju implementáciu, čo v podstate aj spravil.

Zhrnutie

Situácia pri existencii Kubernetes je veľmi zaujímavá: z jednej strany je možné postaviť klaster s Dockerom (v swarm móde), s ktorým je možné spúšťať produktové prostredia pre klientov, čo je obzvlášť aktuálne pre malé tímy (3-5 ľudí), alebo pri malej celkovej záťaži, či pri absencii ochoty rozumieť nuansám nastavenia Kubernetes, vrátane tých pre vysoké zaťaženia.

Podman nenabízí úplnou kompatibilitu, ale má jednu důležitou výhodu – kompatibilitu s Kubernetes, včetně dalších nástrojů (buildah a další). Proto k výběru nástroje pro práci přistupuji takto: pro malé týmy nebo při omezeném rozpočtu – Docker (s možným režimem swarm), pro vývoj pro sebe na osobním localhostu – 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 zároveň se postupně standardizují, ale Podman, i se všemi svými nedostatky (funguje pouze na Linuxu, nemá clustering, sestavování a další činnosti – prostřednictvím externích řešení), má jasnější budoucnost, proto zvu všechny zájemce, aby tyto závěry prodiskutovali v komentářích.

P.S. 3. srpna spouštíme „Videokurz o Dockeru“, kde se můžete podrobněji seznámit s jeho funkcí. Projdeme si 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 je nejlepší Docker použít. Také se podělíme o best practice případy.

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

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster