kubernetes
概述
基础设施变革
部署方式
| 部署方式 | 优点 | 缺点 |
|---|---|---|
| 传统部署 | 没有虚拟化层,性能高 | 维护与扩展性差;若多个应用部署在相同设备,由于应用之间未隔离,安全性低;若是将应用部署在不同设备,资源利用率又低。 |
| 虚拟化部署 | 可以充分利用系统资源;隔离好;易于维护与扩展 | 虚拟机本身存在消耗,如在操作系统之上的每个虚拟机又运行了一个操作系统 |
| 容器部署 | 不存在虚拟机本身的消耗,性能更高。隔离性较好;易于维护与扩展 | 隔离性低于虚拟化部署方式,高于传统部署方式 |

AAS
IAAS
Infrastructure as a Service(基础设施即服务),其底层基于虚拟化技术划分多台虚拟机,通过OpenStack等管理软件,统一对外提供弹性可伸缩的云资源。云供应商提供服务器,使用者需安装操作系统,软件运行环境,即可运行软件。

PAAS
Platform as a Service(平台即服务),其底层基于容器化技术创建多个容器,通过kubernetes等管理软件,统一对外提供弹性可伸缩的云资源。云供应商提供搭建有环境的服务器,使用者可直接运行软件。

SAAS
Software as a Service(软件即服务),直接提供软件供用户使用。
kubernetes组件
宏观架构
控制节点
Api server:kubernetes集群入口,向外部提供接口获取数据,接受外部请求执行操作。
kube-controller-manager:负责 Kubernetes 集群内部的资源管理和控制。
cloud-controller-manager:负责与云提供商的交互,管理云资源。
etcd:具备一致性和高可用性的键值存储,储存当前集群配置和信息
kube-scheduler:接受API server下发的运行应用请求,选择合适的节点运行容器。
webui:通过向API server发送请求获取当前集群数据用于展示
kubectl:kubernetes集群命令行工具,当输入命令,会将请求转发至API server,若是权限允许,则将该请求存入etcd数据库。调度器定期通过API server从etcd获取当前需要执行的任务执行
Node节点(即设备)
kubelet:监听API server,调用CNI插件进行Pod的管理。
k-proxy:当前节点负载均衡、防火墙规则的管理。

组件,插件,附件
组件,kubernetes的核心组成部分,如:Api serve、kube-controller-manager、kube-scheduler、kubelet。
插件,kubernetes提供接口,由外部软件进行扩展。如:Docker、CoreDNS、Ingress Controller。
附件,Prometheus,Dashboard,Federation。
补全插件安装
yum install bash-completion
source /usr/share/bash-completion/bash_completion
cd
# 在最后一行添加:source <(kubectl completion bash)
vim .bashrcpod与网络
pod
kubernetes集群调度的最小单位,一台设备上可以部署一个或多个pod,一个pod内可以部署一个或多个容器。
每个pod节点内都有一个pause容器,其是pod内部第一启动的容器,其他容器与 Pause 容器共享名字空间(Network、PID、IPC),如一个pod内启动两个nginx就会报错(不修改默认端口),因为他们都占用80端口。
作用:
- 初始化网络栈,其他容器将会加入pause创建的网络。
- 为其他容器提供挂载点,容器的存储卷(Volumes)可以挂载到 pause 容器创建的目录结构上。
- 回收僵尸进程,进程X创建master进程,master进程创建多个work进程,当master进程突然死掉,work进程将会挂载到X进程下,这些就是僵尸进程。
PID与IPC:
- PID 命名空间:用于隔离和管理进程 ID,允许共享命名空间的容器看到和管理彼此的进程。
- IPC 命名空间:用于隔离和管理进程间通信资源,允许共享命名空间的容器使用相同的 IPC 资源(Inter-Process Communication,进程间通信),如信号量,管道,共享内存等。

kubernetes网络
pod网络模型原则
总而言之,即先不考虑公网环境,先打通内部和内部之间的网络环境。
- 在不使用网络地址转换(NAT)的情况下,集群中同一个节点上的 Pod 能够与任意其他 Pod 进行通信
- 在不使用网络地址转换 (NAT)的情况下,在集群节点上运行的程序能与同一节点上的任何Pod 进行通信
- 每个 Pod 都有自己的 IP 地址(IP-per-Pod),并且任意其他 Pod 都可以通过相同的这个地址访问它
CNI
概述
CNI(Container Network Interface),由Kubernetes提供的接口,通过集成实现这些接口的插件,实现集群内部网络相互通信(容器和容器间)。
- ADD接口:将容器添加到网络
- DEL接口:从网络中删除一个容器
- CHECK接口:检查容器的网络是否符合预期等
分类
CNI通过 JSON 配置文件描述网络配置,当需要设置容器网络时,由容器运行时(CRI,即Docker等组件)负责执行 CNI 插件,传入配置文件信息,接收插件执行结果。从网络插件功能可以分为五类:
| 插件类型 | 功能描述 | 具体子功能 / 插件举例 |
|---|---|---|
| Main 插件 | 创建具体网络设备 | bridge:连接 container 和 host <br>ipvlan:为容器增加 ipvlan 网卡 <br/>loopback:解决回环接口问题,实现本机设备自我通信 <br/>macvlan:为容器创建一个 MAC 地址<br/>ptp:创建一对 Veth Pair <br/>vlan:分配一个 vlan 设备 <br/>host-device:将已存在的设备移入容器内 |
| IPAM 插件 | 负责分配 IP 地址 | dhcp:容器向 DHCP 服务器发起请求,给 Pod 发放或回收 IP 地址 <br/>host-local:使用预先配置的 IP 地址段来进行分配 <br/>static:为容器分配一个静态 IPv4/IPv6 地址,主要用于 debug |
| META 插件 | 其他功能的插件 | tuning:通过 sysctl 调整网络设备参数 <br/>portmap:通过 iptables 配置端口映射 <br/>bandwidth:使用 Token Bucket Filter 来限流 <br/>sbr:为网卡设置 source based routing <br/>firewal:通过 iptables 给容器网络的进出流量进行限制 |
| Windows 插件 | 专门用于 Windows 平台的 CNI 插件 | win-bridge 与 win-overlay 网络插件 |
| 第三方网络插件 | 第三方开源的网络插件,各有优点及适应场景,难成统一标准组件 | Flannel、Calico、Cilium、OVN 网络插件 |
运行流程

第三方网络插件比对
| 提供商 | 网络模型 | 路由分发 | 网络策略 | 网格 | 外部数据存储 | 加密 | Ingress/Egress 策略 |
|---|---|---|---|---|---|---|---|
| Canal | 封装(VXLAN) | 否 | 是 | 否 | K8s API | 是 | 是 |
| Flannel | 封装(VXLAN) | 否 | 否 | 否 | K8s API | 是 | 否 |
| Calico | 封装(VXLAN,IPIP)或未封装 | 是 | 是 | 是 | Etcd 和 K8s API | 是 | 是 |
| Weave | 封装 | 是 | 是 | 是 | 否 | 是 | 是 |
| Cilium | 封装(VXLAN) | 是 | 是 | 是 | Etcd 和 K8s API | 是 | 是 |
网络模型
Underlay Network(底层网络/非封装网络):
- 物理网络基础设施,包括路由器、交换机、物理链路等,通常使用标准的网络协议,如 IP、TCP、UDP 等。
- 数据包在传输过程中不进行额外的封装,直接使用原始协议进行传输。
Overlay Network(覆盖网络/封装网络):
- 构建在现有网络之上(即OSI七层模型,如VPN通常在传输层或网络层之上创建一个加密的隧道)的虚拟逻辑网络,采用GRE、VXLAN等隧道技术实现。
- 传输数据时,将数据包封装在另一个协议的数据包中进行传输。
路由分发
通过路由协议(如BGP协议),将当前每一个Pod的地址信息记录在路由表中,实现跨集群pod之间的通讯(即两pod不在一个子网下)。
网络策略
默认情况下,所有pod之间都是可以相互访问的,通过网络策略的配置,可以设置pod之间相互访问的权限(如允许pod A访问B,但不允许pod B访问A)
网格
如果是网格网络,则允许在不同的 Kubernetes 集群间进行 service 之间的网络通信。
外部数据储存
使用一个外部数据库来存储CNI网络插件的相关数据,通过K8s API连接etcd,或者直接连接etcd。
加密
允许节点间的网络通信数据加密
Ingress/Egress 策略
允许你管理 Kubernetes 和非 Kubernetes 通信的路由控制。
Calico
Calico 是一个纯三层的虚拟网络,它没有复用 docker 的 docker0 网桥,而是自己实现的,calico 网络不对数据包进行额外封装,不需要 NAT 和端口映射。
- Felix:管理网络接口,编写路由,编写 ACL(访问控制列表),报告状态。
- bird(BGP Client):BGP Client 将通过 BGP 协议广播告诉剩余calico 节点,从而实现网络互通。
- confd:通过监听 etcd 以了解 BGP 配置和全局默认值的更改。Confd 根据 ETCD 中数据的更新动态生成 BIRD 配置文件。当配置文件更改时,confd 触发 BIRD 重新加载新文件。

节点内pod通讯
veth pair两个接口彼此直接连接,数据包从一个接口发送后,会立即出现在另一个接口上。所以可以分别放在不同网络命名空间内,从而实现他们的通信。所以可通过在两个pod间建立veth pair 实现两个pod的通信,但是随着pod数的增多,两两pod都需要相连,将会导致veth pair的大幅度增多。
所以可以添加一个虚拟网桥,所有veth pair的一段连在pod容器内,另一端连在虚拟网桥。其起到一个交换机的作用,当一个pod节点发送数据帧,虚拟网桥根据目的MAC地址,由其通过veth pair转发到合适的其他pod容器(如果目的MAC地址和虚拟网桥自身MAC地址相同,则认为是要发往节点外的数据报,再做相应处理)
节点间pod通讯
VXLAN网络架构
VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是Linux本身支持的一网种网络虚拟化技术。其可以完全在内核态实现封装和解封装工作,从而通过“隧道”机制,构建出覆盖网络
- 代理转发:代理服务器接收你的数据包,处理后重新发送(如修改源IP地址为代理服务器IP后转发)
- 隧道转发:隧道转发将数据包加密并封装,发送到隧道出口服务器。出口服务器拆掉外层封装,再发送原始数据包到最终目的地。
基于三层的二层通信(此处三层、二层是指OSI模型中对应层),源主机将原始数据帧发送给隧道源VTEP,由其添加VXLAN头部,封装在UDP中。发送到隧道目的端VTEP,其去除多余信息,保留原始数据帧传输给目的主机,这个过程好像就在源主机和目的主机的数据链路层创建了一个隧道。
由于VXLAN创建了一个逻辑上的第2层网络(虚拟网络),这个网络在物理上可以跨越多个第3层网络。虚拟机在这个逻辑网络中迁移时,其IP地址和MAC地址保持不变,这对于pod节点的迁移而言是十分方便的。
类似于VLAN,VXLAN中使用VNI来划分网络(长度为24bit,VLAN对应字段仅有12bit),同一个VNI中的虚拟机,才可以进行二层相互通信,这些虚拟机所在域称为桥域(Bridge Domain)
若普通网络想要接入VXLAN,需要进行VLAN与VXLAN之间的映射。

node1上pod1通过veth pair将数据发送至calice0906292e2,由其转发给calico-vxlan,进行vxlan封装后发送给物理网卡转发给另一个节点node2。经过node2节点上的calico-vxlan解封装后交由cali46778cadcf1通过veth pair转发至对应pod2。
- 优势:只要 k8s 节点间三层互通, 可以跨网段,对主机网关路由没有特殊要求。
- 缺点:需要进行 vxlan 的数据包封包和解包会存在一定的性能损耗。

# 取消默认的IPIP,在IPV4和IPV6模式下开启VXLAN
# Enable IPlP
- name: CALICO_IPV4POOL_IPIP
value: "Never"
# Enable or Disable VXLAN on the default lP pool.
- name: CALICO_IPV4POOL_VXLAN
value:"Always"
# Enable or Disable VXLAN on the default lPv6 lp pool.
- name: CALICO_IPV6POOL_VXLAN
value:"Always"
#将backend段修改为基于vxlan实现
#calico_backend: "bird"
calico_backend: "vxlan"
# 关闭网桥就绪和存活探针
#--bird-ready
#--bird-live
IPIP网络架构
Linux 原生内核支持
IPIP 隧道的工作原理是将源主机的IP数据包封装在一个新的 IP 数据包中,新的 IP 数据包的目的地址是隧道的另一端。在隧道的另一端,接收方将解封装原始 IP 数据包,并将其传递到目标主机。IPIP 隧道可以在不同的网络之间建立连接,例如在 IPv4 网络和 IPv6 网络之间建立连接。

node1上pod1通过veth pair将数据发送至cali-pod1,由其转发给tunl0,进行IP封装后发送给物理网卡转发给另一个节点node2;经过node2节点上的tunel1解封装后交由cali-pod2通过veth pair转发至对应pod2。
- 优点:只要 k8s 节点间三层互通,可以跨网段,对主机网关路由没有特殊要求
- 缺点:需要进行 IPIP 的数据包封包和解包会存在一定的性能损耗

# Enable IPlP
- name: CALICO_IPV4POOL_IPIP
value:"Always"
# Enable or Disable VXLAN on the default lP pool.
- name: CALICO_IPV4POOL_VXLAN
value:"Never"
# Enable or Disable VXLAN on the default lPv6 lp pool.
- name: CALICO_IPV6POOL_VXLAN
value:"Never"
BGP网络架构
边界网关协议(Border Gateway Protocol, BGP)是互联网上一个核心的去中心化自治路由协议。它通过维护IP路由表或'前缀’表来实现自治系统(AS)之间的可达性,属于矢量路由协议。
pod1通过veth pair将数据发送至cali2009xsji11,由其直接传给物理网卡转发给另一个节点,不需要进行封装。到达node2后,物理网卡传递给calia93ixsji11通过veth pair将数据发送至pod2。
- 优点:不用封包解包,通过 BGP 协议可实现 pod 网络在主机间的三层可达
- 缺点:跨网段时,配置较为复杂网络要求较高,主机网关路由也需要充当 BGP Speaker。

# Enable IPlP
- name: CALICO_IPV4POOL_IPIP
value:"OFF"
# Enable or Disable VXLAN on the default lP pool.
- name: CALICO_IPV4POOL_VXLAN
value:"Never"
# Enable or Disable VXLAN on the default lPv6 lp pool.
- name: CALICO_IPV6POOL_VXLAN
value:"Never"
kubernetes安装
概述
kubeadm安装
- 组件通过容器化方式运行。
- 优势:简单;容器创建成本低,如果损坏,则直接杀死后创建新容器。
- 缺点:会掩盖部分细节,不利于理解,且不够灵活。
二进制安装
- 组件变成系统进程的方式运行。
- 优势:能够更加灵活的去创建集群,利于了解kubernetes底层细节。
- 缺点:复杂。
集群结构
以三台机器为例,一台master,两台Node,这三台设备均选择仅主机模式。通过Ikuai模拟真实路由器,通过NAT与外部网络相连。

Rocky Linux9基本配置
三个节点均需要进行配置,注意IP地址需要在同一个网段下,但不相同。
下载镜像
Rocky-9.5-x86_64-minimal.iso:为最小镜像,不包含图形化界面等内容
# 官方下载地址
https://rockylinux.org/download
# 阿里云镜像下载地址
https://mirrors.aliyun.com/rockylinux/9/isos/x86_64/?spm=a2c6h.25603864.0.0.29696621VzJej5环境初始化
静态Ip配置
# 进入到对应目录下
cd /etc/NetworkManager/system-connections/
# 对第一块网卡(仅主机模式)进行配置
vi ens160.nmconnection
# 对ipv4的相关配置进行修改,设置静态IP
method=manual
# 配置静态ip和子网掩码
# 注意,address1中静态ip配置需通过VMware左上角编辑,虚拟机网络编辑,看一下Vmnet1的子网IP是什么,配置的静态IP和其在同一个子网下。
# 后面的网关地址为Ikuai路由器中配置的IP,根据自己配置的IP填写
address1=192.168.66.12/24,192.168.66.200
dns=114.114.114.114;8.8.8.8
# 对第二块网卡进行配置(关闭NAT)
vi ens192.nmconnection
autoconnect=false
# 禁用ens192
nmcli d d ens192
# 重启ens160
nmcli d r ens160
nmcli c r ens160镜像源配置
# Rocky 系统软件源更换
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
-i.bak \
/etc/yum.repos.d/[Rr]ocky*.repo
dnf makecache设置系统参数
# 设置系统时区为亚洲/上海
timedatectl set-timezone Asia/Shanghai
# 因为swap分区会影响影响软件运行效率,所以kubernetes会检测交换分区是否关闭,默认未关闭会报错(如果一定需要交换分区的话,可以跳过该错误选项)
# 临时关闭 swap 分区
swapoff -a
# 永久关闭swap分区
vi /etc/fstab
# 注释掉swap相关行,如下
/dev/mapper/rl_bogon-swap none swap defaults 0 0,
# 查看swap分区,大小为0,即关闭成功:Swap: 0B 0B 0B
free -h
# 修改主机名。kubernetes集群默认以主机名做节点名称,修改便于维护
hostnamectl set-hostname k8s-node01
# 在hosts文件中添加主机名和对应的IP
vi /etc/hosts
192.168.126.11 k8s-master01 m1
192.168.126.12 k8s-node01 n1
192.168.126.13 k8s-node02 n2
# 后期可能会使用的harbor镜像
192.168.126.14 harbor
# 在节点m1配置后,将hosts文件发送至节点n1和n2
scp /etc/hosts root@n1:/etc/hosts
scp /etc/hosts root@n2:/etc/hosts网络配置
防火墙配置
# 停止 firewalld 服务
systemctl stop firewalld
# 禁用 firewalld 服务,使其在系统启动时不自动启动
systemctl disable firewalld
# 安装 iptables 服务
yum -y install iptables-services
# 启动 iptables 服务
systemctl start iptables
# 清空所有现有的 iptables 规则
iptables -F
# 使 iptables 服务在系统启动时自动启动
systemctl enable iptables
# 保存当前的 iptables 规则
service iptables save
# 临时禁用 SELinux
setenforce 0
# 修改 SELinux 配置文件,永久禁用 SELinux
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
# 更新所有内核的启动参数,禁用 SELinux
grubby --update-kernel ALL --args selinux=0
# 查看是否禁用 SELinux
# 这条命令用于检查默认内核的启动参数,确认 SELinux 是否被禁用
grubby --info DEFAULT
# 回滚内核层禁用 SELinux 的操作
# 如果需要重新启用 SELinux,可以使用这条命令移除禁用 SELinux 的启动参数
grubby --update-kernel ALL --remove-args selinux网络配置
# 安装 ipvs
yum install -y ipvsadm
# 开启路由转发
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
sysctl -p
# EPEL 提供了许多额外的软件包,这些包在默认的 CentOS/RHEL/Rocky Linux 仓库中不可用
yum install -y epel-release
# 安装 bridge-utils 工具包,提供了用于配置和管理网络桥接的工具
yum install -y bridge-utils
# 加载 br_netfilter 内核模块,是经过网桥的流量被防火墙所处理
modprobe br_netfilter
# 将 br_netfilter 模块添加到 /etc/modules-load.d/bridge.conf 文件中,确保开机自启动
echo 'br_netfilter' >> /etc/modules-load.d/bridge.conf
# IPv4 下,所有网桥的流量需要被防火墙处理
echo 'net.bridge.bridge-nf-call-iptables=1' >> /etc/sysctl.conf
# IPv6 下,所有网桥的流量需要被防火墙处理
echo 'net.bridge.bridge-nf-call-ip6tables=1' >> /etc/sysctl.conf
# 开启路由转发
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
# 重新加载 sysctl 配置文件,使更改生效
sysctl -p固定网卡(可选)
如果没有禁用网卡,可能出现绑定错误网卡的情况,通过下方配置解决该问题
# 目标 IP 或域名可达的网卡作为calico绑定的网卡
- name: calico-node
image: registry.geoway.com/calico/node:v3.19.1
env:
# Auto-detect the BGP IP address.
- name: IP
value: "autodetect"
- name: IP_AUTODETECTION_METHOD
value: "can-reach=www.google.com"
kubectl set env daemonset/calico-node -n kube-system IP_AUTODETECTION_METHOD=can-reach=www.google.com
# 正则匹配目标网卡作为calico绑定的网卡
- name: calico-node
image: registry.geoway.com/calico/node:v3.19.1
env:
# Auto-detect the BGP IP address.
- name: IP
value: "autodetect"
- name: IP_AUTODETECTION_METHOD
value: "interface=eth.*"
# 通过正则排除匹配网卡不作为calico绑定的网卡
- name: calico-node
image: registry.geoway.com/calico/node:v3.19.1
env:
# Auto-detect the BGP IP address.
- name: IP
value: "autodetect"
- name: IP_AUTODETECTION_METHOD
value: "skip-interface=eth.*"
# CIDR网段限制网卡作为calico绑定的网卡
- name: calico-node
image: registry.geoway.com/calico/node:v3.19.1
env:
# Auto-detect the BGP IP address.
- name: IP
value: "autodetect"
- name: IP_AUTODETECTION_METHOD
value: "cidr=192.168.200.0/24,172.15.0.0/24"Ikuai路由器配置
Ikuai下载,选择32位ISO镜像(可运行在低配置虚拟机,若实际生产中,应当选择64位)
创建一个新的虚拟机,1核心,1GB内存,8GB硬盘,网络连接选择仅主机模式(与其他三个节点位于同一个广播域下)。
操作系统类型选择其他,版本也选择其他,其余配置使用默认配置即可。

