Implementando um sistema balançeador de carga HTTP com alta disponibilidade utilizando Nginx como proxy reverso, sincronização rsync, compartilhamento de sessões PHP e SSL

1. Introdução

Este artigo demonstra a configuração de um ambiante de alta disponibilidade para serviços web. Ele poderia ser dividido em vários artigos independentes abordando cada etapa e instalação de cada software, mas reuni todas as informações em um único artigo que aborda um ambiente completo de alta disponibilidade, balanceamento de carga e cache (proxy reverso) de um ambiente web multiplataforma, como Apache + PHP ou Apache + Tomcat por exemplo.

2. Softwares Utilizados

Utilizaremos o keepalived para fornecer failover de endereçamento IP  e checagem de estado dos servidores web. Assim se um dos nós ficar fora de operação, o keepalived, através da sua checagem de estado, cuidará de configurar os devidos endereços e rotas IP nos nós que assumirão o serviço anteriormente de outro servidor.
Já o Nginx (leia-se engine X) é um rápido e leve servidor web e proxy reverso. Sua arquitetura interna otimizada, permite servir centenas de conexões com um pequeno overhead de CPU e memória. Nesta configuração usaremos o Nginx como um proxy reverso, onde as requisições das páginas dos nossos servidores web chegarão primeiramente ao Nginx que por sua vez realizará o balanceamento de carga entre os servidores web Apache servirão as páginas solicitadas. No nosso cenário utilizaremos também o Nginx para servir o conteúdo estático dos sites (como imagens, arquivos pdf, css, js etc), já que ele é muito mais rápido que o apache fazendo este serviço.  Utilizaremos também a opção de fazer cache das páginas solicitadas, assim o Nginx pode responder uma página que já esteja em cache sem precisar abrir nenhuma conexão com os servidores Apache ou Tomcat, deixando-os livres pra processar o que realmente é necessário, no caso o conteúdo dinâmico como PHP, JSP etc.

As sessões são salvas por padrão nas mesmas máquinas que servem o PHP. O que implica que temos que garantir que nossos usuários sejam direcionados para o mesmo servidor web. Mas nosso balanceamento de carga dividirá equalitáriamente as requisições entre os servidores web, então uma sessão iniciada em um servidor não será reconhecida nos outros servidores membros do cluster. Para resolver esse problema utilizaremos o repcached, que é um patch para o memcached, que por sua vez é um sistema que permite realizar cache de quase todo tipo de objetos (como resultados de funções, resultados de consultas de bancos de dados etc) em memória RAM. Realizar cache em memória ao invés carregar dados de um banco de dados, pode aumentar significativamente a performance de sistemas PHP.

O repcached adiciona suporte à replicação dos dados do memcached entre os nós do cluster, assim uma sessão que seja armazenada no repcached de um nó estará disponível em qualquer outro nó do cluster, e se este nó inicial vier a ficar indisponível poderá resgatar os seus dados armazenados nos outros membros do cluster quando voltar a ficar on-line.

Assim sessões estarão ser compartilhadas entre todos os servidores que farão parte do nosso cluster, já que não temos como saber para qual servidor web o usuário será balanceado a cada solicitação.

Como nosso todos os nós do nosso cluster de alta disponibilidade web vai servir o mesmo conteúdo, isto é, as mesmas páginas, temos que estar certificados que eles estejam sincronizados entre si. A solução mais fácil seria utilizar um storage e um sistema de arquivos distribuído como ocfs2 ou gfs conectado aos servidores, mas como nem sempre temos um equipamento como esse disponível, recorremos a outras soluções.

Poderíamos usar um diretório compartilhado por NFS e montado em todos os servidores, mas continuaríamos assim com um gargalo que seria a alta taxa de I/O na rede.

Optamos por usar uma simples solução de espelhamento utilizando rsync nos diretórios publicados nos servidores web.Existem outras opções mais avançadas de espelhamento pela rede como o DRBB e sistemas de arquivos distribuídos como o  GlusterFS que exigiriam um artigo específico falando sobre eles, mas para o nosso ambiente o rsync é suficiente.

3. Cenário

Utilizaremos apenas dois servidores físicos utilizando Debian GNU/Linux 6 (squeeze) amd64 com uma instalação básica através da imagem netinst. Apesar de utilizar apenas dois servidores esta solução é bastante escalável podendo-se facilmente aumentar o número de nós do cluster de acordo com as necessidades.

As requisições aos websites servidos serão primeiramente recebidas no Nginx do Servidor A, denominado master, que realizará o balanceamento das requisições entre os Apache dos Servidores A e B (back end servers) conforme a imagem abaixo:

  • Servidor A: 10.10.10.1
  • Servidor B: 10.10.10.2
  • IP Virtual (VIP): 10.10.10.10
Cenário
Clique para ampliar

