بيان المشكلة
التحدي
تحتاج المؤسسات التي تدير تطبيقات معزولة في Kubernetes غالبًا إلى التقاط والحفاظ على حالة الحاويات التي تعمل من أجل:
- استعادة الكوارث
- ترحيل التطبيقات
- تصحيح الأخطاء/استكشاف الأخطاء
- الحفاظ على الحالة
- إعادة إنتاج البيئة
ومع ذلك، لا توجد طريقة مباشرة وأوتوماتيكية لـ:
- إنشاء نقاط تحقق للحاويات عند الطلب
- تخزين هذه النقاط في تنسيق موحد
- جعلها سهلة الوصول عبر الكتل
- تفعيل نقاط التحقق من خلال واجهة قياسية
القيود الحالية
- يتطلب إنشاء نقطة تحقق يدوي الوصول المباشر إلى الكتلة
- لا يوجد تنسيق تخزين موحد لنقاط التحقق
- تكامل محدود مع سجلات الحاويات
- عدم وجود وصول برمجي للأتمتة
- تنسيق معقد بين containerd وأنظمة التخزين
الحل
خدمة sidecar في Kubernetes تقوم بـ:
- كشف وظيفة نقطة التحقق عبر واجهة برمجة التطبيقات REST
- تحويل نقاط التحقق تلقائيًا إلى صور متوافقة مع OCI
- تخزين الصور في ECR للتوزيع السهل
- التكامل مع البنية التحتية الحالية لـ Kubernetes
- تقديم واجهة موحدة للأتمتة
هذا يحل المشكلات الأساسية عن طريق:
- أتمتة عملية نقطة التحقق
- توحيد تخزين نقاط التحقق
- جعل نقاط التحقق قابلة للنقل
- تمكين الوصول البرمجي
- تبسيط التكامل مع سير العمل الحالي
المستخدمون المستهدفون:
- فرق DevOps
- مهندسو المنصات
- مطورون التطبيقات
- مهندسو موثوقية المواقع (SREs)
استنادًا إلى Checkpoint/Restore In Userspace (CRIU)، يسمح تسجيل نقاط التحقق الحرفية بإنشاء نسخ تحتوي على حالة لحاوية قيد التشغيل دون أن تعرف الحاوية أنها قيد التسجيل. يمكن تحليل نسخة الحاوية واستعادتها في بيئة معزولة عدة مرات دون أن تكون الحاوية الأصلية على علم بذلك. تم تقديم تسجيل نقاط التحقق الحرفية كميزة ألفا في Kubernetes v1.25.
ستوجهك هذه المقالة حول كيفية نشر كود Golang الذي يمكن استخدامه لأخذ نقطة تحقق لحاوية باستخدام واجهة برمجة التطبيقات (API).
يأخذ الكود معرف البود، ويسترجع معرف الحاوية من containerd كمدخل، ثم يستخدم أمر ctr
لتسجيل نقطة تحقق للحاوية المحددة في مساحة أسماء k8s.io
الخاصة بـ containerd:
- عنقود Kubernetes
- قم بتثبيت أداة سطر الأوامر
ctr
. إذا كنت قادرًا على تشغيل أوامر ctr على kubelet أو عقدة العامل؛ إذا لم تكن كذلك، قم بتثبيت أو ضبط AMI لتحتوي على ctr. - تكوين
kubectl
للتواصل مع مجموعتك - تثبيت Docker محليًا
- الوصول إلى سجل الحاويات (مثل Docker Hub، ECR)
- Helm (لاستخدامه في تثبيت تحكم توجيه Nginx Ingress)
الخطوة 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"]
يقوم هذا الملف بفعل الآتي:
- استخدام
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: اختبار واجهة برمجة التطبيقات
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
- إضافة مصادقة إلى واجهة برمجة التطبيقات
- المراقبة. إعداد تسجيل الدخول والمراقبة لواجهة برمجة التطبيقات وعملية التحقق.
- إدارة الموارد. تكوين طلبات وقيود الموارد لحاوية sidecar.
- معالجة الأخطاء. تنفيذ معالجة أخطاء قوية في تطبيق Go.
- الاختبار. اختبار الإعداد بشكل شامل في بيئة غير إنتاجية قبل نشره في الإنتاج.
- التوثيق. الحفاظ على توثيق واضح حول كيفية استخدام واجهة برمجة التطبيقات الخاصة بالتحقق.
الخاتمة
تقوم هذه الإعدادات بنشر حاوية نقاط التفتيش كحاوية جانبية في Kubernetes وتعرض وظائفها من خلال واجهة برمجة التطبيقات القابلة للوصول من خارج العنقود. إنها توفر حلاً مرنًا لإدارة نقاط تفتيش الحاويات في بيئة Kubernetes.
محددات AWS/EKS
الخطوة 7: تثبيت وحدة تحكم موازن تحميل AWS
بدلاً من استخدام وحدة تحكم Nginx Ingress، سنستخدم وحدة تحكم موازن تحميل AWS. ستقوم هذه الوحدة بإنشاء وإدارة ALBs لموارد Ingress الخاصة بنا.
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
استبدل <your-cluster-name>
باسم مجموعة 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: اختبار واجهة برمجة التطبيقات
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 عن طريق استخدام التعليقات المناسبة لتحكم Ingress الخاص بك.
سيقوم هذه التغييرات بإعداد Ingress الخاص بك باستخدام AWS Application Load Balancer بدلاً من Nginx. سيقوم تحكم Ingress Controller الخاص بـ ALB بتوفير وتكوين ALB تلقائيًا استنادًا إلى مصدر Ingress الخاص بك.
الختام
تذكر التأكد من أن عنصر تحكم EKS الخاص بك لديه الأذونات اللازمة لإنشاء وإدارة ALBs. عادةً ما ينطوي ذلك على إنشاء سياسة IAM وحساب خدمة بالأذونات المناسبة.
سيتم الآن استخدام هذا الإعداد حلاً للتوازن التلقائي الأصلي من AWS، الذي يتكامل بشكل جيد مع خدمات AWS الأخرى ويمكن أن يكون أكثر كفاءة من حيث التكلفة في بيئة AWS.
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api