Docker dhe të gjithë, të gjithë, të gjithë

TL;DR: një artikull i përmbledhjes dhe udhëzimit për krahasimin e ambienteve për ekzekutimin e aplikacioneve në kontejnerë. Do të shqyrtohen mundësitë e Docker dhe sistemeve të tjera të ngjashme.

Docker dhe të gjithë, të gjithë, të gjithë

Pak histori, nga erdhi gjithçka

Historia

Mënyra e parë e njohur për izolimin e aplikacionit është chroot. Thirrja e sistemit me të njëjtin emër siguron ndryshimin e katalogut rrënjësor – kështu duke i siguruar programit që e thirri akses vetëm në skedarët brenda këtij katalogu. Por nëse programit brenda i jepen të drejta superpërdoruesi, potencialisht ai mund të "dali" nga chroot dhe të ketë qasje në sistemin operativ kryesor. Po ashtu, me ndërrimin e katalogut rrënjësor nuk kufizohen burimet e tjera (memoria operative, procesori), si dhe qasja në rrjet.

Mënyra tjetër është ekzekutimi i një sistemi operativ të plotë brenda kontejnerit, përmes mekanizmave të bërthamës së sistemit operativ. Në sisteme të ndryshme operative, kjo metodë quhet ndryshe, por thelbi mbetet i njëjtë – ekzekutimi i disa sistemeve të pavarura operative, secila prej të cilave punon me të njëjtin bërthame si ajo e sistemit operativ kryesor. Këtu përfshihen FreeBSD Jails, Solaris Zones, OpenVZ dhe LXC për Linux. Sigurohet izolimi jo vetëm në hapësirën e diskut, por edhe në burime të tjera, në veçanti çdo kontejner mund të ketë kufizime në kohën e procesorit, memorjen operative, dhe gjerësinë e rrjetit. Në krahasim me chroot, dalja nga kontejneri është më e vështirë, pasi superpërdoruesi në kontejner ka qasje vetëm në përbërësit e kontejnerit, megjithatë për shkak të nevojës për të mbajtur sistemin operativ brenda kontejnerit në një gjendje të përditësuar dhe përdorimin e versioneve të vjetra të bërthamave (e rëndësishme për Linux, më pak për FreeBSD) ekziston një probabilitet që nuk është zero për "thyerjen" e sistemit të izolimit të bërthamës dhe marrjen e aksesit në sistemin operativ kryesor.

Në vend të lançimit të një sistemi operativ të plotë në një kontejner (me sistemin e inicializimit, menaxherin e paketave etj.), është e mundur të iniciohen menjëherë aplikacione, gjëja kryesore është të sigurohet që aplikacionet të kenë këtë mundësi (prania e bibliotekave të nevojshme dhe skedarëve të tjerë). Kjo ide shërbeu si bazë për virtualizimin e aplikacioneve me kontejner, përfaqësuesi më i njohur dhe më i njohur i së cilës është Docker. Krahasuar me sistemet e mëparshme, mekanizmat më fleksibël të izolimit së bashku me mbështetje të ndërtuar për rrjete virtuale midis kontejnerëve dhe ndjekjen e gjendjes së aplikacionit brenda kontejnerit ofruan në fund mundësinë e ndërtimit të një mjedisi të vetëm të unifikuar nga një numër i madh serverësh fizikë për të drejtuar kontejnerët - pa nevojën për menaxhim manual të burimeve.

Docker

Docker është programi më i njohur për kontejnerizimin e aplikacioneve. I shkruar në gjuhën Go, përdor mundësitë standarde të bërthamës Linux - cgroups, namespaces, capabilities etj., si dhe sisteme të skedareve Aufs dhe të tjera të ngjashme për kursimin e hapësirës disk.

Docker dhe të gjithë, të gjithë, të gjithë
Burimi: wikimedia

Arkitektura

Para versionit 1.11, Docker funksiononte si një shërbim i vetëm, i cili kryente të gjitha operacionet me kontejnerët: shkarkimi i imazheve për kontejnerët, nisja e kontejnerëve, trajtimi i kërkesave përmes API. Duke filluar nga versioni 1.11, Docker u ndal në disa pjesë që bashkëveprojnë mes tyre: containerd, për të trajtuar gjithë ciklin e jetës së kontejnerëve (ndarjen e hapësirës disk, shkarkimin e imazheve, punën me rrjetin, nisjen, instalimin dhe mb monitoringun e gjendjes së kontejnerëve) dhe runC, mjedisi i ekzekutimit të kontejnerëve, i bazuar në përdorimin e cgroups dhe mundësive të tjera të bërthamës Linux. Shërbimi i vetë docker mbeti, por tani shërben vetëm për trajtimin e kërkesave përmes API, të cilat përkthehen në containerd.

