Pular para o conteúdo

Introdução às ferramentas DevOps para Scrum Masters e POs

Postado em 18 minutos de leitura

Nem sempre o Scrum Master ou o Product Owner tem o domínio técnico necessário para entender o dia a dia do desenvolvedor. Pior ainda se ele tiver que lidar com infraestrutura.
Por isso que nesse artigo vamos abordar de forma objetiva 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 pró-ativos, 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 négocio, trabalha no modelo tradicional de administração, executam operações manuais, são reativos e querem ter certeza 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 satisfaze-los e o Desenvolvimento não conhece a Infra e não entendem totalmente sobre o que pedem. Isso gera ruídos de comunicação e atrapalha o trabalho colaborativo entre as áreas. E 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. Mas o que é Infraestrutura Ágil? É a capacidade de permitir alterações nos servidores de maneira rápida.

A implementação da cultura DevOps é um processo complexo e se pensar nas grandes empresas também se torna doloroso, caro e demorado. A resistência à mudanças e falta do entendimento do todo são os principais obstáculos na adoção da cultura DevOps. Para um melhor esclarecimento, vamos abordar nesse texto o conceito de DevOps, bem 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, cliquem New e digite CentOS no campo Name.
  • Em Type selecione Linux, 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 marcado 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 se 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, esse vídeo explica passo a passo como fazer essa instalação.

Terminada a instalação, execute os comandos à 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. Em 2008 começou a popularizar 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 instalado 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 que 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 adiciona-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 contendo 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/.

Git Workflows

Conforme o Git popularizou e sua adoção aumentou entre grandes empresas, cada organização passou à 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.

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 2, 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 email, 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 email e retorna verdadeiro ou falso se houver um '@'. A maneira de testar manual seria executar o código e digitar um email 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 isso:

..
 — — — —— — — — — — — — — — — — — — — — — — — — — — — — 
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 à 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 de aplicações, 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 também chamados de artefatos ou bibliotecas ficam disponíveis na internet através de vários sites diferentes.

Nem sempre podemos confiar que o componente utilizado e a versão desse componente vai estar disponível 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 à 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 à um repositório remoto. Se um artefato não existe localmente então a solicitação é encaminhada à um repositório remoto. É feito o download do artefato e 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 dela 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 pratica de desenvolvimento de software onde os membros de um time integram seu trabalho frequentemente. Cada integração é verificada por um build automatizado para detectar erros de integração 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 o uso de 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 existentes:

  • 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 para criação dos estágios.
  • 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.

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 3 estágios. No primeiro faz um clone do repositório criado anteriormente, no 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 no terceiro envia esse pacote para o Nexus dentro de um repositório criado por nós antes.
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 2 tipos de job é que um Freestyle Project possui apenas 1 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 3 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 permite 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 um do outro 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 em dia 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 em https://docs.docker.com/engine/getstarted/step_two/.

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 fornecido 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 5 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 uma 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 à partir desse container.

sudo docker run -d --name nginx1 nginx:alpine
sudo docker exec -it nginx1 sh

Entramos dentro do 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. Dockerfile é um arquivo que especifica como uma imagem deve ser criada.

cat > Dockerfile <<EOF
FROM alpine
RUN sh -c 'apk add — update nginx && echo "<html><body>Hello World</body></html>" > /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 build -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.

comments powered by Disqus