OpenShift e o Desafio dos Containers

Apresentando o Container

O primeiro ponto que precisamos entender é o tema Container. Podemos dizer que um Container é um isolamento de aplicações dentro de um sistema operacional, onde é possível individualizar diversas aplicações em uma mesma infraestrutura física compartilhando acesso internet, disco, processamento e memória. Ao usarmos Container conseguimos prover limites de isolamento para cada aplicação, ampliando não só a segurança do ambiente, mas também o desempenho das aplicações. Entretanto, você pode questionar: Denis, mas isto é o conceito de virtualização como eu já conheço! A resposta é um grande e sonoro “não”. Vamos aprofundar o tema para deixar mais claro, pois existem diferenças muito importantes para o nosso estudo.

Primeiro precisamos entender os motivos que estão fazendo com que o uso de Containers seja cada vez mais popular. Tenha em mente que quando fazemos uso da virtualização tradicional usamos um software chamado virtualizador (hypervisor), que é responsável pelo compartilhamento do hardware físico (memória, disco, processador, placa de rede, porta USB, etc) entre diversos sistemas operacionais (Guest OS/Máquina Virtual). Cada sistema operacional pode ser distinto (Windows, Linux, FreeBSD, etc), necessitando apenas manter a compatibilidade com a plataforma física selecionada que suporta este ambiente computacional.

Para compreender mais profundamente, considere o uso de uma máquina virtual, onde colocamos diversas aplicações. Imagine o que pode acontecer se uma aplicação for invadida, por exemplo? Neste cenário, todo o ambiente pode ser comprometido ou ainda o que aconteceria se uma aplicação apresentar um problema de desenvolvimento e consumir grande parte da memória deste servidor? Possivelmente teremos todas as outras aplicações prejudicadas.

Pensando ainda na maneira tradicional, como solucionaríamos este problema? Basta criar um ambiente com servidores redundantes contendo o mesmo virtualizador e configurar nele várias máquinas virtuais (VMs) separando as aplicações. Claro que o impacto negativo deste modelo é guiado principalmente pelo custo, principalmente se desenvolvermos um projeto onde o uso de sistemas operacionais exige licenciamento. Para materializar o que foi dito até o momento, observe o gráfico seguinte:

tabela

 

