Docker and everything, everything, everything

TL;DR: pregledni članak koji služi kao vodič za usporedbu okruženja za pokretanje aplikacija u kontejnerima. Razmotrit će se mogućnosti Dockera i drugih sličnih sustava.

Docker and everything, everything, everything

Malo povijesti — otkuda sve to dolazi

Zgodovina

Prvi općepoznati način izolacije aplikacija je chroot. Isteimeni sistemski poziv omogućava promjenu korijenskog direktorija — na taj način omogućuje pristup programu koji ga je pozvao, samo datotekama unutar tog direktorija. Međutim, ako se programu unutar dodijele superkorisničke privilegije, potencijalno se može "izvući" iz chroota i dobiti pristup glavnom operativnom sustavu. Osim promjene korijenskog direktorija, nisu ograničeni ni drugi resursi (operativna memorija, procesor), niti pristup mreži.

Sljedeći način je pokretanje pune operativne sustave unutar kontejnera, zahvaljujući mehanizmima jezgre operativnog sustava. U različitim operativnim sustavima ovaj se način naziva različito, ali suština je ista — pokretanje nekoliko neovisnih operativnih sustava, svaki od kojih radi s istom jezgrom na kojoj radi i glavni operativni sustav. Ovo uključuje FreeBSD Jails, Solaris Zones, OpenVZ i LXC za Linux. Osigurava se izolacija ne samo prema diskovnom prostoru već i drugim resursima, pri čemu svaki kontejner može imati ograničenja po procesorskom vremenu, operativnoj memoriji i propusnosti mreže. U usporedbi s chrootom, teže je izaći iz kontejnera, budući da superkorisnik unutar kontejnera ima pristup samo komponentama kontejnera, međutim zbog potrebe održavanja operativnog sustava unutar kontejnera u ažurnom stanju i korištenja starih verzija jezgre (što je posebno relevantno za Linux, u manjoj mjeri za FreeBSD) postoji neznatna mogućnost "probijanja" sustava izolacije jezgre i dobivanja pristupa glavnom operativnom sustavu.

Namesto zagonu popolne operacijske sistema v kontejnerju (s sistemom za inicializacijo, upraviteljem paketov itd.) lahko takoj zaženete aplikacije, ključno je, da zagotovite aplikacijam to možnost (prisotnost potrebnih knjižnic in drugih datotek). Ta ideja je služila kot osnova za kontejnersko virtualizacijo aplikacij, katere najbolj izstopajoč in splošno znan predstavnik je Docker. V primerjavi s prejšnjimi sistemi so bolj fleksibilni mehanizmi izolacije skupaj z vgrajeno podporo virtualnim omrežjem med kontejnerji in s spremljanjem stanja aplikacije znotraj kontejnerja omogočili izgradnjo enotnega celostnega okolja iz velikega števila fizičnih strežnikov za zagon kontejnerjev — brez potrebe po ročnem upravljanju virov.

Docker

Docker je najbolj znana programska oprema za kontejnerizacijo aplikacij. Napisan je v jeziku Go, uporablja standardne zmogljivosti jedra Linux — cgroups, namespaces, capabilities itd., ter datotečne sisteme Aufs in druge podobne za varčevanje s prostorom na disku.

Docker and everything, everything, everything
Vir: wikimedia

Arhitektura

Pred različico 1.11 je Docker deloval kot enotni servis, ki je izvajal vse operacije s kontejnerji: prenos slik za kontejnerje, zagon kontejnerjev, obdelavo zahtevkov po API-ju. Od različice 1.11 so Docker razdelili na več delov, ki medsebojno delujejo: containerd, za obdelavo celotnega življenjskega cikla kontejnerjev (dodelitev diska, prenos slik, delo z omrežjem, zagon, namestitev in spremljanje stanja kontejnerjev) in runC, okolje za izvajanje kontejnerjev, ki temelji na uporabi cgroups in drugih zmogljivosti jedra Linux. Sam servis docker je ostal, vendar zdaj služi samo za obdelavo zahtevkov po API-ju, ki se prenesejo v containerd.

Docker and everything, everything, everything

Namestitev in nastavitev

Moj najljubši način namestitve dockerja je docker-machine, ki poleg neposredne namestitve in nastavitve dockerja na oddaljene strežnike (vključno z različnimi oblačnimi storitvami) omogoča delo z datotečnimi sistemi oddaljenih strežnikov, prav tako pa lahko izvaja različne ukaze.

Vendar se od leta 2018 projekt skoraj ne razvija, zato bomo namestitev izvedli na način, ki je standarden za večino distribucij Linux — z dodajanjem repozitorija in namestitvijo potrebnih paketov.

