TL;DR: artículo guía de comparación sobre entornos para ejecutar aplicaciones en contenedores. Se abordarán las funcionalidades de Docker y otros sistemas similares.

Un poco de historia, de dónde proviene todo esto
Historia
La primera forma conocida de aislamiento de aplicaciones es chroot. La llamada llamada del sistema permite cambiar el directorio raíz, proporcionando así a un programa que lo invoca acceso solo a los archivos dentro de ese directorio. Sin embargo, si se le dan privilegios de superusuario, potencialmente puede "escapar" de chroot y obtener acceso al sistema operativo principal. Además, la simple modificación del directorio raíz no limita otros recursos (memoria RAM, CPU), ni el acceso a la red.
La siguiente forma es ejecutar un sistema operativo completo dentro de un contenedor, gracias a los mecanismos del núcleo del sistema operativo. En diferentes sistemas operativos, este método se llama de diversas maneras, pero la esencia es la misma: ejecutar varios sistemas operativos independientes, cada uno de los cuales funciona con el mismo núcleo que el sistema operativo principal. Esto incluye FreeBSD Jails, Solaris Zones, OpenVZ y LXC para Linux. Se asegura el aislamiento no solo por espacio en disco, sino también por otros recursos; en particular, cada contenedor puede tener restricciones en el tiempo de CPU, la memoria RAM y la ancho de banda de red. En comparación con chroot, salir del contenedor es más difícil, ya que el superusuario dentro del contenedor solo tiene acceso a los componentes del contenedor. Sin embargo, debido a la necesidad de mantener el sistema operativo dentro del contenedor actualizado y al uso de versiones antiguas de núcleos (lo cual es relevante para Linux, en menor medida para FreeBSD), existe una probabilidad no nula de "romper" el sistema de aislamiento del núcleo y obtener acceso al sistema operativo principal.
En lugar de iniciar un sistema operativo completo en un contenedor (con un sistema de inicialización, gestor de paquetes, etc.), se pueden lanzar las aplicaciones directamente, siempre que se les brinde esa posibilidad (disponibilidad de las bibliotecas requeridas y otros archivos). Esta idea sirvió como base para la virtualización de aplicaciones en contenedores, cuyo representante más destacado y conocido es Docker. Comparado con los sistemas anteriores, los mecanismos de aislamiento más flexibles, junto con el soporte incorporado para redes virtuales entre contenedores y el seguimiento del estado de la aplicación dentro del contenedor, permitieron construir un entorno cohesivo a partir de un gran número de servidores físicos para ejecutar contenedores, sin necesidad de gestión manual de recursos.
Docker
Docker es el software más conocido para la contenerización de aplicaciones. Está escrito en el lenguaje Go, utiliza las capacidades nativas del núcleo de Linux: cgroups, namespaces, capabilities, etc., así como sistemas de archivos Aufs y otros similares para ahorrar espacio en disco.

Fuente: wikimedia
Arquitectura
Hasta la versión 1.11, Docker funcionaba como un solo servicio que realizaba todas las operaciones con contenedores: descarga de imágenes de contenedores, lanzamiento de contenedores, procesamiento de solicitudes API. A partir de la versión 1.11, Docker se fragmentó en varias partes que interactúan entre sí: containerd, para manejar todo el ciclo de vida de los contenedores (asignación de espacio en disco, descarga de imágenes, trabajo con redes, lanzamiento, instalación y supervisión del estado de los contenedores) y runC, un entorno de ejecución de contenedores basado en el uso de cgroups y otras capacidades del núcleo de Linux. El servicio Docker se mantuvo, pero ahora solo se usa para procesar las solicitudes API que se transmiten a containerd.

