Docker og allt, allt, allt

TL;DR: En oversigt og guide til sammenligning af miljøer til kørsel af applikationer i containere. Vi vil se på funktionerne i Docker og andre lignende systemer.

Docker og allt, allt, allt

Lidt historie om, hvor det hele startede

Historie

Den første velkendte metode til isolation af applikationer er chroot. Den tilsvarende systemopkald sikrer ændring af rodmappen — hvilket giver det kaldende program adgang kun til filer inden for denne mappe. Men hvis programmet indeni får superbrugerrettigheder, kan det potentielt "undslippe" chroot og få adgang til det underliggende operativsystem. Ud over ændringen af rodmappen er der heller ikke begrænsninger for andre ressourcer (RAM, CPU) samt netværksadgang.

Den næste metode er at køre et fuldt operativsystem inde i en container ved hjælp af mekanismer fra operativsystemets kerne. I forskellige operativsystemer kaldes denne metode forskellige ting, men essensen er den samme — at køre flere uafhængige operativsystemer, som hver arbejder med den samme kerne, som den primære operativsystem kører på. Dette inkluderer FreeBSD Jails, Solaris Zones, OpenVZ og LXC for Linux. Det sikrer isolation ikke kun af diskplads, men også af andre ressourcer; specifikt kan hver container have begrænsninger for CPU-tid, RAM og netværksbåndbredde. Sammenlignet med chroot er det sværere at bryde ud af en container, da superbrugeren i containeren kun har adgang til containerens indhold, men på grund af nødvendigheden af at holde operativsystemet inde i containeren opdateret og brugen af ældre versioner af kerner (relevant for Linux, i mindre grad FreeBSD) er der en ikke-ubetydelig risiko for "bøvser" i kerneisolationssystemet og adgang til det primære operativsystem.

I stedet for at køre et komplet operativsystem i en container (med init-system, pakkestyring osv.) kan man straks køre applikationer; det vigtigste er at sikre applikationerne denne mulighed (tilgængeligheden af nødvendige biblioteker og andre filer). Denne idé har lagt grundlaget for containerbaseret applikationsvirtualisering, hvor den mest fremtrædende og velkendte repræsentant er Docker. Sammenlignet med tidligere systemer giver mere fleksible isolationsmekanismer sammen med indbygget support til virtuelle netværk mellem containere og overvågning af applikationens tilstand inde i containeren muligheden for at opbygge et samlet miljø af mange fysiske servere til at køre containere — uden behov for manuel håndtering af ressourcer.

Docker

Docker er den mest kendte software til containerisering af applikationer. Det er skrevet i Go-sprog og bruger de indbyggede funktioner i Linux-kernen — cgroups, namespaces, capabilities osv., samt filsystemer som Aufs og andre lignende til at spare diskplads.

Docker og allt, allt, allt
Kilde: wikimedia

Arkitektur

Inden version 1.11 arbejdede Docker som en enkelt service, der udførte alle operationer med containere: download af containerbilleder, start af containere, behandling af API-forespørgsler. Fra version 1.11 blev Docker opdelt i flere dele, der interagerer med hinanden: containerd til at håndtere hele livscyklussen for containere (tildeling af diskplads, download af billeder, netværksarbejde, opstart, installation og overvågning af containerens tilstand) og runC, en container-runtime baseret på brugen af cgroups og andre funktioner i Linux-kernen. Selve docker-servicen forblev, men fungerer nu kun til behandling af API-forespørgsler, der transmitteres til containerd.

Docker og allt, allt, allt

Installasjon og konfigurasjon

Min yndlingsmetode til at installere docker er docker-machine, som udover selve installationen og konfigurationen af docker på fjernservere (inklusive forskellige cloud-løsninger) også giver mulighed for arbejde med filsystemer på de fjernede servere og kan også udføre forskellige kommandoer.

Men siden 2018 er projektet næsten ikke blevet udviklet, så vi vil installere det på den standardmetode for de fleste Linux-distributioner — ved at tilføje et repository og installere de nødvendige pakker.

Denne metoden brukes også ved automatisert installasjon, for eksempel med Ansible eller lignende systemer, men jeg vil ikke gå nærmere inn på det i denne artikkelen.

