Docker y todo, todo, todo

TL;DR: Una guía general para comparar marcos para ejecutar aplicaciones en contenedores. Se considerarán las capacidades de Docker y otros sistemas similares.

Docker y todo, todo, todo

Un poco de historia de dónde vino todo

historia

El primer método conocido para aislar una aplicación es chroot. La llamada al sistema del mismo nombre garantiza que se cambie el directorio raíz, garantizando así que el programa que la llamó tenga acceso sólo a los archivos dentro de ese directorio. Pero si a un programa se le otorgan privilegios de root internamente, potencialmente puede "escapar" del chroot y obtener acceso al sistema operativo principal. Además, además de cambiar el directorio raíz, otros recursos (RAM, procesador), así como el acceso a la red, no están limitados.

El siguiente método consiste en iniciar un sistema operativo completo dentro de un contenedor, utilizando los mecanismos del núcleo del sistema operativo. Este método se llama de manera diferente en diferentes sistemas operativos, pero la esencia es la misma: iniciar varios sistemas operativos independientes, cada uno de los cuales ejecuta el mismo núcleo en el que se ejecuta el sistema operativo principal. Estos incluyen FreeBSD Jails, Solaris Zones, OpenVZ y LXC para Linux. El aislamiento está garantizado no sólo por el espacio en disco, sino también por otros recursos; en particular, cada contenedor puede tener limitaciones en el tiempo del procesador, la RAM y el ancho de banda de la red. En comparación con chroot, salir del contenedor es más difícil, ya que el superusuario en el contenedor tiene acceso solo al contenido del contenedor, sin embargo, debido a la necesidad de mantener actualizado el sistema operativo dentro del contenedor y al uso de versiones anteriores. de kernels (relevante para Linux, en menor medida para FreeBSD), existe una probabilidad distinta de cero de "romper" el sistema de aislamiento del kernel y obtener acceso al sistema operativo principal.

En lugar de iniciar un sistema operativo completo en un contenedor (con un sistema de inicialización, administrador de paquetes, etc.), puede iniciar aplicaciones inmediatamente, lo principal es brindarles a las aplicaciones esa oportunidad (la presencia de las bibliotecas necesarias y otros archivos). Esta idea sirvió de base para la virtualización de aplicaciones en contenedores, cuyo representante más destacado y conocido es Docker. En comparación con los sistemas anteriores, los mecanismos de aislamiento más flexibles, junto con el soporte integrado para redes virtuales entre contenedores y el seguimiento del estado de las aplicaciones dentro del contenedor, dieron como resultado la capacidad de construir un entorno único y coherente a partir de una gran cantidad de servidores físicos para ejecutar contenedores. sin necesidad de gestión manual de recursos.

Docker

Docker es el software de contenedorización de aplicaciones más famoso. Escrito en lenguaje Go, utiliza las características estándar del kernel de Linux: cgroups, espacios de nombres, capacidades, etc., así como sistemas de archivos Aufs y otros similares para ahorrar espacio en disco.

Docker y todo, todo, todo
Fuente: wikimedia

Arquitectura

Antes de la versión 1.11, Docker funcionaba como un servicio único que realizaba todas las operaciones con contenedores: descargar imágenes para contenedores, ejecutar contenedores y procesar solicitudes de API. A partir de la versión 1.11, Docker se dividió en varias partes que interactúan entre sí: contenedor, para procesar todo el ciclo de vida de los contenedores (asignar espacio en disco, descargar imágenes, trabajar con la red, ejecutar, instalar y monitorear el estado de los contenedores) y runC, el entorno de ejecución de contenedores, basado en el uso de cgroups y otras características del kernel de Linux. El servicio Docker en sí permanece, pero ahora solo sirve para procesar solicitudes de API traducidas a contenedores.

Docker y todo, todo, todo

Instalacion y configuracion

Mi forma favorita de instalar Docker es Docker-machine, que, además de instalar y configurar Docker directamente en servidores remotos (incluidas varias nubes), permite trabajar con sistemas de archivos de servidores remotos y también puede ejecutar varios comandos.

Sin embargo, desde 2018, el proyecto apenas se ha desarrollado, por lo que lo instalaremos de forma estándar para la mayoría de las distribuciones de Linux: agregando un repositorio e instalando los paquetes necesarios.

Este método también se utiliza para la instalación automatizada, por ejemplo utilizando Ansible u otros sistemas similares, pero no lo consideraré en este artículo.

La instalación se realizará en Centos 7, usaré una máquina virtual como servidor, para instalar solo ejecute los siguientes 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

Después de la instalación, debe iniciar el servicio y ponerlo en inicio:

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

