Docker a všichni, všichni, všichni

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

Docker a všichni, všichni, všichni

Trochu historie o tom, kde se to všechno vzalo

Příběh

Prvním známým způsobem, jak izolovat aplikaci, je chroot. Stejnojmenné systémové volání poskytuje změnu kořenového adresáře – tedy poskytuje přístup k programu, který jej volal, přístup pouze k souborům uvnitř tohoto adresáře. Ale pokud má program uvnitř práva superuživatele, může potenciálně „utéct“ z chrootu a získat přístup k hlavnímu operačnímu systému. Kromě změny kořenového adresáře nejsou omezeny ani další prostředky (RAM, procesor) ani přístup k síti.

Dalším způsobem je spuštění plnohodnotného operačního systému uvnitř kontejneru pomocí mechanismů jádra operačního systému. Tato metoda se v různých operačních systémech nazývá různě, ale podstata je stejná – provozování několika nezávislých operačních systémů, z nichž každý běží na stejném jádře, na kterém běží hlavní operační systém. To zahrnuje FreeBSD Jails, Solaris Zones, OpenVZ a LXC pro Linux. Izolace je zajištěna nejen pro místo na disku, ale také pro další zdroje, zejména každý kontejner může mít omezení na čas procesoru, RAM, šířku pásma sítě. Ve srovnání s chrootem je opuštění kontejneru obtížnější, protože superuživatel v kontejneru má přístup pouze do vnitřku kontejneru, nicméně kvůli nutnosti udržovat operační systém uvnitř kontejneru aktuální a použití starého jádra verze (relevantní pro Linux, v menší míře FreeBSD), existuje nenulová pravděpodobnost „prolomení“ izolačního systému jádra a získání přístupu k hlavnímu operačnímu systému.

Místo spouštění plnohodnotného operačního systému v kontejneru (s inicializačním systémem, správcem balíčků atd.) lze aplikace spouštět okamžitě, hlavní je poskytnout aplikacím tuto příležitost (přítomnost potřebných knihoven a ostatní soubory). Tato myšlenka posloužila jako základ pro virtualizaci kontejnerových aplikací, jejichž nejvýraznějším a nejznámějším představitelem je Docker. Ve srovnání s předchozími systémy vedly flexibilnější izolační mechanismy spolu s vestavěnou podporou virtuálních sítí mezi kontejnery a stavem aplikace uvnitř kontejneru k možnosti vybudovat jediné holistické prostředí z velkého počtu fyzických serverů pro provoz kontejnerů – bez potřeba manuální správy zdrojů.

přístavní dělník

Docker je nejznámější aplikační kontejnerizační software. Je napsán v jazyce Go a využívá běžné schopnosti linuxového jádra – cgroups, jmenné prostory, schopnosti atd., stejně jako souborové systémy Aufs a další podobné pro úsporu místa na disku.

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

architektura

Před verzí 1.11 fungoval Docker jako jediná služba, která prováděla všechny operace s kontejnery: stahování obrázků pro kontejnery, spouštění kontejnerů, zpracování požadavků API. Od verze 1.11 je Docker rozdělen do několika částí, které se vzájemně ovlivňují: kontejnery, které zvládají celý životní cyklus kontejnerů (přidělení místa na disku, stahování obrazů, síťování, spouštění, instalace a sledování stavu kontejnerů) a runC , kontejnerové runtime, založené na použití cgroups a dalších funkcích linuxového jádra. Samotná služba dockeru zůstává, ale nyní slouží pouze ke zpracování požadavků API vysílaných do kontejneru.

Docker a všichni, všichni, všichni

Instalace a konfigurace

Můj oblíbený způsob instalace dockeru je docker-machine, který kromě přímé instalace a konfigurace dockeru na vzdálených serverech (včetně různých cloudů) umožňuje pracovat se souborovými systémy vzdálených serverů a umí také spouštět různé příkazy.

Od roku 2018 se však projekt téměř nerozvíjel, a tak jej nainstalujeme běžným způsobem pro většinu linuxových distribucí – přidáním repozitáře a instalací potřebných balíčků.

