Skip to content

4.5 配置网桥过滤和地址转发

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

#让配置生效
sudo sysctl -p
#加载网桥过滤模块
sudo modprobe br_netfilter
#查看模块是否被加载
sudo lsmod |grep br_netfilter

#重启电脑
reboot

4.6 配置ipvs功能

安装配置 ipvs

#安装相应软件
sudo apt install -y ipset ipvsadm

#开机加载模块
sudo tee /etc/modules-load.d/modules.conf << EOF
br_netfilter
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack_ipv4
EOF

reboot

查看ipvs规则

sudo ipvsadm -Ln
如果查看ipvs规则为空,可能意味着 kube-proxy 没有正确配置为使用 ipvs 模式
1
2
3
4
5
6
7
8
9
#查看是否使用 ipvs 模式
kubectl -n kube-system get configmap kube-proxy -o yaml
#检查 kind: KubeProxyConfiguration 旁边的 mode 参数。如果 mode 没有设置为 ipvs,那 kube-proxy 就不会使用 IPVS。

#修改参数,找到 mode ,修改其值为 ipvs 。
kubectl -n kube-system edit configmap kube-proxy 

#删除 proxy 的pod,kube-proxy 是 daenonSet ,删除了会自动新建
kubectl -n kube-system delete pod -l k8s-app=kube-proxy

5. 安装docker

安装必备的软件包以允许apt通过 HTTPS 使用存储库

sudo apt-get -y install ca-certificates curl gnupg lsb-release

添加Docker官方版本库的GPG密钥

sudo mkdir -p /etc/apt/keyrings &&
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

~~添加官方源~~

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

官方源被墙了,转用阿里源

1
2
3
4
#添加 Docker GPG key
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
#添加 apt 源:
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

安装docker

每个节点都必须安装docker

#更新库
sudo apt-get update
#安装
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
#将当前用户加入 docker 组
sudo usermod -aG docker $USER

#配置docker
sudo tee /etc/docker/daemon.json << EOF
{
        "exec-opts":["native.cgroupdriver=systemd"],
        "registry-mirrors":["https://ej8wpiko.mirror.aliyuncs.com"],
        "proxies": {
                "http-proxy": "http://192.168.1.171:7890",
                "https-proxy": "http://192.168.1.171:7890"
        }
}
EOF

#看下docker服务是不是开机自启,不是则设置自启
sudo systemctl daemon-reload && \
sudo systemctl enable docker --now

卸载 docker engine 方式 在安装时,不要卸载!!!

1
2
3
4
5
#卸载 docker engine
sudo apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
#卸载并不会删除 Images, containers, volumes,configuration file。手动删除它们
sudo rm -rf /var/lib/docker && \
sudo rm -rf /var/lib/containerd

6. 安装 kubernetes

6.1 安装 k8s 需要的包

1
2
3
sudo apt-get update
# apt-transport-https 可以是一个虚拟包;如果是这样,你可以跳过这个包
sudo apt-get install -y apt-transport-https ca-certificates curl

6.2 添加 k8s 源

以下源二选一

1
2
3
4
5
6
#阿里源
echo "deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main" >> /etc/apt/sources.list.d/kubernetes.list
#官方源    需要什么版本就把如下版本号更改成需要的
sudo bash -c 'cat << EOF > /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /
EOF'

6.3 添加 k8s 公钥

#下载 Kubernetes 软件包仓库的公共签名密钥到/etc/apt/keyrings/
##在低于 Debian 12 和 Ubuntu 22.04 的发行版本中,/etc/apt/keyrings 默认不存在。 应在 curl 命令之前创建它。
##如果想用其他版本,把命令中的 v1.28 替换为所需版本即可,注:该版本只支持近五代的版本号
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg 
#更新源
sudo apt update
#关闭防火墙
sudo ufw disable

#要将 kubectl 升级到别的次要版本,你需要先升级 /etc/apt/sources.list.d/kubernetes.list 中的版本, 再运行 apt-get update 和 apt-get upgrade 命令

6.4 安装 kubelet kubeadm kubectl

sudo apt-get -y install kubelet kubeadm kubectl

6.5 安装 cri-dockerd

cri-dockerd是容器运行时接口(Container Runtime Interface,CRI),通过 cri 才能使用 docker 。在 v1.24 更早版本中,k8s中自带一个 dockershim 组件,v1.24时就已经把它给移除了,所以需要一个替代品,就是 cri-dockerd 。

control-plane、work-node 节点都需要安装!

#去github下载最新包,ubuntu下 amd64位的。centos下rpm版本

#解压到 /usr/bin 下
tar zxvf *.amd64.tgz
sudo mv cri-dockerd/cri-dockerd /usr/bin/
sudo chown root:root /usr/bin/cri-dockerd

#下载官方守护进程配置到 /etc/systemd/system/ ,并启动
wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service
wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket
sudo mv cri-docker.socket cri-docker.service /etc/systemd/system/

sudo systemctl daemon-reload && \
sudo systemctl enable cri-docker.service && \
sudo systemctl enable --now cri-docker.socket

注意:如果是从 containerd 换成的 cri-dockerd ,那么执行 kubeadm命令时报 : Found multiple CRI endpoints on the host. Please define which one do you wish to use by setting the 'criSocket' field in the kubeadm configuration file: unix:///var/run/containerd/containerd.sock, unix:///var/run/cri-dockerd.sock 在语句后面加上 --cri-socket=unix:///var/run/cri-dockerd.sock

配置kubelet 使用新的容器运行时

1
2
3
4
5
6
7
8
9
# 修改node对象文件,在任意一台能执行kubeadm命令的节点上运行以下命令。每个节点都要修改
kubectl edit no node1

# 更改 kubeadm.alpha.kubernetes.io/cri-socket 值为 unix:///var/run/cri-dockerd.sock

# 设置kubelet 开机自启并立即启动
sudo systemctl enable kubelet --now
#查看节点健康状态
kubectl get nodes -o wide

6.6 初始化控制平面

# kubeadm 初始化
sudo kubeadm init --apiserver-advertise-address=192.168.1.180 \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket=unix:///var/run/cri-dockerd.sock


#然后根据提示执行命令。这段配置用于让普通用户可以有执行kubectl命令的能力,work node也可以执行,不过需要从 control-plane 把admin.conf复制过来。
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

#根据提示在工作节点执行命令,让工作节点添加到集群里
sudo kubeadm join 192.168.0.180:6443 --token 6qh4l9.9w00l5izdugh5jw7         --discovery-token-ca-cert-hash sha256:cd1ad31a6928be824a6eb849120b756c896e9b8c7f9d1de925217a2bde8bff5e
#如果命令忘了可以执行该命名获取
kubeadm token create --print-join-command
# 如果提示 Found multiple CRI endpoints on the host. Please define which one do you wish to use by setting the 'criSocket' field in the kubeadm configuration file: unix:///var/run/containerd/containerd.sock, unix:///var/run/cri-dockerd.sock,在命令后面添加如下参数
--cri-socket=unix:///var/run/cri-dockerd.sock

#如果执行加入命令后,一直 NotReady,看看 是不是没有 **镜像**,用以下命令拉取镜像。
sudo kubeadm config images pull
  • --image-repository registry.aliyuncs.com/google_containers:表示使用阿里云镜像仓库,不然有些镜像下载不下来
  • --kubernetes-version=v1.24.17:指定kubernetes的版本
  • --pod-network-cidr=10.244.0.0/16:指定pod的网段
  • --config kubeadm-config.yaml
  • coredns是一个用go语言编写的开源的DNS服务
  • --apiserver-advertise-address 集群内通告地址,相当于内网地址
  • --control-plane-endpoint=124.223.45.80:6443 参数指定了公网 IP 地址和端口,用于外部访问 Kubernetes 控制平面
  • --service-cidr=10.96.0.0/12 集群内部虚拟网络,Pod统一访问入口
  • --cri-socket 指定cri-dockerd接口,如果是containerd则使用--cri-socket unix:///run/containerd/containerd.sock

6.7 重置 kubeadm (集群有问题时才执行)

按需执行!!!

master节点执行命令

#移除节点
kubectl delete node <节点名称>
#重置 kubeadm 状态 包括 kubeadm init 失败后重置环境,和 node 节点重置
kubeadm reset -f --cri-socket=unix:///var/run/cri-dockerd.sock
#清空之前 kubeadm 的文件
rm -rf /etc/cni /etc/kubernetes /var/lib/dockershim /var/lib/etcd  /var/run/kubernetes 
#重置 iptables 规则
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
#重置 IPVS 表
ipvsadm -C
# 删除所有名称以 "cali" 开头的接口
ip -o link show | awk -F': ' '$2 ~ /^cali/ {print $2}' | xargs -I {} ip link delete {}
ip link delete vxlan.calico

子节点执行命令

#重置 kubeadm 状态 包括 kubeadm init 失败后重置环境,和 node 节点重置
kubeadm reset -f --cri-socket=unix:///var/run/cri-dockerd.sock
#清空之前 kubeadm 的文件
rm -rf /etc/cni /etc/kubernetes /var/lib/dockershim /var/lib/etcd  /var/run/kubernetes 
#重置 iptables 规则
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
#重置 IPVS 表
ipvsadm -C
# 删除所有名称以 "cali" 开头的接口
ip -o link show | awk -F': ' '$2 ~ /^cali/ {print $2}' | xargs -I {} ip link delete {}
ip link delete vxlan.calico

6.8 配置kubectl命令tab键自动补全

1
2
3
4
5
6
7
8
9
#查看kubectl自动补全命令
kubectl --help | grep bash
#添加配置到全局
sudo sh -c 'echo "source <(kubectl completion bash)" >> /etc/profile'
#或者,添加到当前用户下终端,需根据自己所使用的是 bash 还是 zsh 来更改下面的命令
echo "source <(kubectl completion bash)" >>~/.bashrc
#应用配置。修改的哪个文件就应用哪个文件
source /etc/profile
source ~/.bashrc

7. 部署CNI网络插件 calico

虽然现在kubernetes集群已经有1个master节点,2个worker节点,但是此时三个节点的状态都是NotReady的,原因是没有CNI网络插件,为了节点间的通信,需要安装cni网络插件,常用的cni网络插件有calico和flannel,两者区别为:flannel不支持复杂的网络策略,calico支持网络策略,因为今后还要配置kubernetes网络策略networkpolicy,所以本文选用的cni网络插件为calico! calico官方文档

7.1 安装 calico

只需在一台 control-plane 安装即可

# 安装calico的环境
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/tigera-operator.yaml
# 拉取calico配置文件
curl https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/custom-resources.yaml -O

#calico默认 k8s 网段是 `192.168.0.0/16`,如果在 kubeadm init 时指定了网段 --pod-network-cidr=10.244.0.0/16,就要修改calico配置
vim custom-resources.yaml
#找到 cidr,修改网段

# 部署calico
kubectl create -f custom-resources.yaml
#查看calico是否运行
kubectl get pods -n calico-system
#如果没有pod 运行,不要着急。calico要下载images,静候佳音即可!

7.2 清除 calico 网络接口

# master 上执行
kubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/tigera-operator.yaml
kubectl delete -f custom-resources.yaml

# 有时 delete tigera-operator.yaml ,会卡在最后一步,直接ctrl+c取消,执行如下语句
kubectl get namespace tigera-operator -o json > /tmp/tigera-ns.json
jq 'del(.spec.finalizers)' /tmp/tigera-ns.json > /tmp/tigera-ns-clean.json
kubectl replace --raw "/api/v1/namespaces/tigera-operator/finalize" -f /tmp/tigera-ns-clean.json


# 清除 node 节点的网络接口
# 删除所有名称以 "cali" 开头的接口
ip -o link show | awk -F': ' '$2 ~ /^cali/ {print $2}' | xargs -I {} ip link delete {}
ip link delete vxlan.calico

8. 部署nginx测试 k8s 能否正常工作

1
2
3
4
5
6
7
#部署nginx
kubectl create deployment nginx --image=nginx:1.14-alpine
#暴露80端口
kubectl expose deployment nginx --port=80 --type= NodePort
#查看服务状态
kubectl get pods,service
##看 nginx 的 PORT(S) 这列,80端口暴露到本集群的哪个端口两,通过集群任意节点+端口号即可访问nginx

9. 配置 在 node 节点上运行 kubectl 命令

kubectl的配置文件在 $HOME/.kube 目录下,要在node节点运行,配置文件肯定不能少。

#把kube配置传到node节点home目录下
scp -r -P 7777 $HOME/.kube node1:$HOME/

10. 一些设置命令

10.1 允许控制平面上调度 pod

kubectl taint nodes --all node-role.kubernetes.io/control-plane-

11. k8s 升级

k8s升级版本只能一个版本逐级升级,不能跨版本升级!升级节点时,最好每个节点依次升级!

#修改apt源,下面的 v1.24 就是要升级的版本号,可自行修改成想升级的版本号
sudo tee /etc/apt/sources.list.d/kubernetes.list <<EOF
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.24/deb/ /
EOF
#更新apt包索引
sudo apt update
#查看可更新的版本
sudo apt-cache madison kubeadm
# 触发 kube-apiserver 体面关闭
#


#对于第一个控制面节点升级 kubeadm 
sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.29.17-1.1' && \
sudo apt-mark hold kubeadm
#验证版本
kubeadm version
#验证升级计划
sudo kubeadm upgrade plan
#kubeadm upgrade 也会自动对 kubeadm 在节点上所管理的证书执行续约操作。 如果需要略过证书续约操作,可以使用标志 --certificate-renewal=false

#对于 v1.28 之前的版本,kubeadm 默认采用这样一种模式:在 kubeadm upgrade apply 期间立即升级插件(包括 CoreDNS 和 kube-proxy),而不管是否还有其他尚未升级的控制平面实例。 这可能会导致兼容性问题。从 v1.28 开始,kubeadm 默认采用这样一种模式: 在开始升级插件之前,先检查是否已经升级所有的控制平面实例。 你必须按顺序执行控制平面实例的升级,或者至少确保在所有其他控制平面实例已完成升级之前不启动最后一个控制平面实例的升级, 并且在最后一个控制平面实例完成升级之后才执行插件的升级。如果你要保留旧的升级行为,可以通过 kubeadm upgrade apply --feature-gates=UpgradeAddonsBeforeControlPlane=true 启用 UpgradeAddonsBeforeControlPlane 特性门控。Kubernetes 项目通常不建议启用此特性门控, 你应该转为更改你的升级过程或集群插件,这样你就不需要启用旧的行为。 UpgradeAddonsBeforeControlPlane 特性门控将在后续的版本中被移除。
#升级 kubeadm
sudo kubeadm upgrade apply v1.24.2

#升级其他控制节点或工作节点的 kubeadm
sudo kubeadm upgrade node

# 将 <node-to-drain> 替换为你要腾空的控制面节点名称,为升级kubelet和kubectl做准备
kubectl drain <node-to-drain> --ignore-daemonsets
#升级
sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.24.2-00' kubectl='1.24.2-00' && \
sudo apt-mark hold kubelet kubectl
#重启 kubelet
sudo systemctl daemon-reload
sudo systemctl restart kubelet
#解除节点保护
kubectl uncordon <node-to-uncordon>

12. 备份 etcd

etcd 是 k8s 的数据库,数据库目录位于 /var/lib/etcd

使用etcdctl备份和直接备份的区别

12.1 安装 etcdctl

1
2
3
4
5
6
7
8
# 查看当前 etcd 版本
kubectl describe pod etcd-$(hostname) -n kube-system|grep "Image:"
# 若是 3.5.15 则进如下连接下载 amd64 位版本
https://github.com/etcd-io/etcd/releases/tag/v3.5.15
# 解压
tar zxvf etcd-v3.5.15-linux-amd64.tar.gz
# 移动到 bin 目录
sudo mv etcdctl /usr/local/bin/

12.2 检查快照数据完整性

1
2
3
4
5
6
7
8
9
# 已被废弃
ETCDCTL_API=3 etcdctl --endpoints=https://192.168.1.180:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  snapshot status /var/lib/etcd/snapshot-20250311.db

# 新版
sudo etcdutl snapshot status /var/lib/etcd/snapshot-20250311.db

12.3 备份

1
2
3
4
5
ETCDCTL_API=3 etcdctl snapshot save /var/lib/etcd/snapshot-$(date +%Y%m%d).db \
  --endpoints=https://192.168.1.180:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt

12.4 使用快照恢复

# 恢复前需要把所有的 etcd 都停掉
systemctl stop kubelet docker

# 已被废弃
ETCDCTL_API=3 etcdctl --endpoints=https://192.168.1.180:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  snapshot restore /var/lib/etcd/snapshot-20250311.db --data-dir=/var/lib/etcd-restore