Ta metoda se uporablja tudi pri samodejni namestitvi, na primer s pomočjo Ansible ali drugih podobnih sistemov, vendar je v tem članku ne bom obravnaval.

Namestitev bo potekala na Centos 7, kot strežnik bom uporabil virtualni stroj; za namestitev preprosto izvedite spodnje ukaze:

# 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 namestitvi je treba zagnati storitev in jo dodati v samodejno nalaganje:

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

Dodatno lahko ustvarite skupino docker, katere uporabniki bodo lahko delali z dockerjem brez sudo, nastavite beleženje, omogočite dostop do API-ja od zunaj, ne pozabite natančneje nastaviti požarnega zidu (vse kar ni dovoljeno, je prepovedano; v primerih zgoraj in spodaj sem to izpustil zaradi enostavnosti in preglednosti), vendar se tu ne bom podrobneje ustavil.

Druge možnosti

Poleg že omenjenega docker machine obstaja še docker registry, orodje za shranjevanje slik za kontejnerje, ter docker compose — orodje za avtomatizacijo uvajanja aplikacij v kontejnerjih, ki uporablja YAML datoteke za sestavljanje in nastavitev kontejnerjev ter vse druge povezane stvari (na primer omrežja, trajne datotečne sisteme za shranjevanje podatkov).

S pomočjo tega orodja lahko prav tako organizirate cevi za CICD. Druga zanimiva funkcionalnost je delovanje v gruči, tako imenovani swarm mode (do različice 1.12 je bil znan kot docker swarm), ki omogoča združitev več strežnikov v enotno infrastrukturo za zagon kontejnerjev. Podprto je virtualno omrežje nad vsemi strežniki, prav tako je na voljo vgrajen uravnoteževalnik obremenitve in podpora za skrivnosti za kontejnerje.

YAML datoteke iz docker compose s minoritnimi spremembami se lahko uporabljajo za take gruče, popolnoma avtomatizirajoč vzdrževanje manjših in srednjih grozdov za različne namene. Za velike gruče je priporočljivo uporabiti Kubernetes, saj lahko stroški vzdrževanja swarm mode presegajo stroške z Kubernetesom. Poleg runC kot izvajalnega okolja za kontejnerje lahko namestite na primer Kata containers

Rad sa Docker-om

Po namestitvi in konfiguraciji bomo poskusili zgraditi klaster, v katerem bomo namestili GitLab in Docker Registry za razvojno ekipo. Kot strežnike bom uporabil tri virtualne naprave, na katerih bom dodatno namestil distribuirani FS GlusterFS, ki ga bom uporabil kot shrambo za docker volume, na primer za zagon visoko razpoložljive različice docker registry. Ključne komponente za zagon: Docker Registry, Postgresql, Redis, GitLab s podporo za GitLab Runner na Swarmu. Postgresql bomo zagnali s klasterizacijo. Stolon, zato za shranjevanje podatkov Postgresql ni potrebno uporabljati GlusterFS. Druge kritične podatke bomo hranili na GlusterFS.

Za namestitev GlusterFS na vseh strežnikih (ti so poimenovani node1, node2, node3) je treba namestiti pakete, omogočiti delovanje požarnega zidu, ustvariti potrebne mape:

# 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 namestitvi se mora nadaljevati s konfiguracijo GlusterFS z enega vozlišča, na primer 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

Nato je treba priključiti pridobljen volume (ukaz je treba izvesti na vseh strežnikih):

# mount /srv/docker

Nastavitev swarm mode se izvede na enem od strežnikov, ki bo vodja, ostali se bodo morali pridružiti klasterju, zato je treba rezultat izvedbe ukaza na prvem strežniku kopirati in izvesti na preostalih.

Prvotna nastavitev klasterja, ukaz izdajam 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

Kopiramo rezultat drugega ukaza in ga izvedemo na node2 in node3:

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

S tem je predhodna nastavitev strežnikov končana, preidemo na nastavitev storitev, ukazi za izvedbo se bodo izvajali z node1, razen če ni drugače navedeno.

Najprej bomo ustvarili omrežja za vse kontejnerje:

# 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

Nato označimo strežnike, kar je potrebno za vezavo nekaterih storitev na strežnike:

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

Nato ustvarimo mape za shranjevanje podatkov etcd, KV-shranjevanja, ki je potrebno za Traefik in Stolon. Podobno kot Postgresql bodo to kontejnere, vezane na strežnike, zato ta ukaz izvedemo na vseh strežnikih:

# mkdir -p /srv/etcd

