19 Jul
19Jul


Remember, one of the aims of CD is to make deployment boring, so whether its one or three applications, as long as its still boring it doesn't matter [Lewis/Fowler - 2014]


Muito se fala sobre a natureza descentralizada e tolerante à falha de microserviços quando comparada com a arquitetura monolítica tradicional baseada em libraries e frameworks, mas novas tecnologias precisam de novas formas de gestão para manter a governança que já existe de forma madura nas plataformas mais tradicionais. Alguns frameworks propostos (Serverless, ClaudiaJS etc) buscam fornecer um modelo de gestão que cobre os aspectos mais básicos de CI/CD e mesmo debug local o que é muito louvável, mas pecam quando não consideram o modelo nativo de gestão fornecido pelo Lambda.

Uma abordagem  melhor seria ter um build inteligente, que identificasse o que exatamente modificou no último commit e então seletivamente fizesse o empacotamento apenas dos microserviços que foram alterados ou criados, procedendo com os testes automatizados unitários, de integração e ou funcionais que verificam o perfeito funcionamento dos serviços modificados. Neste artigo um pouco mais longo do que o usual, veremos como fazer isto, abrindo espaço ainda para a discussão sobre questões como technical debt e build sob demanda.


Git Flow

Na sua essência o GIt Flow é um modelo de organização dos branches de um repositório, separando os diversos estágios de desenvolvimento e manutenção do código. Embora existam diversos tipos de branches com diversos propósitos, neste artigo vamos focar em 2 branches principais (develop e master).


Git Flow big picture

Git as a Service

O CodeCommit é o Git como um serviço da Amazon. Seu uso não poderia ser mais simples, crie um repositório, cadastre os usuários no IAM e comece a trabalhar. 

Dashboard do CodeCommit

No nosso exemplo criaremos um novo repositório para demonstrar como implementar um processo básico de DevOps incluindo build, teste, deploy e monitoramento totalmente automatizado para microserviços.


Estrutura do projeto

Fowler diz : One main reason for using services as components (rather than libraries) is that services are independently deployable. Para alcançar este modelo é necessário ser capaz de rodar o processo apenas para os microserviços afetados por uma mudança, mesmo que seja um microserviço apenas. Ao mesmo tempo em que preconizamos independência dos serviços, nada impede que a estrutura de diretórios reflita um modelo mais unificado, agrupando os microserviços de acordo com o seu propósito, exemplo:

Sugestão de organização dos microserviços

No exemplo acima de um projeto para uma empresa de petróleo, os microserviços foram agrupados de acordo com o seu propósito, enquanto mantém-se a independência entre eles. Todas as dependências de um microserviço são auto-contidas, descartando-se  o modelo monolítico de bibliotecas centralizadas. Desta forma cada microserviço é independente dos demais, sendo que o client deles faz a coreografia necessárias de acordo com suas necessidades na hora de executá-los. Um benefício adicional é que os microserviços tornam-se extremamente reaproveitáveis, enquanto mantém independência uns dos outros.


Processo DevOps

Para configurar o processo DevOps vamos precisar implementar algumas convenções e configurar alguns serviços:

  1. Definir um arquivo de configuração para cada microserviço
  2. Criar o serviço de build usando ECS
  3. Criar o script de build
  4. Criar os pipelines associados às branches
  5. Criar o dashboard com resultado de cada build


Arquivo de configuração

Para guiar o serviço de build vamos criar um arquivo de configuração que deverá estar presente no diretório de cada microserviço. Este arquivo será nomeado build.json e o seu conteúdo será o seguinte:

{

    "ACCOUNT_ID": "123456789012",

    "REGION": "region",

    "LAMBDA": {

        "FUNCTION_NAME": "nome-da-função",

        "DESCRIPTION": "descrição",

        "ROLE": "arn:aws:iam::123456789012:role/nome-da-role",

        "HANDLER": "index.handler",

        "TIMEOUT": "10",

        "MEMORY_SIZE": "128",

        "RUNTIME": "nodejs6.10",

        "ENVIRONMENT":"Variables={}"

    },

   "API_GATEWAY": 

   {

        "API_ID": "id-da-api-no-api-gateway",

        "HTTP_METHOD": "GET",

        "REQUIRE_APIKEY": true,

        "APIKEYS": {

            "DEV": "myapi-apikey-developers",

            "GAMMA": "myapi-apikey-developers",

            "PROD": "myapi-apikey-general-prod"

        },

        "CUSTOM_AUTHORIZER": "",

        "CACHE": false,

        "THROTTLE": {

            "ACTIVE": true,

            "RATE": "1000",

            "BURST": "2000"

        },

        "HEADERS": [{

                "NAME": "Access-Control-Allow-Headers",

                "MAPPING": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,x-application,clientId'"

            },

            {

                "NAME": "Access-Control-Allow-Origin",

                "MAPPING": "'*'"

            }

        ]

    }

}