虚拟机创建完后,编辑虚拟机,添加一个新的NAT网卡(用于和外部外部网络连接),并且选择对应的Ikuai ISO镜像。

启动虚拟机开始配置,输入1安装到硬盘,输入y确认操作,安装成功后重启Ikuai虚拟机(低版本VMVare可能报错)。
输入2设置lan和vlan地址,输入0设置lan1地址,按照给定格式:IP/子网掩码设置(需要和其他三个节点在同一个网段下),回车设置成功后q退出,再输入q锁定。
浏览器输入刚设置的IP,即可访问(账号密码均为admin)
登录后选择网络设置—>内外网设置—>外网设置,绑定eth1后保存。
软件配置
Docker配置
# 添加Docker yum源(中科大),用于下载Docker
sudo dnf config-manager --add-repo https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo
cd /etc/yum.repos.d
# 切换中科大源
sed -e 's|download.docker.com|mirrors.ustc.edu.cn/docker-ce|g' docker-ce.repo
# 安装 docker-ce
yum -y install docker-ce
# default-ipc-mode设置IPC为共享方式
# data-root:设置Docker数据储存根目录
# exec-opts:设置本地驱动为systemd
# log-driver:设置日志驱动为json-file
# log-opts:设置文件数量,单个文件最大大小
# registry-mirror:设置镜像源
# insecure-registries:添加当前信任仓库地址
cat > /etc/docker/daemon.json <<EOF
{
"default-ipc-mode":"shareable",
"data-root": "/data/docker",
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "100"
},
"insecure-registries": ["harbor.xinxainghf.com"],
"registry-mirrors": [
"https://docker.1ms.run",
"https://registry.docker-cn.com",
"https://dockerhub.azk8s.cn"
]
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
# 重启docker服务
systemctl daemon-reload && systemctl restart docker && systemctl enable dockerCRI Docker
K8s Node节点通过Kubelet调用第三方软件进行容器创建。对于Podman等软件而言,需要使用CRI(Contain Runtime Interface)调用;而对于Docker而言,需要使用OCRI(Open Contain Runtime Interface)调用。
k8s选择了使用CRI进行调用,所以对于Docker而言,就需要有一个中间层进行CRI和OCRI的转换,即CRI Docker(此组件现已不由k8s官方维护,而是交由Docker进行维护)。

# 安装 cri-docker
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.9/cri-dockerd-0.3.9.amd64.tgz
tar -xf cri-dockerd-0.3.9.amd64.tgz
cp cri-dockerd/cri-dockerd /usr/bin/
chmod +x /usr/bin/cri-dockerd
#复制到其他节点
scp /usr/bin/cri-dockerd root@n1:/usr/bin/cri-dockerd
scp /usr/bin/cri-dockerd root@n2:/usr/bin/cri-dockerd
# 配置 cri-docker 服务
cat <<"EOF" > /usr/lib/systemd/system/cri-docker.service
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target firewalld.service docker.service
Wants=network-online.target
Requires=cri-docker.socket
[Service]
Type=notify
ExecStart=/usr/bin/cri-dockerd --network-plugin=cni --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.8
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process
[Install]
WantedBy=multi-user.target
EOF
# 添加 cri-docker 套接字
cat <<"EOF" > /usr/lib/systemd/system/cri-docker.socket
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service
[Socket]
ListenStream=%t/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker
[Install]
WantedBy=sockets.target
EOF
# 启动 cri-docker 对应服务
systemctl daemon-reload
systemctl enable cri-docker
systemctl start cri-docker
systemctl is-active cri-dockerK8s安装
# 添加 kubeadm yum 源(下方是官方的yum源,因为墙的问题可能会无法下载,建议使用本地安装包,或更换国内源)
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
# 使用镜像yum安装
yum install -y kubelet-1.29.0 kubectl-1.29.0 kubeadm-1.29.0
# 本地安装:
tar -zxvf kubernetes-1.29.2-150500.1.1.tar.gz
cd ./kubernetes-1.29.2-150500.1.1
yum -y install *
# r表示递归发送每一个文件
scp -r /root/kubernetes-1.29.2-150500.1.1 root@n1:/root/kubernetes-1.29.2-150500.1.1
scp -r /root/kubernetes-1.29.2-150500.1.1 root@n2:/root/kubernetes-1.29.2-150500.1.1
# kubelet需要以系统进程的方式运行,因为Docker仅能保证容器启动一次,但是不能保证其正常运行,所以需要Kubelet实现
systemctl enable kubelet.service
# 初始化主节点(Node节点不执行下方命令,注意apiserver-advertise-address需要根据主节点IP修改)
kubeadm init --apiserver-advertise-address=192.168.126.11 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version 1.29.2 --service-cidr=10.10.0.0/12 --pod-network-cidr=10.244.0.0/16 --ignore-preflight-errors=all --cri-socket unix:///var/run/cri-dockerd.sock
# 主节点创建之后,需要执行下方命令,在主节点启动的输出信息中包含
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 执行下方命令后,将会显示主节点启动未就绪
kubectl get node
# 子节点通过下方命令加入(存在有效时间,根据实际输出替换),并且需要添加--cri-socket unix:///var/run/cri-dockerd.sock
kubeadm join 192.168.126.11:6443 --token em8pi0.nqbc8m7t4aslvojh \
--discovery-token-ca-cert-hash sha256:fb24ab15bc6f152413dee626a34ecb3b46ef7c99c0bd87932274da3f3905c1d8 --cri-socket unix:///var/run/cri-dockerd.sock
# 如果token过期,主节点执行下方代码获取新token
kubeadm token create --print-join-command
# 执行下方命令后,将会显示一主两从三个节点启动但是未就绪
kubectl get node
网络插件部署
# 使用网络部署(需要访问外网)
# calico部署文件
https://docs.tigera.io/calico/latest/getting-started/kubernetes/self-managed-onprem/onpremises#install-calico-with-kubernetes-api-datastore-more-than-50-nodes
# 下载 Calico 网络插件的配置文件,并将其保存为 calico.yaml 文件
curl https://raw.githubusercontent.com/projectcalico/calico/v3.26.3/manifests/calico-typha.yaml -o calico.yaml
# 使用本地部署
unzip calico.zip
cd ./calico
tar -zxvf calico-images.tar.gz
# 发送给其他节点
scp -r /root/calico/calico-images root@n1:/root/calico-images
scp -r /root/calico/calico-images root@n2:/root/calico-images
# 加载镜像
docker load -i calico-typha-v3.26.3.tar
docker load -i calico-node-v3.26.3.tar
docker load -i calico-kube-controllers-v3.26.3.tar
docker load -i calico-cni-v3.26.3.tar
# 修改calico-typha.yaml配置文件
# 修改CALICO_IPV4POOL_CIDR,指定Pod网段。需要与master节点启动时指定的--pod-network-cidr一致,根据实际值替换
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
# 修改为 BGP 模式
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
#改成Off
value: "Off"
# 主节点启动kubectl,calico-typha.yaml为上方下载的配置文件
# 执行之后三个节点均处于就绪状态
kubectl apply -f calico-typha.yaml
# 查看所有Node节点状况,全部为running表示启动成功
kubectl get pod —A资源清单
资源类别
名称空间级别
方便在一个集群中进行服务的划分。
- 工作负载型资源:Pod、ReplicaSet、Deployment……
- 服务发现及负载均衡型资源:Service、Ingress……
- 配置与存储型资源:Volume、CSl……
- 特殊类型的存储卷:ConfigMap、Secre……
集群级资源
- Namespace、Node、ClusterRoleBinding、ClusterRole。
元数据型资源
- HPA、PodTemplate、LimitRange
资源清单的编写
资源清单结构
# 接口组的版本,由GROUP/VERSION组成,选择不同版本将会调用不同版本的API。不同版本的Kubernetes中对应资源使用的组不一样,其中core/v1默认省略core组,仅显示v1。
# kubectl api-version:可以查看所有的接口组的版本
# kubectl explain pod:可以查看指定资源的信息,这些信息中包含接口组版本(也包含了说明、子属性等信息),此处为查看Pod。
# kubectl explain pod.spec:查看pod的子属性,通过点的方式
# 定义资源时通过上方命令选择合适的版本。
apiVersion: v1
# 需要创建的资源的类别名
kind: Pod
# 元数据
metadata:
# 实例化资源的名称,此处为pod名
name: pod-demo
# 归属的组,不指定默认default
namespace:default
# 标签,用于管理资源,更好的筛选、分类。
# 以key:value键值对表示,可以添加用户指定的多个键值对作为标签(键值名任意)
labels:
app: myapp
version: v1
# 期望,指明需要创建一个怎样的容器
spec:
# 容器组
containers:
# 创建myapp-container容器
- name: myapp1
# 使用wangyanglinux/myapp:v1.0镜像创建容器,其内部运行了一个操作系统,该操作系统上运行了一个nginx (/usr/local下)
image: wangyanglinux/myapp:v1.0
# 需要容器执行的命令
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
# 状态,由Kubernetes实时的更新当前资源的状态,该清单不需要使用者去编写
status:
conditions:
- lastProbeTime: null
…………Pod的生命周期
- 多个mainc容器是并发创建的,同时存在的,所以时间轴上有交集。
- 执行默认启动命令,镜像封装时一般会定义。
- 钩子和探测由kubelet管理执行,而不是kube-controller-manager,因为其只有一个,随着节点数量的上升,会对其造成压力。而使用kubectl每个节点都有一个。
- 钩子和探测可以并存使用,可以根据容器需求,全部、部分、全不使用(就绪探测不定义默认就绪)
- 钩子和探针时间上并不是线性的,即使启动(就绪、存活)探测未成功,容器也可能在运行。