Nato ustvarimo datoteko za nastavitev etcd in jo uporabimo:

00etcd.yml

verzija: '3.7'

storitev:
  etcd1:
    slika: quay.io/coreos/etcd:latest
    gostiteljski_naziv: etcd1
    ukaz:
      - 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
    omrežja:
      - etcd
    volumni:
      - etcd1vol:/data.etcd
    uvajanje:
      replikati: 1
      postavitev:
        omejitve: [node.labels.nodename == node1]
  etcd2:
    slika: quay.io/coreos/etcd:latest
    gostiteljski_naziv: etcd2
    ukaz:
      - 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
    omrežja:
      - etcd
    volumni:
      - etcd2vol:/data.etcd
    uvajanje:
      replikati: 1
      postavitev:
        omejitve: [node.labels.nodename == node2]
  etcd3:
    slika: quay.io/coreos/etcd:latest
    gostiteljski_naziv: etcd3
    ukaz:
      - 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
    omrežja:
      - etcd
    volumni:
      - etcd3vol:/data.etcd
    uvajanje:
      replikati: 1
      postavitev:
        omejitve: [node.labels.nodename == node3]

volumni:
  etcd1vol:
    gonilnik: lokalno
    gonilnik_opts:
      tip: none
      o: bind
      naprava: "/srv/etcd"
  etcd2vol:
    gonilnik: lokalno
    gonilnik_opts:
      tip: none
      o: bind
      naprava: "/srv/etcd"
  etcd3vol:
    gonilnik: lokalno
    gonilnik_opts:
      tip: none
      o: bind
      naprava: "/srv/etcd"

omrežja:
  etcd:
    zunanje: true

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

Po nekaj časa preverimo, ali je klaster etcd zagnan:

# 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

Ustvarimo imenike za Postgresql, ukaz izvedemo na vseh strežnikih:

# mkdir -p /srv/pgsql

Nato ustvarimo datoteko za nastavitev Postgresql:

01pgsql.yml

verzija: '3.7'

usluge:
  pgsentinel:
    slika: sorintlab/stolon:master-pg10
    komanda:
      - 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
    mreže:
      - etcd
      - pgsql
    implementacija:
      replikati: 3
      konfiguracija_azuriranja:
        paralelizam: 1
        odlaganje: 30s
        redosled: stop-prvo
        akcija_u_slucaju_greske: pauza
  pgkeeper1:
    slika: sorintlab/stolon:master-pg10
    hostname: pgkeeper1
    komanda:
      - 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
    mreže:
      - etcd
      - pgsql
    okruženje:
      - PGDATA=/var/lib/postgresql/data
    volumeni:
      - pgkeeper1:/var/lib/postgresql/data
    tajne:
      - pgsql
      - pgsql_repl
    implementacija:
      replikati: 1
      raspored:
        ograničenja: [node.labels.nodename == node1]
  pgkeeper2:
    slika: sorintlab/stolon:master-pg10
    hostname: pgkeeper2
    komanda:
      - 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
    mreže:
      - etcd
      - pgsql
    okruženje:
      - PGDATA=/var/lib/postgresql/data
    volumeni:
      - pgkeeper2:/var/lib/postgresql/data
    tajne:
      - pgsql
      - pgsql_repl
    implementacija:
      replikati: 1
      raspored:
        ograničenja: [node.labels.nodename == node2]
  pgkeeper3:
    slika: sorintlab/stolon:master-pg10
    hostname: pgkeeper3
    komanda:
      - 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
    mreže:
      - etcd
      - pgsql
    okruženje:
      - PGDATA=/var/lib/postgresql/data
    volumeni:
      - pgkeeper3:/var/lib/postgresql/data
    tajne:
      - pgsql
      - pgsql_repl
    implementacija:
      replikati: 1
      raspored:
        ograničenja: [node.labels.nodename == node3]
  postgresql:
    slika: sorintlab/stolon:master-pg10
    komanda: 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
    mreže:
      - etcd
      - pgsql
    implementacija:
      replikati: 3
      konfiguracija_azuriranja:
        paralelizam: 1
        odlaganje: 30s
        redosled: stop-prvo
        akcija_u_slucaju_greske: vraćanje

volumeni:
  pgkeeper1:
    vozač: lokalno
    opcije_vozača:
      tip: none
      o: bind
      uređaj: "/srv/pgsql"
  pgkeeper2:
    vozač: lokalno
    opcije_vozača:
      tip: none
      o: bind
      uređaj: "/srv/pgsql"
  pgkeeper3:
    vozač: lokalno
    opcije_vozača:
      tip: none
      o: bind
      uređaj: "/srv/pgsql"