Docker dhe të gjithë, të gjithë, të gjithë

Instalimi dhe konfigurimi

Mënyra ime e preferuar për të instaluar docker është docker-machine, e cila përveç instalimit dhe konfigurimit të docker në servera të largët (përfshirë nube të ndryshme) ofron mundësinë e punës me sistemet e skedareve të serverëve të largët, si dhe mund të kryejë ekzekutimin e komandave të ndryshme.

Megjithatë, që nga viti 2018 projekti ka almost ndaluar zhvillimin, prandaj instalimin do ta kryejmë në mënyrën standarde për shumicën e shpërndarjeve Linux - duke shtuar repository dhe duke instaluar paketat e nevojshme.

Ky ky po, kjo metode aplikohet gjithashtu kur bëhet instalimi automatizuar, për shembull me ndihmën e Ansible ose sistemeve të ngjashme, por në këtë artikull nuk do ta diskutoj.

Instalimi do të bëhet në CentOS 7, për server do të përdor një makinë virtuale, për instalimin mjafton të kryej komandat më poshtë:

# 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

Pas instalimit, duhet të aktivizoni shërbimin, ta vendosni në ngarkesë automatikisht:

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

Shtesë mund të krijoni një grup docker, ku përdoruesit do të mund të punojnë me docker pa sudo, të konfiguroni regjistrimin, të aktivizoni qasjen në API nga jashtë, pa harruar të konfiguroni më saktë firewall-in (ndalohet gjithçka që nuk është e lejuar, në shembujt më sipër dhe më poshtë kam lënë këtë për thjeshtësi dhe qartësi), por këtu nuk do të ndalem më në detaje.

Mundësi të tjera

Përveç docker machine të përmendur, ekziston gjithashtu docker registry, një mjet për ruajtjen e imazheve për kontejnerët, si dhe docker compose — një mjet për automatizimin e shpërndarjes së aplikacioneve në konteinerë, përdoren skedarët YAML për ndërtimin dhe konfigurimin e kontejnerëve dhe gjërave të tjera të lidhura (për shembull rrjetet, sistemet e skedarëve të përhershëm për ruajtjen e të dhënave).

Po ashtu, me ndihmën e tij mund të organizoni pipeline për CICD. Një mundësi tjetër interesante është puna në modin klaster, i njohur si swarm mode (deri në versionin 1.12 ishte i njohur si docker swarm), që lejon grumbullimin e disa serverëve në një infrastrukturë unike për ekzekutimin e kontejnerëve. Ka mbështetje për rrjetin virtual për të gjithë serverët, ekziston një balancues ngarkese i integruar dhe po ashtu mbështetje për sekrete për kontejnerët.

Skedarët YAML nga docker compose me disa ndryshime mund të përdoren për këto klasterë, duke automatizuar plotësisht mirëmbajtjen e klasterëve të vogla dhe të mesëm për qëllime të ndryshme. Për klasterët më të mëdhenj, preferohet përdorimi i Kubernetes, pasi kostot e mirëmbajtjes së swarm mode mund të kalojnë ato të Kubernetes. Përveç runC si mjedis ekzekutimi për kontejnerët, mund të instalohet, për shembull, Kata containers

Puna me Docker

Pas instalimit dhe konfigurimit, do të përpiqemi të ndërtojmë një kluster, në të cilin do të vendosim GitLab dhe Docker Registry për ekipin e zhvilluesve. Si servera do të përdor tre makina virtuale, në të cilat gjithashtu do të vendos një sistem të shpërndarë dosjesh GlusterFS, të cilin do ta përdor si ruajtje për volumin e docker-it, për shembull për të nisur një version të qëndrueshëm të docker registry. Komponentët kyç për nisjen: Docker Registry, Postgresql, Redis, GitLab me mbështetje për GitLab Runner mbi Swarm. Postgresql do ta nisë me klasterizim. Stolon, prandaj për ruajtjen e të dhënave të Postgresql nuk duhet të përdorim GlusterFS. Të gjitha të dhënat e tjera kritike do të ruhen në GlusterFS.

