Como Configurar um Banco de Dados Remoto para Otimizar o Desempenho do Site com o MySQL no Ubuntu 18.04

Introdução

À medida que sua aplicação ou site cresce, pode chegar um momento em que você superou a configuração atual do seu servidor. Se você estiver hospedando o seu servidor web e o back-end do banco de dados na mesma máquina, pode ser uma boa ideia separar essas duas funções para que cada uma possa operar em seu próprio hardware e compartilhar a carga de responder às solicitações dos visitantes.

Neste guia, veremos como configurar um servidor de banco de dados MySQL remoto ao qual sua aplicação web pode se conectar. Usaremos o WordPress como exemplo para ter algo para trabalhar, mas a técnica é amplamente aplicável a qualquer aplicação suportada pelo MySQL.

Pré-requisitos

Antes de iniciar este tutorial, você precisará de:

  • Dois servidores Ubuntu 18.04. Cada um deles deve ter um usuário não-root com privilégios sudo e um firewall UFW habilitado, conforme descrito em nosso tutorial de Configuração Inicial de servidor com Ubuntu 18.04. Um desses servidores hospedará seu back-end MySQL e, ao longo deste guia, o chamaremos de servidor de banco de dados. O outro se conectará ao seu servidor de banco de dados remotamente e atuará como seu servidor web; da mesma forma, iremos nos referir a ele como servidor web ao longo deste guia.
  • Nginx e PHP instalado em seu servidor web. Nosso tutorial How To Install Linux, Nginx, MySQL, PHP (LEMP stack) in Ubuntu 18.04 o guiará no processo, mas observe que você deve pular o Passo 2 deste tutorial, que se concentra na instalação do MySQL, pois você instalará o MySQL no seu servidor de banco de dados.
  • MySQL instalado em seu servidor de banco de dados. Siga o tutorial Como Instalar o MySQL no Ubuntu 18.04 para configurar isso.
  • Opcionalmente (mas altamente recomendado), certificados TLS/SSL da Let’s Encrypt instalados em seu servidor web. Você precisará comprar um nome de domínio e ter registros DNS configurados para seu servidor, mas os certificados em si são gratuitos. Nosso guia Como Proteger o Nginx com o Let’s Encrypt no Ubuntu 18.04 lhe mostrará como obter esses certificados.

Passo 1 — Configurando o MySQL para Escutar Conexões Remotas

Ter os dados armazenados em um servidor separado é uma boa maneira de expandir elegantemente após atingir o limite máximo de desempenho de uma configuração de uma única máquina. Ela também fornece a estrutura básica necessária para balancear a carga e expandir sua infraestrutura ainda mais posteriormente. Após instalar o MySQL, seguindo o tutorial de pré-requisitos, você precisará alterar alguns valores de configuração para permitir conexões a partir de outros computadores.

A maioria das mudanças na configuração do servidor MySQL pode ser feita no arquivo mysqld.cnf, que é armazenado no diretório /etc/mysql/mysql.conf.d/ por padrão. Abra este arquivo em seu servidor de banco de dados com privilégios de root em seu editor preferido. Aqui, iremos usar o nano:

  • sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Este arquivo é dividido em seções indicadas por labels entre colchetes ([ e ]). Encontre a seção com o label mysqld:

/etc/mysql/mysql.conf.d/mysqld.cnf
. . . [mysqld] . . . 

Nesta seção, procure um parâmetro chamado bind-address. Isso informa ao software do banco de dados em qual endereço de rede escutar as conexões.

Por padrão, isso está definido como 127.0.0.1, significando que o MySQL está configurado para escutar apenas conexões locais. Você precisa alterar isso para fazer referência a um endereço IP externo onde seu servidor pode ser acessado.

Se os dois servidores estiverem em um datacenter com recursos de rede privada, use o IP da rede privada do seu servidor de banco de dados. Caso contrário, você pode usar seu endereço IP público:

/etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld] . . . bind-address = ip_do_servidor_de_banco_de_dados 

Como você se conectará ao seu banco de dados pela Internet, é recomendável que você exija conexões criptografadas para manter seus dados seguros. Se você não criptografar sua conexão MySQL, qualquer pessoa na rede poderá fazer sniff por informações confidenciais entre seus servidores web e de banco de dados. Para criptografar conexões MySQL, adicione a seguinte linha após a linha bind-address que você acabou de atualizar:

/etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld] . . . require_secure_transport = on . . . 

Salve e feche o arquivo quando terminar. Se você estiver usando nano, faça isso pressionando CTRL+X, Y e, em seguida, ENTER.

Para que as conexões SSL funcionem, você precisará criar algumas chaves e certificados. O MySQL vem com um comando que os configura automaticamente. Execute o seguinte comando, que cria os arquivos necessários. Ele também os torna legíveis pelo servidor MySQL, especificando o UID do usuário mysql:

  • sudo mysql_ssl_rsa_setup --uid=mysql

Para forçar o MySQL a atualizar sua configuração e ler as novas informações de SSL, reinicie o banco de dados:

  • sudo systemctl restart mysql

Para confirmar que o servidor agora está escutando na interface externa, execute o seguinte comando netstat:

  • sudo netstat -plunt | grep mysqld
Output
tcp 0 0 ip_do_servidor_de_banco_de_dados:3306 0.0.0.0:* LISTEN 27328/mysqld

O netstat imprime estatísticas sobre o sistema de rede do seu servidor. Esta saída nos mostra que um processo chamado mysqld está anexado ao ip_do_servidor_de_banco_de_dados na porta 3306, a porta padrão do MySQL, confirmando que o servidor está escutando na interface apropriada.

Em seguida, abra essa porta no firewall para permitir o tráfego através dela:

  • sudo ufw allow mysql

Essas são todas as alterações de configuração que você precisa fazer no MySQL. A seguir, veremos como configurar um banco de dados e alguns perfis de usuário, um dos quais você usará para acessar o servidor remotamente.

Passo 2 — Configurando um Banco de Dados para o WordPress e Credenciais Remotas

Embora o próprio MySQL agora esteja escutando em um endereço IP externo, atualmente não há usuários ou bancos de dados habilitados para controle remoto configurados. Vamos criar um banco de dados para o WordPress e um par de usuários que possam acessá-lo.

Comece conectando-se ao MySQL como o usuário root do MySQL:

  • sudo mysql

Nota: Se você tiver a autenticação por senha ativada, conforme descrito no Passo 3 do pré-requisito do tutorial do MySQL, você precisará usar o seguinte comando para acessar o shell do MySQL:

  • mysql -u root -p

Depois de executar este comando, você será solicitado a fornecer sua senha de root do MySQL e, após inseri-la, receberá um novo prompt mysql>.

No prompt do MySQL, crie um banco de dados que o WordPress usará. Pode ser útil atribuir a esse banco de dados um nome reconhecível para que você possa identificá-lo facilmente mais tarde. Aqui, vamos chamá-lo de wordpress:

  • CREATE DATABASE wordpress;

Agora que você criou seu banco de dados, você precisará criar um par de usuários. Criaremos um usuário somente local e um usuário remoto vinculado ao endereço IP do servidor web.

Primeiro, crie seu usuário local, wpuser, e faça com que esta conta corresponda apenas às tentativas de conexão local usando localhost na declaração:

  • CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'senha';

Em seguida, conceda a esta conta acesso total ao banco de dados wordpress:

  • GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';

Agora, esse usuário pode executar qualquer operação no banco de dados do WordPress, mas essa conta não pode ser usada remotamente, pois corresponde apenas às conexões da máquina local. Com isso em mente, crie uma conta complementar que corresponda às conexões exclusivamente do seu servidor web. Para isso, você precisará do endereço IP do seu servidor web.

Observe que você deve usar um endereço IP que utilize a mesma rede que você configurou no seu arquivo mysqld.cnf. Isso significa que, se você especificou um IP de rede privada no arquivo mysqld.cnf, precisará incluir o IP privado do seu servidor web nos dois comandos a seguir. Se você configurou o MySQL para usar a internet pública, você deve fazer isso corresponder ao endereço IP público do servidor web.

  • CREATE USER 'remotewpuser'@'ip_do_servidor_web' IDENTIFIED BY 'senha';

Depois de criar sua conta remota, conceda a ela os mesmos privilégios que o usuário local:

  • GRANT ALL PRIVILEGES ON wordpress.* TO 'remotewpuser'@'ip_do_servidor_web';

Por fim, atualize os privilégios para que o MySQL saiba começar a usá-los:

  • FLUSH PRIVILEGES;

Então saia do prompt do MySQL digitando:

  • exit

Agora que você configurou um novo banco de dados e um usuário habilitado remotamente, você pode testar se consegue se conectar ao banco de dados a partir do seu servidor web.

Passo 3 — Testando Conexões Remotas e Locais

Antes de continuar, é melhor verificar se você pode se conectar ao seu banco de dados tanto a partir da máquina local — seu servidor de banco de dados — quanto pelo seu servidor web.

Primeiro, teste a conexão local a partir do seu servidor de banco de dados tentando fazer login com sua nova conta:

  • mysql -u wpuser -p

Quando solicitado, digite a senha que você configurou para esta conta.

Se você receber um prompt do MySQL, então a conexão local foi bem-sucedida. Você pode sair novamente digitando:

  • exit

Em seguida, faça login no seu servidor web para testar as conexões remotas:

  • ssh sammy@ip_do_servidor_web

Você precisará instalar algumas ferramentas de cliente para MySQL em seu servidor web para acessar o banco de dados remoto. Primeiro, atualize o cache de pacotes local se você não tiver feito isso recentemente:

  • sudo apt update

Em seguida, instale os utilitários de cliente do MySQL:

  • sudo apt install mysql-client

Depois disso, conecte-se ao seu servidor de banco de dados usando a seguinte sintaxe:

  • mysql -u remotewpuser -h ip_do_servidor_de_banco_de_dados -p

Novamente, você deve certificar-se que está usando o endereço IP correto para o servidor de banco de dados. Se você configurou o MySQL para escutar na rede privada, digite o IP da rede privada do seu banco de dados. Caso contrário, digite o endereço IP público do seu servidor de banco de dados.

Você será solicitado a inserir a senha da sua conta remotewpuser. Depois de inseri-la, e se tudo estiver funcionando conforme o esperado, você verá o prompt do MySQL. Verifique se a conexão está usando SSL com o seguinte comando:

  • status

Se a conexão realmente estiver usando SSL, a linha SSL: indicará isso, como mostrado aqui:

Output
-------------- mysql Ver 14.14 Distrib 5.7.18, for Linux (x86_64) using EditLine wrapper Connection id: 52 Current database: Current user: remotewpuser@203.0.113.111 SSL: Cipher in use is DHE-RSA-AES256-SHA Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.18-0ubuntu0.16.04.1 (Ubuntu) Protocol version: 10 Connection: 203.0.113.111 via TCP/IP Server characterset: latin1 Db characterset: latin1 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 3 hours 43 min 40 sec Threads: 1 Questions: 1858 Slow queries: 0 Opens: 276 Flush tables: 1 Open tables: 184 Queries per second avg: 0.138 --------------

Depois de verificar que você pode se conectar remotamente, vá em frente e saia do prompt:

  • exit

Com isso, você verificou o acesso local e o acesso a partir do servidor web, mas não verificou se outras conexões serão recusadas. Para uma verificação adicional, tente fazer o mesmo em um terceiro servidor para o qual você não configurou uma conta de usuário específica para garantir que esse outro servidor não tenha o acesso concedido.

Observe que antes de executar o seguinte comando para tentar a conexão, talvez seja necessário instalar os utilitários de cliente do MySQL, como você fez acima:

  • mysql -u wordpressuser -h ip_do_servidor_de_banco_de_dados -p

Isso não deve ser concluído com êxito e deve gerar um erro semelhante a este:

Output
ERROR 1130 (HY000): Host '203.0.113.12' is not allowed to connect to this MySQL server

