读书笔记:Kubernetes设计模式

Kubernetes设计模式,Bilgin Ibryam,Roland HuB著

前言 .3

使用代码示例
本书主页:https://k8spatterns.io/
Github: https://github.com/k8spatterns/

第1 章 引言 13

1.1 云原生之路 . 13

1.2 分布式原语 . 15

概念 本地原语 分布式原语
行为封装 容器镜像
行为实例 对象 容器
重用单位 jar 容器镜像
组合 A类包含B类 Sidecar模式
继承 A类继承B类 容器的FROM父镜像
部署单位 jar/.war/.ear Pod
构建时/运行时隔离 模块,包,类 命名空间,Pod,容器
初始化前提条件 构造函数 初始化容器
初始化后触发器 初始化方法 postStart
销毁前触发器 销毁方法 preStop
清理程序 fnalize(),关闭钩子 Defer容器
异步和并行执行 ThreadPoolExecutor, ForkJoinPool 作业
定期任务 Timer, ScheduledExecutorService 定时作业
后台任务 守护进程 守护进程集
配置管理 System.getenv(),属性 ConfigMap, Secret

1.2.1 容器 17

1.2.2 Pod. 18

1.2.3 服务 20

服务(Service)负责将名称永久地绑定到IP地址和端口号上。服务就相当于一组Pod的入口,可以利用服务实现服务发现和负载均衡。
1.2.4 标签 20

1.2.5 注释 22

1.2.6 命名空间. 22

1.3 讨论 24

1.4 参考资料 25

第一部分 基础模式

第2 章 可预测的需求 .29

2.1 问题 29

2.2 解决方案 30

2.2.1 运行时依赖 30

依赖于持久卷

spec:
	containers:
	- image:
	  volumeMounts:
	  - mountPath:"/logs"
	    name: log-volume
	volumes:
	- name: log-volume

依赖于ConfigMap

spec:
	containers:
	- image:
	  env:
	  - name: PATTERN
	    valueFrom:
	      configMapKeyRef:

2.2.2 资源配置文件 33

2.2.3 Pod 优先级 34

2.2.4 项目资源. 37

2.2.5 容量规划. 38

2.3 讨论 39

2.4 参考资料 39

第3 章 声明式部署 .41

3.1 问题 41

3.2 解决方案 42

3.2.1 滚动部署. 43

3.2.2 固定部署. 46

当更新过程在服务API中引入了无法向后兼容的更改,并且客户端无法处理时。这种情况我们可以利用Recreate(重建)策略。
这一操作在某一时间段内,旧版本的容器都会停止,但新容器还没有准备好处理新的请求。

3.2.3 蓝绿发布. 46

一旦绿色容器能够处理所有访问后,就可以删除蓝色容器。蓝绿发布方法的优势在于只有一个版本的应用程序响应请求。

3.2.4 金丝雀发布 48

这种技术只让一小部分消费者使用更新后的版本,可以降低将新版本引入生成的风险。
实现方法:为新容器版本创建一个新的副本集(最好使用部署),且只包含金丝雀实例的少量副本。在该阶段,服务需要将一些消费者定向到更新后的pod实例上。在我们确信新副本集能工作后,就可以扩展新副本集。这相当于一次受控的用户测试增量式部署。
3.3 讨论 48

3.4 参考资料 50

第4 章 健康检测 51

4.1 问题 51

4.2 解决方案 52

4.2.1 进程健康检查 52

kubelet针对容器进程不断执行的健康检查。如果容器进程没有运行,那么探针就会被重启。
4.2.2 存活探针. 52

4.2.3 就绪探针. 54

4.3 讨论 55

4.4 参考资料 56

第5 章 生命周期管理 .59

5.1 问题 59

5.2 解决方案 60

5.2.1 SIGTERM 信号 60

5.2.2 SIGKILL 信号 . 61

如果容器进程在收到SIGTERM信号后仍然没有关闭,那么就会被此后的SIGKILL信号强行关闭。Kubernetes不会在发送完SIGTERM信号后立即发送SIGKILL信号,在默认情况下会给30秒的宽限期。
5.2.3 postStart 钩子 61

5.2.4 preStop 钩子 . 62

5.2.5 其他生命周期控制 . 63

5.3 讨论 64

5.4 参考资料 65

