云原生

Xiao He Lv1

1. Docker

1.1 Docker是什么?

[!IMPORTANT]

==Docker是一个开源的容器化平台,Docker的容器中没有系统,它可以帮助你把应用程序和它们的依赖打包成一个“容器”,这样你就可以在任何地方运行这个容器,而不用担心环境的不同==。想象一下,你有一个应用程序,它在你的开发环境中运行得很好,但当你把它部署到生产环境时,可能会遇到各种问题,因为两个环境的配置不一样。Docker可以帮助你解决这个问题。

简单来说Docker就像是一个神奇的盒子,你可以把程序和它需要的东西(比如它喜欢吃的食物、玩具等)一起打包进这个盒子里。这样,不管你把这个盒子搬到哪里,只要那里有电脑,你的程序都能在里面正常运行,而且不会和其他程序打架抢东西。

==总的来说,Docker就是帮你把程序和它需要的东西打包好,让程序在任何地方都能快速、安全、方便地运行。==

1.1.1 容器服务原理

什么时上帝进程?

简单来说就是系统创建之处产生的第一个进程
特点:

  • 没有父进程,PID==1

  • 是所有程序的根进程

  • 上帝进程死亡系统实例也就关闭了

容器的上帝进程

  • 容器的启动进程就是上帝进程

  • 如果容器的启动进程关闭等同于容器关闭

  • 上帝进程无法在后台执行

  • 容器的启动进程必须放在前台执行

1.2 Docker的三大概念

1.2.1 镜像

  • 镜像是一个只读的模板,包含了创建Docker容器所需的所有文件和配置。镜像可以包含操作系统、应用程序、库、环境变量等。镜像是容器的基础,容器是镜像的运行实例。

  • 定义:镜像是一个包含了所有必要文件和配置的只读模板,用于创建Docker容器。

  • 创建:镜像可以通过Dockerfile文件构建,Dockerfile文件定义了镜像的构建步骤。

1.2.2 容器

  • 容器是镜像的运行实例。容器是一个轻量级的、独立的执行环境,它可以运行一个或多个进程。容器与虚拟机不同,它们共享主机操作系统的内核,而不是包含完整的操作系统。

  • 定义:容器是镜像的运行实例,提供一个隔离的执行环境。

  • 运行:容器可以通过docker run命令从镜像启动。

1.2.3 仓库

  • 仓库是存储和分发镜像的地方。Docker仓库可以是公共的(如Docker Hub),也可以是私有的。仓库允许用户上传、下载和共享镜像。

  • 定义:仓库是存储和分发镜像的集合。

  • 访问:可以通过docker pull和docker push命令从仓库下载和上传镜像。

1.2.4 总结

  • 镜像:只读模板,包含创建容器所需的所有文件和配置。

  • 容器:镜像的运行实例,提供一个隔离的执行环境。

  • 仓库:存储和分发镜像的地方。

1.3 Docker常用命令

1.3.1 镜像常用命令

镜像管理命令 说明
docker version 常看服务器与客户端版本
docker info 查看docker服务配置信息
docker images 查看本机镜像
docker pull 镜像名:标签 下载镜像
docker save 镜像名:标签 -0 文件名 备份镜像为tar包
docker load -i 备份文件名称 导入备份的镜像文件
docker history 镜像名:标签 常看镜像的制作历史
docker rmi 镜像名:标签 删除镜像(必须先删除该镜像启动的所有的容器)
docker tag 镜像id:标签 镜像名:新的标签 创建新的镜像名和标签

1.3.2 容器常用命令

容器管理命令 说明
docker run -it(d) 镜像名:标签 创建容器
docker ps -a[q] 查看所有容器的信息/id
docker inspect 镜像名|容器名 查看(镜像/容器)的详细信息
docker [start|stop|restart] 容器 id 启动、停止、重启容器
docker exec -it 容器id 启动命令 在容器内执行命令
docker logs 容器id 查看容器日志
docker cp 路径1 路径2 拷贝文件:路径模式(本机路径、容器id/路径)
docker rm [-f] 容器id 删除容器/强制删除
docker commit 容器id 新镜像名:新标签名 把容器制作成镜像

1.4 Dockerfile

1.4.1 commit的局限

很容易制作简单的镜像,但碰到复杂的情况就十分不方便

例如碰到下面的情况:

  • 需要设置默认的启动命令

  • 需要设置环境变量

  • 需要指定镜像开放某些特定的端口

==Dockerfile就是解决以上问题的方法==

1.4.2 Dockerfile是什么?

  • Dockerfile是一种更强大的镜像制作方式

  • 编写类似脚本的 Dockerfile文件,通过该文件制作镜像

1.4.3 如何使用Dockerfile制作镜像?

  1. 编写 Dockerfile

  2. 制作镜像docker build -t 镜像名称:标签 Dockerfile所在目录

1.4.4 Dockerfile中常用指令

指令 说明
FROM 指定基础镜像(唯一)
RUN 在构建镜像时执行命令,可以写多条
ADD 把文件拷贝到容器内,如果文件是tar.xx格式,会自动解压
COPY 把文件拷贝到容器内,不会自动解压
ENV 设置启动容器的环境变量
WORKDIR 设置启动容器的默认工作目录(唯一)
CMD 容器默认的启动参数(唯一)
ENTRYPOINT 容器默认的启动命令(唯一)
USER 启动容器使用的用户(唯一)
EXPOSE 使用镜像创建的容器默认监听使用的端口号/协议,不会实际发布端口,只是文档说明

[!CAUTION]

ENTRYPOINT一定会执行,CMD可以通过传递参数覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ENTRYPOINT 与 CMD 执行方式为 ${ENTRYPOINT} ${@-${CMD}}
[root@docker ~]# vim myimg/Dockerfile
FROM mylinux:latest
ENTRYPOINT ["echo"]
CMD ["/bin/ls", "-l"]

# 创建镜像
[root@docker ~]# docker build -t img:v2 myimg/

# CMD 做为参数传递,在容器内执行了 echo '/bin/ls -l'
[root@docker ~]# docker run -it --rm img:v2
/bin/ls -l

# CMD 被替换,在容器内执行了 echo id
[root@docker ~]# docker run -it --rm img:v2 id
id

制作apache镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@docker ~]# mkdir apache
[root@docker ~]# tar -czf apache/httpd.tar.gz index.html info.php
[root@docker ~]# docker cp httpd:/etc/httpd/conf.modules.d/00-mpm.conf apache/00-mpm.conf
[root@docker ~]# vim apache/00-mpm.conf
11: LoadModule mpm_prefork_module ... ... # 去掉注释
23: # LoadModule mpm_event_module ... ... # 注释配置
[root@docker ~]# ls apache/
00-mpm.conf Dockerfile httpd.tar.gz
[root@docker ~]# vim apache/Dockerfile
FROM mylinux:latest
RUN dnf install -y httpd php && dnf clean all
COPY 00-mpm.conf /etc/httpd/conf.modules.d/00-mpm.conf
ADD myweb.tar.gz /var/www/html/
ENV LANG=C
WORKDIR /var/www/html/
EXPOSE 80/tcp
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]

[root@docker ~]# docker build -t myhttpd:latest apache

制作nginx镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@docker ~]# ls nginx/
Dockerfile nginx-1.22.1.tar.gz
[root@docker ~]# vim nginx/Dockerfile
# 第一阶段编译程序
FROM mylinux:latest as builder
ADD nginx-1.22.1.tar.gz /
WORKDIR /nginx-1.22.1
RUN dnf install -y openssl-devel pcre-devel gcc make
RUN ./configure --prefix=/usr/local/nginx --with-pcre --with-http_ssl_module
RUN make
RUN make install
RUN echo 'Nginx is running !' >/usr/local/nginx/html/index.html

# 第二阶段最终镜像
FROM mylinux:latest
RUN dnf install -y pcre openssl && dnf clean all
COPY --from=builder /usr/local/nginx /usr/local/nginx
ENV PATH=${PATH}:/usr/local/nginx/sbin
WORKDIR /usr/local/nginx
EXPOSE 80/tcp
CMD ["nginx", "-g", "daemon off;"]

[root@docker ~]# docker build -t mynginx:latest nginx

制作php-fpm镜像

1
2
3
4
5
6
7
8
9
10
11
12
[root@docker ~]# mkdir php
[root@docker ~]# vim php/Dockerfile
FROM mylinux:latest
RUN dnf install -y php-fpm && dnf clean all && \
mkdir -p /run/php-fpm && \
chown -R nobody.nobody /run/php-fpm /var/log/php-fpm && \
sed -ri 's,^(listen =).*,\1 127.0.0.1:9000,' /etc/php-fpm.d/www.conf
USER nobody
EXPOSE 9000/tcp
CMD ["/usr/sbin/php-fpm", "--nodaemonize"]

[root@docker ~]# docker build -t php-fpm:latest php

制作tomcat镜像

1
2
3
4
5
6
7
8
9
10
11
12
[root@tomcat ~]# mkdir myimg
[root@tomcat ~]# cd myimg
[root@tomcat myimg]# # 拷贝 apache-tomcat.tar.gz 到该目录中
[root@tomcat myimg]# vim Dockerfile
FROM myos:8.5
RUN yum install -y java-1.8.0-openjdk && yum clean all
ADD apache-tomcat.tar.gz /usr/local/
WORKDIR /usr/local/apache-tomcat/webapps
EXPOSE 8080
CMD ["/usr/local/apache-tomcat/bin/catalina.sh", "run"]
[root@tomcat myimg]# docker build -t myos:tomcat .
[root@tomcat myimg]# docker run -itd -p 8080:8080 myos:tomcat

1.5 Registry仓库

1.5.1 私有仓库概述

  • 私有仓库是存储者 docker image 的仓库

  • 管理了一个 docker 集群,在所有节点维护镜像的一致性是一个非常麻烦繁琐的任务,使用公共仓库,我们又无法控制仓库中的镜像、版本等数据,私有仓库就是解决这些问题的最佳方法

  • 用户只需要维护私有仓库里面的镜像即可,docker 客户端可以通过私有仓库创建容器服务

  • 主流仓库有 docker Registry 和 [**vmware Harbor**](###2.4.2 部署Harbor仓库)

  • Registry 提供了仓库的核心功能,包括分层传输机制、WEB接口等功能

  • Habor 是在 Registry 上进行了相应的企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括:提供WEB界面,优化用户体验,支持登陆、搜索功能,区分公有、私有镜像,以及基于角色的访问控制,集成日志审计、支持水平扩展等功能

1.5.2 部署registry私有仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#在registry上安装私有仓库
[root@registry ~]# dnf install -y docker-distribution
# 启动私有仓库,并设置开机自启动
[root@registry ~]# systemctl enable --now docker-distribution

#客户端配置
[root@docker ~]# vim /etc/hosts
192.168.88.30 registry
# 修改配置文件
[root@docker ~]# vim /etc/docker/daemon.json
{
"registry-mirrors": ["http://registry:5000", "其他镜像仓库"], #镜像仓库地址
"insecure-registries":["registry:5000"] #信任仓库地址
}
# 重启服务生效
[root@docker ~]# systemctl restart docker
[root@docker ~]# docker info

# 给镜像设置标签,上传本地mylinux:latest镜像到registry:5000的library路径中取名为mylinux:latest
[root@docker ~]# docker tag mylinux:latest registry:5000/library/mylinux:latest
#查看本地镜像
[root@docker-0002 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry:5000/library/mylinux latest 38e93535adaa 2 days ago 249MB
# 上传镜像
[root@docker ~]# docker push registry:5000/library/mylinux:latest

验证测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#验证测试
#查看镜像名称:curl http://仓库IP:5000/v2/_catalog
#查看镜像标签:curl http://仓库IP:5000/v2/镜像路径/tags/list
#使用易读格式:python3 -m json.tool
# 查看仓库中所有镜像的名称
[root@docker ~]# curl http://registry:5000/v2/_catalog
{"repositories":["img/mylinux","library/myhttpd","library/mynginx","library/php-fpm"]}

# 易读格式显示镜像名称
[root@docker-0002 ~]# curl -s http://registry:5000/v2/_catalog |python3 -m json.tool
{
"repositories": [
"img/mylinux",
"library/myhttpd",
"library/mynginx",
"library/php-fpm"
]
}

# 查看某一镜像的所有标签
[root@docker ~]# curl http://registry:5000/v2/img/mylinux/tags/list
{"name":"img/mylinux","tags":["latest","8"]}

#使用registry仓库创建容器
#删除所有容器
[root@docker ~]# docker rm -f $(docker ps -aq)
#删除所有镜像
[root@docker ~]# docker rmi $(docker images --format='{{.Repository}}:{{.Tag}}')

#下载镜像
[root@docker ~]# docker pull registry:5000/img/mylinux:latest
2b7cd6d88a7665dbea0a4b3d99478e9f302c0a5661d7676d6d3bd3cb6d181

#library 是默认路径,可以省略路径地址
[root@docker ~]# docker run -itd --rm myhttpd:latest
634766f788d665dbea0a4b39709e0a2cc8624fd99478e9f302c0a5661d76

1.6 部署容器服务

1.6.1 端口映射

容器化带来的问题

  • 新创建容器的IP 地址是随机的

  • 容器在重启后每次IP者都会发生变化

  • 容器服务只有宿主机才能访问

如何才能使用容器对外提供稳定的服务?

  • 容器端口可以与宿主机的端口进行映射绑定

  • 从而把宿主机变成对应的服务,不用关心容器的IP地址

  • 每个端口都只能和一个容器绑定

端口映射语法:
docker run -itd -p [可选IP]:宿主机端口:容器端口 镜像:标签(可有多个)

示例:

1
[root@docker ~]# docker run -itd --rm --name web -p 80:80 -p 443:443 myos:nginx

1.6.2 映射容器卷

Docker可以映射宿主机文件或目录到容器中

  • 目标对象不存在就自动创建

  • 目标对象存在就直接覆盖掉

  • 多个容器可以映射同一个目标对象来达到数据共享的目的

启动容器时,使用 -v 映射参数(可有多个)
docker run -itd -v宿主机对象:容器内对象 镜像名:标签

示例:

1
2
3
[root@docker ~]# docker run -itd --rm --name web -p 80:80 \
-v /root/conf:/usr/local/nginx/conf \
-v /var/webroot:/usr/local/nginx/html myos:nginx

1.6.3 容器网络通信

docker的网络通信模式

  • bridge 模式,默认模式

  • host模式,与宿主机共享网络

  • none模式,无网络模式

  • container模式,共享其他容器的网络命名空间

  • 自定义网络,自由创建桥接网络或者overlay网络

使用网络命名空间共享网络
参数是 --network=container:容器名|ID

示例:

1
2
[root@docker ~]# docker run -itd --rm --name php --network=container:web \
-v /var/webroot:/usr/local/nginx/html myos:php-fpm

1.7 Docker-compose

1.7.1 微服务概述

微服务并不是一种技术,而是架构思想、它以容器技术为载体,演进出的一种以软件运行环境、产品、研发、运营为一体全新模式。

  • 在微服务架构中每个微服务一般都会包含多个容器实例。

  • 如果每个微服务都要手动管理,那么效率之低、维护量之大可想而知。为了解决编排部署的问题,docker 公司推出了docker Compose 工具

  • Compose是一个用于定义和运行多容器的应用的工具。

  • 使用Compose,可以在一个文件中配置多个容器服务,然后使用一个简单的命令就可以轻松、高效地管理配置中引用的所有容器服务。

1.7.2 微服务治理

创建项目服务
项目文件 docker-compose.yaml

1
2
3
4
5
6
7
8
# 创建项目文件
[root@docker ~]# vim docker-compose.yaml
name: myweb #项目名称
version: "3" #语法格式版本
services: #关键字,定义服务
websvc: #服务名称
container_name: nginx #容器名称
image: myos:nginx #创建容器使用的镜像

docker compose 子命令

docker compose [-f xxx.yaml | -p project] 子命令

指令 说明
up 创建项目并启动容器
ls 列出可以管理的项目
images 列出项目使用的镜像
ps 显示项目中容器的状态
logs 查看下项目中容器的日志
start/stop/restart 启动项目/停止项目/重启项目
down 删除项目容器及网络

1.7.3 微服务编排

  • Compose项目是Docker官方的开源项目,负责实现容器集群的快速编排,==在Compose中有两个核心概念,分别是服务和项目==

  • 服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

  • 项目(project):由一组关联的应用容器组成的一个完整业务单元,在docker-compose.yaml文件中定义

compose语法

指令 说明
networks 配置容器连接的网络
container_name 指定容器名称
depends_on 服务依赖关系 services_[started、healthy、completed_successfully]
command 覆盖容器启动后默认执行的命令
environment 设置环境变量
image 指定为镜像名称或镜像 ID
network_mode 设置网络模式
restart 容器保护策略[always、no、on-failure]
ports 暴露端口信息
volumes 数据卷,支持 [volume、bind、tmpfs、npipe]

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@docker-0001 ~]# cat docker-compose.yml 
name: myweb
version: "3"
services:
websvc:
container_name: nginx
image: myos:nginx
environment:
- "TZ=Asia/Shanghai"
ports:
- 80:80
volumes:
- type: bind
source: /var/webroot
target: /usr/local/nginx/html
- type: bind
source: /root/conf/conf
target: /usr/local/nginx/conf
phpsvc:
container_name: php
image: myos:php-fpm
network_mode: "service:websvc"
restart: always
volumes:
- type: bind
source: /var/webroot
target: /usr/local/nginx/html

嵌入脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@docker-0001 ~]# cat docker-script.yaml 
name: mycmd
version: "3"
services:
shell:
container_name: mycmd
image: myos:8.5
command:
- sh
- -c
- |
for i in {1..5}
do
sleep 1
echo "$${i} ${HOSTNAME} && $${HOSTNAME}"
done
[root@docker-0001 ~]# docker compose -p mycmd logs
mycmd | 1 docker-0001 && aff93cf09d53
mycmd | 2 docker-0001 && aff93cf09d53
mycmd | 3 docker-0001 && aff93cf09d53
mycmd | 4 docker-0001 && aff93cf09d53
mycmd | 5 docker-0001 && aff93cf09d53

2. Kubernetes

[!IMPORTANT]

==kubernetes是一个开源的容器集群管理系统==,用于管理云平台中==多个主机==上的==容器化的应用==,kubernetes的目标是让部署容器化的应用简==单且高效==,可以实现容器集群的==自动化部署、自动扩缩容、自动维护==等功能。

Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。K8s 这个缩写是因为 K 和 s 之间有 8 个字符的关系。 ==Google 在 2014 年开源了 Kubernetes 项目==。 Kubernetes 建立在 Google 大规模运行生产工作负载十几年经验的基础上, 结合了社区中最优秀的想法和实践。

2.1 k8s核心概念

2.1.1 Node

代表集群中的一个节点,可以是物理服务器或虚拟机,==负责运行 Pod==

2.1.2 Pod

==pod(容器组)是Kubernetes中的最小的可部署单元。一个 Pod 中可以运行一个或多个容器,这些容器共享网络命名空间、存储资源等,Pod 中的服务可以互相访问,但Pod 与 Pod 之间是隔离的。可以把 Pod 理解为一个逻辑上的“主机”,容器则是在这个“主机”上运行的具体程序,也可以理解为pod是一个容器组。==

Kubernetes 集群中的 Pod 主要有两种用法:

  1. 运行单个容器的 Pod。”每个 Pod 一个容器”模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。

  2. 运行多个需要协同工作的容器的 Pod。一个pod中运行多个需要互相协作的容器,可以将多个紧密耦合、共享资源且始终在一起运行的容器编排在同一个pod中。

定义:
Pod 是 Kubernetes 中最小的可部署单元,表示在集群中运行的一个或多个容器。Pod 中的容器共享相同的网络和存储资源。

特性:

  • 每个 Pod 都有一个唯一的 IP 地址。

  • Pod 中的容器共享网络命名空间和存储卷。

  • Pod 是短暂的,应用程序的生命周期通常由更高层次的 Kubernetes 对象(如 Deployment)管理。

2.1.3 Service

==对外提供稳定的访问地址。在Kubernetes 中,一个服务可能由多个Pod提供支持,Service 可以将这些 Pod 组合起来,对外暴露一个统一的访问入口,实现负载均衡。这样,即使 Pod 挂掉重启,服务的访问地址也不会改变。比如,一个订单服务部署了多个副本,通过 Service 可以让用户始终通过一个固定的地址来访问该服务。==

定义:

Service 是 Kubernetes 中用于定义一组 Pod 的访问策略的对象。它提供了一个稳定的 IP 地址和 DNS 名称,使得外部服务可以访问这组 Pod

类型:

  • ClusterIP(默认):仅在集群内部可访问

  • NodePort:通过节点的 IP 和端口对外提供服务

  • LoadBalancer:集成云服务商的负载均衡器,对外提供服务

  • ExternalName:通过 CNAME 记录将服务映射到外部服务地址

特性:

  • 提供负载均衡和服务发现

  • 定义了一组 Pod 的访问策略

  • Service 为 Pod 提供了一个稳定的访问地址,即使 Pod 的 IP 地址发生变化,Service 的访问地址保持不变

2.1.4 Deployment

==用于管理无状态服务的控制器。它定义了Pod 的模板,指定副本数量,并提供滚动更新和重建策略。滚动更新可以按照用户定义的速率逐步替换旧的 Pod,确保服务无中断;重建则是直接停掉所有 Pod 再进行升级,适合非关键任务。例如,对于一个 Web 服务应用,使用 Deployment 可以方便地管理其多个副本的部署和更新。==

定义:
Deployment 用于管理无状态应用的部署和更新,确保指定数量的 Pod 副本始终处于运行状态

特点:

  • 支持滚动更新和回滚,可以平滑地更新应用版本

  • 可以定义 Pod 的副本数量,自动管理 Pod 的创建和销毁

  • 与 Service 结合使用,实现应用的高可用和负载均衡

2.1.5 StatefulSet

==用于管理需要状态持久化的Pod。每个 Pod 都有一个唯一的标识符,生命周期有序,Pod启动和停止遵循固定模式。适用于数据库(如 MySQL、MongoDB)、消息队列(如 Kafka、RabbitMQ)、分布式存储系统等需要保存状态信息的应用。与 Deployment 管理的无状态服务不同,StatefulSet管理的服务需要保证数据的一致性和持久性。==

定义:
StatefulSet 用于管理有状态应用的部署和更新,为每个 Pod 提供稳定的网络标识和存储

特点:

  • 每个 Pod 有一个唯一的序号和稳定的网络标识,便于管理有状态应用

  • 与 PersistentVolume 结合使用,实现数据的持久化存储

  • 支持有序的部署和更新,确保有状态应用的稳定运行

2.1.6 DaemonSet

==确保每个节点都运行一个Pod。通常用于集群范围的服务,比如日志收集(如 Fluentd)、监控服务(如Prometheus Node Exporter)等。它可以保证在集群的每个节点上都有一个特定的Pod 在运行,以便收集节点的日志信息或监控节点的状态。==

定义:
DaemonSet 确保集群中的每个节点都运行一个 Pod 副本,常用于部署日志收集、监控等系统级服务

特点:
自动在新加入的节点上创建 Pod 副本,确保每个节点都有相应的服务运行

2.1.7 Job&CronJob

***Job:***用于运行一次性任务,直到任务完成为止

***CronJob:***用于定时运行任务,类似于 Linux 的 cron 任务

***特点:***可以定义任务的执行频率和策略,自动管理任务的执行和重试

2.1.8 ConfigMap&Secret

ConfigMap:
用于存储配置数据,如环境变量、配置文件等,可以被 Pod 以文件或环境变量的形式使用

Secret:
用于存储敏感数据,如密码、密钥、证书等,可以被 Pod 以文件或环境变量的形式使用

特点:将配置和敏感数据与应用程序分离,提高应用程序的可移植性和安全性.

2.1.9 PV&PVC

***PersistentVolume(PV):***集群资源,用于提供持久化存储,可以是本地磁盘、网络存储等

***PersistentVolumeClaim(PVC):***用户对存储的请求,声明所需的存储容量、访问模式等,由 Kubernetes 自动匹配合适的 PV

***特点:***实现数据的持久化存储,与 Pod 的生命周期解耦,确保数据不会因 Pod 的重启或删除而丢失

2.1.10 Namespace

==Namespace(命名空间) 是 Kubernetes 中用于将集群资源划分为多个逻辑分区。它提供了一个机制,用于在同一个物理集群中运行多个虚拟集群。==

特性:

  • 提供资源的逻辑隔离。

  • 支持资源配额和限制。

  • 可以为不同的团队或项目分配独立的 Namespace。

2.1.11 Ingress

==Ingress 是 Kubernetes 中用于管理外部访问的对象。它提供了一个统一的入口,用于将外部请求路由到集群内的服务。==

特性:

  • 提供负载均衡、SSL 终止和名称虚拟主机。

  • 支持路径和主机名的路由规则。

  • 可以集成第三方 Ingress 控制器,如 Nginx、Traefik 和 HAProxy。

2.2 k8s核心组件

2.2.1 Master 组件

API Server

是集群的统一入口,提供了 RESTful API 接口,用于与 Kubernetes 集群进行交互。它处理所有的管理操作,包括创建、更新、删除和查询资源。
***作用:***接收来自用户和其他组件的请求,是操作 Kubernetes 资源的必经之路,保障集群的安全性和合法性。例如,开发人员通过kubectl命令行工具与 API Server 交互来部署应用。


etcd

是一个分布式键值存储系统,用于存储 Kubernetes 集群的所有数据。它是 Kubernetes 的“大脑”,存储了集群的状态和配置信息。
***作用:***存储如节点信息、Pod 定义、服务配置等数据。任何集群状态的改变都会记录在 etcd 中,是集群数据的核心存储,确保数据在节点间同步,维持集群的状态稳定


Controller Manager

Controller Manager 是 Kubernetes 的控制器管理器,负责运行各种控制器,实现集群自动化管理。负责监控集群的状态并确保实际状态与期望状态一致。
***作用:***通过 ReplicaSet 控制器保证 Pod 副本数稳定。不同的控制器协同工作,让集群根据用户定义自动调整状态,像自动修复故障 Pod,保证应用正常运行。


Scheduler

Scheduler 是 Kubernetes 的调度器,负责将 Pod 分配到适当的节点上运行。它根据资源需求、节点状态和调度策略进行决策
***作用:***依据节点资源(CPU、内存等)、亲和性和 Pod 服务质量要求,合理安排 Pod 运行节点,提高资源利用率,保证 Pod 正常运行


Calico

Calico 是开源容器网络插件,是一个纯三层的虚拟网络,它没有复用docker的docker0网桥,而是自己实现的,calico网络不对数据包进行额外封装,不需要NAT和端口映射。
***作用:***网络通信:为 Pod 分配 IP 地址,使 Pod 能通信,不管是否在同一节点。支持 IPIP 和 BGP 模式,前者简单适用于简单场景,后者高效适用于大规模复杂场景。
***网络安全:***通过定义策略控制 Pod 流量,基于标签设置规则,防止恶意访问。在多租户环境中能隔离租户应用,避免干扰和风险。


Metrics-server

Metrics-Server是集群核心监控数据的聚合器,提供节点和Pod的资源使用情况的信息,包括CPU 和内存的指标。
***作用:***通过kubelet 获取 node 和 Pod的CPU,内存等监控数据。为调度器、弹性控制器、以及Dashboard等UI组件提供数据来源

2.2.2 Node 组件

kubelet

Kubelet 是 Kubernetes 的节点代理,负责在节点上运行和管理容器。它确保节点上的容器按照期望状态运行
***作用:***Kubelet 与 API Server 通信,获取 Pod 的配置信息,并确保容器按照配置信息运行。


kube-proxy

Kube-proxy 是 Kubernetes 的网络代理,负责处理节点上的网络流量。它确保服务的网络连接和负载均衡
***作用:***就像节点内的 “流量调度员”。例如在一个微服务架构的集群中,有多个相同服务的 Pod,kube-proxy 会把对这个服务的请求均匀地分配到这些 Pod 上,确保服务的高可用性和高效性。在服务更新或 Pod 数量变化时,也能及时调整请求分配策略。


容器运行时(如 Docker、containerd、CRI-O)

是容器运行的实际执行者,包括镜像下载、容器创建和启动,以及生命周期管理。
***作用:***按照 kubelet 的要求,获取镜像并创建容器,提供资源隔离和限制,保证容器在规定资源范围内运行,是容器运行的基础。

2.2.3 日志与监控组件

Fluentd/Elasticsearch/Kibana (EFK)

日志收集和可视化系统.
***作用:***Fluentd负责将日志从Pod中收集并发送到Elasticsearch,Elasticsearch存储和索引日志数据,Kibana用于日志的可视化和分析,帮助用户快速定位问题和了解应用的运行状态.


Prometheus

开源的监控和告警系统.
***作用:***专门为Kubernetes环境设计,能够收集和存储时间序列数据,提供强大的查询和告警功能,监控集群的资源使用情况、性能指标、应用状态等,帮助用户及时发现和处理问题.


Grafana

开源的可视化平台.
***作用:***常与Prometheus结合使用,提供丰富的图表和仪表盘功能,帮助用户直观地展示监控数据,进行数据分析和可视化,提高监控的可视化效果和用户体验.

k8s架构图-ProcessOn

2.3 k8s集群管理工具

2.3.1 集群服务端口

软件 端口范围 用途
api-server 6443 所有组件接口服务
etcd 2379-2380 核心数据库
kube-scheduler 10259 调度服务
kube-container-manager 10257 控制器管理服务
kubelet 10250 节点代理服务
kube-proxy 10256 网络通讯与负载均衡

2.3.2 kubeadm集群管理工具

kubeadm 是一个用于快速初始化 Kubernetes 集群的工具。它能够自动化完成许多复杂的集群初始化步骤。

命令选项 描述
init 初始化k8s控制平面节点
join 用于将节点加入到已初始化的 Kubernetes 集群
upgrade apply 用于升级 Kubernetes 集群的版本
upgrade node 用于升级集群中的节点
config 用于管理集群配置
token 用于管理集群加入令牌
reset 用于重置节点状态

2.3.3 kubectl集群命令行工具

kubectl 是 Kubernetes 集群的命令行工具,用于与 Kubernetes API Server 进行交互。它可以完成几乎所有与集群资源管理相关的操作。例如,用户可以使用 kubectl 创建、删除、更新和查看各种 Kubernetes 资源,如 Pod、Service、Deployment、StatefulSet 等。

命令选项 描述
cluster-info 显示集群的相关配置信息
api-resources 查看当前服务器上的所有的资源对象
api-versions 查看当前服务器上的所有资源对象的版本
config 管理当前节点上的认证信息

2.3.4 命令的自动补全操作

kubeadm 命令行工具启用 Bash 自动补全功能

1
[root@master ~]# source <(kubeadm completion bash|tee /etc/bash_completion.d/kubeadm)

kubectl 命令行工具启用 Bash 自动补全功能

1
[root@master ~]# source <(kubectl completion bash|tee /etc/bash_completion.d/kubectl)

2.4 k8s集群部署示例

2.4.1 所需主机清单

主机名 IP地址 最低配置
harbor 192.168.10.240 2C/4G
master 192.168.10.10 2C/4G
node01 192.168.10.11 2C/2G
node02 192.168.10.12 2C/2G
node03 192.168.10.13 2C/2G

2.4.2 部署Harbor仓库

1. 部署docker

1
2
3
# 安装部署 docker,事先准备好yum源
[root@harbor ~]# dnf install -y docker-ce
[root@harbor ~]# systemctl enable --now docker

2. 导入harbor项目镜像

1
2
3
4
5
6
7
8
9
10
11
# 导入 harbor 项目镜像,链接: https://pan.baidu.com/s/1388UkBJ754E_kNt2XM6jHw?pwd=1234 提取码: 1234
[root@harbor ~]# tar -zxf harbor-v2.9.2.tgz -C /usr/local/
[root@harbor ~]# cd /usr/local/harbor
[root@harbor harbor]# docker load -i harbor.v2.9.2.tar.gz

# 创建 https 证书
[root@harbor harbor]# mkdir tls
[root@harbor harbor]# openssl genrsa -out tls/cert.key 2048
[root@harbor harbor]# openssl req -new -x509 -days 3652 -key tls/cert.key -out tls/cert.crt \
-subj "/C=CN/ST=BJ/L=BJ/O=Tedu/OU=NSD/CN=harbor" \
-addext "subjectAltName = IP:192.168.10.240"

3. 启动项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 修改配置文件
[root@harbor harbor]# cp harbor.yml.tmpl harbor.yml
[root@harbor harbor]# vim harbor.yml
05: hostname: 192.168.10.240
08: # http:
10: # port: 80
17: certificate: /usr/local/harbor/tls/cert.crt
18: private_key: /usr/local/harbor/tls/cert.key
36: harbor_admin_password: <登录密码>

# 预安装环境检查,生成项目文件
[root@harbor harbor]# /usr/local/harbor/prepare

# 创建并启动项目
[root@harbor harbor]# docker compose -f docker-compose.yml up -d

# 添加开机自启动
[root@harbor harbor]# chmod 0755 /etc/rc.d/rc.local
[root@harbor harbor]# echo "/usr/bin/docker compose -p harbor start" >>/etc/rc.d/rc.local

4. 验证项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看项目
[root@harbor ~]# docker compose ls -a
NAME STATUS CONFIG FILES
harbor running(9) /usr/local/harbor/docker-compose.yml

# 查看容器状态
[root@harbor ~]# docker compose -p harbor ps
NAME COMMAND SERVICE STATUS
harbor-core "/harbor/entrypoint.…" core running (healthy)
harbor-db "/docker-entrypoint.…" postgresql running (healthy)
harbor-jobservice "/harbor/entrypoint.…" jobservice running (healthy)
harbor-log "/bin/sh -c /usr/loc…" log running (healthy)
harbor-portal "nginx -g 'daemon of…" portal running (healthy)
nginx "nginx -g 'daemon of…" proxy running (healthy)
redis "redis-server /etc/r…" redis running (healthy)
registry "/home/harbor/entryp…" registry running (healthy)
registryctl "/home/harbor/start.…" registryctl running (healthy)

5. Harbor仓库管理

容器管理命令 说明
docker login 登录私用镜像仓库
docker logout 退出登录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#=======================Harbor仓库管理示例=====================
# 在带有docker软件的设备上面做管理实验
# 添加主机配置
[root@docker ~]# vim /etc/hosts
192.168.10.240 harbor

# 添加私有仓库配置
[root@docker ~]# vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://harbor:443", "其他镜像仓库"],
"insecure-registries":["harbor:443", "其他镜像仓库"]
}
[root@docker ~]# systemctl restart docker

