手把手二进制部署 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
需要重点修改:
- API Server 证书 SAN 加入 VIP;
- 所有 kubeconfig 的 Server 指向 VIP;
- 6443 前增加负载均衡;
- etcd 使用 3 或 5 个奇数成员;
- Controller Manager 开启 Leader Election;
- Scheduler 开启 Leader Election;
- 定期备份 etcd;
- PKI 私钥做好严格权限保护;
- 对证书有效期做监控;
- 对控制面、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。
但真正帮助我们解决故障的,还是对这些组件之间关系的理解。