读书笔记: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服务器下载内容。
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
观察:当观察到的资源发生变化时,通过观察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