建设目标:掌握K8S、docker的基础知识,常见组件及信息查看方式,pod调度的原理
1.docker
1.1 前言
2013 年前后,以 Cloud Foundry 为代表的开源 PaaS 项目,成为了当时云计算技术中的一股清流。
PaaS 项目被大家接纳的一个主要原因,就是它提供了一种名叫“应用托管”的能力,通过在虚拟机上像以前管理物理服务器那样,用脚本或者手工的方式在这些机器上部署应用。Cloud Foundry 这样的 PaaS 项目,最核心的组件就是一套应用的打包和分发机制,通过调用操作系统的 Cgroups 和 Namespace 机制为每一个应用单独创建一个称作“沙盒”的隔离环境,然后在“沙盒”中启动这些应用进程。但是PaaS打包功能欠佳,用户经常会遇到虚拟机环境与本地环境不一致问题。
docker项目在Cgroups 和 Namespace 实现的“沙盒”基础上,使用docker镜像提供了非常便利的打包机制,这种机制直接打包了应用运行所需要的整个操作系统,从而保证了本地环境和云端环境的高度一致,避免了用户通过“试错”来匹配两种不同运行环境之间差异的痛苦过程。
1.2 Docker概述
Docker是一个开发、发布和运行应用程序的开放平台,能够将应用程序与基础架构分离,以便快速交付软件。有了Docker,可以像管理应用程序一样管理基础设施。通过利用Docker快速发布、测试和部署代码,可以显著减少编写代码和在生产环境中运行代码之间的延迟。
1.3 Docker优点
- 快速、一致地交付应用程序
Docker通过允许开发人员在标准化环境中使用本地容器(提供应用程序和服务)来简化开发生命周期
- 响应式部署和扩展
Docker基于容器的平台允许高度可移植(highly portable),可以运行在开发人员的本地笔记本电脑、数据中心的物理或虚拟机、云提供商或混合环境中。其可移植性和轻量级特性也使得动态管理工作负载、根据业务需求扩缩容变得非常容易、近乎实时。
- 硬件资源消耗低
Docker轻巧快速。它为基于hypervisor的虚拟机提供了一个可行、经济的替代方案,因此可以使用更多的计算能力来实现业务目标
1.4 Docker架构
Docker 使用客户端-服务器架构。Docker 客户端与 Docker 守护进程通信,后者负责构建、运行和分发 Docker 容器的繁重工作。Docker 客户端和守护进程可以在同一系统上运行或分开部署。两者使用 REST API 通过 UNIX 套接字或网络接口进行通信。
Docker daemon
Docker 守护进程(dockerd)监听 Docker API 请求并管理 Docker 对象,例如镜像、容器、网络和卷。守护进程还可以与其他守护进程通信来管理 Docker 服务。
Docker client
Docker 客户端 ( docker) 是 Docker 用户与 Docker 交互的主要方式。当您使用诸如 之类的命令时docker run,客户端会将这些命令发送到dockerd(守护进程),由后者执行这些命令。该docker命令使用 Docker API。Docker 客户端可以与多个守护进程通信。
Docker desktop
Docker桌面适用于Mac OS、Windows系统,包括 Docker 守护进程 ( dockerd)、Docker 客户端 ( docker)、Docker Compose、Docker Content Trust、Kubernetes 和 Credential Helper。
Docker registry
Docker 仓库表存储 Docker 镜像。Docker Hub 是任何人都可以使用的公共仓库,Docker 默认在 Docker Hub 上查找镜像。您甚至可以运行自己的私人仓库。使用docker pull或docker run命令时,Docker 从配置的仓库中提取所需的镜像。当使用该docker push命令时,Docker 会将镜像推送到配置的仓库
Docker objects
当使用 Docker 时正在创建和使用镜像、容器、网络、卷、插件和其他对象
1.5 Docker image
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,他包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件。
1.5.1 Docker镜像分层原则
- Dockerfile中的每个指令都会创建一个新的镜像层
- 镜像层将被缓存和复用
- 当Dockerfile的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层就会失效
- 某一层的镜像缓存失效后它之后的镜像层缓存都会失效
- 镜像层是不可变的,如果在某一层中添加一个文件,然后在下一层中删除,删除文件并不会从镜像中真正删除文件,而是将文件标记为删除,并在下一层中不再包含该文件。

1.5.2 Docker镜像分层结构
Docker镜像的基础是联合文件系统(UnionFs),这是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
- (内核层):由AUFS,LXC,Bootfs(boot file system)组成,为上层的镜像提供kernel内核支持
- AUFS是一个联合文件系统,它使用同一个Linux host上的多个目录,逐个堆叠起来,对外呈现出一个统一的文件系统。AUFS使用该特性,实现了Docker镜像的分层思想
- bootfs:负责与内核交互 主要是引导加载kernel,linux刚启动时会加载bootfs文件系统,在Docker镜像最底层时bootfs,这一层与经典的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统就会卸载bootfs
- rootfs(root file system)属于base images ,在bootfs之上(base images)。比如在linux系统中包含/dev,/proc,/bin,/etc等标准目录和文件,包含创建、启动操作系统的一些必要的组件,rootfs就是各个不同的操作系统的发行版本,比如Ubuntu,CentOS等等
- lxc早期的内核引擎与docker引擎对接交互,docker提供一些库文件和引导文件。现在docker自身内置了所需要的lib库
- base image(基础/系统镜像层):构建镜像运行的操作系统环境
- add image(run指令运行的镜像层):比如nginx镜像的yum安装模块,或者nginx编译安装的指令,使用镜像封装每一个run执行命令
- Container(可读写执行层):将上面的镜像组合运行,提供给docker client使用
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。分层时若有文件更新则直接替换,基础镜像一样时直接复用。
示例:基于 Ubuntu16.04创建一个新的镜像,这就是新镜像的第一层。如果在该镜像中添加Python包,就会在基础镜像层之上创建第二个镜像层。如果继续添加一个安全补丁,就会创健第三个镜像层,如下图所示:

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合。一个简单的例子,每个镜像层包含3个文件,而镜像包含了来自两个镜像层的6个文件,如下图所示:

下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版。上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。

所有镜像层堆并合井,对外提供统一的视图:

1.6 Docker container
Docker container 是一个轻量级、可移植的虚拟化单元,用于运行应用程序及其依赖项。一个 Docker container 实际上是一个运行时实例,它包含了应用程序、运行时环境、系统工具、系统库以及其他依赖项,这些都被封装在一个独立的、隔离的环境中。
特性
- 隔离性:每个 Docker container 都有自己的文件系统、网络栈和进程空间,与宿主系统和其他容器相互隔离。这意味着容器中运行的应用程序不会影响其他容器或宿主系统,也不会受到外部环境的影响。
- 轻量级:Docker container 是轻量级的,它们共享宿主系统的内核,并且不需要额外的虚拟化管理程序。因此,容器可以在几秒钟内启动和停止,并且可以快速部署和扩展。
- 可移植性:Docker container 可以在任何支持 Docker 的平台上运行,而不需要对应用程序进行任何修改。这使得容器可以在开发、测试和生产环境之间轻松迁移,并且可以在不同的云平台和物理服务器上运行。
- 可编排性:Docker container 可以通过编排工具(如 Docker Compose、Kubernetes 等)进行管理和调度,从而实现自动化部署、扩展和运维。
Docker container移植限制
- 架构限制:Docker.容器是基于Linux内核的,因此只能运行在与Linux内核兼容的硬件平台上。例如,x86、x86-64架构的CPU可以使用常见的Linux发行版运行Docker容器,而ARM架构的CPU需要专门的ARM版的Linux发行版才能运行。
- 操作系统限制:Docker容器需要运行在支持Linux内核的操作系统上,如Ubuntu、CentOS等。不同操作系统的内核版本和功能支持可能有所差异,因此在迁移Docker容器时需要确保目标平台上的操作系统和内核版本与原始平台兼容。(因为本质需要共享宿主机的内核)
- 内核功能限制:Docker容器使用了Liux内核的一些特性和功能,如命名空间、控制组等。如果目标平台的内核版本不支持这些功能,或者功能的实现方式有所不同,可能会导致容器无法正常运行。
- 硬件资源限制:Docker容器的硬件资源(如CPU、内存、磁盘)分配是基于宿主机的,因此容器的可移植性受限于宿主机的硬件资源。如果目标平台的硬件资源不足,可能会导致容器运行出现性能问题或无法启动。
1.7 Docker build
为了将我们的应用程序方便在服务器上运行部署,我们需要先将应用程序制作成镜像。为了制作镜像,我们需要在项目中提供一个基于文本的Dockerfile文件,这个文件没有后缀名但是包含脚本内容,Docker依据此脚本制作镜像。
1.7.1 Dockerfile
Docker 通过读取 Dockerfile 中的指令来自动构建镜像,Dockerfile 是一个文本文件,其中按顺序包含构建给定镜像所需的所有命令。Dockerfile文件必须从FROM指令开始,用于指定本次构建的基础镜像
# Version: 0.0.1
FROM debian 1.新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
MAINTAINER wzlinux
RUN apt-get update && apt-get install -y emacs 2.安装 emacs 编辑器。
RUN apt-get install -y apache2 3.安装 apache2。
CMD ["/bin/bash"] 4.容器启动时运行 bash。

Docker脚本命令趣味版

常用命令
- FROM <image>:指定基础镜像,用于构建当前镜像。基础镜像是构建过程的起点。
- COPY <src> <dest>:将源文件或目录从构建上下文复制到镜像中的目标位置。这可以用于将应用程序代码、配置文件等复制到镜像中。
- ADD <src> <dest>:类似于 COPY,但更强大。它可以处理远程 URL、自动解压缩等功能。但在一般情况下,推荐使用 COPY。
- RUN <command>:在镜像中执行指定的命令。可以使用 RUN 执行各种操作,例如安装软件包、运行编译命令等。
- CMD <command>:定义容器启动时要执行的命令。可以在 Dockerfile 中只有一个 CMD 指令(如果在 Dockerfile 中有多个 CMD 指令,只有最后一个会生效),它可以是 Shell 格式的命令,也可以是数组格式的命令。
- ENTRYPOINT <command>:设置容器的入口点,定义容器启动时始终执行的命令。
- EXPOSE <port>:声明容器在运行时将监听的端口。这只是一个元数据,用于告诉用户容器将使用指定的端口。
- ENV <key>=<value>:设置环境变量。可以在 Dockerfile 中设置多个环境变量。
- WORKDIR <path>:设置容器的工作目录,即在容器中执行命令时的默认目录。
- VOLUME <path>:声明容器中的挂载点,用于持久化存储数据。
- USER <username>:指定运行容器时要使用的用户名或 UID
CMD、ENTRYPOINT区别和优先级
区别
- CMD用于指定容器启动时默认要执行的命令,可以被 Dockerfile 中的任何其他命令覆盖,包括 docker run 命令中的命令。
- ENTRYPOIN用于指定容器启动时主执行命令,它不会被 Dockerfile 中的其他命令覆盖,但可以被 docker run 命令的参数替换。如果在运行容器时指定了 --entrypoint 参数,它将覆盖 Dockerfile 中指定的 ENTRYPOINT。
优先级
ENTRYPOINT 的优先级高于 CMD,因此在启动容器时,如果指定了 ENTRYPOINT,则 CMD 会作为 ENTRYPOINT 的参数,而不会被忽略。
1、多个CMD只有最有一个会生效(ENTRYPOINT同理)
FROM ubuntu:latest
// 其他命令
# 启动 nginx 服务
CMD ["nginx", "-g", "daemon off;"]
CMD ["curl", "http://localhost"]
在这个 Dockerfile 中,有两个 CMD 指令。第一个 CMD 指令指定了启动 nginx 服务,而第二个 CMD 指令则是执行 curl 命令。但是,由于 Docker 只会执行最后一个 CMD 指令,所以在容器启动时只会执行 curl http://localhost 命令,而不会启动 nginx 服务。
2、ENTRYPOINT和CMD共存
FROM alpine:latest
# 设置 ENTRYPOINT
ENTRYPOINT ["echo", "Hello"]
# 设置 CMD,会作为 ENTRYPOINT 的参数
CMD ["World"]
在启动容器时,如果指定了 ENTRYPOINT,则 CMD 会作为 ENTRYPOINT 的参数,而不会被忽略
3、多个CMD或ENTRYPOINT,覆盖,只有CMD + ENTRYPOINT会生效
CMD ["nginx", "-g", "daemon off;"]
CMD ["curl", "http://localhost"]
# 设置第一个ENTRYPOINT
ENTRYPOINT ["echo", "Hello"]
# 设置第二个ENTRYPOINT,覆盖了之前的设置
ENTRYPOINT ["echo", "World"]
容器启动会最终在控制台输出World curl http://localhost
1.7.2 Docker build
使用docker build命令时,当前工作目录称为生成上下文(build context)。默认情况下,可以使用文件标志(-f)指定Dockerfile位置。不管Dockerfile实际位于何处,当前目录中文件和目录的所有递归内容都将作为生成上下文发送到Docker守护进程。
命令常用参数如下
- -t, --tag <name:tag>:为构建的镜像指定名称和标签。例如,-t myimage:1.0 将构建的镜像命名为 myimage,并附加标签 1.0。
- -f, --file <filename>:指定用于构建镜像的 Dockerfile 文件路径。默认情况下,Docker 将在当前目录下寻找名为 Dockerfile 的文件,但您可以使用 -f 参数来指定其他位置的 Dockerfile 文件。
- --build-arg <key=value>:设置构建过程中的构建参数。可以在 Dockerfile 中使用 ${key} 的形式引用构建参数。例如,--build-arg VERSION=2.0 设置名为 VERSION 的构建参数的值为 2.0。
- --target <target>:指定在多阶段构建中要构建的目标阶段。Dockerfile 中可以使用 FROM 指令定义多个构建阶段,使用 --target 参数可以选择性地构建指定的阶段。
- --network <network>:指定容器连接的网络。可以将构建过程中的容器连接到指定的自定义网络。
- --no-cache:在构建过程中禁用缓存。默认情况下,Docker 会尝试使用缓存的中间层镜像来加速构建过程。使用 --no-cache 参数可以强制重新构建所有层。
- --pull:在构建过程中尝试拉取最新的基础镜像。默认情况下,Docker 会使用本地已缓存的基础镜像。使用 --pull 参数可以确保使用最新的基础镜像。
- --quiet:减少构建输出的冗余信息,只显示构建过程中的错误和警告。
- --force-rm:在构建完成后强制删除中间容器。默认情况下,Docker 会保留构建过程中使用的中间容器,以便进行调试和分析。
1.8 Docker push
- docker push <镜像名>:将指定的镜像推送到默认的远程仓库。例如:docker push myimage:latest。
- docker push <仓库地址>/<镜像名>:将指定的镜像推送到指定的远程仓库。例如:docker push myregistry.com/myimage:latest。
- docker push --all-tags <镜像名>:将指定镜像的所有标签推送到远程仓库。例如:docker push --all-tags myimage。
- docker push --disable-content-trust <镜像名>:禁用内容信任并将指定镜像推送到远程仓库。内容信任是Docker的安全功能,可以确保镜像的完整性和真实性。使用此选项可禁用该功能。例如:docker push --disable-content-trust myimage:latest。
- docker push --quiet <镜像名>:静默推送镜像,只显示推送过程中的错误信息。例如:docker push --quiet myimage:latest
1.9 Docker run
使用本地或远程pull下来的镜像,使用Docker RUN命令可创建和运行Docker容器。
- 创建容器:docker run 命令可以用来创建一个新的容器。可以通过指定镜像名称或镜像 ID 来选择要基于哪个镜像创建容器。
- 启动容器:docker run 不仅可以创建容器,还可以启动它。一旦容器被创建,它就会立即开始运行。
- 交互式模式:使用 -i 和 -t 参数,可以进入交互式模式,即使容器中没有运行任何命令。这对于需要手动执行命令或进行调试非常有用。
- 后台模式:通过添加 -d 参数,可以在后台运行容器。这使得容器可以在后台持续运行,而不会占用终端。
- 端口映射:使用 -p 参数,可以将容器内的端口映射到主机上的端口。这样可以使得容器内的服务可以被外部访问。
- 挂载数据卷:使用 -v 参数,可以将主机上的目录或文件挂载到容器内部。这样可以实现主机和容器之间的数据共享。
- 环境变量:使用 -e 参数,可以设置容器的环境变量。这样可以在容器内部传递配置信息或其他需要的参数。
- 容器名称:使用 --name 参数,可以为容器指定一个自定义的名称。这样可以方便地对容器进行识别和管理。
- 资源限制:使用 -m 参数,可以设置容器的内存限制。这样可以防止容器使用过多的内存资源。
- 执行命令:可以在 docker run 命令后面跟随要在容器内执行的命令。这些命令会覆盖镜像中定义的默认命令。
1.10 Docker数据管理
Docker默认情况下,在容器中创建的所有文件都存储在可写容器层,有以下缺点
- 当容器不再存在时,数据不会持久,如果另一个进程需要,也很难从容器中获取数据
- 容器的可写层与运行容器的主机紧密耦合,不能轻易地把数据移到别的地方
- 写入容器的可写层需要一个存储驱动程序(storage driver)来管理文件系统,后者使用Linux内核提供联合文件系统。与使用直接写入主机文件系统的数据卷(data volumes)相比,这种额外的抽象降低了性能
Docker支持将文件持久化到主机上,这样即使容器停止,文件也不会丢失,有两种方式:卷(volumes)和绑定挂载(bind mounts)。

1.10.1 数据卷(Volume)
使用场景
适用于需要持久化存储数据、跨容器共享数据以及跨主机共享数据的场景。例如,数据库数据、日志文件、配置文件等需要持久化存储的数据。
特性
- 由Docker创建和管理,并与主机的核心功能隔离。
- 数据卷可以在容器创建时指定,也可以在运行时动态创建。
- 可以在容器之间共享数据,甚至跨主机共享,因为Volume是由Docker守护程序管理的持久化存储机制。
- 数据卷的数据在容器删除后仍然存在,因此非常适合持久化存储。
1.10.2 挂载宿主机目录(Bind Mounts)
使用场景
适用于需要直接访问主机文件系统的场景,以及需要在主机和容器之间共享特定路径的数据的场景。
特性
- 将主机文件系统的目录或文件直接映射到容器中,容器中的数据与主机上的数据是同步的。
- 适用于需要直接操作主机文件系统的应用,如开发环境中的代码同步、日志文件输出等场景。
- Mounts的数据存储在主机文件系统中,不受Docker引擎管理,因此不支持跨主机共享。
- 挂载宿主机目录提供了很高的性能,但限制了容器的可移植性。
volume和mounts区别
- Volume适用于需要持久化存储和跨容器共享数据的场景,而Mounts适用于直接访问主机文件系统和在主机和容器之间共享特定路径的数据场景。
- Volume由Docker引擎管理,支持跨主机共享,而Mounts直接映射主机文件系统路径,不支持跨主机共享。
- volume可以动态创建,mounts目录必须在挂载前存在。
2.k8s基础与调度原理
2.1 应用部署方式演变
在部署应用程序的方式上,主要经历了三个时代:
- 传统部署:互联网早期,会直接将应用程序部署在物理机上
- 虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚拟机都是独立的一个环境
- 容器化部署:与虚拟化类似,但是共享了操作系统

容器化部署方式给带来很多的便利,但是也会出现一些问题,比如说:
- 一个容器故障停机了,怎么样让另外一个容器立刻启动去替补停机的容器
- 当并发访问量变化时,怎么样做到动态伸缩
这些容器管理的问题统称为容器编排问题,为了解决这些容器编排问题,就产生了一些容器编排的软件:
- Swarm:Docker自己的容器编排工具
- Mesos:Apache的一个资源统一管控的工具,需要和Marathon结合使用
- Kubernetes:Google开源的的容器编排工具

2.2 k8s简介
kubernetes,是一个全新的基于容器技术的分布式架构方案,是谷歌Borg系统的一个开源版本,于2014年9月发布第一个版本,2015年7月发布第一个正式版本。
kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化,主要提供了如下的主要功能:
- 自我修复:一旦某一个容器崩溃,能够迅速启动新的容器
- 弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整
- 服务发现:服务可以通过自动发现的形式找到它所依赖的服务
- 负载均衡:如果一个服务启动了多个容器,能够自动实现请求的负载均衡
- 版本管理:如果发现新发布的程序版本有问题,可以立即回退到原来的版本
- 存储编排:可以根据容器自身的需求自动创建存储卷
2.3 k8s组件
一个kubernetes集群主要是由控制节点(master)、工作节点(node)构成,每个节点上都会安装不同的组件。
master:集群的控制平面,负责集群的决策 ( 管理 )
ApiServer : 资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制
Scheduler : 负责集群资源调度,按照预定的调度策略将Pod调度到相应的node节点上
ControllerManager : 负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等
Etcd :负责存储集群中各种资源对象的信息
node:集群的数据平面,负责为容器提供运行环境 ( 干活 )
Kubelet : 负责维护容器的生命周期,即通过控制容器运行时(通常是docker),来创建、更新、销毁容器
KubeProxy : 负责提供集群内部的服务发现和负载均衡
Docker : 负责节点上容器的各种操作(常用的是docker,理论上也可以是别的容器运行时)

下面,以部署一个nginx服务为例,来说明kubernetes系统各个组件调用关系:
- 首先要明确,一旦kubernetes环境启动之后,master和node都会将自身的信息存储到etcd中
- nginx服务的安装请求首先会被发送到master节点的apiServer组件
- apiServer组件会调用scheduler组件来决定到底应该把这个服务安装到哪个node节点上。此时,它会从etcd中读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知apiServer
- apiServer调用controller-manager去调度Node节点安装nginx服务
- kubelet接收到指令后,会通知docker,然后由docker来启动一个nginx的pod
- 至此,一个nginx服务就运行了,如果需要访问nginx,可通过kube-proxy来对pod产生访问的代理
这样,外界用户就可以访问集群中的nginx服务了
2.4 k8s概念
Master:集群控制节点,每个集群需要至少一个master节点负责集群的管控
Node:工作负载节点,由master分配容器到这些node工作节点上,然后node节点上的docker负责容器的运行
NameSpace:命名空间,用来隔离pod的运行环境
Pod:kubernetes的最小控制单元,容器都是运行在pod中的,一个pod中可以有1个或者多个容器
Controller-manager:控制器,通过它来实现对pod的管理,比如启动pod、停止pod、伸缩pod的数量等等
Service:pod对外服务的统一入口,下面可以维护者同一类的多个pod
2.5 资源管理
2.5.1 资源管理介绍
在kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes。
kubernetes的本质上就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署服务,其实就是在kubernetes集群中运行一个个的容器,并将指定的程序跑在容器中。
kubernetes的最小管理单元是pod而不是容器,所以只能将容器放在Pod中,通过Pod控制器来管理。
Pod可以提供服务之后,就要考虑如何访问Pod中服务,kubernetes提供了Service资源实现这个功能。
当然,如果Pod中程序的数据需要持久化,kubernetes还提供了各种存储系统。

2.5.2 资源管理方式
- 命令式对象管理:直接使用命令去操作kubernetes资源
kubectl run nginx-pod --image=nginx:1.17.1 --port=80
- 命令式对象配置:通过命令配置和配置文件去操作kubernetes资源
kubectl create/patch -f nginx-pod.yaml
- 声明式对象配置:通过apply命令和配置文件去操作kubernetes资源
kubectl apply -f nginx-pod.yaml
补充:声明式对象配置跟命令式对象配置很相似,但是它只有一个命令apply。
其实声明式对象配置就是使用apply描述一个资源最终的状态(在yaml中定义状态)
使用apply操作资源:
如果资源不存在,就创建,相当于 kubectl create
如果资源已存在,就更新,相当于 kubectl patch
| 类型 | 操作对象 | 适用环境 | 优点 | 缺点 |
| 命令式对象管理 | 对象 | 测试 | 简单 | 只能操作活动对象,无法审计、跟踪 |
| 命令式对象配置 | 文件 | 开发 | 可以审计、跟踪 | 项目大时,配置文件多,操作麻烦 |
| 声明式对象配置 | 目录 | 开发 | 支持目录操作 | 意外情况下难以调试 |
2.5.2.1 命令式对象管理
kubectl命令
kubectl是kubernetes集群的命令行工具,通过它能够对集群本身进行管理,并能够在集群上进行容器化应用的安装部署。kubectl命令的语法如下:
kubectl [command] [type] [name] [flags]
comand:指定要对资源执行的操作,例如create、get、delete
type:指定资源类型,比如deployment、pod、service
name:指定资源的名称,名称大小写敏感
flags:指定额外的可选参数
查看所有pod
kubectl get pod
查看某个pod
kubectl get pod pod_name
查看某个pod,以yaml格式展示结果
kubectl get pod pod_name -o yaml
资源类型
kubernetes中所有的内容都抽象为资源,可以通过下面的命令进行查看:
kubectl api-resources
经常使用的资源有下面这些:
| 资源分类 | 资源名称 | 缩写 | 资源作用 |
| 集群级别资源 | nodes | no | 集群组成部分 |
| namespaces | ns | 隔离Pod | |
| pod资源 | pods | po | 装载容器 |
| pod资源控制器 | replicationcontrollers | rc | 控制pod资源 |
| replicasets | rs | 控制pod资源 | |
| deployments | deploy | 控制pod资源 | |
| daemonsets | ds | 控制pod资源 | |
| jobs | 控制pod资源 | ||
| cronjobs | cj | 控制pod资源 | |
| horizontalpodautoscalers | hpa | 控制pod资源 | |
| statefulsets | sts | 控制pod资源 | |
| 服务发现资源 | services | svc | 统一pod对外接口 |
| ingress | ing | 统一pod对外接口 | |
| 存储资源 | volumeattachments | 存储 | |
| persistentvolumes | pv | 存储 | |
| persistentvolumeclaims | pvc | 存储 | |
| 配置资源 | configmaps | cm | 配置 |
| secrets | 配置 |
操作
kubernetes允许对资源进行多种操作,可以通过--help查看详细的操作命令
kubectl --help
经常使用的操作有下面这些:
| 命令分类 | 命令 | 翻译 | 命令作用 |
| 基本命令 | create | 创建 | 创建一个资源 |
| edit | 编辑 | 编辑一个资源 | |
| get | 获取 | 获取一个资源 | |
| patch | 更新 | 更新一个资源 | |
| delete | 删除 | 删除一个资源 | |
| explain | 解释 | 展示资源文档 | |
| 运行和调试 | run | 运行 | 在集群中运行一个指定的镜像 |
| expose | 暴露 | 暴露资源为Service | |
| describe | 描述 | 显示资源内部信息 | |
| logs | 日志 | 输出容器在 pod 中的日志 | |
| attach | 缠绕 | 进入运行中的容器 | |
| exec | 执行 | 执行容器中的一个命令 | |
| cp | 复制 | 在Pod内外复制文件 | |
| rollout | 首次展示 | 管理资源的发布 | |
| scale | 规模 | 扩(缩)容Pod的数量 | |
| autoscale | 自动调整 | 自动调整Pod的数量 | |
| 高级命令 | apply | 声明 | 通过文件对资源进行配置 |
| label | 标签 | 更新资源上的标签 | |
| 其他命令 | cluster-info | 集群信息 | 显示集群信息 |
| version | 版本 | 显示当前Server和Client的版本 |
2.6 pod详解
2.6.1 pod介绍
pod结构

