手把手二进制部署 Kubernetes:从证书、etcd 到 Flannel 的完整过程


很多人第一次部署 Kubernetes,接触的是 kubeadm、RKE2、K3s,或者各种自动化安装脚本。

这些方式没有问题。

生产环境中,我也不建议为了“纯手工”而纯手工。

但是,如果想真正理解 Kubernetes:

  • kube-apiserver 到底依赖什么;
  • controller-manager 和 scheduler 怎么连接 API Server;
  • kubelet 为什么能够注册成 Node;
  • etcd 为什么必须单独配置 TLS;
  • Service CIDR、Pod CIDR 和 Cluster DNS 到底是什么关系;
  • 为什么 CNI 没装之前 Node 一直是 NotReady;
  • kubelet 和 containerd 的 cgroup 驱动为什么必须一致;

那么完整做一次二进制部署,仍然非常有价值。

这篇文章就从零开始,把 Kubernetes 的核心组件一层一层搭起来。

本文使用“单控制面 + 两个 Worker”的架构,目的是方便理解 Kubernetes 的内部工作机制。它适合学习、实验和内网测试,不建议原样用于生产环境。


一、实验环境规划

本文规划三台服务器:

主机名 IP 角色
k8s-master 192.168.10.10 etcd、API Server、Controller Manager、Scheduler
k8s-node1 192.168.10.11 containerd、kubelet、kube-proxy
k8s-node2 192.168.10.12 containerd、kubelet、kube-proxy

网络规划:

Service CIDR:10.96.0.0/12

Pod CIDR:10.244.0.0/16

Cluster DNS:10.96.0.10

API Server:
192.168.10.10:6443

本文使用:

Kubernetes:v1.37.0

etcd:3.6.x

containerd:2.3.x

runc:1.5.x

CNI Plugins:1.9.x

Flannel:0.28.x

整个 Kubernetes 可以先粗略理解成:

                 kubectl
                    │
                    ▼
              kube-apiserver
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
      etcd     controller    scheduler


                  Worker
        ┌─────────────────────┐
        │ kubelet             │
        │ kube-proxy          │
        │ containerd          │
        │ CNI / Flannel       │
        └─────────────────────┘

二、二进制部署到底是在部署什么

使用:

kubeadm init

的时候,很多工作都被 kubeadm 帮我们完成了。

例如:

生成 CA
生成组件证书
生成 kubeconfig
初始化 etcd
启动 API Server
启动 Controller Manager
启动 Scheduler
生成 kubelet 配置
配置 RBAC

而二进制部署就是把这些工作拆开。

整个过程大概是:

系统初始化
    ↓
containerd
    ↓
PKI 证书
    ↓
etcd
    ↓
kube-apiserver
    ↓
controller-manager
    ↓
scheduler
    ↓
kubelet
    ↓
kube-proxy
    ↓
Flannel
    ↓
CoreDNS

所以二进制部署真正有价值的地方不是“安装方式更复杂”,而是:

你能够看到 Kubernetes 每一个核心组件到底依赖谁。


三、初始化所有节点

以下操作需要在三台服务器执行。

1. 配置主机名

Master:

hostnamectl set-hostname k8s-master

Node1:

hostnamectl set-hostname k8s-node1

Node2:

hostnamectl set-hostname k8s-node2

2. 配置 hosts

三台机器统一:

cat >> /etc/hosts <<'EOF'
192.168.10.10 k8s-master
192.168.10.11 k8s-node1
192.168.10.12 k8s-node2
EOF

检查:

ping -c 2 k8s-master
ping -c 2 k8s-node1
ping -c 2 k8s-node2

四、关闭 Swap

执行:

swapoff -a

查看:

free -h

然后修改:

vi /etc/fstab

把 Swap 对应行注释。

检查:

swapon --show

如果没有任何输出,说明当前没有启用 Swap。


五、调整 SELinux