tajne:
  pgsql:
    datoteka: "/srv/docker/postgres"
  pgsql_repl:
    datoteka: "/srv/docker/replica"

mreže:
  etcd:
    spoljašnje: true
  pgsql:
    spoljašnje: true

Generiramo skrivnosti, uporabljamo datoteko:

# </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 določenem času (pregledamo izhod ukaza docker service ls, da preverimo, ali so vsi storitve zagnane) inicializiramo PostgreSQL klaster:

# 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

Preverimo stanje PostgreSQL klasterja:

# 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

Konfiguriramo Traefik za omogočanje dostopa do kontejnerjev od zunaj:

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

Zagnamo Redis klaster, zato na vseh vozliščih ustvarimo imenik za shranjevanje:

# 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

Dodajamo Docker Registry:

06registry.yml

verzija: '3.7'

usluge:
  registry:
    slika: registry:2.6
    mreže:
      - traefik
    volumeni:
      - registry_data: /var/lib/registry
    raspored:
      replikati: 1
      pozicioniranje:
        ograničenja: [node.role == manager]
      pravila_povratka:
        stanje: on-failure
      oznake:
        - traefik.enable=true
        - traefik.http.routers.registry.rule=Host(`registry.example.com`)
        - traefik.http.services.registry.loadbalancer.server.port=5000
        - traefik.docker.network=traefik

volumeni:
  registry_data:
    drajver: local
    opcije_drajvera:
      tip: none
      o: bind
      uređaj: "/srv/docker/registry"

mreže:
  traefik:
    spoljašnje: true

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

I konačno — GitLab:

08gitlab-runner.yml

verzija: '3.7'

usluge:
  gitlab:
    slika: gitlab/gitlab-ce:latest
    mreže:
      - pgsql
      - redis
      - traefik
      - gitlab
    portovi:
      - 22222:22
    okruženje:
      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
    volumeni:
      - gitlab_conf:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    raspored:
      način: replicated
      replikati: 1
      pozicioniranje:
        ograničenja:
        - node.role == manager
      oznake:
        - 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:
    slika: gitlab/gitlab-runner:latest
    mreže:
      - gitlab
    volumeni:
      - gitlab_runner_conf:/etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    raspored:
      način: replicated
      replikati: 1
      pozicioniranje:
        ograničenja:
        - node.role == manager

volumeni:
  gitlab_conf:
    drajver: local
    opcije_drajvera:
      tip: none
      o: bind
      uređaj: "/srv/docker/gitlab/conf"
  gitlab_logs:
    drajver: local
    opcije_drajvera:
      tip: none
      o: bind
      uređaj: "/srv/docker/gitlab/logs"
  gitlab_data:
    drajver: local
    opcije_drajvera:
      tip: none
      o: bind
      uređaj: "/srv/docker/gitlab/data"
  gitlab_runner_conf:
    drajver: local
    opcije_drajvera:
      tip: none
      o: bind
      uređaj: "/srv/docker/gitlab/runner"

mreže:
  pgsql:
    spoljašnje: true
  redis:
    spoljašnje: true
  traefik:
    spoljašnje: true
  gitlab:
    spoljašnje: 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

Konačno stanje klastera i usluga:

# 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

Kaj še lahko izboljšate? Obvezno nastavite delovanje kontejnerjev prek https v Traefiku, dodajte šifriranje tls za PostgreSQL in Redis. Na splošno pa je že možno to predati razvijalcem kot PoC. Zdaj si oglejmo še alternative Dockerju.

Podman

Še en precej znan motor za zagon kontejnerjev, združenih v skupine (pods, skupine kontejnerjev, ki so razporejeni skupaj). V nasprotju z Dockerjem ne zahteva nobene storitve za zagon kontejnerjev, vse delo poteka preko knjižnice libpod. Prav tako je napisan v Goju in potrebuje OCI-združljiv runtime za zagon kontejnerjev, na primer runC.

Docker and everything, everything, everything

Delo s Podmanom na splošno spominja na delo z Dockerjem, kar zadeva tudi to, da lahko storite naslednje (zatrjujejo mnogi, ki so poskusili, vključno z avtorjem tega članka):

$ alias docker=podman

in lahko nadaljujete z delom. Splošna situacija s Podmanom je precej zanimiva, saj so zgodnje različice Kubernetes delovale z Dockerjem, od leta 2015, po standardizaciji sveta kontejnerjev (OCI — Open Container Initiative) in razdelitvi Dockerja na containerd in runC, se razvija alternativa Dockerju za zagon v Kubernetes: CRI-O. Podman v tem smislu predstavlja alternativo Dockerju, zgrajeno po načelih Kubernetes, vključno s skupinami kontejnerjev, vendar je osnovni cilj projekta zagon kontejnerjev v slogu Docker brez dodatnih storitev. Iz razumljivih razlogov ne obstaja swarm mode, saj razvijalci jasno povedo, da če potrebujete grozd, vzemite Kubernetes.