Installeringen vil bli utført på CentOS 7, og jeg vil bruke en virtuell maskin som server. For installasjonen er det nok å kjøre kommandoene nedenfor:

# 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

Etter installasjonen må du starte tjenesten og legge den til i oppstartsprogrammet:

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

I tillegg kan du opprette en docker-gruppe slik at brukere kan jobbe med docker uten sudo, konfigurere loggføring, aktivere ekstern API-tilgang, og husk å konfigurere brannmuren grundigere (alt som ikke er tillatt er forbudt; i eksemplene over og nedenfor har jeg utelatt dette for enkelhets skyld), men jeg vil ikke gå nærmere inn på det her.

Andre muligheter

I tillegg til den ovennevnte docker maskinen, finnes det også docker registry, et verktøy for lagring av bilder for containere, samt docker compose — et verktøy for automatisering av distribusjon av applikasjoner i containere, hvor YAML-filer brukes for å bygge og konfigurere containere og andre relaterte elementer (for eksempel nettverk, vedvarende filsystemer for datalagring).

Med dette kan du også organisere CICD-pipelines. En annen interessant mulighet er drift i klynge-modus, kjent som swarm mode (til versjon 1.12 var det kjent som docker swarm), som gir mulighet for å samle flere servere til en enhetlig infrastruktur for kjøring av containere. Det er støtte for et virtuelt nettverk over alle servere, innebygd lastbalansering, samt støtte for hemmeligheter for containere.

YAML-filer fra docker compose kan med små endringer brukes for slike klynger, noe som automatiserer vedlikeholdet av små og mellomstore klynger for ulike formål. For store klynger er det å foretrekke å bruke Kubernetes, da kostnadene for drift av swarm-modus kan overstige de som er for Kubernetes. I tillegg til runC kan du installere for eksempel Kata containers

Jobb med Docker

Etter installasjon og konfigurasjon skal vi prøve å sette opp et klynge der vi vil implementere GitLab og Docker Registry for utviklerteamet. Jeg vil bruke tre virtuelle maskiner som servere, der jeg også vil sette opp et distribuert filsystem, GlusterFS, som jeg vil bruke som lagringsplass for docker volum, for eksempel for å kjøre en høyt tilgjengelig versjon av docker registry. Nøkkelkomponentene for oppsettet er: Docker Registry, Postgresql, Redis, GitLab med støtte for GitLab Runner over Swarm. Postgresql vil bli kjørt med klustering. Stolon, så for lagring av data i Postgresql er det ikke nødvendig å bruke GlusterFS. Andre kritiske data vil bli lagret på GlusterFS.

For å sette opp GlusterFS på alle serverne (de kalles node1, node2, node3) må man installere pakkene, tillate brannmurarbeid, og opprette nødvendige 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

Etter installasjonen må man fortsette med konfigurasjonen av GlusterFS fra en node, for eksempel 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

Deretter må man montere det opprettede volumet (kommandoen må utføres på alle serverne):

# mount /srv/docker

Konfigurasjonen av swarm-modus utføres på en av serverne, som vil bli leder, de andre må koble seg til klyngen, så resultatet av kommandoen på den første serveren må kopieres og utføres på de andre.

Den første konfigurasjonen av klyngen, jeg kjører kommandoen 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 kopierer resultatet av den andre kommandoen, utfører det på node2 og node3:

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

Nå er den foreløpige konfigurasjonen av serverne avsluttet, vi begynner på konfigurasjonen av tjenester, kommandoene som skal utføres vil bli sendt fra node1, med mindre annet er angitt.

Først oppretter vi nettverk for containerne:

# 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

Deretter merker vi serverne, dette er nødvendig for å binde noen tjenester til serverne:

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

Videre oppretter vi kataloger for lagring av data til etcd, KV-lageret som er nødvendig for Traefik og Stolon. På samme måte som Postgresql, vil dette være tilknyttede containere til serverne, så vi utfører denne kommandoen på alle serverne:

# mkdir -p /srv/etcd

Så lager vi en fil for konfigurasjon av etcd og bruker den:

00etcd.yml