Isso é esperado, já que você não criou um usuário do MySQL que tem permissão para se conectar a partir deste servidor, e também é desejado, uma vez que você quer ter certeza de que seu servidor de banco de dados negará o acesso de usuários não autorizados ao seu servidor do MySQL.

Após testar com êxito sua conexão remota, você pode instalar o WordPress em seu servidor web.

Passo 4 — Instalando o WordPress

Para demonstrar os recursos do seu novo servidor MySQL com capacidade remota, passaremos pelo processo de instalação e configuração do WordPress — o popular sistema de gerenciamento de conteúdo — em seu servidor web. Isso exigirá que você baixe e extraia o software, configure suas informações de conexão e então execute a instalação baseada em web do WordPress.

No seu servidor web, faça o download da versão mais recente do WordPress para o seu diretório home:

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

Extraia os arquivos, que criarão um diretório chamado wordpress no seu diretório home:

  • tar xzvf latest.tar.gz

O WordPress inclui um arquivo de configuração de exemplo que usaremos como ponto de partida. Faça uma cópia deste arquivo, removendo -sample do nome do arquivo para que ele seja carregado pelo WordPress:

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

Quando você abre o arquivo, sua primeira abordagem será ajustar algumas chaves secretas para fornecer mais segurança à sua instalação. O WordPress fornece um gerador seguro para esses valores, para que você não precise criar bons valores por conta própria. Eles são usados apenas internamente, portanto, não prejudicará a usabilidade ter valores complexos e seguros aqui.

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

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

Isso imprimirá algumas chaves na sua saída. Você as adicionará momentaneamente ao seu arquivo wp-config.php:

Atenção! É importante que você solicite seus próprios valores únicos sempre. Não copie os valores mostrados aqui!

Output
define('AUTH_KEY', 'L4|2Yh(giOtMLHg3#] DO NOT COPY THESE VALUES %G00o|te^5YG@)'); define('SECURE_AUTH_KEY', 'DCs-k+MwB90/-E(=!/ DO NOT COPY THESE VALUES +WBzDq:7U[#Wn9'); define('LOGGED_IN_KEY', '*0kP!|VS.K=;#fPMlO DO NOT COPY THESE VALUES +&[%8xF*,18c @'); define('NONCE_KEY', 'fmFPF?UJi&(j-{8=$ - DO NOT COPY THESE VALUES CCZ?Q+_~1ZU~;G'); define('AUTH_SALT', '@qA7f}2utTEFNdnbEa DO NOT COPY THESE VALUES t}Vw+8=K%20s=a'); define('SECURE_AUTH_SALT', '%BW6s+d:7K?-`C%zw4 DO NOT COPY THESE VALUES 70U}PO1ejW+7|8'); define('LOGGED_IN_SALT', '-l>F:-dbcWof%4kKmj DO NOT COPY THESE VALUES 8Ypslin3~d|wLD'); define('NONCE_SALT', '4J(<`4&&F (WiK9K#] DO NOT COPY THESE VALUES ^ZikS`es#Fo:V6');

Copie a saída que você recebeu para a área de transferência e abra o arquivo de configuração no seu editor de texto:

  • nano ~/wordpress/wp-config.php

Encontre a seção que contém os valores fictícios para essas configurações. Será algo parecido com isto:

/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 você copiou a partir da linha de comando.

Em seguida, insira as informações de conexão para seu banco de dados remoto. Essas linhas de configuração estão na parte superior do arquivo, logo acima de onde você colou suas chaves. Lembre-se de usar o mesmo endereço IP que você usou no teste de banco de dados remoto anteriormente:

/wordpress/wp-config.php
. . . /** The name of the database for WordPress */ define('DB_NAME', 'wordpress');  /** MySQL database username */ define('DB_USER', 'remotewpuser');  /** MySQL database password */ define('DB_PASSWORD', 'password');  /** MySQL hostname */ define('DB_HOST', 'db_server_ip'); . . . 

E, finalmente, em qualquer lugar do arquivo, adicione a seguinte linha que diz ao WordPress para usar uma conexão SSL para o nosso banco de dados MySQL:

/wordpress/wp-config.php
define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL); 

Salve e feche o arquivo.