# 新版
sudo etcdutl snapshot restore /var/lib/etcd/snapshot-20250311.db --data-dir=/var/lib/etcd-restore

# 替换数据目录
sudo rm -rf /var/lib/etcd
sudo mv /var/lib/etcd-restore /var/lib/etcd

sudo systemctl start docker kubelet

12.5 验证修复

1
2
3
4
5
sudo ETCDCTL_API=3 etcdctl --endpoints=https://192.168.1.180:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  endpoint health

13. kube-proxy 修复

1
2
3
4
5
# 重新生成 kube-proxy 的配置
kubeadm init phase addon kube-proxy

# 检查 kube-proxy 是否运行
kubectl get pods -n kube-system | grep kube-proxy

二. Helm

Helm 是 k8s 的包管理器。类似于 ubunutu 的 apt 和 apt-get 包管理器,用于简化应用的部署和管理。Helm 将 Chart 中的模板文件(templates/*.yaml)与用户提供的 values.yaml 或命令行参数结合,生成最终的 Kubernetes 资源清单(YAML 文件),Helm 客户端直接将渲染后的 YAML 提交给 Kubernetes API Server,创建或更新资源。

helm 中几个概念解释: - Chart: 是创建一个应用的信息集合,包括各种 k8s 对象的配置模板、参数定义、依赖关系、文档说明等。chart 是应用部署的自包含逻辑单元。可以将 chart 想象成 apt 的包管理工具。 - Release: 是 chart 的运行实例,代表了一个正在运行的应用。当 chart 被安装到 k8s 集群,就生成一个 release。chart 能够多次安装到同一个集群,每次安装都是一个 release。 - helm cli: helm 客户端组件,负责和 k8s api Server 通信。 - Repository: 用于发布和存储 chart 的仓库。

helm 有两个版本 v2 , v3: - v2:v2 版本是 C/S 架构,依赖服务端组件 Tiller(运行在集群中),负责管理 Release 状态,存在安全风险(Tiller 拥有集群管理员权限)。 - v3:移除了 Tiller,完全通过客户端(helm 命令)与 Kubernetes API 直接交互,Release 信息存储在集群的 Secrets 中,安全性更高。

helm 版本结构

helm 官网

1. 安装

1.1 ubuntu 安装

1
2
3
4
5
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

1.2 二进制文件安装

  1. 去他们的 Github 仓库 下载需要的版本,下载 amd64架构的。
  2. 解压 tar -zxvf helm*.tar.gz
  3. 在解压目录中找到 helm,移动到需要的目录中 mv linux-amd64/helm /usr/local/bin/helm

1.3 Homebrew

brew install helm

2. 部署应用

helm 可以从多个源进行安装

1
2
3
4
5
6
7
8
# chart 仓库
helm install apache bitnami/apache
# 本地 chart 包
helm install apache path/apache.tgz
# 解压后的 chart 目录
helm install apache path/apache
# 完整的 URL
helm install apache https://charts.bitnami.com/bitnami/apache-11.3.4

2.1 添加 Chart 仓库

helm repo add bitnami https://charts.bitnami.com/bitnami

2.2 安装 Chart 示例

默认部署

1
2
3
4
5
6
7
# 确定我们可以拿到最新的charts列表
helm repo update
# 测试 部署 apache
helm install bitnami/apache --generate-name

# 卸载 apache,--keep-history 保留版本历史,删除应用还能用 list 命令查看到
helm uninstall apache-1747570880 --keep-history

通过 yaml 文件自定义端口部署 编写配置文件 values.yaml。要修改什么就写什么配置,helm 用 values.yaml 去覆盖默认配置。

service:
  type: NodePort

部署应用,-f--values 等价,该参数可以使用多次,最右边的配置文件优先级最高!

helm install -f values.yaml apache bitnami/apache

通过命令行方式自定义端口部署

1
2
3
4
5
6
7
8
# 下面命令会以 --set 命令行方式的配置去部署!
helm install --set service[0].port=12345 -f values.yaml apache bitnami/apache

# 查看应用自定义的配置
helm get values apache

# 清除自定义的 --set 中的值
helm upograde --reset-values

2.3 helm 安装资源顺序

Helm 在安装 Chart 时,会按照 特定顺序 创建 Kubernetes 资源,以确保依赖关系和初始化逻辑的正确性。以下是 Helm 资源安装的核心顺序规则:

Namespace (如果在 Chart 中定义)
NetworkPolicy
ResourceQuota
LimitRange
PodSecurityPolicy (已弃用,但仍可能在某些 Chart 中使用)
Secret
ConfigMap
StorageClass
PersistentVolume
PersistentVolumeClaim
ServiceAccount
Role
ClusterRole
RoleBinding
ClusterRoleBinding
Service
Headless Service
Endpoints
NetworkPolicy (涉及 Pod Selector)
PodDisruptionBudget
StatefulSet
Deployment
ReplicaSet
DaemonSet
Job
CronJob
HorizontalPodAutoscaler
Pod
Ingress
APIService
CustomResourceDefinition (CRD)
MutatingWebhookConfiguration
ValidatingWebhookConfiguration

3. 命令

# 搜索仓库中的某个资源
helm search repo bitmani
# 查看 apache chart 基本信息
helm show chart bitnami/apache
# 查看 apache 所有的信息
helm show all bitnami/apache
# 查看部署信息
helm list --all
# 查看版本号
helm history apache

3.1 upgrade

upgrade 用于升级 chart 新版本,或修改 release 的配置

# 使用 valuse.yaml 文件更新配置
helm upgrade -f values.yaml apache 

3.2 rollback

用于回滚到之前发布的版本 - --dry-run 模拟回滚操作(不实际执行) - --no-hooks 跳过 Hook 的执行 - --disable-openapi-validation 跳过 OpenAPI 验证(兼容旧版本资源) - --force 强制回滚(即使资源已不存在) - --cleanup-on-fail 回滚失败时清理残留资源 - --timeout 5m 设置超时时间(默认 5 分钟),单位 ns,us,ms,s,m,h - --wait 等待所有资源就绪(默认启用)才会标记该 release 为成功。 - --no-wait 异步回滚(不等待资源就绪)

# 回滚到之前版本
helm rollback apache 版本号

s

三. Kubernetes

1. 资源管理方式

资源管理方式主要有三种。 - 命令式对象管理 直接使用命令去操作kubernetes资源;缺点:无法审计和跟踪 kubectl run nginx-pod --image=nginx:1.17.1 --port=80 ···········- 命令式对象配置 通过命令配置和配置文件去操作kubernetes资源;优点:可以审计跟踪;缺点:项目大时配置文件太多 kubectl create/patch -f nginx-pod.yaml - 声明式对象配置 通过apply命令和配置文件去操作kubernetes资源,主要用于创建和更新资源;优点:支持目录操作;缺点:意外情况难调试 kubectl apply -f nginx-pod.yaml

1.1 命令式对象管理

kubectl命令的语法: kubectl [command] [type] [name] [flags] - comand:指定要对资源执行的操作,例如create、get、delete - type:指定资源类型,比如deployment、pod、service - name:指定资源的名称,名称大小写敏感 - flags:指定额外的可选参数

1
2
3
4
5
6
# 查看所有pod
kubectl get pod
# 查看某个pod
kubectl get pod pod_name
# 查看某个pod,以yaml格式展示结果
kubectl get pod pod_name -o yaml
kubernetes中所有的内容都抽象为资源,通过 kubectl api-resources查看;常用资源如下

资源名称 缩写 资源作用
nodes no 集群组成部分
namespaces ns 隔离Pod
pods po 装载容器
replicationcontrollers rc 控制pod资源
replicasets rs 控制pod资源
deployments deploy 控制pod资源
daemonsets ds 控制pod资源
jobs 控制pod资源
cronjobs cj 控制pod资源
horizontalpodautoscalers hpa 控制pod资源
statefulsets sts 控制pod资源
services svc 统一pod对外接口
ingress ing 统一pod对外接口
volumeattachments 存储
persistentvolumes pv 存储
persistentvolumeclaims pvc 存储
configmaps cm 存储
secrets 存储

kubernetes允许对资源进行多种操作,可以通过--help查看详细的操作命令 kubectl --help常用操作如下所示

命令 翻译 命令作用
create 创建 创建一个资源
edit 编辑 编辑一个资源
get 获取 获取一个资源
patch 更新 更新一个资源
delete 删除 删除一个资源
explain 解释 展示资源文档
run 运行 在集群中运行一个指定的镜像
expose 暴露 暴露资源为Service
describe 描述 显示资源内部信息
logs 日志 输出容器在 pod 中的日志
attach 缠绕 进入运行中的容器
exec 执行 执行容器中的一个命令
cp 复制 在Pod内外复制文件
rollout 首次展示 管理资源的发布
scale 规模 扩(缩)容Pod的数量
autoscale 自动调整 自动调整Pod的数量
apply 应用 通过文件对资源进行配置
label 标签 更新资源上的标签
cluster-info 集群信息 显示集群信息
version 版本 显示当前Server和Client的版本

1.2 命令式对象配置

通过命令操作配置文件来管理资源。

创建一个 nginxpod.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: dev

---

apiVersion: v1
kind: Pod
metadata:
  name: nginxpod
  namespace: dev
spec:
  containers:
  - name: nginx-containers
    image: nginx:1.17.1

1.3 声明式对象配置

声明式对象配置只有一个 apply 命令

kubectl apply -f nginxpod.yaml
#资源不存在相当于创建,资源存在就是更新操作

2. 资源简介

2.1 namespace

namespacek8s中的一种非常重要的资源,主要用于实现多租户的资源隔离。 默认情况下,集群中的所有pod都是可以互相访问的。实际中,不可能让所有的pod都能进行访问,namespace就可以解决这个问题。namespace把pod划分到不同的“组”,组内资源可以互相访问,但组外资源不可访问,从而实现资源隔离。此外,namespace还可以结合kubernetes的资源配额机制,限定不同租户所占用的资源,如cpu使用量,内存使用量等,实现多租户的可用资源管理。 kubernetes默认存在4个namespace - default :所有未指定namespace的对象都会被分配在default中 - kube-node-lease:集群节点的心跳维护,v1.13开始引入 - kube-public:该namespace中的资源可以被所有人访问,包括为认证用户 - kube-system:由kubernetes所创建的资源都存在此namespace

# 查看所有
kubectl get ns
# 查看指定
kubectl get ns default
# 按指定格式输出
kubectl get ns default -o yaml
# 查看ns详情
kubectl describe ns default
# 创建namespace
kubectl create ns dev
kubectl create -f ns-dev.yaml 
# 删除namespace
kubectl delete ns dev
kubectl delete -f ns-dev.yaml 

2.2 pod

pod是k8s中管理的最小单元,程序要运行必须部署在容器中,容器又必须存在于pod中。一个pod中可以有一个或者多个容器。

2.2.1 创建并运行

kubectl run podName [options] - --image 指定pod镜像 - --port 指定端口 - --namespace 指定namespace 例:kubectl run nginx --image=nginx:1.17.1 --port=80 --namespace=dev

2.2.2 查看pod

#查看pod基本信息
kubectl get pods -n dev
#查看pod详细信息
kubectl describe pod nginx -n dev
#查看pod IP信息
kubectl get pods -n dev -o wide
#查看dev的namespace下的Pod控制器
kubectl get deploy -n dev
#删除指定pod,被pod控制器管理的pod又会创建个新pod
kubectl delete pod nginx -n dev
#删除pod控制器
 kubectl delete deploy nginx -n dev

2.3 label

Label用于在资源上添加标识,进而对资源进行区分和选择。 - Label以 kv 键值对的形式存在,如“version=1.0”,可以附加在如node、pod、service等资源上。 - 每一资源对象可以添加任意数量label, 每一label也可以分配到任意数量的资源上。 - label通常在资源定义时确定,当然也可以在创建后进行添加和删除。 Label Selector 用于查询和筛选拥有标签的资源对象,有两种方式 - 等式:author!=moloom 选择标签名author值不为moloom的资源 - 集合:author in (moloom,robin) 选择标签名author且值为moloom或robin的资源 author not in (moloom) 选择标签名author值不为moloom的资源

# 添加label
kubectl label pod nginx-test version=1.0 -n dev
# 修改label
kubectl label pod nginx-test -n dev version=1.0.2 --overwrite
# 查看label
kubectl get pod nginx-test -n dev --show-labels
# 筛选标签
kubectl get pod -n dev -l version=2.0 --show-labels
#删除标签
kubectl label pod nginx-test -n dev tag-
在yaml中,label在 metadata 层下
1
2
3
4
5
6
metadata:
    name: nginx-test
    namespace: dev
    labels:
        version: "1.0"
        env: "test"

2.4 deployment

在k8s中,pod作为最小资源个体,很少直接被k8s控制。一般来说,k8s通过pod控制器来控制pod,pod控制器用于pod的管理,确保pod资源符合与其的状态,当pod资源出现故障时,会尝试进行重启或重建pod。 kubectl create deployment podName [options] - --image 指定pod镜像 - --port 指定端口 - --replicas 指定创建pod数量 - --namespace 指定namespace 例:kubectl create deployment nginx --image=nginx:1.17.1 --port=80 --replicas=3 -n dev 注:新版创建deploy控制器是如上命令。旧版用run命令即可,默认就是由deploy控制器控制。 如上命令执行后,deploy控制器会自动添加一个label ,app=$podName 。

1
2
3
4
5
6
#查看deployment的信息
kubectl get deploy -n dev
#查看deployment的详细信息
kubectl describe deploy nginx -n dev
#删除deployment
 kubectl delete deploy nginx -n dev
yaml配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      run: nginx
  template:
    metadata:
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx:1.17.1
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP

2.5 service

Service可以看作是一组同类Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。

1
2
3
4
5
6
7
8
9
#暴露service
kubectl expose deploy nginx --name=svc-nginx1 --type=ClusterIP --port=80 --target-port=80 -n dev
    --type=ClusterIP  创建的是只允许内部访问的ip,不对外暴露端口
    --type=NodePort 创建内部ip ,并对外暴露端口

# 查看service
 kubectl get svc svc-nginx -n dev -o wide
#删除service
 kubectl delete svc svc-nginx-1 -n dev
yaml配置
apiVersion: v1
kind: Service
metadata:
  name: svc-nginx
  namespace: dev
spec:
  clusterIP: 10.109.179.231
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: nginx
  type: ClusterIP

3. Pod

每一个pod中可以有一个或多个容器。pod中有一个名为Pause的容器,她是pod的根容器,整个pod的ip地址被设置在此容器中,其他容器都通过pause容器来实现网络通信,pause也被用于评估整个pod的健康状态。

1
2
3
4
#查看pod的一级属性
kubectl explain pod
#查看pod中metadata的属性
kubectl explain pod.metadata

3.1 pod 关于yaml的配置清单

pod的yaml配置参数详情

apiVersion: v1     #必选,版本号,例如v1
kind: Pod         #必选,资源类型,例如 Pod
metadata:         #必选,元数据
  name: string     #必选,Pod名称
  namespace: string  #Pod所属的命名空间,默认为"default"
  labels:           #自定义标签列表
    - name: string
spec:  #必选,Pod中容器的详细定义
  containers:  #必选,Pod中容器列表
  - name: string   #必选,容器名称
    image: string  #必选,容器的镜像名称
    imagePullPolicy: [ Always|Never|IfNotPresent ]  #获取镜像的策略
    command: [string]   #容器的启动命令列表,如不指定,使用打包时使用的启动命令
    args: [string]      #容器的启动命令参数列表
    workingDir: string  #容器的工作目录
    volumeMounts:       #挂载到容器内部的存储卷配置
    - name: string      #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
      mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
      readOnly: boolean #是否为只读模式
    ports: #需要暴露的端口库号列表
    - name: string        #端口的名称
      containerPort: int  #容器需要监听的端口号
      hostPort: int       #容器所在主机需要监听的端口号,默认与Container相同
      protocol: string    #端口协议,支持TCP和UDP,默认TCP
    env:   #容器运行前需设置的环境变量列表
    - name: string  #环境变量名称
      value: string #环境变量的值
    resources: #资源限制和请求的设置
      limits:  #资源限制的设置
        cpu: string     #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
        memory: string  #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
      requests: #资源请求的设置
        cpu: string    #Cpu请求,容器启动的初始可用数量
        memory: string #内存请求,容器启动的初始可用数量
    lifecycle: #生命周期钩子
        postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
        preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
    livenessProbe:  #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
      exec:         #对Pod容器内检查方式设置为exec方式
        command: [string]  #exec方式需要制定的命令或脚本
      httpGet:       #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
        path: string
        port: number
        host: string
        scheme: string
        HttpHeaders:
        - name: string
          value: string
      tcpSocket:     #对Pod内个容器健康检查方式设置为tcpSocket方式
         port: number
       initialDelaySeconds: 0       #容器启动完成后首次探测的时间,单位为秒
       timeoutSeconds: 0          #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
       periodSeconds: 0           #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged: false
  restartPolicy: [Always | Never | OnFailure]  #Pod的重启策略
  nodeName: <string> #设置NodeName表示将该Pod调度到指定到名称的node节点上
  nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
  imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
  - name: string
  hostNetwork: false   #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  volumes:   #在该pod上定义共享存储卷列表
  - name: string    #共享存储卷名称 (volumes类型有很多种)
    emptyDir: {}       #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
    hostPath: string   #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
      path: string                #Pod所在宿主机的目录,将被用于同期中mount的目录
    secret:          #类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
      scretname: string
      items:
      - key: string
        path: string
    configMap:         #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
      name: string
      items:
      - key: string
        path: string

3.2 配置中的5个根属性

在k8s中,基本所有一级属性都是一样的,包括以下5部分: - apiVersion 《string》,由k8s内部定义,版本好必须是用 kubectl api-version 能查到的 - kind 《tring》 定义该配置是属于哪种资源类型,内部定义,必须是 kubectl api-resource可以查到的 - metadata 《Object》,资源标识和说明,有name、namespace、labels等 - status 状态信息,里面的内容不需要定义,由kubernetes自动生成 - spec 描述,这是配置中最重要的一部分,对各种资源配置的详细描述,以下是他的常用子属性 -containers <[]Object> 容器列表,用于定义容器的详细信息 - nodeName 根据nodeName的值将pod调度到指定的Node节点上 - nodeSelector 根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的Node 上 - hostNetwork 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网 络 - volumes <[]Object> 存储卷,用于定义Pod上面挂在的存储信息 - restartPolicy 重启策略,表示Pod在遇到故障的时候的处理策略

3.3 pod.spec.containers

主要有如下属性

1
2
3
4
5
6
7
8
name  <string>     # 容器名称,必要属性
image <string>     # 容器需要的镜像地址,必要属性
imagePullPolicy  <string> # 镜像拉取策略
command  <[]string> # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
args     <[]string> # 容器的启动命令需要的参数列表
env      <[]Object> # 容器环境变量的配置
ports    <[]Object>     # 容器需要暴露的端口号列表
resources <Object>      # 资源限制和资源请求的设置
imagePullPolicy 镜像拉取策略。共有三种,如下所示。如果镜像版本号是具体的,默认策略是IfNotPresent,如果版本号是latest,默认策略是Always - Always 总是从远程仓库拉取 - IfNotPresent 本地有则用本地的,没有则去远程仓库拉取 - Nerver 一直使用本地镜像,永远不会去远程仓库拉取,如果本地没有则报错 commandargs 启动命令,和参数。command中的内容在容器运行后立即执行,args的参数会传给command。command和args两项其实是实现覆盖Dockerfile中的ENTRYPOINT: - command和args均没有写,那么用Dockerfile的配置。 - command写了,但args没有写,那么Dockerfile默认的配置会被忽略,执行输入的command - command没写,但args写了,那么Dockerfile中配置的ENTRYPOINT的命令会被执行,使用当前args的参数 - command和args都写了,那么Dockerfile的配置被忽略,执行command并追加上args参数 env 环境变量,会直接写入到容器内部的环境变量,不是很推荐,推荐将这些配置单独存储在配置文件 ports 端口。端口有如下几个子属性

1
2
3
4
5
name         <string>  # 端口名称,如果指定,必须保证name在pod中是唯一的    
containerPort<integer> # 容器要监听的端口(0<x<65536)
hostPort     <integer> # 容器要在主机上公开的端口,如果设置,主机上只能运行容器的一个副本(一般省略) 
hostIP       <string>  # 要将外部端口绑定到的主机IP(一般省略)
protocol     <string>  # 端口协议。必须是UDP、TCP或SCTP。默认为“TCP”
resources 资源配额。对内存和cpu的资源进行限制,主要有两个子属性,两个属性都只能对cpu和memory进行配置,内存单位为用Gi、Mi、G、M等形式, - limits:用于限制运行时容器的最大占用资源,当容器占用资源超过limits时会被终止,并进行重启 - requests :用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动
apiVersion: v1
kind: Pod
metadata:
  name: pod-resources
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    resources: # 资源配额
      limits:  # 限制资源(上限)
        cpu: "2" # CPU限制,单位是core数
        memory: "10Gi" # 内存限制
      requests: # 请求资源(下限)
        cpu: "1"  # CPU限制,单位是core数
        memory: "10Mi"  # 内存限制

3.4 生命周期

容器从创建到结束的这个过程叫做生命周期,主要包括:包括pod创建过程,运行初始化容器过程,运行主容器,pod终止过程。 在生命周期中,pod会出现如下五种状态: - 挂起(Pending):apiserver已经创建了pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中 - 运行中(Running):pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成 - 成功(Succeeded):pod中的所有容器都已经成功终止并且不会被重启 - 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态 - 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致

3.4.1 pod的创建过程
1. 用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer
2. apiServer开始生成pod对象的信息并将信息存入etcd然后返回确认信息至客户端
3. apiServer开始反映etcd中的pod对象的变化其它组件使用watch机制来跟踪检查apiServer上的变动
4. scheduler发现有新的pod对象要创建,开始为Pod分配主机并将结果信息更新至apiServer
5. scheduler发现有新的pod对象要创建开始为Pod分配主机并将结果信息更新至apiServer
8. node节点上的kubelet发现有pod调度过来尝试调用docker启动容器并将结果回送至apiServer
6. apiServer将接收到的pod状态信息存入etcd中
3.4.2 pod的终止过程
1. 用户向apiServer发送删除pod对象的命令
2. apiServcer中的pod对象信息会随着时间的推移而更新在宽限期内默认30s),pod被视为dead
3. 将pod标记为terminating状态
4. kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
5. 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
6. 如果当前pod对象定义了preStop钩子处理器则在其标记为terminating后即会以同步的方式启动执行
7. pod对象中的容器进程收到停止信号
8. 宽限期结束后若pod中还存在仍在运行的进程那么pod对象会收到立即终止的信号
9. kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作此时pod对于用户已不可见
3.4.3 init container

pod.spec.initContainers 初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,它具有两大特征: 1. 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么kubernetes需要重启它直到成功完成 2. 初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行 init container 和container平级,pod.spec.initContainers,子属性和container类似 yaml apiVersion: v1 kind: Pod metadata: name: pod-initcontainer namespace: dev spec: containers: - name: main-container image: nginx:1.17.1 ports: - name: nginx-port containerPort: 80 initContainers: - name: test-mysql image: busybox:1.30 command: ['sh', '-c', 'until ping 192.168.109.201 -c 1 ; do echo waiting for mysql...; sleep 2; done;'] - name: test-redis image: busybox:1.30 command: ['sh', '-c', 'until ping 192.168.109.202 -c 1 ; do echo waiting for reids...; sleep 2; done;']

3.4.4 钩子函数

pod.spec.containers.lifecycle.postStart pod.spec.containers.lifecycle.preStop 钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码,kubernetes在主容器的启动之后和停止之前提供了两个钩子函数: - poststart:容器创建之后执行,如果失败了会重启容器 - prestop:容器终止之前执行,执行完成之后容器将成功终止,在其完成之前会阻塞删除容器的操作 钩子函数只支持三种动作

  1. exec:执行一次命令
    1
    2
    3
    4
    5
    6
        lifecycle:
          postStart: 
            exec:
              command:
              - cat
              - /tmp/healthy
    
  2. TCPSocket:访问指定的socket

1
2
3
4
    lifecycle:
      postStart:
        tcpSocket:
          port: 8080
3. HTTPGet:向某url发起http请求
1
2
3
4
5
6
7
    lifecycle:
      postStart:
        httpGet:
          path: / #URI地址
          port: 80 #端口号
          host: 192.168.109.100 #主机地址
          scheme: HTTP #支持的协议,http或者https

3.4.5 容器探测

pod.spec.containers.livenessProbe pod.spec.containers.readinessProbe 容器探测用于检测容器中的应用实例是否正常工作,是保障业务可用性的一种传统机制。如果经过探测,实例的状态不符合预期,那么kubernetes就会把该问题实例" 摘除 ",不承担业务流量。kubernetes提供了两种探针来实现容器探测,分别是: - liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器 - readiness probes:就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s不会转发流量

livenessProbe 决定是否重启容器,readinessProbe 决定是否将请求转发给容器。

容器探测也提供了和钩子函数类似的三种动作: 1. exec:执行一次命令

1
2
3
4
5
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
2. TCPSocket:访问指定的socket
1
2
3
    livenessProbe:
      tcpSocket:
        port: 8080
3. HTTPGet:向某url发起http请求
1
2
3
4
5
6
    livenessProbe:
      httpGet:
        path: / #URI地址
        port: 80 #端口号
        host: 127.0.0.1 #主机地址
        scheme: HTTP #支持的协议,http或者https
除了如上面三种常用的动作,容器探测还有5种用于辅助它们的动作: - initialDelaySeconds :容器启动后等待多少秒执行第一次探测 - timeoutSeconds :探测超时时间。默认1秒,最小1秒 - periodSeconds :执行探测的频率。默认是10秒,最小1秒 - failureThreshold :连续探测失败多少次才被认定为失败。默认是3。最小值是1 - successThreshold :连续探测成功多少次才被认定为成功。默认是1

3.4.6 重启策略

3.5 pod 调度

默认情况下,pod运行在哪个node上,不是随缘的,而是经过一系列算法的计算而得出的,这个过程不受人为控制。但在某些情况下,需要对pod进行固定的调度,把pod运行在某个指定的node上,pod调度就出来了,可以通过pod调度把pod运行到指定的node节点上,kubernetes提供了四大类调度方式: - 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出 - 定向调度:NodeName、NodeSelector - 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity - 污点(容忍)调度:Taints、Toleration

3.5.1 定向调度 Directional Scheduling

定向调度是强制调度,就算是不正常运行的node,也还是会调度到该节点上。定向调度可以通过指定nodeName或者指定nodecSelector来进行定向调度。定向调度的优先级高于亲和性调度!!!

nodeName pod.spec.nodeName 将Pod调度到指定的Name的Node节点上。

nodeSelector pod.spec.nodeSelector 将pod调度到添加了指定标签的node节点上,通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。

示例

apiVersion: v1
kind: Pod
metadata:
  name: pod-schedule-nodeselector1
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.26.3
    ports:
    - name: nginx-port
      containerPort: 80
  nodeName: node1   # 定向调度到 node1 节点
  nodeSelector:  # 定向调度到节点上存在标签 nodeenv: dev 的节点
      nodeenv: dev

3.5.2 亲和性调度 Affinity Scheduling

定向调度,有一个弊端,如若指定的node异常,而且有其他正常的node处于空闲中,pod不会“人性化的”调度到正常的node节点上,而亲和性调度恰恰解决的来这个问题。亲和性调度.... Affinity主要分为三类: - nodeAffinity(node亲和性): 以node为目标,解决pod可以调度到哪些node的问题 - podAffinity(pod亲和性) : 以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题 - podAntiAffinity(pod反亲和性) : 以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题

关于亲和性(反亲和性)使用场景的说明: 亲和性:如果两个应用频繁交互,那就有必要利用亲和性让两个应用的尽可能的靠近,这样可以减少因网络通信而带来的性能损耗。 反亲和性:当应用采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。

NodeAffinity pod.spec.affinity.nodeAffinity 配置模板

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution:  #Node节点必须满足指定的所有规则才可以,相当于硬限制
        nodeSelectorTerms:  #节点选择列表
          matchFields:    #按节点字段列出的节点选择器要求列表  和matchExpressions二选一
          matchExpressions:   #按节点标签列出的节点选择器要求列表(推荐)
            key:    #键
            values: #值
            operator:  #关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
      preferredDuringSchedulingIgnoredDuringExecution: #优先调度到满足指定的规则的Node,相当于软限制 (倾向)
        - weight:  #倾向权重,在范围1-100。
          preference:   #一个节点选择器项,与相应的权重相关联
            matchFields:   #按节点字段列出的节点选择器要求列表    和matchExpressions二选一
            matchExpressions:   #按节点标签列出的节点选择器要求列表(推荐)
              key:    #键
              values: #值
              operator: #关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt

示例

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity1
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      #硬限制
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["",""]中的标签
          - key: user
            operator: In
            values:
              - moloom
      #软限制
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          preference:
            matchExpressions:
              - key: nodeenv
                operator: NotIn
                values: ["devdddv"]

  1. 在亲和性调度中,required和preferred都设置时,只可能会调度到满足required所有条件的node上,在这些node中优先调度符合preferred条件的node
  2. 如果同时定义了定向调度nodeSelector和亲和性调度nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上
  3. 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
  4. 如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功
  5. 如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化

PodAffinity pod.spec.affinity.podAffinity 模板

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity
  namespace: dev
spec:
  containers:
    - name: nginx
      image: nginx:1.17.1
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution: #硬限制
        namespaces: #指定参照pod的namespace
        #topologyKey用于指定调度时作用域,
        #如果指定为kubernetes.io/hostname,那就是以Node节点为区分范围
        #如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分
        topologyKey: #指定调度作用域
        labelSelector: #标签选择器
          matchExpressions: #按节点标签列出的节点选择器要求列表(推荐)
            key:
            values:
            operator: #关系符 支持In, NotIn, Exists, DoesNotExist.
          matchLabels: #指多个matchExpressions映射的内容
      preferredDuringSchedulingIgnoredDuringExecution: #软限制
        podAffinityTerm: #选项
          namespaces:
          topologyKey:
          labelSelector:
            matchExpressions:
              key:
              values:
              operator: #关系符 支持In, NotIn, Exists, DoesNotExist.
            matchLabels:
          weight: #倾向权重,在范围1-100

示例

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: podenv
            operator: In
            values: ["pro","yyy"]
        topologyKey: kubernetes.io/hostname

PodAntiAffinity pod.spec.affinity.podAntiAffinity 反pod亲和度调度,不和指定的pod所在一个node,配置基本和podAffiniity一样

3.5.3 污点 Taint 和容忍 Toleration

污点 Taint 用于定义 node,设置 node 对 pod 的接受程度。taints有三种: - PreferNoSchedule:kubernetes 将尽量避免把 Pod 调度到具有该污点的 Node 上,除非没有其他节点可调度 - NoSchedule:kubernetes 将不会把 Pod 调度到具有该污点的 Node 上,但不会影响当前 Node 上已存在的 Pod - NoExecute:kubernetes 将不会把 Pod 调度到具有该污点的 Node 上,同时也会将 Node 上已存在的 Pod 驱离

注:如若使用 pod.sepc.nodeName 指定node,则污点将不起作用,pod会直接调度到指定的node!

1
2
3
4
5
6
# 添加污点,effct就是上面三种属性,注意大小写!     value可以省略
kubectl taint nodes node1 key=value:effect
# 去除污点
kubectl taint nodes node1 key:effect-
# 去除所有污点
kubectl taint nodes node1 key-

master 节点,默认是 NoSchedule,这也是为什么pod不会调度到master节点

容忍 Toleration pod.spec.tolerations 容忍,字面意思,对污点Taints的容忍,对pod定义,用于将pod调度到有污点的node上去。

可配置项

1
2
3
4
5
   key       # 对应着要容忍的污点的键,空意味着匹配所有的 key
   value     # 对应着要容忍的污点的值,空意味着匹配所有的 value
   operator  # key-value的运算符,支持Equal和Exists(默认)
   effect    # 对应污点的effect,空意味着匹配所有 effect
   tolerationSeconds   # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间

for example

apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 添加容忍
  - key: "node-role.kubernetes.io/master"        # 要容忍的污点的key
    operator: "Equal" # 操作符
    value: "true"    # 容忍的污点的value
    effect: "NoExecute"   # 添加容忍的规则,这里必须和标记的污点规则相同

  tolerations: # 容忍所有的污点
    operator: "Exists" # 操作符

  tolerations: # 没有指定 value 时,表示容忍所有的污点 value,也就是不管 value 是什么,都容忍它
  - key: "key2"
    operator: "Exists" # 操作符
    effect: "NoExecute"   # 这规则有和没有都一样的效果

  tolerations:  # 不指定 effect,表示任何 effect 的规则都能容忍
  - key: "key2"
    operator: "Exists"

4. Pod控制器

pod控制器用于管理pod。通常创建pod有两种方式,一种是直接创建pod,第二种是通过控制器创建pod。 - ReplicationController: 比较老的控制器,已被废弃 - ReplicaSet: 保证一定数量的pod正常运行,支持pod数量的扩缩容,镜像版本升级(通过升级镜像来升级pod) - Deployment: 基于ReplicaSet实现,拥有ReplicaSet的功能,而且还支持滚动升级和回退版本 - Horizontal Pod Autoscaler: 根据机器负载,自动水平调整pod的数量,实现削峰填谷 - DaemonSet: 在指定的node上运行一个副本,一般用于守护进程类的任务 - Job: 一次性执行任务,执行完后立即退出 - Cronjob: 定时执行任务,不需要持续后台运行 - StatefulSet: 管理有状态的任务

4.1 ReplicaSet

ReplicaSet保证一定数量的pod正常运行,支持pod数量的扩缩容,镜像版本升级(通过升级镜像来升级pod)。 配置模板

apiVersion: apps/v1 # 版本号
kind: ReplicaSet # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: #标签
    controller: rs
spec: # 详情描述
  replicas: 3 # 副本数量
  selector: # 选择器,通过它指定该控制器管理哪些pod,下面俩二选一
    matchLabels:      # Labels匹配规则
      app: nginx-pod
    matchExpressions: # Expressions匹配规则
      - {key: app, operator: In, values: [nginx-pod]}
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80
示例
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: pc-replicaset
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
        - name: nginx
          image: nginx:1.17.1
          ports:
          - containerPort: 80
命令
#查看 replicaSet 
kubectl get rs pc-replicaset -n dev -o wide
#修改 yaml文件从而重新配置replicaSet
#这个操作可以 扩缩容、镜像升降级!
kubectl edit rs pc-replicaset -n dev
#扩缩容
kubectl scale rs pc-replicaset --replicas=2 -n dev
#镜像升降级
kubectl set image rs pc-replicaset nginx=nginx:1.16.1  -n dev
#删除rs控制器及其pod
kubectl delete rs pc-replicaset -n dev
#删除rs控制器及其pod
kubectl delete -f pc-replicaset.yaml
#删除rs控制器,不删除其pod
kubectl delete rs pc-replicaset -n dev --cascade=false

replicaSet 的镜像升级设置来之后,正常运行的pod不会升级,还是保持原来版本,只有从该 rs controller 新起来的pod才会是最新版本!

4.2 Deployment

deployment是基于 replicaSet 实现的,拥有 rs 的全部功能。在此之上,还支持滚动更新、回滚版本和发布的停止继续。 配置模板

apiVersion: apps/v1 # 版本号
kind: Deployment # 类型
metadata: # 元数据
  name: # rs名称
  namespace: # 所属命名空间
  labels: #标签
    controller: deploy
spec: # 详情描述
  replicas: 3 # 副本数量
  revisionHistoryLimit: 3 # 保留历史版本
  paused: false # 暂停部署,默认是false
  progressDeadlineSeconds: 600 # 部署超时时间(s),默认是600
  strategy: # 策略
    type: RollingUpdate # 滚动更新策略,还有一种重建策略 Recreate
    rollingUpdate: # 滚动更新
      maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比,也可以为整数
      maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
  selector: # 选择器,通过它指定该控制器管理哪些pod
    matchLabels: # Labels匹配规则
      app: nginx-pod
    matchExpressions: # Expressions匹配规则
      - { key: app, operator: In, values: [nginx-pod] }
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
        - name: nginx
          image: nginx:1.17.1
          ports:
            - containerPort: 80
demo
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pc-deployment
  namespace: dev
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nginx-pod
  revisionHistoryLimit: 3
  paused: false
  progressDeadlineSeconds: 600
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
#      maxUnavilable: 25%
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
        - name: nginx
          image: nginx:1.17.1
          ports:
            - containerPort: 80

针对版本升级相关功能,由 rollout 管理,有如下几个选项: - status 显示当前升级状态 - history 显示 升级历史记录 - pause 暂停版本升级过程 - resume 继续已经暂停的版本升级过程 - restart 重启版本升级过程 - undo 回滚到上一级版本(可以使用--to-revision回滚到指定版本)

1
2
3
4
# 查看当前升级版本的状态
kubectl rollout status deploy pc-deployment -n dev
#金丝雀发布
kubectl rollout undo deploy pc-deployment -n dev &&kubectl rollout pause deploy pc-deployment -n dev

金丝雀发布:金丝雀发布就是在版本升降级时,先升一小部分运行,看看运行情况怎么样。如果正常则再一部分一部分地升级。金丝雀发布虽然麻烦,但是可以尽量减少升降级时造成系统不可用概率。

4.3 Horizontal Pod Autoscaler

在Deploy中,已经可以通过 scale 命令来实现扩缩容了,但是只能手动扩缩容,显得不够智能,为此,Horizontal Pod Autoscaler (HPA)出现了。hpa 能监控pod使用情况,如果pod负载达到一个阈值,hpa 可以自动的扩容,在流量不多时,为了节省资源,会自动缩容到原始pod个数,真正实现智能"削峰填谷"。

4.3.1 安装 metrics-server

metrics-server 仓库

#拉取yaml配置。先去仓库看看最新版本是多少,在下面地址中把版本改成最新版本号
wgethttps://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.1/components.yaml
#修改配置 components.yaml
#找到 name为metrics-server的pod,在spec.template.spec.containers.args添加
- --kubelet-insecure-tls
#在spec.template.spec.containers添加
hostNetwork: true

#运行时可能会报错,看是否是端口被占用了,kubelet也默认用 10250 端口,如果是则更改端口配置。

#运行pod
kubectl apply -f components.yaml
#查看运行状态
kubectl get pods -n kube-system
#测试命令
kubectl top nodes

4.3.2 准备deploy

1
2
3
4
#创建deploy
kubectl create deploy nginx --image=nginx:1.17.1 -n dev
#暴露端口
kubectl expose deploy nginx --type=NodePort --port=80 -n dev

4.3.3 部署hpa

创建 pc-hpa-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
    name: demo-nginx
    namespace: dev
spec:
    replicas: 1
    selector:
        matchLabels:
            app: nginx-pod
    paused: false
    template:
        metadata:
            labels:
                app: nginx-pod
        spec:
            containers:
                - name: nginx
                  image: nginx: 1.17.1
                  ports:
                    - containerPort: 80
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: pc-hpa
  namespace: dev
spec:
  minReplicas: 1    #最小pod数量
  maxReplicas: 10   #最大pod数量
  targetCPUUtilizationPercentage: 3   #CPU使用率指标
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx

注意:被hpa监控的资源,必须要配置 resources.requests.cpu 设置cpu下限,否则get hpa 时,TARGETS 中会显示

运行和测试

1
2
3
4
#运行hpa
kubectl create -f pc-hpa-nginx.yaml
#查看hpa
kubectl get hpa -n dev
用jmeter进行测试

4.4 DaemonSet

daementSet 控制器可以保证在每个集群上(不包括 control-panel)都运行一个副本,适用于日志、监控等场景。 配置模板

apiVersion: apps/v1 # 版本号
kind: DaemonSet # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: #标签
    controller: daemonset
spec: # 详情描述
  revisionHistoryLimit: 3 # 保留历史版本
  updateStrategy: # 更新策略
    type: RollingUpdate # 滚动更新策略
    rollingUpdate: # 滚动更新
      maxUnavailable: 1 # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
  selector: # 选择器,通过它指定该控制器管理哪些pod
    matchLabels:      # Labels匹配规则
      app: nginx-pod
    matchExpressions: # Expressions匹配规则
      - {key: app, operator: In, values: [nginx-pod]}
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80
demo
apiVersion: apps/v1
kind: DaemonSet      
metadata:
  name: pc-daemonset
  namespace: dev
spec: 
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1

4.5 Job

Job执行完任务后自动会销毁,适用于一次性任务。Job在执行成功结束时,会记录成功了的pod数量,当数量达到预先指定的值时,Job将完成执行。 Job的 restartPolicy 不同于其他控制器,Job由于执行完后就销毁,所以不能设置 Always ,只能设置 OnFailure 和 Never。在设置为 OnFailure 参数时,pod故障了只会重启容器,不会新建pod,failed次数不变。在设置为 Nerver 时,pod故障了会新建pod,并且故障的pod不会被删除和重启,failed次数加1。

配置模板

apiVersion: batch/v1  # 版本号
kind: Job   # 类型
metadata:   # 元数据
  name:   # rs名称
  namespace:  # 所属命名空间
  labels:   #标签
    controller: job
spec:   # 详情描述
  completions: 1  # 指定job需要成功运行Pods的次数。默认值: 1
  parallelism: 1  # 指定job在任一时刻应该并发运行Pods的数量。默认值: 1
  activeDeadlineSeconds: 30   # 指定job可运行的时间期限,超过时间还未结束,系统将会尝试进行终止。
  backoffLimit: 6   # 指定job失败后进行重试的次数。默认是6
  manualSelector: true    # 是否可以使用selector选择器选择pod,默认是false
  selector:   # 选择器,通过它指定该控制器管理哪些pod
    matchLabels:  # Labels匹配规则
      app: counter-pod
    matchExpressions:     # Expressions匹配规则
      - { key: app, operator: In, values: [counter-pod] }
  template:     # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: counter-pod
    spec:
      restartPolicy: Never  # 重启策略只能设置为Never或者OnFailure
      containers:
        - name: counter
          image: busybox:1.30
          command:
            ["bin/sh", "-c", "for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done"]

demo

apiVersion: batch/v1
kind: Job      
metadata:
  name: pc-job
  namespace: dev
spec:
  manualSelector: true
  selector:
    matchLabels:
      app: counter-pod
  template:
    metadata:
      labels:
        app: counter-pod
    spec:
      restartPolicy: Never
      containers:
      - name: counter
        image: busybox:1.30
        command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 3;done"]

job每个时刻只会运行 parallelism 个pod,如果没有设置则默认为1。

4.6 CronJob (CJ)

CronJob 定时执行一次Job任务,它基于 Job ,所以它也是在达到完成次数时会销毁。有点类似 Linux 系统中的 crontab 命令。

配置模板

apiVersion: batch/v1beta1 # 版本号
kind: CronJob # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: #标签
    controller: cronjob
spec: # 详情描述
  schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
  concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
  failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
  successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
  startingDeadlineSeconds: # 启动作业错误的超时时长
  jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象;下面其实就是job的定义
    metadata:
    spec:
      completions: 1
      parallelism: 1
      activeDeadlineSeconds: 30
      backoffLimit: 6
      manualSelector: true
      selector:
        matchLabels:
          app: counter-pod
        matchExpressions: 规则
          - {key: app, operator: In, values: [counter-pod]}
      template:
        metadata:
          labels:
            app: counter-pod
        spec:
          restartPolicy: Never 
          containers:
          - name: counter
            image: busybox:1.30
            command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 20;done"]
CronJob 的定时设置
schedule: cron表达式,用于指定任务的执行时间
    */1    *      *    *     *
    <分钟> <小时> <日> <月份> <星期>

    分钟 值从 0 到 59.
    小时 值从 0 到 23.
    日 值从 1 到 31.
    月 值从 1 到 12.
    星期 值从 0 到 6, 0 代表星期日
    多个时间可以用逗号隔开; 范围可以用连字符给出;*可以作为通配符; /表示每...
concurrencyPolicy:
    Allow:   允许Jobs并发运行(默认)
    Forbid:  禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行
    Replace: 替换,取消当前正在运行的作业并用新作业替换它
demo
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: pc-cronjob
  namespace: dev
  labels:
    controller: cronjob
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    metadata:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: counter
            image: busybox:1.30
            command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 3;done"]

5. Service

前面已经学习了 pod 和 pod 控制器,我们现在能很好地使用 k8s 创建 pod 来执行任务了。但是还有些不足,首先,这些 pod 只能在集群内被访问,而且,pod只是临时资源,你无法实时辨别哪些pod是健康与否,它们并不可靠。pod故障重建后,pod 的 ip 会发生变化,原来的ip就不能用了,这就导致了资源访问者无法跟踪和访问要连接的 pod 的 ip 地址,而Service 就是来解决这个问题的! Service 在 k8s 中是一个对象,它一种抽象的概念,实现者是 kube-proxy (除 type 为 ExternalName 外)。service 提供一个入口地址用于访问 service 所匹配的 pod ,且支持负载均衡算法。

5.1 模板和示例

配置模板

apiVersion: v1  # 资源版本
kind: Service  # 资源类型
metadata: # 元数据
  name: service # 资源名称
  namespace: dev # 命名空间
spec: # 描述
  selector: # 标签选择器,用于确定当前service代理哪些pod
    app: nginx
  type: # Service类型,指定service的访问方式
  clusterIP:  # 虚拟服务的ip地址
  sessionAffinity: # session亲和性,支持ClientIP、None两个选项;默认是 None,设置了 ClientIp ,请求会在接下来的一段时间,只访问同一个的 node,而不是轮询每个 node。
  internalTrafficPolicy: # 支持 Cluster 和 Local俩选项;Cluster是默认选项,允许所有的流量进行访问;Local ,只会访问当前节点的这个 svc 所映射的 pod,如果当前节点没有,则访问失败。 
  externalTrafficPolicy: # 支持 Cluster 和 Local俩选项;Cluster是默认选项,允许所有的流量进行访问;Local ,只会访问当前节点的这个 svc 所映射的 pod,如果当前节点没有,则访问失败。 
  ports: # 端口信息
    - name: nginx-port  # 端口名称
      protocol: TCP 
      port: 3017  # service端口
      targetPort: 5003 # pod端口
      nodePort: 31122 # 宿主机端口,官方不建议指定端口!
    - name: nginx-port2 # 端口名称
      protocol: TCP 
      port: 3018  # service端口
      targetPort: 5003 # pod端口
      nodePort: 31122 # 宿主机端口,官方不建议指定端口!

示例

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: dev
  labels:
    app.kubernetes.io/name: proxy
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
      - containerPort: 80
        name: http-web-svc
---
#有 selector 的 svc
apiVersion: v1
kind: Service
metadata:
  namespace: dev
  name: nginx-service
spec:
  selector:
    app.kubernetes.io/name: proxy
  ports:
   - name: name-of-service-port
    protocol: TCP
    port: 80    # service port
    targetPort: http-web-svc  # container port ,可以是pod的 port name ,也可以直接填 端口号

5.2 selector 选择算符

service.spec.selector service 分有 selector 和无 selector 的 service。顾名思义,区别就是在 service 中有没有 selector 这项,有就是 有选择算符的,没有就是 无选择算符的。如上简单示例就是有选择算符的。 上面也介绍过,service 就是为某类 pod 提供一个访问入口的。何为“某类”?就是通过 selector 选择算符来设置的。

5.2.1 有选择算符

有选择算符代表你这个 svc 是指向 pod 集合的。k8s 控制平面会为有选择算符的 svc 创建 EndpointSlice 对象,这个对象包含 service 到 pod 的引用,也就是 svc 和 pod 之间的映射关系。

5.2.2 无选择算符

没有选择算符的 svc 只提供一个 “入口”,并没有转到某个资源。相当于申请了一个公网 ip ,缺没有部署任何服务。

无选择算符的 svc ,控制平面不会创建 EndpointSlice ,但是可以自定义 EndpointSlice。自定义的 EndpointSlice 可以映射到其他类型的资源, 包括在集群外运行的资源。EndpointSlice 在下面章节会详细解释,这里只需了解即可。

Kubernetes API 服务器不允许将流量代理到未被映射至 Pod 上的端点。由于此约束,当 Service 没有选择算符时,诸如 kubectl port-forward service/ forwardedPort:servicePort 之类的操作将会失败。 这可以防止 Kubernetes API 服务器被用作调用者可能无权访问的端点的代理。

5.3 EndpointSlice 端点切片

EndpointSlice 是表示某个 svc 的后端网络端点的对象资源,通过 EndpointSlice 可以建立 svc 与 pod 之间的映射关系。需注意:svc 只会对 就绪状态 的 pod 进行映射!若要对 NotReady 状态的 pod 进行映射,需设置 svc.spec.publishNotReadyAddresses: true 。 一般情况下,在 svc 配置中,若设置了选择算符,在创建 svc 时 control-plane 会自动为 svc 创建 EndpointSlice,无选择算符的 svc ,control-plane 不会创建 EndpointSlice ,但是可以自定义 EndpointSlice,自定义的 EndpointSlice 可以映射到其他类型的资源。

5.3.1 示例

创建一个 svc ,把所有访问此 svc 的请求 转到 百度

apiVersion: v1
kind: Service
metadata:
  name: svc-to-baidu
  namespace: dev            #注意,必须要和 endpointSlice 在同一个 namespace!!!
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80      #指定 svc 的端口
      targetPort: 443   # 指定 endpointSlice 的端口,需和 endpointSlice 中的ports.port 一样
---
# EndpointSlice
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: svc-to-baidu-001   #一般是svc name的延伸
  namespace: dev
  labels:       #绑定 svc 
    kubernetes.io/service-name: svc-to-baidu
    endpointslice.kubernetes.io/managed-by: claster-admins       #k8s识别 endpointslice的标签,endpointslice这章会解释
addressType: IPv4               #支持三种地址类型:IPv4 IPv6 FQDN (完全合格的域名)
ports:
  - name: 'http'    #与svc 端口name一致
    appProtocol: http
    protocol: TCP
    port: 80  #和targetPort 一致
endpoints:    #目标地址列表,可以有多个 addresses 
  - addresses:
    - "180.101.50.188"      #百度的ip
      #- addresses:
      #- "10.1.2.3"

当为 Service 创建 EndpointSlice 对象时,可以为 EndpointSlice 使用任何名称。 一个名字空间中的各个 EndpointSlice 都必须具有一个唯一的名称。通过在 EndpointSlice 上设置 kubernetes.io/service-name 标签可以将 EndpointSlice 链接到 Service。

官方文档建议:自定义的 endpointSlice ,应添加 endpointslice.kubernetes.io/managed-by 标签,用于标示管理 EndpointSlice 的控制器或实体。打上该标签易于集群内的不同控制器来管理不同的 EndpointSlice 对象。标签值需要依据如下类别来设置。 - 如果是使用 kubectl 来创建的: 自己起一个 - 第三方工具创建的:值为全小写的工具名称,并将空格和其他标点符号更改为短划线 (-) - 自定义控制器创建的:使用类似于 "my-domain.example/name-of-controller" 的值

默认情况下,控制面创建和管理的 EndpointSlice 将包含不超过 100 个端点。 你可以使用 kube-controller-manager 的 --max-endpoints-per-slice 标志设置此值,最大值为 1000。

5.3.2 一些注意事项

注:端点 IP 地址必须不是:本地回路地址(IPv4 的 127.0.0.0/8、IPv6 的 ::1/128) 或链路本地地址(IPv4 的 169.254.0.0/16 和 224.0.0.0/24、IPv6 的 fe80::/64)。

端点 IP 地址不能是其他 Kubernetes 服务的集群 IP,因为 kube-proxy 不支持将虚拟 IP 作为目标地址。

Kubernetes API 服务器不允许将流量代理到未被映射至 Pod 上的端点。由于此约束,当 Service 没有选择算符时,诸如 kubectl port-forward service/ forwardedPort:servicePort 之类的操作将会失败。 这可以防止 Kubernetes API 服务器被用作调用者可能无权访问的端点的代理。不过,ExternalName 是 svc 的特例,它没有选择算符,也没有 endpointslice ,它是使用的 DNS 名称!

5.3.3 EndpointSlice 和 Endpoints的区别

两者都是提供 svc 和 pod 之前的映射关系的资源对象。svc 和 Endpoints 是一对一的关系,一个 svc 中的所有 Endpoint 都只能存到同一个 Endpoints 对象中,容易导致 Endpoints 对象过于巨大,不好维护和更新。svc 和 EndpointSlice 是一对多的关系,一个 svc 可以有多个 EndpointSlice,使用 EndpointSlices 时,添加或移除单个 Pod 对于正监视变更的客户端会触发相同数量的更新,而不是像 Endpoints 所有的映射都会更新,此外,EndpointSlices 还支持围绕双栈网络和拓扑感知路由等新功能的创新。 Endpoints 只支持1000个端点映射,超出部分不进行映射。对 EndpointSlice 来说,若超过了 100 个端点,则会创建一个新的 EndpointSlice,可以使用 kube-controller-manager 的 --max-endpoints-per-slice 标志来更改每个 EndpointSlice 最大映射的数量,最大值为 1000,但不推荐使用,会影响代理的性能。

5.4 Service 类型

service.spec.type type类型有4种:ClusterIP、NodePort、LoadBalancer 和 ExternalName。 service_type

5.4.1 ClusterIP

service.spec.clusterIP 默认模式,当 service.spec.type 没有赋值时,默认为 ClusterIP 。k8s会为 svc 分配一个集群内部 ip ,仅供集群内部访问。

5.4.1.1 Headless Service 无头服务

若设置 service.spec.clusterIP: none 则不会分配 IP 地址,该 svc 也变成了 Headless Services 无头服务 每当 svc 被创建时,会默认添加一条 DNS 记录到 coreDNS 服务。域名格式为 svcName.nameSpace.svc.cluster.local 例: svc-to-baidu.dev.svc.cluster.local,该域名指向当前 svc 。 Headless Service 通过内部 DNS 记录报告各个 Pod 的端点 IP 地址,DNS 如何自动配置取决于 Service 是否定义了选择器。 1. 带选择算符的服务:Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象,并且修改 DNS 配置返回 A 或 AAAA 记录(IPv4 或 IPv6 地址), 这些记录直接指向 Service 的后端 Pod 集合。 2. 无选择算符的服务 - 对于 type: ExternalName Service,查找和配置其 CNAME 记录 - 对于其他类型的 svc ,创建对应的 A/AAAA DNS 记录

CNAME(Canonical Name) 是一种 DNS 记录类型,用于将一个域名映射到另一个域名。

5.4.2 NodePort

分配一个 node 的端口给 svc ,可以通过任一 node:port 方式访问 svc 。

5.4.3 LoadBalance

使用云平台的负载均衡器向外部公开 Service。Kubernetes 不直接提供负载均衡组件; 你必须提供一个,或者将你的 Kubernetes 集群与某个云平台集成。来自外部负载均衡器的流量将被直接重定向到后端各个 Pod 上,云平台决定如何进行负载平衡。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  clusterIP: 10.0.171.239
  type: LoadBalancer
  allocateLoadBalancerNodePorts: true  #默认为 true ,设置 false 来禁止 node 节点的端口分配,前提是负载均衡器能实现直接将流量路由到 Pod 而不是 node 端口。若已经分配了端口后,把该项设为 false,那么端口不会自动释放,得手动在 svc 资源清单中删除 nodePorts 项来释放端口。123
status:     #负载均衡器信息
  loadBalancer:
    ingress:
    - ip: 192.0.2.127
k8s 允许使用其他非官方的负载均衡器,通过 service.spec.loadBalancerClass 来设置 。如果使用默认的负载均衡器则会忽略这个属性;

对于 type: LoadBalancer 的 Service,控制器可以设置 .status.loadBalancer.ingress.ipMode。 .status.loadBalancer.ingress.ipMode 指定负载均衡器 IP 的行为方式。 此字段只能在 .status.loadBalancer.ingress.ip 字段也被指定时才能指定。 - 如果流量被传递到节点,然后 DNAT 到 Pod,则目的地将被设置为节点的 IP 和节点端口; - 如果流量被直接传递到 Pod,则目的地将被设置为 Pod 的 IP 和端口。

如果 LoadBalance 类型的 svc 定义了多个端口时,通过 MixedProtocolLBService 来开启或关闭是否支持使用不同的协议。V1.24 起默认开启。

5.4.4 ExternalName

将服务映射到 externalName 字段的内容(例如,映射到主机名 api.foo.bar.example),集群不会为之创建任何类型代理。在访问时,重定向发生在 DNS 级别,而不是通过代理或转发来完成。

1
2
3
4
5
6
7
8
9
# 定义一个 svc 映射到 img.moloom.com 服务器
apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: dev
spec:
  type: ExternalName
  externalName: img.moloom.com

不能映射到 https 服务,会报:SSL 证书未包含请求的主机名!

5.5 Gateway 网关

5.5.1 安装 Gateway API CRDs

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
# 下载 istio 控制器
curl -L https://istio.io/downloadIstio | sh -
tar zxvf istio*
sudo mv -f istio-* /opt
# 建立软连接
sudo ln -sfn /opt/istio-1.24.2/bin/istioctl /usr/local/bin/istioctl
# 安装转发插件 socat
sudo apt-get install socat
# 查看 socat 版本信息
socat -v

5.6 Ingress

ingress 是把集群中服务向外部暴露,供外部访问的一个资源对象,ingress 定义从外部访问集群服务的规则(例如路径转发、域名转发、TLS 等)。下面是 ingress 的流量走向示意图。

ingress

5.6.1 Resource 资源后端

ingress 为 service 提供集群外部的可访问 URL,同时支持对流量进行负载均衡。ingress 不仅可以转发外部请求到 service,还能转发到 Resource,Resource 是一个 ObjectRef 对象,指向同 ns 下面的另一个资源对象。Resource 和 service 是互斥的,一个 ingress 只能转发到其中一个。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-resource-backend
spec:
  defaultBackend:
    resource:
      apiGroup: moarch.com
      kind: StorageBucket
      name: static-assets
  rules:
    - http:
        paths:
          - path: /icons
            pathType: ImplementationSpecific
            backend:
              resource:
                apiGroup: k8s.example.com
                kind: StorageBucket
                name: icon-assets

5.6.2 Path Type 路径类型

Ingress.spec.rules.http.paths.path ingress 当前支持的路径类型有三种: 1. ImplementationSpecific:该路径类型的匹配方法取决于 ingressClass,具体实现可以将其作为单独的 pathType 处理或者作与 Prefix 或 Exact 类型相同的处理。 2. Exact:精确匹配 URL 路径,且区分大小写!例:path: /foo,请求路径为:/foo/,不匹配 3. Prefix:基于以 / 分隔的 URL 路径前缀匹配。匹配区分大小写!类似于 nginx 的匹配规则

在某些情况下,ingress 中会有多条路径与同一个请求匹配。这时匹配路径最长者优先。 如果仍然有两条同等的匹配路径,则 Exact 路径类型 优先于 Prefix 路径类型。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-match-demo
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: moarch.com
    http:
      paths:
      # 最精确的匹配(Exact),优先级最高
      - path: /api/v1/user
        pathType: Exact
        backend:
          service:
            name: svc-c
            port:
              number: 80

      # 较长的前缀匹配,优先级中等
      - path: /api/v1
        pathType: Prefix
        backend:
          service:
            name: svc-b
            port:
              number: 80

      # 最短的前缀匹配,优先级最低
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: svc-a
            port:
              number: 80

5.6.3 主机名通配符

不止 URL 的路径可以匹配,host 主机名也可以匹配。host 默认是精确匹配,如 moarch.com,或者使用通配符来匹配,如 *.moarch.com

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
spec:
  rules:
  - host: "docs.moarch.com"
    http:
      paths:
      - pathType: Prefix
        path: "/docs"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: "*.moarch.com"
    http:
      paths:
      - pathType: Prefix
        path: "/main"
        backend:
          service:
            name: service2
            port:
              number: 80

5.6.4 Ingress Class

ingress Class 是 Kubernetes 1.18 引入的资源对象,用于定义和选择 ingress controller 来实际处理 ingress 转发规则,是 ingress 资源与实际控制器之间的桥梁。每个 ingress 都可以指定一个 ingress Class。 s IngressClass.spec.controller 是一个部署在集群中的组件(通常是 Pod),它根据 ingress 资源的内容,配置自己的反向代理服务(如 NGINX、Traefik)来实现真正的转发逻辑。

IngressClass.spec.parameters 的默认作用域是 Cluster,表示 IngressClass 所引用的即是一个集群作用域的资源。如果你将 .spec.parameters.scope 字段设为了 Namespace,那么该 IngressClass 将会引用一个名字空间作用域的资源。 .spec.parameters.namespace 必须和此资源所处的名字空间相同。

5.6.4.1常见的 Ingress 控制器

常见的 ingress 控制器有以下几种: - NGINX Ingress Controller: - APISIX Ingress :是一个基于 Apache APISIX 网关 的 Ingress 控制器,常用于微服务应用 - Traefik: 是一个基于云原生的 Ingress 控制器。 - HAProxy:是一个针对 HAProxy 的 Ingress 控制器 - Istio Ingress:是一个可观测流量的 Ingress 控制器

可以同时部署任意数量个 ingress 控制器。在 ingress 的清单文件中,通过 spec.ingressClassName 字段来指定要使用的 ingress 控制器,否则使用默认控制器。给 IngressClass 添加注解 ingressclass.kubernetes.io/is-default-class: "true"来设置集群默认的 ingree 控制器。

示例

# 定义默认的 IngressClass
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true" #指定为默认的 ingress控制器
spec:
  controller: k8s.io/nginx-ingress  # 指定控制器类型(格式通常为 <供应商>/<控制器名>)
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: nginx-config  # 可关联自定义参数(可选)
    scope: Cluster

IngressClass 中的 .spec.parameters 字段可用于引用其他资源以提供与该 IngressClass 相关的配置。 参数(parameters)的具体类型取决于你在 IngressClass 的 .spec.controller 字段中指定的 Ingress 控制器

5.6.4.2 集群中默认的 Ingress Class

在 IngressClass 资源清单中设置 ingressclass.kubernetes.io/is-default-class: true 注解来设置集群中默认的 ingressClass。如上示例。

如果集群中设置了多个默认的 ingressClass,那么必须在 ingress 清单中通过 ingressClassName 来指定该 ingress 使用的 ingressClass,否则准入控制器将不允许 ingress 对象的创建。

当然,有些 ingress controller 可以不需要设置默认的 ingressClass,如 ingress-nginx controller 就可以通过参数 --watch-ingress-without-class 来配置。

5.6.4.3 Ingress Class 清单示例
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx                  # 这是 IngressClass 的名字,Ingress 中引用用到 ingressClassName: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"  
    # ↑ 表示这个是默认的 IngressClass
    # 如果 Ingress 中未指定 ingressClassName,就会使用这个默认类(集群中只能有一个默认)
spec:
  controller: k8s.io/ingress-nginx
  # ↑ 这个字段非常关键,用来告诉 Kubernetes:
  #   “这个 IngressClass 是由哪个 Controller 来处理的”
  #   不同 Controller 有不同的标识字符串,例如:
  #     - k8s.io/ingress-nginx (常见的官方 nginx ingress)
  #     - nginx.org/ingress-controller (由 NGINX 官方维护的另一个 controller)
  #     - traefik.io/ingress-controller (Traefik 控制器)
  parameters:
    # 此 IngressClass 的配置定义在一个名为 “external-config-1” 的
    # ClusterIngressParameter(API 组为 k8s.example.net)资源中。
    # 这项定义告诉 Kubernetes 去寻找一个集群作用域的参数资源。
    scope: Cluster
    apiGroup: k8s.example.net
    kind: ClusterIngressParameter
    name: external-config-1

5.6.5 ingress 配置清单模板

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-ingress
  annotations:
    # 重定向 HTTP → HTTPS(Nginx 控制器专用)
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    # 自定义 Rewrite 路径(Traefik 控制器专用)
    traefik.ingress.kubernetes.io/rewrite-target: /
    # 启用 CORS(通用示例)
    kubernetes.io/ingress.allow-http: "false"
spec:
  ingressClassName: nginx-example  # 关联 IngressClass
  tls:  # HTTPS 配置
  - hosts:
    - example.com
    secretName: example-tls-secret  # 引用保存 TLS 证书的 Secret
  rules: 
  - host: example.com  # 域名配置
    http:
      paths:
      - path: /api
        pathType: Prefix  # 路径匹配类型(Prefix/Exact/ImplementationSpecific)
        backend:
          service:
            name: backend-service  # 后端 Service 名称
            port:
              number: 8080  # 后端 Service 端口
      - path: /static
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
---
# Secret(TLS 证书,可选)
apiVersion: v1
kind: Secret
metadata:
  name: example-tls-secret  # 必须与 Ingress 中引用的名称一致
type: kubernetes.io/tls
data:
  tls.crt: <Base64 编码的证书>
  tls.key: <Base64 编码的私钥>

5.6.3 部署 ingress-nginx

在集群中部署 ingress-nginx,ingress-nginx 指南

生成自签名证书

1
2
3
4
5
6
7
8
9
# 生成私钥和证书(有效期365天)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=moarch.com" -addext "subjectAltName=DNS:moarch.com"
# 创建 Kubernetes TLS Secret
kubectl create secret tls tls-secret \
  --cert=tls.crt \
  --key=tls.key \
  -n default

1
2
3
4
5
6
7
8
9
# 清单文件安装
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.2/deploy/static/provider/cloud/deploy.yaml
# 在配置文件中修改集群 CIDR
proxy-real-ip-cidr: 10.244.0.0/16
# 注释掉名叫 ingress-nginx-controller 的 svc 中的所有 annotation

# helm 安装
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx

6. "配置" Configuration

6.1 configmap

ConfigMap(cm) 是 Kubernetes 用来管理 非敏感配置数据 的资源对象,主要作用是将应用配置和容器镜像 解耦,让配置可以独立管理,而不需要重新构建镜像。 主要特点: - 存储普通配置(如环境变量、配置文件),不适合存敏感信息(密码、密钥等要用 Secret) - 支持多种数据格式,如 键值对、完整配置文件和二进制数据 - 灵活的使用方式,可以注入为 环境变量,或挂载容器卷 - 大小不能超过 1MB(etcd 的限制)

configMap 功能在 k8s 1.2 版本引入通常用于配置数据的共享,以 readOnly 的模式挂载。 共享文件的主要两种方式: - 共享:每次在读取文件时,请求共享文件,每隔一段时间请求最新的文件,会浪费网络IO。 - 注入:在开始时就注入,后续若更新则重新注入,不会浪费网络IO。ConfigMap 提供了向容器中注入配置信息的机制。

模板

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-configmap   # ConfigMap 的名称,在同一个命名空间中必须唯一
  labels:   # 可选的标签,可用于选择器或分类
    app: my-app
    tier: backend
  annotations:      # 可选的注解,可以存放非标识性元数据
    version/config: "666"
data:    # 键值对形式的配置数据(简单值)
  APP_COLOR: "blue"
  APP_ENV: "production"

  # 多行文本配置(如配置文件内容)
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5
    level.difficulty=hard

  # 另一个配置文件示例
  ui.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
immutable: true  # 设置为 true 表示禁止修改,k8s将不会再去监听它的值是否改变。false 或不设置则允许修改
binaryData: # 注意:binaryData 和 data 字段是互斥的,不能同时使用相同的key
  # 二进制数据(Base64编码),适用于非文本数据
  secret.key: "U2VjcmV0S2V5VmFsdWU="  # 示例Base64编码值

6.1.1 创建方式

6.1.1.1 命令行直接创建
kubectl create cm redis-config-demo --from-literal=host=127.0.0.1 --from-literal=passwd=123456
6.1.1.2 通过配置文件创建

配置文件 redis.conf

host=127.0.0.1
passwd=123456
创建命令
kubectl create cm redis-config-demo2 --from-file=redis.conf

6.1.1.3 通过资源清单创建

configMap 资源清单 demo

1
2
3
4
5
6
7
8
apiVersion: v1
metadata:
    namespace: dev
    name: redis-config-demo3
kind: ConfigMap
data:
    host: 127.0.0.1
    passwd: 123456

当然,也可以用以下命令,通过配置生成资源清单文件

kubectl create cm redis-config-demo2 --from-file=redis.conf --dry-run=client -o yaml > redis.conf.cm.conf

6.1.2 如何在容器中使用?

6.1.2.1 通过环境变量使用

当用环境变量方式,配置更新,pod 内的配置,也就是环境变量不会更新。

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox:1.28
      command: ['sh', '-c', 'echo "The app is running!" && tail -f /dev/null']
      env:      # 第一种 env 引入方式,环境变量替换
        - name: REDISHOST       # 环境变量参数名
          valueFrom:
            configMapKeyRef:
              name: literal-config  # 环境变量参数值来自的文件名
              key: host     # 环境变量参数值来自文件的哪个字段
      envFrom:      # 第二种 env 引入方式,引用文件中所有键值对
        - configMapRef:
            name : redis-config

6.1.2.2 卷挂载
apiVersion: v1
kind: Pod
metadata:
  name: cm-volume-pod
spec:
  containers:
    - name: test-volume
      image: nginx:1.26.2
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
  volumes:  # cm被修改时,过一段时间容器内的值也会修改
    - name: config-volume
      configMap:
        name: literal-config
        items:
          - key: name
            path: user.conf
          - key: password
            path: passwd.conf
  restartPolicy: Never

一般情况下,用卷挂载时,配置更新,pod 内也会更新,但是有些应用不会自动触发重启,而导致最新配置未生效。这时候只要让 spec.template.metadata.annotations 下的任意一个键值对有变化,就会触发重启。下面是手动触发 nginx pod 重启示例。

# 修改 pod annotations 方式强制出发滚动更新
kubectl patch deployment deploy-cm -n dev --patch '{"spec":{"template":{"metadata":{"annotations":{"version/config":"6"}}}}}'

6.2 Secret

Secret 用来保存敏感的信息,例如密码,令牌和密钥等。将这些信息放在 secret 中必放在 Pod 的定义或者容器镜像中来说更加安全和灵活。 Secret 的特点: - Secret 对象只会存储在需要访问 Secret 的 Pod 所在的机器节点,以保证其安全性 - Secret 只会存储在节点的内存中,永不写入物理存储 - 从 k8s 1.7 版本开始,etcd 会以加密形式存储 Secret

6.2.1 模板

apiVersion: v1
kind: Secret
metadata:
  name: example-secret  # Secret名称,同命名空间内唯一
  labels:   # 推荐打上应用标签
    app: my-app
    env: production
type: Opaque    # 通用类型,也可以是 kubernetes.io/tls 等
data:   # 所有值必须为Base64编码
  username: "YWRtaW4="  # 示例:admin
  password: "MWYyZDFlMmU2N2Rm"  # 示例:1f2d1e2e67df
stringData: # 注意:与data字段互斥,写在这里的值会自动Base64编码
  api_url: "https://api.example.com" 
immutable: true  # 设置为true后禁止修改

6.2.2 创建 Secret

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
  name: secret-example
type: Opaque
data:
  name: bW9sb29tCg==  #  moloom
  password: bW8K    # mo

6.2.3 使用

6.2.3.1 命令获取 secret 值
kubectl get secret secret-example -o jsonpath="{.data.name}"|base64 -d
6.2.3.2 容器卷挂载

挂载 secret 所有数据

apiVersion: v1
kind: Pod
metadata:
  name: secret-volume-pod
spec:
  containers:
    - name: test-volume
      image: nginx:1.26.2
      volumeMounts:
        - name: secret-example
          mountPath: /data
  volumes:  # 值被修改时,过一段时间容器内的值也会修改
    - name: secret-example
      secret:
        secretName: secret-example
        defaultMode: 420    # 设置挂载文件的权限,支持八进制(0644)或者十进制(420)
  restartPolicy: Never

挂载部分数据

apiVersion: v1
kind: Pod
metadata:
  name: secret-volume-pod
spec:
  containers:
    - name: test-volume
      image: nginx:1.26.2
      volumeMounts:
        - name: secret-example
          mountPath: /data
  volumes:  # 值修改时,过一段时间容器内的值也会修改
    - name: secret-example
      secret:
        secretName: secret-example
        items:
          - key: name
            path: username
        defaultMode: 420    # 设置挂载文件的权限,支持八进制(0644)或者十进制(420)
  restartPolicy: Never

6.2.3.3 环境变量

通过环境变量来使用 secret 同 configMap 不支持同步更新。

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
  namespace: default
spec:
  containers:
    - name: test-container
      image: nginx:1.26.2
      envFrom:
        - secretRef:
            name: secret-example
  restartPolicy: Never

6.3 Download API

Download API 时 k8s 中的一个功能,它允许容器在运行时从 k8s API 服务器中获取有关它们自身的信息。这些信息可以作为容器内部的环境变量或文件注入到容器中,以便容器可以获取有关其运行环境的各种信息。

6.3.1 使用 Download API

6.3.1.1 通过 env 获取
apiVersion: v1
kind: Pod
metadata:
  name: use-download-api-by-env
spec:
  containers:
    - image: nginx:1.26.2
      name: use-by-env
      env:
        - name: POD_NAME    # 获取容器名称
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_IP  # 获取容器ip
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: CPU_REQUEST     # 获取运行时最少需要的cpu个数
          valueFrom:
            resourceFieldRef:
              resource: requests.cpu
        - name: CPU_LITMIT      # 获取运行时使用的最大cpu个数
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu
        - name: MEMORY_REQUEST      # 获取运行时最少需要的内存大小
          valueFrom:
            resourceFieldRef:
              resource: requests.memory
        - name: MEMORY_LIMIT        # 获取运行时使用的最大内存大小
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
6.3.1.2 通过 volume 获取

volume 挂载的 download API 数据也支持热更新!

apiVersion: v1
kind: Pod
metadata:
  name: use-download-api-by-volume
spec:
  containers:
  - name: nginx
    image: nginx:1.26.2
    resources:
      requests:
        cpu: "250m"
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "256Mi"
    volumeMounts:
    - name: pod-info
      mountPath: /etc/pod-info
      readOnly: true
  volumes:
  - name: pod-info
    downwardAPI:
      items:
      - path: "cpu_limit"
        resourceFieldRef:
          containerName: nginx
          resource: "limits.cpu"
      - path: "memory_limit"
        resourceFieldRef:
          containerName: nginx
          resource: "limits.memory"
      - path: "cpu_request"
        resourceFieldRef:
          containerName: nginx
          resource: "requests.cpu"
      - path: "memory_request"
        resourceFieldRef:
          containerName: nginx
          resource: "requests.memory"

6.3.1.3 获取别的容器信息

为 Pod 配置 ServiceAccount 权限

apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-info-reader
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-pods
subjects:
- kind: ServiceAccount
  name: pod-info-reader
  namespace: default
roleRef:
  kind: ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Pod
metadata:
  name: curl
  namespace: default
spec:
  serviceAccountName: pod-info-reader
  containers:
  - name: main
    image: curlimages/curl:8.12.1
    command: [ "sleep", "9999" ]

curl Pod 中调用 Kubernetes API

# 进入容器
kubectl exec -it curl -- /bin/sh

# 获取 API Server 地址和 Token
APISERVER="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# 查询所有 Pod 信息
curl -s $APISERVER/api/v1/pods \
  --header "Authorization: Bearer $TOKEN" \
  --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# 查询特定 Pod 的详细信息
curl -s $APISERVER/api/v1/namespaces/default/pods/<target-pod-name> \
  --header "Authorization: Bearer $TOKEN" \
  --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

6.3.2 查询 k8s 的 API 文档

主要用于二次开发 k8s 工具时查阅。

1
2
3
4
5
6
7
8
9
# 将 API Server 映射到本地的 8080 端口
kubectl proxy --port=8080
# 获取 k8s 的接口文档文件,获取完就可以关闭 proxy 的映射
curl http://localhost:8088/openapi/v2 > k8s-swagger.json
# 运行一个 sawgger 容器查看 k8s 的 api 接口文档
docekr run -d --rm -p 8088:8080 \
    -e SWAGGER_JSON=/k8s-swagger.json \
    -v $(pwd)/k8s-swagger.json:/k8s-swagger.json \
    swaggerapi/swagger-ui:v5.20.0

7. Storage

卷为容器提供了一种共享宿主机文件系统的一种方式。按生命周期分类有:临时卷、持久卷。 - 临时卷类型的生命周期与 Pod 相同,当 Pod 不再存在时,Kubernetes 也会销毁临时卷 - 持久卷可以比 Pod 的存活期长,当 Pod 不再存在时,Kubernetes 不会销毁持久卷

7.1 Volume

在容器中,容器一旦关闭,那么容器内部的文件都将丢失且无法找回。若要部署一个数据库,容器每次重启一次数据就丢失了,显然是不可用,容器与宿主机之间要能共享文件,使容器内的有效数据不会丢失,而 volume 就是来解决这个文件的。 volume 卷都在宿主机的 /var/lib/kubelet/pods/{uid}/volumes 目录下。

7.1.1. emptyDir

emptyDir 是 Kubernetes 中的一种临时存储卷,生命周期与 Pod 绑定。当 Pod 被删除时,emptyDir 中的数据也会被清除。它通常用于 容器间共享临时数据 或 缓存。 容器崩溃不会从节点中移除 pod,因此 empty 卷中的数据在容器崩溃时是安全的。 emptyDir 的用法: - 暂存空间,例如用于基于磁盘的合并排序、用于长时间计算崩溃恢复时的检查点 - web 服务器容器提供数据时,保存内容管理器容器提取的文件

示例

apiVersion: v1
kind: Pod
metadata:
  name: shared-emptydir
spec:
  containers:
  - name: writer
    image: alpine:3.21
    command: ["sh", "-c", "echo 'Hello from Container A!' > /shared-data/message.txt && sleep 3600"]
    volumeMounts:
    - name: shared-volume
      mountPath: /shared-data

  - name: reader
    image: alpine:3.21
    command: ["sh", "-c", "cat /shared-data/message.txt && sleep 3600"]
    volumeMounts:
    - name: shared-volume
      mountPath: /shared-data

  volumes:
  - name: shared-volume
    emptyDir: {}    # 完全默认行为(磁盘存储,无大小限制)

  - name: cache-volume
    emptyDir:
      medium: Memory  # 显式指定卷使用内存分配空间(tmpfs)
      sizeLimit: "500Mi"  # 限制大小

7.1.2 hostPath

hostPath 卷将主机节点的文件系统中的文件或目录挂载到集群中。hostPath 卷的 type 字段用于强制校验宿主机路径的类型,确保 Pod 按预期访问宿主机资源。

hostPath 可以挂载符号链接,但是最终所指向的必须是目录,且 type 是 Directory

类型 作用 示例路径 注意事项
(空字符串) 跳过类型检查(兼容旧版本) 可以挂载任意路径
Directory 挂载已存在的目录 /var/log 目录不存在则 Pod 启动失败
DirectoryOrCreate 目录不存在时自动创建(权限 755,属主为 kubelet 用户),不会创建文件的父目录 /tmp/my-dir 适合日志目录等可动态创建的场景
File 挂载已存在的文件 /etc/hosts 文件不存在则 Pod 启动失败
FileOrCreate 文件不存在时自动创建空文件(权限 644,属主为 kubelet 用户) /tmp/config.yaml 需确保父目录已存在
Socket 挂载Unix Socket 文件 /var/run/docker.sock 非 Socket 文件则启动失败
CharDevice 挂载字符设备(如终端、随机数生成器) /dev/random 非字符设备则启动失败
BlockDevice 挂载块设备(如磁盘分区) /dev/sda1 非块设备则启动失败

示例

apiVersion: v1
kind: Pod
metadata:
  name: all-hostpath-types
spec:
  containers:
  - name: demo
    image: alpine:3.21
    volumeMounts:
      # 1. 目录类型(Directory)
      - name: host-dir
        mountPath: /host/dir

      # 2. 文件类型(File)
      - name: host-file
        mountPath: /host/file

      # 3. Socket 文件(Socket)
      - name: host-socket
        mountPath: /host/socket

      # 4. 字符设备(CharDevice)
      - name: host-char-dev
        mountPath: /host/char

      # 5. 块设备(BlockDevice)
      - name: host-block-dev
        mountPath: /host/block

      # 6. 目录或自动创建(DirectoryOrCreate)
      - name: host-dir-or-create
        mountPath: /host/dir-or-create

      # 7. 文件或自动创建(FileOrCreate)
      - name: host-file-or-create
        mountPath: /host/file-or-create

  volumes:
    # 1. 宿主机目录(必须存在)
    - name: host-dir
      hostPath:
        path: /tmp/host-dir-example # 宿主机上的目录
        type: Directory  # 必须确保目录存在,否则Pod启动失败

    # 2. 宿主机文件(必须存在)
    - name: host-file
      hostPath:
        path: /tmp/host-file-example
        type: File  # 必须确保文件存在,否则Pod启动失败

    # 3. 宿主机Unix Socket文件(必须存在)
    - name: host-socket
      hostPath:
        path: /var/run/docker.sock  # Docker Socket示例
        type: Socket  # 必须确保Socket存在,否则Pod启动失败

    # 4. 宿主机字符设备(如/dev/random)
    - name: host-char-dev
      hostPath:
        path: /dev/random
        type: CharDevice  # 必须确保字符设备存在

    # 5. 宿主机块设备(如磁盘/dev/sda1)
    - name: host-block-dev
      hostPath:
        path: /dev/sda1  # 示例块设备
        type: BlockDevice  # 必须确保块设备存在

    # 6. 宿主机目录(不存在则自动创建)
    - name: host-dir-or-create
      hostPath:
        path: /tmp/auto-create-dir
        type: DirectoryOrCreate  # 目录不存在时自动创建(权限755)

    # 7. 宿主机文件(不存在则自动创建空文件)
    - name: host-file-or-create
      hostPath:
        path: /tmp/auto-create-file
        type: FileOrCreate  # 文件不存在时自动创建(权限644)

  # 可选:限制Pod调度到特定节点(因HostPath与节点绑定)
  nodeSelector:
    kubernetes.io/hostname: node1  # 替换为实际节点名

7.2 pv\pvc

pv(PersistentVolume)持久卷,是抽象出来的存储资源对象,和普通 volume 一样,也是使用卷插件来实现的,不同的是它们拥有独立于任何使用 pv 的 Pod 的生命周期。

PVC(PersistentVolumeClaim)持久卷申领,表达的是用户对存储的请求。

7.2.1 pv 示例

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-static-example  # PV名称,集群内唯一
  labels:
    type: local  # 可选标签,可用于PVC匹配
spec:
  capacity:
    storage: 10Gi  # PV的存储容量
  accessModes:
    - ReadWriteOnce  # 支持的访问模式(RWO/ROX/RWX)
  persistentVolumeReclaimPolicy: Retain  # 回收策略:Delete/Retain/Recycle
  storageClassName: manual  # 关联的StorageClass名称(若无则需显式指定)
  hostPath:  # 存储后端类型(此处为本地路径,生产环境建议用CSI/NFS等)
    path: /mnt/data  # 宿主机上的实际路径
---
# 动态制备的PV(由StorageClass自动创建,无需手动定义)
# 用户只需创建PVC即可触发自动生成PV

7.2.1 pvc 示例

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example  # PVC名称,同一命名空间内唯一
spec:
  storageClassName: manual  # 指定要匹配的StorageClass(需与PV一致)
  accessModes:
    - ReadWriteOnce  # 请求的访问模式(必须被PV支持)
  resources:
    requests:
      storage: 5Gi  # 请求的存储大小(PV容量需≥此值)
  # 可选:通过selector匹配特定PV
  selector:
    matchLabels:
      type: local  # 仅匹配带有此标签的PV

7.2.2 pv 访问模式 accessModes

PersistentVolume.spec.accessModes 读写策略,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

  • ReadWriteOnce - RWO:单节点读写,同一节点上运行的多个 Pod 可以访问(读取或写入)该卷。
  • ReadOnlyMany - ROX:多节点只读,无法保证该卷是只读!Pod 仍可能通过其他方式写入数据(需依赖存储插件实现真正的只读)。如果需要强制只读,需在 Pod 的 volumeMounts 中显式设置 readOnly: true
  • ReadWriteMany - RWX:多节点读写
  • ReadWriteOncePod - RWOP:卷只能被单个 Pod 以读写方式挂载。仅适用于 CSI 卷和 Kubernetes v1.22+。

每个卷同一时刻只能以一种访问模式挂载,即使该卷能够支持多种访问模式。 pv 与 pvc 的读写策略必须

卷插件 ReadWriteOnce ReadOnlyMany ReadWriteMany ReadWriteOncePod
AzureFile -
CephFS -
CSI 取决于驱动 取决于驱动 取决于驱动 取决于驱动
FC - -
FlexVolume 取决于驱动 -
GCEPersistentDisk - -
Glusterfs -
HostPath - - -
iSCSI - -
NFS -
RBD - -
VsphereVolume - - Pod 运行于同一节点上时可行 -
PortworxVolume - -

7.2.3 pv 回收策略 persistentVolumeReclaimPolicy

PersistentVolume.spec.persistentVolumeReclaimPolicy 回收策略 persistentVolumeReclaimPolicy,当 pv 不再被使用了之后,对其的处理方式。目前支持三种策略:

  • Retain (保留) 保留数据,pod被删除后,Retain 模式的pv 和其绑定的 pvc 不会被删除,需要管理员手工清理数据
  • Recycle(回收) 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
  • Delete (删除)与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务

当前,只有 NFS 和 HostPath 支持回收策略 。AWS EBS , GCE PD , Azure Disk 和 Cinder 卷支持删除策略。

7.2.4 pv 状态 status

一个 PV 的生命周期中,可能会处于4中不同的阶段:

  • Available(可用):表示可用状态,还未被任何 PVC 绑定
  • Bound(已绑定):表示 PV 已经被 PVC 绑定
  • Released(已释放):表示 PVC 被删除,但是资源还未被集群重新声明
  • Failed(失败):表示该 PV 的自动回收失败

7.2.5 制备

PV 卷的制备有两种方式:静态制备或动态制备。

7.2.5.1 静态制备

集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息, 并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。

7.2.5.2 动态制备

创建一个 pvc,在动态制备下,pvc可能已经创建完毕,control- plane 的控制回路监测新的 pvc 对象,为之寻找相匹配(容量大于 pvc)的 pv 卷,如果此时动态制备了一个新 PV(通过 StorageClass),则该 PV 会直接绑定到当前 PVC(一对一专属绑定),如果找不到匹配的 PV 卷,PVC 申领会无限期地处于未绑定状态。 如果动态制备了一个新 PV(通过 StorageClass),则 该 PV 会直接绑定到当前 PVC(一对一专属绑定)。

pvc 与 pv 绑定涉及到预选和优选算法。预选:需要满足如存储类型一致、访问策略一致、存储大小相符的条件,pvc 和 pv 才有可能绑定,若此时 pvc 还有多个 pv 所选择,这时就需要通过优选法来选择最符合的那一个,pvc 会优先选择回收策略 Retain 的 pv,其次是 Recycle,最后是 Delete 的。

7.2.6 实际部署使用 smb 协议共享的空间

7.2.6.1 安装 smb 驱动
  • 系统上安装 cifs 驱动

    # 每个集群节点都要安装
    sudo apt-get install cifs-utils -y
    

  • 用 helm 安装 csi-smb 驱动 在 master 节点安装就行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 普通部署
    helm repo add csi-driver-smb https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/master/charts
    helm install csi-driver-smb csi-driver-smb/csi-driver-smb --namespace kube-system --version v1.17.0
    
    # 定制部署
    helm install csi-driver-smb2 csi-driver-smb/csi-driver-smb --namespace kube-system --set driver.name="smb2.csi.k8s.io" --set controller.name="csi-smb2-controller" --set rbac.name=smb2 --set serviceAccount.controller=csi-smb2-controller-sa --set serviceAccount.node=csi-smb2-node-sa --set node.name=csi-smb2-node --set node.livenessProbe.healthPort=39643
    
    # 卸载驱动
    helm uninstall csi-driver-smb -n kube-system
    

  • kubectl 安装 csi-smb 驱动

    # 远程方式安装
    curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/v1.17.0/deploy/install-driver.sh | bash -s v1.17.0 hostprocess --
    
    # 本地安装
    git clone https://github.com/kubernetes-csi/csi-driver-smb.git
    cd csi-driver-smb
    git checkout v1.17.0
    ./deploy/install-driver.sh v1.17.0 local,hostprocess
    
    # 检查 pod 状态
    kubectl -n kube-system get pod -o wide --watch -l app=csi-smb-controller
    kubectl -n kube-system get pod -o wide --watch -l app=csi-smb-node
    
    # 联网方式 卸载驱动
    curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/v1.17.0/deploy/uninstall-driver.sh | bash -s --
    
    # 本地方法 卸载驱动
    git clone https://github.com/kubernetes-csi/csi-driver-smb.git
    cd csi-driver-smb
    git checkout v1.17.0
    ./deploy/uninstall-driver.sh v1.17.0 local
    

7.2.6.2 在 pod 和 statefulSet 编写 yaml 文件

use-smb.yaml

apiVersion: v1
kind: Secret
metadata:
  name: smb-credential
  namespace: default
type: microsoft.com/smb
stringData:
  username: "moloom"
  password: "mo"
  domain: WORKGROUP  #192.168.1.170


---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: win-smb-pv01
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: smb
  mountOptions:
    - vers=3.0
    - dir_mode=0777
    - file_mode=0777
    - nobrl
  csi:
    driver: smb.csi.k8s.io
    readOnly: false
    volumeHandle: unique01  # 必须是唯一的ID
    volumeAttributes:
      source: "//192.168.1.170/k8s"
    nodeStageSecretRef:
      name: smb-credential
      namespace: default
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: win-smb-pv02
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: smb
  mountOptions:
    - vers=3.0
    - dir_mode=0777
    - file_mode=0777
    - nobrl
  csi:
    driver: smb.csi.k8s.io
    readOnly: false
    volumeHandle: unique02  # 必须是唯一的ID
    volumeAttributes:
      source: "//192.168.1.170/k8s"
    nodeStageSecretRef:
      name: smb-credential
      namespace: default
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: win-smb-pv03
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  storageClassName: smb
  mountOptions:
    - vers=3.0
    - dir_mode=0777
    - file_mode=0777
    - nobrl
  csi:
    driver: smb.csi.k8s.io
    readOnly: false
    volumeHandle: unique04  # 必须是唯一的ID
    volumeAttributes:
      source: "//192.168.1.170/k8s"
    nodeStageSecretRef:
      name: smb-credential
      namespace: default
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: win-smb-pv04
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  storageClassName: smb
  mountOptions:
    - vers=3.0
    - dir_mode=0777
    - file_mode=0777
    - nobrl
  csi:
    driver: smb.csi.k8s.io
    readOnly: false
    volumeHandle: unique-volumeid  # 必须是唯一的ID
    volumeAttributes:
      source: "//192.168.1.170/k8s"
    nodeStageSecretRef:
      name: smb-credential
      namespace: default

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: win-smb-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: smb
  volumeName: win-smb-pv01  # 明确绑定到 PV

---
apiVersion: v1
kind: Pod
metadata:
  name: use-pv-with-smb
spec:
  containers:
  - name: use-pv
    image: nginx:1.26.3
    volumeMounts:
    - name: win-share
      mountPath: /mnt
  volumes:
  - name: win-share
    persistentVolumeClaim:
      claimName: win-smb-pvc

---

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: webb
  clusterIP: None
  selector:
    app: nginx

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: webb
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels: 
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.26.3
          ports:
            - containerPort: 80
              name: webb
          volumeMounts:
            - name: smb-share
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:     # pvc 构建模版
    - metadata:
        name: smb-share
      spec:
        accessModes: [ "ReadWriteMany" ]
        storageClassName: "smb"
        resources:
          requests:
            storage: 1Gi

应用这些配置

kubectl apply -f use-smb.yaml

7.2.8 StorageClass

上面的 pv 、pvc 都需要自己根据 pod 或控制器的需求来制备,非常不方便。StorageClass 类似于一块空的硬盘,StatefulSet 能在这块“硬盘”上根据 pvc 的需求自动创建 pv。

7.2.8.1 清单示例
# storageclass-aws-gp3.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-gp3  # StorageClass 名称,PVC 通过此名称引用
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"  # 设为默认 StorageClass(可选)
provisioner: ebs.csi.aws.com  # 必须字段,指定存储驱动(不同存储类型不同)
parameters:
  type: gp3                    # 存储类型(aws 支持 gp3/io1/st1 等)
  iops: "3000"                 # 仅对 gp3/io1 有效,自定义 IOPS
  throughput: "125"            # gp3 的吞吐量(MB/s)
  encrypted: "true"            # 是否加密
  kmsKeyId: "arn:aws:kms:..."  # 自定义 KMS 密钥(可选)
  pathPattern: ${.PVC.namespace}/${.PVC.name}
reclaimPolicy: Delete          # PVC 删除后 PV 的处理方式(Delete/Retain)
allowVolumeExpansion: true     # 允许 PVC 动态扩容(需存储后端支持)
volumeBindingMode: WaitForFirstConsumer  # 延迟绑定(避免 Pod 未调度时提前绑定)
mountOptions:                  # 挂载选项(文件系统类型等)
  - debug
  - nobarrier
7.2.8.2 使用

在 StorageClass 中使用 nfs 类型部署 StatefulSet 应用。

nfs-storageclass.yaml

# 创建 NFS StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-sc  # StorageClass 名称
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"  # 是否设为默认(按需修改)
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner  # 必须匹配 NFS Provisioner 的配置
parameters:
  pathPattern: ${.PVC.namespace}/${.PVC.name}
  onDelete: delete
  archiveOnDelete: "false"  # 删除 PVC 时是否归档数据(true=保留,false=删除)
volumeBindingMode: Immediate
allowVolumeExpansion: true  # 允许 PVC 扩容

---
# 创建 StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nfs-web
spec:
  serviceName: "nfs-web"  # Headless Service 名称
  replicas: 2
  selector:
    matchLabels:
      app: nfs-web
  template:
    metadata:
      labels:
        app: nfs-web
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: "/usr/share/nginx/html"
  volumeClaimTemplates:  # 自动为每个 Pod 创建 PVC
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]  # NFS 实际支持 ReadWriteMany,但 Provisioner 限制为 ReadWriteOnce
      storageClassName: nfs-sc  # 引用上面创建的 StorageClass
      resources:
        requests:
          storage: 2Gi  # 初始大小(可动态扩容)