Instalación y configuración
Mi forma favorita de instalar Docker es usando docker-machine, que además de la instalación y configuración de Docker en servidores remotos (incluyendo diversas nubes), permite trabajar con los sistemas de archivos de esos servidores remotos y también puede ejecutar varios comandos.
Sin embargo, desde 2018 el proyecto casi no ha evolucionado, así que realizaremos la instalación de la manera estándar para la mayoría de las distribuciones de Linux: añadiendo el repositorio e instalando los paquetes necesarios.
Este método también se aplica durante la instalación automatizada, por ejemplo, utilizando Ansible u otros sistemas similares, pero no lo abordaré en este artículo.
La instalación se llevará a cabo en CentOS 7, utilizando una máquina virtual como servidor; para la instalación, basta con ejecutar los comandos a continuación:
# 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.ioDespués de la instalación, es necesario iniciar el servicio y configurarlo para el arranque automático:
# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanentAdicionalmente, se puede crear un grupo docker, cuyas cuentas de usuario podrán trabajar con docker sin sudo, configurar el registro, habilitar el acceso a la API desde el exterior, y no olvidar ajustar más precisamente el firewall (en los ejemplos arriba y abajo lo he omitido por simplicidad y claridad), pero aquí no profundizaré más.
Otras capacidades
Además de la máquina docker mencionada, también existe el registro de docker, una herramienta para almacenar imágenes de contenedores, así como docker compose, una herramienta para automatizar la implementación de aplicaciones en contenedores, que utiliza archivos YAML para la creación y configuración de contenedores y otros componentes relacionados (por ejemplo, redes, sistemas de archivos persistentes para almacenamiento de datos).
También se puede organizar flujos de trabajo para CICD. Otra capacidad interesante es el modo clúster, conocido como swarm mode (hasta la versión 1.12 se conocía como docker swarm), que permite construir una infraestructura unificada a partir de varios servidores para ejecutar contenedores. Hay soporte para una red virtual sobre todos los servidores, balanceador de carga incorporado, así como soporte para secretos de contenedores.
Los archivos YAML de docker compose, con ligeras modificaciones, pueden ser utilizados para tales clústeres, automatizando completamente el mantenimiento de clústeres pequeños y medianos para diversos propósitos. Para clústeres grandes, es preferible utilizar Kubernetes, ya que los costos de mantenimiento del modo swarm pueden superar los de Kubernetes. Además de runC, como entorno de ejecución de contenedores, se puede instalar por ejemplo
Adecuado para carga alta
Después de instalar y configurar, intentaremos crear un clúster donde desplegaremos GitLab y Docker Registry para el equipo de desarrollo. Utilizaré tres máquinas virtuales como servidores, en las que además implementaré un sistema de archivos distribuido GlusterFS, que usaré como almacenamiento para los volúmenes de Docker, por ejemplo, para ejecutar una versión tolerante a fallos del docker registry. Los componentes clave para el despliegue son: Docker Registry, Postgresql, Redis, GitLab con soporte para GitLab Runner sobre Swarm. Ejecutaremos Postgresql con clustering. , por lo que no es necesario usar GlusterFS para almacenar los datos de Postgresql. Los demás datos críticos se almacenarán en GlusterFS.
Para desplegar GlusterFS en todos los servidores (que se denominan node1, node2, node3), es necesario instalar los paquetes, permitir la actividad del firewall, y crear los 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/fstabDespués de la instalación, el trabajo de configuración de GlusterFS debe continuar desde un nodo, por ejemplo, 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 dockerLuego hay que montar el volumen resultante (deben ejecutar el comando en todos los servidores):
# mount /srv/dockerLa configuración del modo swarm se realiza en uno de los servidores, que será el Leader, los demás deben unirse al clúster, por lo que el resultado de la ejecución del comando en el primer servidor debe copiarse y ejecutarse en los demás.
Configuración inicial del clúster, ejecutando el comando en 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 managerCopiamos el resultado del segundo comando y lo ejecutamos en node2 y node3:
# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377
This node joined a swarm as a manager.Con esto, la configuración preliminar de los servidores ha terminado, procedemos a la configuración de los servicios, los comandos se ejecutarán desde node1, a menos que se indique lo contrario.
Primero crearemos redes para los contenedores:
# 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 gitlabLuego etiquetamos los servidores, esto es necesario para vincular algunos servicios a los servidores:
# docker node update --label-add nodename=node1 node1
# docker node update --label-add nodename=node2 node2
# docker node update --label-add nodename=node3 node3A continuación, creamos directorios para el almacenamiento de datos etcd, un almacén KV necesario para Traefik y Stolon. Al igual que Postgresql, estos serán contenedores vinculados a los servidores, por lo que este comando se ejecuta en todos los servidores:
# mkdir -p /srv/etcdA continuación, creamos un archivo para la configuración de etcd y lo aplicamos:
00etcd.yml
versión: '3.7'
servicios:
etcd1:
imagen: quay.io/coreos/etcd:latest
nombre de host: etcd1
comando:
- 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
redes:
- etcd
volúmenes:
- etcd1vol:/data.etcd
implementación:
réplicas: 1
colocación:
restricciones: [node.labels.nodename == node1]
etcd2:
imagen: quay.io/coreos/etcd:latest
nombre de host: etcd2
comando:
- 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
redes:
- etcd
volúmenes:
- etcd2vol:/data.etcd
implementación:
réplicas: 1
colocación:
restricciones: [node.labels.nodename == node2]
etcd3:
imagen: quay.io/coreos/etcd:latest
nombre de host: etcd3
comando:
- 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
redes:
- etcd
volúmenes:
- etcd3vol:/data.etcd
implementación:
réplicas: 1
colocación:
restricciones: [node.labels.nodename == node3]
volúmenes:
etcd1vol:
controlador: local
opciones_controlador:
tipo: ninguno
o: bind
dispositivo: "/srv/etcd"
etcd2vol:
controlador: local
opciones_controlador:
tipo: ninguno
o: bind
dispositivo: "/srv/etcd"
etcd3vol:
controlador: local
opciones_controlador:
tipo: ninguno
o: bind
dispositivo: "/srv/etcd"
redes:
etcd:
externo: verdadero# docker stack deploy --compose-file 00etcd.yml etcdDespués de un tiempo, verificamos que el clúster etcd se ha levantado:
# 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 healthyCreamos los directorios para Postgresql, ejecutamos el comando en todos los servidores:
# mkdir -p /srv/pgsqlA continuación, creamos un archivo para la configuración de Postgresql:
01pgsql.yml
versión: '3.7'
servicios:
pgsentinel:
imagen: sorintlab/stolon:master-pg10
comando:
- 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
redes:
- etcd
- pgsql
despliegue:
réplicas: 3
configuración_actualización:
paralelismo: 1
retraso: 30s
orden: stop-first
acción_fallo: pause
pgkeeper1:
imagen: sorintlab/stolon:master-pg10
nombre_host: pgkeeper1
comando:
- 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
redes:
- etcd
- pgsql
entorno:
- PGDATA=/var/lib/postgresql/data
volúmenes:
- pgkeeper1:/var/lib/postgresql/data
secretos:
- pgsql
- pgsql_repl
despliegue:
réplicas: 1
ubicación:
restricciones: [node.labels.nodename == node1]
pgkeeper2:
imagen: sorintlab/stolon:master-pg10
nombre_host: pgkeeper2
comando:
- 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
redes:
- etcd
- pgsql
entorno:
- PGDATA=/var/lib/postgresql/data
volúmenes:
- pgkeeper2:/var/lib/postgresql/data
secretos:
- pgsql
- pgsql_repl
despliegue:
réplicas: 1
ubicación:
restricciones: [node.labels.nodename == node2]
pgkeeper3:
imagen: sorintlab/stolon:master-pg10
nombre_host: pgkeeper3
comando:
- 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
redes:
- etcd
- pgsql
entorno:
- PGDATA=/var/lib/postgresql/data
volúmenes:
- pgkeeper3:/var/lib/postgresql/data
secretos:
- pgsql
- pgsql_repl
despliegue:
réplicas: 1
ubicación:
restricciones: [node.labels.nodename == node3]
postgresql:
imagen: sorintlab/stolon:master-pg10
comando: 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
redes:
- etcd
- pgsql
despliegue:
réplicas: 3
configuración_actualización:
paralelismo: 1
retraso: 30s
orden: stop-first
acción_fallo: rollback
volúmenes:
pgkeeper1:
controlador: local
opciones_controlador:
tipo: none
o: bind
dispositivo: "/srv/pgsql"
pgkeeper2:
controlador: local
opciones_controlador:
tipo: none
o: bind
dispositivo: "/srv/pgsql"
pgkeeper3:
controlador: local
opciones_controlador:
tipo: none
o: bind
dispositivo: "/srv/pgsql"
secretos:
pgsql:
archivo: "/srv/docker/postgres"
pgsql_repl:
archivo: "/srv/docker/replica"
redes:
etcd:
externo: true
pgsql:
externo: trueGenerando secretos, aplicando archivo:
# </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 pgsqlDespués de un tiempo (mira la salida del comando docker service ls, que todos los servicios se estén ejecutando) inicializamos el 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 initVerificamos la disponibilidad del 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 el acceso a los contenedores desde el 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 traefikIniciamos el clúster de Redis, para ello creamos en todos los nodos un directorio para almacenamiento:
# 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 redisAñadimos Docker Registry:
06registry.yml
versión: '3.7'
servicios:
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.ejemplo.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 registryY por último, GitLab:
08gitlab-runner.yml
versión: '3.7'
servicios:
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@ejemplo.com"
gitlab_rails['smtp_password'] = "xxxxxxxxx"
gitlab_rails['smtp_domain'] = "ejemplo.com"
gitlab_rails['gitlab_email_from'] = 'noreply@ejemplo.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.ejemplo.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.ejemplo.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 gitlabEstado final del clúster y servicios:
# 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¿Qué más se puede mejorar? Asegúrese de configurar Traefik para que los contenedores funcionen a través de https, y agregar cifrado tls para Postgresql y Redis. Pero en general ya se puede entregar a los desarrolladores como PoC. Ahora, veamos las alternativas a Docker.
Podman
Otro motor bastante conocido para ejecutar contenedores, agrupados en pods (pods, grupos de contenedores desplegados juntos). A diferencia de Docker, no requiere ningún servicio para ejecutar contenedores, todo se gestiona a través de la biblioteca libpod. También está escrito en Go y necesita un runtime compatible con OCI para ejecutar contenedores, como runC.

