One place for hosting & domains

      Como instalar o WordPress no Ubuntu 20.04 com uma pilha LAMP


      Introdução

      Hoje, o WordPress é uma ferramenta de código aberto extremamente popular para fazer sites e blogs na internet. Usado por 63% de todos os sites que usam um sistema de gerenciamento de conteúdo (CMS), os sites com WordPress representam 36% de todos os sites que estão atualmente online.

      Há muitas abordagens diferentes para se obter acesso ao WordPress e alguns processos de configuração são mais complexos do que outros. Este tutorial destina-se àqueles que desejam instalar e administrar uma instância do WordPress em um servidor na nuvem não gerenciado através da linha de comando. Embora esta abordagem exija mais passos do que uma instalação do WordPress pronta, ela oferece aos administradores controle maior sobre seu ambiente WordPress.

      Se você estiver procurando acessar uma instalação do WordPress pronta, o Marketplace da DigitalOcean oferece um aplicativo com um clique para você começar com a instalação do WordPress ao desenvolver seu servidor.

      Dependendo das suas necessidades e objetivos, você pode encontrar outras opções que sejam mais adequadas. Como software de código aberto, o WordPress pode ser baixado e instalado livremente, mas para ficar disponível na Web é provável que seja necessário comprar uma infraestrutura na nuvem e um nome de domínio. Continue seguindo este guia se você estiver interessado em trabalhar na instalação e configuração da parte do servidor de um site WordPress.

      Este tutorial usará uma pilha LAMP (Linux, Apache, MySQL e PHP), que é uma opção para uma arquitetura de servidor que suporta o WordPress, fornecendo o sistema operacional Linux, o servidor Web Apache, o banco de dados MySQL e a linguagem de programação PHP. Instalaremos e configuraremos o WordPress via LAMP em um servidor Linux Ubuntu 20.04.

      Pré-requisitos

      Para completar este tutorial, você precisará de acesso a um servidor Ubuntu 20.04 e completar esses passos antes de iniciar este guia:

      • Crie seu servidor seguindo nosso guia de configuração inicial de servidor com o Ubunutu 20.04 e certifique-se de possuir um usuário sudo não root.
      • Instale uma pilha LAMP seguindo nosso guia LAMP para instalar e configurar este software.
      • Proteja seu site: o WordPress recebe entradas do usuário e armazena os dados dele, então é importante que haja camada de segurança. O TLS/SSL é a tecnologia que permite criptografar o tráfego do seu site para que sua conexão e a dos seus usuários permaneçam seguras. Aqui estão duas opções disponíveis para atender a este requisito:
        • Caso tenha um nome de domínio… você pode proteger seu site com o Let’s Encrypt, que fornece certificados confiáveis e gratuitos. Siga nosso guia do Let’s Encrypt para o Apache para configurar isto.
        • Se não tiver um domínio…, e esteja usando essa configuração para teste ou uso pessoal, ao invés disso, utilize um certificado auto-assinado. Tal certificado proporciona o mesmo tipo de criptografia, mas sem a validação de domínio. Siga nosso guia SSL auto-assinado para o Apache para configurar.

      Quando tiver concluído os passos de configuração, faça login no seu servidor como seu usuário sudo e continue abaixo.

      Passo 1 — Criando um banco de dados do MySQL e um usuário para o WordPress

      O primeiro passo que vamos dar é um passo preparatório. O WordPress utiliza o MySQL para gerenciar e armazenar as informações do site e as do usuário. Já temos o MySQL instalado, mas precisamos criar um banco de dados e um usuário para o WordPress usar.

      Para começar, faça login na conta root (administrativa) do MySQL emitindo este comando (note que este não é usuário root do seu servidor):

      Será solicitada a senha que você configurou para a conta raiz do MySQL quando instalou o software.

      Nota: Se você não puder acessar seu banco de dados MySQL via root como um usuário sudo, você pode atualizar a senha do seu usuário root fazendo login no banco de dados desta forma:

      Depois de receber o prompt MySQL, fica disponível atualizar a senha do usuário root. Aqui, substitua new_password por uma senha forte da sua escolha.

      • ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'new_password';

      Agora, digite EXIT; e faça novamente login no banco de dados com a senha utilizando o seguinte comando:

      Dentro banco de dados, podemos criar um banco de dados exclusivo para o WordPress controlar. Chame isso do que quiser, mas vamos usar o nome wordpress neste guia. Crie o banco de dados para o WordPress digitando:

      • CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

      Nota: cada instrução do MySQL deve terminar em um ponto e vírgula (;). Verifique para garantir que a instrução foi seguida (ou seja, o ponto e vírgula foi usado), caso estiver enfrentando algum problema.

      Em seguida, vamos criar uma conta de usuário do MySQL separada que vamos usar exclusivamente para operar nosso novo banco de dados. Criar bancos de dados e contas específicas pode nos ser útil do ponto de vista de gestão e segurança. Usaremos o nome wordpressuser neste guia, mas sinta-se livre para usar qualquer nome que seja relevante para você.

      Vamos criar essa conta, definir uma senha e conceder o acesso ao banco de dados que criamos. Podemos fazer isso digitando o seguinte comando: Lembre-se de escolher aqui uma senha forte para seu banco de dados. Substitua-a em password:

      • CREATE USER 'wordpressuser'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

      Em seguida, informe ao banco de dados que nosso usuário wordpressuser deve ter acesso completo ao banco de dados que configuramos:

      • GRANT ALL ON wordpress.* TO 'wordpressuser'@'%';

      Agora, você tem um banco de dados e uma conta de usuário, criados especificamente para o WordPress. Precisamos liberar os privilégios para que a instância atual do MySQL saiba sobre as alterações recentes que fizemos:

      Saia do MySQL digitando:

      No próximo passo, vamos instaurar algumas bases para plug-ins do WordPress baixando extensões PHP para nosso servidor.

      Passo 2 — Como instalar as extensões adicionais do PHP

      Ao configurar nossa pilha LAMP, precisamos apenas de um conjunto mínimo de extensões para fazer com que o PHP se comunique com o MySQL. O WordPress e muitos dos seus plug-ins potencializam extensões adicionais do PHP.

      Podemos baixar e instalar algumas das extensões PHP mais populares para serem usadas com o WordPress digitando:

      • sudo apt update
      • sudo apt install php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip

      Isso irá preparar o terreno para instalarmos plug-ins adicionais em nosso site do WordPress.

      Nota: cada plug-in do WordPress tem seus próprios requisitos. Alguns podem precisar de pacotes adicionais do PHP para serem instalados. Verifique a documentação do plug-in para descobrir seus requisitos PHP. Se estiverem disponíveis, eles podem podem ser instalados com o apt como mostrado acima.

      Precisaremos reiniciar o Apache para carregar essas novas extensões. Considerando que vamos fazer mais configurações no Apache na próxima seção, você pode esperar ou reiniciar agora para completar o processo das extensão do PHP.

      • sudo systemctl restart apache2

      Passo 3 — Ajustando a configuração do Apache para permitir que o .htaccess substitua e reescreva

      A seguir, vamos fazer alguns pequenos ajustes na nossa configuração do Apache. Baseado nos tutoriais de pré-requisitos, você deve ter um arquivo de configuração para seu site no diretório /etc/apache2/sites-available/.

      Neste guia, vamos usar o /etc/apache2/sites-available/wordpress.conf como exemplo, mas você deve substituir o caminho para seu arquivo de configuração, se apropriado. Além disso, utilizaremos o /var/www/wordpress como o diretório root de nossa instalação do WordPress. Você deve usar a raiz da Web especificada em sua própria configuração. Se você seguiu nosso tutorial LAMP, ela pode ser seu nome de domínio em vez de wordpress em ambas as instâncias.

      Nota: é possível que esteja usando a configuração padrão 000-default.conf (com /var/www/html como sua raiz para a Web). Esta configuração pode ser usada sem problemas se você só for hospedar um site neste servidor. Se não for o caso, é melhor dividir a configuração necessária em pacotes lógicos, um arquivo por site.

      Com nossos caminhos identificados, podemos seguir em frente e trabalhar com o .htaccess para que o Apache consiga lidar com as alterações de configuração para cada diretório.

      Habilitando os arquivos de Substituição do .htaccess

      Atualmente, o uso dos arquivos do .htaccess está desabilitado. O WordPress e muitos plug-ins do WordPress usam esses arquivos de maneira intensa dentro do diretório para fazer ajustes no comportamento do servidor Web.

      Abra o arquivo de configuração do Apache para seu site com um editor de texto como o nano.

      • sudo nano /etc/apache2/sites-available/wordpress.conf

      Para permitir os arquivos .htaccess, precisamos configurar a diretiva AllowOverride dentro de um bloco Directory apontando para nossa raiz de documentos. Adicione o seguinte bloco de texto dentro do bloco VirtualHost no seu arquivo de configuração, tendo certeza de usar o diretório root Web correto:

      /etc/apache2/sites-available/wordpress.conf

      <Directory /var/www/wordpress/>
          AllowOverride All
      </Directory>
      

      Quando você terminar, salve e feche o arquivo. No nano, você pode fazer isso pressionando CTRL e X juntos, então Y, depois ENTER.

      Habilitando o módulo Rewrite (Reescrita)

      Em seguida, podemos habilitar o mod_rewrite para que possamos usar o recurso de permalink (ou link permanente) do WordPress:

      Isso permite que você tenha permalinks mais humanamente legíveis para seus posts, como nos dois exemplos a seguir:

      http://example.com/2012/post-name/
      http://example.com/2012/12/30/post-name
      

      O comando a2enmod chama um script que habilita o módulo especificado dentro da configuração do Apache.

      Habilitando as alterações

      Antes de implementarmos as alterações que fizemos, verifique se não cometemos nenhum erro de sintaxe executando o seguinte teste.

      • sudo apache2ctl configtest

      Pode ser que receba um resultado como o seguinte:

      Output

      AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message Syntax OK

      Se quiser suprimir a linha superior, basta adicionar uma diretiva de ServerName ao seu arquivo de configuração principal (global) do apache em Apache em /etc/apache2/apache2.conf. O ServerName pode ser o domínio ou endereço IP do seu servidor. No entanto, isso é apenas uma mensagem, e não afeta a funcionalidade do seu site. Contanto que a saída mostre Syntax OK, você estará pronto para continuar.

      Reinicie o Apache para implementar as alterações. Neste momento, certifique-se de fazer a reinicialização, mesmo que tenha reiniciado anteriormente neste tutorial.

      • sudo systemctl restart apache2

      Em seguida, vamos baixar e configurar o WordPress propriamente dito.

      Passo 4 — Como baixar o WordPress

      Agora que nosso software de servidor está configurado, podemos baixar e configurar o WordPress. Por motivos de segurança, é sempre recomendável obter a versão mais recente do WordPress do site deles.

      Vá até um diretório gravável (recomendamos um temporário como o /tmp) e baixe o lançamento compactado.

      • cd /tmp
      • curl -O https://wordpress.org/latest.tar.gz

      Extraia o arquivo comprimido para criar a estrutura de diretórios do WordPress:

      Vamos mover esses arquivos temporariamente para nossa raiz de documentos. Antes de fazermos isso, adicionamos um arquivo de .htaccess fictício para que esteja disponível para o WordPress usar mais tarde.

      Crie o arquivo digitando:

      • touch /tmp/wordpress/.htaccess

      Também vamos copiar o exemplo de arquivo de configuração no nome do arquivo que o WordPress lê:

      • cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php

      Também podemos criar o diretório upgrade, para que o WordPress não se depare com problemas de permissão ao tentar fazer isso sozinho após uma atualização no software:

      • mkdir /tmp/wordpress/wp-content/upgrade

      Agora, podemos copiar o conteúdo todo do diretório para nossa root de documento. Estamos usando um ponto no final do nosso diretório fonte para indicar que tudo dentro do diretório deve ser copiado, incluindo arquivos escondidos (como o arquivo .htaccess que criamos):

      • sudo cp -a /tmp/wordpress/. /var/www/wordpress

      Certifique-se de substituir o diretório /var/www/wordpress pelo diretório que você configurou no seu servidor.

      Passo 5 — Configurando o diretório do WordPress

      Antes de fazermos a configuração do WordPress baseada na Web, precisamos ajustar alguns itens em nosso diretório do WordPress.

      Como ajustar as propriedades e permissões

      Um passo importante que precisamos realizar é configurar permissões e propriedade dos arquivos que sejam adequadas.

      Vamos começar concedendo a propriedade de todos os arquivos para o usuário e grupo www-data. Este é o usuário sob o qual o servidor Web Apache executa. O Apache terá que ser capaz de ler e gravar arquivos do WordPress para atender o site e realizar atualizações automáticas.

      Atualize a propriedade com o comando chown que lhe permite modificar a propriedade dos arquivos. Certifique-se de apontar para o diretório relevante do seu servidor.

      • sudo chown -R www-data:www-data /var/www/wordpress

      Em seguida, iremos executar dois comandos find para definir as permissões para os diretórios e arquivos do WordPress:

      • sudo find /var/www/wordpress/ -type d -exec chmod 750 {} ;
      • sudo find /var/www/wordpress/ -type f -exec chmod 640 {} ;

      Essas permissões devem ser suficientes para que você trabalhe com o WordPress de maneira efetiva, mas note que alguns plug-ins e procedimentos podem exigir ajustes adicionais.

      Como configurar o arquivo de configuração do WordPress

      Agora, precisamos fazer algumas alterações no arquivo de configuração principal do WordPress.

      Ao abrimos o arquivo,nossa prioridade será ajustar algumas chaves secretas para acrescentar alguma segurança à nossa instalação. O WordPress oferece um gerador seguro para esses valores para que você não tenha que tentar obter bons valores por conta própria. Estes valores são usados internamente, então a usabilidade não será prejudicada se houverem valores complexos e seguros aqui.

      Para pegar valores seguros do gerador de chave secreta do WordPress, digite:

      • curl -s https://api.wordpress.org/secret-key/1.1/salt/

      Você receberá de volta valores únicos que se assemelham ao resultado do bloco abaixo.

      Aviso! É importante que você solicite sempre valores únicos. Não copie os valores abaixo!

      Output

      define('AUTH_KEY', '1jl/vqfs<XhdXoAPz9 DO NOT COPY THESE VALUES c_j{iwqD^<+c9.k<J@4H'); define('SECURE_AUTH_KEY', 'E2N-h2]Dcvp+aS/p7X DO NOT COPY THESE VALUES {Ka(f;rv?Pxf})CgLi-3'); define('LOGGED_IN_KEY', 'W(50,{W^,OPB%PB<JF DO NOT COPY THESE VALUES 2;y&,2m%3]R6DUth[;88'); define('NONCE_KEY', 'll,4UC)7ua+8<!4VM+ DO NOT COPY THESE VALUES #`DXF+[$atzM7 o^-C7g'); define('AUTH_SALT', 'koMrurzOA+|L_lG}kf DO NOT COPY THESE VALUES 07VC*Lj*lD&?3w!BT#-'); define('SECURE_AUTH_SALT', 'p32*p,]z%LZ+pAu:VY DO NOT COPY THESE VALUES C-?y+K0DK_+F|0h{!_xY'); define('LOGGED_IN_SALT', 'i^/G2W7!-1H2OQ+t$3 DO NOT COPY THESE VALUES t6**bRVFSD[Hi])-qS`|'); define('NONCE_SALT', 'Q6]U:K?j4L%Z]}h^q7 DO NOT COPY THESE VALUES 1% ^qUswWgn+6&xqHN&%');

      Essas são linhas de configuração que podemos colar diretamente no nosso arquivo de configuração para definir chaves seguras. Copie o resultado que acabou de receber.

      Em seguida, abra o arquivo de configuração do WordPress:

      • sudo nano /var/www/wordpress/wp-config.php

      Encontre a seção que contém os valores de exemplo para essas configurações.

      /var/www/wordpress/wp-config.php

      . . .
      
      define('AUTH_KEY',         'put your unique phrase here');
      define('SECURE_AUTH_KEY',  'put your unique phrase here');
      define('LOGGED_IN_KEY',    'put your unique phrase here');
      define('NONCE_KEY',        'put your unique phrase here');
      define('AUTH_SALT',        'put your unique phrase here');
      define('SECURE_AUTH_SALT', 'put your unique phrase here');
      define('LOGGED_IN_SALT',   'put your unique phrase here');
      define('NONCE_SALT',       'put your unique phrase here');
      
      . . .
      

      Exclua essas linhas e cole os valores que copiou da linha de comando:

      /var/www/wordpress/wp-config.php

      . . .
      
      define('AUTH_KEY',         'VALUES COPIED FROM THE COMMAND LINE');
      define('SECURE_AUTH_KEY',  'VALUES COPIED FROM THE COMMAND LINE');
      define('LOGGED_IN_KEY',    'VALUES COPIED FROM THE COMMAND LINE');
      define('NONCE_KEY',        'VALUES COPIED FROM THE COMMAND LINE');
      define('AUTH_SALT',        'VALUES COPIED FROM THE COMMAND LINE');
      define('SECURE_AUTH_SALT', 'VALUES COPIED FROM THE COMMAND LINE');
      define('LOGGED_IN_SALT',   'VALUES COPIED FROM THE COMMAND LINE');
      define('NONCE_SALT',       'VALUES COPIED FROM THE COMMAND LINE');
      
      . . .
      

      A seguir, vamos modificar algumas das configurações de conexão do banco de dados no início do arquivo. Você precisa ajustar o nome do banco de dados, o usuário do banco de dados e a senha associada que você configurou no MySQL.

      A outra mudança que precisamos fazer é definir o método que o WordPress deve usar para gravar no sistema de arquivos. Uma vez que demos permissão ao servidor Web para escrever onde ele precisa, podemos definir explicitamente o método do sistema de arquivos como “direct”. Deixar de definir esse método de acesso – usando nossas configurações atuais, levaria o WordPress a solicitar credenciais FTP quando realizássemos algumas ações.

      Esta configuração pode ser adicionada abaixo das configurações de conexão do banco de dados ou em qualquer outro lugar no arquivo:

      /var/www/wordpress/wp-config.php

      . . .
      
      // ** MySQL settings - You can get this info from your web host ** //
      /** The name of the database for WordPress */
      define( 'DB_NAME', 'wordpress' );
      
      /** MySQL database username */
      define( 'DB_USER', 'wordpressuser' );
      
      /** MySQL database password */
      define( 'DB_PASSWORD', 'password' );
      
      /** MySQL hostname */
      define( 'DB_HOST', 'localhost' );
      
      /** Database Charset to use in creating database tables. */
      define( 'DB_CHARSET', 'utf8' );
      
      /** The Database Collate type. Don't change this if in doubt. */
      define( 'DB_COLLATE', '' );
      
      
      . . .
      
      define('FS_METHOD', 'direct');
      

      Salve e feche o arquivo quando você terminar.

      Agora que a configuração do servidor está completa, podemos concluir a instalação através da interface com a Web.

      No seu navegador Web, navegue até o nome de domínio do seu servidor ou endereço IP público:

      https://server_domain_or_IP
      

      Selecione a linguagem que você gostaria de usar:

      WordPress language selection

      Em seguida, você chegará à página de configuração principal.

      Selecione um nome para seu site WordPress e escolha um nome de usuário. Recomenda-se escolher algo único e evitar nomes de usuário comuns como “admin” por fins de segurança. Uma senha forte será gerada automaticamente. Salve essa senha ou selecione uma senha forte alternativa.

      Digite seu endereço de e-mail e selecione se deseja impedir que mecanismos de pesquisa façam a indexação do seu site:

      WordPress setup installation​​​

      Quando clicar adiante, você será levado para uma página que solicita que você faça login:

      WordPress login prompt

      Assim que fizer login, será levado para o painel de administração do WordPress:

      WordPress login prompt

      Neste momento, você pode começar a projetar seu site do WordPress! Se esta é a primeira vez que usa o WordPress, explore a interface um pouco para se familiarizar com seu novo CMS.

      Conclusão

      Parabéns, o WordPress agora está instalado e pronto para ser usado!

      Agora, pode ser interessante começar a fazer o seguinte:

      • Escolher sua configuração de permalinks para posts do WordPress, que pode ser encontrada em Settings > Permalinks.
      • Selecione um novo tema em Appearance > Themes.
      • Instale novos plug-ins para aumentar a funcionalidade do seu site em Plugins > Add New.
      • Se você for colaborar com outros, também pode desejar adicionar usuários adicionais em Users > Add New.

      É possível encontrar recursos adicionais para formas alternativas de instalar o WordPress, aprender como instalar o WordPress em diferentes distribuições de servidor, automatizar suas instalações do WordPress e escalar seus sites do WordPress conferindo nosso marcador comunitário do WordPress.



      Source link

      Como enganar uma rede neural no Python 3


      O autor selecionou a Dev Color para receber uma doação como parte do programa Write for DOnations.

      Será que uma rede neural para classificação de animais pode ser enganada? Enganar um classificador de animais pode gerar poucas consequências, mas e se nosso autenticador facial pudesse ser enganado? Ou o software do nosso protótipo de carro autônomo? Felizmente,existem legiões de engenheiros e pesquisas entre um modelo visual computacional protótipo e modelos de qualidade de produção em nossos dispositivos móveis ou carros. Ainda assim, esses riscos têm implicações significativas e é importante que sejam considerados pelos profissionais de machine learning.

      Neste tutorial, você irá tentar “iludir” ou enganar um classificador de animais. Ao longo do tutorial, você irá usar o OpenCV, uma biblioteca de visão computacional e o PyTorch, uma biblioteca de deep learning. Os seguintes tópicos serão abordados no campo associado do adversarial machine learning (machine learning contraditório):

      • Crie um exemplo contraditório direcionado. Escolha uma imagem, digamos, de um cachorro. Escolha uma classe alvo, digamos, um gato. Seu objetivo é enganar a rede neural para acreditar que o cão retratado é um gato.
      • Crie uma defesa contraditória. Em resumo, proteja sua rede neural contra essas imagens suspeitas, sem saber qual é o truque.

      Ao final do tutorial, você terá uma ferramenta para enganar redes neurais e um entendimento sobre como se defender contra os truques.

      Pré-requisitos

      Para concluir este tutorial, você precisará do seguinte:

      Passo 1 — Criando o projeto e instalando as dependências

      Vamos criar um espaço de trabalho para este projeto e instalar as dependências que você irá precisar. Você irá chamar seu espaço de trabalho AdversarialML:

      Navegue até o diretório AdversarialML:

      Crie um diretório para manter todos os seus recursos:

      • mkdir ~/AdversarialML/assets

      A seguir, crie um novo ambiente virtual para o projeto:

      • python3 -m venv adversarialml

      Ative seu ambiente:

      • source adversarialml/bin/activate

      Em seguida, instale o PyTorch, um framework de deep learning para Python que você usará neste tutorial.

      No macOS, instale o Pytorch com o seguinte comando:

      • python -m pip install torch==1.2.0 torchvision==0.4.0

      No Linux e Windows, utilize os seguintes comandos para uma compilação CPU-only:

      • pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
      • pip install torchvision

      Agora, instale binários pré-empacotados para o OpenCV e numpy, que são bibliotecas para visão computacional e álgebra linear, respectivamente. O OpenCV oferece utilitários como rotações de imagem, e o numpy oferece utilitários de álgebra linear, como inversão de matriz:

      • python -m pip install opencv-python==3.4.3.18 numpy==1.14.5

      Em distribuições Linux, você precisará instalar a libSM.so:

      • sudo apt-get install libsm6 libxext6 libxrender-dev

      Com as dependências instaladas, vamos executar um classificador de animais chamado ResNet18, que descrevemos a seguir.

      Passo 2 — Executando um classificador de animais pré-treinado

      A biblioteca torchvision, que é a biblioteca oficial de visão computacional para o PyTorch, contém versões pré-treinadas de redes neurais de visão computacional comumente usadas. Essas redes neurais são todas treinadas no ImageNet 2012, um conjunto de dados que consiste em 1,2 milhões de imagens de treinamento com 1000 classes. Essas classes incluem veículos, lugares e, acima de tudo, animais. Neste passo, você irá executar uma dessas redes neurais pré-treinadas chamada ResNet18. Chamaremos a rede neural ResNet18 treinada no ImageNet de “classificador de animais”.

      O que é o ResNet18? O ResNet18 é a menor rede neural em uma família de redes neurais chamada redes neurais residuais, desenvolvida pela MSR (He et al.). Em resumo, ele descobriu que uma rede neural (denotada como uma função f, com entrada x, e saída f(x)) teria melhor desempenho com uma “conexão residual” x + f(x). Essa conexão residual é usada prolificamente em redes neurais no estado da arte, mesmo hoje. Por exemplo, FBNetV2, FBNetV3.

      Baixe esta imagem de um cachorro com o seguinte comando:

      • wget -O assets/dog.jpg https://www.xpresservers.com/wp-content/uploads/2020/06/How-To-Trick-a-Neural-Network-in-Python-3.png

      Imagem de um corgi correndo perto de uma lagoa

      Então, baixe um arquivo JSON para converter o resultado da rede neural em um nome de classe humanamente legível:

      • wget -O assets/imagenet_idx_to_label.json https://raw.githubusercontent.com/do-community/tricking-neural-networks/master/utils/imagenet_idx_to_label.json

      Em seguida, crie um script para executar seu modelo pré-treinado na imagem do cão. Crie um novo arquivo chamado step_2_pretrained.py:

      • nano step_2_pretrained.py

      Primeiro, adicione o código padrão Python importando os pacotes necessários e declarando uma função main (principal):

      step_2_pretrained.py

      from PIL import Image
      import json
      import torchvision.models as models
      import torchvision.transforms as transforms
      import torch
      import sys
      
      def main():
          pass
      
      if __name__ == '__main__':
          main()
      

      Em seguida, carregue o mapeamento a partir do resultado da rede neural para nomes de classe humanamente legíveis. Adicione isto diretamente após suas declarações de importação e antes de sua função main:

      step_2_pretrained.py

      . . .
      def get_idx_to_label():
          with open("assets/imagenet_idx_to_label.json") as f:
              return json.load(f)
      . . .
      

      Crie uma função de transformação de imagem que irá garantir que sua imagem de entrada tenha as dimensões corretas e que seja normalizada corretamente. Adicione a seguinte função diretamente após a última:

      step_2_pretrained.py

      . . .
      def get_image_transform():
          transform = transforms.Compose([
            transforms.Resize(224),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])
          ])
          return transform
      . . .
      

      Em get_image_transform, você define um número de transformações diferentes para aplicar às imagens que são passadas para sua rede neural:

      • transforms.Resize(224): redimensiona o lado menor da imagem para 224. Por exemplo, se a imagem tiver 448 x 672, esta operação reduziria a resolução dela para 224 x 336.
      • transforms.CenterCrop(224): faz um recorte do centro da imagem, de tamanho 224 x 224.
      • transforms.ToTensor(): Converte a imagem em um tensor do PyTorch. Todos os modelos PyTorch exigem os tensores do PyTorch como entrada.
      • transforms.Normalize(mean=..., std=...): Normaliza sua entrada primeiro subtraindo a média, então dividindo pelo desvio padrão. Isso é descrito mais precisamente na documentação do torchvision.

      Adicione um utilitário para prever a classe animal, dada a imagem. Este método usa ambos os utilitários anteriores para realizar a classificação de animais:

      step_2_pretrained.py

      . . .
      def predict(image):
          model = models.resnet18(pretrained=True)
          model.eval()
      
          out = model(image)
      
          _, pred = torch.max(out, 1)  
          idx_to_label = get_idx_to_label()  
          cls = idx_to_label[str(int(pred))]  
          return cls
      . . .
      

      Aqui a função de predict (prever) classifica a imagem fornecida usando uma rede neural pré-treinada:

      • models.resnet18(pretrained=True): Carrega uma rede neural pré-treinada chamada ResNet18.
      • model.eval(): modifica o modelo em vigor para ser executar no modo ‘avaliação’. O único outro modo é o modo ‘treinamento’, mas o modo de treinamento não é necessário, pois você não está treinando o modelo (ou seja, atualizando os parâmetros do modelo) neste tutorial.
      • out = model(image): Executa a rede neural na imagem transformada fornecida.
      • _, pred = torch.max(out, 1): A rede neural gera uma probabilidade para cada classe possível. Esse passo computa o índice da classe com a maior probabilidade. Por exemplo, se out = [0.4, 0.1, 0.2], então pred = 0.
      • idx_to_label = get_idx_to_label(): Obtém um mapeamento do índice de classes para nomes de classe humanamente legíveis. Por exemplo, o mapeamento poderia ser {0: cat, 1: dog, 2: fish}.
      • cls = idx_to_label[str(int(pred))]: Converte o índice de classe previsto em um nome de classe. Os exemplos fornecidos nos dois últimos tópicos iriam gerar cls = idx_to_label[0] = 'cat'.

      Em seguida, adicione um utilitário para carregar imagens após a última função:

      step_2_pretrained.py

      . . .
      def load_image():
          assert len(sys.argv) > 1, 'Need to pass path to image'
          image = Image.open(sys.argv[1])
      
          transform = get_image_transform()
          image = transform(image)[None]
          return image
      . . .
      

      Isso irá carregar uma imagem a partir do caminho fornecido no primeiro argumento para o script. transform(image)[None] aplica a sequência de transformações de imagem definida nas linhas anteriores.

      Por fim, preencha sua função main da seguinte forma, para carregar sua imagem e classificar o animal na imagem:

      step_2_pretrained.py

      def main():
          x = load_image()
          print(f'Prediction: {predict(x)}')
      

      Verifique se seu arquivo corresponde ao nosso script do final do passo 2 em step_2_pretrained.py no GitHub. Salve e saia do seu script. Em seguida, execute o classificador de animais:

      • python step_2_pretrained.py assets/dog.jpg

      Isso irá produzir o seguinte resultado, mostrando que seu classificador de animais funciona como esperado:

      Output

      Prediction: Pembroke, Pembroke Welsh corgi

      Isso conclui que há uma inferência em execução com seu modelo pré-treinado. Em seguida, você verá um exemplo contraditório em ação enganando uma rede neural com diferenças imperceptíveis na imagem.

      Passo 3 — Tentando um exemplo contraditório

      Agora, você irá sintetizar um exemplo contraditório e testar a rede neural nesse exemplo. Para este tutorial, você irá compilar exemplos contraditórios da forma x + r, onde x é a imagem original e r é alguma “perturbação”. Eventualmente, você irá criar a perturbação r por conta própria, mas, neste passo, irá baixar uma que já criamos para você. Comece baixando a perturbação r:

      • wget -O assets/adversarial_r.npy https://github.com/do-community/tricking-neural-networks/blob/master/outputs/adversarial_r.npy?raw=true

      Agora, crie uma composição da figura com a perturbação. Crie um novo arquivo chamado step_3_adversarial.py:

      • nano step_3_adversarial.py

      Neste arquivo, você irá realizar o seguinte processo de três etapas, para produzir um exemplo contraditório:

      1. Transformar uma imagem
      2. Aplicar a perturbação r
      3. Fazer a transformação inversa da imagem perturbada

      No final do passo 3, você terá uma imagem contraditória. Primeiro, importe os pacotes necessários e declare uma função main:

      step_3_adversarial.py

      from PIL import Image
      import torchvision.transforms as transforms
      import torch
      import numpy as np
      import os
      import sys
      
      from step_2_pretrained import get_idx_to_label, get_image_transform, predict, load_image
      
      
      def main():
          pass
      
      
      if __name__ == '__main__':
          main()
      

      Em seguida, crie uma “transformação de imagem” que inverte a transformação de imagem anterior. Coloque isto após suas importações, antes da função main:

      step_3_adversarial.py

      . . .
      def get_inverse_transform():
          return transforms.Normalize(
              mean=[-0.485/0.229, -0.456/0.224, -0.406/0.255],  # INVERSE normalize images, according to https://pytorch.org/docs/stable/torchvision/models.html
              std=[1/0.229, 1/0.224, 1/0.255])
      . . .
      

      Assim como antes, a operação transforms.Normalize subtrai a média e divide o valor pelo desvio padrão (ou seja, para a imagem original x, y = transforms.Normalize(mean=u, std=o) = (x - u) / o). Você aplica um pouco de álgebra e define uma nova operação que reverte essa função normalizadora (transforms.Normalize(mean=-u/o, std=1/o) = (y - -u/o) / 1/o = (y + u/o) o = yo + u = x).

      Como parte da transformação inversa, adicione um método que transforma um tensor do PyTorch de volta em uma imagem PIL. Adicione isto após a última função:

      step_3_adversarial.py

      . . .
      def tensor_to_image(tensor):
          x = tensor.data.numpy().transpose(1, 2, 0) * 255.  
          x = np.clip(x, 0, 255)
          return Image.fromarray(x.astype(np.uint8))
      . . .
      
      • O tensor.data.numpy() converte o tensor do PyTorch em uma matriz do NumPy. .transpose(1, 2, 0) reorganiza (channels, width, height) em (height, width, channels). Essa matriz do NumPy está aproximadamente no intervalo (0, 1). Por fim, multiplique isso por 255 para garantir que a imagem esteja agora na faixa (0, 255).
      • O np.clip garante que todos os valores na imagem estejam entre (0, 255).
      • O x.astype(np.uint8) garante que todos os valores de imagem sejam inteiros. Por fim, o Image.fromarray(...) cria um objeto de imagem PIL a partir da matriz do NumPy.

      Em seguida, use esses utilitários para criar o exemplo contraditório da seguinte forma:

      step_3_adversarial.py

      . . .
      def get_adversarial_example(x, r):
          y = x + r
          y = get_inverse_transform()(y[0])
          image = tensor_to_image(y)
          return image
      . . .
      

      Essa função gera o exemplo contraditório como descrito no início da seção:

      1. y = x + r. Pega sua perturbação r e a adiciona à imagem original x.
      2. get_inverse_transform: Obtém e aplica a transformação reversa de imagem que você definiu várias linhas atrás.
      3. tensor_to_image: Por fim, converte o tensor do PyTorch de volta para um objeto de imagem.

      Em último lugar, modifique sua função main para carregar a imagem, carregar a perturbação contraditória r, aplicar a perturbação, salvar o exemplo contraditório no disco e executar uma previsão no exemplo contraditório:

      step_3_adversarial.py

      def main():
          x = load_image()
          r = torch.Tensor(np.load('assets/adversarial_r.npy'))
      
          # save perturbed image
          os.makedirs('outputs', exist_ok=True)
          adversarial = get_adversarial_example(x, r)
          adversarial.save('outputs/adversarial.png')
      
          # check prediction is new class
          print(f'Old prediction: {predict(x)}')
          print(f'New prediction: {predict(x + r)}')
      

      Seu arquivo finalizado deve corresponder ao step_3_adversarial.py no GitHub. Salve o arquivo, saia do editor e inicie seu script com:

      • python step_3_adversarial.py assets/dog.jpg

      Você verá este resultado:

      Output

      Old prediction: Pembroke, Pembroke Welsh corgi New prediction: goldfish, Carassius auratus

      Agora, você criou um exemplo contraditório: enganou a rede neural para pensar que um corgi é um peixe dourado. No próximo passo, você irá efetivamente criar a perturbação r que você usou aqui.

      Passo 4 — Compreendendo um exemplo contraditório

      Para uma cartilha sobre classificação, consulte “How to Build an Emotion-Based Dog Filter”.

      Dando um passo para trás, lembre-se que seu modelo de classificação gera uma probabilidade para cada classe. Durante a inferência, o modelo prevê a classe que tenha a maior probabilidade. Durante o treinamento, você atualiza os parâmetros t do modelo para maximizar a probabilidade da classe correta y, dado os seus dados x.

      argmax_y P(y|x,t)
      

      No entanto, para gerar exemplos contraditórios, agora seu objetivo é outro. Em vez de encontrar uma classe, seu objetivo agora é encontrar uma nova imagem, x. Escolha qualquer classe que não seja a correta. Chamemos essa nova classe de w. Seu novo objetivo é maximizar a probabilidade da classe errada.

      argmax_x P(w|x)
      

      Observe que os pesos t da rede neural foram deixados de fora da expressão acima. Isso ocorre porque agora assumem o papel da contradição: outra pessoa treinou e implantou um modelo. Você só pode criar entradas contraditórias e não é permitido modificar o modelo implantado. Para gerar o exemplo contraditório x, é possível executar um “treinamento”, exceto que, em vez de atualizar os pesos da rede neural, você atualiza a imagem de entrada com o novo objetivo.

      Como um lembrete, para este tutorial, você supõe que o exemplo contraditório é uma transformação afim de x. Em outras palavras, seu exemplo contraditório assume a forma x + r para alguns r. No próximo passo, você irá escrever um script para gerar este r.

      Passo 5 — Criando um exemplo contraditório

      Neste passo, você irá aprender uma perturbação r, para que seu corgi seja classificado erroneamente como um peixe dourado. Crie um novo arquivo chamado step_5_perturb.py:

      Importe os pacotes necessários e declare uma função main:

      step_5_perturb.py

      from torch.autograd import Variable
      import torchvision.models as models
      import torch.nn as nn
      import torch.optim as optim
      import numpy as np
      import torch
      import os
      
      from step_2_pretrained import get_idx_to_label, get_image_transform, predict, load_image
      from step_3_adversarial import get_adversarial_example
      
      
      def main():
          pass
      
      
      if __name__ == '__main__':
          main()
      

      Logo após suas importações e antes da função main, defina duas constantes:

      step_5_perturb.py

      . . .
      TARGET_LABEL = 1
      EPSILON = 10 / 255.
      . . .
      

      A primeira constante, TARGET_LABEL, é a classe que será utilizada para classificar erroneamente o corgi. Neste caso, o índice 1 corresponde a “goldfish”(peixe dourado). A segunda constante do EPSILON é a quantidade máxima de perturbação permitida para cada valor da imagem. Este limite é introduzido para que a imagem seja alterada de maneira imperceptível.

      Após suas duas constantes, adicione uma função auxiliar para definir uma rede neural e o parâmetro de perturbação r:

      step_5_perturb.py

      . . .
      def get_model():
          net = models.resnet18(pretrained=True).eval()
          r = nn.Parameter(data=torch.zeros(1, 3, 224, 224), requires_grad=True)
          return net, r
      . . .
      
      • O model.resnet18(pré-trained=True) carrega uma rede neural pré-treinada chamada ResNet18, como antes. Também como antes, você define o modelo para o modo de avaliação usando .eval.
      • O nn.Parameter(...) define uma nova perturbação r, com o tamanho da imagem de entrada. A imagem de entrada também tem o tamanho (1, 3, 224, 224). O argumento de palavra-chave requires_grad=True garante que você possa atualizar essa perturbação r em linhas posteriores neste arquivo.

      Em seguida, comece a modificar sua função main. Comece carregando o modelo net, carregando as entradas x e definindo a etiqueta label:

      step_5_perturb.py

      . . .
      def main():
          print(f'Target class: {get_idx_to_label()[str(TARGET_LABEL)]}')
          net, r = get_model()
          x = load_image()
          labels = Variable(torch.Tensor([TARGET_LABEL])).long()
        . . .
      

      Em seguida, defina tanto o critério quanto o otimizador em sua função main. O primeiro diz ao PyTorch qual é o objetivo – ou seja, qual perda deve ser minimizada. O último diz ao PyTorch como treinar seu parâmetro r:

      step_5_perturb.py

      . . .
          criterion = nn.CrossEntropyLoss()
          optimizer = optim.SGD([r], lr=0.1, momentum=0.1)
      . . .
      

      Diretamente a seguir, adicione o loop de treinamento principal para seu parâmetro r:

      step_5_perturb.py

      . . .
          for i in range(30):
              r.data.clamp_(-EPSILON, EPSILON)
              optimizer.zero_grad()
      
              outputs = net(x + r)
              loss = criterion(outputs, labels)
              loss.backward()
              optimizer.step()
      
              _, pred = torch.max(outputs, 1)
              if i % 5 == 0:
                  print(f'Loss: {loss.item():.2f} / Class: {get_idx_to_label()[str(int(pred))]}')
      . . .
      

      Em cada iteração deste loop de treinamento, você:

      • r.data.clamp_(...): certifica-se que o parâmetro r é pequeno, dentro do EPSILON de 0.
      • optimizer.zero_grad(): limpa quaisquer gradientes que você computou na iteração anterior.
      • model(x + r): executa uma inferência na imagem modificada x + r.
      • Computa a loss (perda).
      • Computa o gradiente loss.backward.
      • Dá um passo de descendência de gradiente optimizer.step.
      • Computa a previsão pred.
      • Por fim, reporta a perda e a classe prevista print(...).

      Em seguida, salve a perturbação final r:

      step_5_perturb.py

      def main():
          . . .
          for i in range(30):
              . . .
          . . .
          np.save('outputs/adversarial_r.npy', r.data.numpy())
      

      Logo a seguir, ainda na função main, salve a imagem perturbada:

      step_5_perturb.py

      . . .
          os.makedirs('outputs', exist_ok=True)
          adversarial = get_adversarial_example(x, r)
      

      Por fim, execute uma previsão tanto na imagem original quanto no exemplo contraditório:

      step_5_perturb.py

          print(f'Old prediction: {predict(x)}')
          print(f'New prediction: {predict(x + r)}')
      

      Verifique novamente se seu script corresponde ao step_5_perturb.py no GitHub. Salve, saia e execute o script:

      • python step_5_perturb.py assets/dog.jpg

      Seu script irá gerar o seguinte resultado.

      Output

      Target class: goldfish, Carassius auratus Loss: 17.03 / Class: Pembroke, Pembroke Welsh corgi Loss: 8.19 / Class: Pembroke, Pembroke Welsh corgi Loss: 5.56 / Class: Pembroke, Pembroke Welsh corgi Loss: 3.53 / Class: Pembroke, Pembroke Welsh corgi Loss: 1.99 / Class: Pembroke, Pembroke Welsh corgi Loss: 1.00 / Class: goldfish, Carassius auratus Old prediction: Pembroke, Pembroke Welsh corgi New prediction: goldfish, Carassius auratus

      As duas últimas linhas indicam que agora você completou a construção de um exemplo contraditório do zero. Sua rede neural agora classifica uma imagem perfeitamente razoável de um corgi como um peixe dourado.

      Agora, você mostrou que as redes neurais podem ser enganadas com facilidade—mais que isso, a falta de robustez nos exemplos contraditórios tem consequências significativas. Uma pergunta que surge naturalmente é: Como se combate exemplos contraditórios? Muitas pesquisas foram realizadas por várias organizações, incluindo a OpenAI. Na próxima seção, você irá executar uma defesa para impedir este exemplo contraditório.

      Passo 6 — Defendendo-se contra exemplos contraditórios

      Neste passo, você irá implementar uma defesa contra exemplos contraditórios. A ideia é a seguinte: agora você é o proprietário do classificador de animais que está sendo implantado para a produção. Você não sabe quais exemplos contraditórios podem ser gerados, mas pode modificar a imagem ou o modelo para proteger-se contra os ataques.

      Antes de se defender, você deve ver por si mesmo como a manipulação de imagem é imperceptível. Abra as duas imagens a seguir:

      1. assets/dog.jpg
      2. outputs/adversarial.png

      Aqui estão as duas lado a lado. Sua imagem original terá uma taxa de proporção diferente. Consegue dizer qual delas é o exemplo contraditório?

      (esquerda) Corgi como peixe dourado, contraditório, (direita)Corgi como ele mesmo, não contraditório

      Observe que a nova imagem parece ser idêntica à original. Na realidade, a imagem da esquerda é sua imagem contraditória. Para ter certeza disso, faça o download da imagem e execute seu script de avaliação:

      • wget -O assets/adversarial.png https://github.com/alvinwan/fooling-neural-network/blob/master/outputs/adversarial.png?raw=true
      • python step_2_pretrained.py assets/adversarial.png

      Isso irá mostrar como resultado a classe de peixe dourado, para provar sua natureza contraditória:

      Output

      Prediction: goldfish, Carassius auratus

      Você irá por em prática uma defesa bastante ingênua, mas eficaz: comprima a imagem gravando-a em um formato JPEG com perdas. Abra o prompt interativo do Python:

      Em seguida, carregue a imagem contraditória como PNG e a salve de volta como JPEG.

      • from PIL import Image
      • image = Image.open('assets/adversarial.png')
      • image.save('outputs/adversarial.jpg')

      Digite CTRL + D para sair do prompt interativo do Python. Depois disso, realize uma inferência com seu modelo no exemplo contraditório comprimido:

      • python step_2_pretrained.py outputs/adversarial.jpg

      Isso gera uma classe corgi, mostrando a eficácia de sua defesa ingênua.

      Output

      Prediction: Pembroke, Pembroke Welsh corgi

      Agora, você completou sua primeira defesa contraditória. Observe que essa defesa não requer saber como o exemplo contraditório foi gerado. Isso é o que torna uma defesa eficaz. Há ainda muitas outras formas de defesa, muitas das quais envolvem um maior treinamento da rede neural. No entanto, esses procedimentos de treinamento são um tema próprio e estão fora do âmbito deste tutorial. Com isso, isso conclui seu guia sobre o machine learning contraditório.

      Conclusão

      Para compreender as implicações do seu trabalho neste tutorial, revisite as duas imagens lado a lado – o exemplo original e o contraditório.

      (esquerda) Corgi como peixe dourado, contraditório, (direita)Corgi como ele mesmo, não contraditório

      Apesar do fato de ambas as imagens serem idênticas ao olho humano, a primeira foi manipulada para enganar seu modelo. As duas imagens exibem claramente um corgi, mas o modelo está totalmente convencido de que o segundo modelo contém um peixe dourado. Isso deve gerar-lhe preocupação. Enquanto finaliza este tutorial, tenha em mente a fragilidade do seu modelo. Apenas aplicando uma simples transformação, você foi capaz de enganá-lo. Esses são perigos reais e plausíveis que escapam aos olhos, até mesmo de uma pesquisa de ponta. Pesquisas que vão além da segurança em machine learning são igualmente suscetíveis a essas falhas e, como um profissional, cabe a você aplicar o machine learning com segurança. Para mais leituras, confira os seguintes links:

      Para mais conteúdo e tutoriais de machine learning, visite nossa página do tópico de Machine Learning.



      Source link

      Como criar uma pool de armazenamento com redundância usando o GlusterFS no Ubuntu 20.04


      Existe uma versão anterior deste tutorial, escrita por Justin Ellingwood.

      Introdução

      Ao armazenar dados críticos, ter um ponto único de falha é bastante arriscado. Embora muitos bancos de dados e outros softwares permitam que você distribua dados no contexto de um único aplicativo, outros sistemas podem operar no nível de sistema de arquivos para garantir que os dados sejam copiados para outro local sempre que eles são escritos para o disco.

      O GlusterFS é um sistema de arquivos de armazenamento conectado à rede que lhe permite agrupar recursos de armazenamento de várias máquinas. Desta forma, isso permite que você trate vários dispositivos de armazenamento que são distribuídos entre vários computadores como uma unidade única e mais poderosa. O GlusterFS também lhe dá a liberdade de criar diferentes tipos de configurações de armazenamento, muitas das quais são similares em funcionamento aos níveis RAID. Por exemplo, você pode distribuir os dados em diferentes nós no cluster, ou implementar redundância para uma melhor disponibilidade de dados.

      Objetivos

      Neste guia, você irá criar uma matriz de armazenamento em clusters redundante, também conhecida como um sistema de arquivos distribuído ou, como ela está referida na documentação do GlusterFS, uma Trusted Storage Pool (pool de armazenamento confiável). Isso irá proporcionar funcionalidades semelhantes a uma configuração RAID espelhada pela rede: cada servidor independente irá conter sua própria cópia dos dados, permitindo que suas aplicações acessem qualquer cópia, ajudando assim a distribuir sua carga de leitura.

      Esse cluster do GlusterFS redundante consistirá em dois servidores do Ubuntu 20.04. Isso irá agir de maneira semelhante a um servidor NAS com RAID espelhado. Então, você irá acessar o cluster a partir de um terceiro servidor Ubuntu 20.04 configurado para funcionar como um cliente do GlusterFS.

      Quando você adiciona dados a um volume do GlusterFS, esses dados são sincronizados para todas as máquinas na pool de armazenamento onde o volume está hospedado. Este tráfego entre nós não é criptografado por padrão, o que significa que há um risco dele ser interceptado por agentes maliciosos.

      Por isso, se você vai usar o GlusterFS na produção, é recomendado que você execute-o em uma rede isolada. Por exemplo, você pode configurá-lo para executar em uma *Virtual Private Cloud *(VPC) ou com um VPN em funcionamento entre cada um dos nós.

      Se você planeja implantar o GlusterFS na DigitalOcean, você pode configurá-lo em uma rede isolada adicionando sua infraestrutura de servidor a uma Virtual Private Network da Digital Ocean. Para obter detalhes sobre como configurar isso, consulte nossa documentação de produtos VPC.

      Pré-requisitos

      Para seguir este tutorial, você precisará de três servidores executando o Ubuntu 20.04. Cada servidor deve ter um usuário não-root com privilégios administrativos e um firewall configurado com o UFW. Para configurar isso, siga nosso guia de configuração inicial de servidor para o Ubuntu 20.04.

      Note: Como mencionado na seção de Objetivos, este tutorial irá guiá-lo para configurar dois dos seus servidores Ubuntu para que atuem como servidores em sua pool de armazenamento e o servidor restante para agir como um cliente que você usará para acessar esses nós.

      Para maior clareza, este tutorial irá se referir a essas máquinas com os seguintes nomes de host:

      Hostname Papel na pool de armazenamento
      gluster0 Servidor
      gluster1 Servidor
      gluster2 Cliente

      Os comandos que devem ser executados em gluster0 ou gluster1 terão fundo em azul e vermelho, respectivamente:

      Os comandos que só devem ser executados no cliente (gluster2) terão um fundo verde:

      Os comandos que podem ou devem ser executados em mais de uma máquina terão um fundo cinza:

      Passo 1 — Configurando a resolução de DNS em cada máquina

      Configurar algum tipo de resolução de nome de host entre cada computador pode ajudar na gestão da sua pool de armazenamento do Gluster. Desta forma, sempre que precisar fazer referência a uma de suas máquinas em um comando gluster mais tarde neste tutorial, faça isso com um nome de domínio de fácil memorização ou um apelido em vez dos respectivos endereços IP.

      Se você não tiver um nome de domínio sobrando ou se você quiser configurar algo rapidamente, você pode editar o arquivo /etc/hosts em cada computador como alternativa. Este é um arquivo especial em máquinas Linux, onde é possível configurar o sistema de forma estática para atribuir todos os nomes de host contidos no arquivo para endereços IP estáticos.

      Note: Se você quiser configurar seus servidores para se autenticarem com um domínio que você possua, será necessário primeiro obter um nome de domínio de registrador de domínios — como o Namecheap ou o Enom — e configurar os registros de DNS apropriados.

      Depois de configurar um registro A para cada servidor, você pode seguir em frente para o Passo 2. Conforme for seguindo este guia, certifique-se de substituir o glusterN.example.com e o glusterN pelo nome de domínio que direciona para o servidor respectivo referenciado no comando de exemplo.

      Se você obteve sua infraestrutura da DigitalOcean, é possível adicionar seu nome de domínio à DigitalOcean e, em seguida, configurar um registro A único para cada um de seus servidores.

      Usando seu editor de texto preferido, abra este arquivo com privilégios de root em cada uma das suas máquinas. Aqui, usaremos o nano:

      Por padrão, o arquivo vai ficar parecido com isto aqui,com os comentários removidos:

      /etc/hosts

      127.0.1.1 hostname hostname
      127.0.0.1 localhost
      
      ::1 ip6-localhost ip6-loopback
      fe00::0 ip6-localnet
      ff00::0 ip6-mcastprefix
      ff02::1 ip6-allnodes
      ff02::2 ip6-allrouters
      ff02::3 ip6-allhosts
      

      Em um dos seus servidores Ubuntu, adicione o endereço IP de cada servidor seguido dos nomes que você deseja utilizar para fazer referência a eles em comandos abaixo da definição local de host.

      No exemplo a seguir, cada servidor recebe um nome de host longo que segue o padrão de glusterN.example.com e um menor que segue o padrão de glusterN. Você pode alterar as partes glusterN.example.com e glusterN de cada linha para qualquer nome — ou nomes separados por espaços únicos — que você quiser usar para acessar cada servidor. Observe, porém, que este tutorial irá usar estes exemplos a todo momento:

      Nota: Se seus servidores fazem parte de uma pool de infraestrutura Virtual Private Cloud, você deve usar o endereço IP privado de cada servidor no arquivo /etc/hosts em vez de seus IP públicos.

      /etc/hosts

      . . .
      127.0.0.1       localhost
      first_ip_address gluster0.example.com gluster0
      second_ip_address gluster1.example.com gluster1
      third_ip_address gluster2.example.com gluster2
      
      . . .
      

      Quando você terminar de adicionar estas novas linhas ao arquivo /etc/hosts de uma máquina, copie e adicione-as aos arquivos /etc/hosts em suas outras máquinas. Cada arquivo /etc/hosts deve conter as mesmas linhas, ligando os endereços IP dos seus servidores aos nomes que você selecionou.

      Salve e feche cada arquivo quando você terminar. Se usou o nano, faça isso pressionando as teclas CTRL+X, Y e depois ENTER.

      Agora que você configurou a resolução de nomes de host entre cada um dos seus servidores, será mais fácil executar comandos mais tarde enquanto configura uma pool de armazenamento e um volume. Em seguida, você vai percorrer outro passo que deve ser concluído em cada um dos seus servidores. Isto é, você irá adicionar o arquivo de pacotes pessoais (PPA) oficial do projeto Gluster a cada um dos seus três servidores Ubuntu para garantir que você possa instalar a versão mais recente do GlusterFS.

      Passo 2 — Configurando as fontes de software em cada máquina

      Embora os repositórios APT padrão do Ubuntu 20.04 contém os pacotes do GlusterFS, no momento em que este tutorial está sendo escrito, eles não são as versões mais recentes. Uma maneira de instalar a última versão estável do GlusterFS (versão 7.6 no momento em que este tutorial está sendo escrito) é adicionando o PPA oficial do projeto Gluster a cada um dos seus três servidores Ubuntu.

      Adicione o PPA para os pacotes do GlusterFS executando o seguinte comando em cada servidor:

      • sudo add-apt-repository ppa:gluster/glusterfs-7

      Pressione ENTER quando solicitado para confirmar se você realmente deseja adicionar o PPA.

      Depois de adicionar o PPA, atualize o índice de pacotes local de cada servidor. Isso fará com que cada servidor reconheça os novos pacotes disponíveis:

      Depois de adicionar o PPA oficial do projeto Gluster a cada servidor e atualizar o índice de pacotes local, você está pronto para instalar os pacotes do GlusterFS necessários. No entanto, como duas de suas três máquinas irão atuar como servidores Gluster e a outra irá agir como cliente, há dois procedimentos de instalação e configuração separados. Primeiro, você irá instalar e configurar os componentes do servidor.

      Passo 3 — Instalando componentes do servidor e criando uma pool de armazenamento confiável

      Uma pool de armazenamento é qualquer quantidade de capacidade de armazenamento agregada a partir de mais de uma fonte de armazenamento. Neste passo, você irá configurar dois dos seus servidores — o gluster0 e o gluster1 — como os componentes do cluster.

      Em ambos o gluster0 e o gluster1, instale o pacote de servidor do GlusterFS digitando:

      • sudo apt install glusterfs-server

      Quando solicitado, pressione Y e então ENTER para confirmar a instalação.

      O processo de instalação configura automaticamente o GlusterFS para ser executado como um serviço do systemd. No entanto, ele não inicializa automaticamente o serviço ou o habilita para ser executado na inicialização do sistema.

      Para iniciar o glusterd, o serviço do GlusterFS, execute o seguinte comando systemctl start em ambos o gluster0 e gluster1:

      • sudo systemctl start glusterd.service

      Então, execute o seguinte comando em ambos os servidores. Isso permitirá que o serviço seja iniciado sempre que o servidor for inicializado:

      • sudo systemctl enable glusterd.service

      Depois disso, você pode verificar o status do serviço em ambos os servidores:

      • sudo systemctl status glusterd.service

      Se o serviço estiver em operação, você receberá um resultado parecido com este:

      Output

      ● glusterd.service - GlusterFS, a clustered file-system server Loaded: loaded (/lib/systemd/system/glusterd.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-06-02 21:32:21 UTC; 32s ago Docs: man:glusterd(8) Main PID: 14742 (glusterd) Tasks: 9 (limit: 2362) CGroup: /system.slice/glusterd.service └─14742 /usr/sbin/glusterd -p /var/run/glusterd.pid --log-level INFO

      Supondo que você tenha seguido o guia de configuração inicial do servidor pré-requisito, você terá configurado um firewall com o UFW em cada uma das suas máquinas. Por isso, é necessário abrir o firewall em cada nó antes de estabelecer comunicações entre eles e criar uma pool de armazenamento.

      O daemon do Gluster usa a porta 24007, então será necessário permitir que cada nó tenha acesso a essa porta através do firewall de cada um dos nós em sua pool de armazenamento. Para fazer isso, execute o seguinte comando no gluster0. Lembre-se de alterar gluster1_ip_address para o endereço IP do gluster1:

      • sudo ufw allow from gluster1_ip_address to any port 24007

      E execute o seguinte comando no gluster1. Novamente, certifique-se de alterar gluster0_ip_address para o endereço IP do gluster0:

      • sudo ufw allow from gluster0_ip_address to any port 24007

      Também será necessário permitir que sua máquina cliente (gluster2) tenha acesso a esta porta. Caso contrário, você encontrará problemas mais tarde quando for tentar montar o volume. Execute o seguinte comando tanto no gluster0 quanto no gluster1 para abrir esta porta para a sua máquina cliente:

      • sudo ufw allow from gluster2_ip_address to any port 24007

      Então, para garantir que nenhuma outra máquina seja capaz de acessar a porta do Gluster em ambos os servidores, adicione a seguinte regra ampla deny ao gluster0 e ao gluster1:

      Agora, você está pronto para estabelecer uma comunicação entre o gluster0 e o gluster1. Para fazer isso, você precisará executar o comando gluster peer probe em um dos seus nós. Não importa qual nó você use, mas o seguinte exemplo mostra o comando sendo executado no gluster0:

      • sudo gluster peer probe gluster1

      Essencialmente, este comando diz ao gluster0 para confiar no gluster1 e registrá-lo como parte de sua pool de armazenamento. Se a investigação for bem-sucedida, ela irá retornar o seguinte resultado:

      Output

      peer probe: success

      Você pode verificar se os nós estão se comunicando a qualquer momento, executando o comando gluster peer status em cada um dos dois. Neste exemplo, ele está sendo executado no gluster1:

      Se você executar este comando do gluster1, ele irá mostrar um resultado desta forma:

      Output

      Number of Peers: 1 Hostname: gluster0.example.com Uuid: a3fae496-c4eb-4b20-9ed2-7840230407be State: Peer in Cluster (Connected)

      Neste momento, seus dois servidores estão se comunicando e prontos para criar volumes de armazenamento um com o outro.

      Passo 4 — Criando um volume de armazenamento

      Lembre-se que o objetivo principal deste tutorial é criar uma pool de armazenamento redundante. Para este fim, você irá configurar um volume com funcionalidade de réplica. Ele permitirá que você mantenha várias cópias dos seus dados e impedirá que seu cluster tenha um ponto único de falha.

      Para criar um volume, você irá usar o comando gluster volume create com esta sintaxe geral:

      sudo gluster volume create volume_name replica number_of_servers domain1.com:/path/to/data/directory domain2.com:/path/to/data/directory force
      

      Veja o que os argumentos e opções do comando gluster volume create significam:

      • volume_name: Este é o nome que você irá usar para se referir ao volume depois de ele ser criado. O comando de exemplo a seguir cria um volume chamado volume1.
      • replica number_of_servers: Após o nome do volume, você pode definir que tipo de volume você deseja criar. Lembre-se que o objetivo deste tutorial é criar uma pool de armazenamento redundante, então vamos usar o tipo de volume replica. Isso requer um argumento que indique para quantos servidores os dados do volume serão replicados (2, no caso deste tutorial).
      • domain1.com:/… e domain2.com:/…: Eles definem a localização das máquinas e do diretório do bricks — o termo do GlusterFS para sua unidade básica de armazenamento, que inclui todos os diretórios em todas as máquinas que sirvam como parte ou uma cópia de um volume maior — que formarão o volume1. O exemplo a seguir irá criar um diretório chamado gluster-storage no diretório root de ambos os servidores.
      • force: Essa opção irá sobrescrever quaisquer avisos ou opções que de outra forma apareceriam e interromperiam a criação do volume.

      Seguindo as convenções estabelecidas anteriormente neste tutorial, você pode executar este comando para criar um volume. Observe que pode executá-lo tanto a partir do gluster0 quanto do gluster1:

      • sudo gluster volume create volume1 replica 2 gluster0.example.com:/gluster-storage gluster1.example.com:/gluster-storage force

      Se o volume for criado com sucesso, você receberá o seguinte resultado:

      Output

      volume create: volume1: success: please start the volume to access data

      Neste momento, seu volume foi criado, mas ele ainda não está ativo. Você pode iniciar o volume e torná-lo disponível para uso executando o seguinte comando novamente a partir de um dos seus servidores do Gluster:

      • sudo gluster volume start volume1

      Você receberá este resultado se o volume for iniciado corretamente:

      Output

      volume start: volume1: success

      Em seguida, verifique se o volume está online. Execute o seguinte comando a partir de um dos seus nós:

      • sudo gluster volume status

      Isso irá retornar um resultado semelhante a este:

      Output

      Status of volume: volume1 Gluster process TCP Port RDMA Port Online Pid ------------------------------------------------------------------------------ Brick gluster0.example.com:/gluster-storage 49152 0 Y 18801 Brick gluster1.example.com:/gluster-storage 49152 0 Y 19028 Self-heal Daemon on localhost N/A N/A Y 19049 Self-heal Daemon on gluster0.example.com N/A N/A Y 18822 Task Status of Volume volume1 ------------------------------------------------------------------------------ There are no active volume tasks

      Com base neste resultado, os bricks em ambos os servidores estão online.

      Como um passo final para configurar seu volume, você precisará abrir o firewall em ambos os servidores para que sua máquina cliente seja capaz de se conectar e montar o volume. De acordo com o resultado amostral do comando anterior, o volume1 está em execução na porta 49152 em ambas as máquinas. Esta é a porta padrão do GlusterFS para seu volume inicial, e o próximo volume que você criar irá usar a porta 49153, então 49154 e assim por diante.

      Execute o seguinte comando em ambos o gluster0 e o gluster1 para permitir que o gluster2 tenha acesso a esta porta através dos seus respectivos firewalls:

      • sudo ufw allow from gluster2_ip_address to any port 49152

      Então, para uma camada adicional de segurança, adicione outra regra ampla deny para a porta do volume tanto no gluster0 quanto no gluster1. Isso irá garantir que nenhuma máquina que não seja o seu cliente possa acessar o volume em ambos os servidores:

      Agora que seu volume está em operação, você pode configurar sua máquina cliente e começar a usá-la remotamente.

      Passo 5 — Instalando e configurando componentes do cliente

      Seu volume agora está configurado e disponível para uso pela sua máquina cliente. Apesar disso, antes de começar, você precisa instalar o pacote glusterfs-client a partir do PPA que você configurou no Passo 1 na sua máquina cliente. As dependências deste pacote incluem algumas das bibliotecas comuns e módulos de tradutor do GlusterFS, além das ferramentas FUSE necessárias para que ele funcione.

      Execute o seguinte comando no gluster2:

      • sudo apt install glusterfs-client

      Você irá montar seu volume de armazenamento remoto em seu computador cliente em breve. Antes que possa fazer isso, no entanto, você precisa criar um ponto de montagem. Tradicionalmente, ele está localizado no diretório /mnt, mas qualquer lugar que seja conveniente pode ser usado.

      Para simplificar, crie um diretório chamado /storage-pool em sua máquina cliente para servir como o ponto de montagem. Este nome de diretório começa com uma barra (/) que o coloca no diretório root. Sendo assim, você precisará criá-lo com privilégios sudo:

      Agora, você pode montar o volume remoto. Antes disso, porém, dê uma olhada na sintaxe do comando mount que você irá usar para isso.

      sudo mount -t glusterfs domain1.com:volume_name /path/to/mount/point
      

      O mount é um utilitário encontrado em muitos sistemas operacionais semelhantes ao Unix. Ele é usado para montar sistemas de arquivos — qualquer coisa desde dispositivos de armazenamento externo, como cartões SD ou pendrives, até armazenamento ligado à rede como no caso deste tutorial — em diretórios no sistema de arquivos existente da máquina. A sintaxe do comando mount que você irá usar inclui a opção -t, que requer três argumentos: o tipo do sistema de arquivos a ser montado, o dispositivo onde o sistema de arquivos a ser montado pode ser encontrado, e o diretório no cliente onde você irá montar o volume.

      Observe que neste exemplo de sintaxe, o argumento do dispositivo aponta para um nome de host seguido por dois pontos e depois o nome do volume. O GlusterFS abstrai os diretórios de armazenamento reais em cada host, o que significa que este comando não procura montar o diretório /gluster-storage, mas sim o volume volume1.

      Também observe que você só precisa especificar um membro do cluster de armazenamento. Isso pode ser qualquer um dos nós, pois o serviço do GlusterFS os trata como uma máquina.

      Execute o seguinte comando em sua máquina cliente (gluster2) para montar o volume no diretório /storage-pool que você criou:

      • sudo mount -t glusterfs gluster0.example.com:/volume1 /storage-pool

      Depois disso, execute o comando df. Isso irá exibir a quantidade de espaço em disco disponível para sistemas de arquivos aos quais o usuário que o invocou tem acesso:

      Este comando irá mostrar que o volume do GlusterFS está montado na localização correta:

      Output

      Filesystem 1K-blocks Used Available Use% Mounted on . . . gluster0.example.com:/volume1 50633164 1938032 48695132 4% /storage-pool

      Agora, teste se todos os dados que você escreve no volume no seu cliente são replicados para os nós do servidor como esperado.

      Passo 6 — Testando recursos de redundância

      Agora que você configurou seu cliente para usar sua pool de armazenamento e volume, você pode testar sua funcionalidade.

      Em sua máquina cliente (gluster2), navegue até o ponto de montagem que você definiu no passo anterior:

      Em seguida, crie alguns arquivos de teste. O comando a seguir cria dez arquivos vazios separados em sua pool de armazenamento:

      • sudo touch file_{0..9}.test

      Se você examinar os diretórios de armazenamento definidos anteriormente em cada host de armazenamento, você irá descobrir que todos os arquivos estão presentes em cada sistema.

      No gluster0:

      Output

      file_0.test file_2.test file_4.test file_6.test file_8.test file_1.test file_3.test file_5.test file_7.test file_9.test

      Da mesma forma, no gluster1:

      Output

      file_0.test file_2.test file_4.test file_6.test file_8.test file_1.test file_3.test file_5.test file_7.test file_9.test

      Como estes resultados mostram, os arquivos de teste que você adicionou ao cliente também foram escritos em ambos os seus nós.

      Se um dos nós em seu cluster de armazenamento estiver desligado em algum momento, eles podem perder a sincronia com o pool de armazenamento, caso haja alterações feitas no sistema de arquivos. Fazer uma operação de leitura no ponto de montagem do cliente depois que o nó voltar a ficar online irá alertar o nó para obter qualquer arquivo em falta:

      Agora que verificou que seu volume de armazenamento está montado corretamente e pode replicar dados para ambas as máquinas no cluster, você pode bloquear o acesso à pool de armazenamento.

      Passo 7 — Restringindo recursos de redundância

      Neste momento, qualquer computador pode se conectar ao seu volume de armazenamento sem nenhuma restrição. Você pode mudar isso alterando a opção auth.allow que define os endereços IP de quais clientes têm acesso ao volume.

      Se estiver usando a configuração do /etc/hosts, os nomes que você configurou para cada servidor não serão encaminhados corretamente. Você deve usar um endereço IP estático disso.ao invés disso. Por outro lado, se você estiver usando registros DNS, o nome de domínio que você configurou irá funcionar aqui.

      Em um dos seus nós de armazenamento (gluster0 ou gluster1), execute o seguinte comando:

      • sudo gluster volume set volume1 auth.allow gluster2_ip_address

      Se o comando terminar com sucesso, ele irá retornar este resultado:

      Output

      volume set: success

      Se precisar remover a restrição a qualquer momento, você pode digitar:

      • sudo gluster volume set volume1 auth.allow *

      Isso permitirá as conexões de qualquer máquina novamente. Isso é inseguro, mas pode ser útil para depurar problemas.

      Se você tiver vários clientes, pode especificar os endereços IP ou nomes de domínio deles ao mesmo tempo (dependendo se estiver usando a resolução /etc/hosts ou a resolução de nomes de host DNS), separados por vírgulas:

      • sudo gluster volume set volume1 auth.allow gluster_client1_ip,gluster_client2_ip

      Seu pool de armazenamento agora está configurado, seguro e pronto para uso. Em seguida, você irá aprender alguns comandos que lhe ajudarão a obter informações sobre o status da sua pool de armazenamento.

      Quando começar a alterar algumas das configurações para seu armazenamento do GlusterFS, você pode se confundir com as opções disponíveis, quais volumes estão ativos e quais nós estão associados a cada volume.

      Há vários comandos diferentes que estão disponíveis em seus nós para recuperar esses dados e interagir com sua pool de armazenamento.

      Se você quiser informações sobre cada um dos seus volumes, execute o comando gluster volume info:

      Output

      Volume Name: volume1 Type: Replicate Volume ID: a1e03075-a223-43ab-a0f6-612585940b0c Status: Started Snapshot Count: 0 Number of Bricks: 1 x 2 = 2 Transport-type: tcp Bricks: Brick1: gluster0.example.com:/gluster-storage Brick2: gluster1.example.com:/gluster-storage Options Reconfigured: auth.allow: gluster2_ip_address transport.address-family: inet storage.fips-mode-rchecksum: on nfs.disable: on performance.client-io-threads: off

      Da mesma forma, para obter informações sobre quaisquer pares aos quais este nó esteja conectado, você pode digitar:

      Number of Peers: 1
      
      Hostname: gluster0.example.com
      Uuid: cb00a2fc-2384-41ac-b2a8-e7a1793bb5a9
      State: Peer in Cluster (Connected)
      

      Se quiser informações detalhadas sobre como cada nó está desempenhando, faça um perfil de volume digitando:

      • sudo gluster volume profile volume_name start

      Quando este comando for concluído, obtenha as informações que foram reunidas digitando:

      • sudo gluster volume profile volume_name info

      Output

      Brick: gluster0.example.com:/gluster-storage -------------------------------------------- Cumulative Stats: %-latency Avg-latency Min-Latency Max-Latency No. of calls Fop --------- ----------- ----------- ----------- ------------ ---- 0.00 0.00 us 0.00 us 0.00 us 30 FORGET 0.00 0.00 us 0.00 us 0.00 us 36 RELEASE 0.00 0.00 us 0.00 us 0.00 us 38 RELEASEDIR Duration: 5445 seconds Data Read: 0 bytes Data Written: 0 bytes Interval 0 Stats: %-latency Avg-latency Min-Latency Max-Latency No. of calls Fop --------- ----------- ----------- ----------- ------------ ---- 0.00 0.00 us 0.00 us 0.00 us 30 FORGET 0.00 0.00 us 0.00 us 0.00 us 36 RELEASE 0.00 0.00 us 0.00 us 0.00 us 38 RELEASEDIR Duration: 5445 seconds Data Read: 0 bytes Data Written: 0 bytes . . .

      Como mostrado anteriormente, para uma lista de todos os componentes associados ao GlusterFS em execução em cada um dos seus nós, execute o comando gluster volume status:

      • sudo gluster volume status

      Output

      Status of volume: volume1 Gluster process TCP Port RDMA Port Online Pid ------------------------------------------------------------------------------ Brick gluster0.example.com:/gluster-storage 49152 0 Y 19003 Brick gluster1.example.com:/gluster-storage 49152 0 Y 19040 Self-heal Daemon on localhost N/A N/A Y 19061 Self-heal Daemon on gluster0.example.com N/A N/A Y 19836 Task Status of Volume volume1 ------------------------------------------------------------------------------ There are no active volume tasks

      Se você estiver administrando seus volumes de armazenamento do GlusterFS, pode ser uma boa ideia adentrar-se no console do GlusterFS. Isso permitirá que você interaja com seu ambiente do GlusterFS sem precisar digitar sudo gluster antes de tudo:

      Isso lhe dará um prompt onde você pode digitar seus comandos. O help é um bom recurso para se orientar:

      Output

      peer help - display help for peer commands volume help - display help for volume commands volume bitrot help - display help for volume bitrot commands volume quota help - display help for volume quota commands snapshot help - display help for snapshot commands global help - list global commands

      Quando você terminar, execute exit para sair do console do Gluster:

      Com isso, você está pronto para começar a integrar o GlusterFS com seu próximo aplicativo.

      Conclusão

      Após completar este tutorial, você tem agora um sistema de armazenamento redundante que lhe permitirá escrever em dois servidores separados simultaneamente. Isso pode ser útil para uma série de aplicativos e pode garantir que seus dados estejam disponíveis mesmo quando um servidor cair.



      Source link