学习环境为了减少干扰,可以设置:

setenforce 0

修改:

sed -i \
's/^SELINUX=enforcing$/SELINUX=permissive/' \
/etc/selinux/config

生产环境是否调整 SELinux,应按照实际安全规范执行。


六、加载 Kubernetes 所需内核模块

创建:

cat > /etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
EOF

执行:

modprobe overlay
modprobe br_netfilter

检查:

lsmod | grep overlay
lsmod | grep br_netfilter

七、配置内核参数

创建:

cat > /etc/sysctl.d/99-kubernetes.conf <<'EOF'
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

加载:

sysctl --system

检查:

sysctl net.ipv4.ip_forward

应该看到:

net.ipv4.ip_forward = 1

这里非常关键。

如果没有开启 IP Forward,后面 Pod 跨节点网络很容易出现问题。


八、安装基础工具

Rocky Linux:

dnf install -y \
socat \
conntrack-tools \
iproute-tc \
iptables \
ethtool \
chrony \
curl \
wget \
tar

启动 chronyd:

systemctl enable --now chronyd

查看:

chronyc tracking

多节点环境一定要注意时间同步。

证书有有效期,etcd 和控制面组件也依赖一致的时间。

时间严重偏差时,经常会看到各种看起来莫名其妙的 TLS 错误。


九、创建 Kubernetes 目录

所有节点:

mkdir -p \
/etc/kubernetes/pki \
/var/lib/kubelet \
/var/lib/kube-proxy \
/etc/cni/net.d \
/opt/cni/bin

Master:

mkdir -p \
/etc/etcd/pki \
/var/lib/etcd

十、安装 containerd

Kubernetes 自己并不直接启动 Linux 容器。

真正负责容器生命周期管理的是 Container Runtime。

本文使用:

containerd

将 containerd 二进制包解压到:

/usr/local/

例如:

tar -C /usr/local \
-xzf containerd-2.3.x-linux-amd64.tar.gz

安装 runc:

install -m 0755 \
runc.amd64 \
/usr/local/sbin/runc

CNI Plugins 解压:

tar -C /opt/cni/bin \
-xzf cni-plugins-linux-amd64-v1.9.1.tgz

查看:

ls /opt/cni/bin

正常可以看到:

bridge
host-local
loopback
portmap
ptp

十一、配置 containerd

创建:

mkdir -p /etc/containerd

生成默认配置:

containerd config default \
> /etc/containerd/config.toml

首先检查:

grep -n disabled_plugins \
/etc/containerd/config.toml

不能把:

cri

放到:

disabled_plugins

里面。

Kubernetes 需要通过:

CRI

和 containerd 通信。


cgroup 配置

这里是一个非常容易踩坑的地方。

建议 kubelet 和 containerd 都统一使用:

systemd

containerd 2.x 中找到 runc runtime 对应配置,将:

SystemdCgroup = false

改成:

SystemdCgroup = true

如果出现:

containerd = systemd

kubelet = cgroupfs

这样的混合配置,不建议继续使用。


十二、配置 containerd systemd

创建:

vi /etc/systemd/system/containerd.service

内容:

[Unit]
Description=containerd container runtime
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd

Type=notify
Delegate=yes
KillMode=process

Restart=always
RestartSec=5

LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
TasksMax=infinity

OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target

重新加载:

systemctl daemon-reload

启动:

systemctl enable --now containerd

查看:

systemctl status containerd

检查版本:

ctr version

这一层必须正常,再继续往下。


十三、准备 Kubernetes 二进制文件

Master 需要:

kube-apiserver
kube-controller-manager
kube-scheduler
kubectl

Worker 需要:

kubelet
kube-proxy
kubectl

将二进制文件放到:

/usr/local/bin/

然后:

chmod +x /usr/local/bin/kube*

检查:

kube-apiserver --version

以及:

kubectl version --client

十四、二进制安装最重要的一关:PKI

