Declaração do Problema
Desafio
Organizações que executam aplicações conteinerizadas no Kubernetes frequentemente precisam capturar e preservar o estado dos contêineres em execução para:
- Recuperação de desastres
- Migração de aplicações
- Depuração/resolução de problemas
- Preservação de estado
- Reprodução de ambiente
No entanto, não existe uma maneira simples e automatizada de:
- Criar pontos de verificação de contêiner sob demanda
- Armazenar esses pontos de verificação em um formato padronizado
- Torná-los facilmente acessíveis entre clusters
- Acionar o ponto de verificação através de uma interface padrão
Limitações Atuais
- A criação manual de pontos de verificação requer acesso direto ao cluster
- Não há um formato de armazenamento padronizado para pontos de verificação
- 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 sidecar do Kubernetes que:
- Exponha a funcionalidade de ponto de verificação via API REST
- Converta automaticamente os pontos de verificação em imagens compatíveis com OCI
- Armazene imagens no ECR para fácil distribuição
- Integre-se à infraestrutura existente do Kubernetes
- Forneça uma interface padronizada para automação
Isso resolve os problemas centrais ao:
- Automatizar o processo de ponto de verificação
- Padronizando armazenamento de checkpoints
- Tornando checkpoints portáteis
- Permitindo acesso programático
- Simplificando integração com fluxos de trabalho existentes
Usuários-alvo:
- Equipes de DevOps
- Engenheiros de plataforma
- Desenvolvedores de aplicativos
- Engenheiros de Confiabilidade do Site (SREs)
O checkpoint de contêiner forense é 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 várias vezes em um ambiente de sandbox sem que o contêiner original tenha conhecimento disso. O checkpoint de contêiner forense foi introduzido como um recurso alfa no Kubernetes v1.25.
Este artigo irá guiá-lo sobre como implantar código Golang que pode ser usado para fazer 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, usa 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ê conseguir executar comandos ctr no kubelet ou no nó de trabalho; se não, instale ou ajuste a AMI para contener o ctr. kubectl
configurado para se comunicar com seu cluster- Docker instalado localmente
- Acesso a um registro de contêiner (por exemplo, Docker Hub, ECR)
- Helm (para instalar o Nginx Ingress Controller)
Passo 0: Código para Criar Checkpoint 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
}
Passo 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
Passo 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 a fase de construção para compilar seu aplicativo Go. - Usa
amazonlinux:2
como a imagem base final. - Instala o AWS CLI, Docker (que inclui containerd) e skopeo usando yum e amazon-linux-extras.
- Copia o binário Go compilado da fase de construção.
docker build -t <your-docker-repo>/checkpoint-container:v1 .
docker push <your-docker-repo>/checkpoint-container:v1
Substitua <your-docker-repo>
pelo seu repositório Docker real.
Passo 3: Aplique os recursos 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
Aplicar os recursos 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 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.
- Implemente HTTPS configurando certificados TLS
- Adicione autenticação à API
- Monitoramento. Configure log 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 tratamento de erros robusto na aplicação Go.
- Testes. Teste minuciosamente a configuração em um ambiente não de produção antes de implantá-la na produção.
- Documentação. Mantenha uma documentação clara sobre como usar a API de checkpoint.
Conclusão
Esta configuração implanta o container de checkpoint 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 checkpoints de contêiner em um ambiente Kubernetes.
Específico do AWS/EKS
Passo 7: Instalar o Controlador do Balanceador de Carga da AWS
Em vez de usar o Controlador de Ingress do Nginx, usaremos o Controlador do Balanceador de Carga da AWS. Este controlador criará e gerenciará ALBs para nossos recursos de Ingress.
1. Adicione o repositório do gráfico do EKS ao Helm:
helm repo add eks https://aws.github.io/eks-charts
2. Instale o Controlador do 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.
Nota: Certifique-se de ter as permissões IAM necessárias configuradas para o Controlador do 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
Aplique o Ingress:
kubectl apply -f ingress.yaml
Passo 9: Testar a API
1. Obtenha o nome do DNS do ALB:
kubectl get ingress checkpoint-ingress
Procure pelo campo ENDEREÇO, que será o nome do DNS do ALB.
2. Envie 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-DO-DNS-ALB>
pelo nome real do DNS do seu ALB do passo 1.
Considerações Adicionais para o AWS ALB
1. Grupos de segurança. O ALB terá um grupo de segurança criado automaticamente. Certifique-se de permitir tráfego de entrada na porta 80 (e 443 se configurar 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 os 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 desejar usar o 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 a autenticação usando o Amazon Cognito ou OIDC usando as anotações apropriadas do Controlador de Ingress do ALB.
Essas alterações configurarão seu Ingress usando um Balanceador de Carga de Aplicativo da AWS em vez do Nginx. O Controlador de Ingress do ALB provisionará e configurará automaticamente 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 geralmente envolve a criação de uma política IAM e uma conta de serviço com as permissões adequadas.
Essa configuração agora usará a solução nativa de balanceamento de carga da AWS, que se integra bem com outros serviços da AWS e pode ser mais econômica em um ambiente da AWS.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api