Además, puede crear un grupo de Docker, cuyos usuarios podrán trabajar con Docker sin sudo, configurar el registro, habilitar el acceso a la API desde el exterior y no olvide configurar el firewall con mayor precisión (todo lo que no esté permitido). está prohibido en los ejemplos anteriores y siguientes (lo omití por simplicidad y claridad), pero no entraré en más detalles aquí.

Otras características

Además de la máquina Docker mencionada anteriormente, también existe Docker Registry, una herramienta para almacenar imágenes para contenedores, así como Docker Compose, una herramienta para automatizar la implementación de aplicaciones en contenedores, los archivos YAML se utilizan para construir y configurar contenedores. y otras cosas relacionadas (por ejemplo, redes, sistemas de archivos persistentes para almacenar datos).

También se puede utilizar para organizar transportadores para CICD. Otra característica interesante es trabajar en modo clúster, el llamado modo swarm (antes de la versión 1.12 se conocía como docker swarm), que permite ensamblar una única infraestructura a partir de varios servidores para ejecutar contenedores. Hay soporte para una red virtual encima de todos los servidores, hay un equilibrador de carga integrado y soporte para secretos para contenedores.

Los archivos YAML de Docker Compose, con modificaciones menores, se pueden utilizar para dichos clústeres, automatizando completamente el mantenimiento de clústeres pequeños y medianos para diversos fines. Para clústeres grandes, es preferible Kubernetes porque los costos de mantenimiento del modo enjambre pueden exceder los de Kubernetes. Además de runC, puede instalarlo, por ejemplo, como entorno de ejecución de contenedor. Contenedores de katas

Trabajar con Docker

Después de la instalación y configuración, intentaremos montar un clúster en el que implementaremos GitLab y Docker Registry para el equipo de desarrollo. Usaré tres máquinas virtuales como servidores, en las que además implementaré el FS distribuido GlusterFS; lo usaré como almacenamiento de volúmenes de Docker, por ejemplo, para ejecutar una versión tolerante a fallas del registro de Docker. Componentes clave para ejecutar: Docker Registry, Postgresql, Redis, GitLab con soporte para GitLab Runner además de Swarm. Lanzaremos Postgresql con clustering Estolón, por lo que no es necesario utilizar GlusterFS para almacenar datos de Postgresql. Los datos críticos restantes se almacenarán en GlusterFS.

Para implementar GlusterFS en todos los servidores (se llaman nodo1, nodo2, nodo3), debe instalar paquetes, habilitar el 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/fstab

Después de la instalación, el trabajo de configuración de GlusterFS debe continuar desde un nodo, por ejemplo el 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

Luego debe montar el volumen resultante (el comando debe ejecutarse en todos los servidores):

# mount /srv/docker

El modo swarm se configura en uno de los servidores, que será el Líder, el resto tendrá que unirse al cluster, por lo que el resultado de ejecutar el comando en el primer servidor deberá copiarse y ejecutarse en los demás.

Configuración inicial del clúster, ejecuto el comando en el nodo1:

# 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

Copiamos el resultado del segundo comando y lo ejecutamos en el nodo2 y el nodo3:

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

En este punto, se completa la configuración preliminar de los servidores, procedamos a configurar los servicios, los comandos a ejecutar se lanzarán desde el nodo1, a menos que se especifique lo contrario.

Primero que nada, creemos redes para 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 gitlab

Luego marcamos 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 node3

A continuación, creamos directorios para almacenar datos etcd, almacenamiento KV, que es necesario para Traefik y Stolon. Al igual que en Postgresql, estos serán contenedores vinculados a servidores, por lo que ejecutamos este comando en todos los servidores:

# mkdir -p /srv/etcd

A continuación, cree un archivo para configurar etcd y utilícelo:

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

Después de un tiempo, verificamos que el clúster etcd esté activo:

# 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

Creamos directorios para Postgresql, ejecutamos el comando en todos los servidores:

# mkdir -p /srv/pgsql

A continuación, cree un archivo 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

Generamos secretos y utilizamos el 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 pgsql

Después de un tiempo (ver el resultado del comando servicio acoplable lsque todos los servicios estén activos) inicializamos el clúster de 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 la preparación 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 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 traefik

Lanzamos Redis Cluster, para ello creamos un directorio de almacenamiento en todos los 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

Agregar registro Docker:

06registro.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

Y 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

El estado final del clúster y los 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 ejecutar contenedores a través de https, agregue cifrado tls para Postgresql y Redis. Pero, en general, ya se puede entregar a los desarrolladores como PoC. Veamos ahora alternativas a Docker.

Podman

Otro motor bastante conocido para ejecutar contenedores agrupados por pods (pods, grupos de contenedores desplegados juntos). A diferencia de Docker, no requiere ningún servicio para ejecutar contenedores; todo el trabajo se realiza a través de la biblioteca libpod. También escrito en Go, requiere un tiempo de ejecución compatible con OCI para ejecutar contenedores, como runC.

Docker y todo, todo, todo

Trabajar con Podman generalmente recuerda a Docker, hasta el punto de que puedes hacerlo así (como afirman muchos de los que lo han probado, incluido el autor de este artículo):

$ alias docker=podman

y podrás seguir trabajando. En general, la situación con Podman es muy interesante, porque si las primeras versiones de Kubernetes funcionaban con Docker, alrededor de 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 ha estado desarrollando una alternativa a Docker para ejecutarse en Kubernetes: CRI-O. Podman en este sentido es una alternativa a Docker, basada en los principios de Kubernetes, incluida la agrupación de contenedores, pero el objetivo principal del proyecto es lanzar contenedores estilo Docker sin servicios adicionales. Por razones obvias, no existe el modo enjambre, ya que los desarrolladores dicen claramente que si necesita un clúster, elija Kubernetes.

Instalación

Para instalar en Centos 7, simplemente active el repositorio de Extras y luego instale todo con el comando:

# yum -y install podman

Otras características

Podman puede generar unidades para systemd, resolviendo así el problema de iniciar contenedores después de reiniciar el servidor. Además, se declara que systemd funciona correctamente como pid 1 en el contenedor. Hay una herramienta buildah separada para construir contenedores, también hay herramientas de terceros, análogas a docker-compose, que también generan archivos de configuración compatibles con Kubernetes, por lo que la transición de Podman a Kubernetes se simplifica tanto como sea posible.

Trabajando con Podman

Dado que no existe un modo de enjambre (se supone que debemos cambiar a Kubernetes si se necesita un clúster), lo recopilaremos en contenedores separados.

Instale podman-compose:

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

El archivo de configuración resultante para podman es ligeramente diferente, por lo que, por ejemplo, tuvimos que mover una sección de volúmenes separada directamente a la sección de servicios.

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:

# 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

Veamos qué genera para systemd y kubernetes, para ello necesitamos averiguar 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                 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: {}

Sistema:

# 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, además de iniciar contenedores, la unidad generada para systemd no hace nada más (por ejemplo, limpiar contenedores viejos cuando se reinicia dicho servicio), por lo que tendrá que escribir esas cosas usted mismo.

En principio, Podman es suficiente para probar qué son los contenedores, transferir configuraciones antiguas para Docker-Compose y luego pasar a Kubernetes, si necesita un clúster, u obtener una alternativa más fácil de usar a Docker.

rkt

proyecto entró en los archivos Hace unos seis meses debido a que RedHat lo compró, así que no me extenderé más en ello. En general, dejó una muy buena impresión, pero en comparación con Docker y especialmente con Podman, parece una cosechadora. También hubo una distribución CoreOS construida sobre rkt (aunque originalmente tenían Docker), pero esto también terminó en soporte después de la compra de RedHat.

Chapoteo

Más un proyecto, cuyo autor solo quería construir y ejecutar contenedores. A juzgar por la documentación y el código, el autor no siguió los estándares, sino que simplemente decidió escribir su propia implementación, lo cual, en principio, hizo.

Hallazgos

La situación con Kubernetes es bastante interesante: por un lado, con Docker puedes construir un clúster (en modo enjambre), con el que incluso puedes ejecutar entornos de productos para clientes, esto es especialmente cierto para equipos pequeños (3-5 personas). , o con una carga total pequeña , o falta de deseo de comprender las complejidades de configurar Kubernetes, incluso para cargas elevadas.

Podman no ofrece compatibilidad total, pero tiene una ventaja importante: la compatibilidad con Kubernetes, incluidas herramientas adicionales (buildah y otras). Por lo tanto, abordaré la elección de una herramienta para trabajar de la siguiente manera: para equipos pequeños o con un presupuesto limitado: Docker (con un posible modo enjambre), para desarrollar para mí en un host local personal: camaradas Podman y para todos los demás. - Kubernetes.

No estoy seguro de que la situación con Docker no cambie en el futuro, después de todo, son pioneros y también se están estandarizando gradualmente paso a paso, pero Podman, a pesar de todas sus deficiencias (solo funciona en Linux, sin clustering, asamblea y otras acciones son soluciones de terceros) el futuro es más claro, por lo que invito a todos a discutir estos hallazgos en los comentarios.

PS El 3 de agosto lanzamos “Videocurso de Docker", donde podrás conocer más sobre su trabajo. Analizaremos todas sus herramientas: desde abstracciones básicas hasta parámetros de red, matices de trabajar con varios sistemas operativos y lenguajes de programación. Se familiarizará con la tecnología y comprenderá dónde y cómo utilizar mejor Docker. También compartiremos casos de mejores prácticas.

Precio de reserva antes del lanzamiento: 5000 rublos. Puedes ver el programa del curso en vídeo Docker en la página del curso.

Fuente: habr.com

Añadir un comentario