很多人第一次看 Kubernetes 的证书目录会觉得非常复杂。

实际上理解之后并没有那么神秘。

可以把它理解成:

谁访问 API Server
谁就需要证明自己是谁

例如:

kubectl
    │
    │ admin.crt
    ▼
API Server

Controller Manager:

controller-manager
        │
        │ client certificate
        ▼
   kube-apiserver

Scheduler:

scheduler
    │
    ▼
kube-apiserver

kubelet:

system:node:k8s-node1
          │
          ▼
     kube-apiserver

API Server 访问 etcd:

kube-apiserver
      │
      │ apiserver-etcd-client.crt
      ▼
     etcd

所以:

Kubernetes 二进制部署其实很大一部分工作就是把各组件的“身份证”准备正确。


十五、创建 Kubernetes CA

在 Master:

cd /etc/kubernetes/pki

生成 CA 私钥:

openssl genrsa \
-out ca.key \
4096

生成 CA:

openssl req \
-x509 \
-new \
-nodes \
-key ca.key \
-subj "/CN=kubernetes-ca" \
-days 3650 \
-out ca.crt

查看:

openssl x509 \
-in ca.crt \
-noout \
-subject \
-dates

十六、生成 API Server 证书

创建私钥:

openssl genrsa \
-out apiserver.key \
4096

生成 CSR:

openssl req \
-new \
-key apiserver.key \
-subj "/CN=kube-apiserver" \
-out apiserver.csr

准备 SAN:

cat > apiserver-ext.cnf <<'EOF'
subjectAltName = DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:k8s-master,IP:10.96.0.1,IP:192.168.10.10,IP:127.0.0.1
extendedKeyUsage = serverAuth
keyUsage = digitalSignature,keyEncipherment
EOF

签发:

openssl x509 \
-req \
-in apiserver.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out apiserver.crt \
-days 3650 \
-extfile apiserver-ext.cnf

这里最值得注意的是:

10.96.0.1

它不是随便写的。

我们的:

Service CIDR

是:

10.96.0.0/12

其中第一个 Service IP:

10.96.0.1

通常就是:

kubernetes.default

对应的 ClusterIP。

所以它必须包含在 API Server 证书 SAN 中。


十七、API Server 访问 kubelet 的证书

创建:

openssl genrsa \
-out apiserver-kubelet-client.key \
4096

生成:

openssl req \
-new \
-key apiserver-kubelet-client.key \
-subj "/CN=kube-apiserver-kubelet-client/O=system:masters" \
-out apiserver-kubelet-client.csr

客户端扩展:

cat > client-ext.cnf <<'EOF'
extendedKeyUsage = clientAuth
keyUsage = digitalSignature,keyEncipherment
EOF

签发:

openssl x509 \
-req \
-in apiserver-kubelet-client.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out apiserver-kubelet-client.crt \
-days 3650 \
-extfile client-ext.cnf

十八、生成 ServiceAccount 密钥

openssl genrsa \
-out sa.key \
4096

生成公钥:

openssl rsa \
-in sa.key \
-pubout \
-out sa.pub

这套密钥主要用于:

ServiceAccount Token

的签发和验证。


十九、为 etcd 创建独立 CA

进入:

cd /etc/etcd/pki

生成:

openssl genrsa \
-out ca.key \
4096

创建 etcd CA:

openssl req \
-x509 \
-new \
-nodes \
-key ca.key \
-subj "/CN=etcd-ca" \
-days 3650 \
-out ca.crt

二十、生成 etcd Server 证书

openssl genrsa \
-out server.key \
4096
openssl req \
-new \
-key server.key \
-subj "/CN=etcd-server" \
-out server.csr

SAN:

cat > server-ext.cnf <<'EOF'
subjectAltName = DNS:k8s-master,DNS:localhost,IP:192.168.10.10,IP:127.0.0.1
extendedKeyUsage = serverAuth,clientAuth
keyUsage = digitalSignature,keyEncipherment
EOF

