Kubernetes
Kubernetes
概述
Kubernetes 是一个管理容器和服务的开源平台,用于在多主机上高效部署,升级,回滚,扩缩容及管理容器应用,以实现容器应用和服务的负载均衡及故障转移。
相比于传统单主机部署应用及虚拟化多主机部署应用,使用容器化部署应用既可以实现资源隔离,还可以更高效的使用资源。类似于使用 VMware vSphere 管理多个主机上的虚拟机,使用 Kubernetes 管理多个主机上的容器,以实现一个可弹性运行的分布式系统,以满足容器应用的高可用,高性能和高扩展。

架构
Kubernetes 集群使用客户端-服务器架构。

其中:
- Control Plane:控制平面,管理集群,包括:
- kube-apiserver:控制平面的前端接口,暴露 Kubernetes API。
- kube-controller-manager:控制器管理器,运行控制器进程,包括:
- Node controller
- Job controller
- EndpointSlice controller
- ServiceAccount controller
- cloud-controller-manager:用于云服务的控制器管理器,本地部署则没有该组件。
- kube-scheduler:调度器,根据资源及配置来调度,决定将容器部署到哪个工作节点。
- etcd:存储集群数据。
- Node:工作节点,包括:
- kubelet:运行在每个工作节点上的代理,拉取镜像,运行容器。
- kube-proxy:运行在每个工作节点上的网络代理,维护节点上的网络规则。
除了以上 Kubernetes 自带的组件外,为便于系统的扩展,还开放了以下接口:

- CRI(Container Runtime Interface):容器运行时接口,提供容器和镜像服务,常用的实现有 Docker,containerd,CRI-O 等。

- CNI(Container Network Interface):容器网络接口,为容器提供网络服务,常用的实现有 Flannel 和 Calico 等。
- CSI(Container Storage Interface):容器存储接口,为容器提供存储服务。
部署
本节介绍使用 kubeadm 在 CentOS 上部署 Kubernetes 集群。
环境
主机规划:
| No. | Host | IP | CPU | Memory | Role |
|---|---|---|---|---|---|
| 1 | k8sm1 | 192.168.92.140 | 2 | 4 GB | Control Plane |
| 2 | k8sn1 | 192.168.92.141 | 2 | 4 GB | Worker Node 1 |
| 3 | k8sn2 | 192.168.92.142 | 2 | 4 GB | Worker Node 2 |
注意:
CPU 数量至少为 2,内存建议至少 4 GB。
软件版本:
| No. | Name | Release |
|---|---|---|
| 1 | CentOS | 7.9 |
| 2 | Docker | 20.10.23 |
| 3 | Containerd | 1.6.15 |
| 4 | kubernates | 1.23.17 |
| 5 | Flannel | 0.22.2 |
| 6 | Ingress-Nginx | 1.6.4 |
| 7 | Dashboard | 2.5.1 |
注意:
Docker 和 Containerd 的版本对应关系可在 Docker 查看。
Kubernetes 和 Docker 的版本对应关系可在 Github 查看。
Kubernetes 和 Flannel 的版本对应关系可在 Github 查看。
Kubernetes 和 Ingress-Nginx 的版本对应关系可在 Github 查看。
Kubernetes 和 Dashboard 的版本对应关系可在 Github 查看。
准备
安装 CentOS 操作系统,分别配置 3 台主机的主机名,网络地址:
Control Plane:
[root@localhost ~]# hostnamectl set-hostname k8sm1.stonecoding.net
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
IPADDR=192.168.92.140
NETMASK=255.255.255.0
GATEWAY=192.168.92.2
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
UUID="9b6cf18b-7e51-4d1f-818e-142bb03ee6eb"
[root@localhost ~]# systemctl restart network
Node 1:
[root@localhost ~]# hostnamectl set-hostname k8sn1.stonecoding.net
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
IPADDR=192.168.92.141
NETMASK=255.255.255.0
GATEWAY=192.168.92.2
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
UUID="4e6bae92-d721-4188-ab0d-71b1b5d8711e"
[root@localhost ~]# systemctl restart network
Node 2:
[root@localhost ~]# hostnamectl set-hostname k8sn2.stonecoding.net
[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
IPADDR=192.168.92.142
NETMASK=255.255.255.0
GATEWAY=192.168.92.2
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
UUID="3ac41122-f782-4a69-bc93-9df7846db136"
[root@localhost ~]# systemctl restart network
然后创建脚本 k8s-pre.sh:
#!/bin/bash
# 配置 /etc/hosts
echo "=========edit /etc/hosts============="
cat >> /etc/hosts << EOF
192.168.92.140 k8sm1 k8sm1.stonecoding.net
192.168.92.141 k8sn1 k8sn1.stonecoding.net
192.168.92.142 k8sn2 k8sn2.stonecoding.net
EOF
# 配置 DNS
echo "=========edit /etc/resolv.conf ============="
cat >> /etc/resolv.conf << EOF
nameserver 192.168.92.2
EOF
# 安装基础软件
echo "=========install basic sofeware============="
rm -fr /etc/yum.repos.d/CentOS-*
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
# 如果是 RedHat,则需要执行下面替换命令
#sed -i "s/\$releasever/7/g" /etc/yum.repos.d/CentOS-Base.repo
yum -y install wget
# 关闭防火墙
echo "=========stop firewalld============="
systemctl stop firewalld
systemctl disable firewalld
systemctl status firewalld
# 关闭 NetworkManager
echo "=========stop NetworkManager ============="
systemctl stop NetworkManager
systemctl disable NetworkManager
systemctl status NetworkManager
# 关闭 SELinux
echo "=========disable selinux============="
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
getenforce
# 关闭 SWAP
echo "=========close swap============="
sed -ri 's/.*swap.*/#&/' /etc/fstab
swapoff -a
free -m
# 时间同步
echo "=========sync time============="
yum -y install chrony
cat >> /etc/chrony.conf << EOF
server ntp.aliyun.com iburst
EOF
systemctl start chronyd
systemctl enable chronyd
chronyc sources
# 将桥接的 IPv4 流量传递到 iptables 的链
echo "=========Letting iptables see bridged traffic============="
cat > /etc/modules-load.d/k8s.conf << EOF
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat > /etc/sysctl.d/k8s.conf << EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
# 配置 YUM
echo "=========config yum============="
cat > /etc/yum.repos.d/nginx.repo << EOF
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/7/x86_64/
gpgcheck=0
enabled=1
EOF
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum clean all
yum makecache
yum -y install net-tools telnet tree nmap sysstat lrzsz dos2unix bind-utils ipvsadm bash-completion nfs-utils ftp nginx git
# 安装 Docker
echo "=========install docker============="
wget http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
# 如果是 RedHat,则需要执行下面替换命令
#sed -i "s/\$releasever/7/g" /etc/yum.repos.d/docker-ce.repo
sed -i "s/https/http/g" /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-20.10.23 docker-ce-cli-20.10.23 containerd.io-1.6.15
systemctl enable docker
systemctl start docker
docker version
cat > /etc/docker/daemon.json << EOF
{
"insecure-registries": ["harbor.stonecoding.net"],
"live-restore": true,
"registry-mirrors": ["https://docker.m.daocloud.io",
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://docker.1panel.live",
"https://registry.cn-hangzhou.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
systemctl restart docker
# 安装 kubelet kubeadm kubectl
echo "=========install kubeadm============="
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
yum -y install kubelet-1.23.17 kubeadm-1.23.17 kubectl-1.23.17
systemctl enable kubelet
分别在 3 台主机上运行:
[root@k8sm1 ~]# sh k8s-pre.sh
[root@k8sn1 ~]# sh k8s-pre.sh
[root@k8sn2 ~]# sh k8s-pre.sh
控制平面
在主机 k8sm1 上初始化控制平面:
[root@k8sm1 ~]# kubeadm init \
--control-plane-endpoint=192.168.92.140 \
--apiserver-advertise-address=192.168.92.140 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16 \
--kubernetes-version v1.23.17 \
--image-repository=registry.aliyuncs.com/google_containers
[init] Using Kubernetes version: v1.23.17
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8sm1.stonecoding.net kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.92.140]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8sm1.stonecoding.net localhost] and IPs [192.168.92.140 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8sm1.stonecoding.net localhost] and IPs [192.168.92.140 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 5.509988 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.23" in namespace kube-system with the configuration for the kubelets in the cluster
NOTE: The "kubelet-config-1.23" naming of the kubelet ConfigMap is deprecated. Once the UnversionedKubeletConfigMap feature gate graduates to Beta the default name will become just "kubelet-config". Kubeadm upgrade will handle this transition transparently.
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node k8sm1.stonecoding.net as control-plane by adding the labels: [node-role.kubernetes.io/master(deprecated) node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node k8sm1.stonecoding.net as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: xo6ca1.0wa790qn33gx9mwb
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
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/
You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:
kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb \
--discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932 \
--control-plane
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb \
--discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932
其中:
--control-plane-endpoint:指定控制平面接入点,如果控制平面为多节点组成的高可用集群,则该接入点应该为集群的负载均衡地址。--apiserver-advertise-address:指定apiserver的地址。--service-cidr:指定服务网络地址段,默认为 10.96.0.0/12。--pod-network-cidr:指定 Pod 网络地址段,不能与主机网络段相同。--kubernetes-version:指定 Kubernetes 版本。--image-repository:指定镜像仓库,需要指定国内的地址。
根据网络情况,有可能需要较多时间下载镜像,最终下载的镜像如下:
[root@k8sm1 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.aliyuncs.com/google_containers/kube-apiserver v1.23.17 62bc5d8258d6 5 months ago 130MB
registry.aliyuncs.com/google_containers/kube-controller-manager v1.23.17 1dab4fc7b6e0 5 months ago 120MB
registry.aliyuncs.com/google_containers/kube-scheduler v1.23.17 bc6794cb54ac 5 months ago 51.9MB
registry.aliyuncs.com/google_containers/kube-proxy v1.23.17 f21c8d21558c 5 months ago 111MB
registry.aliyuncs.com/google_containers/etcd 3.5.6-0 fce326961ae2 8 months ago 299MB
registry.aliyuncs.com/google_containers/coredns v1.8.6 a4ca41631cc7 22 months ago 46.8MB
registry.aliyuncs.com/google_containers/pause 3.6 6270bb605e12 24 months ago 683kB
根据输出日志,执行以下命令配置 kubectl:
[root@k8sm1 ~]# mkdir -p $HOME/.kube
[root@k8sm1 ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8sm1 ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config
配置命令补全:
[root@k8sm1 ~]# echo 'source <(kubectl completion bash)' >> .bash_profile
[root@k8sm1 ~]# source .bash_profile
完成后,就可以使用 kubectl 命令查看资源情况:
[root@k8sm1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8sm1.stonecoding.net NotReady control-plane,master 64m v1.23.17
[root@k8sm1 ~]# kubectl get componentstatus
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health":"true","reason":""}
[root@k8sm1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 64m
[root@k8sm1 ~]# kubectl get service -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 64m
[root@k8sm1 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8c4cb4d-qqk4w 0/1 Pending 0 65m
coredns-6d8c4cb4d-vz5fs 0/1 Pending 0 65m
etcd-k8sm1.stonecoding.net 1/1 Running 0 65m
kube-apiserver-k8sm1.stonecoding.net 1/1 Running 0 65m
kube-controller-manager-k8sm1.stonecoding.net 1/1 Running 0 65m
kube-proxy-8k87h 1/1 Running 0 65m
kube-scheduler-k8sm1.stonecoding.net 1/1 Running 0 65m
由于还没有安装网络插件,coredns 处于 Pending 状态,安装网络插件后会创建完成。
网络插件
根据输出日志,需要安装网络插件。常用的网络插件有 Flannel 和 Calico,这里使用 Flannel。
下载 Flannel 资源配置清单文件(适用于 Kubernetes 1.17 及以后版本):
[root@k8sm1 ~]# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
如果直接下载失败,可以尝试使用代理下载:
[root@k8sm1 ~]# wget https://ghproxy.com/https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
根据资源配置清单文件,建议先下载好所需要的镜像:
[root@k8sm1 ~]# docker pull crpi-6lwl24m7l5umwvu1.cn-hangzhou.personal.cr.aliyuncs.com/stonecoding/flannel:v0.22.2
[root@k8sm1 ~]# docker tag crpi-6lwl24m7l5umwvu1.cn-hangzhou.personal.cr.aliyuncs.com/stonecoding/flannel:v0.22.2 docker.io/flannel/flannel:v0.22.2
[root@k8sm1 ~]# docker pull crpi-6lwl24m7l5umwvu1.cn-hangzhou.personal.cr.aliyuncs.com/stonecoding/flannel-cni-plugin:v1.2.0
[root@k8sm1 ~]# docker tag crpi-6lwl24m7l5umwvu1.cn-hangzhou.personal.cr.aliyuncs.com/stonecoding/flannel-cni-plugin:v1.2.0 docker.io/flannel/flannel-cni-plugin:v1.2.0
然后根据初始化控制平面时指定的 Pod 网络配置进行修改,最终的资源配置清单文件如下:
---
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: docker.io/flannel/flannel-cni-plugin:v1.2.0
command:
- cp
args:
- -f
- /flannel
- /opt/cni/bin/flannel
volumeMounts:
- name: cni-plugin
mountPath: /opt/cni/bin
- name: install-cni
image: docker.io/flannel/flannel:v0.22.2
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: docker.io/flannel/flannel:v0.22.2
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
部署:
[root@k8sm1 ~]# kubectl apply -f kube-flannel.yml
namespace/kube-flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
[root@k8sm1 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flannel/flannel v0.22.2 d73868a08083 47 hours ago 70.2MB
flannel/flannel-cni-plugin v1.2.0 a55d1bad692b 4 weeks ago 8.04MB
registry.aliyuncs.com/google_containers/kube-apiserver v1.23.17 62bc5d8258d6 5 months ago 130MB
registry.aliyuncs.com/google_containers/kube-controller-manager v1.23.17 1dab4fc7b6e0 5 months ago 120MB
registry.aliyuncs.com/google_containers/kube-scheduler v1.23.17 bc6794cb54ac 5 months ago 51.9MB
registry.aliyuncs.com/google_containers/kube-proxy v1.23.17 f21c8d21558c 5 months ago 111MB
registry.aliyuncs.com/google_containers/etcd 3.5.6-0 fce326961ae2 8 months ago 299MB
registry.aliyuncs.com/google_containers/coredns v1.8.6 a4ca41631cc7 22 months ago 46.8MB
registry.aliyuncs.com/google_containers/pause 3.6 6270bb605e12 24 months ago 683kB
查看:
[root@k8sm1 ~]# kubectl get all -n kube-flannel
NAME READY STATUS RESTARTS AGE
pod/kube-flannel-ds-bq4f8 1/1 Running 0 87s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/kube-flannel-ds 1 1 1 1 1 <none> 74m
[root@k8sm1 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8c4cb4d-qqk4w 1/1 Running 0 15h
coredns-6d8c4cb4d-vz5fs 1/1 Running 0 15h
etcd-k8sm1.stonecoding.net 1/1 Running 1 (3h21m ago) 15h
kube-apiserver-k8sm1.stonecoding.net 1/1 Running 1 (3h21m ago) 15h
kube-controller-manager-k8sm1.stonecoding.net 1/1 Running 1 (3h21m ago) 15h
kube-proxy-8k87h 1/1 Running 1 (3h21m ago) 15h
kube-scheduler-k8sm1.stonecoding.net 1/1 Running 1 (3h21m ago) 15h
网络插件 Flannel 运行后,coredns 处于 Running 状态。
工作节点
根据初始化输出日志,在主机 k8sn1 和 k8sn2 执行加入到集群的命令:
[root@k8sn1 ~]# kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb --discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
[root@k8sn2 ~]# kubeadm join 192.168.92.140:6443 --token xo6ca1.0wa790qn33gx9mwb --discovery-token-ca-cert-hash sha256:63b42c8fda1a3a954c66de806fdd7ba32e6986b433e78f565ede301cd1b9e932
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
在主机 k8sm1 上查看:
[root@k8sm1 ~]# kubectl get pods -n kube-flannel
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-5tf8x 1/1 Running 0 4m26s
kube-flannel-ds-h25mg 1/1 Running 0 7m11s
kube-flannel-ds-ncrrd 1/1 Running 0 4m37s
[root@k8sm1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8sm1.stonecoding.net Ready control-plane,master 15h v1.23.17
k8sn1.stonecoding.net Ready <none> 5m33s v1.23.17
k8sn2.stonecoding.net Ready <none> 4m41s v1.23.17
[root@k8sm1 ~]# kubectl get pods -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-6d8c4cb4d-qqk4w 1/1 Running 0 15h 10.224.0.2 k8sm1.stonecoding.net <none> <none>
coredns-6d8c4cb4d-vz5fs 1/1 Running 0 15h 10.224.0.3 k8sm1.stonecoding.net <none> <none>
etcd-k8sm1.stonecoding.net 1/1 Running 1 (3h31m ago) 15h 192.168.92.140 k8sm1.stonecoding.net <none> <none>
kube-apiserver-k8sm1.stonecoding.net 1/1 Running 1 (3h31m ago) 15h 192.168.92.140 k8sm1.stonecoding.net <none> <none>
kube-controller-manager-k8sm1.stonecoding.net 1/1 Running 1 (3h31m ago) 15h 192.168.92.140 k8sm1.stonecoding.net <none> <none>
kube-proxy-69pt2 1/1 Running 0 5m25s 192.168.92.141 k8sn1.stonecoding.net <none> <none>
kube-proxy-8k87h 1/1 Running 1 (3h31m ago) 15h 192.168.92.140 k8sm1.stonecoding.net <none> <none>
kube-proxy-rs82r 1/1 Running 0 4m33s 192.168.92.142 k8sn2.stonecoding.net <none> <none>
kube-scheduler-k8sm1.stonecoding.net 1/1 Running 1 (3h31m ago) 15h 192.168.92.140 k8sm1.stonecoding.net <none> <none>
如果由于 Token 过期导致加入集群的命令失效,需要在主节点上执行以下命令,这将创建一个新的 Token 并输出完整的 kubeadm join命令:
[root@k8sm1 ~]# kubeadm token create --print-join-command
kubeadm join 192.168.92.140:6443 --token 0yblre.80rb4dc8igdj3736 --discovery-token-ca-cert-hash sha256:e5a82e45fe4c763846cc8c226e47df7e60305bfad68039cbb7769cc7bd3f881f
然后再在节点执行该 kubeadm join命令。
如果该节点之前尝试加入过集群或曾是集群的一部分,需要重置以清理旧配置。在故障节点上执行:
[root@k8sm1 ~]# kubeadm reset
[root@k8sm1 ~]# systemctl restart kubelet
这条命令会清除节点上的旧 Kubernetes 配置,包括 kubelet 配置、iptables 规则、cgroups 配置等,让节点恢复到初始状态。
Ingress
需要安装 Ingress 控制器,以便 Kubernetes 对外提供服务。常用的 Ingress 控制器有 Ingress-Nginx 和 Traefik,这里使用 Ingress-Nginx。
注意:
部署前,需要确认工作节点的 80 和 443 端口没有被占用。
下载 Ingress-Nginx 资源配置清单文件:
[root@k8sm1 ~]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml -O ingress-nginx.yaml
如果直接下载失败,可以尝试使用代理下载:
[root@k8sm1 ~]# wget https://ghproxy.com/https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml -O ingress-nginx.yaml
修改资源配置清单文件中的镜像:
[root@k8sm1 ~]# sed -i "s|registry.k8s.io/ingress-nginx/controller:v1.6.4@sha256:15be4666c53052484dd2992efacf2f50ea77a78ae8aa21ccd91af6baaa7ea22f|registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.6.4|g" ingress-nginx.yaml
[root@k8sm1 ~]# sed -i "s|registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20220916-gd32f8c343@sha256:39c5b2e3310dc4264d638ad28d9d1d96c4cbb2b2dcfb52368fe4e3c63f61e10f|registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20220916-gd32f8c343|g" ingress-nginx.yaml
然后调整资源配置清单文件中 ingress-nginx-controller 这个 Deployment :
- 将 Deployment 修改为 DaemonSet
- 增加
hostNetwork: true,将 Ingress 控制器暴露到宿主机的 80 和 443 端口 - 增加参数
--watch-ingress-without-class=true,避免定义IngressClass - 修改
dnsPolicy为ClusterFirstWithHostNet - 为
nodeSelector增加ingress: nginx,将ingress-nginx-controller部署到指定节点上
最终的资源配置清单文件如下:
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
name: ingress-nginx
---
apiVersion: v1
automountServiceAccountToken: true
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx
namespace: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx
namespace: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- coordination.k8s.io
resourceNames:
- ingress-nginx-leader
resources:
- leases
verbs:
- get
- update
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- create
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission
namespace: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
- namespaces
verbs:
- list
- watch
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission
rules:
- apiGroups:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
verbs:
- get
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
apiVersion: v1
data:
allow-snippet-annotations: "true"
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-controller
namespace: ingress-nginx
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
externalTrafficPolicy: Local
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- appProtocol: http
name: http
port: 80
protocol: TCP
targetPort: http
- appProtocol: https
name: https
port: 443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-controller-admission
namespace: ingress-nginx
spec:
ports:
- appProtocol: https
name: https-webhook
port: 443
targetPort: webhook
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
type: ClusterIP
---
apiVersion: apps/v1
#kind: Deployment
kind: DaemonSet
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
minReadySeconds: 0
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
spec:
hostNetwork: true
containers:
- args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-nginx-leader
- --controller-class=k8s.io/ingress-nginx
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
- --watch-ingress-without-class=true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LD_PRELOAD
value: /usr/local/lib/libmimalloc.so
image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.6.4
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
livenessProbe:
failureThreshold: 5
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: controller
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
- containerPort: 8443
name: webhook
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
requests:
cpu: 100m
memory: 90Mi
securityContext:
allowPrivilegeEscalation: true
capabilities:
add:
- NET_BIND_SERVICE
drop:
- ALL
runAsUser: 101
volumeMounts:
- mountPath: /usr/local/certificates/
name: webhook-cert
readOnly: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
ingress: nginx
kubernetes.io/os: linux
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission-create
namespace: ingress-nginx
spec:
template:
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission-create
spec:
containers:
- args:
- create
- --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
- --namespace=$(POD_NAMESPACE)
- --secret-name=ingress-nginx-admission
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20220916-gd32f8c343
imagePullPolicy: IfNotPresent
name: create
securityContext:
allowPrivilegeEscalation: false
nodeSelector:
kubernetes.io/os: linux
restartPolicy: OnFailure
securityContext:
fsGroup: 2000
runAsNonRoot: true
runAsUser: 2000
serviceAccountName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission-patch
namespace: ingress-nginx
spec:
template:
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission-patch
spec:
containers:
- args:
- patch
- --webhook-name=ingress-nginx-admission
- --namespace=$(POD_NAMESPACE)
- --patch-mutating=false
- --secret-name=ingress-nginx-admission
- --patch-failure-policy=Fail
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20220916-gd32f8c343
imagePullPolicy: IfNotPresent
name: patch
securityContext:
allowPrivilegeEscalation: false
nodeSelector:
kubernetes.io/os: linux
restartPolicy: OnFailure
securityContext:
fsGroup: 2000
runAsNonRoot: true
runAsUser: 2000
serviceAccountName: ingress-nginx-admission
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: nginx
spec:
controller: k8s.io/ingress-nginx
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
app.kubernetes.io/component: admission-webhook
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.6.4
name: ingress-nginx-admission
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: ingress-nginx-controller-admission
namespace: ingress-nginx
path: /networking/v1/ingresses
failurePolicy: Fail
matchPolicy: Equivalent
name: validate.nginx.ingress.kubernetes.io
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- ingresses
sideEffects: None
先为需要部署 ingress-nginx-controller 的工作节点打标签:
[root@k8sm1 ~]# kubectl label nodes k8sn1.stonecoding.net ingress=nginx
node/k8sn1.stonecoding.net labeled
[root@k8sm1 ~]# kubectl label nodes k8sn2.stonecoding.net ingress=nginx
node/k8sn2.stonecoding.net labeled
部署:
[root@k8sm1 ~]# kubectl apply -f ingress-nginx.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
daemonset.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
查看:
[root@k8sm1 ~]# kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create-xltbp 0/1 Completed 0 5m54s
pod/ingress-nginx-admission-patch-gh9p5 0/1 Completed 0 5m54s
pod/ingress-nginx-controller-j49qh 1/1 Running 0 5m54s
pod/ingress-nginx-controller-whd5f 1/1 Running 0 5m54s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.99.217.210 <pending> 80:31412/TCP,443:30818/TCP 5m54s
service/ingress-nginx-controller-admission ClusterIP 10.109.220.22 <none> 443/TCP 5m54s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/ingress-nginx-controller 2 2 2 2 2 ingress=nginx,kubernetes.io/os=linux 5m54s
NAME COMPLETIONS DURATION AGE
job.batch/ingress-nginx-admission-create 1/1 4s 5m54s
job.batch/ingress-nginx-admission-patch 1/1 5s 5m54s
此时在工作节点上就可以看到监听的 80 和 443 端口:
[root@k8sn1 ~]# netstat -natp |egrep ":80|:443"
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 74480/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 74480/nginx: master
[root@k8sn2 ~]# netstat -natp |egrep ":80|:443"
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 43741/nginx: master
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 43741/nginx: master
控制平台节点已经安装了 Nginx,在 Nginx 上配置业务流量的反向代理,凡是访问 *.stonecoding.net 域的请求,都代理到工作节点映射出来的 80 端口,然后由 Ingress 根据服务名称分发到对应的 Pod。
[root@k8sm1 ~]# vi /etc/nginx/conf.d/stonecoding.net.conf
upstream default_backend_ingress {
server 192.168.92.141:80 max_fails=3 fail_timeout=10s;
server 192.168.92.142:80 max_fails=3 fail_timeout=10s;
}
server {
server_name *.stonecoding.net;
client_max_body_size 100m;
location / {
proxy_pass http://default_backend_ingress;
proxy_set_header Host $http_host;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
}
}
[root@k8sm1 ~]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@k8sm1 ~]# systemctl start nginx
[root@k8sm1 ~]# systemctl enable nginx
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.
Dashboard
安装 Dashboard ,使用图形界面管理 Kubernetes。
获取资源配置清单文件:
[root@k8sm1 ~]# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml -O dashboard.yaml
如果直接下载失败,可以尝试使用代理下载:
[root@k8sm1 ~]# wget https://ghproxy.com/https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml -O dashboard.yaml
修改资源配置清单文件,将 kubernetes-dashboard 这个 Service 的端口类型修改为 NodePort,最终的资源配置清单文件如下:
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: v1
kind: Namespace
metadata:
name: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
ports:
- port: 443
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard
type: NodePort
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-certs
namespace: kubernetes-dashboard
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-csrf
namespace: kubernetes-dashboard
type: Opaque
data:
csrf: ""
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-key-holder
namespace: kubernetes-dashboard
type: Opaque
---
kind: ConfigMap
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-settings
namespace: kubernetes-dashboard
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
rules:
# Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
verbs: ["get", "update", "delete"]
# Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["kubernetes-dashboard-settings"]
verbs: ["get", "update"]
# Allow Dashboard to get metrics.
- apiGroups: [""]
resources: ["services"]
resourceNames: ["heapster", "dashboard-metrics-scraper"]
verbs: ["proxy"]
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
verbs: ["get"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
rules:
# Allow Metrics Scraper to get metrics from the Metrics server
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: kubernetes-dashboard
image: kubernetesui/dashboard:v2.5.1
imagePullPolicy: Always
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --namespace=kubernetes-dashboard
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
ports:
- port: 8000
targetPort: 8000
selector:
k8s-app: dashboard-metrics-scraper
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: dashboard-metrics-scraper
template:
metadata:
labels:
k8s-app: dashboard-metrics-scraper
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: dashboard-metrics-scraper
image: kubernetesui/metrics-scraper:v1.0.7
ports:
- containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 30
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
volumes:
- name: tmp-volume
emptyDir: {}
部署:
[root@k8sm1 ~]# kubectl apply -f dashboard.yaml
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created
创建 dashboard-serviceaccount 服务账户:
[root@k8sm1 ~]# kubectl create serviceaccount dashboard-serviceaccount -n kubernetes-dashboard
serviceaccount/dashboard-serviceaccount created
为 dashboard-serviceaccount 服务账户授予 cluster-admin 角色:
[root@k8sm1 ~]# kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-serviceaccount
clusterrolebinding.rbac.authorization.k8s.io/dashboard-cluster-admin created
查看:
[root@k8sm1 ~]# kubectl get all -n kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
pod/dashboard-metrics-scraper-799d786dbf-bm5gt 1/1 Running 0 5m32s
pod/kubernetes-dashboard-fb8648fd9-t4c7k 1/1 Running 0 5m32s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/dashboard-metrics-scraper ClusterIP 10.110.86.109 <none> 8000/TCP 5m32s
service/kubernetes-dashboard NodePort 10.107.3.191 <none> 443:32490/TCP 5m32s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/dashboard-metrics-scraper 1/1 1 1 5m32s
deployment.apps/kubernetes-dashboard 1/1 1 1 5m32s
NAME DESIRED CURRENT READY AGE
replicaset.apps/dashboard-metrics-scraper-799d786dbf 1 1 1 5m32s
replicaset.apps/kubernetes-dashboard-fb8648fd9 1 1 1 5m32s
获取 token:
[root@k8sm1 ~]# kubectl describe secrets -n kubernetes-dashboard $(kubectl -n kubernetes-dashboard get secret | awk '/dashboard-serviceaccount/{print $1}')
Name: dashboard-serviceaccount-token-n4222
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: dashboard-serviceaccount
kubernetes.io/service-account.uid: 6ec8d67b-14b6-4369-a330-4b80eda90f25
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1099 bytes
namespace: 20 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc4NndjNWJSUjYxMDZEcmU3amtGak1OYXpwODFuX2RYQWFQeVVBOEpDRFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkYXNoYm9hcmQtc2VydmljZWFjY291bnQtdG9rZW4tbjQyMjIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGFzaGJvYXJkLXNlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNmVjOGQ2N2ItMTRiNi00MzY5LWEzMzAtNGI4MGVkYTkwZjI1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmVybmV0ZXMtZGFzaGJvYXJkOmRhc2hib2FyZC1zZXJ2aWNlYWNjb3VudCJ9.rq3Z0YvWBaJEn3YJTuD83mXQBgrwJ2bK8XpoFFC592XuFZr4pqvjis54QezAsgvJH4MNkkCeXWYG4w9LjOeWl10v3PqJxqWfzYA8AkFLOnIARJ0f4MoeW0nwKx8hotPVaa5iDa84TaytWwTUCC6PjUtFMx_B-RNgJnsYEvkvv7ImvSFMhLcypHieouEnLCTG7977MR9U7M2zqzeao7r-tIph6AeMCoaSwpKk0QXWNb4tuWLhdNTmKKWUdQamF0k9UE6Vl6Nz_ZkZMGtueCkctj_3pAucLl6_AYgeFjltC1WC_QGrSWg_dL8D9OjobxfMp929LvQ6hqjmpUGm-tOeGw
使用 Firefox 浏览器访问 https://192.168.92.141:32490 或者 https://192.168.92.142:32490,输入 token,点击 “登录”:

切换命名空间为 kube-system,点击 “工作负载”,可以看到各类工作负载的运行情况:

Metrics Server
安装 Dashboard 后,查看 Pod 时无法看到使用的 CPU 和内存,此时需要安装 Metrics Server,用于收集资源指标数据。
下载资源清单配置文件:
[root@k8sm1 ~]# curl -L https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.4/components.yaml -o /root/metrics-server.yml
修改镜像源和参数,最终如下:
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- apiGroups:
- ""
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-insecure-tls
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.6.4
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 4443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100
安装 Metrics Server:
[root@k8sm1 ~]# kubectl apply -f metrics-server.yml
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
[root@k8sm1 ~]# kubectl get pods -n kube-system | grep metrics-server
metrics-server-86c5c7d977-285hx 1/1 Running 0 48s
现在就可以查看节点或者 Pod 的资源使用情况了:
[root@k8sm1 ~]# kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
k8sm1.stonecoding.net 254m 12% 867Mi 23%
k8sn1.stonecoding.net 81m 4% 466Mi 12%
k8sn2.stonecoding.net 81m 4% 458Mi 12%
[root@k8sm1 ~]# kubectl top pods -A
NAMESPACE NAME CPU(cores) MEMORY(bytes)
kube-flannel kube-flannel-ds-g5vfx 11m 20Mi
kube-flannel kube-flannel-ds-j5vfb 8m 21Mi
kube-flannel kube-flannel-ds-p9pkz 11m 21Mi
kube-system coredns-6d8c4cb4d-2wqrh 2m 16Mi
kube-system coredns-6d8c4cb4d-72x4z 2m 18Mi
kube-system etcd-k8sm1.stonecoding.net 24m 83Mi
kube-system kube-apiserver-k8sm1.stonecoding.net 73m 198Mi
kube-system kube-controller-manager-k8sm1.stonecoding.net 23m 53Mi
kube-system kube-proxy-7h2ms 8m 21Mi
kube-system kube-proxy-9wqvl 8m 21Mi
kube-system kube-proxy-sv5mp 1m 20Mi
kube-system kube-scheduler-k8sm1.stonecoding.net 4m 21Mi
kube-system metrics-server-86c5c7d977-285hx 5m 17Mi
资源
在 Kubernetes 中,一切皆资源,管理 Kubernetes 就是管理 Kubernetes 中的资源。
[root@k8sm1 ~]# kubectl api-resources
NAME SHORTNAMES APIVERSION NAMESPACED KIND
bindings v1 true Binding
componentstatuses cs v1 false ComponentStatus
configmaps cm v1 true ConfigMap
endpoints ep v1 true Endpoints
events ev v1 true Event
limitranges limits v1 true LimitRange
namespaces ns v1 false Namespace
nodes no v1 false Node
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
persistentvolumes pv v1 false PersistentVolume
pods po v1 true Pod
podtemplates v1 true PodTemplate
replicationcontrollers rc v1 true ReplicationController
resourcequotas quota v1 true ResourceQuota
secrets v1 true Secret
serviceaccounts sa v1 true ServiceAccount
services svc v1 true Service
mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration
validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration
customresourcedefinitions crd,crds apiextensions.k8s.io/v1 false CustomResourceDefinition
apiservices apiregistration.k8s.io/v1 false APIService
controllerrevisions apps/v1 true ControllerRevision
daemonsets ds apps/v1 true DaemonSet
deployments deploy apps/v1 true Deployment
replicasets rs apps/v1 true ReplicaSet
statefulsets sts apps/v1 true StatefulSet
tokenreviews authentication.k8s.io/v1 false TokenReview
localsubjectaccessreviews authorization.k8s.io/v1 true LocalSubjectAccessReview
selfsubjectaccessreviews authorization.k8s.io/v1 false SelfSubjectAccessReview
selfsubjectrulesreviews authorization.k8s.io/v1 false SelfSubjectRulesReview
subjectaccessreviews authorization.k8s.io/v1 false SubjectAccessReview
horizontalpodautoscalers hpa autoscaling/v2 true HorizontalPodAutoscaler
cronjobs cj batch/v1 true CronJob
jobs batch/v1 true Job
certificatesigningrequests csr certificates.k8s.io/v1 false CertificateSigningRequest
leases coordination.k8s.io/v1 true Lease
endpointslices discovery.k8s.io/v1 true EndpointSlice
events ev events.k8s.io/v1 true Event
flowschemas flowcontrol.apiserver.k8s.io/v1beta2 false FlowSchema
prioritylevelconfigurations flowcontrol.apiserver.k8s.io/v1beta2 false PriorityLevelConfiguration
ingressclasses networking.k8s.io/v1 false IngressClass
ingresses ing networking.k8s.io/v1 true Ingress
networkpolicies netpol networking.k8s.io/v1 true NetworkPolicy
runtimeclasses node.k8s.io/v1 false RuntimeClass
poddisruptionbudgets pdb policy/v1 true PodDisruptionBudget
podsecuritypolicies psp policy/v1beta1 false PodSecurityPolicy
clusterrolebindings rbac.authorization.k8s.io/v1 false ClusterRoleBinding
clusterroles rbac.authorization.k8s.io/v1 false ClusterRole
rolebindings rbac.authorization.k8s.io/v1 true RoleBinding
roles rbac.authorization.k8s.io/v1 true Role
priorityclasses pc scheduling.k8s.io/v1 false PriorityClass
csidrivers storage.k8s.io/v1 false CSIDriver
csinodes storage.k8s.io/v1 false CSINode
csistoragecapacities storage.k8s.io/v1beta1 true CSIStorageCapacity
storageclasses sc storage.k8s.io/v1 false StorageClass
volumeattachments storage.k8s.io/v1 false VolumeAttachment
常用的资源:
| 类别 | 名称 |
|---|---|
| 集群 | Node、Namespace |
| 负载 | Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob |
| 服务 | Service、Ingress |
| 配置 | ConfigMap、Secret、HorizontalPodAutoscaling、CustomResourceDefinition |
| 存储 | Volume、PersistentVolume、LimitRange |
| 身份 | ServiceAccount、Role、ClusterRole、RoleBinding、ClusterRoleBinding |
Namespace
Namespace 是 Kubernetes 的命名空间,用于分组命名空间级别的资源对象。可以为不同的项目创建对应的命名空间。
支持命名空间的资源有:
[root@k8sm1 ~]# kubectl api-resources --namespaced=true
NAME SHORTNAMES APIVERSION NAMESPACED KIND
bindings v1 true Binding
configmaps cm v1 true ConfigMap
endpoints ep v1 true Endpoints
events ev v1 true Event
limitranges limits v1 true LimitRange
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
pods po v1 true Pod
podtemplates v1 true PodTemplate
replicationcontrollers rc v1 true ReplicationController
resourcequotas quota v1 true ResourceQuota
secrets v1 true Secret
serviceaccounts sa v1 true ServiceAccount
services svc v1 true Service
controllerrevisions apps/v1 true ControllerRevision
daemonsets ds apps/v1 true DaemonSet
deployments deploy apps/v1 true Deployment
replicasets rs apps/v1 true ReplicaSet
statefulsets sts apps/v1 true StatefulSet
localsubjectaccessreviews authorization.k8s.io/v1 true LocalSubjectAccessReview
horizontalpodautoscalers hpa autoscaling/v2 true HorizontalPodAutoscaler
cronjobs cj batch/v1 true CronJob
jobs batch/v1 true Job
leases coordination.k8s.io/v1 true Lease
endpointslices discovery.k8s.io/v1 true EndpointSlice
events ev events.k8s.io/v1 true Event
ingresses ing networking.k8s.io/v1 true Ingress
networkpolicies netpol networking.k8s.io/v1 true NetworkPolicy
poddisruptionbudgets pdb policy/v1 true PodDisruptionBudget
rolebindings rbac.authorization.k8s.io/v1 true RoleBinding
roles rbac.authorization.k8s.io/v1 true Role
csistoragecapacities storage.k8s.io/v1beta1 true CSIStorageCapacity
查看所有命名空间:
[root@k8sm1 ~]# kubectl get namespaces
NAME STATUS AGE
default Active 24h
ingress-nginx Active 166m
kube-flannel Active 24h
kube-node-lease Active 24h
kube-public Active 24h
kube-system Active 24h
kubernetes-dashboard Active 119m
其中:
default:默认的命名空间,在创建资源时,如果不指定命名空间,则会创建在此命名空间中。kube-system:集群管理相关组件所在的命名空间。
查看命名空间的 Label:
[root@k8sm1 ~]# kubectl get namespaces --show-labels
NAME STATUS AGE LABELS
default Active 24h kubernetes.io/metadata.name=default
ingress-nginx Active 166m app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx,kubernetes.io/metadata.name=ingress-nginx
kube-flannel Active 24h k8s-app=flannel,kubernetes.io/metadata.name=kube-flannel,pod-security.kubernetes.io/enforce=privileged
kube-node-lease Active 24h kubernetes.io/metadata.name=kube-node-lease
kube-public Active 24h kubernetes.io/metadata.name=kube-public
kube-system Active 24h kubernetes.io/metadata.name=kube-system
kubernetes-dashboard Active 119m kubernetes.io/metadata.name=kubernetes-dashboard
创建命名空间:
[root@k8sm1 ~]# kubectl create namespace app
namespace/app created
使用资源配置清单文件创建命名空间:
apiVersion: v1
kind: Namespace
metadata:
name: app
字段含义如下:
| 名称 | 类型 | 是否必要 | 说明 |
|---|---|---|---|
apiVersion | String | Required | API 的版本,使用 kubectl api-resources 命令查询 |
kind | String | Required | 资源类型,这里为 Namespace |
metadata | Object | Required | 元数据 |
metadata.name | String | Required | Namespace 的名字 |
Pod
Pod 是 Kubernetes 最小部署和调度单位,运行在物理节点上。
架构
一个 Pod 包含一个或者多个容器,共享文件系统和网络。

在 Docker 中,我们知道容器之间是隔离的,有自己的网络和文件系统,为实现 Pod 中多个容器共享文件系统和网络,Kubernetes 使用了一个称为 Pause 的容器:

[root@k8sm1 ~]# docker ps --filter "name=flannel"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3e28576512e7 d73868a08083 "/opt/bin/flanneld -…" About an hour ago Up About an hour k8s_kube-flannel_kube-flannel-ds-h25mg_kube-flannel_f94326b5-c1e4-408e-9dd5-7af526f5d059_2
d5c27e570141 registry.aliyuncs.com/google_containers/pause:3.6 "/pause" About an hour ago Up About an hour k8s_POD_kube-flannel-ds-h25mg_kube-flannel_f94326b5-c1e4-408e-9dd5-7af526f5d059_2
Pause 容器的作用主要有 2 点:
- 在 Pod 中作为 Linux Namespace 共享的基础容器
- 在 PID Namespace 共享的前提下,作为每个 Pod 中的 PID 1,回收僵尸进程
下面来模拟多个容器使用 Pause 容器共享资源,先创建一个 Pause 容器:
[root@k8sm1 ~]# docker run -d --name pause --ipc=shareable -p 8080:80 registry.aliyuncs.com/google_containers/pause:3.6
0ef2e7ba8946da71166ae6b8374c7b837bb9360c49c682440c021d6588cb31ca
其中:
-p 8080:80:将容器的 80 端口映射到主机的 8080 端口--ipc=shareable:拥有私有的 IPC 命名空间, 且可以共享给其他容器
再创建一个 Nginx 容器:
[root@k8sm1 ~]# cat <<EOF >> nginx.conf
error_log stderr;
events { worker_connections 1024; }
http {
access_log /dev/stdout combined;
server {
listen 80 default_server;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:2368;
}
}
}
EOF
[root@k8sm1 ~]# docker run -d --name nginx -v /root/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
cc600daa1bf90cbdb75ad0b174b45259b9354061379cefdda4ccd443b51107a1
其中:
--net:指定共享 Pause 的网络--ipc:指定共享 Pause 的 IPC--pid:指定共享 Pause 的 PID
再创建一个 Ghost 容器:
[root@k8sm1 ~]# docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost:2
448b9f646d6e45c54723959ab41cde6cc32a599f710f2984ef3d0a6780d057e5
完成后,在浏览器中访问 http://192.168.92.140:8080/,发现打开了 Ghost 网页。
从上面的步骤可知:
- Pause 容器将内部 80 端口映射到宿主机 8080 端口并共享网络。
- Nginx 容器使用
--net=container:pause加入到 Pause 网络中。 - Ghost 容器使用
--net=container:pause加入到 Pause 网络中。 - 这样三个容器共享网络后,互相之间就可以使用 localhost 直接通信。
--ipc=container:pause,--pid=container:pause使三个容器的 IPC 和 PID 处于同一个 Namespace中,Init 进程为 Pause,PID 为 1。
[root@k8sm1 ~]# docker exec -it nginx /bin/bash
root@0ef2e7ba8946:/# pidof pause
1
[root@k8sm1 ~]# docker exec -it ghost /bin/bash
root@0ef2e7ba8946:/var/lib/ghost# pidof pause
1

网络
每个 Pod 都会被分配一个唯一的 IP 地址。Pod 中的所有容器共享网络空间,包括 IP 地址和端口,可以使用 localhost 互相通信。
同一宿主机上的 Pod 通过由 Flannel 创建的虚拟网桥 cni0 进行通信,Flannel 为每个 Pod 创建一对 veth 虚拟设备,一端放在容器接口上,一端放在 cni0 网桥上。
访问流程如下:

- Pod 1 (10.244.1.2) 向 Pod 2 (10.244.1.3) 发送数据,通过
eth0接口离开 Pod 1。 - 数据通过
veth0到达cni0,寻找 Pod 2 的地址。 - 数据通过
veth1离开cni0,发往 Pod 2。 - 数据通过
eth1到达 Pod 2。
不同宿主机上的 Pod 通信,则需要使用 CNI 网络插件,常见的 CNI 网络插件有:
| 提供商 | 网络模型 | 路由分发 | 网络策略 | 网格 | 外部数据存储 | 加密 | Ingress/Egress 策略 |
|---|---|---|---|---|---|---|---|
| Canal | 封装 (VXLAN) | 否 | 是 | 否 | K8s API | 是 | 是 |
| Flannel | 封装 (VXLAN) | 否 | 否 | 否 | K8s API | 是 | 否 |
| Calico | 封装 (VXLAN, IPIP) 或未封装 | 是 | 是 | 是 | Etcd 和 K8s API | 是 | 是 |
| Weave | 封装 | 是 | 是 | 是 | 否 | 是 | 是 |
| Cilium | 封装 (VXLAN) | 是 | 是 | 是 | Etcd 和 K8s API | 是 | 是 |
- 网络模型:封装或未封装。
- 路由分发:一种外部网关协议,用于在互联网上交换路由和可达性信息。BGP 可以帮助进行跨集群 Pod 之间的网络。此功能对于未封装的 CNI 网络插件是必须的,并且通常由 BGP 完成。如果你想构建跨网段拆分的集群,路由分发是一个很好的功能。
- 网络策略:Kubernetes 提供了强制执行规则的功能,这些规则决定了哪些 Service 可以使用网络策略进行相互通信。这是从 Kubernetes 1.7 起稳定的功能,可以与某些网络插件一起使用。
- 网格:允许在不同的 Kubernetes 集群间进行 Service 之间的网络通信。
- 外部数据存储:具有此功能的 CNI 网络插件需要一个外部数据存储来存储数据。
- 加密:允许加密和安全的网络控制和数据平面。
- Ingress/Egress 策略:允许你管理 Kubernetes 和非 Kubernetes 通信的路由控制。
这里使用的是 Flannel。
Flannel 的部署文件中包含一个 ConfigMap,其中包含 Flannel CNI 的配置文件以及 Network 和 Backend 配置。Flannel 通过 DaemonSet 调度到每个节点上。Flannel 的 Pod 有一个 Init 容器,负责将 ConfigMap 中的 cni-conf.json 复制到宿主机上的 /etc/cni/net.d/10-flannel.conflist:
[root@k8sm1 ~]# cat /etc/cni/net.d/10-flannel.conflist
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
Flannel 会根据 ConfigMap 中的 Network 来为每个 Node 分配单独的子网,这样就能保证不同 Node 上的 Pod IP 不会冲突。并将集群网络和 Subnet 的配置信息写入文件 /run/flannel/subnet.env:
[root@k8sm1 ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
一般配置 ConfigMap 中的 Backend 为 vxlan(使用类似 VLAN 的封装技术将 OSI 第 2 层以太网帧封装在第 4 层 UDP 数据报中,并使用 4789 作为默认 IANA 分配的目标 UDP 端口号),宿主机会增加 flannel.1 网口,作为 Pod 与外部网络的网关。
3 个节点的网络和路由情况如下:
[root@k8sm1 ~]# ip addr | egrep 'mtu|link|inet'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:fa:5b:e5 brd ff:ff:ff:ff:ff:ff
inet 192.168.92.140/24 brd 192.168.92.255 scope global ens33
inet6 fe80::20c:29ff:fefa:5be5/64 scope link
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:8e:8d:41:6b brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
inet6 fe80::42:8eff:fe8d:416b/64 scope link
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether fa:9d:c6:a2:46:3a brd ff:ff:ff:ff:ff:ff
inet 10.244.0.0/32 scope global flannel.1
inet6 fe80::f89d:c6ff:fea2:463a/64 scope link
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether b2:7a:c6:02:36:44 brd ff:ff:ff:ff:ff:ff
inet 10.244.0.1/24 brd 10.244.0.255 scope global cni0
inet6 fe80::b07a:c6ff:fe02:3644/64 scope link
6: veth71670adb@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether c2:5f:ae:2c:0b:c9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::c05f:aeff:fe2c:bc9/64 scope link
7: vethb988663a@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 0e:48:71:3a:1b:bc brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::c48:71ff:fe3a:1bbc/64 scope link
15: vetha420398@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 62:9b:9f:84:c6:73 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::609b:9fff:fe84:c673/64 scope link
[root@k8sm1 ~]# ip route
default via 192.168.92.2 dev ens33
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
169.254.0.0/16 dev ens33 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.92.0/24 dev ens33 proto kernel scope link src 192.168.92.140
[root@k8sn1 ~]# ip addr | egrep 'mtu|link|inet'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:63:91:cc brd ff:ff:ff:ff:ff:ff
inet 192.168.92.141/24 brd 192.168.92.255 scope global ens33
inet6 fe80::20c:29ff:fe63:91cc/64 scope link
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:d3:ab:d7:ba brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether a2:ad:25:ef:4d:9b brd ff:ff:ff:ff:ff:ff
inet 10.244.1.0/32 scope global flannel.1
inet6 fe80::a0ad:25ff:feef:4d9b/64 scope link
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether f6:71:9e:02:b3:8f brd ff:ff:ff:ff:ff:ff
inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
inet6 fe80::f471:9eff:fe02:b38f/64 scope link
6: veth3c45aab8@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 86:ba:d2:db:17:d8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::84ba:d2ff:fedb:17d8/64 scope link
7: veth9f1c97d6@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether fa:2e:28:2b:1c:11 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::f82e:28ff:fe2b:1c11/64 scope link
[root@k8sn1 ~]# ip -d link show flannel.1
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether a2:ad:25:ef:4d:9b brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 192.168.92.141 dev ens33 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
[root@k8sn1 ~]# ip route
default via 192.168.92.2 dev ens33
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
169.254.0.0/16 dev ens33 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.92.0/24 dev ens33 proto kernel scope link src 192.168.92.141
[root@k8sn2 ~]# ip addr | egrep 'mtu|link|inet'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:23:91:65 brd ff:ff:ff:ff:ff:ff
inet 192.168.92.142/24 brd 192.168.92.255 scope global ens33
inet6 fe80::20c:29ff:fe23:9165/64 scope link
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:f2:73:8f:f1 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether 5e:ae:41:8d:4b:9f brd ff:ff:ff:ff:ff:ff
inet 10.244.2.0/32 scope global flannel.1
inet6 fe80::5cae:41ff:fe8d:4b9f/64 scope link
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 92:7d:fb:49:42:0a brd ff:ff:ff:ff:ff:ff
inet 10.244.2.1/24 brd 10.244.2.255 scope global cni0
inet6 fe80::907d:fbff:fe49:420a/64 scope link
6: veth52664c07@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 6a:ca:b4:3f:da:50 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::68ca:b4ff:fe3f:da50/64 scope link
[root@k8sn2 ~]# ip route
default via 192.168.92.2 dev ens33
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1
169.254.0.0/16 dev ens33 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.92.0/24 dev ens33 proto kernel scope link src 192.168.92.142
可以看到每台宿主机上都有两条使用 flannel.1 网口的路由,作为访问其他宿主机上 Pod 的网关。
Pod 跨宿主机访问流程如下:

Pod 1 (10.244.1.2) 向 Pod 4 (10.244.2.3) 发送数据。因为 Pod 1 和 Pod 4 的 IP 不在一个子网,因此走默认路由表,直接发向接口
cni0的地址10.244.1.1。IP 包到达
cni0接口后,根据主机路由表10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink,下一跳是10.244.2.0,通过flannel.1发送。此时需要知道
10.244.2.0的 MAC 地址,因此检查节点 1 的 ARP 表:[root@k8sn1 ~]# arp Address HWtype HWaddress Flags Mask Iface 10.244.1.6 ether 1e:a2:1e:4d:7e:11 C cni0 10.244.0.0 ether fa:9d:c6:a2:46:3a CM flannel.1 10.244.1.5 ether 62:1d:b8:54:c9:7c C cni0 10.244.2.0 ether 5e:ae:41:8d:4b:9f CM flannel.1 192.168.92.1 ether 00:50:56:c0:00:08 C ens33 k8sm1 ether 00:0c:29:fa:5b:e5 C ens33 gateway ether 00:50:56:e9:3d:8f C ens33发现
10.244.2.0 ether 5e:ae:41:8d:4b:9f CM flannel.1,因此要发送的帧如下:
二层帧的转发需要查找主机的 FDB 表(使用
bridge fdb show命令查看)。这里匹配到5e:ae:41:8d:4b:9f dev flannel.1 dst 192.168.92.142 self permanent。内核将数据封装成vxlan的包从ens33发出去。发出去的报文如下:
节点 2 的
ens33收到包后,发现是vxlan,内核对包解封装,转发到flannel.1上:
此时三层目标地址是
10.244.2.3,因此匹配主机的路由表10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1,转发给cni0。cni0和 Pod 是二层互通的,将包发给 Pod。Pod 收到包。三层的来源地址是
10.244.1.2,二层的来源地址是cni0的 MAC 地址。
配置
通过资源配置清单文件来创建 Pod,使用以下命令查看相关配置项:
[root@k8sm1 ~]# kubectl explain pods
[root@k8sm1 ~]# kubectl explain pods.metadata
[root@k8sm1 ~]# kubectl explain pods.spec
[root@k8sm1 ~]# kubectl explain pods.status
语法如下:
apiVersion: v1
kind: Pod
metadata:
name: string
namespace: string
labels:
- name: string
annotations:
- name: string
spec:
containers:
- name: string
image: string
imagePullPolicy: [Always | Never | IfNotPresent]
command: [string]
args: [string]
workingDir: string
volumeMounts:
- name: string
mountPath: string
readOnly: boolean
ports:
- name: string
containerPort: int
hostPort: int
protocol: [TCP | UDP]
env:
- name: string
value: string
resources:
limits:
cpu: string
memory: string
requests:
cpu: string
memory: string
livenessProbe:
exec:
command: [string]
httpGet:
path: string
port: number
host: string
scheme: string
httpHeaders:
- name: string
value: string
tcpSocket:
port: number
initialDelaySeconds: 0
timeoutSeconds: 0
periodSeconds: 0
successThreshold: 0
failureThreshold: 0
securityContext:
provoleged: false
restartPolicy: [Always | Never | OnFailure]
nodeSelector: object
imagePullSecrets:
- name: string
hostNetwork: false
dnsPolicy: ClusterFirst
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "foo.local"
- "bar.local"
volumes:
- name: string
emptyDir: {}
hostPath:
path: string
secret:
secretName: string
items:
- key: string
path: string
comfigMap:
name: string
items:
- key: string
path: string
字段含义如下:
| 名称 | 类型 | 是否必要 | 说明 |
|---|---|---|---|
apiVersion | String | Required | API 的版本,使用 kubectl api-resources 命令查询 |
kind | String | Required | 资源类型,这里为 Pod |
metadata | Object | Required | 元数据 |
metadata.name | String | Required | Pod 的名字 |
metadata.namespace | String | Required | Pod 的命名空间,默认为 default 命名空间 |
metadata.labels[] | List | 自定义标签列表 | |
metadata.annotation[] | List | 自定义注解列表 | |
Spec | Object | Required | Pod 的详细定义 |
spec.containers[] | List | Required | Pod 的容器定义 |
spec.containers[].name | String | Required | 容器名称 |
spec.containers[].image | String | Required | 容器的镜像名称和版本 |
spec.containers[].imagePullPolicy | String | 镜像拉取策略,包括: Always:默认值,表示每次都尝试重新下载镜像 Never:表示仅使用本地镜像 IfNotPresent:本地没有镜像才下载镜像 | |
spec.containers[].command[] | List | 容器的启动命令列表,如果不指定,则使用镜像的启动命令 | |
spec.containers[].args[] | List | 容器的启动命令参数列表 | |
spec.containers[].workingDir | String | 容器的工作目录 | |
spec.containers[].volumeMounts[] | List | 挂载到容器内部的存储卷配置 | |
spec.containers[].volumeMounts[].name | String | 在 spec.volumes[] 部分定义的共享存储卷名称 | |
spec.containers[].volumeMounts[].mountPath | String | 存储卷在容器内挂载的绝对路径 | |
spec.containers[].volumeMounts[].readOnly | Boolean | 是否为只读模式,默认值为读写模式 | |
spec.containers[].ports[] | List | 容器是否需要暴露的端口号列表 | |
spec.containers[].ports[].name | String | 端口的名称 | |
spec.containers[].ports[].containerPort | Int | 容器需要监听的端口号 | |
spec.containers[].ports[].hostPort | Int | 容器所在宿主机需要监听的端口号,默认与 containerPort 相同。设置 hostPort 时,同一台宿主机将无法启动该容器的第二份副本 | |
spec.containers[].ports[].protocol | Srting | 端口协议,支持 TCP 和 UDP,默认值为 TCP | |
spec.containers[].env[] | List | 容器的环境变量列表 | |
spec.containers[].env[].name | String | 环境变量的名称 | |
spec.containers[].env[].valume | String | 环境变量的值 | |
spec.containers[].resources | Object | 资源限制和资源请求 | |
spec.containers[].resources.limits | Object | 资源限制 | |
spec.containers[].resources.limits.cpu | String | CPU 限制 | |
spec.containers[].resources.limits.memory | String | 内存限制 | |
spec.containers[].resources.requests | Object | 资源请求 | |
spec.containers[].resources.requests.cpu | String | CPU 请求 | |
spec.containers[].resources.requests.memory | String | 内存请求 | |
spec.containers[].livenessProbe | Object | 容器存活探针 | |
spec.containers[].livenessProbe.exec | Object | 使用 exec 方式对 Pod 容器进行探测 | |
spec.containers[].livenessProbe.exec.command[] | String | exec 方式需要执行的命令或脚本 | |
spec.containers[].livenessProbe.httpGet | Object | 使用 httpGet 方式对 Pod 容器进行探测 | |
spec.containers[].livenessProbe.tcpSocket | Object | 使用 tcpSocket 方式对 Pod 容器进行探测 | |
spec.containers[].livenessProbe.initialDelaySeconds | Number | 容器启动完成后到进行初次探测的时间间隔,单位为秒 | |
spec.containers[].livenessProbe.timeoutSeconds | Number | 探测的超时秒数,默认值为 1 秒,超时将重启该容器 | |
spec.containers[].livenessProbe.periodSeconds | Number | 定期探测时间间隔,默认值为 10 秒 | |
spec.containers[].readinessProbe | Object | 容器就绪探针 | |
spec.containers[].readinessProbe.exec | Object | 使用 exec 方式对 Pod 容器进行探测 | |
spec.containers[].readinessProbe.exec.command[] | String | exec 方式需要执行的命令或脚本 | |
spec.containers[].readinessProbe.httpGet | Object | 使用 httpGet 方式对 Pod 容器进行探测 | |
spec.containers[].readinessProbe.tcpSocket | Object | 使用 tcpSocket 方式对 Pod 容器进行探测 | |
spec.containers[].readinessProbe.initialDelaySeconds | Number | 容器启动完成后到进行初次探测的时间间隔,单位为秒 | |
spec.containers[].readinessProbe.timeoutSeconds | Number | 探测的超时秒数,默认值为 1 秒,超时将该容器从服务中剔除 | |
spec.containers[].readinessProbe.periodSeconds | Number | 定期探测时间间隔,默认值为 10 秒 | |
spec.containers[].startupProbe | Object | 容器启动探针 | |
spec.containers[].startupProbe.exec | Object | 使用 exec 方式对 Pod 容器进行探测 | |
spec.containers[].startupProbe.exec.command[] | String | exec 方式需要执行的命令或脚本 | |
spec.containers[].startupProbe.httpGet | Object | 使用 httpGet 方式对 Pod 容器进行探测 | |
spec.containers[].startupProbe.tcpSocket | Object | 使用 tcpSocket 方式对 Pod 容器进行探测 | |
spec.containers[].startupProbe.initialDelaySeconds | Number | 容器启动完成后到进行初次探测的时间间隔,单位为秒 | |
spec.containers[].startupProbe.timeoutSeconds | Number | 探测的超时秒数,默认值为 1 秒 | |
spec.containers[].startupProbe.periodSeconds | Number | 定期探测时间间隔,默认值为 10 秒 | |
spec.volumes[] | List | 共享存储卷列表 | |
spec.volumes[].name | String | 共享存储卷的名称,用于 spec.containers[].volumeMounts[].name | |
spec.volumes[].emptyDir | Object | 类型为 emptyDir 的存储卷,表示与 Pod 同生命周期的一个临时目录,其值为一个空对象:emptyDir: {} | |
spec.volumes[].hostPath | Object | 类型为 hostPath 的存储卷,表示挂载 Pod 所在宿主机的目录 | |
spec.volumes[].hostPath.path | String | Pod 所在宿主机的目录,将用于容器中挂载的目录 | |
spec.volumes[].secret | Object | 类型为 secret 的存储卷,表示挂载集群预定义的 secret 对象到容器内部 | |
spec.volumes[].configMap | Object | 类型为 configMap 的存储卷,表示挂载集群预定义的 configMap 对象到容器内部 | |
spec.restartPolicy | String | Pod 中所有容器的重启策略,包括: Always:默认值,始终重启 OnFailure:以非零退出码终止时才会重启 Never:不重启 | |
spec.nodeSelector | Object | 表示将该 Pod 调度到包含对应 Label 的 Node 上 | |
spec.imagePullSecrets | Object | 拉取镜像时使用的 secret 名称 | |
spec.hostNetwork | Boolean | 是否使用宿主机的地址和端口 | |
spec.dnsPolicy | String | Pode 的 DNS 策略,包括以下四种:Default:从宿主机继承域名解析配置,只能解析注册到互联网上的外部域名,无法解析集群内部域名ClusterFirst:默认策略,应用对接 Kube-DNS/CoreDNS。这种场景下,容器既能够解析 Service 注册的集群内部域名,也能够解析发布到互联网上的外部域名。不过 ClusterFirst 还有一个特例,如果 Pod 设置了hostNetwork: true,则 ClusterFirst 就会被强制转换成 DefaultClusterFirstWithHostNet:如果 Pod 设置了hostNetwork: true,则应显式设置 DNS 策略为 ClusterFirstWithHostNetNone:允许 Pod 忽略 Kubernetes 环境中的 DNS 设置,会使用其 dnsConfig 字段提供的 DNS 设置 | |
spec.hostAliases | Object | 向 Pod 的 /etc/hosts 文件中添加条目 |
管理
使用如下资源配置清单文件定义 Pod:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: app
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
创建 Pod:
[root@k8sm1 ~]# kubectl apply -f simple-pod.yaml
pod/nginx created
查看 Pod 运行情况:
[root@k8sm1 ~]# kubectl get pods -n app
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 63s
查看 Pod 的详细信息:
[root@k8sm1 ~]# kubectl get pods -n app -o yaml
apiVersion: v1
items:
- apiVersion: v1
kind: Pod
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx","namespace":"app"},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx","ports":[{"containerPort":80}]}]}}
creationTimestamp: "2023-08-24T05:09:58Z"
name: nginx
namespace: app
resourceVersion: "134938"
uid: 6aea2cfe-3547-4a9e-b559-d39fccb98282
spec:
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-glhq7
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: k8sn2.stonecoding.net
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: kube-api-access-glhq7
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2023-08-24T05:09:58Z"
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: "2023-08-24T05:10:47Z"
status: "True"
type: Ready
- lastProbeTime: null
lastTransitionTime: "2023-08-24T05:10:47Z"
status: "True"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: "2023-08-24T05:09:58Z"
status: "True"
type: PodScheduled
containerStatuses:
- containerID: docker://1d19c07aff70c409a3d3c9b303a67853f97ce77cf76848aff5a60d7f01b898c4
image: nginx:1.14.2
imageID: docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
lastState: {}
name: nginx
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2023-08-24T05:10:47Z"
hostIP: 192.168.92.142
phase: Running
podIP: 10.244.2.6
podIPs:
- ip: 10.244.2.6
qosClass: BestEffort
startTime: "2023-08-24T05:09:58Z"
kind: List
metadata:
resourceVersion: ""
selfLink: ""
查看 Pod 运行信息:
[root@k8sm1 ~]# kubectl describe pods nginx -n app
Name: nginx
Namespace: app
Priority: 0
Node: k8sn2.stonecoding.net/192.168.92.142
Start Time: Thu, 24 Aug 2023 13:09:58 +0800
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.2.6
IPs:
IP: 10.244.2.6
Containers:
nginx:
Container ID: docker://1d19c07aff70c409a3d3c9b303a67853f97ce77cf76848aff5a60d7f01b898c4
Image: nginx:1.14.2
Image ID: docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Thu, 24 Aug 2023 13:10:47 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-glhq7 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-glhq7:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m19s default-scheduler Successfully assigned app/nginx to k8sn2.stonecoding.net
Normal Pulling 2m18s kubelet Pulling image "nginx:1.14.2"
Normal Pulled 91s kubelet Successfully pulled image "nginx:1.14.2" in 47.377873524s (47.377878779s including waiting)
Normal Created 90s kubelet Created container nginx
Normal Started 90s kubelet Started container nginx
删除 Pod:
[root@k8sm1 ~]# kubectl delete pod nginx -n app
pod "nginx" deleted
生命周期
Kubernetes 中的 Pod 生命周期描述了 Pod 从创建到终止的完整过程,涵盖状态流转、核心控制机制及关键行为。
核心阶段与状态
Pod 的生命周期由以下主要状态(Phase) 定义:
- Pending
- 触发条件:Pod 已被 API Server 接受,但未完成调度或容器镜像下载。
- 典型场景:资源配额不足、节点筛选中、镜像拉取耗时。
- ContainerCreating
- 触发条件:调度完成,节点正在创建容器(如拉取镜像)。
- Running
- 触发条件:所有容器已创建,且至少一个容器处于运行或重启中。
- 注意:此状态不保证服务可用,需依赖就绪探针(Readiness Probe)。
- Succeeded
- 触发条件:所有容器成功退出(
exit code = 0)且不再重启,常见于批处理任务。
- 触发条件:所有容器成功退出(
- Failed
- 触发条件:所有容器终止,且至少一个容器非正常退出(
exit code ≠ 0)。 - 原因:应用崩溃、配置错误或资源不足。
- 触发条件:所有容器终止,且至少一个容器非正常退出(
- Terminating
- 触发条件:用户或控制器发起删除请求,进入终止宽限期(默认 30 秒)。
- Unknown
- 触发条件:
kubelet无法上报状态,通常因节点失联或通信故障。
- 触发条件:
- 其他状态
- CrashLoopBackOff:容器持续崩溃并重启(如配置错误)。
- Evicted:节点资源不足导致驱逐。
关键控制机制
- 初始化容器(Init Containers)
- 作用:在主容器启动前执行串行任务(如依赖检查、数据下载)。
- 特性:
- 必须按顺序成功执行(
exit code = 0),否则阻塞主容器启动。 - 不支持就绪探针,因需在 Pod 就绪前完成。
- 必须按顺序成功执行(
- 探针(Probes)
- 存活探针(Liveness Probe):检测容器崩溃并触发重启。
- 就绪探针(Readiness Probe):判断容器是否可接收流量,失败则从 Service Endpoints 移除。
- 启动探针(Startup Probe):保障慢启动容器完成初始化,启动探测完成后才会进行存活探测和就绪探测。
- 检查方式:
ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的
- 生命周期钩子(Lifecycle Hooks)
- PostStart:容器启动后立即执行(如服务注册)。
- PreStop:容器终止前同步执行(如优雅关闭连接、保存状态)。
- 注意:PreStop 需在宽限期(
terminationGracePeriodSeconds)内完成,否则强制终止。
- 注意:PreStop 需在宽限期(
- 重启策略(RestartPolicy)
Always(默认):容器终止即重启。OnFailure:仅容器异常退出时重启。Never:不重启。
状态转换全景图
最佳实践
优雅终止优化
延长宽限期:
terminationGracePeriodSeconds需大于PreStop耗时(如 60 秒)。PreStop示例:添加sleep等待下游断开连接:lifecycle: preStop: exec: command: ["sleep", "30"]
探针配置黄金法则
- Liveness:轻量检查(避免级联重启),建议
timeout=1s, period=10s。 - Readiness:真实流量检查,设置
failureThreshold=3防抖动。 - 避坑:Liveness 勿依赖外部服务(如数据库),否则可能因依赖故障导致 Pod 重启。
- Liveness:轻量检查(避免级联重启),建议
初始化容器设计
用于等待依赖服务就绪(如数据库解析成功):
initContainers: - name: wait-mysql image: busybox command: ['sh', '-c', 'until nslookup mysql; do sleep 2; done']
资源与监控
- 设置资源限制(CPU/内存)避免因 OOM 被驱逐。
- 通过
kubectl describe pod和事件日志排查状态异常。
Pod 的生命周期是 Kubernetes 编排能力的核心,其状态流转受 初始化容器、探针、钩子及重启策略 精密控制。生产环境中需重点优化 优雅终止、探针容错 及 资源隔离,确保应用高可用性。通过状态机制与控制组件的协同,Kubernetes 实现了从调度、自愈到销毁的全生命周期管理。
初始化容器的阻塞性
根据以下资源配置清单文件创建 Pod 来测试初始化容器的阻塞性:
apiVersion: v1
kind: Pod
metadata:
name: initc-1
labels:
app: initc
spec:
containers:
- name: myapp-container
image: busybox
command: ["sh", "-c", "echo The app is running! && sleep 3600"]
initContainers:
- name: init-myservice
image: busybox
command: ["sh", "-c", "until nslookup myservice.default.svc.cluster.local; do echo waiting for myservice; sleep 2; done;"]
- name: init-mydb
image: busybox
command: ["sh", "-c", "until nslookup mydb.default.svc.cluster.local; do echo waiting for mydb; sleep 2; done;"]
其中:
apiVersion:该 Pod 资源对应的 API 版本,v1是 Pod 等核心资源的稳定版本 。kind:表明资源类型为Pod。metadata:name:Pod 的名称为initc-1。labels:给 Pod 打标签app: initc,用于资源关联和筛选 。
spec:containers:定义业务容器,启动命令是输出提示并休眠 3600 秒 。initContainers:定义初始化容器,init-myservice和init-mydb会在业务容器启动前执行,作用是等待myservice和mydb对应的域名可解析(通过nslookup检查),确保依赖服务就绪后再启动业务容器 ,常用于处理服务依赖问题 。
创建 Pod:
[root@k8sm1 ~]# kubectl apply -f initc-1.yml
pod/initc-1 created
查看 Pod 状态:
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
initc-1 0/1 Init:0/2 0 58s
可以看到 Pod 的 STATUS 为 Init:0/2,表示有 2 个 Init 容器,都没有启动成功。
查看 Pod 事件:
[root@k8sm1 ~]# kubectl describe pod initc-1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 11s default-scheduler Successfully assigned default/initc-1 to k8sn1.stonecoding.net
Normal Pulling 11s kubelet Pulling image "busybox"
Normal Pulled 9s kubelet Successfully pulled image "busybox" in 1.796987824s (1.796992253s including waiting)
Normal Created 9s kubelet Created container init-myservice
Normal Started 9s kubelet Started container init-myservice
可以看到 Pod 当前停留在启动容器 init-myservice 阶段。
查看 init-myservice 容器日志:
[root@k8sm1 ~]# kubectl logs initc-1 -c init-myservice
.....
** server can't find myservice.default.svc.cluster.local: NXDOMAIN
waiting for myservice
可以看到容器的日志显示找不到对应的服务 myservice.default.svc.cluster.local。
手动创建 myservice 服务:
[root@k8sm1 ~]# kubectl create service clusterip myservice --tcp=80:80
service/myservice created
[root@k8sm1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d5h
myservice ClusterIP 10.110.138.6 <none> 80/TCP 4s
再次查看 Pod 状态:
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
initc-1 0/1 Init:1/2 0 98s
可以看到 Pod 的 STATUS 为 Init:1/2,表示已经有 1 个 Init 容器启动成功。
查看 Pod 事件:
[root@k8sm1 ~]# kubectl describe pod initc-1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m53s default-scheduler Successfully assigned default/initc-1 to k8sn1.stonecoding.net
Normal Pulling 2m53s kubelet Pulling image "busybox"
Normal Pulled 2m51s kubelet Successfully pulled image "busybox" in 1.796987824s (1.796992253s including waiting)
Normal Created 2m51s kubelet Created container init-myservice
Normal Started 2m51s kubelet Started container init-myservice
Normal Pulling 110s kubelet Pulling image "busybox"
Normal Pulled 108s kubelet Successfully pulled image "busybox" in 1.604657015s (1.604661216s including waiting)
Normal Created 108s kubelet Created container init-mydb
Normal Started 108s kubelet Started container init-mydb
可以看到 Pod 当前停留在启动容器 init-mydb 阶段。
查看 init-mydb 容器日志:
[root@k8sm1 ~]# kubectl logs initc-1 -c init-mydb
......
** server can't find mydb.default.svc.cluster.local: NXDOMAIN
waiting for mydb
可以看到容器的日志显示找不到对应的服务 mydb.default.svc.cluster.local。
手动创建 mydb 服务:
[root@k8sm1 ~]# kubectl create service clusterip mydb --tcp=80:80
service/mydb created
[root@k8sm1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d5h
mydb ClusterIP 10.96.195.210 <none> 80/TCP 6s
myservice ClusterIP 10.110.138.6 <none> 80/TCP 10m
再次查看 Pod 状态:
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
initc-1 0/1 PodInitializing 0 11m
可以看到 Pod 的 STATUS 为 PodInitializing,表示已经有 2 个 Init 容器启动成功。
探针
探针是由所在节点的 kubelet 对容器执行的定期诊断。诊断时 kubelet 会调用容器实现的 Handler(处理程序)。有三种类型的处理程序:
- Exec:在容器内执行指定命令,命令退出返回码为 0 则诊断成功 。
- TCP Socket:对容器 IP 地址的指定端口做 TCP 检查,端口打开则诊断成功 。
- HTTP GET:对容器 IP 地址的指定端口和路径执行 HTTP Get 请求,响应状态码在 200 - 399 之间则诊断成功 。
每次探测都将获得以下三种结果之一:
- 成功:容器通过了诊断。
- 失败:容器未通过诊断。
- 未知:诊断失败,因此不会采取任何行动。
探针分为:
- 存活探针(
livenessProbe):检测容器崩溃并触发重启。 - 就绪探针(
readinessProbe):判断容器是否可接收流量,失败则从 Service Endpoints 移除。 - 启动探针(
startupProbe):保障慢启动容器完成初始化,启动探测完成后才会进行存活探测和就绪探测。
就绪探针
Kubernetes 中的就绪探针(Readiness Probe)是一种健康检查机制,用于判断 Pod 中的容器是否已经准备好接收外部请求流量。它的核心作用是确保只有完全初始化和健康的 Pod 才会被纳入服务的负载均衡池,从而避免将请求转发给尚未准备就绪的 Pod,提升应用的整体可用性和用户体验。
就绪探针周期性检测容器。当所有配置了就绪探针的容器都探测成功时,Pod 才会被标记为 "就绪"(Ready)状态。只有处于 "就绪" 状态的 Pod,其 IP 地址才会被加入到关联 Service 的 Endpoints 列表中,从而开始接收来自外部的流量。如果探测失败,Kubernetes 会将该 Pod 从 Service 的 Endpoints 列表中移除,暂停向其转发流量。
就绪探针支持三种类型的检测方式:
| 探测类型 | 工作机制 | 适用场景 |
|---|---|---|
| HTTP GET | 向容器指定端口和路径发送HTTP GET请求 | Web服务、REST API等HTTP协议应用 |
| TCP Socket | 尝试与容器指定端口建立TCP连接 | 数据库、消息队列等非HTTP协议服务 |
| Exec | 在容器内执行自定义命令,检查退出状态码是否为0 | 需要复杂逻辑判断就绪状态的场景 |
以通过以下参数控制其行为:
initialDelaySeconds: 容器启动后等待多少秒后开始第一次探测。对于启动较慢的应用,此参数非常重要,需设置得比容器初始化时间稍长。periodSeconds: 每次探测之间的间隔时间(默认10秒)。timeoutSeconds: 每次探测等待响应的超时时间(默认1秒)。successThreshold: 处于失败状态时,连续探测成功多少次才被重新标记为就绪(默认1次)。failureThreshold: 连续探测失败多少次后才将被标记为未就绪(默认3次)。
就绪探针主要用于:
- 平滑滚动更新:确保新版本的 Pod 完全就绪后才逐步替换旧 Pod,实现零停机部署。
- 故障自动隔离:当运行中的 Pod 因依赖服务异常、资源不足等原因无法正常服务时,将其自动从流量池中剔除。
- 保障慢初始化应用:给予需要长时间加载配置、预热缓存或建立连接的应用足够的准备时间,期间不会接收流量。
就绪探针和存活探针通常配合使用,但目的不同:
| 特性 | 就绪探针 (Readiness Probe) | 存活探针 (Liveness Probe) |
|---|---|---|
| 核心目的 | 控制 Pod 是否接收流量 | 检查容器是否需要重启 |
| 失败动作 | 从 Service 的 Endpoints 列表中移除,不重启容器 | 杀死容器并根据 restartPolicy 重启容器 |
| 关注点 | 应用是否"准备好"(Ready) | 应用是否"存活"(Alive) |
注意:
如果 Pod 内的容器没有添加就绪探针,则默认就绪。如果添加了就绪探针,只有检测通过后,才会标记为就绪状态。只有 Pod 内所有容器都就绪,才会标记当前 Pod 为就绪状态。
根据以下资源配置清单文件创建 Pod,基于 HTTP Get 方式来测试就绪探测:
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
labels:
app: myapp
env: test
spec:
containers:
- name: readiness-httpget-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
创建 Pod:
[root@k8sm1 ~]# vi readiness-httpget-pod.yml
[root@k8sm1 ~]# kubectl apply -f readiness-httpget-pod.yml
pod/readiness-httpget-pod created
[root@k8sm1 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
readiness-httpget-pod 0/1 Running 0 6m44s app=myapp,env=test
可以看到 READY 为 0/1,表示该 Pod 还没有就绪。
查看 Pod 事件:
[root@k8sm1 ~]# kubectl describe pod readiness-httpget-pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 108s default-scheduler Successfully assigned default/readiness-httpget-pod to k8sn1.stonecoding.net
Normal Pulled 107s kubelet Container image "wangyanglinux/myapp:v1.0" already present on machine
Normal Created 107s kubelet Created container readiness-httpget-container
Normal Started 107s kubelet Started container readiness-httpget-container
Warning Unhealthy 51s (x22 over 106s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404
可以看到 Readiness probe failed: HTTP probe failed with statuscode: 404 就绪探测失败。
手动创建 myapp 服务:
[root@k8sm1 ~]# kubectl create service clusterip myapp --tcp=80:80
service/myapp created
[root@k8sm1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d5h
myapp ClusterIP 10.96.19.7 <none> 80/TCP 6s
由于 Pod 就绪探测失败,即使 myapp 服务可以匹配到 readiness-httpget-pod,也无法通过该访问访问到 Pod:
[root@k8sm1 ~]# curl 10.96.19.7/hostname.html
curl: (7) Failed connect to 10.96.19.7:80; Connection refused
根据 Pod 就绪探针的定义,进入到该 Pod 的容器中,创建需要的文件:
[root@k8sm1 ~]# kubectl exec -it readiness-httpget-pod -- /bin/bash
readiness-httpget-pod:/# cd /usr/local/nginx/html/
readiness-httpget-pod:/usr/local/nginx/html# ls
50x.html hostname.html index.html
readiness-httpget-pod:/usr/local/nginx/html# echo "stonecoding.net" > index1.html
readiness-httpget-pod:/usr/local/nginx/html# ls
50x.html hostname.html index.html index1.html
readiness-httpget-pod:/usr/local/nginx/html# exit
exit
再次查看 Pod 的状态:
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness-httpget-pod 1/1 Running 0 29m
可以看到 READY 为 1/1,表示该 Pod 已就绪。
现在就可以通过服务 IP 访问该 Pod 了:
[root@k8sm1 ~]# curl 10.96.19.7/hostname.html
readiness-httpget-pod
测试基于 Exec 方式的就绪探针,资源配置清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: readiness-exec-pod
namespace: default
spec:
containers:
- name: readiness-exec-container
image: busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "touch /tmp/live ; sleep 60; rm -rf /tmp/live; sleep 3600"]
readinessProbe:
exec:
command: ["test", "-e", "/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
其中就绪探测会去检测 /tmp/live 这个文件是否存在。
创建 Pod:
[root@k8sm1 ~]# vi readiness-exec-pod.yml
[root@k8sm1 ~]# kubectl apply -f readiness-exec-pod.yml
pod/readiness-exec-pod created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness-exec-pod 1/1 Running 0 5s
Pod 创建时,容器中的 /tmp/live 文件是存在的,故 READY 是 1/1,表示就绪,一分钟后,该文件会被删除,则会变为未就绪:
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness-exec-pod 0/1 Running 0 2m19s
存活探针
Kubernetes Pod 的 存活探针 (Liveness Probe) 是一种健康检查机制,主要用于判断容器中的应用程序是否仍在正常运行。如果探针检测失败,Kubernetes 会重启容器,以尝试恢复服务。
存活探针的核心作用是检测应用内部故障,例如应用程序死锁、进程卡死或无响应等。一旦发现这类问题,kubelet 会重启容器,使其恢复到可用状态,从而保障应用持续服务的能力。
Kubernetes 支持三种主要的存活探针检查方式:
- HTTP GET: 向容器指定端口和路径发送 HTTP 请求。若响应状态码在 200-399 范围内,则视为成功。
- TCP Socket: 尝试与容器指定端口建立 TCP 连接。连接成功即视为健康。
- Exec: 在容器内执行自定义命令。若命令退出状态码为 0,则判定成功。
配置存活探针时,以下几个参数对其行为至关重要:
initialDelaySeconds: 容器启动后等待多久开始第一次探测。必须设置,且需给予应用足够的启动时间,避免误重启,默认值为 0 秒。periodSeconds: 探测的执行周期,即每隔多久检查一次,默认值为 10 秒。timeoutSeconds: 每次探测的超时时间。超过此时长未返回结果视为失败,默认值为 1 秒。failureThreshold: 连续探测失败多少次后,才最终判定容器不健康并重启它。默认为 3 次。
简单来说,存活探针是 Kubernetes 保障应用高可用性的自我修复机制。它通过定期检查,及时发现并重启异常容器,是构建稳健应用不可或缺的配置。
根据以下资源配置清单文件创建 Pod,基于 Exec 方式来测试存活探测:
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: liveness-exec-container
image: busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "touch /tmp/live ; sleep 60; rm -rf /tmp/live; sleep 3600"]
livenessProbe:
exec:
command: ["test", "-e", "/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
创建 Pod:
[root@k8sm1 ~]# vi liveness-exec-pod.yml
[root@k8sm1 ~]# kubectl apply -f liveness-exec-pod.yml
pod/liveness-exec-pod created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 0 13s
过一段时间后查看:
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 3 (53s ago) 5m51s
可以看到 RESTARTS 出现了变化,表示 Pod 被重建了。
根据以下资源配置清单文件创建 Pod,基于 HTTP Get 方式来测试存活探测:
apiVersion: v1
kind: Pod
metadata:
name: liveness-httpget-pod
namespace: default
spec:
containers:
- name: liveness-httpget-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 3
创建 Pod:
[root@k8sm1 ~]# vi liveness-httpget-pod.yml
[root@k8sm1 ~]# kubectl apply -f liveness-httpget-pod.yml
pod/liveness-httpget-pod created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-httpget-pod 1/1 Running 0 103s
根据该 Pod 存活探针的定义,如果容器中不存在 /index.html 文件,则存活探测失败,会重建 Pod。
[root@k8sm1 ~]# kubectl exec -it liveness-httpget-pod -- /bin/bash
liveness-httpget-pod:/# cd /usr/local/nginx/html/
liveness-httpget-pod:/usr/local/nginx/html# mv index.html index.html.bak
liveness-httpget-pod:/usr/local/nginx/html# command terminated with exit code 137
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-httpget-pod 1/1 Running 1 (24s ago) 23m
启动探针
启动探针(Startup Probe)主要为解决启动时间较长的应用容器(如大型 Java 应用、需要加载大量数据或依赖外部服务的应用)的监控问题而设计。
- 主要目的:确保容器内的应用已成功完成启动过程。在启动探针成功之前,Kubernetes 会暂时禁用存活探针(Liveness Probe)和就绪探针(Readiness Probe)。这防止了因应用启动过慢,存活探针或就绪探针误判失败而导致容器被不必要的重启或从服务端点移除。
- 核心价值:为慢启动应用提供了“保护窗口”,允许容器有足够的时间完成初始化操作(如加载数据、建立连接、预热缓存),提升了应用的启动成功率和稳定性。
启动探针在 Pod 的生命周期中扮演着“守门员”的角色:
- Pod 启动后:启动探针开始工作。
- 成功之前:存活探针和就绪探针不会启动。这给了应用一个不受干扰的启动环境。
- 成功之后:启动探针立即停止执行,后续的健康检查工作完全交由存活探针和就绪探针负责。
- 失败之后:如果启动探针在配置的时间内一直失败(达到
failureThreshold次数),kubelet会杀死容器并根据 Pod 的restartPolicy重启容器。
启动探针支持三种检查机制,与存活和就绪探针一致:
| 检查机制 | 说明 | 适用场景 |
|---|---|---|
| Exec | 在容器内执行指定命令。若命令退出码为 0,则视为成功。 | 需要执行自定义脚本检查复杂启动状态的场景。 |
| HTTP Get | 向容器的指定端口和路径发送 HTTP GET 请求。返回状态码在 200-399 范围内视为成功。 | HTTP/HTTPS 服务。例如检查特定的启动状态端点。 |
| TCP Socket | 尝试与容器的指定端口建立 TCP 连接。若连接成功建立,则视为成功。 | 任何基于 TCP 的服务(如数据库、消息中间件),无需应用层提供额外接口。 |
配置启动探针时,以下参数对确保其有效性至关重要:
| 参数 | 含义与作用 | 建议 |
|---|---|---|
initialDelaySeconds | 容器启动后等待多少秒才开始第一次探测。默认 0 秒。 | 对于需要一定时间才能开始响应检查的应用,应设置此值以避免立即失败。 |
periodSeconds | 每次探测之间的时间间隔(秒)。默认 10 秒。 | 根据应用启动的节奏调整。启动期间检查不宜过于频繁。 |
timeoutSeconds | 单次探测的超时时间(秒)。默认 1 秒。 | 如果应用启动阶段响应慢,可适当延长超时时间避免误判。 |
failureThreshold | 探测连续失败多少次后,才最终判定为失败。默认 3 次。 | 这是最关键参数。结合 periodSeconds 共同决定了允许的最大启动时间。例如 failureThreshold: 30 和 periodSeconds: 10 表示允许最多 300 秒的启动时间。 |
successThreshold | 探测失败后,需要连续成功多少次才被判定为成功。对于启动探针,此值必须为 1。 | 通常保持默认值 1 即可。 |
下面是一个包含启动探针的 Pod 资源定义示例:
apiVersion: v1
kind: Pod
metadata:
name: slow-start-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080
# 启动探针 - 使用 HTTP GET 检查
startupProbe:
httpGet:
path: /health/startup # 应用提供的专门用于检查启动状态的端点
port: 8080
initialDelaySeconds: 10 # 容器启动后等待10秒开始第一次检查
periodSeconds: 15 # 每15秒检查一次
failureThreshold: 20 # 允许最多失败20次 (10 + 15 * 20 = 310秒启动时间)
timeoutSeconds: 5 # 每次探测超时时间为5秒
# 存活探针 - 启动成功后接管
livenessProbe:
httpGet:
path: /health/liveness
port: 8080
initialDelaySeconds: 0 # 启动探针成功后立即开始工作
periodSeconds: 10
failureThreshold: 3
# 就绪探针 - 启动成功后接管
readinessProbe:
httpGet:
path: /health/readiness
port: 8080
initialDelaySeconds: 0 # 启动探针成功后立即开始工作
periodSeconds: 5
failureThreshold: 3
在这个例子中,failureThreshold: 20 和 periodSeconds: 15 意味着 Kubernetes 会给该容器最多 310 秒(10秒初始延迟 + 20次 * 15秒/次)的时间来完成启动。
最佳实践与注意事项:
- 为慢启动应用启用启动探针:对于需要较长时间初始化(例如超过几十秒)的应用,强烈建议配置启动探针。这可以避免为存活探针设置过长的
initialDelaySeconds,从而在应用运行后能更灵敏地检测故障。 - 合理估算启动时间:设置
failureThreshold和periodSeconds时,应给予应用足够但不过分的启动时间。可基于应用在压力下的最长启动时间来估算,并留有一定余量。 - 使用专用的健康检查端点:如果使用
httpGet,建议应用程序提供专用于启动状态检查的端点(如/health/startup)。这个端点可以准确反映应用的初始化进度,而非一般的就绪或存活状态。 - 理解探针间的协作:明确启动、存活、就绪三种探针的执行顺序和职责划分:启动探针负责启动期保护,成功后由存活探针负责故障恢复,就绪探针负责流量管理。
- 谨慎设置超时:
timeoutSeconds不宜过短,特别是对于启动阶段本身响应就较慢的应用,避免因网络延迟或应用繁忙导致探测超时失败。
根据以下资源配置清单文件创建 Pod,基于 HTTP Get 方式来测试启动探测:
apiVersion: v1
kind: Pod
metadata:
name: startupprobe-1
namespace: default
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: /index2.html
initialDelaySeconds: 1
periodSeconds: 3
startupProbe:
httpGet:
path: /index1.html
port: 80
failureThreshold: 30
periodSeconds: 10
创建 Pod:
[root@k8sm1 ~]# vi startupprobe-1.yml
[root@k8sm1 ~]# kubectl apply -f startupprobe-1.yml
pod/startupprobe-1 created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
startupprobe-1 0/1 Running 0 12s
可以看到该 Pod 的 READY 为 0/1。进入该 Pod,创建就绪探针所需要的文件 /index2.html:
[root@k8sm1 ~]# kubectl exec -it startupprobe-1 -- /bin/bash
startupprobe-1:/# cd /usr/local/nginx/html/
startupprobe-1:/usr/local/nginx/html# echo "stonecoding.net" > index2.html
startupprobe-1:/usr/local/nginx/html# exit
exit
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
startupprobe-1 0/1 Running 1 (2m26s ago) 7m26s
可以看到该 Pod 的 READY 仍然为 0/1。进入该 Pod,创建启动探针所需要的文件 /index1.html:
[root@k8sm1 ~]# kubectl exec -it startupprobe-1 -- /bin/bash
startupprobe-1:/# cd /usr/local/nginx/html/
startupprobe-1:/usr/local/nginx/html# echo "stonecoding.net" > index1.html
startupprobe-1:/usr/local/nginx/html# exit
exit
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
startupprobe-1 1/1 Running 1 (4m1s ago) 9m1s
可以看到该 Pod 的 READY 变为 1/1 了。
钩子
Kubernetes Pod 的生命周期钩子(Lifecycle Hooks)是一种强大的机制,允许在容器的生命周期中的特定时间点注入自定义逻辑,从而实现更精细化的应用管理和控制。它能确保应用在启动和终止时都能以优雅、可靠的方式进行。
Kubernetes 为每个容器提供了两种主要的生命周期钩子:
| 钩子类型 | 触发时机 | 主要目的 |
|---|---|---|
postStart | 在容器启动后立即执行 | 执行初始化任务,如配置环境、预热缓存、服务注册 |
preStop | 在容器被终止前执行(收到 SIGTERM 信号后) | 执行清理任务,实现优雅关闭,如通知下线、保存状态、关闭连接 |
通过以下两种方式定义钩子处理器:
Exec:在容器内执行一条 shell 命令或脚本。
lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo 'Hello' > /tmp/file"] preStop: exec: command: ["/bin/sh", "-c", "nginx -s quit; sleep 5"]HTTP Get:向容器内特定端口和路径发送 HTTP GET 请求。
lifecycle: preStop: httpGet: path: /graceful-shutdown port: 8080 scheme: HTTP
postStart 钩子在容器创建后立即运行,常用于执行启动后的初始化任务。
- 典型应用场景: 环境初始化:生成配置文件、创建必要目录、设置环境变量。 服务注册:在启动后立即向注册中心(如 Consul、Nacos)注册当前实例。 资源预热:加载缓存数据、初始化数据库连接池,减少主业务处理时的延迟。
- 重要注意事项:
postStart钩子并不保证在容器的ENTRYPOINT或CMD之前运行。 如果postStart钩子执行失败(例如命令返回非零退出码),Kubernetes 会认为容器启动失败,并根据其重启策略(restartPolicy)杀死并重启容器。
preStop 钩子是实现应用优雅关闭(Graceful Shutdown) 的关键。
- 典型应用场景: 从服务网格/负载均衡器下线:通知入口网关或负载均衡器停止向该 Pod 发送新流量。 完成剩余请求:让正在处理请求的进程完成现有工作后再退出。 释放资源:关闭数据库连接、删除临时锁、将内存中的数据持久化到磁盘。
- 工作流程与超时控制: 用户或控制器发起 Pod 删除操作。 Pod 状态变为
Terminating。 kubelet 同步触发preStop钩子并等待其完成。 钩子执行完毕后,kubelet 发送 SIGTERM 信号给容器主进程。 容器在终止宽限期内自行关闭。 如果宽限期结束后容器仍在运行,kubelet 将发送 SIGKILL 信号强制终止进程。
这个终止宽限期由 Pod 规约中的 terminationGracePeriodSeconds 字段定义,默认为 30 秒。preStop 钩子的执行时间包含在这个宽限期内。因此,你必须确保钩子和后续的关闭逻辑能在该时间内完成,否则会被强制杀死。
下面是一个综合示例,展示了如何在一个 Pod 中定义这两种钩子。
# 一个包含 postStart 和 preStop 钩子的 Pod 示例
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
# 设置终止宽限期为 60 秒,为 preStop 钩子和优雅关闭留出足够时间
terminationGracePeriodSeconds: 60
containers:
- name: demo-container
image: busybox:1.28
command: ["/bin/sh", "-c", "echo 'App is running!'; trap 'exit 0' TERM; while true; do sleep 1; done"]
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo '[postStart] Container started at $(date)' >> /proc/1/root/tmp/lifecycle.log"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo '[preStop] Beginning graceful shutdown at $(date)' >> /proc/1/root/tmp/lifecycle.log; sleep 5"]
重要注意事项与实践建议:
- 执行保证:钩子的执行是尽力而为的。例如,在节点崩溃或
kubelet重启的极端情况下,preStop钩子可能无法被执行。 - 资源开销:通过
exec执行的钩子命令所消耗的资源(CPU、内存)都会计入该容器的资源配额。 - 超时管理:务必为
preStop钩子设置合理的terminationGracePeriodSeconds。复杂的关闭逻辑(如等待大量连接关闭)可能需要调大这个值,但要避免设置过长导致 Pod 卡在Terminating状态。 - 初始化 vs 初始化容器:对于必须在应用主进程之前完成的严格初始化任务(如数据库迁移),更推荐使用
Init Containers而非postStart钩子,因为 Init Containers 的执行顺序是得到保证的。
Kubernetes Pod 的生命周期钩子是一个非常实用的功能,它通过 postStart 和 preStop 两个切入点,让你能够管理Pod的启动初始化和优雅终止。
合理使用它们可以显著提升应用的健壮性和可靠性,特别是在复杂的微服务环境中。通常而言,preStop 钩子对于实现零宕期发布和避免数据错误至关重要,是高质量应用部署的标配。
根据以下资源配置清单文件创建 Pod,基于 Exec 方式来测试钩子:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-exec-pod
spec:
containers:
- name: lifecycle-exec-container
image: wangyanglinux/myapp:v1
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo postStart > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo preStop > /usr/share/message"]
创建 Pod:
[root@k8sm1 ~]# vi lifecycle-exec-pod.yml
[root@k8sm1 ~]# kubectl apply -f lifecycle-exec-pod.yml
pod/lifecycle-exec-pod created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
lifecycle-exec-pod 1/1 Running 0 86s
进入容器查看是否运行了 postStart 钩子对应的命令:
[root@k8sm1 ~]# kubectl exec -it lifecycle-exec-pod -- /bin/sh
/ # cat /usr/share/message
postStart
Deployment
在实际工作中,往往不会单独创建一个 Pod,而是使用控制器来创建和管理 Pod。最常用的控制器就是 Deployment,用来创建和管理无状态的 Pod。
在介绍 Deployment 控制器之前,先了解一下 ReplicaSet 控制器。ReplicaSet 的目的是维护一组 Pod,确保任何时间都有指定数量的 Pod 副本在运行,如果有容器异常退出,会自动创建新的 Pod 来替代。可以修改 ReplicaSet 资源配置清单文件中的 spec.replica 数量来创建或删除 Pod,让 Pod 数量符合要求。
Deployment 为 Pod 和 ReplicaSet 提供了一个声明式更新,用于创建或删除 Pod,让 Pod 数量符合要求。通过在 Deployment 对象中描述一个期望的状态,Deployment 就会按照一定的控制速率把实际状态改成期望状态。通过定义一个 Deployment 控制器创建一个新的 ReplicaSet 控制器,通过 ReplicaSet 控制器创建 Pod,删除 Deployment 控制器,也会删除 Deployment 控制器下对应的 ReplicaSet 控制器和 Pod 资源。
使用 Deployment 而不直接创建 ReplicaSet 是因为 Deployment 拥有 ReplicaSet 没有的特性,例如:
- 滚动升级
- 回滚应用
创建
使用如下资源配置清单文件 nginx-deployment.yaml 定义 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: app
labels:
app: nginx
spec:
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
其中:
metadata.name:指定 Deployment 名称,为后续创建 ReplicaSet 和 Pod 的命名基础metadata.labels:指定 Deployment 标签spec.replicas:指定 Pod 数量,默认为 1spec.revisionHistoryLimit:指定版本保留数量,默认为 10,如果为 0,则不保留历史版本,无法回滚spec.selector:spec的必要字段,指定 Pod 的标签选择器,必须与spec.template.metadata.labels匹配spec.strategy:指定用新 Pod 替换旧 Pod 的策略spec.strategy.type:指定策略类型,可以是Recreate(删除当前所有 Pod,再新建)和RollingUpdate(滚动更新),默认为RollingUpdatespec.strategy.rollingUpdate.maxSurge:指定更新过程中可以创建的超出期望的 Pod 个数,默认为 25%,例如原 Pod 为 4 个,则更新过程中,Pod 数量最多为 5 个spec.strategy.rollingUpdate.maxUnavailable:指定更新过程中不可用的 Pod 的个数上限,默认为 25%,例如原 Pod 为 4 个,则更新过程中,Pod 不可用数量最多为 1 个spec.template:spec的必要字段,定义 Pod,字段含义与 Pod 的配置一致,但没有apiVersion和kind字段spec.template.metadata.labels:指定 Pod 标签
创建 Deployment :
[root@k8sm1 ~]# kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
查看 Deployment:
[root@k8sm1 ~]# kubectl get deployments -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 0/3 3 0 48s
[root@k8sm1 ~]# kubectl get deployments -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 2m55s
其中:
NAME:Deployment 的名称READY:可用副本数,格式为就绪数量/期望数量UP-TO-DATE:成功更新的 Pod 数量AVAILABLE:可用副本数AGE:Deployment 运行时间
查看创建的 ReplicaSet:
[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME DESIRED CURRENT READY AGE LABELS
nginx-deployment-9456bbbf9 3 3 3 21m app=nginx,pod-template-hash=9456bbbf9
其中:
NAME:ReplicaSets 的名称,其格式为[DEPLOYMENT-NAME]-[HASH],作为其 Pod 名称的基础,其HASH与标签pod-template-hash的值相同DESIRED:期望副本数量,即spec.replicasCURRENT:当前运行的副本数READY:用户可用的副本数AGE:ReplicaSet 运行时间
查看创建的 Pod:
[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-9456bbbf9-9pssm 1/1 Running 0 18m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8 1/1 Running 0 18m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-lnxq8 1/1 Running 0 18m app=nginx,pod-template-hash=9456bbbf9
可以看到每个 Pod 都有标签 pod-template-hash。
查看 Deployment 详细信息:
[root@k8sm1 ~]# kubectl get deployments nginx-deployment -n app -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx-deployment","namespace":"app"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"nginx"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx","ports":[{"containerPort":80}]}]}}}}
creationTimestamp: "2023-08-25T05:49:21Z"
generation: 1
labels:
app: nginx
name: nginx-deployment
namespace: app
resourceVersion: "173682"
uid: 976e6f8c-f5e9-4691-9316-a8ff20b09533
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 3
conditions:
- lastTransitionTime: "2023-08-25T05:49:23Z"
lastUpdateTime: "2023-08-25T05:49:23Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2023-08-25T05:49:21Z"
lastUpdateTime: "2023-08-25T05:49:23Z"
message: ReplicaSet "nginx-deployment-9456bbbf9" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 3
replicas: 3
updatedReplicas: 3
更新
只有 Deployment 的 Pod 模版(spec.template)发生改变才会触发 Rollout,例如更新了容器镜像的版本,其它诸如扩容 Deployment,不会触发 Rollout。
更新 Deployment,使用 nginx:1.16.1 镜像替换 nginx:1.14.2 :
[root@k8sm1 ~]# kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 -n app
deployment.apps/nginx-deployment image updated
也可以使用以下命令直接编辑资源配置清单或者修改资源配置清单文件后再 Apply:
[root@k8sm1 ~]# kubectl edit deployment/nginx-deployment -n app
查看 Rollout 状态:
[root@k8sm1 ~]# kubectl rollout status deployment/nginx-deployment -n app
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment" successfully rolled out
查看 Deployment 变化,可以看到 UP-TO-DATE 从 1 变到 3:
[root@k8sm1 ~]# kubectl get deployments -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 1 3 77m
[root@k8sm1 ~]# kubectl get deployments -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 2 3 77m
[root@k8sm1 ~]# kubectl get deployments -n app
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 80m
查看 ReplicaSet 变化,可以看到创建了一个新的 ReplicaSet,先在新的 ReplicaSet 中创建一个(spec.strategy.rollingUpdate.maxSurge)新版本的 Pod,再在老的 ReplicaSet 中删除一个(spec.strategy.rollingUpdate.maxUnavailable)旧版本的 Pod,直到新的 ReplicaSet 中的 Pod 数量达到设定值,老的 ReplicaSet 中的Pod 数量变为 0:
[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME DESIRED CURRENT READY AGE LABELS
nginx-deployment-9456bbbf9 3 3 3 76m app=nginx,pod-template-hash=9456bbbf9
[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME DESIRED CURRENT READY AGE LABELS
nginx-deployment-9456bbbf9 3 3 3 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784 1 1 0 15s app=nginx,pod-template-hash=ff6655784
[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME DESIRED CURRENT READY AGE LABELS
nginx-deployment-9456bbbf9 2 2 2 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784 2 2 1 32s app=nginx,pod-template-hash=ff6655784
[root@k8sm1 ~]# kubectl get replicasets -n app --show-labels
NAME DESIRED CURRENT READY AGE LABELS
nginx-deployment-9456bbbf9 0 0 0 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784 3 3 3 48s app=nginx,pod-template-hash=ff6655784
查看 Pod 变化,可以看到新版本 Pod 的创建和老版本 Pod 的删除,只有在足够数量的新版本 Pod 创建成功后才会删除老版本 Pod,也只有在足够数量的老版本 Pod 删除成功后才会再去创建新版本 Pod,以确保至少有 3 个 Pod 可用,最多有 4 个 Pod 可用,也就是可用 Pod 数量在 replicas - maxUnavailable 和 replicas + maxSurge 之间:
[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-9456bbbf9-9pssm 1/1 Running 0 76m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8 1/1 Running 0 76m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-lnxq8 1/1 Running 0 76m app=nginx,pod-template-hash=9456bbbf9
[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-9456bbbf9-9pssm 1/1 Running 0 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8 1/1 Running 0 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-lnxq8 1/1 Running 0 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784-vx5fr 0/1 ContainerCreating 0 17s app=nginx,pod-template-hash=ff6655784
[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-9456bbbf9-9pssm 1/1 Running 0 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-9456bbbf9-gwlf8 1/1 Running 0 77m app=nginx,pod-template-hash=9456bbbf9
nginx-deployment-ff6655784-vx5fr 1/1 Running 0 35s app=nginx,pod-template-hash=ff6655784
nginx-deployment-ff6655784-wlvrk 0/1 ContainerCreating 0 10s app=nginx,pod-template-hash=ff6655784
[root@k8sm1 ~]# kubectl get pods -n app --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-ff6655784-qfz68 1/1 Running 0 4s app=nginx,pod-template-hash=ff6655784
nginx-deployment-ff6655784-vx5fr 1/1 Running 0 51s app=nginx,pod-template-hash=ff6655784
nginx-deployment-ff6655784-wlvrk 1/1 Running 0 26s app=nginx,pod-template-hash=ff6655784

查看 Deployment 运行信息:
[root@k8sm1 ~]# kubectl describe deployments nginx-deployment -n app
Name: nginx-deployment
Namespace: app
CreationTimestamp: Thu, 24 Aug 2023 19:48:31 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 2
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-ff6655784 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 22m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 1
Normal ScalingReplicaSet 21m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 2
Normal ScalingReplicaSet 21m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 2
Normal ScalingReplicaSet 21m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 1
Normal ScalingReplicaSet 21m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 3
Normal ScalingReplicaSet 21m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 0
回滚
如果发现当前版本有问题,可以先回滚到之前的版本。
先查看 Deployment 历史:
[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
1 <none>
2 <none>
[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app --revision=1
deployment.apps/nginx-deployment with revision #1
Pod Template:
Labels: app=nginx
pod-template-hash=9456bbbf9
Containers:
nginx:
Image: nginx:1.14.2
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app --revision=2
deployment.apps/nginx-deployment with revision #2
Pod Template:
Labels: app=nginx
pod-template-hash=ff6655784
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
回滚到前一个版本:
[root@k8sm1 ~]# kubectl rollout undo deployment/nginx-deployment -n app
deployment.apps/nginx-deployment rolled back
查看:
[root@k8sm1 ~]# kubectl get all -n app
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-9456bbbf9-7wcrw 1/1 Running 0 16s
pod/nginx-deployment-9456bbbf9-rw9cx 1/1 Running 0 14s
pod/nginx-deployment-9456bbbf9-t6srk 1/1 Running 0 12s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 3/3 3 3 29m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-9456bbbf9 3 3 3 29m
replicaset.apps/nginx-deployment-ff6655784 0 0 0 28m
[root@k8sm1 ~]# kubectl describe deployment nginx-deployment -n app
Name: nginx-deployment
Namespace: app
CreationTimestamp: Fri, 25 Aug 2023 10:35:03 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 3
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.14.2
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-9456bbbf9 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 30m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 1
Normal ScalingReplicaSet 28m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 2
Normal ScalingReplicaSet 28m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 2
Normal ScalingReplicaSet 25m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 1
Normal ScalingReplicaSet 25m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 3
Normal ScalingReplicaSet 25m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 0
Normal ScalingReplicaSet 102s deployment-controller Scaled up replica set nginx-deployment-9456bbbf9 to 1
Normal ScalingReplicaSet 100s deployment-controller Scaled down replica set nginx-deployment-ff6655784 to 2
Normal ScalingReplicaSet 100s deployment-controller Scaled up replica set nginx-deployment-9456bbbf9 to 2
Normal ScalingReplicaSet 98s (x2 over 31m) deployment-controller Scaled up replica set nginx-deployment-9456bbbf9 to 3
Normal ScalingReplicaSet 98s deployment-controller Scaled down replica set nginx-deployment-ff6655784 to 1
Normal ScalingReplicaSet 96s deployment-controller Scaled down replica set nginx-deployment-ff6655784 to 0
再次先查看 Deployment 历史:
[root@k8sm1 ~]# kubectl rollout history deployment/nginx-deployment -n app
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
2 <none>
3 <none>
回滚到指定版本:
[root@k8sm1 ~]# kubectl rollout undo deployment/nginx-deployment --to-revision=2 -n app
deployment.apps/nginx-deployment rolled back
查看:
[root@k8sm1 ~]# kubectl get all -n app
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-ff6655784-h8qtv 1/1 Running 0 32s
pod/nginx-deployment-ff6655784-j7bj7 1/1 Running 0 36s
pod/nginx-deployment-ff6655784-kwsx7 1/1 Running 0 34s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 3/3 3 3 35m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-9456bbbf9 0 0 0 35m
replicaset.apps/nginx-deployment-ff6655784 3 3 3 34m
[root@k8sm1 ~]# kubectl describe deployment nginx-deployment -n app
Name: nginx-deployment
Namespace: app
CreationTimestamp: Fri, 25 Aug 2023 10:35:03 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 4
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-ff6655784 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 30m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 1
Normal ScalingReplicaSet 30m deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 3
Normal ScalingReplicaSet 30m deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 0
Normal ScalingReplicaSet 6m30s deployment-controller Scaled up replica set nginx-deployment-9456bbbf9 to 1
Normal ScalingReplicaSet 6m28s deployment-controller Scaled up replica set nginx-deployment-9456bbbf9 to 2
Normal ScalingReplicaSet 6m28s deployment-controller Scaled down replica set nginx-deployment-ff6655784 to 2
Normal ScalingReplicaSet 6m26s (x2 over 36m) deployment-controller Scaled up replica set nginx-deployment-9456bbbf9 to 3
Normal ScalingReplicaSet 6m26s deployment-controller Scaled down replica set nginx-deployment-ff6655784 to 1
Normal ScalingReplicaSet 6m24s deployment-controller Scaled down replica set nginx-deployment-ff6655784 to 0
Normal ScalingReplicaSet 55s (x2 over 34m) deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 1
Normal ScalingReplicaSet 53s (x2 over 33m) deployment-controller Scaled up replica set nginx-deployment-ff6655784 to 2
Normal ScalingReplicaSet 53s (x2 over 33m) deployment-controller Scaled down replica set nginx-deployment-9456bbbf9 to 2
Normal ScalingReplicaSet 49s (x3 over 51s) deployment-controller (combined from similar events): Scaled down replica set nginx-deployment-9456bbbf9 to 0
扩缩容
扩容:
[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=4 -n app
deployment.apps/nginx-deployment scaled
[root@k8sm1 ~]# kubectl get all -n app
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-ff6655784-djxbf 1/1 Running 0 109s
pod/nginx-deployment-ff6655784-h8qtv 1/1 Running 0 21m
pod/nginx-deployment-ff6655784-j7bj7 1/1 Running 0 21m
pod/nginx-deployment-ff6655784-kwsx7 1/1 Running 0 21m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 4/4 4 4 56m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-9456bbbf9 0 0 0 56m
replicaset.apps/nginx-deployment-ff6655784 4 4 4 55m
缩容:
[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=2 -n app
deployment.apps/nginx-deployment scaled
[root@k8sm1 ~]# kubectl get all -n app
\NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-ff6655784-j7bj7 1/1 Running 0 21m
pod/nginx-deployment-ff6655784-kwsx7 1/1 Running 0 21m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 2/2 2 2 56m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-9456bbbf9 0 0 0 56m
replicaset.apps/nginx-deployment-ff6655784 2 2 2 55m
金丝雀部署
这里先介绍 kubectl patch 命令,该命令可以动态修改集群中资源的配置,无需直接编辑完整的资源清单文件,适合快速调整资源的部分字段(如副本数、标签、资源限制等),避免重新创建整个资源。
语法:
kubectl patch <资源类型> <资源名称> --patch <补丁内容> [选项]
其中:
<资源类型>:如pod、deployment、service等,支持缩写(如deploy代表deployment)。<资源名称>:要修改的资源的名称。--patch:指定补丁内容(JSON 或 YAML 格式)。- 常用选项:
--type:指定补丁策略(默认strategic,支持json、merge、strategic)。-n <命名空间>:指定资源所在的命名空间(默认default)。
在 default 名称空间下创建 Deployment:
[root@k8sm1 ~]# kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-62gmv 1/1 Running 0 45s
nginx-deployment-9456bbbf9-k87nb 1/1 Running 0 45s
nginx-deployment-9456bbbf9-swnz4 1/1 Running 0 45s
修改前面创建的 nginx-deployment 的 maxSurge 和 maxUnavailable 字段,将 maxSurge 修改为 1,将 maxUnavailable 修改为 0,表示在滚动更新时,先创建一个新版本 Pod,不删除老版本 Pod。
[root@k8sm1 ~]# kubectl patch deployment nginx-deployment --patch '{"spec": {"strategy": {"rollingUpdate": {"maxSurge": 1, "maxUnavailable": 0}}}}'
deployment.apps/nginx-deployment patched
[root@k8sm1 ~]# kubectl get deployment nginx-deployment -o jsonpath='{.spec.strategy.rollingUpdate}'
{"maxSurge":1,"maxUnavailable":0}
然后通过打补丁的方式更新镜像版本,并暂停这个 Deployment 的滚动更新过程:
[root@k8sm1 ~]# kubectl patch deployment nginx-deployment --patch '{"spec": {"template": {"spec": {"containers": [{"name": "nginx","image":"nginx:1.18.0"}]}}}}' && kubectl rollout pause deployment nginx-deployment
deployment.apps/nginx-deployment patched
deployment.apps/nginx-deployment paused
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-79fccc485-7ntgd 1/1 Running 0 75s
nginx-deployment-9456bbbf9-62gmv 1/1 Running 0 32m
nginx-deployment-9456bbbf9-k87nb 1/1 Running 0 32m
nginx-deployment-9456bbbf9-swnz4 1/1 Running 0 32m
可以看到当前有 4 个 Pod,其中包含 3 个老版本的,1 个新版本的。
如果确认新版本没有问题了,再恢复这个 Deployment 的滚动更新过程:
[root@k8sm1 ~]# kubectl rollout resume deployment nginx-deployment
deployment.apps/nginx-deployment resumed
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-79fccc485-4ng87 1/1 Running 0 11s
nginx-deployment-79fccc485-7ntgd 1/1 Running 0 3m44s
nginx-deployment-79fccc485-jhgf2 1/1 Running 0 12s
可以看到该 Deployment 的所有 Pod 都是新版本了。
DaemonSet
Kubernetes DaemonSet 控制器用于确保集群中所有(或指定)节点上都运行着相同的 Pod 副本,非常适合部署需要在每个节点上运行的系统级服务(如日志收集、监控代理、网络插件等)。
| 特性 | DaemonSet | Deployment |
|---|---|---|
| 副本目标 | 确保指定节点上运行 1 个 Pod | 维护用户指定数量的 Pod 副本 |
| 调度方式 | 与节点绑定 | 由调度器分配到可用节点 |
| 典型场景 | 节点级别的服务(日志、监控、网络) | 无状态应用(如 Web 服务) |
| 扩缩容 | 自动随节点增减而增减 | 手动调整 replicas字段 |
核心特性:
- 节点级部署:新节点加入集群时,DaemonSet 会自动在该节点上创建对应的 Pod;节点移除时,相关 Pod 会被自动删除。
- 无状态管理:管理的 Pod 通常是无状态的,生命周期与节点绑定,不追求全局副本数,而是确保 “每个符合条件的节点一个副本”。
- 灵活选择节点:通过
nodeSelector、nodeAffinity或taint/toleration控制 Pod 仅部署在特定节点上。
典型使用场景:
- 网络插件:如 Calico、Flannel 等,需要在每个节点上运行代理组件以实现 Pod 网络通信。
- 日志收集:如 Fluentd、Logstash,在每个节点上收集容器日志并转发到存储系统。
- 监控代理:如 Prometheus Node Exporter,在每个节点上采集硬件和系统指标。
- 存储插件:如 Ceph、GlusterFS 的节点级代理,负责本地存储与集群存储的交互。
DaemonSet 资源配置清单文件:
apiVersion: apps/v1 # API 版本,DaemonSet 在 apps/v1 组下
kind: DaemonSet # 资源类型为 DaemonSet
metadata:
name: daemonset-demo # DaemonSet 的名称
labels:
app: daemonset-demo # 给 DaemonSet 打标签,用于识别和关联
spec:
selector: # 选择器,用于匹配要控制的 Pod(通过标签)
matchLabels:
name: daemonset-demo
template: # Pod 模板,DaemonSet 会根据此模板创建 Pod
metadata:
labels:
name: daemonset-demo # Pod 的标签,需与 selector.matchLabels 匹配
spec:
containers: # 容器定义
- name: daemonset-demo-container # 容器名称
image: wangyanglinux/myapp:v1.0 # 容器使用的镜像
创建 DaemonSet:
[root@k8sm1 ~]# vi daemonset-demo.yml
[root@k8sm1 ~]# kubectl apply -f daemonset-demo.yml
daemonset.apps/daemonset-demo created
[root@k8sm1 ~]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset-demo 1 1 1 1 1 <none> 5m16s
Job
Kubernetes Job 控制器是用于管理一次性、短期任务的资源,核心目标是确保任务(由 Pod 执行)成功完成(即 Pod 状态变为 Completed),区别于 Deployment/DaemonSet 管理的 “长期运行服务”。
核心特性:
- 任务确定性:仅关注任务 “是否完成”,而非持续运行,任务成功后 Pod 会保留(状态为
Completed),失败时可配置重试。 - 重试与超时控制:通过
spec.backoffLimit(默认 6 次)设置失败重试次数,spec.activeDeadlineSeconds设置任务最大运行时长(超时后强制终止)。 - 并行执行:支持通过
spec.parallelism(并行运行的 Pod 数)和spec.completions(需成功完成的总任务数)配置批量并行任务(如数据批量处理)。
典型使用场景:
- 一次性任务:如数据库初始化、配置文件生成、集群初始化脚本执行。
- 批量处理任务:如日志批量分析、数据批量导入 / 导出、定时数据计算(结合 CronJob)。
- 任务结果验证:如部署后自动执行冒烟测试,成功则任务完成,失败则触发重试或告警。
工作原理:Job 控制器的工作流程可以概括为:创建 → 调度 → 监控 → 完成 → 清理。
- 创建与调度:定义一个 Job 后,控制器会根据其 Pod 模板创建 Pod,并由调度器将其分配到合适的节点上运行。
- 监控与完成:Job 控制器会持续监控这些 Pod 的状态。只有当 Pod 内的容器进程正常退出(退出码为 0) 时,该 Pod 才会被标记为“成功完成”(
Completed)。控制器会持续跟踪,直到成功完成的 Pod 数量达到你的设定值。 - 故障处理:若 Pod 运行失败(容器进程以非零退出码终止),控制器会根据
restartPolicy和backoffLimit决定是重启容器还是创建新 Pod 来重试任务。 - 清理:Job 及其 Pod 在完成后不会立即消失。可以通过设置
.spec.ttlSecondsAfterFinished来指定一个延迟时间,让 Kubernetes 在 Job 完成后自动删除它及其 Pod,从而避免资源浪费。
Job 资源配置清单文件:
apiVersion: batch/v1 # API 版本,Job 资源属于 batch/v1 组
kind: Job # 资源类型为 Job,用于管理一次性任务
metadata:
name: job-demo # Job 的名称,用于标识该 Job
spec:
template: # Pod 模板,Job 会根据此模板创建执行任务的 Pod
metadata:
name: job-demo-pod # 要创建的 Pod 的名称
spec:
containers: # Pod 中的容器定义
- name: job-demo-container # 容器的名称
image: wangyanglinux/tools:maqingpythonv1 # 容器使用的镜像
restartPolicy: Never # 重启策略为 Never,即容器退出后不重启 Pod
创建 Job:
[root@k8sm1 ~]# vi job-demo.yml
[root@k8sm1 ~]# kubectl apply -f job-demo.yml
job.batch/job-demo created
[root@k8sm1 ~]# kubectl get job
NAME COMPLETIONS DURATION AGE
job-demo 0/1 17s 17s
[root@k8sm1 ~]# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
job-demo-xnkqw 0/1 ContainerCreating 0 103s
job-demo-xnkqw 0/1 Completed 0 4m37s
job-demo-xnkqw 0/1 Completed 0 4m40s
[root@k8sm1 ~]# kubectl get job
NAME COMPLETIONS DURATION AGE
job-demo 1/1 4m40s 5m11s
CronJob
Kubernetes CronJob 控制器是基于时间调度的任务管理组件,用于周期性运行重复任务(类似 Linux 的 crontab),其核心是按照预设的时间规则自动创建 Job 来执行具体任务,适合需要定时重复的场景。
核心特性:
- 时间驱动调度:通过
spec.schedule字段定义cron表达式(如0 3 * * *表示每天凌晨 3 点执行),精确控制任务运行时间。 - 任务自动化:根据调度规则自动创建 Job 实例,每个 Job 负责执行一次任务,任务完成后 Job 和 Pod 会保留(可配置自动清理)。
- 容错与控制:支持配置任务并发策略(
concurrencyPolicy)、启动超时(startingDeadlineSeconds)、历史任务保留数量(successfulJobsHistoryLimit、failedJobsHistoryLimit)等。
典型使用场景:
- 定时数据处理:如每日日志归档、数据库定时备份、周期性数据同步。
- 定期维护任务:如清理临时文件、检查集群健康状态、证书自动更新检测。
- 周期性监控和报告:如定时生成业务报表、发送系统运行状态邮件。
工作原理:CronJob 控制器的工作流程可以概括为:监听 CronJob 对象 → 解析时间表 → 定时触发 → 创建 Job → 清理历史。
- 监听与解析:CronJob 控制器会持续监听集群中的 CronJob 对象,并解析其定义的
schedule字段(cron表达式)。 - 定时触发:当系统时间匹配
cron表达式时,控制器会根据jobTemplate中定义的模板创建一个新的 Job 对象。 - 任务执行:创建的 Job 对象会进一步负责创建 Pod 来执行具体的任务。Job 控制器会确保 Pod 运行至完成(即任务成功或达到重试限制)。
- 历史清理:任务完成后,CronJob 控制器会根据配置的
successfulJobsHistoryLimit和failedJobsHistoryLimit来保留相应数量的成功或失败 Job 记录,并清理旧的记录,以防止资源无限增长。
示例配置:
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup # CronJob 名称
spec:
schedule: "0 3 * * *" # 每天凌晨 3 点执行(cron 表达式)
concurrencyPolicy: Forbid # 禁止任务并发执行(若前一次未完成,跳过本次)
successfulJobsHistoryLimit: 3 # 保留 3 个成功的历史 Job
failedJobsHistoryLimit: 1 # 保留 1 个失败的历史 Job
jobTemplate: # 任务模板(定义要创建的 Job 及 Pod 信息)
spec:
template:
spec:
containers:
- name: backup
image: backup-tool:v1 # 执行备份的容器镜像
command: ["/scripts/backup.sh"] # 备份脚本
restartPolicy: OnFailure # 任务失败时重启容器
其中:
schedule:cron表达式,格式为分 时 日 月 周(如*/30 * * * *表示每 30 分钟执行一次)。concurrencyPolicy:控制并发执行策略,可选值:Allow(默认):允许并发执行。Forbid:禁止并发,若前一次未完成则跳过本次。Replace:用新任务替换正在运行的旧任务。
startingDeadlineSeconds:任务启动的超时时间(若因调度延迟超过此时长,任务会被标记为失败)。successfulJobsHistoryLimit&failedJobsHistoryLimit:控制保留已完成和失败 Job 的数量,默认为 3 和 1,有助于控制资源使用。suspend:设置为true可以临时暂停 CronJob 的调度而无需删除它
CronJob 资源配置清单文件:
apiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob-demo
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cronjob-demo-container
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
创建 CronJob:
[root@k8sm1 ~]# vi cronjob-demo.yml
[root@k8sm1 ~]# kubectl apply -f cronjob-demo.yml
cronjob.batch/cronjob-demo created
[root@k8sm1 ~]# kubectl get cronjob
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob-demo */1 * * * * False 0 <none> 8s
Service
Service 是访问 Kubernetes 集群内部多个 Pod 的入口,可以理解为多个 Pod 的 VIP。
从前面创建的 Deployment 可知,在 Kubernetes 集群中,无状态的 Pod 副本是可以随时删除、随时创建的,并且重新创建的 Pod 不再保留旧 Pod 的任何信息,包括 IP 地址。在这种情况下,前端 Pod 如何使用后端 Pod 来提供服务就成了问题,因此 Kubernetes 实现了 Service 这样一个抽象概念。对于有 Selector 的 Service,在被创建的时候会自动创建 EndPoint 资源,包含了对应 Lable 的所有 Pod 的 IP 和端口,并且在之后的 Pod 的删除、创建中,也会立即更新相关 Pod 的 IP 和端口信息。同时,Service 的 IP 地址是永远固定的,Service 和 EndPoint 是一一对应的关系。这样,如果前端 Pod 通过固定的 Service IP 来访问后端 Pod 提供的服务,那么就可以在 EndPoint 中找到一个可用 Pod 的 IP 和端口,再将数据包转发到指定的 Pod 上即可。
同时需要注意的是,只有当 Pod 通过就绪探针检查时,Kubernetes 才会将其 IP 地址添加到 Service 的 EndPoint 中,从而确保流量只被转发到已准备就绪的后端 Pod。

使用如下资源配置清单文件 nginx-service.yaml 定义 Service:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: app
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
sessionAffinity: None
其中:
metadata.name:Service 名称metadata.labels:Service 标签spec.type:Service 类型,有以下几种:ClusterIP:默认类型,通过集群的内部 IP 暴露服务,服务只能够从集群内部访问,其他类型的基础NodePort:是ClusterIP类型的超集。使用 NAT 方式,通过每个节点上的 IP 和端口(默认 30000-32767)暴露服务,可以不指定端口,由系统自动分配节点暴露的端口,也可以通过spec.ports[*].nodePort指定节点暴露的端口。前面创建 Dashboard 时就是采用的NodePort方式,直接将服务暴露到所有节点主机的端口上LoadBalancer:用于支持云服务商提供的外部负载均衡器,是NodePort类型的超集ExternalName:是 Service 的一种特殊类型,其核心作用是将集群内部的 Service 名称映射到集群外部的域名(FQDN),实现集群内 Pod 对外部服务的间接访问。与其他 Service 类型(如ClusterIP、NodePort)不同,ExternalName不需要通过selector关联 Pod,而是直接通过externalName字段指定外部域名。工作原理是在集群 DNS 服务器(如 CoreDNS)中添加一条 CNAME 记录,将 Service 名称映射到指定的外部域名。不分配ClusterIP,也不涉及流量转发,仅通过 DNS 解析实现访问跳转。例如访问数据库、消息队列等部署在 Kubernetes 集群外的服务,通过ExternalName统一集群内访问地址,避免直接硬编码外部域名。
spec.selector:指定要访问的 Pod 的标签spec.ports:指定端口信息,包括:spec.ports[*].name:服务端口名称spec.ports[*].protocol:端口协议spec.ports[*].port:服务端口号spec.ports[*].targetPort:对应的 Pod 端口,可以是端口号,也可以是创建 Pod 时指定的端口名称
spec.sessionAffinity:会话亲和性,默认为None,可以配置为ClientIP将同一客户端会话连接到同一 Pod。如果配置为ClientIP,默认超时时间为 3 小时。一般用于 HTTPS 环境中。
其他字段:
spec.internalTrafficPolicy:用于控制 Service 内部流量路由策略的字段,适用于ClusterIP类型的 Service。它决定了来自集群内部的流量(如 Pod 之间的通信)应如何被路由到后端 Endpoint(Pod)。该字段用于限制内部流量仅路由到本地节点上的后端 Pod 或允许路由到集群中任意节点上的后端 Pod,主要目的是优化网络性能(减少跨节点网络开销)或满足特定部署需求。该策略仅对来自集群内部的流量(如 Pod 访问 Service)生效,对外部流量(如通过NodePort或 Ingress 进入的流量)无影响。该字段在 Kubernetes 1.21+ 中稳定(GA)。可选值包括:Cluster(默认值):内部流量可以路由到集群中任意节点上的后端 Pod,不限制节点位置。Kubernetes 会根据 Service 的负载均衡策略(如 round-robin)选择后端 Pod,无论该 Pod 是否与客户端 Pod 在同一节点。适用场景:默认策略,适合大多数通用场景。Local:内部流量仅路由到与客户端 Pod 相同节点上的后端 Pod。如果当前节点上没有可用的后端 Pod,流量会被丢弃(不会转发到其他节点)。适用于:- 降低跨节点网络传输开销,适合对网络延迟敏感的服务。
- 减少网络带宽占用,尤其在大规模集群中。
- 结合 DaemonSet 使用(每个节点有且仅有一个后端 Pod),确保流量本地处理。
spec.externalTrafficPolicy:用于控制外部流量如何被路由到后端 Pod,这里的外部流量是指从集群外部(比如通过节点的物理网络接口)进入到 Service 的流量。适用于NodePort和LoadBalancer类型的 Service 。可选值包括:Cluster(默认值):当设置为Cluster时,Kubernetes 会在集群范围内对外部流量进行负载均衡。无论请求来自哪个节点,流量都可能被转发到集群中任意一个有后端 Pod 的节点上,然后再被分发到对应的后端 Pod。这种方式下,Kubernetes 会根据自身的负载均衡算法(如轮询等)来决定流量的走向,不会特别关注请求进入的节点与后端 Pod 所在节点的关系。适用于大多数通用场景,对外部流量的负载均衡没有特殊的节点亲和性要求,希望在整个集群范围内实现高效的流量分发,以充分利用集群资源。比如,一个普通的 Web 应用服务,多个后端 Pod 都可以平等地处理外部请求,不需要考虑请求从哪个节点进入集群。Local:当设置为Local时,Kubernetes 会尝试将外部流量仅路由到请求进入的节点上的后端 Pod。也就是说,如果某个节点接收到外部流量,只有当该节点上存在与 Service 匹配的后端 Pod 时,流量才会被转发到这些 Pod;如果该节点上没有可用的后端 Pod,那么这个请求将会被丢弃,而不会被转发到其他节点上的后端 Pod。适用场景:网络性能优化:在一些对网络延迟要求较高的场景中,使用
Local策略可以减少跨节点的网络传输,因为流量直接在请求进入的节点上处理,避免了在节点之间进行数据传输带来的额外延迟。例如,一些实时性要求高的多媒体处理服务。网络策略与合规性:当集群存在特定的网络策略或合规要求,需要确保流量在本地节点处理时,该策略可以满足需求。比如,某些安全合规规定要求数据在接收节点上进行本地处理,避免跨节点传输可能带来的安全风险。
结合本地存储或计算资源:如果后端 Pod 需要访问本地节点上的特定资源(如本地磁盘上的数据、本地 GPU 计算资源等),使用
Local策略可以保证请求能够被路由到拥有相应资源的节点上进行处理。
spec.publishNotReadyAddresses:用于控制是否将状态为未就绪(NotReady)的 Pod 的地址发布到对应的 Endpoint 中,进而影响 Service 对流量的转发。可选值包括:false(默认值):表示 Kubernetes 不会将状态为未就绪的 Pod 地址发布到 Service 对应的 Endpoint 中。只有当 Pod 的状态变为就绪(Ready)时,才会将其 IP 地址和端口信息添加到 Endpoint 中,Service 才会将流量转发到该 Pod 上。这是一种比较保守和安全的方式,能确保流量只被发送到可以正常处理请求的 Pod,防止请求被发送到还未完全启动、初始化或者出现故障的 Pod,从而避免请求失败或出现不可预期的结果。true:意味着即使 Pod 处于未就绪状态,其 IP 地址和端口信息也会被发布到 Service 对应的 Endpoint 中。此时,Service 有可能会将流量转发到这些未就绪的 Pod 上。虽然这样做可能会导致部分请求失败,但在某些特定场景下,比如希望提前预热 Pod,或者在业务可以容忍一定失败率的情况下,提前将未就绪 Pod 纳入到流量分发范围,可以让 Pod 更快地进入工作状态,或者让系统更快速地感知到 Pod 的故障。
创建
ClusterIP
创建类型为 ClusterIP 的 Service :
[root@k8sm1 ~]# kubectl apply -f nginx-service.yaml
service/nginx-service created
查看 Service:
[root@k8sm1 ~]# kubectl get services -n app -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-service ClusterIP 10.97.154.99 <none> 80/TCP 52s app=nginx
[root@k8sm1 ~]# kubectl describe services nginx-service -n app
Name: nginx-service
Namespace: app
Labels: app=nginx
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.97.154.99
IPs: 10.97.154.99
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.20:80,10.244.2.19:80,10.244.2.20:80
Session Affinity: None
Events: <none>
可以看到该 Service 对接的 Endpoints 为 10.244.1.20:80,10.244.2.19:80,10.244.2.20:80。
查看与 Service 同名的 Endpoint:
[root@k8sm1 ~]# kubectl get endpoints nginx-service -n app -o yaml
apiVersion: v1
kind: Endpoints
metadata:
annotations:
endpoints.kubernetes.io/last-change-trigger-time: "2023-08-28T02:33:38Z"
creationTimestamp: "2023-08-28T02:33:38Z"
labels:
app: nginx
name: nginx-service
namespace: app
resourceVersion: "198840"
uid: 04d89ad4-c0be-4107-8595-847b56e50072
subsets:
- addresses:
- ip: 10.244.1.20
nodeName: k8sn1.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-b5b79
namespace: app
resourceVersion: "193892"
uid: 15681a8a-7a57-48bc-b7c3-f569a3be0ccd
- ip: 10.244.2.19
nodeName: k8sn2.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-f4dxq
namespace: app
resourceVersion: "193991"
uid: 384b52ec-1c49-4c68-b76a-572b0560ec29
- ip: 10.244.2.20
nodeName: k8sn2.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-j95b6
namespace: app
resourceVersion: "193988"
uid: e9be4521-9f4a-4e09-9d63-2b5dd6857a75
ports:
- name: http
port: 80
protocol: TCP
将 Deployment 扩容为 4 副本:
[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=4 -n app
deployment.apps/nginx-deployment scaled
查看 Endpoint,可以看到将新增的 Pod 加入到 Endpoint 中:
[root@k8sm1 ~]# kubectl get endpoints nginx-service -n app -o yaml
apiVersion: v1
kind: Endpoints
metadata:
annotations:
endpoints.kubernetes.io/last-change-trigger-time: "2023-08-28T05:26:53Z"
creationTimestamp: "2023-08-28T02:33:38Z"
labels:
app: nginx
name: nginx-service
namespace: app
resourceVersion: "214715"
uid: 04d89ad4-c0be-4107-8595-847b56e50072
subsets:
- addresses:
- ip: 10.244.1.20
nodeName: k8sn1.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-b5b79
namespace: app
resourceVersion: "193892"
uid: 15681a8a-7a57-48bc-b7c3-f569a3be0ccd
- ip: 10.244.1.21
nodeName: k8sn1.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-6hn6v
namespace: app
resourceVersion: "214711"
uid: b833c8ad-42bf-4f1e-bb77-6ba5a0c39e95
- ip: 10.244.2.19
nodeName: k8sn2.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-f4dxq
namespace: app
resourceVersion: "193991"
uid: 384b52ec-1c49-4c68-b76a-572b0560ec29
- ip: 10.244.2.20
nodeName: k8sn2.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-j95b6
namespace: app
resourceVersion: "193988"
uid: e9be4521-9f4a-4e09-9d63-2b5dd6857a75
ports:
- name: http
port: 80
protocol: TCP
将 Deployment 缩容为 2 副本:
[root@k8sm1 ~]# kubectl scale deployment/nginx-deployment --replicas=2 -n app
deployment.apps/nginx-deployment scaled
查看 Endpoint,就只有剩下的 Pod 了:
[root@k8sm1 ~]# kubectl get endpoints nginx-service -n app -o yaml
apiVersion: v1
kind: Endpoints
metadata:
creationTimestamp: "2023-08-28T02:33:38Z"
labels:
app: nginx
name: nginx-service
namespace: app
resourceVersion: "215281"
uid: 04d89ad4-c0be-4107-8595-847b56e50072
subsets:
- addresses:
- ip: 10.244.1.20
nodeName: k8sn1.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-b5b79
namespace: app
resourceVersion: "193892"
uid: 15681a8a-7a57-48bc-b7c3-f569a3be0ccd
- ip: 10.244.2.19
nodeName: k8sn2.stonecoding.net
targetRef:
kind: Pod
name: nginx-deployment-9456bbbf9-f4dxq
namespace: app
resourceVersion: "193991"
uid: 384b52ec-1c49-4c68-b76a-572b0560ec29
ports:
- name: http
port: 80
protocol: TCP
NodePort
定义 Deployment 的资源清单配置文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-nodeport-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
svc: nodeport
template:
metadata:
labels:
app: myapp
release: stabel
env: test
svc: nodeport
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
创建 Deployment:
[root@k8sm1 ~]# vi myapp-nodeport-deploy.yaml
[root@k8sm1 ~]# kubectl apply -f myapp-nodeport-deploy.yaml
deployment.apps/myapp-nodeport-deploy created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
myapp-nodeport-deploy-dcf659dd6-7txt2 1/1 Running 0 79s
myapp-nodeport-deploy-dcf659dd6-hdw89 1/1 Running 0 79s
myapp-nodeport-deploy-dcf659dd6-lhhlx 1/1 Running 0 79s
类型为 NodePort 的 Service 的资源配置清单文件:
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
namespace: default
spec:
type: NodePort
selector:
app: myapp
release: stabel
svc: nodeport
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30010
创建 Service:
[root@k8sm1 ~]# vi myapp-nodeport.yaml
[root@k8sm1 ~]# kubectl apply -f myapp-nodeport.yaml
service/myapp-nodeport created
[root@k8sm1 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-nodeport NodePort 10.97.51.225 <none> 80:30010/TCP 44s
由于 NodePort 类似是 ClusterIP 的超级,服务创建完成后,既可以在集群内部使用 10.97.51.225:80 访问后端的 Pod,也可以使用该服务对应的域名访问后端的 Pod:
[root@k8sm1 ~]# kubectl exec -it myapp-nodeport-deploy-dcf659dd6-7txt2 -- /bin/bash
myapp-nodeport-deploy-dcf659dd6-7txt2:/# curl 10.97.51.225/hostname.html
myapp-nodeport-deploy-dcf659dd6-lhhlx
myapp-nodeport-deploy-dcf659dd6-7txt2:/# curl myapp-nodeport.default.svc.cluster.local/hostname.html
myapp-nodeport-deploy-dcf659dd6-hdw89
还可以在集群外部使用所有集群节点的 NodeIP:30010 访问后端的 Pod:
[root@k8sm1 ~]# curl 192.168.92.140:30010/hostname.html
myapp-nodeport-deploy-dcf659dd6-lhhlx
[root@k8sm1 ~]# curl 192.168.92.141:30010/hostname.html
myapp-nodeport-deploy-dcf659dd6-hdw89
如果使用的是 IPVS 模式,则可以看到对应的规则:
[root@k8sn1 ~]# ipvsadm -Ln
TCP 10.97.51.225:80 rr
-> 10.244.1.99:80 Masq 1 0 0
-> 10.244.1.100:80 Masq 1 0 0
-> 10.244.1.101:80 Masq 1 0 0
TCP 192.168.92.141:30010 rr
-> 10.244.1.99:80 Masq 1 0 0
-> 10.244.1.100:80 Masq 1 0 0
-> 10.244.1.101:80 Masq 1 0 0
ExternalName
类型为 ExternalName 的 Service 的资源配置清单文件:
kind: Service
apiVersion: v1
metadata:
name: externalname-service
namespace: default
spec:
type: ExternalName
externalName: stonecoding.net
创建 Service:
[root@k8sm1 ~]# vi externalname-service.yaml
[root@k8sm1 ~]# kubectl apply -f externalname-service.yaml
service/externalname-service created
[root@k8sm1 ~]# kubectl exec -it myapp-nodeport-deploy-dcf659dd6-7txt2 -- /bin/bash
myapp-nodeport-deploy-dcf659dd6-7txt2:/# wget externalname-service.default.svc.cluster.local
Connecting to externalname-service.default.svc.cluster.local (101.34.184.43:80)
saving to 'index.html'
index.html 100% |************************************************************************| 16853 0:00:00 ETA
'index.html' saved
服务发现
对于集群内的 Pod,Kubernetes 支持两种服务发现模式:环境变量和 DNS。
环境变量
创建 Service 之后创建的 Pod 会被注入之前 Service 对应的环境变量,格式为 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT(将服务名称转换为大小,横线转换为下划线),这样 Pod 就可以通过其环境变量访问对应的服务了。
创建一个 Service 及其对应的一个 Nginx 镜像版本为 1.18 的 Deployment:
[root@k8sm1 ~]# vi nginx-v118.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service-v118
namespace: app
labels:
app: nginx-v118
spec:
type: ClusterIP
selector:
app: nginx-v118
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
sessionAffinity: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-v118
namespace: app
labels:
app: nginx-v118
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v118
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: nginx-v118
spec:
containers:
- name: nginx
image: nginx:1.18
ports:
- containerPort: 80
[root@k8sm1 ~]# kubectl apply -f nginx-v118.yaml
service/nginx-service-v118 created
deployment.apps/nginx-deployment-v118 created
[root@k8sm1 ~]# kubectl get all -n app
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-9456bbbf9-5cdgr 1/1 Running 0 8m3s
pod/nginx-deployment-9456bbbf9-87rfh 1/1 Running 0 8m
pod/nginx-deployment-9456bbbf9-jf8zw 1/1 Running 0 7m59s
pod/nginx-deployment-v118-69584f9576-d4cw6 1/1 Running 0 15s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-service ClusterIP 10.97.154.99 <none> 80/TCP 3h52m
service/nginx-service-v118 ClusterIP 10.111.251.40 <none> 80/TCP 15s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 3/3 3 3 3d
deployment.apps/nginx-deployment-v118 1/1 1 1 15s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-6dfc66cc6c 0 0 0 10m
replicaset.apps/nginx-deployment-9456bbbf9 3 3 3 3d
replicaset.apps/nginx-deployment-v118-69584f9576 1 1 1 15s
进入 Pod nginx-deployment-v118-69584f9576-d4cw6 查看环境变量:
[root@k8sm1 ~]# kubectl exec -it nginx-deployment-v118-69584f9576-d4cw6 sh -n app
# env | grep NGINX_SERVICE_V118
NGINX_SERVICE_V118_SERVICE_HOST=10.111.251.40
NGINX_SERVICE_V118_PORT=tcp://10.111.251.40:80
NGINX_SERVICE_V118_SERVICE_PORT=80
NGINX_SERVICE_V118_PORT_80_TCP_ADDR=10.111.251.40
NGINX_SERVICE_V118_PORT_80_TCP_PORT=80
NGINX_SERVICE_V118_PORT_80_TCP_PROTO=tcp
NGINX_SERVICE_V118_PORT_80_TCP=tcp://10.111.251.40:80
NGINX_SERVICE_V118_SERVICE_PORT_HTTP=80
DNS
使用环境变量作为服务发现必须先创建服务,再创建 Pod。更常用的是使用组件 CoreDNS 提供的 DNS 服务来进行服务发现。
[root@k8sm1 ~]# kubectl get pods -n kube-system -o wide | grep dns
coredns-6d8c4cb4d-bwwxw 1/1 Running 5 (5h31m ago) 7d3h 10.244.0.13 k8sm1.stonecoding.net <none> <none>
coredns-6d8c4cb4d-zp7nr 1/1 Running 5 (5h31m ago) 7d3h 10.244.0.12 k8sm1.stonecoding.net <none> <none>
[root@k8sm1 ~]# kubectl get services -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 7d3h
[root@k8sm1 ~]# kubectl describe services kube-dns -n kube-system
Name: kube-dns
Namespace: kube-system
Labels: k8s-app=kube-dns
kubernetes.io/cluster-service=true
kubernetes.io/name=CoreDNS
Annotations: prometheus.io/port: 9153
prometheus.io/scrape: true
Selector: k8s-app=kube-dns
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.0.10
IPs: 10.96.0.10
Port: dns 53/UDP
TargetPort: 53/UDP
Endpoints: 10.244.0.12:53,10.244.0.13:53
Port: dns-tcp 53/TCP
TargetPort: 53/TCP
Endpoints: 10.244.0.12:53,10.244.0.13:53
Port: metrics 9153/TCP
TargetPort: 9153/TCP
Endpoints: 10.244.0.12:9153,10.244.0.13:9153
Session Affinity: None
Events: <none>
创建一个镜像为 busybox 的 Pod:
[root@k8sm1 ~]# vi busybox-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
[root@k8sm1 ~]# kubectl apply -f busybox-pod.yaml
pod/busybox created
[root@k8sm1 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 24s
进入容器查看服务:
[root@k8sm1 ~]# kubectl exec -it busybox -- /bin/sh
/ # nslookup 10.111.251.40
Server: 10.96.0.10
Address: 10.96.0.10:53
40.251.111.10.in-addr.arpa name = nginx-service-v118.app.svc.cluster.local
可以看到,集群内的 CoreDNS 服务会为每个 Service 注册一个 DNS 记录。格式通常为 <Service-Name>.<Namespace>.svc.cluster.local。在同一个命名空间内,Pod 可以直接通过 <Service-Name>访问服务。
服务代理
Kubernetes 集群的每个节点都有 kube-proxy 组件,作为服务代理,为服务实现 Virtual IP(VIP),使发往 Service 的流量负载均衡到正确的后端 Pod。
kube-proxy 监听 API Server 中资源对象的变化情况,包括以下三种:
- Service
- Endpoint/EndpointSlices(如果使用了 EndpointSlice,则会监听 EndpointSlice,否则监听 Endpoint)
- Node
然后根据资源变化情况,为服务配置负载均衡。
在 Linux 上目前 Kube-proxy 可用模式有:
- iptables
- ipvs
iptables
Kube-proxy 默认使用 iptables 模式:
[root@k8sm1 ~]# curl http://localhost:10249/proxyMode
iptables
在这种模式下,Kube-proxy 监视 Kubernetes 控制平面,获知对 Service 和 EndpointSlice 的添加和删除操作。 对于每个 Service,Kube-proxy 会添加 iptables 规则,这些规则捕获流向 Service 的 clusterIP 和 port 的流量, 并将这些流量重定向到 Service 后端 Pod 中的其中之一。对于每个 Endpoint,会添加指向某个后端 Pod 的 iptables 规则。
默认情况下,iptables 模式下的 Kube-proxy 会随机选择一个后端 Pod。
使用 iptables 处理流量的系统开销较低,因为流量由 Linux netfilter 处理,无需在用户空间和内核空间之间切换,也更为可靠。
如果 Kube-proxy 以 iptables 模式运行,并且选择的第一个 Pod 没有响应,那么连接会失败。可以使用 Pod 的就绪探针来验证后端 Pod 是否健康,以避免 Kube-proxy 将流量发送到失败的 Pod 上。
当创建 Service 时,Kubernetes 控制平面会分配一个虚拟 IP 地址,例如 10.97.154.99。当节点上的 Kube-proxy 观察到新 Service 时,会添加一系列 iptables 规则,以便将访问这个地址的流量转到真实的 Pod 上。
[root@k8sm1 ~]# iptables -t nat -nL OUTPUT
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
第一条规则会匹配所有的流量,然后跳到 KUBE-SERVICES 这条链上。看一下 KUBE-SERVICES 的具体内容:
[root@k8sm1 ~]# iptables -t nat -nL KUBE-SERVICES
Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-SVC-EDNDUDH2C75GIR6O tcp -- 0.0.0.0/0 10.98.51.149 /* ingress-nginx/ingress-nginx-controller:https cluster IP */ tcp dpt:443
KUBE-SVC-EZYNCFY2F7N6OQA2 tcp -- 0.0.0.0/0 10.101.134.29 /* ingress-nginx/ingress-nginx-controller-admission:https-webhook cluster IP */ tcp dpt:443
KUBE-SVC-CEZPIJSAUFW5MYPQ tcp -- 0.0.0.0/0 10.107.3.191 /* kubernetes-dashboard/kubernetes-dashboard cluster IP */ tcp dpt:443
KUBE-SVC-CB4FKN7PLFL7IROR tcp -- 0.0.0.0/0 10.97.154.99 /* app/nginx-service:http cluster IP */ tcp dpt:80
KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- 0.0.0.0/0 10.96.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
KUBE-SVC-CG5I4G2RS3ZVWGLK tcp -- 0.0.0.0/0 10.98.51.149 /* ingress-nginx/ingress-nginx-controller:http cluster IP */ tcp dpt:80
KUBE-SVC-JD5MR3NA4I4DYORP tcp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:metrics cluster IP */ tcp dpt:9153
KUBE-SVC-TCOU7JCQXEZGVUNU udp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
KUBE-SVC-Z6GDYMWE5TV2NNJN tcp -- 0.0.0.0/0 10.110.86.109 /* kubernetes-dashboard/dashboard-metrics-scraper cluster IP */ tcp dpt:8000
KUBE-SVC-BMEYHMRRLQBXSPQR tcp -- 0.0.0.0/0 10.111.251.40 /* app/nginx-service-v118:http cluster IP */ tcp dpt:80
KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
这里前面的 KUBE-SVC-* 都是根据 destination,protocol 和目的端口号来匹配的,根据 Service 地址和端口号以及协议,可以匹配到 KUBE-SVC-CB4FKN7PLFL7IROR 这条规则,然后跳到这条链上。接着看这条链的定义:
[root@k8sm1 ~]# iptables -t nat -nL KUBE-SVC-CB4FKN7PLFL7IROR
Chain KUBE-SVC-CB4FKN7PLFL7IROR (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.97.154.99 /* app/nginx-service:http cluster IP */ tcp dpt:80
KUBE-SEP-6IWAUY3HDSBJG6JK all -- 0.0.0.0/0 0.0.0.0/0 /* app/nginx-service:http */ statistic mode random probability 0.33333333349
KUBE-SEP-6SVYFH2G76OTHT2L all -- 0.0.0.0/0 0.0.0.0/0 /* app/nginx-service:http */ statistic mode random probability 0.50000000000
KUBE-SEP-L2XRLFTEOGVDOKM7 all -- 0.0.0.0/0 0.0.0.0/0 /* app/nginx-service:http */
最下面的 2-4 条规则就是使用 iptables 实现负载均衡的地方了。第 2 条规则有 33% 的匹配几率。如果匹配到了其中一条,就会跳到这个链上。比如:
[root@k8sm1 ~]# iptables -t nat -nL KUBE-SEP-6IWAUY3HDSBJG6JK
Chain KUBE-SEP-6IWAUY3HDSBJG6JK (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.244.1.25 0.0.0.0/0 /* app/nginx-service:http */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* app/nginx-service:http */ tcp to:10.244.1.25:80
第 2 条规则将目的 IP 和 Port 改写成 10.244.1.25:80,也就是 Pod 的 IP 和 Port,这样流量就经过负载均衡指向了后端的 Pod。
iptables 模式适合中小规模集群。当集群 Service 和 Pod 数量巨大时,iptables 规则会变得非常冗长(规则线性匹配,时间复杂度为 O(n)),可能导致网络延迟增加和规则更新变慢。对于大规模集群,通常推荐使用 IPVS 模式,它基于哈希表,查询效率更高(O(1)),支持的负载均衡算法也更丰富。
IPVS
在 IPVS 模式下,Kube-proxy 监视 Kubernetes Service 和 EndpointSlice,然后调用 netlink 接口创建 IPVS 规则, 并定期与 Kubernetes Service 和 EndpointSlice 同步 IPVS 规则,以确保 IPVS 状态与期望的状态保持一致。 访问 Service 时,IPVS 会将流量导向到某个后端 Pod。
IPVS 代理模式基于 netfilter 回调函数,类似于 iptables 模式, 但使用哈希表作为底层数据结构,工作在内核空间。 这意味着 IPVS 模式下的 Kube-proxy 比 iptables 模式下的 Kube-proxy 重定向流量的延迟更低,同步代理规则时性能也更好。 当服务数量超过 10000 个时,建议使用 IPVS。
IPVS 可以使用的负载均衡算法有:
rr:轮询lc:最少连接(打开连接数最少)dh:目标地址哈希sh:源地址哈希sed:最短预期延迟nq:最少队列
注意: 要在 IPVS 模式下运行 Kube-proxy,必须在启动 Kube-proxy 之前确保节点上的 IPVS 可用。
当 Kube-proxy 以 IPVS 代理模式启动时,将验证 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 Kube-proxy 将退回到以 iptables 代理模式运行。
要将 kube-proxy 模式修改为 IPVS,先查看所有节点的 IPVS 内核模块是否加载成功:
[root@k8sm1 ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4
ip_vs_sh 12688 0
ip_vs_wrr 12697 0
ip_vs_rr 12600 0
ip_vs 145458 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack_ipv4 15053 9
nf_defrag_ipv4 12729 1 nf_conntrack_ipv4
nf_conntrack 139264 10 ip_vs,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_nat_masquerade_ipv6,nf_conntrack_netlink,nf_conntrack_ipv4,nf_conntrack_ipv6
libcrc32c 12644 4 xfs,ip_vs,nf_nat,nf_conntrack
[root@k8sm1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
然后修改 kube-proxy 的 ConfigMap,修改 mode 为 ipvs:
[root@k8sm1 ~]# kubectl edit configmap kube-proxy -n kube-system
configmap/kube-proxy edited
然后重启所有 kube-proxy Pod,使其加载新配置:
[root@k8sm1 ~]# kubectl get pods -n kube-system -l k8s-app=kube-proxy
NAME READY STATUS RESTARTS AGE
kube-proxy-5n2hd 1/1 Running 5 (6h28m ago) 14d
kube-proxy-sq75x 1/1 Running 8 (6h29m ago) 21d
[root@k8sm1 ~]# kubectl delete pods -n kube-system -l k8s-app=kube-proxy
pod "kube-proxy-5n2hd" deleted
pod "kube-proxy-sq75x" deleted
[root@k8sm1 ~]# kubectl get pods -n kube-system -l k8s-app=kube-proxy
NAME READY STATUS RESTARTS AGE
kube-proxy-9wqvl 1/1 Running 0 3s
kube-proxy-sv5mp 1/1 Running 0 2s
[root@k8sm1 ~]# kubectl logs kube-proxy-9wqvl -n kube-system | grep "Proxier"
I0909 07:12:12.956736 1 server_others.go:269] "Using ipvs Proxier"
I0909 07:12:12.956788 1 server_others.go:271] "Creating dualStackProxier for ipvs"
检查 IPVS 规则是否生成:
[root@k8sm1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.1:443 rr
-> 192.168.92.140:6443 Masq 1 0 0
TCP 10.96.0.10:53 rr
-> 10.244.0.16:53 Masq 1 0 0
-> 10.244.0.17:53 Masq 1 0 0
TCP 10.96.0.10:9153 rr
-> 10.244.0.16:9153 Masq 1 0 0
-> 10.244.0.17:9153 Masq 1 0 0
UDP 10.96.0.10:53 rr
-> 10.244.0.16:53 Masq 1 0 0
-> 10.244.0.17:53 Masq 1 0 0
Ingress
可以使用 Service 的 NodePort 将服务暴露在宿主机的端口上供外部访问,但随着服务的增多,会大量占用宿主机的端口,维护大量的 NodePort 也非常麻烦。此时就需要使用 Ingress 来作为反向代理(类似于 Nginx)处理外部对集群服务的 HTTP 请求。
Ingress 是 Kubernetes 中的一个资源对象,作为集群内服务对外暴露的统一入口,将外部流量路由到 Kubernetes 集群内的服务。可以通过 Ingress 资源来配置不同的转发规则,从而达到根据不同的规则设置访问集群内不同的 Service 所对应的后端 Pod。

为了使 Ingress 正常工作,集群中必须运行 Ingress 控制器,Ingress 控制器有很多种,这里使用前面已经安装好的 Ingress-Nginx。
[root@k8sm1 ~]# kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create-xltbp 0/1 Completed 0 127m
pod/ingress-nginx-admission-patch-gh9p5 0/1 Completed 0 127m
pod/ingress-nginx-controller-j49qh 1/1 Running 0 127m
pod/ingress-nginx-controller-whd5f 1/1 Running 0 127m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.99.217.210 <pending> 80:31412/TCP,443:30818/TCP 127m
service/ingress-nginx-controller-admission ClusterIP 10.109.220.22 <none> 443/TCP 127m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/ingress-nginx-controller 2 2 2 2 2 ingress=nginx,kubernetes.io/os=linux 127m
NAME COMPLETIONS DURATION AGE
job.batch/ingress-nginx-admission-create 1/1 4s 127m
job.batch/ingress-nginx-admission-patch 1/1 5s 127m
[root@k8sm1 ~]# kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
nginx k8s.io/ingress-nginx <none> 167m
其中 Pod 有:
ingress-nginx-controller是 Ingress-Nginx 的控制器组件,负责监视 Kubernetes API Server 上的 Ingress 对象,并根据配置动态地更新 Nginx 配置文件,实现 HTTP(S) 的负载均衡和路由。ingress-nginx-admission-create是一个 Kubernetes Admission Controller,可以拦截 Kubernetes 集群内的 Ingress 对象的创建操作,并在 Ingress 对象创建之前,对其进行一些额外的验证和处理。ingress-nginx-admission-patch也是一个 Kubernetes Admission Controller,可以在 Ingress 对象更新之前,对其进行额外的验证和处理,类似于ingress-nginx-admission-create。
Service 有:
ingress-nginx-controller负责将请求转发到ingress-nginx-controllerPods。它通常会将流量分发到ingress-nginx-controller的多个副本中,并确保副本集的负载平衡。这个 Service 可以被配置为使用NodePort、LoadBalancer或ClusterIP类型,根据需要进行暴露。ingress-nginx-controller-admission用于 Kubernetes Admission Webhooks,允许在创建、更新或删除资源时,对其进行校验或修改。它提供了一个 API Endpoint,用于与 Kubernetes API Server 进行通信,以便进行这些校验或修改。这个 Service 可以被配置为使用NodePort、LoadBalancer或ClusterIP类型,根据需要进行暴露。
创建
使用如下资源配置清单文件 web-ingress.yaml 定义 Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: app
spec:
ingressClassName: nginx
rules:
- host: web.stonecoding.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
其中:
ingressClassName:指定使用的 Ingress 控制器spec.rules[*].http.paths[*].pathType:有 3 种路径类型:ImplementationSpecific:匹配方法取决于 IngressClassExact:精确匹配 URL 路径,且区分大小写Prefix:基于以/分隔的 URL 路径前缀匹配,区分大小写
匹配示例如下:
| Kind | Path(s) | Request path(s) | Matches? |
|---|---|---|---|
| Prefix | / | (all paths) | Yes |
| Exact | /foo | /foo | Yes |
| Exact | /foo | /bar | No |
| Exact | /foo | /foo/ | No |
| Exact | /foo/ | /foo | No |
| Prefix | /foo | /foo, /foo/ | Yes |
| Prefix | /foo/ | /foo, /foo/ | Yes |
| Prefix | /aaa/bb | /aaa/bbb | No |
| Prefix | /aaa/bbb | /aaa/bbb | Yes |
| Prefix | /aaa/bbb/ | /aaa/bbb | Yes, ignores trailing slash |
| Prefix | /aaa/bbb | /aaa/bbb/ | Yes, matches trailing slash |
| Prefix | /aaa/bbb | /aaa/bbb/ccc | Yes, matches subpath |
| Prefix | /aaa/bbb | /aaa/bbbxyz | No, does not match string prefix |
| Prefix | /, /aaa | /aaa/ccc | Yes, matches /aaa prefix |
| Prefix | /, /aaa, /aaa/bbb | /aaa/bbb | Yes, matches /aaa/bbb prefix |
| Prefix | /, /aaa, /aaa/bbb | /ccc | Yes, matches / prefix |
| Prefix | /aaa | /ccc | No, uses default backend |
| Mixed | /foo (Prefix), /foo (Exact) | /foo | Yes, prefers Exact |
创建 Ingress:
[root@k8sm1 ~]# kubectl apply -f web-ingress.yaml
ingress.networking.k8s.io/web created
查看 Ingress:
[root@k8sm1 ~]# kubectl get ingress -n app
NAME CLASS HOSTS ADDRESS PORTS AGE
web nginx web.stonecoding.net 80 22m
[root@k8sm1 ~]# kubectl describe ingress web -n app
Name: web
Labels: <none>
Namespace: app
Address:
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
web.stonecoding.net
/ nginx-service:80 (10.244.1.30:80,10.244.1.31:80,10.244.2.32:80)
Annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 50m nginx-ingress-controller Scheduled for sync
Normal Sync 50m nginx-ingress-controller Scheduled for sync
出现 error: endpoints "default-http-backend" not found 报错,需要创建服务 default-http-backend,资源配置清单文件如下:
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: kube-system
spec:
selector:
app: ingress-nginx-controller
ports:
- protocol: TCP
port: 80
targetPort: 80
创建服务:
[root@k8sm1 ~]# kubectl apply -f default-http-backend-service.yaml
service/default-http-backend created
再次查看 Ingress:
[root@k8sm1 ~]# kubectl describe ingress web -n app
Name: web
Labels: <none>
Namespace: app
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
web.stonecoding.net
/ nginx-service:80 (10.244.1.30:80,10.244.1.31:80,10.244.2.32:80)
Annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 5m44s nginx-ingress-controller Scheduled for sync
Normal Sync 5m44s nginx-ingress-controller Scheduled for sync
配置好 web.stonecoding.net 域名解析,指向地址 192.168.92.140,即可使用该域名进行访问:

Node
Node 是 Kubernetes 集群的物理节点。
查看所有节点信息:
[root@k8sm1 ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8sm1.stonecoding.net Ready control-plane,master 23h v1.23.17 192.168.92.140 <none> CentOS Linux 7 (Core) 3.10.0-1160.el7.x86_64 docker://20.10.23
k8sn1.stonecoding.net Ready <none> 23h v1.23.17 192.168.92.141 <none> CentOS Linux 7 (Core) 3.10.0-1160.el7.x86_64 docker://20.10.23
k8sn2.stonecoding.net Ready <none> 23h v1.23.17 192.168.92.142 <none> CentOS Linux 7 (Core) 3.10.0-1160.el7.x86_64 docker://20.10.23
查看指定节点信息:
[root@k8sm1 ~]# kubectl describe node k8sm1
Name: k8sm1.stonecoding.net
Roles: control-plane,master
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8sm1.stonecoding.net
kubernetes.io/os=linux
node-role.kubernetes.io/control-plane=
node-role.kubernetes.io/master=
node.kubernetes.io/exclude-from-external-load-balancers=
Annotations: flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"fa:9d:c6:a2:46:3a"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 192.168.92.140
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Mon, 21 Aug 2023 11:28:29 +0800
Taints: node-role.kubernetes.io/master:NoSchedule
Unschedulable: false
Lease:
HolderIdentity: k8sm1.stonecoding.net
AcquireTime: <unset>
RenewTime: Wed, 23 Aug 2023 14:39:40 +0800
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
NetworkUnavailable False Wed, 23 Aug 2023 08:51:27 +0800 Wed, 23 Aug 2023 08:51:27 +0800 FlannelIsUp Flannel is running on this node
MemoryPressure False Wed, 23 Aug 2023 14:36:58 +0800 Mon, 21 Aug 2023 11:28:20 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Wed, 23 Aug 2023 14:36:58 +0800 Mon, 21 Aug 2023 11:28:20 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Wed, 23 Aug 2023 14:36:58 +0800 Mon, 21 Aug 2023 11:28:20 +0800 KubeletHasSufficientPID kubelet has sufficient PID available
Ready True Wed, 23 Aug 2023 14:36:58 +0800 Mon, 21 Aug 2023 15:27:04 +0800 KubeletReady kubelet is posting ready status
Addresses:
InternalIP: 192.168.92.140
Hostname: k8sm1.stonecoding.net
Capacity:
cpu: 2
ephemeral-storage: 17394Mi
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3861292Ki
pods: 110
Allocatable:
cpu: 2
ephemeral-storage: 16415037823
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3758892Ki
pods: 110
System Info:
Machine ID: 25ffa45cb6834fb0a7f70f8567485af8
System UUID: 564D0B5B-3F3A-3F0C-7016-A769F6FA5BE5
Boot ID: c11c3bf2-62da-41be-a6a3-4b5c5c2b75af
Kernel Version: 3.10.0-1160.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://20.10.23
Kubelet Version: v1.23.17
Kube-Proxy Version: v1.23.17
PodCIDR: 10.244.0.0/24
PodCIDRs: 10.244.0.0/24
Non-terminated Pods: (8 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
kube-flannel kube-flannel-ds-h25mg 100m (5%) 0 (0%) 50Mi (1%) 0 (0%) 2d3h
kube-system coredns-6d8c4cb4d-bwwxw 100m (5%) 0 (0%) 70Mi (1%) 170Mi (4%) 2d3h
kube-system coredns-6d8c4cb4d-zp7nr 100m (5%) 0 (0%) 70Mi (1%) 170Mi (4%) 2d3h
kube-system etcd-k8sm1.stonecoding.net 100m (5%) 0 (0%) 100Mi (2%) 0 (0%) 2d3h
kube-system kube-apiserver-k8sm1.stonecoding.net 250m (12%) 0 (0%) 0 (0%) 0 (0%) 2d3h
kube-system kube-controller-manager-k8sm1.stonecoding.net 200m (10%) 0 (0%) 0 (0%) 0 (0%) 2d3h
kube-system kube-proxy-4dl9s 0 (0%) 0 (0%) 0 (0%) 0 (0%) 2d3h
kube-system kube-scheduler-k8sm1.stonecoding.net 100m (5%) 0 (0%) 0 (0%) 0 (0%) 2d3h
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 950m (47%) 0 (0%)
memory 290Mi (7%) 340Mi (9%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
Events: <none>
或者:
[root@k8sm1 ~]# kubectl get nodes k8sm1.stonecoding.net -o yaml
apiVersion: v1
kind: Node
metadata:
annotations:
flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"ca:f3:8a:05:77:ab"}'
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: "true"
flannel.alpha.coreos.com/public-ip: 192.168.92.140
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
node.alpha.kubernetes.io/ttl: "0"
volumes.kubernetes.io/controller-managed-attach-detach: "true"
creationTimestamp: "2023-08-21T03:28:29Z"
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: k8sm1.stonecoding.net
kubernetes.io/os: linux
node-role.kubernetes.io/control-plane: ""
node-role.kubernetes.io/master: ""
node.kubernetes.io/exclude-from-external-load-balancers: ""
name: k8sm1.stonecoding.net
resourceVersion: "45356"
uid: c013d0a0-08cd-4afe-abca-3640079df59f
spec:
podCIDR: 10.244.0.0/24
podCIDRs:
- 10.244.0.0/24
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
status:
addresses:
- address: 192.168.92.140
type: InternalIP
- address: k8sm1.stonecoding.net
type: Hostname
allocatable:
cpu: "2"
ephemeral-storage: "16415037823"
hugepages-1Gi: "0"
hugepages-2Mi: "0"
memory: 3758900Ki
pods: "110"
capacity:
cpu: "2"
ephemeral-storage: 17394Mi
hugepages-1Gi: "0"
hugepages-2Mi: "0"
memory: 3861300Ki
pods: "110"
conditions:
- lastHeartbeatTime: "2023-08-22T01:05:38Z"
lastTransitionTime: "2023-08-22T01:05:38Z"
message: Flannel is running on this node
reason: FlannelIsUp
status: "False"
type: NetworkUnavailable
- lastHeartbeatTime: "2023-08-22T03:44:04Z"
lastTransitionTime: "2023-08-21T03:28:20Z"
message: kubelet has sufficient memory available
reason: KubeletHasSufficientMemory
status: "False"
type: MemoryPressure
- lastHeartbeatTime: "2023-08-22T03:44:04Z"
lastTransitionTime: "2023-08-21T03:28:20Z"
message: kubelet has no disk pressure
reason: KubeletHasNoDiskPressure
status: "False"
type: DiskPressure
- lastHeartbeatTime: "2023-08-22T03:44:04Z"
lastTransitionTime: "2023-08-21T03:28:20Z"
message: kubelet has sufficient PID available
reason: KubeletHasSufficientPID
status: "False"
type: PIDPressure
- lastHeartbeatTime: "2023-08-22T03:44:04Z"
lastTransitionTime: "2023-08-21T07:27:04Z"
message: kubelet is posting ready status
reason: KubeletReady
status: "True"
type: Ready
daemonEndpoints:
kubeletEndpoint:
Port: 10250
images:
- names:
- registry.aliyuncs.com/google_containers/etcd@sha256:dd75ec974b0a2a6f6bb47001ba09207976e625db898d1b16735528c009cb171c
- registry.aliyuncs.com/google_containers/etcd:3.5.6-0
sizeBytes: 299475478
- names:
- registry.aliyuncs.com/google_containers/kube-apiserver@sha256:5f6f091e7b3886b9068eb354fe29b4fbd3384e821930e405e47166065e5e859b
- registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.17
sizeBytes: 130012137
- names:
- registry.aliyuncs.com/google_containers/kube-controller-manager@sha256:5495a48e110d4800f2e0dbd27dc2acb7e77cabb1340733519c349e8e04e6566f
- registry.aliyuncs.com/google_containers/kube-controller-manager:v1.23.17
sizeBytes: 119948546
- names:
- registry.aliyuncs.com/google_containers/kube-proxy@sha256:4b3d1119f5fdd8230965c758a467bdca7fdef84c05b3f45605095f67bd64b5ff
- registry.aliyuncs.com/google_containers/kube-proxy:v1.23.17
sizeBytes: 110840983
- names:
- flannel/flannel@sha256:c7214e3ce66191e45b8c2808c703a2a5674751e90f0f65aef0b404db0a22400c
- flannel/flannel:v0.22.2
sizeBytes: 70179967
- names:
- registry.aliyuncs.com/google_containers/kube-scheduler@sha256:eb140d570d1f3d441f49c18141a8d81fee03525e7882441cfe8823d81b634659
- registry.aliyuncs.com/google_containers/kube-scheduler:v1.23.17
sizeBytes: 51856655
- names:
- registry.aliyuncs.com/google_containers/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e
- registry.aliyuncs.com/google_containers/coredns:v1.8.6
sizeBytes: 46829283
- names:
- flannel/flannel-cni-plugin@sha256:ca6779c6ad63b77af8a00151cefc08578241197b9a6fe144b0e55484bc52b852
- flannel/flannel-cni-plugin:v1.2.0
sizeBytes: 8038625
- names:
- registry.aliyuncs.com/google_containers/pause@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db
- registry.aliyuncs.com/google_containers/pause:3.6
sizeBytes: 682696
nodeInfo:
architecture: amd64
bootID: 8e821c8e-7934-4402-ad15-461d87588c60
containerRuntimeVersion: docker://20.10.23
kernelVersion: 3.10.0-1160.el7.x86_64
kubeProxyVersion: v1.23.17
kubeletVersion: v1.23.17
machineID: 25ffa45cb6834fb0a7f70f8567485af8
operatingSystem: linux
osImage: CentOS Linux 7 (Core)
systemUUID: 564D0B5B-3F3A-3F0C-7016-A769F6FA5BE5
查看节点的 Label:
[root@k8sm1 ~]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8sm1.stonecoding.net Ready control-plane,master 24h v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sm1.stonecoding.net,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
k8sn1.stonecoding.net Ready <none> 24h v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn1.stonecoding.net,kubernetes.io/os=linux
k8sn2.stonecoding.net Ready <none> 24h v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn2.stonecoding.net,kubernetes.io/os=linux
维护
维护节点的步骤如下:
- 使用
kubectl cordon命令将节点标记为不可调度
[root@k8sm1 ~]# kubectl cordon k8sn2.stonecoding.net
node/k8sn2.stonecoding.net cordoned
- 使用
kubectl drain安全驱逐节点上面所有 Pod 到其他节点
[root@k8sm1 ~]# kubectl drain k8sn2.stonecoding.net --force --ignore-daemonsets --delete-emptydir-data
node/k8sn2.stonecoding.net already cordoned
WARNING: ignoring DaemonSet-managed Pods: ingress-nginx/ingress-nginx-controller-m5qqj, kube-flannel/kube-flannel-ds-5tf8x, kube-system/kube-proxy-t62d9
evicting pod kubernetes-dashboard/kubernetes-dashboard-fb8648fd9-t4c7k
evicting pod app/nginx-deployment-v118-69584f9576-d4cw6
evicting pod app/nginx-deployment-9456bbbf9-5w9qt
evicting pod ingress-nginx/ingress-nginx-admission-patch-gh9p5
pod/ingress-nginx-admission-patch-gh9p5 evicted
pod/nginx-deployment-9456bbbf9-5w9qt evicted
pod/kubernetes-dashboard-fb8648fd9-t4c7k evicted
pod/nginx-deployment-v118-69584f9576-d4cw6 evicted
node/k8sn2.stonecoding.net drained
- 维护完成后,使用
kubectl uncordon恢复节点可调度
[root@k8sm1 ~]# kubectl uncordon k8sn2.stonecoding.net
node/k8sn2.stonecoding.net uncordoned
Configuration
Kubernetes 中的配置类资源主要用于将应用程序的配置信息与容器镜像分离,实现配置的灵活管理和动态更新。包括:
| 资源类型 | 主要用途 | 数据形式 | 关键特点 |
|---|---|---|---|
| ConfigMap | 存储非敏感的配置数据(如配置文件、环境变量、命令行参数) | 键值对(明文) | 与容器镜像解耦,支持热更新(以卷挂载时) |
| Secret | 存储敏感信息(如密码、令牌、密钥、证书) | 键值对(Base64编码) | 提供一定程度的安全特性(如仅分发到需用的节点、内存存储) |
| Downward API | 将 Pod 或容器自身的元数据(如 IP、节点名、资源限制)暴露给容器内运行的程序 | 键值对 | 让应用程序无需通过 Kubernetes API 即可了解自身运行环境的信息 |
ConfigMap
ConfigMap 是一种用于存储非敏感的配置数据的 Kubernetes 资源对象。它可以用来存储配置文件、命令行参数、环境变量等信息,通过将配置和应用程序代码分离,使得应用程序的配置管理更加灵活和可维护。在应用程序需要更新配置时,无需修改镜像或重新部署 Pod,只需更新 ConfigMap 并进行简单的刷新操作,就能让应用程序使用新的配置。
核心特性:
- 数据形式:以键值对(Key-Value)存储配置,值可以是单行字符串或多行配置文件(如 JSON、XML、Properties 等)。
- 非敏感数据:适用于存储配置参数、环境变量、命令行参数等,不适合存储密码、令牌等敏感信息(敏感信息用 Secret)。
- 动态更新:更新 ConfigMap 后,挂载它的 Pod 可通过重启或 Reload 配置感知变化(无需重新构建镜像)。
- 命名空间隔离:ConfigMap 属于命名空间级资源,仅能被同一命名空间的 Pod 访问。
创建
- 从字面量(键值对)创建
直接在命令行使用 --from-literal 指定键为 app.conf,值为 "max_connections=100",键为 log.level,值为 "info"。
[root@k8sm1 ~]# kubectl create configmap my-config1 --from-literal=app.conf="max_connections=100" --from-literal=log.level="info"
configmap/my-config1 created
[root@k8sm1 ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 22d
my-config1 2 114s
[root@k8sm1 ~]# kubectl describe cm my-config1
Name: my-config1
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
app.conf:
----
max_connections=100
log.level:
----
info
BinaryData
====
Events: <none>
[root@k8sm1 ~]# kubectl get cm my-config1 -o yaml
apiVersion: v1
data:
app.conf: max_connections=100
log.level: info
kind: ConfigMap
metadata:
creationTimestamp: "2025-09-11T03:06:54Z"
name: my-config1
namespace: default
resourceVersion: "199539"
uid: 028be596-72d0-483b-97a9-8ea2a9a925b8
- 从文件创建
从配置文件创建,默认键为文件名,值为文件内容。
[root@k8sm1 ~]# echo "max_connections=100" > app.conf
[root@k8sm1 ~]# kubectl create configmap my-config2 --from-file=app.conf
configmap/my-config2 created
[root@k8sm1 ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 22d
my-config1 2 9m21s
my-config2 1 69s
[root@k8sm1 ~]# kubectl describe cm my-config2
Name: my-config2
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
app.conf:
----
max_connections=100
BinaryData
====
Events: <none>
[root@k8sm1 ~]# kubectl get cm my-config2 -o yaml
apiVersion: v1
data:
app.conf: |
max_connections=100
kind: ConfigMap
metadata:
creationTimestamp: "2025-09-11T03:15:06Z"
name: my-config2
namespace: default
resourceVersion: "200174"
uid: f28d9d81-32e5-415a-98cb-acdc2828d97f
还可以自定义键名,这里指定键为 custom-key,值为 app.conf 的内容:
[root@k8sm1 ~]# kubectl create configmap my-config3 --from-file=custom-key=app.conf
configmap/my-config3 created
[root@k8sm1 ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 22d
my-config1 2 10m
my-config2 1 2m46s
my-config3 1 3s
[root@k8sm1 ~]# kubectl describe cm my-config3
Name: my-config3
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
custom-key:
----
max_connections=100
BinaryData
====
Events: <none>
- 从目录创建
目录下所有文件会被作为键值对,键为文件名,值为文件内容。
[root@k8sm1 ~]# mkdir config-dir
[root@k8sm1 ~]# echo "log.level=info" > config-dir/log.conf
[root@k8sm1 ~]# echo "timeout=30s" > config-dir/timeout.conf
[root@k8sm1 ~]# kubectl create configmap my-config4 --from-file=config-dir/
configmap/my-config4 created
[root@k8sm1 ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 22d
my-config1 2 31m
my-config2 1 22m
my-config3 1 20m
my-config4 2 12s
[root@k8sm1 ~]# kubectl describe cm my-config4
Name: my-config4
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
log.conf:
----
log.level=info
timeout.conf:
----
timeout=30s
BinaryData
====
Events: <none>
- 从 YAML 配置文件创建
ConfigMap 的资源清单配置文件:
# my-config5.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config5
namespace: default # 默认为 default 命名空间
data:
# 单行键值对
log.level: "info"
timeout: "30s"
# 多行配置(如配置文件内容)
app.conf: |
max_connections=100
enable_cache=true
server.port=8080
创建:
[root@k8sm1 ~]# vi my-config5.yaml
[root@k8sm1 ~]# kubectl apply -f my-config5.yaml
configmap/my-config5 created
[root@k8sm1 ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 23d
my-config1 2 33m
my-config2 1 25m
my-config3 1 22m
my-config4 2 2m27s
my-config5 3 4s
[root@k8sm1 ~]# kubectl describe cm my-config5
Name: my-config5
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
app.conf:
----
max_connections=100
enable_cache=true
server.port=8080
log.level:
----
info
timeout:
----
30s
BinaryData
====
Events: <none>
使用
- 作为环境变量注入
将 ConfigMap 中的键值对直接注入到 Pod 中容器的环境变量,Pod 的资源配置清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: config-demo-pod1
spec:
containers:
- name: demo-container1
image: nginx:1.14.2
env:
# 单个键值对注入
- name: LOG_LEVEL # 环境变量名
valueFrom:
configMapKeyRef:
name: my-config5 # 引用的 ConfigMap 名称
key: log.level # 引用的键
- name: TIMEOUT
valueFrom:
configMapKeyRef:
name: my-config5
key: timeout
envFrom:
# 批量注入整个 ConfigMap(环境变量名=键名,值=键值)
- configMapRef:
name: my-config4
创建 Pod:
[root@k8sm1 ~]# kubectl apply -f config-demo-pod1.yaml
pod/config-demo-pod1 created
[root@k8sm1 ~]# kubectl exec -it config-demo-pod1 -- /bin/bash
root@config-demo-pod1:/# env
log.conf=log.level=info
timeout.conf=timeout=30s
LOG_LEVEL=info
TIMEOUT=30s
- 作为命令行参数使用
在容器中通过环境变量间接引用 ConfigMap 的值作为命令行参数,Pod 的资源配置清单文件:
apiVersion: v1
kind: Pod
metadata:
name: config-demo-pod2
spec:
containers:
- name: demo-container2
image: busybox
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: my-config5
key: log.level
command: ["/bin/sh", "-c", "echo $LOG_LEVEL; sleep 3600"]
创建 Pod:
[root@k8sm1 ~]# vi config-demo-pod2.yaml
[root@k8sm1 ~]# kubectl apply -f config-demo-pod2.yaml
pod/config-demo-pod2 created
[root@k8sm1 ~]# kubectl logs config-demo-pod2
info
- 作为数据卷挂载
将 ConfigMap 挂载为容器内的文件或目录,适合配置文件场景,Pod 的资源配置清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: config-volume-pod3
spec:
containers:
- name: demo-container3
image: nginx:1.14.2
volumeMounts:
- name: config-volume # 卷名称,与下方 volumes 对应
mountPath: /etc/config # 挂载到容器内的目录
volumes:
- name: config-volume
configMap:
name: my-config5 # 引用的 ConfigMap 名称
# 可选:仅挂载部分键(默认挂载所有键)
items:
- key: app.conf # ConfigMap 中的键
path: app.conf # 挂载后的文件名(在 /etc/config 目录下)
- key: log.level
path: log/level # 挂载到 /etc/config/log/level 文件
创建 Pod:
[root@k8sm1 ~]# vi config-volume-pod3.yaml
[root@k8sm1 ~]# kubectl apply -f config-volume-pod3.yaml
pod/config-volume-pod3 created
[root@k8sm1 ~]# kubectl exec -it pod/config-volume-pod3 -- /bin/bash
root@config-volume-pod3:/# ls -l /etc/config/
total 0
lrwxrwxrwx 1 root root 15 Sep 11 07:12 app.conf -> ..data/app.conf
lrwxrwxrwx 1 root root 10 Sep 11 07:12 log -> ..data/log
root@config-volume-pod3:/# cat /etc/config/app.conf
max_connections=100
enable_cache=true
server.port=8080
root@config-volume-pod3:/# cat /etc/config/log/level
info
当 ConfigMap 作为卷挂载到容器的目录中时,目录下的文件显示为链接文件(符号链接),是 Kubernetes 为了实现配置动态更新和多源数据合并而设计的机制,具体原因如下:
- 动态更新的实现需求
Kubernetes 需要支持 ConfigMap 内容更新后,挂载到容器内的配置文件能自动同步(无需重建 Pod)。其底层实现依赖于临时目录 + 符号链接的组合:
- 当 ConfigMap 首次挂载时,Kubernetes 会在节点本地创建一个临时目录(如
/var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~configmap/<configmap-name>),并将 ConfigMap 的内容写入该目录下的文件。 - 容器内挂载的目标目录(如
/etc/config)会通过符号链接指向这个临时目录,或者该目录下的文件通过符号链接指向临时目录中的实际文件。 - 当 ConfigMap 更新时,Kubernetes 会创建一个新的临时目录存储更新后的内容,然后通过原子性替换符号链接的方式指向新目录,实现配置的无缝更新(避免文件读写过程中的不一致)。
这种机制确保了配置更新时不会出现 “部分文件已更新、部分未更新” 的中间状态,保证了数据一致性。
- 多源数据合并的需要
如果容器的挂载目录(如 /etc/config)中原本就有其他文件(来自镜像或其他卷),Kubernetes 需避免直接覆盖整个目录(否则会丢失原有文件)。此时:
- 挂载 ConfigMap 卷时,Kubernetes 会将 ConfigMap 的内容以符号链接的形式添加到目标目录中,而非替换整个目录。
- 原有文件会保留,新增的 ConfigMap 配置文件以符号链接形式存在,实现了 “原有文件 + 新配置文件” 的合并。
例如,容器镜像的 /etc/config 目录下已有 default.conf,挂载 ConfigMap 后,会新增 app.conf 符号链接,而 default.conf 仍保持原样。
- 底层存储的技术实现
Kubernetes 的 ConfigMap 卷基于 configmap 存储驱动实现,该驱动本质上是通过临时文件系统(tmpfs)或节点本地磁盘存储配置数据,并通过符号链接将这些数据 “挂载” 到容器的文件系统中。符号链接在这里起到了 “指向实际数据位置” 的桥梁作用,既隔离了节点存储与容器文件系统,又实现了数据的动态关联。
有如下 Nginx 配置文件:
[root@k8sm1 ~]# vi nginx-default.conf
server {
listen 80 default_server;
server_name example.com www.example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
基于此配置文件创建 ConfigMap:
[root@k8sm1 ~]# kubectl create configmap nginx-default-config --from-file=nginx-default.conf
configmap/nginx-default-config created
[root@k8sm1 ~]# kubectl get cm nginx-default-config -o yaml
apiVersion: v1
data:
nginx-default.conf: |
server {
listen 80 default_server;
server_name example.com www.example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
kind: ConfigMap
metadata:
creationTimestamp: "2025-09-15T06:59:49Z"
name: nginx-default-config
namespace: default
resourceVersion: "234012"
uid: 48fcdeed-71c2-4d2e-a669-c29f84e57473
在 Deployment 中挂载使用该 ConfigMap,资源配置清单文件如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: hotupdate-deploy
spec:
replicas: 2
selector:
matchLabels:
app: hotupdate-deploy
template:
metadata:
labels:
app: hotupdate-deploy
spec:
containers:
- image: nginx:1.18.0
name: nginx
volumeMounts:
- name: nginx-default-config-volume
mountPath: /etc/nginx/conf.d/
volumes:
- name: nginx-default-config-volume
configMap:
name: nginx-default-config
创建 Deployment:
[root@k8sm1 ~]# vi hotupdate-deploy.yaml
[root@k8sm1 ~]# kubectl apply -f hotupdate-deploy.yaml
deployment.apps/hotupdate-deploy created
[root@k8sm1 ~]# kubectl exec -it hotupdate-deploy-5968cc568c-mcj7x -- /bin/bash
root@hotupdate-deploy-5968cc568c-mcj7x:/# cat /etc/nginx/conf.d/nginx-default.conf
server {
listen 80 default_server;
server_name example.com www.example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
修改 ConfigMap,将端口修改为 8080:
[root@k8sm1 ~]# kubectl edit cm nginx-default-config
configmap/nginx-default-config edited
[root@k8sm1 ~]# kubectl get cm nginx-default-config -o yaml
apiVersion: v1
data:
nginx-default.conf: |
server {
listen 8080 default_server;
server_name example.com www.example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
kind: ConfigMap
metadata:
creationTimestamp: "2025-09-15T06:59:49Z"
name: nginx-default-config
namespace: default
resourceVersion: "238259"
uid: 48fcdeed-71c2-4d2e-a669-c29f84e57473
过一会查看容器中的配置,可以看到已经更新:
[root@k8sm1 ~]# kubectl exec -it hotupdate-deploy-5968cc568c-mcj7x -- /bin/bash
root@hotupdate-deploy-5968cc568c-mcj7x:/# cat /etc/nginx/conf.d/nginx-default.conf
server {
listen 8080 default_server;
server_name example.com www.example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
但是 Nginx 需要重载才能使用新的配置,此时可以使用以下命令让 Deployment 的 Pod 进行滚动更新:
[root@k8sm1 ~]# kubectl patch deployment hotupdate-deploy --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "2.0"}}}}}'
deployment.apps/hotupdate-deploy patched
更新
可以使用以下命令更新 ConfigMap:
[root@k8sm1 ~]# kubectl edit cm my-config1
也可以通过修改 ConfigMap 的资源清单配置文件,然后再应用:
[root@k8sm1 ~]# vi my-config5.yaml
[root@k8sm1 ~]# kubectl apply -f my-config5.yaml
生效
- 环境变量注入:更新后不会自动生效,需重启 Pod 才能读取新值。
- 数据卷挂载:更新后约 1 分钟内会自动同步到挂载目录(通过
kubelet定期检查),但应用需支持热加载配置(如 Nginx 的nginx -s reload)才能生效,否则仍需重启 Pod。
注意
- 配置大小限制:单个 ConfigMap 数据总量不超过 1 MB(避免过大影响 API Server 性能)。
- 依赖检查:Pod 引用不存在的 ConfigMap 时,Pod 会处于
Pending状态,直到 ConfigMap 创建。 - 只读性:挂载的 ConfigMap 文件默认只读,避免容器内误修改(如需可写,需额外配置
readOnly: false,但不推荐)。 - 命名空间隔离:跨命名空间引用 ConfigMap 需通过其他方式(如挂载外部存储),直接引用不支持。
- 在 Kubernetes 1.19 版本引入了 ConfigMap 的
immutable字段,当设置为true时,表示这个 ConfigMap 一旦创建,其内容就不能被修改 。如果尝试使用kubectl edit、kubectl apply等常规方式修改该 ConfigMap,操作将会失败。而当设置为false或者不设置(默认值)时,ConfigMap 可以像以前一样进行常规的修改操作。
Secret
Secret 是 Kubernetes 中用于存储和管理敏感信息的 API 资源,例如如密码、令牌、证书、SSH 密钥等,核心作用是避免将敏感数据直接硬编码到 Pod 定义、容器镜像或配置文件中,从而降低信息泄露风险。
核心特性
- 敏感数据存储:专门用于处理敏感信息,与 ConfigMap(存储非敏感配置)形成互补。
- 基础编码保护:数据默认使用 Base64 编码(非加密),可通过 Kubernetes 加密配置实现静态加密(存储到 Etcd 时加密)。
- 命名空间隔离:属于命名空间级资源,仅同一命名空间的 Pod 可访问。
- 使用方式灵活:支持通过环境变量注入、数据卷挂载或直接被 API 调用(如镜像拉取密钥)。
- 大小限制:单个 Secret 数据总量不超过 1 MB(与 ConfigMap 一致)。
Kubernetes 定义了多种内置 Secret 类型,用于标准化常见敏感数据的格式:
| 类型 | 用途 | 示例场景 |
|---|---|---|
Opaque(默认) | 存储任意键值对(自定义敏感数据) | 数据库密码、API 令牌 |
kubernetes.io/service-account-token | 服务账户令牌(自动创建) | Pod 访问 API Server 的凭证 |
kubernetes.io/dockercfg | Docker 仓库认证信息(dockercfg 格式) | 私有镜像仓库拉取凭证 |
kubernetes.io/dockerconfigjson | Docker 仓库认证信息(config.json 格式) | 同上(推荐,更通用) |
kubernetes.io/basic-auth | 基础认证(用户名 + 密码) | 访问需要 Basic Auth 的服务 |
kubernetes.io/ssh-auth | SSH 认证信息 | 访问 SSH 服务器的密钥 |
kubernetes.io/tls | TLS 证书和私钥 | Ingress 加密,服务间 TLS 通信 |
bootstrap.kubernetes.io/token | 身份认证和签名 | 集群引导和新节点加入时的身份认证 |
创建
- 从字面量(键值对)创建(
Opaque类型)
在命令行创建,键为 db-password,值为 secret123,会自动 Base64 编码:
[root@k8sm1 ~]# kubectl create secret generic my-secret1 --from-literal=db-password="secret123" --from-literal=api-token="abcdef123456"
secret/my-secret1 created
[root@k8sm1 ~]# kubectl get secret
NAME TYPE DATA AGE
default-token-4hsbp kubernetes.io/service-account-token 3 23d
my-secret1 Opaque 2 17s
[root@k8sm1 ~]# kubectl describe secret my-secret1
Name: my-secret1
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
api-token: 12 bytes
db-password: 9 bytes
[root@k8sm1 ~]# kubectl get secret my-secret1 -o yaml
apiVersion: v1
data:
api-token: YWJjZGVmMTIzNDU2
db-password: c2VjcmV0MTIz
kind: Secret
metadata:
creationTimestamp: "2025-09-11T08:11:01Z"
name: my-secret1
namespace: default
resourceVersion: "223181"
uid: e9de8ffd-7a06-4dc2-b898-e12e6531b01d
type: Opaque
- 从文件创建(
Opaque类型)
从文件读取敏感数据,键为文件名,值为文件内容:
[root@k8sm1 ~]# echo "secret123" > db-password.txt
[root@k8sm1 ~]# kubectl create secret generic my-secret2 --from-file=db-password=db-password.txt
secret/my-secret2 created
[root@k8sm1 ~]# kubectl describe secret my-secret2
Name: my-secret2
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
db-password: 10 bytes
从目录创建,目录下所有文件作为键值对:
[root@k8sm1 ~]# mkdir secret-dir
[root@k8sm1 ~]# echo "abcdef123456" > secret-dir/api-token.txt
[root@k8sm1 ~]# kubectl create secret generic my-secret3 --from-file=secret-dir/
secret/my-secret3 created
[root@k8sm1 ~]# kubectl describe secret my-secret3
Name: my-secret3
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
api-token.txt: 13 bytes
- 创建 Docker 镜像仓库密钥(
dockerconfigjson类型)
用于 Pod 拉取私有镜像:
[root@k8sm1 ~]# kubectl create secret docker-registry my-registry-secret --docker-server=harbor.stonecoding.net --docker-username=myuser --docker-password=mypass
secret/my-registry-secret created
[root@k8sm1 ~]# kubectl describe secret my-registry-secret
Name: my-registry-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: kubernetes.io/dockerconfigjson
Data
====
.dockerconfigjson: 108 bytes
- 创建 TLS 证书密钥(
tls类型)
用于存储 TLS 证书和私钥,假设已有 tls.crt(证书)和 tls.key(私钥)文件:
[root@k8sm1 ~]# openssl genrsa -out private.key 2048
[root@k8sm1 ~]# openssl req -new -key private.key -out request.csr
[root@k8sm1 ~]# openssl x509 -req -days 36500 -in request.csr -signkey private.key -out certificate.crt
[root@k8sm1 ~]# cp private.key tls.key
[root@k8sm1 ~]# cp certificate.crt tls.crt
[root@k8sm1 ~]# kubectl create secret tls my-tls-secret --cert=tls.crt --key=tls.key
secret/my-tls-secret created
[root@k8sm1 ~]# kubectl describe secret my-tls-secret
Name: my-tls-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: kubernetes.io/tls
Data
====
tls.crt: 1281 bytes
tls.key: 1679 bytes
- 从 YAML 配置文件创建
需手动对敏感数据进行 Base64 编码(注意:Base64 编码可逆,仅用于格式转换,非加密)。
先对数据进行 Base64 编码:
[root@k8sm1 ~]# echo -n "secret123" | base64
c2VjcmV0MTIz
[root@k8sm1 ~]# echo -n "abcdef123456" | base64
YWJjZGVmMTIzNDU2
Secret 的资源清单配置文件:
# secret.yaml(Opaque 类型)
apiVersion: v1
kind: Secret
metadata:
name: my-secret4
namespace: default
type: Opaque # 类型标识
data:
db-password: c2VjcmV0MTIz # Base64 编码后的值
api-token: YWJjZGVmMTIzNDU2
创建:
[root@k8sm1 ~]# vi my-secret4.yaml
[root@k8sm1 ~]# kubectl apply -f my-secret4.yaml
secret/my-secret4 created
[root@k8sm1 ~]# kubectl describe secret my-secret4
Name: my-secret4
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
api-token: 12 bytes
db-password: 9 bytes
使用
- 作为环境变量注入
将 Secret 中的敏感数据注入容器环境变量,会自动解码(适合短期使用的敏感信息),Pod 的资源配置清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: secret-demo-pod1
spec:
containers:
- name: secret-demo-container1
image: nginx:1.14.2
env:
# 单个键值对注入
- name: DB_PASSWORD # 环境变量名
valueFrom:
secretKeyRef:
name: my-secret1 # 引用的 Secret 名称
key: db-password # 引用的键
optional: false # 若 Secret 不存在,Pod 启动失败(默认)
envFrom:
# 批量注入整个 Secret(环境变量名=键名,值=解码后的值)
- secretRef:
name: my-secret1
创建 Pod:
[root@k8sm1 ~]# kubectl apply -f secret-demo-pod1.yaml
pod/secret-demo-pod1 created
[root@k8sm1 ~]# kubectl exec -it secret-demo-pod1 -- /bin/bash
root@secret-demo-pod1:/# env
db-password=secret123
api-token=abcdef123456
- 作为数据卷挂载
将 Secret 挂载为容器内的文件(适合证书、密钥文件等场景),Pod 的资源配置清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: secret-volume-pod1
spec:
containers:
- name: secret-volume-container1
image: nginx:1.14.2
volumeMounts:
- name: secret-volume # 卷名称,与下方 volumes 对应
mountPath: /etc/secrets # 挂载到容器内的目录
readOnly: true # 推荐设为只读,防止容器内误修改
volumes:
- name: secret-volume
secret:
secretName: my-secret1 # 引用的 Secret 名称
# 可选:仅挂载部分键
items:
- key: db-password
path: db/password # 挂载到 /etc/secrets/db/password 文件
- key: api-token
path: api/token
创建 Pod:
[root@k8sm1 ~]# vi secret-volume-pod1.yaml
[root@k8sm1 ~]# kubectl apply -f secret-volume-pod1.yaml
pod/secret-volume-pod1 created
[root@k8sm1 ~]# kubectl exec -it secret-volume-pod1 -- /bin/bash
root@secret-volume-pod1:/# ls -l /etc/secrets/
total 0
lrwxrwxrwx 1 root root 10 Sep 11 08:47 api -> ..data/api
lrwxrwxrwx 1 root root 9 Sep 11 08:47 db -> ..data/db
root@secret-volume-pod1:/# cat /etc/secrets/db/password
secret123
root@secret-volume-pod1:/# cat /etc/secrets/api/token
abcdef123456
- 作为镜像拉取密钥
用于 Pod 拉取私有仓库的镜像,Pod 的资源配置清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: private-image-pod1
spec:
containers:
- name: private-image-container1
image: harbor.stonecoding.net/myapp:v1 # 私有镜像
imagePullSecrets:
- name: my-registry-secret # 引用 Docker 仓库密钥
更新
可以使用以下命令更新 Secret:
[root@k8sm1 ~]# kubectl edit secret my-secret1
也可以通过修改 Secret 的资源清单配置文件,然后再应用:
[root@k8sm1 ~]# vi my-secret4.yaml
[root@k8sm1 ~]# kubectl apply -f my-secret4.yaml
生效
- 环境变量注入:更新后不会自动生效,需重启 Pod 才能读取新值。
- 数据卷挂载:更新后约 1 分钟内自动同步到挂载目录(
kubelet定期检查),应用需支持热加载(如重新读取证书文件)才能生效,否则需重启 Pod。
注意
- 启用静态加密:配置 Kubernetes 对 Etcd 中的 Secret 数据进行加密存储(通过
encryption-config.yaml),防止 Etcd 数据泄露导致敏感信息暴露。 - 限制访问权限:通过 RBAC 权限控制,仅允许必要的 ServiceAccount 访问 Secret。
- 避免明文操作:不在命令行或 YAML 文件中直接写明文(
kubectl create secret会自动编码,比手动编码更安全)。 - 定期轮换:敏感信息(如令牌、密码)应定期更新,并通过滚动更新 Pod 使新值生效。
Downward API
在 Kubernetes 中,Downward API 并非独立的资源对象,而是一种将 Pod 自身或容器的元数据、资源信息注入到容器内部的机制。它允许容器内的应用程序感知自身的运行环境(如 Pod 名称、命名空间、资源限制等),而无需直接调用 Kubernetes API 服务器,从而简化了应用与集群环境的交互。
核心作用:
- 环境自省:让容器内的应用程序获取自身所在 Pod 的元数据(如名称、IP、标签)和资源配置(如 CPU / 内存请求与限制)。
- 配置简化:避免在配置文件中硬编码环境相关信息(如 Pod 名称),而是通过动态注入的方式获取,增强配置的灵活性。
- 低耦合:无需在应用中集成 Kubernetes 客户端库,减少对集群 API 的依赖,降低应用复杂度。
Downward API 支持注入两类信息,且均为静态信息(创建 Pod 时确定,运行中不会动态更新):
Pod 元数据:
metadata.name:Pod 名称metadata.namespace:Pod 所在命名空间metadata.uid:Pod 的唯一标识符(UID)metadata.labels:Pod 的标签(可指定单个标签或全部标签)metadata.annotations:Pod 的注解(可指定单个注解或全部注解)
容器资源配置
spec.containers[*].resources.requests.cpu:容器的 CPU 请求值spec.containers[*].resources.requests.memory:容器的内存请求值spec.containers[*].resources.limits.cpu:容器的 CPU 限制值spec.containers[*].resources.limits.memory:容器的内存限制值
Downward API 支持通过两种方式将信息注入容器:环境变量或数据卷挂载。
通过环境变量注入
将信息作为环境变量注入容器,适合单个值的获取(如 Pod 名称、命名空间)。
使用以下资源配置清单文件创建 Pod:
apiVersion: v1
kind: Pod
metadata:
name: downward-api-env-demo
labels:
app: demo
annotations:
version: "v1"
owner: "dev-team"
spec:
containers:
- name: demo-container
image: busybox
command: ["/bin/sh", "-c", "env | grep POD_"] # 打印包含 POD_ 的环境变量
env:
# 获取 Pod 名称
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# 获取 Pod 命名空间
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
# 获取 Pod UID
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
# 获取单个标签(app=demo)
- name: POD_LABEL_APP
valueFrom:
fieldRef:
fieldPath: metadata.labels['app']
# 获取单个注解(version=v1)
- name: POD_ANNOTATION_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.annotations['version']
# 获取 CPU 请求值
- name: CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: demo-container # 目标容器名称(当前容器)
resource: requests.cpu
# 获取内存限制值
- name: MEMORY_LIMIT
valueFrom:
resourceFieldRef:
containerName: demo-container
resource: limits.memory
创建 Pod:
[root@k8sm1 ~]# vi downward-api-env-demo.yaml
[root@k8sm1 ~]# kubectl apply -f downward-api-env-demo.yaml
pod/downward-api-env-demo created
[root@k8sm1 ~]# kubectl logs downward-api-env-demo
POD_ANNOTATION_VERSION=v1
POD_LABEL_APP=demo
POD_NAME=downward-api-env-demo
POD_NAMESPACE=default
POD_UID=9fa854b1-f67b-42c9-8cd5-3690aaff6d79
通过数据卷挂载
将信息以文件形式挂载到容器内,适合批量获取标签、注解或资源信息(如多个标签、全部注解),可以热更新。
使用以下资源配置清单文件创建 Pod:
apiVersion: v1
kind: Pod
metadata:
name: downward-api-volume-demo
labels:
app: demo
env: test
annotations:
version: "v1"
owner: "dev-team"
spec:
containers:
- name: demo-container
image: busybox
command: ["/bin/sh", "-c", "cat /etc/podinfo/*; sleep 3600"] # 打印挂载的文件内容
volumeMounts:
- name: podinfo-volume
mountPath: /etc/podinfo # 挂载目录
volumes:
- name: podinfo-volume
downwardAPI:
items:
# 挂载 Pod 名称到 pod_name 文件
- path: "pod_name"
fieldRef:
fieldPath: metadata.name
# 挂载全部标签到 labels 文件
- path: "labels"
fieldRef:
fieldPath: metadata.labels
# 挂载全部注解到 annotations 文件
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
# 挂载 CPU 限制值到 cpu_limit 文件
- path: "cpu_limit"
resourceFieldRef:
containerName: demo-container
resource: limits.cpu
创建 Pod:
[root@k8sm1 ~]# vi downward-api-volume-demo.yaml
[root@k8sm1 ~]# kubectl apply -f downward-api-volume-demo.yaml
pod/downward-api-volume-demo created
[root@k8sm1 ~]# kubectl exec -it downward-api-volume-demo -- /bin/sh
/ # ls -l /etc/podinfo/
total 0
lrwxrwxrwx 1 root root 18 Sep 18 01:02 annotations -> ..data/annotations
lrwxrwxrwx 1 root root 16 Sep 18 01:02 cpu_limit -> ..data/cpu_limit
lrwxrwxrwx 1 root root 13 Sep 18 01:02 labels -> ..data/labels
lrwxrwxrwx 1 root root 15 Sep 18 01:02 pod_name -> ..data/pod_name
/ # cat /etc/podinfo/labels
app="demo"
env="test"
# cat /etc/podinfo/cpu_limit
2
/ # cat /etc/podinfo/pod_name
downward-api-volume-demo
Storage
Kubernetes 中的存储系统用于解决容器(Pod)的数据持久化问题(容器重启后数据不丢失),并支持不同场景下的数据共享需求。
emptyDir
在 Kubernetes 中,emptyDir 是一种简单的 Volume 类型,主要用于在 Pod 生命周期内提供临时存储空间,适用于 Pod 内容器间共享数据或存储临时文件。
核心特性:
- 生命周期与 Pod 绑定:
emptyDir随着 Pod 的创建而自动生成,随着 Pod 的删除而被清理(数据会丢失),与容器的生命周期无关(容器重启后数据仍保留)。 - 初始为空:创建时目录为空,Pod 中的容器可以向其中写入数据。
- Pod 内共享:同一 Pod 中的所有容器都可以挂载同一个
emptyDir,实现容器间的数据共享。 - 存储位置灵活:默认存储在节点的本地磁盘(由
kubelet管理的临时目录),也可配置为使用节点内存(tmpfs),性能更高但数据易失。
主要用途:
- 容器间数据共享:例如,前端容器生成的临时文件需要后端容器处理,可通过
emptyDir传递。 - 临时缓存空间:应用运行过程中需要临时存储缓存数据(如计算中间结果)。
- 应急存储空间:当容器磁盘空间不足时,可临时使用
emptyDir缓解(需注意节点磁盘容量)。
最佳实践:
- 适合临时数据:仅用于存储无需长期保留的数据(如缓存、临时日志、容器间临时传递的文件)。
- 避免存储大量数据:
emptyDir依赖节点存储,过量使用可能耗尽节点磁盘或内存。 - 内存存储谨慎使用:仅用于对性能要求极高且可容忍数据丢失的场景(如临时计算缓存)。
- 配合健康检查:若应用依赖
emptyDir中的数据,需确保容器启动顺序(如通过initContainer初始化数据)。
使用以下资源配置清单文件创建 Pod,默认存储在节点的本地磁盘上:
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo-pod
spec:
containers:
- name: container-1
image: busybox
command: ["/bin/sh", "-c", "echo 'hello from container-1' > /shared/data.txt; sleep 3600"]
volumeMounts:
- name: shared-volume # 挂载名为 shared-volume 的 emptyDir
mountPath: /shared # 容器内的挂载路径
- name: container-2
image: busybox
command: ["/bin/sh", "-c", "cat /shared/data.txt; sleep 3600"] # 读取 container-1 写入的文件
volumeMounts:
- name: shared-volume # 与 container-1 共享同一个 emptyDir
mountPath: /shared # 可使用不同的挂载路径,但指向同一存储
volumes:
- name: shared-volume
emptyDir: {} # 定义 emptyDir 类型的 Volume
创建 Pod:
[root@k8sm1 ~]# vi emptydir-demo-pod.yaml
[root@k8sm1 ~]# kubectl apply -f emptydir-demo-pod.yaml
pod/emptydir-demo-pod created
[root@k8sm1 ~]# kubectl logs emptydir-demo-pod container-2
hello from container-1
上述示例中,container-1 向 /shared/data.txt 写入数据,container-2 可直接读取该文件,实现 Pod 内的数据共享。
默认路径为节点的 /var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~empty-dir/<volume-name>:
[root@k8sn1 ~]# cat /var/lib/kubelet/pods/567b03d9-ba98-4635-a7bd-8c72b4c10974/volumes/kubernetes.io~empty-dir/shared-volume/data.txt
hello from container-1
使用以下资源配置清单文件创建 Pod,存储在节点内存:
apiVersion: v1
kind: Pod
metadata:
name: emptydir-mem-pod
namespace: default
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80
resources:
limits:
cpu: "1"
memory: 1024Mi
requests:
cpu: "1"
memory: 1024Mi
volumeMounts:
- name: mem-volume
mountPath: /data
volumes:
- name: mem-volume
emptyDir:
medium: Memory # 指定存储介质为内存(tmpfs),不在磁盘上持久化
sizeLimit: 500Mi # 可选,限制最大使用内存(默认无限制)
创建 Pod:
[root@k8sm1 ~]# vi emptydir-mem-pod.yaml
[root@k8sm1 ~]# kubectl apply -f emptydir-mem-pod.yaml
pod/emptydir-mem-pod created
hostPath
在 Kubernetes 中,hostPath 是一种 Volume 类型,它允许将节点上的文件或目录挂载到 Pod 中,从而使 Pod 能够访问节点的本地存储资源。
核心特性
- 直接访问节点存储:
hostPath提供了一种直接访问节点文件系统的方式,使得 Pod 可以使用节点上已有的文件或目录,比如日志目录、配置文件目录等。 - 生命周期与节点关联:其生命周期与所在节点相关联,只要节点存在,挂载的
hostPath数据就存在(除非被手动删除)。不过,当 Pod 被调度到其他节点时,无法访问原节点hostPath挂载的数据。 - 灵活性高:可以挂载文件或目录,并且能指定不同的访问模式和路径类型,以满足不同的使用场景。
- 潜在风险:由于依赖特定节点的文件系统,可能导致 Pod 的调度受限,只能被调度到具有相应
hostPath资源的节点上。同时,不同节点上的hostPath数据可能不一致,会给应用带来不确定性。
主要用途:
- 访问节点系统资源:例如,Pod 中运行的监控代理需要访问节点的
/var/log目录来收集系统日志,就可以通过hostPath将该目录挂载到 Pod 内。 - 共享配置文件:当多个 Pod 需要共享节点上的配置文件时,可以使用
hostPath进行挂载。比如,多个 Pod 需要使用相同的证书文件,将存放证书的目录通过hostPath挂载到各个 Pod 中。 - 开发测试场景:在开发和测试环境中,方便将本地节点的文件或目录挂载到 Pod 中,用于快速验证应用程序对特定文件或目录的访问和操作。
注意事项:
- 调度限制:使用
hostPath会使 Pod 与特定节点的文件系统绑定,可能导致 Pod 只能调度到具有相应hostPath资源的节点上。为解决这个问题,可以结合节点选择器(nodeSelector)、节点亲和性(nodeAffinity)等调度策略,确保 Pod 被调度到合适的节点。 - 数据一致性:不同节点上的
hostPath数据可能不同,在设计应用程序时需要考虑这种差异。例如,在分布式应用中,要避免因不同节点hostPath数据不一致导致的应用行为异常。 - 权限管理:需要确保 Pod 内的容器对挂载的
hostPath具有适当的读写权限。可以通过设置容器的安全上下文(securityContext)来调整权限。 - 升级和维护:当节点进行升级、重启或维护时,
hostPath挂载的数据可能会受到影响。因此,在进行相关操作前,需要提前做好数据备份和迁移工作。
使用以下资源配置清单文件创建 Pod,Volume 类型为 hostPath:
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo-pod
spec:
containers:
- name: hostpath-demo-container
image: busybox
command: ["/bin/sh", "-c", "while true; do echo $(date) >> /data/hostpath.log; sleep 100; done"]
volumeMounts:
- name: hostpath-volume
mountPath: /data # 容器内的挂载路径
volumes:
- name: hostpath-volume
hostPath:
path: /tmp/data # 节点上的文件或目录路径
type: DirectoryOrCreate # 路径类型
其中:
path指定了节点上要挂载的文件或目录路径,这里是/tmp/data。type定义了路径类型,包括:DirectoryOrCreate:如果路径不存在,将创建一个空目录。Directory:路径必须是一个已存在的目录。如果路径不存在,Pod 将无法启动。FileOrCreate:如果路径不存在,将创建一个空文件。File:路径必须是一个已存在的文件。如果路径不存在,Pod 将无法启动。Socket:路径必须是一个已存在的 Unix 套接字。如果路径不存在,Pod 将无法启动。CharDevice:路径必须是一个已存在的字符设备。如果路径不存在,Pod 将无法启动。BlockDevice:路径必须是一个已存在的块设备。如果路径不存在,Pod 将无法启动。
创建 Pod:
[root@k8sm1 ~]# vi hostpath-demo-pod.yaml
[root@k8sm1 ~]# kubectl apply -f hostpath-demo-pod.yaml
pod/hostpath-demo-pod created
[root@k8sm1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
emptydir-demo-pod 2/2 Running 0 47m 10.244.1.130 k8sn1.stonecoding.net <none> <none>
在节点 k8sn1 上查看:
[root@k8sn1 ~]# cat /tmp/data/hostpath.log
Thu Sep 18 07:11:45 UTC 2025
Thu Sep 18 07:13:26 UTC 2025
PersistentVolume
在 Kubernetes 中,PersistentVolume(PV,持久卷)是用于管理持久化存储的关键资源,它代表着集群中的一块存储资源,解耦了存储资源的供应和使用,让应用程序与底层存储细节分离。
PersistentVolume 是由集群管理员创建和管理的集群级资源,它独立于 Pod 的生命周期存在,就像是集群中的一块虚拟磁盘或者共享存储区域,为用户提供了持久化存储的能力。用户可以通过 PersistentVolumeClaim(PVC)来申请使用这些存储资源,而无需关心底层存储的具体实现方式。
主要特性:
- 生命周期独立:PV 的生命周期不依赖于任何 Pod,即使使用它的 Pod 被删除,PV 本身依然存在,除非被手动删除或者根据回收策略进行处理。
- 存储抽象:对底层存储进行抽象,支持多种存储类型,如本地磁盘(
local)、网络文件系统(nfs)、云存储(如 AWS EBS、GCP Persistent Disk 等)、分布式存储(如 CephFS、GlusterFS 等),用户使用 PV 时不需要了解具体存储后端的细节。 - 可配置性:具有多种可配置属性,如容量(
capacity)、访问模式(accessModes)、存储类型(storageClassName)、回收策略(persistentVolumeReclaimPolicy)、挂载选项(mountOptions)等,以满足不同应用场景对存储的需求。
关键属性:
- 容量(
capacity):定义了 PV 的存储大小,例如storage: 10Gi表示该 PV 的容量为 10 GB,用于满足 PVC 对存储容量的申请。 - 访问模式(
accessModes):描述了 PV 的访问权限和共享能力,主要有以下几种:- ReadWriteOnce(RWO):表示该 PV 只能被单个节点以读写模式挂载,这是最常见的访问模式,适用于大多数应用场景,比如单个数据库实例的数据存储。
- ReadOnlyMany(ROX):意味着 PV 可以被多个节点以只读模式挂载,适用于共享只读数据的场景,例如多个 Web 服务器共享静态资源文件。
- ReadWriteMany(RWX):表示 PV 可以被多个节点以读写模式挂载,不过并非所有存储类型都支持该模式 ,常见于分布式文件系统,如 NFS(在配置支持的情况下),适用于多个应用实例同时读写共享数据的场景。
- 存储类型(
storageClassName):用于将 PV 与特定的 StorageClass 关联起来。如果 PV 没有指定storageClassName,则表示它是一个静态 PV;如果指定了,那么它是通过对应的 StorageClass 动态创建的,并且可以享受 StorageClass 定义的一些特性,比如动态扩容、存储供应者的特定配置等。 - 回收策略(
persistentVolumeReclaimPolicy):当 PVC 与 PV 解绑后,定义了如何处理 PV 上的数据,主要有以下几种:- Retain(保留):这是默认策略,PV 被释放后,数据会被保留,管理员需要手动清理数据或者重新配置 PV 才能再次使用。这种策略适用于需要保留历史数据,或者对数据安全性要求较高的场景。
- Delete(删除):PV 被释放后,不仅 PV 资源会被删除,其对应的底层存储资源(如 AWS EBS 卷、GCP 磁盘等)也会被删除,适用于云存储等场景,方便资源的自动清理。
- Recycle(回收,已废弃):曾经用于清除 PV 上的数据,使其可重新使用,目前已被废弃,推荐使用
Retain或Delete策略。
- 挂载选项(
mountOptions):可以指定一些额外的挂载参数,比如对于 NFS 类型的 PV,可以设置mountOptions: ["vers=4.1", "noresvport"]来配置 NFS 挂载的具体选项。
在 Master 节点安装并配置 NFS:
[root@k8sm1 ~]# yum install nfs-utils rpcbind -y
[root@k8sm1 ~]# systemctl start nfs-server
[root@k8sm1 ~]# systemctl enable nfs-server
[root@k8sm1 ~]# systemctl start rpcbind
[root@k8sm1 ~]# mkdir -p /data/{1,2,3,4,5}
[root@k8sm1 ~]# vi /etc/exports
/data/1 192.168.92.0/24(rw,sync,no_root_squash)
/data/2 192.168.92.0/24(rw,sync,no_root_squash)
/data/3 192.168.92.0/24(rw,sync,no_root_squash)
/data/4 192.168.92.0/24(rw,sync,no_root_squash)
/data/5 192.168.92.0/24(rw,sync,no_root_squash)
[root@k8sm1 ~]# exportfs -rv
exporting 192.168.92.0/24:/data/5
exporting 192.168.92.0/24:/data/4
exporting 192.168.92.0/24:/data/3
exporting 192.168.92.0/24:/data/2
exporting 192.168.92.0/24:/data/1
创建基于 NFS 的 PV 时,无需提前手动将 NFS 共享目录挂载到节点,Kubernetes 会通过 kubelet 自动完成挂载。但必须确保所有节点安装 NFS 客户端工具,否则会导致挂载失败。这种自动挂载机制既保证了数据的一致性,又降低了运维成本,是 Kubernetes 管理外部存储的标准方式。
使用以下资源配置清单文件,创建 5 个 PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/1
server: 192.168.92.140
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv2
spec:
capacity:
storage: 2Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Delete
nfs:
path: /data/2
server: 192.168.92.140
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 3Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/3
server: 192.168.92.140
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv4
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
nfs:
path: /data/4
server: 192.168.92.140
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
spec:
capacity:
storage: 5Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/5
server: 192.168.92.140
创建 PV:
[root@k8sm1 ~]# vi nfs-pv.yaml
[root@k8sm1 ~]# kubectl apply -f nfs-pv.yaml
persistentvolume/nfs-pv1 created
persistentvolume/nfs-pv2 created
persistentvolume/nfs-pv3 created
persistentvolume/nfs-pv4 created
persistentvolume/nfs-pv5 created
[root@k8sm1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Available 15s
nfs-pv2 2Gi ROX Delete Available 15s
nfs-pv3 3Gi RWX Retain Available 15s
nfs-pv4 4Gi RWO Delete Available 15s
nfs-pv5 5Gi ROX Retain Available 15s
以上 STATUS 字段反映了 PV 生命周期的不同阶段,用于标识 PV 当前的可用状态和使用情况,包括:
Pending:挂起,表示 PV 已创建,但尚未满足被 PVC 绑定的条件。Bound:已绑定,表示 PV 已成功与某个 PVC 绑定,处于被使用状态。绑定后,PV 会与 PVC 形成一对一的映射关系,其他 PVC 无法再绑定该 PV。此时 PV 会被标记为 “已使用”,直到绑定的 PVC 被删除。Released:已释放,表示绑定的 PVC 已被删除,PV 解除绑定,但尚未根据回收策略进行处理(如数据清理、重新可用等)。PV 此时不再与任何 PVC 绑定,但由于数据可能未清理,无法直接被新的 PVC 绑定。状态会根据 PV 的回收策略(persistentVolumeReclaimPolicy)转换为其他状态。Available:可用,表示 PV 已创建且未被任何 PVC 绑定,处于可被申请的状态。新创建的 PV 若配置正确且无绑定冲突,会直接进入Available状态。Released状态的 PV 经过手动清理(如Retain策略下删除数据并重置)后,也可重新变为Available。Failed:失败,表示 PV 在生命周期中发生错误(如回收策略执行失败、底层存储故障等),无法正常使用。
PV 的状态会随着生命周期的推进发生转换,典型流程如下:
创建 PV → Available(可用)→ 被 PVC 绑定 → Bound(已绑定)→ PVC 被删除 → Released(已释放)→ 根据回收策略转换:
- 若策略为
Delete→ 自动删除 PV(状态消失)。 - 若策略为
Retain→ 保持Released状态(需手动处理后重置为Available)。
PV 的 persistentVolumeReclaimPolicy(回收策略)直接影响 Released 状态后的转换逻辑:
Retain(保留):- PV 进入
Released状态后会保持该状态,数据被保留,需管理员手动清理。 - 清理完成后,需删除 PV 的
spec.claimRef字段(解除与原 PVC 的关联),才能将其重置为Available。
- PV 进入
Delete(删除):- PV 进入
Released状态后,会自动删除 PV 及关联的底层存储(如 NFS 目录、云盘),状态最终消失。
- PV 进入
PersistentistentVolumeClaim
在 Kubernetes 中,PersistentistentVolumeClaim(PVC,持久卷声明) 是用户向集群申请存储资源的 API 资源,它充当了 Pod 与 PersistentVolume(PV)之间的 “中介”,实现了存储资源使用与供应的解耦。
PVC 由用户创建,用于声明对存储资源的需求(如容量、访问模式、存储类型等)。Kubernetes 会根据 PVC 的需求,自动匹配集群中符合条件的 PV 并完成绑定,用户无需关心底层存储的具体实现(如 NFS、云存储等)。
PVC 的生命周期与 Pod 相关联(但独立于单个 Pod),当 PVC 被删除后,其绑定的 PV 会根据回收策略进行处理(保留、删除等)。
PVC 的配置主要包含以下核心字段,用于描述存储需求:
存储容量(
resources.requests.storage):声明需要的存储大小(如10Gi),Kubernetes 只会匹配容量大于或等于该值的 PV。访问模式(
accessModes):指定对存储的访问权限,需与目标 PV 的访问模式兼容,支持以下类型(与 PV 一致):ReadWriteOnce(RWO):仅允许单个节点以读写模式挂载。ReadOnlyMany(ROX):允许多个节点以只读模式挂载。ReadWriteMany(RWX):允许多个节点以读写模式挂载(需存储后端支持)。
存储类(
storageClassName):指定关联的StorageClass名称,用于匹配特定类型的 PV:若指定
storageClassName: "my-sc",则仅匹配具有相同StorageClass的 PV。若不指定(或设为
""),则仅匹配未关联任何StorageClass的 PV(静态 PV)。若集群配置了默认
StorageClass,则不指定时会自动使用默认类动态创建 PV。
选择器(
selector):通过标签选择器(matchLabels或matchExpressions)精确匹配具有特定标签的 PV,进一步缩小匹配范围。
Kubernetes 会按照以下规则为 PVC 匹配最合适的 PV:
- 容量匹配:PV 的容量 ≥ PVC 申请的容量。
- 访问模式匹配:PV 的访问模式必须包含 PVC 声明的所有访问模式。
StorageClass匹配:PV 与 PVC 的storageClassName必须一致(或均未设置)。- 标签匹配:若 PVC 定义了
selector,PV 必须包含匹配的标签。
绑定成功后,PVC 与 PV 形成一对一映射,直到 PVC 被删除。
PVC 的生命周期:
- 创建(Pending):PVC 被创建后,若未找到匹配的 PV,状态为
Pending。 - 绑定(Bound):找到匹配的 PV 并完成绑定后,状态变为
Bound,此时可被 Pod 引用。 - 使用中(InUse):当 Pod 通过
volumeMounts引用该 PVC 时,PV 会标记为InUse,防止被其他 PVC 绑定。 - 释放(Released):当 PVC 被删除后,PV 状态变为
Released,数据处理方式取决于 PV 的回收策略(Retain/Delete)。
注意事项:
- 绑定不可逆:PVC 与 PV 绑定后,无法直接修改 PVC 的核心属性(如容量、访问模式),需删除 PVC 重新创建。
- 命名空间隔离:PVC 属于命名空间级资源,只能引用同一命名空间内的 PV(PV 是集群级资源,但绑定受命名空间限制)。
- 依赖节点 NFS 客户端:若使用 NFS 类型 PV,需确保所有节点安装 NFS 客户端(如
nfs-utils),否则 Pod 挂载会失败。 - 回收策略影响:PV 的回收策略(
Retain/Delete)决定了 PVC 删除后数据的保留方式,需根据业务需求选择。
使用场景:
- 数据库存储:为 MySQL、PostgreSQL 等数据库 Pod 申请持久化存储,确保数据在 Pod 重启或迁移后不丢失。
- 共享文件存储:通过
ReadWriteMany模式的 PVC,实现多个 Pod 共享静态资源(如图片、配置文件)。 - 动态存储分配:结合
StorageClass,让开发人员按需申请存储,无需管理员提前创建 PV。
使用以下 PVC 资源配置清单文件:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-demo1
spec:
accessModes:
- ReadWriteOnce # 要求 PV 支持 RWO 模式
resources:
requests:
storage: 1Gi # 申请 1GiB 存储
# 不指定 storageClassName,匹配未关联 StorageClass 的静态 PV
创建 PVC,会自动匹配到最合适的 PV:
[root@k8sm1 ~]# vi pvc-demo1.yaml
[root@k8sm1 ~]# kubectl apply -f pvc-demo1.yaml
persistentvolumeclaim/pvc-demo1 created
[root@k8sm1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-demo1 Bound nfs-pv1 1Gi RWO 37s
[root@k8sm1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Bound default/pvc-demo1 20h
在以下 Pod 的资源配置清单文件中使用 PVC:
apiVersion: v1
kind: Pod
metadata:
name: pod-using-pvc
spec:
containers:
- name: app
image: nginx:1.14.2
volumeMounts:
- name: data-volume
mountPath: /usr/share/nginx/html # 容器内挂载路径
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: pvc-demo1 # 引用前面创建的 PVC
创建 Pod:
[root@k8sm1 ~]# vi pod-using-pvc.yaml
[root@k8sm1 ~]# kubectl apply -f pod-using-pvc.yaml
pod/pod-using-pvc created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-using-pvc 1/1 Running 0 3m19s
[root@k8sm1 ~]# echo "stonecoding.net" > /data/1/index1.html
[root@k8sm1 ~]# kubectl exec -it pod-using-pvc -- /bin/bash
root@pod-using-pvc:/# cat /usr/share/nginx/html/index1.html
stonecoding.net
StorageClass
在 Kubernetes 中,StorageClass(存储类) 是用于动态管理持久化存储的 API 资源,它允许集群管理员定义不同类型的存储 “模板”,用户通过 PersistentVolumeClaim(PVC)可以按需自动创建 PersistentVolume(PV),无需手动预配置存储资源。
- 动态供应 PV:根据用户的 PVC 请求,自动创建匹配的 PV 和底层存储资源(如 NFS 目录、云盘、分布式存储卷等),简化存储管理流程。
- 标准化存储配置:将存储的性能、类型、回收策略等属性封装为 “类”,用户只需选择存储类即可获得预设的存储特性,无需关心底层细节。
- 支持存储特性扩展:如动态扩容、快照、加密等高级功能,通过存储插件与底层存储系统对接实现。
StorageClass 的配置包含多个关键字段,用于定义存储的供应方式和特性:
provisioner:定义用于创建和管理底层存储资源的插件(存储驱动),是 StorageClass 的核心字段。parameters:传递给供应者的配置参数,用于指定存储的具体特性(因供应者而异)。例如:
parameters:
server: 192.168.1.100 # NFS 服务器地址
path: /nfs/dynamic # 动态创建 PV 的根目录
fsType: ext4 # 文件系统类型
reclaimPolicy:定义动态创建的 PV 在 PVC 删除后的处理方式(默认Delete):Delete:自动删除 PV 及关联的底层存储资源(如删除云盘)。Retain:保留 PV 和数据,需手动清理。
allowVolumeExpansion:布尔值(默认false),指定是否允许通过修改 PVC 扩容存储容量。mountOptions:定义挂载存储时的额外参数(如 NFS 的版本、权限设置)。例如:
mountOptions:
- vers=4.1 # NFS 版本
- noresvport # NFS 连接失败时重新选择端口
volumeBindingMode:控制 PV 与 PVC 的绑定时机(默认Immediate):Immediate:PVC 创建后立即绑定到动态创建的 PV(可能导致 Pod 调度失败,若存储与节点不兼容)。WaitForFirstConsumer:延迟绑定,直到第一个使用该 PVC 的 Pod 被调度后,才根据 Pod 的调度约束(如节点亲和性)创建并绑定 PV(适合本地存储或需与节点关联的存储)。
使用流程:
- 管理员创建 StorageClass:定义供应者、参数、回收策略等。
- 用户创建 PVC:声明存储需求(容量、访问模式),并指定
storageClassName为目标存储类。 - 自动创建 PV:Kubernetes 调用 StorageClass 定义的供应者,创建匹配的 PV 和底层存储。
- 绑定与使用:PV 与 PVC 自动绑定,Pod 通过引用 PVC 使用存储。
创建一个共享目录:
[root@k8sm1 ~]# mkdir /data/dynamic
[root@k8sm1 ~]# vi /etc/exports
/data/1 192.168.92.0/24(rw,sync,no_root_squash)
/data/2 192.168.92.0/24(rw,sync,no_root_squash)
/data/3 192.168.92.0/24(rw,sync,no_root_squash)
/data/4 192.168.92.0/24(rw,sync,no_root_squash)
/data/5 192.168.92.0/24(rw,sync,no_root_squash)
/data/dynamic 192.168.92.0/24(rw,sync,no_root_squash)
[root@k8sm1 ~]# exportfs -rv
exporting 192.168.92.0/24:/data/dynamic
exporting 192.168.92.0/24:/data/5
exporting 192.168.92.0/24:/data/4
exporting 192.168.92.0/24:/data/3
exporting 192.168.92.0/24:/data/2
exporting 192.168.92.0/24:/data/1
部署 nfs-subdir-external-provisioner 插件,用于 NFS 动态存储供应,资源配置清单文件如下:
apiVersion: v1
kind: Namespace
metadata:
name: nfs-provisioner
---
apiVersion: v1
kind: Secret
metadata:
name: nfs-provisioner-secret
namespace: nfs-provisioner
stringData:
nfs-server: "192.168.92.140" # 替换为 NFS 服务器 IP
nfs-path: "/data/dynamic" # 替换为 NFS 共享的根目录
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: nfs-provisioner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-clusterrole
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["csinodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-provisioner-clusterrolebinding
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: nfs-provisioner
roleRef:
kind: ClusterRole
name: nfs-provisioner-clusterrole
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-subdir-external-provisioner
namespace: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-subdir-external-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-subdir-external-provisioner
spec:
serviceAccountName: nfs-provisioner
containers:
- name: nfs-subdir-external-provisioner
#image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
# 若无法访问 k8s.gcr.io,可替换为国内镜像:
image: registry.cn-beijing.aliyuncs.com/xngczl/nfs-subdir-external-provisione:v4.0.0
volumeMounts:
- name: nfs-volume
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner # 供应者名称,需与 StorageClass 匹配
- name: NFS_SERVER
valueFrom:
secretKeyRef:
name: nfs-provisioner-secret
key: nfs-server
- name: NFS_PATH
valueFrom:
secretKeyRef:
name: nfs-provisioner-secret
key: nfs-path
volumes:
- name: nfs-volume
nfs:
server: 192.168.92.140 # 替换为 NFS 服务器 IP(与 Secret 一致)
path: /data/dynamic # 替换为 NFS 共享根目录(与 Secret 一致)
部署:
[root@k8sm1 ~]# vi nfs-subdir-external-provisioner.yaml
[root@k8sm1 ~]# kubectl apply -f nfs-subdir-external-provisioner.yaml
namespace/nfs-provisioner unchanged
secret/nfs-provisioner-secret configured
serviceaccount/nfs-provisioner unchanged
clusterrole.rbac.authorization.k8s.io/nfs-provisioner-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/nfs-provisioner-clusterrolebinding created
deployment.apps/nfs-subdir-external-provisioner created
[root@k8sm1 ~]# kubectl get pod -n nfs-provisioner
NAME READY STATUS RESTARTS AGE
nfs-subdir-external-provisioner-7f78dfd7c9-m5lw6 1/1 Running 0 26s
[root@k8sm1 ~]# kubectl logs -f nfs-subdir-external-provisioner-7f78dfd7c9-m5lw6 -n nfs-provisioner
使用以下资源配置清单文件创建 StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-dynamic-sc # 存储类名称,PVC 需引用此名称
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 与 Deployment 中的 PROVISIONER_NAME 一致
parameters:
archiveOnDelete: "false" # PVC 删除时是否归档目录(false 表示直接删除)
reclaimPolicy: Delete # 动态 PV 的回收策略
allowVolumeExpansion: true # 允许 PVC 扩容
#mountOptions:
# - vers=4.1
# - noresvport
创建 StorageClass:
[root@k8sm1 ~]# vi nfs-dynamic-sc.yaml
[root@k8sm1 ~]# kubectl apply -f nfs-dynamic-sc.yaml
storageclass.storage.k8s.io/nfs-dynamic-sc created
[root@k8sm1 ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-dynamic-sc k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate true 26s
定义一个 PVC 测试是否能自动生成 PV:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-dynamic-sc-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-dynamic-sc # 引用上面创建的存储类
创建这个 PVC:
[root@k8sm1 ~]# vi nfs-dynamic-sc-pvc.yaml
[root@k8sm1 ~]# kubectl apply -f nfs-dynamic-sc-pvc.yaml
persistentvolumeclaim/nfs-dynamic-sc-pvc created
[root@k8sm1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-dynamic-sc-pvc Bound pvc-c0bf9a7f-8ce3-4a28-8cc5-353b0d035f96 1Gi RWX nfs-dynamic-sc 25s
[root@k8sm1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-c0bf9a7f-8ce3-4a28-8cc5-353b0d035f96 1Gi RWX Delete Bound default/nfs-dynamic-sc-pvc nfs-dynamic-sc 2m1s
[root@k8sm1 ~]# ll /data/dynamic/
total 0
drwxrwxrwx 2 root root 6 Sep 19 16:31 default-nfs-dynamic-sc-pvc-pvc-c0bf9a7f-8ce3-4a28-8cc5-353b0d035f96
若 PVC 状态变为 Bound 且对应的 PV 被创建,则说明 nfs-subdir-external-provisioner 部署成功。同时可以看到,创建 PVC 后,自动创建了 PV。
删除这个 PVC:
[root@k8sm1 ~]# kubectl delete pvc nfs-dynamic-sc-pvc
persistentvolumeclaim "nfs-dynamic-sc-pvc" deleted
[root@k8sm1 ~]# kubectl get pv
No resources found
[root@k8sm1 ~]# ll /data/dynamic/
total 0
由于配置了reclaimPolicy 为 Delete,可以看到对应的 PV 被自动删除了,同时对应的目录也被自动删除了。
StatefulSet
在 Kubernetes 中,StatefulSet 是用于管理有状态应用的工作负载 API 资源。与无状态的 Deployment 不同,StatefulSet 为 Pod 提供了稳定的网络标识、持久化存储和有序的部署、扩展 、更新和删除能力,特别适合数据库(如 MySQL、PostgreSQL)、分布式系统(如 Kafka、ZooKeeper)等需要稳定身份和数据持久化的应用。
StatefulSet 的核心设计围绕 “状态稳定性” 展开,主要特性包括:
- 稳定的网络标识
- 每个 Pod 拥有固定的名称(格式为
<statefulset-name>-<ordinal-index>,如web-0、web-1),不会随重建改变。 - 配合 Headless Service(无头服务),为每个 Pod 分配固定的 DNS 记录(格式为
<pod-name>.<service-name>.<namespace>.svc.cluster.local),确保网络访问的稳定性。
- 每个 Pod 拥有固定的名称(格式为
- 稳定的持久化存储
- 每个 Pod 关联的存储通过 PersistentVolumeClaim(PVC)模板创建,且 PVC 名称固定(格式为
<volume-claim-template-name>-<pod-name>),即使 Pod 重建,仍会挂载原 PVC,保证数据不丢失。 - 存储与 Pod 解绑后,PVC 和数据依然保留(由 PV 回收策略决定)。
- 每个 Pod 关联的存储通过 PersistentVolumeClaim(PVC)模板创建,且 PVC 名称固定(格式为
- 有序的操作流程
- 部署扩展:按序号升序创建 Pod(先
web-0,再web-1,以此类推),前一个 Pod 就绪后才创建下一个。 - 更新:默认按序号降序滚动更新(先
web-1,再web-0),支持暂停、继续或回滚。 - 删除:按序号降序删除,且默认等待前一个 Pod 完全终止后再删除下一个。
- 缩容:仅删除最高序号的 Pod(如从 3 个缩容到 2 个,只删除
web-2)。
- 部署扩展:按序号升序创建 Pod(先
注意事项:
- Headless Service 依赖:必须提前创建 Headless Service 并在
serviceName中指定,否则 StatefulSet 无法正常生成网络标识。 - 存储清理:删除 StatefulSet 时,PVC 和数据不会自动删除,需手动清理(
kubectl delete pvc <pvc-name>)。 - 序号唯一性:Pod 序号从 0 开始,且在 StatefulSet 生命周期内唯一,即使缩容后再扩容,新 Pod 会使用新序号(如缩容到 2 后扩容到 3,新 Pod 为
web-2)。 - 更新谨慎:有状态应用的更新可能涉及数据迁移(如数据库 schema 变更),建议先备份数据,再通过
partition分批更新。
一个完整的 StatefulSet 配置通常包含以下部分:
- Headless Service:用于为 StatefulSet 的 Pod 提供固定网络标识(DNS 记录),不分配集群 IP,仅负责域名解析。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
selector:
app: nginx
ports:
- port: 80
name: web
clusterIP: None # 声明为 Headless Service
- StatefulSet:定义 Pod 模板、PVC 模板、副本数、更新策略等核心参数。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
serviceName: "nginx-service" # 关联的 Headless Service 名称
replicas: 3 # 副本数
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www # 与下面的 volumeClaimTemplates 名称对应
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # PVC 模板,为每个 Pod 创建独立 PVC
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs-dynamic-sc" # 引用 StorageClass 动态创建 PV
resources:
requests:
storage: 1Gi
updateStrategy: # 更新策略
type: RollingUpdate # 滚动更新(默认),可选 OnDelete(删除 Pod 后重建)
关键配置参数:
serviceName:必选字段,指定关联的 Headless Service 名称,用于生成 Pod 的 DNS 记录。replicas:指定副本数(默认 1),StatefulSet 会确保实际运行的 Pod 数量与此一致。volumeClaimTemplates:PVC 模板列表,为每个 Pod 自动创建对应的 PVC(名称格式:模板名-pod名),实现数据持久化。若不需要持久化存储,可省略此字段。updateStrategy:定义更新策略:RollingUpdate(默认):按序号降序依次更新 Pod,支持通过partition控制更新范围(如partition: 2表示仅更新序号 ≥2 的 Pod)。OnDelete:手动删除旧 Pod 后,StatefulSet 才会创建新 Pod 替代。
podManagementPolicy:控制 Pod 创建 / 删除的顺序(默认OrderedReady):OrderedReady:严格按序操作(前一个就绪 / 终止后再处理下一个)。Parallel:并行创建 / 删除所有 Pod(适用于对顺序无要求的场景)。
创建 Service 和 StatefulSet:
[root@k8sm1 ~]# vi statefulset-web.yaml
[root@k8sm1 ~]# kubectl apply -f statefulset-web.yaml
service/nginx-service created
statefulset.apps/web created
[root@k8sm1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP None <none> 80/TCP 30s
[root@k8sm1 ~]# kubectl get sts
NAME READY AGE
web 3/3 2m41s
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2m28s
web-1 1/1 Running 0 2m25s
web-2 1/1 Running 0 2m21s
[root@k8sm1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820 1Gi RWO nfs-dynamic-sc 3m28s
www-web-1 Bound pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7 1Gi RWO nfs-dynamic-sc 3m25s
www-web-2 Bound pvc-6b2612b6-97da-481b-824e-31b7f1706dbf 1Gi RWO nfs-dynamic-sc 3m21s
[root@k8sm1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7 1Gi RWO Delete Bound default/www-web-1 nfs-dynamic-sc 3m35s
pvc-6b2612b6-97da-481b-824e-31b7f1706dbf 1Gi RWO Delete Bound default/www-web-2 nfs-dynamic-sc 3m31s
pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820 1Gi RWO Delete Bound default/www-web-0 nfs-dynamic-sc 3m38s
[root@k8sm1 ~]# ll /data/dynamic/
total 0
drwxrwxrwx 2 root root 6 Sep 22 10:28 default-www-web-0-pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820
drwxrwxrwx 2 root root 6 Sep 22 10:28 default-www-web-1-pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7
drwxrwxrwx 2 root root 6 Sep 22 10:28 default-www-web-2-pvc-6b2612b6-97da-481b-824e-31b7f1706dbf
[root@k8sm1 ~]# echo "www-web-0" > /data/dynamic/default-www-web-0-pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820/index.html
[root@k8sm1 ~]# echo "www-web-1" > /data/dynamic/default-www-web-1-pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7/index.html
[root@k8sm1 ~]# echo "www-web-2" > /data/dynamic/default-www-web-2-pvc-6b2612b6-97da-481b-824e-31b7f1706dbf/index.html
[root@k8sm1 ~]# kubectl exec -it busybox -- /bin/sh
/ # wget nginx-service.default.svc.cluster.local && cat index.html && rm index.html
Connecting to nginx-service.default.svc.cluster.local (10.244.1.153:80)
saving to 'index.html'
index.html 100% |****************************************************************************************************| 10 0:00:00 ETA
'index.html' saved
www-web-2
/ # wget nginx-service.default.svc.cluster.local && cat index.html && rm index.html
Connecting to nginx-service.default.svc.cluster.local (10.244.1.152:80)
saving to 'index.html'
index.html 100% |****************************************************************************************************| 10 0:00:00 ETA
'index.html' saved
www-web-1
/ # wget nginx-service.default.svc.cluster.local && cat index.html && rm index.html
Connecting to nginx-service.default.svc.cluster.local (10.244.1.151:80)
saving to 'index.html'
index.html 100% |****************************************************************************************************| 10 0:00:00 ETA
'index.html' saved
www-web-0
/ # wget nginx-service && cat index.html && rm index.html
Connecting to nginx-service (10.244.1.153:80)
saving to 'index.html'
index.html 100% |****************************************************************************************************| 10 0:00:00 ETA
'index.html' saved
www-web-2
可以看到自动创建了 PVC 和 PV,并可以通过无头访问访问。
更新镜像版本:
[root@k8sm1 ~]# kubectl set image statefulset/web nginx=nginx:1.18.0
statefulset.apps/web image updated
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 31m
web-1 1/1 Running 0 31m
web-2 0/1 ContainerCreating 0 1s
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 31m
web-1 0/1 ContainerCreating 0 2s
web-2 1/1 Running 0 5s
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 4s
web-2 1/1 Running 0 7s
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2s
web-1 1/1 Running 0 6s
web-2 1/1 Running 0 9s
[root@k8sm1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820 1Gi RWO nfs-dynamic-sc 32m
www-web-1 Bound pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7 1Gi RWO nfs-dynamic-sc 32m
www-web-2 Bound pvc-6b2612b6-97da-481b-824e-31b7f1706dbf 1Gi RWO nfs-dynamic-sc 31m
[root@k8sm1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7 1Gi RWO Delete Bound default/www-web-1 nfs-dynamic-sc 32m
pvc-6b2612b6-97da-481b-824e-31b7f1706dbf 1Gi RWO Delete Bound default/www-web-2 nfs-dynamic-sc 31m
pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820 1Gi RWO Delete Bound default/www-web-0 nfs-dynamic-sc 32m
可以看到 PVC 和 PV 保持不变。
删除 StatefulSet:
[root@k8sm1 ~]# kubectl delete statefulset web
statefulset.apps "web" deleted
[root@k8sm1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820 1Gi RWO nfs-dynamic-sc 33m
www-web-1 Bound pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7 1Gi RWO nfs-dynamic-sc 33m
www-web-2 Bound pvc-6b2612b6-97da-481b-824e-31b7f1706dbf 1Gi RWO nfs-dynamic-sc 33m
[root@k8sm1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3c509709-6903-4fee-95c4-d0f7c7a86ad7 1Gi RWO Delete Bound default/www-web-1 nfs-dynamic-sc 33m
pvc-6b2612b6-97da-481b-824e-31b7f1706dbf 1Gi RWO Delete Bound default/www-web-2 nfs-dynamic-sc 33m
pvc-91b867bd-cb5c-4c21-bded-4289e6b5c820 1Gi RWO Delete Bound default/www-web-0 nfs-dynamic-sc 33m
可以看到默认保留 PVC,PV 及数据,需要手动删除 PVC。
[root@k8sm1 ~]# kubectl delete pvc www-web-0 www-web-1 www-web-2
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
[root@k8sm1 ~]# kubectl get pvc
No resources found in default namespace.
[root@k8sm1 ~]# kubectl get pv
No resources found
[root@k8sm1 ~]# ll /data/dynamic/
total 0
调度器
在 Kubernetes 中,调度器(Scheduler) 是核心控制平面组件之一,负责将 Pod 合理分配到集群中的节点上,确保资源高效利用且满足 Pod 的运行需求。其核心目标是在合适的时间将合适的 Pod 调度到合适的节点,是实现集群自动化管理的关键组件。
核心功能:
- Pod 分配:根据预设策略和约束,为处于
Pending状态的 Pod 选择最佳节点。 - 资源优化:平衡集群节点的资源利用率(CPU、内存等),避免节点过载或资源浪费。
- 约束满足:确保 Pod 调度到满足其节点亲和性、资源需求、污点 / 容忍等约束的节点。
- 动态适应:当集群状态变化(如节点故障、资源波动)时,配合其他组件(如
kube-controller-manager)实现 Pod 重调度。
Kubernetes 调度器的工作流程可分为 过滤(Filtering) 和 打分(Scoring) 两个核心阶段,最终从候选节点中选择最优节点:
- 过滤阶段(Filtering)
- 目标:从集群所有节点中筛选出满足 Pod 运行条件的候选节点(称为 “可行节点”)。
- 关键策略(预选,Predicates):
- 资源匹配:节点剩余 CPU、内存等资源 ≥ Pod 请求的资源(
resources.requests)。 - 节点选择器:节点标签需匹配 Pod 的
nodeSelector或nodeAffinity规则。 - 污点与容忍:节点的污点(Taint)需被 Pod 的容忍(Toleration)匹配,否则无法调度。
- 端口冲突:避免 Pod 容器端口与节点上已使用的端口冲突(
hostPort配置)。 - 存储可用性:节点需能访问 Pod 所需的存储卷(如 PV 所在的 NFS 服务器、云盘等)。
- 运行时约束:如节点操作系统、架构(
node.kubernetes.io/os、node.kubernetes.io/arch)与 Pod 要求一致。
- 资源匹配:节点剩余 CPU、内存等资源 ≥ Pod 请求的资源(
- 结果:若过滤后无可行节点,Pod 会一直处于
Pending状态,并在事件中提示原因。
- 打分阶段(Scoring)
- 目标:从过滤后的可行节点中,按优先级排序,选择得分最高的节点。
- 关键策略(优选,Priorities):
- 资源利用率平衡:优先选择资源(CPU、内存)使用率较低的节点,避免负载集中。
- 亲和性权重:根据 Pod 的
podAffinity(Pod 间亲和性)或nodeAffinity(节点亲和性)的权重打分,权重越高得分越高。 - 污点容忍度:对带有 Pod 可容忍的污点的节点,根据污点的
effect降低打分(如NoSchedule类型污点会降低节点优先级)。 - 节点拓扑分布:根据
topologySpreadConstraints确保 Pod 均匀分布在不同拓扑域(如机房、机架),提高可用性。 - 镜像本地化:优先选择已缓存 Pod 所需镜像的节点,减少下载时间(轻量级策略)。
- 结果:得分最高的节点被选中,调度器通过
BindAPI 将 Pod 与节点绑定。
Kubernetes 提供了多种调度策略,允许精细控制 Pod 的调度位置。
| 策略类型 | 说明 | 示例/常见用途 |
|---|---|---|
| 资源请求与限制 (Resources) | Pod 可指定 requests(基本保障资源)和 limits(资源使用上限) | 保证关键应用有足够资源,防止单个 Pod 耗尽节点资源。 |
| 节点亲和性 (Node Affinity) | 基于 Node 的标签来约束 Pod 可以调度到哪些节点上,比 nodeSelector更灵活强大。 | requiredDuringSchedulingIgnoredDuringExecution(硬性要求)和 preferredDuringSchedulingIgnoredDuringExecution(软性偏好)。 |
| Pod 亲和性/反亲和性 (Pod Affinity/Anti-Affinity) | 基于已存在 Pod 的标签来约束 Pod 之间的关系。亲和性使 Pod 倾向于聚集,反亲和性使 Pod 相互远离。 | 反亲和性常用于高可用部署,确保同一应用的多个副本分散在不同故障域(如节点、可用区)。 |
| 污点与容忍度 (Taints and Tolerations) | 污点允许 Node 排斥某些 Pod,容忍度允许 Pod 调度到有特定污点的 Node 上。 | 专用节点(如 GPU 节点)、排除特定工作负载的节点。 |
| 拓扑分布约束 (Topology Spread Constraints) | 控制 Pod 在集群故障域(如区域、机架、节点)之间的分布,是更现代和推荐的方式来实现均匀分布和高可用。 | 跨可用区均匀部署 Pod,实现更好的容灾能力。 |
nodeName
在 Kubernetes 中,nodeName 是 Pod 规格(spec)中的一个字段,用于直接指定 Pod 运行在哪个节点上。它是一种简单直接的节点选择机制,允许显式地将 Pod 调度到特定节点。
主要特点:
- 直接绑定:
nodeName是最直接的节点选择方式,Kubernetes 会尝试将 Pod 调度到指定名称的节点 - 优先级高:如果设置了
nodeName,它会忽略其他调度规则(如节点亲和性、污点和容忍等) - 简单但不灵活:适合简单场景,但缺乏动态调度能力
在 Pod 的 YAML 配置中添加 nodeName 字段,指定节点:
apiVersion: v1
kind: Pod
metadata:
name: nodename-pod
spec:
nodeName: k8sn1.stonecoding.net # 显式指定节点名称
containers:
- name: nginx
image: nginx:1.14.2
创建 Pod:
[root@k8sm1 ~]# vi nodename-pod.yaml
[root@k8sm1 ~]# kubectl apply -f nodename-pod.yaml
pod/nodename-pod created
[root@k8sm1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nodename-pod 1/1 Running 0 7s 10.244.1.159 k8sn1.stonecoding.net <none> <none>
可以看到 Pod 被直接调度到 k8sn1.stonecoding.net 节点。
nodeSelector
在 Kubernetes 中,节点选择器(nodeSelector)是一种简单直接的节点选择机制,用于将 Pod 调度到具有特定标签(Label)的节点上。它通过键值对匹配节点标签,实现 Pod 与节点的 “绑定”,是控制 Pod 调度位置的基础方式。
作用:
- 简化的调度控制:通过节点标签快速筛选目标节点,让 Pod 仅能调度到符合标签条件的节点。
- 基础的节点隔离:例如将 “生产环境” 的 Pod 调度到带有
env=production标签的节点,将 “测试环境” 的 Pod 调度到env=test标签的节点。 - 硬件匹配:将需要特定硬件的 Pod(如依赖 GPU 的应用)调度到带有
gpu=true标签的节点。
工作原理:
- 节点打标签:管理员为目标节点添加特定标签(如
disk=ssd、zone=zone1)。 - Pod 配置
nodeSelector:在 Pod 定义中通过nodeSelector声明需要匹配的标签键值对。 - 调度器匹配:Kubernetes 调度器在调度时,只会将 Pod 调度到标签完全匹配
nodeSelector的节点上。
关键特性:
- 精确匹配:
nodeSelector仅支持完全匹配标签键值对(key=value),不支持模糊匹配或复杂条件(如In、NotIn等操作符)。 - 多标签 “与” 关系:当
nodeSelector包含多个键值对时,节点必须同时满足所有标签才能被选中(即 “与” 逻辑)。 - 调度严格性:若没有节点匹配
nodeSelector的标签,Pod 会一直处于Pending状态,直到有符合条件的节点出现。 - 动态标签的影响:
- 若节点标签在 Pod 调度后被修改,已运行的 Pod 不会被驱逐(
nodeSelector仅影响调度阶段)。 - 若节点标签被删除,新的 Pod 不会再调度到该节点,但已运行的 Pod 不受影响。
- 若节点标签在 Pod 调度后被修改,已运行的 Pod 不会被驱逐(
注意事项:
- 标签管理:确保节点标签的准确性和一致性,避免因标签错误导致 Pod 调度失败。
- 避免过度约束:若
nodeSelector条件过于严格(如同时匹配多个不常用标签),可能导致无节点可用,需谨慎设计标签规则。 - 标签更新:若需修改节点标签,已运行的 Pod 不会被重新调度,需手动删除 Pod 使其重新调度到符合新标签的节点。
- 与其他调度规则的配合:
nodeSelector可与节点亲和性、污点 / 容忍等机制同时使用,调度器会同时满足所有规则。
使用 kubectl label 命令为节点添加标签:
[root@k8sm1 ~]# kubectl label nodes k8sn2.stonecoding.net disk=ssd
node/k8sn2.stonecoding.net labeled
[root@k8sm1 ~]# kubectl label nodes k8sn2.stonecoding.net env=production
node/k8sn2.stonecoding.net labeled
[root@k8sm1 ~]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8sm1.stonecoding.net Ready control-plane,master 34d v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sm1.stonecoding.net,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
k8sn1.stonecoding.net Ready <none> 27d v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn1.stonecoding.net,kubernetes.io/os=linux
k8sn2.stonecoding.net Ready <none> 26m v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,env=production,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn2.stonecoding.net,kubernetes.io/os=linux
在 Pod 的 YAML 配置中添加 nodeSelector 字段,指定需要匹配的标签:
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-nodeselector
spec:
containers:
- name: nginx
image: nginx:1.14.2
nodeSelector:
disk: ssd # 仅调度到带有 disk=ssd 标签的节点
env: production # 同时匹配 env=production 标签(多标签为“与”关系)
上述配置表示:该 Pod 只会被调度到同时拥有 disk=ssd 和 env=production 标签的节点上。
创建 Pod:
[root@k8sm1 ~]# vi nginx-with-nodeselector.yaml
[root@k8sm1 ~]# kubectl apply -f nginx-with-nodeselector.yaml
pod/nginx-with-nodeselector created
[root@k8sm1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-with-nodeselector 1/1 Running 0 60s 10.244.2.2 k8sn2.stonecoding.net <none> <none>
若没有节点匹配标签,Pod 会一直处于 Pending 状态,可通过 kubectl describe pod 查看原因。
nodeAffinity
在 Kubernetes 中,节点亲和性(nodeAffinity) 是一种调度约束机制,允许 Pod 按照预设规则 “倾向于” 或 “必须” 调度到特定节点上。它通过节点标签(Label)实现 Pod 与节点的匹配,是控制 Pod 调度位置的核心手段之一,常用于将 Pod 部署到满足特定条件的节点(如配备 SSD 硬盘、特定硬件架构或属于某个可用区的节点)。
作用:
- 精准调度:根据节点属性(如硬件类型、地理位置、环境标签)将 Pod 调度到目标节点。
- 资源优化:将高负载 Pod 调度到资源充足的节点,或把需要 GPU 的 Pod 调度到有 GPU 的节点。
- 高可用设计:将 Pod 分布到不同可用区或机架的节点,避免单点故障。
节点亲和性分为两种类型,分别对应不同的调度严格程度:
requiredDuringSchedulingIgnoredDuringExecution(硬亲和性)特性:必须满足的调度条件,若没有节点符合规则,Pod 会一直处于
Pending状态。适用场景:Pod 运行依赖节点的特定属性(如必须在配备 SSD 的节点上运行)。
preferredDuringSchedulingIgnoredDuringExecution(软亲和性)特性:倾向于满足的调度条件,若没有节点符合规则,会调度到其他节点(不严格限制)。
适用场景:希望 Pod 尽量调度到某类节点,但不强制(如优先调度到离数据源近的节点以减少网络延迟)。
注:两种类型名称中的
IgnoredDuringExecution表示:当节点标签发生变化导致不再满足亲和性规则时,已运行的 Pod 不会被驱逐(仅影响新调度的 Pod)。
节点亲和性通过 matchExpressions 定义匹配规则,支持多种操作符(Operator):
| 操作符 | 含义 | 适用场景 |
|---|---|---|
In | 节点标签值在指定列表中 | 匹配标签值为枚举值的节点(如 zone in [zone1, zone2]) |
NotIn | 节点标签值不在指定列表中 | 排除特定标签值的节点 |
Exists | 节点存在指定标签(不检查标签值) | 匹配所有带有某标签的节点 |
DoesNotExist | 节点不存在指定标签 | 排除所有带有某标签的节点 |
Gt | 节点标签值(整数)大于指定值 | 匹配标签值为数值且满足大于条件的节点 |
Lt | 节点标签值(整数)小于指定值 | 匹配标签值为数值且满足小于条件的节点 |
在 Pod 的 YAML 配置中添加 preferredDuringSchedulingIgnoredDuringExecution 字段,定义软亲和性:
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-preferred
labels:
app: node-affinity-preferred
spec:
containers:
- name: node-affinity-preferred-pod
image: nginx:1.14.2
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: domain
operator: In
values:
- stonecoding.net
创建 Pod:
[root@k8sm1 ~]# vi node-affinity-preferred.yaml
[root@k8sm1 ~]# kubectl apply -f node-affinity-preferred.yaml
pod/node-affinity-preferred created
[root@k8sm1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-affinity-preferred 1/1 Running 0 26s 10.244.2.3 k8sn2.stonecoding.net <none> <none>
可以看到,使用软亲和性,即使没有满足指定条件的节点,也会调度成功。
在 Pod 的 YAML 配置中添加 requiredDuringSchedulingIgnoredDuringExecution 字段,定义硬亲和性:
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-required
labels:
app: node-affinity-required
spec:
containers:
- name: node-affinity-required-pod
image: nginx:1.14.2
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: domain
operator: In
values:
- stonecoding.net
创建 Pod:
[root@k8sm1 ~]# vi node-affinity-required.yaml
[root@k8sm1 ~]# kubectl apply -f node-affinity-required.yaml
pod/node-affinity-required created
[root@k8sm1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-affinity-required 0/1 Pending 0 30s <none> <none> <none> <none>
由于当前没有节点的标签满足 Pod 硬亲和性条件,故当前 STATUS 为 Pending。
为节点添加标签:
[root@k8sm1 ~]# kubectl label nodes k8sn1.stonecoding.net domain=stonecoding.net
node/k8sn1.stonecoding.net labeled
[root@k8sm1 ~]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8sm1.stonecoding.net Ready control-plane,master 34d v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sm1.stonecoding.net,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
k8sn1.stonecoding.net Ready <none> 28d v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,domain=stonecoding.net,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn1.stonecoding.net,kubernetes.io/os=linux
k8sn2.stonecoding.net Ready <none> 65m v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,env=production,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8sn2.stonecoding.net,kubernetes.io/os=linux
[root@k8sm1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-affinity-required 1/1 Running 0 4m53s 10.244.1.155 k8sn1.stonecoding.net <none> <none>
添加标签后,可以看到 Pod 被调度到对应的节点。
与节点选择器的区别:
| 特性 | nodeSelector | nodeAffinity |
|---|---|---|
| 匹配逻辑 | 仅支持精确匹配(key=value) | 支持 In/NotIn/Exists 等多种操作符 |
| 多条件组合 | 仅支持 “与” 关系(所有标签必须匹配) | 支持 “与”“或” 组合逻辑 |
| 调度严格程度 | 仅支持 “必须满足”(不满足则 Pending) | 支持 “必须满足”(硬亲和性)和 “倾向满足”(软亲和性) |
| 灵活性 | 低(适合简单场景) | 高(适合复杂场景) |
podAffinity And podAntiAffinity
在 Kubernetes 中,Pod 亲和性(podAffinity) 和 Pod 反亲和性(podAntiAffinity) 是基于已有 Pod 的标签(Label)实现的调度约束机制,用于控制 Pod 之间的 “聚集” 或 “隔离” 关系。与基于节点属性的 “节点亲和性” 不同,它们通过判断目标节点上已运行的 Pod 标签来决定新 Pod 的调度位置,是实现高可用、负载均衡或服务依赖部署的核心工具。
Pod 亲和性与反亲和性的核心逻辑是:根据节点上已存在的 Pod 标签,决定新 Pod 是否调度到该节点,具体作用如下:
- Pod 亲和性:让新 Pod “倾向于” 或 “必须” 调度到已运行特定 Pod 的节点(实现 Pod 聚集,如 “应用 Pod 与数据库 Pod 同节点以减少网络延迟”)。
- Pod 反亲和性:让新 Pod “倾向于” 或 “必须” 避免调度到已运行特定 Pod 的节点(实现 Pod 隔离,如 “同一服务的多个副本分散到不同节点以提高可用性”)。
两者均依赖拓扑域(Topology Domain)概念(通过 topologyKey 定义,如节点、机架、可用区等),确保调度约束在指定的拓扑范围内生效。
无论是 Pod 亲和性还是反亲和性,配置均包含以下关键字段,需结合业务需求组合使用:
| 配置字段 | 作用说明 |
|---|---|
topologyKey | 拓扑域的标签键(如 kubernetes.io/hostname 表示 “节点级拓扑”,topology.kubernetes.io/zone 表示 “可用区级拓扑”),约束仅在同一拓扑域内生效。 |
labelSelector | 用于匹配节点上已有 Pod 的标签规则,支持 matchLabels(精确匹配)和 matchExpressions(复杂匹配,如 In/NotIn/Exists)。 |
namespaces | 可选,指定仅匹配特定命名空间内的 Pod(默认匹配所有命名空间)。 |
| 调度严格程度 | 分为 “硬约束” 和 “软约束”,与节点亲和性逻辑一致: 硬约束: requiredDuringSchedulingIgnoredDuringExecution(必须满足,否则 Pod Pending)软约束: preferredDuringSchedulingIgnoredDuringExecution(倾向满足,无匹配节点时仍可调度,通过 weight 1-100 设置优先级) |
为 Pod 配置 Pod 软亲和性,让 Pod 倾向于调度到已运行标签为 app=cache 的 Pod 的节点:
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-prefer
labels:
app: pod-aff
spec:
containers:
- name: myapp
image: nginx:1.14.2
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
创建 Pod:
[root@k8sm1 ~]# vi pod-affinity-prefer.yaml
[root@k8sm1 ~]# kubectl apply -f pod-affinity-prefer.yaml
pod/pod-affinity-prefer created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-affinity-prefer 1/1 Running 0 24s
可以看到即使当前没有标签为 app=cache 的 Pod,也会调度成功。
为 Pod 配置 Pod 硬亲和性,让 Pod 必须调度到已运行标签为 app=cache 的 Pod 的节点:
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-require
labels:
app: pod-aff-req
spec:
containers:
- name: pod-aff-req-c
image: nginx:1.14.2
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
创建 Pod:
[root@k8sm1 ~]# vi pod-affinity-require.yaml
[root@k8sm1 ~]# kubectl apply -f pod-affinity-require.yaml
pod/pod-affinity-require created
[root@k8sm1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-affinity-prefer 1/1 Running 0 6m3s
pod-affinity-require 0/1 Pending 0 19s
由于当前没有标签为 app=cache 的 Pod,故调度失败,创建一个 Pod 并将其标签设置为 app=cache:
[root@k8sm1 ~]# kubectl run nginx-pod --image=nginx:1.14.2 --labels="app=cache"
pod/nginx-pod created
[root@k8sm1 ~]# kubectl get pod --show-labels -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
nginx-pod 1/1 Running 0 44s 10.244.2.5 k8sn2.stonecoding.net <none> <none> app=cache
pod-affinity-prefer 1/1 Running 0 10m 10.244.2.4 k8sn2.stonecoding.net <none> <none> app=pod-aff
pod-affinity-require 1/1 Running 0 5m2s 10.244.2.6 k8sn2.stonecoding.net <none> <none> app=pod-aff-req
标签为 app=cache 的 Pod 创建完成后,名称为 pod-affinity-require 的 Pod 调度成功。
为 Pod 配置 Pod 软反亲和性,让 Pod 不倾向于调度到已运行标签为 app=cache 的 Pod 的节点:
apiVersion: v1
kind: Pod
metadata:
name: pod-antiaff-prefer
labels:
app: pod-antiaff
spec:
containers:
- name: myapp
image: nginx:1.14.2
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
创建 Pod:
[root@k8sm1 ~]# vi pod-antiaff-prefer.yaml
[root@k8sm1 ~]# kubectl apply -f pod-antiaff-prefer.yaml
pod/pod-antiaff-prefer created
[root@k8sm1 ~]# kubectl get pod --show-labels -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
nginx-pod 1/1 Running 0 27m 10.244.2.5 k8sn2.stonecoding.net <none> <none> app=cache
pod-affinity-prefer 1/1 Running 0 37m 10.244.2.4 k8sn2.stonecoding.net <none> <none> app=pod-aff
pod-affinity-require 1/1 Running 0 31m 10.244.2.6 k8sn2.stonecoding.net <none> <none> app=pod-aff-req
pod-antiaff-prefer 1/1 Running 0 7s 10.244.1.156 k8sn1.stonecoding.net <none> <none> app=pod-antiaff
可以看到名称为 pod-antiaff-prefer 的这个 Pod 被调度与节点 k8sn1.stonecoding.net,与标签为 app=cache 的 Pod 不在一个节点上。
为 Pod 配置 Pod 硬反亲和性,让 Pod 不能调度到已运行标签为 app=cache 的 Pod 的节点:
apiVersion: v1
kind: Pod
metadata:
name: pod-antiaff-req
labels:
app: pod-antiaff-req
spec:
containers:
- name: pod-antiaff-req-c
image: nginx:1.14.2
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
创建 Pod:
[root@k8sm1 ~]# vi pod-antiaff-req.yaml
[root@k8sm1 ~]# kubectl apply -f pod-antiaff-req.yaml
pod/pod-antiaff-req created
[root@k8sm1 ~]# kubectl get pod --show-labels -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
nginx-pod 1/1 Running 0 33m 10.244.2.5 k8sn2.stonecoding.net <none> <none> app=cache
pod-affinity-prefer 1/1 Running 0 43m 10.244.2.4 k8sn2.stonecoding.net <none> <none> app=pod-aff
pod-affinity-require 1/1 Running 0 38m 10.244.2.6 k8sn2.stonecoding.net <none> <none> app=pod-aff-req
pod-antiaff-prefer 1/1 Running 0 6m48s 10.244.1.156 k8sn1.stonecoding.net <none> <none> app=pod-antiaff
pod-antiaff-req 1/1 Running 0 24s 10.244.1.157 k8sn1.stonecoding.net <none> <none> app=pod-antiaff-req
可以看到名称为 pod-antiaff-req 的这个 Pod 被调度与节点 k8sn1.stonecoding.net,与标签为 app=cache 的 Pod 不在一个节点上。
与节点亲和性的区别:
| 对比维度 | Pod 亲和性 / 反亲和性 | 节点亲和性 |
|---|---|---|
| 匹配依据 | 节点上已运行的 Pod 标签 | 节点自身的标签(如硬件、环境属性) |
| 核心目标 | 控制 Pod 之间的聚集 / 隔离关系 | 控制 Pod 与节点属性的匹配关系 |
| 依赖要素 | 需 topologyKey 定义拓扑域 | 无需拓扑域,直接匹配节点标签 |
| 适用场景 | 服务依赖、高可用分散、负载均衡 | 硬件筛选、环境隔离(如生产 / 测试节点区分) |
Taint And Toleration
在 Kubernetes 中,污点(Taint)和容忍(Toleration)是一对配合使用的机制,用于控制 Pod 在节点上的调度行为。它们允许管理员对节点设置 "排斥规则",同时让特定 Pod 获得 "准入许可",是实现节点资源精细化管理的重要工具。
Taint
污点是节点(Node)的属性,用于阻止不匹配的 Pod 调度到该节点。它就像节点设置的 "门槛",只有能跨过门槛(有对应容忍)的 Pod 才能被调度过来。
每个污点由三部分组成,格式为:key=value:effect
- key:污点的标识(字符串,如
node-type、hardware) - value:污点的具体值(字符串,如
gpu、high-memory) - effect:污点的作用效果(核心部分,决定如何排斥 Pod),有以下类型:
NoSchedule:硬限制,仅阻止新 Pod 调度到该节点,不影响已运行的 Pod(即使无容忍也不会被驱逐)NoExecute:强限制,既阻止新 Pod 调度,也会立即驱逐已运行的无对应容忍的 PodPreferNoSchedule:软限制,尽量不调度 Pod 到该节点,但不是绝对禁止(调度器会优先选择其他节点)
污点的生效时机:
- 调度阶段:当 Pod 处于待调度状态(Pending)时,调度器会过滤掉所有 Pod 无法容忍的污点节点(除非污点
effect 为PreferNoSchedule)。 - 运行阶段:若节点的污点被修改(如添加
NoExecute污点),Kubernetes 会立即检查该节点上已运行的 Pod:- 对无对应容忍的 Pod,会触发驱逐(
NoExecute特性)。 - 对有对应容忍的 Pod,继续保留。
- 对无对应容忍的 Pod,会触发驱逐(
为节点添加污点并查看:
[root@k8sm1 ~]# kubectl describe node k8sn2.stonecoding.net | grep Taint
Taints: <none>
[root@k8sm1 ~]# kubectl taint nodes k8sn2.stonecoding.net hardware=gpu:NoSchedule
node/k8sn2.stonecoding.net tainted
[root@k8sm1 ~]# kubectl describe node k8sn2.stonecoding.net | grep Taint
Taints: hardware=gpu:NoSchedule
为节点删除污点并查看:
[root@k8sm1 ~]# kubectl taint nodes k8sn2.stonecoding.net hardware=gpu:NoSchedule-
node/k8sn2.stonecoding.net untainted
[root@k8sm1 ~]# kubectl describe node k8sn2.stonecoding.net | grep Taint
Taints: <none>
除了用户手动添加的污点,Kubernetes 会为节点自动添加一些污点,用于应对节点异常或特殊状态,常见的系统污点包括:
| 系统污点 | 触发场景 | effect 类型 | 作用 |
|---|---|---|---|
node.kubernetes.io/not-ready | 节点未就绪(如网络故障) | NoExecute | 驱逐节点上的 Pod(默认等待 30 秒,可通过容忍修改) |
node.kubernetes.io/unreachable | 节点与控制平面失联 | NoExecute | 驱逐节点上的 Pod(默认等待 30 秒) |
node.kubernetes.io/out-of-disk | 节点磁盘空间不足 | NoSchedule | 阻止新 Pod 调度到该节点 |
node.kubernetes.io/memory-pressure | 节点内存压力高 | NoSchedule | 阻止新 Pod 调度到该节点(可通过 --eviction-hard 调整触发阈值) |
node-role.kubernetes.io/master | 主节点(控制平面)默认污点 | NoSchedule | 阻止普通 Pod 调度到主节点(保障控制平面安全) |
典型应用场景:
Kubernetes 主节点默认带有污点
node-role.kubernetes.io/master:NoSchedule,防止普通 Pod 被调度到主节点,保障控制平面安全。如需在主节点部署 Pod,需添加对应容忍。为 GPU 节点添加污点
hardware=gpu:NoSchedule,同时只给需要 GPU 的 Pod 添加容忍,确保 GPU 资源不被普通 Pod 占用。下线节点前,添加
NoExecute污点自动驱逐 Pod:# 标记节点不可调度并驱逐已有Pod(等待30秒) kubectl taint nodes node-1 node.kubernetes.io/unschedulable:NoExecute
Toleration
容忍是 Pod 的属性,用于声明 Pod 可以 "容忍" 节点的哪些污点,从而允许被调度到带有这些污点的节点上。
要让 Pod 能够被调度到有污点的节点,需要在 Pod 的 spec.tolerations 中定义容忍规则,匹配节点的污点。
容忍的配置格式:
tolerations:
- key: "key" # 匹配污点的 key
operator: "Equal" # 匹配方式:Equal(精确匹配)或 Exists(仅检查 key 存在)
value: "value" # 当 operator 为 Equal 时需指定,匹配污点的 value
effect: "effect" # 匹配污点的 effect(如 NoSchedule)
tolerationSeconds: 300 # 仅对 NoExecute 有效:Pod 被驱逐前的等待时间(秒)
对于 operator 字段:
operator: Equal(默认值):要求key和value与污点完全一致(精确匹配)。例:若节点污点为hardware=gpu:NoSchedule,则 Pod 容忍需同时指定key=hardware、value=gpu才能匹配。operator: Exists:只需key与污点一致,忽略value(即 "只要节点有这个 key 的污点,不管值是什么,都容忍")。假如节点有hardware=gpu:NoSchedule或hardware=fpga:NoSchedule,Pod 容忍key=hardware, operator=Exists均可匹配。# 匹配所有 key 为 hardware 的污点(无论 value 是什么) tolerations: - key: "hardware" operator: "Exists" effect: "NoSchedule"若省略
effect:表示容忍该key对应的所有 effect 类型的污点。例:tolerations: [{key: "hardware", operator: "Exists"}]会容忍hardware=xxx:NoSchedule、hardware=xxx:NoExecute等所有effect。若省略
key并指定operator: Exists:表示容忍节点上的所有污点(例如kube-proxy)。tolerations: - operator: "Exists" # 容忍节点上的所有污点(不推荐在生产环境使用)
如果节点 k8sn2.stonecoding.net 有污点 hardware=gpu:NoSchedule:
[root@k8sm1 ~]# kubectl taint nodes k8sn2.stonecoding.net hardware=gpu:NoSchedule
node/k8sn2.stonecoding.net tainted
[root@k8sm1 ~]# kubectl describe node k8sn2.stonecoding.net | grep Taint
Taints: hardware=gpu:NoSchedule
则以下 Pod 可以被调度到该节点:
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda
tolerations:
- key: "hardware"
operator: "Equal"
value: "gpu"
effect: "NoSchedule" # 精确匹配节点的污点
注意事项:
- 污点是节点属性,容忍是 Pod 属性,两者需配合使用
- 节点可以有多个污点,Pod 需容忍所有污点(或污点为
PreferNoSchedule)才能被调度。 NoExecute的风险:会立即驱逐无容忍的 Pod,可能影响业务连续性,建议配合tolerationSeconds平滑过渡。
污点 + 容忍与节点亲和性(Node Affinity)并不冲突,而是互补关系,实际场景中常结合使用:
- 污点 + 容忍:控制 "节点排斥哪些 Pod"。
- 节点亲和性:控制 "Pod 想调度到哪些节点"。
典型组合场景:
- 给 GPU 节点添加污点
hardware=gpu:NoSchedule(排斥普通 Pod)。 - 给需要 GPU 的 Pod 添加对应容忍(允许被调度到 GPU 节点)。
- 同时给 Pod 配置节点亲和性
nodeSelectorTerms: [{matchExpressions: [{key: hardware, operator: In, values: [gpu]}]}](主动选择 GPU 节点)。
通过这种组合,既能防止普通 Pod 占用 GPU 资源,又能确保 GPU Pod 精准调度到 GPU 节点。
日志
对于单个 Pod,可以使用 kubectl logs 查看其容器的日志。但对于大规模的集群,使用命令挨个查看 Pod 容器的日志就不合适了,此时就需要使用分布式日志系统 Loki 来采集,传输,存储和分析。
Loki 是由 Grafana Labs 开发的开源日志聚合系统,专为云原生环境设计,特别是在 Kubernetes 中表现出色。
核心组件:
- Promtail:日志采集代理,通常以 DaemonSet 的形式部署在 Kubernetes 节点上。它负责收集容器日志,并根据 Pod 的元数据自动为日志附加标签,如 Pod 名称、命名空间等。Promtail 通过监听 “/var/log/pods” 目录来采集容器日志,支持动态发现 Pod 并自动打标签。
- Loki:Loki 是日志系统的核心服务,包含多个模块。其中,Distributor 负责接收 Promtail 发送的日志,并将其分发给 Ingester;Ingester 将日志压缩为 Chunk,并写入到存储后端;Querier 则处理用户的日志查询请求,从 Ingester 和存储中获取数据并返回结果。
- Grafana:用于日志的可视化展示,它与 Loki 深度集成,提供了友好的用户界面,支持使用 LogQL 查询语言来搜索和分析日志,用户可以方便地通过标签过滤、时间范围筛选等方式查看所需的日志信息。
工作原理:
Promtail 首先从 Kubernetes 节点上的容器日志路径收集日志数据,并为其添加相应的标签。然后,Promtail 将这些带标签的日志发送给 Loki 的 Distributor 组件。Distributor 对接收到的日志流进行校验和分发,将其发送给 Ingester。Ingester 将日志数据压缩成 Chunk,并按照一定的规则写入到指定的存储中,如对象存储或本地文件系统。当用户通过 Grafana 发起日志查询请求时,Loki 的 Querier 会根据查询条件,先从 Ingester 的内存中查找是否有匹配的数据,如果没有,则再从存储后端读取相应的 Chunk,进行解析和过滤,最终将查询结果返回给 Grafana 进行展示。
资源配置清单文件如下:
apiVersion: v1
kind: Namespace
metadata:
name: loki
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: loki-pvc
namespace: loki
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-dynamic-sc
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
namespace: loki
data:
loki.yaml: |
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9095
common:
path_prefix: /data/loki
storage:
filesystem:
chunks_directory: /data/loki/chunks
rules_directory: /data/loki/rules
replication_factor: 1
ingester:
max_transfer_retries: 0 # 必须设为0
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
wal:
enabled: true # 显式启用 WAL
dir: /data/loki/wal # 新增 WAL 目录配置
limits_config:
ingestion_rate_mb: 50 # >>> 调高全局速率限制
ingestion_burst_size_mb: 100 #>>> 调高突发速率限制
per_stream_rate_limit: 50MB #>>> 新增单 Stream 速率限制(默认无此配置)
per_stream_rate_limit_burst: 100MB #>>> 新增单 Stream 突发限制(默认无此配置)
max_streams_per_user: 100000
max_line_size: 10485760
retention_period: 720h
reject_old_samples: true
reject_old_samples_max_age: 168h
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /data/loki/index
cache_location: /data/loki/boltdb-cache
shared_store: filesystem
compactor:
working_directory: /data/loki/compactor
shared_store: filesystem
compaction_interval: 10m
retention_enabled: true
query_range:
max_retries: 3
cache_results: true
results_cache:
cache:
enable_fifocache: true
fifocache:
max_size_bytes: 512MB
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki
namespace: loki
spec:
replicas: 1
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
containers:
- name: loki
image: grafana/loki:2.9.4
args:
- -config.file=/etc/loki/loki.yaml # 指定配置文件路径
ports:
- containerPort: 3100
volumeMounts:
- name: config
mountPath: /etc/loki # 挂载 ConfigMap
- name: storage
mountPath: /data/loki # 挂载持久化数据目录
resources:
limits:
memory: 2Gi
cpu: "2"
requests:
memory: 1Gi
cpu: "1"
volumes:
- name: config
configMap:
name: loki-config # 关联 ConfigMap
- name: storage
persistentVolumeClaim:
claimName: loki-pvc # 关联 PVC
---
apiVersion: v1
kind: Service
metadata:
name: loki
namespace: loki
spec:
ports:
- port: 3100
targetPort: 3100
selector:
app: loki
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: promtail-pvc
namespace: loki
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-dynamic-sc
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: promtail
namespace: loki
labels:
app: promtail
component: log-collector
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: promtail
labels:
app: promtail
component: log-collector
rules:
- apiGroups: [""]
resources:
- nodes # 节点基本信息
- nodes/proxy # 新增:访问 Kubelet API(需谨慎)
- pods # Pod 发现
- pods/log # 日志读取(核心权限)
- services # 服务发现
- endpoints # 新增:端点监控
- namespaces # 命名空间元数据
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: promtail
labels:
app: promtail
component: log-collector
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: promtail
subjects:
- kind: ServiceAccount
name: promtail
namespace: loki
---
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-config
namespace: loki
labels:
app: promtail
data:
promtail.yaml: |
# ================= 全局配置 =================
server:
http_listen_port: 3101 # 与 DaemonSet 中健康检查端口对齐
grpc_listen_port: 0
log_level: info # 生产环境建议使用 info 级别
client:
backoff_config:
max_period: 5m
max_retries: 10
min_period: 500ms
batchsize: 1048576
batchwait: 1s
external_labels: {}
timeout: 10s
url: http://loki.loki.svc.cluster.local:3100/loki/api/v1/push # 保持与 DaemonSet 参数一致
positions:
filename: /var/lib/promtail-positions/positions.yaml # 与 PVC 挂载路径匹配
# ================= 日志抓取规则 =================
scrape_configs:
# ========== Docker 容器日志采集 ==========
- job_name: docker-containers
pipeline_stages:
- docker: {} # 使用 Docker 日志解析
static_configs:
- targets: [localhost]
labels:
job: docker
__path__: /var/lib/docker/containers/*/*.log # 匹配您的自定义路径
host: ${HOSTNAME} # 使用 DaemonSet 注入的环境变量
# ========== Kubernetes Pod 日志主配置 ==========
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {} # 改为 CRI 解析器以更好支持 containerd
relabel_configs:
# 系统命名空间过滤
- action: drop
regex: 'kube-system|kube-public|loki' # 增加自身命名空间过滤
source_labels: [__meta_kubernetes_namespace]
# 路径生成规则优化
- action: replace
source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name]
separator: /
target_label: __path__
replacement: /var/log/pods/*$1/*.log
# 标准标签映射
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- action: replace
source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- action: replace
source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
- action: replace
source_labels: [__meta_kubernetes_node_name]
target_label: node
# 自动发现业务标签
- action: replace
source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
replacement: ${1}
regex: (.+)
- action: replace
source_labels: [__meta_kubernetes_pod_label_release]
target_label: release
replacement: ${1}
regex: (.+)
# ========== 精简控制器日志采集 ==========
- job_name: kubernetes-controllers
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {}
relabel_configs:
- action: drop
regex: 'kube-system|kube-public|loki'
source_labels: [__meta_kubernetes_namespace]
- action: keep
regex: '[0-9a-z-.]+-[0-9a-f]{8,10}'
source_labels: [__meta_kubernetes_pod_controller_name]
- action: replace
regex: '([0-9a-z-.]+)-[0-9a-f]{8,10}'
source_labels: [__meta_kubernetes_pod_controller_name]
target_label: controller
- action: replace
source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: promtail
namespace: loki
labels:
app: promtail # 添加统一标签
spec:
selector:
matchLabels:
app: promtail
updateStrategy: # 添加更新策略(新增内容)
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app: promtail
spec:
serviceAccountName: promtail
# 安全上下文调整(保持 root 但限制权限)
securityContext:
runAsUser: 0
runAsGroup: 0
fsGroup: 0
containers:
- name: promtail
image: grafana/promtail:2.9.4
imagePullPolicy: IfNotPresent # 新增镜像拉取策略
args:
- -config.file=/etc/promtail/promtail.yaml
# 建议添加 Loki 地址(重要!根据实际情况修改)
- -client.url=http://loki.loki.svc.cluster.local:3100/loki/api/v1/push
env:
- name: HOSTNAME # 新增节点名称获取(重要)
valueFrom:
fieldRef:
fieldPath: spec.nodeName
ports:
- containerPort: 3101 # 添加监控端口(新增)
name: http-metrics
protocol: TCP
volumeMounts:
- name: config
mountPath: /etc/promtail
- name: docker-logs
mountPath: /var/lib/docker/containers
readOnly: true
- name: pods-logs
mountPath: /var/log/pods
readOnly: true
- name: positions
mountPath: /var/lib/promtail-positions
securityContext: # 调整容器安全上下文(重要)
readOnlyRootFilesystem: true # 增强安全性
privileged: false # 移除特权模式
readinessProbe: # 新增健康检查(重要)
httpGet:
path: /ready
port: http-metrics
initialDelaySeconds: 10
timeoutSeconds: 1
tolerations: # 新增容忍度(重要)
- operator: Exists # 允许调度到所有节点包括 master
volumes:
- name: config
configMap:
name: promtail-config
- name: docker-logs
hostPath:
path: /var/lib/docker/containers
type: Directory
- name: pods-logs
hostPath:
path: /var/log/pods
type: Directory
- name: positions
persistentVolumeClaim:
claimName: promtail-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-pvc
namespace: loki # 与 Grafana 同一命名空间
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-dynamic-sc
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: loki
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
containers:
- name: grafana
image: grafana/grafana:9.5.0
ports:
- containerPort: 3000
volumeMounts:
- name: storage
mountPath: /var/lib/grafana # Grafana 数据目录
volumes:
- name: storage
persistentVolumeClaim:
claimName: grafana-pvc # 关联 PVC
---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: loki # 确保与 Grafana Deployment 同一命名空间
spec:
type: NodePort # 关键配置
ports:
- port: 3000 # Service 端口(集群内部访问)
targetPort: 3000 # 容器端口(与 Grafana 容器端口一致)
nodePort: 30030 # 节点端口(范围 30000-32767)
selector:
app: grafana # 必须与 Grafana Deployment 的 Pod 标签匹配
注意:
使用的
storageClassName为 StorageClass 这一节创建的nfs-dynamic-sc这个存储类。
部署:
[root@k8sm1 ~]# kubectl apply -f loki.yaml
namespace/loki created
persistentvolumeclaim/loki-pvc created
configmap/loki-config created
deployment.apps/loki created
service/loki created
persistentvolumeclaim/promtail-pvc created
serviceaccount/promtail created
clusterrole.rbac.authorization.k8s.io/promtail created
clusterrolebinding.rbac.authorization.k8s.io/promtail created
configmap/promtail-config created
daemonset.apps/promtail created
persistentvolumeclaim/grafana-pvc created
deployment.apps/grafana created
service/grafana created
查看:
[root@k8sm1 ~]# kubectl get all -n loki
NAME READY STATUS RESTARTS AGE
pod/grafana-c5d6cb64f-27kqn 1/1 Running 0 9m42s
pod/loki-849c854746-zz899 1/1 Running 0 9m42s
pod/promtail-bk65z 1/1 Running 0 9m42s
pod/promtail-cb56q 1/1 Running 0 9m42s
pod/promtail-wntf5 1/1 Running 0 9m42s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/grafana NodePort 10.106.156.20 <none> 3000:30030/TCP 9m42s
service/loki ClusterIP 10.107.212.116 <none> 3100/TCP 9m42s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/promtail 3 3 3 3 3 <none> 9m42s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/grafana 1/1 1 1 9m42s
deployment.apps/loki 1/1 1 1 9m42s
NAME DESIRED CURRENT READY AGE
replicaset.apps/grafana-c5d6cb64f 1 1 1 9m42s
replicaset.apps/loki-849c854746 1 1 1 9m42s
[root@k8sm1 ~]# kubectl get pvc -n loki
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
grafana-pvc Bound pvc-200dac67-cfc8-457f-ba74-afe4c77bb208 1Gi RWX nfs-dynamic-sc 12m
loki-pvc Bound pvc-b82c0e6e-f7f0-4adf-ac0c-7a3618334a20 1Gi RWX nfs-dynamic-sc 12m
promtail-pvc Bound pvc-04ba857f-e68e-48c3-af8c-0104b0b0da46 1Gi RWX nfs-dynamic-sc 12m
使用浏览器访问 Grafana 地址:http://192.168.92.141:30030/ ,使用默认的用户名 admin,密码 admin 登录:

添加 Loki 数据源:



这里 URL 填写:http://loki.loki.svc.cluster.local:3100,保存后点击 Explore 进入日志查找页面:

创建 Deployment 和 Service 进行测试:
[root@k8sm1 ~]# kubectl create deployment myapp --image=wangyanglinux/myapp:v1.0 --replicas=3
deployment.apps/myapp created
[root@k8sm1 ~]# kubectl create service clusterip myapp --tcp=80:80
service/myapp created
[root@k8sm1 ~]# kubectl get service myapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp ClusterIP 10.110.251.181 <none> 80/TCP 56s
[root@k8sm1 ~]# while true;
> do
> curl 10.110.251.181/hostname.html
> sleep 1s
> done
每秒发起一次请求,在 Grafana 的 Label browser 中选中对应的 Pod,然后点击右上角的 Live,就可以实时查看日志了
安全
Kubernetes 的安全机制是一个多层次的防护体系,覆盖从集群访问到容器运行的全生命周期,核心可归纳为以下几个层面:
身份认证(Authentication):验证访问者身份,支持证书、ServiceAccount 令牌、OIDC(集成外部账号)等方式。例如,管理员用证书登录,Pod 内应用通过自动挂载的 ServiceAccount 令牌访问 API。
授权(Authorization):控制"谁能做什么",主流用 RBAC(基于角色的访问控制),通过 Role/ClusterRole 定义权限,再绑定到用户或服务账号。例如,限制开发团队仅能操作特定命名空间的 Pod。
准入控制(Admission Control):对创建/更新资源的请求进行最后校验或修改,如禁止特权容器、强制添加安全标签。内置多种控制器(如 PodSecurity 限制容器权限),也支持自定义 WebHook。
容器运行时安全:限制容器权限:禁止
root用户运行、禁用特权容器、限制内核能力(如禁止修改系统时间),通过 Seccomp/AppArmor 限制系统调用,确保容器隔离。网络安全:用 Network Policy 控制 Pod 间通信,默认拒绝所有流量,仅允许规则定义的通信(如前端只能访问后端特定端口)。
敏感数据保护:用 Secret 存储密码、令牌等,支持加密存储在 etcd 中;通过 RBAC 限制 Secret 访问,避免明文暴露。
镜像安全:限制从可信仓库拉取镜像,部署前扫描镜像漏洞,通过准入控制拦截危险镜像。
这些机制相互配合,形成"纵深防御"体系,保障集群及应用的安全性。
Authentication
Kubernetes 的 Authentication(身份认证) 是集群安全体系的第一道防线,用于验证所有访问 Kubernetes API 服务器(kube-apiserver)的实体身份,包括用户、管理员、Pod 内的应用程序以及集群组件(如 kubelet、kube-controller-manager 等)。只有通过认证的实体,才能进入后续的授权(Authorization)和准入控制(Admission Control)流程。
一、认证的核心目标
- 验证身份合法性:确保访问 API 服务器的实体是已知的、可信的(如合法用户、授权的服务账号等)。
- 支持多类型实体:区分「人类用户」和「服务账号(ServiceAccount)」,前者由集群外系统管理,后者由 Kubernetes 自动创建和管理(供 Pod 内程序使用)。
- 无状态设计:API 服务器本身不存储用户信息,认证逻辑通过配置的插件或外部服务实现。
二、认证的触发时机
所有对 kube-apiserver 的请求(如通过 kubectl 命令、客户端 SDK 调用、集群组件通信等)都必须经过认证,包括:
kubectl get pods等用户操作- Pod 内应用通过 ServiceAccount 访问 API(如获取其他 Pod 信息)
- kubelet 向 API 服务器上报节点状态
- kube-controller-manager 执行控制器逻辑(如创建 Deployment 对应的 ReplicaSet)
三、认证方式与实现
Kubernetes 支持多种认证机制,可同时启用多种方式(按配置顺序尝试,直到成功或全部失败)。常用的认证方式如下:
- X.509 客户端证书认证(最常用)
通过数字证书验证身份,是集群组件和管理员最常用的认证方式,安全性高。
工作原理:
- 集群初始化时会生成一个根证书(CA),所有其他证书由 CA 签发。
- 每个实体(如管理员、kubelet、控制器)拥有一个唯一的客户端证书,证书的
Subject字段中包含身份信息:Common Name(CN):表示用户名(如admin、kube-proxy)。Organization(O):表示用户所属的组(如system:masters组拥有集群管理员权限)。
- 客户端访问 API 服务器时,在 TLS 握手阶段出示证书,API 服务器通过 CA 验证证书的有效性,并从证书中提取用户名和组。
配置与使用:
管理员证书通常在
~/.kube/config中配置,例如:users: - name: kubernetes-admin user: client-certificate: /etc/kubernetes/admin.conf.crt # 客户端证书路径 client-key: /etc/kubernetes/admin.conf.key # 客户端私钥路径集群组件(如 kubelet)的证书由
kubeadm等工具自动生成,并配置在组件启动参数中(如--client-certificate和--client-key)。
特点:
- 安全性高,证书可设置有效期(需定期轮换)。
- 适用于长期运行的集群组件和管理员访问。
- ServiceAccount 令牌认证(供 Pod 内应用使用)
ServiceAccount 是 Kubernetes 内置的「服务账号」,专为 Pod 内的应用程序访问 API 服务器设计,由 Kubernetes 自动管理。
工作原理:
- 每个命名空间默认有一个名称为
default的 ServiceAccount,若 Pod 未指定serviceAccountName,则自动使用该命名空间的default账号。 - ServiceAccount 创建时,Kubernetes 会自动生成一个 Secret(包含令牌、CA 证书等),并挂载到使用该账号的 Pod 的
/var/run/secrets/kubernetes.io/serviceaccount/目录:token:用于认证的 JWT 令牌(JSON Web Token)。ca.crt:集群 CA 证书(用于验证 API 服务器的身份)。namespace:当前命名空间。
- Pod 内的应用访问 API 服务器时,通过 HTTP 头部
Authorization: Bearer <token>传递令牌,API 服务器验证令牌的有效性(是否由集群签发、是否过期等)。
配置示例:
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
serviceAccountName: my-serviceaccount # 指定使用的 ServiceAccount
containers:
- name: app
image: myapp:v1
特点:
- 令牌自动轮换(默认 1 小时),无需手动管理。
- 权限通过 RBAC 绑定控制,默认权限极低(仅能访问自身相关资源)。
- 是 Pod 内应用访问 API 的标准方式。
- 静态令牌文件认证(简单场景)
通过预定义的令牌列表进行认证,适用于简单场景(不推荐生产环境)。
工作原理:
- 管理员创建一个静态令牌文件,格式为
token,username,useruid,group1,group2,...(每行一个令牌)。 - API 服务器通过启动参数
--token-auth-file=<文件路径>加载该文件。 - 客户端访问时,通过 HTTP 头部
Authorization: Bearer <token>传递令牌,API 服务器在文件中匹配令牌并获取用户名和组。
示例令牌文件:
abcdef123456,alice,1001,dev-team,ops-team
7890abcd, bob,1002,qa-team
特点:
- 配置简单,但令牌无法自动轮换(需手动更新文件并重启 API 服务器)。
- 适用于测试或临时访问场景。
- OAuth2 / OpenID Connect(OIDC)认证(集成外部身份系统)
通过外部身份提供商(如 LDAP、Google 账号、企业 SSO 系统)实现认证,支持单点登录(SSO)。
工作原理:
- 基于 OAuth2 授权框架和 OIDC 身份层(以 JWT 形式返回用户身份)。
- 流程:
- 用户通过外部身份提供商登录,获取 OIDC 令牌(JWT)。
- 客户端使用该令牌访问 API 服务器,通过 HTTP 头部
Authorization: Bearer <oidc-token>传递。 - API 服务器验证令牌的签名(通过 OIDC 提供商的公钥),并从令牌中提取用户名和组。
配置示例(API 服务器启动参数):
kube-apiserver \
--oidc-issuer-url=https://accounts.google.com \ # OIDC 提供商地址
--oidc-client-id=my-k8s-cluster \ # 客户端 ID
--oidc-username-claim=email \ # 从 JWT 的 email 字段提取用户名
--oidc-groups-claim=groups # 从 JWT 的 groups 字段提取用户组
特点:
- 适合企业级集群,可集成现有身份系统(无需在 Kubernetes 中单独管理用户)。
- 支持复杂的身份验证逻辑(如多因素认证 MFA)。
- WebHook 认证(自定义认证逻辑)
通过外部 HTTP 服务自定义认证逻辑,适用于特殊场景(如企业内部认证系统)。
工作原理:
- API 服务器将认证请求转发给外部 WebHook 服务(HTTP/HTTPS 端点)。
- WebHook 服务验证请求中的身份信息(如令牌、证书等),并返回认证结果(成功 / 失败,以及用户名和组)。
- API 服务器根据 WebHook 响应决定是否通过认证。
配置示例(WebHook 配置文件):
apiVersion: authentication.k8s.io/v1
kind: WebhookConfiguration
metadata:
name: my-auth-webhook
webhooks:
- name: auth.example.com
clientConfig:
url: https://auth-service.example.com/authenticate # 外部 WebHook 地址
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["*"] # 对所有操作应用该 WebHook
failurePolicy: Fail # WebHook 调用失败时,拒绝请求
特点:
- 灵活性极高,可实现任意自定义认证逻辑。
- 需自行维护外部 WebHook 服务的可用性和安全性。
四、认证失败的处理
- 若认证失败,API 服务器返回 HTTP 401 Unauthorized 错误。
- 常见失败原因:证书无效(过期、未由 CA 签发)、令牌错误或过期、请求未携带任何认证信息等。
五、关键注意事项
- 区分用户类型:
- 人类用户:由集群外系统管理(如 OIDC、LDAP),Kubernetes 不提供用户管理 API。
- ServiceAccount:由 Kubernetes 管理(通过
kubectl create serviceaccount创建),仅用于 Pod 内应用。
- 安全性最佳实践:
- 优先使用 X.509 证书(管理员 / 组件)和 ServiceAccount 令牌(Pod)。
- 避免使用静态令牌文件(生产环境)。
- 定期轮换证书和令牌(可通过
kubeadm certs renew等工具自动处理)。 - 启用 TLS 加密所有 API 通信(
--tls-cert-file和--tls-private-key-file配置)。
- 认证与授权的关系:
- 认证仅验证 [身份是否合法],不判断 [是否有权限执行操作]。
- 认证通过后,才进入授权阶段(如 RBAC 检查权限)。
Kubernetes 的 Authentication 机制通过多种认证方式(证书、ServiceAccount 令牌、OIDC、WebHook 等)覆盖了不同场景的身份验证需求,核心是确保只有可信实体才能访问 API 服务器。在实际部署中,需根据集群规模和安全要求选择合适的认证方式,并结合授权机制构建完整的访问控制体系。
排错
查看日志:
[root@k8sm1 ~]# kubectl logs -f nginx-deployment-9456bbbf9-5w9qt -n app

