Servidor Commento nativo com Docker Compose

Nota: esta é uma tradução da minha postagem (em inglês), descrevendo a implementação do servidor de comentários usado no mesmo site em que o original está localizado.


Versão TL; DR: Desenvolvi a configuração do servidor Commento, que é fácil e simplesmente implementada no modo semiautomático. Copie este repositório para você do GitHub e siga as instruções no README .

Há algum tempo, eu queria irresistivelmente mudar o Disqus - que é talvez o sistema mais comum para adicionar comentários às páginas - para um Commento gratuito e aberto.


Por que Commento?


O problema com o Disqus, como muitos outros produtos "gratuitos", é que, nesse caso, o produto é o usuário - ou seja, você. Além disso, o Disqus “enriquece” todas as páginas em que é usado com megabytes de scripts e mais de cem solicitações HTTP adicionais.


Além disso, sua versão gratuita mostra anúncios dos quais você pode pagar "apenas" por US $ 9 por mês (plano Plus). Isso por si só é suficiente para querer encontrar algo melhor.


Em algum momento, me deparei com este post e descobri a existência de um servidor de comentários gratuito chamado Commento . Por uma coincidência de sorte, o Commento recentemente se tornou completamente aberto - antes de estar disponível em duas versões, Comunidade gratuita e Empresa comercial. Graças ao seu desenvolvedor Adhityaa Chandrasekar.


O Commento tem ordens de magnitude mais eficientes que o Disqus, o tamanho típico da carga adicional é de cerca de 11 KB , além dos próprios comentários, é claro. Aproximadamente a mesma situação com as solicitações HTTP necessárias.


Outra vantagem do servidor Commento é que ele é muito rápido, como está escrito em Go.


Bem, como uma cereja no bolo, ele tem uma importação de comentários do Disqus, com o que mais ele poderia sonhar?


Casos de uso para o Commento


Para usuários não avançados (tecnicamente), o Commento possui um serviço de nuvem pronto para uso no commento.io . O autor oferece a você a escolha da taxa mensal, mas ela não pode ser inferior a US $ 3 "por razões técnicas".


O Sr. Chandrasekar também oferece generosamente uma conta gratuita no Commento.io em troca de "patches não triviais" para o produto.


Bem, eu escolhi a terceira opção: aumentar o servidor Commento. Nesse caso, você não depende de ninguém (além do hoster, é claro), e eu amo a independência.


Dificuldades


Sou um grande fã dos contêineres do Docker e também uso frequentemente o Docker Compose , uma ferramenta para gerenciar grupos de vários contêineres relacionados. O Commento possui uma imagem do Docker pronta para uso no registro de contêiner do GitLab.


Portanto, a decisão de usar contêineres amadureceu por si só - mas primeiro algumas coisas tiveram que ser decididas.


Dificuldade Nº 1: PostgreSQL


O Commento requer uma versão bastante recente do servidor PostgreSQL, infelizmente nenhum outro servidor SQL é suportado.


Bem, ainda executamos tudo em contêineres, então é bem simples.


Dificuldade # 2: Não há suporte para HTTPS


O Commento em si é um servidor da Web, mas suporta apenas o protocolo HTTP inseguro.


Deve-se notar que essa prática é bastante comum atualmente: nesse caso, o servidor está oculto atrás do proxy reverso , que também executa a descarga de SSL. O fato é que o suporte a SSL / HTTPS é absolutamente obrigatório nesse caso, afinal de contas, no pátio de 2019 e procurar tentativas de autorizar um usuário usando um protocolo de Internet inseguro será muito distorcido.


Decidi usar o servidor Nginx ; primeiro, tive uma experiência considerável trabalhando com ele; segundo, é muito rápido, econômico e estável. E publica as versões oficiais das imagens do Docker .


O segundo ingrediente na receita HTTPS é o certificado SSL para o domínio. Sou eternamente grato à EFF e à Mozilla por criar a Let's Encrypt Certificate Authority , que emite milhões de certificados gratuitos todos os meses.


O Let's Encrypt também fornece um utilitário de linha de comando gratuito chamado certbot , que simplifica bastante o processo de obtenção e atualização de um certificado. Bem, e - é claro - uma imagem do Docker para ele!


Dificuldade # 3: Problema do ovo de galinha Certbot


Mas esse truque é mais complicado.


Queremos nos referir ao certificado SSL na configuração de nosso proxy reverso no Nginx, o que significa que, sem um certificado, ele simplesmente se recusa a iniciar. Ao mesmo tempo, para obter um certificado SSL para um domínio, você precisa de um servidor HTTP que funcione, o Let's Encrypt irá provar sua propriedade desse domínio.


Consegui resolver esse problema e, ao que me parece, com bastante elegância:


  1. Primeiro, é gerado um certificado inválido e fictício, cujo único objetivo é permitir que o Nginx inicie.
  2. Nginx e certbot recebem em conjunto um novo certificado agora válido.
  3. Assim que o certificado é recebido, o certbot entra em "modo de espera", acordando a cada 12 horas para verificar se precisa ser atualizado - de acordo com as recomendações do Let's Encrypt.
  4. Quando chegar o momento e o certificado for renovado, o certbot sinalizará ao Nginx para reiniciar.

Dificuldade nº 4: algo deve ser preservado


Suspeito fortemente que você deseja que os comentários do usuário sejam salvos após uma reinicialização ou atualização do sistema.


Além disso, para que o Let's Encrypt não o bane devido a solicitações muito frequentes, seria bom manter os certificados recebidos por toda a data de validade.


Ambos os pontos foram resolvidos na configuração proposta usando os volumes do Docker, criados automaticamente pelo systemd quando o Commento foi lançado. Como os volumes são marcados como "externos", o Docker os ignora ao remover contêineres usando docker-compose down -v .


Junte tudo


Agora você pode ver como tudo funciona juntos.


A figura abaixo mostra a interação e o tráfego entre os quatro contêineres:



Eu apliquei a opção depends_on Docker Compose depends_on para garantir que os contêineres iniciem na ordem correta.


Se você deseja iniciar apenas seu próprio servidor Commento, pode pular o restante do artigo e ir diretamente para o código no GitHub .


Bem, falarei mais sobre essa implementação em mais detalhes posteriormente.


Como tudo isso funciona


Redigir arquivo


Como você pode ver na figura acima, minha “composição” consiste em quatro serviços:


  1. certbot - utilitário certbot da EFF
  2. nginx - proxy reverso implementando descarregamento de SSL
  3. app - servidor Commento
  4. postgres - banco de dados PostgreSQL

O docker-compose.yml contém declarações de sua própria rede Docker, chamada commento_network , e três volumes, dos quais dois são externos (ou seja, devem ser criados fora do Compose):


  • commento_postgres_volume armazena dados do servidor PostgreSQL para o Commento: usuários, moderadores, comentários, etc.
  • certbot_etc_volume contém certificados recebidos pelo certbot .

Nginx


O contêiner Nginx é baseado em uma imagem oficial leve baseada no Alpine e usa o seguinte script para executar:


 #!/bin/sh trap exit TERM # Wait for the certificate file to arrive wait_for_certs() { echo 'Waiting for config files from certbot...' i=0 while [[ ! -f /etc/letsencrypt/options-ssl-nginx.conf ]]; do sleep 0.5 [[ $((i++)) -gt 20 ]] && echo 'No files after 10 seconds, aborting' && exit 2 done } # Watches for a "reload flag" (planted by certbot container) file and reloads nginx config once it's there watch_restart_flag() { while :; do [[ -f /var/www/certbot/.nginx-reload ]] && rm -f /var/www/certbot/.nginx-reload && echo 'Reloading nginx' && nginx -s reload sleep 10 done } # Wait for certbot wait_for_certs # Start "reload flag" watcher watch_restart_flag & # Run nginx in the foreground echo 'Starting nginx' exec nginx -g 'daemon off;' 

  • Linha 3 ( ARRGHHH, Habr não suporta a exibição de números de linha no código - aprox. Transl. ) Um manipulador de interrupção é registrado para que o Nginx e o processo de monitoramento em segundo plano concluam com êxito o trabalho quando o contêiner para.
  • A linha 27 chama a função de espera, que interrompe o processo de inicialização do Nginx até que os arquivos de configuração SSL criados pelo contêiner certbot . Sem isso, o Nginx se recusaria a começar.
  • A linha 30 cria um processo em segundo plano que regularmente, a cada dez segundos, verifica a presença de um arquivo de sinalizador chamado .nginx-reload e, assim que é detectado, instrui o Nginx a recarregar a configuração. Este arquivo também cria o certbot quando o certificado é atualizado.
  • A linha 34 inicia o Nginx no modo normal. Nesse caso, exec significa que o processo atual do shell é substituído pelo processo Nginx.

Outro arquivo importante nesta imagem é a configuração do servidor virtual Commento, que força o Nginx a encaminhar solicitações HTTPS para o contêiner do commento :


 server { listen [::]:443 ssl ipv6only=on; listen 443 ssl; server_tokens off; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name __DOMAIN__; location / { proxy_pass http://app:8080/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } ssl_certificate /etc/letsencrypt/live/__DOMAIN__/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/__DOMAIN__/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; } server { listen 80 default_server; listen [::]:80 default_server; server_tokens off; server_name __DOMAIN__; location /.well-known/acme-challenge/ { root /var/www/certbot; } # Redirect to HTTPS on port 80 location / { return 301 https://$host$request_uri; } } 

O primeiro bloco do servidor (linhas 1-21 ) descreve como trabalhar com HTTPS e a regra de encaminhamento. É aqui que os arquivos do certificado Let's Encrypt são mencionados (ou stubs usados).


O domínio atendido pelo servidor é passado como argumento ao criar a imagem; ele substitui a linha __DOMAIN__ na configuração do servidor.


O segundo bloco (linhas 23-38 ) é a configuração do servidor HTTP, que é usada pelo certbot para confirmar a propriedade do domínio (o chamado "desafio da ACME"). Todos os outros pedidos causam um redirecionamento para o endereço correspondente via HTTPS.


certbot


Nossa imagem certbot é baseada na compilação oficial com o seguinte script:


 #!/bin/sh trap exit TERM # Wait until nginx is up and running, up to 10 seconds wait_for_nginx() { echo 'Waiting for nginx...' i=0 while ! nc -z nginx 80 &>/dev/null; do sleep 0.5 [[ $((i++)) -gt 20 ]] && echo "nginx isn't online after 10 seconds, aborting" && exit 4 done echo 'nginx is up and running' } # Check vars [[ -z "$DOMAIN" ]] && echo "Environment variable 'DOMAIN' isn't defined" && exit 2 [[ -z "$EMAIL" ]] && echo "Environment variable 'EMAIL' isn't defined" && exit 2 TEST="${TEST:-false}" # Check external mounts data_dir='/etc/letsencrypt' www_dir='/var/www/certbot' [[ ! -d "$data_dir" ]] && echo "Directory $data_dir must be externally mounted" [[ ! -d "$www_dir" ]] && echo "Directory $www_dir must be externally mounted" # If the config/certificates haven't been initialised yet if [[ ! -e "$data_dir/options-ssl-nginx.conf" ]]; then # Copy config over from the initial location echo 'Initialising nginx config' cp /conf/options-ssl-nginx.conf /conf/ssl-dhparams.pem "$data_dir/" # Copy dummy certificates mkdir -p "$data_dir/live/$DOMAIN" cp /conf/privkey.pem /conf/fullchain.pem "$data_dir/live/$DOMAIN/" # Wait for nginx wait_for_nginx # Remove dummy certificates rm -rf "$data_dir/live/$DOMAIN/" # Run certbot to validate/renew certificate test_arg= $TEST && test_arg='--test-cert' certbot certonly --webroot -w /var/www/certbot -n -d "$DOMAIN" $test_arg -m "$EMAIL" --rsa-key-size 4096 --agree-tos --force-renewal # Reload nginx config touch /var/www/certbot/.nginx-reload # nginx config has been already initialised - just give nginx time to come up else wait_for_nginx fi # Run certbot in a loop for renewals while :; do certbot renew # Reload nginx config touch /var/www/certbot/.nginx-reload sleep 12h done 

Um breve tour por suas linhas:


  • A linha 3 , como no script anterior, é necessária para a conclusão regular do contêiner.
  • As linhas 17-19 verificam as variáveis ​​necessárias.
  • E nas linhas 22-25 - que os diretórios necessários para que o certbot funcione sejam montados corretamente.
  • O garfo segue:
    • As linhas 30-50 são executadas apenas no primeiro início do contêiner:
      • Um certificado fictício é copiado, permitindo que o Nginx inicie normalmente.
      • Enquanto isso, o Nginx aguarda o final desse processo, após o qual continua o download.
      • Depois que o Nginx é iniciado, o certbot inicia o processo de obtenção de um certificado válido no Let's Encrypt.
      • E, finalmente, assim que o certificado é recebido, o arquivo .nginx-reload é criado, sugerindo ao Nginx que é hora de recarregar a configuração.
    • A linha 54 aguarda o início do Nginx - no caso em que um certificado completo já esteja disponível.
  • Depois de tudo isso (linhas 58-63 ), ele continua o ciclo, uma vez a cada 12 horas, verificando a necessidade de renovar o certificado e sinalizando ao Nginx para reiniciar.

Commento e PostgreSQL


Os contêineres do app e do postgres usam as imagens originais fornecidas pelos desenvolvedores sem nenhuma alteração.


Serviço Systemd


A última parte deste quebra-cabeça é o arquivo da unidade systemd commento.service , no qual você precisa criar um link simbólico em /etc/systemd/system/commento.service para que ele inicie em um bom momento quando o sistema for iniciado:


 [Unit] Description=Commento server [Service] TimeoutStopSec=30 WorkingDirectory=/opt/commento ExecStartPre=-/usr/bin/docker volume create commento_postgres_volume ExecStartPre=-/usr/bin/docker volume create certbot_etc_volume ExecStartPre=-/usr/local/bin/docker-compose -p commento down -v ExecStart=/usr/local/bin/docker-compose -p commento up --abort-on-container-exit ExecStop=/usr/local/bin/docker-compose -p commento down -v [Install] WantedBy=multi-user.target 

Linhas:


  • A linha 6 implica que o código do projeto é clonado no diretório /opt/commento - isso é muito mais simples.
  • As linhas 7-8 criam volumes externos, se ainda não estiverem.
  • Na linha 9 , os possíveis restos dos contêineres anteriores são excluídos. Os volumes externos são preservados.
  • A linha 10 marca o lançamento real do Docker Compose. O --abort-on-container-exit todo o bando de contêineres quando algum deles é --abort-on-container-exit . Graças a isso, o systemd estará ciente de que o serviço está parado.
  • A linha 11 está novamente limpando e excluindo contêineres, redes e volumes.

Código fonte


Uma implementação totalmente funcional, exigindo apenas a configuração de variáveis ​​no docker-compose.yml , está disponível no GitHub . Você só precisa seguir cuidadosamente as etapas descritas em README .


O código está sujeito à licença MIT .


Obrigado por ler neste lugar, os comentários são freneticamente bem-vindos!

Source: https://habr.com/ru/post/pt464105/


All Articles