Tato metoda se také používá pro automatizovanou instalaci, například pomocí Ansible nebo jiných podobných systémů, ale v tomto článku ji nebudu uvažovat.

Instalace bude provedena na Centos 7, jako server použiji virtuální stroj, k instalaci stačí spustit níže uvedené 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 musíte službu spustit, dát ji do automatického načítání:

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

Dále si můžete vytvořit docker skupinu, jejíž uživatelé budou moci pracovat s dockerem bez sudo, nastavovat logování, povolit přístup k API zvenčí, nezapomenout doladit firewall (vše, co není povoleno, je zakázáno v příkladech výše a níže - to jsem pro jednoduchost a vizualizaci vynechal), ale nebudu se zde blíže rozepisovat.

Další funkce

Kromě výše uvedeného docker stroje existuje také docker registr, nástroj pro ukládání obrázků pro kontejnery, stejně jako docker compose - nástroj pro automatizaci nasazení aplikací v kontejnerech, soubory YAML slouží k vytváření a konfiguraci kontejnerů a další související věci (například sítě, trvalé souborové systémy pro ukládání dat).

Může být také použit k organizaci potrubí pro CICD. Další zajímavou funkcí je práce v clusterovém režimu, tzv. swarm mode (před verzí 1.12 byl znám jako docker swarm), který umožňuje sestavit jedinou infrastrukturu z několika serverů pro provoz kontejnerů. Nad všemi servery existuje podpora pro virtuální síť, je zde vestavěný nástroj pro vyrovnávání zatížení a také podpora pro tajemství pro kontejnery.

Soubory YAML z docker compose lze pro takové clustery s drobnými úpravami použít, což plně automatizuje údržbu malých a středních clusterů pro různé účely. Pro velké clustery je výhodnější Kubernetes, protože náklady na údržbu v režimu roje mohou převážit nad náklady Kubernetes. Kromě runC, jako prováděcího prostředí pro kontejnery, můžete nainstalovat např Kata kontejnery

Práce s Dockerem

Po instalaci a konfiguraci se pokusíme postavit cluster, do kterého nasadíme GitLab a Docker Registry pro vývojový tým. Jako servery použiji tři virtuální stroje, na které dodatečně nasadím distribuovaný FS GlusterFS, použiji jej jako úložiště svazků dockerů, například pro spuštění bezpečné verze docker registru. Klíčové komponenty ke spuštění: Docker Registry, Postgresql, Redis, GitLab s podporou GitLab Runner nad Swarmem. Postgresql bude spuštěn s clusteringem Odnož, takže k ukládání dat Postgresql nemusíte používat GlusterFS. Zbytek kritických dat bude uložen na GlusterFS.

Chcete-li nasadit GlusterFS na všechny servery (nazývají se node1, node2, node3), musíte nainstalovat balíčky, povolit firewall, 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 musí práce na konfiguraci GlusterFS pokračovat 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é musíte připojit výsledný svazek (příkaz musí být spuštěn na všech serverech):

# mount /srv/docker

Režim Swarm je nakonfigurován na jednom ze serverů, kterým bude Leader, zbytek se bude muset připojit ke clusteru, takže výsledek spuštění příkazu na prvním serveru bude nutné zkopírovat a spustit na zbytku.

Počáteční nastavení clusteru spustím příkaz 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írujte výsledek druhého příkazu, proveďte 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.

Tím je předběžná konfigurace serverů dokončena, začněme konfigurovat služby, příkazy, které se mají provést, budou spouštěny z node1, pokud není uvedeno jinak.

Nejprve vytvořte sítě pro kontejnery:

# 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, to je nutné pro navázání některých služeb na servery:

# 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 vytvoříme adresáře pro ukládání dat etcd, KV úložiště, které Traefik a Stolon potřebují. Podobně jako u Postgresql se bude jednat o kontejnery vázané na servery, takže tento příkaz provedeme na všech serverech:

# mkdir -p /srv/etcd

Dále vytvořte soubor pro konfiguraci etcd a použijte jej:

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 zkontrolujeme, že se cluster etcd zvedl:

# 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řte adresáře pro Postgresql, spusťte příkaz na všech serverech:

# mkdir -p /srv/pgsql

