Docker och allt, allt, allt

TL;DR: en översiktsartikel-guide som jämför miljöer för att köra applikationer i containrar. Möjligheterna med Docker och andra liknande system kommer att diskuteras.

Docker och allt, allt, allt

Lite historia, varifrån allt kommer

Historien

Det första allmänt kända sättet att isolera en applikation är chroot. Systemanropet med samma namn ändrar rotkatalogen - på så sätt ges programmet som utför det åtkomst endast till filer inuti den katalogen. Men om programmet inuti ges superanvändarbeteenden kan det potentiellt "fly" från chroot och få tillgång till det underliggande operativsystemet. Dessutom begränsas inte andra resurser (arbetsminne, processor) eller nätverksåtkomst vid ändringen av rotkatalogen.

Nästa metod är att köra ett fullt operativsystem inuti containern, tack vare kärnans mekanismer i operativsystemet. I olika operativsystem kallas denna metod på olika sätt, men principen är densamma - att köra flera oberoende operativsystem, där varje körs med samma kärna som det grundläggande operativsystemet. Detta inkluderar FreeBSD Jails, Solaris Zones, OpenVZ och LXC för Linux. Isoleringen omfattar inte bara diskutrymme, utan även andra resurser; varje container kan ha begränsningar för processorutnyttjande, arbetsminne och nätverksbandbredd. Jämfört med chroot är det svårare att ta sig ut ur containern, eftersom superanvändaren i containern har tillgång endast till innehållet i containern. Men på grund av behovet av att hålla operativsystemet inuti containern uppdaterat och använda äldre kärnversioner (aktuellt för Linux, i mindre utsträckning FreeBSD) finns det en icke-noll chans att "bryta" isoleringssystemet i kärnan och få tillgång till det grundläggande operativsystemet.

Istället för att köra ett fullt operativsystem i en container (med initieringssystem, paketförvaltare osv.) kan man direkt köra applikationer, det viktigaste är att ge applikationerna den möjligheten (genom att ha nödvändiga bibliotek och andra filer). Denna idé ligger till grund för containerbaserad virtualisering av applikationer, där den mest framträdande och kända representanten är Docker. Jämfört med tidigare system har mer flexibla isoleringsmekanismer tillsammans med inbyggt stöd för virtuella nätverk mellan containrar och övervakning av applikationens tillstånd inuti containern möjliggjort skapandet av en enhetlig miljö från ett stort antal fysiska servrar för körning av containrar — utan behov av manuell resursadministration.

Docker

Docker är den mest kända programvaran för containerisering av applikationer. Den är skriven i Go och använder standardfunktioner i Linux-kärnan — cgroups, namespaces, capabilities osv., samt filsystem som Aufs och andra liknande för att spara diskutrymme.

Docker och allt, allt, allt
Källa: wikimedia

Arkitektur

Före version 1.11 fungerade Docker som en enda tjänst som verkställde alla operationer med containrar: nedladdning av bilder för containrar, körning av containrar, behandling av API-förfrågningar. Från och med version 1.11 har Docker delats upp i flera komponenter som interagerar med varandra: containerd, för att hantera hela livscykeln för containrar (tilldelning av diskutrymme, nedladdning av bilder, arbete med nätverket, körning, installation och övervakning av tillståndet för containrar) och runC, en körmiljö för containrar baserad på användning av cgroups och andra funktioner i Linux-kärnan. Tjänsten docker finns fortfarande, men nu används den endast för att behandla API-förfrågningar som översätts till containerd.

Docker och allt, allt, allt

Installation och konfiguration

Mitt favorit sätt att installera docker är docker-machine, som förutom själva installationen och konfigurationen av docker på fjärrservrar (inklusive olika moln) också ger möjlighet att arbeta med filsystem på fjärrservrar, samt kan köra olika kommandon.

Men sedan 2018 har projektet knappt utvecklats, därför kommer vi att göra installationen på det vanliga sättet för de flesta Linux-distributioner — genom att lägga till ett repository och installera nödvändiga paket.

Denna metod används också vid automatiserad installation, till exempel med Ansible eller liknande system, men jag kommer inte att behandla det i denna artikel.

Installationen kommer att genomföras på CentOS 7, jag kommer att använda en virtuell maskin som server, för installationen räcker det att köra kommandona nedan:

# 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

Efter installationen måste tjänsten startas och läggas till vid systemstart:

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

Dessutom kan man skapa en docker-grupp där användare kan arbeta med docker utan sudo, konfigurera loggning, aktivera extern API-åtkomst, och viktigt - konfigurera brandväggen mer specifikt (blockera allt som inte är tillåtet, något jag utelämnar här för enkelhetens skull), men jag kommer inte att gå in mer på detaljer här.

Andra möjligheter

Förutom den nämnda docker maskinen finns det även docker registry, ett verktyg för att lagra bilder för containrar, samt docker compose - ett verktyg för att automatisera distribution av applikationer i containrar, där YAML-filer används för att bygga och konfigurera containrar och andra relaterade objekt (som nätverk och permanenta filsystem för datalagring).

Med det här verktyget kan man också organisera pipelines för CI/CD. En annan intressant funktion är arbete i klusterläge, så kallat swarm mode (känd som docker swarm före version 1.12), vilket möjliggör att skapa en enhetlig infrastruktur för att köra containrar från flera servrar. Det finns stöd för virtuella nätverk över alla servrar, en inbyggd belastningsbalanserare och stöd för hemligheter för containrar.

YAML-filer från docker compose med små ändringar kan användas för sådana kluster, vilket helt automatiserar underhållet av små och medelstora kluster för olika ändamål. För stora kluster är det att föredra att använda Kubernetes, eftersom underhållskostnaderna för swarm mode kan överstiga de för Kubernetes. Utöver runC kan exempelvis Kata containers

Arbete med Docker

Efter installation och konfiguration ska vi försöka skapa ett kluster där vi deployar GitLab och Docker Registry för utvecklarteamet. Jag kommer att använda tre virtuella maskiner som servrar, där jag dessutom kommer att deploya ett distribuerat filsystem GlusterFS, som jag kommer att använda som lagring för docker volumes, till exempel för att köra en redundant version av docker registry. Nyckelkomponenterna för att starta är: Docker Registry, Postgresql, Redis, GitLab med stöd för GitLab Runner ovanpå Swarm. Vi kommer att starta Postgresql med klustring. Stolon, därför behöver vi inte använda GlusterFS för att lagra Postgresql-data. Övriga kritiska data kommer att lagras på GlusterFS.

För att deploya GlusterFS på alla servrar (dessa benämns node1, node2, node3) måste vi installera paket, tillåta brandväggens arbete och skapa nödvändiga kataloger:

# 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

Efter installationen bör arbetet med att konfigurera GlusterFS fortsätta från en nod, till exempel 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

Därefter måste vi montera den skapade volymen (kommandot ska köras på alla servrar):

# mount /srv/docker

Konfigurationen av swarm-läget utförs på en av servrarna, som kommer att vara Leader. De andra måste ansluta sig till klustret, så resultatet av kommandot på den första servern måste kopieras och köras på de övriga.

Den första konfigurationen av klustret, jag kör kommandot på 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

Vi kopierar resultatet av det andra kommandot och kör det på node2 och node3:

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

Så här har vi slutfört den preliminära konfigurationen av servrarna, vi går vidare till konfigurationen av tjänsterna, kommandona för utförande kommer att köras från node1, om inget annat anges.

Det första vi gör är att skapa nätverk för containrar:

# 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

Därefter märker vi servrarna, detta behövs för att koppla vissa tjänster till servrar:

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

Vidare skapar vi kataloger för lagring av etcd-data, KV-lagringen som behövs för Traefik och Stolon. Precis som Postgresql kommer dessa att vara kopplade till servercontainrar, så vi kör detta kommando på alla servrar:

# mkdir -p /srv/etcd

Därefter skapar vi en fil för konfiguration av etcd och tillämpar den:

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

Efter en stund kontrollerar vi att etcd-klustret har startat:

# 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

Vi skapar kataloger för Postgresql, kommandot körs på alla servrar:

# mkdir -p /srv/pgsql

Därefter skapar vi en fil för konfigurationen av 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

Vi genererar hemligheter, tillämpar filen:

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

Efter en stund (vi kollar kommandots utdata docker service ls, att alla tjänster har startats) initierar vi PostgreSQL-klustret:

# 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

Vi kontrollerar att PostgreSQL-klustret är redo:

# 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

Vi konfigurerar Traefik för att öppna åtkomst till containrar utifrån:

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

Vi startar Redis-klustret, för detta skapar vi en katalog för lagring på alla noder:

# 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

Vi lägger till 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

Och slutligen — 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

Det slutgiltiga tillståndet för klustret och tjänsterna:

# 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

Vad mer kan förbättras? Se till att konfigurera Traefik för att köra containrar över https, lägg till tls-kryptering för Postgresql och Redis. Men i stort sett kan det redan lämnas till utvecklarna som en PoC. Låt oss nu titta på alternativa lösningar till Docker.

Podman

En annan ganska känd motor för att köra containrar, grupperade i pods (pods, grupper av containrar som distribueras tillsammans). Till skillnad från Docker kräver den ingen tjänst för att köra containrar, allt arbete utförs via biblioteket libpod. Den är också skriven i Go och kräver en OCI-kompatibel runtime för att köra containrar, exempelvis runC.

Docker och allt, allt, allt

Arbetet med Podman liknar i stort sett arbetet med Docker, till och med så att man kan göra så här (det har rapporterats av många testare, inklusive författaren av denna artikel):

$ alias docker=podman

och man kan fortsätta arbeta. I stort sett är situationen med Podman ganska intressant, för om de tidiga versionerna av Kubernetes arbetade med Docker, så började det ungefär 2015, efter standardiseringen av containervärlden (OCI — Open Container Initiative) och uppdelningen av Docker i containerd och runC, att utvecklas ett alternativ till Docker för att köras i Kubernetes: CRI-O. Podman är i detta avseende ett alternativ till Docker, byggt på principer från Kubernetes, inklusive gruppering av containrar, men det huvudsakliga målet med projektet är att köra containrar i Docker-stil utan extra tjänster. Av uppenbara skäl finns inget swarm-läge, eftersom utvecklarna tydligt säger att om man behöver kluster — ta Kubernetes.

Installation

För installation i Centos 7 räcker det med att aktivera Extras-repot, varefter man installerar allt med kommandot:

# yum -y install podman

Andra möjligheter

Podman kan generera enheter för systemd, vilket löser uppgiften att köra containrar efter en serveromstart. Dessutom har det rapporterats att systemd fungerar korrekt som pid 1 i containern. För att bygga containrar finns det ett separat verktyg, buildah, och det finns också tredjepartsverktyg — analoger till docker-compose, som även genererar konfigurationsfiler som är kompatibla med Kubernetes, så övergången från Podman till Kubernetes har gjorts så enkel som möjligt.

Arbete med Podman

Eftersom det inte finns något swarm-läge (det förutsätts att man går över till Kubernetes om man behöver kluster) — kommer vi att bygga med separata containrar.

Installera podman-compose:

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

Den resulterande konfigurationsfilen för podman skiljer sig något, så t.ex. var man tvungen att flytta en separat sektion för volumes direkt till sektionen med tjänster.

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

Resultat av arbetet:

# 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

Låt oss se vad det genererar för systemd och Kubernetes, först måste vi veta namnet eller ID:t på podden:

# 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

Tyvärr gör den genererade enheten för systemd mer än att bara starta containrar (till exempel rensa gamla containrar vid omstart av en sådan tjänst), så sådana saker måste skrivas in manuellt.

I princip är Podman tillräckligt för att prova vad containrar är, att överföra gamla konfigurationer för docker-compose, och efter det gå vidare till Kubernetes om man behöver ett kluster, annars få en enklare alternativ till Docker.

rkt

Projektet har lagts ned ungefär för sex månader sedan eftersom det köptes av RedHat, så jag kommer inte att gå djupare in på det. Generellt sett gav det ett ganska bra intryck, men jämfört med Docker, och speciellt med Podman, framstår det som en komplicerad lösning. Det fanns också en distribution av CoreOS som byggdes på rkt (även om de ursprungligen hade Docker), men dess stöd upphörde också efter att RedHat köpte det.

Plash

Mer ett projekt, vars skapare ville helt enkelt samla och köra containrar. Enligt dokumentationen och koden följde skaparen inte standarder, utan bestämde sig för att skriva sin egen implementation, vilket han i princip gjorde.

Slutsatser

Situationen med Kubernetes ser mycket intressant ut: å ena sidan kan man med Docker bygga ett kluster (i swarm-läget), vilket till och med kan användas för att köra produktionsmiljöer för kunder, detta är särskilt relevant för små team (3-5 personer) eller vid en låg total belastning, eller om man inte vill hantera nyanserna i installationen av Kubernetes, särskilt för hög belastning.

Podman erbjuder inte fullständig kompatibilitet, men det har en viktig fördel — kompatibilitet med Kubernetes, inklusive ytterligare verktyg (buildah med flera). Därför kommer jag att närma mig valet av verktyg på följande sätt: för små team eller med begränsad budget — Docker (eventuellt i swarm-läge), för personlig utveckling på lokal localhost — Podman med kollegor, och för alla andra — Kubernetes.

Jag är inte säker på att situationen med Docker inte kommer att förändras i framtiden, de är trots allt pionjärer och standardiseras gradvis, men Podman, med alla sina nackdelar (fungerar endast på Linux, ingen klustring, byggande och andra åtgärder — genom tredjepartslösningar), har en tydligare framtid, därför inbjuder jag alla intresserade att diskutera dessa slutsatser i kommentarerna.

P.S. 3 augusti lanserar vi "Videokurs om Docker", där man kan få mer information om dess funktion. Vi kommer att gå igenom alla dess verktyg: från grundläggande abstraktioner till nätverksinställningar, nyanser inom olika operativsystem och programmeringsspråk. Du kommer att bekanta dig med teknologin och förstå var och hur du bäst kan använda Docker. Vi kommer också att dela best practice-fall.

Förhandsbeställning fram till lansering: 5000 kr. Programmet för "Videokurs om Docker" kan ses på kursens sida.

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster