問題陳述
挑戰
在 Kubernetes 中運行容器化應用程序的組織通常需要捕獲和保留運行容器的狀態,以供:
- 災難恢復
- 應用程序遷移
- 調試/故障排除
- 狀態保存
- 環境再現
然而,目前沒有直接的自動化方法來:
- 按需創建容器檢查點
- 以標準化格式存儲這些檢查點
- 使它們在叢集之間輕鬆訪問
- 通過標準界面觸發檢查點
目前的限制
- 手動創建檢查點需要直接訪問叢集
- 檢查點沒有標準化的存儲格式
- 與容器註冊表的集成有限
- 缺乏自動化的程式訪問
- 容器d和存儲系統之間的複雜協調
解決方案
一個 Kubernetes sidecar 服務:
- 通過 REST API 公開檢查點功能
- 自動將檢查點轉換為符合 OCI 標準的映像
- 將映像存儲在 ECR 中以便分發
- 與現有的 Kubernetes 基礎設施集成
- 為自動化提供了標準化界面
這通過以下方式解決了核心問題:
- 自動化檢查點過程
- 標準化檢查點存儲
- 使檢查點可攜
- 啟用程式化存取
- 簡化與現有工作流程的整合
目標用戶:
- DevOps 團隊
- 平台工程師
- 應用程序開發人員
- 網站可靠性工程師(SREs)
法醫容器檢查點基於用戶空間中的檢查點/恢復(CRIU),允許創建運行容器的有狀態副本,而容器不知道正在被檢查點。容器的副本可以在沙箱環境中進行多次分析和恢復,而原始容器不會察覺。法醫容器檢查點是在 Kubernetes v1.25 中作為 Alpha 功能引入的。
本文將指導您如何部署 Golang 代碼,以使用 API 進行容器檢查點。
代碼接受一個 pod 識別符,從 containerd 檢索容器 ID 作為輸入,然後使用ctr
命令在 containerd 的k8s.io
命名空間中檢查點特定容器:
- Kubernetes 集群
- 安裝
ctr commandline
工具。如果您能在kubelet或工作節點上運行ctr命令,則執行ctr命令; 如果不能,請安裝或調整AMI以包含ctr。 kubectl
已配置以與您的叢集通信- 本地安裝了Docker
- 訪問容器註冊表(例如Docker Hub、ECR)
- Helm(用於安裝Nginx Ingress Controller)
步驟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
作為最終基礎映像。 - 使用yum和amazon-linux-extras安裝AWS CLI、Docker(其中包括containerd)和skopeo。
- 從構建階段複製編譯的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"}'
用实际的外部 IP 替换 <EXTERNAL-IP>
。
其他注意事项
- 安全性。
- 通过设置 TLS 证书实现 HTTPS
- 为 API 添加身份验证
- 监控。为 API 和检查点进程设置日志记录和监控。
- 资源管理。为 sidecar 容器配置资源请求和限制。
- 错误处理。在 Go 应用程序中实现健壮的错误处理。
- 测试。在非生产环境中彻底测试设置,然后再部署到生产环境中。
- 文档。保持关于如何使用检查点 API 的清晰文档。
结论
此設置將檢查點容器部署為 Kubernetes 中的側車,並通過從集群外部訪問的API公開其功能。它為在Kubernetes環境中管理容器檢查點提供了靈活的解決方案。
AWS/EKS 特定
步驟 7:安裝 AWS 負載均衡器控制器
我們將使用 AWS 負載均衡器控制器 代替使用 Nginx Ingress 控制器。此控制器將為我們的入口資源創建和管理 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
將<your-cluster-name>
替換為您的 EKS 集群名稱。
注意:確保您已為 AWS 負載均衡器控制器設置了必要的 IAM 權限。您可以在 AWS 文檔中找到詳細的 IAM 策略。
步驟 8:創建入口資源
創建一個名為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
應用入口:
kubectl apply -f ingress.yaml
步驟 9:測試 API
1. 獲取 ALB DNS 名稱:
kubectl get ingress checkpoint-ingress
查找 ADDRESS 欄,這將是 ALB 的 DNS 名稱。
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>
替換為您在步驟 1 中的 ALB 實際 DNS 名稱。
AWS ALB 的其他考慮事項
1. 安全群組。ALB 將會自動建立一個安全群組。確保它允許端口 80 的入站流量(如果您設置了 HTTPS,也要允許端口 443 的流量)。
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 註釋。
這些更改將使用 AWS 應用負載平衡器而不是 Nginx 來設置您的 Ingress。ALB Ingress Controller 將根據您的 Ingress 資源自動配置和設置 ALB。
結論
請記得確保您的 EKS 叢集具備創建和管理 ALB 所需的 IAM 權限。通常需要創建一個具有適當權限的 IAM 政策和服務帳戶。
這個設置現在將使用 AWS 的原生負載平衡解決方案,它與其他 AWS 服務整合良好,在 AWS 環境中可能更具成本效益。
Source:
https://dzone.com/articles/container-checkpointing-kubernetes-api