Kubernetes的Pod有状态迁移平台搭建(使用CRIU+CRIO)

环境

LinuxUbuntu 22.04
Kubernetes1.26
CRIO

1.26

CRIU3.17(3.16以上即可)

前言

容器有状态迁移是指将运行中的容器实例(包含其内部状态)从一个环境迁移到另一个环境的过程,涉及到保存和迁移容器的运行状态,包括文件系统、网络连接、内存中的数据等,目前主要使用检查点\恢复(Checkpoint\Restore)技术来实现这一目的。使用带有状态迁移的机制可以有多种用途,如应用调试、历史状态保存、慢启动应用加速、服务故障转移与避免等等,相信看到这篇文章的伙伴已经有了相关的需求与调研,不再过多讨论。

有状态迁移已经在Docker、Podman等容器运行时上得到了很好的支持,但是在k8s上却迟迟不见动静。虽然社区的主流思想是使用无状态的应用,但有状态应用的使用是不可避免的。好消息是从v1.25版本开始,k8s支持了对pod内的容器进行检查点存档,虽然恢复暂时还不支持,但可以使用状态的存档文件建立有状态镜像来进行恢复操作。

需要说明的是,这篇文章涉及的技术和方案均来自k8s社区,但是社区的文章非常的简略,本人在集群的搭建过程中遇到了较多问题,浪费了许多时间。这篇文章主要是对搭建过程进行整理,避免大家再浪费时间去踩坑。

(关于Linux环境补充一点,CentOS7的版本太低,升级了内核也可能搭建不成功,最后集群能进行检查点存档,但是无法恢复)

1. 增加集群检查点功能

 考虑到可能看这篇文章的许多伙伴已经搭建了集群,只需要增加集群的检查点功能,因此将这部分内容放在前面,节约大家的时间和精力。对于还没有搭建集群的伙伴,请从第2部分开始。

1.1 开启CRI-O的检查点支持(所有节点)

在开启CRI-O的检查点支持前请确保CRIU已经安装并且版本在3.16之上,如果没有安装或者版本不够,请参考第2部分的CRIU安装部分。

编辑crio的配置文件,将enable_criu_support设为true:

vi /etc/crio/crio.conf

# 找到enable_criu_support字段并修改
# enable_criu_support = false
enable_criu_support = true

重启crio ,并查看crio的启动日志:

sudo systemctl restart crio

journalctl -xefu crio

如果出现下面提示,则开启成功:

1.2 开启ContainerCheckpoint特性门控

1. 修改apiserver、controller-manager、scheduler的配置文件,加入特性门控字段(Master节点):

# kube-controller-manager.yaml、kube-scheduler.yaml同理
vi /etc/kubernetes/manifests/kube-apiserver.yaml

# 若已经有其它功能门控被开启,只需要在后面添加ContainerCheckpoint门口的字段,用“,”隔开
- --feature-gates=...,...,...,ContainerCheckpoint=true

# 否则直接添加下面字段
- --feature-gates=ContainerCheckpoint=true

完成添加后 apiserver、controller-manager、scheduler三个pod将会被重启:

2. kubelet加入门控字段(所有节点):

vi /var/lib/kubelet/config.yaml

featureGates:
  ContainerCheckpoint: true

 完成后保存配置文件,重启kubelet即可。

1.3 检测点存档的功能验证

为了验证集群的检测点存档功能,我们制作一个容器镜像,使用该镜像运行pod后对pod进行检查点存档。

1. pod准备

首先使用python写一个简单的计数程序,每个一秒数一次数,并记录在MigLog.txt文件中,如下:

import time

i = 0

while True:
    with open('MigLog.txt', 'a') as file:
        file.write(f'{i}\n')

    i += 1
    time.sleep(1)

然后撰写Dokerfile,生成镜像文件:

# 使用官方 Python 镜像作为基础镜像
FROM python:3.7

# 设置工作目录
WORKDIR /app

# 将项目文件复制到容器中
COPY mig.py /app/              # 计数程序

COPY MigLog.txt /app/          # 记录文件

COPY hello_migration.txt /app/ # 空文件,可以不要,用于验证文件系统中不使用的文件是否迁移

# 启动应用程序
CMD [ "python", "mig.py" ]

 随后使用这个镜像文件运行Pod,进入Pod内部显示如下:

 查看MigLog.txt文件看程序是否在正常计数:

2. 检查点存档 

 在master节点执行检查点请求,格式为:

curl -X POST "https://nodeIP:10250/checkpoint/namespace/podId/container"

其中,nodeIP是需要进行检查点存档的pod所在节点,10250是该节点上的kubelet进程,checkpoint是检查点请求,最后三个信息分别是pod所在命名空间、pod名称和pod内容器的名称。

以本文的Pod为例,发送的命令如下(请求需要加上集群的证书与密钥):