Namestitev

Za namestitev v Centos 7 je dovolj aktivirati repozitorij Extras, nato pa vse namestiti z ukazom:

# yum -y install podman

Druge možnosti

Podman lahko generira enote za systemd, tako da reši težavo z zagonom kontejnerjev po ponovnem zagonu strežnika. Dodatno je bila navedena pravilna uporaba systemd kot pid 1 v kontejnerju. Za gradnjo kontejnerjev obstaja ločeno orodje buildah, na voljo so tudi različna orodja — analogi docker-compose, ki generirajo tudi konfiguracijske datoteke, združljive s Kubernetes, tako da je prehod z Podmana na Kubernetes olajšan kolikor je le mogoče.

Delo s Podmanom

Ker ni swarm mode (predvideva se prehod na Kubernetes, če potrebujete grozd) — se bomo zbrali v ločenih kontejnerjih.

Namestimo podman-compose:

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

Konfiguracijska datoteka za podman se nekoliko razlikuje, tako da je bilo na primer potrebno prenesti ločen odsek volumes neposredno v odsek s storitvami.

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

Результат работы:

# 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

Давайте посмотрим, что он сгенерирует для systemd и kubernetes, для этого надо узнать имя или id пода:

# 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

К сожалению кроме запуска контейнеров сгенерированный юнит для systemd больше ничего не делает (например зачистку старых контейнеров при перезапуске такого сервиса), поэтому такие вещи придется дописывать самостоятельно.

В принципе Podman достаточно для того, чтобы попробовать, что такое контейнеры, перенести старые конфигурации для docker-compose, после чего уйти в сторону Kubernetes, если надо на кластер, либо получить более простую в работе альтернативу Docker.

rkt

Projekt ушел в архив примерно полгода назад из-за того, что его купил RedHat, поэтому не буду останавливаться на нем детальнее. В целом он оставлял весьма неплохое впечатление, однако по сравнению с Docker и тем более с Podman выглядит комбайном. Существовал также дистрибутив CoreOS, построенный на основе rkt (хотя у них изначально был Docker), однако его поддержка также окончилась после покупки RedHat.

Plash

Še один проект, автор которого хотел просто собирать и запускать контейнеры. Судя по документации и коду — автор не следовал стандартам, а просто решил написать свою реализацию, что в принципе и сделал.

Zaključki

Ситуация при наличии Kubernetes складывается весьма интересная: с одной стороны с Docker можно собрать кластер (в swarm mode), с которым даже можно запускать продуктовые среды для клиентов, это особенно актуально для небольших команд (3-5 человек), либо при небольшой общей нагрузке, или же отсутствию желания разбираться в тонкостях настройки Kubernetes в том числе и для высоких нагрузок.

Podman ne zagotavlja popolne združljivosti, vendar ima eno pomembno prednost — združljivost s Kubernetesom, vključno z dodatnimi orodji (buildah in drugi). Zato bom pri izbiri orodja za delo pristopil tako: za majhne ekipe ali ob omejenem proračunu — Docker (z morebitnim načinom swarm), za razvoj zase na osebnem localhostu — Podman s kolegi, za vse ostale pa — Kubernetes.

Nisem prepričan, da se situacija z Dockerjem ne bo spremenila v prihodnosti, saj so vseeno pionirji ter korak za korakom postopoma standardizirajo, ampak ima Podman, kljub vsem svojim pomanjkljivostim (deluje le na Linuxu, ni klasterizacije, sestavljanje in druge aktivnosti — s strani rešitev), bolj jasno prihodnost, zato vabim vse, ki jih zanima, da te ugotovitve razpravljajo v komentarjih.

P.S. 3. avgusta začenjamo „Video tečaj o Dockerju“, kjer boste lahko podrobneje spoznali njegovo delovanje. Razložili bomo vsa njegova orodja: od osnovnih abstrakcij do parametrov omrežja, podrobnosti dela z različnimi operacijskimi sistemi in programskimi jeziki. Spoznali boste tehnologijo in razumeli, kje in kako najbolje uporabljati Docker. Prav tako bomo delili best practice primere.

Cena prednaročila do izida: 5000 r. S programom „Video tečaja o Dockerju“ se lahko seznanite na strani tečaja.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster