
简介容器技术通过操作系统级别的虚拟化实现了应用及其依赖的打包与隔离其核心原理是基于Linux内核的cgroups和namespace机制。这项技术为现代软件部署带来了环境一致性、资源高效利用和快速弹性伸缩的核心价值已成为云原生和微服务架构的基石。在信创国产化替代的背景下其应用场景正从通用的x86服务器扩展至ARM架构的国产化环境。本文聚焦于在ARM架构的银河麒麟V10操作系统上使用containerd作为容器运行时部署稳定版Kubernetes 1.26.15集群的完整工程实践详细拆解了从系统调优、运行时配置到集群初始化的全流程并针对信创环境中常见的镜像拉取、cgroup驱动配置等挑战提供了解决方案。1. 项目背景与核心挑战最近在帮一个做信创项目的团队做技术支撑他们手头有几台基于ARM架构的服务器操作系统是银河麒麟V10。需求很明确要在上面部署一套Kubernetes集群用于跑一些国产化的中间件和业务应用。说实话接到这个需求时我第一反应是“这活儿有点意思但坑肯定不少”。因为平时我们玩K8S大多是在x86的CentOS或者Ubuntu上用Docker作为容器运行时教程一抓一大把。但切换到ARM架构的麒麟V10并且指定要用containerd而不是Docker很多“理所当然”的步骤和包在这里可能就完全不是一回事了。为什么是containerd这其实是一个趋势。从K8S 1.24版本开始Dockershim就被移除了这意味着K8S不再直接支持Docker作为运行时。虽然Docker本身可以配置使用containerd但直接使用containerd作为运行时架构更清晰资源占用也更少特别是在资源相对受限的ARM服务器上这个优势会被放大。所以直接上containerd既是顺应技术潮流也是贴合实际资源状况的选择。至于为什么选K8S 1.26.15这个相对较旧的版本而不是最新的这里就有实战经验了。信创环境下的软件生态尤其是操作系统和CPU架构的特定组合对新版本的支持往往有滞后性。最新的K8S版本可能依赖较新的内核特性或系统库在麒麟V10上可能会遇到兼容性问题。选择一个经过一段时间社区验证的、相对稳定的次新版本1.26是2023年初发布的15是它的一个补丁版本能最大程度避免成为“小白鼠”减少在基础环境上踩坑的时间。我们的目标是快速搭建一个稳定可用的集群而不是追新。所以这个项目的核心挑战就清晰了在ARM架构的银河麒麟V10操作系统上绕过Docker直接使用containerd作为容器运行时部署一个指定版本1.26.15的Kubernetes集群。这涉及到操作系统层面的依赖调整、ARM架构的镜像获取、containerd的特定配置以及K8S组件与这套独特环境的适配。接下来我就把从零开始到集群就绪的完整过程包括每一步的原理、踩过的坑和解决方案详细拆解出来。2. 环境准备与系统调优兵马未动粮草先行。在ARM服务器上部署K8S第一步不是急着装软件而是要把操作系统这个“地基”打好。麒麟V10基于Linux内核但它在软件源、安全策略和一些默认配置上与常见的CentOS/Ubuntu有显著差异。2.1 硬件与操作系统确认首先你需要确认你的服务器确实是ARM架构并且系统版本无误。通过几条命令就能搞定# 查看CPU架构 uname -m # 输出应为 aarch64这是ARM 64位架构的通用标识 # 查看操作系统详细信息 cat /etc/os-release # 确认 NAME 字段包含 “Kylin Linux Advanced Server V10”为什么强调这个因为后续所有二进制包如kubeadm, kubelet, kubectl, containerd都必须下载aarch64版本x86_64即amd64的包是绝对无法运行的。这是第一个也是最容易犯的错。2.2 系统基础配置与内核参数调优麒麟V10默认的安全策略和内核参数可能不适合运行K8S。我们需要进行一系列优化。关闭SwapK8S从设计上就不希望使用Swap因为内存交换会导致性能不可预测可能影响调度。虽然从1.22版本开始在特定配置下可以开启但为了稳定和简化我们首选关闭。# 临时关闭 swapoff -a # 永久关闭注释掉 /etc/fstab 文件中所有包含 swap 的行 sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab关闭防火墙与SELinux在实验或内网环境中为了排除网络干扰我们通常选择关闭防火墙。麒麟V10默认使用firewalld。同时SELinux在容器环境下配置复杂初期也建议关闭。# 停止并禁用firewalld systemctl stop firewalld systemctl disable firewalld # 关闭SELinux临时 setenforce 0 # 永久关闭修改 /etc/selinux/config将 SELINUXenforcing 改为 SELINUXdisabled sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config注意生产环境请务必根据安全规范配置精确的防火墙规则和SELinux策略而不是简单关闭。此处仅为搭建测试环境提供便利。配置内核参数并加载模块K8S需要一些特定的内核参数来支持网络、桥接流量转发等。编辑/etc/sysctl.d/k8s.conf文件如果不存在则创建cat EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables 1 net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 EOF # 使配置生效 sysctl --system加载overlay和br_netfilter内核模块overlay是containerd常用的存储驱动br_netfilter用于桥接网络流量过滤。# 手动加载 modprobe overlay modprobe br_netfilter # 确保开机自动加载 cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF这里有个麒麟V10的“特色坑”tmpfs大小问题。在安装系统时如果/tmp分区设置过小可能会影响容器镜像的临时解压操作。虽然我们主要使用/var/lib/containerd但一些临时操作仍会用到/tmp。建议确保/tmp有至少2GB的可用空间。可以通过df -h查看。如果太小可以考虑在安装系统时调整或者后续通过绑定挂载的方式将/var/lib/containerd/tmp这类目录指向更大的存储空间。2.3 配置国内软件源麒麟V10默认的源可能速度慢或不包含我们需要的软件包如conntrack、socat。我们需要配置一个可靠的国内源。由于银河麒麟的源有其特殊性通常建议使用官方源或可靠的国内镜像站如清华、中科大镜像站中提供的麒麟源。以下以配置阿里云镜像站为例请根据实际情况调整有时需要联系麒麟官方获取最佳源地址# 备份原源文件 cp /etc/yum.repos.d/kylin_aarch64.repo /etc/yum.repos.d/kylin_aarch64.repo.bak # 编辑源文件将 baseurl 替换为阿里云镜像地址示例具体URL需查询确认 # 例如baseurlhttp://mirrors.aliyun.com/kylin/KYLIN-ALL/ # 注意不同版本如SP1 SP2路径不同务必匹配。如果找不到合适的第三方镜像使用官方源并耐心等待可能是最稳妥的选择。同时我们可以添加EPEL源Extra Packages for Enterprise Linux来获取更多工具。但要注意EPEL主要为CentOS/RHEL设计在麒麟上可能存在兼容性问题需谨慎使用。# 安装EPEL源谨慎操作 yum install -y https://mirrors.aliyun.com/epel/epel-release-latest-8.noarch.rpm # 安装完成后可以尝试安装一些工具如 conntrack-tools, socat完成源配置后更新系统并安装基础工具yum makecache yum update -y yum install -y vim wget curl net-tools conntrack-tools socat ipvsadm ipsetipvsadm和ipset是后续使用IPVS作为kube-proxy负载均衡模式时需要的工具先装上备用。3. 容器运行时Containerd的安装与配置这是与传统Docker方案分道扬镳的关键一步。Containerd是一个更底层、更专注的容器运行时K8S的kubelet会通过CRIContainer Runtime Interface插件与它交互。3.1 安装Containerd我们不能直接使用麒麟V10默认仓库里可能存在的旧版本containerd。需要去GitHub releases页面下载指定版本的二进制包。我们选择与K8S 1.26兼容的稳定版本例如containerd-1.6.x。# 定义版本号 export CONTAINERD_VERSION1.6.28 # 下载ARM架构的发布包 wget https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-arm64.tar.gz # 解压到系统目录 sudo tar Cxzvf /usr/local containerd-${CONTAINERD_VERSION}-linux-arm64.tar.gz解压后二进制文件containerd和ctr就在/usr/local/bin/下了。但这样运行缺乏系统服务管理。我们需要配置systemd服务。# 下载containerd的service文件 wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service # 将其放到系统服务目录 sudo mv containerd.service /usr/lib/systemd/system/ # 重新加载systemd配置 sudo systemctl daemon-reload # 设置开机自启 sudo systemctl enable --now containerd此时运行systemctl status containerd服务应该是inactive或启动失败因为还没有配置文件。3.2 生成与修改Containerd配置文件Containerd默认的配置文件路径是/etc/containerd/config.toml。我们可以用命令生成一个默认配置sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml现在我们需要编辑这个配置文件主要是两个地方修改pause镜像地址K8S使用的“沙盒”容器镜像默认是Google的k8s.gcr.io/pause:3.9在国内无法拉取。我们必须将其替换为国内可访问的镜像。配置Systemd Cgroup驱动为了与K8S的kubelet保持一致我们需要使用systemd作为cgroup驱动而不是默认的cgroupfs。用vim打开/etc/containerd/config.toml找到并修改# 在 plugins.io.containerd.grpc.v1.cri 部分下找到 sandbox_image [plugins.io.containerd.grpc.v1.cri] sandbox_image registry.aliyuncs.com/google_containers/pause:3.9 # 或者使用其他国内镜像如 registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9 # 在 plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options 部分下修改SystemdCgroup [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true重要提示配置文件中可能有多个runc相关的部分请确保修改的是plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options这个路径下的。有时候默认配置里可能没有SystemdCgroup这一项需要你手动添加。3.3 启动Containerd并安装runc配置完成后启动containerd服务sudo systemctl restart containerd sudo systemctl enable containerd sudo systemctl status containerd # 检查状态是否为 active (running)Containerd依赖runc来实际运行容器。我们之前下载的containerd包中不包含runc需要单独安装。同样去GitHub下载ARM版本。# 下载runc的ARM64二进制文件 wget https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.arm64 # 安装 sudo install -m 755 runc.arm64 /usr/local/sbin/runc验证安装sudo runc --version应能输出版本信息。3.4 配置crictl工具可选但推荐crictl是类似于docker的命令行工具用于调试和排查CRI运行时如containerd的问题。K8S集群部署中有时需要用它来查看容器状态。# 下载crictl wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.28.0/crictl-v1.28.0-linux-arm64.tar.gz # 解压 sudo tar zxvf crictl-v1.28.0-linux-arm64.tar.gz -C /usr/local/bin # 生成配置文件 cat EOF | sudo tee /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false EOF现在可以测试一下containerd是否工作正常sudo crictl ps应该返回空列表因为没有容器在运行。sudo crictl images也是空的。至此容器运行时环境就准备妥当了。这里最关键的教训是配置文件中的SystemdCgroup true和sandbox_image的替换是后续kubelet能正常启动Pod的基础缺一不可。我曾在测试时漏掉了SystemdCgroup配置导致kubelet一直报cgroup驱动不匹配的错误排查了很久。4. Kubernetes组件安装与Master节点初始化有了健康的containerd我们就可以安装K8S的核心三件套了kubeadm,kubelet,kubectl。同样我们必须使用ARM64架构的版本。4.1 配置Kubernetes阿里云镜像源由于网络原因我们无法直接访问Google的仓库。阿里云提供了K8S组件的镜像源。# 创建软件源文件 cat EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-aarch64 enabled1 gpgcheck1 repo_gpgcheck1 gpgkeyhttps://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF注意baseurl中的aarch64这指明了是ARM架构的仓库。x86_64的仓库地址是不同的。4.2 安装指定版本的K8S组件我们不安装最新版而是指定1.26.15版本。# 更新yum缓存 yum makecache # 安装指定版本 --disableexcludes 是为了防止某些仓库排除kubernetes包 yum install -y kubelet-1.26.15 kubeadm-1.26.15 kubectl-1.26.15 --disableexcludeskubernetes # 设置kubelet开机自启但先不启动等kubeadm init之后再启动 systemctl enable kubelet4.3 配置kubelet使用containerd告诉kubelet容器运行时是containerd并且cgroup驱动是systemd。编辑kubelet的默认参数文件cat EOF | sudo tee /etc/sysconfig/kubelet KUBELET_EXTRA_ARGS--container-runtimeremote --container-runtime-endpointunix:///run/containerd/containerd.sock --cgroup-driversystemd EOF这个配置至关重要。--container-runtimeremote表示使用CRI接口--container-runtime-endpoint指定了containerd的socket文件位置--cgroup-driver必须与containerd配置中的SystemdCgroup设置一致。4.4 使用kubeadm初始化Master节点这是最核心的一步。我们需要生成一个初始化配置文件因为有很多参数需要定制。# 生成默认配置 kubeadm config print init-defaults kubeadm-init.yaml现在编辑这个kubeadm-init.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: 192.168.1.100 # 修改为Master节点的实际IP地址 bindPort: 6443 nodeRegistration: criSocket: unix:///run/containerd/containerd.sock # 指定CRI socket imagePullPolicy: IfNotPresent name: k8s-master # 节点名按需修改 taints: [] --- 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.15 # 指定版本 networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 podSubnet: 192.168.0.0/16 # 设置Pod网段需要与后续安装的CNI插件匹配这里以Calico为例 scheduler: {} --- # 添加一个额外的配置用于配置kubelet的cgroup驱动与前面sysconfig配置呼应 apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd重点解释advertiseAddress: 必须是Master节点其他节点能访问到的IP。criSocket: 明确指向containerd。imageRepository: 这是成功的关键将所有k8s.gcr.io的镜像拉取地址替换为阿里云镜像仓库否则初始化会因网络问题卡住。podSubnet: 需要与你将要安装的网络插件CNI的默认网段一致。例如Calico默认使用192.168.0.0/16。如果你用Flannel可能是10.244.0.0/16。务必提前规划好并在安装CNI时确认。配置文件准备好后开始初始化sudo kubeadm init --configkubeadm-init.yaml --upload-certs | tee kubeadm-init.log这个过程会持续几分钟它会拉取所需的ARM架构镜像从阿里云并生成各种证书和静态Pod清单。如果一切顺利你会在最后看到类似这样的成功信息Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run kubectl apply -f [podnetwork].yaml with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.1.100:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:一串很长的哈希值务必把最后那段kubeadm join的命令保存好这是Worker节点加入集群的凭证。按照提示配置kubectlmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config现在你可以用kubectl get nodes查看节点但Master节点会处于NotReady状态因为还没有安装网络插件CNI。5. 安装CNI网络插件与Worker节点加入没有网络Pod之间无法通信节点状态也就不会是Ready。我们选择Calico作为CNI插件它功能强大且对ARM架构支持良好。5.1 在Master节点安装Calico去Calico官网查找对应K8S版本的安装 manifest。对于1.26.15我们可以使用Calico v3.26.x。同样由于网络问题我们需要修改manifest中的镜像地址。# 下载Calico的Operator安装manifest wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/tigera-operator.yaml # 下载自定义资源定义 wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/custom-resources.yaml在应用之前必须修改custom-resources.yaml中的cidr使其与我们在kubeadm-init.yaml中设置的podSubnet192.168.0.0/16一致。# custom-resources.yaml apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - blockSize: 26 cidr: 192.168.0.0/16 # 确保这里与kubeadm配置一致 encapsulation: VXLANCrossSubnet natOutgoing: Enabled nodeSelector: all()然后应用这两个文件kubectl create -f tigera-operator.yaml kubectl create -f custom-resources.yaml等待Calico Pod全部运行起来watch kubectl get pods -n calico-system当所有Pod状态都变为Running后再查看节点状态kubectl get nodesMaster节点应该会变为Ready。5.2 Worker节点加入集群在另一台ARM架构的麒麟V10服务器上Worker节点重复第2章环境准备与系统调优和第3章容器运行时Containerd的安装与配置的所有步骤。确保系统参数一致关闭swap、防火墙等。containerd安装并配置成功特别是pause镜像和systemd cgroup驱动。安装相同版本的kubeadm和kubelet版本必须与Master一致这里是1.26.15。配置kubelet使用containerd/etc/sysconfig/kubelet。然后在Worker节点上执行之前保存的kubeadm join命令sudo kubeadm join 192.168.1.100:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:一串很长的哈希值如果token过期默认24小时可以在Master节点上重新生成# 在Master节点执行 kubeadm token create --print-join-command加入成功后在Master节点执行kubectl get nodes应该能看到两个节点状态都是Ready。5.3 验证集群状态执行一些基本命令来验证集群功能# 查看所有节点 kubectl get nodes -o wide # 查看所有命名空间的Pod kubectl get pods --all-namespaces # 部署一个测试应用 kubectl create deployment nginx-test --imagenginx:alpine kubectl expose deployment nginx-test --port80 --typeNodePort # 查看服务获取NodePort端口 kubectl get svc nginx-test # 在集群内访问测试在Master或Worker上执行 curl ClusterIP:80 # 或者通过节点的IP和NodePort从外部访问如果网络可达 curl Node_IP:NodePort如果测试应用能正常访问说明你的基于ARM麒麟V10和containerd的K8S 1.26.15集群已经成功搭建完成6. 常见问题排查与经验总结在整个部署过程中我遇到了几个典型问题这里总结一下方便你快速定位。问题一kubeadm init 卡在[preflight]或拉取镜像失败。原因最常见的原因是网络问题无法从k8s.gcr.io拉取镜像。解决确保kubeadm-init.yaml中的imageRepository已正确设置为国内镜像源如registry.aliyuncs.com/google_containers。可以手动拉取镜像kubeadm config images pull --config kubeadm-init.yaml先测试。问题二kubelet启动失败日志报failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs或类似cgroup驱动不匹配。原因containerd的cgroup驱动在/etc/containerd/config.toml中设置与kubelet的cgroup驱动在/etc/sysconfig/kubelet中设置不一致。解决确保两者都设置为systemd。修改后重启containerd和kubeletsystemctl restart containerd systemctl restart kubelet。问题三Pod一直处于Pending状态describe pod 显示0/1 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }。原因节点未就绪。通常是因为CNI网络插件未安装或安装失败。解决检查Calico或其他CNI的Pod是否全部Runningkubectl get pods -n calico-system。查看异常Pod的日志kubectl logs -n calico-system pod-name。常见原因是镜像拉取失败可以手动在节点上crictl pull对应的ARM镜像。问题四Worker节点加入失败提示[ERROR Port-10250]: Port 10250 is in use或其他端口冲突。原因可能之前安装过K8S或Docker有残留进程。解决在Worker节点上彻底清理kubeadm reset -f然后rm -rf /etc/cni/net.d /etc/kubernetes /var/lib/etcd并重启containerd和kubelet再重新执行join命令。问题五使用crictl images看不到k8s的镜像但Pod能运行。原因crictl默认配置可能不对或者containerd的命名空间问题。K8S的镜像在k8s.io命名空间下。解决确保/etc/crictl.yaml配置正确。查看镜像时指定命名空间crictl images --namespacek8s.io。一些重要的经验点版本对齐K8S组件kubeadm, kubelet, kubectl、CNI插件Calico、容器运行时containerd之间的版本兼容性非常重要。最好参考官方兼容性矩阵进行选择。镜像架构所有镜像系统组件pause、Calico插件、业务应用如nginx都必须有ARM64aarch64版本。在拉取公共镜像时默认可能是x86的。对于业务应用需要在Dockerfile中指定--platformlinux/arm64构建或寻找支持多架构的镜像。配置文件是王道containerd的config.toml和kubeadm的init-config.yaml是核心。任何修改后务必重启相关服务。日志排查当遇到问题时journalctl -u kubelet -f和journalctl -u containerd -f是你的好朋友。kubectl describe pod pod-name和kubectl logs pod-name能提供Pod层面的详细信息。这套从ARM麒麟V10裸机到K8S集群的搭建流程虽然步骤繁琐但每一步都有其必要性。理解每一步背后的“为什么”比单纯复制命令更重要。希望这份超详细的记录能帮你顺利趟平这条路。本文还有配套的精品资源点击获取