O blog da AWS
Entrega Contínua de clusters Amazon EKS usando AWS CDK e CDK Pipelines
Por Jairo da Silva Junior, Arquiteto de Soluções Senior na AWS e
Davi Garcia, Arquiteto de Soluções Especialista Senior na AWS
Clientes estão buscando formas de automatizar a implantação de clusters Amazon EKS em diferentes versões, ambientes, contas e regiões. A implantação desses clusters envolve tarefas como criar seus clusters com a configuração de rede e logging apropriada, selecionar as versões dos seus Amazon EKS add-ons, e quando finalizado, implantar outros componentes de infraestrutura. Este post mostra como utilizar o AWS CDK e CDK Pipelines para implantar clusters Amazon EKS.
Visão geral
Neste post mostraremos um pipeline de exemplo utilizando CDK Pipelines, uma biblioteca de constructs de alto nível que torna fácil configurar pipelines de implantação contínua para suas aplicações CDK utilizando o AWS CodePipeline.
Este pipeline cria dois clusters distintos do Amazon EKS, um na versão 1.20 e outro na versão 1.21, com um Managed Node Group para cada, e implanta alguns Controllers e Operators utilizando o AWS CDK, como o AWS Load Balancer Controller, Calico for Network Policies, ExternalDNS, Cluster Autoscaler, Metrics Server e o Container Insights.
Ele também inclui um stage para configurar um registro no Amazon Route 53 apontando para um dois subdomínios, gerenciado pelo ExternalDNS de cada um dos clusters, e realiza a troca dos usuários da aplicação de exemplo utilizando uma estratégia Blue/Green.
Stages do Pipeline:
- Source: Este stage busca o código da sua aplicação CDK do seu repositório no GitHub e dispara uma execução do pipeline toda vez que você realiza push de novos commits.
- Build: Este stage compila seu código (se necessário) e realiza um synth do cdk. A saída deste passo é um “cloud assembly”, o qual é utilizado para realizar todas as ações no restante do pipeline.
- UpdatePipeline: Este stage realiza mudanças no pipeline caso seja necessário. Por exemplo, se você atualizar o código para incluir um novo stage ao pipeline ou adicionar um novo asset a sua aplicação, ele automaticamente atualiza o pipeline para refletir as mudanças que você fez.
- PublishAssets: Este stage prepara e publica todos os arquivos de assets que você está utilizando na sua aplicação para o Amazon Simple Storage Service (Amazon S3) e todas as imagens de container para o Amazon Elastic Container Registry (Amazon ECR)em todas as contas e regiões das quais ele será consumido, para que possa ser utilizado nos stages posteriores.
- Deploy (DeployEKSClusters): Este stage implanta sua aplicação CDK em duas Stacks diferentes que descrevem seus clusters Amazon EKS, sua configuração e seus componentes..
- Validate: Este stage valida que as cargas de trabalho implantadas nos stages anteriores estão funcionais.
- Release (PromoteEnvironment): Este stage atualiza seu registro Route 53para apontar para o cluster definido como o ambiente de produção no seu código. Requer uma Aprovação Manual para executar.
Em alto nível, utilizaremos os passos a seguir para implantar a infraestrutura acima:
- Fork do repositório de exemplo
- Crie parâmetros e secrets específicos do seu ambiente.
- Crie subdomínios para cada cluster (e.g. my.domainand green.my.domain) no Amazon Route 53.
- Faça mudanças em seu fork e faça push delas.
- Implante as suas Stacks CDK.
Pré-requisitos:
- Uma conta AWS
- Um conta do GitHub
- Uma Zona Pública no seu Amazon Route 53, ou você pode registrar um novo domínio com Amazon Route 53ou utilizá-lo para um domínio existente.
- AWS CDKversão 1.121.0 ou superior.
- Utilizamos o AWS CLI para simplificar algumas atividades.
Começando
- Primeiro, crie um fork do nosso repositório de exemplo (https://github.com/aws-samples/aws-cdk-pipelines-eks-cluster) e faça o clone:
$ git clone https://github.com/YOUR-USERNAME/aws-cdk-pipelines-eks-cluster
- Crie um Personal Access Token no GitHub com os scopes repoe admin:repo_hook utilizando este link ou seguindo este passo a passo.
- Armazene o Token criado no passo anterior no AWS Secrets Managerutilizando:
$ aws secretsmanager create-secret --name github-oauth-token --description "Secret for GitHub" --secret-string TOKEN-GENERATED-PREVIOUS-STEP
- Armazena o Host Zone ID e nome do domínio no AWS Systems Manager Parameter Storeutilizando:
$ aws ssm put-parameter --name '/eks-cdk-pipelines/hostZoneId' --type String --value YOUR-HOSTED-ZONE-ID
$ aws ssm put-parameter --name '/eks-cdk-pipelines/zoneName' --type String --value YOUR-ZONE-NAME
Navegue para o Route 53 utilizando o AWS Management Console para acessar as suas Hosted Zones, e utilize o Domain Name como zoneName
e Hosted Zone ID como hostZoneId
.
- Você pode utilizar o script
parameters.sh
dentro do seu fork para preencher os dados acima de forma interativa:
$ chmod +x parameters.sh; ./parameters.sh
- Crie um subdomínio para cada cluster (e.g. my.domain and green.my.domain) no seu DNS:
Iremos criar um subdomínio por cluster, para que cada cluster tenha seu domínio e possamos chavear o tráfego entre o ambiente Blue e Green utilizando o pipeline. Para o nosso exemplo criaremos os subdomínios blue e green dentro do domínio que utilizamos no passo anterior.
Para um domínio gerenciado pelo Amazon Route 53 você pode utilizar os passos a seguir:
- Primeiro, crie uma hosted zone com o mesmo nome que o subdomínio que você deseja rotear o seu tráfego, por exemplo example.org(substitua example.org pelo seu próprio domínio).
- Obtenha os name servers que o Route 53 associou a nova hosted zone quando você a criou.
- Crie um novo registro NS na hosted zone do seu domínio pai (example.org), especifique os quatro name servers que obteve no passo 3.
- Repita os passos 1-3 utilizando green.example.com.
Ao final o seu domínio pai deve se parecer com o exemplo abaixo:
Faça as mudanças em seu fork
Temos apenas uma única mudança obrigatória no seu código:
- Atualize a definição do seu pipeline em
lib/eks-pipeline-stack.ts
para apontar para o seu próprio fork, substituindo aws-samples pelo seu nome de usuário no GitHub.
const pipeline = new CodePipeline(this, "Pipeline", { synth: new ShellStep("Synth", { input: CodePipelineSource.gitHub( "aws-samples/aws-cdk-pipelines-eks-cluster", "main", { authentication: cdk.SecretValue.secretsManager("github-oauth-token"), } ), commands: ["npm ci", "npm run build", "npx cdk synth"], }), pipelineName: "EKSClusterBlueGreen", });
Nota: Certifique-se de realizar commit e push do seu código após modificá-lo, caso contrário o pipeline será atualizado para o último commit que fez push durante o stage de SelfMutate.
O código acima irá criar os stages de Source, Build, UpdatePipeline e PublishAssets, e você apenas irá precisar definir os próximos stages. No nosso exemplos temos dois Stages, um para cada cluster EKS (blue e green) e eles executam em paralelo utilizando uma Wave:
const clusterANameSuffix = "blue"; const clusterBNameSuffix = "green"; const eksClusterStageA = new EksClusterStage(this, "EKSClusterA", { clusterVersion: eks.KubernetesVersion.V1_20, nameSuffix: clusterANameSuffix, }); const eksClusterStageB = new EksClusterStage(this, "EKSClusterB", { clusterVersion: eks.KubernetesVersion.V1_21, nameSuffix: clusterBNameSuffix, }); const eksClusterWave = pipeline.addWave("DeployEKSClusters");
Estamos reutilizando o EksClusterStage com dois parâmetros: clusterVersion, e nameSuffix, que também são utilizados como subdomínio para gerenciar nossos registros no DNS a partir do nosso cluster Kubernetes com o ExternalDNS. Este Stage implanta uma Stack que contem a definição do nosso cluster e seus principais componentes:
const cluster = new eks.Cluster(this, `acme-${props.nameSuffix}`, { clusterName: `acme-${props.nameSuffix}`, version: props.clusterVersion, defaultCapacity: 0, vpc, vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }], }); new EksManagedNodeGroup(this, "EksManagedNodeGroup", { cluster: cluster, nameSuffix: props.nameSuffix, }); new AWSLoadBalancerController(this, "AWSLoadBalancerController", { cluster: cluster, }); new ExternalDNS(this, "ExternalDNS", { cluster: cluster, hostZoneId: hostZoneId, domainFilters: [`${props.nameSuffix}.${zoneName}`], }); new ClusterAutoscaler(this, "ClusterAutoscaler", { cluster: cluster, }); new ContainerInsights(this, "ContainerInsights", { cluster: cluster, }); new Calico(this, "Calico", { cluster: cluster, }); new Prometheus(this, "Prometheus", { cluster: cluster, }); new Echoserver(this, "EchoServer", { cluster: cluster, nameSuffix: props.nameSuffix, domainName: zoneName, });
Você pode explorar o código de cada Construct em lib/infrastructure
e lib/app
; eles são os blocos de construção de aplicações AWS CDK. Um construct representa um componente de nuvem e encapsula tudo que o AWS CloudFormation precisa para criar o componente. A seguir temos uma descrição detalhada do papel de cada Construct que construímos:
- EksManagedNodeGroup: Descreve um Managed Node Grouputilizando um Custom Launch Template, especifica o tipo de instância como t3a.medium, define tags para Name e Environment, e também especifica a AMI (Amazon Linux 2) e um Node Role com as Managed Policies necessárias, e também adiciona AmazonSSMManagedInstanceCore, para que possa conectar aos Nodes utilizando AWS System Manager Session Manager.
- AWSLoadBalancerController: Instalação do AWS Load Balancer Controllera partir do repositório eks-charts utilizando Helm v3, configuração da Política IAM necessária, IRSA (IAM Role for Service Account) e custom CRDs.
- ExternalDNS: Instalação do ExternalDNS integrado com o Amazon Route 53utilizando Helm v3 e incluindo a configuração da Política IAM e IRSA (IAM Role for Service Account) para atualizar registros no Route 53.
- ClusterAutoscaler: Instalação do Kubernetes Cluster Autoscaler utilizando Helm v3, configuração das Políticas IAM necessárias e IRSA (IAM Role for Service Account).
- ContainerInsights: Configuração do Containers Insightsutilizando aws-for-fluent-bit e aws-cloudwatch-metrics utilizando o Helm v3 a partir do repositório eks-charts e as permissões necessárias com IRSA (IAM Role for Service Account).
- Calico: Instalação do tigera-operatorutilizando Helm v3 para segmentação de redes e segmentação de tenants.
- EchoServer: Instalação de uma aplicação de exemplo para validar que outros componentes funcionam, como o AWS Load Balancer Controllere o ExternalDNS.
Assim que concluir as alterações no seu código você deve realizar o bootstrap do seu ambiente:
$ npm install
$ cdk bootstrap
Após a conclusão, faça commit e push das mudanças realizadas no seu repositório utilizando:
$ git add .
$ git commit -m "Update cluster configuration."
$ git push
Primeira implantação
A primeira vez você terá que implantar o seu pipeline manualmente, utilizando o comando cdk deploy, mas depois disto, cada mudança que você fizer push para o seu repositório irá disparar o seu Pipeline, que irá se auto atualizar e executar.
Sua primeira execução vai levar um tempo já que recursos como EKS Cluster(s) e Managed Node Groups podem levar alguns minutos até estarem prontos. Você pode acompanhar o progresso da execução do seu pipeline através do AWS CodePipeline.
Se você verificar as suas Stacks do AWS CloudFormation encontrará uma Stack para o Pipeline (EksPipelineStack), e uma Stack (com Nested Stacks) para cada cluster EKS.
No output das Stack(s) dos seus clusters EKS são exibidos comandos para configurar o kubeconfig
para acessar o seu cluster, copie e execute-o, por exemplo:
$ aws eks update-kubeconfig --name acme-green --region <CLUSTER_REGION> --role-arn arn:aws:iam::<ACCOUNT_ID>:role/EKSClusterB-EKSCluster-acmegreenMastersRole
Então você poderá utilizar o kubectl
para executar seus comandos:
$ kubectl version
$ kubectl get po -A
A aplicação pode ser acessada diretamente pelo seu navegador utilizando o nome echoserver.subdomain.my.domain (substituindo subdomain.my.domain pelo seu subdomínio) ou apontando para o ELB do Ingress do echoserver no namespace echoserver, utilizando curl teríamos algo como:
$ curl -H “Host: app.<MEU_SUBDOMINIO>” elb.<AWS_REGION>.elb.amazonaws.com
A infraestrutura final provisionada para este pipeline será semalhante ao diagrama abaixo:
A definição do pipeline utiliza a variável prodEnv
para definir o cluster alvo, em outras palavras, onde os usuários do echoserver irão acessar. Quando defini-la e fizer push para o seu fork, essa mudança terá que ser aprovada manualmente no CodePipeline ao clicar em Review:
Após finalizar a atualização do registro DNS e a alteração for propagada, você pode verificar para onde o registro app.my.domain está apontando e acessar a aplicação através deste endpoint.
Limpeza
$ cdk destroy -y
$ aws cloudformation delete-stack —stack-name EKSClusterA-EKSCluster
$ aws cloudformation delete-stack —stack-name EKSClusterB-EKSCluster
$ aws cloudformation delete-stack —stack-name UpdateDNS-AppDns
Conclusão
Neste post mostramos como utilizar o AWS CDK e CDK Pipelines para implantar e gerenciar todo o ciclo de vida dos seus clusters Amazon EKS. Esta abordagem de Infraestrutura como Código entrega mudanças através de um pipeline automatizado e padronizado que também é definido em AWS CDK. Permitindo que você possa implantar e atualizar clusters de maneiras consistente entre diferentes versões, ambientes, contas e regiões, enquanto mantem registro destas mudanças através do Git. O código de exemplo provê diversos exemplos de componentes comumente instalados em clusters EKS como AWS Load Balancer Controller, Calico for Network Policies, ExternalDNS, Cluster Autoscaler, Metrics Server e Container Insights.
Sobre os autores
Jairo Silva Junior é Arquiteto de Soluções na AWS no time de Public Sector com foco em Governo. Anteriormente atuou em diversos papéis no ciclo de entrega de software, como desenvolvimento, arquitetura e operação, em diversos tipos de empresas. Possui Mestrado em Ciência da Computação e é apaixonado por viagens e comida.
Davi Garcia é Arquiteto Especialista de Soluções, focado em Modernização de Aplicações na AWS, e ajuda clientes de diversos segmentos na América Latina. Possui larga experiência em plataforma de containers e automação de infraestrutura. Ele gosta de contribuir com projetos open-source, se divertir em família com jogos de tabuleiros e velejar.