Ejecutando 3 contenedores para 3 dominios en una sola instalación con SSL

Después de varios días trabajando con Let’s Encrypt, aquí está una guía para cualquiera interesado en ejecutar múltiples foros.

1 Propósito

Supongamos que tienes un dominio como este y quieres tener 3 foros:

bbs.antivte.com
cp.antivte.com
ytb.antivte.com

2 Contenedor Discourse

2.1 Preparación

Debes tener 3 archivos app.yml para tus diferentes foros, con nombres como bbs.yml, cp.yml, ytb.yml (puedes elegir los nombres que prefieras). El contenido debe ser similar a este:
Por favor, ten en cuenta que usamos un socket Unix para conectar el nginx externo y el contenedor backend de Discourse, en lugar de escuchar los puertos 80 y 443, y también eliminamos la configuración SSL del contenedor backend.
Ten en cuenta también que aquí tenemos un solo contenedor por foro, no contenedores separados de datos y web para cada foro.

## Este es la plantilla de contenedor Docker Discourse todo-en-uno, independiente
##
## Después de realizar cambios en este archivo, DEBES reconstruir
## /var/discourse/launcher rebuild app
##
## ¡TEN *MUCHO* CUIDADO AL EDITAR!
## ¡LOS ARCHIVOS YAML SON SUPER SENSIBLES A ERRORES EN ESPACIOS EN BLANCO O ALINEACIÓN!
## Visita http://www.yamllint.com/ para validar este archivo según sea necesario

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
## Descomenta estas dos líneas si deseas agregar Let's Encrypt (https)
#  - "templates/web.ssl.template.yml"
#  - "templates/web.letsencrypt.ssl.template.yml"
  - "templates/web.socketed.template.yml"  # <-- Agregado
## ¿Qué puertos TCP/IP debe exponer este contenedor?
## Si deseas que Discourse comparta un puerto con otro servidor web como Apache o nginx,
## consulta https://meta.discourse.org/t/17247 para más detalles
**#expose:**
**#  - "8080:80"   # http**
**#  - "8443:443" # https**

params:
  db_default_text_search_config: "pg_catalog.english"

  ## Establece db_shared_buffers en un máximo del 25% de la memoria total.
  ## Se establecerá automáticamente durante el arranque según la RAM detectada, o puedes sobrescribirlo
  db_shared_buffers: "256MB"

  ## Puede mejorar el rendimiento de ordenación, pero añade uso de memoria por conexión
  #db_work_mem: "40MB"

  ## ¿Qué revisión de Git debe usar este contenedor? (predeterminado: tests-passed)
  #version: tests-passed

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en

  ## ¿Cuántas solicitudes web simultáneas se admiten? Depende de la memoria y los núcleos de CPU.
  ## Se establecerá automáticamente durante el arranque según los CPUs detectados, o puedes sobrescribirlo
  UNICORN_WORKERS: 4

  ## TODO: El nombre de dominio al que responderá esta instancia de Discourse
  ## Obligatorio. Discourse no funcionará con una dirección IP sin nombre.
  DISCOURSE_HOSTNAME: bbs.antivte.com

  ## Descomenta si deseas que el contenedor se inicie con el mismo
  ## nombre de host (opción -h) que se especificó arriba (predeterminado "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: Lista de correos electrónicos separados por comas que serán administradores y desarrolladores
  ## en el registro inicial, ejemplo 'usuario1@ejemplo.com,usuario2@ejemplo.com'
  DISCOURSE_DEVELOPER_EMAILS: 'techempower@163.com'

  ## TODO: El servidor SMTP utilizado para validar nuevas cuentas y enviar notificaciones
  ## La dirección, el nombre de usuario y la contraseña SMTP son obligatorios
  ## ¡ADVERTENCIA: el carácter '#' en la contraseña SMTP puede causar problemas!
  DISCOURSE_SMTP_ADDRESS: smtp.mailgun.org
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: postmaster@mail.antivte.com
  DISCOURSE_SMTP_PASSWORD: "67c9458eb7a6ff11b4db70f097b1b5c3-f7910792-0e7dbcc9"
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (opcional, predeterminado true)

  ## Si agregaste la plantilla de Let's Encrypt, descomenta a continuación para obtener un certificado SSL gratuito
  LETSENCRYPT_ACCOUNT_EMAIL: techempower@163.com

  ## La dirección CDN http o https para esta instancia de Discourse (configurada para extraer)
  ## consulta https://meta.discourse.org/t/14857 para más detalles
  #DISCOURSE_CDN_URL: https://discourse-cdn.ejemplo.com

