Não é todo mundo que trabalha com tecnologia que tem o domínio técnico necessário para entender o dia a dia do desenvolvedor. Pior ainda se a pessoa tiver que lidar com infraestrutura. Por isso, vamos abordar neste artigo os conceitos e exemplos de uso sobre a cultura DevOps e algumas ferramentas utilizadas para alcançar uma infraestrutura ágil.
O termo DevOps foi criado por Patrick Debois e descreve uma cultura cujo objetivo é melhorar a dinâmica na relação entre as áreas de infraestrutura e desenvolvimento para agregar valor ao negócio.
Existe um conflito entre essas áreas. Desenvolvimento se preocupa em aumentar o valor do negócio, trabalha com metodologias ágeis, são proativos, evolutivos e querem colocar suas aplicações em produção o mais rápido possível.
A Infra se preocupa em proteger o valor do negócio, trabalha no modelo tradicional de administração, executa operações manuais, é reativa e quer ter certeza de que a aplicação está estável o suficiente para entrar em produção sem gerar incidentes. A Infra não conhece o Desenvolvimento e não sabe como mudar para satisfazê-lo, e o Desenvolvimento não conhece a Infra e não entende totalmente sobre o que ela pede. Tudo isso gera ruídos de comunicação e atrapalha o trabalho colaborativo entre as áreas. Foi buscando e discutindo soluções para essas necessidades que surgiu a cultura DevOps.
Nessa cultura, existe um papel importante chamado especialista DevOps, que faz a ponte entre as áreas e possui conhecimento em desenvolvimento e infraestrutura ágil, ou seja, na capacidade de permitir alterações nos servidores de maneira rápida.
A implementação da cultura DevOps é um processo complexo e, se pensarmos em grandes empresas, também doloroso, caro e demorado. A resistência às mudanças e falta de entendimento do todo são os principais obstáculos na adoção da cultura DevOps. Dito isso, vamos agora falar sobre como instalar e configurar algumas das ferramentas mais populares utilizadas no Application Lifecycle Management (ALM) e infraestrutura ágil.
Todos os exemplos serão executados no CentOS 7 usando o VirtualBox.
Configurando o laboratório
A configuração mínima recomendada é um computador com 4 GB de memória RAM e 50 GB de espaço livre em disco.
Instalando o VirtualBox
O VirtualBox é um aplicativo de virtualização que permite criar ambientes que simulam outro computador. Faça o download em https://www.virtualbox.org/wiki/Downloads e instale no seu computador.
- Abra o VirtualBox, clique em New e digite CentOS no campo Name.
- Em Type, selecione Linux, clique em Version Red Hat (64 bit) e clique no botão Continue.
- Em Memory Size, configure para 2048 MB, avance para a próxima tela, selecione Create a virtual hard disk now e clique no botão Create.
- Selecione o tipo VDI, avance, deixe marcada a opção Dynamically Allocated e na próxima tela configure o tamanho do HD virtual para 30 GB.
Configurando o CentOS
Faça o download do CentOS-7-x86_64-LiveGNOME.iso. Abra o VirtualBox, selecione a máquina virtual CentOS e clique no botão Settings. Na janela que abrir, clique na aba Storage. Selecione onde está escrito Empty, clique no ícone de cd-rom ao lado de Optical Drive e depois em Choose Virtual Optical Disk File para selecionar a iso do CentoOS.
Siga a instalação padrão do CentOS. Caso tenha dúvidas, este vídeo explica passo a passo como fazer essa instalação.
Terminada a instalação, execute os comandos a seguir um por um terminal.
$ sudo wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-8.noarch.rpm $ sudo rpm -ivh epel-release-7–8.noarch.rpm # configura o Epel $ sudo yum update -y # atualiza os pacotes instalados $ sudo yum --enablerepo=epel install -y dkms gcc* python-pip java-1.8.0-openjdk
Epel é um repositório criado pela comunidade do projeto Fedora que contém softwares 100% livres. DKMS (Dynamic Kernel Module Support) é um programa que permite gerar módulos do kernel do Linux. GCC é um compilador da linguagem C, PIP é um gerenciador de pacotes para Python e OpenJDK é uma versão do Java Open Source. Esses pacotes são dependências necessárias das ferramentas que serão mostradas adiante.
Git
Criado em 2005 como alternativa para o versionamento do código-fonte do kernel do Linux, começou a popularizar em 2008, depois do lançamento do GitHub, um serviço de web hosting com aspectos de redes sociais. O GitHub permite a criação de repositórios Git e permite que desenvolvedores possam copiar, contribuir e discutir sobre projetos de software. Em 2011, foi lançado o Gitlab, serviço similar ao GitHub mas que possui uma versão open source que permite ser instalada em servidores privados.
Apenas o Git já é suficiente para configurar um servidor de repositórios. Ferramentas como Gitlab facilitam bastante o trabalho de gerenciar repositórios, mas não são obrigatórias.
Vamos começar a brincadeira instalando o Git:
$ sudo yum install -y git
E uma configuração básica:
$ git config --global user.name "Gustavo Henrique" $ git config --global user.email iam@gustavohenrique.net $ git config --global push.default simple
Então criamos um repositório chamado curriculum dentro da pasta Documents.
$ cd $HOME/Documents && git init --bare curriculum.git
Em seguida, entramos na pasta /tmp e fazemos um clone do repositório. O clone é onde de fato as alterações ocorrem, onde trabalhamos, e somente após concluirmos nosso trabalho enviamos as modificações (commit) para o repositório.
$ cd /tmp $ git clone ~/Documents/curriculum.git myresume Cloning into 'myresume'... warning: You appear to have cloned an empty repository. done.
Vamos criar um arquivo resume.txt e adicioná-lo ao repositório. Um commit salva as alterações localmente e um push envia para o repositório, tornando a alteração disponível para outras pessoas.
$ cd myresume/ $ echo "Occupation: DevOps Specialist" > resume.txt $ git status $ git add resume.txt $ git commit -m "I created a resume for a devops position" $ git push $ git log
Por padrão, a branch principal recebe o nome de master. Mas é possível criar com outro nome ou mesmo criar várias branches.
$ git checkout -b frontend_developer_position $ echo "Occupation: Frontend Developer" > resume.txt $ git add — all . $ git commit -m "I changed my resume for a developer position." $ git push
Acima, criamos um novo branch chamado frontend_developer_position, que contém o arquivo resume.txt alterado. Depois de executar o push, ficamos com duas branches (master e frontend_developer_position), cada uma contendo o mesmo arquivo resume.txt, mas com conteúdo diferente.
Criamos um repositório local apenas para brincar. Na prática, utilizamos alguma ferramenta como Gitlab ou Gitblit ou algum serviço na nuvem como GitHub ou Bitbucket para gerenciar melhor nossos repositórios e permitir acesso remoto para outras pessoas.
Configurando Gitlab
Para instalar, abra um terminal e digite:
$ sudo yum install -y curl policycoreutils openssh-server openssh-clients postfix $ sudo systemctl enable sshd $ sudo systemctl start sshd $ sudo systemctl enable postfix $ sudo systemctl start postfix $ sudo firewall-cmd --permanent --add-service=http $ sudo systemctl reload firewalld $ curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh -o script.rpm.sh $ sudo sh script.rpm.sh $ sudo yum install -y gitlab-ce $ sudo gitlab-ctl reconfigure $ firefox http://localhost
Se tudo der certo, o Firefox vai abrir a página do Gitlab que solicita a criação de uma senha. Crie uma senha e faça login utilizando o nome de usuário root. Após a autenticação, é possível importar ou criar repositórios e definir as permissões de acesso. Uma vez criado, será exibida a URL do repositório que vai ser clonado.
Para mais detalhes consulte a documentação em https://docs.gitlab.com/ce/.
Git Workflows
Conforme o Git foi popularizando e sendo adotado por grandes empresas, cada organização passou a trabalhar de maneira diferente. Pensando nisso, foram criadas orientações com o propósito de organizar a forma de trabalho e mostrar as possibilidades de se trabalhar com o Git.
Separei alguns textos que explicam e comparam os diferentes workflows existentes.
Interfaces Gráficas para Git
Para quem trabalha com Git no dia a dia, talvez seja mais produtivo utilizar comandos no terminal. Porém, há quem goste de utilizar interfaces gráficas e para isso existem várias disponíveis na web.
- GitKraken (Windows, Mac e Linux)
- SourceTree (Windows e Mac)
- TortoiseGit (Windows)
Saiba mais sobre Git
- Try Git – aprenda Git em 15 minutos com um pseudo-terminal
- Git From the Inside Out
- Git Branching
- Atlassian Git Tutorial
Automação de testes
Automação de teste é o uso de software específico para controlar a execução do teste de software (o software sendo testado), a comparação dos resultados esperados com os resultados reais e outras funções de controle e relatório de teste. O objetivo dos testes é assegurar a qualidade do projeto, descobrindo problemas ou pontos de melhorias antes de por o software em produção.
Existem diversos tipos de testes e abordagens utilizadas. Vamos falar apenas de dois: testes de unidade e testes de interface.
Testes de unidade
Uma unidade é a menor parte testável de um programa de computador. Dependendo da linguagem de programação, uma unidade é chamada de função, procedure ou método.
Vamos criar um exemplo em Python de um código que poderia ser parte de uma aplicação. Uma validação muito simples de e-mail, apenas para esse exemplo.
Vamos utilizar o terminal para criar o arquivo mail.py. Atenção aos espaços antes da palavra return.
$ cat > mail.py <<EOF def is_valid(email): return email and '@' in email EOF
O código acima é uma função que recebe um e-mail e retorna verdadeiro ou falso se houver um ‘@’. A maneira manual de testar seria executar o código e digitar um e-mail qualquer. Mas, para automatizar, escrevemos um código que faz esse teste.
Crie o arquivo mail_test.py:
$ cat > mail_test.py <<EOF import unittest import mail class MailTest(unittest.TestCase): def test_should_return_true_when_email_is_valid(self): self.assertTrue(mail.is_valid('iam@gustavohenrique.net')) def test_should_return_false_when_email_is_invalid(self): self.assertFalse(mail.is_valid('xxxxx')) if __name__ == '__main__': unittest.main() EOF
Para rodar o teste, execute:
$ python mail_test.py
E o resultado deve ser parecido com isto:
.. ------------------------------------------------------------------- Ran 2 tests in 0.000s OK
Testes de interface gráfica
Verifica se a navegabilidade e se os objetivos da interface funcionam como especificados.
Esse tipo de teste costuma ser feito manualmente por equipes de testes ou Quality Assurance (QA). Quando automatizado, é comum utilizar ferramentas como o Selenium Webdriver, que possui suporte para diversas linguagens de programação.
Precisamos primeiro fazer o download do driver para o Firefox e instalar o Selenium antes de escrever o teste.
$ wget https://github.com/mozilla/geckodriver/releases/download/v0.11.1/geckodriver-v0.11.1-linux64.tar.gz $ tar zxf geckodriver-v0.11.1-linux64.tar.gz $ sudo mv geckodriver /usr/bin/ $ sudo pip install selenium==3.0.1
O teste abaixo vai abrir uma janela do Firefox, acessar a página inicial do Google e verificar o atributo title do logotipo.
$ cat > google_test.py <<EOF import unittest from selenium import webdriver class GoogleTest(unittest.TestCase): def test_verify_title_in_logo(self): driver = webdriver.Firefox() driver.implicitly_wait(30) driver.get('https://www.google.com') logo = driver.find_element_by_id('hplogo') title = logo.get_attribute('title') self.assertEquals(u'Google', title) driver.quit() if __name__ == '__main__': unittest.main() EOF
Para executar o teste:
$ python google_test.py
Resultado:
‘NoneType’ object has no attribute ‘path’ . — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — Ran 1 test in 16.561s OK
Sonatype Nexus
Nexus é um gerenciador de repositório de artefatos. Em desenvolvimento, quase sempre a aplicação possui dependências de outros softwares ou componentes para efetuar algum trabalho. Por exemplo, se uma aplicação possui integração com o Facebook, então ela depende de componentes fornecidos pelo próprio Facebook para permitir esse tipo de integração. Esses componentes são também chamados de artefatos ou bibliotecas, e ficam disponíveis na Internet em vários sites diferentes.
Nem sempre podemos confiar que o componente utilizado e a versão desse componente vão estar disponíveis em sites de terceiros com o passar do tempo. Por isso é importante manter um repositório privado dentro da empresa para garantir a disponibilidade desses componentes.
Mesmo as aplicações desenvolvidas dentro de uma empresa podem ser disponibilizadas no Nexus; assim, elas são facilmente distribuídas para todos os interessados.
Inicialmente, foi desenvolvido para armazenar dependências de projetos Java (arquivos .jar), mas hoje possui suporte para outros formatos, como Docker, NPM, Bower, PyPI e Ruby Gems.
Vamos fazer o download, descompactar e executar o Nexus:
$ wget https://sonatype-download.global.ssl.fastly.net/nexus/3/nexus-3.0.2-02-unix.tar.gz $ tar zxf nexus-3.0.2–02-unix.tar.gz $ ./nexus-3.0.2–02/bin/nexus start
Abra a interface web do Nexus pelo browser:
$ firefox http://localhost:8081
No menu superior do lado direito, existe um botão escrito Sign in. Clique nele e utilize como username admin e password admin123.
Depois de autenticado, vá para a tela de gerenciamento de repositórios http://localhost:8081/#admin/repository/repositories. Clique no botão Create Repository e escolha um repositório do tipo raw (hosted). Informe o nome do repositório como my-app-example, selecione o Storage default e desmarque o checkbox de validação em Strict Content Type Validation.
Atualmente, o Nexus 3 suporta os seguintes tipos de repositórios:
- Proxy: é um repositório ligado a um repositório remoto, ou seja, se um artefato não existe localmente, a solicitação é encaminhada para um repositório remoto. O download do artefato é feito, e ele é armazenado localmente. As próximas solicitações para o mesmo artefato serão atendidas localmente, funcionando de forma parecida com um serviço de cache, evitando consultar novamente o repositório remoto.
- Hosted: o próprio Nexus armazena os artefatos e possui autoridade para modificar informações sobre eles.
- Group: permite combinar outros repositórios como um único repositório. Com isso, os usuários podem acessar o repositório por uma única URL, e o administrador pode configurar outros repositórios em máquinas diferentes.
Vamos criar uma aplicação, gerar um artefato e armazenar no Nexus:
$ echo "print 'Hello World'" > app.py $ tar czf app-1.0.0.tar.gz app.py $ curl -v --user 'admin:admin123' --upload-file app-1.0.0.tar.gz http://localhost:8081/repository/my-app-example/app-1.0.0.tar.gz
Com isso, o artefato fica disponível para outros usuários em http://localhost:8081/#browse/browse/components:my-app-example.
Saiba mais sobre o Nexus 3 em http://www.sonatype.org/nexus/category/nexus-3/.
Integração Contínua com Jenkins
Integração Contínua é uma prática de desenvolvimento de software na qual os membros de um time integram seu trabalho frequentemente. Neste artigo, você pode aprender um pouco mais sobre como funciona. Cada integração é verificada por um build automatizado para detectar erros o mais rápido possível. A atividade de build consiste basicamente em obter o código-fonte da aplicação, executar testes automatizados e gerar um pacote no formato necessário para fazer o deploy.
O Jenkins é uma ferramenta poderosa para integração contínua. Surgiu como uma variante (fork) da ferramenta Hudson, após alguns desenvolvedores da empresa Sun Microsystem, detentora do sistema, se desentenderem com a Oracle, empresa que adquiriu a Sun em 2010.
Possui uma comunidade muito ativa, é bem extensível com plugins e seu uso vai além da integração contínua, sendo utilizado por muitos administradores de redes como ferramenta para agendamento de tarefas, por exemplo.
Faça o download e execute o Jenkins na porta 8888 para evitar conflitos com o GitLab:
$ wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war $ java -jar jenkins.war --httpPort=8888 $ firefox http://localhost:8888
Ao acessar o Jenkins pela primeira vez, será solicitada a senha de administrador gerada automaticamente por ele e armazenada dentro de um arquivo no servidor. Para visualizar essa senha:
$ cat $HOME/.jenkins/secrets/initialAdminPassword
Copie essa senha no Jenkins. Clique em Select plugins to install. Desmarque todos os plugins selecionados e selecione apenas o plugin Pipeline. Aguarde enquanto o Jenkins faz o download do plugin Pipeline Suite e suas dependências.
Após o download, pule a etapa de criar usuários e clique em Continue as admin.
Criando um job
Jobs são tarefas executáveis, controladas e gerenciadas pelo Jenkins. Um build é o resultado da execução de um job.
Tipos de jobs:
- Freestyle Project: o tipo clássico, simples e genérico que suporta diferentes tecnologias.
- Pipeline: permite separar o processo de build em estágios e exibe o resultado visualmente em forma de pipeline. Utiliza a linguagem de programação Groovy.
- Multibranch Pipeline: similar ao Pipeline, porém detecta qual branch no repositório de arquivos (Git) possui um arquivo chamado Jenkinsfile que contém o código Groovy.
Pipeline
Vamos criar um job do tipo Pipeline e demonstrar como seria um pipeline simples de integração contínua.
Clique em Create new jobs, insira o nome Hello, selecione o tipo Pipeline e clique em Ok. Em seguida, você será redirecionado para a tela de configuração do job. Dependendo dos plugins instalados, as opções de configuração aumentam.
Na seção Pipeline, insira o código abaixo no campo de texto chamado Script e salve as alterações:
node { def workspace = pwd() def packageName = "${JOB_NAME}-${BUILD_NUMBER}.tar.gz" stage ('Clone from Git') { git url: "/home/gustavo/Documents/curriculum.git" } stage ('Build') { sh "echo Command to run tests here" sh "tar czf ${packageName} ${workspace}" } stage ('Upload to Nexus') { sh "curl -v --user admin:admin123 --upload-file ${packageName} http://localhost:8081/repository/my-app-example/${packageName}" } }
O script acima possui três estágios. O primeiro faz um clone do repositório criado anteriormente, o segundo cria um pacote .tar.gz com o conteúdo do repositório (o nome do pacote é composto pelo nome do job e o número do build) e o terceiro envia esse pacote para o Nexus dentro de um repositório criado por nós anteriormente.
Clique em Build Now para executar o build e ver o resultado.
Freestyle Project
Crie um novo job clicando em New Item, insira um nome e selecione o tipo Freestyle Project. Em seguida, na seção Build, clique no botão Add build step e selecione Execute shell. Adicione o script abaixo no campo de texto chamado Command:
packageName="$JOB_NAME-$BUILD_NUMBER.tar.gz" rm rf * git clone /home/gustavo/Documents/curriculum.git echo "Command to run tests here" tar czf ${packageName} ${WORKSPACE} curl -v --user admin:admin123 --upload-file ${packageName} http://localhost:8081/repository/my-app-example/${packageName}
A principal diferença entre os dois tipos de job é que um Freestyle Project possui apenas um estágio.
Saiba mais sobre a terminologia do Jenkins em https://wiki.jenkins-ci.org/display/JENKINS/Terminology.
Docker containers
Para entender melhor o que é Docker, antes é preciso entender algumas coisas sobre o kernel (núcleo) do Linux.
A partir da versão 2.6 do kernel, foram adicionadas três grandes funcionalidades: cgroups (control groups), Namespaces e seccomp-bpf (secure computing mode).
O cgroups limita o uso de recursos da máquina. Por exemplo, ele permite limitar um grupo de processos para utilizar somente 100 MB de memória.
Namespaces permitem que grupos de processos sejam separados de modo que um grupo não afete os recursos de outro grupo.
Seccomp-bpf permite filtrar quais chamadas aos sistema (system calls) um processo pode executar. Por exemplo, negar acesso aos recursos de rede.
Resumindo, cgroups limita, Namespaces isola e Seccomp-bpf libera ou bloqueia acesso aos recursos de hardware (cpu, memória, disco…).
Para testar o uso do Namespaces, execute os comandos:
$ sudo unshare --fork --pid --mount-proc bash $ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 1.0 0.1 116564 3304 pts/1 S 18:06 0:00 bash root 27 0.0 0.0 151032 1808 pts/1 R+ 18:06 0:00 ps aux
Repare que, ao usar o comando ps aux, não conseguimos visualizar outros processos. Isso comprova que executamos o bash de forma isolada dos outros processos da máquina.
Trabalhar com cgroups e Seccomp-bpf exige mais comandos, então não vamos abordar por aqui.
Mas o que são containers? Em algum momento, alguém pensou: “Legal, vou construir uns scripts para utilizar esses recursos e vou poder rodar softwares isolados uns dos outros e com recursos limitados, de forma segura, simples, leve e rápida”. Algumas pessoas pensaram nisso também. Eles criaram uma coisa chamada “Docker containers” que utiliza essas features. E assim nasceu o Docker, uma camada de alto nível que fornece uma API (uma forma de integração com outros sistemas) para utilizar esses recursos do kernel. É claro que o Docker possui muito mais recursos hoje, mas muito do que o Docker é capaz utiliza os recursos primitivos do kernel do Linux.
Container vs Imagem
Uma imagem Docker NÃO É uma imagem ISO, daquelas utilizadas para gravar em CD/DVD. No contexto do Docker, uma imagem é um sistema de arquivos que nunca muda, e um container é uma execução da imagem.
Confuso? Sim. É difícil explicar com palavras. Mas vamos demonstrar adiante.
Uma informação mais detalhada pode ser encontrada neste link aqui. Este texto do Wesley também é bem legal sobre o assunto, e eu já escrevi sobre isso aqui.
Instalando e utilizando o Docker
Abra um terminal e execute:
$ sudo yum install -y docker $ sudo systemctl enable docker $ sudo systemctl start docker
Vamos criar um container de um servidor web a partir da imagem nginx fornecida pelo repositório público de imagens do Docker chamado de Docker Hub.
$ sudo docker pull nginx:alpine $ sudo docker run -d -p 8000:80 nginx:alpine
O Nginx é um servidor web que roda na porta 80. A imagem nginx:alpine possui o servidor Nginx instalado e configurado, baseado em outra imagem que utiliza como base o Alpine Linux, distribuição enxuta específica para trabalhar com o Docker.
O comando acima cria um container e mapeia a porta local 8000 para a porta 80 do container. Com isso, é possível acessar a URL http://localhost:8000 e ver o Nginx funcionando. Fácil assim, sem precisar instalar e configurar nenhum servidor no CentOS. É possível criar de maneira leve e rápida outros containers:
$ sudo docker run -d -p 8001:80 nginx:alpine $ sudo docker run -d -p 8002:80 nginx:alpine $ sudo docker run -d -p 8003:80 nginx:alpine $ sudo docker run -d -p 8004:80 nginx:alpine $ sudo docker run -d -p 8005:80 nginx:alpine
Com isso, foram criados cinco containers rodando Nginx, acessíveis via http://localhost:8001, http://localhost:8002, http://localhost:8003, http://localhost:8004 e http://localhost:8005.
Não é possível alterar uma imagem, apenas um container. Um container pode ser criado e apagado a qualquer momento, não é capaz de persistir dados. A maneira de alterar um container e persistir essa alteração é somente criando outra imagem. Por exemplo, se desejar alterar a porta padrão do Nginx para 90, é necessário criar um container, entrar nele, alterar o arquivo de configuração do Nginx e criar uma nova imagem a partir desse container.
$ sudo docker run -d --name nginx1 nginx:alpine $ sudo docker exec -it nginx1 sh
Entramos no container. Agora podemos alterar o arquivo de configuração na linha 2 de listen 80 para listen 90.
$ vi /etc/nginx/conf.d/default.conf
Para a mudança surtir efeito, precisamos criar uma nova imagem e depois executar um outro container usando nossa nova imagem.
$ sudo docker commit nginx1 my-nginx $ sudo docker run -d -p 9000:90 --name nginx2 my-nginx
Abra o Firefox em http://localhost:9000 para confirmar que está funcionando.
Dockerfile
Algumas vezes, a configuração de uma imagem pode ser complexa. Instalar dependências, criar uma estrutura de diretórios, modificar arquivos de configuração… e, nesse caso, é uma boa prática criar um arquivo que especifica como fazer esse trabalho. É isso que o Dockerfile faz.
$ cat > Dockerfile <<EOF FROM alpine RUN sh -c 'apk add — update nginx && echo "Hello World" > /usr/share/nginx/html/index.html' EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] EOF
Com esse Dockerfile, podemos criar uma imagem com Nginx e personalizar a página inicial.
Para criar uma imagem utilizando esse arquivo:
$ sudo docker built -t=my-custom-nginx .
Conclusão
O ecossistema DevOps é muito amplo e abrangente, e não cabe em apenas um texto. A intenção aqui é passar um conhecimento básico para pessoas não técnicas que tomam decisões técnicas em ambientes caóticos corporativos. Se ficou alguma dúvida ou tem algo a dizer, aproveite os campos abaixo! Até a próxima.
***
Artigo publicado originalmente em http://www.concretesolutions.com.br/2016/11/23/devops-ferramentas-pos/.