O blog da AWS

Construindo Imagens de Containers no Kubernetes sem o docker.sock

Por Douglas Ramiro, Arquiteto de Soluções na AWS e
Lucas Duarte,Arquiteto Especialista na AWS

 

Em 2020, a Cloud Native Computing Foundation (CNCF), que mantem a distribuição open source do Kubernetes, anunciou que o Docker, como ambiente de execução, seria depreciado, sendo trocado por ambientes de execução que utilizam o Container Runtime Interface (CRI) criado especificamente para o Kubernetes. No entanto, imagens construídas com o Docker continuaram funcionando no seu cluster com todos os ambientes de execução. Para o Amazon EKS, desde a versão 1.21, o ambiente de execução containerd é suportado, porém não é a opção padrão. Na próxima versão do EKS, que será baseado no Kubernet die es 1.22, o ambiente de execução padrão será substituído de Docker para containerd.

Usualmente as tarefas de CI/CD que necessitam gerar uma imagem Docker realizam a construção dentro de um container Docker, como por exemplo,  agentes do Jenkins rodando no EKS, GitLab Runners, etc.  Essa técnica é chamada de Docker dind (Docker in Docker). Para fazer isso, você precisar montar o socket /var/run/docker.sock disponível na máquina host que está rodando o container dentro do container que vai construir a nova imagem.

Com o socket montado, você tem a possibilidade de rodar comandos Docker dentro de containers. Observe que montar o socket não te disponibiliza um novo ambiente de Docker. Ao invés disso, a instalação do Docker existente será utilizada dentro do container. Dessa forma, você pode construir imagens de Docker dentro de um container Docker.

 

Como você vai construir uma imagem de container com Docker in Docker se esse ambiente de execução vai ser depreciado no Kubernetes?

Uma possibilidade para essa questão é utilizar o Img. Img é um construtor de imagem de container independente, compatível com Dockerfile e OCI, sem daemon e sem privilégios. O Img permite fazer pequenos ajustes nas suas pipelines para construir suas imagens. Basicamente você irá substituir o comando docker build pelo comando img build.

Solução

Para começar a utilizar o Img, você precisa seguir os passos abaixo:

  1. Crie um cluster EKS que irá hospedar o pod do Img.
  2. Construa uma imagem de container que contenha o Img através de um Dockerfile.
  3. Publique essa imagem em um repositório utilizando o AWS Elastic Container Registry (ECR).
  4. Implante um novo pod a partir da imagem gerada.
  5. Inicie uma nova construção simulando uma ferramenta de CI como Jenkins, Gitlab Ci ou outra. Para fazer isso, você vai acessar o container que contêm o Img e vai iniciar uma nova construção sem mapear o socket.

Você vai precisar dos seguintes pré requisitos para completar esse tutorial:

Exportando Variáveis de Ambiente

Algumas variáveis de ambiente são necessárias para criar um cluster EKS. Abra um novo terminal e exporte essas variáveis de ambiente:

export AWS_REGION="us-east-1"
export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export AZS=($(aws ec2 describe-availability-zones --query 'AvailabilityZones[].ZoneName' --output text --region $AWS_REGION))
export AMI_ID=$(aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.21/amazon-linux-2/recommended/image_id --region $AWS_REGION --query "Parameter.Value" --output text)

Crie um cluster EKS

Você irá utilizar o eksctl para provisionar o seu cluster. Observe que você está definindo o ambiente de execução continerd. Crie o arquivo de manifesto do cluster:

cat << EOF > containerd-eks.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: containerd-cluster
  region: ${AWS_REGION}
  version: '1.21'
availabilityZones: ["${AZS[1]}", "${AZS[2]}", "${AZS[3]}"]
iam:
  withOIDC: true
managedNodeGroups:
- name: default-ng
  ami: "${AMI_ID}"
  instanceType: m5.large
  minSize: 1
  maxSize: 3
  overrideBootstrapCommand: |
    #!/bin/bash
    /etc/eks/bootstrap.sh containerd-cluster --container-runtime containerd
  desiredCapacity: 2
  labels: {role: mngworker}
cloudWatch:
  clusterLogging:
    enableTypes: ["*"]
EOF

Aplique o manifesto para criar o cluster, executando o seguinte comando abaixo:

eksctl create cluster -f containerd-eks.yaml

Caso não tenha instalado o eksctl em seu ambiente, siga estes passos para instalá-lo. O cluster será criado em aproximadamente 15 minutos.

Crie o Dockerfile com o Img

Agora, é necessário criar o arquivo Dockerfile que vai instalar o Img. Você pode utilizar como imagem base o alpine. O pacote do Img vem por padrão na imagem base alpine. Crie o arquivo Dockerfile:

cat << EOF > Dockerfile
FROM alpine:latest
LABEL maintainer="lucasdu@amazon.com"
RUN apk add img
RUN apk add vim
RUN apk add --no-cache \
    python3 \
    py3-pip \
&& pip3 install --upgrade pip \
&& pip3 install --no-cache-dir \
    awscli \
