skip to content
A covered up pug in the woodsBlog

AWS EKS Fargate with Terraform (2)

/ 14 min read

En la segunda parte de este tutorial veremos como desplegar nuestras aplicaciones al cluster

repositorio parte 1 parte 3

Crear un registry en AWS ECR para nuestras imagenes de frontend y backend

Necesitaremos deployar las imagenes de nuestro frontend y backend al registry de amazon (ECR) para que nuestros pods puedan tomar pullear esas imagenes.

Para crear el recurso ECR debemos crear el siguiente archivo terraform/9-ecr.tf:

resource "aws_ecr_repository" "frontend" {
  name = "frontend"
}

resource "aws_ecr_repository" "backend" {
  name = "backend"
}

Pusheamos para correr el pipeline y si vamos a nuestro ECR deberiamos ver lo siguiente:

ecr

Pushear nuestras imagenes del frontend y backend

Para el frontend crearemos una aplicacion simple de NextJS y para nuestro backend un servidor HTTP simple con Kotlin. El codigo del frontend y backend pueden copiarlo del repositorio de este tutorial, lo que veremos aqui es como crear los Dockerfile y como pushear las imagenes al registry con GitHub Actions.

Backend

Una vez que ya tengamos el codigo de nuestro backend (tomar de referencia el del repositorio), debemos crear el siguiente Dockerfile para construir la imagen de nuestro back.

FROM gradle:7.2.0-jdk16 as builder

USER root
ARG COMMIT_SHA

ADD . .

RUN printf %s ".${COMMIT_SHA}" >> VERSION

RUN gradle --parallel --build-cache -Dorg.gradle.console=plain -Dorg.gradle.daemon=false :shadowJar

###########################################################################
FROM openjdk:16-alpine

ENV PORT 6060

WORKDIR /app

COPY --from=builder /home/gradle/build/libs/backend_api.jar backend_api.jar

EXPOSE $PORT

CMD java -Xms256m -Xmx256m -Xss512k -jar backend_api.jar

Y dentro de la carpeta .github/workflows crearemos el archivo backend.yml para pushear nuestra imagen al registry de amazon previamente creado para nuestro backend.