El trabajo con Podman recuerda en general al de Docker, hasta el punto de que se puede hacer así (lo han declarado muchos que lo han probado, incluido el autor de este artículo):
$ alias docker=podmany se puede seguir trabajando. En general, la situación con Podman es bastante interesante, ya que si las versiones anteriores de Kubernetes trabajaban con Docker, desde aproximadamente 2015, después de la estandarización del mundo de los contenedores (OCI — Open Container Initiative) y la división de Docker en containerd y runC, se está desarrollando una alternativa a Docker para ejecutar en Kubernetes: CRI-O. Podman, en este sentido, es una alternativa a Docker construida según los principios de Kubernetes, incluida la agrupación de contenedores, pero el objetivo principal de este proyecto es ejecutar contenedores al estilo Docker sin servicios adicionales. Por razones comprensibles, no hay modo swarm, ya que los desarrolladores dejan claro que si se necesita un clúster — tome Kubernetes.
Instalación
Para instalar en Centos 7, solo necesita activar el repositorio Extras, después de lo cual puede instalar todo con el comando:
# yum -y install podmanOtras capacidades
Podman puede generar unidades para systemd, resolviendo así el problema de iniciar contenedores después del reinicio del servidor. Además, se ha declarado un funcionamiento correcto de systemd como pid 1 en el contenedor. Para construir contenedores, existe una herramienta separada llamada buildah, y también hay herramientas de terceros — análogos de docker-compose, que generan, además, archivos de configuración compatibles con Kubernetes, de modo que la transición de Podman a Kubernetes se simplifica tanto como sea posible.
Trabajo con Podman
Dado que no hay modo swarm (se supone que se debe cambiar a Kubernetes si se necesita un clúster) — agruparemos contenedores individuales.
Instalamos podman-compose:
# yum -y install python3-pip
# pip3 install podman-composeEl archivo de configuración resultante para podman es un poco diferente, por ejemplo, se tuvo que mover una sección separada de volumes directamente a la sección de servicios.
gitlab-podman.yml
versión: '3.7'
servicios:
gitlab:
imagen: gitlab/gitlab-ce:latest
nombre_anfitrión: gitlab.ejemplo.com
reiniciar: a menos que se detenga
entorno:
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['gitlab_shell_ssh_port'] = 22222
puertos:
- "80:80"
- "22222:22"
volúmenes:
- /srv/podman/gitlab/conf:/etc/gitlab
- /srv/podman/gitlab/data:/var/opt/gitlab
- /srv/podman/gitlab/logs:/var/log/gitlab
redes:
- gitlab
gitlab-runner:
imagen: gitlab/gitlab-runner:alpine
reiniciar: a menos que se detenga
depende_de:
- gitlab
volúmenes:
- /srv/podman/gitlab/runner:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
redes:
- gitlab
redes:
gitlab:# podman-compose -f gitlab-runner.yml -d upResultado de la operación:
# 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_1Veamos qué generará para systemd y kubernetes, para esto necesitamos saber el nombre o id del pod:
# 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.targetDesafortunadamente, además de iniciar contenedores, la unidad generada para systemd no hace nada más (por ejemplo, no limpia contenedores antiguos al reiniciar este servicio), por lo que estas cosas deberán añadirse manualmente.
En principio, Podman es suficiente para probar qué son los contenedores, trasladar configuraciones antiguas de docker-compose, después de lo cual se puede optar por Kubernetes, si se necesita un clúster, o tener una alternativa más simple a Docker.
rkt
Proyecto hace aproximadamente seis meses porque fue comprado por RedHat, así que no me detendré en él más a fondo. En general, dejaba una impresión bastante buena, sin embargo, en comparación con Docker y aún más con Podman parece un monstruo. También existió una distribución de CoreOS, construida sobre rkt (aunque originalmente tenían Docker), sin embargo, su soporte también terminó después de la compra de RedHat.
Plash
Más , cuyo autor simplemente quería compilar y ejecutar contenedores. Según la documentación y el código, el autor no siguió los estándares, sino que decidió escribir su propia implementación, lo que de hecho hizo.
Conclusiones
La situación con Kubernetes es bastante interesante: por un lado, con Docker se puede construir un clúster (en modo swarm), con el que incluso se pueden ejecutar entornos de producción para los clientes, lo cual es especialmente relevante para equipos pequeños (3-5 personas), o cuando hay una carga general baja, o simplemente cuando no se desea lidiar con las complejidades de la configuración de Kubernetes, incluso para cargas altas.
Podman no proporciona compatibilidad total, pero tiene una ventaja importante: es compatible con Kubernetes y también con herramientas adicionales (buildah y otras). Por lo tanto, mi enfoque para elegir una herramienta será el siguiente: para equipos pequeños o con presupuesto limitado, Docker (con posible modo swarm), para desarrollo personal en localhost, Podman con amigos, y para todos los demás, Kubernetes.
No estoy seguro de que la situación con Docker no cambie en el futuro, ya que son pioneros y están estandarizándose poco a poco. Sin embargo, a pesar de todas sus desventajas (funciona solo en Linux, no tiene clustering, construcciones y otras acciones deben hacerse con soluciones externas), el futuro de Podman parece más claro. Por ello, invito a todos los interesados a discutir estas conclusiones en los comentarios.
P.D. El 3 de agosto lanzamos "" donde podrás aprender más sobre su funcionamiento. Analizaremos todas sus herramientas: desde las abstracciones básicas hasta los parámetros de red, matices de trabajo con diferentes sistemas operativos y lenguajes de programación. Te familiarizarás con la tecnología y entenderás dónde y cómo utilizar mejor Docker. También compartiremos casos de buenas prácticas.
El precio de la preorden hasta el lanzamiento es: 5000 r. Puedes consultar el programa del "Curso de video sobre Docker" .
Fuente: habr.com