签发:

openssl x509 \
-req \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out server.crt \
-days 3650 \
-extfile server-ext.cnf

二十一、API Server 访问 etcd 的证书

openssl genrsa \
-out apiserver-etcd-client.key \
4096

创建 CSR:

openssl req \
-new \
-key apiserver-etcd-client.key \
-subj "/CN=kube-apiserver-etcd-client" \
-out apiserver-etcd-client.csr

签发:

openssl x509 \
-req \
-in apiserver-etcd-client.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out apiserver-etcd-client.crt \
-days 3650 \
-extfile /etc/kubernetes/pki/client-ext.cnf

二十二、启动 etcd

将:

etcd
etcdctl

放到:

/usr/local/bin

创建:

vi /etc/systemd/system/etcd.service

内容:

[Unit]
Description=etcd
After=network.target

[Service]
Type=notify

ExecStart=/usr/local/bin/etcd \
  --name=k8s-master \
  --data-dir=/var/lib/etcd \
  --listen-client-urls=https://127.0.0.1:2379,https://192.168.10.10:2379 \
  --advertise-client-urls=https://192.168.10.10:2379 \
  --listen-peer-urls=https://192.168.10.10:2380 \
  --initial-advertise-peer-urls=https://192.168.10.10:2380 \
  --initial-cluster=k8s-master=https://192.168.10.10:2380 \
  --initial-cluster-state=new \
  --client-cert-auth=true \
  --trusted-ca-file=/etc/etcd/pki/ca.crt \
  --cert-file=/etc/etcd/pki/server.crt \
  --key-file=/etc/etcd/pki/server.key \
  --peer-client-cert-auth=true \
  --peer-trusted-ca-file=/etc/etcd/pki/ca.crt \
  --peer-cert-file=/etc/etcd/pki/server.crt \
  --peer-key-file=/etc/etcd/pki/server.key

Restart=always
RestartSec=5

LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

启动:

systemctl daemon-reload
systemctl enable --now etcd

检查:

systemctl status etcd

查看日志:

journalctl -u etcd -n 100 --no-pager

二十三、验证 etcd

执行:

ETCDCTL_API=3 \
etcdctl \
--endpoints=https://192.168.10.10:2379 \
--cacert=/etc/etcd/pki/ca.crt \
--cert=/etc/etcd/pki/server.crt \
--key=/etc/etcd/pki/server.key \
endpoint health

正常应该看到:

is healthy

这里有一个非常重要的排障原则:

etcd 不正常,不要继续启动 API Server。

因为:

API Server

最终状态数据全部依赖 etcd。


二十四、启动 kube-apiserver

创建:

vi /etc/systemd/system/kube-apiserver.service

核心配置:

[Unit]
Description=Kubernetes API Server
After=network.target etcd.service
Wants=etcd.service

[Service]

ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=192.168.10.10 \
  --bind-address=0.0.0.0 \
  --secure-port=6443 \
  --authorization-mode=Node,RBAC \
  --enable-admission-plugins=NodeRestriction \
  --client-ca-file=/etc/kubernetes/pki/ca.crt \
  --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
  --tls-private-key-file=/etc/kubernetes/pki/apiserver.key \
  --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \
  --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \
  --kubelet-preferred-address-types=InternalIP,Hostname,ExternalIP \
  --etcd-servers=https://192.168.10.10:2379 \
  --etcd-cafile=/etc/etcd/pki/ca.crt \
  --etcd-certfile=/etc/etcd/pki/apiserver-etcd-client.crt \
  --etcd-keyfile=/etc/etcd/pki/apiserver-etcd-client.key \
  --service-cluster-ip-range=10.96.0.0/12 \
  --service-node-port-range=30000-32767 \
  --service-account-key-file=/etc/kubernetes/pki/sa.pub \
  --service-account-signing-key-file=/etc/kubernetes/pki/sa.key \
  --service-account-issuer=https://kubernetes.default.svc.cluster.local \
  --allow-privileged=true \
  --v=2

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启动:

systemctl daemon-reload
systemctl enable --now kube-apiserver

查看:

systemctl status kube-apiserver

检查:

ss -lntp | grep 6443

如果出现:

LISTEN

说明 API Server 已经开始监听。


二十五、创建管理员证书

进入:

cd /etc/kubernetes/pki

生成:

openssl genrsa \
-out admin.key \
4096
openssl req \
-new \
-key admin.key \
-subj "/CN=kubernetes-admin/O=system:masters" \
-out admin.csr

签发:

openssl x509 \
-req \
-in admin.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out admin.crt \
-days 3650 \
-extfile client-ext.cnf

二十六、生成 admin kubeconfig

kubectl config set-cluster kubernetes \
--server=https://192.168.10.10:6443 \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--kubeconfig=/etc/kubernetes/admin.kubeconfig

配置用户:

kubectl config set-credentials kubernetes-admin \
--client-certificate=/etc/kubernetes/pki/admin.crt \
--client-key=/etc/kubernetes/pki/admin.key \
--embed-certs=true \
--kubeconfig=/etc/kubernetes/admin.kubeconfig

创建 Context:

kubectl config set-context kubernetes-admin@kubernetes \
--cluster=kubernetes \
--user=kubernetes-admin \
--kubeconfig=/etc/kubernetes/admin.kubeconfig

切换:

kubectl config use-context kubernetes-admin@kubernetes \
--kubeconfig=/etc/kubernetes/admin.kubeconfig

复制:

mkdir -p ~/.kube
cp /etc/kubernetes/admin.kubeconfig \
~/.kube/config

验证:

kubectl get --raw='/readyz?verbose'

如果 API Server 正常,会看到各检查项逐步显示:

ok

二十七、部署 Controller Manager

Controller Manager 需要自己的证书。

它的 CN 使用:

system:kube-controller-manager

生成方式和前面的客户端证书相同。

然后生成:

/etc/kubernetes/controller-manager.kubeconfig

创建:

vi /etc/systemd/system/kube-controller-manager.service

配置:

[Unit]
Description=Kubernetes Controller Manager
After=kube-apiserver.service

[Service]

ExecStart=/usr/local/bin/kube-controller-manager \
  --kubeconfig=/etc/kubernetes/controller-manager.kubeconfig \
  --bind-address=127.0.0.1 \
  --leader-elect=true \
  --cluster-name=kubernetes \
  --cluster-cidr=10.244.0.0/16 \
  --allocate-node-cidrs=true \
  --service-cluster-ip-range=10.96.0.0/12 \
  --root-ca-file=/etc/kubernetes/pki/ca.crt \
  --service-account-private-key-file=/etc/kubernetes/pki/sa.key \
  --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt \
  --cluster-signing-key-file=/etc/kubernetes/pki/ca.key \
  --use-service-account-credentials=true \
  --v=2

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启动:

systemctl daemon-reload
systemctl enable --now kube-controller-manager

二十八、部署 Scheduler

Scheduler 的身份:

system:kube-scheduler

同样生成:

/etc/kubernetes/scheduler.kubeconfig

然后创建:

vi /etc/systemd/system/kube-scheduler.service

内容:

[Unit]
Description=Kubernetes Scheduler
After=kube-apiserver.service

[Service]

ExecStart=/usr/local/bin/kube-scheduler \
  --kubeconfig=/etc/kubernetes/scheduler.kubeconfig \
  --bind-address=127.0.0.1 \
  --leader-elect=true \
  --v=2

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启动:

systemctl daemon-reload
systemctl enable --now kube-scheduler

检查:

systemctl status kube-apiserver
systemctl status kube-controller-manager
systemctl status kube-scheduler