InitC(Init Container)
InitC除了无法定义就绪探测(readinessProbe),此外同其他容器定义相同。多个initc是线性启动的,只有前一个返回码为0(创建成功),后一个才可以创建,所有Initc创建完之后才会启动主容器(所以InitC并不会长时间存在)。
如果Pod的某个Init容器创建失败,则Kubernetes会不断的重建该Pod(即需要从头重建所有InitC),直到所有Init容器都成功为止(如果Pod对应的restartPolicy为Never将不会重建)
InitC可以与应用容器使用不同的镜像,将一些危险操作放在其内部进行。因为具有阻塞特性,可以进行一些初始化操作,确保容器具备运行环境后才被启动(如在initc中对mysql进行检测,只有确保mysql容器启动后,nginx容器才启动,避免请求发送时数据库无法访问)
检测InitC阻塞性
apiVersion: v1
kind: Pod
metadata:
name: initc-1
labels:
app: initc
spec:
containers:
# 创建一个容器myapp-container
- name: myapp-container
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
# 定义InitC
initContainers:
# 创建容器init-myservice
- name: init-myservice
image: wangyanglinux/tools:busybox
# 不断地检查名为 myservice 的服务是否可解析,如果不可用则每隔 2 秒输出一次 "waiting for myservice" 并重试,直到 myservice 服务可用为止。只有在 myservice 服务可用后,初始化容器才会完成,主容器才会启动。
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: wangyanglinux/tools:busybox
# 检测mydb服务是否可以使用
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
# 下方命令用于创建myservice和mydb服务,当服务创建成功之后,对应的InitC将会结束阻塞,当两个InitC都完成后,主容器才会创建
# kubectl create svc clusterip myservice --tcp=80:80
# kubectl create svc clusterip mydb --tcp=80:80探针
探针是由kubelet 调用由容器实现的 Handler进行定期诊断,有三种类型的处理程序:
- ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
- TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
- HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
每次探测都将获得以下三种结果之一:
- 成功:容器通过了诊断。
- 失败:容器未通过诊断。
- 未知:但无法确定容器的状态
三种就绪探测都可以进行如下配置
- initialDelaySeconds:容器启动后要等待多少秒后探针开始工作,单位为“秒”,默认是 0 秒,最小值是 0。对于就绪和存活探测,如果设置了启动探测,则它们会在启动探测成功后initialDelaySeconds时间才开始计时。
- periodSeconds:执行探测的时间间隔,单位为“秒”,默认为 10 秒,最小值是 1。
- timeoutSeconds:探针执行检测请求后(TCP和HTTP等),等待响应的超时时间,单位为“秒”,默认为 1 秒,最小值是 1。
- successThreshold:探针检测失败后认为成功的最小连接成功次数,默认值为 1。必须为 1 才能激活和启动,最小值为 1。
- failureThreshold:探测失败的重试次数,重试一定次数后将认为失败,默认值为 3,最小值为 1。
就绪探测(readinessProbe)
如果 pod 内部的容器不添加就绪探测,默认就绪。如果添加了就绪探测, 只有就绪通过以后,才标记修改为就绪状态。当前 pod 内的所有的容器都就绪,才标记当前 Pod 就绪,允许负载均衡供用户访问。
。
在旧版中,就绪探测成功后就结束;在新版中,就绪探测伴随容器整个生命周期,即使就绪探测成功,在一段时间后达不到就绪状态,依旧会被标记为未就绪。
- 成功:将当前的容器标记为就绪
- 失败:静默
- 未知:静默
基于HTTP GET
# 按照下方yaml配置创建容器,容器将会进行就绪探测,就绪探测为成功时pod将无法就绪
# ss-httpget-pod 0/1 ContainerCreating 0 15s
# 进入nginx容器:kubectl exec -it readiness-httpget-pod -- /bin/bash
# 创建index1.html:cd /usr/local/nginx/html/,echo "hello world" >> index1.html
# 此时http发送至index1.html返回将会是成功,容器状态将进入成功
# readiness-httpget-pod 1/1 Running 0 3m32s
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
labels:
app: myapp
env: test
spec:
containers:
- name: readiness-httpget-container
image: wangyanglinux/myapp:v1.0
# 如果当前设备存在镜像则不下载,使用该镜像,若是不存在则从远端拉取镜像。
imagePullPolicy: IfNotPresent
# 就绪探测
readinessProbe:
# 下方配置访问的是当前pod下80端口的/index1.html,因为一个pod是共用网络。
httpGet:
port: 80
# 访问当前服务根目录下的index1.html,由于其不存在,所以会阻塞
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3基于EXEC
apiVersion: v1
kind: Pod
metadata:
name: readiness-exec-pod
namespace: default
spec:
containers:
- name: readiness-exec-container
image: wangyanglinux/tools:busybox
imagePullPolicy: IfNotPresent
# /bin/sh:这是要执行的 Shell 程序。-c:表示接下来的参数是要由 Shell 执行的命令字符串。
# 先创建live文件,60s后再删除这个live文件
command: ["/bin/sh", "-c", "touch /tmp/live; sleep 60; rm -rf /tmp/live; sleep 3600"]
readinessProbe:
exec:
# 就绪探测live文件是否存在,由于执行的命令会将其删除,所以60s后这个容器将会就绪探测失败,进入未就绪状态
# 如果文件存在,命令返回成功(退出状态码为 0);如果文件不存在,命令返回失败(退出状态码为非 0)。
command: ["test", "-e", "/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3基于TCP
apiVersion: v1
kind: Pod
metadata:
name: readiness-tcp-pod
spec:
containers:
- name: readiness-tcp-container
image: wangyanglinux/myapp:v1.0
readinessProbe:
# 只要当前80端口可以访问成功,就代表就绪探测通过,但实际上端口可以联通并不意味着服务可用,所以这种方法并不常用。
tcpSocket:
port: 80
initialDelaySeconds: 5
timeoutSeconds: 1存活探测(livenessProbe)
如果 pod 内部不指定存活探测,可能会发生容器运行但是无法提供服务的情况
成功:静默
失败:根据重启的策略进行重启的动作,默认是一直重启
未知:静默。
基于HTTP GET
# 进入容器:kubectl exec -it liveness-httpget-pod -- /bin/bash
# 删除index.html或者将其重命名,退出后查看其重启(重建)次数将会增加,因为存活检测失败。
# 再次进入容器,index.html页面将会重新出现
# NAME READY STATUS RESTARTS AGE
# liveness-httpget-pod 1/1 Running 1 (8s ago) 2m20s
apiVersion: v1
kind: Pod
metadata:
name: liveness-httpget-pod
namespace: default
spec:
containers:
- name: liveness-httpget-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
path: /index.html
port: 80
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 3基于EXEC
# 容器创建成功后,等待60s,由于live文件被删除,所以存活探测失败,容器就会被重建(通过观察restart次数可以看出)
# 使用kubectl get pod -w监控
# NAME READY STATUS RESTARTS AGE
# liveness-exec-pod 0/1 ContainerCreating 0 13s
# liveness-exec-pod 1/1 Running 0 14s
# liveness-exec-pod 1/1 Running 1 (1s ago) 112s
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: liveness-exec-container
image: wangyanglinux/tools:busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "touch /tmp/live; sleep 60; rm -rf /tmp/live; sleep 3600"]
livenessProbe:
exec:
command: ["test", "-e", "/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3基于TCP
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp-pod
spec:
containers:
- name: liveness-tcp-container
image: wangyanglinux/myapp:v1.0
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 5
timeoutSeconds: 1启动探测(startupProbe)
保障存活探针在执行的时候不会因为时间设定问题导致无限死亡或者延迟很长的情况
成功:开始允许存活探测 就绪探测开始执行
失败:静默
未知:静默
基于HTTP GET
同样可以基于EXEC、TCP,此处仅以基于HTTP GET举例。
# 添加一个就绪探测,检测index2.html;一个启动探测,检测index1.html。
# 进入容器:kubectl exec -it startupprobe-1 -- /bin/bash
# 首先创建index1.html,此时查看pod,发现pod依旧未启动
# 在创建index2.html,此时查看pod,发现pod已经启动成了
# 即就绪探测、存活探测等只有在启动探测成功后才会开始
apiVersion: v1
kind: Pod
metadata:
name: startupprobe-1
namespace: default
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
path: /index2.html
port: 80
initialDelaySeconds: 1
periodSeconds: 3
startupProbe:
httpGet:
path: /index1.html
port: 80
failureThreshold: 30
periodSeconds: 10钩子
启动前钩子:并不能保证其一定在执行启动命令之前,他们可能有交集。所以在启动钩子中编译文件,在执行启动命令时运行编译后文件,这个想法是错的。
关闭前钩子:接受到关闭信号后会执行关闭前钩子(保存数据,关闭服务等),只有其被执行完成,这个关闭信号才会被发送给容器。但是对于关闭前的钩子执行有一个最多可以容忍的时间(grace period),默认是30s,超过时间将会强制杀死pod(在kubectl delete时,可以通过--grace-period指定时间,覆盖默认配置)。
基于命令
# 进入容器:kubectl exec -it lifecycle-exec-pod -- /bin/bash,查看/usr/share/message可以看到启动后钩子写入的:postStart
# 在容器内执行while true; do cat /usr/share/message done,一直输出message下的内容。复制标签页在另一个终端杀死pod,可以看到最后执行了关闭前钩子,输出preStop。
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-exec-pod
spec:
containers:
- name: lifecycle-exec-container
image: wangyanglinux/myapp:v1
lifecycle:
# 启动后钩子
postStart:
exec:
command: ["/bin/sh", "-c", "echo postStart > /usr/share/message"]
# 关闭前钩子
preStop:
exec:
command: ["/bin/sh", "-c", "echo preStop > /usr/share/message"]基于HTTP GET
# 使用docker运行一个容器,后续查看docker的日志就可以看到启动后钩子与关闭前钩子执行的访问
# docker run -it --rm -p 1234:80 wangyanglinux/myapp:v1.0
# 192.168.126.13 - - [10/Dec/2024:22:05:25 +0800] "GET /index.html HTTP/1.1" 200 48 "-" "kube-lifecycle/1.29"
# 192.168.126.13 - - [10/Dec/2024:22:06:15 +0800] "GET /hostname.html HTTP/1.1" 200 13 "-" "kube-lifecycle/1.29"
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-httpget-pod
labels:
name: lifecycle-httpget-pod
spec:
containers:
- name: lifecycle-httpget-container
image: wangyanglinux/myapp:v1.0
ports:
# 当前容器的端口
- containerPort: 80
lifecycle:
# 启动后钩子,会向192.168.66.11访问index.html
postStart:
httpGet:
host: 192.168.126.11
path: index.html
port: 1234
# 关闭前钩子,会向192.168.66.11访问index.html
preStop:
httpGet:
host: 192.168.126.11
path: hostname.html
port: 1234Pod调度流程

Pod控制器
控制器通过调谐确保集群的当前状态与期望状态保持一致。如下方配置文件中指定创建8个pod,若某个节点宕机导致pod数量不足,Deployment将会重建至8个;若是pod数量多于8个,则会删除一些pod(一般是新建的pod)
声明式与命令式
kubectl replace(命令式)
例:kubectl replace -f deployment.yaml
如果目标对象与文件本身发生改变,那么会根据新的配置文件完全重建此对象(替换)。
kubectl create(命令式)
例:kubectl create -f deployment.yaml
kubectl apply(声明式)
例:kubectl apply -f deployment.yaml
如果目标对象与文件本身发生改变,那么会根据新的配置文件,更新现有状态与配置中指定的不同的部分(部分更新),未指定的部分不受影响。
但是如果此文件描述的对象存在,那么即使文件描述的信息发生了改变,再次提交时也不会应用
# 使用下方配置文件创建一个Deployment控制器,将会创建一个pod
# myapp-deploy-7977896984-5s2lw 1/1 Running 0 2m5s
# kubectl scale deployment my-deploy --replicas=3:修改deployment的pod数量,执行之后将会运行5个pod。
# myapp-deploy-7977896984-5s2lw 1/1 Running 0 2m30s
# myapp-deploy-7977896984-f7bv9 1/1 Running 0 12s
# myapp-deploy-7977896984-lsnj4 1/1 Running 0 12s
# kubectl apply -f dm-1.yaml:修改配置文件中镜像版本为2.0,然后通过apply运行,通过curl podId可以看到返回信息修改为了version2.0(通过kubectl get pod -o wide获取IP),但是运行的pod依旧还是三个
# myapp-deploy-58b4dc6f5-95m9l 1/1 Running 0 8m29s
# myapp-deploy-58b4dc6f5-bbhh4 1/1 Running 0 8m47s
# myapp-deploy-58b4dc6f5-mx9vd 1/1 Running 0 8m35s
# www.xinxianghf.com | hello MyAPP | version v2.0
# kubectl replace -f dm-1.yaml:修改配置文件中镜像版本为3.0,然后通过replace运行,通过curl podId可以看到返回信息修改为了version3.0,但是运行的pod变为了一个(因为是重建,配置文件中没指定,直接使用了默认值1)
# www.xinxianghf.com | hello MyAPP | version v3.0
# myapp-deploy-6fd574ffd6-zfskt 1/1 Running 0 34s
# 查询当前配置文件与创建资源使用的配置文件有什么区别
# kubectl diff -f dm-1.yaml
# kubectl create -f dm-1.yaml:对于已有实例的资源清单,将无法执行
# Error from server (AlreadyExists): error when creating "dm-1.yaml": deployments.apps "myapp-deploy" already exists
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
# 此处是Deployment的标签,而不是pod的标签
labels:
app: myapp-deploy
spec:
# replicas未指定,默认为1
# 配置相关的标签选择器
selector:
matchLabels:
app: myapp-deploy
template:
metadata:
labels:
app: myapp-deploy
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0ReplicationController和ReplicaSet
在新版本的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController 。他们没有本质的不同,只是ReplicaSet 支持集合式的 selector。
删除控制器创建的pod是没有意义的,因为控制器会根据replicas自动重建pod。需要删除控制器,其创建的pod也会被删除:kubectl delete rc/rs --all。
ReplicationController
保障当前的 Pod 数量与期望值一致(期望保持一致,但实际上需要根据集群可以提供的资源决定最多可以运行多少pod),相较于pod保持其内部容器的运行,是不同的层级。
实际上除了RC控制器,其他控制器的标签都支持两种运算:子级运算和运算符运算。
# kubectl create -f rc.yaml:根据kubectl get pod可以查看创建了3个pod
# kubectl delete rc-demo-f7d7f:删除掉一个pod(模拟pod损坏,如某个节点宕机,导致上面所有pod不可用),再次查看可以看到新创建了一个pod。
# NAME READY STATUS RESTARTS AGE
# rc-demo-6mgnw 1/1 Running 0 11m
# rc-demo-sp2rn 1/1 Running 0 11m
# rc-demo-zqxkq 1/1 Running 0 3s
# kubectl label pod rc-demo-6mgnw app=test --overwrite:修改pod标签后,由于子集匹配的数量不足,rc控制器将会新创建一个pod。
# NAME READY STATUS RESTARTS AGE LABELS
# rc-demo-6mgnw 1/1 Running 0 18m app=test,key=value
# rc-demo-9wpfd 1/1 Running 0 12s app=rc-demo
# rc-demo-sp2rn 1/1 Running 0 18m app=rc-demo
# rc-demo-zqxkq 1/1 Running 0 7m11s app=rc-demo
# kubectl label pod rc-demo-6mgnw app=rc-demo --overwrite,将原先的pod标签修改回去,由于子集匹配的数量超过定义数量,rc控制器将会删除一个pod(即最新创建的pod,因为旧pod拥有的连接、资源、产生的数据一般相较于新pod是更多的)
# kubectl scale rc rc-demo --replicas=5:动态的调整rc控制器创建pod的数量
# RC 控制器
apiVersion: v1
kind: ReplicationController
metadata:
name: rc-demo
spec:
# 期待的pod数量,只有由当前控制器创建,且标签符合的pod才会记录
replicas: 3
# 选择器,符合选择器的pod将会被RC控制器管理,进行子集运算,即其是下方pod定义的子集
selector:
app: rc-demo
# 模板,创建pod的信息描述是
template:
# 定义pod的子属性,如钩子、探针在下方配置中均可以使用
metadata:
# pod的标签,如果当前标签不是选择器的子集,启动时将会报错
labels:
app: rc-demo
spec:
containers:
- name: rc-demo-container
image: wangyanglinux/myapp:v1.0
# 通过env来配置当前pod容器的环境变量
env:
- name: GET_HOSTS_FROM
value: dns
- name: zhangsan
value: "123"
ports:
- containerPort: 80ReplicaSet
rs 在标签选择器上,除了可以定义键值对的选择形式,还支持 matchExpressions 字段,可以提供多种选择。
- In:label 的值在某个列表中
- NotIn:label 的值不在某个列表中
- Exists:某个 label 存在
- DoesNotExist:某个 label 不存在
# kubectl label pod rs-me-exists-demo-48fmk app=test --overwrite:修改标签的values值,不会重新创建容器
# rs-me-exists-demo-48fmk 1/1 Running 0 3m10s app=test
# 不使用matchExpressions,效果和rc控制器一样
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-ml-demo
spec:
# 创建3个pod
replicas: 3
selector:
# 匹配标签,用于和下方的pod进行子集运算
matchLabels:
app: rs-ml-demo
template:
metadata:
labels:
app: rs-ml-demo
spec:
containers:
- name: rs-ml-demo-container
image: wangyanglinux/myapp:v1.0
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80# RS 控制器 - matchExpressions: Exists
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-me-exists-demo
spec:
# 未指明期待的pod数量,默认为1。(只有由当前控制器创建,且标签符合的pod才会记录)
selector:
# 只要存在标签key为app的pod即认可
matchExpressions:
- key: app
operator: Exists
template:
metadata:
labels:
app: spring-k8s
spec:
containers:
- name: rs-me-exists-demo-container
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80
# RS 控制器 - matchExpressions: In
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-me-in-demo
spec:
selector:
matchExpressions:
# 标签为app,且值要在下方values中
- key: app
operator: In
values:
- spring-k8s
- hahahah
template:
metadata:
labels:
# value不符合标签选择,运行将会直接报错,需要修改为hahahah或spring-k8s
app: sg-k8s
spec:
containers:
- name: rs-me-in-demo-container
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80Deployment
Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的 ReplicationController 来方便的管理应用。典型的应用场景包括
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-demo
labels:
app: deployment-demo
spec:
replicas: 5
selector:
matchLabels:
app: deployment-demo
template:
metadata:
labels:
app: deployment-demo
spec:
containers:
- name: deployment-demo-container
image: wangyanglinux/myapp:v1.0滚动升级
滚动流程
Deployment实际上是通过创建RS,由RS进行pod的创建。当进行升级时,为了避免对pod的访问中断,Deployment会先创建一个新的rs,由rs创建一部分新的pod,然后杀死一部分老的pod,以此类推直到所有pod升级完成。(滚动更新)
而对于原先的RS,并不会将其删除,而是继续存储在etcd数据库中,用于回滚操作(即对滚动更新做逆向操作,依次按照旧的资源清单创建pod)。

假设在执行了一个命令,将当前deployment升级为2.0。但是在部分升级后,再次执行命令,将deployment升级为3.0,此时无论处在V1还是V2的pod,都将会直接升级到V3,而不是先升级到V2,再升级到V3.

更新策略
Deployment可以保证在滚动升级时只有一定数量的pod是down的,最多比期望的pod数量多一定数量的pod是up,在apps/v1beta接口中这两个数量都是1,而在apps/v1接口中这两个数量都是25%。
# 假设maxSurge和maxUnavailable都是2,那么滚动升级时,可以先创建两个新的pod,然后再删除两个旧的pod(假设此时新pod还不能用)。
# 滚动幅度越大,滚动升级越快,但是在大规模用户情况下可能出现连接中断问题。
spec:
strategy:
rollingUpdate:
# 指定超出副本数可以有几个,使用数量或者百分比指定
maxSurge: 25%
# 最多有几个不可用,同样使用数量或者百分比指定
maxUnavailable: 25%
# 也可以将滚动更新替换为重建
type: RollingUpdate回滚应用
通过etcd记录
# 通过修改rs的期望,将当前pod回滚到上一个状态。注意,只能回滚到上一个状态,假设S1 -> S2 -> S3,当到达S3后,执行回滚将到达S2,再次执行回滚将回到S3,而不是S1。
$ kubectl rollout undo deployment/deployment-demo
# 检查 deployment-demo 部署的滚动更新状态。它会显示当前滚动更新的进度,并告知你是否已经完成更新。
$ kubectl rollout status deployments deployment-demo
# 查看 deployment-demo 部署的滚动更新历史记录,CHANGE-CAUSE是空的是因为在变更资源清单时没有指定--record(假设上次指定,本次次未指定,那么k8s会复制上次的record作为本次的record)
# REVISION CHANGE-CAUSE
# 3 <none>
# 4 <none>
$ kubectl rollout history deployment/deployment-demo
# 回滚到指定修订版本,这个版本需要通过history命令查看
$ kubectl rollout undo deployment/deployment-demo --to-revision=2通过配置文件
若通过配置文件方式回滚,则etcd中的记录就没有必要存在了。通过设置 spec.revisionHistoryLimit 项来指定 deployment 最多保留多少revision 历史记录。
默认的会保留所有的 revision,如果将该项设置为0,Deployment就不允许回退了7
# 不直接修改配置文件,而是复制一份当前配置文件,使用文件名描述相关信息,在该文件上修改。如果要回滚,直接应用对应版本的文件即可。
# 升级
cp dm-2.yaml dm-2.yaml.2024-12-13-9:25
kubectl apply -f dm-2.yaml.2024-12-13-9:25
# 回滚
kubectl apply -f dm-2.yaml扩容和缩容
扩容和缩容和rc和rs控制器相同
# 扩容
$ kubectl scale deployment nginx-deployment --replicas 10
# 最小值10个pod(节省资源,但是也需要保证最小值可以应对突然的流量高峰),最大值15个pod(避免代码问题、恶意攻击导致资源无限使用)
# 自动扩容,当 Pod 的平均 CPU 使用率超过 80% 时,HPA 会增加 Pod 的副本数;否则HPA 会减少 Pod 的副本数。但是数量会位于最小值和最大值间
$ kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80暂停和继续
在进行更新时,我们可以先将部分pod替换为新版本,然后暂停滚动,此时存在少部分新版本和大部分旧版本,等待用户使用不存在bug后(若是存在bug,因为只有少部分新pod,也仅会影响部分用户),再继续滚动所有pod至新版本。(金丝雀部署)
# 使用patch添加补丁,指定超出副本数可以为1,最多有0个不可用
kubectl patch deployment deployment-demo -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
# 修改镜像,并暂停滚动更新,此时将会多出一个新pod,而旧pod数量不变
kubectl patch deployment deployment-demo --patch '{"spec": {"template": {"spec": {"containers": [{"name": "deployment-demo-container","image": "wangyanglinux/myapp:v3.0"}]}}}}' && kubectl rollout pause deploy deployment-demo
# 继续滚动更新,最终所有pod将会被替换为新pod
kubectl rollout resume deploy deployment-demoDaemonSet
DaemonSet 确保全部(或者部分,如master节点上默认不运行pod)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod。当有 Node 从集群移除时,这些 Pod 也会被回收。删除DaemonSet 将会删除它创建的所有 Pod。
用于每个节点上都需要运行的程序,如日志收集daemon,监控 daemon。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: daemonset-demo
labels:
app: daemonset-demo
spec:
# 不需要设置副本数量,会自动根据节点修改。
selector:
matchLabels:
name: daemonset-demo
template:
metadata:
labels:
name: daemonset-demo
spec:
containers:
- name: daemonset-demo-container
image: wangyanglinux/myapp:v1.0Job/CronJob
Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束
Job
pod返回为零表示当前pod运行成功。
基本使用
# 运行下方job控制器后,通过kubectl logs podName可以查看运算输出的结果
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
spec:
template:
metadata:
name: job-demo-pod
spec:
containers:
- name: job-demo-container
# 该镜像封装了一个python的解释器,将会执行一份代码,输出求解π的前10000位
image: wangyanglinux/tools:maqingpythonv1
# pod中容器重启策略,Job控制器仅仅支持Never和OnFailure,不支持Always。设置为Never后,如果pod成功的数量没有达到completions规定的(默认为1),job控制器会再次重新创建新pod
restartPolicy: Never错误码为0
# 控制为 1 的错误返回码
apiVersion: batch/v1
kind: Job
metadata:
name: rand-exitcode-1
spec:
template:
metadata:
name: rand-exitcode-1
spec:
containers:
- name: rand
# 镜像里面封装了一个go程序,传入exitcode可以指定镜像运行返回结果(非零都表示失败)
image: wangyanglinux/tools:randexitv1
args: ["--exitcode=1"]
restartPolicy: Never随机错误码
# 随机生成返回码
apiVersion: batch/v1
kind: Job
metadata:
name: rand
spec:
# 标志 Job 结束需要成功运行的 Pod 个数,默认为 1
completions: 10
# 标志并行运行的 Pod 的个数,默认为 1(即所有pod需要串行运行)
parallelism: 5
# 标志失败 Pod 的重试最大时间,超过这个时间不会继续重试
activeDeadlineSeconds: 600
template:
metadata:
name: rand
spec:
containers:
- name: rand
# 该镜像不指定返回码参数,则50%概率返回0,50%返回非零。
image: wangyanglinux/tools:randexitv1
restartPolicy: NeverCronJob
CronJob会在给定的时间点周期性调度job运行,要求Kubernetes 集群版本 >= 1.8,可以用于数据备份、邮件发送等操作。
apiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob-demo
spec:
# 用于指定任务运行周期,格式同 Cron
schedule: "*/1 * * * *"
# 启动 Job 的期限(秒级别),如果因任何原因错过了被调度的时间,那么错过执行时间的 Job 将被认为是失败的。
# 假设原本规定在凌晨3点(流量低谷)进行数据库备份job任务,但是由于某种原因到上午8点(流量波峰)才开始job调度,如果此时继续数据库备份,可能导致服务崩溃
startingDeadlineSeconds: 60
# 并发策略,假设每分钟调度一次,但是上一job还未运行完成,下一个job就已经创建了,此时就需要根据并发策略确定job的运行方式。
# 注意:下方策略是对于同一个Cron Job创建的Job而言,如果存在多个Cron Job,它们创建的Job间总是允许并发运行的。
# Allow:允许并发运行pod
# Forbid:禁止并发运行,如果前一个还未完成,则直接跳过下一个
# Replace:取消当前正在运行的pod,用一个新的替代。
concurrencyPolicy: Allow
# 挂起字段,若为true,后续所有Job的执行都会被挂起,但是对已经开始执行的Job不起作用,可以用于暂时暂停定时任务
suspend: false
# 可以保留多少完成的Job,默认为3
successfulJobsHistoryLimit: 3
# 可以保留多少失败完成的Job,默认为1(用于排错)
failedJobsHistoryLimit: 1
# 指定需要运行的任务,格式同 Job
jobTemplate:
spec:
template:
spec:
containers:
- name: cronjob-demo-container
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailureService
Kubernetes Service提供了一个稳定的 IP 地址和 DNS 名称,用于访问一组 Pod(通过标签选择器来确定它所代理的 Pod),并提供四层负载均衡。
当Pod离线或者有新Pod加入时,Service会自动将其剔除或者纳入,确保对外提供的Pod始终可用,这样调用者只需要访问Service就可以获得服务了,而无需关心Pod的具体运行情况了。

Service概念
在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy进程。kube-proxy负责为Service实现了一种 VIP(虚拟 IP)的形式
- 在 Kubernetes v1.0 版本,代理完全在 userspace。
- 在 Kubernetes v1.1 版本,新增了 iptables 代理但并不是默认的运行模式。
- 在 Kubernetes v1.2 版本,默认就是 iptables 代理。
- 在 Kubernetes v1.8.0-beta.0 版本中,添加了ipvs 代理(默认还是iptables)
Kubectl将Service规则发送到apiserver,存储到etcd中。kube-proxy监听apiserver,将etcd中的规则落地到ipvs/Iptables中。

相关概念
- Iptables:一个用户空间工具,用于配置 Linux 内核中的 Netfilter 防火墙。它主要用于设置、维护和检查 IP 数据包过滤规则,可以控制数据包的转发、丢弃、修改等操作。同时,Iptables也可以通过 NAT(网络地址转换)和 DNAT(目标网络地址转换)规则支持简单的负载均衡。
- Ipvs(IP Virtual Server): Linux 内核中的一个模块,专门用于实现负载均衡,支持更多的负载均衡算法,运行在内核态(减少切换损失),采用HASH表查找链接信息(Iptables采用链表),效率比Iptables更高。
- 四层负载均衡:基于传输层的负载均衡,通过检查传输层的协议头信息(如IP地址和端口号)来决定如何分发网络流量
- 七层负载均衡:基于应用层的负载均衡,通过检查应用层的内容(如HTTP头、URL路径、Cookie等)来决定如何分发网络流量。
基于userspace
当客户端向 Service 的 ClusterIP 地址和端口发起请求,经过Iptables规则,会将请求重定向到 Kube-proxy,某个节点上的Kube-proxy 收到请求后,根据 Service 的负载均衡策略选择一个后端 Pod代理该请求,最后将响应返回。
- 每个节点上的kube-proxy监听api-server,获取负载均衡的改变信息(也就是Service),将其写入Iptables。
- 代理来自当前节点 pod 的用户请求。
<img src="k8s_image/image-20241214212359886.png" alt="image-20241214212359886" style="zoom: 67%;" />
基于Iptables
kube-proxy只负责监听 APISERVER,将 Service 变化修改本地的 iptables 规则中。相对于 userspace,kube-proxy 功能解耦,压力较小
<img src="k8s_image/image-20241214212520274.png" alt="image-20241214212520274" style="zoom:67%;" />
基于Ipvs
和基于Iptables方式基本一致,只是将负载均衡由Iptables切换为了ipvs,但默认Kubernetes没有启用该配置(因为存在设备可能未开启该功能),如果开启,该配置的效率一定是高于Iptables的。
# 修改Service配置:mode: ipvs,基于ipvs模式
kubectl edit configmap kube-proxy -n kube-system
# 删除所有包含kube-system标签的Pod,自动重建后的Pod将会采用ipvs模式
kubectl delete pod -l k8s-app=kube-proxy -n kube-system
# 查看ipvs配置的负载均衡表
ipvsadm -Ln<img src="k8s_image/image-20241214212444473.png" alt="image-20241214212444473" style="zoom:67%;" />
Service工作模式
ClusterIP
默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP,如下方配置,Nginx Pod直接在配置文件中绑定Cluster IP提供的地址,就可以负载均衡到所有标签为Tomcat的Pod。但ClusterIP提供的IP,在集群外部是无法访问的。
只有就绪且标签子集匹配Clusterip标签的Pod才可以被加入负载均衡。

基本使用
# 创建下方两个资源清单,kubectl get svc可以查看到创建的ClusterIP提供了一个对外IP:10.4.191.211:80
# ipvsadm -Ln:TCP 10.4.191.211:80 rr,可以查看到该ip负载均衡规则,由于三个Pod都没有就绪探测成功,所以下方负载ip为空
# 通过下方命令完成就绪探测,完成的Pod将会纳入Cluster IP的负载均衡中,curl 10.4.191.211:80/index1.html可访问对应的Pod,多次访问可以查看负载均衡现象。
# exec -it myapp-clusterip-deploy-5c9cc9b64-jjkm7 -- /bin/bash
# date >> /usr/local/nginx/html/index1.html
# Service资源清单
apiVersion: v1
kind: Service
metadata:
name: myapp-clusterip
namespace: default
spec:
type: ClusterIP
# 选择器需要时Pod选择器的子集
selector:
app: myapp
release: stabel
svc: clusterip
ports:
- name: http
# 对外负载均衡的端口
port: 80
# 后端Pod真实服务的端口
targetPort: 80
# deploy资源清单
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-clusterip-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
svc: clusterip
template:
metadata:
labels:
app: myapp
release: stabel
env: test
svc: clusterip
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
# name用于给端口命名,在其他配置中引用可以使用http代替80
- name: http
# 容器对外使用的端口80
containerPort: 80
# 就绪探测
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3DNS解析
每个Service在创建时都会被自动分配一个域名,格式为Service名字.名称空间.svc.集群域名(如myapp-clusterip.default.svc.cluster.local),其中集群域名不修改默认为cluster.local。
通过集群内的DNS插件解析该域名,得到的结果就是Service对外暴露的IP,相较于直接使用IP访问,使用域名访问可以在Servic还未创建时便得知访问地址。
# 进入上方的某一个pod
kubectl exec -it myapp-clusterip-deploy-5c9cc9b64-kqc72 -- /bin/bash
# 通过域名发起访问,结果和使用clusterIP结果一致。
curl http://myapp-clusterip.default.svc.cluster.local./index1.html配置
访问配置
如果clusterIP仅需要本地访问,建议修改为Local,这样可以节省大量资源。
spec:
# ClusterIP是整个集群准备好的节点都可以访问,跨节点
internalTrafficPolicy: Cluster
# local是只有本节点的pod和本地可以访问,不能跨节点
# curl: (7) Failed to connect to 10.4.191.211 port 80: Connection refused
internalTrafficPolicy: Local持久化链接
有限的时间内,将用户的请求定位到一台设备上。
# 配置前访问ClusterIP,会进行负载均衡到不同Pod上。
# 配置后访问ClusterIP,同一个设备发送的请求,将会被定位到同一个Pod上。
# ipvsadm -Ln,将会在svc IP后面显示持久化链接的时间
# TCP 10.4.191.211:80 rr persistent 10800
# -> 10.244.58.208:80 Masq 1 0 4
# -> 10.244.58.211:80 Masq 1 0 0
# 修改sessionAffinity为ClientIp即可进行持久化链接(默认为None)
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
# 配置持久化连接的时间,单位为秒,该值位于0-86400(1天)中,默认为10800(3小时)
timeoutSeconds: 10800 NodePort
在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,任一台设备都可以通过<NodeIP>:<NodePort>来访问该服务,并自动负载均衡到绑定的Pod上,所以集群内的任意设备都可以作为外部访问的接口。
- 单个浏览器访问刷新,可能由于缓存等原因无法展现出负载均衡现象,可以通过多开控制面板,使用curl模拟。
- NodePort也支持集群内部的访问,但是其消耗的资源会比ClusterIP更多,所以如果不需要集群外访问,就直接使用ClusterIP。

基本使用
# ipvsadm -Ln查看
# 在每台设备上的所有的物理网卡,都对三个Pod进行了负载均衡
# TCP 172.17.0.1:30001 rr
# -> 10.244.58.213:80 Masq 1 0 0
# -> 10.244.58.216:80 Masq 1 0 0
# -> 10.244.85.207:80 Masq 1 0 0
# TCP 192.168.126.11:30001 rr
# -> 10.244.58.213:80 Masq 1 0 0
# -> 10.244.58.216:80 Masq 1 0 0
# -> 10.244.85.207:80 Masq 1 0 0
# 在集群内部,也对Pod进行了负载均衡
# TCP 10.11.153.141:80 rr
# -> 10.244.58.213:80 Masq 1 0 0
# -> 10.244.58.216:80 Masq 1 0 0
# -> 10.244.85.207:80 Masq 1 0 0
# 创建deploy,拥有三个Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-nodeport-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
svc: nodeport
template:
metadata:
labels:
app: myapp
release: stabel
env: test
svc: nodeport
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
# 创建NodePort
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
namespace: default
spec:
type: NodePort
# Pod的标签需要包含以下标签
selector:
app: myapp
release: stabel
svc: nodeport
ports:
- name: http
# 集群内部访问NodePort的端口
port: 80
#
targetPort: 80
nodePort: 30001配置
spec:
# ClusterIP是整个集群准备好的节点都可以访问,跨节点
internalTrafficPolicy: Cluster
# local是只有本节点的pod和本地可以访问,不能跨节点
# curl: (7) Failed to connect to 10.4.191.211 port 80: Connection refused
internalTrafficPolicy: Local
# 支持集群外通过NodePort在物理网卡上暴露的端口进行访问
externalTrafficPolicy: Cluster
# 不支持集群外通过NodePort在物理网卡上暴露的端口进行访问
externalTrafficPolicy: LocalLoadBalancer
在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到<NodeIP>:<NodePort>。负载到集群内的所有机器,而不是使用单一节点接受外部请求,这样可以避免某一个节点离线导致服务宕机。

# 下方为阿里云提供的负载均衡组件配置样例
apiVersion: v1
kind: Service
metadata:
# annotations用于与第三方进行某些配置约定
annotations:
# 配置阿里云的LoadBalancer Id
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-id: ${YOUR_LB_ID}
# 配置 Kubernetes 在每次更新 Service 时覆盖阿里云负载均衡器上现有的监听器配置。
service.beta.kubernetes.io/alicloud-loadbalancer-force-override-listeners: 'true'
labels:
app: nginx
name: my-nginx-svc
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancerExternalName
把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持。
如下图:ExternalName指定一个外部地址(如192.168.66.55),在创建时会自动分配一个域名:svc名字.名称空间.svc.集群域名(如db.default.svc.cluster.local),Pod直接访问该域名,然后通过CoreDNS解析到192.168.66.55。
如果需要修改Pod访问的地址,只需要修改CoreDNS指向的外部地址即可(如果Pod直接指向访问地址,就需要一个一个Pod修改了)。

# 进入Pod容器:exec -it myapp-nodeport-deploy-685dcc6ddf-vvqb6 -- /bin/bash
# ping为ExternalName分配的域名:ping my-service-1.default.svc.cluster.local
# 结果被解析到了103.235.47.188,即百度的域名
# PING my-service-1.default.svc.cluster.local (103.235.47.188): 56 data bytes
# 64 bytes from 103.235.47.188: seq=1 ttl=126 time=320.174 ms
# --- my-service-1.default.svc.cluster.local ping statistics ---
# 4 packets transmitted, 3 packets received, 25% packet loss
# round-trip min/avg/max = 303.768/317.198/327.652 ms
kind: Service
apiVersion: v1
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: www.baidu.comService-Endpoints
Endpoint 是一个 Kubernetes 对象,它动态记录了与某个 Service 相关的 Pod 的 IP 地址和端口,并且由kube-proxy 将这些IP和端口同步到Ipvs的负载均衡中。

自动关联体系
当一个 Service 被创建时,Kubernetes 会自动创建一个对应的 Endpoint 对象,并将符合条件的 Pod 的 IP 地址和端口添加到这个 Endpoint 对象中(标签匹配且Pod状态为 Running),并且在之后持续监测。
# 执行kubectl get ep
# NAME ENDPOINTS AGE
# kubernetes 192.168.126.11:6443 14d
# myapp-nodeport 10.244.58.213:80,10.244.58.216:80,10.244.85.207:80 5h37m
手动关联体系
由于未指定Service的选择器,所以不会创建对应EndPoint,需要在同一个空间下手动创建同名的EndPoint。

# curl 10.12.180.74:6666,访问集群IP,将会被负载均衡到对应Endpoints指定的IP上。
# 创建一个Service,由于未指定标签选择器,所以不会创建对应的EndPoint。
apiVersion: v1
kind: Service
metadata:
name: nginx-noselectt
spec:
ports:
- protocol: TCP
# 集群对外端口为6666
port: 6666
# 负载均衡的Pod端口为80
targetPort: 80
# 手动创建对应的EndPoint,指定远程服务的IP和端口。
apiVersion: v1
kind: Endpoints
metadata:
name: nginx-noselectt
subsets:
- addresses:
- ip: 192.168.126.12
ports:
- port: 80
# 在远程服务器运行镜像,此时通过Service即可访问到。
docker run -itd -p 80:80 --net host wangyanglinux/myapp:v1Service-publish NotReadyAddresses
Service进行Pod匹配时,仅会匹配**标签符合且状态为 Running **的Pod。但是通过修改publishNotReadyAddresses,可以使未就绪的Pod也加入到Service中。
# 直接访问,将会提示无法连接
# curl: (7) Failed to connect to 10.14.91.112 port 80: Connection refused
# 修改之后再访问,将会访问成功:kubectl patch service myapp -p '{"spec":{"publishNotReadyAddresses": true}}'
# www.xinxianghf.com | hello MyAPP | version v1.0
# 创建一个Pod,其将会阻塞在就绪探测中,将处于未就绪状态。
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
labels:
app: myapp
env: test
spec:
containers:
- name: readiness-httpget-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
# 创建一个Service,选择匹配的Pod
apiVersion: v1
kind: Service
metadata:
labels:
app: myapp
name: myapp
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: myapp
type: ClusterIP
# 添加配置,允许未就绪的Pod被Service选中,进行负载均衡
kubectl patch service myapp -p '{"spec":{"publishNotReadyAddresses": true}}'存储
元数据
configMap:用于保存配置数据(明文)
Secret:用于保存敏感性数据(编码)
Downward API:容器在运行时从 Kubernetes APl服务器获取有关它们自身的信息
真实数据
- Volume:用于存储临时或者持久性数据
- PersistentVolume:申请制的持久化存储
ConfigMap
Kubernetes1.2 版本中引入,ConfigMap API提供了向容器中注入配置信息的机制。其可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制等对象
共享:文件存储在外部,每一次读取文件时,都需要进行IO请求配置文件。
注入:文件直接注入Pod内,多次读取不再消耗网络IO。
ConfigMap创建
基于文件方式创建,如果文件内容是key-value形式,那么可以注入Pod内作为环境变量使用;也可以作为文件进行挂载,key为文件名,value为文件内容;如果文件内容不是key-value形式,就只能作为文件进行挂载。
# 创建一个Configmap,名字为game-Config,基于hongfu.file这个文件创建
$ kubectl create configmap file-configmap --from-file=hongfu.file
# 创建一个configmap,名字为env-config,基于指定的环境变量文件创建
$ kubectl create configmap env-configmap -from-env-file=path/to/env/file
# 创建一个configmap,名字为literal-config(literal:文字的),直接指定configmap的key-value组
$ kubectl create configmap literal-configmap --from-literal=name=dave --from-literal=password=pass
# 也可以基于资源清单的方式进行创建。configMap中信息的查看
$ kubectl get configmap file-configmap -o yaml
# apiVersion: v1
# data:
# hongfu.file: |
# username=yeliangcheng
# passwd=liangchengye
# kind: ConfigMap
# metadata:
# creationTimestamp: "2024-12-19T15:13:19Z"
# name: file-configmap
# namespace: default
# resourceVersion: "206017"
# uid: dd6404c5-4675-427b-a4d3-dc600a88d440
$ kubectl describe configmap file-configmap
# Name: file-configmap
# Namespace: default
# Labels: <none>
# Annotations: <none>
# Data
# ====
# hongfu.file:
# ----
# username=yeliangcheng
# passwd=liangchengye
# BinaryData
# ====
# Events: <none>ConfigMap基本使用
环境变量
configmap中的值可以作为环境变量使用,通过使用${}可以直接获取这些环境变量,作为执行命令的参数使用。
# 创建下方两个configmap,而后myapp-container便可从configmap中获取配置配置
# kubectl logs cm-env-pod,输出的环境变量中将会包含下述三个参数
# USERNAME=dave
# log_level=INFO
# PASSWORD=pass
# command: dave pass
# 同一个文件中,需要创建的多个的资源使用---分隔
apiVersion: v1
kind: ConfigMap
metadata:
name: literal-config
namespace: default
# configmap中的数据
data:
name: dave
password: pass
---
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
---
apiVersion: v1
kind: Pod
metadata:
name: cm-env-pod
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
# 容器运行后将会执行下方命令
# env:输出当前Pod内的环境变量
# ${}:从环境变量中取值用于命令参数
command: [ "/bin/sh", "-c", "env&&echo command: $(USERNAME) $(PASSWORD)" ]
# 配置环境变量
env:
# 设置环境变量名为USERNAME
- name: USERNAME
# 从literal-config这个configmap中获取key为name的value作为USERNAME环境变量的值
valueFrom:
configMapKeyRef:
name: literal-config
key: name
- name: PASSWORD
valueFrom:
configMapKeyRef:
name: literal-config
key: password
# 直接将enc-config引入Pod,以其原有的key、value直接作为环境变量
envFrom:
- configMapRef:
name: env-config
restartPolicy: Never卷挂载
卷挂载的文件,存储在对应挂载目录下的只是一个软链接,指向真实的数据文件。

当ConfigMap中的文件更新时,会在Pod内注入一个新的文件,然后再修改软链接从旧文件指向这个新的文件,这是热更新的基础。(而不是直接修改原先文件,这样可以避免配置文件读取时操作导致损坏)

# 使用文件创建一个configmap:kubectl create configmap file-configmap --from-file=volume-mount-text.txt
# 创建下方Pod:kubectl create -f volume-mount-text.yaml
# 进入容器:kubectl exec -it cm-volume-pod -- /bin/bash
# 查看挂载的文件,可以看到volume-mount-text.txt中输入的内容:cat /etc/config/volume-mount-text.txt
# 这是容器卷挂载文件
apiVersion: v1
kind: Pod
metadata:
name: cm-volume-pod
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
# 将声明卷(config-volume)挂载到容器内的/etc/config下
volumeMounts:
- name: config-volume
mountPath: /etc/config
# 声明卷,名称为config-volume,从literal-config中获取
volumes:
- name: config-volume
configMap:
name: literal-config
restartPolicy: NeverConfigMap热更新
- 使用 ConfigMap 挂载的 Env 不会随ConfigMap同步更新
- 使用 ConfigMap 挂载的 Volume 中的数据需要一段时间才能同步更新
将nginx配置文件创建为configmap
# 使用下方nginx配置文件创建configmap
$ kubectl create cm default-nginx --from-file=default.conf
# nginx配置文件
server {
listen 80 default_server;
server_name example.com www.example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}创建deploy,在Pod内挂载configmap中的配置文件
# 进入容器:kubectl exec -it hotupdate-deploy-69596c4666-s9m59 -- /bin/bash
# cat /etc/nginx/conf.d/default.conf
# server {
# listen 80 default_server;
# server_name example.com www.example.com;
# location / {
# root /usr/share/nginx/html;
# index index.html index.htm;
# }
# }
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: hotupdate-deploy
name: hotupdate-deploy
spec:
replicas: 5
selector:
matchLabels:
app: hotupdate-deploy
template:
metadata:
labels:
app: hotupdate-deploy
spec:
containers:
- image: nginx
name: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: config-volume
# 因为nginx会加载子配置项配置,所以在启动时会应用该配置
# include /etc/nginx/conf.d/*.conf;
mountPath: /etc/nginx/conf.d/
volumes:
- name: config-volume
configMap:
name: default-nginx修改configmap,过一段时间之后查看Pod中容器内对应的配置文件,可以发现其已经修改了。因为k8s中configmap对于配置文件的注入并不是立刻执行,而是根据当前集群状态,对Pod进行逐一的替换。
# 修改nginx配置文件中的端口为8080
$ kubectl edit configmap default-nginx
# 进入容器:kubectl exec -it hotupdate-deploy-69596c4666-s9m59 -- /bin/bash
# cat /etc/nginx/conf.d/default.conf
# server {
# listen 80 default_server;
# server_name example.com www.example.com;
# location / {
# root /usr/share/nginx/html;
# index index.html index.htm;
# }
# }配置文件修改,并不意味着其被当前容器应用,对于部分软件(如nginx),还需要通过手动触发其重新加载的方式实现热更新(对于deploy而言);而对于支持云原生的应用,只需要修改配置文件其便可自动重载。
# curl 10.244.85.218:8080:配置文件修改,但是还未应用,对8080端口的访问无法生效。
# curl: (7) Failed to connect to 10.244.85.218 port 8080: Connection refused
# 通过打补丁的方式修改annotations的version/config为任意其他值,即可触发当前Pod滚动更新
# 如果手动修改,需要注意value需要使用双引号扩起 version/config: "66666666"
$ kubectl patch deployment hotupdate-deploy --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "66666666"}}}}}'
# 直接修改配置文件
spec:
template:
metadata:
annotations:
version/config: "66666666"ConfigMap不可变性
immutable可以用于configmap和secret,不可变性对于大量使用 configmap 的集群有如下优势:
- 防止意外(或非预期的)更新导致应用程序中断
- 通过将 configmap 标记为不可变来关闭 kube-apiserver 对其的监视,从而显著降低 kubeapiserver 的负载,提升集群性能。
apiVersion: v1
# 和apiserver位于同一个层级,修改为true后,这个ConfigMap将不可修改(即使将immutable修改为true或将其删除都无法修改,只能通过删除原先configmap再新建解决)
immutable: trueSecret
Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。
- Kubernetes 通过仅仅将 Secret 分发到需要访问 Secret 的 Pod 所在的机器节点来保障其安全性。
- 从 Kubernetes1.7 版本开始,etcd 会以加密形式存储 Secret,一定程度的保证了 Secret 安全性。
- Secret 只会存储在节点的内存中,永不写入节点的物理存储(etcd的物理存储存储有Secret),这样从节点删除 secret 时就不需要擦除磁盘数据。
| 内置类型 | 用法 |
|---|---|
| Opaque | 用户定义的任意数据,默认类型,数据需编码后放入 |
| kubernetes.io/service - account - token | 服务账号令牌 |
| kubernetes.io/docker | ~/.docker/config 文件的序列化形式,用于旧版docker存储Docker 镜像仓库的认证信息 |
| kubernetes.io/dockerconfigjson | ~/.docker/config.json 文件的序列化形式,用于新版docker存储Docker 镜像仓库的认证信息 |
| kubernetes.io/basic - auth | 用于基本身份认证的凭据 |
| kubernetes.io/ssh - auth | 用于 SSH 身份认证的凭据 |
| kubernetes.io/tls | 用于 TLS 客户端或者服务器端的数据 |
| bootstrap.kubernetes.io/token | 启动引导令牌数据 |
Opaque(不透明的)
和ConfigMap使用基本一致,只是需要对内容进行Base64编解码。
Opaque创建
Opaque中value必须是经过Base64编码的字符串,因为使用时会默认进行Base64解码
# 对admin进行编码为Base64
echo -n "admin" | base64
# 将Base64字符串进行解码
echo -n "YWRtaW4=" | base64 -d# 得到对应的Secret信息:kubectl get secret mysecret -o yaml
# apiVersion: v1
# data:
# 存储的是编码过后的信息,通过解码可以查看信息
# password: MWYyZDFlMmU2N2Rm
# username: YWRtaW4=
apiVersion: v1
# 创建一个Opaque类型的Secret
kind: Secret
metadata:
name: mysecret
type: Opaque
# 同configmap,设置不可变性
immutable: true
data:
password: MWYyZDFlMmU2N2Rm
username: YWRtaW4=
# 基于文件创建opaque
kubectl create secret generic my-secret --from-file=username=./username.txt --from-file=password=./password.txt
# 使用命令行直接创建
kubectl create secret generic my-secret --from-literal=username=admin --from-literal=password=secretOpaque基本使用
作为环境变量
# 进入容器:kubectl exec -it opaque-secret-env-deploy-7f868565fb-2c9q2 -- /bin/bash
# 查看环境变量,可以看到显示的是Base64编码前的值:env
# TEST_USER=admin
# TEST_PASSWORD=1f2d1e2e67df
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: opaque-secret-env
name: opaque-secret-env-deploy
spec:
replicas: 5
selector:
matchLabels:
app: op-se-env-pod
template:
metadata:
labels:
app: op-se-env-pod
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp-continaer
ports:
- containerPort: 80
# 配置环境变量
env:
# 创建环境变量TEST_USER,从名称为mysecret的Secret中取key为username的value
- name: TEST_USER
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: TEST_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password作为卷挂载
和configmap一样,当已经存储于卷中被使用的 Secret 被更新时,被映射的键也将终将被更新(这个过程并不是立刻的)。

