Problemstellung
Herausforderung
Organisationen, die containerisierte Anwendungen in Kubernetes ausführen, müssen oft den Zustand laufender Container für:
- Wiederherstellung nach Katastrophen
- Anwendungsmigration
- Debugging/Fehlerbehebung
- Zustandserhaltung
- Umgebungsreproduktion
erfassen und bewahren. Allerdings gibt es keinen direkten, automatisierten Weg, um:
- Container-Checkpoints bei Bedarf zu erstellen
- Diese Checkpoints in einem standardisierten Format zu speichern
- Sie leicht zugänglich über Cluster zu machen
- Checkpointing über eine standardisierte Schnittstelle auszulösen
Aktuelle Einschränkungen
- Manuelle Erstellung von Checkpoints erfordert direkten Zugriff auf den Cluster
- Kein standardisiertes Speicherformat für Checkpoints
- Begrenzte Integration mit Container-Registern
- Fehlender programmatischer Zugriff für Automatisierung
- Komplexe Koordination zwischen Containerd und Speichersystemen
Lösung
Ein Kubernetes Sidecar-Service, der:
- Checkpoint-Funktionalität über REST-API bereitstellt
- Checkpoints automatisch in OCI-konforme Images konvertiert
- Bilder in ECR speichert für einfache Verteilung
- Integration mit bestehender Kubernetes-Infrastruktur
- Standardisierte Schnittstelle für Automatisierung bereitstellt
Dies löst die Kernprobleme durch:
- Automatisierung des Checkpoint-Prozesses
- Standardisierung der Checkpoint-Speicherung
- Checkpoints portabel machen
- Programmgesteuerten Zugriff ermöglichen
- Integration mit bestehenden Workflows vereinfachen
Zielbenutzer:
- DevOps-Teams
- Plattformingenieure
- Anwendungsentwickler
- Site Reliability Engineers (SREs)
Forensisches Container-Checkpointing basiert auf Checkpoint/Restore In Userspace (CRIU) und ermöglicht die Erstellung von zustandsbehafteten Kopien eines laufenden Containers, ohne dass der Container weiß, dass er gecheckpointet wird. Die Kopie des Containers kann analysiert und in einer Sandbox-Umgebung mehrfach wiederhergestellt werden, ohne dass der ursprüngliche Container davon Kenntnis hat. Forensisches Container-Checkpointing wurde als Alpha-Funktion in Kubernetes v1.25 eingeführt.
Dieser Artikel wird Sie anleiten, wie Sie Golang-Code bereitstellen, der verwendet werden kann, um einen Container-Checkpoint über eine API zu erstellen.
Der Code nimmt eine Pod-ID, ruft die Container-ID von containerd als Eingabe ab und verwendet dann den ctr
-Befehl, um den spezifischen Container im k8s.io
-Namespace von containerd zu checkpointen:
- Kubernetes-Cluster
- Installieren Sie das
ctr-Befehlszeilen
-Tool. Wenn Sie in der Lage sind, ctr-Befehle auf dem kubelet oder Worker-Knoten auszuführen; wenn nicht, installieren oder passen Sie das AMI an, um den ctr zu enthalten. kubectl
konfiguriert, um mit Ihrem Cluster zu kommunizieren- Docker lokal installiert
- Zugriff auf ein Container-Registry (z.B. Docker Hub, ECR)
- Helm (zur Installation des Nginx Ingress Controllers)
Schritt 0: Code zum Erstellen eines Container-Checkpoint mit GO
Erstellen Sie eine Datei mit dem Namen checkpoint_container.go
mit dem folgenden Inhalt:
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
}
Schritt 1: Initialisieren des go-Moduls
go mod init checkpoint_container
Ändern Sie die Datei 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
)
Führen Sie den folgenden Befehl aus:
go mod tidy
Schritt 2: Docker-Image erstellen und veröffentlichen
Erstellen Sie ein Dockerfile
im selben Verzeichnis:
# 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"]
Dieses Dockerfile tut folgendes:
- Verwendet
golang:1.20
als Build-Stage zum Kompilieren Ihrer Go-Anwendung. - Verwendet
amazonlinux:2
als endgültiges Basisimage. - Installiert die AWS CLI, Docker (einschließlich containerd) und skopeo unter Verwendung von yum und amazon-linux-extras.
- Kopiert das kompilierte Go-Binary aus der Build-Stage.
docker build -t <your-docker-repo>/checkpoint-container:v1 .
docker push <your-docker-repo>/checkpoint-container:v1
Ersetzen Sie <your-docker-repo>
durch Ihr tatsächliches Docker-Repository.
Schritt 3: Die RBAC-Ressourcen anwenden
Erstellen Sie eine Datei mit dem Namen 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
Wenden Sie die RBAC-Ressourcen an:
kubectl apply -f rbac.yaml
Schritt 4: Erstellen einer Kubernetes-Bereitstellung
Erstellen Sie eine Datei namens 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
Bewerben Sie die Bereitstellung:
kubectl apply -f deployment.yaml
In deployment.yaml
aktualisieren Sie folgendes:
image <your-docker-repo>/checkpoint-container v1
Schritt 5: Kubernetes-Service
Erstellen Sie eine Datei namens service.yaml
:
apiVersion v1
kind Service
metadata
name checkpoint-service
namespace default
spec
selector
app main-app
ports
protocol TCP
port80
targetPort8080
Bewerben Sie den Service:
kubectl apply -f service.yaml
Schritt 6: Ngnix Ingress-Controller installieren
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx
Schritt 7: Ingress-Ressource erstellen
Erstellen Sie eine Datei namens 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
Bewerben Sie den Ingress:
kubectl apply -f ingress.yaml
Schritt 8: API testen
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"}'
Ersetzen Sie <EXTERNAL-IP>
durch die tatsächliche externe IP.
Zusätzliche Überlegungen
- Sicherheit.
- Richten Sie HTTPS durch Einrichten von TLS-Zertifikaten ein
- Fügen Sie der API Authentifizierung hinzu
- Überwachung. Richten Sie Logging und Überwachung für die API und den Checkpoint-Prozess ein.
- Ressourcenmanagement. Konfigurieren Sie Ressourcenanfragen und -limits für den Sidecar-Container.
- Fehlerbehandlung. Implementieren Sie eine robuste Fehlerbehandlung in der Go-Anwendung.
- Testen. Testen Sie die Einrichtung gründlich in einer nicht produktiven Umgebung, bevor Sie sie in die Produktion übernehmen.
- Dokumentation. Pflegen Sie eine klare Dokumentation zur Verwendung der Checkpoint-API.
Abschluss
Dieses Setup implementiert den Checkpoint-Container als Sidecar in Kubernetes und stellt dessen Funktionalität über eine von außerhalb des Clusters zugängliche API bereit. Es bietet eine flexible Lösung zur Verwaltung von Container-Checkpoints in einer Kubernetes-Umgebung.
AWS/EKS-Spezifisch
Schritt 7: Installieren des AWS Load Balancer Controllers
Anstelle des Nginx Ingress Controllers verwenden wir den AWS Load Balancer Controller. Dieser Controller erstellt und verwaltet ALBs für unsere Ingress-Ressourcen.
1. Fügen Sie das EKS-Chart-Repository zu Helm hinzu:
helm repo add eks https://aws.github.io/eks-charts
2. Installieren Sie den AWS Load Balancer Controller:
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
Ersetzen Sie <Ihr-Cluster-Name>
durch den Namen Ihres EKS-Clusters.
Hinweis: Stellen Sie sicher, dass Sie die erforderlichen IAM-Berechtigungen für den AWS Load Balancer Controller eingerichtet haben. Die detaillierte IAM-Richtlinie finden Sie in der AWS-Dokumentation.
Schritt 8: Erstellen der Ingress-Ressource
Erstellen Sie eine Datei mit dem Namen 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
Wenden Sie die Ingress-Ressource an:
kubectl apply -f ingress.yaml
Schritt 9: Testen der API
1. Holen Sie sich den ALB-DNS-Namen:
kubectl get ingress checkpoint-ingress
Suchen Sie nach dem ADDRESS-Feld, das der DNS-Name des ALB ist.
2. Senden Sie eine Testanfrage:
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"}'
Ersetzen Sie <ALB-DNS-NAME>
durch den tatsächlichen DNS-Namen Ihres ALB aus Schritt 1.
Zusätzliche Überlegungen für AWS ALB
1. Sicherheitsgruppen. Die ALB wird automatisch eine Sicherheitsgruppe erstellen. Stellen Sie sicher, dass sie den eingehenden Datenverkehr auf Port 80 zulässt (und 443, wenn Sie HTTPS eingerichtet haben).
2. SSL/TLS: Um HTTPS zu aktivieren, können Sie die folgenden Annotationen zu Ihrem Ingress hinzufügen:
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. Zugriffsprotokolle. Aktivieren Sie die Zugriffsprotokolle für Ihren ALB, indem Sie Folgendes hinzufügen:
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. WAF-Integration. Wenn Sie AWS WAF mit Ihrem ALB verwenden möchten, können Sie Folgendes hinzufügen:
alb.ingress.kubernetes.io/waf-acl-id your-waf-web-acl-id
5. Authentifizierung. Sie können die Authentifizierung mithilfe von Amazon Cognito oder OIDC einrichten, indem Sie die entsprechenden ALB Ingress Controller-Annotationen verwenden.
Diese Änderungen richten Ihren Ingress mithilfe eines AWS Application Load Balancers anstelle von Nginx ein. Der ALB Ingress Controller wird den ALB basierend auf Ihrem Ingress-Ressourcen automatisch bereitstellen und konfigurieren.
Schlussfolgerung
Vergewissern Sie sich, dass Ihr EKS-Cluster über die erforderlichen IAM-Berechtigungen verfügt, um ALBs zu erstellen und zu verwalten. Dies beinhaltet in der Regel das Erstellen einer IAM-Richtlinie und eines Dienstkontos mit den entsprechenden Berechtigungen.
Diese Konfiguration wird jetzt die native Lastenausgleichslösung von AWS verwenden, die gut mit anderen AWS-Diensten integriert ist und in einer AWS-Umgebung kosteneffizienter sein kann.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api