---
# 创建 Headless Service
apiVersion: v1
kind: Service
metadata:
  name: nfs-web
spec:
  clusterIP: None  # Headless Service
  selector:
    app: nfs-web
  ports:
  - port: 80
    name: web

8. scheduler 调度器

Kubernetes Scheduler 是集群的核心组件,scheduler 的主要任务是把定义的 pod 分配到集群的节点上运行。schedule 是作为单独的程序运行的,启动之后会一致监听 API Server,获取 PodSpec.NodeName 为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。

scheduler 分配 pod 过程:scheduler 会筛选出所有符合 pod 要求的 node(如资源充足;是否和指定的nodeName匹配;host模式下node 端口和 pod 端口是否冲突;标签匹配),对符合条件的 node 进行优先级排序(如资源利用率越低的 Node 得分越高;cpu 和 内存使用率越接近,得分越高;节点若有要使用的镜像,镜像总大小越大,得分越高),这些一系列的操作计算出来的分数来决定 node 节点的排名,并将 pod 绑定到得分最高的 node 上。

8.1 自定义 scheduler

编写 deploy 清单文件 my-scheduler.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      schedulerName: my-scheduler   #自定义调度器
      containers:
        - image: nginx:1.26.3
          name: myapp

deploy 的 pod 状态是 ==pending== ,无法正常运行,因为设置了不存在的自定义调度器,它不会使用默认的调度器 ==default-scheduler== 进行调度。

