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.

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.

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.

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.ioEfter 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 --permanentDessutom 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
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. , 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/fstabEfter 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 dockerDärefter måste vi montera den skapade volymen (kommandot ska köras på alla servrar):
# mount /srv/dockerKonfigurationen 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 managerVi 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 gitlabDä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 node3Vidare 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/etcdDä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 etcdEfter 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 healthyVi skapar kataloger för Postgresql, kommandot körs på alla servrar:
# mkdir -p /srv/pgsqlDä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: trueVi 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 pgsqlEfter 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 initVi 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 traefikVi startar Redis-klustret, för detta skapar vi en katalog för lagring på alla noder:
# mkdir -p /srv/redis05redis.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 redisVi 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 registryOch 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 gitlabDet 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/tcpVad 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.

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=podmanoch 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 podmanAndra 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-composeDen 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 upResultat 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_1Lå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 db40ab8bf84bKubernetes:
# 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.targetTyvä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 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 , 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 "", 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 .
Källa: habr.com