每个Pod中都可以包含一个或者多个容器,这些容器可以分为两类:
- 用户程序容器,数量可多可少,执行具体的业务
- Pause容器,这是每个Pod都会有的一个根容器,它的作用有两个:
可以以它为依据,评估整个Pod的健康状态
可以在根容器上设置Ip地址,其它容器都此IP(Pod IP),以实现Pod内部的网路通信
这里是Pod内部的通讯,Pod之间的通讯采用虚拟二层网络技术来实现.
2.6.2 Pod定义
下面是pod的资源清单:
apiVersion: v1 #必选,版本号,例如v1
kind: Pod #必选,资源类型,例如 Pod
metadata: #必选,元数据
name: string #必选,Pod名称
namespace: string #Pod所属的命名空间,默认为"default"
labels: #自定义标签列表
- name: string
spec: #必选,Pod中容器的详细定义
containers: #必选,Pod中容器列表
- name: string #必选,容器名称
image: string #必选,容器的镜像名称
imagePullPolicy: [ Always|Never|IfNotPresent ] #获取镜像的策略
command: [string] #容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [string] #容器的启动命令参数列表
workingDir: string #容器的工作目录
volumeMounts: #挂载到容器内部的存储卷配置
- name: string #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
readOnly: boolean #是否为只读模式
ports: #需要暴露的端口库号列表
- name: string #端口的名称
containerPort: int #容器需要监听的端口号
hostPort: int #容器所在主机需要监听的端口号,默认与Container相同
protocol: string #端口协议,支持TCP和UDP,默认TCP
env: #容器运行前需设置的环境变量列表
- name: string #环境变量名称
value: string #环境变量的值
resources: #资源限制和请求的设置
limits: #资源限制的设置
cpu: string #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
memory: string #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
requests: #资源请求的设置
cpu: string #Cpu请求,容器启动的初始可用数量
memory: string #内存请求,容器启动的初始可用数量
lifecycle: #生命周期钩子
postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
livenessProbe: #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
exec: #对Pod容器内检查方式设置为exec方式
command: [string] #exec方式需要制定的命令或脚本
httpGet: #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: #对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 #容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] #Pod的重启策略
nodeName: <string> #设置NodeName表示将该Pod调度到指定到名称的node节点上
nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
- name: string
hostNetwork: false #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: #在该pod上定义共享存储卷列表
- name: string #共享存储卷名称 (volumes类型有很多种)
emptyDir: {} #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
path: string #Pod所在宿主机的目录,将被用于同期中mount的目录
secret: #类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string
在kubernetes中基本所有资源的一级属性都是一样的,主要包含5部分:
- apiVersion <string> 版本,由kubernetes内部定义,版本号必须可以用 kubectl api-versions 查询到
- kind <string> 类型,由kubernetes内部定义,版本号必须可以用 kubectl api-resources 查询到
- metadata <Object> 元数据,主要是资源标识和说明,常用的有name、namespace、labels等
- spec <Object> 描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述
- status <Object> 状态信息,里面的内容不需要定义,由kubernetes自动生成
在上面的属性中,spec是接下来研究的重点,继续看下它的常见子属性:
- containers <[]Object> 容器列表,用于定义容器的详细信息
- nodeName <String> 根据nodeName的值将pod调度到指定的Node节点上
- nodeSelector <map[]> 根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的Node 上
- hostNetwork <boolean> 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
- volumes <[]Object> 存储卷,用于定义Pod上面挂在的存储信息
- restartPolicy <string> 重启策略,表示Pod在遇到故障的时候的处理策略
小提示:
在这里,可通过一个命令来查看每种资源的可配置项
kubectl explain 资源类型(如configMap) 查看某种资源可以配置的一级属性
kubectl explain 资源类型.属性(如configMap.kind) 查看属性的子属性
2.6.3 Pod生命周期
我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程:
- pod创建过程
- 运行初始化容器(init container)过程
- 运行主容器(main container)
- 容器启动后钩子(post start)、容器终止前钩子(pre stop)
- 容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
- pod终止过程

在整个生命周期中,Pod会出现5种状态(相位),分别如下:
- 挂起(Pending):apiserver已经创建了pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
- 运行中(Running):pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成
- 成功(Succeeded):pod中的所有容器都已经成功终止并且不会被重启(通常对应执行一次性任务的job类型)
- 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
- 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致
2.6.3.1 创建和终止
pod的创建过程
- 用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer
- apiServer开始生成pod对象的信息,并将信息存入etcd,然后返回确认信息至客户端
- apiServer开始反映etcd中的pod对象的变化,其它组件使用watch机制来跟踪检查apiServer上的变动
- scheduler发现有新的pod对象要创建,开始为Pod分配主机并将结果信息更新至apiServer
- node节点上的kubelet发现有pod调度过来,尝试调用docker启动容器,并将结果回送至apiServer
- apiServer将接收到的pod状态信息存入etcd中

