AWS sin VMs - Capitulo 1: EKS Fargate

Objetivo

En esta serie de artículos intentaré cumplir un sueño personal, que es implementar una arquitectura de microservicios production-ready en AWS sin tener que administrar ni una sola maquina virtual. En términos de AWS, no vamos a crear ninguna instancia EC2.

Vamos a hacer uso extensivo de los servicios manejados de AWS, incluyendo Elastic Kubernetes Service, API Gateway, AWS WAF, ElasticSearch, Grafana, entre otros.

A continuación les presento un diagrama de lo que vamos a estar desplegando en éste y los próximos capítulos.

En esta primera edición vamos a enfocarnos en el despliegue de Kubernetes, haciendo uso de Fargate, lo que nos va a permitir tener un cluster de K8s totalmente manejado por AWS.

Requisitos

Vamos a asumir que tienen un conocimiento básico de AWS, Kubernetes y Terraform, y de que cuentan con las respectivas herramientas instaladas en sus máquinas.

El listado de herramientas a utilizar en este artículo son:

EKS Fargate

AWS nos presenta dos principales formas de desplegar un cluster de Kubernetes. Por un lado, tenemos la posibilidad de que AWS se haga cargo sólo del plano de control, dejándonos a nosotres la gestión de los nodos de cómputo.

Por otro lado, tenemos Fargate, que AWS describe como un motor de cómputo serverless que es compatible tanto con EKS como con ECS (el servicio de contenedores propio de AWS). Esto nos permite dejar a AWS la gestión completa de los nodos, tanto del plano de control, como de los nodos de cómputo. Como ya dijimos al comienzo, nuestro objetivo es no tener que manejar ninguna instancia, por lo cual iremos por esta alternativa.

Vamos a utilizar Terraform para crear nuestro cluster EKS y los recursos que éste necesita. A continuación les comparto un extracto relevante de éste.

resource "aws_eks_cluster" "main" {
  name     = "${var.name}-${var.environment}"
  role_arn = aws_iam_role.eks_cluster_role.arn

  enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]

  vpc_config {
    subnet_ids = concat(aws_subnet.public.*.id, aws_subnet.private.*.id)
  }

  timeouts {
    delete = "30m"
  }

  depends_on = [
    aws_cloudwatch_log_group.eks_cluster,
    aws_iam_role_policy_attachment.AmazonEKSClusterPolicy,
    aws_iam_role_policy_attachment.AmazonEKSServicePolicy
  ]
}

Clonamos el repositorio con los archivos de Terraform y desplegamos el cluster. Pueden editar el archivo test.tfvars para modificar parámetros como el nombre del cluster, o los CIDR de las subredes.

git clone https://github.com/BenjaDiaz/aws-sin-vms
cd aws-sin-vms/1-fargate
terraform apply -var-file=test.tfvars

Una vez creado el cluster, podemos configurar nuestro kubeconfig. Vamos a cargar en una variable de ambiente el nombre de nuestro cluster para así facilitarnos la vida.

CLUSTER_NAME=pinkiepie-test
aws --region us-east-1 eks update-kubeconfig --name $CLUSTER_NAME

Ahora deben configurar kubectl para que utilice el contexto correspondiente a nuestro nuevo cluster. Les recomiendo que para el manejo de contextos utilicen kubectx.

Ahora debiésemos poder listar los pods disponibles. Por ahora, sólo debiesen figurar los de coredns.

kubectl -n kube-system get pods

Fargate profiles

Antes de poder desplegar un pod en Fargate, es necesario definir un perfil que establezca qué pods van a poder ejecutarse sobre éste. A continuación crearemos uno para todos los recursos del namespace ponyville. Sólo podremos desplegar pods que tengan un perfil de Fargate asociado.

eksctl create fargateprofile \
  --cluster $CLUSTER_NAME \
  --name ponyville \
  --namespace ponyville

CoreDNS Fix

Por defecto, CoreDNS asume que se va a ejecutar sobre una instancia EC2. Para poder hacerlo funcionar en Fargate, es necesario hacer algunas modificaciones.

Primero creamos un perfil de Fargate para los pods de core-dns, que se encuentran en el namespace de kube-system:

eksctl create fargateprofile \
  --cluster $CLUSTER_NAME \
  --name coredns \
  --namespace kube-system \
  --labels k8s-app=kube-dns

Luego, editamos los pods de CoreDNS para que se puedan desplegar sobre Fargate:

kubectl patch deployment coredns \
    -n kube-system \
    --type json \
    -p='[{"op": "remove", "path": "/spec/template/metadata/annotations/eks.amazonaws.com~1compute-type"}]'

Al cabo de unos minutos debiesen poder ver los pods de coredns en estado Ready.

La documentación oficial de AWS para esto la encuentran aquí:
https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html#fargate-gs-coredns

Ingress controller

Para poder acceder a nuestros servicios vía Ingress, necesitamos configurar un Ingress Controller que se haga cargo de la creación de Load Balancers en AWS. Hay una gran gama de alternativas, pero en nuestro caso utilizaremos ALB Ingress Controller.

Primero necesitamos habilitar la integración de roles IAM con Service Accounts de Kubernetes. Para esto hay que asociar un provider OIDC.

eksctl utils associate-iam-oidc-provider --cluster pinkiepie-test --approve

Luego creamos la politica IAM que va a utilizar el controller.

curl -o alb-ingress-iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/master/docs/examples/iam-policy.json

aws iam create-policy --policy-name ALBIngressControllerIAMPolicy --policy-document file://alb-ingress-iam-policy.json

El controller también va a requerir de una ServiceAccount con los permisos respectivos. Primero creamos el ClusterRole y el RoleBinding.

cat > rbac-role.yaml <<-EOF
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/name: alb-ingress-controller
  name: alb-ingress-controller
rules:
  - apiGroups:
      - ""
      - extensions
    resources:
      - configmaps
      - endpoints
      - events
      - ingresses
      - ingresses/status
      - services
    verbs:
      - create
      - get
      - list
      - update
      - watch
      - patch
  - apiGroups:
      - ""
      - extensions
    resources:
      - nodes
      - pods
      - secrets
      - services
      - namespaces
    verbs:
      - get
      - list
      - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/name: alb-ingress-controller
  name: alb-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: alb-ingress-controller
subjects:
  - kind: ServiceAccount
    name: alb-ingress-controller
    namespace: kube-system
EOF

kubectl apply -f rbac-role.yaml

Ahora podemos crear la ServiceAccount.

AWS_ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account')

eksctl create iamserviceaccount \
--name alb-ingress-controller \
--namespace kube-system \
--cluster $CLUSTER_NAME \
--attach-policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/ALBIngressControllerIAMPolicy \
--approve

Antes de desplegar el controller, creamos otro perfil de Fargate para esto:

eksctl create fargateprofile \
  --cluster $CLUSTER_NAME \
  --name alb-ingress-controller \
  --namespace kube-system \
  --labels "app.kubernetes.io/name"=alb-ingress-controller

Finalmente desplegamos el controller.

VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-vpc" | jq -r '.Vpcs[0].VpcId')
AWS_REGION=us-east-1

cat > alb-ingress-controller.yaml <<-EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: alb-ingress-controller
  name: alb-ingress-controller
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: alb-ingress-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: alb-ingress-controller
    spec:
      containers:
      - name: alb-ingress-controller
        args:
        - --ingress-class=alb
        - --cluster-name=$CLUSTER_NAME
        - --aws-vpc-id=$VPC_ID
        - --aws-region=$AWS_REGION
        image: docker.io/amazon/aws-alb-ingress-controller:v1.1.6
      serviceAccountName: alb-ingress-controller
EOF
kubectl apply -f alb-ingress-controller.yaml

Hola Mundo

Ya tenemos todo listo para desplegar una aplicación de prueba.

kubectl create namespace ponyville
kubectl -n ponyville apply -f 2048.yaml

Ahora esperen unos minutos y debiesen poder ver el campo ADDRESS en el estado del ingress.

kubectl -n ponyville get ingresses.networking.k8s.io

Debiesen poder acceder a esa URL y visualizar la aplicación de prueba.

Resumen

Gracias por llegar al final del primer capítulo de esta aventura! Desplegamos un cluster EKS, configuramos un Ingress Controller y desplegamos una aplicación de prueba. Todo esto sin tener que levantar ninguna máquina virtual!

En el próximo episodio vamos a automatizar la creación de registros en Route53 y luego configuraremos un API Gateway para poder acceder a un par de servicios de prueba que ejecutaremos en nuestro cluster EKS.

Nos vemos!

Recursos

Todos los recursos que vayamos a utilizar en esta serie de artículos los pueden encontrar aquí:

Referencias

24