Проблема
Вызов
Организации, работающие с контейнеризированными приложениями в Kubernetes, часто должны захватывать и сохранять состояние запущенных контейнеров для:
- Восстановления после катастрофы
- Миграции приложений
- Отладки/поиска неисправностей
- Сохранения состояния
- Воспроизведения среды
Однако нет прямого автоматизированного способа:
- Создать контрольные точки контейнеров по требованию
- Сохранить эти контрольные точки в стандартизированном формате
- Сделать их легко доступными на разных кластерах
- Запустить создание контрольной точки через стандартный интерфейс
Текущие ограничения
- Создание контрольной точки вручную требует прямого доступа к кластеру
- Нет стандартизированного формата хранения контрольных точек
- Ограниченная интеграция с контейнерными реестрами
- Отсутствие программного доступа для автоматизации
- Сложная координация между контейнером и системами хранения
Решение
Сервис-сопровождающее приложение Kubernetes, которое:
- Предоставляет функциональность контрольных точек через REST API
- Автоматически преобразует контрольные точки в образы, совместимые с OCI
- Хранит изображения в ECR для удобного распространения
- Интегрируется с существующей инфраструктурой Kubernetes
- Предоставляет стандартизированный интерфейс для автоматизации
Это решает основные проблемы:
- Автоматизация процесса создания контрольной точки
- Стандартизация хранения контрольных точек
- Сделать контрольные точки переносимыми
- Обеспечение программного доступа
- Упрощение интеграции с существующими рабочими процессами
Целевая аудитория:
- Команды DevOps
- Инженеры платформы
- Разработчики приложений
- Инженеры по надежности сайтов (SRE)
Форенсическая контрольная точка контейнера основана на Checkpoint/Restore In Userspace (CRIU) и позволяет создавать состояния копии работающего контейнера, не уведомляя контейнер о том, что он подвергается контрольной точке. Копия контейнера может быть проанализирована и восстановлена в песочнице несколько раз без ведома оригинального контейнера. Форенсическая контрольная точка контейнера была введена как альфа-функция в Kubernetes v1.25.
Эта статья поможет вам развернуть код на Golang, который можно использовать для создания контрольной точки контейнера с помощью API.
Код принимает идентификатор пода, извлекает идентификатор контейнера из containerd в качестве входных данных, а затем использует команду ctr
для создания контрольной точки конкретного контейнера в пространстве имен k8s.io
containerd:
- Кластер Kubernetes
- Установите инструмент
ctr commandline
. Если вы можете выполнять команды ctr на kubelet или рабочем узле; если нет, установите или настройте AMI, чтобы содержать ctr. kubectl
настроен для взаимодействия с вашим кластером- Docker установлен локально
- Доступ к реестру контейнеров (например, Docker Hub, ECR)
- Helm (для установки контроллера входа Nginx)
Шаг 0: Код для создания контрольной точки контейнера с использованием GO
Создайте файл с именем checkpoint_container.go
со следующим содержимым:
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
}
Шаг 1: Инициализация модуля go
go mod init checkpoint_container
Измените файл 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
)
Выполните следующую команду:
go mod tidy
Шаг 2: Создание и публикация образа Docker
Создайте Dockerfile
в том же каталоге:
# 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"]
Этот Dockerfile выполняет следующее:
- Использует
golang:1.20
в качестве этапа сборки для компиляции вашего Go приложения. - Использует
amazonlinux:2
в качестве окончательного базового образа. - Устанавливает AWS CLI, Docker (в который входит containerd), и skopeo с помощью yum и amazon-linux-extras.
- Копирует скомпилированный бинарный файл Go из этапа сборки.
docker build -t <your-docker-repo>/checkpoint-container:v1 .
docker push <your-docker-repo>/checkpoint-container:v1
Замените <your-docker-repo>
на ваш фактический репозиторий Docker.
Шаг 3: Примените ресурсы RBAC
Создайте файл с именем 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
Примените ресурсы RBAC:
kubectl apply -f rbac.yaml
Шаг 4: Создание развертывания Kubernetes
Создайте файл с именем 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
Примените развертывание:
kubectl apply -f deployment.yaml
В deployment.yaml
обновите следующее:
image <your-docker-repo>/checkpoint-container v1
Шаг 5: Служба Kubernetes
Создайте файл с именем service.yaml
:
apiVersion v1
kind Service
metadata
name checkpoint-service
namespace default
spec
selector
app main-app
ports
protocol TCP
port80
targetPort8080
Примените службу:
kubectl apply -f service.yaml
Шаг 6: Установка контроллера Ngnix Ingress
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx
Шаг 7: Создание ресурса Ingress
Создайте файл с именем 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
Примените Ingress:
kubectl apply -f ingress.yaml
Шаг 8: Тестирование 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"}'
Замените <EXTERNAL-IP>
на фактический внешний IP.
Дополнительные соображения
- Безопасность.
- Реализуйте HTTPS, настроив TLS сертификаты
- Добавьте аутентификацию к API
- Мониторинг. Настройте регистрацию и мониторинг для API и процесса контрольных точек.
- Управление ресурсами. Настройте запросы ресурсов и ограничения для контейнера sidecar.
- Обработка ошибок. Реализуйте надежную обработку ошибок в приложении Go.
- Тестирование. Тщательно протестируйте настройку в среде, не предназначенной для производства, перед развертыванием в производственной среде.
- Документация. Поддерживайте четкую документацию о том, как использовать API контрольных точек.
Заключение
Эта настройка разворачивает контейнер Checkpoint в качестве побочного контейнера в Kubernetes и предоставляет его функционал через API, к которому можно получить доступ извне кластера. Он предоставляет гибкое решение для управления контейнерными точками контроля в среде Kubernetes.
AWS/EKS Specific
Шаг 7: Установите контроллер балансировщика нагрузки AWS
Вместо использования контроллера входа Nginx, мы будем использовать контроллер балансировщика нагрузки AWS. Этот контроллер будет создавать и управлять ALB для наших ресурсов входа.
1. Добавьте репозиторий EKS в Helm:
helm repo add eks https://aws.github.io/eks-charts
2. Установите контроллер балансировщика нагрузки 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
Замените <имя-вашего-кластера>
на имя вашего кластера EKS.
Примечание: Убедитесь, что у вас есть необходимые разрешения IAM настроены для контроллера балансировщика нагрузки AWS. Подробную политику IAM можно найти в документации AWS.
Шаг 8: Создайте ресурс Ingress
Создайте файл с именем 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
Примените Ingress:
kubectl apply -f ingress.yaml
Шаг 9: Проверьте работу API
1. Получите имя DNS ALB:
kubectl get ingress checkpoint-ingress
Найдите поле ADDRESS, которое будет именем DNS ALB.
2. Отправьте тестовый запрос:
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"}'
Замените <ALB-DNS-NAME>
на фактическое имя DNS вашего ALB из шага 1.
Дополнительные соображения для AWS ALB
1. Группы безопасности. ALB будет автоматически создана. Убедитесь, что разрешен входящий трафик на порт 80 (и 443, если вы настроили HTTPS).
2. SSL/TLS: Чтобы включить HTTPS, вы можете добавить следующие аннотации к вашему 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. Журналы доступа. Включите журналы доступа для вашего ALB, добавив следующее:
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. Если вы хотите использовать AWS WAF с вашим ALB, вы можете добавить:
alb.ingress.kubernetes.io/waf-acl-id your-waf-web-acl-id
5. Аутентификация. Вы можете настроить аутентификацию, используя Amazon Cognito или OIDC, используя соответствующие аннотации ALB Ingress Controller.
Эти изменения настроят ваш Ingress, используя балансировщик нагрузки приложений AWS вместо Nginx. Контроллер входа ALB автоматически предоставит и настроит ALB на основе ресурса Ingress.
Заключение
Не забудьте убедиться, что ваш кластер EKS имеет необходимые разрешения IAM для создания и управления ALB. Обычно это включает создание политики IAM и учетной записи службы с соответствующими разрешениями.
Эта настройка теперь будет использовать решение балансировки нагрузки AWS, которое хорошо интегрируется с другими службами AWS и может быть более экономичным в среде AWS.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api