pod的终止过程
- 用户向apiServer发送删除pod对象的命令
- apiServer中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead
- 将pod标记为terminating状态
- kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
- 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
- 如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行
- pod对象中的容器进程收到停止信号
- 宽限期结束后,若pod中还存在仍在运行的进程,那么pod对象会收到立即终止的信号
- kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已不可见
2.6.3.2 容器探测
容器探测用于检测容器中的应用实例是否正常工作,是保障业务可用性的一种传统机制。如果经过探测,实例的状态不符合预期,那么kubernetes就会把该问题实例" 摘除 ",不承担业务流量。kubernetes提供了两种探针来实现容器探测,分别是:
- 就绪探针(Readiness Probe):
- 用于指示应用程序是否已经准备好接收流量。如果就绪探针失败,K8s将不会将流量发送到该容器。
- 用于确保应用程序在启动过程中已经完成了一些初始化工作,或者在应用程序接收到流量之前,需要确保某些外部依赖项已经准备就绪。
- 存活性探针(Liveness Probe):
- 用于检测应用程序是否仍然处于活动状态。如果存活性探针失败,K8s将会尝试重启该容器。
- 存活性探针常用于检测应用程序是否已经崩溃或进入了无法响应请求的状态,以便K8s可以及时地对其进行恢复。
livenessProbe 决定是否重启容器,readinessProbe 决定是否将请求转发给容器。
上面两种探针目前均支持三种探测方式:
- Exec命令:在容器内执行一次命令,如果命令执行的退出码为0,则认为程序正常,否则不正常
- TCPSocket:将会尝试访问一个用户容器的端口,如果能够建立这条连接,则认为程序正常,否则不正常
- HTTPGet:调用容器内Web应用的URL,如果返回的状态码在200和399之间,则认为程序正常,否则不正常
2.6.3.3 重启策略
一旦容器探测出现了问题,kubernetes就会对容器所在的Pod进行重启,其实这是由pod的重启策略决定的,pod的重启策略有 3 种,分别如下:
- Always :容器失效时,自动重启该容器所在的pod,这也是默认值。
- OnFailure : 容器终止运行且退出码不为0时重启
- Never : 不论状态为何,都不重启该容器所在的pod
重启策略适用于pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为10s、20s、40s、80s、160s和300s,300s是最大延迟时长。
2.7 pod控制器详解
2.7.1 Pod控制器介绍
Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类:
- 自主式pod:kubernetes直接创建出来的Pod,这种pod删除后就没有了,也不会重建
- 控制器创建的pod:kubernetes通过控制器创建的pod,这种pod删除了之后还会自动重
什么是Pod控制器
Pod控制器是管理pod的中间层,使用Pod控制器之后,只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它会创建出满足条件的Pod并确保每一个Pod资源处于用户期望的目标状态。如果Pod资源在运行中出现故障,它会基于指定策略重新编排Pod。
在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:
- ReplicaSet:保证副本数量一直维持在期望值,并支持pod数量扩缩容,镜像版本升级
- Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本
- StatefulSet:管理有状态应用
- DaemonSet:在集群中的指定Node上运行且仅运行一个副本,一般用于守护进程类的任务
- Job:它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务
- Cronjob:它创建的Pod负责周期性任务控制,不需要持续后台运行
2.7.2 ReplicaSet(RS)
ReplicaSet的主要作用是保证一定数量的pod正常运行,它会持续监听这些Pod的运行状态,一旦Pod发生故障,就会重启或重建。同时它还支持对pod数量的扩缩容和镜像版本的升降级。