第6 章 自动放置 67

6.1 问题 67

6.2 解决方案 68

6.2.1 可利用的节点资源 . 68

节点容量

可分配的容量 [可以分配给应用程序Pod的容量] =
	节点的容量 [ 节点上可以利用的容量 ]
	- Kubernetes预留的容量 [ kubelet、容器运行时等Kubernetes守护进程]
	- 系统预留的容量 [ sshd、udev等操作系统守护进程 ]

6.2.2 容器资源需求 69

6.2.3 放置策略. 69

6.2.4 调度的过程 70

6.2.5 节点亲和性 72

亲和性{(Affinity)只有当必要规则得到满足,Pod才会被调度到某个节点。节点亲和性功能还支持In、NotIn、Exists、DoesNotExist、Gt或Lt等运算符。
6.2.6 Pod 亲和性和反亲和性 73

6.2.7 污点和容忍 75

6.3 讨论 79

6.4 参考资料 81

第二部分 行为模式

本部分主要讨论Pod与管理平台之间的通信机制和交互。根据管理器的类型,Pod可以运行直到完成,或者也可以设定计划定期运行。

第7 章 批处理作业 .85

7.1 问题 85

7.2 解决方案 86

7.3 讨论 89

7.4 参考资料 90

第8 章 定期作业 91

8.1 问题 91

8.2 解决方案 92

定时作业实例类似于UNIX的crontab,它管理的是作业的时间。

kind: CronJob

8.3 讨论 94

8.4 参考资料 94

第9 章 守护进程服务 .95

9.1 问题 95

操作系统级别的守护进程是一种长期运行的自我恢复计算机程序。一般都作为后台进程运行。在UNIX中,守护程序的名称以“d”结尾,例如httpd、sshd。
9.2 解决方案 96

9.3 讨论 99

9.4 参考资料 99

第10 章 单例服务 . 101

10.1 问题 101

10.2 解决方案 . 102

10.2.1 应用程序外锁定 102

10.2.2 应用程序内锁定 105

10.2.3 Pod 中断预算 107

10.3 讨论 108

10.4 参考资料 . 108

第11 章 有状态服务 . 111

11.1 问题 111

11.1.1 存储 . 112

11.1.2 网络 . 113

11.1.3 标识 . 113

11.1.4 序数 . 114

11.1.5 其他需求 114

11.2 解决方案 . 114

11.2.1 存储 . 116

11.2.2 网络 . 117

11.2.3 标识 . 119

11.2.4 序数 . 119

11.2.5 其他特性 120

11.3 讨论 122

11.4 参考资料 . 123

第12 章 服务发现 . 125

12.1 问题 125

部署在Kubernetes上的应用程序很少单独存在,通常它们必须与集群内的其他服务或集群外的系统进行交互。这种交互可能由内部发起,也可能由外界的刺激触发。
12.2 解决方案 . 126

12.2.1 内部服务发现 . 127

12.2.2 手动服务发现 . 131

12.2.3 集群外部的服务发现 134

12.2.4 应用层服务发现 138

12.3 讨论 141

名称 配置 客户端类型 摘要
ClusterP type: ClusterIP. spec. selector 内部 最常见的内部发现机制
手动IP type: ClusterIP kind: Endpoints 内部 外部IP发现
手动FQDN type: ExternalName. spec. externalName 内部 外部FQDN发现
Headless 服务 type: ClusterIP. spec.clusterIP: None 内部 没有虚拟IP的基于DNS的发现
NodePort type: NodePort 外部 非HTTP流量首选
负载均衡器 type: LoadBalancer 外部 需要支持云基础架构
Ingress kind: Ingress 外部 基于L7/HTTP的智能路由机制

12.4 参考资料 . 142

第13 章 自我意识 . 145

13.1 问题 145

13.2 解决方案 . 146

Kubernetes的方法更优雅,更易于使用。Downward API允许通过环境变量和文件,将有关Pod的元数据传递给容器和集群。我们还可以利用相同的机制从ConfigMaps和Secrets传递应用程序相关的数据。

13.3 讨论 149

Downward API的一个劣势在于,它只提供固定数量的键,如果你的应用程序需要更多数据,尤其是其他资源或与集群相关的元数据,则可以在API服务器上查询。
13.4 参考资料 . 149

第三部分 结构化模式

第14 章 初始化容器 153

14.1 问题 153

初始化是许多编程语言都普遍关注的问题。
初始化容器与之类似,只不过不是在类级别,而是Pod级别。如果Pod中有一个或多个容器代表主应用程序,则这些容器在启动之前可能需要具备一些先决条件,其中可能包括设置文件系统。

14.2 解决方案 . 154

此外,初始化容器还可以分离关注点,让容器保持单一用途。应用程序容器可以由应用程序工程师创建,且仅关注应用程序的逻辑。初始化容器则由部署工程师编写,且仅关注配置和初始化 任务。
sidecar可以实现类似的效果,但使用sidecar时,你无法知道哪个容器会先运行,而且sidecar本来就是为持续、并排运行的容器而设计的。如果你在保证初始化的同时还需要不断更新数据,那么也可以同时使用sidecar和初始化容器。

14.3 讨论 159

初始化容器需要在应用程序容器之前运行, 更重要的是,初始化容器需要分阶段运行,只有当前的初始化容器完成之后才能进行下一个阶段,也就是说初始化的每一步都可以确保前一步已经成功。 相反,应用程序容器则采用并行方式运行,而且无法提供与初始化容器相同的保障。

14.4 参考资料 . 159

第15 章 Sidecar 161

sidecar容器可以在无需修改容器的前提下扩展并增强已有容器。

15.1 问题 161

15.2 解决方案 . 162

Git同步器(sidecar)为HTTP服务器下载内容。

flowchart TD     A[req] -->|请求| B(主容器 HTTP)     C(sidecar容器 git)     B -->|读取| D[DB]     C -->|写入| D

15.3 讨论 164

15.4 参考资料 . 165

第16 章 适配器 . 167

16.1 问题 167

16.2 解决方案 . 167

输出随机数生成器的日志文件,其中包含生成随机数所用的时间。这次我们来使用Prometheus监控。不幸的是,日志的格式与Prometheus期望的格式不相符。在这种情况下,适配器是最理想的选择:通过Sidecar容器启动一个小型HTTP服务器,并在每个请求中读取自定义的日志文件,然后将其转换为便于Prometheus理解的格式。这种配置可以解除Prometheus的耦合,因此主应用程序无需在意Prometheus的任何信息。

16.3 讨论 170

16.4 参考资料 . 171

第17 章 外交官 . 173

17.1 问题 173

17.2 解决方案 . 174

17.3 讨论 176

17.4 参考资料 . 176

第四部分 配置模式

每个应用程序都需要配置,最简单的方法是将配置存储到源代码中。这种方式存在定的弊端: 配置与代码同生共死,请参照“不可变服务器”一文的具体介绍(地址: https://martinfowler.com/bliki/ImmutableServer.html) 。然而,我们仍然需要灵活性,在不重建应用程序镜像的前提下调整配置。实际上,重建很耗时,而且与持续交付的方法背道而驰,因为我们希望只需次性创建应用程序,然后一成不变地经历部署管道的各个阶段,最后到达生产环境。

第18 章 环境变量配置 179

在环境变量配置(EnvVar Configuration)模式中,我们将介绍最简单的配置
应用程序的方法。当我们只有少量配置值时,最简单的配置外置的方法是将它们放入环境变量中,因为任何地方都支持环境变量。Kubernetes有多种声明环境变量的方法,但通过环境变量管理复杂的配置也有一定的局限性。

18.1 问题 179

18.2 解决方案 . 179

十二要素应用》(The Twelve-Factor App)宣言建议使用环境变量来存储应用程序的配置。
编程语言都可以访问这些环境变量,可以说,任何地方都可以使用环境变量。最常见的使用环境变量的方法是,在构建时定义硬编码的默认值,然后在运行时覆盖。

Docker镜像可以使用ENV指令定义环境变量。

// 示例: 在Java中读取环境变量
public Random initRandom() {
	long seed = Long.parseLong(System.getenv("SEED"));
	return new Random(seed);
}

Kubernetes中设置

  • 直接设置环境变量。env: - name: LOG_FILE
  • ConfigMap的环境变量。configMapKeyRef
  • Secret的环境变量。secretKeyRef

18.3 讨论 183

环境变量在任何地方都可以使用,因此,我们可以设置不同级别的环境变量。这种做法会导致配置定义碎片化,而且很难追踪某个环境变量的来源。如果没有一个集中的位置统一定 义所有环境变量,那么调试配置问题的难度会非常大。
环境变量的另一个 劣势在于,我们只能在应用程序启动之前设置环境变量,待应用程序启动后就无法更改了。

18.4 参考资料 . 184

第19 章 配置资源 . 185

19.1 问题 185

19.2 解决方案 . 185

19.3 讨论 190

19.4 参考资料 . 191

第20 章 不可变配置 193

20.1 问题 193

20.2 解决方案 . 193

我们可以将所有特定于环境的配置数据放人某个不活跃的数据镜像中,然后将其当作常规容器镜像发放出去。

20.2.1 Docker 卷 194

20.2.2 Kubernetes 初始化容器 195

20.2.3 OpenShift 模板 198

20.3 讨论 199

20.4 参考资料 . 200

第21 章 配置模板 203

21.1 问题 203

大型配置文件通常在不同的执行环境下仅有微小的区别。这种相似性导致ConfigMaps中存在大量重复和冗余,因为每个环境的数据大部分都相同。

21.2 解决方案 . 204

为了减少重复,ConfigMap或环境变量中应该只存储有差异的配置值(比如数据库连接参数)。然后 在启动容器期间,通过配置模板处理这些值,并创建完整的配置文件。在应用程序初始化期间处理模板的工具有很多,例如Tiller (Ruby) 或Gomplate(Go)。

21.3 讨论 209

21.4 参考资料 . 210

第五部分 高级模式

第22 章 控制器 . 213

22.1 问题 213

那么,怎样才能创建新的Pod呢?这部分工作由控制器在内部完成。每次修改资源状态(比如更改部署的replicas属性值),Kubernetes 都会建立一个事件,并将其广播给所有感兴趣的监听器。接下来,这些监听器会做出相应的反应: 修改、删除或创建新资源,而这些新资源反过来又会创建其他事件,例如Pod创建事件。然后,由其他控制器在接收到这些事件后执行特定的操作,

22.2 解决方案 . 214

flowchart TD     A[控制器]<-->|行动/观察| B(API服务器)     B --> D[节点主件 kubelet]     D -->|管理| C(应用程序运行时)

观察:当观察到的资源发生变化时,通过观察Kubernetes发布的事件来发现实际状态。
分析:确定实际状态与理想状态的差异。
行动:执行操作,推动实际状态向理想状态靠拢。

22.3 讨论 226

22.4 参考资料 . 226

第23 章 操作器 . 229

23.1 问题 229

操作器是Kubernetes的控制器,它同时拥有两个领域的知识:一个领域是Kubernetes,另一个领域是其他知识。操作器可以通过结合这两个领域的知识来自动执行任务,而通常这些任务只能由那些同时了解两个领域的人类操作员负责。

23.2 解决方案 . 230

23.2.1 自定义资源定义 230

23.2.2 控制器和操作器的分类 . 233

23.2.3 操作器的开发与部署 236

23.2.4 示例 . 239

23.3 讨论 243

23.4 参考资料 . 244

第24 章 弹性伸缩 . 247

24.1 问题 247

24.2 解决方案 . 248

应用程序的伸缩主要有两种方式:水平伸缩与重直伸缩。Kubernetes 的水平
伸缩实际上就是创建更多的Pod副本。而垂直伸缩则意味着为运行Pod管理
的容器提供更多资源。理论上看似很简单,但是在共享云平台上创建能够自
动伸缩的应用程序配置,同时又不影响其他服务和集群本身,这个过程需要
大量的反复试验。

24.2.1 手动水平伸缩 . 248

24.2.2 Pod 水平自动伸缩 250

24.2.3 Pod 垂直自动伸缩 255

24.2.4 集群自动伸缩 . 259

24.2.5 伸缩级别 262

24.3 讨论 264

24.4 参考资料 . 265

第25 章 镜像构建 . 267

25.1 问题 267

25.2 解决方案 . 269

25.2.1 OpenShift Build . 269

25.2.2 Knative Build 277

25.3 讨论 282

25.4 参考资料 . 283

后记 285

作者介绍 287

封面介绍 287

posted @ 2024-12-25 14:14  liqinglucky  阅读(6)  评论(0编辑  收藏  举报