DEV Community

Stefano Martins
Stefano Martins

Posted on • Updated on

Subindo um cluster Kubernetes no AWS EKS com eksctl

Por mais surpreendente que seja, subir um cluster Kubernetes no AWS EKS há uns tempos atrás não era uma tarefa lá muito fácil, ainda mais comparado com outros cloud providers como o GCP. Foi então que surgiu o eksctl, ferramenta desenvolvida em Go pela comunidade e que hoje é patrocinada pela Weave, sendo o CLI oficial da AWS para gestão do EKS. Com ela, nós podemos criar e gerenciar clusters utilizando arquivos YAML e por debaixo dos panos são utilizadas stacks no CloudFormation para gestão dos recursos necessários.

Instalação

Assim como outros binários escritos em Go, a instalação é super simples:

curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv m/tmp/eksctl /usr/local/bin
eksctl version
Enter fullscreen mode Exit fullscreen mode

Configuração de uma conta AWS

Não utilize sua conta root para a criação do cluster. Aliás, não utilize sua conta root para nada que não seja criar outras contas ou habilitar e gerir recursos muito específicos da AWS.

Caso seja sua conta pessoal, crie um outro usuário. Agora, caso seja uma conta corporativa, eu sugiro que habilite o AWS Organizations.

Crie um novo usuário no IAM com as seguintes policies atreladas a ele:

  • AmazonEC2FullAccess

  • AWSCloudFormationFullAccess

  • EksAllAccess

  • IamLimitedAccess

As duas primeiras são policies gerenciadas pela própria AWS e você não precisará criá-las. Já as duas últimas terão o seguinte conteúdo:

EkaAllAccess:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "eks:*",
            "Resource": "*"
        },
        {
            "Action": [
                "ssm:GetParameter",
                "ssm:GetParameters"
            ],
            "Resource": [
                "arn:aws:ssm:*:<account_id>:parameter/aws/*",
                "arn:aws:ssm:*::parameter/aws/*"
            ],
            "Effect": "Allow"
        },
        {
             "Action": [
               "kms:CreateGrant",
               "kms:DescribeKey"
             ],
             "Resource": "*",
             "Effect": "Allow"
        },
        {
             "Action": [
               "logs:PutRetentionPolicy"
             ],
             "Resource": "*",
             "Effect": "Allow"
        }        
    ]
}
Enter fullscreen mode Exit fullscreen mode

IamLimitedAccess:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateInstanceProfile",
                "iam:DeleteInstanceProfile",
                "iam:GetInstanceProfile",
                "iam:RemoveRoleFromInstanceProfile",
                "iam:GetRole",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:AttachRolePolicy",
                "iam:PutRolePolicy",
                "iam:ListInstanceProfiles",
                "iam:AddRoleToInstanceProfile",
                "iam:ListInstanceProfilesForRole",
                "iam:PassRole",
                "iam:DetachRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:GetRolePolicy",
                "iam:GetOpenIDConnectProvider",
                "iam:CreateOpenIDConnectProvider",
                "iam:DeleteOpenIDConnectProvider",
                "iam:TagOpenIDConnectProvider",
                "iam:ListAttachedRolePolicies",
                "iam:TagRole",
                "iam:GetPolicy",
                "iam:CreatePolicy",
                "iam:DeletePolicy",
                "iam:ListPolicyVersions"
            ],
            "Resource": [
                "arn:aws:iam::<account_id>:instance-profile/eksctl-*",
                "arn:aws:iam::<account_id>:role/eksctl-*",
                "arn:aws:iam::<account_id>:policy/eksctl-*",
                "arn:aws:iam::<account_id>:oidc-provider/*",
                "arn:aws:iam::<account_id>:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup",
                "arn:aws:iam::<account_id>:role/eksctl-managed-*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:GetRole"
            ],
            "Resource": [
                "arn:aws:iam::<account_id>:role/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "iam:AWSServiceName": [
                        "eks.amazonaws.com",
                        "eks-nodegroup.amazonaws.com",
                        "eks-fargate.amazonaws.com"
                    ]
                }
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Substitua <account_id> pelo ID da sua conta AWS.