apiVersion: apps/v1 # 版本号
kind: ReplicaSet # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: rs
spec: # 详情描述
replicas: 3 # 副本数量
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: nginx-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [nginx-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
在这里面,需要新了解的配置项就是spec下面几个选项:
- replicas:指定副本数量,其实就是当前rs创建出来的pod的数量,默认为1
- selector:选择器,它的作用是建立pod控制器和pod之间的关联关系,采用的Label Selector机制在pod模板上定义label,在控制器上定义选择器,就可以表明当前控制器能管理哪些pod了
- template:模板,就是当前控制器创建pod所使用的模板板,里面其实就是前一章学过的pod的定义
2.7.3 Deployment(Deploy)
为了更好的解决服务编排的问题,kubernetes在V1.2版本开始,引入了Deployment控制器。值得一提的是,这种控制器并不直接管理pod,而是通过管理ReplicaSet来简介管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。

Deployment主要功能有下面几个:
- 支持ReplicaSet的所有功能
- 支持发布的停止、继续
- 支持滚动升级和回滚版本
Deployment的资源清单文件:
apiVersion: apps/v1 # 版本号
kind: Deployment # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: deploy
spec: # 详情描述
replicas: 3 # 副本数量
revisionHistoryLimit: 3 # 保留历史版本
paused: false # 暂停部署,默认是false
progressDeadlineSeconds: 600 # 部署超时时间(s),默认是600
strategy: # 策略
type: RollingUpdate # 滚动更新策略
rollingUpdate: # 滚动更新
maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比,也可以为整数
maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: nginx-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [nginx-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
2.7.4 DaemonSet(DS)
DaemonSet类型的控制器可以保证在集群中的每一台(或指定)节点上都运行一个副本。一般适用于日志收集、节点监控等场景。也就是说,如果一个Pod提供的功能是节点级别的(每个节点都需要且只需要一个),那么这类Pod就适合使用DaemonSet类型的控制器创建。

DaemonSet控制器的特点:
- 每当向集群中添加一个节点时,指定的 Pod 副本也将添加到该节点上
- 当节点从集群中移除时,Pod 也就被垃圾回收了
下面先来看下DaemonSet的资源清单文件
apiVersion: apps/v1 # 版本号
kind: DaemonSet # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: daemonset
spec: # 详情描述
revisionHistoryLimit: 3 # 保留历史版本
updateStrategy: # 更新策略
type: RollingUpdate # 滚动更新策略
rollingUpdate: # 滚动更新
maxUnavailable: 1 # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: nginx-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [nginx-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
2.7.5 Job
Job,主要用于负责批量处理(一次要处理指定数量任务)短暂的一次性(每个任务仅运行一次就结束)任务。Job特点如下:
- 当Job创建的pod执行成功结束时,Job将记录成功结束的pod数量
- 当成功结束的pod达到指定的数量时,Job将完成执行

Job的资源清单文件:
apiVersion: batch/v1 # 版本号
kind: Job # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: job
spec: # 详情描述
completions: 1 # 指定job需要成功运行Pods的次数。默认值: 1
parallelism: 1 # 指定job在任一时刻应该并发运行Pods的数量。默认值: 1
activeDeadlineSeconds: 30 # 指定job可运行的时间期限,超过时间还未结束,系统将会尝试进行终止。
backoffLimit: 6 # 指定job失败后进行重试的次数。默认是6
manualSelector: true # 是否可以使用selector选择器选择pod,默认是false
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: counter-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [counter-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never # 重启策略只能设置为Never或者OnFailure
containers:
- name: counter
image: busybox:1.30
command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done"]
关于重启策略设置的说明:
如果指定为OnFailure,则job会在pod出现故障时重启容器,而不是创建pod,failed次数不变
如果指定为Never,则job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数加1
如果指定为Always的话,就意味着一直重启,意味着job任务会重复去执行了,当然不对,所以不能设置为Always
2.7.6 CronJob(CJ)
CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点及重复运行的方式。也就是说,CronJob可以在特定的时间点(反复的)去运行job任务。

CronJob的资源清单文件:
apiVersion: batch/v1beta1 # 版本号
kind: CronJob # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: cronjob
spec: # 详情描述
schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
startingDeadlineSeconds: # 启动作业错误的超时时长
jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象;下面其实就是job的定义
metadata:
spec:
completions: 1
parallelism: 1
activeDeadlineSeconds: 30
backoffLimit: 6
manualSelector: true
selector:
matchLabels:
app: counter-pod
matchExpressions: 规则
- {key: app, operator: In, values: [counter-pod]}
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
需要重点解释的几个选项:
schedule: cron表达式,用于指定任务的执行时间
concurrencyPolicy:
Allow: 允许Jobs并发运行(默认)
Forbid: 禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行
Replace: 替换,取消当前正在运行的作业并用新作业替换它
2.7.7 StatefulSet
StatefulSet 控制器用于管理有状态应用程序的部署和扩展。与 Deployment 控制器不同,StatefulSet 确保其管理的 Pod 拥有唯一的标识符和稳定的网络标识符,并且它们的部署和扩展顺序是有序的。
StatefulSet的资源清单文件:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql # StatefulSet 的名称
spec:
serviceName: mysql # Headless Service 的名称,用于为 StatefulSet 中的 Pod 提供稳定的 DNS 记录
replicas: 3 # StatefulSet 中 Pod 的副本数
selector:
matchLabels:
app: mysql # 选择器,用于匹配属于该 StatefulSet 的 Pod
template: # Pod 模板
metadata:
labels:
app: mysql # Pod 的标签
spec:
containers:
- name: mysql # 容器名称
image: mysql:5.7 # 容器镜像
ports:
- containerPort: 3306 # 容器监听的端口
env:
- name: MYSQL_ROOT_PASSWORD # 环境变量:MySQL root 用户的密码
value: "password"
volumeMounts:
- name: mysql-persistent-storage # 持久化存储卷的名称
mountPath: /var/lib/mysql # 挂载路径
volumeClaimTemplates: # 持久化存储卷声明模板
- metadata:
name: mysql-persistent-storage # 持久化存储卷的名称
spec:
accessModes: [ "ReadWriteOnce" ] # 存储卷的访问模式
resources:
requests:
storage: 1Gi # 存储卷的请求存储量
主要特性:
- 稳定的网络标识符:每个 StatefulSet 中的 Pod 都具有稳定的 DNS 名称,格式为 pod-name.service-name.namespace.svc.cluster.local,这使得在应用程序中引用其他 Pod 更加方便。
- 唯一的标识符:每个 StatefulSet 中的 Pod 都具有唯一的索引,从 0 开始递增,可以通过该索引来访问或管理特定的 Pod。
- 有序部署和扩展:StatefulSet 控制器保证其管理的 Pod 的有序部署和扩展,即在扩展或缩减时,每个 Pod 都会按照预定义的顺序进行操作,确保系统的稳定性。
- 有状态的持久化存储:StatefulSet 支持动态卷声明,可以为每个 Pod 分配独立的持久化存储卷,从而实现应用程序的有状态数据持久化。
使用场景:
- 数据库集群:例如 MySQL、PostgreSQL 等数据库的部署和管理。
- 分布式缓存:如 Redis、Memcached 等分布式缓存的部署和管理。
- 分布式应用程序:一些有状态的微服务应用程序,如分布式日志记录系统、分布式队列等。
2.8 k8s调度原理
在Kubernetes(K8s)中,调度器(Scheduler)是一个核心组件,负责将Pod调度到集群中的节点上,以便实现高效的容器部署和资源利用。
2.8.1 k8s调度过程
K8s调度过程可以分为以下几个步骤:
- 接收调度请求:调度器从ApiServer接收待调度的Pod信息,包括Pod的资源需求、约束条件等。
- 筛选可用节点:调度器通过筛选集群中符合Pod资源需求和约束条件的节点,生成一个候选节点列表。
- 评分和选择节点:对于候选节点列表中的每个节点,调度器会根据一系列的调度策略和评分规则对其进行评分,最终得分最高的节点。
- 更新调度结果:调度器将选择的节点信息更新到ApiServer中,触发Kubernetes系统将Pod调度到目标节点上。
2.8.2 k8s调度策略
K8s默认提供了一些调度策略,用户也可以自定义调度策略。在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:
- 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出
- 定向调度:NodeName、NodeSelector
- 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity
- 污点(容忍)调度:Taints、Toleration
2.8.2.1 定向调度
定向调度,指的是利用在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。
NodeName(节点名称)
NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: node1 # 指定调度到node1节点上
NodeSelector(节点选择器)
NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。
1.首先分别为node节点添加标签
[root@k8s-master01 ~]# kubectl label nodes node1 nodeenv=pro
node/node2 labeled
[root@k8s-master01 ~]# kubectl label nodes node2 nodeenv=test
node/node2 labeled
2.创建一个pod-nodeselector.yaml文件,并使用它创建Pod
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeselector
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeSelector:
nodeenv: pro # 指定调度到具有nodeenv=pro标签的节点上
2.8.2.2 亲和性调度
上面介绍了两种定向调度的方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的Node,那么Pod将不会被运行,即使在集群中还有可用Node列表也不行,这就限制了它的使用场景。
基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity)。它在NodeSelector的基础之上的进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。
Affinity主要分为三类:
- nodeAffinity(node亲和性): 以node为目标,解决pod可以调度到哪些node的问题
- podAffinity(pod亲和性) : 以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题
- podAntiAffinity(pod反亲和性) : 以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题
关于亲和性(反亲和性)使用场景的说明:
亲和性:如果两个应用频繁交互,那就有必要利用亲和性让两个应用的尽可能的靠近,这样可以减少因网络通信而带来的性能损耗。
反亲和性:当应用的采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。
NodeAffinity(node亲和性)
首先来看一下NodeAffinity的可配置项:
pod.spec.affinity.nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution Node节点必须满足指定的所有规则才可以,相当于硬限制(必须)
nodeSelectorTerms 节点选择列表
matchFields 按节点字段列出的节点选择器要求列表
matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
key 键
values 值
operator 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
preferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
preference 一个节点选择器项,与相应的权重相关联
matchFields 按节点字段列出的节点选择器要求列表
matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
key 键
values 值
operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
weight 倾向权重,在范围1-100
其中,关系符的使用详细说明如下:
- matchExpressions:
- key: nodeenv # 匹配存在标签的key为nodeenv的节点
operator: Exists
- key: nodeenv # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
operator: In
values: ["xxx","yyy"]
- key: nodeenv # 匹配标签的key为nodeenv,且value大于"xxx"的节点
operator: Gt
values: "xxx"
下面,分别给出两个亲和性的使用示例。
硬限制示例:requiredDuringSchedulingIgnoredDuringExecution
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
nodeAffinity: #设置node亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
nodeSelectorTerms:
- matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
- key: nodeenv
operator: In
values: ["xxx","yyy"]
软限制示例:preferredDuringSchedulingIgnoredDuringExecution
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-preferred
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
nodeAffinity: #设置node亲和性
preferredDuringSchedulingIgnoredDuringExecution: # 软限制
- weight: 1
preference:
matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签(当前环境没有)
- key: nodeenv
operator: In
values: ["xxx","yyy"]
NodeAffinity规则设置的注意事项:
1 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上
2 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
3 如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功
4 如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化
PodAffinity(pod亲和性)
PodAffinity主要实现以运行的Pod为参照,实现让新创建的Pod跟参照pod在一个区域的功能。
PodAffinity的可配置项:
pod.spec.affinity.podAffinity
requiredDuringSchedulingIgnoredDuringExecution 硬限制
namespaces 指定参照pod的namespace
topologyKey 指定调度作用域
labelSelector 标签选择器
matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
key 键
values 值
operator 关系符 支持In, NotIn, Exists, DoesNotExist.
matchLabels 指多个matchExpressions映射的内容
preferredDuringSchedulingIgnoredDuringExecution 软限制
podAffinityTerm 选项
namespaces
topologyKey
labelSelector
matchExpressions
key 键
values 值
operator
matchLabels
weight 倾向权重,在范围1-100
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
podAffinity: #设置pod亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
- key: podenv
operator: In
values: ["xxx","yyy"]
topologyKey: kubernetes.io/hostname
上面配置表达的意思是:新Pod必须要与拥有标签nodeenv=xxx或者nodeenv=yyy的pod在同一Node上。
关于PodAffinity的 preferredDuringSchedulingIgnoredDuringExecution,这里不再演示。
PodAntiAffinity(pod反亲和性)
PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不在一个区域中的功能。
它的配置方式和选项跟PodAffinty是一样的,这里不再做详细解释。直接给出一个测试示例
apiVersion: v1
kind: Pod
metadata:
name: pod-podantiaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
podAntiAffinity: #设置pod反亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions: # 匹配podenv的值在["pro"]中的标签
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname
上面配置表达的意思是:新Pod必须要与拥有标签nodeenv=pro的pod不在同一Node上
2.8.2.3 污点和容忍
污点(Taints)
亲和性调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。
Node被设置上污点之后就和Pod之间存在了一种相斥的关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。
污点的格式为:key=value:effect, key和value是污点的标签,effect描述污点的作用,支持如下三个选项:
- PreferNoSchedule:kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度
- NoSchedule:kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已存在的Pod
- NoExecute:kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离

使用kubectl设置和去除污点的命令示例如下:
# 设置污点
kubectl taint nodes node1 key=value:effect
# 去除污点
kubectl taint nodes node1 key:effect-
# 去除所有污点
kubectl taint nodes node1 key-
注: 使用kubeadm搭建的集群,默认就会给master节点添加一个污点标记,所以pod就不会调度到master节点上.
容忍(Toleration)
上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍。

污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,Pod通过容忍忽略拒绝
piVersion: v1
kind: Pod
metadata:
name: pod-toleration
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
tolerations: # 添加容忍
- key: "tag" # 要容忍的污点的key
operator: "Equal" # 操作符
value: "heima" # 容忍的污点的value
effect: "NoExecute" # 添加容忍的规则,这里必须和标记的污点规则相同
容忍支持的详细配置:
[root@k8s-master01 ~]# kubectl explain pod.spec.tolerations
......
FIELDS:
key # 对应着要容忍的污点的键,空意味着匹配所有的键
value # 对应着要容忍的污点的值
operator # key-value的运算符,支持Equal和Exists(默认)
effect # 对应污点的effect,空意味着匹配所有影响
tolerationSeconds # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间
2.8.3 实践建议
为了充分利用K8s的调度能力,您可以遵循以下建议:
- 合理分配资源:为每个节点分配合适的资源,以满足不同Pod的需求。
- 使用标签与亲和性规则:通过标签和亲和性规则,将相关的Pod部署在同一节点上,以提高性能和可靠性。
- 设置污点与容忍度:利用污点与容忍度机制,实现特殊场景下的调度需求。
- 调整优先级:根据业务需求,为不同的Pod设置不同的优先级,以实现资源的合理分配。
总之,K8s的调度原理为我们提供了强大的容器管理和调度能力。通过了解和掌握这些原理,您可以更好地利用K8s实现高效的容器编排。
2.9 k8s资源限制
在Kubernetes中,资源限制的原理主要依赖于 Linux 内核的 cgroup(Control Groups)功能。cgroup是Linux内核提供的一种机制,用于限制、管理和隔离一个或多个进程组的资源使用。Kubernetes利用cgroup来实现对容器的资源管理和限制。
以下是Kubernetes资源限制的工作原理:
- 定义资源请求和限制:在创建Pod时,用户可以通过容器的资源请求和限制来指定容器所需的CPU和内存资源。资源请求(requests)表示容器所需的最小资源量,而资源限制(limits)则表示容器能够使用的最大资源量。
- 生成cgroup配置:当用户提交Pod到Kubernetes集群时,Kubernetes的kubelet组件会根据Pod中容器的资源请求和限制,生成相应的cgroup配置。这些配置会告诉Linux内核如何对容器的资源使用进行限制和管理。
- 应用cgroup配置:kubelet会将生成的cgroup配置应用到相应的cgroup层次结构中。每个容器都会被分配一个独立的cgroup,该cgroup会包含该容器的所有进程,并根据容器的资源请求和限制进行配置。
- 资源分配和限制:Linux内核会根据cgroup配置对容器的资源使用进行管理和限制。当容器请求的资源超过其限制时,内核会限制其使用,并可能触发相应的操作,如OOM(Out of Memory)Killer用于处理内存耗尽情况。
- 监控和调整:Kubernetes会定期监控每个Pod和容器的资源使用情况,并根据需要进行调整。如果发现某个容器的资源使用超过了其限制,Kubernetes可能会触发相应的操作,如重新调度Pod或报警通知管理员。
2.10 k8s高可用策略
Kubernetes如何做到高可用:
- 自我修复:Kubernetes的控制平面组件(如kube-scheduler、kube-controller-manager、kube-apiserver等)都是通过多个副本运行在集群中的多个节点上。如果某个节点上的组件发生故障,Kubernetes会自动将其重新调度到其他健康节点上,以确保控制平面的高可用性。
- 应用程序高可用:通过Kubernetes的故障自愈机制,如存活性探针,当应用程序发生故障时,Kubernetes可以及时地将其重启或重新调度到其他节点上,从而保证应用程序的高可用性。
- 节点高可用:Kubernetes支持在集群中配置多个节点,并且具有自动检测和替换故障节点的功能。如果某个节点发生故障,Kubernetes会自动将Pod调度到其他健康节点上,从而确保应用程序的持续可用性。
- 故障域和容错:Kubernetes支持在集群中配置多个故障域,并且具有容错机制,以确保即使在部分故障的情况下,集群仍然能够正常运行。例如,可以通过配置Pod的副本集和节点亲和性来确保Pod在不同的故障域中运行,从而提高整个集群的容错能力
2731

被折叠的 条评论
为什么被折叠?