Note como é diferente o desenho de aplicações inseridas em VMs e aplicações projetadas para ambientes com Containers.  Se continuarmos a olhar para o tema “custo”, perceberemos que licenciamos apenas um sistema operacional e configuramos diversas aplicações individualizadas em quantos Containers forem necessários ou suportados pelo hardware usado. Vamos ampliar os horizontes este conceito para o ambiente de Cloud Pública ou Cloud Privada. Ao fazermos isto, perceberemos que é cada vez mais natural o uso de duas tecnologias importantes: Docker (https://www.docker.com/) e Kubernetes (http://kubernetes.io/).  Vamos estudá-los um pouco e identificar quais benefícios podemos encontrar neles.

 

Entendendo Docker, Kubernetes e OpenShift

Ponto 1: Docker é uma plataforma onde um Container é tratado como uma “imagem” contendo todos os elementos para o funcionamento de uma aplicação. Assim a ideia de compartilhar e colaborar muito usada por uma equipe de desenvolvedores é facilmente mantida.

Ponto 2: Docker consegue controlar atualizações, mapear as mudanças ou controlar de maneira automática todo o ciclo de vida de uma aplicação contida em uma imagem. Neste cenário não existe a preocupação em depositar uma aplicação em um ambiente de homologação e sofrer com problemas de funcionamento ao migrar para o ambiente de produção. O segredo é que tudo que é necessário para a aplicação funcionar está dentro do Container.

Ponto 3: E o que podemos falar a respeito do Kubernetes? É importante termos em mente que é inviável controlar manualmente o ciclo de vida de uma quantidade elevada de Containers devido a diversos fatores, dentre eles podemos citar a ordem com que eles são iniciados, por exemplo. É neste tema que entra o Kubernetes. O Google faz uso dele há mais de 15 anos para controlar as cargas de trabalhos (workloads) existentes em seus Data Centers. Desta forma podemos encontrar nele elementos como:

  • Controle da saúde de cada aplicação durante o processo de deploy, facilitando a identificação de problemas, possibilitando a reinicialização do Container que falhar, substituindo ou até reagendando um Container quando uma máquina virtual falhar;
  • Presença de Load Balance (Balanceador de Carga) para controlar o fluxo de acesso a aplicação duplicada;
  • Crescimento ou demolição automática da quantidade de réplicas de uma aplicação. Isto é feito com o controle da quantidade de Containers seguindo as regras previamente estabelecidas ou interagindo diretamente via linha de comando.
  • Crescimento horizontal da aplicação usando Containers;
  • Execução de tarefas usando arquivos em lotes para otimizar e automatizar atividades de maneira sequencial que normalmente seriam feitas por técnicos ou programadores (Batch).

Imagine se uníssemos em uma única plataforma todas as funcionalidades e benefícios do Docker com o Kubernetes. Claro que seria algo fantástico, mas será que realmente é possível? Tenho uma ótima notícia para você, esta pergunta já foi respondida com um grande “sim” pela Red Hat com a criação do OpenShift em 2013. O OpenShift posiciona-se como um PaaS (Plataforma como Serviço/Platform as a Service) permitindo simplificar o desenvolvimento, acrescentando ganho de escala nas aplicações em diversos tipos de ambientes e deixando mais simples o armazenamento de aplicações.

Para o UOLDIVEO introduzir o OpenShift em nossas atividades foi um caminho natural trilhado pela parceria de longa data com a Red Hat. Nossa experiência nos possibilita entender a necessidade de dinamismo em um mundo que segue com força para a transformação digital e nos capacita a desenvolver diversos modelos de projetos envolvendo OpenShift.

Esta parceria proporciona ganhos para os nossos clientes, pois permite um acesso direto ao suporte especializado e diferenciado, juntamente com treinamentos e certificações para nossa equipe, garantindo acesso às novidades e lançamentos da Red Hat com bastante antecedência.

 

Denis Souza

 

Links indicados:

 

Boas Práticas com Docker

No post sobre DevOps foram citadas inúmeras ferramentas que facilitam e permitem cultivar uma cultura DevOps. Minha escolha para iniciar esta jornada foi o Docker. Em vez de convencer que o Docker é bom ou explicar como instalar e subir o primeiro container, vou focar em questões da nossa experiência com o Docker. Questões simples mas importantes acabam passando desapercebidas e geram problemas em produção.

Se você está iniciando em Docker, recomendo começar pela documentação oficial, que melhorou bastante nos últimos tempos. Se você ainda está em dúvida para onde as coisas caminham, dê uma olhada neste gráfico. Containers já são uma realidade.

Entendendo Problemas de Consistência

Com todo o hype em torno de containers, é natural que existam sentimentos do tipo “precisamos usar Docker porque todo mundo está usando” ou “vamos usar Docker porque é legal”. Docker, como qualquer ferramenta, tem seus principais valores. E no caso, posso resumir o valor do Docker em uma palavra: consistência. É importante entender o que isso significa para implantar a ferramenta de forma adequada em seu ciclo produtivo.

Pré-Puppet (ou Chef, Ansible…)

Vamos supor que você precisa subir uma aplicação nova. Máquina provisionada, SO instalado, você loga na máquina e:

  • Atualiza o sistema operacional.
  • Instala os pacotes da sua aplicação (apt, yum, zip (!!) etc).
  • Edita os arquivos de configuração.
  • Coloca o serviço no boot.
  • Reboot.

Este fluxo representa a maioria dos casos, e é um bom exemplo.

Agora solicitaram subir mais máquinas idênticas… será tedioso.

Mecanizando o Processo

De posse de ferramentas de gestão de configuração, você escreve o código que mecaniza todo o processo. Maravilha! Máquinas novas provisionadas para produção em segundos!

Porém, saem novas versões da aplicação o tempo todo, problemas aparecem em produção e a únia solução é “formatar” a máquina…

Gerenciando Alterações

Vamos entender onde o problema ocorre. Você recebe a máquina M1, apenas com o SO (BareMetal):

M1 > BareMetal

Então, aplica a sua automação, v1, em cima:

M1 > BareMetal > v1

E leva a máquina para produção, na versão v1. Mas claro, vem a v2 da automação, e você aplica:

M1 > BareMetal > v1 > v2

Agora você adiciona uma nova máquina M2, para suportar a v3:

M1 > BareMetal > v1 > v2 > v3

M2 > BareMetal > v3

E de repente, a M1 funciona, mas a M2 não! Claramente, M1 foi submetida a um fluxo (v1 > v2 > v3) totalmente diferente da M2 (v3), e fatalmente o estado real e final da M2 não é o mesmo da M1, mesmo que ambas foram submetidas à mesma receita v3. Uma causa comum por exemplo, é que a v2 instala uma dependência, que a v3 não pede.

Para resolver o problema, só começando do zero.

Entregando Com Consistência

Achado o bug, criamos uma v4, e entregamos em uma máquina M3:

M3 > v4

Isso funciona porque o fluxo é mais consistente. Pra fechar a questão, matamos as máquinas M1 e M2, e provisionamos as máquinas M4 e M5, no mesmo fluxo da M3:

M4 > v4

M5 > v4

Maravilha! Um mês depois, você provisiona a M6, para suportar a carga crescente:

M6 > v4

E novamente, problemas! Como pode? M4 e M5 foram submetidas ao mesmo fluxo do M6! Fatalmente é algum fator externo, como por exemplo updates do SO que estão na receita. Alguma versão mais nova de dependência que está presente somente na M6 está gerando problemas.

“Que maravilha!”, você pensa. Agora vamos à estaca zero para versionar TODOS os pacotes do sistema operacional, repositórios, etc…. não vai ter fim.

Containers Para a Salvação!

Bem, é exatamente todo esse stress que containers evitam. Fazendo uma analogia, containers são como um snapshot de uma VM em um estado confiável. Este snapshot pode ser utilizado para instanciar quantas VMs forem preciso, de forma confiável. Na terminologia do Docker, os snapshots se chamam imagens e as VMs containers:

Dockerfile > Image > Containers

Uma vez com a imagem pronta, você pode instanciar quantos containers forem precisos, onde for preciso, rapidamente e com confiança!

Além do Ambiente de Produção

 

Um container é algo tão leve e rápido, que consegue ser utilizado na máquina do desenvolvedor e também no servidor em produção, com fidelidade altíssima entre os ambientes. Além disso, o Docker Registry resolve o problema de se compartilhar imagens. Imaginem que eu como desenvolvedor preciso fazer o QA do meu sistema, que tem dependência de um sistema terceiro. Eu posso simplesmente utilizar a imagem do container de produção deste sistema! Nada mais de “o QA está fora”, ou “o orçamento para máquinas de QA está alto”.

 

Docker traz assertividade, confiabilidade e redução de custos, levando para o início do ciclo de desenvolvimento a mesma tecnologia e performance que existem no ambiente de produção.

Pontos de Atenção Com Docker

Criar um Dockerfile e subir o primeiro container é quase trivial. Entretanto,alguns pontos importantes, triviais de se implementar, muitas vezes são esquecidos. Em especial, no Docker Hub, há imagens prontas para praticamente qualquer coisa. Entretanto, a maturidade e qualidade delas varia grotescamente. Olhe as imagens e valide se os Dockerfiles seguem bons princípios. Somente então as utilize.

 

Alguns dos pontos levantados aqui são parte do http://12factor.net/, vale a leitura complementar.

Não Executar Como root

Um dos anti-patterns mais comuns com o Docker é executar os processos do container como root. Há uma certa argumentação válida de que a kernel já garante isolamento entre containers, mesmo eles sendo executados como root. Vou dar crédito para isso. Porém, há uma série de abusos que podem ser feitos dentro do container, como sobrescrever binários, arquivos de configuração etc… Pense assim: para quê rodar como root? O único caso que consigo pensar é executar um container privilegiado, que por si só é algo tão fora do padrão, que pode ser até considerado um anti-pattern.

Logs Fora do Container

Apesar de não haver nada proibindo você de gravar logs dentro do container, isso fatalmente irá te causar problemas. Na próxima atualização, quando for entregue um novo container, TODO o conteúdo dos logs antigos será perdido. As opções são:

  • Mapear o diretório de logs para um volume que será persistente, e anexado a novas versões do container.
  • Enviar os logs para outra ferramenta externa (ex: Elastic Search, Splunk).
  • Enviar logs para o STDOUT / STDERR.

Este último é a boa prática com Docker, utilize se possível. É trivial configurar o Docker Engine para que os logs de TODOS os containers sejam redirecionados para algum local externo. Cuidado com esta configuração e garanta que os logs estão sendo devidamente rotacionados, e os antigos apagados.

Volumes de Dados

Pelo mesmo motivo dos logs, dados importantes persistidos pela aplicação devem sair do container (ex: /var/lib/mysql). Caso contrário serão perdidos quando o container for destruído.

 

Este ponto é melhor compreendido se olharmos para uma arquitetura completa:

Web Tier > App Tier > BD

Apenas a camada de BD persiste dados, logo deve ser a única elegível para utilizar volume de dados (sujeito a backup). Pense nas demais camadas como “imutáveis”: os containers nela devem poder ser destruídos sem qualquer tipo de prejuízo, a qualquer momento. Isso facilita bastante a administração e acaba sendo um requisito importante se você está pensando em qualquer tipo de auto-scaling.

Configurações Através de Variáveis de Ambiente

Outro anti-pattern comum é colocar um grande volume de configurações no container, em arquivos. Não há nada de errado nisso, mas você tem dois pontos a considerar:

  • O que muda do container de produção para o de QA?
  • A mesma imagem pode ser utilizada para criar containers em ambientes distintos (eg: Apache como proxy para 2 sistemas)?

O que for identificado como necessário para que a mesma imagem atenda mais de um cenário deve ser informado via variáveis de ambiente na criação do container (ver –env)! O seu ENTRYPOINT deve ser responsável por interpretar estas variáveis e fazer os ajustes necessários. Uma opção comum é colocar no ENTRYPOINT um shell script que lê as variáveis de ambiente (tipicamente usuário e senha), gera o arquivo de configuração adequado e depois executa o processo do container.

 

Permitir “reciclar” a mesma imagem em mais de um cenário tem sempre que estar em mente. Minimamente produção e QA acabarão existindo. Tome cuidado para não abusar deste modelo. Uma coisa é colocar os certificados HTTPS em variável (um pattern que já vi algumas vezes) e outra é colocar TODO o httpd.conf do Apache em uma variável de ambiente. Ambos funcionam, mas claramente há um pouco de bom senso nisso. A regra do dia a dia é: se é algo que muda entre cada ambiente, vai para variável de ambiente, caso contrário vai para o container diretamente.

Operating System updates

Já pensou o como corrigir o próximo bug nojento do OpenSSL em todos os seus containers em produção? A resposta é simples: não se corrige. Pense em containers como algo imutável. Se você precisa atualizar algo, inclua no seu Dockerfile um comando pra atualizar o SO e o que mais for necessário e entregue a nova versão do container. Tecnicamente é possível entrar em um container e atualizar o SO, mas faz pouco sentido pois viola o princípio da imutabilidade.

Nomes das Tags

Uma consequência de atualizar o SO diretamente do Dockerfile (ex: apt-get upgrade) é que um mesmo Dockerfile, utilizado para criar imagens em dois momentos distintos, gera duas imagens distintas. Isso ocorre devido a dependências externas na criação do container, que mudam com o tempo (ex: versão do OpenSSL). Uma opção seria controlar também a versão dos pacotes no repositório do SO porém uma solução mais simples, seria adotar um padrão para a tag de cada imagem:

[git tag]_[data]

Ex:

V1.0.10_2016-07-07_18-36

Apesar de um pouco extenso, o node permitirá melhor gestão. Ex: você só precisa atualizar o SO do container, mas a versão da aplicação é a mesma. Sem problemas. Fica claro pelo nome que é a mesma versão da aplicação, mas dependências externas são diferentes.

 

Utilizar a tag “latest”, além da tag específica, também é uma prática comum para indicar qual a versão de produção.

Pacotes de SO / Prateleira

Se você vai executar uma aplicação que não foi feita para ser executada em container, tenha os seguintes pontos em mente:

  • Os scripts de start / stop do SO não foram feitos para serem executados em container.
    • Eles irão fazer fork() do daemon, e você verá uma morte prematura do container.
    • Você deve invocar o binário do daemon diretamente no ENTRYPOINT do container.
  • Rotação e purga de logs geralmente funcionam via cron, porém não há cron no container.
    • Tente redirecionar os logs do daemon para stdout / stderr.
    • Se não por possível, a alternativa é usar um entrypoint que suba o cron no container, e depois execute o daemon.

Um Processo por Container

Esse é um mantra comum na comunidade Docker, mas é bastante mal compreendido. Pense em um Apache MPM, que abre diversos processos para atender requisições. Ele viola o princípio do Docker? Bem… não. Uma melhor formulação do mantra seria “uma aplicação por container”. Exemplo: se você tem um Apache e um Jetty para subir, coloque cada um em seu container. Este artigo explora o cenário de mais de um processo por container.

Tecnicamente, você pode rodar o init em um container, e “subir o SO do zero”. Nada te impede disso. Porém isso vai contra a mentalidade de containers serem leves, auto-contidos e descartáveis. Um SO completo sobe uma variedade de serviços que sua aplicação no container não precisa… inclusive o próprio init.

Conclusão

Não pense em Docker / containers como uma opção, mas sim como algo inevitável. Quanto antes você estiver nesta onda, melhor. Mas compreenda bem a mentalidade por trás e use a ferramenta certa, da maneira certa, para o problema certo. Não somente “siga o hype” cegamente.