Kubernetes Patterns

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 个 Pod
  • maxUnavailable 表示最多额外不可用几个 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,它还有其它两个值:OnFailureNever

根据两个参数的不同,有如下几种类型的作业:

  • 单 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 结尾:httpdnamedsshd

单例服务

任何时候只有一个应用程序在运行,当然肯定是高可用的。

当我们需要 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 其实也类似于组合模式。

Adapter 模式