## El contenedor Docker es sin estado; todos los datos se almacenan en /shared
volumes:
  - volume:
      **host: /var/discourse/shared/bbs**
      guest: /shared
  - volume:
      **host: /var/discourse/shared/bbs/log/var-log**
      guest: /var/log

## Los plugins van aquí
## consulta https://meta.discourse.org/t/19157 para más detalles
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/procourse/procourse-installer

## Cualquier comando personalizado para ejecutar después de la compilación
run:
  - exec: echo "Inicio de comandos personalizados"
  ## Si deseas establecer la dirección de correo electrónico 'De' para tu primer registro, descomenta y cambia:
  ## Después de recibir el primer correo de registro, vuelve a comentar la línea. Solo necesita ejecutarse una vez.
  #- exec: rails r "SiteSetting.notification_email='info@sinconfigurar.discourse.org'"
  - exec: echo "Fin de comandos personalizados"

2.2 Configuración

Crea un script de configuración como este:

#!/usr/bin/env bash
./launcher bootstrap bbs
./launcher bootstrap test
./launcher bootstrap cp
./launcher bootstrap ytb
./launcher start bbs
./launcher  start test
./launcher  start  cp
./launcher  start  ytb

Si ya has usado este script y ejecutado cada contenedor una vez, y editas cualquier contenido de app.yml, debes usar el siguiente comando para que el contenedor tome los cambios:

./launcher rebuild bbs

2.3 Verificación

Verás que los 3 contenedores se ejecutan correctamente y puedes verificar si el socket Unix funciona correctamente:

root@docker-s-1vcpu-2gb-sgp1-01:~# docker ps
CONTAINER ID        IMAGE                 COMMAND             CREATED             STATUS              PORTS               NAMES
9702f94ea9b4        local_discourse/bbs   "/sbin/boot"        9 horas atrás         Up 9 horas                              bbs
dc13c303c38e        local_discourse/cp    "/sbin/boot"        9 horas atrás         Up 9 horas                              cp
dafa592ee16f        local_discourse/ytb   "/sbin/boot"        9 horas atrás         Up 9 horas                              ytb
root@docker-s-1vcpu-2gb-sgp1-01:~#  curl --unix-socket /var/discourse/shared/bbs/nginx.http.sock http:/images/json
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>血栓之家</title>

3 nginx externo

Instala nginx/OpenResty en tu servidor y crea un archivo nginx.conf en cualquier directorio que prefieras. El directorio solo afecta el comando de ejecución de nginx. El contenido de nginx.conf es como este:
Aquí tengo un certificado y clave de Cloudflare y los he colocado en el directorio del host de esta manera:

3.1 nginx.conf

    ssl_certificate      /var/discourse/shared/ssl/antivte.com.cert.pem;
    ssl_certificate_key  /var/discourse/shared/ssl/antivte.com.key.pem;

En el futuro, te explicaré cómo usar certificados y claves automáticos de Let’s Encrypt en nginx externo.

events {
  worker_connections 1024;
}