&& rm -rf /var/cache/apk/*
CMD exec /bin/sh -c "while true; do sleep 30; done"
EOF

Observe a diretiva CMD no Dockerfile. O loop infinito (“while true”) serve para que o pod fique sempre rodando dentro do cluster EKS.

Crie um repositório ECR e construa a imagem

Para hospedar a sua imagem Docker, primeiro você precisa criar um repositório de imagens no ECR:

aws ecr create-repository –repository-name img –region ${AWS_REGION}

Depois de criar o repositório, você precisa recuperar o token de autenticação e autenticar o cliente Docker ao servidor de hospedagem:

aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

Agora você pode construir a imagem que contêm o Img:

docker build -t ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/img:latest .

Quando a construção for finalizada, envie a imagem para o repositório:

docker push ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/img:latest

Criar o manifesto de Deployment

Antes de criar o manifesto de implantação (deployment), você precisar criar uma conta de serviço associada a uma IAM role. Isso é necessário porque o container que estará rodando precisa de permissões para enviar a nova imagem de container para o repositório no ECR. Crie a conta de serviço usando os seguintes comandos do eksctl:

eksctl utils associate-iam-oidc-provider --region=us-east-1 --cluster=containerd-cluster --approve

eksctl create iamserviceaccount \
    --name img-service-account \
    --namespace default \
    --cluster containerd-cluster \
    --attach-policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess \
    --approve \
    --override-existing-serviceaccounts

Para implantar a imagem que contem o Img dentro do cluster EKS, com o objetivo de construir uma imagem de container dentro de um container rodando, você precisa primeiro criar um manifesto de implantação do Kubernetes:

cat << EOF > img-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: img-deploy
  name: img-deploy
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: img-deploy
  template:
    metadata:
      labels:
        app: img-deploy
    spec:
      serviceAccountName: img-service-account
      containers:
      - image: ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/img:latest
        name: img
        securityContext:
          privileged: true
EOF

Aplique o manifesto para implantar a aplicação:

kubectl apply -f img-deployment.yaml

Verifique se o Pod está rodando:

kubectl get pods -ndefault | grep -i img

Aguarde até que pod esteja com o status de Running antes de proseguir.

img-deploy-5d74bd95b4-46b9r   1/1     Running   0          3d17h

Construa uma imagem de container dentro de um pod

Nessa parte desse blog, você irá simular uma ferramenta de CI que constrói uma imagem de container. Você irá acessar o terminal do pod que está rodando o Img e construir a imagem de container sem a necessidade de montar o docker.sock

Acesso ao terminal do pod:

kubectl exec -it po/$(kubectl get pods -ndefault | grep -i img | awk '{print $1}') -ndefault -- /bin/sh

Após o acesso, crie um Dockerfile simples para testar o build:

cat << EOF > /opt/Dockerfile
FROM alpine:latest
RUN apk add nginx
RUN mkdir -p /run/nginx
RUN touch /run/nginx/nginx.pid
RUN adduser -D -g 'www' www
RUN mkdir /www
RUN chown -R www:www /var/lib/nginx
RUN chown -R www:www /www
RUN ["./usr/sbin/nginx"]
EOF

Construa uma nova imagem de container utilizando a ferramenta Img:

cd /opt/ && img build -t test-app .

O resultado será similar ao log abaixo:

=> exporting to image                                                                                                                                                                               0.0s
 => => exporting layers                                                                                                                                                                              0.0s
 => => exporting manifest sha256:fb751b3025262b3391ba4dd8347c758cd326c1485e104dd81a9cdac58b023763                                                                                                    0.0s
 => => exporting config sha256:85d5328384a59576394b174291c6afb17376c15b114231c6f2ba8b278c2e1980                                                                                                      0.0s
 => => naming to docker.io/library/test-app:latest                                                                                                                                                   0.0s
 => exporting cache                                                                                                                                                                                  0.0s
 => => preparing build cache for export                                                                                                                                                              0.0s
Successfully built docker.io/library/test-app:latest

Concluído. Agora você tem uma imagem de container construída pelo Img. Neste momento você já pode enviar essa imagem para o Amazon ECR e implanta-la aonde você quiser.

Conclusão

Primeiro de tudo, não fique em pânico. A única coisa que será depreciado é o Docker como ambiente de execução. Amazon EKS irá continuar funcionando com imagens de containers que foram construídas com o Docker. Entretanto, containerd será o ambiente de execução padrão.

Existem diversas soluções para readaptar as suas pipelines devido a esta mudança, algumas delas mais complexas e outras mais simples como essa apresentada.

 


Sobre os autores

Douglas Ramiro é arquiteto de soluções com especialização em Spot e Graviton na AWS. Todos os dias, os clientes da AWS economizam ao adotar essas tecnologias. Douglas tem a missão de disseminar essas tecnologias em LATAM para que os clientes AWS possam reduzir seus custos.

 

 

 

 

Lucas Duarte é um Arquiteto Especialista em Containers na AWS com focos em clientes LATAM. Entusiasta de automação, Cloud, e cultura DevOps. Com experiência prévia em projetos focados nesse segmento em empresas como iFood, Guiabolso e Mandic. Tem trabalhado em diferentes projetos relacionados principalmente a orquestração de containers e microserviços.