# 登录 harbor 仓库
[root@docker ~]# docker login harbor:443
Username: <登录用户>
Password: <登录密码>
... ...
Login Succeeded
# 认证信息记录文件
[root@docker ~]# cat /root/.docker/config.json
{
"auths": { ... ... }
}
# 退出登录
[root@docker ~]# docker logout harbor:443
Removing login credentials for harbor:443

# =====================上传镜像到harbor仓库中==================
# 设置标签
[root@docker ~]# docker tag myos:httpd harbor:443/private/httpd:latest
# 没有登录上传失败
[root@docker ~]# docker push harbor:443/private/httpd:latest
65dbea0a4b39: Preparing
unauthorized: unauthorized to access repository ......

# 登录成功后才可以上传
[root@docker ~]# docker login harbor:443
Username: <登录用户>
Password: <登录密码>

Login Succeeded
# 上传成功
[root@docker ~]# docker push harbor:443/private/httpd:latest
The push refers to repository [harbor:443/private/httpd]
......

# 上传镜像到 library 项目
[root@docker ~]# docker tag myos:latest harbor:443/library/myos:latest
# 没有权限上传失败
[root@docker ~]# docker push harbor:443/library/myos:latest
The push refers to repository [harbor:443/library/myos]
65dbea0a4b39: Preparing
unauthorized: unauthorized to access repository:
......

# 赋权后重新上传镜像
[root@docker ~]# docker push harbor:443/library/myos:latest
The push refers to repository [harbor:443/library/myos]
......

2.4.3 安装控制节点

1. 环境配置

1
2
3
4
# 禁用 firewall 和 swap
[root@master ~]# sed '/swap/d' -i /etc/fstab
[root@master ~]# swapoff -a
[root@master ~]# dnf remove -y firewalld-*

2. 安装软件包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@master ~]# vim /etc/hosts
192.168.10.240 harbor
192.168.10.10 master
192.168.10.11 node01
192.168.10.12 node02
192.168.10.13 node03

# 配置好yum仓库(要有docker和k8s相关的软件包),然后安装部署k8s所需的软件包
[root@master ~]# dnf install -y kubeadm kubelet kubectl containerd.io ipvsadm ipset iproute-tc
# 导出containerd默认的配置文件,然后进行修改
[root@master ~]# containerd config default >/etc/containerd/config.toml
[root@master ~]# vim /etc/containerd/config.toml
61: sandbox_image = "harbor:443/k8s/pause:3.9"
125: SystemdCgroup = true
154 行新插入:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://192.168.10.240:443"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."harbor:443"]
endpoint = ["https://192.168.10.240:443"]
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.10.240:443".tls]
insecure_skip_verify = true
[root@master ~]# systemctl enable --now kubelet containerd

3. 配置内核参数

1
2
3
4
5
6
7
8
9
10
11
12
13
# 加载内核模块
[root@master ~]# vim /etc/modules-load.d/containerd.conf
br_netfilter
xt_conntrack
[root@master ~]# systemctl start systemd-modules-load.service

# 设置内核参数
[root@master ~]# vim /etc/sysctl.d/99-kubernetes-cri.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.netfilter.nf_conntrack_max = 1000000
[root@master ~]# sysctl -p /etc/sysctl.d/99-kubernetes-cri.conf

如果出现了以下的报错,可以使用modprobe命令加载模块

image-20250703100513495

1
2
3
[root@node01 ~]# modprobe bridge
[root@node01 ~]# modprobe nf_conntrack
[root@node01 ~]# modprobe br_netfilter

执行命令后就可以了

image-20250703100754865

4. 导入k8s所需的镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 先安装docker
[root@master ~]# dnf install -y docker-ce
# 配置docker指定镜像仓库,要事先准备好harbor仓库
[root@master ~]# vim /etc/docker/daemon.json
{
"registry-mirrors":["https://harbor:443"],
"insecure-registries":["harbor:443"]
}
[root@master ~]# systemctl enable --now docker
[root@master ~]# docker info

# 查看当前k8s所需的镜像
[root@master ~]# kubeadm config images list
I0506 11:49:25.749132 9305 version.go:256] remote version is much newer: v1.33.0; falling back to: stable-1.29
registry.k8s.io/kube-apiserver:v1.29.15
registry.k8s.io/kube-controller-manager:v1.29.15
registry.k8s.io/kube-scheduler:v1.29.15
registry.k8s.io/kube-proxy:v1.29.15
registry.k8s.io/coredns/coredns:v1.11.1
registry.k8s.io/pause:3.9
registry.k8s.io/etcd:3.5.10-0

# 登录仓库,并上传k8s所需的镜像
[root@master ~]# docker login harbor:443
Username: <登录用户>
Password: <登录密码>
Login Succeeded

# https://download.csdn.net/download/hehe228/90768818,在该地址有k8s-v1.29.2版本所需要的初始化镜像tar包
[root@master ~]# docker load -i init/v1.29.2.tar.xz
[root@master ~]# docker images|while read i t _;do
[[ "${t}" == "TAG" ]] && continue
[[ "${i}" =~ ^"harbor:443/".+ ]] && continue
docker tag ${i}:${t} harbor:443/k8s/${i##*/}:${t}
docker push harbor:443/k8s/${i##*/}:${t}
docker rmi ${i}:${t} harbor:443/k8s/${i##*/}:${t}
done

5. master安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 可以参考https://download.csdn.net/download/hehe228/90768834,事先准备好初始化的yaml文件,也可以使用kubeadm命令生成一个初始化yaml文件,
[root@master ~]# vim /root/init/init.yaml
13: advertiseAddress: 192.168.10.10 # 修改成master主机的IP地址

# 测试系统环境
[root@master ~]# kubeadm init --config=init/init.yaml --dry-run 2>error.log
# 如果文件为空,说明测试初始化没有什么大问题
[root@master ~]# cat error.log

# 主控节点初始化
[root@master ~]# rm -rf error.log /etc/kubernetes/tmp
# 这里即使上一步测试没有问题,有的时候这一步也可能出错,一般都是环境的问题
[root@master ~]# kubeadm init --config=init/init.yaml |tee init/init.log

# 管理授权
[root@master ~]# mkdir -p $HOME/.kube
[root@master ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@master ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 验证安装结果
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master NotReady control-plane 19s v1.29.2

6. 安装网络插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# https://download.csdn.net/download/hehe228/90769045,该地址有calico所需的镜像,https://download.csdn.net/download/hehe228/90769052,该地址有calico所需的yaml文件
[root@master ~]# ls calico/
calico.tar.xz calico.yaml

[root@master calico]# docker load -i calico.tar.xz
# 上传到harbor仓库
[root@master calico]# docker images|while read i t _;do
[[ "${t}" == "TAG" ]] && continue
[[ "${i}" =~ ^"harbor:443/".+ ]] && continue
docker tag ${i}:${t} harbor:443/plugins/${i##*/}:${t}
docker push harbor:443/plugins/${i##*/}:${t}
docker rmi ${i}:${t} harbor:443/plugins/${i##*/}:${t}
done

# 安装calico网络插件
# 修改yaml文件,指定到harbor仓库
[root@master calico]# sed -ri 's,^(\s*image: )(.*/)?(.+),\1harbor:443/plugins/\3,' calico.yaml

[root@master calico]# kubectl apply -f calico.yaml
[root@master calico]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane 23m v1.29.2

2.4.4 安装计算节点

获取凭证

1
2
3
4
5
6
7
8
9
10
11
12
# 查看 token
[root@master ~]# kubeadm token list
TOKEN TTL EXPIRES
abcdef.0123456789abcdef 23h 2022-04-12T14:04:34Z
# 删除 token
[root@master ~]# kubeadm token delete abcdef.0123456789abcdef
bootstrap token "abcdef" deleted
# 创建 token,并记录返回的命令
[root@master ~]# kubeadm token create --ttl=0 --print-join-commandesi
kubeadm join 192.168.10.10:6443 --token fhf6gk.bhhvsofvd672yd41 --discovery-token-ca-cert-hash sha256:ea07de5929dab8701c1bddc347155fe51c3fb6efd2ce8a4177f6dc03d5793467
# 获取 hash 值 [1、在创建 token 时候显示 2、使用 openssl 计算]
[root@master ~]# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt |openssl rsa -pubin -outform der |openssl dgst -sha256 -hex

node安装

1
2
3
4
5
6
7
8
9
10
11
# https://github.com/adminzhh/ansible_playbook/,该地址是使用ansible自动化来部署node节点的,要在master节点上面执行。
[root@node ~]# 控制节点(安装步骤 1)
[root@node ~]# 控制节点(安装步骤 2)
[root@node ~]# 控制节点(安装步骤 3)

[root@node ~]# kubeadm join 192.168.10.10:6443 --token <你的token> --discovery-token-ca-cert-hash sha256:<ca 证书 hash>
#------------------------ 在 master 节点上验证---------------------------
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane 76m v1.29.2
node01 Ready <none> 61s v1.29.2

验证节点状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 验证节点工作状态
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane 99m v1.29.2
node01 Ready <none> 23m v1.29.2
node02 Ready <none> 57s v1.29.2
node03 Ready <none> 57s v1.29.2

# 验证容器工作状态,kube-system空间里的pod都是集群的核心pod,状态必须都得为Running运行状态
[root@master ~]# kubectl -n kube-system get pods
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-fc945b5f7-p4xnj 1/1 Running 0 77m
calico-node-6s8k2 1/1 Running 0 59s
calico-node-bxwdd 1/1 Running 0 59s
calico-node-d5g6x 1/1 Running 0 77m
calico-node-sjngw 1/1 Running 0 24m
coredns-844c6bb88b-89lzt 1/1 Running 0 59m
coredns-844c6bb88b-qpbvk 1/1 Running 0 59m
etcd-master 1/1 Running 0 70m
kube-apiserver-master 1/1 Running 0 70m
kube-controller-manager-master 1/1 Running 0 70m
kube-proxy-5xjzw 1/1 Running 0 59s
kube-proxy-9mbh5 1/1 Running 0 59s
kube-proxy-g2pmp 1/1 Running 0 99m
kube-proxy-l7lpk 1/1 Running 0 24m
kube-scheduler-master 1/1 Running 0 70m

2.5 k8s资源对象与管理

[!IMPORTANT]

==k8s中把可以创建或配置的应用和服务称为资源对象,我们在集群中创建的Pod、负载均衡、存储、网络服务等都是资源对象,对象可以看作是资源的运行实例==

如何创建资源对象?

  • 简单资源对象可以使用kubectI直接创建

  • 高级资源对象需要使用资源清单文件创建

2.5.1 kubectl子命令使用

子命令 说明 备注
run 创建pod对象 创建即运行,没有停止的概念
create 创建资源对象 不能创建pod
get 查看资源对象的状态信息 可选参数:-o 显示格式
describe 查询资源对象的属性信息
logs 查看容器的报错信息 可选参数:-c容器名称
exec 在某一个容器内执行特定的命令 可选参数:-c容器名称
cp 在容器和宿主机之间拷贝文件/目录 可选参数:-c容器名称
delete 删除资源对象 可选参数:-f文件名称

kubectl create <资源对象> [选项/参数]命令创建资源

示例:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建名称空间资源对象
[root@master ~]# kubectl create namespace work
namespace/work created

# 查看名称空间
[root@master ~]# kubectl get namespaces
NAME STATUS AGE
default Active 39h
kube-node-lease Active 39h
kube-public Active 39h
kube-system Active 39h
work Active 11s

查看系统命名空间

查看命名空间中的资源对象:kubectl get pods -n kube-system

  • default 默认的命名空间,不声明命名空间的Pod都在这里

  • kube-node-lease为高可用提供心跳监视的命名空间

  • kube-public 公共数据,所有用户都可以读取它

  • kube-system 系统服务对象所使用的命名空间

==k8s系统核心服务都运行在kube-system名称空间中==

创建Pod资源对象

kubectl -n <命名空间名> run <pod名> --image=<镜像名:标签>

示例:

1
2
3
4
5
6
7
8
# 在 work 名称空间创建 Pod
[root@master ~]# kubectl -n work run myhttp --image=myos:httpd
pod/myhttp created

# 查询资源对象
[root@master ~]# kubectl -n work get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
myhttp 1/1 Running 0 3s 10.244.2.2 node02

Pod状态分析

Pod的status字段是一个PodStatus的对象,Pod对象总是应该处于其生命进程中以下几个相位(phase)之一

  • Pending Pod创建过程中,但它尚未被调度完成

  • Running Pod中所有容器都已经被创建成功,正在运行

  • Completed Pod所有容器都已经成功终止,并不会被重启

  • Failed Pod中的所有容器中至少有一个容器退出是非0状态

  • Unknown 无法正常获取到Pod对象的状态信息,一般防火墙和网络的问题

Pod管理命令

kubectl get 资源类型 [资源名称] [选项/参数]

常用参数

  • 参数 [-o name] 只显示名字

  • 参数 [-o wide] 显示更加的详细信息

  • 参数 [-o yaml/json] 以yaml/json格式显示资源对象

kubectl exec [选项/参数] Pod名称 -- 操作命令

常用参数 -it 分配交互式终端

1
2
3
4
5
6
# 在容器内执行命令,--是一个分隔符,防止命令参数被误解析
[root@master ~]# kubectl exec -it myweb -- ls
index.html info.php

[root@master ~]# kubectl exec -it myweb -- bash
[root@myweb html]# ifconfig eth0

kubectl cp [选项/参数] 原文件 目标文件

pod的路径格式为: [Pod名称:绝对路径]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 与容器进行文件或目录传输
[root@master ~]# kubectl cp myweb:/etc/yum.repos.d /root/aaa
tar: Removing leading `/' from member names
[root@master ~]# tree /root/aaa
/root/aaa
├── local.repo
├── Rocky-AppStream.repo
├── Rocky-BaseOS.repo
└── Rocky-Extras.repo

0 directories, 4 files
[root@master ~]# kubectl -n work cp /etc/passwd myhttp:/root/mima
[root@master ~]# kubectl -n work exec -it myhttp -- ls /root/
mima

kubectl delete [选项/参数] 资源类型 资源名称

==集群中所有资源都可用delete命令删除==

1
2
3
4
5
6
7
8
9
10
11
# 删除资源对象
[root@master ~]# kubectl delete pods myweb
pod "myweb" deleted

# 删除 work 名称空间下所有 Pod 对象
[root@master ~]# kubectl -n work delete pods --all
pod "myhttp" deleted

# 删除名称空间
[root@master ~]# kubectl delete namespaces work
namespace "work" deleted

2.5.2 排错三剑客

get子命令

kubectl get 资源类型 [资源名称] [选项/参数]

主要看状态是否正常,不正常使用describe、logs继续排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看 Pod 资源对象
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myweb 1/1 Running 0 10m
# 查看名称空间
[root@master ~]# kubectl get namespaces
NAME STATUS AGE
default Active 39h
kube-node-lease Active 39h
kube-public Active 39h
kube-system Active 39h
# 查看名称空间中的 Pod 信息
[root@master ~]# kubectl -n kube-system get pods
NAME READY STATUS RESTARTS AGE
etcd-master 1/1 Running 0 39h
kube-apiserver-master 1/1 Running 0 39h
kube-controller-manager-master 1/1 Running 0 39h
kube-scheduler-master 1/1 Running 0 39h

describe子命令

kubectl describe 资源类型 [资源名称] [选项/参数]

Events下是事务日志,常用于排错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看资源对象的配置信息
[root@master ~]# kubectl -n work describe pod myhttp
Name: myhttp
Namespace: work
Priority: 0
Service Account: default
Node: node02/192.168.10.12
... ...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 7s default-scheduler Successfully assigned work/myhttp to node02
Normal Pulling 6s kubelet Pulling image "myos:httpd"
Normal Pulled 2s kubelet Successfully pulled image "myos:httpd" in 4.495s (4.495s including waiting)
Normal Created 2s kubelet Created container myhttp
Normal Started 2s kubelet Started container myhttp

log子命令

kubectl logs <pod名称> [选项/参数]

没有输出就是没错

1
2
3
# 查看容器日志
[root@master ~]# kubectl -n work logs myhttp
[root@master ~]#

2.5.3 资源清单文件管理

常用子命令

子命令 说明 备注
create 创建文件中定义的资源 支持指令式和资源清单文件配置
apply 创建(更新)文件中定义的资源 只支持资源清单文件(声明式)
delete 删除文件中定义的资源 支持指令式和资源清单文件配置
replace 更改/替换资源对象 强制重建 –force

最简单的资源清单文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@master metrics]# cat base.yaml
--- #yaml文件起始标志
kind: Pod #当前创建资源的类型
apiVersion: v1 #当前资源对应的版本
metadata: #属性信息,元数据
name: myweb #属性信息,资源的名称
spec: #资源的特性描述(规约)
containers: #容器资源特征描述
- name: nginx01 #容器的名称
image: myos:nginx #启动容器使用的镜像
status: {} #资源状态,运行后自动生成

#json格式
[root@master ~]# cat a.json
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {"name":"myweb"},
"spec": {"containers":[{"name":"nginx","image":"myos:nginx"}]},
"status": {}
}

命令管理示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 创建资源对象
[root@master ~]# kubectl create -f myweb.yaml
# 使用资源清单文件删除
[root@master ~]# kubectl delete -f myweb.yaml
# 创建资源对象
[root@master ~]# kubectl apply -f myweb.yaml
pod/myweb created
# 更新资源对象
[root@master ~]# kubectl apply -f myweb.yaml
pod/myweb configured
# 强制重建资源对象
[root@master ~]# kubectl replace --force -f myweb.yaml
pod "myweb" deleted
pod/myweb created

# 拓展提高
# 与 kubectl apply -f myweb.yaml 功能相同
[root@master metrics]# for i in web{1..5}
> do
> sed -r "s/myweb/$i/" base.yaml
> done | kubectl apply -f -
pod/web1 created
pod/web2 created
pod/web3 created
pod/web4 created
pod/web5 created
[root@master metrics]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web1 1/1 Running 0 21s
web2 1/1 Running 0 21s
web3 1/1 Running 0 21s
web4 1/1 Running 0 20s
web5 1/1 Running 0 20s

2.5.4 模板与帮助信息

生成模板

资源对象模板使用create生成,生成模板命令:[--dry-run=client -o yaml|json]

示例:

1
2
3
4
5
6
7
8
9
# 获取资源对象模板
[root@master ~]# kubectl create namespace work --dry-run=client -o yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: work
spec: {}
status: {}

获取帮助信息

kubectl explain 资源对象.层级关系

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查询帮助信息
[root@master ~]# kubectl explain Pod.metadata
KIND: Pod
VERSION: v1

FIELD: metadata <ObjectMeta> ... ...

namespace <string>
Namespace defines the space within which each name must be unique. An empty
namespace is equivalent to the "default" namespace, but "default" is the
canonical representation. Not all objects are required to be scoped to a
namespace - the value of this field for those objects will be empty.

[root@master ~]# kubectl explain Pod.metadata.namespace

2.5.6 多容器Pod

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@master ~]# cat mynginx.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: mynginx
namespace: default
spec:
containers: #容器资源是个数组
- name: nginx
image: myos:nginx
- name: php #容器的名称唯一,不能重复
image: myos:php-fpm #镜像

管理多容器Pod

受多容器配置影响,以下命令需要使用 <-c 容器名>来指定容器
受影响命令:[logs exec cp]

示例:

1
2
3
4
5
6
7
8
# 查看日志
[root@master ~]# kubectl logs mynginx -c nginx

# 执行命令
[root@master ~]# kubectl exec -it mynginx -c nginx -- ls /root

# 拷贝文件
[root@master ~]# kubectl cp mynginx:/etc/php-fpm.conf /root/php.conf -c php

2.5.7 Pod自定义任务

自定义命令

创建 Pod 时,可以为其设置启动时要执行的自定义命令,==如果配置了自定义命令,那么镜像中自带的默认启动命令将不再执行。==

自定义命令设置在command字段下,如果命令有参数,需要填写在args 字段下。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# cat mycmd.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: mycmd
spec:
containers:
- name: http
image: myos:httpd
command: ["sh"] #调用sh命令
args: #设置命令参数
- -c #读取脚本命令
- | #多行字符串格式
id=${RANDOM} #脚本指令,注意缩进
for i in {1..8}
do
echo "${id}: Hello World"
sleep 6
done

restartPolicy策略

  1. Pod级别配置
    restartPolicy是Pod级别的设置,会作用于Pod内的所有容器,无法单独为某个容器指定不同策略。
  2. 与控制器协同
    • Deployment/StatefulSet:通常使用Always,确保应用持续运行。
    • Job:通常使用OnFailure,任务失败时自动重试。
    • CronJob:根据需求选择OnFailureNever
  3. 默认值
    未显式指定时,默认值为Always
  4. 该策略有以下三个选项
  • 总是重启 [Always]
  • 失败不重启 [Never]
  • 失败就重启 [OnFailure]

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@master ~]# cat mycmd.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: mycmd
spec:
terminationGracePeriodSeconds: 0
restartPolicy: OnFailure #失败就重启
containers:
- name: http
image: myos:httpd
command: ["sh"]
args:
- -c
- |
id=${RANDOM} #取随机数
for i in {1..8}
do
echo "${id}: Hello World"
sleep 6
done
exit $(($id%2)) #退出返回非0,表示失败

terminationGracePeriodSeconds 宽限期策略

1. 核心作用

当 Pod 需要被删除或终止时(如手动删除、滚动更新、节点驱逐等),terminationGracePeriodSeconds 定义了:

  • 宽限期(Grace Period):Kubernetes 在强制杀死容器前,等待容器正常停止的最长时间(单位为秒)。
  • 用途:确保容器有足够时间完成清理操作(如保存状态、关闭连接、释放资源等)。

2. 默认值

  • 默认 30 秒:若未显式设置,Kubernetes 会使用默认值 30
  • 特殊场景:某些控制器(如 Job)可能设置不同的默认值。

示例:

1
2
3
4
5
6
7
8
9
10
11
[root@master ~]# cat mycmd.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: mycmd
spec:
terminationGracePeriodSeconds: 0 # 设置宽限期
restartPolicy: OnFailure
containers:
......

activeDeadlineSeconds 策略

1. 核心作用

  • 硬性超时机制:当 Pod 运行时间超过 activeDeadlineSeconds 指定的秒数时,Kubernetes 会立即终止该 Pod 及其所有容器,无论任务是否完成。
  • 适用场景
    • 批处理任务(Job/CronJob)需要限制最大执行时间。
    • 防止因代码逻辑错误(如死循环)导致 Pod 长期占用资源。

为了防止Pod循环死锁,设置Pod运行的最长时间,默认永久,时间到期后会向 Pod 发送 signal,如果 Pod 无法结束就把他强制关闭,并且设置为 Error 状态

2. 与 terminationGracePeriodSeconds 的区别

策略 activeDeadlineSeconds terminationGracePeriodSeconds
作用 限制 Pod 总运行时间 控制 Pod 终止时的优雅退出时间
触发条件 Pod 运行时间 > 设定值 Pod 被删除或终止时
终止行为 立即强制终止(SIGKILL 先发送 SIGTERM,超时后发送 SIGKILL
典型用途 任务超时控制 优雅关闭应用(如保存状态)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@master ~]# vim mycmd.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: mycmd
spec:
terminationGracePeriodSeconds: 0
activeDeadlineSeconds: 60 # 可以执行的最大时长为60s
restartPolicy: OnFailure
containers:
......

#验证
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
mycmd 1/1 Running 0 7s
mycmd 0/1 Error 0 62s #60秒左右Pod状态为Error

2.6 Pod调度与标签

2.6.1 什么是调度分配?

在 k8s 中,调度是将 Pod 分配到合适的计算节点上,然后对应节点上的Kubelet 运行这些Pod

==kube-scheduler是默认调度器,是集群的核心组件==

2.6.2 调度流程

调度器给一个Pod做调度选择包含两个步骤:筛选和优选

  1. 筛选:

首先要筛选出满足pod所有的资源请求的节点,包含cpu,内存,存储,网络,端口号等,如果没有节点能满足 Pod 的需求,Pod 将一直停留在 Pending 状态,直到调度器能够找到合适的节点运行它

  1. 优选:

在优选阶段,调度器会根据打分规则,为每一个可调度节点进行打分。选出其中得分最高的节点来运行于Pod。如果存在多个得分最高的节点,调度器会从中随机选取一个

  1. 绑定

在确定了某个节点运行Pod之后,调度器将这个调度决定通知给 kube-apiserver,这个过程叫做绑定

2.6.3 Pod定向调度

基于节点名称的调度

在创建 Pod 的过程中,我们可以配置相关的调度规则,从而让 Pod 运行在制定的节点上,给pod添加nodeName标签,让 Pod 运行在制定的节点上

[!CAUTION]

注意:如果标签指定的节点无法运行Pod,它不会迁移到其他节点,将一直等待下去

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@master ~]# vim myhttp.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: myhttp
spec:
nodeName: node01 # 基于节点名称进行调度
containers:
- name: apache
image: myos:httpd

#查看是否运行在node01节点上
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
myhttp 1/1 Running 0 3s 10.244.1.6 node01

2.6.4 Pod标签调度

Pod标签管理

标签的什么

标签(Labels)是附加到Kubernetes对象上的键值对

标签的用途

  • k8s在创建、删除、修改资源对象的时候可以使用标签来确定要修改的资源对象。在 Pod 调度的任务中,使用标签可以更加灵活的完成调度任务。

  • 标签可以在创建时附加到对象,也可以在创建之后随时添加和修改。标签可以用于组织和选择对象的子集

管理标签语法格式

  • 查看标签:kubectl get 资源类型 [资源名称] --show-labels

  • 设置标签:kubectl label 资源类型 [资源名称] <key>=<value>

  • 删除标签:kubectl label 资源类型 [资源名称] <key>-

  • 使用标签选择:kubectl get 资源类型 -l <key>=<value>

基于标签调度

标签选择运算符

  • 与名称和 UID 不同,标签不支持唯一性。通常,我们希望许多对象携带相同的标签。

  • 通过标签选择运算符,客户端/用户可以识别一组对象。

  • 标签选择运算符可以由多个需求组成。在多个需求的情况下,必须满足所有要求,相当于逻辑与(&&)运算符。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#给node主机配置标签
[root@master ~]# kubectl label nodes node02 ssd=v1
node/node02 labeled
[root@master ~]# kubectl label nodes node03 ssd=v1
node/node03 labeled
#查看node主机的标签情况
[root@master ~]# kubectl get nodes --show-labels
......
#编写资源子清单文件
[root@master ~]# cat ssd.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: ssd
labels: #给Pod配置标签
hh: jj #定义标签,可以多个
spec:
nodeSelector: #在Pod.spec中声明标签选择运算符
ssd: v1 #选择的标签
containers:
- name: mynginx
image: myos:nginx

#验证结果
[root@master ~]# kubectl apply -f ssd.yaml
pod/ssd created
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
http 1/1 Running 0 21m 10.244.147.17 node02 <none> <none>
ssd 1/1 Running 0 15s 10.244.243.218 node03 <none> <none>

2.7 Pod生命周期与资源管理

2.7.1 Pod生命周期概述

什么是Pod生命周期?

  • Pod对象自从其创建开始至终止的时间范围称为生命周期

  • 在这段时间中Pod处在不同的状态并可以执行相关操作

生命周期有什么用途

  • 复杂服务运行时有启动顺序、依赖关系等,我们可以在生命周期中配置相关性,解决启动关系依赖等问题

  • 容器服务在启动前或运行过程中需要做的相关操作,例如:配置文件生成、数据预加载、依赖服务的检测、安装等

生命周期中能做什么

  • 必须操作:启动运行main(主)容器

  • 可选操作:设置容器的初始化方法:(InitContainer)

  • 可选操作:配置容器探针:生命探测(IivenessProbe)、就绪探测(readinessProbe)、启动探测(startupProbe)

  • 可选操作:添加事件处理函数:启动后回调(PostStart)、结束前回调(PreStop)

2.7.2 Init容器

  • init容器是一种特殊容器,在Pod内主容器启动之前执行,可以依据需求的不同定义多个

  • init容器可以使用其他镜像,也可以包括一些主容器镜像中不存在的实用工具和安装脚本

  • init容器的生命是有限的,不能无休止的运行下去,只有在初始化容器执行完成以后才会启动主容器,即只要初始化容器的退出状态代码非0,则不会启动主容器

init容器和普通容器非常像,除了以下三点

  • 顺序执行,每个容器都必须等待上一个容器成功完成执行,如果init容器失败,kubelet会不断地重启该init容器,直到该容器成功为止

  • 如果Pod对应的restartPolicy值为Never,并且Pod的init容器失败,则k8s会将整个Pod状态设置为失败,主容器将无法执行。

  • 它们必须全部运行到生命周期结束

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@master ~]# vim web1.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: web1
spec:
restartPolicy: Never # 任务失败不重启
initContainers: # 定义初始化任务
- name: task1 # 如果初始化任务失败,主容器不会启动
image: myos:latest # 初始化可以使用不同的镜像
command: ["sh"] # 任务,一般用脚本实现
args: # 任务
- -c # 任务
- | # 任务
ID=${RANDOM} # 任务
echo "${ID}" # 任务
sleep 3 # 任务
exit $((ID%2)) # 状态 0 成功,非0失败,如果失败会重新执行初始化
- name: task2
image: myos:latest
command: ["sh"]
args:
- -c
- |
ID=${RANDOM}
echo "${ID}"
sleep 3
exit $((ID%2))
containers:
- name: web
image: myos:httpd

# 重建Pod
[root@master ~]# kubectl replace --force -f web1.yaml
pod "web1" deleted
pod/web1 replaced

# 必须所有init容器都成功,否则Pod创建失败
# 初始化任务失败,因为设置的restartPolicy策略是从不重启,所以一旦一个init容器失败,后面的init容器和主容器都不会运行
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
web1 0/1 Init:0/2 0 1s
web1 0/1 Init:1/2 0 3s
web1 0/1 Init:Error 0 5s

2.7.3 容器探针

什么是容器探针(可选配置)

==是由kubelet对容器执行的定期诊断和检查==

探针类型

  • startupProbe(启动探测):探测目标应用是否已经启动正常

  • livenessProbe(生命探测):探测容器是否能够正常运行

  • readinessProbe(就绪探测):指示容器是否准备好为请求提供服务

容器探针的检查方式共有4种,分别为:

  • exec:在容器内执行指定命令进行检测

  • httpGet:使用HTTP协议诊断服务状态

  • tcpSocket:对指定的IP地址上的端口执行TCP检查

  • gRPC:使用gRPC健康检查协议进行检测,多用于检测用户自己开发的程序或返回状态

每次探测都会获得以下三种结果之一:

  • Success 成功,容器通过了诊断

  • Failure 失败,容器未通过诊断

  • Unknown 未知,诊断失败,不会采取任何行动

startupProbe启动探测类型

  • startupProbe启动探测用于确定容器是否已经启动成功

  • 探测失败:kubelet将杀死容器,依据重启策略执行

  • 如果配置了启动探针,其他所有探针都会被禁用,直到此探针执行成功为止

  • 如果启动探针执行成功,且主容器已经启动,启动探针不会重复执行

  • 如果未定义启动探针,默认为Success状态

使用startupProbe保护慢启动的服务:

基于tcpSocket 的端口检测,只检测端口状态,不需要返回数据,如果端口状态为 Open 则诊断被认为是成功的

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 用于检测容器启动过程中依赖的某个重要服务,启动成功后结束
[root@master ~]# vim web2.yaml
......
spec:
containers:
- name: web
image: myos:httpd
startupProbe: # 启动探针
initialDelaySeconds: 60 # 首次检查延时
periodSeconds: 10 # 检查间隔
failureThreshold: 6 # 可失败的次数
tcpSocket: # 使用 tcp 协议检测
port: 80 # 端口号

[root@master ~]# kubectl apply -f web2.yaml
pod/web2 created
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
web2 0/1 Running 0 7s
web2 0/1 Running 1 (1s ago) 31s
web2 0/1 Running 1 (10s ago) 40s
web2 1/1 Running 1 (11s ago) 70s #60s+10s后检测到容器的80端口,该容器就会成功创建

livenessProbe生命探测类型

  • livenessProbe生命探测用于确定容器是否处于运行状态

  • livenessProbe在Pod的全部生命周期中运行,如果发现资源无法获取,kubelet将杀死容器,依据重启策略执行

  • 如果没有设置restartPolicy默认使用镜像重建容器

  • livenssProbe检测失败重启容器的行为会重新激活startupProbe但不会重新执行InitContainer

  • 如果未定义生命探针,默认为Success状态

使用httpGet配置livenessProbe检测

在httpGet的检测中,kubelet目标IP上指定的端口和路径执行HTTP GET请求。如果响应的状态码大于等于200且小于400,则诊断被认为是成功的

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 判断某个核心资源是否可用,在 Pod 的全部生命周期中(重启)
[root@master ~]# vim web2.yaml
......
spec:
containers:
- name: web
image: myos:httpd
startupProbe:
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 6
tcpSocket:
port: 80
livenessProbe: # 定义存活探针
timeoutSeconds: 3 # 服务影响超时
httpGet: # 使用 HTTP 协议检测
path: /info.php # 请求的 URL 路径
port: 80 # 服务端口号

[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
web2 0/1 Running 0 2s
web2 1/1 Running 0 71s
web2 0/1 Running 1 (1s ago) 101s #如果中途info.php文件被删除,它会检测失败重建容器
web2 1/1 Running 1 (11s ago) 171s #很明显执行了启动指针,然后重建容器成功

readinessProbe就绪探测型

  • readinessProbe在Pod的生命周期中一直存在,用来探测容器是否准备好提供服务,就绪探测用于确定容器是否已经准备好接收流量。

  • 配置了就绪探测探针,初始的值默认状态为Failure

  • 探测失败:端点控制器将拒绝Pod所提供的服务(这里是等待,并不重建容器)

  • 如果未定义就绪探针,默认为Success状态

使用exec配置readinessProbe检测:

exec选项用来执行自定义命令的检测,通过返回的状态码判断是否成功,如果$?==0则诊断被认为是成功的,其他判断为失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 附加条件检测,在 Pod 的全部生命周期中(禁止调用,不重启)
[root@master ~]# vim web2.yaml
......
readinessProbe: # 定义就绪探针
failureThreshold: 3 # 失败确认次数
periodSeconds: 10 # 检测间隔
exec: # 执行命令进行检测
command: # 检测命令
- sh
- -c
- |
read ver </var/www/html/version.txt
if (( ${ver:-0} > 2 ));then
res=0
fi
exit ${res:-1} # 版本大于 2 成功,否则失败

[root@master ~]# kubectl replace --force -f web2.yaml
pod/web2 created
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
web2 0/1 Running 0 5s
web2 1/1 Running 0 20s
web2 0/1 Running 0 40s # 检测到版本不大于2,就一直等着,不会重启

2.7.4 事件处理函数

postStart和prestop

  • postStart是一个生命周期钩子函数,它与InitContainer不同,postStart是在主容器创建之后被调用。这可以用于执行主容器的初始化工作,通常用于确保容器对外提供服务之前已经完全准备好

  • prestop也是一个生命周期钩子函数,它在容器被停止之前被调用。这可以用于执行清理工作,通常用于确保资源被正确释放,避免数据丢失或损坏

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 在主容器启动之后或结束之前执行的附加操作
[root@master ~]# vim web3.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: web3
spec:
containers:
- name: web
image: myos:httpd
lifecycle: # 定义启动后事件处理函数
postStart:
exec:
command:
- sh
- -c
- |
echo "Hello World" |tee -a /tmp/web.log
sleep 10
preStop: # 定义关闭前事件处理函数
exec:
command:
- sh
- -c
- |
echo "Bay~" |tee -a /tmp/web.log
sleep 10

[root@master ~]# kubectl apply -f web3.yaml
pod/web3 created
[root@master ~]# kubectl exec -it web3 -- bash
[root@web3 html]# cat /tmp/web.log
Hello World

# 在其他终端执行
[root@master ~]# kubectl delete pods web3
pod "web3" deleted

[root@web3 html]# cat /tmp/web.log
Hello World
Bay~

2.7.5 Pod资源管理

资源配额

当多个应用共享固定节点数目的集群时,人们担心某些应用无法获得足够的资源,从而影响到其正常运行,我们需要设定一些规则,用来保证应用能获得其运行所需资源

CPU资源类型

  • CPU资源的约束和请求以毫核(m)为单位。在 k8s中1m是最小的调度单元,CPU的一个核心可以看作1000m

  • 如果你有2颗CPU,且每CPU为4核心,那么你的CPU资源总量就是8000m

示例:

1
2
3
4
5
6
7
8
lscpu 
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 2 #2个cpu
On-line CPU(s) list: 0,1
Thread(s) per core: 2 #每个核心2个线程数
Core(s) per socket: 1 #每个插槽1个核心

内存资源类型

memory的约束和请求以字节为单位

你可以使用以下单位来表示内存:E、P、T、G、M、k

你也可以使用对应的2的幂数:Ei、Pi、Ti、Gi、Mi、K!

例如,以下表达式所代表的是相同的值:

1K == 1000

1Ki == 1024

资源配额示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[root@master ~]# vim app.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: app
spec:
containers:
- name: web
image: myos:httpd
resources: # 配置资源策略
requests: # 配额策略
cpu: 1500m # 计算资源配额
memory: 600Mi # 内存资源配额

[root@master ~]# kubectl apply -f app.yaml
pod/app created

[root@master ~]# kubectl describe pods app
......
Ready: True
Restart Count: 0
Requests:
cpu: 1500m
memory: 600Mi

# 当查看pod被分配到的节点的信息,会看到配额占用了1500m,但实际cpu并没有这么高,只是被该pod占用了
[root@master ~]# kubectl describe node node03
......
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 1850m (92%) 0 (0%)
memory 670Mi (39%) 170Mi (10%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)

# 该node节点只有2000m,如果再到此节点的pod的配额超过了150m,那么pod会起不来
......
spec:
nodeName: node03 # 指定pod调度node03上面,做实验
containers:
- name: test-resource
image: myos:httpd
resources:
requests:
cpu: 200m
memory: 100Mi
[root@master ~]# kubectl apply -f test-resources.yaml
pod/app created
# 会发现调度到node03的pod创建不出来
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
app 1/1 Running 0 11m
test-resource 0/1 OutOfcpu 0 94s

资源限额

为什么要使用资源限额?

  • 限额策略是为了防止某些应用对节点资源过度使用,而配置的限制性策略,限额与配额相反,它不检查节点资源的剩余情况,只限制应用对资源的最大使用量

  • 资源限额使用 limits进行配置

  • 资源限额需要与配额共同配置

  • 资源限额必须大于等于资源配额,否则报错

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@master ~]# vim app.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: app
spec:
containers:
- name: web
image: myos:httpd
resources: # 配置资源策略
limits: # 限额策略
cpu: 600m # 计算资源限额
memory: 800Mi # 内存资源限额

[root@master ~]# kubectl apply -f app.yaml
pod/app created

# 资源清单文件没有设置资源配额,但是查看资源策略时却有,这表明限额和配额同时存在的,资源清单文件中不指定配额的话,默认与限额一致
[root@master ~]# kubectl describe pods app
......
Ready: True
Restart Count: 0
Limits:
cpu: 800m
memory: 600Mi
Requests:
cpu: 800m
memory: 600Mi

节点压力驱逐

kubelet监控集群节点的内存、磁盘空间和文件系统等资源,当这些资源中的其中一个或者多个出现消耗瓶颈的时候,kubelet会主动终止一个或多个Pod,回收资源以防止饥饿的过程

  • 在节点压力驱逐过程期间,kubelet将所选Pod的状态设置为Failed并终止Pod的运行

  • 一般来说,k8s在出发节点压力驱逐时会考虑QoS类别,进行如下处理

    • Guaranteed(保障型):这类Pod有稳定(配限额==相等==或==单独==配置限额)的资源限额/配额,通常情况下最不易被驱逐
    • Burstable(爆发型):配额值==小于==限额值或==单独==配置配额时,它们在节点资源不足时可能会被驱逐
    • BestEffort(尽量型):==没有==设置配限额的Pod,被认为是首选被驱逐的对象

Quota资源管理

ResourceQuota 是 Kubernetes 中用于 限制命名空间(Namespace)内资源使用量 的机制,主要解决多租户或团队共享集群时的资源公平性和隔离性问题。其核心作用包括:

  1. 防止资源饥饿
    • 避免单个命名空间过度占用集群资源(如 CPU、内存、Pod 数量),影响其他业务。
  2. 成本控制
    • 为不同团队或项目分配明确的资源上限,便于核算资源消耗。
  3. 优先级管理
    • 通过配额区分高/低优先级业务(例如限制 BestEffort Pod 数量,保障 Guaranteed Pod 资源)。
  4. 规范化资源使用
    • 强制要求开发团队在部署时声明资源请求(Requests/Limits),避免资源浪费。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
[root@master 5.17]# cat resourcequota.yml 
---
kind: Namespace
apiVersion: v1
metadata:
name: test
spec: {}

---
kind: ResourceQuota # 全局资源限额对象
apiVersion: v1
metadata:
name: quota-1 # 规则名称
namespace: test # 规则作用的名称空间
spec:
hard: # 创建强制规则
pods: 3 # 限制创建资源对象总量
scopes: # 配置服务质量的类型
- BestEffort # 可选(NotBestEffort)适用于有明确资源设置的Pod,包括Guaranteed和Burstable类型的Pod。

[root@master 5.17]# kubectl apply -f resourcequota.yml
namespace/test created
resourcequota/quota-1 created
# 查看test名称空间的属性
[root@master 5.17]# kubectl describe namespaces test
Name: test
Labels: kubernetes.io/metadata.name=test
Annotations: <none>
Status: Active

Resource Quotas
Name: quota-1
Scopes: BestEffort
* Matches all pods that do not have resource requirements set. These pods have a best effort quality of service.
Resource Used Hard
-------- --- ---
pods 0 3

No LimitRange resource.

=======================创建pod,验证效果========================
[root@master 5.17]# cat app.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: app1
namespace: test
spec:
containers:
- name: app1
image: myos:httpd
[root@master 5.17]# sed 's/app1/app11/' app.yaml | kubectl apply -f -
pod/app11 created
[root@master 5.17]# sed 's/app1/app12/' app.yaml | kubectl apply -f -
pod/app12 created
[root@master 5.17]# sed 's/app1/app13/' app.yaml | kubectl apply -f -
Error from server (Forbidden): error when creating "STDIN": pods "app13" is forbidden: exceeded quota: quota-1, requested: pods=1, used: pods=3, limited: pods=3
[root@master 5.17]# kubectl describe namespaces test
Name: test
Labels: kubernetes.io/metadata.name=test
Annotations: <none>
Status: Active

Resource Quotas
Name: quota-1
Scopes: BestEffort
* Matches all pods that do not have resource requirements set. These pods have a best effort quality of service.
Resource Used Hard
-------- --- ---
pods 3 3

No LimitRange resource.

[root@master 5.17]# cat Burstable.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: burstable
spec:
containers:
- name: burstable
image: myos:httpd
resources:
limits:
cpu: 80m
memory: 80Mi
# 在test名称空间创建一个Guaranteed类型的pod,就可以创建
[root@master 5.17]# cat Burstable.yaml | kubectl -n test apply -f -

2.8 Pod控制器

2.8.1 Pod的定义

Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类:

  • 自主式pod:kubernetes直接创建出来的Pod,这种pod删除后就没有了,也不会重建

  • 控制器创建的pod:kubernetes通过控制器创建的pod,这种pod删除了之后还会自动重建

2.8.2 什么是Pod控制器

[!IMPORTANT]

==Pod控制器是管理pod的中间层,使用Pod控制器之后,只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它会创建出满足条件的Pod并确保每一个Pod资源处于用户期望的目标状态。如果Pod资源在运行中出现故障,它会基于指定策略重新编排Pod。==

在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的Pod控制器有下面这些:

  • ReplicationController(RC):比较原始的pod控制器,已经被废弃,由ReplicaSet替代

  • ReplicaSet(RS):保证副本数量一直维持在期望值,并支持pod数量扩缩容,镜像版本升级

  • Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本

  • Horizontal Pod Autoscaler(HPA):可以根据集群负载自动水平调整Pod的数量,实现削峰填谷

  • DaemonSet:在集群中的指定Node上运行且仅运行一个副本,一般用于守护进程类的任务(比如部署node_exporter)

  • Job:它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务

  • Cronjob:它创建的Pod负责周期性任务控制,不需要持续后台运行

  • StatefulSet:管理有状态应用

2.8.3 ReplicaSet(RS)

==ReplicaSet的主要作用是保证一定数量的pod正常运行,它会持续监听这些Pod的运行状态,一旦Pod发生故障,就会重启或重建。同时它还支持对pod数量的扩缩容和镜像版本的升降级。==

RS在标签选择器上,除了可以定义键值对的选择形式,还支持selector.matchExpressions字段,可以提供多种选择。目前支持的操作包括:

  • In:label的值在某个列表中

  • Notln:label的值不在某个列表中

  • Exists:某个label存在

  • DoesNotExist:某个label不存在

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@master test]# vim rs01.yaml 
---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs01
spec:
selector: # 选择器,它的作用是建立pod控制器和pod之间的关联关系,采用的Label Selector机制
# 在pod模板上定义label,在控制器上定义选择器,就可以表明当前控制器能管理哪些pod了
# matchLabels: #开启标签匹配
# app: nginx #和哪个key:value匹配
matchExpressions: #开启匹配运算符
- key: version
operator: Exists #意思是key是version的就匹配
replicas: 3 #指定副本数量,其实就是当前rs创建出来的pod的数量,默认为1
template: #模板,就是当前控制器创建pod所使用的模板
metadata:
labels:
app: nginx
version: v1
spec:
containers:
- name: nginx
image: myos:nginx
ports:
- containerPort: 80
[root@master test]# kubectl apply -f rs01.yaml
replicaset.apps/rs created

验证ReplicaSet(RS):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 查看rs
# DESIRED:期望副本数量
# CURRENT:当前副本数量
# READY:已经准备好提供服务的副本数量
[root@master test]# kubectl get rs
NAME DESIRED CURRENT READY AGE
rs01 3 3 3 7s

# 查看当前控制器创建出来的pod
# 这里发现控制器创建出来的pod的名称是在控制器名称后面拼接了-xxxxx随机码
[root@master test]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
rs01-9t866 1/1 Running 0 24s app=nginx,version=v1
rs01-pr6vg 1/1 Running 0 24s app=nginx,version=v1
rs01-xpthg 1/1 Running 0 24s app=nginx,version=v1

# 使用scale命令实现扩缩容, 后面--replicas=n直接指定目标数量即可
[root@master test]# kubectl scale rs rs01 --replicas=2
replicaset.apps/rs scaled
[root@master test]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
rs01-pr6vg 1/1 Running 0 2m20s app=nginx,version=v1
rs01-xpthg 1/1 Running 0 2m20s app=nginx,version=v1

# 镜像升级,查看镜像
[root@master test]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
rs01 3 3 3 5m17s nginx myos:nginx app in (lili,spring-k8s)
# kubectl set image rs rs名称 容器=镜像版本 -n namespace
[root@master test]# kubectl set image rs rs01 nginx=myos:latest
replicaset.apps/rs image updated
# 再次查看,发现镜像版本已经变更了
[root@master test]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
rs 3 3 3 7m10s nginx myos:latest app in (lili,spring-k8s)

删除ReplicaSet(RS)

1
2
3
4
5
6
# 使用kubectl delete命令会删除此RS以及它管理的Pod
# 在kubernetes删除RS前,会将RS的replicasclear调整为0,等待所有的Pod被删除后,在执行RS对象的删除
[root@master test]# kubectl delete rs rs01
replicaset.apps "rs01" deleted
[root@master test]# kubectl get pods
No resources found in default namespace.

2.8.4 Deployment(Deploy)

为了更好的解决服务编排的问题,kubernetes在V1.2版本开始,引入了Deployment控制器。值得一提的是,这种控制器并不直接管理pod,而是通过管理ReplicaSet来简介管理Pod,即:==Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。==

Deployment主要功能有下面几个:

  • 支持ReplicaSet的所有功能

  • 支持发布的停止、继续

  • 支持滚动升级和回滚版本

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@master test]# cat pc-deployment.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: pc-deployment
namespace: default
spec:
replicas: 3 # 副本数量
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: test
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: test
spec:
containers:
- name: test
image: myos:nginx
# 创建deployment
[root@master test]# kubectl apply -f pc-deployment.yaml
deployment.apps/pc-deployment

# 查看deployment控制器
[root@master test]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 3/3 3 3 14m

# 查看replicaset控制器
[root@master test]# kubectl get rs
NAME DESIRED CURRENT READY AGE
pc-deployment-f864b4d67 3

# 查看pod是否创建
[root@master test]# kubectl get pods
NAME READY STATUS RESTARTS AGE
pc-deployment-f864b4d67-6prdd 1/1 Running 0 13m
pc-deployment-f864b4d67-dt8p8 1/1 Running 0 13m
pc-deployment-f864b4d67-hklm2 1/1 Running 0 13m

扩缩容

deployment控制器的扩缩容和replicaset的方法一样,都可以进行edit在线编辑和使用scale命令进行扩缩容。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 可以通过edit命令进入编辑器里改replicas的值
[root@master ~]# kubectl edit deployment pc-deployment
deployment.apps/pc-deployment edited

# 使用scale命令扩缩容
[root@master ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 5/5 5 5 4h21m
[root@master ~]# kubectl scale deploy pc-deployment --replicas=3
deployment.apps/pc-deployment scaled
[root@master ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 3/3 3 3 4h23m

镜像更新

deployment支持两种更新策略:重建更新 和 滚动更新 ,可以通过 strategy 指定策略类型,支持两个属性。

deployment默认就是滚动更新

1
2
3
4
5
6
7
8
strategy  #指定新的Pod替换旧的Pod的策略, 支持两个属性:
type #指定策略类型,支持两种策略
Recreate #在创建出新的Pod之前会先杀掉所有已存在的Pod
RollingUpdate #滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本Pod
#------//如果配置了滚动更新,就需要配置下面的-----
rollingUpdate #当type为RollingUpdate时生效,用于为RollingUpdate设置参数,支持两个属性:
maxSurge # 用来指定在升级过程中可以超过期望的Pod的最大数量,默认为25%。
maxUnavailable #用来指定在升级过程中不可用Pod的最大数量,默认为25%。

重建策略示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
[root@master test]# cat pc-deployment.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: pc-deployment
namespace: default
spec:
strategy: # 设置更新策略
type: Recreate # 重建策略
replicas: 5
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: test
image: myos:httpd
# 更新资源清单文件
[root@master test]# kubectl apply -f pc-deployment.yaml
deployment.apps/pc-deployment configured

# 查看现在的镜像是myos:httpd
[root@master test]# kubectl get deploy -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 5/5 5 5 4h46m test myos:httpd app=test

# 更新镜像为myos:nginx
[root@master test]# kubectl set image deployment pc-deployment test=myos:nginx
deployment.apps/pc-deployment image updated

# 更新镜像前进行实时监控pod,会发现直接把老的pod干掉了,重新创建的pod
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
pc-deployment-6dfdccd467-2vcqn 1/1 Running 0 58s
pc-deployment-6dfdccd467-52vwg 1/1 Running 0 58s
pc-deployment-6dfdccd467-6t5zh 1/1 Running 0 58s
pc-deployment-6dfdccd467-dqqbb 1/1 Running 0 58s
pc-deployment-6dfdccd467-ff4ww 1/1 Running 0 58s
pc-deployment-6dfdccd467-dqqbb 1/1 Terminating 0 83s
pc-deployment-6dfdccd467-2vcqn 1/1 Terminating 0 83s
pc-deployment-6dfdccd467-52vwg 1/1 Terminating 0 83s
pc-deployment-f864b4d67-8r5v8 0/1 Pending 0 0s
pc-deployment-f864b4d67-zlf2d 0/1 Pending 0 0s
pc-deployment-f864b4d67-7b7jf 0/1 Pending 0 0s
pc-deployment-f864b4d67-kkg44 0/1 Pending 0 0s
pc-deployment-f864b4d67-zlf2d 0/1 Pending 0 0s
pc-deployment-f864b4d67-p7xw9 0/1 ContainerCreating 0 0s
pc-deployment-f864b4d67-8r5v8 0/1 ContainerCreating 0 0s
pc-deployment-f864b4d67-7b7jf 0/1 ContainerCreating 0 0s
pc-deployment-f864b4d67-kkg44 0/1 ContainerCreating 0 0s
pc-deployment-f864b4d67-zlf2d 0/1 ContainerCreating 0 0s
pc-deployment-f864b4d67-p7xw9 0/1 ContainerCreating 0 1s
pc-deployment-f864b4d67-kkg44 1/1 Running 0 3s
pc-deployment-f864b4d67-p7xw9 1/1 Running 0 3s
pc-deployment-f864b4d67-7b7jf 1/1 Running 0 3s
pc-deployment-f864b4d67-zlf2d 1/1 Running 0 4s
pc-deployment-f864b4d67-8r5v8 1/1 Running 0 5s

# 查看镜像已经更新为myos:nginx
[root@master test]# kubectl get deploy -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 5/5 5 5 4h46m test myos:nginx app=test

滚动更新

添加滚动更新策略(这个为默认策略)

滚动策略示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@master test]# vim pc-deployment.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: pc-deployment
namespace: default
spec:
strategy: # 更新策略
type: RollingUpdate # 滚动更新策略
rollingUpdate: # 滚动更新
maxSurge: 25% # 最大额外可以存在的副本数,可以为百分比,也可以为整数
maxUnavailable: 25% # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
replicas: 5
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: test
image: myos:nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 更新资源清单文件
[root@master test]# kubectl apply -f pc-deployment.yaml
deployment.apps/pc-deployment configured

# 更新镜像为myos:httpd
[root@master test]# kubectl set image deployment pc-deployment test=myos:httpd
deployment.apps/pc-deployment image updated

# 更新镜像前进行实时监控pod,结果是先创建一个新的pod再干掉一个老的
[root@master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
pc-deployment-f864b4d67-5r2q2 1/1 Running 0 99s
pc-deployment-f864b4d67-6mzq6 1/1 Running 0 99s
pc-deployment-f864b4d67-jw24s 1/1 Running 0 102s
pc-deployment-f864b4d67-kv8sq 1/1 Running 0 102s
pc-deployment-f864b4d67-zcx54 1/1 Running 0 102s
pc-deployment-6dfdccd467-vvqrr 0/1 Pending 0 0s
pc-deployment-6dfdccd467-hjs6c 0/1 Pending 0 0s
pc-deployment-f864b4d67-kv8sq 1/1 Terminating 0 104s
pc-deployment-6dfdccd467-hjs6c 0/1 Pending 0 0s
pc-deployment-6dfdccd467-hjs6c 0/1 ContainerCreating 0 1s
pc-deployment-f864b4d67-kv8sq 1/1 Terminating 0 105s
pc-deployment-6dfdccd467-vvqrr 0/1 ContainerCreating 0 1s
pc-deployment-f864b4d67-5r2q2 1/1 Terminating 0 105s
pc-deployment-f864b4d67-6mzq6 1/1 Terminating 0 105s
pc-deployment-f864b4d67-5r2q2 0/1 Terminating 0 105s
pc-deployment-f864b4d67-6mzq6 0/1 Terminating 0 105s
pc-deployment-6dfdccd467-h7znq 1/1 Running 0 2s
pc-deployment-f864b4d67-jw24s 1/1 Terminating 0 108s
pc-deployment-f864b4d67-5r2q2 0/1 Terminating 0 105s
pc-deployment-f864b4d67-jw24s 1/1 Terminating 0 109s
pc-deployment-6dfdccd467-c2nrp 1/1 Running 0 2s
pc-deployment-f864b4d67-6

# 查看镜像已经更新为myos:httpd
[root@master test]# kubectl get deployment -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 5/5 5 5 5h8m test myos:httpd app=test

版本管理

当我们进行deployment版本升级时,因为它是操作replicaset控制器的,可以查看一下rs的变化。

1
2
3
4
5
6
7
root@master test]# kubectl get rs
NAME DESIRED CURRENT READY AGE
pc-deployment-575cdc66c8 0 0 0 47m
pc-deployment-6dfdccd467 5 5 5 41m
pc-deployment-f864b4d67 0 0 0 5h27m
# 为什么旧的RS依然存在呢?
# 这就是deployment的特殊的功能,它管控这replicaset来进行版本回退的操作。

deployment支持版本升级过程中的暂停、继续功能以及版本回退等诸多功能,下面具体来看:

kubectl rollout 命令: 版本升级相关功能,支持下面的选项

  • status : 显示当前升级状态

  • history: 显示升级历史记录

  • pause: 暂停版本升级过程

  • resume: 继续已经暂停的版本升级过程

  • restart : 重启版本升级过程

  • undo: 回滚到上一级版本(可以使用 –to-revision 回滚到指定版本)

示例:

1
2
3
4
5
6
7
8
9
10
# 查看升级回退的状态
[root@master test]# kubectl rollout status deployment pc-deployment
deployment "pc-deployment" successfully rolled out
# 查看历史版本
[root@master test]# kubectl rollout history deployment pc-deployment
deployment.apps/pc-deployment
REVISION CHANGE-CAUSE
2 <none>
6 <none>
7 <none>

给新版本添加注释信息补充:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 查看历史版本
[root@master ~]# kubectl rollout history deployment mydeploy
deployment.apps/mydeploy
REVISION CHANGE-CAUSE
1 <none>

# 添加注释信息
[root@master ~]# kubectl annotate deployments mydeploy kubernetes.io/change-cause="httpd.v1"
deployment.apps/mydeploy annotated

[root@master ~]# kubectl rollout history deployment mydeploy
deployment.apps/mydeploy
REVISION CHANGE-CAUSE
1 httpd.v1

# 更新资源清单文件
[root@master ~]# vim mydeploy.yaml
# 在创建容器的镜像下面添加
imagePullPolicy: Always

[root@master ~]# kubectl apply -f mydeploy.yaml
deployment.apps/mydeploy patched

# 更新版本信息
[root@master ~]# kubectl annotate deployments mydeploy kubernetes.io/change-cause="add imagePullPolicy"
deployment.apps/mydeploy annotated

[root@master ~]# kubectl rollout history deployment mydeploy
deployment.apps/mydeploy
REVISION CHANGE-CAUSE
1 httpd.v1
2 add imagePullPolicy

版本回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看当前版本是myos:nginx02
[root@master test]# kubectl get deployment pc-deployment -owide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 5/5 5 5 5h35m test myos:nginx02 app=test

# 返回到上一个版本
[root@master test]# kubectl rollout undo deployment pc-deployment
deployment.apps/pc-deployment rolled back

# 再次查看,版本变成myos:nginx01
[root@master test]# kubectl get deployment pc-deployment -owide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 5/5 5 5 5h35m test myos:nginx01 app=test

# 查看rs信息,发现rs中的pod的位置也变成到老的版本中
[root@master test]# kubectl get rs
NAME DESIRED CURRENT READY AGE
pc-deployment-575cdc66c8 0 0 0 56m
pc-deployment-6dfdccd467 0 0 0 50m
pc-deployment-f864b4d67 5 5 5 5h36m

回滚到指定的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看历史版本
[root@master test]# kubectl rollout history deployment pc-deployment
deployment.apps/pc-deployment
REVISION CHANGE-CAUSE
2 <none> #nginx03版
7 <none> #nginx02版
8 <none> #nginx01版,当前所在的版本

# 回滚到指定的版本
[root@master test]# kubectl rollout undo deployment pc-deployment --to-revision=7
deployment.apps/pc-deployment rolled back

# 查看版本为myos:nginx02
[root@master test]# kubectl get deployment pc-deployment -owide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 5/5 5 5 5h56m test myos:nginx02 app=test

# rs中的pod也换了位置
[root@master test]# kubectl get rs
NAME DESIRED CURRENT READY AGE
pc-deployment-575cdc66c8 0 0 0 76m
pc-deployment-6dfdccd467 5 5 5 71m
pc-deployment-f864b4d67 0 0 0 5h56m

金丝雀部署

Deployment控制器支持控制更新过程中的控制,如“暂停(pause)”或“继续(resume)”更新操作。

==比如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。==

版本升级,然后暂停示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看当前的rs信息和镜像版本
[root@master test]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-6dfdccd467 0 0 0 9m27s test myos:nginx01 app=test,pod-template-hash=6dfdccd467
pc-deployment-f864b4d67 3 3 3 11m test myos:nginx02 app=test,pod-template-hash=f864b4d67

# 将nginx02升级到nginx03,升级一个,然后暂停升级
[root@master test]# kubectl set image deployment pc-deployment test=myos:nginx03 && kubectl rollout pause deployment pc-deployment
deployment.apps/pc-deployment image updated
deployment.apps/pc-deployment paused

# 查看更新状态(新开一个终端,会占用前台)
# 监控更新的过程,可以看到已经新增了一个资源,但是并未按照预期的状态去删除一个旧的资源,就是因为使用了pause暂停命令
[root@master test]# kubectl rollout status deployment pc-deployment
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...

# 发现nginx03版本更新了一个,老版本还在
[root@master test]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-6dfdccd467 0 0 0 11m test myos:nginx01 app=test,pod-template-hash=6dfdccd467
pc-deployment-79bf44dbdc 1 1 1 18s test myos:nginx03 app=test,pod-template-hash=79bf44dbdc
pc-deployment-f864b4d67 3 3 3 13m test myos:nginx02 app=test,pod-template-hash=f864b4d67

新版本确认没问题,进行全部滚动更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 确认新版本没问题,继续滚动更新
[root@master test]# kubectl rollout resume deployment pc-deployment
deployment.apps/pc-deployment resumed

# 查看版本全部更新成功
[root@master test]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-6dfdccd467 0 0 0 13m test myos:httpd app=test,pod-template-hash=6dfdccd467
pc-deployment-79bf44dbdc 3 3 3 2m test myos:php-fpm app=test,pod-template-hash=79bf44dbdc
pc-deployment-f864b4d67 0 0 0 15m test myos:nginx app=test,pod-template-hash=f864b4d67

# 最后出现successfully成功代表更新成功
[root@master test]# kubectl rollout status deployment pc-deployment
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment spec update to be observed...
Waiting for deployment spec update to be observed...
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "pc-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "pc-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "pc-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "pc-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "pc-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "pc-deployment" successfully rolled out

删除deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@master test]# kubectl get rs
NAME DESIRED CURRENT READY AGE
pc-deployment-6dfdccd467 0 0 0 28m
pc-deployment-79bf44dbdc 3 3 3 16m
pc-deployment-f864b4d67 0 0 0 30m

[root@master test]# kubectl delete deployment pc-deployment
deployment.apps "pc-deployment" deleted

[root@master test]# kubectl get rs
No resources found in default namespace.

[root@master test]# kubectl get deploy
No resources found in default namespace.

2.8.5 Statefulset(sts)

StatefulSet 是一个==用于运行有状态应用程序的 Kubernetes 控制器。它为每个 Pod 分配了一个唯一的标识符,也称作稳定的网络标识符(Stable Network Identifier, 简称为 SNI)==。这个唯一的标识符允许 StatefulSet 根据指定的顺序(例如按照字母顺序或者按照时间戳)来创建和更新 Pod,并在每次更新时保证这些 Pod 的稳定性。这使得有状态应用程序(如数据库)的部署和扩展变得更加容易。

StatefulSet特点

Deployment、ReplicationController 是为无状态服务而设计的,它们中 pod 的名称、主机名、存储都是不稳定的,且 pod 的启动、销毁顺序随机。管理的 pod 具有如下特点:

  • **唯一性:**对于包含 N 个副本的 StatefulSet,每个 pod 会被分配一个 [0,N)范围内的唯一序号。
  • **顺序性:**StatefulSet 中 pod 的启动、更新、销毁默认都是按顺序进行的。
  • **稳定的网络身份标识:**pod 的主机名、DNS 地址不会随着 pod 被重新调度而发生变化。
  • **稳定的持久化存储:**当 pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。

==在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service。==

  • headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。

  • 除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:

$(podname).(headless server name)

FQDN:$(podname).(headless server name).namespace.svc.cluster.local

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建无头服务
[root@master ~]# vim stssvc.yaml
---
kind: Service
apiVersion: v1
metadata:
name: stssvc # 服务名称
spec:
type: ClusterIP
clusterIP: None # 设置 IP 为 None
selector:
app: sts-httpd # 设置 Pod 标签
ports:
- protocol: TCP
port: 80
targetPort: 80

[root@master ~]# kubectl apply -f stssvc.yaml
service/stssvc created

[root@master ~]# kubectl get services stssvc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
stssvc ClusterIP None <none> 80/TCP 51s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 创建sts控制器
[root@master ~]# vim mysts.yaml
---
kind: StatefulSet # 资源对象类型
apiVersion: apps/v1
metadata:
name: mysts # 控制器名称
spec:
serviceName: stssvc # 新增 headless 服务名称
replicas: 3
selector:
matchLabels:
app: sts-httpd # 修改标签防止冲突
template:
metadata:
labels:
app: sts-httpd # 修改标签防止冲突
spec:
containers:
- name: web
image: myos:httpd

# statefulset 主要解决了 Pod 创建顺序的问题
# statefulset 主要解决了访问指定 Pod 的问题
[root@master ~]# kubectl apply -f mysts.yaml
statefulset.apps/mysts created

# pod是一个一个创建的
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
mysts-0 1/1 Running 0 3s
mysts-1 1/1 Running 0 2s
mysts-2 1/1 Running 0 1s

# 所有 Pod IP 地址
[root@master ~]# host stssvc.default.svc.cluster.local 10.245.0.10
Using domain server:
Name: 10.245.0.10
Address: 10.245.0.10#53
Aliases:
stssvc.default.svc.cluster.local has address 10.244.1.81
stssvc.default.svc.cluster.local has address 10.244.2.82
stssvc.default.svc.cluster.local has address 10.244.3.83

# 单个 Pod IP 地址
[root@master ~]# host mysts-0.stssvc.default.svc.cluster.local 10.245.0.10
Using domain server:
Name: 10.245.0.10
Address: 10.245.0.10#53
Aliases:
mysts-0.stssvc.default.svc.cluster.local has address 10.244.1.81

# 删除sts控制器
[root@master ~]# kubectl delete -f mysts.yaml -f stssvc.yaml
statefulset.apps "mysts" deleted
service "stssvc" deleted

2.8.6 DaemonSet(DS)

==DaemonSet 类型的控制器可以保证在集群中的每一台(或指定)节点上都运行一个副本,一般适用于日志收集、节点监控等场景。 也就是说,如果一个Pod提供的功能是节点级别的,那么这类Pod就适合使用DaemonSet类型的控制器创建。==

特点:

  • 每当向集群中添加一个节点时,指定的Pod副本也将添加到该节点上。

  • 当节点从集群中移除时,Pod也就被垃圾回收了。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@master test]# vim pc-daemonset.yaml 
---
kind: DaemonSet
apiVersion: apps/v1
metadata:
name: pc-daemonset
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: myos:export
[root@master test]# kubectl apply -f pc-daemonset.yaml
daemonset.apps/pc-daemonset created

# 查看daemonset
[root@master test]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
pc-daemonset 2 2 2 2 2 <none> 8s

# 查看pod,发现在每个Node上都运行一个pod,master节点有NoSchedule不调度污点所以master节点没添加该pod
[root@master test]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pc-daemonset-8qn25 1/1 Running 0 14s 10.244.85.237 node01 <none> <none>
pc-daemonset-s565r 1/1 Running 0 14s 10.244.58.220 node02 <none> <none>

2.8.7 Job

==Job负责批处理任务,专门用来执行一次的任务,它保证批处理任务的一个或多个pod成功结束==

Job特点:

  • 当 Job 创建的Pod执行成功结束时,Job将记录成功结束的Pod数量

  • 当成功结束的Pod达到指定的数量时,Job将完成任务。

Job资源清单文件:

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

关于重启策略设置的说明:

  • 如果指定为OnFailure,则job会在pod出现故障时重启容器,而不是创建pod,failed次数不变

  • 如果指定为Never,则job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数加1

  • 如果指定为Always的话,就意味着一直重启,意味着job任务会重复去执行了,当然不对,所以不能设置为Always

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[root@master test]# vim pc-job.yaml 
---
kind: Job
apiVersion: batch/v1
metadata:
name: pc-job
spec:
manualSelector: true #允许使用selector选择器选择pod
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
restartPolicy: Never
containers:
- name: nginx
image: myos:nginx
command:
- sh
- -c
- |
for i in {1..9}
do
echo $i
sleep 3
done
# 创建job
[root@master test]# kubectl create -f pc-job.yaml
job.batch/pc-job created
# 实时查看job
[root@master test]# kubectl get job -w
NAME COMPLETIONS DURATION AGE
pc-job 0/1 8s 8s
pc-job 0/1 34s 34s
pc-job 1/1 34s 34s
# 通过观察pod状态可以看到,pod在运行完毕任务后,就会变成Completed状态
[root@master test]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
pc-job-dg252 0/1 Pending 0 0s
pc-job-dg252 0/1 Pending 0 0s
pc-job-dg252 0/1 ContainerCreating 0 0s
pc-job-dg252 0/1 ContainerCreating 0 1s
pc-job-dg252 1/1 Running 0 3s
pc-job-dg252 0/1 Completed 0 30s
pc-job-dg252 0/1 Completed 0 32s

修改Pod运行的总数量和并行数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 直接更新是不行的,对于同一个Job,这是不可变参数
[root@master test]# kubectl delete -f pc-job.yaml
job.batch "pc-job" deleted

[root@master test]# vim pc-job.yaml
---
kind: Job
apiVersion: batch/v1
metadata:
name: pc-job
spec:
completions: 6 # 指定job需要成功运行Pods的次数为6
parallelism: 3 # 指定job并发运行Pods的数量为3
manualSelector: true
selector:
matchLabels:
app: nginx
template:
......
# 创建job
[root@master test]# kubectl create -f pc-job.yaml
job.batch/pc-job created

# 创建并执行查看结果
[root@master test]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
pc-job-xnzjh 0/1 Pending 0 0s
pc-job-xrv5p 0/1 Pending 0 0s
pc-job-xd6qv 0/1 Pending 0 0s
pc-job-xnzjh 0/1 Pending 0 0s
pc-job-xd6qv 0/1 Pending 0 0s
pc-job-xnzjh 0/1 ContainerCreating 0 0s
pc-job-xd6qv 0/1 ContainerCreating 0 0s
pc-job-xrv5p 0/1 ContainerCreating 0 1s
pc-job-xd6qv 1/1 Running 0 3s
pc-job-xnzjh 1/1 Running 0 3s
pc-job-xrv5p 1/1 Running 0 3s # 从这里可以看见并发创建了3个pod
pc-job-xd6qv 0/1 Completed 0 30s
pc-job-xrv5p 0/1 Completed 0 30s
pc-job-xnzjh 0/1 Completed 0 30s
pc-job-xd6qv 0/1 Completed 0 31s # completed字样代表执行成功
......

2.8.8 CronJob(CJ)

==CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性计划任务的方式控制其运行时间点及重复运行的方式。也就是说,CronJob可以在特定的时间点(反复的)去运行job任务。==

CronJob资源清单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
---
apiVersion: batch/v1 # 版本号
kind: CronJob # 类型
metadata: # 元数据
name: # CJ名称
namespace: # 所属命名空间
labels: #标签
controller: cronjob
spec: # 详情描述
schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
startingDeadlineSeconds: # 启动作业错误的超时时长
jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象;下面其实就是job的定义
metadata:
spec:
completions: 1
parallelism: 1
activeDeadlineSeconds: 30
backoffLimit: 6
manualSelector: true
selector:
matchLabels:
app: counter-pod
matchExpressions: # 规则
- {key: app, operator: In, values: [counter-pod]}
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 20;done"]
--------------------------------------------------------------
需要重点解释的几个选项:
schedule: cron表达式,用于指定任务的执行时间
*/1 * * * *
<分> <时> <日> <月> <周>

分钟: 值从 0 到 59.
小时: 值从 0 到 23.
日: 值从 1 到 31.
月: 值从 1 到 12.
周: 值从 0 到 6, 0 代表星期日
多个时间可以用逗号隔开; 范围可以用连字符给出;*可以作为通配符; /表示每...

concurrencyPolicy:
Allow: 允许Jobs并发运行(默认)
Forbid: 禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行
Replace: 替换,取消当前正在运行的作业并用新作业替换它

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
[root@master test]# vim pc-cronjob.yaml 
---
kind: CronJob
apiVersion: batch/v1
metadata:
name: pc-cronjob
labels:
controller: cronjob
spec:
schedule: "*/1 * * * *" #指定执行任务周期,这里是每分钟
jobTemplate:
metadata:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: latest
image: myos:latest
command: # 执行的任务
- sh
- -c
- |
for i in {1..5}
do
echo $i
sleep 6
done
# 创建cronjob
[root@master test]# kubectl create -f pc-cronjob.yaml
cronjob.batch/pc-cronjob created

# 实时查看cj的信息,是按照我们定义的每分钟执行
[root@master test]# kubectl get cj -w
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
pc-cronjob */1 * * * * False 0 <none> 2s
pc-cronjob */1 * * * * False 1 0s 10s
pc-cronjob */1 * * * * False 2 0s 70s
pc-cronjob */1 * * * * False 3 0s 2m10s
pc-cronjob */1 * * * * False 2 10s 2m20s
pc-cronjob */1 * * * * False 2 10s 2m20s
pc-cronjob */1 * * * * False 3 0s 3m10s
pc-cronjob */1 * * * * False 4 0s 4m10s

# 实时查看job
root@master ~]# kubectl get job -w
NAME COMPLETIONS DURATION AGE
pc-cronjob-28954353 0/1 9s 9s
pc-cronjob-28954353 0/1 10s 10s
pc-cronjob-28954353 1/1 10s 10s # 一个任务已完成
pc-cronjob-28954354 0/1 10s 10s
pc-cronjob-28954354 0/1 11s 11s
pc-cronjob-28954354 1/1 11s 11s

# 发现pod是一个pod一个pod的创建,执行完一个才能创建下一个pod
[root@master test]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
pc-cronjob-28954353-xn2tw 0/1 Pending 0 0s
pc-cronjob-28954353-xn2tw 0/1 Pending 0 0s
pc-cronjob-28954353-xn2tw 0/1 ContainerCreating 0 0s
pc-cronjob-28954353-xn2tw 0/1 ContainerCreating 0 1s
pc-cronjob-28954353-xn2tw 1/1 Running 0 2s
pc-cronjob-28954353-xn2tw 0/1 Completed 0 8s
pc-cronjob-28954353-xn2tw 0/1 Completed 0 9s
pc-cronjob-28954354-q2zcn 0/1 Pending 0 0s
pc-cronjob-28954354-q2zcn 0/1 Pending 0 0s
pc-cronjob-28954354-q2zcn 0/1 ContainerCreating 0 0s
pc-cronjob-28954354-q2zcn 0/1 ContainerCreating 0 1s
pc-cronjob-28954354-q2zcn 1/1 Running 0 3s
pc-cronjob-28954354-q2zcn 0/1 Completed 0 9s
pc-cronjob-28954354-q2zcn 0/1 Completed 0 10s

2.8.9 HPA

==HPA(Horizontal Pod Autoscaling)Pod 水平自动伸缩,是根据Pod占用CPU的比率,到达一定的阀值,会触发伸缩机制,Kubernetes 有一个 HPA 的资源,HPA 可以根据 CPU 利用率自动伸缩一个 Replication Controller、 Deployment 或者Replica Set 中的 Pod 数量。==

HPA的规则:

  • 定义pod的时候必须要有资源限制,否则HPA无法进行监控

  • 扩容时即时的,只要超过阀值会立刻扩容,不是立刻扩容到最大副本数。他会在最小值和最大值之间波动。如果扩容的数量满足了需求,不会在扩容

  • 缩容时缓慢的。如果业务的峰值较高,回收的策略太积极的话,可能会产生业务的崩溃。缩容的速度比较慢,扩容比较快

HPA监控的是CPU,根据CPU自动扩缩容。扩的比较快(要立即满足需求),缩的比较慢(方式业务崩溃),周期性的检测Pod的CPU使用率,默认30s检测一次

HPA示例:

配置后端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 为 Deploy 模板添加资源配额
[root@master ~]# vim mycluster.yaml
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: mydeploy
spec:
replicas: 1
selector:
matchLabels:
app: deploy-httpd
template:
metadata:
labels:
app: deploy-httpd
spec:
containers:
- name: web
image: myos:httpd
resources: # 为该资源设置配额
requests: # HPA 控制器会根据配额使用情况伸缩集群
cpu: 300m # CPU 配额

---
kind: Service
apiVersion: v1
metadata:
name: websvc
spec:
type: ClusterIP
clusterIP: 10.245.1.80
selector:
app: deploy-httpd
ports:
- protocol: TCP
port: 80
targetPort: 80

[root@master ~]# kubectl replace --force -f mycluster.yaml
deployment.apps/mydeploy replaced
service/websvc replaced

# 验证服务
[root@master ~]# kubectl top pods
NAME CPU(cores) MEMORY(bytes)
mydeploy-b4f9dc786-w4x2z 6m 18Mi

[root@master ~]# curl -s http://10.245.1.80/info.php
<pre>
Array
(
[REMOTE_ADDR] => 10.244.219.64
[REQUEST_METHOD] => GET
[HTTP_USER_AGENT] => curl/7.61.1
[REQUEST_URI] => /info.php
)
php_host: mydeploy-b4f9dc786-w4x2z
1229

部署HPA控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@master ~]# vim myhpa.yaml 
---
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2
metadata:
name: myhpa
spec:
behavior: # 配置控制器参数
scaleDown: # 缩容参数
stabilizationWindowSeconds: 60 # 窗口稳定期
scaleTargetRef: # 监控的资源对象
kind: Deployment # 资源对象类型
apiVersion: apps/v1 # 版本
name: mydeploy # 资源对象名称
minReplicas: 1 # Pod副本最小值
maxReplicas: 4 # Pod副本最大值
metrics: # 定义度量资源
- type: Resource # 检测资源对象类型
resource: # 目标定义
name: cpu # 资源名称
target: # 参考指标
type: Utilization # 类型
averageUtilization: 50 # 被使用资源占配额的百分比

[root@master ~]# kubectl apply -f myhpa.yaml
horizontalpodautoscaler.autoscaling/myhpa created

# 刚刚创建 unknown 是正常现象,最多等待 60s 就可以正常获取数据
[root@master ~]# kubectl get horizontalpodautoscalers
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
myhpa Deployment/mydeploy <unknown>/50% 1 5 0

[root@master ~]# kubectl get horizontalpodautoscalers
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
myhpa Deployment/mydeploy 0%/50% 1 5 3

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 终端 1 访问提高负载
[root@master ~]# while sleep 1;do curl -s "http://10.245.1.80/info.php?id=100000" -o /dev/null; done &
# 终端 2 监控 HPA 变化
[root@master ~]# kubectl get hpa -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
myhpa Deployment/mydeploy 0%/50% 1 3 1 1m
myhpa Deployment/mydeploy 31%/50% 1 3 1 2m
myhpa Deployment/mydeploy 70%/50% 1 3 1 2m15s
myhpa Deployment/mydeploy 72%/50% 1 3 2 2m30s
myhpa Deployment/mydeploy 36%/50% 1 3 2 2m45s
myhpa Deployment/mydeploy 55%/50% 1 3 2 3m
myhpa Deployment/mydeploy 58%/50% 1 3 3 3m15s
myhpa Deployment/mydeploy 39%/50% 1 3 3 3m30s
... ...
myhpa Deployment/mydeploy 66%/50% 1 3 3 5m

# 如果 60s 内平均负载小于标准值,就会自动缩减集群规模
[root@master ~]# kubectl get hpa -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
myhpa Deployment/mydeploy 52%/50% 1 3 3 13m
myhpa Deployment/mydeploy 44%/50% 1 3 3 13m15s
myhpa Deployment/mydeploy 38%/50% 1 3 3 13m30s
myhpa Deployment/mydeploy 35%/50% 1 3 3 13m45s
myhpa Deployment/mydeploy 28%/50% 1 3 3 14m
... ...
myhpa Deployment/mydeploy 8%/50% 1 3 3 18m30s
myhpa Deployment/mydeploy 13%/50% 1 3 2 18m45s
myhpa Deployment/mydeploy 12%/50% 1 3 2 19m
myhpa Deployment/mydeploy 28%/50% 1 3 1 19m15s
myhpa Deployment/mydeploy 15%/50% 1 3 1 19m30s
myhpa Deployment/mydeploy 0%/50% 1 3 1 20m

2.9 Service详解

2.9.1 Service 存在的意义?

==引入 Service 主要是解决 Pod 的动态变化,通过创建 Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。==

若提供服务的容器应用是分布式,所以存在多个 pod 副本,而 Pod 副本数量可能在运行过程中动态改变,比如水平扩缩容,或者服务器发生故障 Pod 的 IP 地址也有可能发生变化。当 pod 的地址端口发生改变后,客户端再想连接访问应用就得人工干预,很麻烦,这时就可以通过 service 来解决问题。

概念:
Service 主要用于提供网络服务,通过 Service 的定义,能够为客户端应用提供稳定的访问地址(域名或 IP 地址)和负载均衡功能,以及屏蔽后端 Endpoint 的变化,是 K8s 实现微服务的核心资源。

svc 特点:

  1. 服务发现,防止阴滚动升级等因素导致 Pod IP 发生改变而失联,找到提供同一个服务的 Pod。
  2. 负载均衡,定义一组 Pod 的访问策略。

svc 与 pod 关系:

  1. pod 在创建时,与资源没有明确关联,通过 service 标签和 pod 标签相匹配来以此关联。
  2. 可以通过 endpoints 来查看关联的 pod。

2.9.2 svc的三大特征

服务的自动感知

服务会创建一个clusterlP这个地址对应资源地址,不管Pod 如何变化,服务总能找到对应的Pod,且clusterIP保持不变

服务的负载均衡

如果服务后端对应多个pod,则会通过Iptables/LVS规则将访问的请求最终映射到pod的容器内部,自动在多个容器间实现负载均衡

服务的自动发现

服务创建时会自动在内部DNS上注册域名

域名:<服务名称>.<名称空间>.svc.cluster.local

svc选中pod的逻辑

  1. pod是处于running状态

  2. pod的标签是svc标签的集合(同一个名称空间下)

2.9.3 ClusterIP(集群内部使用)

==默认方式,分配一个稳定的IP地址,即VIP,只能在集群内部访问==

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 创建Deployment,pod的副本数为2
[root@master test]# cat server.yaml
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: http-test
labels:
run: http
spec:
replicas: 2
selector:
matchLabels:
run: http
template:
metadata:
labels:
run: http
spec:
containers:
- name: http
image: myos:httpd

# 创建service
[root@master test]# cat service.yaml
---
kind: Service
apiVersion: v1
metadata:
name: http-test # 定义service的名称
labels: # 定义Service的标签,用于与其他资源关联
run: http
spec: # 定义Service的规格
selector: # 定义Service选择器,用于选择具有特定标签的Pod
run: http
ports: # 定义Service的端口配置
- name: http # 定义多个端口的话需要为每个端口指定名字
protocol: TCP # 指定协议为TCP,不填的话默认 TCP
port: 80 # service 暴露的端口
targetPort: 80 # 关联的 Pod 的端口,不填的话默认和 port 字段相同
# 也可以定义端口别名,但是已有的pod要有端口别名才能被service调用
# 创建以上两个资源清单文件
[root@master test]# kubectl apply -f service.yaml -f server.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 查看标签为run=http的pod详细信息和ip地址
[root@master test]# kubectl get pods -owide -l run=http
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
http-test-57fb9c77d5-djrmd 1/1 Running 0 12m 10.244.58.193 node02 <none> <none>
http-test-57fb9c77d5-k4znk 1/1 Running 0 12m 10.244.85.219 node01 <none> <none>

# 查看service的属性信息
[root@master test]# kubectl describe svc http-test
Name: http-test # Service的名称
Namespace: default
Labels: run=http # Service自己的标签
Annotations: <none>
Selector: run=http # 选择run=http标签的pod
Type: ClusterIP # Service的类型
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.245.50.225 # Service的IP地址
IPs: 10.245.50.225
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.58.193:80,10.244.85.219:80 # Endpoints是Service的实际后端,即流量最终被转发到的地方。
Session Affinity: None
Events: <none>

# 访问serviceIP有负载的效果
[root@master test]# curl -s http://10.245.50.225/info.php | grep php_host
php_host: http-test-57fb9c77d5-djrmd
[root@master test]# curl -s http://10.245.50.225/info.php | grep php_host
php_host: http-test-57fb9c77d5-k4znk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看ipvs规则
[root@master test]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.245.0.1:443 rr
-> 192.168.10.10:6443 Masq 1 6 0
TCP 10.245.0.10:53 rr
-> 10.244.235.207:53 Masq 1 0 0
-> 10.244.235.208:53 Masq 1 0 0
TCP 10.245.0.10:9153 rr
-> 10.244.235.207:9153 Masq 1 0 0
-> 10.244.235.208:9153 Masq 1 0 0
TCP 10.245.50.225:80 rr # 自动添加的规则,以下是后端pod的IP
-> 10.244.58.193:80 Masq 1 0 0
-> 10.244.85.219:80 Masq 1 0 0
UDP 10.245.0.10:53 rr
-> 10.244.235.207:53 Masq 1 0 0
-> 10.244.235.208:53 Masq 1 0 0

#-------------------------------------------------------
# 这些规则是由 Kubernetes 的 kube-proxy 组件自动管理的。kube-proxy 负责维护 ipvs 规则,以确保流量能够正确地从 Service 转发到后端的 Pod。

域名自动注册

==域名自动注册就是说,当你创建了一个svc资源的时候,k8s内部会自动的给相对应的svc资源注册一个域名,k8s内部的CoreDNS是能解析该域名的,然后我们就可以在pod中通过该域名来解析相对应的服务了,域名格式为<服务名称>.<名称空间>.svc.cluster.local.==

[!CAUTION]

注意:Node节点是不能解析该域名的,因为节点使用的是宿主机自己的 /etc/resolv.conf,不认识 svc.cluster.local 域名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装工具软件包
[root@master ~]# dnf install -y bind-utils
# 查看 DNS 服务地址
[root@master test]# kubectl -n kube-system get service kube-dns
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.245.0.10 <none> 53/UDP,53/TCP,9153/TCP 5d22h
# 域名解析测试
[root@master test]# host http-test.default.svc.cluster.local. 10.245.0.10
Using domain server:
Name: 10.245.0.10
Address: 10.245.0.10#53
Aliases:
# 以下是拿到了svc的IP地址了,说明可以解析,我们的pod可以通过域名的方式来访问服务了
http-test.default.svc.cluster.local has address 10.245.50.225
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建pod充当客户端来测试
[root@master test]# vim client-test.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: test-client
spec:
containers:
- name: client-test
image: myos:latest
command:
- sleep
- "3600"
# 创建pod
[root@master test]# kubectl apply -f client-test.yaml
# 进入容器里,用域名来访问实现负载的效果
[root@master test]# kubectl exec -it test-client -- sh
/ # curl -s http-test.default.svc.cluster.local./info.php | grep host
php_host: http-test-57fb9c77d5-djrmd
/ # curl -s http-test.default.svc.cluster.local./info.php | grep host
php_host: http-test-57fb9c77d5-k4znk

补充一(内部交通策略):

svc.spec.internalTrafficPolicy 是 Kubernetes 中 Service 对象的一个字段,用于控制来自集群内部源的流量如何被路由。该字段有两个可能的值:Cluster 和 Local。

  • 当设置为 Cluster(默认值)时,Kubernetes 会选择所有的服务端点来处理集群内部的流量。

  • 当设置为 Local 时,kube-proxy 仅对集群内部流量使用节点本地端点。==这意味着流量只会被路由到与请求发起的客户端位于同一节点上的后端 Pod==。这种策略通常用于优化性能,减少跨节点的网络流量,从而降低延迟和提高可靠性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master test]# kubectl explain svc.spec.internalTrafficPolicy
KIND: Service
VERSION: v1

FIELD: internalTrafficPolicy <string>

DESCRIPTION:
InternalTrafficPolicy describes how nodes distribute service traffic they
receive on the ClusterIP. If set to "Local", the proxy will assume that pods
only want to talk to endpoints of the service on the same node as the pod,
dropping the traffic if there are no local endpoints. The default value,
"Cluster", uses the standard behavior of routing to all endpoints evenly
(possibly modified by topology and other features).

Possible enum values:
# 默认值为Cluster,svc所定义的所有后端Pod提供请求
- `"Cluster"` routes traffic to all endpoints.
# 值为Local时,只会被路由到与请求发起的客户端位于同一节点上的后端 Pod,如果该节点没有运行相对应的后端Pod话,请求会失败
- `"Local"` routes traffic only to endpoints on the same node as the client
pod (dropping the traffic if there are no local endpoints).

补充二(会话保持):

==svc.spec.sessionAffinity.clientIP 是 Kubernetes 中 Service 对象的一个字段,用于配置基于客户端 IP 的会话亲和性。会话亲和性是一种机制,它确保来自同一客户端 IP 的请求总是被路由到同一个后端 Pod,从而在多个请求之间保持会话状态。==

ServiceAffinity 默认值是”None”,如果 ServiceAffinity 设置为 “ClientIP”,则该值必须大于 0 且小于等于 86400(1 天)。默认值为 10800(3 小时)。这意味着在默认情况下,来自同一客户端 IP 的请求将在 3 小时内被路由到同一个 Pod。如果在这段时间内没有来自该客户端 IP 的新请求,会话亲和性将不再生效,后续的请求可能会被路由到不同的 Pod。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@master test]# kubectl explain svc.spec.sessionAffinity
KIND: Service
VERSION: v1

FIELD: sessionAffinity <string>

DESCRIPTION:
Supports "ClientIP" and "None". Used to maintain session affinity. Enable
client IP based session affinity. Must be ClientIP or None. Defaults to
None. More info:
https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

Possible enum values:
- `"ClientIP"` is the Client IP based.
- `"None"` - no session affinity.

2.9.4 NodePort(对外暴露应用)

NodePort相当于是ClusterIP的一个升级版可以实现ClusterIP的完整功能,在每个节点启用一个端口来暴露服务,可以在集群外部访问,通过NodeIP:NodePort访问端口范围:30000~32767

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 创建deployment,pod副本数为3
[root@master test]# vim deploy_nodeport.yaml
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: nodeprot-deploy
spec:
replicas: 3
selector:
matchLabels:
app: apache
template:
metadata:
labels:
app: apache
spec:
containers:
- name: http
image: myos:httpd
# 创建service的nodeport类型,实现集群外部访问
[root@master test]# vim nodeport.yaml
---
kind: Service
apiVersion: v1
metadata:
name: nodeport
spec:
type: NodePort # NodePort类型
selector:
app: apache
ports:
- name: http # 定义多个端口的话需要为每个端口指定名字
port: 80 # clusterIP 暴露的端口
nodePort: 30333 # 集群的每台机器上要暴露的端口,不填的话由系统随机指定 (30000-32767)
targetPort: 80 # 关联的 Pod 的端口,不指定的话默认和 port 字段相同

# 创建以上资源清单文件
[root@master test]# kubectl apply -f deploy_nodeport.yaml -f nodeport.yaml
deployment.apps/nodeprot-deploy configured
service/nodeport unchanged
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@master test]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nodeprot-deploy-644c7f78f7-6czqq 1/1 Running 0 26m 10.244.85.197 node01 <none> <none>
nodeprot-deploy-644c7f78f7-7pm4n 1/1 Running 0 26m 10.244.58.196 node02 <none> <none>
nodeprot-deploy-644c7f78f7-r4lkx 1/1 Running 0 26m 10.244.58.195 node02 <none> <none>

[root@master test]# kubectl describe svc nodeport
Name: nodeport # service名称
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=apache # 选择app=apache标签的pod
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.245.67.236 # 访问集群内部的IP
IPs: 10.245.67.236
Port: http 80/TCP # 端口名为http,svc暴露的端口
TargetPort: 80/TCP # 关联pod的端口
NodePort: http 30333/TCP # 端口名为http,集群节点暴露的端口
# 以下3个IP是关联pod的ip
Endpoints: 10.244.58.195:80,10.244.58.196:80,10.244.85.197:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

查看ipvs规则发现每个节点的每张物理网卡都自动绑定了30333端口,而且还有rr算法轮询的效果,是NAT模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[root@master test]# ipvsadm -Ln | grep :30333 -A 3
TCP 172.17.0.1:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
TCP 192.168.10.10:30333 rr
-> 10.244.58.195:80 Masq 1 0 1
-> 10.244.58.196:80 Masq 1 0 2
-> 10.244.85.197:80 Masq 1 0 1
TCP 10.244.235.192:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
.....
[root@node01 ~]# ipvsadm -Ln | grep :30333 -A 3
TCP 192.168.10.11:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
TCP 192.168.122.1:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
TCP 10.244.85.192:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
......
[root@node02 ~]# ipvsadm -Ln | grep :30333 -A 3
TCP 192.168.10.12:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
TCP 192.168.122.1:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
TCP 10.244.58.192:30333 rr
-> 10.244.58.195:80 Masq 1 0 0
-> 10.244.58.196:80 Masq 1 0 0
-> 10.244.85.197:80 Masq 1 0 0
......

访问每个节点的30333端口实现k8s集群外部访问的效果
验证效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@master test]# curl -s http://192.168.10.10:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-r4lkx
[root@master test]# curl -s http://192.168.10.10:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-6czqq
[root@master test]# curl -s http://192.168.10.10:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-7pm4n
[root@master test]# curl -s http://192.168.10.11:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-6czqq
[root@master test]# curl -s http://192.168.10.11:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-7pm4n
[root@master test]# curl -s http://192.168.10.11:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-r4lkx
[root@master test]# curl -s http://192.168.10.12:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-6czqq
[root@master test]# curl -s http://192.168.10.12:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-7pm4n
[root@master test]# curl -s http://192.168.10.12:30333/info.php | grep host
php_host: nodeprot-deploy-644c7f78f7-r4lkx

补充(外部交通策略):

svc.spec.externalTrafficPolicy字段是 Kubernetes 中 Service(类型为 NodePortLoadBalancer)的一个重要字段,用于控制 来自集群外部的流量 如何被路由到后端 Pod,该策略和svc.spec.internalTrafficPolicy 有点类似,只是一个是控制外部流量的走向,一个是控制内部流量的走向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@master 5.28]# kubectl explain svc.spec.externalTrafficPolicy
KIND: Service
VERSION: v1

FIELD: externalTrafficPolicy <string>

DESCRIPTION:
externalTrafficPolicy describes how nodes distribute service traffic they
receive on one of the Service's "externally-facing" addresses (NodePorts,
ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will
configure the service in a way that assumes that external load balancers
will take care of balancing the service traffic between nodes, and so each
node will deliver traffic only to the node-local endpoints of the service,
without masquerading the client source IP. (Traffic mistakenly sent to a
node with no endpoints will be dropped.) The default value, "Cluster", uses
the standard behavior of routing to all endpoints evenly (possibly modified
by topology and other features). Note that traffic sent to an External IP or
LoadBalancer IP from within the cluster will always get "Cluster" semantics,
but clients sending to a NodePort from within the cluster may need to take
traffic policy into account when picking a node.

Possible enum values:
- `"Cluster"` # 默认值,将流量路由到所有节点的后端Pod。
- `"Cluster"` routes traffic to all endpoints.
- `"Local"` # 值为Local时,外部访问节点的IP:Port的时候,只会被路由到与请求发起的客户端位于同一节点上的后端 Pod,如果该节点没有运行相对应的后端Pod话,请求会失败
- `"Local"` preserves the source IP of the traffic by routing only to
endpoints on the same node as the traffic was received on (dropping the
traffic if there are no local endpoints).

2.9.5 LoadBalancer(对外暴露应用,适用于公有云)

与NodePort类似,在每个节点启用一个端口来暴露服务。除此之外,K8s请求底层云平台的负载均衡器,把每个[Node IP]:[NodePort]作为后端添加进去

华为云示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 使用 CCE 集群服务调用 ELB 负载均衡
[root@harbor ~]# vim websvc.yaml
---
kind: Service
apiVersion: v1
metadata:
name: websvc
annotations:
kubernetes.io/elb.class: union # ELB 类型
kubernetes.io/elb.id: <your_elb_id> # ELB 服务的 ID
kubernetes.io/elb.transparent-client-ip: 'true' # 获取客户端 IP
spec:
type: LoadBalancer
selector:
app: xk8s-httpd
ports:
- name: websvc
protocol: TCP
port: 80
targetPort: webport

[root@harbor ~]# kubectl apply -f websvc.yaml
service/websvc created

[root@harbor ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP
kubernetes ClusterIP 10.245.0.1 <none>
websvc LoadBalancer 10.245.209.220 139.9.168.100,192.168.0.250
# 使用浏览器访问公网IP 139.9.168.100,就能访问集群内的pod

2.9.6 ExternalName

==ExternalName 类型的 Service 是 Kubernetes 中用于提供从集群内部访问外部 DNS 名称的一种方式。它的主要目的是简化集群内部应用访问外部服务的过程。当你创建一个类型为 ExternalName 的 Service 时,Kubernetes 会为这个 Service 创建一个 DNS 条目,该条目将解析为指定的外部 DNS 名称。==

[!CAUTION]

与 NodePort 或 LoadBalancer 类型的 Service 不同,ExternalName 类型的 Service 不会暴露任何集群内部的服务到外部网络,而是相反,它让集群内部的服务可以访问外部的服务。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@master test]# cat test.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: test
spec:
containers:
- name: test
image: myos:httpd
[root@master test]# cat externalname.yaml
---
kind: Service
apiVersion: v1
metadata:
name: externalname
spec:
type: ExternalName # 类型
externalName: www.baidu.com # 外部的dns

# 创建以上两个资源清单
[root@master test]# kubectl apply -f test.yaml -f externalname.yaml
# 查看svc信息
[root@master test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
externalname ExternalName <none> www.baidu.com <none> 11m
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 6d17h
# 进入测试pod
[root@master test]# kubectl exec -it test -- bash
# 访问名为externalname的svc的域名,得到是百度的响应就成功
[root@test html]# ping externalname.default.svc.cluster.local.
PING www.a.shifen.com (103.235.47.188) 56(84) bytes of data.
64 bytes from 103.235.47.188 (103.235.47.188): icmp_seq=1 ttl=127 time=228 ms
64 bytes from 103.235.47.188 (103.235.47.188): icmp_seq=2 ttl=127 time=236 ms
64 bytes from 103.235.47.188 (103.235.47.188): icmp_seq=3 ttl=127 time=231 ms
^C
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2008ms
rtt min/avg/max/mdev = 228.156/231.495/235.757/3.170 ms

2.9.7 Endpoints与Service和Pod间的关联

Kubernetes中的Service,它定义了一组Pods的逻辑集合和一个用于访问它们的策略。一个Service的目标Pod集合通常是由LabelSelector来决定的

Endpoints是一组实际服务的端点集合。一个Endpoint是一个可被访问的服务端点,即一个状态为running的pod的可访问端点。一般Pod都不是一个独立存在,所以一组Pod的端点合在一起称为EndPoints。==只有被ServiceSelector匹配选中并且状态为Running的Pod才会被加入到和Service同名的Endpoints 中。==

Service与endpoints的关联关系如下:

  • 有定义标签选择器

    • 自动创建一个同名的endpoints资源对象
      • 匹配对象:当前名称空间的pod
        1. 标签子集运算
        2. 就绪的状态
  • 没有定义标签选择器

    • 不会创建同名的endpoints资源对象,需要管理员手动创建
      • 匹配对象
        1. 管理员填写的端点信息,灵活性强

手动关联service示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
[root@master test02]# vim test_service.yaml 
---
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
type: ClusterIP # service的类型
clusterIP: 10.245.8.8 # 固定service的IP
ports:
- targetPort: 80 # 访问后端服务器的端口
port: 8888 # service暴露的端口
protocol: TCP # 使用的协议
---
kind: Endpoints
apiVersion: v1
metadata:
name: nginx # 必须要和关联的service的名一样
subsets: # 定义关联的后端服务器的IP
- addresses:
- ip: 192.168.10.12 # 关联集群中的nodeIP,可以是多个
ports:
- port: 80 # 访问nodeIP的80端口

# 创建资源清单文件
[root@master test02]# kubectl apply -f test_service.yaml
service/nginx created
endpoints/nginx created

# 查看svc信息
[root@master test02]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 7d3h
nginx ClusterIP 10.245.8.8 <none> 8888/TCP 6s

# 查看endpoints信息
[root@master test02]# kubectl get ep
NAME ENDPOINTS AGE
kubernetes 192.168.10.10:6443 7d3h
nginx 192.168.10.12:80 9s

# 查看ipvs规则,自动添加的规则
[root@master test02]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.245.0.1:443 rr
-> 192.168.10.10:6443 Masq 1 6 0
TCP 10.245.0.10:53 rr
-> 10.244.235.207:53 Masq 1 0 0
-> 10.244.235.208:53 Masq 1 0 0
TCP 10.245.0.10:9153 rr
-> 10.244.235.207:9153 Masq 1 0 0
-> 10.244.235.208:9153 Masq 1 0 0
TCP 10.245.8.8:8888 rr # svcIP的规则,自动绑定后端ip,实现轮询效果
-> 192.168.10.12:80 Masq 1 0 0
UDP 10.245.0.10:53 rr
-> 10.244.235.207:53 Masq 1 0 0
-> 10.244.235.208:53 Masq 1 0 0

# 验证访问svc的8888端口,得到的是自己关联的后端的80端口的信息
[root@master test02]# curl 10.245.8.8:8888
wo shi hehe

2.10 k8s存储

2.10.1 概述

在Kubernetes(K8s)中,存储系统是一个关键的组成部分,用于管理容器化应用的数据持久性和共享性。K8s的存储分类可以从多个维度进行理解,但主要分为两大类:==临时存储和持久存储==。关于元数据和真实数据的分类,虽然这两个概念在存储系统中普遍存在,但在K8s的存储分类中,它们并不是直接用于分类存储类型的标准。不过,可以从K8s存储类型如何管理和使用这些数据的角度来探讨。

2.10.2 k8s支持的卷类型

  • 持久卷:持久卷是集群中的存储资源,就像他的名字一样,在里面存储的数据不会随着 Pod 的删除而丢失。
  • 临时卷:有些应用程序需要额外的存储,但并不关心数据在重启后是否仍然可用。卷会遵从 Pod的生命周期,与Pod一起创建和删除。
  • 投射卷:它允许您将多个现有卷源映射到同一个目录。通过将这些不同类型的卷源组合成一个统一的卷,可以更方便地管理和使用这些资源

2.10.3 临时存储

**EmptyDir:**EmptyDir是一种在Pod中创建的空目录,用于在容器之间共享文件。它的数据存储在Pod所在节点的本地磁盘上,当Pod被删除时,数据也会被删除。这种存储方式适用于需要临时存储数据的场景,如缓存数据。在这种情况下,元数据(如目录结构、文件属性等)和真实数据(文件内容)都是临时的,与Pod的生命周期绑定。

2.10.4 持久存储

PersistentVolume (PV) 和 PersistentVolumeClaim (PVC):

PV是由管理员配置的存储资源,而PVC是用户请求的存储资源。PVC允许用户抽象地请求存储资源,而不需要关心具体的存储后端。PV和PVC的结合使用,可以动态地分配和释放存储资源,用于持久化存储真实数据。元数据(如PV和PVC的配置信息)存储在K8s的etcd数据库中,而真实数据则存储在配置的存储后端(如NFS、Ceph等)上。

NFS:

NFS卷将网络文件系统(NFS)挂载到容器中,允许跨多个Pod和节点共享数据。元数据(如NFS文件系统的目录结构、文件权限等)和真实数据都存储在NFS服务器上,实现了数据的持久化和共享。

ConfigMap 和 Secret:

虽然ConfigMap和Secret主要用于挂载配置文件和密钥到容器中,但它们也可以视为一种存储形式。这些资源对象的元数据(如配置项的名称、值等)和真实数据(配置文件内容、密钥值等)都存储在K8s的etcd数据库中。不过,它们的主要用途是配置和安全性,而非大规模的数据存储。

StatefulSet:

StatefulSet是一种用于管理有状态应用的控制器,它确保每个Pod都有稳定的标识和顺序。StatefulSet通常会为每个Pod分配一个独特的持久卷(通过PVC实现),以存储Pod的持久化数据。在这种情况下,元数据(如StatefulSet的配置、Pod的标识等)存储在K8s的etcd数据库中,而真实数据则存储在分配的持久卷上。

总结

在K8s中,元数据和真实数据的存储和管理是通过不同的机制实现的。元数据通常存储在K8s的etcd数据库中,用于管理集群的状态和配置。而真实数据则根据所选的存储类型(如PV、PVC、NFS等)存储在相应的存储后端上。通过合理配置和使用这些存储类型,K8s能够提供灵活、可靠的数据存储解决方案,满足各种应用场景的需求。

2.10.5 ConfigMap

K8s(Kubernetes)中的ConfigMap是一种用于存储配置数据的API对象,它属于Kubernetes中的核心对象。==ConfigMap的主要作用是将应用程序的配置信息与容器镜像分离,以便在不重新构建镜像的情况下进行配置的修改和更新。==

用途:

==ConfigMap用于存储键值对形式的配置数据,这些数据可以包括环境变量、命令行参数、配置文件等。它提供了一种集中管理和传递配置信息的机制,使得应用程序能够从ConfigMap中获取配置数据,从而在不修改容器镜像的前提下,动态地修改应用程序的配置参数。==

与Secret的区别

ConfigMap主要用于存储非敏感的配置数据,如应用程序的配置文件、环境变量等,而Secret则用于存储敏感的数据,如密码、密钥等。Secret提供了更高的安全性和访问控制机制。

通过命令行管理ConfigMap示例:

使用文字值创建,利用 –from-literal 参数传递配置信息,该参数可以使用多次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[root@master test02]# kubectl create cm literal-config --from-literal=name=lili --from-literal=passwd=123
configmap/literal-config created

# 查看指定cm的属性信息
[root@master test02]# kubectl describe cm literal-config
Name: literal-config
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
name:
----
lili
passwd:
----
123

BinaryData
====

Events: <none>

# 使用YAML格式输出
[root@master test02]# kubectl get cm literal-config -o yaml
apiVersion: v1
data:
name: lili
passwd: "123"
kind: ConfigMap
metadata:
creationTimestamp: "2025-01-24T03:33:55Z"
name: literal-config
namespace: default
resourceVersion: "391106"
uid: 860618b7-3177-4f8c-ac83-62d458da7dc0

通过目录创建

–from-file 指定在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 以下两个文件里分别有两个值
[root@master ~]# ls test_cm/
name passwd

# 通过目录创建
[root@master ~]# kubectl create cm test-cmdir --from-file=./test_cm/
configmap/test-cmdir created

# 查看configmap
[root@master ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 9d
literal-config 2 35m
test-cmdir 2 10s

# 查看属性信息
[root@master ~]# kubectl describe cm test-cmdir
Name: test-cmdir
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
name:
----
haha
lili

passwd:
----
1234
6789


BinaryData
====

Events: <none>

# 通过yaml格式输出
[root@master ~]# kubectl get cm test-cmdir -o yaml
apiVersion: v1
data:
name: |
haha
lili
passwd: |
1234
6789
kind: ConfigMap
metadata:
creationTimestamp: "2025-01-24T04:09:00Z"
name: test-cmdir
namespace: default
resourceVersion: "394234"
uid: 35476d3a-3ac0-4409-b04d-06936cde7d1e

通过文件创建

–from-file 参数只要指定为一个文件就可以从单个文件中创建 ConfigMap。–from-file 这个参数可以使用多次,你可以使用两次分别指定上个实例中的那两个配置文件,效果就跟指定整个目录是一样的

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@master test02]# kubectl create cm test-config --from-file=test_cm.file 
configmap/test-config created

# 查看configmap
[root@master test02]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 9d
literal-config 2 108m
test-cmdir 2 73m
test-config 1 6s

# 查看cm的属性信息
[root@master test02]# kubectl describe cm test-config
Name: test-config
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
test_cm.file:
----
name=hehe
passwd=123456


BinaryData
====

Events: <none>

# 以yaml的格式输出指定cm
[root@master test02]# kubectl get cm test-config -o yaml
apiVersion: v1
data:
test_cm.file: |
name=hehe
passwd=123456
kind: ConfigMap
metadata:
creationTimestamp: "2025-01-24T05:22:24Z"
name: test-config
namespace: default
resourceVersion: "396512"
uid: bfa82c9a-2aea-425e-8623-fe26aee5473b

环境变量

**环境变量:**可以将ConfigMap中的数据设置为Pod中容器的环境变量。这样,容器在启动时就可以从环境变量中获取配置信息。

**注意:**使用该 ConfigMap 挂载的 Env 不会同步更新

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
[root@master test02]# vim config-env.yaml 
---
kind: ConfigMap
apiVersion: v1
metadata:
name: literal-config
data:
name: hehe # 定义数据
passwd: admin
---
kind: ConfigMap
apiVersion: v1
metadata:
name: env-config
data:
log_level: INFO
---
kind: Pod # 定义Pod
apiVersion: v1
metadata:
name: env-pod
spec:
restartPolicy: Never # 重启策略 永不
containers:
- name: test
image: myos:nginx
command: # 启动命令 打印容器内部env环境变量
- sh
- -c
- env
env: # 定义env 为容器内部添加环境变量
- name: USERNAME # 环境变量名字
valueFrom: # 值来源
configMapKeyRef: # 值来源于configmap
name: literal-config # 值来源的configmap类别的名字
key: name # key的名字/字段
- name: PASSWORD
valueFrom:
configMapKeyRef:
name: literal-config
key: passwd
envFrom: # 直接引入名为env-config的configmap
- configMapRef:
name: env-config
#创建以上资源清单文件
[root@master test02]# kubectl apply -f config-env.yaml
configmap/literal-config unchanged
configmap/env-config unchanged
pod/env-pod created

[root@master test02]# kubectl get pods
NAME READY STATUS RESTARTS AGE
env-pod 0/1 Completed 0 5m10s

[root@master test02]# kubectl logs env-pod
NGINX_PORT_8888_TCP=tcp://10.245.8.8:8888
NGINX_PORT_8888_TCP_ADDR=10.245.8.8
HOSTNAME=env-pod
USERNAME=hehe # literal-config添加的环境变量
NGINX_PORT_8888_TCP_PORT=8888
PASSWORD=admin # literal-config添加的环境变量
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.245.0.1
NGINX_PORT=tcp://10.245.8.8:8888
KUBERNETES_PORT=tcp://10.245.0.1:443
PWD=/usr/local/nginx/html
HOME=/root
NGINX_SERVICE_PORT=8888
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.245.0.1:443
SHLVL=1
NGINX_PORT_8888_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
log_level=INFO # env-config添加的环境变量
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/nginx/sbin
KUBERNETES_SERVICE_HOST=10.245.0.1
NGINX_SERVICE_HOST=10.245.8.8
_=/usr/bin/env

命令行参数

命令行参数:将ConfigMap中的数据作为命令行参数传递给容器中的应用程序。这通常需要先将ConfigMap的数据保存在环境变量中,然后通过环境变量的方式引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@master test02]# vim cm-command.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: cm-command-pod
spec:
restartPolicy: Never
containers:
- name: test
image: myos:nginx
command:
- sh
- -c
- |
echo ${USERNAME} ${PASSWORD}
env: # 这里引用上面案例的configmap
- name: USERNAME
valueFrom:
configMapKeyRef:
name: literal-config
key: name
- name: PASSWORD
valueFrom:
configMapKeyRef:
name: literal-config
key: passwd

[root@master test02]# kubectl apply -f cm-command.yaml
pod/cm-command-pod created

[root@master test02]# kubectl logs cm-command-pod
hehe admin

卷挂载

卷挂载:ConfigMap可以作为卷挂载到Pod中,使得容器可以直接读取ConfigMap中的配置文件。每个键值对都会生成一个文件,其中键为文件名,值为文件内容。这样,应用程序就可以根据需要读取配置文件中的配置信息。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@master test02]# vim cm-volume.yaml 
---
apiVersion: v1
kind: Pod
metadata:
name: cm-volume-pod
spec:
restartPolicy: Never
containers:
- name: test
image: myos:nginx
volumeMounts: # volume挂载
- name: config-volume # 挂载下面指定的 volume
mountPath: /etc/config # 挂载到的目录(容器内路径,该目录下,文件名就是键名,文件内容就是键值)
volumes:
- name: config-volume # volume 名称
configMap: # 来自configmap
name: literal-config # 上边的示例已经定义过

[root@master test02]# kubectl apply -f cm-volume.yaml
pod/cm-volume-pod created

[root@master test02]# kubectl exec -it cm-volume-pod -- bash

[root@cm-volume-pod html]# ls /etc/config
name passwd
# 这种方式创建的是链接文件 热更新
[root@cm-volume-pod html]# cat /etc/config/name
hehe[root@cm-volume-pod html]# cat /etc/config/passwd
admin[root@cm-volume-pod html]# ls -l /etc/config
total 0
lrwxrwxrwx 1 root root 11 Jan 24 07:16 name -> ..data/name
lrwxrwxrwx 1 root root 13 Jan 24 07:16 passwd -> ..data/passwd

热更新

通过kubectl edit configmap [configmap name]命令直接修改内容就可以达到热更新

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@master test02]# cat cm-update.yaml 
---
apiVersion: v1
kind: ConfigMap
metadata:
name: log-config
namespace: default
data:
log_level: INFO
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 1
selector:
matchLabels:
run: my-nginx
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: nginx
image: myos:nginx
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config # 容器内这个目录下会有 log_level 这个文件,内容为 INFO
volumes:
- name: config-volume
configMap:
name: log-config
# 查看容器里的文件
[root@master test02]# kubectl exec -it my-nginx-746bd4859b-qw6wp -- cat /etc/config/log_level
INFO
# 使用edit命令修改cm的键值为NOTICE
[root@master test02]# kubectl edit cm log-config
configmap/log-config edited
# 过个10秒再次查看容器里的文件,文件里的配置修改了
[root@master test02]# kubectl exec -it my-nginx-746bd4859b-qw6wp -- cat /etc/config/log_level
NOTICE

补充:添加不可改变选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 添加不可改变选项 immutable: true 
[root@master test02]# kubectl edit cm log-config
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
log_level: NOTICE
immutable: true # 添加不可改变选项
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"log_level":"INFO"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}
creationTimestamp: "2025-01-24T08:56:32Z"
name: log-config
namespace: default
resourceVersion: "429330"
uid: 4e138ec5-861f-4205-8611-69c00035e973
# 添加之后就无法进行热更新了
# 添加之后是不可逆的 需要重新创建一个cm

补充

Pod滚动更新

ConfigMap 更新后,并不会让相应的文件重载。例如,Nginx 在启动时,会加载一次配置文件(配置文件中有 ConfigMap 的相关参数),加载完成后,无论这个配置文件再怎么变化,Nginx 都不会再加载它。==因此需要 ConfigMap 更新后,再滚动更新 Pod。==

可以通过修改 pod annotations 的方式强制触发滚动更新。这里我们在 Deployment.spec.template.metadata.annotations 中添加 version/config字段来实现pod的滚动更新

1
kubectl patch deployment my-nginx --patch '{"spec": {"template": {"metadata": {"annotations":{"version/config": "20250124" }}}}}'

注意:更新 ConfigMap 后:

  • 使用该 ConfigMap 挂载的 Env 不会同步更新

  • 使用该 ConfigMap 挂载的 Volume 中的数据需要一段时间(实测大概10秒)才能同步更新

CM的优势

  • **配置解耦:**将配置信息与容器镜像解耦,使得配置可以在不重新构建镜像的情况下进行修改和管理。
  • **动态更新:**ConfigMap中的配置可以在运行时动态更新,而不需要重新启动应用程序。
  • **版本控制:**ConfigMap中的配置可以使用版本控制系统进行管理,随时回滚到之前的版本。
  • **共享和复用:**ConfigMap可以被多个应用程序共享和复用,提高了配置的一致性和可维护性。

综上所述,K8s ConfigMap是Kubernetes中用于存储和管理配置数据的重要组件,它提供了灵活的配置管理方式,使得应用程序的配置更加清晰、易于管理和更新。

2.10.6 Secret

==K8s(Kubernetes)中的Secret是一种用于保存敏感信息的资源对象,如密码、OAuth令牌、ssh密钥等。这些信息如果直接放在Pod的定义中或镜像中,可能会带来安全风险,因为Pod的定义和镜像都可能被存储在版本控制系统中,或者被不同的用户访问。通过使用Secret,可以更安全地管理这些敏感信息。==

Secret特性

  • **安全性:**Secret中的信息被加密存储(实际上是Base64编码,但Kubernetes社区通常称之为加密),以减少敏感信息泄露的风险。
  • **灵活性:**Secret可以以多种方式被Pod使用,包括作为环境变量、挂载到Pod中的卷中的文件,或者在kubelet为Pod拉取镜像时使用。
  • **可重用性:**多个Pod可以引用同一个Secret,从而避免在多个地方重复存储相同的敏感信息。

Secret的类型

  • Opaque:这是默认的Secret类型,用于存储任意格式的敏感信息。数据以Base64编码的形式存储在Secret中。

  • kubernetes.io/service-account-token:由Kubernetes自动创建,用于Pod与API Server之间的通信认证。

  • kubernetes.io/dockerconfigjson:用于存储私有Docker Registry的认证信息。

  • kubernetes.io/tls:用于存储TLS证书和私钥,以便Pod能够使用SSL/TLS协议进行安全通信。

  • kubernetes.io/basic-auth:用于存储基本认证信息,如用户名和密码

Opaque类型 (使用最多)

Opaque和configMap很像, 数据是一个 map 类型,要求 value 是 base64 编码格式,可以用于环境变量和数据卷挂载

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 使用base64编码
[root@master test02]# echo -n hehe | base64
aGVoZQ==
[root@master test02]# echo -n 321 | base64
MzIx
# 解码
[root@master test02]# echo -n aGVoZQ== | base64 -d
hehe
# 使用加密后的用户名和密码创建 Secret
[root@master test02]# vim secret.yaml
---
kind: Secret
apiVersion: v1
metadata:
name: mysecret
type: Opaque
data:
passwd: MzIx
username: aGVoZQ==

[root@master test02]# kubectl apply -f secret.yaml
secret/mysecret created
# 查看secret
[root@master test02]# kubectl get secret
NAME TYPE DATA AGE
mysecret Opaque 2 2m18s
# 查看secret属性信息
[root@master test02]# kubectl describe secrets mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>

Type: Opaque

Data
====
passwd: 3 bytes
username: 4 bytes
# 以yaml的格式输出指定的secret
[root@master test02]# kubectl get secrets mysecret -o yaml
apiVersion: v1
data:
passwd: MzIx
username: aGVoZQ==
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"passwd":"MzIx","username":"aGVoZQ=="},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"type":"Opaque"}
creationTimestamp: "2025-01-24T15:57:16Z"
name: mysecret
namespace: default
resourceVersion: "442203"
uid: 9bff22dc-aaf2-4c1f-af1f-d4bda8accd8c
type: Opaque

在pod中使用secret

环境变量

作为环境变量(不可以热更新)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@master test02]# vim mysecret.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: deploy-secret
spec:
replicas: 3
selector:
matchLabels:
app: secret
template:
metadata:
labels:
app: secret
spec:
containers:
- name: test
image: myos:httpd
command:
- sh
- -c
- |
echo ${TEST_USER} ${TEST_PASS}
env: # 添加环境变量
- name: TEST_USER # 环境变量名
valueFrom: # 值来源
secretKeyRef: # 从 Secret 中获取
name: mysecret # Secret 的名字,在以上示例已定义
key: username # Secret 中的键名,上面案例定义的
- name: TEST_PASS
valueFrom:
secretKeyRef:
name: mysecret
key: passwd
[root@master test02]# kubectl apply -f mysecret.yaml
deployment.apps/deploy-secret created

# secret中的键值会自动的解码
[root@master test02]# kubectl logs deploy-secret-6c848876bf-dcb2s
hehe 321
卷挂载

secret也是支持热更新的和configmap一样,==但是使用secret作为子路径卷挂载的容器不会收到secret更新==

将secret挂载到volume中示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@master test02]# vim secret_volume.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: secret-test
spec:
volumes: # 创建一个卷
- name: secret # 卷名
secret: # 卷使用的方案
secretName: mysecret # 来自于上面案例创建的 mysecret
containers:
- name: test
image: myos:nginx
volumeMounts: # 卷挂载
- name: secret # 挂载的是上面声明的 secrets
mountPath: /data # 挂载的目录(容器内目录)
readOnly: true # 只读 

[root@master test02]# kubectl apply -f secret_volume.yaml
pod/secret-test created

[root@master test02]# kubectl get pods
NAME READY STATUS RESTARTS AGE
secret-test 1/1 Running 0 4s

# 进入容器
[root@master test02]# kubectl exec -it secret-test -- bash
[root@secret-test html]# cd /data
# Opaque Secret 中的用户名和密码都已经挂载进来了
[root@secret-test data]# ls
passwd username
# 查看内容,发现内容已经自动被解码
[root@secret-test data]# cat passwd
321[root@secret-test data]# cat username
hehe

注意事项:

  • 当使用Secret时,应确保Pod有足够的权限来访问这些Secret。

  • Secret中的信息虽然被加密(实际上是Base64编码),但应尽量避免将过于敏感的信息存储在Kubernetes集群中,以防止潜在的泄露风险。

  • 定期检查并更新Secret中的敏感信息,以确保系统的安全性。

2.10.7 Downward API

在Kubernetes(k8s)中,Downward API 是一种特殊类型的 API,它允许 Pod 中的容器获取关于 Pod 本身及其所在环境的元数据信息。这些信息可以通过两种方式注入到容器内部:环境变量和卷挂载(Volume Mounts)。

Downward API 的两种注入方式

环境变量

环境变量是 Downward API 注入信息到容器的常用方式,适用于单个变量的情况。通过 Downward API,可以将 Pod 的 IP 地址、名称、命名空间等基本信息以环境变量的形式注入到容器内部。这样,容器内的应用程序就可以通过读取这些环境变量来获取 Pod 的相关信息。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
[root@master test02]# vim test_downwardapi.yaml 
kind: Pod
apiVersion: v1
metadata:
name: downward-api-pod
spec:
restartPolicy: Never
containers:
- name: test
image: myos:nginx
env: # 定义容器的环境变量
- name: POD_NAME # POD_NAME 环境变量,其值来源于 Pod 的元数据字段 metadata.name
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: CPU_LIMIT # CPU_LIMIT 环境变量,其值来源于 Pod 的资源限制字段 limits.cpu
valueFrom:
resourceFieldRef:
resource: limits.cpu
- name: CPU_REQUEST
valueFrom:
resourceFieldRef:
resource: requests.cpu

[root@master test02]# kubectl apply -f test_downwardapi.yaml
pod/downward-api-pod created

[root@master test02]# kubectl get pods
NAME READY STATUS RESTARTS AGE
downward-api-pod 1/1 Running 0 9s
secret-test 1/1 Running 0 5h58m

# 验证效果
[root@master test02]# kubectl exec -it downward-api-pod -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/nginx/sbin
HOSTNAME=downward-api-pod
POD_IP=10.244.85.215 # 得到的pod的IP地址
CPU_LIMIT=2 # 限制2个cpu
CPU_REQUEST=0 # 0代表没有进行配额的设置
POD_NAME=downward-api-pod # pod的名称
NAMESPACE=default
NGINX_PORT_8888_TCP_PORT=8888
KUBERNETES_SERVICE_PORT_HTTPS=443
NGINX_PORT_8888_TCP_ADDR=10.245.8.8
NGINX_PORT=tcp://10.245.8.8:8888
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
NGINX_SERVICE_HOST=10.245.8.8
NGINX_PORT_8888_TCP=tcp://10.245.8.8:8888
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.245.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.245.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.245.0.1
NGINX_SERVICE_PORT=8888
NGINX_PORT_8888_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=10.245.0.1
TERM=xterm
HOME=/root
卷挂载

是将 Pod 的信息生成为文件,并通过卷挂载的方式将这些文件注入到容器内部。这种方式适用于需要批量处理或复杂查询 Pod 信息的情况。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
[root@master test02]# vim test_downwardapi02.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: test-volume-pod
labels:
app: volume
spec:
restartPolicy: Never
containers:
- name: test
image: myos:nginx
resources:
limits:
cpu: 1
memory: 400Mi
requests:
cpu: 1
memory: 300Mi
volumeMounts:
- name: downwardapi-volume
mountPath: /podinfo
volumes:
- name: downwardapi-volume
downwardAPI:
items:
- path: "labels" # 挂载的文件路径
fieldRef:
fieldPath: metadata.labels # 引用的字段路径
- path: "name"
fieldRef:
fieldPath: metadata.name
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
- path: "uid"
fieldRef:
fieldPath: metadata.uid
- path: "cpuRequest"
resourceFieldRef:
containerName: test # 引用的容器名称
resource: requests.cpu # 引用的资源字段
- path: "memoryRequest" # 挂载的文件路径
resourceFieldRef:
containerName: test
resource: requests.memory
- path: "cpuLimit"
resourceFieldRef:
containerName: test
resource: limits.cpu
- path: "memoryLimit"
resourceFieldRef:
containerName: test
resource: limits.memory

[root@master test02]# kubectl get pods
NAME READY STATUS RESTARTS AGE
downward-api-pod 1/1 Running 0 136m
secret-test 1/1 Running 0 8h
test-volume-pod 1/1 Running 0 8m2s
# 进入容器验证效果
[root@master test02]# kubectl exec -it test-volume-pod -- bash
[root@test-volume-pod html]# cd /podinfo
[root@test-volume-pod podinfo]# ls
cpuLimit labels memoryRequest namespace
cpuRequest memoryLimit name uid
[root@test-volume-pod podinfo]# cat name
test-volume-pod[root@test-volume-pod podinfo]# cat labels
app="volume"

Downward API 支持的字段

Downward API 支持的字段包括但不限于:

  • spec.nodeName:宿主机名字

  • status.hostIP:宿主机 IP

  • metadata.name:Pod 的名字

  • metadata.namespace:Pod 的 Namespace

  • status.podIP:Pod 的 IP

  • spec.serviceAccountName:Pod 的 Service Account 的名字

  • metadata.uid:Pod 的 UID

  • metadata.labels[‘’]:指定 的 Label 值

  • metadata.annotations[‘’]:指定 的 Annotation 值

  • metadata.labels:Pod 的所有 Label

  • metadata.annotations:Pod 的所有 Annotation

使用 Downward API 的步骤

  1. 创建包含 Downward API 信息的 Pod:编写 Pod 的 YAML 配置文件,定义需要注入的环境变量或卷挂载。

  2. 使用 kubectl 创建 Pod:使用 kubectl apply -f <pod-config-file.yaml> 命令创建 Pod。

  3. 在容器中读取 Downward API 注入的信息:进入 Pod 的容器内部,通过环境变量或文件来读取注入的信息。

通过以上步骤,你可以在 Kubernetes 中使用 Downward API 来获取 Pod 的相关信息,并将其注入到容器内部,以满足应用程序的需求。

2.10.8 Volume

==K8s(Kubernetes)中的Volume(存储卷)是一种用于在Pod中持久存储数据的机制,它为Pod中的容器提供了一个共享的存储空间==

定义与用途

**定义:**在K8s中,Volume是一种抽象的概念,用于提供Pod中容器的持久化存储。它允许将数据存储在Pod的生命周期之外,以便在容器重启、迁移或重新调度时保留数据。

用途:

  • 数据持久化:将数据存储在Volume中,确保容器重启后数据仍然存在。

  • 数据共享:Volume可以连接到Pod中的一个或多个容器,使它们能够共享相同的数据。

  • 数据备份和恢复:使用Volume来备份和还原应用程序的数据。

  • 数据迁移和复制:将Volume从一个Pod迁移到另一个Pod,或将Volume复制到其他地方。

kubernets支持的卷的类型

官网:https://kubernetes.io/zh/docs/concepts/storage/volumes/

k8s支持的卷的类型如下:

awsElasticBlockStore 、azureDisk、azureFile、cephfs、cinder、configMap、csidownwardAPI、emptyDir、fc (fibre channel)、flexVolume、flocker、gcePersistentDisk、gitRepo (deprecated)、glusterfs、hostPath、iscsi、local、nfs、persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret、storageos、vsphereVolume

emptyDir

==当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。该卷可以挂载到 Pod 每个容器中的相同或不同路径上,并且每个容器都可以读取和写入 emptyDir 卷中的文件。当出于任何原因从节点中删除 Pod 时, emptyDir 中的数据将被永久删除。==

***注意:***容器崩溃不会从节点中移除 pod,因此 emptyDir 卷中的数据在容器时是安全的

emptyDir 的用法有:

  • 存放临时文件,例如用于基于磁盘的归并排序;

  • 用作长时间计算崩溃恢复时的检查点,供容器崩溃后恢复使用;

  • Web 服务器容器提供数据时,保存内容管理器容器提取的文件;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@master test02]# vim test_emptydir.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: test-emptydir-pod
spec:
containers:
- name: test01
image: myos:nginx
volumeMounts:
- name: emptydir-volume # 通过哪个 volume 挂载
mountPath: /test01_volume # 挂载到容器的哪个目录下
- name: test02
image: myos:php-fpm
volumeMounts:
- name: emptydir-volume
mountPath: /test02_volume
volumes:
- name: emptydir-volume # volume 名称
emptyDir: {} # volume 类型

[root@master test02]# kubectl apply -f test_emptydir.yaml
pod/test-emptydir-pod created

[root@master test02]# kubectl get pods
NAME READY STATUS RESTARTS AGE
test-emptydir-pod 2/2 Running 0 4s

# 在容器1的挂载目录下,创建一个 info.txt 文件
[root@master test02]# kubectl exec -it test-emptydir-pod -c test01 -- touch /test01_volume/info.txt

# 查看容器2的 /test02_volume挂载目录,会出现info.txt,实现了共享emptydir卷
[root@master test02]# kubectl exec -it test-emptydir-pod -c test02 -- ls /test02_volume
info.txt
数据存储落地路径

在kubelet的工作目录,默认为/var/lib/kubelet,会为每个使用了emptyDir:{}的pod创建一个目录,格式为/var/lib/kubelet/pods/{podid}/volumes/kubernetes.io~empty-dir/,所有放在emptyDir中数据,最终都是落在了node的上述路径中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 找到运行emptyDir的pod所在的节点,然后查找数据落地的地方
[root@node01 3de59b4b-8838-4c79-a036-8c007774d24e]# pwd
/var/lib/kubelet/pods/3de59b4b-8838-4c79-a036-8c007774d24e
[root@node01 3de59b4b-8838-4c79-a036-8c007774d24e]# tree .
.
├── containers
│   ├── hello
│   │   └── b3ac762c
│   └── nginx
│   └── 0518943d
├── etc-hosts
├── plugins
│   └── kubernetes.io~empty-dir
│   ├── emptydir-volume
│   │   └── ready
│   └── wrapped_kube-api-access-4wqhw
│   └── ready
└── volumes
├── kubernetes.io~empty-dir
│   └── emptydir-volume
│   ├── access.log
│   ├── error.log
│   └── nginx.pid
└── kubernetes.io~projected
└── kube-api-access-4wqhw
├── ca.crt -> ..data/ca.crt
├── namespace -> ..data/namespace
└── token -> ..data/token

12 directories, 11 files
[root@node01 3de59b4b-8838-4c79-a036-8c007774d24e]# cd volumes/kubernetes.io~empty-dir/emptydir-volume/
[root@node01 emptydir-volume]# echo hello world >> access.log

# 从这里就可以知道,该目录下的文件就是emptyDir共享的文件,还可以实现热更新。
[root@master ~]# kubectl logs emptydir hello
10.244.219.64 - - [03/Jun/2025:03:07:09 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
10.244.219.64 - - [03/Jun/2025:03:07:11 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
10.244.219.64 - - [03/Jun/2025:03:07:14 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
10.244.219.64 - - [03/Jun/2025:03:07:16 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
10.244.219.64 - - [03/Jun/2025:03:07:18 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
10.244.219.64 - - [03/Jun/2025:03:07:20 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
10.244.219.64 - - [03/Jun/2025:03:07:38 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.61.1"
hello world

hostPath

==在Kubernetes(k8s)中,HostPath是一种特殊的卷类型,它允许将节点(Node)上的文件或目录直接挂载到Pod中。这种挂载方式使得Pod能够访问宿主机上的文件系统,从而实现了数据的持久化存储,即使Pod被删除或重建,只要宿主机上的文件或目录仍然存在,数据就不会丢失。==

HostPath的配置参数

在Kubernetes中配置HostPath卷时,通常需要指定以下参数:

  • path:指定宿主机上的目录或文件路径,这是必选字段。

  • type(可选):指定节点之上存储类型,包括以下几种:

  • DirectoryOrCreate:如果给定的路径不存在,则创建一个空目录,权限设置为755。

  • Directory:目录必须存在。

  • FileOrCreate:如果给定的文件不存在,则创建一个空文件,权限设置为644。

  • File:文件必须存在。

  • Socket:UNIX套接字,必须存在。

  • CharDevice:字符设备,必须存在。

  • BlockDevice:块设备,必须存在。

HostPath卷适用于以下场景:

  • 需要Pod直接访问宿主机上的特定文件或目录,例如访问Docker内部机制或系统文件。

  • 在某些特定场景下,如运行管理任务的系统级Pod资源,需要访问节点上的特定资源。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@master ~]# vim web1.yaml
---
kind: Pod
apiVersion: v1
metadata:
name: web1
spec:
volumes: # 卷定义
- name: logdata # 卷名称
hostPath: # 资源类型
path: /var/weblog # 宿主机路径
type: DirectoryOrCreate # 目录不存在就创建
containers:
- name: nginx
image: myos:nginx
volumeMounts: # mount 卷
- name: logdata # 卷名称
mountPath: /usr/local/nginx/logs # 容器内路径

[root@master ~]# kubectl apply -f web1.yaml
pod/web1 created
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
web1 1/1 Running 0 45m 10.244.2.16 node02

[root@master ~]# curl http://10.244.2.16/
Nginx is running !

# 删除Pod ,日志数据也不会丢失
[root@master ~]# kubectl delete pod web1
pod "web1" deleted

# 来到 node 上查看日志
[root@node02 ~]# cat /var/weblog/access.log
10.244.0.0 - - [27/Jun/2022:02:00:12 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.29.0"

NFS

==k8s 中允许将 nfs 存储以卷的方式挂载到你的 Pod 中。在删除 Pod 时,nfs 存储卷会被卸载(umount),而不是被删除。nfs 卷可以在不同节点的 Pod 之间共享数据。==

NFS卷的用途

==NFS最大的功能就是在不同节点的不同Pod中共享读写数据。本地 NFS 的客户端可以透明地读写位于远端 NFS 服务器上的文件,就像访问本地文件一样==

示例:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建共享目录,并部署测试页面,这里harbor主机充当nfs服务器
[root@harbor ~]# mkdir -p /var/webroot
[root@harbor ~]# echo "nfs server" >/var/webroot/index.html

# 部署 NFS 服务
[root@harbor ~]# dnf install -y nfs-utils
[root@harbor ~]# vim /etc/exports
/var/webroot 192.168.0.0/16(rw,no_root_squash)
[root@harbor ~]# systemctl enable --now nfs-server.service
#----------------------------------------------------------#
# 所有 node 节点都要安装 nfs 软件包
[root@node ~]# dnf install -y nfs-utils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@master day05]# vim web1.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: test
spec:
volumes:
- name: logdata
hostPath:
path: /var/weblog
type: DirectoryOrCreate
- name: website
nfs:
server: 192.168.10.240 # nfs服务器的地址
path: /var/webroot # nfs共享的目录
containers:
- name: web
image: myos:nginx
volumeMounts:
- name: logdata
mountPath: /usr/local/nginx/logs
- name: website
mountPath: /usr/local/nginx/html # 映射到容器中的路径

[root@master ~]# kubectl apply -f web1.yaml
pod/web1 created

[root@master day05]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test 1/1 Running 0 9s 10.244.147.29 node02 <none> <none>

[root@master day05]# curl 10.244.147.29
nfs server

# 在nfs服务器上添加数据
[root@harbor ~]# echo love you k8s >> /var/webroot/index.html

[root@master day05]# curl 10.244.147.29
nfs server
love you k8s

使用流程

使用K8s Volume的一般流程如下:

  1. 创建存储卷:根据需求选择合适的Volume类型,并创建相应的存储卷资源。

  2. 挂载存储卷:在Pod的配置文件中指定要挂载的存储卷,并将其挂载到Pod中的容器上。

  3. 访问存储卷中的数据:在Pod的容器中,通过挂载路径访问存储卷中的数据。

注意事项

  1. Volume的生命周期与Pod相关,但与容器的生命周期不相关。当Pod被删除时,与其关联的Volume(除非设置为持久化存储)也会被删除。

  2. 在使用网络存储或持久化存储时,需要确保存储系统的稳定性和可靠性,以避免数据丢失或损坏。

  3. 对于敏感数据的存储,建议使用Secret或ConfigMap等机制来保护数据安全。

[!CAUTION]

总之,K8s Volume是K8s中非常重要的一个概念,它为Pod中的容器提供了持久化存储和数据共享的能力。通过合理使用不同类型的Volume和正确的配置方法,可以确保应用程序的稳定性和可靠性。

2.10.9 PV/PVC

在Kubernetes(K8s)中,PV(Persistent Volume)和PVC(Persistent Volume Claim)是两个重要的概念,用于管理集群中的持久化存储资源。以下是对PV和PVC的详细解析:

PV(Persistent Volume)

定义与功能:

  • PV是Kubernetes中用于表示持久化存储资源的API对象。它是一块网络存储,独立于Pod存在,可以是云提供商的存储、NFS、iSCSI、本地存储等多种类型。

  • 管理员负责创建PV,并配置其细节,如容量、访问模式(ReadWriteOnce、ReadOnlyMany、ReadWriteMany)、存储类别等。

  • PV有自己的生命周期,包括可用(Available)、绑定(Bound)、释放(Released)、回收(Retained)等状态。

访问模式

  • ReadWriteOnce(RWO):单个节点读写模式,即卷可以被一个节点以读写方式挂载。

  • ReadOnlyMany(ROX):多个节点只读模式,即卷可以被多个节点以只读方式挂载。

  • ReadWriteMany(RWX):多个节点读写模式,即卷可以被多个节点以读写方式挂载。

  • ReadWriteOncePod(RWOP):卷可以被单个 Pod 以读写方式挂载。 如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用 ReadWriteOncePod 访问模式。这只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本。

PVC(Persistent Volume Claim)

定义与功能:

  • PVC是用户对PV的存储请求。用户在PVC中定义存储的大小、访问模式等需求,而不需要指定具体的PV。

  • 当PVC被创建时,Kubernetes会尝试将其与满足其要求的PV进行绑定。如果没有合适的PV可以绑定,PVC将处于Pending状态,直到有合适的PV可用或动态创建一个新的PV为止。

  • PVC的存在使得Pod与具体的存储实现解耦,提高了可移植性。

工作流程

  1. 用户根据需求创建PVC,声明所需的存储资源规格。

  2. Kubernetes根据PVC中的需求寻找合适的PV进行绑定。

  3. 如果环境支持动态存储配额,当没有合适的PV可用时,可以根据PVC请求动态创建一个新的PV。

  4. Pod在定义中引用PVC,当Pod被调度到节点上时,PV会被挂载到Pod指定的路径上,供Pod使用。

PV与PVC的关系

  • PV和PVC之间的关系是一种动态的匹配和绑定关系。PVC声明了对存储资源的需求,而PV则是提供这些资源的实际载体。

  • 当PVC被创建时,Kubernetes会尝试将其与满足其要求的PV进行绑定。匹配的过程是根据PVC的标签选择器和PV的标签进行匹配,只有匹配成功的PV才能被绑定到PVC。

  • 一旦绑定成功,Pod可以通过PVC访问PV提供的存储资源。如果没有合适的PV可以绑定,PVC将处于Pending状态,直到有合适的PV可用为止。

PV与PVC的关联条件

  • **存储类一致:**如果 PV 指定了存储类,PVC 必须请求相同的存储类,除非 PVC 不指定存储类。
  • **访问模式兼容:**PVC 请求的访问模式必须与 PV 支持的访问模式兼容。
  • **容量足够:**PVC 请求的存储容量不能超过 PV 的容量。
  • **选择器匹配:**如果 PV 定义了选择器(标签),PVC 必须匹配这些选择器才能绑定。
  • **绑定策略:**PV 可以指定绑定策略(动态分配或静态绑定),PVC 必须满足这些策略要求。
  • **状态要求:**PVC 必须处于待处理状态(Pending),PV 必须处于可用状态(Available),才能成功绑定。

==简而言之,PVC 必须符合 PV 的要求,才能成功绑定并使用 PV 提供的持久化存储。==

PV回收策略

  • Retain(保留):手动回收

  • Recycle(回收):基本擦除( 相当于执行了 rm -rf /thevolume/* ),然后 PV 状态重置为Available供重新绑定。

  • Delete(删除):关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除

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

PV状态

卷可以处于以下的某种状态:

  • Available(可用):一块空闲资源还没有被任何声明绑定

  • Bound(已绑定):卷已经被声明绑定

  • Released(已释放):声明被删除,但是资源还未被集群重新声明

  • Failed(失败):该卷的自动回收失败

命令行会显示绑定到 PV 的 PVC 的名称。

示例:
在master部署nfs服务端,node01和node02为客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
yum install -y nfs-common nfs-utils rpcbind
# 使用master上面的主机作nfs磁盘分享
mkdir /nfs
chmod 666 /nfs
chown nfsnobody /nfsdata # 没有nfsnobody 使用nobody

[root@master ~]# vim mkdirnfs.sh
#!/bin/bash
for i in {0..9}
do
mkdir /nfs/$i
echo "$i" > /nfs/$i/index.html
echo "/nfs/$i *(rw,no_root_squash,no_all_squash,sync)" >> /etc/exports
done
# 执行上面的脚本,查看/nfs
[root@master ~]# tree /nfs
/nfs
├── 0
│ └── index.html
├── 1
│ └── index.html
├── 2
│ └── index.html
├── 3
│ └── index.html
├── 4
│ └── index.html
├── 5
│ └── index.html
├── 6
│ └── index.html
├── 7
│ └── index.html
├── 8
│ └── index.html
└── 9
└── index.html

10 directories, 10 files

root@master ~]# cat /etc/exports
/nfs/0 *(rw,no_root_squash,no_all_squash,sync)
/nfs/1 *(rw,no_root_squash,no_all_squash,sync)
/nfs/2 *(rw,no_root_squash,no_all_squash,sync)
/nfs/3 *(rw,no_root_squash,no_all_squash,sync)
/nfs/4 *(rw,no_root_squash,no_all_squash,sync)
/nfs/5 *(rw,no_root_squash,no_all_squash,sync)
/nfs/6 *(rw,no_root_squash,no_all_squash,sync)
/nfs/7 *(rw,no_root_squash,no_all_squash,sync)
/nfs/8 *(rw,no_root_squash,no_all_squash,sync)
/nfs/9 *(rw,no_root_squash,no_all_squash,sync)
[root@master ~]# showmount -e localhost
Export list for localhost:
/nfs/9 *
/nfs/8 *
/nfs/7 *
/nfs/6 *
/nfs/5 *
/nfs/4 *
/nfs/3 *
/nfs/2 *
/nfs/1 *
/nfs/0 *
[root@master ~]# cat /nfs/9/index.html
9

# 验证nfs共享
[root@node01 ~]# mkdir /testnfs
[root@node01 ~]# mount master:/nfs/9 /testnfs
[root@node01 ~]# tree /testnfs/
/testnfs/
└── index.html

0 directories, 1 file
[root@node01 ~]# cat /testnfs/index.html
9
[root@node01 ~]# umount /testnfs
[root@node01 ~]# tree /testnfs/
/testnfs/

0 directories, 0 files

部署pv示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
[root@master test02]# vim pv.yaml 
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv0 # pv 名字
spec:
capacity: # 容量
storage: 0.5Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle # 持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/0 # nfs共享路径
server: 192.168.10.10 # nfs服务器地址
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv1 # pv 名字
spec:
capacity: # 容量
storage: 1Gi # 存储空间
accessModes: # 存储模式
- ReadWriteMany # 多个节点读写模式,即卷可以被多个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle # 持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/1 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv2 # pv 名字
spec:
capacity: # 容量
storage: 1Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle # 持久卷回收策略
storageClassName: nfs1 # 存储类的名字
nfs:
path: /nfs/2 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv3 # pv 名字
spec:
capacity: # 容量
storage: 1Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Retain # 持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/3 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv4 # pv 名字
spec:
capacity: # 容量
storage: 1Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle # 持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/4 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv5 # pv 名字
spec:
capacity: # 容量
storage: 1Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle # 持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/5 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv6 # pv 名字
spec:
capacity: # 容量
storage: 1.5Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle #持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/6 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv7 # pv 名字
spec:
capacity: #容量
storage: 1Gi #存储空间
accessModes: #存储模式
- ReadWriteOnce #单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle #持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/7 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv8 # pv 名字
spec:
capacity: #容量
storage: 1Gi #存储空间
accessModes: #存储模式
- ReadWriteOnce #单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle #持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/8 # nfs共享路径
server: 192.168.10.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv9 # pv 名字
spec:
capacity: # 容量
storage: 1Gi # 存储空间
accessModes: # 存储模式
- ReadWriteOnce # 单个节点读写模式,即卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Recycle # 持久卷回收策略
storageClassName: nfs # 存储类的名字
nfs:
path: /nfs/9 # nfs共享路径
server: 192.168.10.10

[root@master test02]# kubectl apply -f pv.yaml
persistentvolume/nfspv0 created
persistentvolume/nfspv1 created
persistentvolume/nfspv2 created
persistentvolume/nfspv3 created
persistentvolume/nfspv4 created
persistentvolume/nfspv5 created
persistentvolume/nfspv6 created
persistentvolume/nfspv7 created
persistentvolume/nfspv8 created
persistentvolume/nfspv9 created
[root@master test02]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
nfspv0 512Mi RWO Recycle Available nfs <unset> 13s
nfspv1 1Gi RWX Recycle Available nfs <unset> 13s
nfspv2 1Gi RWO Recycle Available nfs1 <unset> 13s
nfspv3 1Gi RWO Retain Available nfs <unset> 13s
nfspv4 1Gi RWO Recycle Available nfs <unset> 13s
nfspv5 1Gi RWO Recycle Available nfs <unset> 13s
nfspv6 1536Mi RWO Recycle Available nfs <unset> 13s
nfspv7 1Gi RWO Recycle Available nfs <unset> 13s
nfspv8 1Gi RWO Recycle Available nfs <unset> 13s
nfspv9 1Gi RWO Recycle Available nfs <unset> 13s

创建服务与pvc示例

本案例是基于StatefuSet控制器的方式创建的pvc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
[root@master test02]# vim pvc.yaml 
---
apiVersion: v1
kind: Service # service类
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
# clusterIP模式没有vip的话,ipvs工作方式就没有负载均衡
# 无头服务专门给StatefulSet使用
clusterIP: None # 没有vip
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet # 有状态服务,数据可能发生改变的服务,如mysql
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx" # 匹配无头服务service nginx
replicas: 5
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: myos:nginx
ports: # 定义端口
- containerPort: 80 # 端口80
name: web # 端口名称web
volumeMounts: # 卷绑定
- name: www # 卷名
mountPath: /usr/local/nginx/html # 卷挂载路径
volumeClaimTemplates: # pvc模版
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ] # 单节点读取
storageClassName: "nfs" # 存储类
resources:
requests:
storage: 1Gi # 存储期望

[root@master test02]# kubectl apply -f pvc.yaml
service/nginx unchanged
statefulset.apps/web configured

# 查看 pvc,每个Pod有一个pvc,是一个一个创建的
[root@master test02]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
www-web-0 Bound nfspv5 1Gi RWO nfs <unset> 8m20s
www-web-1 Bound nfspv7 1Gi RWO nfs <unset> 6m24s
www-web-2 Bound nfspv8 1Gi RWO nfs <unset> 6m20s
www-web-3 Bound nfspv4 1Gi RWO nfs <unset> 6m16s
www-web-4 Bound nfspv3 1Gi RWO nfs <unset> 6m10s

# 查看pv
[root@master test02]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
nfspv0 512Mi RWO Recycle Available nfs <unset> 53m
nfspv1 1Gi RWX Recycle Available nfs <unset> 53m
nfspv2 1Gi RWO Recycle Available nfs1 <unset> 53m
nfspv3 1Gi RWO Retain Bound default/www-web-4 nfs <unset> 53m
nfspv4 1Gi RWO Recycle Bound default/www-web-3 nfs <unset> 53m
nfspv5 1Gi RWO Recycle Bound default/www-web-0 nfs <unset> 53m
nfspv6 1536Mi RWO Recycle Available nfs <unset> 53m
nfspv7 1Gi RWO Recycle Bound default/www-web-1 nfs <unset> 53m
nfspv8 1Gi RWO Recycle Bound default/www-web-2 nfs <unset> 53m
nfspv9 1Gi RWO Recycle Available nfs <unset> 53m

# 获取podIP
[root@master test02]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 10m 10.244.58.193 node02 <none> <none>
web-1 1/1 Running 0 8m15s 10.244.85.193 node01 <none> <none>
web-2 1/1 Running 0 8m11s 10.244.85.194 node01 <none> <none>
web-3 1/1 Running 0 8m7s 10.244.58.195 node02 <none> <none>
web-4 1/1 Running 0 8m1s 10.244.85.195 node01 <none> <none>
[root@master test02]# curl 10.244.58.193
5
# 在 NFS 服务器的 /nfs/5 目录中添加数据,然后通过 nginx 来访问
[root@master test02]# echo hehe >> /nfs/5/index.html
[root@master test02]# curl 10.244.58.193
5
hehe

注意:StatefulSet是有序部署的,当有Pod的PVC没有绑定到一个PV,就会处于Pending状态,后序的Pod也没法创建了

尝试删除 Pod,pv 中的数据不会丢失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@master ~]# kubectl delete pods web-0
pod "web-0" deleted
# 删除pod后,重新创建的pod读取nfs数据,更改后的数据依然存在,pod级别数据持久化
[root@master ~]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 15s 10.244.58.196 node02 <none> <none>
web-1 1/1 Running 0 5h24m 10.244.85.193 node01 <none> <none>
web-2 1/1 Running 0 5h23m 10.244.85.194 node01 <none> <none>
web-3 1/1 Running 0 5h23m 10.244.58.195 node02 <none> <none>
web-4 1/1 Running 0 5h23m 10.244.85.195 node01 <none> <none>
[root@master ~]# curl 10.244.58.196
5
hehe

# pod内部互相访问使用域名
# 域名格式
#podName.headlessSvcName.nsName.svc.cluster.local.
#statefulSetName-num.headlessSvcName.nsName.svc.cluster.local.
[root@master test]# kubectl exec -it web-0 -- bash
[root@web-0 html]# curl http://web-1.nginx.default.svc.cluster.local.
7
[root@web-0 html]# curl http://web-2.nginx.default.svc.cluster.local.
8

删除 StatefulSet 后,pvc 不会自动删除,pv也不会自动释放,需要手动删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 删除 StatefulSet 后,pvc 仍然存在
[root@master test]# kubectl delete statefulset web
statefulset.apps "web" deleted
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
www-web-0 Bound nfspv5 1Gi RWO nfs <unset> 5h53m
www-web-1 Bound nfspv7 1Gi RWO nfs <unset> 5h51m
www-web-2 Bound nfspv8 1Gi RWO nfs <unset> 5h51m
www-web-3 Bound nfspv4 1Gi RWO nfs <unset> 5h51m
www-web-4 Bound nfspv3 1Gi RWO nfs <unset> 5h50m
# 删除 pvc 后,pv 没有自动释放
[root@master test]# kubectl delete pvc --all
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deleted
[root@master test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
nfspv0 512Mi RWO Recycle Available nfs <unset> 6h38m
nfspv1 1Gi RWX Recycle Available nfs <unset> 6h38m
nfspv2 1Gi RWO Recycle Available nfs1 <unset> 6h38m
nfspv3 1Gi RWO Retain Released default/www-web-4 nfs <unset> 6h38m
nfspv4 1Gi RWO Recycle Released default/www-web-3 nfs <unset> 6h38m
nfspv5 1Gi RWO Recycle Released default/www-web-0 nfs <unset> 6h38m
nfspv6 1536Mi RWO Recycle Available nfs <unset> 6h38m
nfspv7 1Gi RWO Recycle Failed default/www-web-1 nfs <unset> 6h38m
nfspv8 1Gi RWO Recycle Released default/www-web-2 nfs <unset> 6h38m
nfspv9 1Gi RWO Recycle Available nfs <unset>

# 手动释放pv
[root@master test]# kubectl edit pv nfspv3
# 将下面的 spec.claimRef 删除
spec:
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: www-web-0
namespace: default
resourceVersion: "619064"
uid: 99cea07e-339e-431c-bcb6-c398c884b29c
# 再次查看 pv nfspv3已经得到释放
[root@master test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
nfspv0 512Mi RWO Recycle Available nfs <unset> 6h41m
nfspv1 1Gi RWX Recycle Available nfs <unset> 6h41m
nfspv2 1Gi RWO Recycle Available nfs1 <unset> 6h41m
nfspv3 1Gi RWO Retain Available nfs <unset> 6h41m
nfspv4 1Gi RWO Recycle Released default/www-web-3 nfs <unset> 6h41m
nfspv5 1Gi RWO Recycle Released default/www-web-0 nfs <unset> 6h41m
nfspv6 1536Mi RWO Recycle Available nfs <unset> 6h41m
nfspv7 1Gi RWO Recycle Failed default/www-web-1 nfs <unset> 6h41m
nfspv8 1Gi RWO Recycle Released default/www-web-2 nfs <unset> 6h41m
nfspv9 1Gi RWO Recycle Available nfs <unset>

2.11 Pod调度策略管理

2.11.1 亲和性

[!TIP]

理解本章内容请先了解Pod调度的特性[点我跳转](###2.6 Pod调度与标签)

在 Kubernetes(k8s)中,亲和性(Affinity) 是一种调度机制,用于控制 Pod 如何分配到节点(Node)或与其他 Pod 共存。它分为两类:

  1. Node Affinity(节点亲和性):定义 Pod 倾向于调度到哪些节点。
  2. Pod Affinity/Anti-Affinity(Pod 亲和性/反亲和性):定义 Pod 倾向于与哪些 Pod 运行在相同或不同的节点/拓扑域。

Node Affinity(节点亲和性)

控制 Pod 调度到符合特定标签的节点上,替代了早期的 nodeSelector,功能更强大。

  • requiredDuringSchedulingIgnoredDuringExecution(硬亲和性):必须满足条件,否则 Pod 无法调度。
  • preferredDuringSchedulingIgnoredDuringExecution(软亲和性):优先满足条件,但不强制。
软亲和示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
[root@master 6.13]# vim 1.affinity_preferred.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: affinity-preferred
spec:
containers:
- name: affinity
image: myos:httpd
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions: # 标签选择器表达式
- key: domain
operator: In
values:
- henan
- beijing

# 我的集群的所有节点都是没有打标签的,都是默认的,然后使用while创建pod后,会发现pod一直被调度到node02节点,虽然没有满足标签的node节点,但是也会根据scheduler的调度策略调度到其他的节点,因为node02节点的资源相比其他节点的资源是多的,所以删除后创建还是调度到了node02节点。
[root@master 6.13]# while true;
> do
> kubectl apply -f 1.affinity_preferred.yaml
> kubectl get pods -o wide
> kubectl delete -f 1.affinity_preferred.yaml
> done
pod/affinity-preferred unchanged
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 1/1 Running 0 71s 10.244.140.83 node02 <none> <none>
pod "affinity-preferred" deleted
pod/affinity-preferred created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 0/1 ContainerCreating 0 0s <none> node02 <none> <none>
pod "affinity-preferred" deleted
pod/affinity-preferred created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 0/1 ContainerCreating 0 0s <none> node02 <none> <none>
pod "affinity-preferred" deleted
pod/affinity-preferred created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 0/1 ContainerCreating 0 0s <none> node02 <none> <none>
pod "affinity-preferred" deleted
pod/affinity-preferred created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 0/1 ContainerCreating 0 0s <none> node02 <none> <none>
pod "affinity-preferred" deleted

# 创建deployment来占用node02的资源
[root@master 6.13]# vim 2.deployment_test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: test
name: test
spec:
replicas: 10
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- image: myos:httpd
name: myos
nodeName: node02 # 把10副本的控制器全调度到node02上
[root@master 6.13]# kubectl apply -f 2.deployment_test.yaml
deployment.apps/test created

# 重建带有节点软亲和的pod
[root@master 6.13]# kubectl replace --force -f 1.affinity_preferred.yaml
pod "affinity-preferred" deleted
pod/affinity-preferred replaced

# 会发现节点软亲和的pod这次被调度到了node03上,因为node02运行着10个pod,所以资源相比其他节点肯定会变少,所以被调度到了node03上
[root@master 6.13]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 1/1 Running 0 1s 10.244.186.212 node03 <none> <none>
test-5f4449bccc-24jb8 1/1 Running 0 32s 10.244.140.103 node02 <none> <none>
test-5f4449bccc-4rqkx 1/1 Running 0 32s 10.244.140.96 node02 <none> <none>
test-5f4449bccc-5cgqf 1/1 Running 0 32s 10.244.140.97 node02 <none> <none>
test-5f4449bccc-6kfl7 1/1 Running 0 32s 10.244.140.100 node02 <none> <none>
test-5f4449bccc-7rb4m 1/1 Running 0 32s 10.244.140.101 node02 <none> <none>
test-5f4449bccc-jlm4v 1/1 Running 0 32s 10.244.140.98 node02 <none> <none>
test-5f4449bccc-kq7qb 1/1 Running 0 32s 10.244.140.104 node02 <none> <none>
test-5f4449bccc-nnj4s 1/1 Running 0 32s 10.244.140.105 node02 <none> <none>
test-5f4449bccc-p2xgh 1/1 Running 0 32s 10.244.140.99 node02 <none> <none>
test-5f4449bccc-sn9jd 1/1 Running 0 32s 10.244.140.102 node02 <none> <none>

# 给node02打一个上面定义的标签中的其中一个标签
[root@master 6.13]# kubectl label nodes node02 domain=henan
node/node02 labeled
[root@master 6.13]# kubectl replace --force -f 1.affinity_preferred.yaml
pod "affinity-preferred" deleted
pod/affinity-preferred replaced
# 这时候软亲和的特性就体现出来了,配置节点软亲和的pod被调度到了node02上面(优先找满足标签的节点,找不到,scheduler再通过策略调度到其他节点)
[root@master 6.13]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-preferred 1/1 Running 0 4s 10.244.140.107 node02 <none> <none>
test-5f4449bccc-24jb8 1/1 Running 0 107s 10.244.140.103 node02 <none> <none>
test-5f4449bccc-4rqkx 1/1 Running 0 107s 10.244.140.96 node02 <none> <none>
test-5f4449bccc-5cgqf 1/1 Running 0 107s 10.244.140.97 node02 <none> <none>
test-5f4449bccc-6kfl7 1/1 Running 0 107s 10.244.140.100 node02 <none> <none>
test-5f4449bccc-7rb4m 1/1 Running 0 107s 10.244.140.101 node02 <none> <none>
test-5f4449bccc-jlm4v 1/1 Running 0 107s 10.244.140.98 node02 <none> <none>
test-5f4449bccc-kq7qb 1/1 Running 0 107s 10.244.140.104 node02 <none> <none>
test-5f4449bccc-nnj4s 1/1 Running 0 107s 10.244.140.105 node02 <none> <none>
test-5f4449bccc-p2xgh 1/1 Running 0 107s 10.244.140.99 node02 <none> <none>
test-5f4449bccc-sn9jd 1/1 Running 0 107s 10.244.140.102 node02 <none> <none>
硬亲和示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@master 6.13]# vim 3.affinity_required.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: affinity-required
spec:
containers:
- name: test
image: myos:httpd
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- SSD
[root@master 6.13]# kubectl apply -f 3.affinity_required.yaml
pod/affinity-required unchanged

# 如果没有找到满足的节点时,节点硬亲和pod的状态会为pending,会一直等待,直到有节点满足要求才会被调度到该节点
[root@master 6.13]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-required 0/1 Pending 0 44s <none> <none> <none> <none>
# 给node01节点打一个节点硬亲和pod所满足要求的标签
[root@master 6.13]# kubectl label nodes node01 disk=SSD
node/node01 labeled
# 查看pod,会发现节点硬亲和pod直接被调度到了node01节点
[root@master 6.13]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
affinity-required 1/1 Running 0 3m2s 10.244.196.141 node01 <none> <none>

Pod Affinity(Pod 亲和性)

让 Pod 倾向于 与满足条件的其他 Pod 部署在同一个拓扑域(如同一节点、同一可用区)。

典型场景

  • 延迟敏感型应用:例如前端 Pod 与缓存 Pod 部署在同一节点,减少网络延迟。
  • 数据本地化:计算 Pod 与数据存储 Pod 同节点,避免跨节点数据传输。
  • 资源复用:多个 Pod 共享本地资源(如 GPU)。

关键字段

  • labelSelector:选择目标 Pod 的标签(下面示例是 app=pod-1)。
  • topologyKey:定义“同一拓扑域”的粒度(如 kubernetes.io/hostname 表示同一节点)。
  • weight:仅用于软规则,优先级权重。
软亲和示例:

==和Node Affinity的软亲和的概念一样(如果有更好,没有就算了)==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@master 6.13]# vim  4.podAffinity_preferred.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: pod-affinity
spec:
containers:
- name: test
image: myos:httpd
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-1
topologyKey: kubernetes.io/hostname

# 如果创建后,没有找到带有app=pod-1标签的pod的话,它会根据scheduler的调度策略自动被调度到合适的节点
# 下面我们创建一个有app=pod-1标签的pod
[root@master 6.13]# kubectl run test --image=myos:httpd
[root@master 6.13]# kubectl label pod test app=pod-1
# 被调度到了node03节点
[root@master 6.13]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
test 1/1 Running 0 17m 10.244.186.217 node03 <none> <none> app=pod-1,run=test

# 创建带有pod软亲和性的pod
[root@master 6.13]# kubectl apply -f 4.podAffinity_preferred.yaml
# 这个时候,带有pod软亲和性的pod,会根据pod的标签来选择该pod要调度到所匹配到的pod所在的节点上面,这里被调度到了node03节点上
[root@master 6.13]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-affinity 1/1 Running 0 13m 10.244.186.218 node03 <none> <none> <none>
test 1/1 Running 0 17m 10.244.186.217 node03 <none> <none> app=pod-1,run=test
硬亲和示例:

==和Node Affinity的软亲和的概念一样(必须要有,没有就不调度)==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@master 6.19]# vim pod_affinity.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: pod-affinity
spec:
containers:
- name: test
image: myos:httpd
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-1
topologyKey: kubernetes.io/hostname
[root@master 6.19]# kubectl apply -f pod_affinity.yaml

# 查看pod的状态时pending的,因为没有满足条件的pod,所以不调度,会一直pending等待的状态, 直到有满足条件的pod为止
[root@master 6.19]# kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-affinity 0/1 Pending 0 5s

# 创建满足标签条件的pod
[root@master 6.19]# kubectl run test --image=myos:httpd
[root@master 6.19]# kubectl label pods test app=pod-1

# 这个时候,带有pod硬亲和性的pod,会根据pod的标签来选择该pod要调度到所匹配到的pod所在的节点上面,这里被调度到了node02节点上
[root@master 6.19]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-affinity 1/1 Running 0 84s 10.244.140.108 node02 <none> <none> <none>
test 1/1 Running 0 48s 10.244.140.106 node02 <none> <none> app=pod-1,run=test

Pod AntiAffinity(Pod反亲和性)

确保 Pod 与特定 Pod 共存在同一节点或拓扑域,提高高可用性,避免资源竞争。

核心参数

  • requiredDuringScheduling...(硬性):必须满足,否则 Pod 无法调度。
  • preferredDuringScheduling...(软性):尽量满足,不保证。
  • labelSelector:匹配要避开哪些 Pod(通过标签)。
  • topologyKey:定义”同一位置”的范围(如 hostname=节点,zone=可用区)。
软反亲和示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@master 6.19]# vim 2.pod_antiaffinity_ruan.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: pod-anti-affinity
spec:
containers:
- name: test
image: myos:httpd
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-2
topologyKey: kubernetes.io/hostname
# 把所匹配的pod分布在每个节点,然后看带有软反亲和的pod会被调度在哪
[root@master 6.19]# kubectl run test01 --image=myos:httpd --overrides='{"spec": {"nodeName": "node01"}}' --labels="app=pod-2"
pod/test01 created
[root@master 6.19]# kubectl run test02 --image=myos:httpd --overrides='{"spec": {"nodeName": "node02"}}' --labels="app=pod-2"
pod/test02 created
[root@master 6.19]# kubectl run test03 --image=myos:httpd --overrides='{"spec": {"nodeName": "node03"}}' --labels="app=pod-2"
pod/test03 created
[root@master 6.19]# kubectl apply -f 2.pod_antiaffinity_ruan.yaml
pod/pod-anti-affinity created

# 创建软反亲和的pod后,因为是软反亲和,所以会根据scheduler调度的策略来调度
[root@master 6.19]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-anti-affinity 1/1 Running 0 15s 10.244.140.114 node02 <none> <none> <none>
test01 1/1 Running 0 102s 10.244.196.152 node01 <none> <none> app=pod-2
test02 1/1 Running 0 92s 10.244.140.112 node02 <none> <none> app=pod-2
test03 1/1 Running 0 85s 10.244.186.233 node03 <none> <none> app=pod-2
硬反亲和示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[root@master 6.19]# vim 3.pod_antiaffinity_ying.yaml 
---
kind: Pod
apiVersion: v1
metadata:
name: pod-antiaffinity-ying
spec:
containers:
- name: test
image: myos:httpd
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod-2
topologyKey: kubernetes.io/hostname
# 查看已有的pod标签和所在节点
[root@master 6.19]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
test01 1/1 Running 0 3h7m 10.244.196.152 node01 <none> <none> app=pod-2
test02 1/1 Running 0 3h7m 10.244.140.112 node02 <none> <none> app=pod-2
test03 1/1 Running 0 3h7m 10.244.186.233 node03 <none> <none> app=pod-2


[root@master 6.19]# kubectl apply -f 3.pod_antiaffinity_ying.yaml
# 因为所有节点上都有在运行满足条件的pod,策略又是硬反亲和性,所以该pod不会被调度状态位pending
[root@master 6.19]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-antiaffinity-ying 0/1 Pending 0 23s <none> <none> <none> <none> <none>
test01 1/1 Running 0 3h15m 10.244.196.152 node01 <none> <none> app=pod-2
test02 1/1 Running 0 3h15m 10.244.140.112 node02 <none> <none> app=pod-2
test03 1/1 Running 0 3h14m 10.244.186.233 node03 <none> <none> app=pod-2

# 删除运行在node01节点上面的满足条件的pod标签
[root@master 6.19]# kubectl label pods test01 app-
# 这个时候带有pod硬反亲和性的pod,就被调度到了node01节点上了
[root@master 6.19]# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-antiaffinity-ying 1/1 Running 0 10m 10.244.196.144 node01 <none> <none> <none>
test01 1/1 Running 0 3h24m 10.244.196.152 node01 <none> <none> <none>
test02 1/1 Running 0 3h24m 10.244.140.112 node02 <none> <none> app=pod-2
test03 1/1 Running 0 3h24m 10.244.186.233 node03 <none> <none> app=pod-2

总结

调度策略 匹配标签 操作符 拓扑域支持 调度目标
nodeAffinity node In,Notin,Exists,DoesNotExist,Gt,Lt 指定主机
podAffinity Pod In,Notin,Exists,DoesNotExist Pod与指定Pod同一拓扑域
podAnitAffinity Pod In,Notin,Exists,DoesNotExist Pod与指定Pod不在同一拓扑域

2.11.1 污点概述

什么是污点

==污点(Taint)是使节点与Pod产生排斥的一类规则==

污点策略是如何实现

污点策略通过嵌合在键值对上的污点标签进行声明

污点标签

  • 尽量不调度:PreferNoSchedule 尽量避免将 Pod 调度到该节点,但不是强制的。

  • 不会被调度:NoSchedule 新创建的 Pod 不会被调度到该节点,已在节点上运行的 Pod 不受影响。

  • 驱逐节点:NoExecute 不仅新创建的 Pod 不会被调度到该节点,已在节点上运行的且无法容忍该污点的 Pod 会被驱逐

2.11.2 管理污点标签

污点标签必须绑定在键值对上,格式为:key=value:[污点标签]

  • 查看污点标签:kubectl describe nodes [节点名字]

  • 设置污点标签:kubectl taint node [节点名字] key=value:污点标签

  • 删除污点标签:kubectl taint node [节点名字] key=value:污点标签-

管理污点标签示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# NoExecute 会删除节点上的 Pod
[root@master ~]# kubectl taint node node01 k=v:NoExecute
node/node01 tainted

# node02 设置污点策略 PreferNoSchedule
[root@master ~]# kubectl taint node node02 k=v:PreferNoSchedule
node/node02 tainted

# node03 设置污点策略 NoSchedule
[root@master ~]# kubectl taint node node03 k=v:NoSchedule
node/node03 tainted

[root@master ~]# kubectl describe nodes |grep Taints
Taints: node-role.kubernetes.io/control-plane:NoSchedule
Taints: k=v:NoExecute
Taints: k=v:PreferNoSchedule
Taints: k=v:NoSchedule

# 删除污点
[root@master ~]# kubectl taint node node-000{1..3} k-
[root@master ~]# kubectl describe nodes |grep Taints
Taints: node-role.kubernetes.io/control-plane:NoSchedule
Taints: <none>
Taints: <none>
Taints: <none>

2.11.3 容忍策略

容忍策略是什么?

容忍刚好与污点相反,某些时候我们需要在有污点的节点上运行Pod,这种无视污点标签的调度方式称为容忍,当节点被设置多个污点(Taints)时,Pod 必须满足**所有污点的容忍(Tolerations)**才能被调度到该节点

示例:

1
2
3
4
5
6
7
8
9
#配置容忍策略示例
spec:
tolerations: # 定义容忍策略
- operator: "Equal" # 匹配方式,必选(Equal,Exists)
key: "kl" # 设置键值对的key,为空时代表任意键值对
value: "vl" # 设置values 的值
effect:"NoSchedule" # 设置容忍的标签,为空时代表所有污点标签
containers:
......

示例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 容忍 k=v1:NoSchedule 污点
[root@master ~]# vim myphp.yaml
......
spec:
tolerations:
- operator: Equal # 完全匹配键值对
key: k # 键
value: v1 # 值
effect: NoSchedule # 污点标签
containers:
......

# 容忍 k=*:NoSchedule 污点
[root@master ~]# vim myphp.yaml
......
spec:
tolerations:
- operator: Exists # 部分匹配,存在即可
key: k # 键
effect: NoSchedule # 污点标签
containers:
......

# 容忍键为k的所有node上的污点
[root@master ~]# vim myphp.yaml
......
spec:
tolerations:
- operator: Exists # 模糊匹配
key: k # 键
effect: "" # 设置空或删除,代表所有污点标签
containers:
......

# 当不指定key值时,表示容忍所有的污点key
[root@master ~]# vim myphp.yaml
......
spec:
tolerations:
- operator: Exists # 模糊匹配
containers:
......

2.11.4 抢占与优先级

优先级是什么?

优先级表示一个Pod相对于其他Pod的重要性。

优先级有什么用?

优先级可以保证重要的Pod被调度运行

如何使用优先级和抢占

  • 配置优先级类PriorityClass

  • 创建Pod时为其设置对应的优先级

PriorityClass简介

  • PriorityClass是一个全局资源对象,它定义了从优先级类名称到优先级整数值的映射。在优先级类中有两个重要选项,分别是 value 和 preemptionPolicy。

  • value是一个整数值,值越大,优先级越高,取值范围是0到1000000000之间。

  • preemptionPolicy 表示在资源不足时候的行为,在队列中等待或者直接抢夺低优先级应用的资源

优先级策略:

  • 抢占策略:PreemptLowerPriority

  • 非抢占策略:Never

描述信息与默认优先级

在PriorityClass中还有两个可选字段,是description和 globalDefault,description用来配置描述性信息,告诉用户优先级的用途,globaIDefault用于设置默认优先级状态,如果没有任何优先级设置Pod 的优先级为0

示例:

创建优先级类和pod设置优先级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
[root@master ~]# vim mypriority.yaml
---
kind: PriorityClass
apiVersion: scheduling.k8s.io/vl
metadata:
name: high-non # 优先级名称
preemptionPolicy: Never # 策略:非抢占
value: 1000 # 优先级

---
kind: PriorityClass
apiVersion: scheduling.k8s.io/v1
metadata:
name: low-non
preemptionPolicy: Never
value: 500

---
kind: PriorityClass
apiVersion: scheduling.k8s.io/v1
metadata:
name: high
preemptionPolicy: PreemptLowerPriority
value: 1000

---
kind: PriorityClass
apiVersion: scheduling.k8s.io/vl
metadata:
name: low # 优先级名称
preemptionPolicy: PreemptLowerPriority # 策略:抢占
value: 500 # 优先级

[root@master ~]# kubectl apply -f mypriority.yaml
[root@master ~]# kubectl get priorityclasses.scheduling.k8s.io
NAME VALUE GLOBAL-DEFAULT AGE
high 1000 false 8s
high-non 1000 false 24m
low 500 false 8s
low-non 500 false 24m
system-cluster-critical 2000000000 false 21d
system-node-critical 2000001000 false 21d

# 无优先级的pod
[root@master ~]# vim php1.yaml
......
spec:
nodeSelector:
kubernetes.io/hostname: node03
containers:
- name: php
image: myos:php-fpm
resources:
requests:
cpu: "1200m"

# 低优先级 Pod
[root@master ~]# vim php2.yaml
......
spec:
nodeSelector:
kubernetes.io/hostname: node03
priorityClassName: low-non # 优先级名称
containers:
- name: php
image: myos:php-fpm
resources:
requests:
cpu: "1200m"

# 高优先级 Pod
[root@master ~]# vim php3.yaml
......
spec:
nodeSelector:
kubernetes.io/hostname: node03
priorityClassName: high-non # 优先级名称
containers:
- name: php
image: myos:php-fpm
resources:
requests:
cpu: "1200m"

验证非抢占优先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@master ~]# kubectl apply -f php1.yaml 
pod/php1 created
[root@master ~]# kubectl apply -f php2.yaml
pod/php2 created
[root@master ~]# kubectl apply -f php3.yaml
pod/php3 created
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
php1 1/1 Running 0 9s
php2 0/1 Pending 0 6s
php3 0/1 Pending 0 4s
[root@master ~]# kubectl delete pod php1
pod "php1" deleted
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
php2 0/1 Pending 0 20s
php3 1/1 Running 0 18s

# 清理实验 Pod
[root@master ~]# kubectl delete pod php2 php3
pod "php2" deleted
pod "php3" deleted

验证抢占优先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 替换优先级策略,?是匹配任意字符
[root@master ~]# sed 's,-non,,' -i php?.yaml

# 默认优先级 Pod
[root@master ~]# kubectl apply -f php1.yaml
pod/php1 created
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
php1 1/1 Running 0 6s

# 高优先级 Pod
[root@master ~]# kubectl apply -f php3.yaml
pod/php3 created

# php1被抢占
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
php3 1/1 Running 0 9s

# 低优先级 Pod
[root@master ~]# kubectl apply -f php2.yaml
pod/php2 created

# 由于php2的优先级低于php3,所以是Pending状态
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
php2 0/1 Pending 0 3s
php3 1/1 Running 0 9s

# 清理实验 Pod
[root@master ~]# kubectl delete pod --all

2.12 用户认证&RBAC授权

2.12.1 用户认证

  • 所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的服务账户和普通用户。

  • 服务账户是给运行在集群中的 Pod 或其他工作负载使用的身份,用于与 Kubernetes API Server 进行交互。例如,Pod 中的应用程序可能需要通过服务账户读取 ConfigMap、访问其他资源或与 API Server 通信。

  • 普通账户是给集群外部的用户或系统使用的身份,例如管理员、开发者或外部服务(如通过 kubectl 操作集群的用户)。

创建服务账户示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# vim admin-user.yaml
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: kube-admin
namespace: kubernetes-dashboard

[root@master ~]# kubectl -n kubernetes-dashboard get sa
NAME SECRETS AGE
default 0 26m
kubernetes-dashboard 0 26m

[root@master ~]# kubectl apply -f admin-user.yaml
serviceaccount/kube-admin created
[root@master ~]# kubectl -n kubernetes-dashboard get serviceaccounts
NAME SECRETS AGE
default 0 16m
kube-admin 0 11s
kubernetes-dashboard 0 16m

创建普通账户的步骤:

普通账户的创建本质是通过外部认证机制向 Kubernetes 注册用户身份,核心步骤为:

  • 生成用户证书(或 Token)。
  • 配置 kubeconfig。
  • 通过 RBAC 分配权限
1. 创建普通用户证书的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master pki]# pwd
/etc/kubernetes/pki
[root@master pki]# vim devuser.json
{
"CN": "devuser", // Common Name,即用户名,这里设置为devuser
"hosts": [], // 证书可用于哪些主机,空表示不限制主机
"key": { // 密钥配置
"algo": "rsa", // 使用RSA算法
"size": 2048 // 密钥长度2048位
},
"names": [ // 证书的详细信息
{
"C": "CN", // 国家(Country)-中国
"ST": "BeiJing", // 州/省(State)-北京
"L": "BeiJing", // 城市(Locality)-北京
"O": "k8s", // 组织(Organization)-k8s
"OU": "System" // 组织单位(Organizational Unit)-System
}
]
}
2. 下载证书生成工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

# 使用cfssl工具生成用户证书
cfssl gencert \
-ca=ca.crt \ # 指定CA证书
-ca-key=ca.key \ # 指定CA私钥
-profile=kubernetes \ # 指定证书用途模板
devuser.json \ # 用户证书的配置文件
| cfssljson -bare devuser # 处理输出并生成文件
3. 设置集群参数
1
2
3
4
5
6
export KUBE_APISERVER="https://192.168.10.100:6443"
kubectl config set-cluster kubernetes \
--certificate-authority=ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=devuser.kubeconfig
4. 设置客户端认证参数
1
2
3
4
5
kubectl config set-credentials devuser \
--client-certificate=devuser.pem \
--client-key=devuser-key.pem \
--embed-certs=true \
--kubeconfig=devuser.kubeconfig
5. 设置上下文参数
1
2
3
4
5
kubectl config set-context devuser@kubernetes \
--cluster=kubernetes \
--user=devuser \
--namespace=dev \ # 指定用户默认的名称空间
--kubeconfig=devuser.kubeconfig
6. 使用上下文
1
2
3
4
5
6
7
8
# 创建名称空间做实验
kubectl create ns dev
# 给devuser用户授权,并指定名称空间为dev
kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev
# 切换上下文
kubectl config use-context devuser@kubernetes --kubeconfig=devuser.kubeconfig
# 用devuser配置 覆盖系统默认配置,*记得备份原有的默认配置*
cp -f ./devuser.kubeconfig /root/.kube/config
7. 验证普通用户
1
2
3
4
5
6
7
# 因为没有在dev名称空间创建pod,所有为空
[root@master ~]# kubectl get pods
No resources found in dev namespace.

# 查看default名称空间的pod,发现被限制了,说明devuser用户不能访问default空间下的资源,因为前面该devuser用户只授权了dev名称空间
[root@master ~]# kubectl get pods -n default
Error from server (Forbidden): pods is forbidden: User "devuser" cannot list resource "pods" in API group "" in the namespace "default"
8. 给系统用户创建集群普通用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建系统用户做实验
useradd dve
# 给系统用户创建一个密码
passwd dev
# 在系统用户家目录创建.kube目录
mkdir /home/dev/.kube
# 修改家目录的属主和属组
chown -R dev.dev /home/dev/.kube
# 将devuser配置文件拷贝到dev系统用户的家目录的.kube里
cp /etc/kubernetes/pki/devuser.kubeconfig /home/dev/.kube/config
# 切换到dev系统用户
su - dev

# 查看结果
[dev@master ~]$ id
uid=1001(dev) gid=1001(dev) 组=1001(dev)

[dev@master ~]$ kubectl get pods
No resources found in dev namespace.
# 访问default空间被限制了,所以验证成功,这样系统用户就和k8s集群普通用户关联起来了。
[dev@master ~]$ kubectl get pods -n default
Error from server (Forbidden): pods is forbidden: User "devuser" cannot list resource "pods" in API group "" in the namespace "default"

角色与授权

如果想访问和管理kubernetes集群,就要对身份以及权限做验证,kubernetes 支持的鉴权模块有 Node、RBAC、ABAC、Webhook API

  • Node:一种特殊用途的鉴权模式,专门对kubelet发出的请求进行鉴权

  • RBAC:是一种基于组织中用户的角色来控制资源使用的方法

  • ABAC:基于属性的访问控制,是一种通过将用户属性与权限组合在一起像用户授权的方法

  • Webhook:是一个HTTP回调。

2.12.2 RBAC授权

==K8s RBAC(Role-Based Access Control,基于角色的访问控制)是K8s中用于控制用户对集群资源访问权限的一种机制。RBAC允许管理员通过定义角色(Role)和角色绑定(RoleBinding)来管理用户对集群资源的访问权限==

image-20250622140946300

角色(Role)与集群角色(ClusterRole)
==在 Kubernetes 中,RBAC(Role-Based Access Control)的核心概念包括角色(Role)和集群角色(ClusterRole),它们用于定义对资源的访问权限。==

角色(Role)

  • Role 是一种 Kubernetes 对象,它定义了对特定命名空间内资源的一组权限。这些权限可以是创建、读取、更新和删除等操作(权限只会增加),针对特定的 API 资源对象(如 Pod、Service、ConfigMap 等)。
  • Role 对象只在特定命名空间内生效,即它所定义的权限只适用于该命名空间内的资源。

集群角色(ClusterRole)

  • ClusterRole 也是一种 Kubernetes 对象,它与 Role 类似,但是作用范围更广泛,可以应用于整个集群,而不限于特定的命名空间。

  • ClusterRole 定义了对集群范围内资源的权限,可以包括所有命名空间中的资源,也可以包括集群级别的资源,例如节点、命名空间等。

[!IMPORTANT]

总的来说,角色(Role)和集群角色(ClusterRole)的作用是相似的,都用于定义对 Kubernetes 资源的访问权限。它们的区别主要在于作用范围:Role 仅作用于特定命名空间内的资源,而 ClusterRole 则作用于整个集群的资源

角色绑定(RoleBinding)与集群角色绑定(ClusterRoleBinding)
==在 Kubernetes 中,角色绑定(RoleBinding)和集群角色绑定(ClusterRoleBinding)用于将用户、组或服务账户与角色或集群角色关联起来,从而赋予它们相应的权限。它们的作用是将权限分配给特定的实体,使其能够执行定义在角色或集群角色中的操作。==

角色绑定(RoleBinding)

RoleBinding 是一种 Kubernetes 对象,用于将特定的角色(Role)与特定的用户、组或服务账户绑定在一起,从而赋予它们在某个命名空间内的资源上执行操作的权限。

集群角色绑定(ClusterRoleBinding)

ClusterRoleBinding 类似于 RoleBinding,但是作用范围更广泛,它用于将集群角色(ClusterRole)与用户、组或服务账户绑定在一起,赋予它们在整个集群范围内执行操作的权限。

资源对象角色与作用域

资源对象 描述 作用域
ServiceAccount 服务账号,为 Pod 中运行的进程提供了一个身份 单一名称空间
Role 角色,包含一组代表相关权限的规则 单一名称空间
ClusterRole 角色,包含一组代表相关权限的规则 全集群
RoleBinding 将权限赋予用户,Role、ClusterRole 均可使用 单一名称空间
ClusterRoleBinding 将权限赋予用户,只可以使用 ClusterRole 全集群

资源对象权限

create delete deletecollection get list patch update watch
创建 删除 删除集合 获取属性 获取列表 补丁 更新 监控

主体(Subject)

==在 Kubernetes 中,主体(Subject)是指具有身份的实体,通常是用户、组或服务账户。主体通过角色绑定(RoleBinding)或集群角色绑定(ClusterRoleBinding)与角色或集群角色关联在一起,从而获取对 Kubernetes 资源的访问权限。==

image-20250622141751660

主体可以是以下几种类型之一:

  • **用户:**Kubernetes 可以与外部身份验证服务集成,以允许使用基于用户名和密码的身份验证机制登录到集群中。登录成功后,用户将被视为主体,并根据其被分配的角色获得相应的权限。
  • **组:**在某些情况下,将一组用户组织在一起,并为整个组分配权限可能更为方便。在 Kubernetes 中,可以通过角色绑定或集群角色绑定将组与角色或集群角色关联起来,从而将权限分配给组内的所有成员。
  • **服务账户:**服务账户是 Kubernetes 中的一种特殊类型的身份,用于表示正在运行的容器或 Pod。它们通常用于实现应用程序和其他 Kubernetes 部署的自动化任务。通过为服务账户分配适当的角色或集群角色,可以确保它们具有执行所需操作所需的权限。

总之,主体在 Kubernetes 中代表了具有身份的实体,通过角色绑定或集群角色绑定与角色或集群角色相关联,从而获得对 Kubernetes 资源的访问权限。

自定义角色和角色绑定示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[root@master ~]# vim myrole.yaml 
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: myrole
namespace: default
rules: # 定义规则
- apiGroups: # 资源对象所属组信息
- "" # 分组信息
resources: # 要设置权限的资源对象
- pods # 授权资源对象名称
verbs: # 权限设置
- get # 权限
- list # 权限

# 给上面创建的服务账户授普通用户权限
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kube-admin-role
namespace: default
roleRef: # 关联权限
apiGroup: rbac.authorization.k8s.io # 角色对象组
kind: Role # 角色对象
name: myrole # 角色名称
subjects: # 授权信息
- kind: ServiceAccount # 账号资源对象
name: kube-admin # 账号名称
namespace: kubernetes-dashboard # 账号所在的名称空间

[root@master ~]# kubectl apply -f myrole.yaml
role.rbac.authorization.k8s.io/myrole created
rolebinding.rbac.authorization.k8s.io/kube-admin-role created
[root@master ~]# kubectl describe role myrole
Name: myrole
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] servi [] [get list]
[root@master ~]# kubectl describe rolebindings.rbac.authorization.k8s.io
Name: kube-admin-role
Labels: <none>
Annotations: <none>
Role:
Kind: Role
Name: myrole
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount kube-admin kubernetes-dashboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 查看集群角色,cluster-admin集群角色相当于Linux中的root管理员
[root@master ~]# kubectl get clusterrole | grep cluster-admin
cluster-admin 2025-01-14T11:22:09Z
[root@master ~]# vim admin-user.yaml
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: kube-admin
namespace: kubernetes-dashboard

# 给上面创建的服务账户授管理员权限
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-admin-role
roleRef: # 关联权限
apiGroup: rbac.authorization.k8s.io # 角色对象组
kind: ClusterRole # 角色对象
name: cluster-admin # 角色名称
subjects: # 授权信息
- kind: ServiceAccount # 账号资源对象
name: kube-admin # 账号名称
namespace: kubernetes-dashboard # 账号所在的名称空间

[root@master ~]# kubectl apply -f admin-user.yaml
serviceaccount/kube-admin unchanged
clusterrolebinding.rbac.authorization.k8s.io/kube-admin-role created

[root@master ~]# kubectl describe clusterrole | grep -A 9 cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
[*] [] [*]

[root@master ~]# kubectl describe clusterrolebindings.rbac.authorization.k8s.io | grep -A 8 kube-admin
Name: kube-admin-role
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: cluster-admin
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount kube-admin kubernetes-dashboard

2.13 Helm包管理工具

2.13.1 Helm的简介

在没使用 helm 之前,向 kubernetes 部署应用,我们要依次部署 deployment、svc 等,步骤较繁琐。 况且随着很多项目微服务化,复杂的应用在容器中部署以及管理显得较为复杂,helm 通过打包的方式,支持发布的版本管理和控制, 很大程度上简化了 Kubernetes 应用的部署和管理。

==Helm 本质就是让 K8s 的应用管理(Deployment、Service 等)可配置,可以通过类似于传递环境变量的方式能动态生成。==通过动态生成 K8s 资源清单文件(deployment.yaml、service.yaml)。然后调用 Kubectl 自动执行 K8s 资源部署。

Helm 是官方提供的类似于 YUM 的包管理器,是部署环境的流程封装。

2.13.2 Helm 中三个重要的概念

  • **Chart:**Helm 的软件包,采用 tar 格式。类似于 APT 的 DEB 包或者 YUM 的 RPM 包,其包含了一组定义 Kubernetes 资源相关的 YAML 文件。
  • **Repository(仓库):**Helm 的软件仓库,Repository 本质上是一个 Web 服务器,该服务器保存了一系列的 Chart 软件包以供用户下载,并且提供了一个该 Repository 的 Chart 包的清单文件以供查询。Helm 可以同时管理多个不同的 Repository。
  • **Release:**使用 helm install 命令在 Kubernetes 集群中部署的 Chart 称为 Release。可以理解为 Helm 使用 Chart 包部署的一个应用实例。一个 chart 通常可以在同一个集群中安装多次。每一次安装都会创建一个新的 release

以 MySQL chart 为例,如果你想在你的集群中运行两个数据库,你可以安装该 chart 两次。每一个数据库都会拥有它自己的 release 和 release name。可以将 release 想象成应用程序发布的版本号。

2.13.3 Helm3 与 Helm2 的区别

Helmv2是C/S 架构,主要分为客户端helm 和服务器端tiller。而由于 RBAC 等权限控制体系的逐渐完善,多租户和安全的需求日益兴起,tiller变得越来越不安全,社区在权限控制领域遇到了极大的阻碍。所以在Helm3 版本中,直接将tiller 这一核心组件移除,helm 直接和kubernetes API 进行通信。直接带来的好处如下:

  • Helm的架构变的更为简单和灵活
  • 不再需要创建ServiceAccount,直接使用当前环境中的kubeconfig配置
  • 可以直接和kubernetesAPl交互,更为安全
  • 不再需要使用helminit来进行初始化

2.13.4 Helm常用命令

功能分类 命令示例 使用场景 核心参数
基础操作 helm install [RELEASE] [CHART] 部署应用到 K8s 集群(如 helm install myapp bitnami/nginx -f values.yaml:自定义配置文件 --set key=value:临时覆盖值 --dry-run:模拟安装不执行
helm upgrade [RELEASE] [CHART] 升级应用版本或配置(如 helm upgrade myapp bitnami/nginx --version 15.3.1 --version x.y.z:指定 Chart 版本 --reset-values:重置非默认配置值
helm uninstall [RELEASE] 卸载应用(如 helm uninstall myapp -n prod --keep-history:保留历史记录 -n NAMESPACE:指定命名空间
Release 管理 helm list 查看已部署的应用列表(如 helm ls -a -n dev -a/--all:显示所有状态的 Release -n NAMESPACE:指定命名空间
helm status [RELEASE] 查看应用详细状态(如 helm status myapp -o yaml -o json/yaml:输出 JSON/YAML 格式
helm history [RELEASE] 查看应用升级 / 回滚历史(如 helm history myapp --max=5 --max=10:显示最近 10 次历史
helm rollback [RELEASE] [REVISION] 回滚到指定版本(如 helm rollback myapp 2
Chart 操作 helm repo add [NAME] [URL] 添加 Chart 仓库(如 helm repo add bitnami https://charts.bitnami.com/bitnami --username/--password:认证信息 --force-update:强制更新索引
helm repo update 更新本地仓库索引(获取最新 Charts 列表)
helm search repo [KEYWORD] 搜索仓库中的 Charts(如 helm search repo nginx --versions --version x.y.z:指定版本过滤 -l/--versions:显示所有版本
helm pull [CHART] 下载 Chart 到本地(如 helm pull bitnami/nginx --untar --untar:解压到当前目录 --destination /path:指定下载路径
配置与调试 helm show values [CHART] 查看 Chart 默认配置(如 helm show values bitnami/nginx
helm template [RELEASE] [CHART] 本地渲染模板(不部署),用于调试 YAML(如 helm template myapp bitnami/nginx -f values.yaml:自定义配置 --show-only templates/deployment.yaml:仅显示特定模板
helm lint [CHART_DIR] 检查 Chart 语法和结构(如 helm lint ./mychart
插件扩展 helm plugin install [URL] 安装插件(如 helm plugin install https://github.com/databus23/helm-diff
helm plugin list 列出已安装插件

2.13.5 集群部署Helm & 实操

安装Helm

1
2
3
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

添加chart仓库

当您已经安装好了Helm之后,您可以添加一个chart 仓库。

1
2
3
4
5
6
7
8
9
10
11
# 添加chart仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
# 当添加完成,您将可以看到可以被您安装的 charts 列表
helm search repo bitnami
# 更新charts列表
helm repo update

# 查看repo仓库
helm repo ls
# 移除仓库
helm repo remove <仓库名称>

安装chart示例

1
2
3
4
5
6
7
8
# 安装一个apache的chart包
helm search repo apache # Helm 搜索使用模糊字符串匹配算法
helm install web bitnami/apache # release名为web
helm install bitnami/apache --generate-name # 随机名

# 查看已部署的应用,也就是查看release
helm list
helm show chart bitnami/apache # 查看chart的基本信息

卸载chart示例

helm v2 版本中,当一个 release 被删除,会保留一条删除记录。而在 Helm 3 中,删除也会移除 release 的记录。 如果你想保留删除记录,使用 helm uninstall --keep-history 。使用 helm list --uninstalled 只会展示使用了 --keep-history 删除的 release
helm list --all 会展示 Helm 保留的所有 release 记录,包括失败或删除的条目(指定了 --keep-history

1
2
3
4
5
# 卸载已部署的名为web的release
helm uninstall web # 该命令会从Kubernetes卸载web它将删除和该版本相关的所有相关资源(service、deployment、 pod等)甚至版本历史
--keep-history # 该选项, Helm 将会保存版本历史

helm status web # 查看该版本的信息

安装前自定义chart

安装过程中有两种方式传递配置数据

  • --values (或 -f ):使用 YAML 文件覆盖配置。可以指定多次,优先使用最右边的文件
  • --set:通过命令行的方式对指定项进行覆盖

如果同时使用两种方式,则--set 中的值会被合并到 --values 中,但是 --set 中的值优先级更高。在 --set 中覆盖的内容会被被保存在 ConfigMap 中。可以通过 helm get values <release-name> 来查看指定 release 中 --set 设置的值。也可以通过运行 helm upgrade 并指定 --reset-values 字段来清除 --set 中设置的值

1
2
3
4
5
6
7
8
9
10
11
# 查看 chart 中的可配置选项
helm show values bitnami/apache

# 使用 YAML 格式的文件覆盖上述任意配置项,并在安装过程中使用该文件
vim values.yaml
service:
type: NodePort
 
helm install web -f values.yaml bitnami/apache
# 查看svc,service的类型就是NOdePort类型了
kubectl get svc

下载chart包到本地

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 下载apache的chart包
[root@master 7.2]# helm pull bitnami/apache
[root@master 7.2]# ls
apache-11.3.18.tgz
[root@master 7.2]# tar xf apache-11.3.18.tgz
[root@master 7.2]# ls
apache apache-11.3.18.tgz

# 目录里的templates存放K8s部署资源模板,Chart.yaml:包含chart的基本信息(版本、名称等)
[root@master 7.2]# ls apache
Chart.lock Chart.yaml README.md values.schema.json
charts files templates values.yaml

[root@master 7.2]# vim 1.values.yaml
image:
registry: harbor:443
repository: library/apache
tag: 2.4.63
global:
security:
allowInsecureImages: true # 显式允许非标准镜像
service:
type: ClusterIP
[root@master 7.2]# helm install test -f 1.values.yaml apache/.

helm upgrade升级

当你想升级到 chart 的新版本,或是修改 release 的配置,你可以使用 helm upgrade 命令。Helm 会尝试执行最小侵入式升级。即它只会更新自上次发布以来发生了更改的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 创建配置项,然后在安装chart的时候,指定该yaml配置项
[root@master 7.2]# vim 1.values.yaml
image:
registry: harbor:443
repository: library/apache
tag: 2.4.63
global:
security:
allowInsecureImages: true # 显式允许非标准镜像
service:
type: ClusterIP

# 安装chart包,并使用-f选项修改配置项
[root@master 7.2]# helm install web -f 1.values.yaml bitnami/apache
# 部署完后,查看svc类型是不是被修改成了ClusterIP
[root@master 7.2]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 63d
web-apache ClusterIP 10.245.134.139 <none> 80/TCP,443/TCP 50s

# 再次创建配置项,来修改svc的类型
[root@master 7.2]# vim 2.values.yaml
service:
type: NodePort
# 使用upgrade子命令来升级web这个release的svc类型
[root@master 7.2]# helm upgrade -f 2.values.yaml web bitnami/apache
# 升级后,查看svc的类型就变成了NodePort
[root@master 7.2]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 63d
web-apache NodePort 10.245.134.139 <none> 80:31221/TCP,443:32027/TCP 2m

# 看看配置值是否真的生效了
[root@master 7.2]# helm get values web
USER-SUPPLIED VALUES:
service:
type: NodePort

helm rollback回滚

现在,假如在一次发布过程中,发生了不符合预期的事情,也很容易通过 helm rollback [RELEASE] [REVISION]命令回滚到之前的发布版本
release 版本其实是一个增量修订(revision)。 每当发生了一次安装、升级或回滚操作,revision 的值就会加1。第一次 revision 的值永远是1。我们可以使用 helm history [RELEASE] 命令来查看一个特定 release 的修订版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看历史版本
[root@master 7.2]# helm history web
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Wed Jul 2 16:05:50 2025 superseded apache-11.3.182.4.63 Install complete
2 Wed Jul 2 16:07:39 2025 deployed apache-11.3.182.4.63 Upgrade complete

# 回滚到1版本
[root@master 7.2]# helm rollback web 1
Rollback was a success! Happy Helming!
# 回滚后,查看svc的类型就变成了1版本的ClusterIP类型
[root@master 7.2]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 63d
web-apache ClusterIP 10.245.134.139 <none> 80/TCP,443/TCP 93m
# 再次查看历史版本
[root@master 7.2]# helm history web
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Wed Jul 2 16:05:50 2025 superseded apache-11.3.18 2.4.63 Install complete
2 Wed Jul 2 16:07:39 2025 superseded apache-11.3.18 2.4.63 Upgrade complete
3 Wed Jul 2 17:39:17 2025 deployed apache-11.3.18 2.4.63 Rollback to 1

创建一个自己的chart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 创建一个模板
[root@master 7.2]# helm create mychart
Creating mychart
[root@master 7.2]# ls
1.values.yaml 2.values.yaml apache apache-11.3.18.tgz mychart
[root@master 7.2]# tree mychart/
mychart/
├── charts # 子 Chart(依赖的其他 Charts)
├── Chart.yaml # Chart 元数据(名称、版本、依赖等)
├── templates # Kubernetes 资源模板(YAML 文件)
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt # 安装后的提示信息
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│   └── test-connection.yaml
└── values.yaml # 默认配置(可被用户覆盖)

3 directories, 10 files

[root@master 7.2]# cd mychart/
# 删除模板中的文件,我们自己创建
[root@master mychart]# rm -fr values.yaml templates/*
=======================================================
[root@master mychart]# vim templates/NOTES.txt
1、这是一个测试的 myapp chart
2、myapp release 名字:myapp-test-{{ now | date "20060102030405" }}-deploy
3、service 名字:myapp-test-{{ now | date "20060102030405" }}-svc
=======================================================
[root@master mychart]# vim templates/svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: mychart
name: mychart-test-{{ now | date "20060102030405" }}-svc
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
{{- if eq .Values.service.type "NodePort" }}
nodePort: {{ .Values.service.nodeport }}
{{- end }}
selector:
app: mychart
type: {{ .Values.service.type | quote }}
=======================================================
[root@master mychart]# vim templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mychart
name: mychart-test-{{ now | date "20060102030405" }}-deploy
spec:
replicas: {{.Values.replicaCount}}
selector:
matchLabels:
app: my-dep
template:
metadata:
labels:
app: my-dep
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: mychart
=======================================================
[root@master mychart]# vim values.yaml
# Default values for myapp.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 5
image:
repository: library/myos
tag: httpd
service:
type: NodePort
nodeport: 31111
========================================================
# 创建自己的chart,并指定参数配置项
[root@master mychart]# helm install mychart -f values.yaml ../mychart/
NAME: mychart
LAST DEPLOYED: Wed Jul 2 21:58:59 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1、这是一个测试的 myapp chart
2、myapp release 名字:myapp-test-20250702095859-deploy
3、service 名字:myapp-test-20250702095859-svc

# 查看release
[root@master mychart]# helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
mychart default 1 2025-07-02 21:58:59.099379134 +0800 CSdeployed mychart-0.1.0 1.16.0
test default 1 2025-07-02 18:16:54.973706955 +0800 CSdeployed apache-11.3.18 2.4.63
web default 3 2025-07-02 17:39:17.204102761 +0800 CSdeployed apache-11.3.18 2.4.63

# 查看svc类型是不是我们上面指定NodePort类型的31111端口
[root@master mychart]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 63d
mychart-test-20250702095859-svc NodePort 10.245.144.95 <none> 80:31111/TCP 20s
test-apache ClusterIP 10.245.228.226 <none> 80/TCP,443/TCP 3h42m
web-apache ClusterIP 10.245.134.139 <none> 80/TCP,443/TCP 5h53m
# 查看deployment是不是5副本的
[root@master mychart]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
mychart-test-20250702095859-deploy 5/5 5 5 33s
test-apache 1/1 1 1 3h42m
web-apache 1/1 1 1 5h53m
# 查看pod的状态是不是running运行的状态
[root@master mychart]# kubectl get pods
NAME READY STATUS RESTARTS AGE
mychart-test-20250702095859-deploy-7f4bf7d6bf-4wz8h 1/1 Running 0 38s
mychart-test-20250702095859-deploy-7f4bf7d6bf-fh4wz 1/1 Running 0 38s
mychart-test-20250702095859-deploy-7f4bf7d6bf-j7thf 1/1 Running 0 38s
mychart-test-20250702095859-deploy-7f4bf7d6bf-ktzp9 1/1 Running 0 38s
mychart-test-20250702095859-deploy-7f4bf7d6bf-qw8z7 1/1 Running 0 38s
test-apache-5646477d78-r8szb 1/1 Running 1 (77m ago) 3h42m
web-apache-7d8c6d8cbb-dllfl 1/1 Running 1 (77m ago) 5h53m

image-20250702222803616

2.14 Ingress-nginx

2.14.1 ingress-nginx是什么?

它是 Kubernetes 的官方入口网关,用 NGINX 实现七层(HTTP/HTTPS)流量管理,==自动将域名和路径路由到集群内不同服务。==


核心功能

  1. 统一入口
    • 通过单个IP+端口(80/443)暴露所有HTTP服务,替代多个NodePort/LoadBalancer
  2. 智能路由
    • 根据 域名Host)和 路径Path)转发请求到后端Service。
  3. 自动化
    • 监听K8s API,动态更新NGINX配置,无需手动重启。

解决了什么问题?

  • 痛点:直接用Service的NodePortLoadBalancer时:
    • ❌ 无法基于域名路由
    • ❌ 需维护多个IP/端口
    • ❌ 手动管理TLS证书
  • 方案:Ingress-NGINX 通过声明式配置(YAML)一键搞定。

2.14.2 为什么引入Ingress?

我们说k8s 的服务(service)时,说暴露了service的三种方式ClusterIP、NodePort与LoadBalance,这几种方式都是在service的维度提供的,service的作用体现在两个方面,对集群内部,它不断跟踪pod的变化,更新endpoint中对应pod的对象,提供了ip不断变化的pod的服务发现机制,对集群外部,他类似负载均衡器,可以在集群内外部对pod进行访问。但是,单独用service暴露服务的方式,在实际生产环境中不太合适:

  • ClusterIP的方式只能在集群内部访问。

  • NodePort方式的话,测试环境使用还行,当有几十上百的服务在集群中运行时,NodePort的端口管理是灾难。

  • LoadBalance方式受限于云平台,且通常在云平台部署ELB还需要额外的费用。

所幸k8s还提供了一种集群维度暴露服务的方式,也就是ingress。==ingress可以简单理解为service的service,他通过独立的ingress对象来制定请求转发的规则,把请求路由到一个或多个service中。这样就把服务与请求规则解耦了,可以从业务维度统一考虑业务的暴露,而不用为每个service单独考虑。==

service通过iptables、ipvs实现。有缺陷:每一组业务都要开启nodePort,这样nodePort越来越多越来越难管理

解决方案:多加一层管理庞大的service

  • 本来nodePort是映射到Service的,现在在往上抽一层,映射到上层,由上层再分发到service,此方案Ingress实现了
  • 而Ingress是K8S中的资源,接口,可以由不同的控制器实现
  • Ingress-nginx是其中的一种实现,是对nginx的二次开发,来满足Ingress的规范ingress-nginx官方讲解

image-20250705134253402

2.14.3 基于Helm部署ingress-nginx

官方地址: https://github.com/kubernetes/ingress-nginx

确保自己的harbor仓库有这两个镜像,可以在国内的渡渡鸟镜像网站里下载

  • 控制器镜像(controller):Ingress-Nginx 的核心组件,负责监听 Kubernetes API 并根据 Ingress 规则配置负载均衡器。它处理所有进入集群的 HTTP/HTTPS 流量,并将请求路由到对应的 Service。
  • kube-webhook-certgen 镜像(可选):用于自动生成和管理 Ingress-Nginx 的 admission webhook 所需的 TLS 证书。如果启用了 webhook 验证功能(默认启用),则需要此镜像。

image-20250706182600179

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@master 7.6]# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
[root@master 7.6]# helm pull ingress-nginx/ingress-nginx
[root@master 7.6]# ls
ingress-nginx-4.12.3.tgz
[root@master 7.6]# tar -xf ingress-nginx-4.12.3.tgz
[root@master 7.6]# ls
ingress-nginx ingress-nginx-4.12.3.tgz

# 修改values.yaml文件
1- 修改global.image.registry=harbor:443
2- 修改image的拉取地方为harbor仓库里的镜像,tag也是
3- 修改 hostNetwork 的值为 true ,共享宿主机网络
4- dnsPolicy的值改为: clusterFirstWithHostNet
5- kind类型更改为:DaemonSet,启冗余作用
6- 关闭所有镜像的 digest
7- ingressClassResource.default=true

# 创建一个名称空间为ingress
[root@master 7.6]# kubectl create ns ingress
# 部署ingress-nginx
[root@master 7.6]# helm install ingress-nginx -f ingress-nginx/values.yaml -n ingress ingress-nginx/
# 查看pod状态,为running就部署成功!
[root@master 7.6]# kubectl get pods -n ingress -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-controller-8lsvd 1/1 Running 0 3m37s 192.168.10.12 node02 <none> <none>
ingress-nginx-controller-hhzmz 1/1 Running 0 3m37s 192.168.10.13 node03 <none> <none>
ingress-nginx-controller-lqxlj 1/1 Running 0 3m37s 192.168.10.11 node01 <none>

2.14.4 Ingress HTTP 代理访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[root@master 7.6]# vim 1.ingress_http.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: ingress-http-www1
spec:
replicas: 2
selector:
matchLabels:
hostname: www1
template:
metadata:
labels:
hostname: www1
spec:
containers:
- name: web
image: myos:httpd
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: ingress-http-www1
spec:
ports:
- port: 80
targetPort: 80
selector:
hostname: www1
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ingress-http-www1
spec:
ingressClassName: nginx # 关联到ingress-nginx控制器
rules: # ingress规则定义
- host: www1.zkbr.com # 定义域名
http: # 使用的协议
paths: # 定义访问的路径
- path: / # 访问的url路径
pathType: Prefix # 路径的类型(Exact、Prefix)
backend: # 定义后端服务
service: # 声明svc
name: ingress-http-www1 # svc的名称
port: # 声明端口号
number: 80 # 访问服务的端口号
[root@master 7.6]# kubectl apply -f 1.ingress_http.yaml
[root@master 7.6]# kubectl get pods
NAME READY STATUS RESTARTS AGE
ingress-http-www1-c8bd7b98-6pptq 1/1 Running 0 5s
ingress-http-www1-c8bd7b98-r9zg4 1/1 Running 0 6s
[root@master 7.6]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-http-www1 ClusterIP 10.245.44.237 <none> 80/TCP 15s
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 67d
[root@master 7.6]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-http-www1 nginx www1.zkbr.com 80 19s

测试环境要配置host域名解析

image-20250706205150221

image-20250706210209844

访问域名,ingress会根据定义的规则来调用后端的svc响应

1
2
# hostNetwork 模式的访问链路
用户 → DNS 解析 → 宿主机 IP:80/443(直接绑定 ingress-nginx-controller Pod) → Nginx 匹配 Ingress 规则 → 转发到后端 Service → 后端 Pod → 响应返回给用户

image-20250706210313747

基于ingress-nginx创建多虚拟主机的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
[root@master 7.6]# vim 2.ingress_http.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: ingress-http-www2
spec:
replicas: 2
selector:
matchLabels:
hostname: www2
template:
metadata:
labels:
hostname: www2
spec:
containers:
- name: web
image: myos:httpd
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: ingress-http-www2
spec:
ports:
- port: 80
targetPort: 80
selector:
hostname: www2
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ingress-http-www2
spec:
ingressClassName: nginx
rules:
- host: www2.zkbr.com # 也可以创建多个host来实现多虚拟主机(推荐)
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ingress-http-www2
port:
number: 80
[root@master 7.6]# kubectl apply -f 2.ingress_http.yaml
[root@master 7.6]# kubectl get pods
NAME READY STATUS RESTARTS AGE
ingress-http-www1-c8bd7b98-6pptq 1/1 Running 0 30m
ingress-http-www1-c8bd7b98-r9zg4 1/1 Running 0 30m
ingress-http-www2-786b6bcd67-h2gnx 1/1 Running 0 10s
ingress-http-www2-786b6bcd67-jxmpj 1/1 Running 0 10s
[root@master 7.6]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-http-www1 ClusterIP 10.245.44.237 <none> 80/TCP 34m
ingress-http-www2 ClusterIP 10.245.60.88 <none> 80/TCP 3m50s
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 67d
[root@master 7.6]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-http-www1 nginx www1.zkbr.com 80 34m
ingress-http-www2 nginx www2.zkbr.com 80 3m54s

添加解析记录

image-20250706211507155

访问www2.zkbr.com/info.php

image-20250706211840279

2.14.5 Ingress HTTPS 代理访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 生成测试环境中的私钥和证书,生产环境中肯定是要去购买
[root@master 7.7]# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
Generating a RSA private key
..............+++++
...................................+++++
writing new private key to 'tls.key'
-----
[root@master 7.7]# ls
tls.crt tls.key
# 创建tls类型的secret,并指定私钥和证书
[root@master 7.7]# kubectl create secret tls ingress-nginx-tls --key tls.key --cert tls.crt

# 编写资源清单文件
[root@master 7.7]# vim 1.ingress-nginx-https.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-http-ssl
spec:
replicas: 2
selector:
matchLabels:
app: ingress-http-ssl
template:
metadata:
labels:
app: ingress-http-ssl
spec:
containers:
- image: myos:httpd
name: myos
---
apiVersion: v1
kind: Service
metadata:
name: ingress-http-ssl
spec:
ports:
- port: 80
targetPort: 80
selector:
app: ingress-http-ssl
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ingress-http-ssl
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # 强制 HTTPS 重定向,将所有 HTTP 请求(端口 80)重定向到 HTTPS(端口 443)
spec:
ingressClassName: nginx
tls:
- hosts:
- ssl.zkbr.com
secretName: ingress-nginx-tls
rules:
- host: ssl.zkbr.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ingress-http-ssl
port:
number: 80
[root@master 7.7]# kubectl apply -f 1.ingress-nginx-https.yaml
[root@master 7.7]# kubectl get pods
NAME READY STATUS RESTARTS AGE
ingress-http-ssl-678cf7dcf6-29p2b 1/1 Running 0 6m6s
ingress-http-ssl-678cf7dcf6-wc87h 1/1 Running 0 6m6s
ingress-http-www1-c8bd7b98-6pptq 1/1 Running 1 (12h ago) 24h
ingress-http-www1-c8bd7b98-r9zg4 1/1 Running 1 (12h ago) 24h
ingress-http-www2-786b6bcd67-h2gnx 1/1 Running 1 (12h ago) 24h
ingress-http-www2-786b6bcd67-jxmpj 1/1 Running 1 (12h ago) 24h
[root@master 7.7]# kubectl get svc,ingress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-http-ssl ClusterIP 10.245.100.178 <none> 80/TCP 6m29s
service/ingress-http-www1 ClusterIP 10.245.44.237 <none> 80/TCP 24h
service/ingress-http-www2 ClusterIP 10.245.60.88 <none> 80/TCP 24h
service/kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 68d

NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/ingress-http-ssl nginx ssl.zkbr.com 80, 443 6m29s
ingress.networking.k8s.io/ingress-http-www1 nginx www1.zkbr.com 80 24h
ingress.networking.k8s.io/ingress-http-www2 nginx www2.zkbr.com 80 24h

测试环境添加域名解析记录

image-20250707213237375

image-20250707213425552

2.14.6 Ingress basicAuth 代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
[root@master 7.7]# dnf -y install httpd-tools
[root@master 7.7]# htpasswd -c auth hehe
# 添加用户
[root@master 7.7]# htpasswd auth lili
[root@master 7.7]# cat auth
hehe:$apr1$.qikKmFZ$9sbloDIxooQ20Phrd0.pu/
lili:$apr1$H8rRr66e$6dDOrWsGERrQhcrDXpgt80
[root@master 7.7]# kubectl create secret generic ingress-basic-auth --from-file=auth
[root@master 7.7]# vim 2.ingress-nginx-auth.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-http-auth
spec:
replicas: 2
selector:
matchLabels:
app: ingress-http-auth
template:
metadata:
labels:
app: ingress-http-auth
spec:
containers:
- image: myos:httpd
name: myos
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: ingress-http-auth
spec:
ports:
- port: 80
targetPort: 80
selector:
app: ingress-http-auth
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-http-auth
annotations: # 添加释文
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: ingress-basic-auth
nginx.ingress.kubernetes.io/auth-realm: 'Admin Area'
spec:
ingressClassName: nginx
rules:
- host: auth.zkbr.com
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: ingress-http-auth
port:
number: 80
tls:
- hosts:
- auth.zkbr.com
secretName: ingress-nginx-tls
[root@master 7.7]# kubectl apply -f 2.ingress-nginx-auth.yaml
[root@master 7.7]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-http-auth nginx auth.zkbr.com 80, 443 6m59s
ingress-http-ssl nginx ssl.zkbr.com 80, 443 44h
ingress-http-www1 nginx www1.zkbr.com 80 2d20h
ingress-http-www2 nginx www2.zkbr.com 80 2d20h

测试环境添加解析记录

image-20250709173214089

image-20250709173526218

image-20250709173611222

2.14.7 Ingress-nginx 域名重定向

Redirect(重定向):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@master 7.9]# vim 1.ingress-redirect.yaml 
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ingress-redirect
annotations:
# 设置301永久重定向
nginx.ingress.kubernetes.io/permanent-redirect: https://www.jd.com
spec:
ingressClassName: nginx
rules:
- host: redirect.zkbr.com
http: # 因为我们要把整个域名重定向到京东,所以下面就不用定义访问路径了

测试效果

1
2
3
4
5
6
7
8
[root@master 7.9]# echo "192.168.10.13 redirect.zkbr.com" >> /etc/hosts
[root@master 7.9]# curl -I redirect.zkbr.com
HTTP/1.1 301 Moved Permanently
Date: Wed, 09 Jul 2025 10:35:54 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://www.jd.com # 被301重定向到了jd

image-20250709183048497

image-20250709183024812

2.14.8 Ingress-nginx 地址重写

Rewrite (重写)

  • 作用:重写是指修改请求的路径,但是客户端不会察觉到这个变化,它仅在服务器内部发生。在Kubernetes 中,可以通过Ingress 的注解来配置重写规则
  • 示例:比如你有一个服务部署在/v1路径下,但是你希望用户访问时不需要输入/v1,那么你可以使用重写将请求从根路径/重写到/v1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@master 7.9]# vim 2.ingress-rewrite.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-rewrite
annotations:
# 启用URL重写功能
nginx.ingress.kubernetes.io/rewrite-target: /info.$1
# 启用正则表达式匹配
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- host: rewrite.zkbr.com
http:
paths:
- path: /test.(.*)
pathType: ImplementationSpecific
backend:
service:
name: ingress-http-www1 # 请求被该svc响应
port:
number: 80

测试环境添加域名解析

image-20250709192722359

image-20250709193201321

2.14.9 Ingress-nginx 自定义错误页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[root@master 7.10]# vim 1.ingress-error.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: ingress-error
spec:
replicas: 2
selector:
matchLabels:
name: error
template:
metadata:
labels:
name: error
spec:
containers:
- name: test
image: library/errorweb:v1.0 # 该镜像是我提前下载到我的harbor仓库中的,使用的是汪洋老师的镜像
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: ingress-error
spec:
selector:
name: error
ports:
- port: 80
targetPort: 80
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ingress-error
annotations:
# 指定需要自定义的错误码
nginx.ingress.kubernetes.io/custom-http-errors: "404,500"
# 将错误请求转发到后端的svc来响应
nginx.ingress.kubernetes.io/default-backend: "ingress-error"
spec:
rules:
- host: error.zkbr.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ingress-http-www1 # 该svc,是之前定义的svc
port:
number: 80
[root@master 7.10]# kubectl apppl -f 1.ingress-error.yaml
[root@master 7.10]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-error nginx error.zkbr.com 80 15m
ingress-http-auth nginx auth.zkbr.com 80, 443 28h
ingress-http-ssl nginx ssl.zkbr.com 80, 443 3d
ingress-http-www1 nginx www1.zkbr.com 80 4d1h
ingress-http-www2 nginx www2.zkbr.com 80 4d
ingress-redirect nginx redirect.zkbr.com 80 27h
ingress-rewrite nginx rewrite.zkbr.com 80 26h

测试环境添加域名解析记录

image-20250710220504133

image-20250710221048591

image-20250710220907380

2.14.10 Ingress-nginx snippet 自定义配置片段

Ingress-nginx 中,snippet 通常指的是通过注解(Annotations)向 Nginx 配置注入的自定义配置片段。下面从其作用、使用方式、优势与风险等方面详细介绍:

作用

Ingress-nginx 作为 Kubernetes 中常用的 Ingress 控制器,负责将外部流量路由到集群内的服务上。尽管它提供了丰富的默认配置和参数,但在一些复杂场景下,默认配置无法满足需求。这时,snippet 就派上用场了,它允许用户灵活地自定义 Nginx 的配置,从而实现更精细的流量控制、安全策略、性能优化等功能 。例如:

  • 自定义重定向:根据请求的来源、客户端类型等条件,设置特定的重定向规则。
  • 添加自定义 HTTP 头:在响应中添加自定义的 HTTP 头部信息,用于实现特定的业务逻辑或安全策略。
  • 配置缓存策略:对某些资源设置缓存规则,提高响应速度。

优势与风险

  • 优势:极大地增强了 Ingress-nginx 的灵活性和扩展性,能满足多样化的业务需求,而无需修改 Ingress-nginx 的代码或重新部署。
  • 风险:直接修改 Nginx 配置存在一定的安全风险,如果配置不当,可能会导致服务不可用、安全漏洞等问题。因此,默认情况下,Ingress-nginx 禁止通过注解注入配置片段,需要在 Ingress-nginx 的 ConfigMap 中设置 allow-snippet-annotations: "true" 来开启该功能,并且在使用时需要严格测试和审核配置内容 。

开启注解注入配置片段的权限

allow-snippet-annotations参数设为 true 时,用户可以在 Ingress 资源的 annotations 中使用以下特殊注解,直接注入 Nginx 配置片段:

  • nginx.ingress.kubernetes.io/configuration-snippet:注入到 Nginx 服务器(server)块的配置中(适用于路由级别的自定义,如重定向、缓存策略等)。
  • nginx.ingress.kubernetes.io/server-snippet:注入到 Nginx 全局 http 块的配置中(适用于全局级别的自定义,如日志格式、全局变量等)。
  • nginx.ingress.kubernetes.io/location-snippet:注入到 Nginx location 块的配置中(适用于特定路径的细节配置)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 开启自定义配置片段功能
[root@master 8.10]# kubectl edit -n ingress cm ingress-nginx-controller
data:
allow-snippet-annotations: "true"
[root@master 8.10]# vim snippet.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: snippet
spec:
replicas: 1
selector:
matchLabels:
app: snippet
template:
metadata:
labels:
app: snippet
spec:
containers:
- name: test
image: myos:httpd
---
kind: Service
apiVersion: v1
metadata:
name: snippet
spec:
ports:
- name: 80-80
port: 80
targetPort: 80
protocol: TCP
selector:
app: snippet
type: ClusterIP
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: snippet.zkbr.com
annotations:
# 配置全局变量和地址重定向
nginx.ingress.kubernetes.io/server-snippet: |
set $agentflag 0;
if ($http_user_agent ~* "(Firefox)") {
set $agentflag 1;
}
if ($agentflag = 1){
# 如果用户使用Firefox浏览器访问,那么会临时重定向到百度
return 302 http://www.baidu.com;
break;
}
if ($http_user_agent ~* "(chrome)") {
set $agentflag 2;
}
if ($agentflag = 2){
# 如果用户使用chrome浏览器访问,那么会临时重定向到京东
return 302 http://www.jd.com;
break;
}
spec:
rules:
- host: snippet.zkbr.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: snippet
port:
number: 80
[root@master 8.10]# kubectl apply -f snippet.yaml

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 添加解析记录
[root@master 8.10]# echo "192.168.10.12 snippet.zkbr.com" >> /etc/hosts
[root@master 8.10]# curl snippet.zkbr.com -H 'User-Agent: chrome' -I
HTTP/1.1 302 Moved Temporarily # 使用的是302临时重定向
Date: Sun, 10 Aug 2025 09:35:43 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Location: http://www.jd.com # 被重定向到了京东

[root@master 8.10]# curl snippet.zkbr.com -H 'User-Agent: Firefox' -I
HTTP/1.1 302 Moved Temporarily # 使用的是302临时重定向
Date: Sun, 10 Aug 2025 09:36:12 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Location: http://www.baidu.com # 被重定向到了百度

[root@master 8.10]# curl snippet.zkbr.com -I
HTTP/1.1 200 OK # 核心状态码:请求成功处理
Date: Sun, 10 Aug 2025 09:36:18 GMT # 服务器处理时间
Content-Type: text/html; charset=UTF-8 # 响应体是 HTML 文本(UTF-8 编码)
Content-Length: 23 # 响应体大小为 23 字节
Connection: keep-alive # 保持长连接
Last-Modified: Mon, 16 Jan 2023 04:21:54 GMT # 资源最后修改时间
ETag: "17-5f259ec4c7c80" # 资源唯一标识(用于缓存验证)
Accept-Ranges: bytes # 服务器支持断点续传(按字节范围请求

2.14.11 Ingress-nginx 配置黑白名单

配置方案

  • Annotations:只对指定的ingress生效
  • ConfigMap:全局生效
  • 若是同时配置了Annotations和configmap,一般都是annotations 生效,configmap不生效,因为annotations优先级比configmap高

黑白名单概念

  • 白名单:仅允许指定 IP 地址(或 IP 段)访问后端服务,其他 IP 被拒绝。
  • 黑名单:拒绝指定 IP 地址(或 IP 段)访问,其他 IP 被允许。

配置方案建议

  • 黑名单建议使用ConfigMap去配置
  • 白名单建议使用Annotations去配置

Configmap 添加黑名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@master 8.10]# kubectl edit -n ingress cm ingress-nginx-controller 
data:
  allow-snippet-annotations: "true"
# 添加黑名单
block-cidrs: 192.168.10.12

# 用之前部署的ingress做实验
[root@master 8.10]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-error nginx error.zkbr.com 80 30d
ingress-http-auth nginx auth.zkbr.com 80, 443 32d
ingress-http-ssl nginx ssl.zkbr.com 80, 443 33d
ingress-http-www1 nginx www1.zkbr.com 80 34d
ingress-http-www2 nginx www2.zkbr.com 80 34d
ingress-redirect nginx redirect.zkbr.com 80 32d
ingress-rewrite nginx rewrite.zkbr.com 80 31d
snippet.zkbr.com nginx snippet.zkbr.com 80 107m
[root@master 8.10]# echo "192.168.10.12 www1.zkbr.com" >> /etc/hosts
[root@master 8.10]# curl www1.zkbr.com
Welcome to The Apache.

# 用node01(192.168.10.11)访问,能访问
[root@node01 ~]# echo "192.168.10.13 www1.zkbr.com" >> /etc/hosts
[root@node01 ~]# curl www1.zkbr.com
Welcome to The Apache.

# 用node03(192.168.10.13)访问,不能访问,权限被拒绝
[root@node03 ~]# echo "192.168.10.12 www1.zkbr.com" >> /etc/hosts
[root@node03 ~]# curl www1.zkbr.com
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>

Annotations 添加白名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
[root@master 8.10]# vim white.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
name: white-deploy
spec:
replicas: 1
selector:
matchLabels:
app: white
template:
metadata:
labels:
app: white
spec:
containers:
- image: myos:httpd
name: myapp
---
apiVersion: v1
kind: Service
metadata:
labels:
app: white
name: white-svc
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: white
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# 配置白名单IP,这了就192.168.10.13一个IP
nginx.ingress.kubernetes.io/whitelist-source-range: 192.168.10.13
name: white-zkbr-com
labels:
app: white
spec:
rules:
- host: white.zkbr.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: white-svc
port:
number: 80

[root@master 8.10]# kubectl apply -f white.yaml
[root@master 8.10]# echo "192.168.10.12 white.zkbr.com" >> /etc/hosts
# 访问失败,因为我们配置了白名单,只能192.168.10.13能访问
[root@master 8.10]# curl white.zkbr.com
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>

# 访问失败,因为我们配置了白名单,只能192.168.10.13能访问
[root@node01 ~]# echo "192.168.10.12 white.zkbr.com" >> /etc/hosts
[root@node01 ~]# curl white.zkbr.com
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>

==========================================================
# 访问成功,因为node03的IP是192.168.10.13
# 同时我们也证明了一点,当configmap和Annotations同时配置的时候,configmap是不生效的,因为Annotations优先级高,即生效。
[root@node03 ~]# echo "192.168.10.12 white.zkbr.com" >> /etc/hosts
[root@node03 ~]# curl white.zkbr.com
Welcome to The Apache.

2.14.12 Ingress-nginx 金丝雀部署和灰度发布

在 Kubernetes(K8s)中,金丝雀部署(Canary Deployment)灰度发布(Gray Release) 是两种常用的渐进式发布策略,用于在生产环境中安全地验证新版本应用的稳定性。Ingress 作为 K8s 中管理外部流量入口的 API 对象,可通过 Ingress 控制器(如 Nginx Ingress、Traefik 等)的扩展能力实现这两种策略,核心是通过精细化的流量规则将部分流量导向新版本,逐步扩大范围直至全量切换。

核心概念

  • 金丝雀部署:通常基于流量比例(如 10%、30%、50%)将部分流量路由到新版本,验证无误后逐步提高比例,最终全量切换。
  • 灰度发布:更强调精准的用户 / 场景筛选(如按用户 ID、地域、浏览器类型等),仅让特定群体访问新版本,验证通过后分批扩大覆盖范围。

金丝雀部署示例:

这里我们是把业务web服务的apache改为nginx,企业中一般都是从v1版本升级到v2版本,思路是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
[root@master 8.13]# vim 1.old_ingress.yaml 
---
# apache版本的Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-http
spec:
replicas: 5
selector:
matchLabels:
app: myapp-http
template:
metadata:
labels:
app: myapp-http
spec:
containers:
- name: http
image: myos:httpd
ports:
- containerPort: 80
---
# apache版本的Service
apiVersion: v1
kind: Service
metadata:
name: myapp-http-svc
spec:
selector:
app: myapp-http
ports:
- port: 80
targetPort: 80
---
# apache版本的ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-http-ingress
spec:
rules:
- host: myapp-web.zkbr.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-http-svc
port:
number: 80
[root@master 8.13]# kubectl apply -f 1.old_ingress.yaml
[root@master 8.13]# echo "192.168.10.12 myapp-web.zkbr.com" >> /etc/hosts
# 访问apache的ingress,不管怎么访问都是apache给的回应,因为我们还没部署nginx
[root@master 8.13]# curl myapp-web.zkbr.com
Welcome to The Apache.
[root@master 8.13]# curl myapp-web.zkbr.com
Welcome to The Apache.

=======================新版本Ingress====================
[root@master 8.13]# vim 2.new_ingress.yaml
---
# nginx版本的Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-nginx
spec:
replicas: 5
selector:
matchLabels:
app: myapp-nginx
template:
metadata:
labels:
app: myapp-nginx
spec:
containers:
- name: nginx
image: myos:nginx
ports:
- containerPort: 80
---
# nginx版本的Service
apiVersion: v1
kind: Service
metadata:
name: myapp-nginx-svc
spec:
selector:
app: myapp-nginx
ports:
- port: 80
targetPort: 80
---
# 金丝雀Ingress(路由部分流量到nginx版本)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-canary-ingress
annotations:
nginx.ingress.kubernetes.io/canary: "true" # 声明为金丝雀Ingress
nginx.ingress.kubernetes.io/canary-weight: "10" # 10%流量到nginx版本(0-100之间)
spec:
rules:
- host: myapp-web.zkbr.com # 与apache的Ingress同域名,共享流量规则
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-nginx-svc # 金丝雀流量到nginx版本
port:
number: 80
[root@master 8.13]# kubectl apply -f 2.new_ingress.yaml
[root@master 8.13]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
myapp-canary-ingress nginx myapp-web.zkbr.com 80 18m
myapp-http-ingress nginx myapp-web.zkbr.com 80 25m

验证金丝雀部署:

1
2
3
4
5
6
7
8
9
10
# 我们使用脚本来验证
[root@master 8.13]# for i in {1..100};do curl myapp-web.zkbr.com >> sum;done
# 10% 的请求会被路由到nginx,90% 到apache;
[root@master 8.13]# cat sum | sort | uniq -c
9 Nginx is running !
91 Welcome to The Apache.

# 调整:若 nginx版本 稳定,可逐步将canary-weight从 10→30→50→100,最终删除主 Ingress 完成全量切换。
# 把canary-weight的值设置为50
[root@master 8.13]# kubectl edit ingress myapp-canary-ingress

image-20250813221857293

1
2
3
4
5
[root@master 8.13]# for i in {1..100};do curl myapp-web.zkbr.com >> sum;done^C
# 50% 的请求会被路由到nginx,50% 到apache
[root@master 8.13]# cat sum | sort | uniq -c
57 Nginx is running !
43 Welcome to The Apache.

灰度发布示例:

2.15 Loki日志方案

2.15.1 Loki 核心定位与设计理念

Loki 是由 Grafana Labs 开发的轻量级日志聚合系统,专为云原生环境(尤其是 Kubernetes)设计,核心理念是 “只索引元数据,不索引日志内容”,旨在解决传统日志系统(如 ELK)在大规模集群下存储成本高、查询性能差的问题。

其核心设计原则可总结为:

  • 低成本:避免对日志全文进行索引,仅通过标签(Labels,如 Pod 名、命名空间、容器名)建立索引,大幅降低存储和计算开销。
  • 云原生友好:原生支持 Kubernetes 标签,日志与集群资源(Pod、Service)的元数据天然对齐,便于关联查询。
  • 与 Grafana 深度集成:可直接在 Grafana 中实现 “日志 - 指标 - 监控面板” 的联动分析,无需切换工具。

2.15.2 Loki 的核心组成结构

Loki 的架构是微服务式的,每个组件有明确的职责。你可以根据规模选择全部一起运行(单机模式)或分开独立部署(微服务模式)。

组件 核心职责 关键说明
Promtail 日志采集客户端 (Agent) 1. 以 DaemonSet 形式运行在每个 K8s 节点上。
2. 核心工作:监控节点上的容器日志文件,从 K8s API 获取 Pod 的元数据(标签) 并附加到日志流上,然后将日志推送给 Loki。
Distributor 日志入口网关 1. 接收来自 Promtail 的日志流。
2. 负责校验数据格式和标签的合法性。
3. 使用一致性哈希算法,将日志数据分发给后端的多个 Ingester,实现负载均衡。
Ingester 日志处理与暂存器 1. 最核心、最有状态的组件
2. 在内存中构建日志的压缩数据块 (Chunk)。
3. 定期将完整的 Chunk 持久化到后端存储中。
4. 通过副本机制保证数据高可用。
Querier 查询引擎 1. 接收查询请求(如从 Grafana 来)。
2. 先从索引存储中查找哪些 Chunk 包含目标日志,再从对象存储中拉取实际的日志数据。
3. 在内存中执行 LogQL 查询,返回结果。
Query Frontend 查询前端 (可选,但推荐) 1. 坐在 Querier 前面,用于提升查询性能。
2. 将大查询拆分成多个小查询并行执行。
3. 提供查询结果缓存。

2.15.3 Loki 与 EFK 的核心区别

EFK 栈指的是 Elasticsearch + Fluentd + Kibana。它与 Loki 的设计哲学完全不同。

对比维度 Loki EFK
1. 核心设计哲学 只索引元数据 (标签),不对日志正文内容建索引。 对全文进行索引,包括所有关键字。
2. 资源消耗 (成本关键) 非常低 非常高
索引小:只存标签,量很小。 • 存储便宜:日志块(Chunk)可放在廉价对象存储(如S3)。 索引巨大:因为索引了所有内容,索引大小可能接近甚至超过原始日志大小。 • 存储昂贵:需要高性能的本地SSD来保证Elasticsearch的读写速度。
3. 存储成本 极低 较高
4. 查询方式 使用 LogQL,类似 Prometheus 的 PromQL。先通过标签筛选日志流,再对内容进行 grep 式的文本搜索。查询思路是:“先缩小范围,再搜索内容” 使用 Elasticsearch 的 Query DSL。功能极其强大,可以对索引过的任何字段进行复杂、快速的聚合、分析和模糊查询。
5. 运维复杂度 相对简单 非常复杂
组件职责单一,架构清晰。存储与计算分离,扩展方便。 Elasticsearch 集群本身的运维是一项专业且繁重的工作(调优、分片、扩容等)。
6. 与 K8s/Prometheus 集成 无缝集成 需要配置
标签模型与 Prometheus 完全一致,在 Grafana 中能实现从监控指标到日志的一键跳转 需要额外配置才能实现类似的关联性。
7. 优势 成本极致优化云原生友好运维简单 • 与 Prometheus 生态完美融合 查询功能强大且灵活全文检索速度快社区成熟,生态庞大
8. 劣势 对日志内容的复杂查询和分析能力较弱。 资源消耗大,总拥有成本高,运维挑战大。

2.15.4 k8s集群部署Loki日志

1
2
3
4
5
6
7
8
9
10
11
12
13
# 官方推荐使用helm来安装loki
# 添加grafana官方图表库
$ helm repo add grafana https://grafana.github.io/helm-charts

# 更新图表
$ helm repo update

# 搜索grafana图表,需要安装loki的chart包
$ helm search repo grafana | grep loki

# 下载loki-stack的chart包到本地,来根据自己的需求定义内容
$ helm pull grafana/loki-stack .
$ tar -xf loki-stack-2.10.2.tgz
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 创建默认配置文件
$ cd loki-stack && ls
$ vim values_new.yaml

loki:
enabled: true
image:
registry: harbor:443 # 使用的是自己的私有仓库
repository: grafana/loki # 使用的是私有仓库里的镜像,确保私有仓库有以下镜像
tag: 2.9.0
persistence:
enabled: true
accessModes:
- ReadWriteOnce
size: 2Gi
storageClassName: nfs-client # 这里存储使用的是动态存储类
promtail:
enabled: true
image:
registry: harbor:443
repository: grafana/promtail
tag: latest
defaultVolumes:
- name: run
hostPath:
path: /run/promtail
- name: containers
hostPath:
path: /var/lib/containerd/io.containerd.grpc.v1.cri/containers/ # 这个路径是 containerd 容器运行时存储容器元数据,CRI是docker的话,就不是这个路径了。
- name: pods
hostPath:
path: /var/log/pods
defaultVolumeMounts:
- name: run
mountPath: /run/promtail
- name: containers
mountPath: /var/lib/containerd/io.containerd.grpc.v1.cri/containers/ # 这里要与上面卷的主机路径一致
readOnly: true
- name: pods
mountPath: /var/log/pods
readOnly: true
grafana:
enabled: true
sidecar:
image:
registry: harbor:443
repository: library/k8s-sidecar
tag: 1.19.2
sha: ""
initChownData:
enabled: true
image:
registry: harbor:443
image:
registry: harbor:443
repository: grafana/grafana
tag: 9.5.1
service:
enabled: true
type: NodePort # 这里的grafana使用的nodepod(外部访问)类型
persistence:
enabled: true
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
size: 1Gi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 创建一个名称空间
$ kubectl create ns loki

# 安装loki
$ helm install loki -n loki -f values_new.yaml .

# 查看loki的chart包产生的pod
[root@master loki-stack]# kubectl get pods -n loki
NAME READY STATUS RESTARTS AGE
loki-0 1/1 Running 2 (5h48m ago) 46h
loki-grafana-78fb7b4475-dhprw 2/2 Running 4 (5h48m ago) 46h
loki-promtail-hfc4k 1/1 Running 2 (5h48m ago) 46h
loki-promtail-n4mcv 1/1 Running 2 (5h48m ago) 46h
loki-promtail-rkzqv 1/1 Running 2 (5h48m ago) 46h
loki-promtail-wrhfw 1/1 Running 2 (5h48m ago) 46h

# 查看loki的chart包产生的service
[root@master loki-stack]# kubectl get -n loki svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
loki ClusterIP 10.245.253.16 <none> 3100/TCP 46h
loki-grafana NodePort 10.245.99.172 <none> 80:30275/TCP 46h
loki-headless ClusterIP None <none> 3100/TCP 46h
loki-memberlist ClusterIP None <none> 7946/TCP 46h
[root@master loki-stack]# kubectl get -n loki deployments
NAME READY UP-TO-DATE AVAILABLE AGE
loki-grafana 1/1 1 1 46h
[root@master loki-stack]# kubectl get -n loki ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
loki-promtail 4 4 4 4 4 <none> 46h
[root@master loki-stack]# kubectl get -n loki secrets
NAME TYPE DATA AGE
loki Opaque 1 46h
loki-grafana Opaque 3 46h
loki-promtail Opaque 1 46h
sh.helm.release.v1.loki.v1 helm.sh/release.v1 1 46h

查看Grafana的密码

image-20250928150933757

访问Grafana

image-20250928152255125

验证loki日志体系

1
2
3
4
5
6
7
8
# 创建一个名为mynginx的应用来做试验
[root@master loki-stack]# kubectl create deploy mynginx --image=nginx:1.27.0 --replicas=2
[root@master loki-stack]# kubectl create svc clusterip mynginx --tcp=80:80
[root@master loki-stack]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 39d
myapp ClusterIP 10.245.127.190 <none> 80/TCP 46h
mynginx ClusterIP 10.245.90.133 <none> 80/TCP 5s

image-20250928170124658

image-20250928171117871

  • 标题: 云原生
  • 作者: Xiao He
  • 创建于 : 2024-12-01 00:00:00
  • 更新于 : 2025-10-09 00:00:00
  • 链接: https://redefine.ohevan.com/2024/12/01/云原生/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
云原生