Kubernetes Patterns
简介
创建好的云原生应用所需要的技能:
- Domain-Driven Design:从为了使架构更加贴切现实世界的业务视角来去进行软件设计。
- 微服务架构:提供了设计分布式应用的一系列准则甚至标准。
- 容器:打包和运行分布式应用的标准。
程序员在 Kubernetes 中可用的资源:
可预测的需求
你应该怎样声明你应用程序需要哪些 runtime 依赖、资源需求?
磁盘
容器文件系统是临时的,容器关闭的时候就消失了;Kubernetes 提供了 Pod 级别的存储:volume
。如果应用需要比 Pod 级别更长生命周期的存储机制,那么需要使用 volumnes
来主动声明:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
volumeMounts:
- mountPath: "/logs"
name: log-volume
volumes:
- name: log-volume
persistentVolumeClaim:
claimName: random-generator-log
参数配置
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
env:
- name: PATTERN
valueFrom:
configMapKeyRef:
name: random-generator-config
key: pattern
资源限制
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
requests
声明的是初始的资源限制,而 limits
是我们能使用的最大的资源限制。
Pod 优先级
资源饥荒的时候应该先杀死哪个 Pod?应该保留哪个 Pod?
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
globalDefault: false
description: This is a very high priority Pod class
---------
apiVersion: v1
kind: Pod
metadata:
name: random-generator
labels:
env: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
priorityClassName: high-priority
声明式部署
服务上线过程:平滑的停止旧的 Pod,启动新的 Pod,等待,验证,或者回滚 … 这些事情要靠人去做吗?
滚动更新
这种机制确保服务零停机:
apiVersion: apps/v1
kind: Deployment
metadata:
name: random-generator
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: random-generator
template:
metadata:
labels:
app: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
readinessProbe:
exec:
command: [ "stat", "/random-generator-ready" ]
maxSurge
表示最多额外多运行几个 Pod,也就是此次最多运行 4 个 PodmaxUnavailable
表示最多额外不可用几个 Pod,也就是此次至少 2 个 Pod 处于可以运行状态
Fixed 部署
滚动更新的弊端:两个不同版本的服务同时运行,有些情况下可能不太合适。使用 Recreate
策略即可。
蓝绿发布
先把绿色的都启动起来,然后一把切换流量:
金丝雀发布
替换一小部分的实例为新的版本:
对比
健康探测
如何知道一个服务是否能够接受请求?
Liveness 探测
怎么知道程序死锁了?
Kubelet 代理定时询问容器是否依然健康。
- HTTP GET 方法发送到 IP 地址,期望得到一个位于 200-399 之间的 HTTP 响应码
- TCP Socket 检测是否可以正常创建 TCP 连接
Exec
探测是否可以执行命令,并且返回码是0
apiVersion: v1
kind: Pod
metadata:
name: pod-with-liveness-check
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
env:
- name: DELAY_STARTUP
value: "20"
ports:
- containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
Readiness 探测
容器正在启动、容器过载、容器响应延迟比较大,你打算让它缓一下过一会儿再接受请求。readiness 探测如果失败,那么这个容器将会被移除掉,不会再接受任何请求。
apiVersion: v1
kind: Pod
metadata:
name: pod-with-readiness-check
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
readinessProbe:
exec:
command: ["stat", "/var/run/random-generator-ready"]
生命周期管理
成为云原生世界的公民,就要听从这个平台发布的各种事件、关注平台的各种生命周期。
SIGTERM
信号:Kubernetes 要关闭容器了。收到这个信号,应用程序应该应该要尽快的 shut down 自己。SIGKILL
信号:SIGTERM
之后容器进程没有 shut down,那么SIGKILL
信号将会在等待 30 秒 (.spec.terminationGracePeriodSeconds
) 之后发出来以便强制关闭。postStart
hook:容器创建之后将会执行,postStart
执行期间,Pod 处于Pending
状态。
apiVersion: v1
kind: Pod
metadata:
name: post-start-hook
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
lifecycle:
postStart:
exec:
command:
- sh
- -c
- sleep 30 && echo "Wake up!" > /tmp/postStart_done
preStop
hook:容器 terminated 之前将会执行。
apiVersion: v1
kind: Pod
metadata:
name: pre-stop-hook
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
lifecycle:
preStop:
httpGet:
port: 8080
path: /shutdown
Automated Placement
这种功能是 Kubernetes 的核心功能,它能够自动将 Pod 放置到正确的节点上,以便满足容器资源以及调度策略的需要。调度器做的事情简而言之:为每一个新创建的 Pod 选择一个节点。
调度策略示例:
{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
{"name" : "PodFitsHostPorts"},
{"name" : "PodFitsResources"},
{"name" : "NoDiskConflict"},
{"name" : "NoVolumeZoneConflict"},
{"name" : "MatchNodeSelector"},
{"name" : "HostName"}
],
"priorities" : [
{"name" : "LeastRequestedPriority", "weight" : 2},
{"name" : "BalancedResourceAllocation", "weight" : 1},
{"name" : "ServiceSpreadingPriority", "weight" : 2},
{"name" : "EqualPriority", "weight" : 1}
]
}
调度过程
某些情况下,选择合适 node 可能需要一些约束条件,比如必须选择具备 SSD 磁盘类型的:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
nodeSelector:
disktype: ssd
批量作业
用来可靠地运行 short-lived Pods,运行完之后然后关闭容器。
创建 Pod 有三种方式:
- Bare Pod:手工创建 Pod 以便运行容器
- ReplicaSet:确保 Pod 运行的平稳持续
- DaemonSet:在每一个节点上运行单独 Pod 的 Controller,一般用于:监控、日志聚合、存储容器等
上述 Pod 都属于长期运行的进程。
Kubernetes Job
这种 Job 创建数个 Pod,然后确保它们运行成功。如果运行结束,那么 Pod 也会关闭。Job 运行结束,不会被删除以便用以追踪的目的,Pod 结束也不会被删除以便检查容器日志。
apiVersion: batch/v1
kind: Job
metadata:
name: random-generator
spec:
completions: 5
parallelism: 2
template:
metadata:
name: random-generator
spec:
restartPolicy: OnFailure
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
command: [ "java", "-cp", "/", "RandomRunner", "/numbers.txt", "10000" ]
上述定义了一个 Job,运行 5 个 Pod,这 5 个 Pod 必须都成功,最多并行两个 Pod。.spec.template.spec.restartPolicy
在 ReplicaSet 中的默认值是 Always
,它还有其它两个值:OnFailure
和 Never
。
根据两个参数的不同,有如下几种类型的作业:
- 单 Pod 作业:
.completions
和.parallelism
都不设置,也就是都是默认值 1. - 固定完成数的作业:
.completions
是大于 1 的值。也就是只有满足.completions
个 Pod 都成功,才会人为是成功的。 - 任务队列的作业:
.completions
和.parallelism
都大于 1.
周期作业
- Cron、Quartz、Spring Batch、
ScheduledThreadPoolExecutor
高可用不是很容易
CronJob
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: random-generator
spec:
# Every three minutes
schedule: "*/3 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
command: [ "java", "-cp", "/", "RandomRunner", "/numbers.txt", "10000" ]
restartPolicy: OnFailure
Daemon 服务
daemon 通常指一个长期运行、自我恢复的一个后台程序。在 Unix 中,daemons 的服务通常以 d
结尾:httpd
、named
、sshd
。
单例服务
任何时候只有一个应用程序在运行,当然肯定是高可用的。
当我们需要 active-passive (或者也可以叫做 master-slave) 拓扑结构的时候,我们就需要确保只有一个实例是激活的状态,其它的都是 passive 状态。
Out-of-Application 锁
Kubernetes 启动一个具有一个副本的 Pod,当然我们还需要为 Pod 配备一个 controller 例如 ReplicaSet 来让其变为高可用的单例。注意 ReplicaSet 在副本的个数层级,执行的语义是 “at least” 而非 “at most”。能够提供严格单例保证的是 StatefulSet,其更倾向于一致性。
In-Application 锁
这种实现需要外部锁,例如 ZooKeeper、Consul、Redis、Etcd 等实现的锁。在 Kubernetes 中,最好的方式是使用自带的 API 来通过 Etcd 来实现锁的能力,Etcd 内部通过 Raft 协议来维护副本一致性。
有状态的服务
StatefulSet
在 Kubernetes 中运行的有状态的分布式应用程序:
服务发现
在 Kubernetes 到来之前,一般服务发现都是采用如下 Client-side 服务发现的方式:
如下展示了 Kubernetes 是如何进行服务注册与发现的 (Server-side 服务发现):
Self Awareness
询问 Kubernetes 的一些元信息:
Init 容器
就是执行一些初始化逻辑,这些逻辑是运行你应用程序的前置条件,是强依赖:
Init 容器需要是幂等的。
Sidecar
Sidecar 无需改变即可增强单容器的功能。一个理想的容器功能应该像 Linux 的一个进程一样,它解决并且只解决一件事,将他解决好。Sidecar 模式提供了达到可重用/扩展容器功能/容器间协作的方式。
下面是一种 Sidecar 示例:
Sidecar 其实也类似于组合模式。