Gere agora para o usuário uma credencial e guarde a access key ID e a secret key. O eksctl necessita que o AWS CLI esteja instalado. Caso não o tenha, execute:

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws configure
Enter fullscreen mode Exit fullscreen mode

Serão solicitados dados como região padrão da AWS (us-east-1, por exemplo), a access key ID e a secret key para criação do perfil padrão (default). Agora, caso você já tenha o AWS CLI instalado e configurado, você pode criar o novo perfil manualmente editando o arquivo ~/.aws/credentials.

Atenção: quando possuir múltiplos profiles dentro do seu arquivo ~/.aws/credentials, informe qual você quer utilizar setando o valor da variável AWS_PROFILE com o comando export AWS_PROFILE=meu_perfil.

Criação do cluster

O eksctl por padrão criará o cluster em um VPC separado utilizando instâncias do tipo m5.large. Isso traz algumas implicações:

  • Pela quota padrão, pode-se ter no máximo cinco VPCs por conta;

  • Caso esteja utilizando um ambiente de estudos, laboratórios ou testes, esse tipo de instância pode ser considerado um pouco caro;

  • Caso você precise que as aplicações do cluster comuniquem-se com recursos da AWS - uma instância no RDS, por exemplo - localizados em outro VPC, é necessária a configuração de VPC Peering e de rotas para que isso ocorra.

Nosso cluster terá as seguintes características:

  • Ele será chamado de demo-eks e estará localizado em us-east-1 (Virgínia do Norte);

  • Quatro subredes serão criadas no novo VPC sendo duas públicas e duas privadas. Por padrão, o eksctl aloca os nós do cluster nas subredes privadas;

  • Será criado um nodegroup chamado ng-01 com um auto scaling group, com no mínimo uma instância e no máximo 5 do tipo t3.medium;

  • Policies do IAM para external-dns, auto-scaling, cert-manager, ebs e efs. Falaremos mais sobre esses caras logo menos.

---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: demo-eks
  region: us-east-1

managedNodeGroups:
  - name: ng-01
    instanceType: t3.medium
    desiredCapacity: 2
    minSize: 1
    maxSize: 5
    iam:
      withAddonPolicies:
        autoScaler: true
        externalDNS: true
        certManager: true
        ebs: true
        efs: true
Enter fullscreen mode Exit fullscreen mode

Como pode-se notar, o YAML é um manifesto muito parecido com os utilizados no Kubernetes.

Salve o arquivo acima como demo-eks.yaml e agora apenas execute eksctl cluster create -f demo-eks.yaml --kubeconfig=demo-eks-kubeconfig e deixe a mágica acontecer (naturalmente). O eksctl gerará a stack (ou stacks) dentro do CloudFormation e criará todos os recursos necessários, o que demorará cerca de quinze a vinte minutos (sim, é um pouco demorado) e pronto, você terá um cluster Kubernetes para uso no AWS EKS, com as credenciais para acesso ao mesmo no arquivo demo-eks-kubeconfig.yaml.

Importante: Caso a opção --kubeconfig seja omitida, o eksctl gerará o arquivo de acesso em ~/.kube/config, o que pode ser um problema caso você já possua outros clusters configurados. Caso seja o caso, você pode criar um diretório chamado ~/.kube/configs, para armazenar os arquivos e alternar entre os mesmos setando a variável de KUBECONFIG apontando para o caminho do arquivo desejado (ex: export KUBECONFIG=${HOME}/.kube/configs/demo-eks-kubeconfig). Este passo é essencial para que o kubectl consiga conectar-se ao cluster.

Instalação do Helm

Muito provavelmente você já ouviu falar do Helm, mas se isso não ocorreu, relaxa. O Helm é a grosso modo um gerenciador de pacotes para o Kubernetes. Esses pacotes são disponibilizados na forma de charts e com ele podemos criar os manifestos das nossas aplicações de uma forma mais dinâmica, organizada, parametrizável, e posteriormente hospedando-o em repositórios para deployment.