A fim de se evitar um Ponto Único de Falhas, (SPOF, Singe Point of Failure) realizaremos o chamado failover entre os processos Nginx, assim caso o Nginx  do Servidr A venha a falhar o Servidor B assumirá seu papel evitando que todo o sistema venha a ficar indisponível, mesmo estando os servidores web de pé e funcionando. Temos, portanto, dois níveis de Alta Disponibilidade, entre os balanceadores de carga (NginX) e entre os Servidores Web A e B (Apache), pois caso um deles fique indisponível as requisições serão direcionadas apenas ao outro servidor até que o servidor defeituoso volta a ficar on-line. A ilustração abaixo demonstra o cenário em possíveis falhas:

  • Falha 1 (Servidor B totalmente inoperante):
Falha 1
Clique para ampliar

Enquanto o Servidor B estiver indisponível, as requisições serão encaminhadas somente ao Servidor A.

  • Falha 2 (Processo Nginx do Servidor A com problemas):
Falha 2
Clique para ampliar

Aqui temos um problema com o processo balanceador de carga do Servidor A, porém o servidor web Apache está funcionando e pronto para receber requisições. O Keepalived reconhecerá que o Nginx do Servidor A caiu e o Servidor B ira atribuir para si o IP Virtual e tornara-se o balanceador de carga do sistema, encaminhando as requisições aos Apache dos Servidores A e B.

  • Falha 3 (Servidor A recupera-se da falha e volta ao estado de Master)
Falha 3
Clique para ampliar

O Servidor Web A tornase totalmente disponível novamente, tanto o processo balanceador de carga Nginx quando o servidor web Apache. Então o keepalived atribui novamente o IP Virtual para o Servidor A e este volta ao estado original do sistema, balanceando as requisições entre os Apache dos Servidores A e B.

4. Instalação

 4.1 Keepalived

Os seguintes comandos devem ser executados nos dois servidores membros do balanceamento de cargo, Servidores A e B.

Instalamos o keepalived pelo apt-get:


apt-get install keepalived

Após, temos que editar o sysctl.conf e ativar a opção net.ipv4.ip_nonlocal_bind para permitir que processos escutem em endereços IP ainda não atribuídos:


echo net.ipv4.ip_nonlocal_bind=1 >> /etc/sysctl.conf
sysctl -p

Daqui pra frente a configuração será dividida entre os Servidores A e B, já que possuem detalhes pertinentes a cada um.

Executaremos os seguintes comandos no Servidor A, que será denominado master no keepalived:

Criaremos o arquivo de configuração do keepalived em /etc/keepalived/keepalived.conf com o seguinte conteúdo:

vrrp_script chk_http_port {
  script "/usr/bin/killall -0 nginx"
  interval 2
  weight 2
}

vrrp_instance VI_1 {
  interface eth0
  state MASTER
  virtual_router_id 53
  priority 101     # 101 on master, 100 on backup
  authentication {
    auth_type PASS
    auth_pass som3_an0th3r_p4ss
}
track_script {
  chk_http_port
}
virtual_ipaddress {
  10.10.10.10/24 dev eth0
}

}

O que instrui o keepalived a ficar monitorando o status os processos nginx. Enquanto o processo estiver rodando, os endereços ip definidos na seção virtual_ipaddress (10.10.10.10)serão atribuídos ao servidor. Caso o processo venha a cair, ou o servidor inteiro ficar inoperante, o outro nó detectará e caso o seu processo nginx esteja de pé ele auto configurará o endereço ip virtual neste servidor, mantendo assim o serviço sempre de pé.

Os parâmetros virtual_router_ip e priority configura valores do protocolo de redundância vrrp. Deve-se setar o mesmo valor de virtual_router_id para todos
os nós participantes do balanceamento. Já o valor de priority deve ser o maior no servidor master, aquele que iniciará com o endereço ip virtual atribuído,
e valores inferiores a este nos servidores backups.

Agora configuraremos o keepalived do Servidor B.

Criaremos o arquivo de configuração do keepalived em /etc/keepalived/keepalived.conf com o seguinte conteúdo:

vrrp_script chk_http_port {
  script "/usr/bin/killall -0 nginx";
  interval 2
  weight 2
}

vrrp_instance VI_1 {
  interface eth0
  state MASTER
  virtual_router_id 53
  priority 100     # 101 on master, 100 on backup
  authentication {
    auth_type PASS
    auth_pass som3_an0th3r_p4ss
}
track_script {
  chk_http_port
}
virtual_ipaddress {
  10.10.10.10/24 dev eth0
}

}

Depois de salvos os arquivos, devemos iniciar o processo keepalived nos Servidores A e B:

service keepalived start

Após isso, mensagens da inicialização do keepalived poderão ser vistas nos logs, em especial em /var/log/messages.