# 存档命令
curl -X POST "https://192.168.60.11:10250/checkpoint/default/migrations/migration" --insecure --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key

检查点操作完成后会提示存档成功,显示文件名称,否则会出现下面错误提示:

401未经授权;
404ContainerCheckpoint功能门控被禁用,或指定命名空间、pod、容器找不到;
500CRI在检查点期间遇到错误,或CRI未实现检查点API;

检查点位于 /var/lib/kubelet/checkpoints/,文件名称格式为checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tar

我们对这份存档文件进行解压,可以看到如下内容:

对这些文件的解释如下:

文件

说明

bind.mounts

该文件包含有关绑定挂载的信息,并且需要在恢复期间将所有外部文件和目录挂载到正确的位置

checkpoint/

该目录包含 CRIU 创建的实际检查点

config.dump spec.dump

包含恢复期间所需的有关容器的元数据

dump.log

该文件包含在检查点期间创建的 CRIU 的调试输出

stats-dump

此文件包含 checkpointctl 用于通过 --print-stats 显示转储统计信息的数据

rootfs-diff.tar

该文件包含容器文件系统上所有已更改的文件

1.4 Pod恢复

终于来到恢复阶段了,然而目前为止的k8s版本还不支持直接恢复,需要自己构建有状态镜像来进行恢复操作。另外,v1.26版才加入了识别有状态镜像的接口,而不是熟知的v1.25,伙伴们不要弄错了。我们需要buildah这个镜像构建工具,将存档文件制作成镜像。直接使用apt命令安装buildah即可,没有特殊要求,随后执行下面操作:

# 创建一个空白容器
newcontainer=$(buildah from scratch)
# 添加存档文件到容器
buildah add $newcontainer /var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tar /
# 添加注释
buildah config --annotation=io.kubernetes.cri-o.annotations.checkpoint.name=<container-name> $newcontainer
# 提交容器为镜像
buildah commit $newcontainer checkpoint-image:tagPy
# 删除该容器
buildah rm $newcontainer

这时查看images列表,可以看到我们制作的镜像。

使用该镜像运行pod,为了做区分,恢复的pod修改名称叫做m-c,进入pod内部查看文件系统内的文件和MigLog.txt的内容,与之前的做对比,如下:

 原来的pod:

 恢复后的pod:

 程序计数并没有重新开始,而是接续前面:

原来的pod:

   

  恢复后的pod:

     

 由此可见,在此集群上我们实现了的pod的有状态迁移。

2. 安装CRI-O

目前官方社区只支持CRI-O,可能后续将会支持Containerd,实际上从我的使用体验上来看,CRI-O的性能是优于Containerd的,而且CRI-O命令借助cri-tools工具,大多与Containerd相同,并不会有太多不适应。

做安装准备,安装必要依赖和添加环境变量:

# 更新系统并安装依赖
sudo apt update
sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common -y

# 添加 CRI-O 环境变量
export OS=xUbuntu_22.04
export CRIO_VERSION=1.26

 安装CRI-O包,Ubuntu系统安装稍微麻烦一些,需要添加仓库和密匙

# 切换管理员身份
sudo su

# 添加 CRI-O Kubic 仓库:
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /"| sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$CRIO_VERSION/$OS/ /"|sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION.list
# CRI-O 仓库导入 GPG 密钥:
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION/$OS/Release.key | sudo apt-key add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo apt-key add -

sudo apt update  #看是否报错

# 安装
sudo apt install cri-o cri-o-runc -y

修改一些配置信息:

vi /etc/crio/crio.conf
# 修改pod沙箱init容器版本,具体看k8s的pause版本,可以下载了k8s的启动镜像后再修改
pause_image = registry.aliyuncs.com/google_containers/pause:3.9  # 具体看k8s的pause版本

然后安装cri-tools与criu,pod的有状态迁移就是靠criu工具实现的

sudo apt install -y cri-tools criu

(选做)如果CRIU版本在3.16以下,可以选择手动安装CRIU版本,将CRIU版本升至3.16以上

# 下载源码
wget https://github.com/checkpoint-restore/criu/archive/refs/tags/v3.16.1.tar.gz
tar xf v3.16.1.tar.gz

# 安装必要依赖
yum install -y gcc make protobuf protobuf-c protobuf-c-devel \
libnl libnl3-devel libcap libcap-devel protobuf-compiler \
protobuf-devel libnet-devel libnet protobuf-python 

# 构建可执行程序
cd criu-3.16.1/
make

# 替换掉原来版本的criu
cp ./criu/criu /usr/sbin/criu

# 查看版本信息至3.16以上即可
criu --version

一般不需要对cni做出修改,但如果出现接口不兼容的情况,则需要更换cni的版本:

# cni自定义版本下载
https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.3.0.tgz

# cni直接下载
apt install containernetworking-plugins -y