Curiosidade inútil: "chart" pode ser traduzido para "carta de navegação". "Kubernetes" = "timão". Sacou, sacou? Hehehe.

Sua instalação é super simples:

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
Enter fullscreen mode Exit fullscreen mode

Agora, caso você utilize uma distribuição Debian-like e deseje utilizar um pacote:

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
Enter fullscreen mode Exit fullscreen mode

Instalação de add-ons

Metrics Server

O Metrics Server é um recurso necessário para o uso de HPAs (Horizontal Pod Autoscalers). HPAs são extremamente úteis quando nós queremos escalonar nossas aplicações horizontalmente quando as mesmas necessitam de mais recursos.

helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install --create-namespace -n metrics-server metrics-server metrics-server/metrics-server
Enter fullscreen mode Exit fullscreen mode

Prometheus

O Prometheus é uma solução de monitoramento. Caso você utilize o Lens ou outro dashboard para monitoramento dos seus clusters, ele irá fornecer gráficos de uso de CPU, memória, rede e disco para o cluster, nodes, controllers e pods. O Prometheus é comumente utilizado junto com o Grafana.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install --create-namespace -n kube-prometheus prometheus bitnami/kube-prometheus
Enter fullscreen mode Exit fullscreen mode

ExternalDNS

O ExternalDNS sincroniza recursos do Kubernetes com registros DNS externos hospedados em providers como AWS Route 53, Google Cloud DNS, AzureDNS, etc. Com ele, nós não precisamos manualmente criar entradas DNS toda vez que subimos uma nova aplicação no nosso cluster. Bacana né?

helm install external-dns stable/external-dns \
  --create-namespace \
  --namespace external-dns \
  --set provider=aws \
  --set aws.zoneType=public \
  --set txtOwnerId=<ZONE_ID>
  --set policy=sync
  --set registry=txt
  --set interval=20s
  --set domainFilters[0]=<DOMAIN>
Enter fullscreen mode Exit fullscreen mode

Altere <ZONE_ID> para o ID da sua zona no AWS Route 53 e <DOMAIN> para o seu domínio no formato dominio.com.

cert-manager

O cert-manager é um CRD (Custom Resource Definition) que gerará dinamicamente certificados TLS/SSL para as nossas aplicações utilizando o Let's Encrypt (embora ele também suporte outros issuers).

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install --create-namespace -n cert-manager cert-manager bitnami/cert-manager \
  --create-namespace \
  --namespace cert-manager \
  --set installCRDs=true
Enter fullscreen mode Exit fullscreen mode

Com o chart instalado, é necessária a criação de dois CRDs, cada qual representando um issuer diferente do Let's Encrypt, um para produção e outro para staging. A diferença entre ambos é basicamente a limite de requisições que nós podemos realizar. Em ambiente de testes, prefira utilizar o ambiente de staging.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <meu_email>
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx
Enter fullscreen mode Exit fullscreen mode
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: <meu_email>
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx
Enter fullscreen mode Exit fullscreen mode

Nota: Substitua <meu_email> pelo seu endereço de e-mail. Caso você esteja utilizando um Ingress diferente do Nginx, é necessário alterar os manifestos acima setando a classe adequada.

Execute o comando kubectl apply -f cert-manager-staging.yaml -f cert-manager-prod.yaml para criá-los no cluster.

NGINX Ingress Controller

É por meio do NGINX Ingress Controller que nós permitiremos acesso às nossas aplicações. Você pode enxergar um Ingress como um proxy reverso.

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace
Enter fullscreen mode Exit fullscreen mode

Auto Scaling Group

Um dos recursos mais legais de cloud computing é a elasticidade. Imagine que durante o ano todo nós temos uma demanda relativamente constante, mas em certas épocas - como Black Friday - elas podem aumentar consideravelmente e logo você vai precisar de mais nós dentro dos seus nodegroups para suprir as necessidades dos seus pods. Para que não tenhamos que fazer isso manualmente, vamos adicionar a funcionalidade de escalonamento automático do nosso nodegroup.