Jan 18 10:50:34 servidorA Keepalived_vrrp: Registering Kernel netlink reflector
Jan 18 10:50:34 servidorA Keepalived_vrrp: Registering Kernel netlink command channel
Jan 18 10:50:34 servidorA Keepalived_vrrp: Registering gratutious ARP shared channel
Jan 18 10:50:34 servidorA Keepalived_vrrp: IPVS: Can't initialize ipvs: Protocol not available
Jan 18 10:50:34 servidorA Keepalived_vrrp: Opening file '/etc/keepalived/keepalived.conf'.
Jan 18 10:50:34 servidorA Keepalived_vrrp: Configuration is using : 62094 Bytes
Jan 18 10:50:34 servidorA Keepalived_vrrp: Using LinkWatch kernel netlink reflector...
Jan 18 10:50:34 servidorA Keepalived_healthcheckers: IPVS: Can't initialize ipvs: Protocol not available
Jan 18 10:50:34 servidorA Keepalived_healthcheckers: Registering Kernel netlink reflector
Jan 18 10:50:34 servidorA Keepalived_healthcheckers: Registering Kernel netlink command channel
Jan 18 10:50:34 servidorA Keepalived_healthcheckers: Opening file '/etc/keepalived/keepalived.conf'.
Jan 18 10:50:34 servidorA Keepalived_healthcheckers: Configuration is using : 4249 Bytes
Jan 18 10:50:34 servidorA Keepalived_healthcheckers: Using LinkWatch kernel netlink reflector...
Jan 18 10:50:35 servidorA Keepalived_vrrp: VRRP_Instance(VI_1) Transition to MASTER STATE
Jan 18 10:50:36 servidorA Keepalived_vrrp: VRRP_Instance(VI_1) Entering MASTER STATE

Verificaremos se o endereço ip virtual foi atribuído ao servidor master do balanceamento com sucesso:

ip addr show dev eth0

O que no Servidor Web A retorna:

root@servidorA:~# ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 54:52:00:07:9c:2e brd ff:ff:ff:ff:ff:ff
inet 10.10.10.1/24 brd 10.10.10.255 scope global eth0
inet 10.10.10.10/24 scope global secondary eth0
inet6 fe80::5652:ff:fe07:9c2e/64 scope link
valid_lft forever preferred_lft forever

Já oServidor Web B retorna:

root@servidorB:~# ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 54:52:00:00:00:ac brd ff:ff:ff:ff:ff:ff
inet 10.10.10.2/24 brd 10.10.10.255 scope global eth0
inet6 fe80::5652:ff:fe00:ac/64 scope link
valid_lft forever preferred_lft forever

Como podemos ver o endereço IP Virtual (VIP) está atribuído ao Servidor Web A, que é o servidor master do keepalived pois possui um valor da diretiva priority maior.

Podemos testar se o failover está realmente funcionando parando o serviço keepalived no Servidor Web A. Neste caso o Servidor Web B identificará o problema e atribuirá para si o endereço IP Virtual.

Paramos o keepalived no Servidor Web A:

service keepalived stop

Após poucos segundos, verificaremos os endereços IP atribuídos ao Servidor Web B:


root@servidorB:~# ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 54:52:00:00:00:ac brd ff:ff:ff:ff:ff:ff
inet 10.10.10.2/24 brd 10.10.10.255 scope global eth0
inet 10.10.10.10/24 scope global secondary eth0
inet6 fe80::5652:ff:fe00:ac/64 scope link
valid_lft forever preferred_lft forever

Como vimos o endereço IP foi atribuído com sucesso ao Servidor B, garantindo failover IP.

4.2 Apache

Para exemplificar, iremos hospedar um domínio no nosso sistema com as seguintes características:

Endereço DNS: www.exemplo.com
Endereço IP: 10.10.10.10

Como vemos, os nossos sites precisam resolver para o endereço IP Virtual, que por sua vez balanceará as solicitações entre os Servidores Web.

Instalaremos o Apache com apenas as configurações necessárias para o nosso sistema funcionar, opções de hardenização e segurança não serão abordadas aqui.

Os comandos desta seção devem ser executados em ambos os Servidores Web A e B:

apt-get install apache2 php5

Para que o Apache possa logar corretamente o IP do cliente que será encaminhado pelo Nginx, devemos localizar e substituir as seguintes linhas de definições de log no arquivo /etc/apache2/apache2.conf:

LogFormat "%v:%p %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" vhost_combined
LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined

por

#LogFormat "%v:%p %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" vhost_combined
LogFormat "%v %{X-Forwarded-For}i %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" vhost_combined
#LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined
LogFormat "%{X-Forwarded-For}i %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined

Agora criaremos o arquivo de definições do VirtualHost  do nosso domínio em /etc/apache2/sites-available/www.exemplo.com


<VirtualHost *:81>
DocumentRoot "/var/www"
ServerName www.exemplo.com
ErrorLog  "/var/log/apache2/www.exemplo.com.error_log"
CustomLog "/var/log/apache2/www.exemplo.com.access_log" common

</VirtualHost>

Agora ativamos o VirtualHost criado:

a2ensite www.exemplo.com

No arquivo /etc/apache2/ports.conf localizaremos as seguintes diretivas que configuram as portas nas quais o apache escuta:

NameVirtualHost *:80
Listen 80

e substiruiremos por:

NameVirtualHost *:81
Listen 81

