Kubernetes
Kubernetes (又名 k8s) 是一个开源系统,用于自动化容器化应用程序的部署、扩展和管理。
一个 k8s 集群由其控制平面组件和节点组件组成(每个组件代表一台或多台运行容器运行时和 kubelet.service
的主机)。安装 Kubernetes 有两种选择,“真正的” Kubernetes,这里描述的,以及使用 k3s、kind 或 minikube 进行本地安装。
安装
有许多方法可以设置 Kubernetes 集群。本文将重点介绍使用 kubeadm 进行引导。
部署工具
kubeadm
当使用 kubeadm 引导 Kubernetes 集群时,在每个节点上安装 kubeadm 和 kubelet。
手动安装
当手动创建 Kubernetes 集群时,安装 etcdAUR 和 软件包组 kubernetes-control-plane(用于控制平面节点)和 kubernetes-node(用于工作节点)。
集群管理
要控制 Kubernetes 集群,请在控制平面主机以及任何应该能够与集群交互的外部主机上安装 kubectl。
容器运行时
控制平面节点和常规工作节点都需要容器运行时,供其 kubelet
实例使用,用于托管容器。安装 containerd 或 cri-o 以满足此依赖项。
containerd 运行时
有两种方法可以安装 containerd
- 安装 containerd 软件包。
- 要安装 rootless containerd,请使用 nerdctl-full-binAUR,这是一个完整的 nerdctl 软件包,与 containerd、CNI 插件和 RootlessKit 捆绑在一起。可以使用
containerd-rootless-setuptool.sh install
启动 Rootless containerd。
请记住,Arch Linux 使用 systemd 作为其 init 系统,因此在部署控制平面之前,您需要选择 systemd cgroup 驱动。
(可选)软件包管理器
helm 是一个用于管理预配置 Kubernetes 资源的工具,可能对入门有所帮助。
配置
集群中的所有节点(控制平面和工作节点)都需要运行 kubelet.service
的实例。
kubelet.service
或使用 kubeadm
之前,请仔细阅读以下小节。所有提供的 systemd 服务都接受环境变量文件中的 CLI 覆盖
kubelet.service
:/etc/kubernetes/kubelet.env
kube-apiserver.service
:/etc/kubernetes/kube-apiserver.env
kube-controller-manager.service
:/etc/kubernetes/kube-controller-manager.env
kube-proxy.service
:/etc/kubernetes/kube-proxy.env
kube-scheduler.service
:/etc/kubernetes/kube-scheduler.env
禁用交换分区
Kubernetes 当前不支持在系统上启用交换分区。有关详细信息,请参阅 KEP-2400:节点系统交换分区支持。
有关如何禁用交换分区,请参阅 Swap#Disabling swap。
为 containerd 选择 cgroup 驱动
要在 /etc/containerd/config.toml
中使用 systemd
cgroup 驱动程序和 runc
,请设置
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true
如果 /etc/containerd/config.toml
不存在,可以使用 [1] 生成默认配置
# mkdir -p /etc/containerd/ # containerd config default > /etc/containerd/config.toml
记住要重启 containerd.service
以使更改生效。
有关是保留 cgroupfs
驱动程序还是使用 systemd
cgroup 驱动程序的更深入讨论,请参阅官方文档。
选择容器运行时接口 (CRI)
在 kubelet.service
可以使用容器运行时之前,必须配置并启动容器运行时。
您将传递带有容器运行时接口端点 --cri-socket
标志给 kubeadm init
或 kubeadm join
,以便创建或加入集群。
例如,如果您选择 containerd 作为 CRI 运行时,则 --cri-socket
标志将是
kubeadm init --cri-socket /run/containerd/containerd.sock
Containerd
在 Kubernetes 1.27.4 版本之前,当使用 containerd 作为容器运行时时,需要向 kubeadm init
或 kubeadm join
提供其 CRI 端点。为此,请将其 --cri-socket
标志指定为 /run/containerd/containerd.sock
[2]。
kubeadm join --cri-socket=/run/containerd/containerd.sock
在 Kubernetes 1.27.4 版本之后,kubeadm 将自动检测此 CRI,只有在您安装了多个 CRI 时才需要 --cri-socket
标志。
CRI-O
当使用 CRI-O 作为容器运行时时,需要向 kubeadm init
或 kubeadm join
提供其 CRI 端点:--cri-socket='unix:///run/crio/crio.sock'
选择集群网络参数
选择 Pod CIDR 范围
必须为各自的容器运行时配置集群的网络设置。这可以使用 cni-plugins 完成。
Pod CIDR 地址指的是 Kubernetes 集群中分配给 Pod 的 IP 地址范围。当 Pod 被调度到集群中的节点上运行时,将从这个 CIDR 范围分配 IP 地址给它们。
Pod CIDR 范围在部署 Kubernetes 集群时指定,并限制在集群网络内。它不应与集群内使用的其他 IP 范围(例如服务 CIDR 范围)重叠。
您将传递带有虚拟网络的 CIDR 值的 --pod-network-cidr
标志给 kubeadm init
或 kubeadm join
,以便创建或加入集群。
例如
kubeadm init --pod-network-cidr='10.85.0.0/16'
将您的 Kubernetes 的 Pod CIDR 范围设置为 10.85.0.0/16
。
(可选)选择 API 服务器广播地址
如果您的控制平面节点位于多个子网中(例如,您可能安装了 tailscale tailnet),当使用 kubeadm init 初始化 Kubernetes master 时,您可以指定 API 服务器将使用 --apiserver-advertise-address
标志广播的 IP 地址。此 IP 地址应该可以被集群中的所有节点访问。
(可选)选择备用节点网络代理提供程序
节点代理提供程序(如 kube-proxy
)是一个网络代理,它在集群中的每个节点上运行,维护节点上的网络规则,以允许从集群内部或外部的网络会话与您的 Pod 进行网络通信。
默认情况下,kubeadm 选择 kube-proxy
作为在集群中每个节点上运行的节点代理。
容器网络接口 (CNI) 插件(如 Cilium)为 kube-proxy 提供了完整的替代方案。
如果您想使用 Cilium 的节点网络代理实现来充分利用 Cilium 的网络策略功能,则应将 --skip-phases=addon/kube-proxy
标志传递给 kubeadm init
,以跳过 kube-proxy 的安装。
Cilium 将在其安装过程中安装完整的替代方案。有关详细信息,请参阅此[3]。
创建集群
在使用 kubeadm
创建新的 Kubernetes 集群 之前,启动 并 启用 kubelet.service
。
kubelet.service
将会失败(但会重启)。不使用配置文件的 kubeadm
当使用 kubeadm
创建新的 Kubernetes 集群 时,必须先创建一个控制平面,然后才能有更多工作节点加入它。
- 如果集群应该变成高可用性集群(堆叠式 etcd 拓扑),则稍后需要在
kubeadm init
中提供--control-plane-endpoint=<IP 或域名>
(这样做无法追溯!)。 - 可以使用 配置文件 来代替一组参数用于
kubeadm init
。
初始化控制平面
要初始化控制平面,您需要将以下必要的标志传递给 kubeadm init
如果运行成功,kubeadm init
将会在 /etc/kubernetes/
和 /var/lib/kubelet/
下为 kubelet
和各种控制平面组件生成配置。
最后,它将输出准备好复制和粘贴的命令,以设置 kubectl 并使工作节点加入集群(基于令牌,有效期为 24 小时)。
要将 kubectl
与新创建的控制平面节点一起使用,请设置配置(以 root 用户或普通用户身份)
$ mkdir -p $HOME/.kube # cp -i /etc/kubernetes/admin.conf $HOME/.kube/config # chown $(id -u):$(id -g) $HOME/.kube/config
安装 CNI 插件(Pod 网络插件)
Pod 网络附加组件 (CNI 插件) 以不同于简单解决方案(如 flannel)到更复杂解决方案(如 calico)的方式实现 Kubernetes 网络模型。有关更多选项,请参阅此列表。
一个越来越被采用的先进 CNI 插件是 cilium,它通过 eBPF 实现了令人印象深刻的性能。要安装 cilium
作为 CNI 插件,请使用 cilium-cli
# cilium-cli install
这将创建 /opt/cni/bin/cilium-cni
插件、配置文件 /etc/cni/net.d/05-cilium.conflist
并在 Kubernetes 集群上部署两个 Pod。
/opt/cni/bin/
插件目录。请参阅 CRI-O#插件目录。使用配置文件的 kubeadm
您很可能会发现,创建控制平面需要多次尝试才能找到适合您特定设置的最佳配置。为了使这更容易(并且通常使 kubeadm 过程更具可重复性),您可以运行使用配置文件的初始化步骤。
创建初始化配置文件
您可以在任何位置创建此文件,但在此示例中,我们将使用 /etc/kubeadm
。
# mkdir -pv /etc/kubeadm # cd /etc/kubeadm # kubeadm config print init-defaults > init.yaml
这将生成以下文件。
/etc/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: 1.2.3.4 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: node 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.k8s.io kind: ClusterConfiguration kubernetesVersion: 1.29.0 networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 scheduler: {}
大多数默认设置应该可以工作,但您需要更新其中的一些设置。
引导令牌
使用 kubeadm token generate
创建令牌,并在配置中使用它代替 token: abcdef.0123456789abcdef
。
广播地址
advertiseAddress: 1.2.3.4
应该是正在初始化的控制平面上的网络接口的 IPv4 地址,可能位于 192.168.0.0/16
子网中。
节点名称
默认节点名称可以保留为 node
并添加到您的本地 DNS 服务器或 hosts 文件中,或者您可以将其更改为在本地网络上可路由的地址。它应该是一个 DNS 兼容的主机名,例如 kcp01.example.com
。这将允许在您加入其他节点时在本地网络上找到您的控制平面。
初始化集群
设置完所有这些更改后,我们可以初始化我们的集群。
# kubeadm init --config /etc/kubeadm/init.yaml
这将产生大量的输出,这些输出将提供有关如何将节点加入集群、更新您的 kubeconfig 以与新集群交互以及其他任务的说明。
使用 Calico 进行 CNI 配置
在您可以开始添加节点和运行工作负载之前,您需要的最后一件事是正确配置的 CNI。此示例将使用 calico 来实现。
# cd /etc/cni/net.d # curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/calico.yaml -O # kubectl create -f calico.yaml
如果此操作成功完成,您就可以开始在集群上添加节点和运行工作负载了。
创建重置配置文件
以防万一 kubeadm 第一次初始化失败,您还可以创建一个配置文件用于重置命令
# kubeadm config print reset-defaults > /etc/kubeadm/reset.yaml
这将创建以下文件
/etc/kubeadm/reset.yaml
apiVersion: kubeadm.k8s.io/v1beta4 certificatesDir: /etc/kubernetes/pki criSocket: unix:///var/run/containerd/containerd.sock kind: ResetConfiguration
重置集群
要将集群重置为零,请运行以下命令
# kubeadm reset --config /etc/kubeadm/reset.yaml
可以根据需要多次执行此操作,以整理出集群的理想配置。
创建加入配置文件
最有可能的是,一旦您初始化集群,您将能够使用 init 命令的输出中列出的命令加入任何节点,但如果您碰巧遇到问题,那么在您要加入的节点上拥有一个加入配置文件将很有帮助。您可以在控制平面上创建此文件,或者在您打算加入集群的节点上运行该命令,我们假设您执行了后者。
# kubeadm config print join-defaults > /etc/kubeadm/join.yaml
这将创建以下文件。
/etc/kubeadm/join.yaml
apiVersion: kubeadm.k8s.io/v1beta3 caCertPath: /etc/kubernetes/pki/ca.crt discovery: bootstrapToken: apiServerEndpoint: kcp01.example.com:6443 token: abcdef.0123456789abcdef unsafeSkipCAVerification: true timeout: 5m0s tlsBootstrapToken: abcdef.0123456789abcdef kind: JoinConfiguration nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: node01.example.com taints: null
discovery.bootstrapToken.token
,第二个用于 discovery.tlsBootstrapToken
属性。加入集群
使用 #创建集群 中生成的令牌信息,可以使用命令 kubeadm join
使另一台机器作为工作节点加入集群。
请记住,您还需要通过将 <SOCKET>
标志传递给命令 kubeadm join
,为工作节点选择容器运行时接口。
例如
# kubeadm join <api-server-ip>:<port> --token <token> --discovery-token-ca-cert-hash sha256:<hash> --node-name=<name_of_the_node> --cri-socket=<SOCKET>
要生成新的引导令牌,
kubeadm token create --print-join-command
如果您使用 Cilium 并发现工作节点仍然处于 NotReady
状态,请使用以下命令检查工作节点的状态
kubectl describe node <node-id> --namespace=kube-system
如果您发现以下条件状态
Type Status Reason ---- ------ ------ NetworkUnavailable False CiliumIsUp Ready False KubeletNotReady container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
重启 工作节点上的 containerd.service
和 kubelet.service
技巧和窍门
拆除集群
kubectl drain <node name> --delete-local-data --force --ignore-daemonsets
这里的 <节点名称>
是应该被驱逐和重置的节点的名称。使用 kubectl get node -A
列出所有节点。
然后重置节点
# kubeadm reset
在代理后方操作
kubeadm
读取 https_proxy
、http_proxy
和 no_proxy
环境变量。Kubernetes 内部网络应包含在最新的环境变量中,例如
export no_proxy="192.168.122.0/24,10.96.0.0/12,192.168.123.0/24"
其中第二个是默认服务网络 CIDR。
故障排除
获取容器统计信息失败
如果 kubelet.service
发出
Failed to get system container stats for "/system.slice/kubelet.service": failed to get cgroup stats for "/system.slice/kubelet.service": failed to get container info for "/system.slice/kubelet.service": unknown container "/system.slice/kubelet.service"
则需要为 kubelet 添加配置(请参阅 相关的上游工单)。
/var/lib/kubelet/config.yaml
systemCgroups: '/systemd/system.slice' kubeletCgroups: '/systemd/system.slice'
使用 Flannel CNI 和 systemd-networkd 时 Pod 无法通信
请参阅 上游错误报告。
systemd-networkd 为每个链接分配一个持久 MAC 地址。此策略在其附带的配置文件 /usr/lib/systemd/network/99-default.link
中定义。但是,Flannel 依赖于能够选择自己的 MAC 地址。要覆盖 systemd-networkd 对 flannel*
接口的行为,请创建以下配置文件
/etc/systemd/network/50-flannel.link
[Match] OriginalName=flannel* [Link] MACAddressPolicy=none
然后重启 systemd-networkd.service
。
如果集群已经在运行,您可能需要在每个节点(包括 master 节点)上手动删除 flannel.1
接口和 kube-flannel-ds-*
Pod。Pod 将立即重新创建,它们本身将重新创建 flannel.1
接口。
删除接口 flannel.1
# ip link delete flannel.1
删除 kube-flannel-ds-*
Pod。使用以下命令删除所有节点上的所有 kube-flannel-ds-*
Pod
$ kubectl -n kube-system delete pod -l="app=flannel"
CoreDNS Pod 永远处于 pending 状态,控制平面节点保持 “NotReady” 状态
当在单台机器上使用 kubeadm init
引导 Kubernetes,并且没有其他机器 kubeadm join
加入集群时,控制平面节点默认会被污点化。因此,没有工作负载将被调度到工作机器上。
可以通过以下命令确认控制平面节点是否被污点化
kubectl get nodes -o json | jq '.items[].spec.taints
要临时允许在控制平面节点上进行调度,请执行
kubectl taint nodes <your-node-name> node-role.kubernetes.io/control-plane:NoSchedule-
然后重启 containerd.service
和 kubelet.service
以应用更新。
[kubelet-finalize] malformed header: missing HTTP content-type
您可能忘记选择 systemd cgroup 驱动。请参阅 kubeadm issue 2767 报告此问题。
CoreDNS Pod 由于循环未启动
当主机节点运行本地 DNS 缓存(例如 systemd-resolved)时,CoreDNS 可能会由于检测到转发循环而无法启动。可以通过以下方式检查这一点
# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE cilium-jc98m 1/1 Running 0 21m cilium-operator-64664858c8-zjzcq 1/1 Running 0 21m coredns-7db6d8ff4d-29zfg 0/1 CrashLoopBackOff 6 (41s ago) 21m coredns-7db6d8ff4d-zlvsm 0/1 CrashLoopBackOff 6 (50s ago) 21m etcd-k8s 1/1 Running 19 21m kube-apiserver-k8s 1/1 Running 17 21m kube-controller-manager-k8s 1/1 Running 16 21m kube-proxy-cvntt 1/1 Running 0 21m kube-scheduler-k8s 1/1 Running 23 21m
# kubectl logs -n kube-system coredns-7db6d8ff4d-29zfg
... [FATAL] plugin/loop: Loop ([::1]:46171 -> :53) detected for zone ".", see https://coredns.golang.ac.cn/plugins/loop#troubleshooting. Query: "HINFO 64811921068182325.3042126689798234092."
这是由 kubelet 将主机 /etc/resolv.conf
文件传递给所有使用默认 dnsPolicy
的 Pod 引起的。CoreDNS 使用此 /etc/resolv.conf
作为上游列表来转发请求。由于它包含一个环回地址(例如 127.0.0.53),CoreDNS 最终会将请求转发给自己。
请参阅 https://coredns.golang.ac.cn/plugins/loop/#troubleshooting 以解决此问题。
参见
- Kubernetes 文档 - 上游文档
- 使用 Kubeadm 创建 Kubernetes 集群 - 关于如何使用 kubeadm 设置 Kubernetes 集群的上游文档
- Kubernetes 术语表 - 解释所有 Kubernetes 特定术语的官方术语表
- Kubernetes 插件 - 第三方插件列表
- Kubelet 配置文件 - 关于 Kubelet 配置文件的文档
- 污点和容忍度 - 关于节点亲和性和污点的文档