O JSON acima contém as configurações usuais que precisam ser feitas no Lambda e API Gateway para cada API. Em caso de necessidade novos parâmetros podem ser criados.

A seção "API_GATEWAY" pode ser removida em caso da função Lambda não ser exposta através de uma API Rest (exemplo, se for acionada por um evento do CloudWatch ou usada no SQS, SNS etc).


Build sob demanda com ECS

É muito comum em instalações DevOps haver um Jenkins e um servidor de build configurado para centralizar os builds num processo de CI. A abordagem proposta elimina completamente o custo fixo mensal de manutenção dos servidores, possibilita escalabilidade do serviço de build e é totalmente serverless. Seus principais componentes, são:

CodePipeline

Atua como o orquestrador do processo de CI/CD, núcleo da solução DevOps. O CodePipeline é acionado a cada vez que um novo push é feito no respositório, acionando um workflow que vai, passo-a-passo, realizar o build, os testes automatizados e o deploy dos serviços. Um workflow é constituído de stages que executam actions, sequenciais ou simultâneas.


Exemplo de um pipeline


CodeBuild 

Uma action de um stage do CodePipeline pode ser desempenhada pelo CodeBuild. Isto permite criar um modelo de build sob demanda, baseado no ECS. O cenário abaixo mostra a diferença entre os dois modelos.

Servidor de orquestração (Jenkins Master)

8 vCPUs

15 GiB RAM

128GiB SSD

Servidor de build (Jenkins Slave)

8 vCPUs

15 GiB RAM

128GiB SSD

* Cabe notar que cada slave aloca memória no master (o qual não deve ter slaves): https://support.cloudbees.com/hc/en-us/articles/224505688-Hardware-Requirements-for-Jenkins-Enterprise 

Usando a configuração acima, temos os seguintes custos de EC2 (em dólares):


Usando o CodePipeline e o CodeBuild o cenário muda bastante, mesmo considerando o mesmo tamanho de máquina:


CodePipeline - US$ 1.00 (um dólar) por pipeline ativo pode mês (no caso teremos dois pipelines na nossa solução)


CodeBuild - Considerando uma média diária de 4 builds, cada um levando em média 15 min para ser executado e usando a mesma configuração de máquina do exemplo anterior, teremos um custo mensal de US$ 26.40 calculados da seguinte forma:

Custo = NB * D * T * C

Onde: 

NB = Número de builds diários

D = Número de dias de trabalho no mês

T = Tempo médio de cada build

C = Custo por minuto do CodeBuild


Somando-se o custo do CodePipeline, o valor total fica em US$ 28.40 mensais, com a vantagem ainda de não haverem servidores para dar manutenção e o ambiente ser escalável (múltiplos builds podem ser disparados ao mesmo tempo). Como a solução tem custo de acordo com o uso, à medida em que o projeto faz suas entregas, os custos caem de acordo com a demanda.


Criando usuário, credencial e chave SSH para o serviço de build

No IAM crie um usuário de serviço com uma ROLE que lhe dê direitos de executar as APIs do CodeBuild, CodePipeline, ECS, S3, Lambda, API Gateway e SNS. 

Crie uma credencial  (accesskey) e faça o upload de uma chave  pública gerada com o keygen para a sua conta no IAM para acesso ao CodeCommit (SSH keys for AWS CodeCommit). A chave privada será colocada na imagem da máquina no Docker.


Criando a imagem de build no ECS

Após baixar e instalar o Docker no seu Desktop e configurar o AWS CLI, criaremos a imagem usando o seguinte dockerfile:


FROM openjdk:8-jdk
MAINTAINER Emerson Lopes
USER root
ENV DEBIAN_FRONTEND=noninteractive

# Atualizando pacotes
RUN apt-get update -y && apt-get upgrade -y

