Pular para o conteúdo

Guia rápido do Linux para Servidores Web: Entenda como funciona e aprenda a configurar um servidor do zero

Postado em 25 minutos de leitura

Um Sistema Operacional moderno como o Linux é bem complexo possuindo vários componentes rodando e se comunincando simultâneamente. Você não precisa entender todos os detalhes do funcionamento interno do sistema para conseguir gerencia-lo. Esse guia, divido em nove partes, vai resumir como o Linux funciona e mostrar os comandos básicos para configurar um servidor para aplicações web.

Parte 1: Kernel e Processos

O Linux está divido em três camadas: hardware, kernel e processos. Hardware é a parte física de qualquer máquina, a base de tudo. Kernel é o núcleo do sistema operacional que reside na memória RAM e diz à CPU o que fazer. Processos são programas em execução que são gerenciados pelo kernel.

A memória RAM é dividia em duas regiões: kernel space e user space. Kernel space é reservado para a execução do kernel e da maioria dos drivers de dispositivo. User space é onde os programas são executados. O kernel tem acesso às duas regiões e os processos em execução apenas à user space. Esses processos podem acessar somente uma pequena parte do kernel através de uma interface exposta conhecida por system call (ou syscall).

CPUs modernas incluem uma Unidade de Gerenciamento de Memória (MMU - Memory Management Unit) que permite que a memória acesse um esquema chamado memória virtual. Quando o processo acessa parte de sua memória, a MMU intercepta o acesso e usa um mapa de endereço de memória para converter o local da memória virtual do processo para um local real da memória física na máquina.

Todo processo que roda em user space possui um usuário, dono dele. Um usuário pode terminar ou modificar o comportamento do processo dele, mas não consegue interferir no processo de outro usuário. O usuário root é uma excessão porque ele pode alterar e terminar o processo de outros usuários. Por esse motivo ele é chamado de super usuário.

Parte 2: Shell e Permissões

Shell é um programa que permite a execução de comandos ou outros programas. Parte do sistema operacional Linux é composta por shell scripts - arquivos textos contendo comandos comandos shell.

A shell padrão do Unix é a Bourne Shell (sh) e a maioria das shells existentes herdaram as funcionalidades dela. No Linux, as shells mais populares são "Bourne-again" shell (bash) e Z shell (zsh). Bash é padrão em muitas distros Linux mas ZSH continua ganhando popularidade entre desenvolvedores de software por trazer funcionalidades como suporte a temas visuais e navegar entre diretórios (pastas) digitando apenas o nome.

Os processos utilizam I/O streams para ler e gravar dados. Processos leem dados do input stream (stdin) e escrevem dados no output stream (stdout). Existe um terceiro I/O stream, o standard error (stderr), usado pelos processos para saída de erros. O kernel fornece a cada processo um fluxo de saída padrão, onde ele pode gravar sua saída.

Para enviar a saída do comando para um arquivo em vez do stdout, use o caractere de redirecionamento >. Ex.: echo "hey" > hi.txt. Para enviar a saída padrão de um comando (stdout) para a entrada padrão de outro comando (stdin), use o caractere de barra vertical | (pipe). Ex.: head /proc/cpuinfo | tr a-z A-Z. E para enviar a saída de erro padrão para um arquivo, use 2>. Ex.: ls /fffffffff 2> error.txt.

A shell pode armazenar variáveis temporárias, chamadas de variáveis de shell, contendo os valores texto do tipo string. Para atribuir um valor a uma variável de shell, utilize o sinal de igual (=). Ex.: NAME=Gustavo. Para acessar esta variável, use $NAME. Ex .: echo $NAME.

Uma variável de ambiente (envvar) é como uma variável de shell. A principal diferença entre as variáveis de ambiente e shell é que o sistema operacional passa todas as variáveis de ambiente para os programas executados pelo shell, enquanto as variáveis do shell não podem ser acessadas nos comandos executados.

PATH é uma variável de ambiente especial contendo uma lista de diretórios do sistema que o shell pesquisa ao tentar localizar um comando. Por exemplo, quando você executa ls, o shell pesquisa nos diretórios listados em PATH o programa ls. Se programas com o mesmo nome aparecerem em vários diretórios no caminho, o shell executará o primeiro da lista.