O mesmo deve ser feito para o arquivo do VirtualHost padrão, caso este esteja ativado, localizado em /etc/apache2/sites-available/default:

Substituir:

<VirtualHost *:80>

por:

<VirtualHost *:81>

Conferimos se há algum erro de configuração:


root@servidorA:~# apache2ctl configtest
Syntax OK

Agora reiniciamos o Apache:


apache2ctl restart

4.3 Nginx

Os comandos desta seção devem ser executados em ambos os Servidores Web A e B:

Instalamos o pacote via apt-get:

apt-get install nginx

Utilizaremos o seguinte arquivo de configuração /etc/nginx/nginx.conf:

user www-data;
worker_processes  2;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
 worker_connections  1024;
 use epoll;
 # multi_accept on;
 }

http {
 include       /etc/nginx/mime.types;
 default_type application/octet-stream;
 access_log  /var/log/nginx/access.log;
 gzip_disable "MSIE [1-6].(?!.*SV1)";

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
server_names_hash_bucket_size 33;
}

Em seguida criaremos o arquivo /etc/nginx/conf.d/options.conf:

# Size Limits
client_body_buffer_size         128K;
client_header_buffer_size       128K;
client_max_body_size            50M;    # php's upload_max_filesize
large_client_header_buffers     8 8k;
proxy_buffers                   8 16k;
proxy_buffer_size               32k;

# Timeouts
client_body_timeout             60;
client_header_timeout           60;
expires                         off;
keepalive_timeout               60 60;
send_timeout                    60;

# General Options
ignore_invalid_headers          on;
keepalive_requests              100;
limit_zone gulag $binary_remote_addr 5m;
recursive_error_pages           on;
sendfile                        on;
server_name_in_redirect         off;
server_tokens                   off;

# TCP options
tcp_nodelay                     on;
tcp_nopush                      on;

# Compression
gzip                            on;
gzip_buffers                    16 8k;
gzip_comp_level                 6;
gzip_http_version               1.0;
gzip_min_length                 0;
gzip_types                      text/plain text/css image/x-icon application/x-perl application/x-httpd-cgi;
gzip_vary                       on;

# Log Format
log_format                      main    '$remote_addr $host $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'"$gzip_ratio"';
proxy_cache_path                /var/cache/nginx/ levels=1:2 keys_zone=cache:100m inactive=1h max_size=1024M;

Na sequência criaremos o arquivo /etc/nginx/conf.d/proxy.conf:

proxy_cache_valid     1h; # 200, 301 and 302 will be cached.
proxy_cache_use_stale error
    timeout
    invalid_header
    http_500
    http_502
    http_504
    http_404;

proxy_buffering           on;
proxy_cache_min_uses       3;
proxy_ignore_client_abort off;
proxy_intercept_errors    on;
proxy_next_upstream       error timeout invalid_header;
proxy_redirect            off;
proxy_set_header          X-Forwarded-For $remote_addr;
proxy_connect_timeout     600;
proxy_send_timeout        600;
proxy_read_timeout        600;
proxy_ignore_headers      Expires Cache-Control;
proxy_cache_key          "$scheme$host$uri$is_args$args";

E o arquivo /etc/nginx/conf.d/upstream.conf:

upstream lb {
    server 10.10.10.1:81 max_fails=10 fail_timeout=300s;  # Web Server A
    server 10.10.10.2:81 max_fails=10 fail_timeout=300s;  # Web Server B
}

Crie o  arquivo de definições do domínio no Nginx /etc/nginx/sites-available/www.exemplo.com com o seguinte conteúdo:

server {
  listen   10.10.10.10:80;
  server_name  www.exemplo.com;
  access_log  /var/log/nginx/www.exemplo.com.access.log;

  location / {
    proxy_pass              http://lb;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

Agora, uma explicação mais detalhada dos principais parâmetros setados nestes arquivos de configuração:

  • user www-data; Define com qual usuário o processo nginx será iniciado.
  • worker_processes 2; Define o número de threads do nginx. Geralmente seta-se este valor com o número de núcleos da CPU do servidor. É sempre importante manipular esse valor fazendo um benchmark de conexões.
  • worker_connections 1024; Número máximo de conexões por worker. Para saber o número de conexões do servidor multiplique: max_clients = worker_processes * worker_connections
  • proxy_cache_valid 1h; Período em que um hit pode ficar no cache. Caso um objeto entre no cache, ele passará 1h marcado como válido, e o servidor o usará quando tal objeto for requisitado. Após expirado esse tempo o objeto sai do cache e novas requisições por ele serão realizadas diretamente aos servidores backend.
  • upstream lb; Define um conjunto de servidores e portas para onde serão encaminhadas as requisições que não estiverem em cache. O nginx balanceará as requisições de forma equalitária entre os servidores usando o método round-robin.
  • listen 10.10.10.10:80; Endereço e porta onde o Nginx escutará por conexões.
  • server_name www.exemplo.com; Endereço DNS do VirtualHost.
  • proxy_pass http://lb; Encaminha as requisições ao upstream lb definido no arquivo upstream.conf.

Agora criamos o link que ativa o VirtualHost para o diretório /etc/nginx/sites-enabled:

cd /etc/nginx/sites-enabled/
ln -s ../sites-available/www.exemplo.com.br

Criamos também o diretório onde será armazenado o cache do Nginx e setamos suas permissões:

mkdir /var/cache/nginx/
chown -R www-data:www-data /var/cache/nginx/

Vamos testar a configuração do Nginx antes de inicia-lo, caso exista algum erro nos arquivos de configuração este teste acusará:

root@servidorA:~# nginx -t
the configuration file /etc/nginx/nginx.conf syntax is ok
configuration file /etc/nginx/nginx.conf test is successful

Agora, podemos iniciar o serviço Nginx nos Servidores A e B:

service nginx start

Agora temos o nosso cluster de alta disponibilidade parcialmente completo, mas já podemos testar o FailOver IP e a distribuição das conexões entre os backends.

Criaremos um arquivo dentro do diretório /var/www de cada servidor com o hostname do servidor:

root@servidorA:~# hostname -f >> /var/www/index.html

Agora iremos abrir no Navegador o endereço do site que acabamos de configurar e pressionando o botão Atualizar por várias vezes, poderemos ver que ele o sistema está balanceando as conexões entre os dois servidores web.

4.4 Repcached

Utilizaremos o repcached para replicar as sessões dos sistemas que rodarão nos dois servidores, assim usuários logados em um servidor não perderão suas sessões quando balanceados para o outro servidor.

Os comandos a seguir devem ser executados em ambos Servidores A e B.

Temos que editar os seguintes valores na seção Session do arquivo /etc/php5/apache2/php.ini:

[Session]
 session.save_handler = memcache
 session.save_path = "tcp://10.10.10.1:11211, tcp://10.10.10.2:11211"

Assim dizemos ao php para gravar as seções no memcache, e informamos os endereços IP dos servidores que farão parte da replicação.

Editamos também o arquivo /etc/php5/conf.d/memcache.ini:


memcache.maxratio=0
memcache.allow_failover=1

Agora procedemos com a instalação do memcached:

apt-get install memcached

Após isso devemos baixar o código-fonte do repcached, compilar e instala-lo no servidor. Infelizmente o repcached ainda não é empacotado no repositório oficial do Debian.


cd /usr/src/
wget "http://ufpr.dl.sourceforge.net/project/repcached/repcached/2.2-1.2.8/memcached-1.2.8-repcached-2.2.tar.gz"
tar zxvf  memcached-1.2.8-repcached-2.2.tar.gz
cd memcached-1.2.8-repcached-2.2
./configure --enable-replication
make
make install

No Servidor A usaremos o seguinte arquivo de configuração /etc/repcached.conf:

# repcached config file
# 2011 - jean caffou

# Run repcached as a daemon. This command is implied, and is not needed for the
# daemon to run. See the README.Debian that comes with this package for more
# information.
-d

# Log repcached's output to /var/log/repcached
logfile /var/log/repcached.log

# Be verbose
# -v

# Be even more verbose (print client commands as well)
# -vv

# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
# Note that the daemon will grow to this size, but does not start out holding this much
# memory
-m 64

# Default connection port is 11211
-p 11211

# Run the daemon as root. The start-repcached will default to running as root if no
# -u command is present in this config file
-u nobody

# Specify which IP address to listen on. The default is to listen on all IP addresses
# This parameter is one of the only security measures that repcached has, so make sure
# it's listening on a firewalled interface.
# -l 127.0.0.1

# Limit the number of simultaneous incoming connections. The daemon default is 1024
# -c 1024

# Lock down all paged memory. Consult with the README and homepage before you do this
# -k

# Return error when memory is exhausted (rather than removing items)
# -M

# Maximize core file limit
# -r

# Port for server replication. Default is 11212
-X 11212

# IP for repcached peer server
-x 10.10.10.2

E o seguinte no Servidor B:

# repcached config file
# 2011 - jean caffou

# Run repcached as a daemon. This command is implied, and is not needed for the
# daemon to run. See the README.Debian that comes with this package for more
# information.
-d

# Log repcached's output to /var/log/repcached
logfile /var/log/repcached.log

# Be verbose
# -v

# Be even more verbose (print client commands as well)
# -vv

# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
# Note that the daemon will grow to this size, but does not start out holding this much
# memory
-m 64

# Default connection port is 11211
-p 11211

# Run the daemon as root. The start-repcached will default to running as root if no
# -u command is present in this config file
-u nobody

# Specify which IP address to listen on. The default is to listen on all IP addresses
# This parameter is one of the only security measures that repcached has, so make sure
# it's listening on a firewalled interface.
# -l 127.0.0.1

# Limit the number of simultaneous incoming connections. The daemon default is 1024
# -c 1024

# Lock down all paged memory. Consult with the README and homepage before you do this
# -k

# Return error when memory is exhausted (rather than removing items)
# -M

# Maximize core file limit
# -r

# Port for server replication. Default is 11212
-X 11212

# IP for repcached peer server
-x 10.10.10.1

Como podemos observar no final destes arquivos é informado o endereço IP do outro nó que vai rodar o repcached. Assim o Servidor A informa no seu arquivo de configuração o endereço IP do Servidor e vice-versa. Aproveitaremos alguns arquivos da instalação do memcached para usarmos no repcached:

cp /etc/default/memcached /etc/default/repcached

Em seguida editamos o arquivo /etc/default/repcached e alteramos a seguinte linha de:

ENABLE_MEMCACHED=no

para

ENABLE_REPCACHED=YES

E desativamos a inicialização do memcached editando o arquivo /etc/default/memcached de:

ENABLE_MEMCACHED=YES

para

ENABLE_MEMCACHED=no

Utilizaremos o seguinte script rc para o repcached, localizado em /etc/init.d/repcached:

#! /bin/sh
### BEGIN INIT INFO
# Provides:             repcached
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Should-Start:         $local_fs
# Should-Stop:          $local_fs
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Start repcached daemon
# Description:          Start up repcached, a high-performance memory caching daemon with replication
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/bin/memcached
DAEMONBOOTSTRAP=/usr/share/memcached/scripts/start-repcached
NAME=repcached
DESC=repcached
PIDFILE=/var/run/$NAME.pid

test -x $DAEMON || exit 0
test -x $DAEMONBOOTSTRAP || exit 0

set -e

. /lib/lsb/init-functions

# Edit /etc/default/repcached to change this.
ENABLE_REPCACHED=no
test -r /etc/default/repcached && . /etc/default/repcached

case "$1" in
  start)
        echo -n "Starting $DESC: "
  if [ $ENABLE_REPCACHED = yes ]; then
        start-stop-daemon --start --quiet --exec $DAEMONBOOTSTRAP
        echo "$NAME."
        else
                echo "$NAME disabled in /etc/default/repcached."
        fi
        ;;
  stop)
        echo -n "Stopping $DESC: "
        start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON
        echo "$NAME."
        rm -f $PIDFILE
        ;;

  restart|force-reload)
        #
        #       If the "reload" option is implemented, move the "force-reload"
        #       option to the "reload" entry above. If not, "force-reload" is
        #       just the same as "restart".
        #
        echo -n "Restarting $DESC: "
        start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
        rm -f $PIDFILE
        sleep 1
        start-stop-daemon --start --quiet --exec $DAEMONBOOTSTRAP
        echo "$NAME."
        ;;
  status)
        status_of_proc $DAEMON $NAME
        ;;
  *)
        N=/etc/init.d/$NAME
        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
        exit 1
        ;;
esac

exit 0

Substituiremos também o arquivo /usr/share/memcached/scripts/start-repcached pelo o seguinte:


#!/usr/bin/perl -w

# start-repcached
# 2011 - Jean Caffou <jean@briskula.si>
# This script handles the parsing of the /etc/repcached.conf file
# and was originally created for the Debian distribution.
# Anyone may use this little script under the same terms as
# memcached itself.

use strict;

if($> != 0 and $< != 0)
{
    print STDERR "Only root wants to run start-repcached.n";
    exit;
}

my $params; my $etchandle; my $etcfile = "/etc/repcached.conf";

# This script assumes that repcached is located at /usr/local/bin/memcached, and
# that the pidfile is writable at /var/run/repcached.pid

my $memcached = "/usr/local/bin/memcached";
my $pidfile = "/var/run/repcached.pid";

# If we don't get a valid logfile parameter in the /etc/repcached.conf file,
# we'll just throw away all of our in-daemon output.
my $fd_reopened = "/dev/null";

sub handle_logfile
{
    my ($logfile) = @_;
    $fd_reopened = $logfile;
}

sub reopen_logfile
{
    my ($logfile) = @_;

    open *STDERR, ">>$logfile";
    open *STDOUT, ">>$logfile";
    open *STDIN, ">>/dev/null";
    $fd_reopened = $logfile;
}

# This is set up in place here to support other non -[a-z] directives

my $conf_directives = {
    "logfile" => &handle_logfile,
};

if(open $etchandle, $etcfile)
{
    foreach my $line (<$etchandle>)
    {
        $line ||= "";
        $line =~ s/#.*//g;
        $line =~ s/s+$//g;
        $line =~ s/^s+//g;
        next unless $line;
        next if $line =~ /^-[dh]/;

        if($line =~ /^[^-]/)
        {
            my ($directive, $arg) = $line =~ /^(.*?)s+(.*)/;
            $conf_directives->{$directive}->($arg);
            next;
        }

        push @$params, $line;
    }

}else{
    $params = [];
}

push @$params, "-u root" unless(grep "-u", @$params);
$params = join " ", @$params;