on:
  push:
    branches:
      - main
    paths:
      - backend/*
      - .github/workflows/backend.yml

jobs:
  backend-push-image:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Deploy api
        run: |
          cd backend
          echo "Build docker image"
          docker build --tag backend:latest --build-arg COMMIT_SHA=${GITHUB_SHA} .
          echo "Push image to ECR"
          docker run --rm -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} amazon/aws-cli ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend
          docker tag backend:latest 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend:latest
          docker push 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend

La funcion de este archivo es que cada vez que se modifique el backend, se active el pipeline para pushear la nueva imagen del backend al registry de amazon. Una vez que pusheemos podemos corroborrar que en repositorio de nuestra imagen en ECR esta la imagen buildeada.

Frontend

Una vez que ya tengamos el codigo de nuestro frontend (tomar de referencia el del repositorio), debemos crear el siguiente Dockerfile para construir la imagen de nuestro front.

# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
  else echo "Lockfile not found." && exit 1; \
  fi

# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN yarn build

# If using npm comment out above and use below instead
# RUN npm run build

# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]

Y dentro de la carpeta .github/workflows crearemos el archivo frontend.yml para pushear nuestra imagen al registry de amazon previamente creado para nuestro backend.

on:
  push:
    branches:
      - main
    paths:
      - frontend/*
      - .github/workflows/frontend.yml

jobs:
  frontend-push-image:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Push frontend image
        run: |
          cd frontend
          echo "Build docker image"
          docker build --tag frontend:latest --build-arg COMMIT_SHA=${GITHUB_SHA} .
          echo "Push image to ECR"
          docker run --rm -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} amazon/aws-cli ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend
          docker tag frontend:latest 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend:latest
          docker push 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend

La funcion de este archivo es que cada vez que se modifique el frontend, se active el pipeline para pushear la nueva imagen del frontend al registry de amazon. Una vez que pusheemos podemos corroborrar que en repositorio de nuestra imagen en ECR esta la imagen buildeada.

Desplegar nuestros pods

El proximo paso es desplegar nuestro backend y frontend a nuestro cluster. Para eso debemos crear otro Fargate profile, ya que nuestro pods estaran en un namespace diferente al de kube-system. Recordemos que por cada namespace necesitamos un Fargate profile para otorgarle privilegios a Fargate para que pueda crear nodos y asignarlos a nuestros pods.

Para crear el Fargate profile crearemos el archivo terraform/8-staging-profile.tf con lo siguiente:

resource "aws_eks_fargate_profile" "staging" {
  cluster_name           = aws_eks_cluster.cluster.name
  fargate_profile_name   = "staging"
  pod_execution_role_arn = aws_iam_role.eks-fargate-profile.arn

  # These subnets must have the following resource tag:
  # kubernetes.io/cluster/<CLUSTER_NAME>.
  subnet_ids = [
    aws_subnet.private-us-east-1a.id,
    aws_subnet.private-us-east-1b.id
  ]

  selector {
    namespace = "staging"
  }
}

En la raiz del proyecto crearemos una carpeta llamada k8s y ahi escribiremos nuestros archivos de Kubernetes.

Primero el namespace de staging en k8s/amespace.yaml:

---
apiVersion: v1
kind: Namespace
metadata:
  name: staging

El frontend en k8s/rontend-deployent.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: staging
spec:
  selector:
    matchLabels:
      run: frontend
  # remove replica if using gitops
  replicas: 1
  template:
    metadata:
      labels:
        run: frontend
    spec:
      containers:
        - name: frontend
          image: 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend # Es importante colocar la direccion de su registry
          ports:
            - containerPort: 3000
          resources:
            limits:
              cpu: 200m
              memory: 256Mi
            requests:
              cpu: 200m
              memory: 256Mi

Y el backend en backend-deployment.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: staging
spec:
  selector:
    matchLabels:
      run: backend
  # remove replica if using gitops
  replicas: 1
  template:
    metadata:
      labels:
        run: backend
    spec:
      containers:
        - name: backend
          image: 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend # Es importante colocar la direccion de su registry
          ports:
            - containerPort: 6060
          resources:
            limits:
              cpu: 200m
              memory: 256Mi
            requests:
              cpu: 200m
              memory: 256Mi

Para probar que nuestros archivos esten correctos, podemos aplicarlos con kubectl antes de automatizar este proceso. Para eso debemos correr los siguientes comandos.

ldamore@Desktop/aws-eks-fargate:~ kubectl apply -f k8s/namespace.yaml
ldamore@Desktop/aws-eks-fargate:~ kubectl apply -f k8s/frontend-deployment.yaml
ldamore@Desktop/aws-eks-fargate:~ kubectl apply -f k8s/backend-deployment.yaml

Luego de unos minutos si ejecutamos el comando kubectl get pods -n staging podemos ver que nuestros pods ya estan deployados.

Ahora para automatizar este proceso debemos modificar nuestros archivos .github/workflows/backend.yml y .github/workflows/frontend.yml lo que se agrega a estos archivos es la configuracion de aws-cli, la instalacion de kubectl, actualizar kube config para que kubectl este apuntando a nuestro cluster y por ultimo aplicar los archivos correspondientes de kubernetes.

Dentro de .github/workflows/frontend.yml:

on:
  push:
    branches:
      - main
    paths:
      - frontend/*
      - .github/workflows/frontend.yml

jobs:
  frontend-push-image:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Install kubectl
        uses: azure/setup-kubectl@v1
        with:
          version: 'v1.21.3'
        id: install
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      - name: Push frontend image
        run: |
          cd frontend
          echo "Build docker image"
          docker build --tag frontend:latest --build-arg COMMIT_SHA=${GITHUB_SHA} .
          echo "Push image to ECR"
          docker run --rm -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} amazon/aws-cli ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend
          docker tag frontend:latest 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend:latest
          docker push 186990837788.dkr.ecr.us-east-1.amazonaws.com/frontend
      - name: Update kube config
        run: aws eks update-kubeconfig --name demo --region us-east-1
      - name: Deploy image to Amazon EKS
        run: |
          kubectl apply -f k8s/frontend-deployment.yaml

Dentro de .github/workflows/backend.yml:

on:
  push:
    branches:
      - main
    paths:
      - backend/*
      - .github/workflows/backend.yml

jobs:
  backend-push-image:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Install kubectl
        uses: azure/setup-kubectl@v1
        with:
          version: 'v1.21.3'
        id: install
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      - name: Push backend image
        run: |
          cd backend
          echo "Build docker image"
          docker build --tag backend:latest --build-arg COMMIT_SHA=${GITHUB_SHA} .
          echo "Push image to ECR"
          docker run --rm -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} amazon/aws-cli ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend
          docker tag backend:latest 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend:latest
          docker push 186990837788.dkr.ecr.us-east-1.amazonaws.com/backend
      - name: Update kube config
        run: aws eks update-kubeconfig --name demo --region us-east-1
      - name: Deploy image to Amazon EKS
        run: |
          kubectl apply -f k8s/backend-deployment.yaml

Crear servicios para nuestros pods

Es tiempo de crear servicios a nuestros pods para que proximamente esten expuestos a internet a traves de un ingress.

Dentro de la carpeta k8s crearemos un archivo frontend-service.yaml y otro backend-service.yaml

En k8s/frontend-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: staging
  labels:
    app: frontend
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
    - port: 80
      targetPort: 3000

Y en k8s/backend-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: backend
  namespace: staging
  labels:
    app: backend
spec:
  type: NodePort
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 6060

Para crear los servicios deberemos agregar en: .github/workflows/backend.yml

luego de la linea para aplicar el deployment, la siguiente linea kubectl apply -f k8s/backend-service.yaml

y en: .github/workflows/frontend.yml

luego de la linea para aplicar el deployment, la siguiente linea kubectl apply -f k8s/frontend-service.yaml

Para poder ver los servicios que creamos podemos ejecutar kubectl get services -n staging

Mejorar la estabilidad de nuestros pods con Pod Disruption Budget

Amazon EKS debe periodicamente patchear los pods que corren en nodos de AWS Fargate para mantenerlos seguros. A veces esto significa que los pods necesitan ser recreados. Para limitar el impacto en nuestra aplicacion debemos crear un pod disruption budget apropiado para controlar el numero de pods que estan caidos al mismo tiempo.

Para eso dentro de la carpeta k8s crearemos los siguientes archivos:

k8s/frontend-pdb.yaml:

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: frontend
  namespace: staging
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      run: frontend

k8s/ackend-pdb.yaml:

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: backend
  namespace: staging
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      run: backend

Para crear los pod disruption badgets deberemos agregar en: .github/workflows/backend.yml

luego de la linea para aplicar el service, la siguiente linea kubectl apply -f k8s/backend-pdb.yaml

y en: .github/workflows/frontend.yml

luego de la linea para aplicar el service, la siguiente linea kubectl apply -f k8s/frontend-pdb.yaml

Para poder ver los servicios que creamos podemos ejecutar kubectl get pdb -n staging

Crear IAM OIDC provider usando Terraform

Podemos asociar un IAM role a una Kubernetes service account. Esta service account puede proveer AWS permissions a los container en cualquier pod que este usando esa service account.

Con este feature no es mas necesario configurar a mano los permisos de los nodos para que los pods puedan hacer llamadas a AWS APIs. Esta service account la necesitaremos para un pod que va a necesitar tener permisos para crear load balancers.

Para eso dentro de la carpeta k8s crearemos el archivo 10-iam-oidc.tf :

data "tls_certificate" "eks" {
  url = aws_eks_cluster.cluster.identity[0].oidc[0].issuer
}

resource "aws_iam_openid_connect_provider" "eks" {
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
  url             = aws_eks_cluster.cluster.identity[0].oidc[0].issuer
}

Una vez que pusheamos podemos corroborrar que se se creo el IAM OIDC provider con el siguiente comando

aws iam list-open-id-connect-providers

Desplegat AWS Load Balancer Controller usando Terraform

El proximo paso es deployar un AWS Load Balancer controller, pero primero, necesitamos un IAM role y establecer confianza con la Kubernetes service account.

Tendremos un pod con este AWS Load Balancer controller que estara viendo cuando se crea un ingress, en el momento que se crea un ingress, el pod creara un Application Load Balancer basandose en la definicion del ingress. Es por eso que necesitamos establecer confianza con la Kubernetes service account, para que permita a este pod crear recursos como el Application Load Balancer.

Crearemos el archivo terraform/11-iam-lb-controller.tf:

data "aws_iam_policy_document" "aws_load_balancer_controller_assume_role_policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"

    condition {
      test     = "StringEquals"
      variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub"
      values   = ["system:serviceaccount:kube-system:aws-load-balancer-controller"]
    }

    principals {
      identifiers = [aws_iam_openid_connect_provider.eks.arn]
      type        = "Federated"
    }
  }
}

resource "aws_iam_role" "aws_load_balancer_controller" {
  assume_role_policy = data.aws_iam_policy_document.aws_load_balancer_controller_assume_role_policy.json
  name               = "aws-load-balancer-controller"
}

resource "aws_iam_policy" "aws_load_balancer_controller" {
  policy = file("./AWSLoadBalancerController.json")
  name   = "AWSLoadBalancerController"
}

resource "aws_iam_role_policy_attachment" "aws_load_balancer_controller_attach" {
  role       = aws_iam_role.aws_load_balancer_controller.name
  policy_arn = aws_iam_policy.aws_load_balancer_controller.arn
}

output "aws_load_balancer_controller_role_arn" {
  value = aws_iam_role.aws_load_balancer_controller.arn
}

Ademas debemos crear el archivo AWSLoadBalancerController.json que tendra la policy con los permisos necesarios para el IAM role.

Dentro de la carpeta terraform crear terraform/AWSLoadBalancerController.json:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeAccountAttributes",
                "ec2:DescribeAddresses",
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeVpcs",
                "ec2:DescribeVpcPeeringConnections",
                "ec2:DescribeSubnets",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeInstances",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribeTags",
                "ec2:GetCoipPoolUsage",
                "ec2:DescribeCoipPools",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeListenerCertificates",
                "elasticloadbalancing:DescribeSSLPolicies",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetGroupAttributes",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:DescribeTags"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cognito-idp:DescribeUserPoolClient",
                "acm:ListCertificates",
                "acm:DescribeCertificate",
                "iam:ListServerCertificates",
                "iam:GetServerCertificate",
                "waf-regional:GetWebACL",
                "waf-regional:GetWebACLForResource",
                "waf-regional:AssociateWebACL",
                "waf-regional:DisassociateWebACL",
                "wafv2:GetWebACL",
                "wafv2:GetWebACLForResource",
                "wafv2:AssociateWebACL",
                "wafv2:DisassociateWebACL",
                "shield:GetSubscriptionState",
                "shield:DescribeProtection",
                "shield:CreateProtection",
                "shield:DeleteProtection"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateSecurityGroup"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": "arn:aws:ec2:*:*:security-group/*",
            "Condition": {
                "StringEquals": {
                    "ec2:CreateAction": "CreateSecurityGroup"
                },
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags",
                "ec2:DeleteTags"
            ],
            "Resource": "arn:aws:ec2:*:*:security-group/*",
            "Condition": {
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "true",
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress",
                "ec2:DeleteSecurityGroup"
            ],
            "Resource": "*",
            "Condition": {
                "Null": {
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:CreateTargetGroup"
            ],
            "Resource": "*",
            "Condition": {
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:CreateRule",
                "elasticloadbalancing:DeleteRule"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:RemoveTags"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*"
            ],
            "Condition": {
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "true",
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:RemoveTags"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*",
                "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*",
                "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*",
                "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:SetIpAddressType",
                "elasticloadbalancing:SetSecurityGroups",
                "elasticloadbalancing:SetSubnets",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:ModifyTargetGroup",
                "elasticloadbalancing:ModifyTargetGroupAttributes",
                "elasticloadbalancing:DeleteTargetGroup"
            ],
            "Resource": "*",
            "Condition": {
                "Null": {
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:DeregisterTargets"
            ],
            "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:SetWebAcl",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:AddListenerCertificates",
                "elasticloadbalancing:RemoveListenerCertificates",
                "elasticloadbalancing:ModifyRule"
            ],
            "Resource": "*"
        }
    ]
}

Por ultimo creamos la controller usando Helm, ya que la definicion para el pod ya esta hecha por AWS.

Creamos el archivo terraform/12-lb-controller.tf

provider "helm" {
  kubernetes {
    host                   = aws_eks_cluster.cluster.endpoint
    cluster_ca_certificate = base64decode(aws_eks_cluster.cluster.certificate_authority[0].data)
    exec {
      api_version = "client.authentication.k8s.io/v1beta1"
      args        = ["eks", "get-token", "--cluster-name", aws_eks_cluster.cluster.id]
      command     = "aws"
    }
  }
}

resource "helm_release" "aws-load-balancer-controller" {
  name = "aws-load-balancer-controller"

  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-load-balancer-controller"
  namespace  = "kube-system"
  version    = "1.4.1"

  set {
    name  = "clusterName"
    value = aws_eks_cluster.cluster.id
  }

  set {
    name  = "image.tag"
    value = "v2.4.2"
  }

  set {
    name  = "replicaCount"
    value = 1
  }

  set {
    name  = "serviceAccount.name"
    value = "aws-load-balancer-controller"
  }

  set {
    name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
    value = aws_iam_role.aws_load_balancer_controller.arn
  }

  # EKS Fargate specific
  set {
    name  = "region"
    value = "us-east-1"
  }

  set {
    name  = "vpcId"
    value = aws_vpc.main.id
  }

  depends_on = [aws_eks_fargate_profile.kube-system]
}

Pusheamos para que se apliquen los cambios en terraform.

Si corremos el comando kubectl get pods -n kube-system, veremos que ya tenemos nuestro pod con la AWS Load Balancer Controller: