Probleemstelling
Uitdaging
Organisaties die containerapplicaties in Kubernetes uitvoeren, moeten vaak de status van draaiende containers vastleggen en behouden voor:
- Rampenherstel
- Applicatiemigratie
- Debuggen/oplossen van problemen
- Staat behouden
- Omgevingsreproductie
Er is echter geen eenvoudige, geautomatiseerde manier om:
- Containercontrolepunten on-demand te maken
- Deze controlepunten op te slaan in een gestandaardiseerd formaat
- Ze gemakkelijk toegankelijk te maken over clusters heen
- Controlepuntactivering via een standaardinterface te activeren
Huidige beperkingen
- Handmatige controlepuntcreatie vereist directe toegang tot clusters
- Geen gestandaardiseerd opslagformaat voor controlepunten
- Beperkte integratie met containerregistries
- Gebrek aan programatische toegang voor automatisering
- Complex coördinatie tussen containerd en opslagsystemen
Oplossing
Een Kubernetes sidecar-service die:
- Controlepuntfunctionaliteit blootstelt via REST API
- Controlepunten automatisch omzet naar OCI-compatibele images
- Images opslaat in ECR voor gemakkelijke distributie
- Integreert met bestaande Kubernetes-infrastructuur
- Biedt een gestandaardiseerde interface voor automatisering
Dit lost de kernproblemen op door:
- Het automatiseren van het controlepuntenproces
- Standaardiseren van checkpoint-opslag
- Checkpoint’s draagbaar maken
- Programmatische toegang inschakelen
- Integratie met bestaande workflows vereenvoudigen
Doelgebruikers:
- DevOps-teams
- Platform engineers
- Applicatieontwikkelaars
- Site Betrouwbaarheidsingenieurs (SREs)
Forensisch containercheckpointing is gebaseerd op Checkpoint/Restore In Userspace (CRIU) en maakt het mogelijk om statuskopieën te maken van een draaiende container zonder dat de container weet dat deze wordt gecheckt. De kopie van de container kan meerdere keren worden geanalyseerd en hersteld in een sandbox-omgeving zonder dat de oorspronkelijke container hiervan op de hoogte is. Forensisch containercheckpointing werd geïntroduceerd als een alfaversie in Kubernetes v1.25.
Dit artikel zal u begeleiden bij het implementeren van Golang-code die kan worden gebruikt om een containercheckpoint te nemen met behulp van een API.
De code neemt een pod-identificatie, haalt de container-ID op van containerd als invoer, en gebruikt vervolgens het ctr
commando om de specifieke container in de k8s.io
namespace van containerd te checken:
- Kubernetes-cluster
- Installeer de
ctr commandline
tool. Als je in staat bent om ctr commando’s uit te voeren op de kubelet of worker node; zo niet, installeer of pas de AMI aan om de ctr te bevatten. kubectl
geconfigureerd om te communiceren met je cluster- Docker lokaal geïnstalleerd
- Toegang tot een containerregister (bijv. Docker Hub, ECR)
- Helm (voor het installeren van Nginx Ingress Controller)
Stap 0: Code om een container checkpoint te maken met GO
Maak een bestand met de naam checkpoint_container.go
met de volgende inhoud:
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
}
Stap 1: Initialiseer de go-module
go mod init checkpoint_container
Wijzig het go.mod
bestand:
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
)
Voer het volgende commando uit:
go mod tidy
Stap 2: Bouw en publiceer een Docker-image
Maak een Dockerfile
in dezelfde map:
# 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"]
Deze Dockerfile doet het volgende:
- Gebruikt
golang:1.20
als de build stage om je Go-toepassing te compileren. - Gebruikt
amazonlinux:2
als de uiteindelijke basisimage. - Installeert de AWS CLI, Docker (inclusief containerd), en skopeo met behulp van yum en amazon-linux-extras.
- Kopieert de gecompileerde Go-binair van de build stage.
docker build -t <your-docker-repo>/checkpoint-container:v1 .
docker push <your-docker-repo>/checkpoint-container:v1
Vervang <jouw-docker-repo>
door je daadwerkelijke Docker-repository.
Stap 3: Pas de RBAC-resources toe
Maak een bestand met de naam 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
Pas de RBAC-resources toe:
kubectl apply -f rbac.yaml
Stap 4: Maak een Kubernetes-implementatie
Maak een bestand met de naam 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
Pas de implementatie toe:
kubectl apply -f deployment.yaml
In deployment.yaml
, werk het volgende bij:
image <your-docker-repo>/checkpoint-container v1
Stap 5: Kubernetes-service
Maak een bestand met de naam service.yaml
:
apiVersion v1
kind Service
metadata
name checkpoint-service
namespace default
spec
selector
app main-app
ports
protocol TCP
port80
targetPort8080
Pas de service toe:
kubectl apply -f service.yaml
Stap 6: Installeer 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
Stap 7: Maak Ingress-bronnen
Maak een bestand met de naam 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
Pas de Ingress toe:
kubectl apply -f ingress.yaml
Stap 8: Test de 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"}'
Vervang <EXTERNAL-IP>
door het daadwerkelijke externe IP-adres.
Extra Overwegingen
- Beveiliging.
- Implementeer HTTPS door TLS-certificaten in te stellen
- Voeg authenticatie toe aan de API
- Monitoring. Stel logging en monitoring in voor de API en het controlepuntproces.
- Resourcebeheer. Configureer resourcenaanvragen en limieten voor de sidecar-container.
- Foutafhandeling. Implementeer robuuste foutafhandeling in de Go-toepassing.
- Testen. Test de opstelling grondig in een niet-productieomgeving voordat u deze in productie implementeert.
- Documentatie. Onderhoud duidelijke documentatie over hoe de checkpoint-API te gebruiken.
Conclusie
Deze installatie implementeert de controlecontainer als een sidecar in Kubernetes en maakt de functionaliteit ervan toegankelijk via een API die van buiten het cluster bereikbaar is. Het biedt een flexibele oplossing voor het beheren van containercontroles in een Kubernetes-omgeving.
Specifiek voor AWS/EKS
Stap 7: Installeer de AWS Load Balancer Controller
In plaats van de Nginx Ingress Controller zullen we de AWS Load Balancer Controller gebruiken. Deze controller zal ALB’s maken en beheren voor onze Ingress-bronnen.
1. Voeg de EKS-chartrepository toe aan Helm:
helm repo add eks https://aws.github.io/eks-charts
2. Installeer de 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
Vervang <jouw-clusternaam>
door de naam van je EKS-cluster.
Let op: Zorg ervoor dat je de benodigde IAM-machtigingen hebt ingesteld voor de AWS Load Balancer Controller. Je vindt het gedetailleerde IAM-beleid in de AWS-documentatie.
Stap 8: Maak Ingress-bron
Maak een bestand met de naam 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
Voer de Ingress uit:
kubectl apply -f ingress.yaml
Stap 9: Test de API
1. Haal de ALB-DNS-naam op:
kubectl get ingress checkpoint-ingress
Zoek naar het VELD ADRES, dat de DNS-naam van de ALB zal zijn.
2. Stuur een testverzoek:
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"}'
Vervang <ALB-DNS-NAAM>
door de daadwerkelijke DNS-naam van je ALB uit stap 1.
Extra overwegingen voor AWS ALB
1. Beveiligingsgroepen. De ALB zal automatisch een beveiligingsgroep hebben die is aangemaakt. Zorg ervoor dat deze inkomend verkeer op poort 80 (en 443 als je HTTPS instelt) toestaat.
2. SSL/TLS: Om HTTPS in te schakelen, kun je de volgende annotaties aan je Ingress toevoegen:
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. Toegangslogs. Schakel toegangslogs voor je ALB in door het volgende toe te voegen:
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-integratie. Als je AWS WAF met je ALB wilt gebruiken, kun je het volgende toevoegen:
alb.ingress.kubernetes.io/waf-acl-id your-waf-web-acl-id
5. Authenticatie. Je kunt authenticatie instellen met behulp van Amazon Cognito of OIDC door de juiste annotaties voor de ALB Ingress Controller te gebruiken.
Deze wijzigingen zullen je Ingress instellen met behulp van een AWS Application Load Balancer in plaats van Nginx. De ALB Ingress Controller zal automatisch de ALB provisioneren en configureren op basis van je Ingress-bron.
Conclusie
Vergeet niet ervoor te zorgen dat je EKS-cluster de nodige IAM-machtigingen heeft om ALB’s te creëren en te beheren. Dit houdt doorgaans in dat je een IAM-beleid en een serviceaccount met de juiste machtigingen moet aanmaken.
Deze opstelling zal nu gebruikmaken van AWS’s native load-balancingoplossing, die goed integreert met andere AWS-diensten en kosteneffectiever kan zijn in een AWS-omgeving.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api