http {
  # El diccionario compartido "auto_ssl" debe definirse con suficiente espacio de almacenamiento para
  # guardar los datos de tus certificados. 1 MB de almacenamiento guarda certificados para
  # aproximadamente 100 dominios separados.
  lua_shared_dict auto_ssl 1m;
  # El diccionario compartido "auto_ssl_settings" se usa para almacenar temporalmente varias configuraciones
  # como la clave secreta utilizada por el servidor de hooks en el puerto 8999. No lo cambies ni
  # lo omitas.
  lua_shared_dict auto_ssl_settings 64k;

  # Debe definirse un resolvedor DNS para que funcione la estampa OCSP.
  #
  # Este ejemplo usa el servidor DNS de Google. Quizás quieras usar los servidores DNS predeterminados
  # de tu sistema, que se encuentran en /etc/resolv.conf. Si tu red no es compatible con IPv6,
  # puedes desactivar los resultados IPv6 usando la bandera "ipv6=off" (como "resolver 8.8.8.8 ipv6=off").
  resolver 8.8.8.8;
server {
    listen 80; listen [::]:80;
    server_name bbs.antivte.com;  # <-- Cambia esto

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;  listen [::]:443 ssl http2;
    server_name bbs.antivte.com;  # <-- Cambia esto

    ssl_certificate      /var/discourse/shared/ssl/antivte.com.cert.pem;
    ssl_certificate_key  /var/discourse/shared/ssl/antivte.com.key.pem;
#    ssl_dhparam          /var/discourse/shared/standalone/ssl/dhparams.pem;
    ssl_session_tickets off;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;

    http2_idle_timeout 5m; # aumentado desde el predeterminado de 3m

    location / {
        proxy_pass http://unix:/var/discourse/shared/bbs/nginx.http.sock;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80; listen [::]:80;
    server_name cp.antivte.com;  # <-- Cambia esto

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;  listen [::]:443 ssl http2;
    server_name cp.antivte.com;  # <-- Cambia esto

    ssl_certificate      /var/discourse/shared/ssl/antivte.com.cert.pem;
    ssl_certificate_key  /var/discourse/shared/ssl/antivte.com.key.pem;
#    ssl_dhparam          /var/discourse/shared/standalone/ssl/dhparams.pem;
    ssl_session_tickets off;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;

    http2_idle_timeout 5m; # aumentado desde el predeterminado de 3m

    location / {
        proxy_pass http://unix:/var/discourse/shared/cp/nginx.http.sock;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}



server {
    listen 80; listen [::]:80;
    server_name ytb.antivte.com;  # <-- Cambia esto

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;  listen [::]:443 ssl http2;
    server_name ytb.antivte.com;  # <-- Cambia esto

    ssl_certificate      /var/discourse/shared/ssl/antivte.com.cert.pem;
    ssl_certificate_key  /var/discourse/shared/ssl/antivte.com.key.pem;
#    ssl_dhparam          /var/discourse/shared/standalone/ssl/dhparams.pem;
    ssl_session_tickets off;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;

    http2_idle_timeout 5m; # aumentado desde el predeterminado de 3m

    location / {
        proxy_pass http://unix:/var/discourse/shared/ytb/nginx.http.sock;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}



}

3.2 Inicio de nginx

Ve al directorio donde colocaste tu nginx.conf y ejecuta el siguiente comando:

 nginx -p `pwd`/ -c nginx.conf

4. ¡Entonces obtendrás lo que quieres!

¡Woola!

Esto parece ser una versión más complicada del multisitio de Discourse ya disponible. Me pregunto qué ventaja ofrece ejecutar tres contenedores separados compitiendo por los mismos recursos en lugar de ejecutar uno (o dos, en caso de que haya web y datos separados).

Eso es todo en cuanto al tema del presupuesto. Y puedes considerar esto como una etapa previa cuando ejecutas pruebas de prototipos con tus clientes y marketing.

Incluso en la fase de presupuestación, creo que una implementación multisitio sería mucho más sencilla y práctica. De esa manera, tendré menos cosas de las que preocuparme en cuanto a configuración y más tiempo para dedicarme a los requisitos del cliente.

Todo esto se debe a que quiero realizar pruebas y producción en mi servidor individual. Si los puertos 80 y 443 del host están ocupados, no tengo otra forma de lograr todo esto.

¡Muchas gracias por compartir, pero siento que esto necesita mucho más detalle antes de convertirse en un howto, lo estoy recategorizando.