Për vendosjen e GlusterFS në të gjitha serverat (të cilët quhen node1, node2, node3), duhet të instaloni paketat, të lejoni punën e firewal-it, të krijoni katalogët e nevojshëm:

# 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

Pas instalimit, puna për konfigurimin e GlusterFS duhet të vazhdojë nga një nod, për shembull 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

Pastaj duhet të montoni volumin e fituar (komandën duhet ta ekzekutoni në të gjithë serverat):

# mount /srv/docker

Konfigurimi i modalitetit swarm bëhet në një nga serverat, i cili do të jetë Lider, të tjerët do të duhet të bashkohen me klasterin, prandaj rezultatin e ekzekutimit të komandës në serverin e parë duhet ta kopjoni dhe ta ekzekutoni në të tjerët.

Konfigurimi fillestar i klasterit, komandën e nis në 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

Kopjojmë rezultatin e komandës së dytë, e ekzekutojmë në node2 dhe node3:

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

Në këtë pikë, konfigurimi paraprak i serverave ka përfunduar, kalojmë në konfigurimin e shërbimeve, komandat për ekzekutim do të përfundojnë nga node1, përveç nëse tregohet ndryshe.

Së pari, do të krijojmë rrjetet për kontejnerët:

# 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

Pastaj etiketojmë serverat, kjo është e nevojshme për lidhjen e disa shërbimeve me serverat:

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

Më pas krijojmë katalogë për ruajtjen e të dhënave etcd, një ruajtje KV që është e nevojshme për Traefik dhe Stolon. Njëlloj si Postgresql, këto do të jenë konteinerë të lidhur me serverat, prandaj këtë komandë e ekzekutojmë në të gjithë serverat:

# mkdir -p /srv/etcd

Më pas krijojmë një skedë për konfigurimin e etcd dhe e aplikojmë atë:

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

Pas një kohë, kontrollojmë që klasteri etcd është ngritur:

# 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

Krijojmë kataloge për Postgresql, komandën e ekzekutojmë në të gjitha serverat:

# mkdir -p /srv/pgsql

Më pas krijojmë një skedar për konfigurimin e Postgresql:

01pgsql.yml

version: '3.7'

shërbimet:
  pgsentinel:
    imazh: sorintlab/stolon:master-pg10
    komandë:
      - 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
    rrjetet:
      - etcd
      - pgsql
    shpërndarje:
      replika: 3
      update_config:
        parallelizmi: 1
        vonesë: 30s
        renditja: stop-first
        veprimi në rast dështimi: pause
  pgkeeper1:
    imazh: sorintlab/stolon:master-pg10
    hostname: pgkeeper1
    komandë:
      - 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
    rrjetet:
      - etcd
      - pgsql
    ambient:
      - PGDATA=/var/lib/postgresql/data
    volumat:
      - pgkeeper1:/var/lib/postgresql/data
    sekrete:
      - pgsql
      - pgsql_repl
    shpërndarje:
      replika: 1
      vendosje:
        kufizime: [node.labels.nodename == node1]
  pgkeeper2:
    imazh: sorintlab/stolon:master-pg10
    hostname: pgkeeper2
    komandë:
      - 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
    rrjetet:
      - etcd
      - pgsql
    ambient:
      - PGDATA=/var/lib/postgresql/data
    volumat:
      - pgkeeper2:/var/lib/postgresql/data
    sekrete:
      - pgsql
      - pgsql_repl
    shpërndarje:
      replika: 1
      vendosje:
        kufizime: [node.labels.nodename == node2]
  pgkeeper3:
    imazh: sorintlab/stolon:master-pg10
    hostname: pgkeeper3
    komandë:
      - 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
    rrjetet:
      - etcd
      - pgsql
    ambient:
      - PGDATA=/var/lib/postgresql/data
    volumat:
      - pgkeeper3:/var/lib/postgresql/data
    sekrete:
      - pgsql
      - pgsql_repl
    shpërndarje:
      replika: 1
      vendosje:
        kufizime: [node.labels.nodename == node3]
  postgresql:
    imazh: sorintlab/stolon:master-pg10
    komandë: 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
    rrjetet:
      - etcd
      - pgsql
    shpërndarje:
      replika: 3
      update_config:
        parallelizmi: 1
        vonesë: 30s
        renditja: stop-first
        veprimi në rast dështimi: rollback