启动 API Server 代理:kubectl proxy --port=8001 & 安装 jq 命令 sudo apt-get install jq

编写脚本,把 pod 随机调度到可用的节点 binding.sh

#!/bin/bash

SERVER='localhost:8001'

# 单次处理模式:仅执行一轮调度
POD_NAMES=$(kubectl --server $SERVER get pods -o json | jq -r '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name')

if [ -z "$POD_NAMES" ]; then
    echo "没有待调度的Pod,脚本退出。"
    exit 0
fi

NODES=($(kubectl --server $SERVER get nodes -o json | jq -r '.items[].metadata.name'))
NUM_NODES=${#NODES[@]}

for PODNAME in $POD_NAMES; do
    if [ $NUM_NODES -eq 0 ]; then
        echo "错误:没有可用节点!"
        exit 1
    fi

    # 随机选择一个节点
    CHOSEN=${NODES[$(( RANDOM % NUM_NODES ))]}

    # 发送绑定请求
    curl --header "Content-Type: application/json" \
         --request POST \
         --data "{\"apiVersion\":\"v1\", \"kind\":\"Binding\", \"metadata\":{\"name\":\"$PODNAME\"}, \"target\":{\"apiVersion\":\"v1\", \"kind\":\"Node\", \"name\":\"$CHOSEN\"}}" \
         "http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/"

    echo "已将Pod $PODNAME 分配到节点 $CHOSEN"
done

echo "所有待调度的Pod已处理完毕,脚本退出。"
exit 0

9. Security

Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。所谓的安全性其实就是保证对 Kubernetes 的各种客户端进行认证和鉴权操作。

在 Kubernetes 集群中,客户端通常有两类: - User Account:一般是独立于 kubernetes 之外的其他服务管理的用户账号。 - Service Account:kubernetes 管理的账号,用于为 Pod 中的服务进程在访问 Kubernetes 时提供身份标识。

认证、授权与准入控制

ApiServer 是访问及管理资源对象的唯一入口。任何一个请求访问 ApiServer,都要经过下面三个流程: - Authentication(认证):身份鉴别,只有正确的账号才能够通过认证 - Authorization(授权): 判断用户是否有权限对访问的资源执行特定的动作 - Admission Control(准入控制):用于补充授权机制以实现更加精细的访问控制功能。

9.1 Authentication 认证模式

Kubernetes 集群安全的最关键点在于如何识别并认证客户端身份,它提供了 3 种客户端身份认证方式,安全级别从上往下越来越高 : - HTTP Token 认证:通过一个Token来识别合法用户。用一个很长的难以被模仿的字符串 Token 来表明客户身份的一种方式。每个 Token 对应一个用户名,当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token,API Server 接到 Token 后会跟服务器中保存的 token 进行比对,然后进行用户身份认证的过程。 - HTTP Base 认证:通过用户名+密码的方式认证。这种认证方式是把“用户名:密码”用 BASE64 算法进行编码后的字符串放在 HTTP 请求中的 Header Authorization 域里发送给服务端。服务端收到后进行解码,获取用户名及密码,然后进行用户身份认证的过程。 - HTTPS 证书认证:基于 CA 根证书签名的双向数字证书认证方式。安全性最高的一种方式,但是同时也是操作起来最麻烦的一种方式。

Controller Manager,Scheduler 与 APIServer 在同一台机器,所以直接使用 APIServer 的非安全端口访问 --insecure-bind-address=127.0.0.1 kubectl,kubelet,kube-proxy 因为可以不和 APIServer 安装在同台机器,所以都需要证书进行 HTTPS 双向认证。

HTTPS 认证大体分为3个过程: 1. 证书申请和下发。HTTPS 通信双方的服务器向CA机构申请证书,CA 机构下发根证书、服务端证书及私钥给申请者 2. 客户端和服务端的双向认证。 - 客户端向服务器端发起请求,服务端下发自己的证书给客户端, - 客户端接收到证书后,通过私钥解密证书,在证书中获得服务端的公钥, - 客户端利用服务器端的公钥认证证书中的信息,如果一致,则认可这个服务器 - 客户端发送自己的证书给服务器端,服务端接收到证书后,通过私钥解密证书,在证书中获得客户端的公钥,并用该公钥认证证书信息,确认客户端是否合法 3. 服务器端和客户端进行通信。服务器端和客户端协商好加密方案后,客户端会产生一个随机的秘钥并加密,然后发送到服务器端。服务器端接收这个秘钥后,双方接下来通信的所有内容都通过该随机秘钥加密

证书认证过程

注意: Kubernetes允许同时配置多种认证方式,只要其中任意一个方式认证通过即可

ServiceAccount 在每个 namespace 都会有一个 Service Account,如果 pod 在创建时没有指定 ServiceAccount,就会使用 pod 所属的 namespace 的 ServiceAccount。默认挂载目录 /run/secrets/kubenetes.io/serviceaccount/

证书签发模式

  • 手动签发。通过 k8s 集群的跟 CA 进行签发 HTTPS 证书。
  • 自动签发。kubelet 首次访问 APIServer 时,使用 token 做认证,通过后,Controller Manager 会为 kubelet 生成一个证书,以后的访问都是证书做认证了。

9.2 Authorization 鉴权

授权发生在认证成功之后,通过认证就可以知道请求用户是谁, 然后 Kubernetes 会根据事先定义的授权策略来决定用户是否有权限访问,这个过程就称为授权。

每个发送到 ApiServer 的请求都带上了用户和资源的信息:比如发送请求的用户、请求的路径、请求的动作等,授权就是根据这些信息和授权策略进行比较,如果符合策略,则认为授权通过,否则会返回错误。

API Server目前支持以下几种授权策略: - AlwaysDeny:表示拒绝所有请求,一般用于测试 - AlwaysAllow:允许接收所有请求,相当于集群不需要授权流程(Kubernetes 默认的策略) - ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制 - Webhook:通过调用外部REST服务对用户进行授权 - Node:是一种专用模式,用于对kubelet发出的请求进行访问控制 - RBAC:基于角色的访问控制(kubeadm安装方式下的默认选项)

9.2.1 RBAC(Role-Based Access Control) 基于角色的访问控制

RBAC 在 k8s 1.5 版本引入,RBAC 把用户与角色绑定在一起,用于控制用户的权限。主要有以下几种优势: - 对集群中的资源和非资源均拥有完整的覆盖 - 整个 RBAC 完全由几个 API 对象完成,同其他 API 对象一样,可以用 kubectl 或 API 进行操作 - 可以在运行时进行调整,无需重启 APIServer。

RBAC 引入了4个顶级资源对象,如下所示。角色与角色绑定的关系类似于 pv 与 pvc 的关系,角色表示本身具有的权限,角色绑定像一条纽带,绑定用户,让用户拥有该角色的权限。绑定的对象不止用户,可以是 Group,User,Service Account。 - 角色:Role、ClusterRole;用于指定一组权限,Role - 角色绑定:RoleBinding、ClusterRoleBinding;用于将角色(权限)赋予给对象

rolebinding relationship

9.2.1.1 Role

Role 是 命名空间(Namespace)级别 的资源,其权限仅作用于单个命名空间内的资源。必须与 RoleBinding 配合使用,且 RoleBinding 必须位于同一命名空间。

示例

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default  # 必须指定命名空间
  name: pod-reader
rules:
- apiGroups: [""]  # 支持的API组列表,"" 空字符串,表示核心API群
  resources: ["pods"] # 支持的资源对象列表
  verbs: ["get", "watch", "list"] # 允许的对资源对象的操作方法列表

---
# RoleBinding 在 `default` 命名空间中将 Role 权限授予用户
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User    # 所要授权的对象类型
  name: alice   # 所要授权的对象 名称
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role    # 角色类型
  name: pod-reader  #角色名
  apiGroup: rbac.authorization.k8s.io

apiGroups Role.rules.apiGroups 支持的 API 组列表有

"","apps", "autoscaling", "batch"

resources Role.rules.resources 支持的资源对象列表有

1
2
3
"services", "endpoints", "pods","secrets","configmaps","crontabs","deployments","jobs",
"nodes","rolebindings","clusterroles","daemonsets","replicasets","statefulsets",
"horizontalpodautoscalers","replicationcontrollers","cronjobs"

verbs Role.rules.verbs 对资源对象的操作方法列表

"get", "list", "watch", "create", "update", "patch", "delete", "exec"
9.2.1.2 ClusterRole
ClusterRole 是集群级别的资源,权限可覆盖所有命名空间中的资源(如 Pods)、集群范围资源(node)、非资源端点(/healthz)、跨命名空间资源(如需要跨 Namespace 的权限聚合)。

ClusterRole 可以和 ClusterRoleBinding 或者 RoleBinding 绑定 - 绑定 RoleBinding:权限仅作用于单个命名空间(复用 ClusterRole 的权限定义) - 绑定 ClusterRoleBinding:权限作用于整个集群

示例

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: global-reader
rules:
- apiGroups: [""]
  resources: ["pods/log"]   #只授权 pod的log日志
  verbs: ["get", "list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list"]

---
# ClusterRole + RoleBinding,将 ClusterRole 的权限限制到 `test` 命名空间
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-global
  namespace: test  # 权限仅在 test 命名空间生效
subjects:
- kind: User
  name: bob
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: global-reader
  apiGroup: rbac.authorization.k8s.io

---
# ClusterRole + ClusterRoleBinding,授予整个集群的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-binding
subjects:
- kind: User
  name: admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

9.2.1.3 创建一个用户只能管理 dev 命令空间

创建一个 linux 用户 songyun,只给予它对 pod 资源的 get、watch、delete 权限。

创建 linux 用户 songyun

1
2
3
4
sudo useradd -m -s /bin/bash songyun
sudo passwd songyun
su - songyun
mkdir -p ~/.kube

创建用户凭证

# 进入 k8s 集群的证书目录,该步骤不是必须。若是多用户主机,建议放在当前用户目录下 ~/.kube
cd /etc/kubernetes/pki
cd ~/.kube

# 生成私钥和 CSR
openssl genrsa -out songyun.key 2048
# # CN=用户名,O=用户组(用于RBAC绑定)
openssl req -new -key songyun.key -out songyun.csr -subj "/CN=songyun/O=dev-group"
# 使用 Kubernetes CA 签署证书(需访问集群 CA)
sudo openssl x509 -req -in songyun.csr \
  -CA /etc/kubernetes/pki/ca.crt \
  -CAkey /etc/kubernetes/pki/ca.key \
  -CAcreateserial -out songyun.crt -days 365

# 将用户添加到 kubeconfig
kubectl config set-credentials songyun \
  --client-certificate=songyun.crt \
  --client-key=songyun.key \
  --kubeconfig=/home/songyun/.kube/config2  # 指定 kubeconfig文件,若文件不存在则会生成新配置文件

# 设置 k8s 集群
kubectl config set-cluster kubernetes \
  --server=https://192.168.1.180:6443 \
  --certificate-authority=/etc/kubernetes/pki/ca.crt

# 创建 Context
kubectl config set-context dev-user-context \
  --cluster=kubernetes \
  --namespace=dev \
  --user=songyun    \
  --kubeconfig=dev-user.kubeconfig  # 指定用户的 k8s 配置文件 ~/.kube/config,这里可以不用该参数

编写 role 和 rolebinding cluster-role-demo.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: dev-full-access
rules:
  - apiGroups: [ "" ] 
    resources: [ "*" ]  # 给予所有资源对象的权限
    verbs: [get, watch, delete]


---
#绑定用户到 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev   # 关键:必须与 Role 同一命名空间
  name: dev-user-context
subjects:
  - kind: User       # 绑定到用户
    name: mo   # 用户名需与证书中的 CN 一致
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-full-access
  apiGroup: rbac.authorization.k8s.io
下面语句可以代替 rolebinding 清单
1
2
3
4
kubectl create rolebinding dev-user-context --role=dev-full-access --user=songyun -n dev

# 使用 官方 提供的 view clusterRole给 songyun 用户对命名空间 dev 的权限限制
kubectl create rolebinding dev-user-context --clusterrole=view --user=songyun -n dev

使用 dev-user 上下文测试权限

# 设置用户的默认上下文为 dev-user-context
kubectl config use-context dev-user-context  \
  --kubeconfig=/home/songyun/.kube/config
# 或者直接在命令中加上参数,使用 dev-user 上下文
kubectl get svc -o wide --context=dev-user-context

# 检查 dev-user-context 是否正确指向集群的 API Server
kubectl config view --minify --context=dev-user-context

# 检查权限
kubectl auth can-i create deploy -n dev    # 应返回 "no"
kubectl auth can-i list pods -n default    # 应返回 "yes"

dev-user-context view

9.2.2 官方预定义角色

在 Kubernetes 的 RBAC(基于角色的访问控制) 模型中,官方提供了一系列预定义角色(Predefined Roles),这些角色可以直接使用,无需手动定义,可以使用命令 kubectl get clusterrole 查看。

名称 权限范围 一般用途
view 允许读取除 Role、RoleBinding、Secret 对象外的命名空间内的所有资源 第三方人员
edit 允许读取和修改 Secret,但是不允许查看和修改 RoleBinding,防止权限扩散 开发人员
admin 一个命名空间的资源完全控制权由 admin 赋予,该角色可以修改和访问除 ResourceQuota 和命名空间资源本身外的任何资源 运维人员
cluster-admin 超级管理员(完全控制集群) 集群管理员

9.3 Admission Control 准入控制

通过了前面的认证和授权之后,还需要经过准入控制处理通过之后,apiserver才会处理这个请求。鉴权只是大范围的判断该用户是否满足权限,准入控制则是更加细粒度得判断该操作的合法性。例如:linux 系统中的 rm 命令,普通用户执行 rm 命令,只能删除掉所属该用户的权限,在命令前面加 sudo 获得了管理员权限,则可以删除任意文件,这一步有点类似 鉴权 操作。像执行 sudo rm -rf / 命令,该命令可以不可恢复式破坏 linux 系统,而 k8s 为了防止用户对集群做这种类似的破坏性操作,引入了 Admission Control 准入控制,添加服务,可以防止一些有权限但不合理的操作。

当前可配置的 Admission Control 准入控制如下:

  • AlwaysAdmit:允许所有请求
  • AlwaysDeny:禁止所有请求,一般用于测试
  • AlwaysPullImages:在启动容器之前总去下载镜像
  • DenyExecOnPrivileged:它会拦截所有想在 Privileged Container 上执行命令的请求
  • ImagePolicyWebhook:这个插件将允许后端的一个Webhook程序来完成 admission controller 的功能。
  • Service Account:实现 ServiceAccount 实现了自动化。官方推荐使用
  • SecurityContextDeny:这个插件将使用 SecurityContext 的 Pod 中的定义全部失效
  • ResourceQuota:用于资源配额管理目的,观察所有请求,确保在 namespace 上的配额不会超标。官方推荐使用
  • LimitRanger:用于资源限制管理,作用于 namespace 上,确保对 Pod 进行资源限制。官方推荐使用
  • InitialResources:为未设置资源请求与限制的 Pod,根据其镜像的历史资源的使用情况进行设置
  • NamespaceLifecycle:如果尝试在一个不存在的 namespace 中创建资源对象,则该创建请求将被拒绝。当删除一个 namespace 时,系统将会删除该 namespace 中所有对象。官方推荐使用
  • DefaultStorageClass:为了实现共享存储的动态供应,为未指定 StorageClass 或 PV 的 PVC 尝试匹配默认的 StorageClass,尽可能减少用户在申请 PVC 时所需了解的后端存储细节
  • DefaultTolerationSeconds:这个插件为那些没有设置 forgiveness tolerations 并具有 notready:NoExecute 和 unreachable:NoExecute 两种 taints 的 Pod 设置默认的“容忍”时间,为 5min
  • PodSecurityPolicy:这个插件用于在创建或修改 Pod 时决定是否根据 Pod的security context 和可用的 PodSecurityPolicy 对 Pod 的安全策略进行控制

10. 搭建高可用 k8s 集群

准备 3 台master,最少 1 台 node,搭建集群,k8s 组件分布如下图所示

k8s高可用集群组件部署图

etcd 因选举规则的原因,所以安装了 etcd 的 master 节点必须得是奇数台。

10.1 搭建高可用集群

部署集群前需要把各主机配置好基础设置。k8s 对主机的要求有: 关防火墙、关闭 SELinux、关 swap、安装 ipvs

各主机信息

主机名 IP 角色
arch 192.168.1.172 master
master 192.168.1.180 master
node1 192.168.1.181 node
node2 192.168.1.182 master

安装脚本和部署脚本在目录中

kubernetes 底层原理

CRI (container runtime interface) 容器运行时

容器运行时 CRI (container runtime interface) 是 k8s 从 1.5 版本开始,在遵循了 OCI 基础上,将容器操作抽象为一个个的接口。各种容器引擎(如 Docker 、 Rocket )通过实现这个接口来接入 k8s。在 k8s 中,通过 Kubelet 发送接口请求来实现容器启动和管理。

k8s调用链

2017年,由 Google、RedHat、Intel、SUSE、IBM 联合发起的 CRI-O(Container Runtime Interface Orchestrator)项目发布了首个正式版本。 CRI-O 非常纯粹,目标就是兼容 CRI 和 OCI,使得 k8s 不依赖于传统的容器引擎(比如 Docker),也能够实现管理容器各项工作。

Docker 在早期单独把 Containerd 开源了,还没有捐给 CNCF ,这时的 k8s 调用容器有以下两种方式: - kubelet 发请求,dockershim 调用 Docker ,Docker 调用 containerd 操作容器 - kubelet 发请求,cri-containerd 调用 Containerd 操作容器

Containerd与Docker交互

显而易见,dockershim 层层调用,调用链过于臃肿。与此同时,kubelet 的代码和 dockershim 的代码都是放在一个仓库内的,这意味着 dockershim 得由 k8s 进行组织开发和维护,但 Docker 版本的更新是 k8s 无法控制和管理的,所以 Docker 每次发布新的版本,k8s 都要集中精力去快速地更新维护 dockershim。因此 k8s 在 v1.24 版本正式弃用了 dockershim 。2018年,Docker 将 Containerd 捐献给 CNCF ,CNCF 在接手之后发布了 1.1 版本。该版本完美支持了 CRI 标准,也就是说,kubectl 将不再需要 cri-containerd 就可以直接和 containerd 交互!

contained 1.1 版本的改进

根据 Kubernetes 官方给出的 Containerd 1.1 对比 Docker 18.03 性能测试数据,Pod 的启动延迟降低了大约 20%;CPU 使用率降低了 68%;内存使用率降低了 12%,这是一个相当大的性能改善。

performance comparison

Reference

etcd

k8s 的存储层使用的是 etcd。etcd 是 CoreOS 开源的一个高可用强一致性的分布式存储服务,k8s 使用 etcd 作为数据存储后端,把需要记录的 pod、rc、svc 等资源信息存储在 etcd 中。 etcd 使用 RAFT 共识算法将一组主机组成集群,RAFT 集群中的每个节点都可以根据集群运行的情况在三种状态间切换:follower,candidate、leader。leader 和 follower 之间保持心跳,如果 follwer 在一段时间内没有收到来自 leader 的心跳,就会转为 candidate,发送新的选主请求。

k8s组件框架图