if(-e $pidfile)
{
    open PIDHANDLE, "$pidfile";
    my $localpid = <PIDHANDLE>;
    close PIDHANDLE;

    chomp $localpid;
    if(-d "/proc/$localpid")
    {
        print STDERR "repcached is already running.n";
        exit;
    }else{
        `rm -f $localpid`;
    }

}

my $pid = fork();

if($pid == 0)
{
    reopen_logfile($fd_reopened);
    exec "$memcached $params";
    exit(0);

}else{
    if(open PIDHANDLE,">$pidfile")
    {
        print PIDHANDLE $pid;
        close PIDHANDLE;
    }else{

        print STDERR "Can't write pidfile to $pidfile.n";
    }
}

Em seguida configuramos a inicialização do repcached durante o boot do sistema:


chmod +x /etc/init.d/repcached
update-rc.d repcached defaults

Iniciamos o repcached:

service repcached start

Agora vamos fazer um teste para estarmos seguros de que a replicação de sessão esteja funcionando corretamente:

Primeiro, no Servidor A iremos escrever uma chave chamada “mykey” com o valor “12345” e recuperaremos seu valor no Servidor B:

[user@host ~]$telnet 10.10.10.1 11211
Trying 10.10.10.1...
Connected to 10.10.10.1
Escape character is '^]'.
set mykey 1 600 5
12345
STORED
get mykey
VALUE mykey 1 5
12345
END

Após isso, a chave deve ter sido replicada para o Servidor B, onde recuperaremos seu valor:

[user@host ~]$telnet 10.10.10.2 11112
Trying 10.10.10.2...
Connected to 10.10.10.2
Escape character is '^]'.
get mykey
VALUE mykey 1 5
12345
END

Como vimos o valor do objeto mykey foi setado no Servidor A, replicado e recuperado no Servidor B. Agora temos nossa configuração do repcached funcionando perfeitamente.

4.5 Rsync Mirror

Iremos configurar o rsync para sincronizar o conteúdo do diretório /var/www do Servidor A para o Servidor B, assim deveremos copiar os arquivos para os sites no Servidor A que automaticamente os copiará para o Servidor B. Portanto serviços como FTP, usados para atualizar os dados dos websites servidos, devem ser instalados no Servidor A.

apt-get install rsync

Em seguida criaremos um usuário rsync nos dois hosts e configuraremos autenticação por chaves RSA no ssh, para não haver a pergunta de senha a cada vez que o comando for executado:

No Servidor B:


root@servidorB:~# useradd rsync -c "Rsync User" -d /var/www/ -s /bin/false
root@servidorB:~# mkdir ~rsync/.ssh
root@servidorB:~# chown rsync:root /var/www /var/www/.ssh
root@servidorB:~# chmod 0755 /var/www
root@servidorB:~# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
6f:5b:a1:6b:e9:f6:78:4e:a1:9f:f4:86:79:73:cd:46 root@marina
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|                 |
|                 |
|                 |
|        S   o    |
|         . o o  E|
|          =.+o o.|
|         .+O+o+ =|
|         +=+=o.+ |
+-----------------+

No Servidor A:

root@servidorA:~# useradd rsync -c "Rsync User" -d /var/www/ -s /bin/bash
root@servidorA:~# chown rsync:root /var/www
root@servidorA:~# chmod 0755 /var/www

Agora copiamos o conteúdo da chave recém-criada do Servidor B para o Servidor A conforme os passos abaixo:

Servidor B:


root@servidorB:~# scp /root/.ssh/id_rsa.pub root@10.10.10.1:~rsync/.ssh/authorized_keys

E setamos as devidas permissões dos arquivos criados no Servidor A:


root@servidorA:~# chown -R rsync:rsync ~/rsync/.ssh/
root@servidorA:~# chmod 0600 ~/rsync/.ssh/authorized_keys

Agora criaremos um diretório no Servidor A, e em seguida executaremos o rsync no Servidor B, que deverá sincronizar os dois diretórios:

Servidor A:


root@servidorA:~# mkdir /var/www/rsync-test

Servidor B:


root@servidorB:~# rsync -av --delete rsync@10.10.10.1/var/www/ /var/www/

Se o rsync copiou o diretório criado no Servidor A para o Servidor B, nossa configuração está correta. Basta apenas adicionar o comando ao cron para ser executado de minuto a minuto para manter os diretórios o mais sincronizados possíveis.

No Servidor A abrimos o arquivo crontab do usuário root com o seguinte comando:


crontab -e

E adicionamos a seguinte linha:


# rsync
*/1 * * * * rsync -a --delete rsync@10.10.10.1:/var/www/ /var/www/

A configuração da sincronização do Servidor A para o Servidor B foi concluída e será executada uma vez a cada minuto.

4.6 Suporte SSL

Se desejarmos utilizar https nos sites hospedados em nosso cluster de alta disponibilidade, deveremos configurar os certificados digitais no Nginx. Já o Apache deve servir os sites em texto plano como mostra a figura abaixo:

Cenário HTTPS
Clique para ampliar