至此:

etcd
API Server
Controller Manager
Scheduler

控制面基本完成。


二十九、理解 kubelet 的身份

Worker 节点真正加入 Kubernetes,靠的是:

kubelet

例如:

k8s-node1

它使用的证书身份应该是:

CN=system:node:k8s-node1

O=system:nodes

Node2:

CN=system:node:k8s-node2

O=system:nodes

这是一个非常重要的细节。

Kubernetes 的:

Node Authorizer

会根据:

system:node:<NodeName>

识别 kubelet。

如果这里写错,后面经常会看到各种:

Forbidden
Unauthorized

或者 Node 无法正常注册的问题。


三十、配置 kubelet

每个 Worker 准备:

/var/lib/kubelet/kubelet.crt

/var/lib/kubelet/kubelet.key

/var/lib/kubelet/kubeconfig

配置:

vi /var/lib/kubelet/config.yaml

内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

authentication:
  anonymous:
    enabled: false

  webhook:
    enabled: true

  x509:
    clientCAFile: /etc/kubernetes/pki/ca.crt

authorization:
  mode: Webhook

cgroupDriver: systemd

clusterDomain: cluster.local

clusterDNS:
  - 10.96.0.10

failSwapOn: true

serializeImagePulls: false

tlsCertFile: /var/lib/kubelet/kubelet.crt
tlsPrivateKeyFile: /var/lib/kubelet/kubelet.key

三十一、创建 kubelet systemd

Node1:

vi /etc/systemd/system/kubelet.service

内容:

[Unit]
Description=Kubernetes Kubelet
After=containerd.service
Requires=containerd.service

[Service]

ExecStart=/usr/local/bin/kubelet \
  --config=/var/lib/kubelet/config.yaml \
  --kubeconfig=/var/lib/kubelet/kubeconfig \
  --container-runtime-endpoint=unix:///run/containerd/containerd.sock \
  --node-ip=192.168.10.11

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Node2:

192.168.10.11

改为:

192.168.10.12

启动:

systemctl daemon-reload
systemctl enable --now kubelet

三十二、查看 Node

回到 Master:

kubectl get nodes -o wide

这时很可能看到:

NAME        STATUS     VERSION
k8s-node1   NotReady   v1.37.0
k8s-node2   NotReady   v1.37.0

看到:

NotReady

先别急。

如果 Node 已经成功注册,而 CNI 还没有安装:

NotReady

是正常现象。

这说明:

kubelet
    ↓
API Server

这条链路已经通了。


三十三、部署 kube-proxy

kube-proxy 同样需要自己的:

kubeconfig

配置:

vi /var/lib/kube-proxy/config.yaml

内容:

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration

mode: iptables

clusterCIDR: 10.244.0.0/16

clientConnection:
  kubeconfig: /var/lib/kube-proxy/kubeconfig

创建:

vi /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube Proxy
After=network.target

[Service]

ExecStart=/usr/local/bin/kube-proxy \
  --config=/var/lib/kube-proxy/config.yaml

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

启动:

systemctl daemon-reload
systemctl enable --now kube-proxy

检查:

systemctl status kube-proxy

三十四、部署 Flannel

本文:

Pod CIDR

使用:

10.244.0.0/16

这正好和 Flannel 默认网络一致。

部署 Flannel 后:

kubectl get pods \
-n kube-flannel \
-o wide

正常情况下,每个 Node 都应该出现一个:

kube-flannel-ds

Pod。

然后再查看:

kubectl get nodes

正常情况下:

NotReady

会逐渐变成:

Ready

如果没有:

kubectl describe node k8s-node1

重点看:

NetworkUnavailable

以及:

Ready

对应的 Condition。

再看:

journalctl -u kubelet -n 200 --no-pager

三十五、为什么一定要安装 CNI

kubelet 能够启动容器。

但 kubelet 自己并不负责解决:

