TL;DR: Un artigo xeral: unha guía para comparar ambientes para executar aplicacións en contedores. Consideraranse as posibilidades de Docker e outros sistemas similares.
Unha pequena historia de onde veu todo
Historia
A primeira forma coñecida de illar unha aplicación é chroot. A chamada ao sistema do mesmo nome proporciona un cambio ao directorio raíz, proporcionando así acceso ao programa que o chamou, acceso só aos ficheiros dentro deste directorio. Pero se o programa recibe dereitos de superusuario dentro, pode "escapar" do chroot e acceder ao sistema operativo principal. Ademais, ademais de cambiar o directorio raíz, outros recursos (RAM, procesador), así como o acceso á rede, non están limitados.
O seguinte xeito é lanzar un sistema operativo completo dentro do contedor, utilizando os mecanismos do núcleo do sistema operativo. Este método chámase de forma diferente en diferentes sistemas operativos, pero a esencia é a mesma: executar varios sistemas operativos independentes, cada un dos cales se executa no mesmo núcleo que executa o sistema operativo principal. Isto inclúe cárceres de FreeBSD, Solaris Zones, OpenVZ e LXC para Linux. O illamento ofrécese non só para o espazo en disco, senón tamén para outros recursos, en particular, cada contedor pode ter restricións sobre o tempo do procesador, a memoria RAM e o ancho de banda da rede. En comparación co chroot, deixar o contenedor é máis difícil, xa que o superusuario do contenedor só ten acceso ao interior do contenedor, non obstante, debido á necesidade de manter actualizado o sistema operativo dentro do contenedor e ó uso do núcleo antigo. versións (relevantes para Linux, en menor medida FreeBSD), hai unha probabilidade distinta de cero de romper o sistema de illamento do núcleo e acceder ao sistema operativo principal.
En lugar de lanzar un sistema operativo completo nun contedor (cun sistema de inicialización, un xestor de paquetes, etc.), as aplicacións pódense lanzar inmediatamente, o principal é proporcionarlles esta oportunidade (a presenza das bibliotecas necesarias e outros ficheiros). Esta idea serviu de base para a virtualización de aplicacións en contedores, cuxo representante máis destacado e coñecido é Docker. En comparación cos sistemas anteriores, os mecanismos de illamento máis flexibles, xunto co soporte integrado para redes virtuais entre contedores e o estado das aplicacións dentro dun contedor, deron como resultado a capacidade de construír un único ambiente holístico a partir dun gran número de servidores físicos para executar contedores sen a necesidade dunha xestión manual dos recursos.
Estivador
Docker é o software de contenerización de aplicacións máis coñecido. Escrito na linguaxe Go, utiliza as capacidades habituais do núcleo de Linux: cgroups, espazos de nomes, capacidades, etc., así como sistemas de ficheiros Aufs e outros similares para aforrar espazo no disco.
Fonte: wikimedia
Arquitectura
Antes da versión 1.11, Docker funcionaba como un servizo único que realizaba todas as operacións con contedores: descarga de imaxes para contedores, lanzamento de contedores, procesamento de solicitudes de API. Desde a versión 1.11, Docker dividiuse en varias partes que interactúan entre si: containerd, para xestionar todo o ciclo de vida dos contedores (asignación de espazo en disco, descarga de imaxes, conexión en rede, lanzamento, instalación e seguimento do estado dos contedores) e runC , tempos de execución de contedores, baseados no uso de cgroups e outras funcións do núcleo de Linux. O servizo docker en si permanece, pero agora só serve para procesar as solicitudes de API transmitidas a containerd.
Instalación e configuración
A miña forma favorita de instalar docker é docker-machine, que, ademais de instalar e configurar directamente docker en servidores remotos (incluíndo varias nubes), permíteche traballar cos sistemas de ficheiros de servidores remotos e tamén pode executar varios comandos.
Non obstante, desde 2018, o proxecto apenas se desenvolveu, polo que instalarémolo do xeito habitual para a maioría das distribucións de Linux: engadindo un repositorio e instalando os paquetes necesarios.
Este método tamén se usa para a instalación automatizada, por exemplo, usando Ansible ou outros sistemas similares, pero non o vou considerar neste artigo.
A instalación realizarase en Centos 7, usarei unha máquina virtual como servidor, para instalala, só tes que executar os seguintes comandos:
# 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
Despois da instalación, cómpre iniciar o servizo, poñelo en carga automática:
# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanent
Ademais, podes crear un grupo docker, cuxos usuarios poderán traballar con docker sen sudo, configurar o rexistro, habilitar o acceso á API desde o exterior, non esquezas axustar o firewall (todo o que non está permitido é prohibido nos exemplos anteriores e abaixo - omitiino por simplicidade e visualización), pero non entrarei en máis detalles aquí.
Outras características
Ademais da máquina docker anterior, tamén hai un rexistro docker, unha ferramenta para almacenar imaxes para contedores, así como docker compose - unha ferramenta para automatizar a implantación de aplicacións en contedores, os ficheiros YAML úsanse para construír e configurar contedores e outras cousas relacionadas (por exemplo, redes, sistemas de ficheiros persistentes para almacenar datos).
Tamén se pode usar para organizar canalizacións para CICD. Outra característica interesante é traballar en modo cluster, o chamado modo swarm (antes da versión 1.12 coñecíase como docker swarm), que permite montar unha única infraestrutura desde varios servidores para executar contedores. Hai soporte para unha rede virtual encima de todos os servidores, hai un equilibrador de carga integrado, así como soporte para segredos para contedores.
Os ficheiros YAML de docker compose pódense usar para tales clústeres con pequenas modificacións, automatizando totalmente o mantemento de clústeres pequenos e medianos para varios fins. Para clústeres grandes, é preferible Kubernetes porque os custos de mantemento do modo enxame poden superar os de Kubernetes. Ademais de runC, como ambiente de execución para contedores, podes instalar, por exemplo
Traballando con Docker
Despois da instalación e configuración, tentaremos construír un clúster no que implementaremos GitLab e Docker Registry para o equipo de desenvolvemento. Como servidores, usarei tres máquinas virtuais, nas que implementarei adicionalmente o FS distribuído GlusterFS, empregareino como almacenamento de volumes docker, por exemplo, para executar unha versión a prueba de fallos do rexistro docker. Compoñentes clave para executar: Docker Registry, Postgresql, Redis, GitLab con soporte para GitLab Runner enriba de Swarm. Postgresql lanzarase con clustering
Para implementar GlusterFS en todos os servidores (denomínanse node1, node2, node3), cómpre instalar paquetes, habilitar o firewall, crear os directorios necesarios:
# 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
Despois da instalación, o traballo na configuración de GlusterFS debe continuar desde un nodo, por exemplo nodo1:
# 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
A continuación, cómpre montar o volume resultante (o comando debe executarse en todos os servidores):
# mount /srv/docker
O modo Swarm está configurado nun dos servidores, que será Leader, o resto terá que unirse ao clúster, polo que o resultado de executar o comando no primeiro servidor terá que ser copiado e executado no resto.
Configuración inicial do clúster, executo o comando no 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
Copie o resultado do segundo comando, execute no node2 e node3:
# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377
This node joined a swarm as a manager.
Deste xeito complétase a configuración preliminar dos servidores, imos comezar a configurar os servizos, os comandos a executar lanzaranse desde o nodo1, salvo que se especifique o contrario.
Primeiro de todo, imos crear redes para contedores:
# 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
Despois marcamos os servidores, isto é necesario para vincular algúns servizos aos servidores:
# docker node update --label-add nodename=node1 node1
# docker node update --label-add nodename=node2 node2
# docker node update --label-add nodename=node3 node3
A continuación, creamos directorios para almacenar datos etcd, o almacenamento KV que necesitan Traefik e Stolon. Do mesmo xeito que Postgresql, estes serán contedores ligados a servidores, polo que executamos este comando en todos os servidores:
# mkdir -p /srv/etcd
A continuación, cree un ficheiro para configurar etcd e aplícao:
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
Despois dun tempo, comprobamos que o clúster etcd aumentou:
# 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
Cree directorios para Postgresql, execute o comando en todos os servidores:
# mkdir -p /srv/pgsql
A continuación, cree un ficheiro para configurar 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
Xeramos segredos, aplicamos o ficheiro:
# </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
Algún tempo despois (mira a saída do comando servizo docker lsque todos os servizos aumentaron) inicialice o clúster Postgresql:
# docker exec $(docker ps | awk '/pgkeeper/ {print $1}') stolonctl --cluster-name=stolon-cluster --store-backend=etcdv3 --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 init
Comprobando a preparación do clúster Postgresql:
# docker exec $(docker ps | awk '/pgkeeper/ {print $1}') stolonctl --cluster-name=stolon-cluster --store-backend=etcdv3 --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 status
=== Active sentinels ===
ID LEADER
26baa11d false
74e98768 false
a8cb002b true
=== Active proxies ===
ID
4d233826
9f562f3b
b0c79ff1
=== Keepers ===
UID HEALTHY PG LISTENADDRESS PG HEALTHY PG WANTEDGENERATION PG CURRENTGENERATION
pgkeeper1 true pgkeeper1:5432 true 2 2
pgkeeper2 true pgkeeper2:5432 true 2 2
pgkeeper3 true pgkeeper3:5432 true 3 3
=== Cluster Info ===
Master Keeper: pgkeeper3
===== Keepers/DB tree =====
pgkeeper3 (master)
├─pgkeeper2
└─pgkeeper1
Configuramos traefik para abrir o acceso aos contedores desde o exterior:
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
Iniciamos Redis Cluster, para iso creamos un directorio de almacenamento en todos os nodos:
# 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 <<EOF > /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
Engadir Rexistro Docker:
06 rexistro.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
E finalmente - 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'] = "[email protected]"
gitlab_rails['smtp_password'] = "xxxxxxxxx"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['gitlab_email_from'] = '[email protected]'
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
O estado final do clúster e dos servizos:
# 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
Que máis se pode mellorar? Asegúrate de configurar Traefik para que funcione con contedores https, engade o cifrado tls para Postgresql e Redis. Pero, en xeral, xa podes darllo aos desenvolvedores como PoC. Vexamos agora alternativas a Docker.
podman
Outro motor bastante coñecido para facer funcionar contedores agrupados por vainas (vainas, grupos de contedores despregados xuntos). A diferenza de Docker, non require ningún servizo para executar contedores, todo o traballo realízase a través da biblioteca libpod. Tamén escrito en Go, necesita un tempo de execución compatible con OCI para executar contedores como runC.
Traballar con Podman en xeral aseméllase ao de Docker, na medida en que podes facelo así (reivindicado por moitos que o probaron, incluído o autor deste artigo):
$ alias docker=podman
e podes seguir traballando. En xeral, a situación con Podman é moi interesante, porque se as primeiras versións de Kubernetes funcionaban con Docker, entón desde aproximadamente 2015, despois de estandarizar o mundo dos contedores (OCI - Open Container Initiative) e dividir Docker en containerd e runC, unha alternativa a Docker estase a desenvolver para funcionar en Kubernetes: CRI-O. A este respecto, Podman é unha alternativa a Docker, construída sobre os principios de Kubernetes, incluíndo a agrupación de contedores, pero o obxectivo principal do proxecto é executar contedores ao estilo Docker sen servizos adicionais. Por razóns obvias, non hai modo enxame, xa que os desenvolvedores din claramente que se necesitas un clúster, toma Kubernetes.
Instalación
Para instalar en Centos 7, só tes que activar o repositorio de Extras e, a continuación, instala todo co comando:
# yum -y install podman
Outras características
Podman pode xerar unidades para systemd, resolvendo así o problema de iniciar os contedores despois do reinicio do servidor. Ademais, declárase que systemd funciona correctamente como pid 1 no contedor. Para construír contedores, hai unha ferramenta de compilación separada, tamén hai ferramentas de terceiros: análogos de docker-compose, que tamén xera ficheiros de configuración compatibles con Kubernetes, polo que a transición de Podman a Kubernetes é o máis sinxela posible.
Traballando con Podman
Dado que non hai modo de enxame (suponse que debe cambiar a Kubernetes se é necesario un clúster), ensamblarémolo en contedores separados.
Instalar podman-compose:
# yum -y install python3-pip
# pip3 install podman-compose
O ficheiro de configuración resultante para podman é lixeiramente diferente, xa que, por exemplo, tivemos que mover unha sección de volumes separada directamente á sección de servizos.
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
Resultado do traballo:
# 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
Vexamos que xerará para systemd e kubernetes, para iso necesitamos descubrir o nome ou id do pod:
# 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
Desafortunadamente, ademais do lanzamento de contedores, a unidade xerada para systemd non fai outra cousa (por exemplo, limpar contedores antigos cando se reinicia ese servizo), polo que terás que engadir tales cousas ti mesmo.
En principio, Podman é suficiente para probar cales son os contedores, transferir configuracións antigas para docker-compose e despois ir cara a Kubernetes, se é necesario, nun clúster ou obter unha alternativa máis fácil de usar a Docker.
rkt
Proxecto
Flash
Máis
Descubrimentos
A situación con Kubernetes é moi interesante: por unha banda, con Docker, pódese montar un clúster (en modo enxame), co que incluso se poden executar contornas de produción para clientes, isto é especialmente certo para equipos pequenos (3-5 persoas). ), ou cunha pequena carga xeral , ou a falta de ganas de comprender as complejidades de configurar Kubernetes, incluso para cargas elevadas.
Podman non ofrece compatibilidade total, pero ten unha vantaxe importante: a compatibilidade con Kubernetes, incluíndo ferramentas adicionais (buildah e outras). Polo tanto, abordarei a elección dunha ferramenta para traballar do seguinte xeito: para equipos pequenos ou cun orzamento limitado - Docker (cun posible modo de enxame), para desenvolver por min mesmo nun host local persoal - compañeiros de Podman e para todos os demais. - Kubernetes.
Non estou seguro de que a situación con Docker non cambie no futuro, ao fin e ao cabo, son pioneiros, e tamén se van estandarizando pouco a pouco paso a paso, pero Podman, con todas as súas deficiencias (funciona só en Linux, sen clustering, montaxe). e outras accións son decisións de terceiros) o futuro está máis claro, polo que invito a todos a comentar estes descubrimentos nos comentarios.
PS O 3 de agosto lanzamos "
Custo da reserva antes do lanzamento: 5000 rublos. Pódese atopar o programa "Docker Video Course".
Fonte: www.habr.com