versjon: '3.7'

tenester:
  etcd1:
    bilde: quay.io/coreos/etcd:latest
    vertsnavn: etcd1
    kommando:
      - 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
    nettverk:
      - etcd
    volum:
      - etcd1vol:/data.etcd
    distribusjon:
      replikar: 1
      plassering:
        restriksjoner: [node.labels.nodename == node1]
  etcd2:
    bilde: quay.io/coreos/etcd:latest
    vertsnavn: etcd2
    kommando:
      - 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
    nettverk:
      - etcd
    volum:
      - etcd2vol:/data.etcd
    distribusjon:
      replikar: 1
      plassering:
        restriksjoner: [node.labels.nodename == node2]
  etcd3:
    bilde: quay.io/coreos/etcd:latest
    vertsnavn: etcd3
    kommando:
      - 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
    nettverk:
      - etcd
    volum:
      - etcd3vol:/data.etcd
    distribusjon:
      replikar: 1
      plassering:
        restriksjoner: [node.labels.nodename == node3]

volum:
  etcd1vol:
    driver: lokal
    driver_opts:
      type: none
      o: bind
      enhet: "/srv/etcd"
  etcd2vol:
    driver: lokal
    driver_opts:
      type: none
      o: bind
      enhet: "/srv/etcd"
  etcd3vol:
    driver: lokal
    driver_opts:
      type: none
      o: bind
      enhet: "/srv/etcd"

nettverk:
  etcd:
    ekstern: true

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

Etter en stund sjekker vi at etcd-klyngen har startet:

# 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 lager kataloger for Postgresql, kommandoen utføres på alle serverne:

# mkdir -p /srv/pgsql

Deretter lager vi en fil for konfigurasjon av Postgresql:

01pgsql.yml

versjon: '3.7'

tjenester:
  pgsentinel:
    bilde: sorintlab/stolon:master-pg10
    kommando:
      - 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
    nettverk:
      - etcd
      - pgsql
    distribuer:
      replikaer: 3
      oppdateringskonfig:
        parallellitet: 1
        forsinkelse: 30s
        rekkefølge: stopp-først
        feilhandling: pause
  pgkeeper1:
    bilde: sorintlab/stolon:master-pg10
    vertsnavn: pgkeeper1
    kommando:
      - 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
    nettverk:
      - etcd
      - pgsql
    miljø:
      - PGDATA=/var/lib/postgresql/data
    volumer:
      - pgkeeper1:/var/lib/postgresql/data
    hemmeligheter:
      - pgsql
      - pgsql_repl
    distribuer:
      replikaer: 1
      plassering:
        begrensninger: [node.labels.nodename == node1]
  pgkeeper2:
    bilde: sorintlab/stolon:master-pg10
    vertsnavn: pgkeeper2
    kommando:
      - 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
    nettverk:
      - etcd
      - pgsql
    miljø:
      - PGDATA=/var/lib/postgresql/data
    volumer:
      - pgkeeper2:/var/lib/postgresql/data
    hemmeligheter:
      - pgsql
      - pgsql_repl
    distribuer:
      replikaer: 1
      plassering:
        begrensninger: [node.labels.nodename == node2]
  pgkeeper3:
    bilde: sorintlab/stolon:master-pg10
    vertsnavn: pgkeeper3
    kommando:
      - 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
    nettverk:
      - etcd
      - pgsql
    miljø:
      - PGDATA=/var/lib/postgresql/data
    volumer:
      - pgkeeper3:/var/lib/postgresql/data
    hemmeligheter:
      - pgsql
      - pgsql_repl
    distribuer:
      replikaer: 1
      plassering:
        begrensninger: [node.labels.nodename == node3]
  postgresql:
    bilde: sorintlab/stolon:master-pg10
    kommando: 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
    nettverk:
      - etcd
      - pgsql
    distribuer:
      replikaer: 3
      oppdateringskonfig:
        parallellitet: 1
        forsinkelse: 30s
        rekkefølge: stopp-først
        feilhandling: tilbakekall

volumer:
  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"

hemmeligheter:
  pgsql:
    fil: "/srv/docker/postgres"
  pgsql_repl:
    fil: "/srv/docker/replica"