volumat:
  pgkeeper1:
    drejtues: lokal
    opsionet_e_drejtuesit:
      lloji: asnjë
      o: lidhje
      pajisja: "/srv/pgsql"
  pgkeeper2:
    drejtues: lokal
    opsionet_e_drejtuesit:
      lloji: asnjë
      o: lidhje
      pajisja: "/srv/pgsql"
  pgkeeper3:
    drejtues: lokal
    opsionet_e_drejtuesit:
      lloji: asnjë
      o: lidhje
      pajisja: "/srv/pgsql"

sekrete:
  pgsql:
    skedar: "/srv/docker/postgres"
  pgsql_repl:
    skedar: "/srv/docker/replica"

rrjetet:
  etcd:
    jashtë: e vërtetë
  pgsql:
    jashtë: e vërtetë

Krijojmë sekrete, përdorim skedarin:

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

Pas një kohe (shikojmë rezultatin e komandës docker service ls, që ngjiten të gjitha shërbimet) fillojmë klasterin 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

Kontrollojmë gatishmërinë e klasterit 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

Konfigurojmë traefik për të hapur aksesin në kontejnerët nga jashtë:

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

Nisni Klasterin Redis, për këtë krijoni në të gjitha nyjat një katalog për ruajtje:

# 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

Shtojmë Docker Registry:

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

Dhe dhe final — 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'] = "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
    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

Gjendja përfundimtare e klasterit dhe shërbimeve:

# 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

Çfarë mund të përmirësohet akoma? Sigurohuni të konfiguroni Traefik për të punuar me kontejnerët përmes https, shtoni enkriptimin tls për Postgresql dhe Redis. Por në përgjithësi, mund t'ua dorëzojmë tashmë zhvilluesve si PoC. Tani do të shqyrtojmë alternativat ndaj Docker.

Podman

Një motor tjetër mjaft i njohur për të lançuar kontejnerë, të grupuar në Pod-e (pods, grupe kontejnerësh të vendosur së bashku). Në dallim nga Docker, nuk kërkon asnjë shërbim për të lançuar kontejnerët; e gjithë puna bëhet përmes bibliotekës libpod. Po ashtu është shkruar në Go, dhe kërkon një runtime të përputhshëm me OCI për të lançuar kontejnerët, për shembull runC.

Docker dhe të gjithë, të gjithë, të gjithë

Puna me Podman në përgjithësi i ngjan asaj për Docker, deri në pikën që dikush mund ta bëjë këtë (sipërmarrë nga shumë që e kanë provuar, përfshirë autorin e këtij artikulli):

$ alias docker=podman

dhe mund të vazhdojme të punojmë. Në përgjithësi, situata me Podman është mjaft interesante, sepse nëse versionet e hershme të Kubernetes punonin me Docker, që nga rreth vitit 2015, pas standardizimit të botës së kontejnerëve (OCI — Open Container Initiative) dhe ndarjen e Docker në containerd dhe runC, është zhvilluar një alternativë e Docker për të ankoruar në Kubernetes: CRI-O. Podman në këtë përputhje është një alternativë e Docker, e ndërtuar sipas parimeve të Kubernetes, përfshirë dhe grupimin e kontejnerëve, por qëllimi kryesor i ekzistencës së projektit është lançimi i kontejnerëve në stilin Docker pa shërbime shtesë. Për arsye të qarta, nuk ka modalitetin swarm, pasi zhvilluesit thjesht thonë se nëse keni nevojë për klaster — merrni Kubernetes.

Instalimi

Për të instaluar në Centos 7 mjafton të aktivizoni repository-n Extras, pas së cilës mund ta instaloni me komandën:

# yum -y install podman

Mundësi të tjera

Podman mund të gjenerojë njësitë për systemd, duke zgjidhur kështu problemin e lançimit të kontejnerëve pas rinisjes së serverit. Gjithashtu, është deklaruar funksionimi korrekt i systemd si pid 1 në kontejner. Për ndërtimin e kontejnerëve ka një mjet të veçantë buildah, ka gjithashtu mjete të tjera të jashtme — ekuivalente me docker-compose, që gjithashtu gjenerojnë skedarë konfigurimi të përputhshëm me Kubernetes, kështu që kalimi nga Podman në Kubernetes sa është e mundur, është e thjeshtë.

Puna me Podman

Pasi nuk ka modalitetin swarm (parashikohet kalimi në Kubernetes, nëse nevojitet klaster) — do të grumbullojmë me kontejnerë të ndarë.

Instalojmë podman-compose:

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

Skedari rezultues i konfigurimit për podman paksa ndryshon, kështu që për shembull, na duhej të transferonim një seksion të veçantë volumes direkt në seksionin me shërbimet.

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

Rezultati i punës:

# 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

Le të shohim se çfarë do të generohet për systemd dhe kubernetes, për këtë duhet të dimë emrin ose ID-në e pod-it:

# 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

Për fat të keq, përveç nisjes së konteinerëve, një njësi e gjeneruar për systemd nuk bën asgjë tjetër (për shembull, pastrimin e konteinerëve të vjetër gjatë rinisjes së një shërbimi të tillë), prandaj këto gjëra do të duhet t'i shkruani vetë.

Në parim, Podman është mjaftueshëm për të provuar se çfarë janë konteinerët, për të transferuar konfigurimet e vjetra për docker-compose, pas së cilës mund të kaloni në Kubernetes nëse nevojitet për një grumbull, ose të merrni një alternativë më të thjeshtë për t'u punuar me Docker.

rkt

Projekti ka shkuar në arkiv rreth gjashtë muaj më parë sepse u ble nga RedHat, prandaj nuk do të ndalem në të më hollësisht. Në përgjithësi, ai la një përshtypje mjaft të mirë, por krahasuar me Docker dhe sidomos me Podman, duket si një kombajn. Ekzistonte gjithashtu një shpërndarje CoreOS, e ndërtuar mbi bazën e rkt (pavarësisht se fillimisht kishin Docker), por mbështetja e saj gjithashtu përfundoi pas blerjes nga RedHat.

Plash

Më shumë një projekt, autori i të cilit dëshiroi thjesht të mbledhë dhe të nisë konteinerët. Sipas dokumentacionit dhe kodit — autori nuk ndoqi standardet, por thjesht vendosi të shkruajë implementimin e tij, gjë që në parim e bëri.

Përfundimet

Situata kur ka Kubernetes është shumë interesante: nga njëra anë, me Docker mund të ndërtohet një grumbull (në modin swarm), me të cilin madje mund të nisen mjedise produkti për klientët, kjo është veçanërisht relevante për ekipet e vogla (3-5 persona), ose në rastin e një ngarkese të vogël të përgjithshme, ose mungesës së dëshirës për të kuptuar nuancat e konfigurimit të Kubernetes, përfshirë edhe për ngarkesa të larta.

Podman nuk ofron pajtueshmëri të plotë, por ka një përparësi të rëndësishme — pajtueshmërinë me Kubernetes, përfshirë edhe mjetet shtesë (buildah dhe të tjerë). Prandaj, për të zgjedhur mjetin për punë do ta qas kështu: për ekipe të vogla, ose me buxhet të kufizuar — Docker (me mode të mundshme swarm), për zhvillim për usevet personal në localhost — Podman me shokë, dhe për të gjithë të tjerët — Kubernetes.

Nuk jam i sigurt se situata me Docker do të ndryshojë në të ardhmen, pas të gjithash ata janë pionierë, dhe gjithashtu po standardizohen ngadalë për çdo hap, por e ardhmja e Podman, përkundër të gjitha mangësive të tij (funksionon vetëm në Linux, nuk ka klasterizim, ndërtimi dhe veprime të tjera janë me zgjidhje të jashtme) është më e qartë, prandaj ftoj të gjithë të interesuarit të diskutojnë këto përfundime në komente.

P.S. 3 gusht, nisemi me "visa1", ku do të mund të mësoni më shumë rreth funksionimit të tij. Ne do të analizojmë të gjitha mjetet e tij: nga abstraksionet kryesore deri te parametrat e rrjetit, nuancat e punës me sisteme të ndryshme operative dhe gjuhët e programimit. Do të njiheni me teknologjinë dhe do të kuptoni se ku dhe si është më mirë të përdorni Docker. Gjithashtu do të ndajmë best practice rastet.

Çmimi i parakohshëm deri në lëshim: 5000 r. Mund të njiheni me programin "Videokursit për Docker" në faqen e kursit.

Burimi: habr.com

Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS 🔥 Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS | ProHoster