Em seguida, copie os arquivos e diretórios encontrados no diretório ~/wordpress para a raiz de documentos do Nginx. Observe que este comando inclui a flag -a para garantir que todas as permissões existentes sejam transferidas:

  • sudo cp -a ~/wordpress/* /var/www/html

Depois disso, a única coisa a fazer é modificar a propriedade do arquivo. Altere a propriedade de todos os arquivos na raiz de documentos para www-data, o usuário padrão do servidor web do Ubuntu:

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

Com isso, o WordPress está instalado e você está pronto para executar sua rotina de configuração baseada em web.

Passo 5 — Configurando o WordPress Através da Interface Web

O WordPress possui um processo de configuração baseado na web. Conforme você avança, ele fará algumas perguntas e instalará todas as tabelas necessárias no seu banco de dados. Aqui, abordaremos as etapas iniciais da configuração do WordPress, que você pode usar como ponto de partida para criar seu próprio site personalizado que usa um back-end de banco de dados remoto.

Navegue até o nome de domínio (ou endereço IP público) associado ao seu servidor web:

http://example.com 

Você verá uma tela de seleção de idioma para o instalador do WordPress. Selecione o idioma apropriado e clique na tela principal de instalação:

WordPress install screen

Depois de enviar suas informações, você precisará fazer login na interface de administração do WordPress usando a conta que você acabou de criar. Você será direcionado para um painel onde poderá personalizar seu novo site WordPress.

Conclusão

Ao seguir este tutorial, você configurou um banco de dados MySQL para aceitar conexões protegidas por SSL a partir de uma instalação remota do WordPress. Os comandos e técnicas usados neste guia são aplicáveis a qualquer aplicação web escrita em qualquer linguagem de programação, mas os detalhes específicos da implementação serão diferentes. Consulte a documentação do banco de dados da aplicação ou linguagem para obter mais informações.

DigitalOcean Community Tutorials

Etapas Adicionais Recomendadas para Novos Servidores CentOS 7

Introdução

Depois de definir a configuração mínima para um novo servidor, existem algumas etapas adicionais que são altamente recomendadas na maioria dos casos. Neste guia, continuaremos a configuração de nossos servidores, abordando alguns procedimentos recomendados, mas opcionais.

Pré-requisitos e Objetivos

Antes de iniciar este guia, você deve passar pelo guia de Configuração Inicial do Servidor com o CentOS 7. Isso é necessário para configurar suas contas de usuário, configurar a elevação de privilégios com o sudo e bloquear o SSH por segurança.

Depois de concluir o guia acima, você pode continuar com este artigo. Neste guia, nos concentraremos na configuração de alguns componentes opcionais, mas recomendados. Isso envolverá a configuração do nosso sistema com um firewall e um arquivo de swap, e configurar a sincronização do Network Time Protocol.

Configurando um Firewall Básico

Os firewalls fornecem um nível básico de segurança para o seu servidor. Esses aplicativos são responsáveis por negar tráfego a todas as portas do servidor com exceções das portas/serviços que você aprovou. O CentOS vem com um firewall chamado firewalld. Uma ferramenta chamada firewall-cmd pode ser usada para configurar suas políticas de firewall. Nossa estratégia básica será bloquear tudo o que não tivermos uma boa razão para manter em aberto. Primeiro instale o firewalld:

  • sudo yum install firewalld

O serviço firewalld tem a capacidade de fazer modificações sem perder as conexões atuais, assim podemos ativá-lo antes de criar nossas exceções:

  • sudo systemctl start firewalld

Agora que o serviço está funcionando, podemos usar o utilitário firewall-cmd para obter e definir informações de política para o firewall. O aplicativo firewalld usa o conceito de “zonas” para rotular a confiabilidade dos outros hosts em uma rede. Essa rotulagem nos dá a capacidade de atribuir regras diferentes, dependendo de quanto confiamos em uma rede.

Neste guia, estaremos ajustando somente as políticas para a zona padrão ou default. Quando recarregarmos nosso firewall, essa será a zona aplicada às nossas interfaces. Devemos começar adicionando exceções ao nosso firewall para serviços aprovados. O mais essencial deles é o SSH, já que precisamos manter o acesso administrativo remoto ao servidor.

Se você não modificou a porta em que o daemon SSH está sendo executado, é possível ativar o serviço pelo nome digitando:

  • sudo firewall-cmd --permanent --add-service=ssh

Se você alterou a porta SSH do seu servidor, você terá que especificar a nova porta explicitamente. Você também precisará incluir o protocolo que o serviço utiliza. Somente digite o seguinte caso seu servidor SSH já tenha sido reiniciado para usar a nova porta:

  • sudo firewall-cmd --permanent --remove-service=ssh
  • sudo firewall-cmd --permanent --add-port=4444/tcp

Isso é o mínimo necessário para manter o acesso administrativo ao servidor. Se você planeja executar serviços adicionais, também precisa abrir o firewall para esses serviços.

Se você planeja executar um servidor web HTTP convencional, você precisará habilitar o serviço http:

  • sudo firewall-cmd --permanent --add-service=http

Se você planeja executar um servidor web com SSL/TLS ativado, você também deve permitir o tráfego de https:

  • sudo firewall-cmd --permanent --add-service=https

Se você precisar que o email SMTP esteja ativado, você pode digitar:

  • sudo firewall-cmd --permanent --add-service=smtp

Para ver quaisquer serviços adicionais que você possa ativar por nome, digite:

  • sudo firewall-cmd --get-services

Quando terminar, você poderá ver a lista das exceções que serão implementadas digitando:

  • sudo firewall-cmd --permanent --list-all

Quando você estiver pronto para implementar as mudanças, recarregue o firewall:

  • sudo firewall-cmd --reload

Se, após o teste, tudo funcionar conforme o esperado, você deverá certificar-se de que o firewall será iniciado na inicialização:

  • sudo systemctl enable firewalld

Lembre-se de que você terá que abrir explicitamente o firewall (com serviços ou portas) para quaisquer serviços adicionais que você venha a configurar posteriormente.

Configurar Fuso Horário e Sincronização do Network Time Protocol

O próximo passo é ajustar as configurações de localização do seu servidor e configurar a sincronização do Network Time Protocol (NTP).

O primeiro passo garantirá que seu servidor esteja operando no fuso horário correto. O segundo passo configurará seu servidor para sincronizar o relógio do sistema com o horário padrão mantido por uma rede global de servidores NTP. Isso ajudará a evitar algum comportamento inconsistente que pode surgir com relógios fora de sincronia.

Configurar Fusos Horários

Nosso primeiro passo é definir o fuso horário do nosso servidor. Este é um procedimento muito simples que pode ser realizado usando o comando timedatectl:

Primeiro, dê uma olhada nos fusos horários disponíveis digitando:

  • sudo timedatectl list-timezones

Isto lhe dará uma lista dos fusos horários disponíveis para o seu servidor. Quando você encontrar a configuração de região/fuso horário que estiver correta para o seu servidor, defina-a digitando:

  • sudo timedatectl set-timezone região/fuso_horário

Por exemplo, para configurá-lo para o horário do leste dos Estados Unidos, você pode digitar:

  • sudo timedatectl set-timezone America/New_York

Seu sistema será atualizado para usar o fuso horário selecionado. Você pode confirmar isso digitando:

  • sudo timedatectl

Configurar a Sincronização NTP

Agora que você tem o seu fuso horário definido, devemos configurar o NTP. Isso permitirá que seu computador fique em sincronia com outros servidores, levando a uma maior previsibilidade nas operações que dependem da hora correta.

Para a sincronização NTP, usaremos um serviço chamado ntp, que podemos instalar a partir dos repositórios padrão do CentOS:

  • sudo yum install ntp

Em seguida, você precisa iniciar o serviço para esta sessão. Também habilitaremos o serviço para que ele seja iniciado automaticamente sempre que o servidor for inicializado:

  • sudo systemctl start ntpd
  • sudo systemctl enable ntpd

Seu servidor agora corrigirá automaticamente o relógio do sistema para se alinhar aos servidores globais.

Criar um Arquivo de Swap

Adicionar “swap” a um servidor Linux permite que o sistema mova as informações acessadas por um programa em execução com menos frequência da RAM para um local no disco. Acessar os dados armazenados no disco é muito mais lento do que acessar a RAM, mas ter o swap disponível pode ser a diferença entre o aplicativo permanecer ativo e a falha. Isso é especialmente útil se você planeja hospedar bancos de dados em seu sistema.

Conselhos sobre o melhor tamanho para um espaço de swap variam significativamente dependendo da fonte consultada. Geralmente, um valor igual ou o dobro da quantidade de RAM do seu sistema é um bom ponto de partida.

Aloque o espaço que você deseja usar para o seu arquivo de swap usando o utilitário fallocate. Por exemplo, se precisarmos de um arquivo de 4 Gigabytes, podemos criar um arquivo de swap localizado em /swapfile digitando:

  • sudo fallocate -l 4G /swapfile

Depois de criar o arquivo, precisamos restringir o acesso a ele para que outros usuários ou processos não consigam ver o que é gravado lá:

  • sudo chmod 600 /swapfile

Agora temos um arquivo com as permissões corretas. Para dizer ao nosso sistema para formatar o arquivo para swap, podemos digitar:

  • sudo mkswap /swapfile

Agora, diga ao sistema que ele pode usar o arquivo de swap digitando:

  • sudo swapon /swapfile

Nosso sistema está usando o arquivo de swap para esta sessão, mas precisamos modificar um arquivo de sistema para que nosso servidor faça isso automaticamente na inicialização. Você pode fazer isso digitando:

  • sudo sh -c 'echo "/swapfile none swap sw 0 0" >> /etc/fstab'

Com essa adição, seu sistema deve usar seu arquivo de swap automaticamente a cada inicialização.

Para Onde Ir a partir Daqui?

Agora você tem uma configuração inicial muito decente para o seu servidor Linux. A partir daqui, existem alguns lugares que você pode ir. Primeiro, você pode querer tirar um instantâneo ou snapshot do seu servidor em sua configuração atual.

Tirando um Snapshot da sua Configuração atual

Se você está satisfeito com sua configuração e deseja usar isso como uma base para futuras instalações, você pode tirar um snapshot do seu servidor através do painel de controle da DigitalOcean. A partir de outubro de 2016, os snapshots custam $ 0.05 por gigabyte por mês, com base na quantidade de espaço utilizado no sistema de arquivos.

Para fazer isso, desligue seu servidor pela linha de comando. Embora seja possível fazer um snapshot de um sistema em execução, o desligamento garante que os arquivos no disco estejam todos em um estado consistente:

  • sudo poweroff

Agora, no painel de controle da DigitalOcean, você pode tirar um snapshot visitando a guia “Snapshots” do seu servidor:

DigitalOcean snapshot

Depois de tirar seu snapshot, você poderá usar essa imagem como base para instalações futuras, selecionando o snapshot a partir da guia “My Snapshots” para imagens durante o processo de criação:

DigitalOcean use snapshot

Recursos Adicionais e Próximos Passos

A partir daqui, o seu caminho depende inteiramente do que você deseja fazer com o seu servidor. A lista de guias abaixo não é de forma alguma exaustiva, mas representa algumas das configurações mais comuns que os usuários recorrem:

Conclusão

Nesse ponto, você deve saber como configurar uma base sólida para seus novos servidores. Espero que você também tenha uma boa ideia para os próximos passos. Sinta-se à vontade para explorar o site para mais ideias que você pode implementar em seu servidor.

DigitalOcean Community Tutorials

Criando Containers Otimizados para o Kubernetes

Introdução

Imagens de container são o formato de empacotamento principal para a definição de aplicações no Kubernetes. Usadas como base para pods e outros objetos, as imagens desempenham um papel importante ao aproveitar os recursos do Kubernetes para executar aplicações com eficiência na plataforma. Imagens bem projetadas são seguras, altamente eficientes e focadas. Elas são capazes de reagir a dados de configuração ou instruções fornecidas pelo Kubernetes e também implementar endpoints que o sistema de orquestração usa para entender o estado interno da aplicação.

Neste artigo, vamos apresentar algumas estratégias para criar imagens de alta qualidade e discutir algumas metas gerais para ajudar a orientar suas decisões ao containerizar aplicações. Vamos nos concentrar na criação de imagens destinadas a serem executadas no Kubernetes, mas muitas das sugestões se aplicam igualmente à execução de containers em outras plataformas de orquestração ou em outros contextos.

Características de Imagens de Container Eficientes

Antes de passarmos por ações específicas a serem tomadas ao criar imagens de container, falaremos sobre o que torna boa uma imagem de container. Quais devem ser seus objetivos ao projetar novas imagens? Quais características e quais comportamentos são mais importantes?

Algumas qualidades que podem ser indicadas são:

Um propósito único e bem definido

Imagens de container devem ter um único foco discreto. Evite pensar em imagens de container como máquinas virtuais, onde pode fazer sentido agrupar funcionalidades relacionadas. Em vez disso, trate suas imagens de container como utilitários Unix, mantendo um foco estrito em fazer bem uma pequena coisa. As aplicações podem ser coordenadas fora do escopo do container para compor funcionalidades complexas.

Design genérico com a capacidade de injetar configuração em tempo de execução

Imagens de container devem ser projetadas com a reutilização em mente quando possível. Por exemplo, a capacidade de ajustar a configuração em tempo de execução geralmente é necessária para atender aos requisitos básicos, como testar suas imagens antes de fazer o deploy em produção. Imagens pequenas e genéricas podem ser combinadas em diferentes configurações para modificar o comportamento sem criar novas imagens.

Tamanho pequeno da imagem

Imagens menores têm vários benefícios em ambientes em cluster, como o Kubernetes. Elas baixam rapidamente para novos nodes e geralmente têm um conjunto menor de pacotes instalados, o que pode melhorar a segurança. As imagens de container reduzidas simplificam o debug de problemas, minimizando a quantidade de software envolvida.

Estado gerenciado externamente

Containers em ambientes clusterizados experimentam um ciclo de vida muito volátil, incluindo desligamentos planejados e não planejados devido à escassez de recursos, dimensionamento ou falhas de node. Para manter a consistência, auxiliar na recuperação e na disponibilidade de seus serviços e evitar a perda de dados, é essencial armazenar o estado da aplicação em um local estável fora do container.

Fácil de entender

É importante tentar manter as imagens de container tão simples e fáceis de entender quanto possível. Ao solucionar problemas, a capacidade de raciocinar facilmente sobre o problema exibindo a configuração da imagem do container ou testando o comportamento dele pode ajudá-lo a alcançar uma resolução mais rapidamente. Pensar em imagens de container como um formato de empacotamento para sua aplicação, em vez de uma configuração de máquina, pode ajudá-lo a encontrar o equilíbrio certo.

Siga as práticas recomendadas do software em container

As imagens devem ter como objetivo trabalhar dentro do modelo de container, em vez de agir contra ele. Evite implementar práticas convencionais de administração de sistema, como incluir sistemas init completos e aplicações como daemon. Faça o log para a saída padrão, para que o Kubernetes possa expor os dados aos administradores, em vez de usar um daemon de log interno. Cada um desses itens difere das melhores práticas para sistemas operacionais completos.

Aproveite totalmente os recursos do Kubernetes

Além de estar em conformidade com o modelo de container, é importante entender e reconciliar o ambiente e as ferramentas que o Kubernetes fornece. Por exemplo, fornecer endpoints para verificações de prontidão e disponibilidade ou ajustar a operação com base nas alterações na configuração ou no ambiente pode ajudar suas aplicações a usar o ambiente de deploy dinâmico do Kubernetes a seu favor.

Agora que estabelecemos algumas das qualidades que definem imagens de container altamente funcionais, podemos mergulhar mais fundo em estratégias que ajudam você a atingir essas metas.

Reutilizar Camadas de Base Compartilhadas Mínimas

Podemos começar examinando os recursos a partir dos quais as imagens de container são criadas: imagens de base. Cada imagem de container é construída ou a partir de uma imagem pai, uma imagem usada como ponto de partida ou da camada abstrata scratch, uma camada de imagem vazia sem sistema de arquivos. Uma imagem de base é uma imagem de container que serve como fundação para futuras imagens, através da definição do sistema operacional básico e do fornecimento da funcionalidade principal. As imagens são compostas por uma ou mais camadas de imagem construídas umas sobre as outras para formar uma imagem final.

Nenhum utilitário padrão ou sistema de arquivos está disponível ao trabalhar diretamente a partir do scratch, o que significa que você só tem acesso a funcionalidades extremamente limitadas. Embora as imagens criadas diretamente a partir do scratch possam ser muito simples e minimalistas, seu objetivo principal é definir imagens de base. Normalmente, você deseja construir suas imagens de container sobre uma imagem pai que configura um ambiente básico no qual suas aplicações são executadas, para que você não precise construir um sistema completo para cada imagem.

Embora existam imagens base para uma variedade de distribuições Linux, é melhor ser deliberado sobre quais sistemas você escolhe. Cada nova máquina terá que baixar a imagem principal e as camadas complementares que você adicionou. Para imagens grandes, isso pode consumir uma quantidade significativa de largura de banda e aumentar significativamente o tempo de inicialização de seus containers em sua primeira execução. Não há como reduzir uma imagem pai usada como downstream no processo de criação de containers, portanto, começar com uma imagem pai mínima é uma boa ideia.

Ambientes ricos em recursos, como o Ubuntu, permitem que sua aplicação seja executada em um ambiente com o qual você esteja familiarizado, mas há algumas desvantagens a serem consideradas. As imagens do Ubuntu (e imagens de distribuição convencionais semelhantes) tendem a ser relativamente grandes (acima de 100 MB), o que significa que quaisquer imagens de container construídas a partir delas herdarão esse peso.

O Alpine Linux é uma alternativa popular para imagens de base porque ele compacta com sucesso muitas funcionalidades em uma imagem de base muito pequena (~ 5MB). Ele inclui um gerenciador de pacotes com repositórios consideráveis e possui a maioria dos utilitários padrão que você esperaria de um ambiente Linux mínimo.

Ao projetar suas aplicações, é uma boa ideia tentar reutilizar o mesmo pai para cada imagem. Quando suas imagens compartilham um pai, as máquinas que executam seus containers baixam a camada pai apenas uma vez. Depois disso, elas só precisarão baixar as camadas que diferem entre suas imagens. Isso significa que, se você tiver recursos ou funcionalidades comuns que gostaria de incorporar em cada imagem, criar uma imagem pai comum para herdar talvez seja uma boa ideia. Imagens que compartilham uma linhagem ajudam a minimizar a quantidade de dados extras que você precisa baixar em novos servidores.

Gerenciando Camadas de Container

Depois que você selecionou uma imagem pai, você pode definir sua imagem de container acrescentando software adicional, copiando arquivos, expondo portas e escolhendo processos para serem executados. Certas instruções no arquivo de configuração da imagem (um Dockerfile se você estiver usando o Docker) adicionarão camadas complementares à sua imagem.

Por muitas das mesmas razões mencionadas na seção anterior, é importante estar ciente de como você adiciona camadas às suas imagens devido ao tamanho resultante, à herança e à complexidade do runtime. Para evitar a criação de imagens grandes e de difícil controle é importante desenvolver um bom entendimento de como as camadas de container interagem, como o mecanismo de criação faz o cache das camadas e como diferenças sutis em instruções semelhantes podem ter um grande impacto nas imagens que você cria.

Entendendo as Camadas de Imagem e Construindo o Cache

O Docker cria uma nova camada de imagem toda vez que executa as instruções RUN, COPY ou ADD. Se você construir a imagem novamente, o mecanismo de construção verificará cada instrução para ver se ela possui uma camada de imagem armazenada em cache para a operação. Se ele encontrar uma correspondência no cache, ele usará a camada de imagem existente em vez de executar a instrução novamente e reconstruir a camada.

Esse processo pode reduzir significativamente os tempos de criação, mas é importante entender o mecanismo usado para evitar possíveis problemas. Para instruções de cópia de arquivos como COPY e ADD, o Docker compara os checksums dos arquivos para ver se a operação precisa ser executada novamente. Para instruções RUN, o Docker verifica se possui uma camada de imagem existente armazenada em cache para aquela sequência de comandos específica.

Embora não seja imediatamente óbvio, esse comportamento pode causar resultados inesperados se você não for cuidadoso. Um exemplo comum disso é a atualização do índice de pacotes local e a instalação de pacotes em duas etapas separadas. Estaremos usando o Ubuntu para este exemplo, mas a premissa básica se aplica igualmente bem às imagens de base para outras distribuições:

Dockerfile de exemplo de instalação de pacotes
FROM ubuntu:18.04 RUN apt -y update RUN apt -y install nginx . . . 

Aqui, o índice de pacotes local é atualizado em uma instrução RUN (apt -y update) e o Nginx é instalado em outra operação. Isso funciona sem problemas quando é usado pela primeira vez. No entanto, se o Dockerfile for atualizado posteriormente para instalar um pacote adicional, pode haver problemas:

Dockerfile de exemplo de instalação de pacotes
FROM ubuntu:18.04 RUN apt -y update RUN apt -y install nginx php-fpm . . . 

Nós adicionamos um segundo pacote ao comando de instalação executado pela segunda instrução. Se uma quantidade significativa de tempo tiver passado desde a criação da imagem anterior, a nova compilação poderá falhar. Isso ocorre porque a instrução de atualização de índice de pacotes (RUN apt -y update) não foi alterada, portanto, o Docker reutiliza a camada de imagem associada a essa instrução. Como estamos usando um índice de pacotes antigo, a versão do pacote php-fpm que temos em nossos registros locais pode não estar mais nos repositórios, resultando em um erro quando a segunda instrução é executada.

Para evitar esse cenário, certifique-se de consolidar quaisquer etapas que sejam interdependentes em uma única instrução RUN para que o Docker reexecute todos os comandos necessários quando ocorrer uma mudança:

Dockerfile de exemplo de instalação de pacotes
FROM ubuntu:18.04 RUN apt -y update && apt -y install nginx php-fpm . . . 

A instrução agora atualiza o cache do pacotes local sempre que a lista de pacotes é alterada.

Reduzindo o Tamanho da Camada de Imagem Ajustando Instruções RUN

O exemplo anterior demonstra como o comportamento do cache do Docker pode subverter as expectativas, mas há algumas outras coisas que devem ser lembradas com relação à maneira como as instruções RUN interagem com o sistema de camadas do Docker. Como mencionado anteriormente, no final de cada instrução RUN, o Docker faz o commit das alterações como uma camada de imagem adicional. A fim de exercer controle sobre o escopo das camadas de imagens produzidas, você pode limpar arquivos desnecessários no ambiente final que serão comitados prestando atenção aos artefatos introduzidos pelos comandos que você executa.

Em geral, o encadeamento de comandos em uma única instrução RUN oferece um grande controle sobre a camada que será gravada. Para cada comando, você pode configurar o estado da camada (apt -y update), executar o comando principal (apt install -y nginx php-fpm) e remover quaisquer artefatos desnecessários para limpar o ambiente antes de ser comitado. Por exemplo, muitos Dockerfiles encadeiam rm -rf /var/lib/apt/lists/* ao final dos comandos apt, removendo os índices de pacotes baixados, para reduzir o tamanho final da camada:

Dockerfile de exemplo de instalação de pacotes
FROM ubuntu:18.04 RUN apt -y update && apt -y install nginx php-fpm && rm -rf /var/lib/apt/lists/* . . . 

Para reduzir ainda mais o tamanho das camadas de imagem que você está criando, tentar limitar outros efeitos colaterais não intencionais dos comandos que você está executando pode ser útil. Por exemplo, além dos pacotes explicitamente declarados, o apt também instala pacotes “recomendados” por padrão. Você pode incluir --no-install-recommends aos seus comandos apt para remover esse comportamento. Você pode ter que experimentar para descobrir se você confia em qualquer uma das funcionalidades fornecidas pelos pacotes recomendados.

Usamos os comandos de gerenciamento de pacotes nesta seção como exemplo, mas esses mesmos princípios se aplicam a outros cenários. A idéia geral é construir as condições de pré-requisito, executar o comando mínimo viável e, em seguida, limpar quaisquer artefatos desnecessários em um único comando RUN para reduzir a sobrecarga da camada que você estará produzindo.

Usando Multi-stage Builds

Multi-stage builds foram introduzidos no Docker 17.05, permitindo aos desenvolvedores controlar mais rigidamente as imagens finais de runtime que eles produzem. Multi-stage builds ou Compilações em Vários Estágios permitem que você divida seu Dockerfile em várias seções representando estágios distintos, cada um com uma instrução FROM para especificar imagens pai separadas.

Seções anteriores definem imagens que podem ser usadas para criar sua aplicação e preparar ativos. Elas geralmente contêm ferramentas de compilação e arquivos de desenvolvimento necessários para produzir a aplicação, mas não são necessários para executá-la. Cada estágio subsequente definido no arquivo terá acesso aos artefatos produzidos pelos estágios anteriores.

A última declaração FROM define a imagem que será usada para executar a aplicação. Normalmente, essa é uma imagem reduzida que instala apenas os requisitos de runtime necessários e, em seguida, copia os artefatos da aplicação produzidos pelos estágios anteriores.

Este sistema permite que você se preocupe menos com a otimização das instruções RUN nos estágios de construção, já que essas camadas de container não estarão presentes na imagem de runtime final. Você ainda deve prestar atenção em como as instruções interagem com o cache de camadas nos estágios de construção, mas seus esforços podem ser direcionados para minimizar o tempo de construção em vez do tamanho final da imagem. Prestar atenção às instruções no estágio final ainda é importante para reduzir o tamanho da imagem, mas ao separar os diferentes estágios da construção do container, é mais fácil obter imagens simplificadas sem tanta complexidade no Dockerfile.

Escopo de Funcionalidade ao Nível de Container e de Pod

Embora as escolhas que você faz em relação às instruções de criação de containers sejam importantes, decisões mais amplas sobre como containerizar seus serviços geralmente têm um impacto mais direto em seu sucesso. Nesta seção, falaremos um pouco mais sobre como fazer uma melhor transição de suas aplicações de um ambiente mais convencional para uma plataforma de container.

Containerizando por Função

Geralmente, é uma boa prática empacotar cada parte de uma funcionalidade independente em uma imagem de container separada.

Isso difere das estratégias comuns empregadas nos ambientes de máquina virtual, em que os aplicativos são frequentemente agrupados na mesma imagem para reduzir o tamanho e minimizar os recursos necessários para executar a VM. Como os containers são abstrações leves que não virtualizam toda a pilha do sistema operacional, essa abordagem é menos atraente no Kubernetes. Assim, enquanto uma máquina virtual de stack web pode empacotar um servidor web Nginx com um servidor de aplicações Gunicorn em uma única máquina para servir uma aplicação Django, no Kubernetes eles podem ser divididos em containeres separados.

Projetar containers que implementam uma parte discreta de funcionalidade para seus serviços oferece várias vantagens. Cada container pode ser desenvolvido independentemente se as interfaces padrão entre os serviços forem estabelecidas. Por exemplo, o container Nginx poderia ser usado para fazer proxy para vários back-ends diferentes ou poderia ser usado como um balanceador de carga se tivesse uma configuração diferente.

Depois de fazer o deploy, cada imagem de container pode ser escalonada independentemente para lidar com várias restrições de recursos e de carga. Ao dividir suas aplicações em várias imagens de container, você ganha flexibilidade no desenvolvimento, na organização e no deployment.

Combinando Imagens de Container em Pods

No Kubernetes, pods são a menor unidade que pode ser gerenciada diretamente pelo painel de controle. Os pods consistem em um ou mais containers juntamente com dados de configuração adicionais para informar à plataforma como esses componentes devem ser executados. Os containers em um pod são sempre lançados no mesmo worker node no cluster e o sistema reinicia automaticamente containers com falha. A abstração do pod é muito útil, mas introduz outra camada de decisões sobre como agrupar os componentes de suas aplicações.

Assim como as imagens de container, os pods também se tornam menos flexíveis quando muita funcionalidade é agrupada em uma única entidade. Os próprios pods podem ser escalados usando outras abstrações, mas os containers dentro deles não podem ser gerenciados ou redimensionados independentemente. Portanto, para continuar usando nosso exemplo anterior, os containers Nginx e Gunicorn separados provavelmente não devem ser empacotados juntos em um único pod, para que possam ser controlados e deployados separadamente.

No entanto, há cenários em que faz sentido combinar containers funcionalmente diferentes como uma unidade. Em geral, eles podem ser categorizadas como situações em que um container adicional suporta ou aprimora a funcionalidade central do container principal ou ajuda-o a adaptar-se ao seu ambiente de deployment. Alguns padrões comuns são:

  • Sidecar: O container secundário estende a funcionalidade central do container principal, agindo em uma função de utilitário de suporte. Por exemplo, o container sidecar pode encaminhar logs ou atualizar o sistema de arquivos quando um repositório remoto é alterado. O container principal permanece focado em sua responsabilidade central, mas é aprimorado pelos recursos fornecidos pelo sidecar.
  • Ambassador: Um container Ambassador é responsável por descobrir e conectar-se a recursos externos (geralmente complexos). O container principal pode se conectar a um container Ambassador em interfaces conhecidas usando o ambiente interno do pod. O Ambassador abstrai os recursos de back-end e o tráfego de proxies entre o container principal e o pool de recursos.
  • Adaptor: Um container Adaptor é responsável por normalizar as interfaces primárias de container, os dados e os protocolos para alinhar com as propriedades esperadas por outros componentes. O container principal pode operar usando formatos nativos e o container Adaptor traduz e normaliza os dados para se comunicar com o mundo externo.

Como você deve ter notado, cada um desses padrões suporta a estratégia de criar imagens genéricas e padronizadas de container principais que podem ser implantadas em contextos e configurações variados. Os containers secundários ajudam a preencher a lacuna entre o container principal e o ambiente de deployment específico que está sendo usado. Alguns containers Sidecar também podem ser reutilizados para adaptar vários containers primários às mesmas condições ambientais. Esses padrões se beneficiam do sistema de arquivos compartilhado e do namespace de rede fornecidos pela abstração do pod, ao mesmo tempo em que permitem o desenvolvimento independente e o deploy flexível de containers padronizados.

Projetando para Configuração de Runtime

Existe alguma tensão entre o desejo de construir componentes reutilizáveis e padronizados e os requisitos envolvidos na adaptação de aplicações ao seu ambiente de runtime. A configuração de runtime é um dos melhores métodos para preencher a lacuna entre essas preocupações. Componentes são criados para serem genéricos e flexíveis e o comportamento necessário é descrito no runtime, fornecendo ao software informações adicionais sobre a configuração. Essa abordagem padrão funciona para containers, assim como para aplicações.

Construir com a configuração de runtime em mente requer que você pense à frente durante as etapas de desenvolvimento de aplicação e de containerização. As aplicações devem ser projetadas para ler valores de parâmetros da linha de comando, arquivos de configuração ou variáveis de ambiente quando forem iniciados ou reiniciados. Essa lógica de análise e injeção de configuração deve ser implementada no código antes da containerização.

Ao escrever um Dockerfile, o container também deve ser projetado com a configuração de runtime em mente. Os containers possuem vários mecanismos para fornecer dados em tempo de execução. Os usuários podem montar arquivos ou diretórios do host como volumes dentro do container para ativar a configuração baseada em arquivo. Da mesma forma, as variáveis de ambiente podem ser passadas para o runtime interno do container quando o msmo é iniciado. As instruções de Dockerfile CMD e ENTRYPOINT também podem ser definidas de uma forma que permita que as informações de configuração de runtime sejam passadas como parâmetros de comando.

Como o Kubernetes manipula objetos de nível superior, como pods, em vez de gerenciar containeres diretamente, há mecanismos disponíveis para definir a configuração e injetá-la no ambiente de container em runtime. Kubernetes ConfigMaps e Secrets permitem que você defina os dados de configuração separadamente e projete os valores no ambiente de container como variáveis de ambiente ou arquivos em runtime. ConfigMaps são objetos de finalidade geral destinados a armazenar dados de configuração que podem variar de acordo com o ambiente, o estágio de teste etc. Secrets oferecem uma interface semelhante, mas são projetados especificamente para dados confidenciais, como senhas de contas ou credenciais de API.

Ao entender e utilizar corretamente as opções de configuração de runtime disponíveis em todas as camadas de abstração, você pode criar componentes flexíveis que retiram suas entradas dos valores fornecidos pelo ambiente. Isso possibilita reutilizar as mesmas imagens de container em cenários muito diferentes, reduzindo a sobrecarga de desenvolvimento, melhorando a flexibilidade da aplicação.

Implementando o Gerenciamento de Processos com Containers

Ao fazer a transição para ambientes baseados em container, os usuários geralmente iniciam movendo as cargas de trabalho existentes, com poucas ou nenhuma alteração, para o novo sistema. Eles empacotam aplicações em containers agrupando as ferramentas que já estão usando na nova abstração. Embora seja útil utilizar seus padrões usuais para colocar as aplicações migradas em funcionamento, cair em implementações anteriores em containers pode, às vezes, levar a um design ineficaz.

Tratando Containers como Aplicações, não como Serviços

Frequentemente surgem problemas quando os desenvolvedores implementam uma funcionalidade significativa de gerenciamento de serviços nos containers. Por exemplo, a execução de serviços systemd no container ou tornar daemons os servidores web pode ser considerada uma prática recomendada em um ambiente de computação normal, mas elas geralmente entram em conflito com as suposições inerentes ao modelo de container.

Os hosts gerenciam os eventos do ciclo de vida do container enviando sinais para o processo que opera como PID (ID do processo) 1 dentro do container. O PID 1 é o primeiro processo iniciado, que seria o sistema init em ambientes de computação tradicionais. No entanto, como o host só pode gerenciar o PID 1, usar um sistema init convencional para gerenciar processos dentro do container às vezes significa que não há como controlar a aplicação principal. O host pode iniciar, parar ou matar o sistema init interno, mas não pode gerenciar diretamente a aplicação principal. Os sinais às vezes propagam o comportamento pretendido para a aplicação em execução, mas isso adiciona complexidade e nem sempre é necessário.

Na maioria das vezes, é melhor simplificar o ambiente de execução dentro do container para que o PID 1 esteja executando a aplicação principal em primeiro plano. Nos casos em que vários processos devem ser executados, o PID 1 é responsável por gerenciar o ciclo de vida de processos subsequentes. Certas aplicações, como o Apache, lidam com isso nativamente gerando e gerenciando workers que lidam com conexões. Para outras aplicações, um script wrapper ou um sistema init muito simples como o dumb-init ou o sistema init incluído tini podem ser usados em alguns casos. Independentemente da implementação escolhida, o processo que está sendo executado como PID 1 no container deve responder adequadamente aos sinais TERM enviados pelo Kubernetes para se comportar como esperado.

Gerenciando a Integridade do Container no Kubernetes

Os deployments e serviços do Kubernetes oferecem gerenciamento de ciclo de vida para processos de longa duração e acesso confiável e persistente a aplicações, mesmo quando os containers subjacentes precisam ser reiniciados ou as próprias implementações são alteradas. Ao retirar a responsabilidade de monitorar e manter a integridade do serviço do container, você pode aproveitar as ferramentas da plataforma para gerenciar cargas de trabalho saudáveis.

Para que o Kubernetes gerencie os containers adequadamente, ele precisa entender se as aplicações em execução nos containers são saudáveis e capazes de executar o trabalho. Para ativar isso, os containers podem implementar análises de integridade: endpoints de rede ou comandos que podem ser usados para relatar a integridade da aplicação. O Kubernetes verificará periodicamente as sondas de integridade definidas para determinar se o container está operando conforme o esperado. Se o container não responder adequadamente, o Kubernetes reinicia o container na tentativa de restabelecer a funcionalidade.

O Kubernetes também fornece sondas de prontidão, uma construção similar. Em vez de indicar se a aplicação em um container está íntegra, as sondas de prontidão determinam se a aplicação está pronta para receber tráfego. Isso pode ser útil quando uma aplicação em container tiver uma rotina de inicialização que deve ser concluída antes de estar pronta para receber conexões. O Kubernetes usa sondas ou testes de prontidão para determinar se deve adicionar um pod ou remover um pod de um serviço.

A definição de endpoints para esses dois tipos de sondagem pode ajudar o Kubernetes a gerenciar seus containers com eficiência e pode evitar que problemas no ciclo de vida do container afetem a disponibilidade do serviço. Os mecanismos para responder a esses tipos de solicitações de integridade devem ser incorporados à própria aplicação e devem ser expostos na configuração da imagem do Docker.

Conclusão

Neste guia, abordamos algumas considerações importantes para se ter em mente ao executar aplicações em container no Kubernetes. Para reiterar, algumas das sugestões que examinamos foram:

  • Use imagens pai mínimas e compartilháveis para criar imagens com o mínimo de inchaço e reduzir o tempo de inicialização
  • Use multi-stage builds para separar os ambientes de criação e de runtime do container
  • Combine as instruções do Dockerfile para criar camadas de imagem limpas e evitar erros de cache de imagem
  • Containerize isolando a funcionalidade discreta para permitir a escalabilidade e o gerenciamento flexíveis
  • Crie pods para ter uma responsabilidade única e focada
  • Empacote os containers auxiliares para melhorar a funcionalidade do container principal ou para adaptá-lo ao ambiente de deployment
  • Crie aplicações e containers para responder à configuração de runtime para permitir maior flexibilidade ao fazer deploy
  • Execute aplicações como processos principais em containers, para que o Kubernetes possa gerenciar eventos do ciclo de vida
  • Desenvolva endpoints de integridade e atividade dentro da aplicação ou container para que o Kubernetes possa monitorar a integridade do container

Durante todo o processo de desenvolvimento e deployment, você precisará tomar decisões que podem afetar a robustez e a eficácia do seu serviço. Compreender as maneiras pelas quais as aplicações conteinerizadas diferem das aplicações convencionais e aprender como elas operam em um ambiente de cluster gerenciado pode ajudá-lo a evitar algumas armadilhas comuns e permitir que você aproveite todos os recursos oferecidos pelo Kubernetes.

DigitalOcean Community Tutorials

Monitoramento para Deployments Distribuídos e de Microsserviços

Introdução

O monitoramento de sistemas e da infraestrutura é uma responsabilidade central de equipes de operações de todos os tamanhos. A indústria desenvolveu coletivamente muitas estratégias e ferramentas para ajudar a monitorar servidores, coletar dados importantes e responder a incidentes e condições em alteração em ambientes variados. No entanto, à medida que as metodologias de software e os projetos de infraestrutura evoluem, o monitoramento deve se adaptar para atender a novos desafios e fornecer insights em um território relativamente desconhecido.

Até agora, nesta série, discutimos o que são métricas, monitoramento e alertas, e as qualidades de bons sistemas de monitoramento. Conversamos sobre coletar métricas de sua infraestrutura e aplicações e os sinais importantes para monitorar toda a sua infraestrutura. Em nosso último guia, cobrimos como colocar em prática métricas e alertas entendendo os componentes individuais e as qualidades do bom projeto de alertas.

Neste guia, vamos dar uma olhada em como o monitoramento e a coleta de métricas são alterados para arquiteturas e microsserviços altamente distribuídos. A crescente popularidade da computação em nuvem, dos clusters de big data e das camadas de orquestração de instâncias forçou os profissionais de operações a repensar como projetar o monitoramento em escala e a enfrentar problemas específicos com uma melhor instrumentação. Vamos falar sobre o que diferencia os novos modelos de deployment e quais estratégias podem ser usadas para atender a essas novas demandas.

Quais Desafios Criam as Arquiteturas Altamente Distribuídas?

Para modelar e espelhar os sistemas monitorados, a infraestrutura de monitoramento sempre foi um pouco distribuída. No entanto, muitas práticas modernas de desenvolvimento — incluindo projetos em torno de microsserviços, containers e instâncias de computação intercambiáveis e efêmeras — alteraram drasticamente o cenário de monitoramento. Em muitos casos, os principais recursos desses avanços são os fatores que tornam o monitoramento mais difícil. Vamos analisar algumas das maneiras pelas quais elas diferem dos ambientes tradicionais e como isso afeta o monitoramento.

O Trabalho é Dissociado dos Recursos Subjacentes

Algumas das mudanças mais fundamentais na forma como muitos sistemas se comportam são devido a uma explosão em novas camadas de abstração em torno das quais o software pode ser projetado. A tecnologia de containers mudou o relacionamento entre o software deployado e o sistema operacional subjacente. Os aplicações com deploy em containers têm relacionamentos diferentes com o mundo externo, com outros programas e com o sistema operacional do host, do que com as aplicações cujo deploy foi feito por meios convencionais. As abstrações de kernel e rede podem levar a diferentes entendimentos do ambiente operacional, dependendo de qual camada você verificar.

Esse nível de abstração é incrivelmente útil de várias maneiras, criando estratégias de deployment consistentes, facilitando a migração do trabalho entre hosts e permitindo que os desenvolvedores controlem de perto os ambientes de runtime de suas aplicações. No entanto, esses novos recursos surgem às custas do aumento da complexidade e de um relacionamento mais distante com os recursos que suportam cada processo.

Aumento na Comunicação Baseada em Rede

Uma semelhança entre paradigmas mais recentes é uma dependência crescente da comunicação de rede interna para coordenar e realizar tarefas. O que antes era o domínio de uma única aplicação, agora pode ser distribuído entre muitos componentes que precisam coordenar e compartilhar informações. Isso tem algumas repercussões em termos de infraestrutura de comunicação e monitoramento.

Primeiro, como esses modelos são construídos na comunicação entre serviços pequenos e discretos, a saúde da rede se torna mais importante do que nunca. Em arquiteturas tradicionais, mais monolíticas, tarefas de coordenação, compartilhamento de informações e organização de resultados foram amplamente realizadas em aplicações com lógica de programação regular ou através de uma quantidade comparativamente pequena de comunicação externa. Em contraste, o fluxo lógico de aplicações altamente distribuídas usa a rede para sincronizar, verificar a integridade dos pares e passar informações. A saúde e o desempenho da rede impactam diretamente mais funcionalidades do que anteriormente, o que significa que é necessário um monitoramento mais intensivo para garantir a operação correta.

Embora a rede tenha se tornado mais crítica do que nunca, a capacidade de monitorá-la é cada vez mais desafiadora devido ao número estendido de participantes e linhas de comunicação individuais. Em vez de rastrear interações entre algumas aplicações, a comunicação correta entre dezenas, centenas ou milhares de pontos diferentes torna-se necessária para garantir a mesma funcionalidade. Além das considerações de complexidade, o aumento do volume de tráfego também sobrecarrega os recursos de rede disponíveis, aumentando ainda mais a necessidade de um monitoramento confiável.

Funcionalidade e Responsabilidade Particionada para um Nível Maior

Acima, mencionamos de passagem a tendência das arquiteturas modernas de dividir o trabalho e a funcionalidade entre muitos componentes menores e discretos. Esses projetos podem ter um impacto direto no cenário de monitoramento porque tornam a clareza e a compreensão especialmente valiosas, mas cada vez mais evasivas.

Ferramentas e instrumentação mais robustas são necessárias para garantir um bom funcionamento. No entanto, como a responsabilidade de concluir qualquer tarefa é fragmentada e dividida entre diferentes workers (possivelmente em muitos hosts físicos diferentes), entender onde a responsabilidade reside em questões de desempenho ou erros pode ser difícil. Solicitações e unidades de trabalho que tocam dezenas de componentes, muitos dos quais são selecionados de um pool de possíveis candidatos, podem tornar impraticável a visualização do caminho da solicitação ou a análise da causa raiz usando mecanismos tradicionais.

Unidades de Vida Curta e Efêmera

Uma batalha adicional na adaptação do monitoramento convencional é monitorar sensivelmente as unidades de vida curta ou efêmeras. Independentemente de as unidades de interesse serem instâncias de computação em nuvem, instâncias de container ou outras abstrações, esses componentes geralmente violam algumas das suposições feitas pelo software de monitoramento convencional.

Por exemplo, para distinguir entre um node problemático e uma instância intencionalmente destruída para reduzir a escala, o sistema de monitoramento deve ter um entendimento mais íntimo de sua camada de provisionamento e gerenciamento do que era necessário anteriormente. Para muitos sistemas modernos, esses eventos ocorrem com muito mais frequência, portanto, ajustar manualmente o domínio de monitoramento a cada vez não é prático. O ambiente de deployment muda mais rapidamente com esses projetos, portanto, a camada de monitoramento deve adotar novas estratégias para permanecer valiosa.

Uma questão que muitos sistemas devem enfrentar é o que fazer com os dados das instâncias destruídas. Embora as work units possam ser aprovisionadas e desprovisionadas rapidamente para acomodar demandas variáveis, é necessário tomar uma decisão sobre o que fazer com os dados relacionados às instâncias antigas. Os dados não perdem necessariamente seu valor imediatamente porque o worker subjacente não está mais disponível. Quando centenas ou milhares de nodes podem entrar e sair todos os dias, pode ser difícil saber como melhorar a construção de uma narrativa sobre a integridade operacional geral de seu sistema a partir dos dados fragmentados de instâncias de vida curta.

Quais Alterações são Necessárias para Escalar seu Monitoramento?

Agora que identificamos alguns dos desafios únicos das arquiteturas e microsserviços distribuídos, podemos falar sobre como os sistemas de monitoramento podem funcionar dentro dessas realidades. Algumas das soluções envolvem reavaliar e isolar o que é mais valioso sobre os diferentes tipos de métricas, enquanto outras envolvem novas ferramentas ou novas formas de entender o ambiente em que elas habitam.

Granularidade e Amostragem

O aumento no volume total de tráfego causado pelo elevado número de serviços é um dos problemas mais simples de se pensar. Além do aumento nos números de transferência causados por novas arquiteturas, a própria atividade de monitoramento pode começar a atolar a rede e roubar recursos do host. Para lidar melhor com o aumento de volume, você pode expandir sua infraestrutura de monitoramento ou reduzir a resolução dos dados com os quais trabalha. Vale à pena olhar ambas as abordagens, mas vamos nos concentrar na segunda, pois representa uma solução mais extensível e amplamente útil.

Alterar suas taxas de amostragem de dados pode minimizar a quantidade de dados que seu sistema precisa coletar dos hosts. A amostragem é uma parte normal da coleção de métricas que representa com que frequência você solicita novos valores para uma métrica. Aumentar o intervalo de amostragem reduzirá a quantidade de dados que você precisa manipular, mas também reduzirá a resolução — o nível de detalhes — de seus dados. Embora você deva ter cuidado e compreender sua resolução mínima útil, ajustar as taxas de coleta de dados pode ter um impacto profundo em quantos clientes de monitoramento seu sistema pode atender adequadamente.

Para diminuir a perda de informações resultante de resoluções mais baixas, uma opção é continuar a coletar dados em hosts na mesma frequência, mas compilá-los em números mais digeríveis para transferência pela rede. Computadores individuais podem agregar e calcular valores médios de métricas e enviar resumos para o sistema de monitoramento. Isso pode ajudar a reduzir o tráfego da rede, mantendo a precisão, já que um grande número de pontos de dados ainda é levado em consideração. Observe que isso ajuda a reduzir a influência da coleta de dados na rede, mas não ajuda, por si só, com a pressão envolvida na coleta desses números no host.

Tome Decisões com Base em Dados Agregados de Várias Unidades

Como mencionado acima, um dos principais diferenciais entre sistemas tradicionais e arquiteturas modernas é a quebra de quais componentes participam no processamento de solicitações. Em sistemas distribuídos e microsserviços, é muito mais provável que uma unidade de trabalho ou worker seja dado a um grupo de workers por meio de algum tipo de camada de agendamento ou arbitragem. Isso tem implicações em muitos dos processos automatizados que você pode construir em torno do monitoramento.

Em ambientes que usam grupos de workers intercambiáveis, as políticas de verificação de integridade e de alerta podem ter relações complexas com a infraestrutura que eles monitoram. As verificações de integridade em workers individuais podem ser úteis para desativar e reciclar unidades defeituosas automaticamente. No entanto, se você tiver a automação em funcionamento, em escala, não importa muito se um único servidor web falhar em um grande pool ou grupo. O sistema irá se auto-corrigir para garantir que apenas as unidades íntegras estejam no pool ativo recebendo solicitações.

Embora as verificações de integridade do host possam detectar unidades defeituosas, a verificação da integridade do pool em si é mais apropriada para alertas. A capacidade do pool de satisfazer a carga de trabalho atual tem maior importância na experiência do usuário do que os recursos de qualquer worker individual. Os alertas com base no número de membros íntegros, na latência do agregado do pool ou na taxa de erros do pool podem notificar os operadores sobre problemas mais difíceis de serem mitigados automaticamente e mais propensos a causar impacto nos usuários.

Integração com a Camada de Provisionamento

Em geral, a camada de monitoramento em sistemas distribuídos precisa ter um entendimento mais completo do ambiente de deploy e dos mecanismos de provisionamento. O gerenciamento automatizado do ciclo de vida se torna extremamente valioso devido ao número de unidades individuais envolvidas nessas arquiteturas. Independentemente de as unidades serem containers puros, containers em uma estrutura de orquestração ou nodes de computação em um ambiente de nuvem, existe uma camada de gerenciamento que expõe informações de integridade e aceita comandos para dimensionar e responder a eventos.

O número de peças em jogo aumenta a probabilidade estatística de falha. Com todos os outros fatores sendo iguais, isso exigiria mais intervenção humana para responder e mitigar esses problemas. Como o sistema de monitoramento é responsável por identificar falhas e degradação do serviço, se ele puder conectar-se às interfaces de controle da plataforma, isso pode aliviar uma grande classe desses problemas. Uma resposta imediata e automática desencadeada pelo software de monitoramento pode ajudar a manter a integridade operacional do seu sistema.

Essa relação estreita entre o sistema de monitoramento e a plataforma de deploy não é necessariamente obrigatória ou comum em outras arquiteturas. Mas os sistemas distribuídos automatizados visam ser auto-reguláveis, com a capacidade de dimensionar e ajustar com base em regras pré-configuradas e status observado. O sistema de monitoramento, neste caso, assume um papel central no controle do ambiente e na decisão sobre quando agir.

Outro motivo pelo qual o sistema de monitoramento deve ter conhecimento da camada de provisionamento é lidar com os efeitos colaterais de instâncias efêmeras. Em ambientes onde há rotatividade frequente nas instâncias de trabalho, o sistema de monitoramento depende de informações de um canal paralelo para entender quando as ações foram intencionais ou não. Por exemplo, sistemas que podem ler eventos de API de um provisionador podem reagir de maneira diferente quando um servidor é destruído intencionalmente por um operador do que quando um servidor repentinamente não responde sem nenhum evento associado. A capacidade de diferenciar esses eventos pode ajudar seu monitoramento a permanecer útil, preciso e confiável, mesmo que a infraestrutura subjacente possa mudar com frequência.

Rastreio Distribuído

Um dos aspectos mais desafiadores de cargas de trabalho altamente distribuídas é entender a interação entre os diferentes componentes e isolar a responsabilidade ao tentar a análise da causa raiz. Como uma única solicitação pode afetar dúzias de pequenos programas para gerar uma resposta, pode ser difícil interpretar onde os gargalos ou alterações de desempenho se originam. Para fornecer melhores informações sobre como cada componente contribui para a latência e sobrecarga de processamento, surgiu uma técnica chamada rastreamento distribuído.

O rastreamento distribuído é uma abordagem dos sistemas de instrumentação que funciona adicionando código a cada componente para iluminar o processamento da solicitação à medida que ela percorre seus serviços. Cada solicitação recebe um identificador exclusivo na borda de sua infraestrutura que é transmitido conforme a tarefa atravessa sua infraestrutura. Cada serviço usa essa ID para relatar erros e os registros de data e hora de quando viu a solicitação pela primeira vez e quando ela foi entregue para a próxima etapa. Ao agregar os relatórios dos componentes usando o ID da solicitação, um caminho detalhado com dados de tempo precisos pode ser rastreado através de sua infraestrutura.

Esse método pode ser usado para entender quanto tempo é gasto em cada parte de um processo e identificar claramente qualquer aumento sério na latência. Essa instrumentação extra é uma maneira de adaptar a coleta de métricas a um grande número de componentes de processamento. Quando mapeado visualmente com o tempo no eixo x, a exibição resultante mostra o relacionamento entre diferentes estágios, por quanto tempo cada processo foi executado e o relacionamento de dependência entre os eventos que devem ser executados em paralelo. Isso pode ser incrivelmente útil para entender como melhorar seus sistemas e como o tempo está sendo gasto.

Melhorando a Capacidade de Resposta Operacional para Sistemas Distribuídos

Discutimos como as arquiteturas distribuídas podem tornar a análise da causa raiz e a clareza operacional difíceis de se obter. Em muitos casos, mudar a forma como os humanos respondem e investigam questões é parte da resposta a essas ambiguidades. Configurar as ferramentas para expor as informações de uma maneira que permita analisar a situação metodicamente pode ajudar a classificar as várias camadas de dados disponíveis. Nesta seção, discutiremos maneiras de se preparar para o sucesso ao solucionar problemas em ambientes grandes e distribuídos.

Definindo Alertas para os Quatro Sinais de Ouro em Todas as Camadas

O primeiro passo para garantir que você possa responder a problemas em seus sistemas é saber quando eles estão ocorrendo. Em nosso guia Coletando Métricas de sua Infraestrutura e Aplicações, apresentamos os quatro sinais de ouro – indicadores de monitoramento identificados pela equipe de SRE do Google como os mais vitais para rastrear. Os quatro sinais são:

  • latência
  • tráfego
  • taxa de erro
  • saturação

Esses ainda são os melhores locais para começar quando estiver instrumentando seus sistemas, mas o número de camadas que devem ser observadas geralmente aumenta para sistemas altamente distribuídos. A infraestrutura subjacente, o plano de orquestração e a camada de trabalho precisam de um monitoramento robusto com alertas detalhados definidos para identificar alterações importantes.

Obtendo uma Visão Completa

Depois que seus sistemas identificarem uma anomalia e notificarem sua equipe, esta precisa começar a coletar dados. Antes de continuar a partir desta etapa, eles devem ter uma compreensão de quais componentes foram afetados, quando o incidente começou e qual condição de alerta específica foi acionada.

A maneira mais útil de começar a entender o escopo de um incidente é começar em um nível alto. Comece a investigar verificando dashboards e visualizações que coletam e generalizam informações de seus sistemas. Isso pode ajudá-lo a identificar rapidamente os fatores correlacionados e a entender o impacto imediato que o usuário enfrenta. Durante esse processo, você deve conseguir sobrepor informações de diferentes componentes e hosts.

O objetivo deste estágio é começar a criar um inventário mental ou físico de itens para verificar com mais detalhes e começar a priorizar sua investigação. Se você puder identificar uma cadeia de problemas relacionados que percorrem diferentes camadas, a camada mais baixa deve ter precedência: as correções para as camadas fundamentais geralmente resolvem os sintomas em níveis mais altos. A lista de sistemas afetados pode servir como uma lista de verificação informal de locais para validar as correções posteriormente quando a mitigação é implementada.

Detalhando Problemas Específicos

Quando você perceber que tem uma visão razoável do incidente, faça uma pesquisa detalhada sobre os componentes e sistemas da sua lista em ordem de prioridade. As métricas detalhadas sobre unidades individuais ajudarão você a rastrear a rota da falha até o recurso responsável mais baixo. Ao examinar painéis de controle e entradas de log mais refinados, consulte a lista de componentes afetados para tentar entender melhor como os efeitos colaterais estão sendo propagados pelo sistema. Com microsserviços, o número de componentes interdependentes significa que os problemas se espalham para outros serviços com mais frequência.

Este estágio é focado em isolar o serviço, componente ou sistema responsável pelo incidente inicial e identificar qual problema específico está ocorrendo. Isso pode ser um código recém-implantado, uma infraestrutura física com defeito, um erro ou bug na camada de orquestração ou uma alteração na carga de trabalho que o sistema não pôde manipular normalmente. Diagnosticar o que está acontecendo e porquê permite descobrir como mitigar o problema e recuperar a saúde operacional. Entender até que ponto a resolução deste problema pode corrigir problemas relatados em outros sistemas pode ajudá-lo a continuar priorizando as tarefas de mitigação.

Mitigando e Resolvendo os Problemas

Depois que os detalhes forem identificados, você poderá resolver ou mitigar o problema. Em muitos casos, pode haver uma maneira óbvia e rápida de restaurar o serviço fornecendo mais recursos, revertendo ou redirecionando o tráfego para uma implementação alternativa. Nestes cenários, a resolução será dividida em três fases:

  • Execução de ações para contornar o problema e restaurar o serviço imediato
  • Resolução do problema subjacente para recuperar a funcionalidade total e a integridade operacional
  • Avaliação completa do motivo da falha e implementação de correções de longo prazo para evitar recorrência

Em muitos sistemas distribuídos, a redundância e os componentes altamente disponíveis garantirão que o serviço seja restaurado rapidamente, embora seja necessário mais trabalho em segundo plano para restaurar a redundância ou tirar o sistema de um estado degradado. Você deve usar a lista de componentes impactados compilados anteriormente como uma base de medição para determinar se a mitigação inicial resolve problemas de serviço em cascata. À medida que a sofisticação dos sistemas de monitoramento evolui, ele também pode automatizar alguns desses processos de recuperação mais completos enviando comandos para a camada de provisionamento para lançar novas instâncias de unidades com falha ou para eliminar unidades que não se comportam corretamente.

Dada a automação possível nas duas primeiras fases, o trabalho mais importante para a equipe de operações geralmente é entender as causas-raiz de um evento. O conhecimento obtido a partir desse processo pode ser usado para desenvolver novos gatilhos e políticas para ajudar a prever ocorrências futuras e automatizar ainda mais as reações do sistema. O software de monitoramento geralmente obtém novos recursos em resposta a cada incidente para proteger contra os cenários de falha recém-descobertos. Para sistemas distribuídos, rastreamentos distribuídos, entradas de log, visualizações de séries temporais e eventos como deploys recentes podem ajudá-lo a reconstruir a sequência de eventos e identificar onde o software e os processos humanos podem ser aprimorados.

Devido à complexidade específica inerente aos grandes sistemas distribuídos, é importante tratar o processo de resolução de qualquer evento significativo como uma oportunidade para aprender e ajustar seus sistemas. O número de componentes separados e os caminhos de comunicação envolvidos forçam uma grande dependência da automação e das ferramentas para ajudar a gerenciar a complexidade. A codificação de novas lições nos mecanismos de resposta e conjuntos de regras desses componentes (bem como nas políticas operacionais que sua equipe segue) é a melhor maneira de seu sistema de monitoramento manter a pegada de gerenciamento de sua equipe sob controle.

Conclusão

Neste guia, falamos sobre alguns dos desafios específicos que as arquiteturas distribuídas e os projetos de microsserviço podem introduzir para o software de monitoramento e visibilidade. As maneiras modernas de se construir sistemas quebram algumas suposições dos métodos tradicionais, exigindo abordagens diferentes para lidar com os novos ambientes de configuração. Exploramos os ajustes que você precisará considerar ao passar de sistemas monolíticos para aqueles que dependem cada vez mais de workers efêmeros, baseados em nuvem ou em containers e alto volume de coordenação de rede. Posteriormente, discutimos algumas maneiras pelas quais a arquitetura do sistema pode afetar a maneira como você responde a incidentes e a resolução.

DigitalOcean Community Tutorials

Como Otimizar Imagens Docker para Produção

O autor escolheu a Code.org para receber uma doação como parte do programa Write for DOnations.

Introdução

Em um ambiente de produção, o Docker facilita a criação, o deployment e a execução de aplicações dentro de containers. Os containers permitem que os desenvolvedores reúnam aplicações e todas as suas principais necessidades e dependências em um único pacote que você pode transformar em uma imagem Docker e replicar. As imagens Docker são construídas a partir de Dockerfiles. O Dockerfile é um arquivo onde você define como será a imagem, qual sistema operacional básico ela terá e quais comandos serão executados dentro dela.

Imagens Docker muito grandes podem aumentar o tempo necessário para criar e enviar imagens entre clusters e provedores de nuvem. Se, por exemplo, você tem uma imagem do tamanho de um gigabyte para enviar toda vez que um de seus desenvolvedores aciona uma compilação, a taxa de transferência que você cria em sua rede aumentará durante o processo de CI/CD, tornando sua aplicação lenta e, consequentemente, custando seus recursos. Por causa disso, as imagens Docker adequadas para produção devem ter apenas as necessidades básicas instaladas.

Existem várias maneiras de diminuir o tamanho das imagens Docker para otimizá-las para a produção. Em primeiro lugar, essas imagens geralmente não precisam de ferramentas de compilação para executar suas aplicações e, portanto, não há necessidade de adicioná-las. Através do uso de um processo de construção multi-stage, você pode usar imagens intermediárias para compilar e construir o código, instalar dependências e empacotar tudo no menor tamanho possível, depois copiar a versão final da sua aplicação para uma imagem vazia sem ferramentas de compilação. Além disso, você pode usar uma imagem com uma base pequena, como o Alpine Linux. O Alpine é uma distribuição Linux adequada para produção, pois possui apenas as necessidades básicas que sua aplicação precisa para executar.

Neste tutorial, você otimizará as imagens Docker em algumas etapas simples, tornando-as menores, mais rápidas e mais adequadas à produção. Você construirá imagens para um exemplo de API em Go em vários containers Docker diferentes, começando com o Ubuntu e imagens específicas de linguagens, e então passando para a distribuição Alpine. Você também usará compilações multi-stage para otimizar suas imagens para produção. O objetivo final deste tutorial é mostrar a diferença de tamanho entre usar imagens padrão do Ubuntu e as equivalentes otimizadas, e mostrar a vantagem das compilações em vários estágios (multi-stage). Depois de ler este tutorial, você poderá aplicar essas técnicas aos seus próprios projetos e pipelines de CI/CD.

Nota: Este tutorial utiliza uma API escrita em Go como um exemplo. Esta simples API lhe dará uma compreensão clara de como você abordaria a otimização de microsserviços em Go com imagens Docker. Embora este tutorial use uma API Go, você pode aplicar esse processo a praticamente qualquer linguagem de programação.

Pré-requisitos

Antes de começar, você precisará de:

Passo 1 — Baixando a API Go de Exemplo

Antes de otimizar sua imagem Docker, você deve primeiro fazer o download da API de exemplo, a partir da qual você construirá suas imagens Docker. O uso de uma API Go simples mostrará todas as principais etapas de criação e execução de uma aplicação dentro de um container Docker. Este tutorial usa o Go porque é uma linguagem compilada como o C++ ou Java, mas ao contrário dele, tem uma pegada muito pequena.

No seu servidor, comece clonando a API Go de exemplo:

  • git clone https://github.com/do-community/mux-go-api.git

Depois de clonar o projeto, você terá um diretório chamado mux-go-api em seu servidor. Mova-se para este diretório com cd:

  • cd mux-go-api

Este será o diretório home do seu projeto. Você construirá suas imagens Docker a partir desse diretório. Dentro dele você encontrará o código fonte para uma API escrita em Go no arquivo api.go. Embora essa API seja mínima e tenha apenas alguns endpoints, ela será apropriada para simular uma API pronta para produção para os propósitos deste tutorial.

Agora que você baixou a API Go de exemplo, você está pronto para criar uma imagem base do Ubuntu no Docker, com a qual você poderá comparar as imagens posteriores e otimizadas.

Passo 2 — Construindo uma Imagem Base do Ubuntu

Para a sua primeira imagem Docker, será útil ver como ela é quando você começa com uma imagem base do Ubuntu. Isso irá empacotar sua API de exemplo em um ambiente similar ao software que você já está rodando no seu servidor Ubuntu. Isso irá empacotar sua API de exemplo em um ambiente similar ao software que você já está rodando no seu servidor Ubuntu. Dentro da imagem, você instalará os vários pacotes e módulos necessários para executar sua aplicação. Você descobrirá, no entanto, que esse processo cria uma imagem bastante pesada do Ubuntu que afetará o tempo de compilação e a legibilidade do código do seu Dockerfile.

Comece escrevendo um Dockerfile que instrui o Docker a criar uma imagem do Ubuntu, instalar o Go e executar a API de exemplo. Certifique-se de criar o Dockerfile no diretório do repositório clonado. Se você clonou no diretório home, ele deve ser $ HOME/mux-go-api.

Crie um novo arquivo chamado Dockerfile.ubuntu. Abra-o no nano ou no seu editor de texto favorito:

  • nano ~/mux-go-api/Dockerfile.ubuntu

Neste Dockerfile, você irá definir uma imagem do Ubuntu e instalar o Golang. Em seguida, você vai continuar a instalar as dependências necessárias e construir o binário. Adicione o seguinte conteúdo ao Dockerfile.ubuntu:

~/mux-go-api/Dockerfile.ubuntu
FROM ubuntu:18.04  RUN apt-get update -y \   && apt-get install -y git gcc make golang-1.10  ENV GOROOT /usr/lib/go-1.10 ENV PATH $  GOROOT/bin:$  PATH ENV GOPATH /root/go ENV APIPATH /root/go/src/api  WORKDIR $  APIPATH COPY . .  RUN \    go get -d -v \   && go install -v \   && go build  EXPOSE 3000 CMD ["./api"] 

Começando do topo, o comando FROM especifica qual sistema operacional básico a imagem terá. A seguir, o comando RUN instala a linguagem Go durante a criação da imagem. ENV define as variáveis de ambiente específicas que o compilador Go precisa para funcionar corretamente. WORKDIR especifica o diretório onde queremos copiar o código, e o comando COPY pega o código do diretório onde o Dockerfile.ubuntu está e o copia para a imagem. O comando RUN final instala as dependências do Go necessárias para o código-fonte compilar e executar a API.

Nota: Usar os operadores && para unir os comandos RUN é importante para otimizar os Dockerfiles, porque todo comando RUN criará uma nova camada, e cada nova camada aumentará o tamanho da imagem final.

Salve e saia do arquivo. Agora você pode executar o comando build para criar uma imagem Docker a partir do Dockerfile que você acabou de criar:

  • docker build -f Dockerfile.ubuntu -t ubuntu .

O comando build constrói uma imagem a partir de um Dockerfile. A flag -f especifica que você deseja compilar a partir do arquivo Dockerfile.ubuntu, enquanto -t significa tag, o que significa que você está marcando a imagem com o nome ubuntu. O ponto final representa o contexto atual onde o Dockerfile.ubuntu está localizado.

Isso vai demorar um pouco, então sinta-se livre para fazer uma pausa. Quando a compilação estiver concluída, você terá uma imagem Ubuntu pronta para executar sua API. Mas o tamanho final da imagem pode não ser ideal; qualquer coisa acima de algumas centenas de MB para essa API seria considerada uma imagem excessivamente grande.

Execute o seguinte comando para listar todas as imagens Docker e encontrar o tamanho da sua imagem Ubuntu:

  • docker images

Você verá a saída mostrando a imagem que você acabou de criar:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 61b2096f6871 33 seconds ago 636MB . . .

Como é destacado na saída, esta imagem tem um tamanho de 636MB para uma API Golang básica, um número que pode variar um pouco de máquina para máquina. Em múltiplas compilações, esse grande tamanho afetará significativamente os tempos de deployment e a taxa de transferência da rede.

Nesta seção, você construiu uma imagem Ubuntu com todas as ferramentas e dependências necessárias do Go para executar a API que você clonou no Passo 1. Na próxima seção, você usará uma imagem Docker pré-criada e específica da linguagem para simplificar seu Dockerfile e agilizar o processo de criação.

Passo 3 — Construindo uma Imagem Base Específica para a Linguagem

Imagens pré-criadas são imagens básicas comuns que os usuários modificaram para incluir ferramentas específicas para uma situação. Os usuários podem, então, enviar essas imagens para o repositório de imagens Docker Hub, permitindo que outros usuários usem a imagem compartilhada em vez de ter que escrever seus próprios Dockerfiles individuais. Este é um processo comum em situações de produção, e você pode encontrar várias imagens pré-criadas no Docker Hub para praticamente qualquer caso de uso. Neste passo, você construirá sua API de exemplo usando uma imagem específica do Go que já tenha o compilador e as dependências instaladas.

Com imagens base pré-criadas que já contêm as ferramentas necessárias para criar e executar sua aplicação, você pode reduzir significativamente o tempo de criação. Como você está começando com uma base que tem todas as ferramentas necessárias pré-instaladas, você pode pular a adição delas ao seu Dockerfile, fazendo com que pareça muito mais limpo e, finalmente, diminuindo o tempo de construção.

Vá em frente e crie outro Dockerfile e nomeie-o como Dockerfile.golang. Abra-o no seu editor de texto:

  • nano ~/mux-go-api/Dockerfile.golang

Este arquivo será significativamente mais conciso do que o anterior, porque tem todas as dependências, ferramentas e compilador específicos do Go pré-instalados.

Agora, adicione as seguintes linhas:

~/mux-go-api/Dockerfile.golang
FROM golang:1.10  WORKDIR /go/src/api COPY . .  RUN \     go get -d -v \     && go install -v \     && go build  EXPOSE 3000 CMD ["./api"] 

Começando do topo, você verá que a instrução FROM agora é golang:1.10. Isso significa que o Docker buscará uma imagem Go pré-criada do Docker Hub que tenha todas as ferramentas Go necessárias já instaladas.

Agora, mais uma vez, compile a imagem do Docker com:

  • docker build -f Dockerfile.golang -t golang .

Verifique o tamanho final da imagem com o seguinte comando:

  • docker images

Isso produzirá uma saída semelhante à seguinte:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE golang latest eaee5f524da2 40 seconds ago 744MB . . .

Embora o próprio Dockerfile seja mais eficiente e o tempo de compilação seja menor, o tamanho total da imagem aumentou. A imagem pré-criada do Golang está em torno de 744MB, uma quantidade significativa.

Essa é a maneira preferida de criar imagens Docker. Ela lhe dá uma imagem base que a comunidade aprovou como o padrão a ser usado para a linguagem especificada, neste caso, Go. No entanto, para tornar uma imagem pronta para produção, você precisa cortar partes que a aplicação em execução não precisa.

Tenha em mente que o uso dessas imagens pesadas é bom quando você não tem certeza sobre suas necessidades. Sinta-se à vontade para usá-las como containers descartáveis, bem como a base para a construção de outras imagens. Para fins de desenvolvimento ou teste, onde você não precisa pensar em enviar imagens pela rede, é perfeitamente aceitável usar imagens pesadas. Mas, se você quiser otimizar os deployments, precisará fazer o seu melhor para tornar suas imagens o menor possível.

Agora que você testou uma imagem específica da linguagem, você pode passar para a próxima etapa, na qual usará a distribuição leve do Alpine Linux como uma imagem base para tornar a imagem Docker mais leve.

Passo 4 — Construindo Imagens Base do Alpine

Um dos passos mais fáceis para otimizar as imagens Docker é usar imagens base menores. Alpine é uma distribuição Linux leve projetada para segurança e eficiência de recursos. A imagem Docker do Alpine usa musl libc e BusyBox para ficar compacta, exigindo não mais que 8MB em um container para ser executada. O tamanho minúsculo é devido a pacotes binários sendo refinados e divididos, dando a você mais controle sobre o que você instala, o que mantém o ambiente menor e mais eficiente possível.

O processo de criação de uma imagem Alpine é semelhante ao modo como você criou a imagem do Ubuntu no Passo 2. Primeiro, crie um novo arquivo chamado Dockerfile.alpine:

  • nano ~/mux-go-api/Dockerfile.alpine

Agora adicione este trecho:

~/mux-go-api/Dockerfile.alpine
FROM alpine:3.8  RUN apk add --no-cache \     ca-certificates \     git \     gcc \     musl-dev \     openssl \     go  ENV GOPATH /go ENV PATH $  GOPATH/bin:/usr/local/go/bin:$  PATH ENV APIPATH $  GOPATH/src/api RUN mkdir -p "$  GOPATH/src" "$  GOPATH/bin" "$  APIPATH" && chmod -R 777 "$  GOPATH"  WORKDIR $  APIPATH COPY . .  RUN \     go get -d -v \     && go install -v \     && go build  EXPOSE 3000 CMD ["./api"] 

Aqui você está adicionando o comando apk add para utilizar o gerenciador de pacotes do Alpine para instalar o Go e todas as bibliotecas que ele requer. Tal como acontece com a imagem do Ubuntu, você precisa definir as variáveis de ambiente também.

Vá em frente e compile a imagem:

  • docker build -f Dockerfile.alpine -t alpine .

Mais uma vez, verifique o tamanho da imagem:

  • docker images

Você receberá uma saída semelhante à seguinte:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest ee35a601158d 30 seconds ago 426MB . . .

O tamanho caiu para cerca de 426MB.

O tamanho reduzido da imagem base Alpine reduziu o tamanho final da imagem, mas há mais algumas coisas que você pode fazer para torná-la ainda menor.

A seguir, tente usar uma imagem Alpine pré-criada para o Go. Isso tornará o Dockerfile mais curto e também reduzirá o tamanho da imagem final. Como a imagem Alpine pré-criada para o Go é construída com o Go compilado dos fontes, sua tamanho é significativamente menor.

Comece criando um novo arquivo chamado Dockerfile.golang-alpine:

  • nano ~/mux-go-api/Dockerfile.golang-alpine

Adicione o seguinte conteúdo ao arquivo:

~/mux-go-api/Dockerfile.golang-alpine
FROM golang:1.10-alpine3.8  RUN apk add --no-cache --update git  WORKDIR /go/src/api COPY . .  RUN go get -d -v \   && go install -v \   && go build  EXPOSE 3000 CMD ["./api"] 

As únicas diferenças entre Dockerfile.golang-alpine e Dockerfile.alpine são o comando FROM e o primeiro comando RUN. Agora, o comando FROM especifica uma imagem golang com a tag 1.10-alpine3.8 e RUN só tem um comando para a instalação do Git. Você precisa do Git para o comando go get para trabalhar no segundo comando RUN na parte inferior do Dockerfile.golang-alpine.

Construa a imagem com o seguinte comando:

  • docker build -f Dockerfile.golang-alpine -t golang-alpine .

Obtenha sua lista de imagens:

  • docker images

Você receberá a seguinte saída:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE golang-alpine latest 97103a8b912b 49 seconds ago 288MB

Agora o tamanho da imagem está em torno de 288MB.

Mesmo que você tenha conseguido reduzir bastante o tamanho, há uma última coisa que você pode fazer para preparar a imagem para a produção. É chamado de uma compilação de múltiplos estágios ou multi-stage. Usando compilações multi-stage, você pode usar uma imagem para construir a aplicação enquanto usa outra imagem mais leve para empacotar a aplicação compilada para produção, um processo que será executado no próximo passo.

Passo 5 — Excluindo Ferramentas de Compilação em uma Compilação Multi-Stage

Idealmente, as imagens que você executa em produção não devem ter nenhuma ferramenta de compilação instalada ou dependências redundantes para a execução da aplicação de produção. Você pode removê-las da imagem Docker final usando compilações multi-stage. Isso funciona através da construção do binário, ou em outros termos, a aplicação Go compilada, em um container intermediário, copiando-o em seguida para um container vazio que não tenha dependências desnecessárias.

Comece criando outro arquivo chamado Dockerfile.multistage:

  • nano ~/mux-go-api/Dockerfile.multistage

O que você vai adicionar aqui será familiar. Comece adicionando o mesmo código que está em Dockerfile.golang-alpine. Mas desta vez, adicione também uma segunda imagem onde você copiará o binário a partir da primeira imagem.

~/mux-go-api/Dockerfile.multistage
FROM golang:1.10-alpine3.8 AS multistage  RUN apk add --no-cache --update git  WORKDIR /go/src/api COPY . .  RUN go get -d -v \   && go install -v \   && go build  ##  FROM alpine:3.8 COPY --from=multistage /go/bin/api /go/bin/ EXPOSE 3000 CMD ["/go/bin/api"] 

Salve e feche o arquivo. Aqui você tem dois comandos FROM. O primeiro é idêntico ao Dockerfile.golang-alpine, exceto por ter um AS multistage adicional no comando FROM. Isto lhe dará um nome de multistage, que você irá referenciar na parte inferior do arquivo Dockerfile.multistage. No segundo comando FROM, você pegará uma imagem base alpine e copiará para dentro dela usando o COPY, a aplicação Go compilada da imagem multiestage. Esse processo reduzirá ainda mais o tamanho da imagem final, tornando-a pronta para produção.

Execute a compilação com o seguinte comando:

  • docker build -f Dockerfile.multistage -t prod .

Verifique o tamanho da imagem agora, depois de usar uma compilação multi-stage.

  • docker images

Você encontrará duas novas imagens em vez de apenas uma:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE prod latest 82fc005abc40 38 seconds ago 11.3MB <none> <none> d7855c8f8280 38 seconds ago 294MB . . .

A imagem <none> é a imagem multistage construída com o comando FROM golang:1.10-alpine3.8 AS multistage. Ela é apenas um intermediário usado para construir e compilar a aplicação Go, enquanto a imagem prod neste contexto é a imagem final que contém apenas a aplicação Go compilada.

A partir dos 744MB iniciais, você reduziu o tamanho da imagem para aproximadamente 11,3MB. Manter o controle de uma imagem minúscula como esta e enviá-la pela rede para os servidores de produção será muito mais fácil do que com uma imagem de mais de 700MB e economizará recursos significativos a longo prazo.

Conclusão

Neste tutorial, você otimizou as imagens Docker para produção usando diferentes imagens Docker de base e uma imagem intermediária para compilar e construir o código. Dessa forma, você empacotou sua API de exemplo no menor tamanho possível. Você pode usar essas técnicas para melhorar a velocidade de compilação e deployment de suas aplicações Docker e de qualquer pipeline de CI/CD que você possa ter.

Se você estiver interessado em aprender mais sobre como criar aplicações com o Docker, confira o nosso tutorial Como Construir uma Aplicação Node.js com o Docker. Para obter informações mais conceituais sobre como otimizar containers, consulte Building Optimized Containers for Kubernetes.

DigitalOcean Community Tutorials