Um comando ou programa consegue ser executado pelo shell quando ele possui uma permissão de execução +x. Todo arquivo no Linux possui um conjunto de permissões que determinam se você pode ler, gravar ou executar o arquivo. O comando ls -l exibe as permissões. Cada conjunto de permissões pode conter quatro representações básicas:

  • r: significa que o arquivo pode ser lido.
  • w: significa que o arquivo pode ser escrito.
  • x: significa que o arquivo pode ser executado.
  • -: não significa nada.

Alguns arquivos executáveis têm um s na lista de permissões em vez de um x. Isso indica que o executável possui um setuid, o que significa que, quando você executa o programa, ele é executado como se o dono do arquivo fosse o usuário root (setuid dispensa a necessidade de usar o sudo para executar o arquivo).

Para mudar as permissões de um arquivo ou diretório, utilize o comando chmod. Ex.:

chmod u+x program
chmod u-w filename           # -w desabilita escrita
chmod u+rw -Rf /tmp/folder1  # aplica +r e +w recursivamente em /tmp/folder1 e nos arquivos la dentro

Parte 3: Dispositvos

É fácil manipular a maioria dos dispositivos em um sistema Linux porque o kernel apresenta como arquivos muitas das interfaces de I/O do dispositivo. Esses arquivos também são chamados de devices nodes.

O tradicional diretório /dev possui um esquema conveniente, porém muito simplista, para que processos consigam acessar os dispositivos suportados pelo kernel. Já o /sys, fornecido pela interface sysfs, apresenta uma visão completa dos dispositivos conectados com base em seus atributos reais de hardware, como fabricante, modelo etc.

A maioria dos discos rígidos conectados aos sistemas Linux atuais corresponde aos nomes dos dispositivos com um prefixo sd, como /dev/sda, /dev/sdb e assim por diante. Esses dispositivos representam discos inteiros; o kernel cria arquivos de dispositivo separados, como /dev/sda1 e /dev/sda2, para as partições em um disco.

Linux atribui dispositivos a arquivos de dispositivos na ordem em que forem encontrados. Esse esquema de atribuição de dispositivos tradicionalmente causava problemas ao reconfigurar o hardware. Por exemplo, você possui um sistema com três discos: /dev/sda, /dev/sdb e /dev/sdc. Se precisar remover o disco /dev/sdb por algum motivo, o antigo /dev/sdc será renomeado para /dev/sdb. Para resolver esse problema, os sistemas Linux mais modernos utilizam o UUID (Universally Unique Identifier) para acesso persistente ao dispositivo de disco. Utilize o comando blkid para ver o UUID de um dispositivo. Ex.:

sudo blkid /dev/sdb1
/dev/sdb1: UUID="58faf878-ae9e-4e03-9427-439a27eeba41" BLOCK_SIZE="1024" TYPE="ext2" PARTUUID="47cc11bf-01"

Em Linux modernos, você não precisa criar seus arquivos de dispositivos; isso é feito por um programa chamado udev. O kernel do Linux envia um evento para o processo systemd-udevd ao detectar um novo dispositivo no sistema. O udevd escuta esse evento, obtém os atributos via sysfs e executa uma série regras de acordo com o tipo de evento. Essas regras podem ser encontradas nos diretórios /lib/udev/rules.d e /etc/udev/rules.d.

Utilize o comando udevadm para mostrar as informações de um dispositivo. Ex.:

udevadm info --query=all --name=/dev/sda

Todo disco precisa ser subdivido em pelo menos uma partição para poder ser utilizado. O kernel apresenta cada partição como um dispositivo de bloco, como faria com um disco inteiro. Partições são definidas em uma pequena área do disco chamada tabela de partição. A próxima camada após a partição é o sistema de arquivos, onde ficam os arquivos e diretórios que os processos interagem.

Existem diversos tipos de tabelas de partição. A tabela tradicional é a encontrada no MBR (Master Boot Record). Um padrão mais recente é a Globally Unique Identifier Partition Table (GPT). O MBR básico tem um limite de quatro partições primárias; portanto, se você quiser mais de quatro, designe uma partição como uma partição estendida. Em seguida, você subdivide a partição estendida em partições lógicas. As duas ferramentas mais comuns para gerenciar partições são cfdisk e parted (ou gparted em modo gráfico).

É necessário criar um filesystem (formatar a partição, como dizem usuários Windows) para cada partição. Caso contrário ela não tem serventia alguma. Linux suporta vários tipos de filesystem mas os mais populares são:

  • ext4 Fourth Extended filesystem é uma melhoria incremental com suporte para arquivos maiores que o suporte ext2 ou ext3 e um número maior de subdiretórios. Talvez seja o mais popular pois é o padrão sugerido pela maioria das distros. Possui suporte a journaling, uma funcionalidade que garante a consistência do dado escrito no disco em troca de um pouco de perda em performance, principalmente em discos com mais de 50 TiB.
  • xfs X File System também suporta journaling e fornece um ótimo desempenho em casos onde há um grande número de processos escrevendo no disco ao mesmo tempo. Eu já vi casos de perda de dados após um desligamento repentino da máquina ou redimensionamento da partição.
  • zfs Z File System vem sendo considerado a nova geração de filesystem, prometendo reparo automático de dados, replicação rápida e snapshots atômicos. Há algumas discussões sobre seu uso devido à sua licença CDDL ser diferente da GPL utilizada pelo kernel do Linux.
  • btrfs B-Tree Filesystem foca nos mesmos objetivos do zfs, porém ainda há diversos bugs em sua implementação.

O utilitário mkfs é compatível com a maioria dos sistemas de arquivos. Ex.: mkfs -t ext4 /dev/sda2. Para montar e desmontar um filesystem existe os comandos mount e unmount:

sudo mount /dev/sda2 /media/hd
sudo umount /media/hd

Linux "bufferiza" escritas no disco. O kernel não escreve as mudanças em arquivos no disco de imediato. Ele armazena os dados na memória RAM até que seja conveniente aplicar a mudança no disco. Isso é transparente para as aplicações e seu objetivo é melhorar a performance. Você pode forçar o kernel à escrever as mudanças em disco usando o comando sync. Ex.: cp file.mp4 /media/pendrive && sync.

Existe um filesystem especial montado como /proc, O nome proc é na verdade uma abreviação de process. Cada diretório numerado dentro de /proc é realmente o ID de um processo atual no sistema; os arquivos nesses diretórios representam vários aspectos dos processos. O sistema de arquivos proc do Linux inclui muitas informações adicionais sobre kernel e hardware em arquivos como /proc/cpuinfo.

Parte 4: Boot e Inicialização do Kernel

O processo de boot funciona assim:

  1. A BIOS da máquina (ou um boot firmware) carrega e executa o boot loader, um programa especial inserido durante a instalação do sistema.
  2. O boot loader procura a imagem do kernel no disco, carrega para memória e executa ela.
  3. O kernel inicializa os drivers para dispositivos.
  4. O kernel monta o root filesystem.
  5. O kernel inicializa um programa chamado init com um process ID 1 (PID 1). Nesse ponto é onde começa o user space. O init inicia outros processos e em algum momento executa o processo que permite o login do usuário.

A tarefa de um boot loader parece simples: carregar o kernel na memória e, em seguida, iniciar o kernel passando um conjunto de parâmetros.

Nos PCs, os boot loaders usam a BIOS (Basic Input / Output System) ou a UEFI (Unified Extensible Firmware Interface) para acessar os discos. Quase todo o hardware de disco possui firmware que permite que o BIOS acesse o hardware de armazenamento conectado com o LBA (Linear Block Addressing). Mas apresenta baixo desempenho. A maioria dos boot loaders modernos pode ler tabelas de partição e acesso somente leitura aos sistemas de arquivos.

O GRUB (Grand Unified Boot Loader) é o boot loader padrão do Linux. Um dos recursos mais importantes do GRUB é a navegação no sistema de arquivos, que permite uma seleção de imagem e configuração do kernel muito mais fácil. Para acessar o menu GRUB, pressione e segure Shift quando a tela de inicialização da BIOS ou firmware aparecer pela primeira vez. Ele possui seu próprio "kernel" e seu próprio comando insmod para carregar dinamicamente os módulos do GRUB, completamente independente do kernel do Linux. Muitos comandos do GRUB são semelhantes aos comandos do shell Unix; existe até um comando ls para listar arquivos.

O UEFI exige que os boot loaders sejam assinados digitalmente por uma autoridade confiável para serem executados. O resultado é que, se você tentar instalar um boot loader não assinado (que é a maioria das distribuições atuais do Linux), ele não será carregado.

A maneira mais fácil de contornar isso para qualquer pessoa sem interesse no Windows é desabilitar o secure boot nas configurações de EFI. No entanto, isso pode não funcionar corretamente em sistemas dual boot (Windows + Linux). Algumas distribuições Linux estão oferecendo gerenciadores de inicialização assinados. Algumas soluções são apenas front-ends para o GRUB, algumas oferecem uma sequência de carregamento totalmente assinada e outras são gerenciadores de inicialização totalmente novos.

O user space inicia nessa ordem:

  1. init.
  2. Serviços essenciais como udevd e syslogd.
  3. Configuração de rede.
  4. Outros serviços não essenciais para o kernel e provavelmente instalados pelo usuário.
  5. Prompt de login ou interface gráfica.

Existem três grandes implementações de init até o momento: System V, Upstart e systemd. A maioria das distribuições já migraram para o systemd. O systemd foi inspirado no launchd da Apple, usado no MacOS. Ele permite iniciar processos em paralelo e consequentemente tornando o boot mais rápido. Além disso, o systemd não opera apenas processos e serviços; Ele também pode montar sistemas de arquivos, monitorar sockets de rede, executar jobs e muito mais. Cada tipo de recurso é chamado de unit type e cada recurso específico é chamado de unit.

Alguns unit types que executam as tarefas necessárias durante o boot em qualquer sistema Linux:

  • Service Units: controla os daemons de serviço tradicionais em um sistema Linux.
  • Mount Units: controla a montagem do filesystem.
  • Target Units: controla outras units, geralmente agrupando-as.

As tarefas de inicialização do Linux são tolerantes a falhas e geralmente podem falhar sem causar problemas sérios nos serviços padrão. systemd oferece uma infinidade de tipos e estilos de dependência. Os tipos básicos são:

  • Requires: dependência estrita. Ao ativar uma unit que possui como dependência outra unit, o systemd tenta ativar a unit de dependência. Se a unit de dependência falhar, o systemd desativa a unit dependente.
  • Wants: dependências apenas para ativação. Ao ativar uma unit, o systemd ativa as dependências de Wants da unit, mas não se importa se essas dependências falharem.
  • Requisite: units que já devem estar ativas. Antes de ativar uma unit com uma dependência necessária, o systemd primeiro verifica o status da dependência. Se a dependência não tiver sido ativada, o systemd falhará na ativação da unit com a dependência.
  • Conflicts: dependências negativas. Ao ativar uma unit com uma dependência Conflict, o systemd desativa automaticamente a dependência, se estiver ativa. A ativação simultânea de duas units conflitantes falhará.

Também é possível anexar dependências "ao contrário" usando WantedBy na configuração da unit A, que é uma dependência da unit B.

Para ativar units em uma ordem específica, você pode usar os seguintes modificadores de dependência:

  • Before: a unit atual será ativada antes das units listadas. Por exemplo, se Before=bar.target aparecer em foo.target, o systemd ativará foo.target antes de bar.target.
  • After: A unit atual é ativada após as units listadas.

Muitas keywords de condição operam em vários estados do sistema operacional em vez de systemd units. Por exemplo:

  • ConditionPathExists
  • ConditionPathIsDirectory
  • ConditionFileNotEmpty

Existem dois diretórios principais para a configuração do systemd: o diretório das units do sistema /usr/lib/systemd/system e o diretório de configuração do sistema /etc/systemd/system. Evite fazer alterações no diretório da unit do sistema, porque sua distribuição Linux pode sobrescrever algum arquivo durante uma atualização. Faça suas alterações locais no diretório de configuração do sistema. Portanto, quando for dada a opção entre modificar algo em /usr e /etc, prefira /etc.

E para finalizar o assunto sobre systemd, abaixo os comandos mais utilizados:

systemctl list-jobs
systemctl list-units
systemctl status <unit>
systemctl enable <unit>
systemctl disable <unit>
systemctl stop <unit>
systemctl reload <unit>
systemctl daemon-reload           # Recarrega todas as configuracoes.
journalctl --no-pager -eu <unit>  # Exibe mensagens de log

Parte 5: Configuração do Relógio e Timezone

Sistemas Linux lidam com dois relógios: o relógio do hardware (RTC - Real Time Clock), geralmente alimentado por uma bateria na placa-mãe, e o relógio do sistema, que é o total de tempo em segundos desde 01/01/1970 00:00:00h UTC, mantido pelo Kernel com base no RTC no momento do boot. É recomendado manter o relógio do hardware no horário universal coordenado (UTC) para evitar qualquer problema com as correções de fuso horário ou horário de verão. Você pode definir o RTC para o relógio UTC do seu kernel usando este comando:

sudo hwclock --hctosys --utc
date +%s  # Total seconds

Como as máquinas Linux geralmente ficam ligadas por meses ou anos sem serem reiniciadas, elas tendem a desenvolver um desvio no tempo. Esse desvio de tempo é a diferença atual entre o tempo do kernel e o tempo real (conforme definido por um relógio atômico ou outro relógio muito preciso). A melhor forma de manter a hora do sistema correta é executar o serviço Network Time Protocol (NTP) para manter o horário correto usando um servidor remoto de alta precisão. Esse serviço roda como um daemon, não impacta no desempenho, mas requer uma conexão constante com a internet.

O timezone padrão é o UTC e todos os timezones suportados são encontrados no diretório /usr/share/zoneinfo. Para mudar o timezone manualmente, basta copiar o arquivo de timezone para /etc/localtime. O comando timedatectl oferece uma maneira mais elegante:

sudo timedatectl set-timezone America/Sao_Paulo

Para usar um timezone diferente apenas para uma sessão do shell (ou shell script), configure a variável de ambiente TZ com o nome de um dos arquivos em /usr/share/zoneinfo :

export TZ=America/Sao_Paulo

Parte 6: Análise de Processos e Recursos utilizados

Existem três tipos básicos de recursos de hardware: CPU, memória e I/O. O trabalho do kernel é alocar recursos de maneira justa para os processos.

top é o programa padrão para exibir o status atual do sistema. As teclas mais importantes para utiliza-lo são:

  • Barra de Espaço Atualiza as informações exibidas.
  • M Exibe os processos por ordem de uso de memória.
  • P Exibe os processos por ordem de uso da CPU.
  • u Exibe apenas processos do usuário.
  • f Seleciona diferentes estatísticas para serem exibidas.
  • H Exibe o total threads.

Algumas distribuições também trazem seus sucessores mais modernos como htop e * atop*.

O comando lsof exibe os arquivos abertos e os processos utilizando eles. Ele também pode listar recursos de rede, bibliotecas dinâmicas, pipes e muito mais. Exemplos de uso:

lsof /tmp               # arquivos abertos no diretório
lsof -U | grep -i sock  # arquivos de sockets em uso. Ex.: mysql.sock
lsof -p 111             # arquivos abertos pelo processo com PId 111
sudo lsof -ti tcp:443   # exibe o processo escutando na porta 443 TCP

Para exibir o consumo de recursos de um processo, usamos o utilitário pidstat. Ex.: pidstat -r -d.

Se você precisar se aprofundar ainda mais para ver os recursos de I/O usados por processos individuais, a ferramenta iotop pode ajudar. Usar o iotop é semelhante ao uso do top. Há uma exibição continuamente atualizada que mostra os processos que consomem mais I/O, com um resumo geral na parte superior.

Parte 7: Rede de Computadores

Um computador transmite dados através de uma rede em pequenos blocos chamados pacotes, que consistem em duas partes: um cabeçalho e um payload. O cabeçalho contém informações de identificação, como os hosts de origem / destino e o protocolo básico. O payload contém os dados reais que o computador deseja enviar.

Para ver os endereços que estão ativos na máquina Linux, execute:

ip address show  # ifconfig virou deprecated

Ao usar o TCP, um aplicativo abre uma conexão entre uma porta em sua própria máquina e uma porta em um host remoto. Por exemplo, um browser pode abrir uma conexão entre a porta 38213 em sua própria máquina e a porta 80 em um host remoto. Do ponto de vista do browser, 38213 é a porta local e a 80 é a porta remota. Você pode usar o netstat para visualizar as conexões atualmente abertas na sua máquina:

netstat -plunt