apiVersion: v1
kind: Pod
metadata:
labels:
name: secret-volume
name: secret-volume-pod
spec:
# 声明一个挂载卷volumes12,来源于mysecret
volumes:
- name: volumes12
secret:
secretName: mysecret
# 挂载文件的权限,填写的数字是十进制,需要转化为对应八进制。不指定默认值是 644,表示文件所有者有读写权限,组和其他用户有读权限。
defaultMode: 256
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp-container
# 挂载volumes12,将其挂载到data下
volumeMounts:
- name: volumes12
mountPath: "/data"使用Secret 作为子路径卷挂载的容器不会收到 Secret 更新,参考下图,对应挂载文件的位置并不是文件软链接(因为不需要动态更新)。

apiVersion: v1
kind: Pod
metadata:
labels:
name: secret-volume
name: secret-volume-pod
spec:
# 声明一个挂载卷volumes12,来源于mysecret,但只取用mysecret中的username,挂载的目录为mountPath下的my-group/my-username
volumes:
- name: volumes12
secret:
secretName: mysecret
items:
- key: username
# 文件将会位于data/my-group下,文件名为my-username
path: my-group/my-username
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp-container
volumeMounts:
- name: volumes12
mountPath: "/data"Downward API
容器通过Downward Api,容器在运行时可以从 Kubernetes APl 服务器获取有关它们自身的信息。这些信息可以作为容器内部的环境变量或文件注入到容器中,如 Pod 名称、命名空间、标签等。
通过DownWard APi获取
作为环境变量
# 进入Pod容器内部:kubectl exec -it downward-api-env-example -- /bin/bash
# 查看环境变量:env
# CPU_REQUEST=0
# POD_NAME=downward-api-env-example
# POD_NAMESPACE=default
# POD_IP=10.244.58.213
# CPU_LIMIT=2
# MEMORY_LIMIT=2728841216
# MEMORY_REQUEST=0
apiVersion: v1
kind: Pod
metadata:
name: downward-api-env-example
spec:
containers:
- name: my-container
image: wangyanglinux/myapp:v1.0
# 环境变量
env:
# 从Pod的metadata中获取Pod的名称
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# 从Pod的metadata中获取Pod的命名空间
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
# 从Pod的status中获取Pod的IP地址
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
# 从Pod的资源请求中获取CPU请求值
- name: CPU_REQUEST
valueFrom:
resourceFieldRef:
resource: requests.cpu
# 从Pod的资源限制中获取CPU限制值
- name: CPU_LIMIT
valueFrom:
resourceFieldRef:
resource: limits.cpu
# 从Pod的资源请求中获取内存请求值
- name: MEMORY_REQUEST
valueFrom:
resourceFieldRef:
resource: requests.memory
# 从Pod的资源限制中获取内存限制值
- name: MEMORY_LIMIT
valueFrom:
resourceFieldRef:
resource: limits.memory
restartPolicy: Never作为卷挂载
使用卷挂载,可以传递一个容器的资源字段到另一个容器中(同Pod下)
下方就将my-container中的资源字段,传递到了my-container2容器中
kubectl exec -it downward-api-volume-example -c my-container2 -- /bin/bash