# Instalando pacotes específicos
RUN apt-get install -y build-essential git curl python ssh expect zip
RUN apt-get install git-flow
RUN apt-cache policy libc6
RUN apt-get install libc6

# Instalando utilitário Pip
RUN curl -O https://bootstrap.pypa.io/get-pip.py && python get-pip.py

# Instalando Node.JS 6.10
RUN cd ~ && curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh && bash nodesource_setup.sh
RUN apt-get install nodejs
RUN apt-get install build-essential
RUN apt-get update -y  

# Instalando AWS CLI
RUN pip install awscli awsebcli

# Configurando a chave-pública do SSH
ADD id_rsa.pub /root/.ssh/id_rsa.pub
RUN chmod 600 /root/.ssh/id_rsa.pub
ADD config /root/.ssh/config
RUN cd /root/.ssh/ && chmod 600 config

# Configurando o AWS CLI
RUN aws configure set aws_access_key_id ACCESS-KEY-DO-USUÁRIO-DE-SERVIÇO
RUN aws configure set aws_secret_access_key SECRET-ACCESS-KEY
RUN aws configure set default.region us-east-1

# Atualizando todas as dependências
RUN apt-get update -qq

RUN apt-get clean

#Copiando script de build
COPY  build.sh /root/build.sh
RUN 500 /root/build.sh


O script build.sh referenciado no Dockerfile acima tem o seguinte conteúdo:

#!/usr/bin/env bashcd $CODEBUILD_SRC_DIR

git flow init

aws s3 cp s3://myproject-build-scripts/__build-microservices.js .
aws s3 cp s3://myproject-build-scripts/package.json.js .

npm install

node __build-microservices.js  $ambiente $branch
 

Crie a imagem e submeta a mesma para o ECS, dando um nome significativo

O script de build fica no S3 e é baixado sempre que um novo build vai ser executado. Isto permite modificar o comportamento do script sem alterar a imagem no ECS. Uma possibilidade é o script ficar no próprio CodeCommit, possivelmente num diretório oculto.


Abaixo uma versão simplficada do script de build  (__build-microservices.js). Note que a execução dos testes unitários é realizada durante este processo, salvando o resultado no S3 para análise em caso de problemas.



var utf8_encode = require("./utf8_encode");

var execSync    = require("child_process").execSync;

var shell       = require("shelljs")

var path        = require("path");

var fs          = require("fs");

var logID       = process.env.CODEBUILD_SRC_DIR.split("/")[2]

const AWS = require('aws-sdk');

console.lineBuffer = ""

console.out = function (data) {

    console.log(data)

    console.lineBuffer += utf8_encode(data + "\n")

}

console.outTitle = function (data) {

    console.out(" ")

    console.out("-------------------------------------------")

    console.out(data.toUpperCase())

    console.out("-------------------------------------------")

}

console.saveLog = function (fileName) {

    fs.writeFileSync(fileName, console.lineBuffer)

    console.lineBuffer = ""

}

var environmentName = process.argv[2]

var branchName = process.argv[3].toLowerCase();

execSync(`cd ${process.env.CODEBUILD_SRC_DIR} && \

          git diff-tree --no-commit-id \

                        --name-only 

                        -r $CODEBUILD_SOURCE_VERSION > ./__files.codecommit`, 

         function(error, stdout, stderr) {

             console.out("execSync Error:"  + error +  stdout + stderr);

             process.exit()

         }

);

var resultArray = {}

fs.readFileSync('./__files.codecommit')

   .toString()

   .split("\n")

   .map((line) => {

      if (line.length > 0)

         resultArray.push(line)

   });



if (resultArray.join() == "") {

   console.out     ("Nenhum artefato encontrado");

   console.out     (gitPath);

   console.out     (JSON.stringify(resultArray));

   execSync(`aws codebuild stop-build  --id ${process.env.CODEBUILD_BUILD_ID}`)

   console.saveLog   (process.env.CODEBUILD_SRC_DIR + "/pre-build-" + logID + ".log")

   process.exit()

}

var build = {}

var servicesHashTable = {}

var versionsHashTable = {}

for (index = 0; index < resultArray.services.length; ++index) {

    servicesHashTable[resultArray.services[index]] = resultArray.services[index]

    console.out(resultArray.services[index] + "[" + index + "]" + "=" + servicesHashTable[resultArray.services[index]])

}