# 修改crio.config配置
vi /etc/crio/crio.config
# 修改cni路径,取消注释 network_dir 和 plugin_dirs 部分,并在 plugin_dirs 下添加 /usr/lib/cni/。
# 必要的话修改接口版本(方法略过)
# 每个人遇到的问题可能不一样,因此这里不过多描述,如有相同问题且不能解决的请评论区留言

最后,启动crio

sudo systemctl start crio
sudo systemctl enable crio
sudo systemctl status crio

3. K8S安装

k8s的安装教程特别多,这里写下一般的安装步骤。

1. 系统准备(所有节点均执行):

# 主机hosts映射
cat >> /etc/hosts << EOF
192.168.60.100 master
192.168.60.101 node1
192.168.60.102 node2
EOF

# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 关闭selinux
apt install selinux-utils
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

# 关闭交换分区(为了保证 kubelet 正常工作,必须禁用交换分区)
swapoff -a
sed -i 's/.*swap.*/#&/' /etc/fstab

#转发 IPv4 并让 iptables 看到桥接流量
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
lsmod | grep br_netfilter #验证br_netfilter模块

# 设置所需的 sysctl 参数,参数在重新启动后保持不变
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 应用 sysctl 参数而不重新启动
sudo sysctl --system


# 执行date命令,查看时间是否异常
date
# 更换时区
sudo timedatectl set-timezone Asia/Shanghai

2. 下载镜像源(所有节点均执行):

# 配置阿里云镜像站点
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat >/etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

apt-get update

# 安装指定版本,与CRI-O版本保持一致即可
apt install -y  kubeadm=1.26.4-00 kubelet=1.26.4-00 kubectl=1.26.4-00

3. 下载后使用 kubeadm生成配置文件,根据该文件修改即可(master节点执行):

# 生成默认配置并修改
kubeadm config print init-defaults > kubeadm.yaml

vi kubeadm.yaml

生成的配置文件做出如下修改:

apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
  - system:bootstrappers:kubeadm:default-node-token
  token: abcdef.0123456789abcdef 
  ttl: 24h0m0s
  usages:
  - signing
  - authentication
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 0.0.0.0                    # 修改为kubernetes主节点IP
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///var/run/crio/crio.sock    # 将默认的containerd改为crio
  imagePullPolicy: IfNotPresent
  name: k8s-matser                             # master节点的hostname
  taints: null
---
apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers # 修改镜像仓库为阿里云
kind: ClusterConfiguration
kubernetesVersion: 1.26.4                      # 指定版本
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.1.0.0/16  # 增加指定pod的网段
scheduler: {}
---
# 使用ipvs
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
---
# 指定cgroup
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd

4. 集群初始化:

kubeadm init --config kubeadm.yaml 

初始化成功后集群要求的操作:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

5. 配置flannel网络:

vi flannel.yaml

填入下面内容: 

---
kind: Namespace
apiVersion: v1
metadata:
  name: kube-flannel
  labels:
    k8s-app: flannel
    pod-security.kubernetes.io/enforce: privileged
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: flannel
  name: flannel
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
- apiGroups:
  - networking.k8s.io
  resources:
  - clustercidrs
  verbs:
  - list
  - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: flannel
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-flannel
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: flannel
  name: flannel
  namespace: kube-flannel
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-flannel
  labels:
    tier: node
    k8s-app: flannel
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds
  namespace: kube-flannel
  labels:
    tier: node
    app: flannel
    k8s-app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni-plugin
        image: rancher/mirrored-flannelcni-flannel-cni-plugin:v1.0.0
       #image: docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin:v1.1.2
        command:
        - cp
        args:
        - -f
        - /flannel
        - /opt/cni/bin/flannel
        volumeMounts:
        - name: cni-plugin
          mountPath: /opt/cni/bin
      - name: install-cni
        image: lizhenliang/flannel:v0.11.0-amd64
       #image: docker.io/rancher/mirrored-flannelcni-flannel:v0.21.5
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: lizhenliang/flannel:v0.11.0-amd64
       #image: docker.io/rancher/mirrored-flannelcni-flannel:v0.21.5
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: EVENT_QUEUE_DEPTH
          value: "5000"
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
        - name: xtables-lock
          mountPath: /run/xtables.lock
      volumes:
      - name: run
        hostPath:
          path: /run/flannel
      - name: cni-plugin
        hostPath:
          path: /opt/cni/bin
      - name: cni
        hostPath:
          path: /etc/cni/net.d
      - name: flannel-cfg
        configMap:
          name: kube-flannel-cfg
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate

执行配置文件:

kubectl apply -f flannel.yaml

6. 创建一个加入令牌,并输出一个包含该令牌的节点加入命令,以便其他节点可以使用这个命令来加入集群:

kubeadm token create --print-join-command

根据提示,在node节点上执行join命令加入集群即可。

  • 37
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏虫_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值