会保持热更新的特性
挂载目录下全部都是软链接,这是为了热更新所做的准备,为Pod添加标签,查看容器内挂载的文件,可以看到添加的标签。
kubectl label pod downward-api-volume-example update-label=test

apiVersion: v1
kind: Pod
metadata:
# Pod的名称
name: downward-api-volume-example
spec:
containers:
- name: my-container
image: wangyanglinux/tools:busybox
resources:
# 容器不做资源限制,理论上可以使用的最大资源就是当前节点的最大资源
# requests 确保容器有足够的资源启动和运行。
limits:
# CPU限制
cpu: "1"
# 内存限制
memory: "512Mi"
# limits 限制容器使用的资源量,防止其占用过多资源
requests:
# CPU请求
cpu: "0.5"
# 内存请求
memory: "256Mi"
- name: my-container2
image: wangyanglinux/myapp:v1.0
volumeMounts:
# 挂载的卷名称
- name: downward-api-volume
# 挂载路径
mountPath: /etc/podinfo
# 声明挂载卷
volumes:
- name: downward-api-volume
downwardAPI:
items:
# 将Pod的注释写入文件annotations
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
# 将Pod的标签写入文件labels
- path: "labels"
fieldRef:
fieldPath: metadata.labels
# 将Pod的名称写入文件name
- path: "name"
fieldRef:
fieldPath: metadata.name
# 将Pod的命名空间写入文件namespace
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
# 将Pod的UID写入文件uid
- path: "uid"
fieldRef:
fieldPath: metadata.uid
# 将container容器的CPU请求写入文件cpuRequest
- path: "cpuRequest"
resourceFieldRef:
containerName: my-container
resource: requests.cpu
# 将container容器的内存请求写入文件memoryRequest
- path: "memoryRequest"
resourceFieldRef:
containerName: my-container
resource: requests.memory
# 将container容器的CPU限制写入文件cpuLimit
- path: "cpuLimit"
resourceFieldRef:
containerName: my-container
resource: limits.cpu
# 将container容器的内存限制写入文件memoryLimit
- path: "memoryLimit"
resourceFieldRef:
containerName: my-container
resource: limits.memory
restartPolicy: Never通过ApiServer获取
Downward API仅仅可以暴露一个 pod 自身部分元数据传递给在它们内部运行的进程;而通过APIServer则可以获得完整的信息。
基本使用
# 进行授权操作,使得Pod可以访问APiserver接口
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test-api-cluster-admin-binding
subjects:
- kind: ServiceAccount
name: test-api
namespace: default
roleRef:
kind: ClusterRole
# 绑定的ClusterRole的名称
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
# 创建一个Pod,在这一个Pod中进行访问接口
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
serviceAccountName: test-api
containers:
- name: main
image: alpine/curl
command: ["sleep", "9999"]# 创建一个sa用于授权操作
kubectl create sa test-api
# 进入容器内部
kubectl exec -it curl -- /bin/sh
# 读取存储在文件 /var/run/secrets/kubernetes.io/serviceaccount/token 中的ServiceAccount令牌,并将其存储在环境变量 TOKEN 中。这个令牌用于身份验证。
TOKEN=$( cat /var/run/secrets/kubernetes.io/serviceaccount/token )
# 将Kubernetes集群的CA证书路径存储在环境变量 CAPATH 中。这个证书用于验证Kubernetes API服务器的SSL证书。
CAPATH="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
# 读取存储在文件 /var/run/secrets/kubernetes.io/serviceaccount/namespace 中的命名空间名称,并将其存储在环境变量 NS 中。这个命名空间是当前Pod所在的命名空间
NS=$( cat /var/run/secrets/kubernetes.io/serviceaccount/namespace )
# 拼接令牌、证书、访问接口,使用 curl 命令向Kubernetes API服务器发送HTTPS请求。
# 也可以不使用环境变量NS,手动指定接口路径,如:https://kubernetes/api/v1/namespaces/kube-system/pods
curl -H "Authorization: Bearer $TOKEN" --cacert $CAPATH https://kubernetes/api/v1/namespaces/$NS/pods
# 连接实际上访问的是Service下的kubernetes,但因为Pod和kubernetes在同一个名称空间下(default),所以可以使用名称代替域名访问下方访问与上方是等价的。
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 17d
curl -H "Authorization: Bearer $TOKEN" --cacert $CAPATH https://kubernetes.default.svc.cluster.local/api/v1/namespaces/$NS/pods
API文档
通过swagger可以显示k8s所提供的接口,通过这些接口可以获得数据(需要令牌和访问证书),根据当前k8s状态做某些操作。

# 启动一个代理服务器,允许你通过本地网络访问 Kubernetes API
kubectl proxy --port=8080
# 从 http://localhost:8080/openapi/v2 URL 获取数据
curl localhost:8080/openapi/v2 > k8s/swagger.json
# --rm:容器停止后自动删除容器。
# -e 用于指定 Swagger UI 应加载的 Swagger 配置文件路径。
# -v 将当前的swagger.json挂载到容器内
# swaggerapi/swagger-ui:使用 swaggerapi/swagger-ui 镜像来启动容器。
docker run \
--rm \
-d \
-p 80:8080 \
-e SWAGGER_JSON=/k8s/swagger.json \
-v $(pwd)/k8s/swagger.json:/k8s/swagger.json \
swaggerapi/swagger-uiVolume
Kubernetes 中的Volume 的抽象主要是为了解决以下问题:
- 当容器运行退出后,即使 kubelet 重启它,但是容器中的文件依旧会丢失。
- 在
Pod中同时运行多个容器时,这些容器之间通常需要共享文件。
emptyDir
用于Pod内容器临时的数据共享,Pod被删除,emptyDir卷也会被删除,但其在容器崩溃时是安全的
当 Pod 被分配给节点时,首先创建emptyDir卷(最初是空的),该卷的生命周期同所在Pod的生命周期。而Pod 中的多个容器可以将该卷挂载到容器内部的相同/不同路径下,共同读取和写入 emptyDir 卷中的文件。
基于磁盘
在 kubelet 的工作目录(root-dir 参数控制),默认为 /var/lib/kubelet,会为每个使用了 emptyDir:{} 的 pod创建一个目录,格式如 /var/lib/kubelet/pods/{podid}/volumes/kubernetes.io~empty-dir/,存放在Pod所在的节点上,即所有放在 emptyDir 中数据,最终都是落在了 node 的上述路径中。

# 进入busybox容器:kubectl exec -it volume-emptydir-disk-pod -c busybox -- /bin/sh
# 在另一个终端对nginx容器发起访问: curl 10.244.58.232
# 在busybox容器中可以看到nginx容器写入的日志:cat /usr/local/nginx/logs/access.log
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir-disk-pod
namespace: default
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80
# 将logs-volume挂载到/usr/local/nginx/logs,该目录下原本就有的文件,会复制到emptyDir卷中
volumeMounts:
- name: logs-volume
mountPath: /usr/local/nginx/logs
- name: busybox
image: wangyanglinux/tools:busybox
command: ["/bin/sh", "-c", "touch /logs/access.log && tail -f /logs/access.log"]
# 将logs-volume挂载到logs下,并创建文件access.log,输出其内容
volumeMounts:
- name: logs-volume
mountPath: /logs
volumes:
- name: logs-volume
# 声明卷,emptyDir初始值需要为{},代表初始空的环境
emptyDir: {}基于内存
相较于基于文件方式,速度更快,但是稳定性较低
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir-mem
namespace: default
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80
resources:
limits:
cpu: "1"
memory: 1024Mi
requests:
cpu: "1"
memory: 1024Mi
volumeMounts:
- name: mem-volume
mountPath: /data
volumes:
- name: mem-volume
emptyDir:
# 基于内存的方式创建共享卷,sizeLimit的大小不能超过容器限制的大小
medium: Memory
sizeLimit: 500Mihostpath
用于将主机节点文件系统中的文件挂载到k8s集群中,Pod消失,文件还在
- 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的pod 在不同节点上的行为可能会有所不同。
- 当 Kubernetes 按照计划添加资源感知调度时,将无法考虑
hostPath使用的资源在底层主机上创建的文件或目录,因为其不属于Pod。 - 只能由 root 写入,需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入
hostPath卷。
| 值 | 行为 |
|---|---|
| 空字符串(默认) | 用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。 |
| DirectoryOrCreate | 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelete 具有相同的组和所有权。 |
| Directory | 给定的路径下必须存在目录 |
| FileOrCreate | 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelete 具有相同的组和所有权。 |
| File | 给定的路径下必须存在文件 |
| Socket | 给定的路径下必须存在 UNIX 套接字 |
| CharDevice | 给定的路径下必须存在字符设备 |
| BlockDevice | 给定的路径下必须存在块设备 |
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# 将当前主机的test目录作为将要挂载到容器内的目录
path: /test
# /test必须存在
type: DirectoryPV / PVC
Persistent Volume与Persistent Volume Claim
注意,挂载之后,PV目录会直接覆盖原先的目录,即使其路径下有文件。(通过StoreClass创建的PV同理)

概念
PV/PVC-关联条件
当满足关联条件后,若存在多个预选结果,则容量最接近的优先级高,保留策略的优先级高于回收策略,若是都一样,则随机选择。
- 容量:PV 的值不小于 PVC 要求,可以大于,最好一致,目的是节约资源。
- 读写策略:必须完全匹配,因为部分文件系统只有本地锁,而没有网络锁,如果多个节点同时写同一个文件的话,可能发生冲突,造成文件损坏。
- 单节点读写(ReadWriteOnce - RWO)
- 多节点只读(ReadOnlyMany - ROX)
- 多节点读写(ReadWriteMany - RWX)。
- 存储类:PV 的类与 PVC 的类必须一致,不存在包容降级关系,使用类可以对存储进行分类。
PV/PVC 回收策略
目前只有 NFS 和 HostPath 支持回收策略,AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。
如果PVC不删除,则其绑定的PV也不会删除。如果PVC删除,则其绑定的PV将会有如下回收策略:
保留(Retain):PV变成不可绑定状态,即使此时有新的,符合条件的PVC,也不会对其进行绑定,需要运维人员手动回收。
编辑对应的配置文件,将其绑定信息删除即可重新变成Available状态。