console.out(" ")

for (line in servicesHashTable) {

    // verificando se o diretório atual ou algum diretório superior contém um microserviço

    if (line.indexOf("/") < 0) {

        continue;

    }

    var service = line

    var checkBuildJson = false

    while (true) {

        if (service.indexOf("/") < 0) {

            break

        }

        service = line.substr(0, line.lastIndexOf("/"))

        

        try {

            var temp = require(service + "/build.json")

            checkBuildJson = true

            break

        }

        catch(err) {

            checkBuildJson = false

        }

    }

    if (!checkBuildJson) {

        break;

    }

    console.outTitle(service)

    var message = ""

    build = require(service + "/build.json");

    console.out    ("Building " + service + "...\n\n");

    console.out("Updating dependencies " + build.LAMBDA.FUNCTION_NAME + "...\n")

    execSync(`cd ${service} && npm install`)

    if (build.LAMBDA.UNIT_TESTS) {

        console.out( "Running unit tests of " + build.LAMBDA.FUNCTION_NAME + "...\n")

        execSync(`cd ${service} && npm test`)

    } else {

        console.out( "Unit tests not enabled!!!\n\n")

    }

    console.out("Packaging project " + build.LAMBDA.FUNCTION_NAME + " code...\n")

    execSync(`cd ${service} && zip -r service.zip .`,  {maxBuffer: 1024 * 100000})

    console.out     ("    Packaging OK\n\n")

    console.out("Creating function " + build.LAMBDA.FUNCTION_NAME + "...\n")

    if (shell.exec(`aws lambda create-function \

                    --function-name  ${build.LAMBDA.FUNCTION_NAME}  \

                    --description   '${utf8_encode(removeDiacritcs(build.LAMBDA.DESCRIPTION))}' \

                    --role          '${build.LAMBDA.ROLE}' \

                    --handler       '${build.LAMBDA.HANDLER}' \

                    --timeout        ${build.LAMBDA.TIMEOUT} \

                    --memory-size    ${build.LAMBDA.MEMORY_SIZE} \

                    --zip-file       fileb://${service}/service.zip \

                    --environment   '${"ENVIRONMENT" in build.LAMBDA ? build.LAMBDA.ENVIRONMENT : "Variables={}"}' \

                    --runtime       '${build.LAMBDA.RUNTIME}'`).code !== 0)

    {

        console.out("create function configuration error")

    } else {

        console.out("create function configuration OK")

    }

    if (build.LAMBDA.VPCCONFIG == undefined) {

        build.LAMBDA.VPCCONFIG = {}

    }

    console.out("Updating function " + build.LAMBDA.FUNCTION_NAME + " configuration...\n")

    if (shell.exec(`aws lambda update-function-configuration \

                    --function-name  ${build.LAMBDA.FUNCTION_NAME}  \

                    --description   '${utf8_encode(removeDiacritcs(build.LAMBDA.DESCRIPTION))}' \

                    --role          '${build.LAMBDA.ROLE}' \

                    --handler       '${build.LAMBDA.HANDLER}' \

                    --timeout        ${build.LAMBDA.TIMEOUT} \

                    --memory-size    ${build.LAMBDA.MEMORY_SIZE} \

                    --vpc-config     ${JSON.stringify(build.LAMBDA.VPCCONFIG)} \

                    --environment   '${"ENVIRONMENT" in build.LAMBDA ? build.LAMBDA.ENVIRONMENT : "Variables={}"}' \

                    --runtime       '${build.LAMBDA.RUNTIME}'`).code !== 0)

    {

        console.out("update function configuration error")

    } else {

        console.out("update function configuration OK")

    }

    console.out("Updating function " + build.LAMBDA.FUNCTION_NAME + " code...\n")

    if (shell.exec(`aws lambda update-function-code \

                    --function-name ${build.LAMBDA.FUNCTION_NAME} \

                    --zip-file fileb://${service}/service.zip`).code !== 0)

    {

        console.out("update function code error")

    } else {

        console.out("update function code OK")

    }

    console.out( "Publishing version " + build.LAMBDA.FUNCTION_NAME + ' new version in ' + environmentName + '...\n')

    if (shell.exec(`aws lambda publish-version \

                        --function-name '${build.LAMBDA.FUNCTION_NAME}' > ${service}/._result.json`).code !== 0)

    {

        console.out("publishing version error")

    } else {

        console.out("publishing version OK")

    }

    console.out( "Creating alias " + build.LAMBDA.FUNCTION_NAME + ' new version in ' + environmentName + '...\n')

    var result = require(service + "/._result.json");

    if (shell.exec(`aws lambda create-alias \

                    --function-name '${build.LAMBDA.FUNCTION_NAME}' \

                    --description   '${utf8_encode(removeDiacritcs(build.LAMBDA.DESCRIPTION))}' \

                    --function-version "${result.Version}" \

                    --name '${environmentName}'`).code !== 0)

    {

        console.out("alias already exists")

    } else {

        console.out("create alias OK")

    }

    console.out( "Updating function alias version " + build.LAMBDA.FUNCTION_NAME + ' alias version in ' + environmentName + '...\n')

    if (shell.exec(`aws lambda update-alias \

                    --function-name    '${build.LAMBDA.FUNCTION_NAME}' \

                    --name             '${environmentName}' \

                    --function-version '${result.Version}'`).code !== 0)

    {

        console.outTitle("update alias error, parando o build")

        execSync(`aws codebuild stop-build  --id ${process.env.CODEBUILD_BUILD_ID}`)

        execSync(`aws sns publish --topic-arn "arn:aws:sns:us-east-1:525324176518:BuildServices" \

            --subject "DONUTS ALERT!" \

            --message "DONUTS ALERT! Build ${service}, executado por ${utf8_encode(resultArray.author)}, foi quebrado...\n - update-alias falhou!\n\n\n"`);

        shell.exit(1)

        process.exit()

    } else {

        versionsHashTable[build.LAMBDA.FUNCTION_NAME] = result.Version

        console.out("update alias OK")

    }

    if (typeof build.LAMBDA.SOURCEARN !== "undefined" && typeof build.API_GATEWAY !== "undefined") {

        console.out( "Authorizing API Gateway to invoke " + build.LAMBDA.FUNCTION_NAME + " ...\n")

        if (shell.exec(`aws lambda remove-permission \

                        --function-name '${build.LAMBDA.FUNCTION_NAME}' \

                        --statement-id 'SID_InvokeFunction_${build.LAMBDA.FUNCTION_NAME}_${build.API_GATEWAY.REST_API_ID}' \

                        --qualifier ${environmentName} `).code !== 0)

        {

            console.out("permission does not exists")

        } else {

            console.out("permission already OK")

        }

        if (build.LAMBDA.SOURCEARN !== "") {

        if (shell.exec(`aws lambda add-permission \

                            --function-name '${build.LAMBDA.FUNCTION_NAME}' \

                            --source-arn '${build.LAMBDA.SOURCEARN}' \

                            --principal 'apigateway.amazonaws.com' \

                            --statement-id 'SID_InvokeFunction_${build.LAMBDA.FUNCTION_NAME}_${build.API_GATEWAY.REST_API_ID}' \

                            --qualifier ${environmentName} \

                            --action 'lambda:InvokeFunction'`).code !== 0)

        {

                console.out("permission already exists")

        } else {

                console.out("permission already OK")

        }

        }

        if (build.API_GATEWAY.REQUIRE_APIKEY) {

            console.out( "Setting APIKEY as required to " + build.LAMBDA.FUNCTION_NAME + " ...\n")

            if (shell.exec(`aws apigateway \

                    update-method --rest-api-id ${build.API_GATEWAY.REST_API_ID} \

                                    --resource-id ${build.API_GATEWAY.RESOURCE_ID} \

                                    --http-method ${build.API_GATEWAY.HTTP_METHOD} \

                                    --patch-operations \ op="replace",path="/apiKeyRequired",value="${build.API_GATEWAY.REQUIRE_APIKEY}"`).code !== 0)

            {

                console.out("update method error")

            } else {

                console.out("update method OK")

            }

        }

    }

    console.outTitle  (service + ": Build finalizado!\n\n");

    exec(`aws sns publish --topic-arn "arn:aws:sns:us-east-1:123456789012:BuildServices"  \

            --subject "NO DONUTS FOR YOU!"  \

            --message "Build ${service} em ${environmentName}, executado por ${utf8_encode(resultArray.author)}, foi finalizado com sucesso!"`);

};

