Dichiarazione del Problema
Sfida
Le organizzazioni che eseguono applicazioni containerizzate in Kubernetes spesso devono catturare e preservare lo stato dei container in esecuzione per:
- Recupero da disastri
- Migrazione delle applicazioni
- Debug/troubleshooting
- Preservazione dello stato
- Riproduzione dell’ambiente
Tuttavia, non esiste un modo semplice e automatizzato per:
- Creare checkpoint dei container su richiesta
- Memorizzare questi checkpoint in un formato standardizzato
- Rendere facilmente accessibili i checkpoint tra i cluster
- Attivare il checkpointing tramite un’interfaccia standard
Limitazioni Attuali
- La creazione manuale di checkpoint richiede accesso diretto al cluster
- Nessun formato di memorizzazione standardizzato per i checkpoint
- Integrazione limitata con i registri dei container
- Mancanza di accesso programmatico per l’automazione
- Coordinamento complesso tra containerd e i sistemi di storage
Soluzione
Un servizio sidecar di Kubernetes che:
- Espone la funzionalità di checkpoint tramite REST API
- Converte automaticamente i checkpoint in immagini conformi a OCI
- Memorizza le immagini in ECR per una facile distribuzione
- Si integra con l’infrastruttura Kubernetes esistente
- Fornisce un’interfaccia standardizzata per l’automazione
Questo risolve i problemi principali:
- Automatizzando il processo di checkpoint
- Standardizzazione dello storage dei checkpoint
- Rendere i checkpoint portabili
- Abilitare l’accesso programmatico
- Semplificare l’integrazione con i flussi di lavoro esistenti
Utenti target:
- Team DevOps
- Ingegneri di piattaforma
- Sviluppatori di applicazioni
- Ingegneri di affidabilità del sito (SRE)
Il checkpointing forense dei container si basa su Checkpoint/Restore In Userspace (CRIU) e consente la creazione di copie stateful di un container in esecuzione senza che il container stesso sappia di essere sottoposto a checkpoint. La copia del container può essere analizzata e ripristinata in un ambiente sandbox più volte senza che il container originale ne sia a conoscenza. Il checkpointing forense dei container è stato introdotto come funzionalità alpha in Kubernetes v1.25.
Questo articolo ti guiderà su come distribuire codice Golang che può essere utilizzato per prendere un checkpoint di un container utilizzando un’API.
Il codice prende un identificatore del pod, recupera l’ID del container da containerd come input e poi utilizza il comando ctr
per checkpointare il container specifico nel namespace k8s.io
di containerd:
- Cluster Kubernetes
- Installare lo strumento
ctr commandline
. se sei in grado di eseguire comandi ctr sul kubelet o sul nodo worker; in caso contrario, installare o regolare AMI per contenere il ctr. kubectl
configurato per comunicare con il tuo cluster- Docker installato localmente
- Accesso a un registro dei container (ad esempio, Docker Hub, ECR)
- Helm (per installare Nginx Ingress Controller)
Passo 0: Codice per Creare un Punto di Controllo del Container Utilizzando GO
Crea un file chiamato checkpoint_container.go
con il seguente contenuto:
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: Inizializzare il modulo go
go mod init checkpoint_container
Modifica il file 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
)
Esegui il seguente comando:
go mod tidy
Passo 2: Costruisci e Pubblica l’Immagine Docker
Crea un Dockerfile
nella stessa directory:
# 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"]
Questo Dockerfile fa quanto segue:
- Utilizza
golang:1.20
come fase di compilazione per compilare la tua applicazione Go. - Utilizza
amazonlinux:2
come immagine di base finale. - Installa AWS CLI, Docker (che include containerd) e skopeo usando yum e amazon-linux-extras.
- Copia il binario Go compilato dalla fase di build.
docker build -t <your-docker-repo>/checkpoint-container:v1 .
docker push <your-docker-repo>/checkpoint-container:v1
Sostituisci <your-docker-repo>
con il tuo effettivo repository Docker.
Passo 3: Applica le risorse RBAC
Crea un file chiamato 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
Applica le risorse RBAC:
kubectl apply -f rbac.yaml
Passo 4: Creare un Deployment Kubernetes
Crea un file chiamato 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
Applica il deployment:
kubectl apply -f deployment.yaml
Nel file deployment.yaml
, aggiorna quanto segue:
image <your-docker-repo>/checkpoint-container v1
Passo 5: Servizio Kubernetes
Crea un file chiamato service.yaml
:
apiVersion v1
kind Service
metadata
name checkpoint-service
namespace default
spec
selector
app main-app
ports
protocol TCP
port80
targetPort8080
Applica il servizio:
kubectl apply -f service.yaml
Passo 6: Installa Ngnix Ingress Controller
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx
Passo 7: Crea una Risorsa Ingress
Crea un file chiamato 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
Applica l’Ingress:
kubectl apply -f ingress.yaml
Passo 8: Testa l’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"}'
Sostituisci <EXTERNAL-IP>
con l’effettivo IP esterno.
Considerazioni Aggiuntive
- Sicurezza.
- Implementare HTTPS configurando certificati TLS
- Aggiungere autenticazione all’API
- Monitoraggio. Configurare logging e monitoraggio per l’API e il processo del checkpoint.
- Gestione delle Risorse. Configurare richieste e limiti delle risorse per il container del sidecar.
- Gestione degli Errori. Implementare una gestione degli errori robusta nell’applicazione Go.
- Test. Testare approfonditamente la configurazione in un ambiente non di produzione prima di implementarla in produzione.
- Documentazione. Mantenere una documentazione chiara su come utilizzare l’API del checkpoint.
Conclusione
Questa configurazione dispiega il contenitore del checkpoint come sidecar in Kubernetes ed espone la sua funzionalità tramite un’API accessibile dall’esterno del cluster. Fornisce una soluzione flessibile per gestire i checkpoint dei contenitori in un ambiente Kubernetes.
Specifico per AWS/EKS
Passaggio 7: Installa il Controller del Bilanciatore di Carico AWS
Invece di utilizzare il Controller di Ingress Nginx, useremo il Controller del Bilanciatore di Carico AWS. Questo controller creerà e gestirà gli ALB per le nostre risorse di Ingress.
1. Aggiungi il repository del chart EKS a Helm:
helm repo add eks https://aws.github.io/eks-charts
2. Installa il Controller del Bilanciatore di Carico 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
Sostituisci <nome-del-tuo-cluster>
con il nome del tuo cluster EKS.
Nota: Assicurati di avere le necessarie autorizzazioni IAM configurate per il Controller del Bilanciatore di Carico AWS. Puoi trovare la policy IAM dettagliata nella documentazione AWS.
Passaggio 8: Crea la Risorsa di Ingress
Crea un file chiamato 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
Applica l’Ingress:
kubectl apply -f ingress.yaml
Passaggio 9: Testa l’API
1. Ottieni il nome DNS dell’ALB:
kubectl get ingress checkpoint-ingress
Cerca il campo INDIRIZZO, che sarà il nome DNS dell’ALB.
2. Invia una richiesta di test:
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"}'
Sostituisci <NOME-DNS-ALB>
con il nome DNS effettivo del tuo ALB dal passaggio 1.
Considerazioni aggiuntive per AWS ALB
1. Gruppi di sicurezza. L’ALB avrà un gruppo di sicurezza creato automaticamente. Assicurati che consenta il traffico in ingresso sulla porta 80 (e sulla 443 se hai configurato HTTPS).
2. SSL/TLS: Per abilitare HTTPS, puoi aggiungere le seguenti annotazioni al tuo 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. Log di accesso. Abilita i log di accesso per il tuo ALB aggiungendo quanto segue:
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. Integrazione WAF. Se desideri utilizzare AWS WAF con il tuo ALB, puoi aggiungere:
alb.ingress.kubernetes.io/waf-acl-id your-waf-web-acl-id
5. Autenticazione. Puoi configurare l’autenticazione utilizzando Amazon Cognito o OIDC tramite le opportune annotazioni del controller di Ingress ALB.
Queste modifiche configureranno il tuo Ingress utilizzando un bilanciatore di carico delle applicazioni AWS anziché Nginx. Il controller di Ingress ALB provvederà automaticamente a creare e configurare l’ALB in base alla risorsa Ingress.
Conclusione
Ricorda di assicurarti che il tuo cluster EKS abbia le autorizzazioni IAM necessarie per creare e gestire gli ALB. Di solito ciò comporta la creazione di una policy IAM e di un account servizio con le autorizzazioni appropriate.
Questa configurazione utilizzerà ora la soluzione di bilanciamento del carico nativa di AWS, che si integra bene con altri servizi AWS e può essere più conveniente in un ambiente AWS.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api