回收(Recycle):基本擦除(
rm -rf /thevolume/*),将清除上一个Pod产生的数据,清除完后可被符合条件的PVC再次绑定。删除(Delete):PV会被删除。
PV/PVC 状态
命令行会显示绑定到 PV 的 PVC 的名称。
- 可用(Available):一块空闲资源,还没有被任何声明所绑定。
- 已绑定(Bound):卷已经被声明绑定。
- 已释放(Released):声明被删除,但是资源还未被集群重新声明。
- 失败(Failed):该卷的自动回收失败。
PV/PVC保护
当启用 PVC 保护功能时:
- 如果PVC未被删除,则对应PV不允许被删除。
- 如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除,PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用。
使用
搭建nfs服务器
# 每个节点都需要安装,因为要支持nfs挂载
dnf install nfs-utils rpcbind
# 在提供nfs文件系统的节点,进行如下操作
#创建nfs文件夹,并授权
mkdir /nfs
chmod 666 /nfs
chown nobody /nfs
# 创建十个文件夹,并分别向index.html写入当前文件夹数字
for i in {1..10}
do
mkdir -p /nfs/$i
echo $i > /nfs/$i/index.html
done
# 设置nfs服务器配置
vim /etc/exports
# 导出/nfs/1,* 表示允许所有客户端访问,rw表示允许读写,sync表示同步,所有数据在请求完成前写入,no_subtree_check:禁用子树检查。NFS 服务器不会检查文件是否在导出的子目录中
# 将十个目录全部导出,模拟文件系统提供的多个PV
/nfs/1 *(rw,sync,no_root_squash,no_all_squash)
/nfs/2 *(rw,sync,no_root_squash,no_all_squash)
/nfs/3 *(rw,sync,no_root_squash,no_all_squash)
/nfs/4 *(rw,sync,no_root_squash,no_all_squash)
/nfs/5 *(rw,sync,no_root_squash,no_all_squash)
/nfs/6 *(rw,sync,no_root_squash,no_all_squash)
/nfs/7 *(rw,sync,no_root_squash,no_all_squash)
/nfs/8 *(rw,sync,no_root_squash,no_all_squash)
/nfs/9 *(rw,sync,no_root_squash,no_all_squash)
/nfs/10 *(rw,sync,no_root_squash,no_all_squash)
# 重启用restart
systemctl start rpcbind
systemctl start nfs-server
# 查看当nfs点提供的目录
showmount -e 192.168.126.11
# /nfs/10 *
# /nfs/9 *
# /nfs/8 *
# /nfs/7 *
# /nfs/6 *
# /nfs/5 *
# /nfs/4 *
# /nfs/3 *
# /nfs/2 *
# /nfs/1 *
# 在其他节点进行挂载
mount -t nfs 192.168.126.11:/nfs/1 /nfstest
# 在其他节点解除挂载(注意,需要离开当前被挂载的目录)
umount /nfstest创建PV

# 以下放为模板,创造若干个PV,为他们设置不同的容量,访问模式,回收车略,归属类名等
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv1
spec:
# 为PV分配的储存
capacity:
storage: 1Gi
# 为PV设置的访问模式
accessModes:
- ReadWriteOnce
# 回收策略
persistentVolumeReclaimPolicy: Recycle
# 归属类名称
storageClassName: nfs
nfs:
# 要挂载的路径
path: /nfs
# nfs服务器的地址
server: 192.168.126.11创建服务并使用PVC
StatefulSet控制器,用于有状态服务的创建
StatefulSet在部署和更新时,只有当前 Pod 更新完成并处于运行状态后,才会进行下一个 Pod的创建;缩减和删除时,会按照逆序进行Pod的回收。

数据将持久化储存,即使Pod被删除后由StatefulSet重新创建,新的Pod依旧会绑定原先的pvc,使用新的Pod IP依旧可以访问到原先内容。
通过域名可以固定的访问pod
http://<pod-name>.<service-name>.<namespace>.svc.cluster.local
有状态与无状态
- 无状态服务在处理每个客户端请求时不保留任何状态信息,每个请求都是独立的,如web服务器。
- 有状态服务在处理客户端请求时会保留客户端的状态信息,如mysql等数据库。
# pvc的命名格式,由挂载卷名称+Pod名称组成
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
# www-web-0 Bound nfspv1 1Gi RWO nfs <unset> 26s
# www-web-1 Bound nfspv3 1Gi RWO nfs <unset> 24s
# www-web-2 Bound nfspv2 1181116006400m RWO nfs <unset> 21s
# 创建一个无头服务,通常用于有状态应用程序,例如数据库。所以不分配集群 IP(不需要负载均衡),而是通过域名直接将请求路由到后端的 Pod。
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
# 未设置ClusterIP,即创建一个无头服务
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
# 用于指定StatefulSet所以依赖的无头服务的名称,StatefulSet 中的每个 Pod 都有一个稳定的 DNS 名称,格式为 <pod-name>.<service-name>.<namespace>.svc.cluster.local。其中,<service-name> 就是 serviceName 字段指定的服务名称。
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80
name: web
# 挂载卷,将www挂载到/usr/local/nginx/html
volumeMounts:
- name: www
mountPath: /usr/local/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
# 访问策略
accessModes: [ "ReadWriteOnce" ]
# 区分类名
storageClassName: "nfs"
# 分配资源大小为1Gi
resources:
requests:
storage: 1GiStorageClass
StorageClass用于定义持久卷的动态供给策略,允许管理员定义不同类型的存储,并指定如何动态创建持久卷以供应用程序使用。
注意:k8s.dockerproxy.com被拦截,无法拉取nfs-client-provisioner镜像

搭建nfs服务器
mkdir -p /nfs/share
chown nobody /nfs
# 添加一个挂载点作为测试
vim /etc/exports
/nfs/share *(rw,sync,no_root_squash,no_all_squash)
# 重启用restart
systemctl start rpcbind
systemctl start nfs-server
# 查看挂载点:/nfs/share *
showmount -e 192.168.126.11部署nfs-client-provisioner
nfs-client-provisioner 是一个 Kubernetes 供应商,用于动态提供由 NFS(Network File System)共 享支持的持久卷。其自动化了根据需要创建持久卷的过程,通过与 NFS 服务器交互。
# 创建名称空间,提供给nfs-client-provisioner使用
kubectl create ns nfs-storageclass
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: nfs-storageclass
spec:
# 如果需要高可用的话,可以将副本数量修改为2
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: k8s.dockerproxy.com/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
# nfs服务器的地址
value: 192.168.126.11
- name: NFS_PATH
# nfs中挂载的目录
value: /nfs/share
volumes:
- name: nfs-client-root
nfs:
server: 192.168.126.11
path: /nfs/share为nfs-client-provisioner进行权限配置
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: nfs-storageclass
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-storageclass
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: leader-locking-nfs-client-provisioner
namespace: nfs-storageclass
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: leader-locking-nfs-client-provisioner
namespace: nfs-storageclass
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-storageclass
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io创建存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
namespace: nfs-storageclass
# 供应商提供的接口,此处是nfs-client-provisioner
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
# 创建出PV的在挂载目录下的存储路径,下方即表示/nfs/share/namespace/name
pathPattern: ${.PVC.namespace}/${.PVC.name}
# 如果删除pvc,pv是否需要回收
onDelete: delete创建Pod测试
# 声明一个PVC,,也可以在Pod下的volumeClaimTemplates中声明
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-claim
annotations:
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
storageClassName: nfs-client
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: wangyanglinux/myapp:v1.0
# 将/usr/local/nginx/html挂载到nfs-pvc卷下
volumeMounts:
- name: nfs-pvc
mountPath: "/usr/local/nginx/html"
restartPolicy: "Never"
# 声明卷
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claimK8s调度器
调度器概述
kubernetes 的默认调度器为default-scheduler,其作为单独的程序运行,启动之后会一直监听 APlServer,获取PodSpec.NodeName为空的 pod(不为空则通过预选直接部署到对应节点,不经过优选),对这些 pod 创建一个 binding,表明该 pod 应该放到哪个节点上。
调度流程
调度时,首先进行预选(过滤掉不满足条件的节点);然后进行优选(对通过的节点按照优先级排序),最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误
如果在预选过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度(频率会逐渐降低),直到有节点满足条件。
常见预选指标
- PodFitsResources:节点上剩余的资源是否大于 pod 请求的资源。
- PodFitsHost:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配。
- PodFitsHostPorts:节点上已经使用的 port 是否和 pod 申请的 port 冲突。
- PodSelectorMatches:过滤掉和 pod 指定的 label 不匹配的节点。
- NoDiskConflict:已经 mount 的 volume 和 pod 指定的 volume 是否不冲突(除非它们都是只读)。
常见优选指标
优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。
- LeastRequestedPriority:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。
- BalancedResourceAllocation:节点上 CPU 和 Memory 使用率越接近,权重越高。(假设节点A CPU占用99%,内存占用%1,节点B GPU占用70%,内存占用70%,如果仅使用LeastRequestedPriority策略,那么Pod应该分配到节点A上,但这显然不合理,所以需要配合该策略使用)。
- ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小越大,权重越高。(因为镜像总大小越大,越可能存在需要使用的镜像,如果使用判断节点是否存在镜像的方式,资源消耗太大)
自定义调度器
除了使用Kubernetes自带的调度器,也可以编写自己的调度器,通过配置Pod的spec:schedulername参数指定调度器的名字。
下方调度器只是一个简单的demo,并不能用于实际生产中,一般而言,默认的调度器已经可以较好满足大部分情况下的使用需求了。
# 由于不存在my-schedule,所以对应Pod会一直处于pending状态
# NAME READY STATUS RESTARTS AGE
# myapp-75cdc5c7df-vnf9m 0/1 Pending 0 11s
# 创建配置文件
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: myapp
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
# 指定该Pod使用的调度器名字
schedulerName: my-scheduler
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp使用shell脚本模拟自定义调度器
# 当运行shell脚本后,会匹配schedulerName: my-scheduler的Pod,并向k8s发送请求,为该Pod分配节点
# NAME READY STATUS RESTARTS AGE
# myapp-75cdc5c7df-bx28t 1/1 Running 0 72s
# 在 kubernetes Master 节点开启 apiServer 的代理,通过8001端口可以直接访问集群,而无需安全认证
kubectl proxy --port=8001
# 编写shell脚本,用于模拟自定义调度器
#!/bin/bash
SERVER='localhost:8001' # 定义API服务器地址
while true; do # 无限循环
# 获取所有使用my-scheduler调度器且未分配节点的Pod名称
for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName =="my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"'); do
# 获取所有节点名称
NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))
NUMNODES=${#NODES[@]} # 计算节点数量
CHOSEN=${NODES[$[ $RANDOM % $NUMNODES]]} # 随机选择一个节点
# 发送绑定请求,将Pod绑定到选择的节点
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 "Assigned $PODNAME to $CHOSEN" # 输出绑定信息
done
sleep 1 # 等待1秒后继续循环
done
# 为schedule.sh赋予执行权限
chmod a+x schedule.sh
# 安装jq,json的解析库
yum -y install epel-release
yum -y install jq亲和性
节点亲和性
通过节点的标签,干涉Pod应该放在哪一个节点上,但是不会影响已部署的Pod。
| 调度策略 | 匹配标签 | 操作符 | 拓扑域支持 | 调度目标 |
|---|---|---|---|---|
| nodeAffinity | 主机 | In, NotIn, Exists, DoesNotExist, Gt, Lt | 否 | 指定主机 |
| podAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD 与指定 POD 同一拓扑域 |
| podAntiAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD 与指定 POD 不在同一拓扑域 |
In:匹配标签键的值在指定的值列表中。
yamlmatchExpressions: - key: environment operator: In values: - production - stagingNotIn:匹配标签键的值不在指定的值列表中。
Exists:匹配存在指定标签键的节点或Pod,不关心标签值。
yamlmatchExpressions: - key: environment operator: ExistsDoesNotExist:匹配不存在指定标签键的节点或Pod,不关心标签值。
gt和lt,假设你有一些节点带有一个名为
cpu-count的标签,你希望Pod只调度到cpu-count大于4且小于8的节点上,可以通过lt和gt来实现。yaml- matchExpressions: - key: cpu - count operator: Gt values: - "4" - key: cpu - count operator: Lt values: - "8"
软性策略
添加软策略,如果不存在匹配的节点,则会按照优选的流程进行匹配
# 当不存在满足标签的节点时,会按照优选流程进行匹配
# node-affinity-preferred 1/1 Running 0 11s 10.244.58.255 k8s-node02
# 删除原先配置Pod,并为节点1添加标签,再次创建将会防止到节点1:kubectl label node k8s-node01 domain=xinxianghf
# node-affinity-preferred 1/1 Running 0 10s 10.244.85.207 k8s-node01
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-preferred
labels:
app: node-affinity-preferred
spec:
containers:
- name: node-affinity-preferred-pod
image: wangyanglinux/myapp:v1.0
# 亲和性
affinity:
# 节点亲和性
nodeAffinity:
# 软性策略
preferredDuringSchedulingIgnoredDuringExecution:
# 表示该规则的权重为 1,权重越高,优先级越高
- weight: 1
preference:
# 匹配存在domain标签,且值存在于values内(即xinxianghf)
matchExpressions:
- key: domain
# 表示键的值在指定的值列表中
operator: In
values:
- xinxianghf硬性策略
添加硬策略,如果不存在匹配的节点,那么Pod会阻塞在pending
# 不存在匹配的节点,Pod阻塞在pending
# node-affinity-required 0/1 Pending 0 4s
# 修改节点1标签,符合后节点将会成功运行: kubectl label node k8s-node01 domain=required --overwrite
# node-affinity-required 1/1 Running 0 110s
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-required
labels:
app: node-affinity-required
spec:
containers:
- name: node-affinity-required-pod
image: wangyanglinux/myapp:v1.0
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: domain
operator: In
values:
- requiredPod亲和性
Pod亲和性
软性策略
调度器会优先选择那些既在指定拓扑域内,又有目标标签Pod的节点。
如果没有节点同时满足这两个条件,调度器会尽量满足其中一个条件,通常是拓扑域条件。
最终,如果没有任何节点满足软亲和条件,Pod 仍然可以调度到其他节点上。
# 进行Pod的创建,所有节点上都不存在满足标签的Pod,最终Pod会经过优选被分配的节点上去
# pod-aff-prefer 1/1 10.244.58.194 k8s-node02
# 创建一个新的Pod:pod-aff,这个Pod运行在节点1上,并为其添加上标签
# kubectl label pod pod-aff app=pod-1 --overwrite
# 删除pod-aff-prefer再次创建:kubectl create -f pod-prefer.yaml,其会运行在Pod2
# pod-aff-prefer 1/1 10.244.58.194 k8s-node01
apiVersion: v1
kind: Pod
metadata:
name: pod-aff-prefer
labels:
app: pod-aff
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
# 定义亲和性
affinity:
# 定义Pod亲和性
podAffinity:
# 软策略
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
# 定义 Pod 间亲和性的条件
podAffinityTerm:
labelSelector:
matchExpressions:
# 目标Pod的key需要为app,值要在values中(即Pod-1)
- key: app
operator: In
values:
- pod-1
# 拓扑域,用于定义节点之间关系的逻辑分组。它通过标签和键值对来表示,常见的拓扑域包括节点、区域和可用区等
# 本质上就是需要给节点添加上这个标签:kubectl label nodes node-1 kubernetes.io/hostname=node-1
topologyKey: kubernetes.io/hostname硬性策略
必须存在指定的拓扑域(例如 kubernetes.io/hostname);必须存在符合 labelSelector 条件的目标 Pod。若是任一条件不满足,将无法进行Pod调度。
apiVersion: v1
kind: Pod
metadata:
name: pod-aff-req
labels:
app: pod-aff-req
spec:
containers:
- name: pod-aff-req-c
image: wangyanglinux/myapp:v1.0
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-1
topologyKey: kubernetes.io/hostnamePod反亲和性
软性策略
调度器会优先选择那些在指定拓扑域内,但是节点上没有目标标签Pod的节点。
如果不存在可选项,那么会走优选流程,Pod 仍然可以调度到其他节点上
# 如果pod-antiaff2不进行创建,则两个Pod被定义到不同节点上
# pod-antiaff1 10.244.85.216 k8s-node01
# pod-antiaff-prefer 10.244.58.195 k8s-node02
# 如果pod-antiaff1进行创建,即使两台设备上都存在指定标签,pod-antiaff-prefer依旧会被创建
# pod-antiaff-prefer 10.244.58.253 k8s-node02
# pod-antiaff1 10.244.85.214 k8s-node01
# pod-antiaff2 10.244.58.252 k8s-node02
apiVersion: v1
kind: Pod
metadata:
name: pod-antiaff-prefer
labels:
app: pod-aff
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
# 指定需要避免与之共存的Pod的标签。
matchExpressions:
- key: app
operator: In
values:
- pod-2
# 指定在哪个拓扑域上应用反亲和性规则
topologyKey: kubernetes.io/hostname
---
apiVersion: v1
kind: Pod
metadata:
name: pod-antiaff1
labels:
app: pod-2
spec:
nodeName: k8s-node01
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
---
apiVersion: v1
kind: Pod
metadata:
name: pod-antiaff2
labels:
app: pod-2
spec:
nodeName: k8s-node02
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0硬性策略
调度器必须选择那些在指定拓扑域内,但是节点上没有目标标签Pod的节点,如果不存在则拒绝调度。
apiVersion: v1
kind: Pod
metadata:
name: pod-aff-req
labels:
app: pod-aff-req
spec:
containers:
- name: pod-aff-req-c
image: wangyanglinux/myapp:v1.0
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-1
topologyKey: kubernetes.io/hostname容忍与污点
Taint(污点) 和 toleration(容忍) 相互配合,可以用来避免 pod 被分配到不合适的节点上。
- 通过配置节点亲和性,不部署在某些节点的Pod都需要声明;
- 通过配置容忍和污点,需要部署在某些节点上的Pod才需要申明。
假设我们存在两种Pod,PodA需要GPU资源,PodB需要CPU资源;两种节点,NodeA主要提供GPU资源,NodeB主要提供CPU资源。我们可以为NodeA添加一个污点,PodA容忍这个污点,这样所有PodB都会落在NodeB上。此外,为了所有PodA落在NodeA上,我们还需要为PodA添加硬亲和性。
污点
每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用,支持如下三个选项:
- NoSchedule:表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
- PreferNoSchedule:表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
- NoExecute:表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的Pod 驱逐出去
# 设置污点
kubectl taint nodes nodename key=value:effect
# 查看五点,节点说明中,查找 Taints字段。
# 查看主节点,可以看到其存在一个污点,这也就是为什么master节点不会被部署Pod
# Taints: node-role.kubernetes.io/control-plane:NoSchedule
kubectl describe nodes k8s-master01
# 去除污点,如果污点有值
kubectl taint nodes nodename key=value:effect-
# 如果污点没有值
kubectl taint nodes nodename key:effect-# 删除master节点的污点
kubectl taint node k8s-master01 node-role.kubernetes.io/control-plane:NoSchedule-
# 可以看到master节点也部署了Pod
# daemonset-demo-cg9d8 1/1 Running k8s-node01
# daemonset-demo-ftm59 1/1 Running k8s-node02
# daemonset-demo-jj4c2 0/1 ContainerCreating k8s-master01
# 还原节点
kubectl taint node k8s-master01 node-role.kubernetes.io/control-plane:NoSchedule
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: daemonset-demo
labels:
app: daemonset-demo
spec:
# 不需要设置副本数量,会自动根据节点修改。
selector:
matchLabels:
name: daemonset-demo
template:
metadata:
labels:
name: daemonset-demo
spec:
containers:
- name: daemonset-demo-container
image: wangyanglinux/myapp:v1.0容忍
设置了容忍的 Pod 将可以容忍污点的存在,可以被调度(不是一定)到存在污点的 Node 上
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# 容忍的配置层级如下
tolerations:
# 容忍污点名为key1,值为value,effect为NoSchedule的节点
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
# Pod 可以容忍带有 key1=value1 且效果为 NoExecute 的污点的节点,并且在该节点上容忍该污点的时间为 3600 秒(1 小时)。如果超过这个时间,Pod 将会被驱逐
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
# 当不指定 value 时,表示容忍所有的污点 value
tolerations:
- key: "key2"
operator: "Exists"
effect: "NoSchedule"
# 表示容忍所有的污点
tolerations:
- operator: "Exists"
# 容忍所有key为key1的污点,无论operator和effect
tolerations:
- key: "key1"
operator: "Exists"
# 当有多个master存在,可以适当允许部分Pod调度到master节点上运行
kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule固定节点调度
nodename
直接将Pod调度到指定的Node节点上,只需要节点名存在,且满足预选规则就会强制匹配,不会受到污点规则限制(如果不存在,Pod就会处于Pending状态)
# 所有节点都部署在master上,不受污点的影响
# nodename-test-9f759ff94-4hsfb k8s-node01 <none> <none>
# nodename-test-9f759ff94-btbc5 k8s-node01 <none> <none>
# nodename-test-9f759ff94-dfmms k8s-node01 <none> <none>
# nodename-test-9f759ff94-lp4df k8s-node01 <none> <none>
# nodename-test-9f759ff94-nntkm k8s-node01 <none> <none>
# nodename-test-9f759ff94-rrztt k8s-node01 <none> <none>
# nodename-test-9f759ff94-wf9gw k8s-node01 <none> <none>
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodename-test
spec:
replicas: 7
selector:
matchLabels:
app: nodename
template:
metadata:
labels:
app: nodename
spec:
# 指定要调度的节点名称为k8s-node01
nodeName: k8s-node01
containers:
- name: myweb
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80nodeselect
通过nodeselect进行选择,将经过预选的节点调度到存在指定标签的节点上,该方法会受到污点的限制(如果不存在,Pod就会处于Pending状态)
# 为主节点添加标签,Pod依旧未分配到其上运行,因为污点的存在
# kubectl label nodes k8s-node01 type=nodeselect
# nodeselect-test-7cc9848798-5svnm 0/1 Pending
# nodeselect-test-7cc9848798-8hd4g 0/1 Pending
# 为从节点添加标签,可以看到Pod分配到其上运行了
# nodeselect-test-7cc9848798-5svnm 1/1 Running k8s-node01
# nodeselect-test-7cc9848798-8hd4g 1/1 Running k8s-node01
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodeselect-test
spec:
replicas: 2
selector:
matchLabels:
app: nodeselect
template:
metadata:
labels:
app: nodeselect
spec:
nodeSelector:
type: nodeselect
containers:
- name: myweb
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80Kubernetes集群安全
对于API Server的访问进行限制
- 认证:确认请求方是否可信。
- 鉴权:确定请求方是否有权限执行对应操作。
- 准入机制:确认请求方的操作是否合理。
认证
HTTPS
对称加密与非对称加密
对称加密
- 对称加密在加密和解密过程中使用相同的秘钥。
- 加密和解密速度较快。
- 需要安全地管理和分发密钥,密钥泄露会导致数据泄露。
非对称加密
- 通过RSA算法计算得到公钥和私钥,它们之间存在特定的数学关系,使得使用公钥加密的数据只能通过对应的私钥解密,反之同样成立。
- 公钥加密,私钥解密,谁都可以加密,但是只有拥有私钥的人才能解密,适合用来加密数据。
- 私钥加密,公钥解密,谁都可以解密,但是只有拥有私钥的人才能解密,适合用来验证身份。
- 加密和解密速度较慢(RSA算法的消耗大)。
- 即使公钥公开,数据也只能由持有私钥的一方解密,而私钥一般保存在本地,不进行网络传输,安全性高。
数据传输安全发展
当客户端与服务器之间直接进行数据传输,其内容都是明文显示,当中间人截获了这个请求,就可以轻而易举的获取传输内容。
所以可以采用对称加密,双方可以在每一次会话都约定一个私钥(会话秘钥),发送方使用这个秘钥对请求加密;接收方使用这个秘钥对接收到的数据进行解密,这样请求对中间人就是不可见的,但是因为私钥需要双方约定,在这个过程中存在泄漏的可能性,而私钥泄漏后,加密便没有作用了。
对此我们可以采用非对称加密,服务端生成一个公钥和私钥,私钥保留在本地,公钥发送给客户端。客户端接受到公钥后,对发送的数据使用公钥进行加密,服务器收到加密数据后再使用私钥进行解密。而中间人虽然获得公钥和加密后的数据,但是由于没有私钥,最终无法读取数据。
但是仍旧存在这样一种可能,中间人接收到了网站所发送的公钥,将其保存,并生成自己的公钥和私钥,然后将生成的公钥发送给客户端。客户端拿到中间人的公钥对数据进行加密,中间人通过自己的私钥便可以对其进行解密,篡改请求数据后再使用服务端的公钥加密后返回给服务端。相当于在客户端和中间人,中间人和服务端间建立了两个会话。

对于上述问题,我们引入第三方CA机构(Certificate Authority),服务端首先将公钥提交给CA,CA将其和其他信息封装为数字证书(即将网站和公钥绑定在一起)。在进行交互时,服务端首先将数字证书发送给客户端,客户端确定其颁发者是可信的CA,便将公钥从数字证书中获取出来用于后续交互。
为了确保证书没有被篡改,就需要使用数字签名。当CA收到服务端提交的公钥和其他其他信息后,会对其进行hash运算。然后CA会生成一对公钥和私钥,将公钥放在数字证书中,使用私钥对hash运算结果加密,即可得到数字签名,并将其包含在数字证书中。当客户端收到数字证书后,使用CA生成的公钥对数字签名进行解密得到hash结果,再将证书其他信息进行hash得到另一个hash结果,若是二者不一致,则说明证书被篡改过(中间人不存在私钥,无法加密出可以被公钥解密的数据)。

但是通过数字签名验证后,只能确认证书是由某CA签发的,但无法确认该颁发机构本身是否可信。所以需要上级CA为该CA颁发证书,直到根CA(根证书通常存储在操作系统、浏览器内,根CA通过自己的私钥对数字证书进行加密)。
当客户端(如浏览器)收到最终用户证书时,会通过证书链逐级验证每个证书的签名,直到根证书(服务端可以将证书链发送给客户端,也可由客户端自行下载)。如果根证书在受信任的根证书存储中,则整个证书链被认为是可信的。
证书中还包含当前证书所属域名(客户端可以通过这个字段进行验证),当前证书的有效期,CRL分发点(当私钥泄漏,就可以向CA申请吊销证书,通过CRL分发点中存储的URL,即可访问到被吊销证书的列表,但其并不是实时的),OCSP(在线证书状态协议,通过访问这个URL,可以实时获得证书是否被吊销)。

传输流程
当会话密钥生成之后,后续都会使用会话密钥对数据进行加密(非对称加密,减少资源消耗),而初始所进行的所有沟通都是为了生成这个会话秘钥。(下方图中所有流程都位于HTTP的三次握手和四次挥手之间)
TLS/SSL( Transport Layer Security/ Secure Sockets Layer,传输层安全/安全套接字层),其中TLS是基于SSL3.0开发而成,在下方用于会话密钥的生成。

认证概述
HTTP Token 认证
通过 Token 来识别合法用户,每一个 Token 对应一个用户名存储在 API Server 能访问的文件中。当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token,由API Servers进行验证。
HTTP Base 认证
将用户名和密码用 BASE64 算法进行编码后放在 HTTP 请求头中的Authorization里发送给服务端,服务端收到后进行编码,获取用户名及密码进行验证
HTTPS CA验证
基于 CA 根证书签名的客户端身份认证方式。与一般的HTTPS不同,在K8s中,客户端与服务端双方都需要证书验证自己的身份

Kubernetes使用的相关证书都存储在/etc/kubernetes/pki下

不同的组件有不同的权限需求。通过为每个组件分配单独的证书,可以确保每个组件只能访问其所需的资源,减少潜在的安全风险。
- apiserver-etcd-client.crt 和 apiserver-etcd-client.key:API服务器作为客户端与etcd通信时使用的证书和私钥。
- apiserver-kubelet-client.crt 和 apiserver-kubelet-client.key:API服务器作为客户端与kubelet通信时使用的证书和私钥。
- apiserver.crt 和 apiserver.key: API 服务器与客户端之间的安全通信
- ca.crt 和 ca.key:这些是集群的根证书颁发机构(CA)证书和私钥,用于签署和验证集群内的其他证书。
- sa.key 和 sa.pub:这些是服务账户(Service Account)的私钥和公钥。
- ………………………………
认证机制

无需认证
通过kubeadm方式安装,Controller Manager、Scheduler 与 API Server 在同一台机器时,可以直接使用 API Server 的非安全端口访问,使用参数--insecure-bind-address=127.0.0.1。
组件认证
Kubernetes 组件对 API Server 的访问包括:kubectl、Controller Manager、Scheduler、kubelet、kube - proxy等
证书签发方式
手动签发:需要手动的通过 k8s 集群的根 CA 进行 HTTPS 证书的签发。
自动签发:kubelet 首次访问 API Server 时,可以看做证书的自动签发。
shell# 节点加入集群流程 # 新节点使用kubeadm join命令后,首先携带token向主节点发送加入请求,如果令牌有效,主节点会向新节点发送CA证书。 # 新节点接收到CA证书后,将会验证CA证书的哈希值是否与提供的一致。如果一致,则携带公钥、相关信息等发送请求至主节点申请证书。 # 主节点收到请求后,为其颁发证书,并使用私钥进行数字签名后返回给新节点。 # 新节点接收到证书保存,完成认证,后续通过HTTPS进行通信。 # --token(集群对节点的验证):用于验证加入集群的节点的身份,存在有效期。 # --discovery-token-ca-cert-hash(节点对集群的验证):值是控制节点的证书的hash值,用于验证控制平面节点的身份,防止中间人攻击。 # 当新的节点尝试加入集群时,它会从控制平面节点获取 CA 证书。计算 CA 证书的哈希值: # 对证书进行hash计算得到hash值,将其与--discovery-token-ca-cert-hash提供的hash值进行比较,如果匹配则证明节点是可信的。 kubeadm join 192.168.126.11:6443 --token em8pi0.nqbc8m7t4aslvojh \ --discovery-token-ca-cert-hash sha256:fb24ab15bc6f152413dee626a34ecb3b46ef7c99c0bd87932274da3f3905c1d8 --cri-socket unix:///var/run/cri-dockerd.sock
kubeconfig
Kubenetes 组件通过启动时指定不同的 kubeconfig 文件可以切换到不同的集群。该文件内包含集群参数(CA证书、APlServer地址),客户端参数(上面生成的证书和私钥)、集群 context 信息(集群名称、用户名)。
# 当使用kubectl命令时,就会调用当前登录用户的根目录下.kube/config下的配置文件,以root用户为例:/root/.kube/config
apiVersion: v1
clusters:
- cluster:
# 当前集群的根CA证书,用于客户端对服务端的验证(根CA是由自己私钥对证书进行签名的)
certificate-authority-data: <CA证书数据>
# 集群的地址
server: https://192.168.126.11:6443
# 集群的名称
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
# 当前客户端的证书
client-certificate-data: <客户端证书数据>
# 当前客户端的私钥
client-key-data: <客户端密钥数据>Pod认证
Kubernetes 的 Pod 对API Server的访问包括:如calico、dns插件等。
因为 Pod 的创建、销毁是动态的,所以要为它手动生成证书就不可行了,Kubernetes 采用ServiceAccount 实现安全认证(单向的,只有Pod需要访问API Server,对其提供service-account-token)。
默认情况下,每个 namespace 都会有一个 ServiceAccount,如果 Pod 在创建时没有指ServiceAccount,就会使用 Pod 所属的 namespace 的 ServiceAccount。默认挂载进入Pod的如下目录:/run/secrets/kubernetes.io/serviceaccount/

Service Account的组成如下
# 创建一个sa,在nsName空间下,注意:创建的sa不具备任何权限,需要对其绑定角色才会有权限
kubectl create sa saName -n nsName- service-account-token:APl Server 私钥签名的 JWT。访问API Server时携带,用于其对Pod的认证(Secret的一种,另一种是用于保存用户自定义保密信息的 Opaque)
- ca.crt:根证书。用于Pod使用其中的公钥,验证API Server发送的证书,确定当前访问的API Server是否可信。
- namespace:命名空间,标识这个 service-account-token 的作用域名空间
Pod认证流程主要如下
创建
ServiceAccount时,API Server生成一个关联的令牌,并将其存储在 etcd 中。当 Pod 使用某个ServiceAccount启动时,API 服务器会确保该 Pod 挂载相应的令牌和 CA 证书。当 Pod 使用令牌访问 Kubernetes API 服务器时,API 服务器会验证这个令牌是否有效。
认证通过后,API 服务器会根据
ServiceAccount的权限(通过角色和角色绑定)来决定是否允许执行请求的操作。
鉴权
授权策略
APl Server 目前支持以下几种授权策略(通过 API Server 的启动参数 --authorization-mode 设置)
AlwaysDeny:拒绝所有请求,通常用于测试。
AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可采用该策略。
ABAC(Attribute Based Access Control):基于属性的访问控制,即用户/组/sa拥有直接拥有某种权限。
Webhook:通过外部 HTTP 服务动态决定权限
yamlapiVersion: apiserver.k8s.io/v1 kind: WebhookConfiguration metadata: name: example-webhook webhooks: - name: example.k8s.io clientConfig: url: "https://example.com/auth" rules: - apiGroups: ["*"] apiVersions: ["*"] operations: ["*"] resources: ["*"]RBAC(Role - Based Access Control):基于角色的访问控制,是现行默认规则。即用户/组/sa拥有直接拥有某些角色,角色拥有某些权限,即在ABAC之间添加了一层角色(初始用户/组/sa权限为空,只有为其添加角色才有权限)。
RBAC
概述
RBAC 在 Kubernetes 1.5 中引入,在现行版本中成为默认标准。
- 对集群中的资源和非资源均有完整的覆盖。
- 整个 RBAC 完全由几个 API 对象完成,和其他 API 对象一样,可以用 kubectl 或 API 进行操作。
- 可以在运行时进行调整,无需重启 API Server。

RBAC引入了4个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding
- Role 和 RoleBinding:将 Role 绑定到用户、组或 ServiceAccount,使其在特定命名空间内具有相应的权限。
- ClusterRole 和 ClusterRoleBinding:将 ClusterRole 绑定到用户、组或 ServiceAccount,使其在整个集群范围内具有相应的权限。
- ClusterRole也和 RoleBinding:将 ClusterRole绑定到特定命名空间内的用户、组或 ServiceAccount,从而在该命名空间内使用集群级别的权限,适用于需要在多个命名空间中复用权限配置的情况。

资源创建
创建Role
# 创建Role
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# 指定命名空间
namespace: default
name: pod-reader
rules:
# 应用的接口组
- apiGroups: [""]
# 资源
resources: ["pods"]
# 对资源可以进行的操作
verbs: ["get", "watch", "list"]创建ClusterRole
# 创建ClusterRole
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]Role和RoloBinding绑定
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-pods
namespace: default
# 指定要绑定的用户、组或SA
# 用户、组的命名不能以system:开头,因为这个命名由系统保留使用
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
# 指定要为其分配的角色
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioClusterRole也和 RoleBinding绑定
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-secrets
# 在dev命名空间下使用
namespace: dev
subjects:
- kind: User
name: dave
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.ioClusterRole 绑定ClusterRoleBinding,对整个集群中的所有命名空间资源权限进行授权
# 授权 manager 组内所有用户在全部命名空间中对 secrets 进行访问
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io对资源下的子资源进行限制,通过 / 分隔符进行分隔。
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]实际使用
创建一个用户只能管理dev命名空间(创建证书 —> 转换为kubeconfig文件 —> 创建名字空间 —> 角色绑定)
创建证书签名请求文件(CSR)
// 注意用户名、证书适用主机列表、用户组的定义
// 创建下方json文件时需要移出注释
{
// 用户名
"CN": "devuser",
// 证书适用的主机名列表,只有这些主机才能使用该证书访问,为空默认所有都可以
"hosts": [],
// 密钥信息
"key": {
// 密钥算法
"algo": "rsa",
// 密钥大小
"size": 2048
},
// 证书的主体信息
"names": [
{
// 国家
"C": "CN",
// 州或省
"ST": "BeiJing",
// 地区或城市
"L": "BeiJing",
// 用户组
"O": "k8s",
// 组织单位
"OU": "System"
}
]
}生成证书
# 下载证书工具,也可以直接从本地上传到/usr/local/bin/目录下
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
mv cfssl-certinfo_linux-amd64 /usr/local/bin/cfssl-certinfo
# 添加执行权限
chmod a+x /usr/local/bin/cfssl*
# 进入证书目录下
cd /etc/kubernetes/pki
# -ca=ca.crt:提供公钥和身份信息
# -ca-key=ca.key:提供进行数字签名的私钥
# -profile=kubernetes:指定使用的配置文件(profile),定义了证书的属性和策略
# /root/devuser.csr.json:指定证书签名请求(CSR)的 JSON 文件(即上方的json文件)
# cfssljson -bare devuser:将生成的证书和密钥转换为 JSON 格式,并以 devuser 为前缀保存文件
cfssl gencert -ca=ca.crt -ca-key=ca.key -profile=kubernetes .devuser.json | cfssljson -bare devuser
# 将会生成如下文件:
# devuser.pem:生成的证书文件。
# devuser-key.pem:生成的私钥文件。
# devuser.csr:生成的证书签名请求文件。创建kubeconfig
# 设置集群参数
# --certificate-authority:指定服务端证书
# --embed-certs=true:将 CA 证书嵌入到生成的 kubeconfig 文件中,而不是引用外部文件。
# --server=${KUBE_APISERVER}:指定集群的 API 服务器地址。
# --kubeconfig=devuser.kubeconfig:指定生成或修改的 kubeconfig 文件的路径。
export KUBE_APISERVER="https://192.168.126.11:6443"
kubectl config set-cluster kubernetes \
--certificate-authority=ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=devuser.kubeconfig
# 设置客户端认证参数
kubectl config set-credentials devuser \
--client-certificate=devuser.pem \
--client-key=devuser-key.pem \
--embed-certs=true \
--kubeconfig=devuser.kubeconfig
# 设置上下文参数
# --cluster=kubernetes:指定上下文关联的集群名称为 kubernetes。
# --user=devuser:指定上下文关联的用户名称为 devuser。
# --namespace=dev:指定上下文默认的命名空间为 dev。
kubectl config set-context kubernetes \
--cluster=kubernetes \
--user=devuser \
--namespace=dev \
--kubeconfig=devuser.kubeconfig角色绑定
# 创建dev命名空间
kubectl create ns dev
# 创建一个名为 devuser-admin-binding 的角色绑定。
# --clusterrole=admin:将集群角色 admin 绑定到角色绑定中。
# --user=devuser:指定将角色绑定到用户 devuser。
# --namespace=dev:指定角色绑定所在的命名空间为 dev。
kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev切换用户
# 切换上下文之后,将只能在dev命名空间下进行操作,如果对其他空间进行操作,将会提示无权限
# kubectl get pod -n default
# Error from server (Forbidden): pods is forbidden: User "devuser" cannot list resource "pods" in API group "" in the namespace "default"
# 使用 devuser.kubeconfig 文件中的配置信息,将当前上下文切换到名为 kubernetes 的上下文,以后访问将会使用kubeconfig配置文件
kubectl config use-context kubernetes --kubeconfig=devuser.kubeconfig
# 先保存原先Config
mv /root/.kube/config /root/
# 测试完后将原先配置文件放回
# cp -f /root/config /root/.kube/config
# Kubernetes 默认会在 ~/.kube/config 路径下查找 kubeconfig 文件,所以需要将其移动到改位置
cp -f ./devuser.kubeconfig /root/.kube/config配合Linux用户实现隔离
# 当用户登录成功后执行kubectl命令,将会使用其家目录下的.kube/config文件来访问API Server
# 创建用户并未其设置密码
useradd dev
passwd dec
# 在用户家目录下创建kube文件夹
mkdir /home/dev/.kube
# 将对应kubeconfig文件移动过去
cp ./devuser.kubeconfig /home/dev/.kube/config
# 为用户授权
chown -R dev:dev /home/dev/.kube/常见预定义角色
view ClusterRole
允许读取一个命名空间中的大多数资源,除了Role、RoleBinding和Secret。
edit ClusterRole
允许读取和修改Secret。但是,它也不允许查看或修改Role和RoleBinding,这是为了防止权限扩散。
admin ClusterRole
一个命名空间中的资源的完全控制权是由admin ClusterRole赋予的。有这个ClusterRole的主体可以读取和修改命名空间中的任何资源,除了ResourceQuota和命名空间资源本身。e
dit和adminClusterRole之间的主要区别是能否在命名空间中查看和修改Role和RoleBinding。
cluster-admin ClusterRole
可以获得Kubernetes集群完全控制的权限。
准入机制
准入控制是APIServer的插件集合,通过添加不同的插件,实现额外的准入控制规则,以下是几个常见的准入控制规则:
- NamespaceLifecycle:防止在不存在的 namespace 上创建对象,防止删除系统预置 namespace,删除 namespace 时,连带删除它的所有资源对象。
- LimitRanger:确保请求的资源不会超过资源所在 Namespace 的 LimitRange 的限制。
- ServiceAccount:实现了自动化添加 ServiceAccount(没创建一个命名空间,都会自动创建一个默认的sa)。
- ResourceQuota:确保请求的资源不会超过资源的 ResourceQuota 限制。
Helm
HELM 概述
Helm 是 Kubernetes 的一个包管理工具,简化了在集群上部署和维护复杂应用的操作。
- Chart:类似于镜像,包括各种 Kubernetes 对象的配置模板、参数定义、依赖关系、文档说明等。
- Release:类似于容器,Chart 的一个运行实例,可以通过一个Chart实例化出多个Release
- Helm cli:helm 客户端组件,负责和 kubernetes API Server通信
- Repository:用于发布和存储 Chart 的仓库
在HELM V2版本中,由客户端将请求发送至Tiler(kube-system下运行的一个Pod),再由其请求API Server完成对应操作,并维护 Helm 发布的状态和历史记录。
在HELM V3版本中,移除了Tiler组件,由客户端直接使用Kubeconfig文件访问API Server。
Helm 的架构变的更为简单和灵活
不再需要创建 ServiceAccount,直接使用当前环境中的 kubeconfig 配置,其权限受到Kubeconfig限制,不会出现权限溢出。
可以直接和 kubernetes APl交互,更为安全
不再需要使用 helm init 来进行初始化(因为不需要Tiler这个Pod的创建)

HRLM 基本使用
初始化
# 远程下载HELM,也可以通过本地上传,注意需要将helm文件放到/usr/local/bin的目录下
curl -fsSL -o get_helm.sh
https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
# 为其授予执行权限
chmod a+x /usr/local/bin/helm
# 查看HELM版本
# version.BuildInfo{Version:"v3.12.3", GitCommit:"3a31588ad33fe3b89af5a2a54ee1d25bfe6eaa5e", GitTreeState:"clean", GoVersion:"go1.20.7"}
helm version镜像源指令
# 将 Bitnami 的 Helm Chart 仓库添加到本地 Helm 客户端的仓库列表,以后就可以从中安装或搜索Chart
helm repo add kube-charts-mirror https://burdenbear.github.io/kube-charts-mirror/# 国内源
helm repo add bitnami https://charts.bitnami.com/bitnami # 官方源
# 移除镜像源
helm repo remove bitnami
# 查看当前配置的镜像源
helm repo ls
helm repo list
# 更新本地 Helm 仓库列表,确保获取到最新的 Chart 信息
helm repo update搜索
# 查看仓库下的所有chart
helm search repo
# 使用模糊字符串匹配算法查询本地库
helm search repo wordpress
# 用于在 Helm Hub(https://hub.helm.sh)上搜索 Helm charts
helm search hub wordpress信息查看
# 显示 bitnami/apache Chart 的可更改部分
helm show values bitnami/nginx-ingress
# 显示 bitnami/apache Chart 的chart文件信息
helm show chart bitnami/nginx-ingress
# 显示 bitnami/nginx-ingress Chart 的所有信息
helm show all bitnami/nginx-ingress
# 显示名为 apache-1612624192 的 Helm 发布的状态信息,包括其资源、状态和相关事件
helm status nginx-ingress-1612624192 安装(实例化)
基本指令
# 拉取对应chart包,但是不进行安装,可以手动修改values等文件后在进行安装。
helm pull bitnami/nginx-ingress
# 安装对应chart,并自动生成一个名称
helm install bitnami/nginx-ingress --generate-name
# 安装对应chart,并指定一个名称
helm install nginx-ingress bitnami/nginx-ingress
# 卸载对应helm,若使用--keep-history将会保存版本历史,用于回滚。
helm uninstall apache-1612624192 --keep-history
# 展示 Helm 保留的所有 release 记录,包括失败或删除的条目(指定了 --keep-history )
helm list --all
# 使用本地 chart 压缩包安装,命名为foo
helm install foo foo-0.1.1.tgz
# 使用解压后的 chart 目录进行安装,命名为foo
helm install foo path/to/foo
# 使用完整的 URL 进行安装,命名为foo
helm install foo https://example.com/charts/foo-1.2.3.tgz )安装资源顺序
Namespace -> NetworkPolicy -> ResourceQuota -> LimitRange -> PodSecurityPolicy -> PodDisruptionBudget -> ServiceAccount -> Secret -> SecretList -> ConfigMap -> StorageClass -> PersistentVolume -> PersistentVolumeClaim -> CustomResourceDefinition -> ClusterRole -> ClusterRoleList -> ClusterRoleBinding -> ClusterRoleBindingList -> Role -> RoleList -> RoleBinding -> RoleBindingList -> Service -> DaemonSet -> Pod -> ReplicationController -> ReplicaSet -> Deployment -> HorizontalPodAutoscaler -> StatefulSet -> Job -> CronJob -> Ingress -> APIService
自定义chart
基于文件
可以指定多次,Helm 会按顺序合并这些配置文件,后面的配置文件会覆盖前面配置文件中的相同配置项。
# 查看 chart 中的可配置选项
helm show values bitnami/redis
# 使用 YAML 格式的文件覆盖上述任意配置项,并在安装过程中使用该文件
service:
type: NodePort
# 安装时指定该文件即可
helm install -f values.yaml bitnami/apache --generate-name基于指令
同时使用两种方式,则 --set 中的值会被合并到 --values 中,但是 --set 中的值优先级更高。
--set 中覆盖的内容会被被保存在 ConfigMap 中。可以通过 helm get values <release-name>来查看指定 release 中 --set 设置的值。也可以通过运行 helm upgrade 并指定 --reset-values字段来清除 --set 中设置的值。
# 等价于yaml文件中name:value格式
--set name=value
# 多个值之间使用逗号进行分隔:
--set name1=value1,name2=value2
# 等价于yaml文件中的如下格式:
# outer:
# inner: value
--set outer.inner=value
# 等价于yaml文件中的如下格式:
# name:
# -a
# -b
# -c
--set name={a,b,c}
# 某些value可以被设为空数组或null
# 等价于yaml文件中的如下格式:
# name:[]
# a:null
--set name=[],a=null
# 使用数组下标的语法来访问列表中的元素
# 等价于yaml文件中的如下格式:
# servers:
# -port: 80
# host: example
--set servers[0].port=80,server[0].host=example
# 在--set中使用特殊字符,可以使用反斜杠进行转义
# 等价于yaml文件中的如下格式:
# name: "value1,value2"
--set name=value1\,value2
# 等价于yaml文件中的如下格式:
# nodeSelector:
# kubernetes.io/role: master
--set nodeSelector."kubernetes.io/role"=master更新
# 只会更新发生变化的部分,最终值为原先chart下values + 当前指定参数(不是在上一个版本上更新)
helm upgrade <release-name> <chart-path> -f <values-file>
helm upgrade <release-name> <chart-path> --set <key>=<value>
# 查看指定release设置的values
helm get values apache-23213213
# 每当发生了一次安装、升级或回滚操作,revision 的值就会加1。第一次 revision 的值永远是1。
# 查看一个特定 release 的修订版本号
helm history [RELEASE]
# 回滚到指定的版本
helm rollback [RELEASE] [REVISION]常见参数
- --timeout:
- 作用:设置 Helm 操作(如安装、升级、回滚等)的超时时间。如果操作在指定的时间内没有完成,Helm 会终止操作并返回一个错误。
- 示例:
--timeout 5m表示操作的超时时间为 5 分钟。
- --wait:
- 作用:等待所有的 Pods、PVCs、Services 和 Deployments 处于就绪状态后才认为操作完成。
- 示例:
--wait表示在所有资源就绪之前不会返回操作结果。
- --no-hooks:
- 作用:在执行操作时跳过运行钩子(hooks)。钩子是 Helm Chart 中定义的特殊资源,可以在特定的生命周期事件(如安装前、安装后、升级前、升级后等)触发执行。
- 示例:
--no-hooks表示在安装、升级或删除时不运行预定义的钩子。
- --recreate-pods:
- 作用:在升级过程中删除并重新创建所有的 Pods。
- 示例:
--recreate-pods表示在升级过程中强制重新创建所有的 Pods。
自定义chart
创建模板

- Chart.yaml:当前Chart的描述信息。
- charts:当前Chart依赖的Chart放在该目录下。
- templates:需要创建的Kubernetes资源文件放在该目录下。
- values.yaml:存储配置文件的动态替换
# 创建一个chart模板
helm create mychart定义资源
# 当实例化chart时,会输出该文件下的内容。
vim templates/NOTES.txt
# 删除原先的配置文件
rm -rf /templates/*
# Deployment 配置文件,放在templates下
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: myapp-test
# 使用当前时间戳生成唯一的 Deployment 名称,格式化为年月日的格式
# 20060102030405 是 Go 语言中的时间格式化模板,年:2006,月:01(两位数),日:02(两位数),时:03(两位数,12小时制),分:04 (两位数),秒:05(两位数)
name: myapp-test-{{ now | date "20060102030405" }}-deploy
spec:
# 副本数量,从 values 文件中获取
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: myapp-test
template:
metadata:
labels:
app: myapp-test
spec:
containers:
# 容器镜像,从 values 文件中获取
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: myapp
# Service 配置文件,放在templates下
apiVersion: v1
kind: Service
metadata:
labels:
app: myapp-test
# 使用当前时间戳生成唯一的 Service 名称,格式化为年月日的格式
name: myapp-test-{{ now | date "20060102030405" }}-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
# 如果 service 类型是 NodePort,则设置 nodePort,否则不设置
{{- if eq .Values.service.type "NodePort" }}
nodePort: {{ .Values.service.nodeport }}
{{- end }}
selector:
app: myapp-test
# 服务类型,从 values 文件中获取并加引号
type: {{ .Values.service.type | quote }}
# values.yaml文件,放在创建chart的根目录下,和templates同一层级
replicaCount: 5
image:
repository: wangyanglinux/myapp
tag: "v1.0"
service:
type: NodePort
nodeport: 31232发布部署
helm install myapp myapp/ Ingress-nginx
概述
负载均衡
四层负载均衡: 负载均衡器只起到转发的作用,所以客户端与负载均衡器,负载均衡器与后端服务器,是同一个TCP连接。
七层负载均衡: 因为负载均衡器需要解析应用层的数据(如HTTP头、URL、Cookie等),然后根据这些信息做出负载均衡决策。所以需要建立两个TCP连接(客户端与负载均衡器,负载均衡器与后端服务器)。

架构
Nginx-Ingress架构

总体架构

安装
helm配置
# 从github中下载对应的chart
https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.12.0
# 解压后编辑values文件,进行如下修改
# 将 hostNetwork 设置为 true,表示 Pod 将使用主机网络。这通常用于需要直接访问主机网络的情况
hostNetwork: true
# 将 dnsPolicy 设置为 ClusterFirstWithHostNet,表示在使用主机网络时,DNS 策略将优先使用集群 DNS,如果解析失败,则回退到主机的 DNS 配置。
dnsPolicy: ClusterFirstWithHostNet
# 将 kind 设置为 DaemonSet,用于高可用配置,避免多个ingress-nginx部署在同一个节点上
kind: DaemonSet
# 当创建 Ingress 资源时,如果没有指定 ingressClassName,将使用这个默认的 IngressClass。
ingressClassResource:
default: true
# 注释掉所有镜像的digest
#digest: sha256:e6b8de175acda6ca913891f0f727bca4527e797d52688cbe9fec9040d6f6b6fa
#digestChroot: sha256:87c88e1c38a6c8d4483c8f70b69e2cca49853bb3ec3124b9b1be648edf139af3
#digest: sha256:aaafd456bda110628b2d4ca6296f38731a3aaf0bf7581efae824a41c770a8fc4镜像安装
# 通过下方命令可以查看对应的镜像仓库指定镜像的版本有哪些
skopeo list-tags docker://swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/defaultbackend-amd64
# 拉取镜像到本地
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/controller:v1.12.0
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.5.0
# 此处的版本号没有v,可以通过skopeo命令查看版本有哪些
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/defaultbackend-amd64:1.5
# 为拉取的镜像修改标签,使得其可以被helm检查到,而不去云端拉取(也可以直接修改values配置文件中的仓库)
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/controller:v1.12.0 registry.k8s.io/ingress-nginx/controller:v1.12.0
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.5.0 registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.5.0
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/defaultbackend-amd64:1.5 registry.k8s.io/defaultbackend-amd64:1.5
# 清理旧镜像
docker rmi swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/controller:v1.12.0
docker rmi swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.5.0
docker rmi swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/defaultbackend-amd64:1.5
# 导出
docker save -o ingress-nginx-images.tar \
registry.k8s.io/ingress-nginx/controller:v1.12.0 \
registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.5.0 \
registry.k8s.io/defaultbackend-amd64:1.5
# 将其分发到其他设备
scp ingress-nginx-images.tar root@k8s-node02:/root/
scp ingress-nginx-images.tar root@k8s-node01:/root/
# 导入
docker load -i ingress-nginx-images.tar实例化
kubectl create namespace ingress
helm install ingress-nginx ./ingress-nginx -n ingress
# NAME READY STATUS RESTARTS AGE
# ingress-nginx-controller-fktjv 1/1 Running 0 32m
# ingress-nginx-controller-kdqhr 1/1 Running 0 32m
kubectl get pod -n ingress案例
Http代理
创建以下资源后,直接访问192.168.126.12或13将会返回404(因为nginx-ingress是七层负载均衡,下述配置中通过域名进行了匹配,直接使用IP将匹配为空)。
在以下路径:C:\Windows\System32\drivers\etc\hosts(Linux对应路径为/etc/hosts,注意取消只读属性),添加对应域名解析记录即可通过域名访问。
# 复制以下配置文件,将域名进行修改:%s/www1/www2,并且将镜像进行替换为wangyanglinux/myapp:v2.0,再创建一次资源
# hosts文件添加一下dns解析,通过域名均可以访问对应pod。
# 192.168.128.12 www1.xinxianghf.com
# 192.168.128.12 www2.xinxianghf.com
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-httpproxy-www1
spec:
replicas: 2
selector:
matchLabels:
hostname: www1
template:
metadata:
labels:
hostname: www1
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: ingress-httpproxy-www1
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
hostname: www1
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-httpproxy-www1 # Ingress 资源的名称
spec:
# 如果配置了默认的ingressClassResource.default: true可以不指定
# kubectl get ingressclass:查看当前可以使用的对应资源
ingressClassName: nginx # 使用名为 nginx 的 IngressClass
rules:
- host: www1.xinxianghf.com # 处理 www1.xinxianghf.com 域名的请求
http:
paths:
- path: / # 匹配路径为 / 的请求
pathType: Prefix # 路径匹配类型为前缀匹配
backend:
service:
name: ingress-httpproxy-www1 # 转发到名为 ingress-httpproxy-www1 的服务
port:
number: 80 # 服务的端口号为 80Https代理
# 生成了一个有效期为365天的自签名证书 tls.crt 和对应的私钥 tls.key,并将它们保存到当前目录中。这个证书可以用于配置 Nginx 或其他需要 SSL/TLS 的服务
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
# 将证书封装为Secret
kubectl create secret tls ingress-nginx-tls --key tls.key --cert tls.crt
# 在hosts文件内添加dns解析,此时访问该域名将会使用https(因为证书不是信任CA签发的,所以会提示不安全)
192.168.126.12 ssl.xinxianghf.comapiVersion: v1
kind: Service
metadata:
name: ingress-httpproxy-ssl
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
hostname: ssl
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-httpproxy-ssl
spec:
replicas: 2
selector:
matchLabels:
hostname: ssl
template:
metadata:
labels:
hostname: ssl
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v3.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-httpproxy-ssl
namespace: default
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # 启用SSL重定向,将HTTP请求重定向到HTTPS
spec:
ingressClassName: nginx # 指定使用的Ingress类,这里是nginx
rules:
- host: ssl.xinxianghf.com # 定义处理的域名
http:
paths:
- path: / # 定义路径规则,这里是根路径
pathType: Prefix # 路径类型为前缀匹配
backend:
service:
name: ingress-httpproxy-ssl # 后端服务的名称
port:
number: 80 # 后端服务的端口号
tls:
- hosts:
- ssl.xinxianghf.com # 定义使用TLS的域名
secretName: ingress-nginx-tls # 存储TLS证书的Secret名称 basicAuth
# 安装 httpd-tools 包
dnf -y install httpd-tools
# 创建一个基本认证文件,用户为 xinxianghf,密码为随后输入的内容
htpasswd -c auth xinxianghf
# 创建一个 Kubernetes Secret,包含基本认证文件
kubectl create secret generic ingress-basic-auth --from-file=auth
# 在hosts文件内添加dns解析,此时再次访问需要使用之前创建的认证信息进行登录。
192.168.126.12 auth.xinxianghf.comapiVersion: v1
kind: Service
metadata:
name: ingress-with-auth
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
hostname: auth
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-with-auth
spec:
replicas: 2
selector:
matchLabels:
hostname: auth
template:
metadata:
labels:
hostname: auth
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v3.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-auth
annotations:
nginx.ingress.kubernetes.io/auth-type: basic # 使用基本认证
nginx.ingress.kubernetes.io/auth-secret: ingress-basic-auth # 指定存储认证信息的 Secret
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - xinxianghf' # 认证提示信息
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
- host: auth.xinxianghf.com # 定义处理的域名
http:
paths:
- path: / # 定义路径规则,这里是根路径
pathType: ImplementationSpecific # 具体的 Ingress 控制器实现决定
backend:
service:
name: ingress-with-auth # 后端服务的名称
port:
number: 80 # 后端服务的端口号默认错误后端
# 需要在使用helm实例化镜像的时候指定,错误页面封装在镜像之中,当出现错误时机会返回这一个页面
defaultBackend:
enabled: true
name: defaultbackend
image:
registry: docker.io
image: wangyanglinux/tools
tag: "errweb1.0"
port: 80定制后端错误
# 在hosts文件内添加dns解析,访问不存在的页面时将会跳转到自定义错误页
# 192.168.126.12 err.xinxianghf.com
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: errcode
name: errcode
spec:
replicas: 1
selector:
matchLabels:
app: errcode
template:
metadata:
labels:
app: errcode
spec:
containers:
- image: wangyanglinux/tools:errweb1.0
name: tools
---
apiVersion: v1
kind: Service
metadata:
labels:
app: errcode
name: errcode
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: errcode
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: errtest
name: errtest
spec:
replicas: 1
selector:
matchLabels:
app: errtest
template:
metadata:
labels:
app: errtest
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: tools
---
apiVersion: v1
kind: Service
metadata:
labels:
app: errtest
name: errtest
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: errtest
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: err.xinxianghf.com
namespace: default
annotations:
# 当错误码是404,415时,将会errcode这个service返回错误页
nginx.ingress.kubernetes.io/default-backend: "errcode"
nginx.ingress.kubernetes.io/custom-http-errors: "404,415"
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
- host: err.xinxianghf.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: errtest # 后端服务的名称
port:
number: 80 # 后端服务的端口号黑白名单
黑白名单配置可以使用configmap或annotation,若是同时配置了二者,annotation优先。
- Configmap:全局生效
- Annotation:只对指定的ingress生效
Config配置
# 编辑配置,添加黑名单
kubectl edit cm ingress-nginx-controller -n ingress
# data:
# allow-snippet-annotations: "true"
# block-cidrs: 192.168.10.12
# 编辑配置,添加白名单
kubectl edit cm ingress-nginx-controller -n ingress
# data:
# allow-snippet-annotations: "true"
# whitelist-source-range: 192.168.10.11黑白名单配置
# 黑名单配置
# 在hosts文件内添加dns解析,192.168.126.11将无法访问
# echo 192.168.126.12 black.xinxianghf.com >> /etc/hosts
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: black
name: black-deploy
spec:
replicas: 1
selector:
matchLabels:
app: black
template:
metadata:
labels:
app: black
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: black
name: black-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: black
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/server-snippet: |-
deny 192.168.126.11;
allow all;
name: black.xinxianghf.com
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
- host: black.xinxianghf.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: black-svc
port:
number: 80
# 白名单配置
# 在hosts文件内添加dns解析,仅有192.168.126.11可以访问
# echo 192.168.126.12 white.xinxianghf.com >> /etc/hosts
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: white
name: white-deploy
spec:
replicas: 1
selector:
matchLabels:
app: white
template:
metadata:
labels:
app: white
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: white
name: white-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: white
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: 192.168.126.11
name: white.xinxianghf.com
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
- host: white.xinxianghf.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: white-svc
port:
number: 80金丝雀(灰度)部署
for i in {1..100};do curl svc.xinxianghf.com >> sum;done
cat sum | sort | uniq -c
# 可以看到有约10%的pod被替换了,如果后续需要修改比重,修改V2后再次apply即可
# 92 www.xinxianghf.com | hello MyAPP | version v1.0
# 8 www.xinxianghf.com | hello MyAPP | version v2.0V1版本
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: v1
name: v1-deploy
spec:
replicas: 10
selector:
matchLabels:
app: v1
template:
metadata:
labels:
app: v1
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: v1
name: v1-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: v1
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: v1.xinxianghf.com
namespace: default
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
- host: svc.xinxianghf.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: v1-svc
port:
number: 80V2版本
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: v2
name: v2-deploy
spec:
replicas: 10
selector:
matchLabels:
app: v2
template:
metadata:
labels:
app: v2
spec:
containers:
- image: wangyanglinux/myapp:v2.0
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: v2
name: v2-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: v2
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: v2.xinxianghf.com
namespace: default
annotations:
nginx.ingress.kubernetes.io/canary: "true" # 启用金丝雀发布
nginx.ingress.kubernetes.io/canary-weight: "10" # 设置流量权重为 10%,即新版会在总体中占10%
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
# 需要与V1版本同名
- host: svc.xinxianghf.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: v2-svc
port:
number: 80四层TCP代理
# 访问http://192.168.126.13:9000/,会将请求代理到对应的svc
# kubectl edit ds -n ingress ingress-nginx-controller,进行如下配置
# spec:
# containers:
# - args:
# - /nginx-ingress-controller
# 指定 TCP 服务的配置文件,在当前pod所在命名空间下的nginx-ingress-tcp-configmap
# - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-configmap
apiVersion: v1
kind: ConfigMap
metadata:
# 名字需要和上方配置中指定的一样
name: nginx-ingress-tcp-configmap
namespace: ingress
data:
# 定义 TCP 服务的转发规则,将 9000 端口的请求转发到 default 命名空间中的 proxyhttps-svc 服务的 443 端口
"9000": "default/proxyhttps-svc:443"
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: proxyhttps
name: proxyhttps-deploy
spec:
replicas: 1
selector:
matchLabels:
app: proxyhttps
template:
metadata:
labels:
app: proxyhttps
spec:
containers:
- image: wangyanglinux/tools:httpsv1
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: proxyhttps
name: proxyhttps-svc
spec:
ports:
- name: 443-443
port: 443
protocol: TCP
targetPort: 443
selector:
app: proxyhttps
type: ClusterIP四层UDP代理
# kubectl edit ds -n ingress ingress-nginx-controller
# spec:
# containers:
# - args:
# - /nginx-ingress-controller
# # 指定 UDP 服务的配置文件,在当前pod所在命名空间下的nginx-ingress-udp-configmap
# - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-configmap
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ingress-udp-configmap
namespace: ingress
data:
"53": "kube-system/kube-dns:53"速率限制
- nginx.ingress.kubernetes.io/limit-rps:限制每秒的连接数,针对单个IP。
- nginx.ingress.kubernetes.io/limit-rpm:限制每分钟的连接数,针对单个IP。
- nginx.ingress.kubernetes.io/limit-rate:限制客户端每秒传输的字节数,单位为K,需要开启 proxy-buffering。
- nginx.ingress.kubernetes.io/limit-whitelist:速率限制白名单。
- nginx.ingress.kubernetes.io/limit-connections:限制每个客户端 IP 地址的并发连接数。
# 在hosts文件内添加dns解析
# echo 192.168.126.12 speed.xinxianghf.com >> /etc/hosts
# ab -c 10 -n 100 http://speed.xinxianghf.com/ | grep requests
# Complete requests: 100
# Failed requests: 86
# Time per request: 0.755 [ms] (mean, across all concurrent requests)
# Percentage of the requests served within a certain time (ms)
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: speed
name: speed-deploy
spec:
replicas: 1
selector:
matchLabels:
app: speed
template:
metadata:
labels:
app: speed
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: speed
name: speed-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: speed
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: speed.xinxianghf.com
namespace: default
annotations:
# 限制并发个数
nginx.ingress.kubernetes.io/limit-connections: "1"
spec:
ingressClassName: nginx # 指定使用的 Ingress 类
rules:
- host: speed.xinxianghf.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: speed-svc
port:
number: 80重定向
# 在hosts文件内添加对应的dns域名解析,通过域名访问将会被定向到百度
192.168.126.12 redirect.xinxianghf.com
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: redirect.xinxianghf.com
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/permanent-redirect: https://www.baidu.com # 永久重定向目标
nginx.ingress.kubernetes.io/permanent-redirect-code: '301' # 重定向状态码
spec:
ingressClassName: "nginx"
rules:
- host: redirect.xinxianghf.com # 定义处理的主机名
# 因为进行了重定向,所以下方的子属性都不需要了
http:重写
# 在hosts文件内添加对应的dns域名解析
# 访问rew.xinxianghf.com将会404,访问rew.xinxianghf.com/api将会重写到/
192.168.126.12 rew.xinxianghf.com
apiVersion: v1
kind: Service
metadata:
name: ingress-with-rewrite
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
hostname: rewrite
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-with-rewrite
spec:
replicas: 2
selector:
matchLabels:
hostname: rewrite
template:
metadata:
labels:
hostname: rewrite
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v3.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rew.xinxianghf.com
namespace: default
annotations:
# 定义重写目标路径,/$2表示将匹配的路径的第二个捕获组作为重写后的目标路径,捕获组是通过正则表达式定义的。
# 当客户端请求 http://rew.xinxianghf.com/api/foo 时,路径将被重写为 /foo,然后请求将被转发到 www1svc 服务的 80 端口
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: "nginx"
rules:
- host: rew.xinxianghf.com # 定义处理的主机名
http:
paths:
# 匹配以 /api 开头的路径。(/|$) 表示 /api 后面可以跟 / 或者结束,(.*) 表示匹配剩余的任何字符。
- path: /api(/|$)(.*) # 定义路径规则
pathType: ImplementationSpecific # 路径类型为实现特定
backend:
service:
name: ingress-with-rewrite # 后端服务的名称
port:
number: 80 # 后端服务的端口号Ingress-nginx snippet
curl snippet.xinxianghf.com
# www.xinxianghf.com | hello MyAPP | version v1.0
curl snippet.xinxianghf.com -H 'User-Agent: Android' -I
# HTTP/1.1 302 Moved Temporarily
# Date: Sat, 11 Jan 2025 15:42:20 GMT
# Content-Type: text/html
# Content-Length: 138
# Connection: keep-alive
# Location: http://www.baidu.com # kubectl edit cm ingress-nginx-controller -n ingress,编辑如下配置文件,开启snippet,可以清空ingress-nginx pod,确保对应配置生效:kubectl delete pod --all -n ingress
# data:
# allow-snippet-annotations: "true"
# annotations-risk-level: Critical
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: snippet
name: snippet
spec:
replicas: 1
selector:
matchLabels:
app: snippet
template:
metadata:
labels:
app: snippet
spec:
containers:
- image: wangyanglinux/myapp:v1.0
name: tools
---
apiVersion: v1
kind: Service
metadata:
labels:
app: snippet
name: snippet
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: snippet
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: snippet.xinxianghf.com # Ingress 资源的名称
namespace: default # Ingress 资源所在的命名空间
annotations:
nginx.ingress.kubernetes.io/server-snippet: | # 自定义 Nginx 配置片段
set $agentflag 0; # 初始化变量 $agentflag 为 0
if ($http_user_agent ~* "(Android|IPhone)") { # 如果 User-Agent 包含 "Android" 或 "IPhone"
set $agentflag 1; # 将 $agentflag 设置为 1
}
if ($agentflag = 1) { # 如果 $agentflag 为 1
return 302 http://www.baidu.com; # 重定向到 http://www.baidu.com
}
spec:
ingressClassName: "nginx"
rules:
- host: snippet.xinxianghf.com # 定义处理的主机名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: snippet # 后端服务的名称
port:
number: 80 # 后端服务的端口号后端HTTPS代理
从nginx发向后端的请求也使用HTTPS协议,而不是HTTP
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: proxyhttps
name: proxyhttps-deploy
spec:
replicas: 1
selector:
matchLabels:
app: proxyhttps
template:
metadata:
labels:
app: proxyhttps
spec:
containers:
- image: wangyanglinux/tools:httpsv1
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: proxyhttps
name: proxyhttps-svc
spec:
ports:
- name: 443-443
port: 443
protocol: TCP
targetPort: 443
selector:
app: proxyhttps
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: proxyhttps.xinxianghf.com
namespace: default
annotations:
nginx.ingress.kubernetes.io/backend-protocol: HTTPS # 指定后端服务使用 HTTPS 协议
spec:
ingressClassName: nginx
rules:
- host: proxyhttps.xinxianghf.com # 定义处理的主机名
http:
paths:
- path: /
pathType: ImplementationSpecific # 路径类型为实现特定
backend:
service:
name: proxyhttps-svc # 后端服务的名称
port:
number: 443 # 后端服务的端口号,使用 443 端口(HTTPS)