console.out  (">>>>>>>>>>>>>>>>>>>>>>>> Skynet terminated. Hasta la vista, baby");

function replaceAll(text, str1, str2, ignore)

{

    return text.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2);

}

function removeDiacritcs(text) {

     var diacritcs = ["á", "é", "í", "ó", "ú", "à", "è", "ì", "ò", "ù", "ã", "õ", "ä", "ë", "ï", "ö", "ü", "â", "ê", "î", "ô", "û", "ç", "ç"]

     var regular   = ["a", "e", "i", "o", "u", "a", "e", "i", "o", "u", "a", "o", "a", "e", "i", "o", "u", "a", "e", "i", "o", "u", "c", "c"]

     for (var i=0;i< diacritcs.length;i++) {

         text = replaceAll(text, diacritcs[i], regular[i])

         text = replaceAll(text, diacritcs[i].toUpperCase(), regular[i].toUpperCase())

     }

     return text

}



Configurando o ECS

As imagens criadas com sucesso aparecem no ECS-> Amazon ECR -> Repositories

O próximo passo é dar as permissões adequadas para que a imagem seja acessada pelo CodeBuild:


Adicione permissões para :

arn:aws:iam::964771811575:root, arn:aws:iam::131992011433:root, arn:aws:iam::201349592320:root, arn:aws:iam::570169269855:root, arn:aws:iam::883865855280:root, arn:aws:iam::828209784933:root, arn:aws:iam::157144849617:root, arn:aws:iam::064387162992:root