Para visualizar as conexões abertas, utilize o ss. Pressione Ctrl+c para encerrar o programa:

watch ss -tp

O DNS difere dos outros componentes de rede porque acontece completamente no user space. O processo de resolução normalmente se acontece assim:

  1. O aplicativo chama uma função para procurar o endereço IP de um hostname. Essa função está na biblioteca compartilhada do sistema, portanto, o aplicativo não precisa saber os detalhes de como ele funciona ou se a implementação será alterada.
  2. Quando a função na biblioteca compartilhada é executada, ela age de acordo com um conjunto de regras (encontrado em /etc/nsswitch.conf) para determinar um plano de ação nas pesquisas. Por exemplo, as regras geralmente dizem que, mesmo antes de acessar o DNS, verifique se há uma substituição manual no arquivo /etc/hosts.
  3. Quando a função decide usar o DNS para a pesquisa de nomes, ela consulta um arquivo de configuração adicional para encontrar um servidor de nomes DNS. O servidor de nomes é acessado pelo endereço IP. Ex.: 8.8.8.8.
  4. A função envia uma solicitação de pesquisa de DNS (pela rede) para o servidor de nomes.
  5. O servidor de nomes responde com o endereço IP do hostname e a função retorna esse endereço IP ao aplicativo.

O arquivo de configuração que contém o IP dos servidores DNS é /etc/resolv.conf. Geralmente ele é sobrescrito pelo daemon NetworkManager quando a máquina se conecta à uma rede. É possível modifica-lo manualmente porém ele será sobrescrito ao reiniciar o serviço NetworkManager. Abaixo um exemplo usando o servidor de nomes do Google:

echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf

O Secure Shell (SSH) é o programa padrão para acesso remoto a uma máquina Linux. Quando configurado, o SSH permite logins de shell seguros, execução remota de programas, compartilhamento simples de arquivos e muito mais. O pacote OpenSSH vem com o cliente ssh e o servidor sshd. O SSH faz o seguinte:

  • Criptografa sua senha e todos os outros dados da sessão, protegendo você dos bisbilhoteiros.
  • Oferece clientes para praticamente qualquer sistema operacional.
  • Usa chaves para autenticação de host.

Apenas instalar e iniciar o OpenSSH Server já é suficiente para permitir conexões. Configurações avançadas, como bloquear acesso remoto do usuário root, podem ser feitas no arquivo de configuração /etc/ssh/sshd_config. No Linux e MacOS utilizamos o cliente ssh passando o username existente na máquina remota e informando o password quando solicitado. A porta padrão do sshd é a 22. Ex.:

ssh gustavo@10.243.187.3

Para evitar ter que digitar a senha toda vez, você pode gerar um par de chaves criptográficas. A geração de um par de chaves fornece duas longas sequências de strings: uma chave pública e uma privada. Você pode colocar a chave pública em qualquer servidor e desbloqueá-la utilizando chave privada. Quando os dois coincidem, o sistema é desbloqueado sem a necessidade de senha. Para criar um par de chaves, faça o seguinte:

ssh-keygen -t rsa -f ~/.ssh/mykey

O comando acima vai criar a chave privada ~/.ssh/mykey e a chave pública ~/.ssh/mykey.pub. Copie a chave pública para o servidor:

# MacOS: brew install ssh-copy-id
ssh-copy-id -i ~/.ssh/mykey.pub gustavo@10.243.187.3

# Se nao tem o ssh-copy-id instalado:
# cat ~/.ssh/mykey.pub | ssh gustavo@10.243.187.3 "mkdir -p ~/.ssh && chmod 600 ~/.ssh && cat >>  ~/.ssh/authorized_keys"

O comando scp é utilizado para cópia de arquivos e diretórios. Exemplos de uso:

scp arquivo1.txt arquivo2.txt gustavo@10.243.187.3:/tmp/  # copia dois arquivos para o servidor
scp -r ~/Documents gustavo@10.243.187.3:/tmp/             # copia o diretorio Documents
scp gustavo:10.243.187.3:/tmp .                           # copia o diretorio /tmp do servidor remoto para o diretorio local atual

Parte 8: Transferência de Arquivos com Rsync

Se você deseja mover uma estrutura de diretório inteira, pode fazê-lo com scp -r ou, se precisar de um pouco mais de desempenho, tar em um pipeline:

tar cBzf - ~/myapp | ssh remote_host tar zxBpf -

Esses métodos funcionam, mas não são muito flexíveis. Após a transferência ser concluída, o host remoto pode não ter uma cópia exata do diretório. Se o diretório já existir na máquina remota e tiver alguns arquivos diferentes, esses arquivos continuarão existindo após a transferência. No Linux, o rsync é o sincronizador padrão, oferecendo bom desempenho e muitas maneiras úteis de realizar transferências.

O programa rsync deve ser instalado na máquina de origem e na máquina de destino. A maneira mais fácil de transferir arquivos é usar uma conta shell remota usando o acesso SSH. No entanto, o rsync pode ser útil mesmo para copiar arquivos e diretórios entre locais na mesma máquina, como de um disco para outro.

Exemplos usando rsync:

rsync file1 file2 user@host:/path
rsync -nva dir host:destination_dir          # transfere toda a estrutura incluindo symlinks
rsync -a --exclude=.git src host:/path
rsync -a --exclude=/src/.git src host:/path  # exclui um item especifico
rsync -az dir host:destination_di            # comprime

Por padrão, o rsync copia arquivos e diretórios sem considerar o conteúdo anterior do diretório de destino. Para criar uma réplica exata do diretório de origem, você deve excluir os arquivos no diretório de destino que não existem no diretório de origem.

rsync -a --delete dir remove_host:/path

O rsync também é bem utilizado para backups simples. Para backups avançados, dê uma pesquisada no bacula.

Parte 9: Servidor Web

Um servidor da Web é um software que pode atender às solicitações dos clientes da World Wide Web, geralmente por HTTP/HTTPS.

O HTTPS utiliza o protocolo HTTP já conhecido e simplesmente coloca uma camada de criptografia SSL/TLS sobre ela. Servidores e clientes ainda falam exatamente o mesmo HTTP entre si, mas por uma conexão SSL segura que criptografa e descriptografa suas solicitações e respostas. A camada SSL tem 2 objetivos principais:

  • Verificar se você está conversando diretamente com o servidor com o qual pensa estar conversando.
  • Garantir que apenas o servidor possa ler o que você envia e somente você possa ler o que ele envia de volta.

Uma conexão SSL entre um cliente e um servidor é configurada por um handshake. Depois que a conexão é estabelecida, ambas as partes podem usar o algoritmo e as chaves acordados para enviar mensagens entre si com segurança. O handshake é divido em três fases principais - Hello, Troca de certificados e Troca de chaves.

O handshake começa com o cliente enviando uma mensagem ClientHello. Contém todas as informações que o servidor precisa para se conectar ao cliente via SSL. O servidor responde com um ServerHello, que contém informações semelhantes exigidas pelo cliente, incluindo uma decisão com base nas preferências do cliente sobre qual codificação e versão do SSL serão usadas.

Com o contato estabelecido, o servidor precisa provar sua identidade para o cliente. Isso é feito usando seu certificado SSL. Um certificado SSL contém vários dados, incluindo o nome do proprietário, o domínio, a chave pública do certificado, a assinatura digital e as informações sobre as datas de validade do certificado. O cliente verifica se confia no certificado ou se é verificado e confiável por uma das várias autoridades de certificação (CAs) em que ele também confia.

A criptografia da mensagem trocada entre o cliente e servidor será feita usando um algoritmo simétrico. Um algoritmo simétrico usa uma única chave para criptografia e descriptografia, e assimétrico exige um par de chaves pública/privada. Ambas as partes precisam concordar com essa chave única e simétrica, um processo que é realizado com segurança usando criptografia assimétrica e as chaves pública/privada do servidor.

O cliente gera uma chave aleatória a ser usada para o algoritmo simétrico principal. Ele o criptografa usando um algoritmo também acordado durante a fase Hello e a chave pública do servidor (encontrada em seu certificado SSL). Ele envia essa chave criptografada para o servidor, onde é descriptografada usando a chave privada do servidor e assim o handshake está concluído.

As partes estão satisfeitas por estarem conversando com a parte certa e concordaram com uma chave para criptografar simetricamente os dados que estão prestes a enviar um ao outro. Agora, as solicitações e respostas HTTP podem ser enviadas, formando uma mensagem de texto puro e depois criptografando e enviando essa mensagem. Apenas o servidor consegue descriptografar a mensagem do cliente e vice-versa.