Dále vytvořte soubor pro konfiguraci Postgresql:

01pgsql.yml

version: '3.7'

services:
  pgsentinel:
    image: sorintlab/stolon:master-pg10
    command:
      - gosu
      - stolon
      - stolon-sentinel
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
      - --log-level=debug
    networks:
      - etcd
      - pgsql
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 30s
        order: stop-first
        failure_action: pause
  pgkeeper1:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper1
    command:
      - gosu
      - stolon
      - stolon-keeper
      - --pg-listen-address=pgkeeper1
      - --pg-repl-username=replica
      - --uid=pgkeeper1
      - --pg-su-username=postgres
      - --pg-su-passwordfile=/run/secrets/pgsql
      - --pg-repl-passwordfile=/run/secrets/pgsql_repl
      - --data-dir=/var/lib/postgresql/data
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    environment:
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - pgkeeper1:/var/lib/postgresql/data
    secrets:
      - pgsql
      - pgsql_repl
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node1]
  pgkeeper2:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper2
    command:
      - gosu
      - stolon 
      - stolon-keeper
      - --pg-listen-address=pgkeeper2
      - --pg-repl-username=replica
      - --uid=pgkeeper2
      - --pg-su-username=postgres
      - --pg-su-passwordfile=/run/secrets/pgsql
      - --pg-repl-passwordfile=/run/secrets/pgsql_repl
      - --data-dir=/var/lib/postgresql/data
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    environment:
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - pgkeeper2:/var/lib/postgresql/data
    secrets:
      - pgsql
      - pgsql_repl
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node2]
  pgkeeper3:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper3
    command:
      - gosu
      - stolon 
      - stolon-keeper
      - --pg-listen-address=pgkeeper3
      - --pg-repl-username=replica
      - --uid=pgkeeper3
      - --pg-su-username=postgres
      - --pg-su-passwordfile=/run/secrets/pgsql
      - --pg-repl-passwordfile=/run/secrets/pgsql_repl
      - --data-dir=/var/lib/postgresql/data
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    environment:
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - pgkeeper3:/var/lib/postgresql/data
    secrets:
      - pgsql
      - pgsql_repl
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node3]
  postgresql:
    image: sorintlab/stolon:master-pg10
    command: gosu stolon stolon-proxy --listen-address 0.0.0.0 --cluster-name stolon-cluster --store-backend=etcdv3 --store-endpoints http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 30s
        order: stop-first
        failure_action: rollback

volumes:
  pgkeeper1:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/pgsql"
  pgkeeper2:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/pgsql"
  pgkeeper3:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/pgsql"

secrets:
  pgsql:
    file: "/srv/docker/postgres"
  pgsql_repl:
    file: "/srv/docker/replica"

networks:
  etcd:
    external: true
  pgsql:
    external: true

Vygenerujeme 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

O něco později (podívejte se na výstup příkazu docker service lsže všechny služby vzrostly) inicializujte 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

Kontrola připravenosti 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

Nakonfigurujeme traefik pro otevření přístupu ke kontejnerům zvenčí:

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

Spustíme Redis Cluster, za tímto účelem vytvoříme adresář úložiště na všech uzlech:

# mkdir -p /srv/redis

05redis.yml

version: '3.7'

services:
  redis-master:
    image: 'bitnami/redis:latest'
    networks:
      - redis
    ports:
      - '6379:6379'
    environment:
      - REDIS_REPLICATION_MODE=master
      - REDIS_PASSWORD=xxxxxxxxxxx
    deploy:
      mode: global
      restart_policy:
        condition: any
    volumes:
      - 'redis:/opt/bitnami/redis/etc/'

  redis-replica:
    image: 'bitnami/redis:latest'
    networks:
      - redis
    ports:
      - '6379'
    depends_on:
      - redis-master
    environment:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master
      - REDIS_MASTER_PORT_NUMBER=6379
      - REDIS_MASTER_PASSWORD=xxxxxxxxxxx
      - REDIS_PASSWORD=xxxxxxxxxxx
    deploy:
      mode: replicated
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: any

  redis-sentinel:
    image: 'bitnami/redis:latest'
    networks:
      - redis
    ports:
      - '16379'
    depends_on:
      - redis-master
      - redis-replica
    entrypoint: |
      bash -c 'bash -s <<EOF
      "/bin/bash" -c "cat <<EOF > /opt/bitnami/redis/etc/sentinel.conf
      port 16379
      dir /tmp
      sentinel monitor master-node redis-master 6379 2
      sentinel down-after-milliseconds master-node 5000
      sentinel parallel-syncs master-node 1
      sentinel failover-timeout master-node 5000
      sentinel auth-pass master-node xxxxxxxxxxx
      sentinel announce-ip redis-sentinel
      sentinel announce-port 16379
      EOF"
      "/bin/bash" -c "redis-sentinel /opt/bitnami/redis/etc/sentinel.conf"
      EOF'
    deploy:
      mode: global
      restart_policy:
        condition: any