helm repo add autoscaler https://kubernetes.github.io/autoscaler
helm install --create-namespace -n cluster-autoscaler autoscaler/cluster-autoscaler \
  --set 'autoDiscovery.clusterName=<cluster_name>'
Enter fullscreen mode Exit fullscreen mode

Nota: Substitua <cluster_name> pelo nome do seu cluster (neste caso, demo-eks).

Para testar, após subir uma aplicação qualquer, aumente a quantidade de réplicas para um número não atendido pelos recursos disponíveis no seu nodegroup. O cluster-autoscaler disponibilizará novos nós até o número máximo setado no nodegroup. Quando a quantidade de pods consumir menos do que 50% dos recursos de um nó por mais do que 10 minutos, este nó será removido do nodegroup. Bacana né?

Subindo uma aplicação

Com o nosso cluster montado e os add-ons instalados, está na hora de subir uma aplicação teste. Abaixo os manifestos dentro de um único arquivo hello-kubernetes.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: hello-kubernetes
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: hello-kubernetes
  name: hello-kubernetes
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-kubernetes
  template:
    metadata:
      labels:
        app: hello-kubernetes
    spec:
      containers:
      - name: hello-kubernetes
        image: paulbouwer/hello-kubernetes:1.5
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "128Mi"
            cpu: "250m"
          limits:
            memory: "256Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  namespace: hello-kubernetes
  name: hello-kubernetes
spec:
  type: ClusterIP
  selector:
    app: hello-kubernetes
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: hello-kubernetes
  name: hello-kubernetes-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    external-dns.alpha.kubernetes.io/hostname: <app.DOMINIO>
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - <app.DOMINIO>
      secretName: <app.DOMINIO>
  rules:
    - host: <app.DOMINIO>
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: hello-kubernetes
              port: 
                number: 80
Enter fullscreen mode Exit fullscreen mode

Nota: Substitua ` pelo domínio que você quer utilizar na sua aplicação, algo como app-01.juquinha.net.

Se tudo der certo, eis o que devera acontecer:

  • Entradas serão criadas dentro da sua zona DNS lá dentro do AWS Route 53 de acordo com o que foi configurado no seu ingress e nas configurações do seu ExternalDNS e dentro de alguns minutos você poderá acessar sua aplicação por meio desse nome. Você pode monitorar a propagação por meio da ferramenta dig;

  • Um certificado será gerado automaticamente para sua aplicação. Isso pode demorar um tempo para aplicações novas;

  • Nós alocamos duas réplicas para a aplicação e aqui cabe uma ressalva muito importante: O Kubernetes por padrão fará um balanceamento do tipo round-robin entre os pods. Em grande parte das aplicações web atuais nós trabalhamos com sessões e como consequência disso, pode ocorrer o cenário em que o usuário se autentica em um pod, e na próxima requisição ser redirecionado ao outro, onde a sessão não existe. Como consequência, são apresentados comportamentos estranhos para o usuário, como o redirecionamento para a tela de login, mensagens de "Não autorizado", intermitência no acesso, etc. Para que isso não ocorra, nós normalmente utilizamos algum serviço como o Redis, memcached (estes podendo ou não serem no AWS ElastiCache) ou a funcionalidade de sticky-sessions, em que um usuário é constantemente enviado para o mesmo pod.

 Conclusão

Kubernetes é um assunto extremamente extenso e complexo é há uma infinidade de possibilidades com ele. Apresentei aqui algumas delas que podem ser utilizadas não só no AWS EKS, mas em instalações on-premises (com excessão do cluster-autoscaler, a menos que você esteja utilizando OpenStack na sua empresa). Existe também uma série de melhorias de segurança que podem ser realizadas no cluster montado neste artigo. Espero que vocês tenham curtido!

Referências

Top comments (0)