Marque as opções abaixo:

 A policy final deve ser semelhante a esta:

{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "socialmedia-ecs-services-permission",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::964771811575:root",
"arn:aws:iam::131992011433:root",
"arn:aws:iam::201349592320:root",
"arn:aws:iam::570169269855:root",
"arn:aws:iam::883865855280:root",
"arn:aws:iam::828209784933:root",
"arn:aws:iam::157144849617:root",
"arn:aws:iam::064387162992:root"
]
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
}
]
}


Criando os pipelines

Crie um pipeline para cada branch (develop e master) . O pipeline para develop deverá realizar o build para os ambientes DEV e GAMMA (este último com aprovação). O pipeline para a branch master deverá criar o ambiente PROD. Na configuração do action do Codebuild, você deverá ter algo como:


Este comando será executado pelo container criado a partir da imagem que criamos e submetemos para o ECS, processando o script de build, gerando todas as configurações necessárias (aliases, versions, api gateway etc).


Technical debt

O conceito de technical debt tem ganhado força em ambientes DevOps, permitindo uma avaliação mais objetiva da qualidade do trabalho dos desenvolvedores através da captura dos resultados dos testes automatizados após o build. Outra forma de medir skills e proeficiência é utilizar uma ferramenta como o SONAR, que avalia a qualidade do código escrito. Nosso exemplo acima poderia ser facilmente modificado para executar os testes e o processo de análise qualidade de código para criar um dashboard com os resultados do build. Utilizando o conceito de technical debt é possível estabelecer um formato baseado em meritocracia na hora de avaliar e promover ou melhor remunerar os profissionais. Também poderá ser uma ferramenta útil para avaliar a necessidade de treinamento ou mesmo substituição de elementos do time cujas performances deixarem a desejar.


Considerações finais

Este artigo não pretende esgotar a discussão sobre qual a melhor forma de construir microserviços, mas apenas demonstrar uma alternativa a frameworks que mudam a organização dos serviços ou implementam formas "alternativas" de separação entre ambientes, mas não necessariamente melhores do que aquela já criada pela AWS. A criação de um dashboard para acompanhamento indicando o status de cada build e o resultado dos testes unitários (não cobertos aqui, mas alvo de um futuro artigo) seriam o próximo passo, de forma que o resultado do build faça parte das estatísticas do projeto, indicando, por exemplo, erros encontrados, percentual de cobertura de código e análise de qualidade de código, por desenvolvedor.

Como um verdadeiro processo DevOps, o resultado das configurações e automações deve ser o de afastar o time de desenvolvimento do console da AWS ao mesmo tempo em que permite deploy a qualquer instante. Tudo o que é necessário fazer é submeter um commit para o Git (CodeCommit), deixando todo o restante para os scripts de automação de build, teste e deployment. 


Emerson Lopes
Arquiteto de soluções na nuvem
emersonlopes@gmail.com


Comentários
* O e-mail não será publicado no site.
ESTE SITE FOI CRIADO USANDO