volumes:
  redis:
    driver: local
    driver_opts:
      type: 'none'
      o: 'bind'
      device: "/srv/redis"

networks:
  redis:
    external: true

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

Přidat registr Docker:

06registry.yml

version: '3.7'

services:
  registry:
    image: registry:2.6
    networks:
      - traefik
    volumes:
      - registry_data:/var/lib/registry
    deploy:
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        - traefik.http.routers.registry.rule=Host(`registry.example.com`)
        - traefik.http.services.registry.loadbalancer.server.port=5000
        - traefik.docker.network=traefik

volumes:
  registry_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/registry"

networks:
  traefik:
    external: true

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

A nakonec - GitLab:

08gitlab-runner.yml

version: '3.7'

services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    networks:
      - pgsql
      - redis
      - traefik
      - gitlab
    ports:
      - 22222:22
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        postgresql['enable'] = false
        redis['enable'] = false
        gitlab_rails['registry_enabled'] = false
        gitlab_rails['db_username'] = "gitlab"
        gitlab_rails['db_password'] = "XXXXXXXXXXX"
        gitlab_rails['db_host'] = "postgresql"
        gitlab_rails['db_port'] = "5432"
        gitlab_rails['db_database'] = "gitlab"
        gitlab_rails['db_adapter'] = 'postgresql'
        gitlab_rails['db_encoding'] = 'utf8'
        gitlab_rails['redis_host'] = 'redis-master'
        gitlab_rails['redis_port'] = '6379'
        gitlab_rails['redis_password'] = 'xxxxxxxxxxx'
        gitlab_rails['smtp_enable'] = true
        gitlab_rails['smtp_address'] = "smtp.yandex.ru"
        gitlab_rails['smtp_port'] = 465
        gitlab_rails['smtp_user_name'] = "[email protected]"
        gitlab_rails['smtp_password'] = "xxxxxxxxx"
        gitlab_rails['smtp_domain'] = "example.com"
        gitlab_rails['gitlab_email_from'] = '[email protected]'
        gitlab_rails['smtp_authentication'] = "login"
        gitlab_rails['smtp_tls'] = true
        gitlab_rails['smtp_enable_starttls_auto'] = true
        gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
        external_url 'http://gitlab.example.com/'
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    volumes:
      - gitlab_conf:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
        - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.http.routers.gitlab.rule=Host(`gitlab.example.com`)
        - traefik.http.services.gitlab.loadbalancer.server.port=80
        - traefik.docker.network=traefik
  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    networks:
      - gitlab
    volumes:
      - gitlab_runner_conf:/etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
        - node.role == manager

volumes:
  gitlab_conf:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/conf"
  gitlab_logs:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/logs"
  gitlab_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/data"
  gitlab_runner_conf:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/runner"

networks:
  pgsql:
    external: true
  redis:
    external: true
  traefik:
    external: true
  gitlab:
    external: true

# mkdir -p /srv/docker/gitlab/conf
# mkdir -p /srv/docker/gitlab/logs
# mkdir -p /srv/docker/gitlab/data
# mkdir -p /srv/docker/gitlab/runner
# docker stack deploy --compose-file 08gitlab-runner.yml gitlab

Konečný stav clusteru 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? Nezapomeňte nakonfigurovat Traefik pro práci s kontejnery https, přidejte šifrování tls pro Postgresql a Redis. Ale obecně to už můžete dát vývojářům jako PoC. Podívejme se nyní na alternativy k Dockeru.

Podman

Další poměrně známý engine pro provoz kontejnerů seskupených podle podů (pody, skupiny kontejnerů rozmístěné společně). Na rozdíl od Dockeru nevyžaduje ke spuštění kontejnerů žádnou službu, veškerá práce probíhá přes knihovnu libpod. Také napsáno v Go, potřebuje runtime kompatibilní s OCI, aby spouštělo kontejnery jako runC.

Docker a všichni, všichni, všichni

Práce s Podmanem se obecně podobá práci s Dockerem, a to do té míry, že to můžete udělat takto (tvrdí to mnozí, kteří to vyzkoušeli, včetně autora tohoto článku):

$ alias docker=podman

a můžete pokračovat v práci. Obecně je situace s Podmanem velmi zajímavá, protože pokud rané verze Kubernetes fungovaly s Dockerem, pak zhruba od roku 2015 po standardizaci světa kontejnerů (OCI – Open Container Initiative) a rozdělení Dockeru na kontejnery a runC vznikla alternativa k Docker je vyvíjen pro běh v Kubernetes: CRI-O. Podman je v tomto ohledu alternativou k Dockeru, postavenou na principech Kubernetes, včetně seskupování kontejnerů, ale hlavním cílem projektu je provozovat kontejnery ve stylu Dockeru bez dalších služeb. Ze zřejmých důvodů neexistuje režim rojení, protože vývojáři jasně říkají, že pokud potřebujete cluster, vezměte Kubernetes.

Instalace

Chcete-li nainstalovat na Centos 7, stačí aktivovat úložiště Extras a poté vše nainstalovat pomocí příkazu:

# yum -y install podman

Další funkce

Podman může generovat jednotky pro systemd, čímž řeší problém se spouštěním kontejnerů po restartu serveru. Navíc je systemd deklarováno, že funguje správně jako pid 1 v kontejneru. Pro vytváření kontejnerů existuje samostatný nástroj buildah, existují také nástroje třetích stran - analogy docker-compose, které také generují konfigurační soubory kompatibilní s Kubernetes, takže přechod z Podman na Kubernetes je co nejjednodušší.

Práce s Podmanem

Vzhledem k tomu, že neexistuje režim roje (v případě potřeby clusteru se má přepnout na Kubernetes), sestavíme jej do samostatných kontejnerů.

Nainstalujte podman-compose:

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

Výsledný konfigurační soubor pro podman je mírně odlišný, protože jsme například museli přesunout samostatnou sekci svazků přímo do sekce služeb.

gitlab-podman.yml

version: '3.7'

services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    hostname: gitlab.example.com
    restart: unless-stopped
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    ports:
      - "80:80"
      - "22222:22"
    volumes:
      - /srv/podman/gitlab/conf:/etc/gitlab
      - /srv/podman/gitlab/data:/var/opt/gitlab
      - /srv/podman/gitlab/logs:/var/log/gitlab
    networks:
      - gitlab

  gitlab-runner:
    image: gitlab/gitlab-runner:alpine
    restart: unless-stopped
    depends_on:
      - gitlab
    volumes:
      - /srv/podman/gitlab/runner:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - gitlab

networks:
  gitlab:

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

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 to vygeneruje pro systemd a kubernetes, k tomu musíme zjistit název nebo id pod:

# podman pod ls
POD ID         NAME   STATUS    CREATED          # OF CONTAINERS   INFRA ID
71fc2b2a5c63   root   Running   11 minutes ago   3                 db40ab8bf84b

Kubernetes:

# podman generate kube 71fc2b2a5c63
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.6.4
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-07-29T19:22:40Z"
  labels:
    app: root
  name: root
