Declaração do Problema
Desafio
Organizações que executam aplicativos em contêineres no Kubernetes frequentemente precisam capturar e preservar o estado dos contêineres em execução para:
- Recuperação de desastres
- Migração de aplicativos
- Depuração/resolução de problemas
- Preservação de estado
- Reprodução de ambiente
No entanto, não há uma maneira direta e automatizada de:
- Criar checkpoints de contêineres sob demanda
- Armazenar esses checkpoints em um formato padronizado
- Torná-los facilmente acessíveis em clusters
- Desencadear a criação de checkpoints por meio de uma interface padrão
Limitações Atuais
- A criação manual de checkpoints exige acesso direto ao cluster
- Não há um formato de armazenamento padronizado para checkpoints
- Integração limitada com registros de contêineres
- Falta de acesso programático para automação
- Coordenação complexa entre containerd e sistemas de armazenamento
Solução
Um serviço auxiliar do Kubernetes que:
- Expõe funcionalidade de checkpoint via API REST
- Converte automaticamente checkpoints em imagens compatíveis com OCI
- Armazena imagens no ECR para fácil distribuição
- Integra-se com a infraestrutura existente do Kubernetes
- Fornece uma interface padronizada para automação
Isso resolve os problemas principais por:
- Automatizar o processo de checkpoint
- Padronização do armazenamento de checkpoints
- Tornando checkpoints portáteis
- Permitindo acesso programático
- Simplificando a integração com fluxos de trabalho existentes
Usuários-alvo:
- Equipes de DevOps
- Engenheiros de plataforma
- Desenvolvedores de aplicativos
- Engenheiros de Confiabilidade de Site (SREs)
O checkpoint forense de contêineres é baseado em Checkpoint/Restore In Userspace (CRIU) e permite a criação de cópias com estado de um contêiner em execução sem que o contêiner saiba que está sendo checkpointed. A cópia do contêiner pode ser analisada e restaurada em um ambiente de sandbox várias vezes sem que o contêiner original tenha conhecimento disso. O checkpoint forense de contêineres foi introduzido como um recurso alfa no Kubernetes v1.25.
Este artigo irá orientá-lo sobre como implantar código Golang que pode ser usado para realizar um checkpoint de contêiner usando uma API.
O código recebe um identificador de pod, recupera o ID do contêiner do containerd como entrada e, em seguida, utiliza o comando ctr
para fazer o checkpoint do contêiner específico no namespace k8s.io
do containerd:
- Cluster Kubernetes
- Instale a ferramenta
ctr commandline
. Se você puder executar comandos ctr no kubelet ou nó de trabalho; caso contrário, instale ou ajuste o AMI para conter o ctr. kubectl
configurado para se comunicar com seu cluster- O Docker instalado localmente
- Acesso a um registro de contêineres (por exemplo, Docker Hub, ECR)
- Helm (para instalar o Controlador de Ingresso Nginx)
Etapa 0: Código para Criar um Ponto de Verificação de Contêiner Usando GO
Crie um arquivo chamado checkpoint_container.go
com o seguinte conteúdo:
package main
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/containerd/containerd"
"github.com/containerd/containerd/namespaces"
)
func init() {
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)
}
func main() {
if len(os.Args) < 4 {
log.Fatal("Usage: checkpoint_container <pod_identifier> <ecr_repo> <aws_region>")
}
podID := os.Args[1]
ecrRepo := os.Args[2]
awsRegion := os.Args[3]
log.Printf("Starting checkpoint process for pod %s", podID)
containerID, err := getContainerIDFromPod(podID)
if err != nil {
log.Fatalf("Error getting container ID: %v", err)
}
err = processContainerCheckpoint(containerID, ecrRepo, awsRegion)
if err != nil {
log.Fatalf("Error processing container checkpoint: %v", err)
}
log.Printf("Successfully checkpointed container %s and pushed to ECR", containerID)
}
func getContainerIDFromPod(podID string) (string, error) {
log.Printf("Searching for container ID for pod %s", podID)
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
return "", fmt.Errorf("failed to connect to containerd: %v", err)
}
defer client.Close()
ctx := namespaces.WithNamespace(context.Background(), "k8s.io")
containers, err := client.Containers(ctx)
if err != nil {
return "", fmt.Errorf("failed to list containers: %v", err)
}
for _, container := range containers {
info, err := container.Info(ctx)
if err != nil {
continue
}
if strings.Contains(info.Labels["io.kubernetes.pod.uid"], podID) {
log.Printf("Found container ID %s for pod %s", container.ID(), podID)
return container.ID(), nil
}
}
return "", fmt.Errorf("container not found for pod %s", podID)
}
func processContainerCheckpoint(containerID, ecrRepo, region string) error {
log.Printf("Processing checkpoint for container %s", containerID)
checkpointPath, err := createCheckpoint(containerID)
if err != nil {
return err
}
defer os.RemoveAll(checkpointPath)
imageName, err := convertCheckpointToImage(checkpointPath, ecrRepo, containerID)
if err != nil {
return err
}
err = pushImageToECR(imageName, region)
if err != nil {
return err
}
return nil
}
func createCheckpoint(containerID string) (string, error) {
log.Printf("Creating checkpoint for container %s", containerID)
checkpointPath := "/tmp/checkpoint-" + containerID
cmd := exec.Command("ctr", "-n", "k8s.io", "tasks", "checkpoint", containerID, "--checkpoint-path", checkpointPath)
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("checkpoint command failed: %v, output: %s", err, output)
}
log.Printf("Checkpoint created at: %s", checkpointPath)
return checkpointPath, nil
}
func convertCheckpointToImage(checkpointPath, ecrRepo, containerID string) (string, error) {
log.Printf("Converting checkpoint to image for container %s", containerID)
imageName := ecrRepo + ":checkpoint-" + containerID
cmd := exec.Command("buildah", "from", "scratch")
containerId, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to create container: %v", err)
}
cmd = exec.Command("buildah", "copy", string(containerId), checkpointPath, "/")
err = cmd.Run()
if err != nil {
return "", fmt.Errorf("failed to copy checkpoint: %v", err)
}
cmd = exec.Command("buildah", "commit", string(containerId), imageName)
err = cmd.Run()
if err != nil {
return "", fmt.Errorf("failed to commit image: %v", err)
}
log.Printf("Created image: %s", imageName)
return imageName, nil
}
func pushImageToECR(imageName, region string) error {
log.Printf("Pushing image %s to ECR in region %s", imageName, region)
sess, err := session.NewSession(&aws.Config{
Region: aws.String(region),
})
if err != nil {
return fmt.Errorf("failed to create AWS session: %v", err)
}
svc := ecr.New(sess)
authToken, registryURL, err := getECRAuthorizationToken(svc)
if err != nil {
return err
}
err = loginToECR(authToken, registryURL)
if err != nil {
return err
}
cmd := exec.Command("podman", "push", imageName)
err = cmd.Run()
if err != nil {
return fmt.Errorf("failed to push image to ECR: %v", err)
}
log.Printf("Successfully pushed checkpoint image to ECR: %s", imageName)
return nil
}
func getECRAuthorizationToken(svc *ecr.ECR) (string, string, error) {
log.Print("Getting ECR authorization token")
output, err := svc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
if err != nil {
return "", "", fmt.Errorf("failed to get ECR authorization token: %v", err)
}
authData := output.AuthorizationData[0]
log.Print("Successfully retrieved ECR authorization token")
return *authData.AuthorizationToken, *authData.ProxyEndpoint, nil
}
func loginToECR(authToken, registryURL string) error {
log.Printf("Logging in to ECR at %s", registryURL)
cmd := exec.Command("podman", "login", "--username", "AWS", "--password", authToken, registryURL)
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to login to ECR: %v", err)
}
log.Print("Successfully logged in to ECR")
return nil
}
Etapa 1: Inicialize o Módulo go
go mod init checkpoint_container
Modifique o arquivo go.mod
:
module checkpoint_container
go 1.23
require (
github.com/aws/aws-sdk-go v1.44.298
github.com/containerd/containerd v1.7.2
)
require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
github.com/pkg/errors v0.9.1 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)
Execute o seguinte comando:
go mod tidy
Etapa 2: Construa e Publique a Imagem Docker
Crie um Dockerfile
no mesmo diretório:
# Build stage
FROM golang:1.20 as builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o checkpoint_container
# Final stage
FROM amazonlinux:2
# Install necessary tools
RUN yum update -y && \
amazon-linux-extras install -y docker && \
yum install -y awscli containerd skopeo && \
yum clean all
# Copy the built Go binary
COPY --from=builder /app/checkpoint_container /usr/local/bin/checkpoint_container
EXPOSE 8080
ENTRYPOINT ["checkpoint_container"]
Este Dockerfile faz o seguinte:
- Usa
golang:1.20
como estágio de compilação para compilar sua aplicação Go. - Usa
amazonlinux:2
como imagem base final. - Instala o AWS CLI, Docker (que inclui o containerd) e skopeo usando yum e amazon-linux-extras.
- Copia o binário Go compilado do estágio de compilação.
docker build -t <your-docker-repo>/checkpoint-container:v1 .
docker push <your-docker-repo>/checkpoint-container:v1
Substitua <seu-repositório-docker>
pelo seu repositório Docker real.
Etapa 3: Aplique os recursos de RBAC
Crie um arquivo chamado rbac.yaml
:
apiVersion v1
kind ServiceAccount
metadata
name checkpoint-sa
namespace default
---
apiVersion rbac.authorization.k8s.io/v1
kind Role
metadata
name checkpoint-role
namespace default
rules
apiGroups""
resources"pods"
verbs"get" "list"
---
apiVersion rbac.authorization.k8s.io/v1
kind RoleBinding
metadata
name checkpoint-rolebinding
namespace default
subjects
kind ServiceAccount
name checkpoint-sa
namespace default
roleRef
kind Role
name checkpoint-role
apiGroup rbac.authorization.k8s.io
Aplique os recursos de RBAC:
kubectl apply -f rbac.yaml
Passo 4: Criar um Deployment Kubernetes
Crie um arquivo chamado deployment.yaml
:
apiVersion apps/v1
kind Deployment
metadata
name main-app
namespace default
spec
replicas1
selector
matchLabels
app main-app
template
metadata
labels
app main-app
spec
serviceAccountName checkpoint-sa
containers
name main-app
image nginx latest # Replace with your main application image
name checkpoint-sidecar
image <your-docker-repo>/checkpoint-container v1
ports
containerPort8080
securityContext
privilegedtrue
volumeMounts
name containerd-socket
mountPath /run/containerd/containerd.sock
volumes
name containerd-socket
hostPath
path /run/containerd/containerd.sock
type Socket
Aplique o deployment:
kubectl apply -f deployment.yaml
No arquivo deployment.yaml
, atualize o seguinte:
image <your-docker-repo>/checkpoint-container v1
Passo 5: Serviço Kubernetes
Crie um arquivo chamado service.yaml
:
apiVersion v1
kind Service
metadata
name checkpoint-service
namespace default
spec
selector
app main-app
ports
protocol TCP
port80
targetPort8080
Aplique o serviço:
kubectl apply -f service.yaml
Passo 6: Instalar o Controlador de Ingress Ngnix
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx
Passo 7: Criar Recurso de Ingress
Crie um arquivo chamado ingress.yaml
:
apiVersion networking.k8s.io/v1
kind Ingress
metadata
name checkpoint-ingress
annotations
kubernetes.io/ingress.class nginx
nginx.ingress.kubernetes.io/ssl-redirect"false"
spec
rules
http
paths
path /checkpoint
pathType Prefix
backend
service
name checkpoint-service
port
number80
Aplique o Ingress:
kubectl apply -f ingress.yaml
Passo 8: Testar a API
kubectl get services ingress-ngnix-contoller -n ingress-ngnix
curl -X POST http://<EXTERNAL-IP>/checkpoint \
-H "Content-Type: application/json" \
-d '{"podId": "your-pod-id", "ecrRepo": "your-ecr-repo", "awsRegion": "your-aws-region"}'
Substitua <EXTERNAL-IP>
pelo IP externo real.
Considerações Adicionais
- Segurança.
- Implementar HTTPS configurando certificados TLS
- Adicionar autenticação à API
- Monitoramento. Configure o registro e monitoramento para a API e processo de checkpoint.
- Gerenciamento de Recursos. Configure solicitações e limites de recursos para o contêiner sidecar.
- Tratamento de Erros. Implemente um tratamento de erros robusto na aplicação Go.
- Testes. Teste minuciosamente a configuração em um ambiente não produtivo antes de implantá-lo em produção.
- Documentação. Mantenha uma documentação clara sobre como usar a API de checkpoint.
Conclusão
Esta configuração implanta o contêiner de verificação como um sidecar no Kubernetes e expõe sua funcionalidade por meio de uma API acessível de fora do cluster. Ele fornece uma solução flexível para gerenciar verificações de contêiner em um ambiente Kubernetes.
Específico do AWS/EKS
Passo 7: Instalar o Controlador de Balanceador de Carga da AWS
Em vez de usar o Controlador de Ingress do Nginx, iremos usar o Controlador de Balanceador de Carga da AWS. Este controlador criará e gerenciará ALBs para nossos recursos de Ingress.
1. Adicione o repositório de gráficos do EKS ao Helm:
helm repo add eks https://aws.github.io/eks-charts
2. Instale o Controlador de Balanceador de Carga da AWS:
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=<your-cluster-name> \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller
Substitua <nome-do-seu-cluster>
pelo nome do seu cluster EKS.
Observação: Certifique-se de que as permissões IAM necessárias estejam configuradas para o Controlador de Balanceador de Carga da AWS. Você pode encontrar a política IAM detalhada na documentação da AWS.
Passo 8: Criar Recurso de Ingress
Crie um arquivo chamado ingress.yaml
:
apiVersion networking.k8s.io/v1
kind Ingress
metadata
name checkpoint-ingress
annotations
kubernetes.io/ingress.class alb
alb.ingress.kubernetes.io/scheme internet-facing
alb.ingress.kubernetes.io/target-type ip
spec
rules
http
paths
path /checkpoint
pathType Prefix
backend
service
name checkpoint-service
port
number80
Aplicar o Ingress:
kubectl apply -f ingress.yaml
Passo 9: Testar a API
1. Obter o nome DNS do ALB:
kubectl get ingress checkpoint-ingress
Procure pelo campo ENDEREÇO, que será o nome DNS do ALB.
2. Enviar uma solicitação de teste:
curl -X POST http://<ALB-DNS-NAME>/checkpoint \
-H "Content-Type: application/json" \
-d '{"podId": "your-pod-id", "ecrRepo": "your-ecr-repo", "awsRegion": "your-aws-region"}'
Substitua <NOME-DNS-ALB>
pelo nome DNS real do seu ALB do passo 1.
Considerações adicionais para AWS ALB
1. Grupos de segurança. O ALB terá um grupo de segurança criado automaticamente. Certifique-se de que permite tráfego de entrada na porta 80 (e 443 se configurou HTTPS).
2. SSL/TLS: Para habilitar HTTPS, você pode adicionar as seguintes anotações ao seu Ingress:
alb.ingress.kubernetes.io/listen-ports'[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn arn aws acm region account-id certificate/certificate-id
3. Logs de acesso. Ative logs de acesso para seu ALB adicionando o seguinte:
alb.ingress.kubernetes.io/load-balancer-attributes access_logs.s3.enabled=true,access_logs.s3.bucket=your-log-bucket,access_logs.s3.prefix=your-log-prefix
4. Integração WAF. Se deseja usar AWS WAF com seu ALB, você pode adicionar:
alb.ingress.kubernetes.io/waf-acl-id your-waf-web-acl-id
5. Autenticação. Você pode configurar autenticação usando Amazon Cognito ou OIDC através das anotações apropriadas do controlador de Ingress do ALB.
Essas alterações configurarão seu Ingress usando um Balanceador de Carga de Aplicativo AWS em vez do Nginx. O Controlador de Ingress do ALB irá automaticamente provisionar e configurar o ALB com base no seu recurso de Ingress.
Conclusão
Lembre-se de garantir que seu cluster EKS tenha as permissões IAM necessárias para criar e gerenciar ALBs. Isso normalmente envolve criar uma política IAM e uma conta de serviço com as permissões apropriadas.
Essa configuração agora utilizará a solução de balanceamento de carga nativa da AWS, que se integra bem com outros serviços da AWS e pode ser mais econômica em um ambiente AWS.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api