Pod IP
跨节点通信
网络路由

这些问题由:

CNI

负责。

所以:

kubelet 正常

并不等于:

Node Ready

如果没有正确的 CNI:

Node

通常一直会是:

NotReady

三十六、部署 CoreDNS

有了 Flannel,只意味着:

Pod 网络

基本打通。

还缺:

服务发现

Kubernetes 中通常由:

CoreDNS

完成。

我们前面规划:

Cluster DNS

为:

10.96.0.10

所以 CoreDNS Service 对应的:

clusterIP

应该是:

10.96.0.10

部署后:

kubectl get pod -n kube-system

确认 CoreDNS:

Running

三十七、验证整个集群

查看节点

kubectl get nodes -o wide

正常:

NAME        STATUS   VERSION
k8s-node1   Ready    v1.37.0
k8s-node2   Ready    v1.37.0

创建 nginx

kubectl create deployment nginx \
--image=nginx

查看:

kubectl get pods -o wide

确认 Pod 被调度到 Worker。


创建 Service

kubectl expose deployment nginx \
--port=80 \
--type=ClusterIP

查看:

kubectl get svc

三十八、验证 DNS

创建:

kubectl run dns-test \
--image=busybox:1.36 \
--restart=Never \
-- sleep 3600

进入:

kubectl exec -it dns-test -- \
nslookup kubernetes.default

正常应该能够解析到:

10.96.0.1

这时候:

containerd
etcd
kube-apiserver
controller-manager
scheduler
kubelet
kube-proxy
Flannel
CoreDNS

整个基础链路才算真正跑通。


三十九、常用 Kubernetes 端口

二进制部署环境特别容易碰到:

服务启动正常
但是节点就是连接不上

除了检查日志,也一定要检查防火墙。

主要端口包括:

端口 用途
6443/TCP kube-apiserver
2379-2380/TCP etcd
10250/TCP kubelet
10257/TCP controller-manager
10259/TCP scheduler
10256/TCP kube-proxy
30000-32767/TCP/UDP NodePort

Flannel VXLAN 还需要保证节点之间相应的 Overlay 网络通信正常。

在生产环境中,不建议简单粗暴执行:

systemctl stop firewalld

更合理的做法是:

根据 Kubernetes、CNI 和实际业务需要精确开放网络策略。


四十、出现问题应该怎么排查

二进制部署最大的忌讳是:

一个服务起不来,同时改十几个配置。

应该按照依赖顺序,一层一层确认。


第一层:containerd

systemctl status containerd
ctr version

第二层:etcd

systemctl status etcd
journalctl \
-u etcd \
-n 100 \
--no-pager

第三层:API Server

systemctl status kube-apiserver
journalctl \
-u kube-apiserver \
-n 100 \
--no-pager
ss -lntp | grep 6443

第四层:Controller 和 Scheduler

systemctl status kube-controller-manager
systemctl status kube-scheduler

第五层:kubelet

systemctl status kubelet
journalctl \
-u kubelet \
-n 200 \
--no-pager

第六层:网络

kubectl get nodes
kubectl get pods -A -o wide

重点检查:

/opt/cni/bin

/etc/cni/net.d

/run/flannel

四十一、最容易踩的坑

1. cgroup 驱动不一致

最典型:

containerd
SystemdCgroup=true

但:

kubelet
cgroupDriver=cgroupfs

尽量统一为:

systemd

2. API Server SAN 漏 IP

例如你使用:

192.168.10.10

访问 API Server。

结果证书中只有:

k8s-master

可能出现:

x509 certificate is valid for ...
not 192.168.10.10

所以签 API Server 证书之前,就要规划:

主机名

控制面 IP

VIP

127.0.0.1

10.96.0.1

kubernetes

kubernetes.default

3. Service CIDR 配置不统一

比如:

API Server
10.96.0.0/12

结果 CoreDNS 使用:

10.10.0.10