spec:
  containers:
  - command:
    - /assets/wrapper
    env:
    - name: PATH
      value: /opt/gitlab/embedded/bin:/opt/gitlab/bin:/assets:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: HOSTNAME
      value: gitlab.example.com
    - name: container
      value: podman
    - name: GITLAB_OMNIBUS_CONFIG
      value: |
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    - name: LANG
      value: C.UTF-8
    image: docker.io/gitlab/gitlab-ce:latest
    name: rootgitlab1
    ports:
    - containerPort: 22
      hostPort: 22222
      protocol: TCP
    - containerPort: 80
      hostPort: 80
      protocol: TCP
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
    volumeMounts:
    - mountPath: /var/opt/gitlab
      name: srv-podman-gitlab-data
    - mountPath: /var/log/gitlab
      name: srv-podman-gitlab-logs
    - mountPath: /etc/gitlab
      name: srv-podman-gitlab-conf
    workingDir: /
  - command:
    - run
    - --user=gitlab-runner
    - --working-directory=/home/gitlab-runner
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: HOSTNAME
    - name: container
      value: podman
    image: docker.io/gitlab/gitlab-runner:alpine
    name: rootgitlab-runner1
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
    volumeMounts:
    - mountPath: /etc/gitlab-runner
      name: srv-podman-gitlab-runner
    - mountPath: /var/run/docker.sock
      name: var-run-docker.sock
    workingDir: /
  volumes:
  - hostPath:
      path: /srv/podman/gitlab/runner
      type: Directory
    name: srv-podman-gitlab-runner
  - hostPath:
      path: /var/run/docker.sock
      type: File
    name: var-run-docker.sock
  - hostPath:
      path: /srv/podman/gitlab/data
      type: Directory
    name: srv-podman-gitlab-data
  - hostPath:
      path: /srv/podman/gitlab/logs
      type: Directory
    name: srv-podman-gitlab-logs
  - hostPath:
      path: /srv/podman/gitlab/conf
      type: Directory
    name: srv-podman-gitlab-conf
status: {}

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ě spouštění kontejnerů nedělá vygenerovaná jednotka pro systemd nic jiného (například uklízí staré kontejnery při restartu takové služby), takže si takové věci budete muset přidávat sami.

Podman v zásadě stačí vyzkoušet, co jsou kontejnery, přenést staré konfigurace pro docker-compose a pak jít směrem ke Kubernetes, pokud je to nutné, na clusteru, nebo získat snáze použitelnou alternativu k Dockeru.

Rkt

projekt přešel do archivu asi před půl rokem kvůli tomu, že si to koupil RedHat, tak se tomu nebudu blíže věnovat. Obecně to zanechalo velmi dobrý dojem, ale ve srovnání s Dockerem a ještě více s Podmanem působí jako kombajn. Existovala také distribuce CoreOS postavená na rkt (ačkoli původně měli Docker), ale ta také skončila po nákupu RedHat.

Louže

Více jeden projekt, jehož autor chtěl právě stavět a provozovat kontejnery. Soudě podle dokumentace a kódu se autor neřídil standardy, ale prostě se rozhodl napsat vlastní implementaci, což v zásadě udělal.

Závěry

Situace s Kubernetes je velmi zajímavá: na jedné straně s Dockerem můžete sestavit cluster (v režimu swarm), se kterým můžete dokonce provozovat produkční prostředí pro klienty, to platí zejména pro malé týmy (3-5 lidí ), nebo s malým celkovým zatížením nebo nedostatkem touhy porozumět složitosti nastavení Kubernetes, a to i pro vysoké zatížení.

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

Nejsem si jistý, že se situace s Dockerem v budoucnu nezmění, přeci jen jsou průkopníci, a také se pomalu krok za krokem standardizují, ale Podman se všemi jeho nedostatky (funguje pouze na Linuxu, žádné shlukování, montáž a další akce jsou rozhodnutí třetích stran) budoucnost je jasnější, proto vyzývám všechny k diskusi o těchto zjištěních v komentářích.

PS 3. srpna spouštíme „Docker video kurzkde se můžete dozvědět více o jeho práci. Rozebereme všechny jeho nástroje: od základních abstrakcí po síťové parametry, 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 Docker používat. Budeme také sdílet případy osvědčených postupů.

Cena předobjednávky před vydáním: 5000 rublů. Program "Docker Video Course" lze nalézt na stránce kurzu.

Zdroj: www.habr.com

Přidat komentář