Vamos adicionar suporte SSL ao VirtualHost www.exemplo.com que já foi configurado no Nginx. Vamos assumir que já temos os certificados SSL em mãos, porém se você precisar de um certificado SSL válido e gratuito recomendo o htttp://www.startssl.com que possui suporte na maioria dos navegadores e possui uma versão gratuita de certificados válidos.

Usaremos o seguinte arquivo /etc/nginx/sites-available/www.exemplo.com:

server {
listen          10.10.10.10:443;
server_name     www.exemplo.com;

access_log      /var/log/nginx/www.exemplo.com.access.log;
error_log       /var/log/nginx/www.exemplo.com.error.log;

ssl on;
ssl_certificate      /etc/apache2/ssl.crt/www.exemplo.com.crt;
ssl_certificate_key  /etc/apache2/ssl.key/www.exemplo.com.key;

ssl_session_cache  shared:SSL:10m;
ssl_session_timeout  5m;
ssl_protocols  TLSv1;
ssl_ciphers HIGH:!ADH:!MD5;
ssl_prefer_server_ciphers   on;
keepalive_timeout    60;

location / {
proxy_pass              http://lb;
proxy_set_header        Host $host;
proxy_set_header        X-Real-IP $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

4.7 Ativando o Cache do Nginx

A opção de cache do Nginx deve ser usada com cuidado, principalmente quando estamos servindo conteúdo dinâmico ou páginas de sistemas.

Uma boa aplicação do cache seria em páginas estáticas muito acessadas, por exemplo resultados de concursos, páginas iniciais de instituições entre outras aplicações.

Por exemplo, queremos ativar o cache da página www.exemplo.com/fotos/

Iremos alterar o arquivo de configuração /etc/nginx/sites-enabled/www.exemplo.com:

location /fotos/  {
 proxy_pass              http://lb;
 proxy_set_header        Host $host;
 proxy_set_header        X-Real-IP $remote_addr;
 proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
 # cache settings
 include                 /etc/nginx/conf.d/proxy.conf;
 proxy_cache             cache;
}

As principais diretivas que controlam como o cache funciona são:

  • proxy_cache_min_uses 3; Define o número de hits necessários para uma página entrar em cache. Neste exemplo caso a página configurada seja acessada por 3 vezes ela será inclusa no cache do Nginx por um período de tempo definido na diretiva proxy_cache_valid, e durante este intervalo o servidor Apache não será consultado para esta página.
  • proxy_cache_key          “$scheme$host$uri$is_args$args”; Define a chave que será utilizada para os objetos em cache. No exemplo utilizamos a URL completa com argumentos, mas poderíamos setar cookies nas aplicações para que determinadas páginas não entrassem no cache;
  • proxy_cache_valid     1h; Período de tempo em que uma página pode permanecer no cache. Após expirado este tempo, a página volta a ser consultada nos servidores back end e após proxy_cache_min_uses vezes ela voltará ao cache.

Para mais informações à respeito das diretivas do Nginx recomendo a leitura da documentação on-line no Wiki do projeto em: http://wiki.nginx.org/Modules.

6 Replies to “Implementando um sistema balançeador de carga HTTP com alta disponibilidade utilizando Nginx como proxy reverso, sincronização rsync, compartilhamento de sessões PHP e SSL”

  1. Prezado Pedro, obrigado por sua contribuição, principalmente para mim foi de grande valia. Mais uma vez, obrigado por compartilhar, ter “gastado” tempo e ter empenhado-se para desenvolver um artigo de qualidade com este.

    Que Deus te abençoe amigo.

    Abraços

  2. Opa! Pedro!
    Muito interessante sua implementação, a parte do telnet para testar o Repcached ficou meio obscura pra mim. Não seria para gerar a chave na porta 11211 em ambos os servidores?

  3. Pedro, a princípio gostaria de agradecer pela consideração com os profissionais da área publicando completo e detalhado de um assunto extremamente importante para quem trabalha com serviços críticos. Bom, estou configurando um ambiente com todos os detalhes da sua publicação e estou com um probleminha na parte da replicação das sessões. Realizei toda a instalação e configuração do repcached sem nenhum erro de compilação e instalação do pacote. O serviço inicia sem nenhum problema só que quando crio a variável no servidor A a mesma não é replicada para o servidor B como mostrado no seu tutorial.
    Listando os processos é mostrado o seguinte processo nos dois servidores:
    nobody /usr/local/bin/memcached -, 64 -p 11211 -u nobody -X 11212 -x 10.10.10.1 (servidor A)
    A replicação é imediata?
    Pode me ajudar , falta pouco.
    Adredeço antecipadamente pela força.
    Abraços.

  4. Pessoal, andei meio sem tempo para ver o site e só agora vim ver os comentários. Nos próximos dias eu respondo as perguntas, agora já são quase 2:30 da manhã. rs. Obrigado pelo feedback.

  5. Olá,

    Gostaria de criar um blog utilizando o word press, não entendo como usar o apache e gnix. se pude me ajudar, agradeço

    Abraço

Leave a Reply

Your email address will not be published. Required fields are marked *