Você pode comprar um certificado de uma autoridade certificadora. Os valores variam de de acordo com o tipo de certificado, criptografia utilizada e data de expiração. Após a compra, você ativa o certificado para um ou mais domínios. Em 2016 foi criada uma autoridade certificadora gratuita, automatizada e open source chamada Let's Encrypt. Agora todos podem gerar certificados SSL, válidos por 90 dias, sem precisar pagar por isso.

O processo para obter um certificado Let's Encrypt é automatizado com o protocolo ACME e pode ser realizado de três maneiras (tipos de desafio): HTTP, DNS e TLS-ALPN. O desafio HTTP sobe um servidor Web temporário, na porta 80, que receberá uma requisição e em caso de resposta, o certificado será emitido. Para obter um certificado wildcard, que permite utilizar HTTPS com subdomínios, por exemplo api.meudominio.com, você deve utilizar o desafio DNS.

Execute o código abaixo no servidor que responde pelo domínio:

# Precisa do PIP (gerenciador de pacotes Python) instalado
sudo pip install certbot
DOMAIN="meudominio.com"
EMAIL="no-reply@gustavohenrique.com"
sudo certbot certonly \
  --logs-dir /etc/letsencrypt/log/ \
  --config-dir /etc/letsencrypt/config/ \
  --work-dir /etc/letsencrypt/work/ \
  -d $DOMAIN \
  --cert-name $DOMAIN \
  -m $EMAIL \
  --agree-tos \
  --non-interactive \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --standalone \
  --preferred-challenges http

Os certificados gerados pelo comando acima ficarão no diretório /etc/letsencrypt/config/archive/meudominio.com.

Linux possui uma grande variedade de servidores Web. Por muitos anos o Apache Http Server teve o domínio quase absoluto da internet. A arquitetura do Apache2 cria uma nova thread no sistema operacional para cada requisição HTTP. Isso pode levar à um consumo excessivo dos recursos da máquina, causando lentidão nas respostas, quando há uma grande quantidade de requisição.

Em 2006 foi lançado o Nginx, uma alternativa ao Apache2, com uma arquitetura diferente, baseada em eventos, permitindo que uma thread consiga lidar com múltiplas requisições, consumindo menos recursos que o Apache2. Com isso, Nginx tornou-se o queridinho, principalmente para servir arquivos estáticos (css, javascript, imagens...), enquanto o Apache2 ainda tem espaço principalmente devido à facilidade em trabalhar com a linguagem PHP.

Escrito em Golang, lançado em 2015, o Caddy chegou com a proposta de ser um servidor Web moderno, fácil de usar e configurar, e com suporte nativo para o Let's Encrypt. Sendo apenas um arquivo binário, você pode executa-lo no seu desktop para compartilhar arquivos estáticos na sua rede internet como também usar como servidor Web para aplicações em produção.

Caddy tem sido minha preferência pessoal como servidor Web nos últimos anos. Vou mostrar como configurar o Caddy para servir uma aplicação Web rodando na porta 5000 e servir arquivos estáticos ao adicionar /static na URL.

Para instalar:

curl https://getcaddy.com | bash -s personal
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy

Baixei o Caddy e atribuí o setcap para o processo conseguir escutar na porta 443 sem ser root. Em seguida, criei um arquivo /tmp/Caddyfile contendo:

meudominio.com:443 {
    root /tmp/www/static
    tls /etc/letsencrypt/config/archive/meudomino.com/fullchain1.pem /etc/letsencrypt/config/archive/meudomino.com/privkey1.pem
    proxy / 127.0.0.1:5000
    log /tmp/meudominio.com.log {
        rotate_age 1
        rotate_keep 2
    }
}

E depois executar o caddy passando como parâmetro o arquivo Caddyfile:

caddy -conf /tmp/Caddyfile

Conclusão

Nesse guia, eu tentei ser bem objetivo nos conceitos fundamentais sobre Linux, processos e ferramentas básicas. Já existe um material bem abrangente na web sobre como instalar e configurar diversas distribuições e serviços espero ter facilitado o entendimento sobre como as coisas funcionam.

Links