这种错误不会一定在第一时间暴露。

最后可能表现成:

DNS 不通

Service 不通

Pod 访问异常

所以部署之前最好直接写下来:

SERVICE_CIDR=10.96.0.0/12

POD_CIDR=10.244.0.0/16

CLUSTER_DNS=10.96.0.10

4. Pod CIDR 和 Flannel 不一致

例如 Controller Manager:

10.244.0.0/16

Flannel:

10.10.0.0/16

后面很容易出现:

Node NotReady

Pod 无法启动

跨节点 Pod 不通

5. kubelet 证书 CN 错误

正确格式:

CN=system:node:<NodeName>

组织:

O=system:nodes

例如:

CN=system:node:k8s-node1

O=system:nodes

这不是一个随便起的名字。

它直接关系 Kubernetes Node Authorizer 的权限识别。


四十二、生产环境怎么改成高可用

本文使用:

1 Control Plane

1 etcd

2 Worker

这显然没有高可用。

真正生产环境至少应该变成:

                 VIP / LB
                    │
                  :6443
                    │

      ┌─────────────┼─────────────┐
      ▼             ▼             ▼

control-plane1 control-plane2 control-plane3


      ┌─────────────┼─────────────┐
      ▼             ▼             ▼

    etcd1         etcd2         etcd3

需要重点修改:

  1. API Server 证书 SAN 加入 VIP;
  2. 所有 kubeconfig 的 Server 指向 VIP;
  3. 6443 前增加负载均衡;
  4. etcd 使用 3 或 5 个奇数成员;
  5. Controller Manager 开启 Leader Election;
  6. Scheduler 开启 Leader Election;
  7. 定期备份 etcd;
  8. PKI 私钥做好严格权限保护;
  9. 对证书有效期做监控;
  10. 对控制面、etcd、节点建立完整监控。

四十三、为什么 etcd 通常用奇数节点

etcd 基于:

Raft

一致性协议。

核心不是节点越多越好,而是:

多数派

必须能够存活。

3 节点:

允许故障 1 个

5 节点:

允许故障 2 个

但:

4 节点

相比:

3 节点

容忍故障数量并不会增加。

所以常见生产部署基本都是:

3

或者

5

四十四、做完二进制部署以后,Kubernetes 就没那么神秘了

最后再把整个系统串起来。

Kubernetes 本质就是一组长期运行的程序:

etcd

kube-apiserver

kube-controller-manager

kube-scheduler

kubelet

kube-proxy

containerd

它们之间通过:

TLS

+

kubeconfig

+

Kubernetes API

+

RBAC

连接起来。

核心工作链路就是:

用户创建资源
      ↓
kube-apiserver
      ↓
     etcd
      ↓
Controller 发现期望状态
      ↓
Scheduler 选择 Node
      ↓
kubelet 得到 Pod
      ↓
containerd 创建容器
      ↓
CNI 创建 Pod 网络
      ↓
kube-proxy 处理 Service
      ↓
CoreDNS 提供服务发现

看明白这一条链路,再去排查 Kubernetes 问题时,思路会完全不一样。


写在最后

我并不认为生产环境一定要使用二进制方式安装 Kubernetes。

事实上,生产环境更应该追求:

标准化

自动化

可重复

可审计

可升级

可恢复

所以:

kubeadm

Ansible

Cluster API

RKE2

以及成熟 Kubernetes 发行版

在很多场景下都会比纯手工二进制部署更加合理。

但是:

如果你想真正理解 Kubernetes 为什么能够运行,完整做一次二进制部署仍然非常值得。

因为生产环境真正出现故障的时候,你最终排查的依然是:

systemd

证书

kubeconfig

etcd

API Server

kubelet

containerd

CNI

iptables

DNS

自动化工具能够帮我们快速搭建 Kubernetes。

但真正帮助我们解决故障的,还是对这些组件之间关系的理解。