Configuração de Implantação de Discursos Opiniativos do MKJ

Tenho administrado um fórum Discourse com uma quantidade substancial de conteúdo e muitas imagens nos últimos anos. Maker Forums tem mais de 100 GB de imagens e mais de 400.000 posts, dos quais uma quantidade significativa foi importada, principalmente do Google+, e o restante foi criado no site. Este post descreve elementos de como configurei eventualmente o Maker Forums e, mais tarde, algumas outras instâncias do Discourse. É o que eu gostaria de saber quando comecei, e que usei para ajudar outras pessoas a evitar algumas das mesmas armadilhas em suas próprias instâncias do Discourse.

Hora de um público mais amplo.

:warning: Aviso: Se você não se sentir confortável trabalhando como administrador de sistemas Linux, este guia provavelmente não é para você. Eu nem mesmo estou ciente de todas as maneiras pelas quais ele presume conhecimento sobre Linux. Se parecer esclarecedor de ler, você pode ser o público-alvo. Se parecer confuso de ler, você provavelmente não é o público-alvo. Se parecer trabalho, considere pagar para a CDCK ou @pfaffman administrarem o Discourse para você; eles sabem o que estão fazendo. Ou comece com um site gratuito discourse.group e depois pague pelo que você for crescendo. :warning:

:warning: Como se isso não fosse suficiente: tenho mais experiência em Linux do que em Discourse. Minhas opiniões vêm sem garantia. Se tentar seguir meus conselhos fizer algo seu quebrar (seu fórum Discourse, seu sistema host ou seu coração), você fica com os dois pedaços, com todas as arestas cortantes. Não tenho planos de fornecer qualquer forma de suporte para o conteúdo deste post. :warning:

Planejo (mas não prometo) manter este documento atualizado com minhas práticas cobrindo as instâncias do Discourse nas quais participo da manutenção. Isso é escrito na forma de conselho, mas tenho a intenção principalmente como conselho para mim mesmo e para quaisquer administradores que herdem implantações do Discourse das quais fui responsável. Caso contrário, você deve considerá-lo como um ponto de partida para sua própria pesquisa para determinar como gostaria de implantar o Discourse.

Configuração do Sistema

Use um sistema operacional derivado do CentOS ou Ubuntu LTS. Qualquer coisa que suporte Docker provavelmente pode ser feita para funcionar, mas usei esses dois.

Docker

Sou usuário do Fedora. Fui o primeiro Líder do Projeto Fedora na Red Hat, e preferiria muito mais executar o Discourse sobre o Podman porque opino que seu modelo de segurança é preferível ao do Docker. No entanto, as implantações do Discourse são suportadas apenas no Docker, e você será um verdadeiro pioneiro se tentar executar sobre qualquer outra coisa. (Talvez, algum dia, funcione com Podman usando podman-compose se docker-compose for suportado.)

Agora que o Docker suporta cgroups v2, você pode instalar as compilações oficiais do Docker em um sistema derivado do CentOS:

dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install --allowerasing docker-ce docker-ce-cli

(Inclua --allowerasing devido a um conflito com podman, runc e buildah que podem já estar instalados; eles precisam ser removidos para instalar o docker.)

systemctl enable --now docker

Testei isso com AlmaLinux 9.

Segurança

Esta seção realmente não tem nada a ver com o Discourse per se, mas faz parte da minha prática normal de segurança. Não permita acesso shell apenas com senha a nenhum sistema na rede, incluindo uma VM executando o Discourse. Configure o acesso baseado em SSH usando uma chave SSH criptografada com frase de passe, e configure o servidor ssh na sua VM para não permitir acesso por senha.

laptop$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/.../.ssh/id_rsa):
Enter passphrase (empty for no passphrase): ALGUMA FRASE LONGA
Enter same passphrase again: ALGUMA FRASE LONGA
Your identification has been saved in .ssh/id_rsa
Your public key has been saved in .ssh/id_rsa.pub

As distribuições Linux normalmente são configuradas para lembrar a frase de passe na memória, então você só precisa digitá-la uma vez por inicialização. O Windows não é tão conveniente; você pode considerar usar Pageant com PuTTY para fazer o mesmo.

Primeiro, valide que o SSH entrante funciona sem senha. Só depois de fazer isso, no servidor, modifique o arquivo /etc/ssh/sshd_config e encontre a linha PasswordAuthentication. Defina como no para desativar o acesso por senha entrante.

PasswordAuthentication no

Firewall

Você precisará deixar as portas 80 e 443 geralmente abertas para o letsencrypt gerar e renovar seus certificados SSL, mesmo antes de seu Discourse estar aberto ao público.

Se você estiver usando firewalld, estes comandos farão isso:

firewall-cmd --add-service http --add-service https --zone public
firewall-cmd --runtime-to-permanent

Dispositivo e sistema de arquivos separados

Faça /var/discourse/shared um dispositivo separado com seu próprio sistema de arquivos, com 20 GB de espaço mais pelo menos o dobro do espaço que você precisa para imagens; adicione mais espaço se você for usar prometheus. Se o dispositivo for fácil de expandir mais tarde (como LVM ou qualquer armazenamento em bloco de nuvem como AWS elastic block storage) você pode monitorar e aumentá-lo conforme necessário; caso contrário, seja generoso no início. Se você estiver usando um dispositivo de armazenamento em bloco de rede, não coloque uma tabela de partições nele. Usá-lo sem uma tabela de partições facilitará a expansão; você não terá que modificar uma tabela de partições. Em muitos casos, você poderá expandir sem qualquer tempo de inatividade do sistema.

No Maker Forums, este é um dispositivo de armazenamento em bloco de rede anexado à VM na qual o Maker Forums está executando. Em outro fórum Discourse, é um Volume de Armazenamento em Bloco da Digital Ocean. Na Amazon, seria o AWS Elastic Block Storage. Em meu sistema de teste executando uma VM KVM sob libvirt no Fedora, é um volume LVM no host Fedora exportado para a VM AlmaLinux como um disco virtual. Em cada caso, eu poderia criar uma nova VM, copiar arquivos-chave para ela, parar a VM antiga, anexar o volume /var/discourse/shared à nova VM e estar de volta em minutos. Isso torna as atualizações do sistema operacional na VM relativamente de baixo risco.

Certifique-se de começar com pelo menos 25 GB no sistema de arquivos raiz para sua VM, não incluindo nenhum espaço para /var/discourse/shared. Isso será usado para todos os contêineres docker, e o lançador do discourse falhará se houver menos de 5 GB livres a qualquer momento. Você também quer bastante espaço disponível para atualizações do sistema. Se você não tiver espaço suficiente em disco, é difícil se recuperar disso.

Na configuração do site, defina force_https mas leve os avisos a sério. Configure em teste, antes de tornar um site Discourse público. Note que mesmo com force_https você precisa da porta 80 aberta, tanto para redirecionar para SSL na porta 443 quanto para renovar seu certificado SSL letsencrypt. (No entanto, se você estiver usando cloudflare, use seu recurso em vez disso; relata-se que não é compatível com force_https no Discourse.)

Configuração do Kernel

Redis (um dos componentes-chave nos quais o Discourse é construído) recomenda fortemente desativar páginas enormes transparentes ao usar persistência em disco (o que o Discourse faz), e eu também permito supercomprometimento de memória.

echo 'sys.kernel.mm.transparent_hugepage.enabled=never' > /etc/sysctl.d/10-huge-pages.conf
echo 'vm.overcommit_memory=1' > /etc/sysctl.d/90-vm_overcommit_memory.conf
sysctl --system

Instalação do Discourse

Enquanto a instalação padrão é um único contêiner, isso torna cada atualização, recomendada mensalmente, tipicamente um tempo de inatividade de 10-15 minutos se feita pela linha de comando, o que é necessário para algumas atualizações, incluindo aquelas que atualizam as ferramentas sobre as quais o Discourse é construído, por segurança ou novos recursos, ou quando a atualização ao vivo da interface falha por qualquer motivo. Você pode reduzir esse tempo de inatividade na prática com a instalação de dois contêineres.

Instalação de dois contêineres

Inicie a configuração com dois contêineres.

./discourse-setup --two-container --skip-rebuild
${EDITOR:-nano} containers/data.yml
./launcher rebuild data
# minha preferência é usar app.yml mas você pode ficar com web_only.yml, leia o texto
mv containers/web_only.yml containers/app.yml
${EDITOR:-nano} containers/app.yml
./launcher rebuild app

Isso torna o tempo de inatividade obrigatório do sistema a cada poucos meses bastante curto, raramente perceptível; muitos usuários não notarão nada se não clicarem ou rolarem além do conteúdo durante a interrupção. Isso facilita a aplicação da maioria das atualizações de segurança; é apenas um piscar de olhos em vez de aproximadamente 15 minutos reconstruindo tudo. O processo a seguir funciona para a maioria das atualizações e tipicamente dá aproximadamente 30-90 segundos de tempo de inatividade, dependendo principalmente do desempenho do sistema host e do conjunto de plugins instalados.

cd /var/discourse
git pull
./launcher bootstrap app
./launcher destroy app && ./launcher start app
./launcher cleanup

Não haja atraso entre as invocações de bootstrap e destroy/start. Raramente (talvez uma ou duas vezes por ano na prática), as migrações de banco de dados feitas perto do final da fase de bootstrap causarão erros mais ou menos sérios para os usuários do aplicativo, devido ao código mais antigo acessando o banco de dados atualizado.

Isso significa que quando você atualiza o Discourse, você também precisa verificar se deve atualizar o contêiner de dados também, mas isso raramente é necessário (tipicamente espere uma ou duas vezes por ano). Dependendo do conteúdo de seus contêineres de dados e aplicativos e da velocidade do sistema, isso resultará tipicamente em um tempo de inatividade entre 5 e 20 minutos.

cd /var/discourse
git pull
./launcher stop app
./launcher rebuild data
./launcher rebuild app

Para mais informações sobre quando atualizar o contêiner de dados, veja:

(Nas minhas próprias implantações, escolhi pessoalmente chamar o contêiner web_only de app tanto porque é mais fácil de digitar quanto porque torna a maioria das instruções mais fáceis de seguir. Isso é não padrão, mas continuo apreciando a facilidade de uso. No entanto, foi trabalho extra, e funciona para mim porque sei o que está acontecendo. Se isso soar mal para você, fique com o padrão web_only em vez disso para uma implantação multi-contêiner.)

Note que em algum ponto no futuro, o Docker pode forçá-lo a fazer uma migração para uma nova configuração para conectar seus contêineres:

Se você tiver 4 GB ou mais de memória, ou vários CPUs, leia os conselhos em:

Cronograma de Atualização

Acompanhe a tag release-notes (Clique em release-notes e clique no sino no canto superior direito; eu uso “Watching First Post”) e/ou adicione https://meta.discourse.org/tag/release-notes.rss ao seu feed RSS para saber quando há lançamentos. Leia as notas de lançamento antes de atualizar. Se houver uma mudança no banco de dados, as notas de lançamento mencionarão isso. Elas também destacarão lançamentos que contêm atualizações de segurança. Leia todas as notas de lançamento mesmo se você pular a atualização para alguma versão; se você não ler as notas de lançamento para um lançamento que atualiza o banco de dados, você pode perder instruções de atualização do banco de dados nas notas de lançamento que você não leu.

E-mail

O e-mail ainda é uma das principais maneiras de manter as pessoas conectadas. Configure e-mail de saída e de entrada para fazer o e-mail trabalhar para você. Se você tiver problemas, veja:

Continue se comunicando

O Maker Forums viu visitantes ocasionais que estão ausentes por longos períodos antes de retornar. Por padrão, o Discourse para de enviar e-mails de resumo após um ano. Considere definir o suppress_digest_email_after_days para algo maior que o padrão de 365 dias se você quiser encorajar visitantes ocasionais a voltar quando virem algo novo e interessante. Eu o fiz substancialmente mais longo para o Maker Forums para apoiar visitantes ocasionais se mantendo atualizados. Ler e-mails de resumo é uma maneira válida de “lurk” em um fórum, e você nunca sabe quando algo vai despertar o interesse de alguém em contribuir.

Da mesma forma, por padrão, usuários não privilegiados que não interagiram muito (nível de confiança 0 sem posts) são eventualmente excluídos após 730 dias sem fazer login. Defina “limpar usuários inativos após dias” para 0 para desativar a exclusão de usuários, se você quiser que eles possam lurk lendo e-mails de resumo indefinidamente.

Considere adicionar o plugin yearly review que, uma vez por ano, gerará um post como 2020: The Year in Review e eventualmente enviará por e-mail para seus usuários inativos o que pode encorajá-los a renovar a participação.

Contêiner receptor de e-mail

Configure um terceiro contêiner como receptor de e-mail. Ele garante o processamento de bounces, torna seu processamento de bounces independente do provedor de e-mail de saída e dá a você a opção de resposta por e-mail.

Certifique-se de ter SPF configurado para confiar no seu remetente de e-mail; minimamente, uma política como v=spf1 +mx -all se você enviar e receber através do mesmo MX, mas mais específico pode ser mais confiável como proteção contra spam. Considere DKIM também.

Se você usar o mesmo nome de host para receber e-mail, você realmente deve terminar o SSL fora do seu contêiner como para uma “página offline” (veja abaixo) e precisará mapear seus certificados certbot para o contêiner e reiniciar o contêiner após executar o certbot.

Termine conexões SSL de usuário fora do contêiner

Há duas escolhas para terminar SSL fora do contêiner, qualquer uma das quais traz vantagens substanciais sobre terminar dentro do contêiner. Configure uma delas após ter concluído com sucesso discourse-setup e inicializado seu fórum.

nginx externo

Use o nginx executando no sistema host, em vez de apenas em um contêiner, tanto para hospedar uma página de manutenção, quanto para suportar registro de endereços IPv6 se seu host tiver suporte IPv6. (Caso contrário, todas as conexões IPv6 serão registradas como vindas de um endereço RFC1918 interno associado à sua interface de rede virtual docker local.) Esta configuração apresentará uma página de manutenção temporária durante a maioria das operações de manutenção que eventualmente redirecionará de volta para a página que um usuário estava visualizando.

Note que as instruções naquela página (atualmente) sugerem instalar um pacote chamado letsencrypt mas agora é normalmente chamado certbot em vez disso. Se você seguir as instruções naquela página para usar --certonly, você não precisará do plugin nginx para certbot, mas instalar o plugin nginx é outro mecanismo. Em derivados do CentOS, isso é:

dnf config-manager --set-enabled crb
dnf install epel-release
dnf install certbot python3-certbot-nginx
systemctl enable --now certbot-renew.timer

Certifique-se de que o certbot reinicie o nginx e o contêiner receptor de e-mail para que você não termine com navegadores ou e-mail bloqueando tráfego com seu site devido a continuar usando um certificado antigo e expirado.

# systemctl edit certbot-renew

Para um sistema sem receptor de e-mail, adicionei as duas linhas:

[Service]
ExecStartPost=/bin/systemctl reload nginx

Em um sistema onde estou usando um contêiner receptor de e-mail separado que também compartilha o certificado do sistema:

[Service]
ExecStartPost=/bin/systemctl reload nginx
ExecStartPost=/bin/sh -c 'cd /var/discourse && ./launcher restart mail-receiver'

Se você estiver usando SELinux, os contêineres Ubuntu não estão configurados para rotular o arquivo nginx.http.sock com httpd_sys_content_t para o nginx externo poder acessá-lo. Você tem duas escolhas.

A primeira é executar o nginx em modo permissivo, removendo a proteção SELinux para ele: semanage permissive -a httpd_t

No entanto, isso remove a proteção SELinux do que provavelmente é o serviço mais relevante! Para manter o SELinux habilitado, você precisará permitir que o nginx acesse as páginas de erro e mude de proxy por um soquete de domínio unix para uma porta (que é alguns µs mais lento, mas não deve ser perceptível para seus usuários).

Primeiro, execute estes comandos para permitir que o nginx acesse páginas de erro:

semanage fcontext -a -t httpd_sys_content_t /var/www
restorecon -R -v /var/www

Então no seu app.yaml, comente ou remova o - "templates/web.socketed.template.yml", exponha a porta 80 como uma porta diferente na máquina local e reconstrua o contêiner.

expose:
  - "8008:80"   # http

Não use https aqui — você terminou o SSL no nginx externo, e o cabeçalho X-Forwarded-Proto diz ao Discourse que a requisição veio via https. Certifique-se de que a porta 8008 (ou qualquer outra porta que você escolheu) não esteja exposta publicamente pelas configurações do seu firewall.

Então execute este comando para permitir que o nginx se conecte pela rede ao contêiner:

setsebool -P httpd_can_network_connect 1

Então modifique sua configuração externa do nginx de proxy via nginx.http.sock para http://127.0.0.1:8008 (ou sua porta escolhida) e limpe o cabeçalho padrão Connection: close, para que o nginx externo não precise estabelecer uma nova conexão IP para cada requisição.

...
  location / {
    proxy_pass http://127.0.0.1:8008;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    # Disable default "Connection: close"
    proxy_set_header "Connection" "";
...

Remover web.socketed.template.yml também removeu a invocação real_ip, então adicione isso de volta. Certifique-se de que a faixa de endereços IP que você usa faça sentido; o padrão do Docker é usar o espaço de endereços RFC1918 172.16* que não são roteados na internet pública por política. Adicione ao seu arquivo app.yml algo como isso na seção de execução, selecionando uma ou mais das faixas de endereços RFC1918 ou qualquer outra coisa apropriada para sua implantação:

run:
  - file:
     path: /etc/nginx/conf.d/outlets/server/real-ip-recursive.conf
     chmod: 644
     contents: |
       real_ip_recursive on;
  - file:
     path: /etc/nginx/conf.d/outlets/server/real-ip-header.conf
     chmod: 644
     contents: |
       real_ip_header X-Forwarded-For;
  - file:
     path: /etc/nginx/conf.d/outlets/server/set-real-ip-from.conf
     chmod: 644
     contents: |
       set_real_ip_from 192.168.0.0/16;
       set_real_ip_from 172.16.0.0/12;
       set_real_ip_from 10.0.0.0/8;

Isso é necessário para que a limitação de taxa funcione corretamente, bem como para atribuir endereços IP de registro e último uso para usuários.

Para mais informações:

Serviço externo

Eu não configurei Fastly ou Cloudflare na frente do Discourse, mas outros fizeram, e ao contrário do nginx externo executando no host, eles podem permitir que você sirva uma página de manutenção enquanto o sistema host está completamente fora, como ao reiniciar durante uma atualização do sistema no seu host. Se isso for valioso para você, aqui está como fazer:

Não corra para uploads S3

Tenha certeza absoluta de que sempre quer usar S3 (ou equivalente) para imagens carregadas antes de habilitar enable_s3_uploads durante a configuração, ou migrar para isso mais tarde. Esteja ciente de que usar S3 (s3_endpoint) com seu CDN associado (s3_cdn_url) para imagens também resultará em servir javascript via esse CDN. Migrar de S3 de volta para armazenamento local não é suportado e não há planos concretos para implementá-lo neste momento. É uma “porta de mão única” que nem mesmo pode ser desfeita por um backup e restauração completos. Se você usar S3 ou similar, não use Digital Ocean Spaces em vez de S3. Há referências aqui no meta sobre não ser confiável.

Movi meu site para servir imagens através do Digital Ocean Spaces e seu CDN associado no início, e tive que escrever centenas de linhas de código personalizado para migrar de volta para armazenamento local, causando danos menores à minha instância do Discourse no processo, devido à “porta de mão única” não ser bem compreendida.

Para mais informações:

Você não precisa habilitar uploads S3 para usar um CDN para seu Discourse. Considere usar um CDN independente (por exemplo, Cloudflare, CloudFront, Fastly, GCS CDN) na frente de um Discourse que gerencia suas próprias imagens. É minha compreensão de segunda mão que o aviso sobre Cloudflare não ser recomendado é devido ao “Rocket Loader” modificando JavaScript; e que neste momento, desde que você não use “Rocket Loader”, funciona corretamente.

Configurações do Discourse para moderação

Em qualquer site onde a moderação esteja ativa, considere fortemente a configuração enable_whispers que permite que moderadores e administradores conversem sobre um tópico em linha. Além disso, moderadores de categoria receberam mais habilidades em versões recentes do Discourse. Vale a pena estar ciente de enable_category_group_moderation se você tiver especialistas em diferentes tópicos com suas próprias categorias, ou se você tiver categorias funcionalmente separadas como para suporte.

A geolocalização pode ser útil ao tentar entender se uma conta é legítima.

O recurso Discourse Templates é realmente útil para moderadores. Permite que você colabore em respostas comuns. Temos algumas dezenas no Maker Forums. Tem mais recursos do que o plugin anterior “Canned Responses” que ele substitui.

O recurso User Notes ajudará moderadores a compartilhar notas sobre usuários. Você pode usar isso para coisas como:

  • “Fique de olho neste usuário, eles podem ser maliciosos porque…”
  • “Embora este comportamento pareça suspeito, validei que este é um usuário legítimo por…”
  • “Já estou tendo uma conversa com este usuário para abordar preocupações, outros moderadores não precisam se juntar.”

Acessibilidade de Informações

O plugin Discourse Solved não apenas marca problemas resolvidos para que os visitantes do site possam identificá-los mais facilmente, mas entendo que também pode priorizar resultados de pesquisa do google.

Informações públicas são mais acessíveis do que informações privadas. No Maker Forums, nosso FAQ desencoraja fortemente mensagens pessoais e lembra a todos que mensagens pessoais não são realmente privadas. No entanto, por padrão, os usuários podem ver a mensagem:

Você respondeu ao usuário 3 vezes, sabia que poderia enviar uma mensagem pessoal a eles?

Se você realmente quiser encorajar os usuários a irem para mensagens pessoais, sugiro que você vá para Admin → Customize → Text e mude o modelo get_a_room para corrigir o erro de vírgula.

Se, como o Maker Forums, você quiser manter a conversa em público para beneficiar todos, Admin → Settings → Other → get_a_room_threshold pode ser definido mais alto, como 1000000.

Da mesma forma, se você tiver um fórum fornecendo ajuda, o padrão max_replies_in_first_day 10 pode empurrar novos usuários em uma conversa pedindo ajuda para mensagens pessoais quando usarem seu orçamento de respostas. Considere aumentar esta configuração para evitar empurrar conversas para mensagens pessoais.

Conecte usuários, construa uma comunidade

Alguns plugins podem ajudar a conectar usuários entre si.

Se seu fórum não tiver muitos usuários simultâneos, considere o plugin Who’s Online para dar às pessoas mais sensação de conexão. Você pode querer limitar a exibição a usuários logados, possivelmente apenas aqueles que atingiram pelo menos o nível de confiança 1. Você pode usá-lo apenas para adicionar presença flair (whos_online_avatar_indicator) aos avatares definindo whose_online_minimum_display muito alto e whos_online_hide_below_minimum_display true. Isso pode ser útil para fóruns de suporte para apoiar e encorajar perguntas e respostas rápidas enquanto ajuda os usuários a resolver problemas.

No entanto, uma sensação de presença pode cortar em ambas as direções. Um usuário que está online em um horário diferente da maioria dos usuários do fórum pode se sentir sozinho, ou o fórum pode parecer uma “cidade fantasma” para eles.

Se você tiver usuários em muitos países e quiser que eles tenham dicas sobre quando uns aos outros estão mais provavelmente disponíveis, considere o plugin National Flags, e encoraje os usuários a definir uma bandeira nacional em seu perfil.

Um complicado é tradução. Seria conveniente ajudar as pessoas a se comunicarem quando não falam o mesmo idioma, mas atualmente (até a data desta escrita) não há serviços de tradução com níveis gratuitos de serviço. Se você escolher pagar por serviços de tradução, você pode habilitar tradução com o plugin Discourse Translator.

Backup

Para arquivos do sistema, considere fazer backup pelo menos de:

  • /var/discourse/containers (para detalhes de configuração do discourse)
  • /var/www (para páginas de erro)
  • /etc/ssl (para configuração letsencrypt, para evitar ter que inicializar o certbot como parte de restaurar um backup; caso contrário você tem que comentar a porção SSL da sua configuração nginx enquanto está inicializando; isso funciona apenas se você mantiver os backups recentes porque os certificados têm validade curta)
  • /etc/systemd/system/backup-uploads.service (para fazer backups de imagens para S3)
  • /usr/local/bin/mc (minio-client como ferramenta de backup de imagem, se você escolher usá-lo)
  • /root/.mc (configuração para backup de imagem com minio-client)
  • /root/.ssh (autenticação de sessão SSH entrante)

Alguns desses arquivos você pode fazer backup verificando-os no Git e empurrando para algum lugar fora do site. Se os arquivos que você verificar no Git incluírem segredos (como senhas de banco de dados), definitivamente não os empurre para um repositório público. Alternativamente, você poderia scriptar copiá-los do sistema e verificá-los no Git em um sistema de supervisão que você controle. Scripte isso com frequência suficiente para manter seus backups de /etc/ssl frescos.

O objetivo é ter backups tanto em caso de desastre quanto para ter registro de mudanças em caso de erro.

Uma alternativa melhor para a maioria desses arquivos é manter as cópias canônicas em outro lugar, e usar uma ferramenta como Ansible para manter a configuração no sistema, o que torna tão fácil atualizar após um backup. Mas se você for fazer isso, você provavelmente descobriu sem eu dizer!

Configuração do Discourse para backup

  • Faça backup de miniaturas com include_thumbnails_in_backups. Uma restauração sem miniaturas leva muito tempo para regenerá-las. Se seu site não tiver muitas imagens, as miniaturas ocupam espaço insignificante. Se seu site for rico em imagens, regenerar miniaturas pode levar dias. Enquanto as miniaturas estão sendo regeneradas, as notificações por e-mail serão desativadas. De qualquer forma, não faz sentido omitir miniaturas dos backups.

  • Não inclua imagens nos backups se você tiver muitas imagens. Isso tornará os backups lentos e desajeitados. Faça backup deles separadamente. Se você fizer backup de imagens após o backup do banco de dados, seus backups serão consistentes.

  • Organize para que os backups vão para fora do site de alguma forma.

Esta página mostra como configurar backups de banco de dados para S3 ou algo como S3:

Faça backup com restic

Configure o discourse para fazer backup para o sistema de arquivos, então faça backup do sistema de arquivos para um destino de backup remoto com restic. Aqui está uma receita de exemplo.

# dnf install restic
# mkdir /var/restic
# mkdir /opt/backup
# cd /opt/backup
# cat > backup <<EOF
#!/usr/bin/bash

set -e
. /opt/backup/backup-config

restic --cache-dir=/var/restic \
	backup \
	/etc \
	/root \
	/var/discourse \
	/opt/backup \
	--exclude /var/discourse/shared/data/postgres_data

restic --cache-dir=/var/restic forget \
	--prune --keep-hourly 24 --keep-daily 7 --keep-monthly 3
EOF
# chmod +x backup

Esses detalhes variarão dependendo do destino do restic. Nem todos usam variáveis de ambiente da AWS, portanto, leia a documentação do restic.

# cat > backup-config <<EOF
### Esses detalhes dependem de qual destino do restic você configura, então altere-os
export AWS_ACCESS_KEY_ID=seu-aqui
export AWS_SECRET_ACCESS_KEY=igual
export RESTIC_PASSWORD_FILE=/root/restic-password
export RESTIC_REPOSITORY=veja-a-documentação-do-restic
EOF
# {$EDITOR:-nano} /root/restic-password

Você precisará armazenar uma cópia do que colocar em /root/restic-password, caso contrário, não conseguirá ler os backups! Use seu cofre de senhas.

Por fim, crie alguns serviços, inicialize o repositório restic e defina um timer para que os backups comecem.

# cat > /etc/systemd/system/backup.service <<EOF
[Unit]
Description=Back up to remote target
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
StandardOutput=file:/var/log/backup.out
StandardError=file:/var/log/backup.err
WorkingDirectory=/var/discourse
ExecStart=/opt/backup/backup

[Install]
WantedBy=multi-user.target
EOF
# cat > /etc/systemd/system/backup.timer <<EOF
[Unit]
Description=Regular system backups

[Timer]
Persistent=true
OnCalendar=00/4:30:00
Unit=backup.service

[Install]
WantedBy=timers.target
EOF

# systemctl daemon-reload
# . /opt/backup/backup-config
# restic init
# /opt/backup/backup
# systemctl enable backup.timer

Após esse processo, você deverá conseguir ver que criou seu backup inicial.

# restic snapshots

Eu usei com sucesso backups do restic feitos dessa maneira para mover um servidor Discourse de um sistema para outro usando o comando restic restore.

Backups de imagem em streaming com minio

Como alternativa ao Restic, embora os backups de banco de dados possam ser armazenados no S3, não há backup de imagem S3 separado do serviço de imagens do S3. Uma alternativa é usar o minio-client para copiar imagens para qualquer armazenamento semelhante ao S3. Isso pode ser muitos destinos semelhantes ao S3, incluindo S3 e minio, mas não DigitalOcean Spaces porque é construído sobre o sistema de arquivos Ceph, que não implementa a API ListObjectsV2 da mesma maneira que o S3.

No S3, crie um bucket que bloqueie o acesso público (PermissionsBlock public access é a maneira fácil de fazer isso corretamente na AWS).

Instale o minio-client (mc) de alguma forma. Aqui está uma maneira.

curl https://dl.min.io/client/mc/release/linux-amd64/mc > /usr/local/bin/mc && chmod +x /usr/local/bin/mc

Configure o minio-client com um alias chamado backup usando um comando semelhante a este:

# mc alias set backup https://s3.amazonaws.com ACCESSKEY SECRETKEY --api S3v4
# mc mirror /var/discourse/shared/standalone/uploads backup/UPLOADS-BACKUP-BUCKET

Em seguida, crie um serviço /etc/systemd/system/backup-uploads.service assim

[Unit]
Description=Neartime remote backup sync of discourse uploads
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=600
User=root
ExecStart=/usr/local/bin/mc mirror --overwrite -a --watch /var/discourse/shared/app/uploads backup/UPLOADS-BACKUP-BUCKET

[Install]
WantedBy=multi-user.target

Observe que UPLOADS-BACKUP-BUCKET aqui deve ser um bucket diferente do s3_backup_bucket no qual você configura o discourse para fazer upload de backups de banco de dados. Além disso, observe que o caminho será /var/discourse/shared/web_only/uploads se você usar a implantação padrão de vários contêineres.

# systemctl enable backup-uploads
# systemctl start backup-uploads
# journalctl -fu backup-uploads

Faça upload de uma imagem de teste e verifique se você vê linhas para backup bem-sucedido das imagens originais e otimizadas. Control-C sairá do modo de acompanhamento no journalctl.

Recuperação

Eu nunca tive que testar esse plano até o momento desta escrita. Este resumo pode perder algo.

  • Restaurar todos os arquivos backupados em geral
  • Iniciar nginx (agora sua página de manutenção será exibida)
  • Faça uma implantação normal do Discourse usando os arquivos restaurados em /var/discourse/containers
  • Instale o minio-client em /usr/local/bin/mc se você não o restaurou dos backups
  • Se você não fez backup de /root/mc, configure o alias de backup # mc alias set backup https://s3.amazonaws.com ACCESSKEY SECRETKEY --api S3v4
  • # mc cp backup/UPLOADS-BACKUP-BUCKET /var/discourse/shared/app/uploads
  • Restaure o backup mais recente do banco de dados; recomendo que você Restore a backup from the command line
  • Somente após confirmar que o site está operacional, reconfigure o backup de uploads para o S3 conforme documentado acima.

Backups em streaming do postgresql

No futuro, posso criar, testar e fornecer uma configuração para habilitar o uso de arquivamento WAL contínuo para transmitir backups do Postgres quase instantâneos com minio-client usando o archive-command no postgresql, semelhante aos backups de uploads em streaming.

Monitoramento de desempenho

Há pelo menos duas abordagens para monitoramento de desempenho.

Contêiner Prometheus

Configure o prometheus, colocando os logs do prometheus em /var/discourse/shared/prometheus se você estiver executando-o no mesmo sistema. Os arquivos do Prometheus podem crescer muito, e você não quer que eles preencham o sistema de arquivos raiz; você também provavelmente quer levá-los consigo se mover para um sistema host mais novo (seja atualizando para uma VM maior ou uma VM com uma instalação de sistema operacional mais recente).

Se você implantar o prometheus no sistema discourse (ou em qualquer outro lugar na internet pública), configure a segurança na frente dele. Instalado dessa maneira, uma opção seria a configuração do nginx assim:

  location /prometheus/ {
    auth_basic "Prometheus";
    auth_basic_user_file /etc/nginx/prometheus-htpasswd;
    proxy_pass http://localhost:9090/;
  }

Sysstat

Se Prometheus for demais, considere usar sysstat em vez disso.

  • dnf install sysstat (ou apt install sysstat no debian e derivados)
  • systemctl enable --now sysstat
  • systemctl enable --now sysstat-collect.timer
  • systemctl enable --now sysstat-summary.timer
  • systemctl edit sysstat-collect.timer e altere OnCalendar=*:00/10 para OnCalendar=*:00/2
  • Se /etc/default/sysstat existir, altere false para true

Após isso, o comando sar pode dizer se você está ficando sem recursos de vez em quando.

Outros Recursos

Aqui está uma discussão complementar (e mais compacta) sobre o uso do Discourse internamente como forma principal de comunicação interna.

54 Curtiram

Olá, obrigado por este tutorial

Quais são sua CPU atual (núcleos) e RAM?
Quais são suas configurações atuais:

  db_shared_buffers: "xGB"
  db_work_mem: "xMB"
  UNICORN_WORKERS:

2 vCPUs (Xeon L5640), 4 GB de RAM — mas, graças à importação massiva, temos mais conteúdo por usuário simultâneo do que o típico Discourse construído inteiramente de forma orgânica. Raramente temos mais de 5 usuários simultâneos.

Atualmente, não configurei db_work_mem no data.yml, mas parece que está definido como 10 MB em /etc/postgresql/13/main/postgresql.conf. No meu data.yml, defini db_shared_buffers: "768MB", mas agora vejo que /etc/postgresql/13/main/postgresql.conf no meu container de dados diz shared_buffers = 512MB, o que me surpreende. Aparentemente, não reconstruí meu container de dados após minha última alteração. :roll_eyes: Fiz a alteração de configuração antes de adicionar o Prometheus (que usa mais memória), então, antes de alterar isso, provavelmente vou mover o Prometheus para fora dessa instância de servidor.

No aplicativo, configurei UNICORN_WORKERS: 4

1 Curtiu

obrigado @mcdanlj por todos esses brindes. Algum conselho sobre manutenção periódica/agendada? como reinicializações semanais/mensais? ou monitoramento de subida/descida com reinicialização automática? Alguma outra manutenção periódica manual ou automatizada?

1 Curtiu

@jaffadog Fique de olho na tag release-notes (é o sino no canto superior direito; eu uso “Assistindo a Primeira Postagem”) e/ou adicione https://meta.discourse.org/tag/release-notes.rss ao seu feed RSS para saber quando houver lançamentos. Isso é tipicamente mensal para o aplicativo, o que cobre reinicializações mensais. Eu não reinicio com mais frequência em um cronograma. Além disso:

Se você usa letsencrypt e mail_receiver, provavelmente deve configurá-lo para reiniciar o mail_receiver após obter um novo certificado.

É tudo o que me vem à mente no momento. Obrigado por perguntar, e incorporei como acompanhar release-notes e mais detalhes sobre reinicializações no texto principal. Acho que tudo agora está totalmente coberto na postagem original.

Eu aumentei as instruções de atualização para buscar atualizações em /var/discourse a fim de alterar a versão da imagem base, pois, ao analisar Update base image for polkit vulnerability, percebi que não ser explícito sobre esta etapa poderia ser enganoso. Eu estava pensando nisso como uma daquelas coisas que fazem parte da documentação de linha de base. O script launcher contém uma referência específica à versão da imagem base e, até que você execute git pull, você estará construindo sobre uma imagem base mais antiga e não executando o que foi testado. (Procure por image= perto do topo do arquivo.)

1 Curtiu

É pior do que isso: ele é configurado por padrão para excluir contas inativas de nível 0 após algum período. No meu caso, isso realmente não era o que eu queria! Verifique “limpar usuários inativos após dias” e defina para zero, ou um número realmente grande.

2 Curtiram

Eu olhei para isso, mas, pelo que entendi, significa que eles nunca responderam ao fluxo de “confirme seu endereço de e-mail” e, portanto, não receberiam e-mails de qualquer maneira. Não acho que “inativo” aqui signifique “não fazer login no site”, mas gostaria de saber se estou enganado.

Não, “inativo” não significa active=false aqui.

Significa usuários onde todas as condições abaixo se aplicam:

  • nível de confiança 0
  • sem postagens
  • não é administrador, nem moderador
  • visto pela última vez há mais de X dias.

E sim, essa redação é realmente confusa, embora a configuração explique um pouco (“nível de confiança 0 sem nenhuma postagem”).

8 Curtiram

Na verdade, eu estava confundindo configurações diferentes e tinha em mente “limpar usuários em staging não utilizados após X dias”. Eu já havia definido “limpar usuários inativos após X dias” para zero no Maker Forums há muito tempo e não mencionei isso aqui; devo ter esquecido em minha auditoria de configurações de site alteradas quando estava procurando por coisas que poderiam ser de interesse geral. Obrigado a ambos, @Ed_S e @RGJ! Atualizei a postagem com outro parágrafo sobre habilitar o “lurking”. :smiling_face:

4 Curtiram

Olá @mcdanlj, obrigado pela sua excelente visão que você compartilha aqui. Em relação a uma instalação de 2 contêineres, estou confuso sobre como isso oferece uma vantagem na redução do tempo de inatividade durante as atualizações. Na minha instalação de teste, uma atualização via interface GUI /admin/upgrade#/upgrade/all leva vários minutos, como você descreve, mas o site permanece operacional para os usuários durante todo o processo.

Quando você tem que reconstruir a partir da linha de comando, a instalação dos dois contêineres permite que você inicialize a nova imagem enquanto a antiga continua em execução.

2 Curtiram

Como sempre, o @pfaffman é mais rápido do que eu e entende do assunto. :smiley:

Eu nunca faço atualizações pela GUI. Dessa forma, toda vez que eu atualizo, incluo quaisquer atualizações de segurança do sistema nos contêineres subjacentes sobre os quais o Discourse está rodando. Isso não quer dizer que usar a GUI seja ruim, nem é uma recomendação para todos os outros. As atualizações da GUI têm um tempo de inatividade ligeiramente menor (reiniciar o contêiner da web faz o serviço bipar momentaneamente, o que é um dos vários motivos para considerar isso em conjunto com o nginx externo), então é uma troca, e eu escolhi o caminho menos percorrido.

Uma instalação de contêiner único tem interrupções mais longas com mais frequência, se você atualizar regularmente para atualizações de segurança, bem como para as atualizações ocasionais de versão do banco de dados, pois o Discourse aproveita os novos recursos do Postgresql. Sem revisar dados reais, minha intuição é que há um motivo para reconstruir isso 3-4 vezes por ano. Se essa quantidade de tempo de inatividade for aceitável do seu ponto de vista, não há muitos motivos para assumir a complexidade da implantação de 2 contêineres.

4 Curtiram

Isso é muito gentil, mas o assunto são as suas opiniões. :wink:

Eu também não. Exceto com meu painel, que tem um monte de coisas extras no contêiner (como Ansible, e não me lembro exatamente o quê), e se alguém estiver fazendo uma atualização usando dashboard.literatecomputing.com e eu destruir esse contêiner, a reconstrução deles será encerrada, o que pode ser problemático. Então, tenho feito algumas atualizações do docker_manager ultimamente, e elas são muito boas.

Não exatamente. É pelo menos na maioria das vezes que, se houver uma nova imagem base, o docker_manager forçará você a obtê-la (pelo menos, ele tentará).

Isso está mais ou menos certo. Para constar, e não recomendo, mas interagi com um monte de gente que ficou anos sem fazer nenhuma atualização, sem nenhum problema.

1 Curtiu

Sim, não há dúvida de que as atualizações dentro do contêiner são bem implementadas!

Sim, era isso que eu queria dizer. :tada:

2 Curtiram

Olá novamente, obrigado a ambos pela visão. Então, ainda estou tentando decidir o que fazer para minha configuração de produção. Entendo em teoria por que uma instalação de 2 contêineres levaria a menos tempo de inatividade. Mas ainda estou vendo muito pouco tempo de inatividade com o mecanismo de atualização da GUI. Acabei de cronometrar, tive uma atualização do docker-manager e o Discourse estava 22 commits atrás. Todo o processo levou menos de 5 minutos e durante todo o processo o fórum estava totalmente operacional. Concedido que não houve atualizações do PostgreSQL desta vez, mas se fosse necessário atualizá-lo também, eu estaria olhando para a quantidade máxima de tempo de inatividade, mesmo com o método de 2 contêineres, correto? Então, se estou entendendo corretamente, o método de 2 contêineres só reduz o tempo de inatividade quando um login SSH e a reconstrução do contêiner do aplicativo são necessários devido a uma alteração de configuração (adicionar/remover plugins, etc.)? Não prevejo mudanças frequentes na minha configuração de produção, então não vejo nenhuma redução potencial no tempo de inatividade no meu caso. Ou eu também preciso fazer login via SSH e reconstruir os contêineres para aplicar certos tipos de atualizações de recursos/segurança?

Tudo bem.

Sim, você tem o tempo de inatividade total toda vez que atualiza o postgresql (a cada um ou dois anos, suponho, mais atualizações de segurança para o próprio PostgreSQL, não que elas tenham sido tão frequentes).

Antes de mudar, tive algumas falhas ao fazer as atualizações da GUI, mas não me lembro mais dos detalhes; história antiga.

As atualizações da GUI não atualizam a imagem base, portanto, todas as atualizações de segurança no software fornecido, como o processamento de imagens, não serão aplicadas lá.

Eu gosto de reconstruir o contêiner do aplicativo rapidamente como meu modo normal para consumir atualizações de segurança para o contêiner do aplicativo quando disponíveis, para que eu nunca adie isso por 10 minutos de inatividade para reconstruir tudo. É por isso que git pull no launcher faz parte da primeira parte da aplicação de todas as atualizações; para que, se a imagem base com coisas como programas de processamento de imagem tiver sido atualizada, elas sejam aplicadas sem que eu precise pensar em perguntar se há atualizações de segurança a serem aplicadas. :smiling_face:

Mas, no final, eu pessoalmente acho a abordagem de dois contêineres mais simples para mim, e eu absolutamente não estou tentando convencer outras pessoas a usá-la. Se não parecer mais simples para você com base em seu conhecimento e experiência, não o faça apenas porque meu guia pessoal e opinativo o identifica como útil em um determinado contexto. :grin:

2 Curtiram

Entendi! :wink: Eu também costumo ter opiniões fortes sobre detalhes de implementação de tecnologia, mas aprecio sua perspectiva adicional.

Hmm, então isso ainda é verdade?

De qualquer forma, em minha experiência com fóruns tradicionais instalados sem containerização em uma pilha LAMP/LEMP, o vetor de vulnerabilidade/ataque típico que leva a sites comprometidos no mundo real é quase sempre no código do aplicativo web ou em um de seus frameworks de desenvolvimento web. Portanto, eu tendo a ter um senso maior de urgência em relação às atualizações do codebase do Discourse, que parecem ser tratadas pela GUI, então acho que estou pendendo para esse lado.


A propósito, sobre o assunto de tempo de inatividade, uma rápida menção ao Clear Linux: Comecei a testá-lo devido às suas otimizações de baixo nível em pura computação numérica para tentar economizar algumas horas no meu enorme processo de importação de fórum. Pode haver de fato algumas melhorias de velocidade para esse caso, mas, em geral, santo Deus, é incrivelmente rápido para reiniciar, especialmente como um convidado KVM. Na camada mais barata de VPS, posso reiniciar e fazer login novamente no SSH em pouco menos de 5 segundos. Portanto, estou ansioso para usá-lo quando houver atualizações importantes do sistema operacional host.

Sim. Mas algumas vezes por ano você precisa fazer uma reconstrução pela linha de comando porque algum componente no contêiner foi atualizado. E então você terá de 5 a 15 minutos de inatividade. Eu praticamente só faço atualizações pela linha de comando (exceto no meu painel, onde posso atualizar apenas o plugin do painel várias vezes ao dia), e tenho vários clientes para os quais faço essas atualizações pela linha de comando quando são necessárias (presumivelmente eles estão fazendo atualizações pela interface web, mas muitas vezes não o fazem).

2 Curtiram

O painel do Discourse notifica especificamente sobre esse tipo de atualização necessária? Ou devo ficar de olho nos PSAs do Debian?