nettverk:
  etcd:
    ekstern: true
  pgsql:
    ekstern: true

Vi genererer hemmeligheter, bruker 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

Etter en stund (se på utskriften av kommandoen docker service ls, som viser at alle tjenester er oppe) initialiserer vi PostgreSQL-klyngen:

# 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 sjekker beredskapen til PostgreSQL-klyngen:

# 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 konfigurerer Traefik for å åpne tilgang til containere utenfra:

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 starter Redis Cluster, så vi oppretter katalog for lagring på alle 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 legger til Docker Registry:

06registry.yml

versjon: '3.7'

tenester:
  registry:
    bilde: registry:2.6
    nettverk:
      - traefik
    volum:
      - registry_data: /var/lib/registry
    distribuer:
      replikaer: 1
      plassering:
        restriksjoner: [node.role == manager]
      restart_policy:
        tilstand: on-failure
      etiketter:
        - traefik.enable=true
        - traefik.http.routers.registry.rule=Host(`registry.example.com`)
        - traefik.http.services.registry.loadbalancer.server.port=5000
        - traefik.docker.network=traefik

volum:
  registry_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      enhet: " /srv/docker/registry"

nettverk:
  traefik:
    ekstern: true

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

Og til slutt - GitLab:

08gitlab-runner.yml

versjon: '3.7'

tenester:
  gitlab:
    bilde: gitlab/gitlab-ce:latest
    nettverk:
      - pgsql
      - redis
      - traefik
      - gitlab
    porter:
      - 22222:22
    miljø:
      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
    volum:
      - gitlab_conf: /etc/gitlab
      - gitlab_logs: /var/log/gitlab
      - gitlab_data: /var/opt/gitlab
    distribuer:
      modus: replicated
      replikaer: 1
      plassering:
        restriksjoner:
        - node.role == manager
      etiketter:
        - 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:
    bilde: gitlab/gitlab-runner:latest
    nettverk:
      - gitlab
    volum:
      - gitlab_runner_conf: /etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    distribuer:
      modus: replicated
      replikaer: 1
      plassering:
        restriksjoner:
        - node.role == manager

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

nettverk:
  pgsql:
    ekstern: true
  redis:
    ekstern: true
  traefik:
    ekstern: true
  gitlab:
    ekstern: 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

Den endelige tilstanden til klyngen og tjenestene:

# 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

Hva annet kan forbedres? Det er viktig å konfigurere Traefik for å få containerne til å kjøre over https, legge til tls-kryptering for Postgresql og Redis. Men generelt kan dette allerede overleveres til utviklerne som en PoC. La oss nå se på alternativene til Docker.

Podman

En annen ganske kjent motor for å kjøre containere, gruppert i pods (podder, grupper av containere distribuert sammen). I motsetning til Docker krever det ikke noen tjeneste for å kjøre containere, alt arbeidet gjøres gjennom biblioteket libpod. Det er også skrevet i Go, og trenger en OCI-kompatibel runtime for å kjøre containere, for eksempel runC.

Docker og allt, allt, allt

Arbeidet med Podman ligner generelt på arbeidet med Docker, til og med slik at man kan gjøre dette (utloves av mange som har prøvd, inkludert forfatteren av denne artikkelen):

$ alias docker=podman

og man kan fortsette å jobbe. Generelt er situasjonen med Podman ganske interessant, for hvis tidligere versjoner av Kubernetes jobbet med Docker, så etter standardiseringen av containerverdenen (OCI - Open Container Initiative) og delingen av Docker i containerd og runC, har man utviklet et alternativ til Docker for kjøring i Kubernetes: CRI-O. Podman er i denne sammenhengen et alternativ til Docker, bygget etter prinsippene til Kubernetes, inkludert gruppering av containere, men hovedmålet med prosjektet er å kjøre containere i Docker-stil uten ekstra tjenester. Av åpenbare grunner finnes det ikke swarm mode, ettersom utviklerne tydelig sier at hvis man trenger et klynge, bør man ta Kubernetes.

Installasjon

For installasjon på Centos 7 er det tilstrekkelig å aktivere Extras-repositoriet, hvoretter man kan installere alt med kommandoen:

# yum -y install podman

Andre muligheter

Podman kan generere enheter for systemd, og på den måten løse problemet med å starte containere etter serverrestart. I tillegg er det uttalt at systemd fungerer korrekt som pid 1 i containeren. For å bygge containere er det et eget verktøy buildah, og det finnes også tredjepartsverktøy - alternativer til docker-compose, som også genererer konfigurasjonsfiler som er kompatible med Kubernetes, slik at overgangen fra Podman til Kubernetes er gjort så enkel som mulig.

Arbeid med Podman

Siden det ikke er swarm mode (det antas at man går over til Kubernetes hvis man trenger en klynge) - skal vi samle i separate containere.

Vi installerer podman-compose:

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

Den resulterende konfigurasjonsfilen for podman er litt annerledes, for eksempel måtte vi flytte en egen seksjon for volumer direkte inn i seksjonen med tjenester.

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 arbeidet:

# 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

La oss se hva det vil generere for systemd og kubernetes, for dette må vi finne navnet eller ID-en til pod'en:

# 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

Dessverre gjør ikke den genererte enheten for systemd mer enn å starte containere (for eksempel å rydde opp gamle containere ved omstart av en slik tjeneste), så slike ting må skrives manuelt.

I prinsippet er Podman tilstrekkelig for å prøve hva containere er, overføre gamle konfigurasjoner for docker-compose, for deretter å gå mot Kubernetes hvis det er behov for en klynge, eller få et enklere alternativ til Docker.

rkt

Prosjekt har gått i arkiv cirka et halvt år siden fordi RedHat kjøpte det, så jeg vil ikke gå nærmere inn på det. Generelt sett etterlot det et ganske godt inntrykk, men sammenlignet med Docker og spesielt Podman ser det ut som en kombinasjon. Det fantes også en distribusjon av CoreOS, bygget på rkt (selv om de opprinnelig hadde Docker), men støtten for dette ble også avsluttet etter RedHat-kjøpet.

Plash

Mer et prosjekt, forfatteren av dette ønsket bare å samle og kjøre containere. Ifølge dokumentasjonen og koden – fulgte ikke forfatteren standardene, men bestemte seg for å skrive sin egen implementering, noe han i prinsippet gjorde.

Konklusjoner

Situasjonen med Kubernetes er ganske interessant: på den ene siden kan man bygge en klynge med Docker (i swarm-modus), som man til og med kan bruke til å kjøre produksjonsmiljøer for kunder, noe som er spesielt relevant for små team (3-5 personer), eller ved lav total belastning, eller mangel på lyst til å sette seg inn i detaljene ved å sette opp Kubernetes, også for høy belastning.

Podman tilbyr ikke fullstendig kompatibilitet, men har en viktig fordel — kompatibilitet med Kubernetes, inkludert ekstra verktøy (buildah og andre). Derfor vil jeg nærme meg valget av verktøy på denne måten: for små team eller med et begrenset budsjett — Docker (med mulig swarm mode), for personlig utvikling på localhost — Podman med kolleger, og for alle andre — Kubernetes.

Jeg er ikke sikker på om situasjonen med Docker vil endre seg i fremtiden, de er tross alt pionerer, og de standardiseres gradvis, men Podman, til tross for sine mangler (kun støtte for Linux, ingen klusterløsning, bygging og andre handlinger — eksterne løsninger), har en klarere fremtid. Derfor inviterer jeg alle interesserte til å diskutere disse konklusjonene i kommentarene.

P.S. 3. august lanserer vi "Videokurs om Docker", hvor du kan lære mer om hvordan det fungerer. Vi vil gå gjennom alle verktøyene, fra grunnleggende abstraksjoner til nettverksinnstillinger, detaljene rundt arbeid med ulike OS og programmeringsspråk. Du vil bli kjent med teknologien og forstå hvor og hvordan det er best å bruke Docker. Vi deler også best practice caser.

Prisen for forhåndsbestilling før lansering: 5000 kr. Du kan finne programmet til "Videokurs om Docker" på kurssiden.

Kilde: habr.com

Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere 🔥 Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster