Bootstrap

读懂k8s 容器编排控制器 Deployment

我有这样一个需求: 同一套代码,包含不同的数据处理业务逻辑线,这两条逻辑线要跑两个独立的进程,分别加载两个不同的配置文件。

但这两个进程又有某种“亲密关系”:

  • 共享一个Docker Image;

  • 共享同一个 Volume(存储卷);

  • 共享同一个 Network;

且我不想管理两个 Deployment 控制器。

那么我该如何去写这个 Deployment 控制器来编排我的应用。

我是这样写的:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: demo-app
  name: demo-app-deployment
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      volumes:
        - name: demo-app
          persistentVolumeClaim:
            claimName: demo-pvc
      imagePullSecrets:
        - name: demohub
      containers:
        - name: demo-app-user
          image: hub.demo.cn/demo/demo-app:dev
          command:
            - sh
            - -c
            - ./bin/server -c configs/dev/user.json
          env:
            - name: LOG_LEVEL
              value: "TRACE"
          imagePullPolicy: "Always"
          volumeMounts:
          - mountPath: "/var/log/demo-app"
            name: demo-app
          resources:
            limits:
              cpu: "1"
              memory: "1G"
            requests:
              cpu: "500m"
              memory: "512M"
        - name: demo-app-event
          image: hub.demo.cn/demo/demo-app:dev
          command:
            - sh
            - -c
            - ./bin/server -c configs/dev/event.json
          env:
            - name: LOG_LEVEL
              value: "TRACE"
          imagePullPolicy: "Always"
          volumeMounts:
          - mountPath: "/var/log/demo-app"
            name: demo-app
          resources:
            limits:
              cpu: "1"
              memory: "1G"
            requests:
              cpu: "500m"
              memory: "512M"

那么,如何理解这个 Deployment 呢? 先对这个 yaml 做一个简单的拆解:

图中把控制器拆成两部分,上半部分为控制器定义,下半部分是被控制对象的模板(template),这个模板在 k8s 就叫做 PodTemplate(Pod 模板)。

Deployment 控制器定义

  • apiVersion

创建该对象所使用的 Kubernetes API 的版本;

  • kind

想要创建的对象的类别;

  • metadata

唯一性标识对象的数据;

上面的例子中 metadata 包含了标识该控制器的标签(labels),对象名称(name),对象所属的 k8s 命名空间(namespace)。

  • replicas

他确保了 app=demo-app 标签的 Pod 数量等于 spec.replicas 指定的个数,即 1 个。

如果在这个集群的 namespace 中,携带 app=demo-app 标签的 Pod 的个数大于 1 的时候,就会有旧的 Pod 被删除;反之,就会有新的 Pod 被创建。

这使得 水平扩展或收缩 非常容易实现,Deployment 控制器只要修改它所控制的 ReplicaSet 的 Pod 副本个数就可以了, 效果如下:

在这个效果中,可以看到四个状态:

  • DESIRED(期望)

用户期望的 Pod 副本个数(spec.replicas 的值);

  • CURRENT(当前)

当前处于 Running 状态的 Pod 的个数;

  • UP-TO-DATE(就绪)

当前处于最新版本的 Pod 的个数。

  • AVAILABLE(已创建)

当前已经可用的 Pod 的个数,即:既是 Running 状态,又是最新版本,且已经处于健康检查正确(Ready)状态的 Pod 的个数。

利用 ReplicaSet 实现水平扩展、动态伸缩、滚动升级这里不再深入了。

被控制对象的 PodTemplate(Pod 模板)

先简单理解下 Pod,它是一个逻辑概念,可理解为是由容器组抽象而来的,类似 Linux 的进程组概念。

Pod 中的所有容器,共享的是同一个 Network,并且可以声明共享同一个 Volume。

我这里也写了 Pod 下的两个容器了共享存储卷(volumes) demo-app。

  • imagePullSecrets

imagePullSecrets 表示拉取运行 Containers 的 Docker image Docker 仓库密码对象(Secret)。

它是 k8s 支持的 Projected Volume,把 Pod 想要访问的加密数据存放到 Etcd 中。与 Secret 类似的是 ConfigMap,只不过ConfigMap 保存的是不需要加密的、应用所需的配置信息。 我写的 yaml 直接把配置 json 拷贝到容器了,没有使用 ConfigMap。

  • containers

我在的模板中 Pod 包含了两个业务逻辑的工作容器:

demo-app-user、demo-app-event。

这两个工作容器启动时加载不同的配置,但是他们共享存储,日志文件均bind mount 挂载到 volumes demo-app。

我的两个工作容器没有依赖关系,但假如我想让两个容器启动有先后顺序呢?

sidecar

  • initContainers

接着上面的问题,办法总是有的:

在 Pod 中,还有一类容器 initContainers。

所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动。

并且,Init Container 容器会按顺序逐一启动,直到它们都启动并退出了,用户容器(spec.containers )才会启动。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: demo-app
  name: demo-app-deployment
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      volumes:
        - name: demo-app
          persistentVolumeClaim:
            claimName: demo-pvc
      imagePullSecrets:
        - name: demohub
      initContainers:
        - name: demo-app-user
          image: hub.demo.cn/demo/demo-app:dev
          command:
            - sh
            - -c
            - ./bin/server -c configs/dev/user.json
          env:
            - name: LOG_LEVEL
              value: "TRACE"
          imagePullPolicy: "Always"
          volumeMounts:
          - mountPath: "/var/log/demo-app"
            name: demo-app
          resources:
            limits:
              cpu: "1"
              memory: "1G"
            requests:
              cpu: "500m"
              memory: "512M"
      containers:
        - name: demo-app-event
          image: hub.demo.cn/demo/demo-app:dev
          command:
            - sh
            - -c
            - ./bin/server -c configs/dev/event.json
          env:
            - name: LOG_LEVEL
              value: "TRACE"
          imagePullPolicy: "Always"
          volumeMounts:
          - mountPath: "/var/log/demo-app"
            name: demo-app
          resources:
            limits:
              cpu: "1"
              memory: "1G"
            requests:
              cpu: "500m"
              memory: "512M"

这种玩法通常叫做:sidecar。

让我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。

比如容器的日志收集,我在 Pod 里同时运行一个 sidecar 容器,它声明挂载同一个 Volume 到 /var/log/demo-app 目录上,不断从自己的 /var/log/demo-app 目录里读取日志文件,转发到 Elasticsearch 中存储起来。

这样就实现了日志收集并落地存储的工作。

以上。

欢迎讨论!

----------------------

公众号:life-is-x

知乎:OneZero