Kubernetes常用资源对象-DaemonSet和StatefulSet

前面我们学习了大部分资源对象的使用方法,通过部署 WordPress 的案例把我们前面学到的内容进行融会贯通。今天我们来讲讲另外一个 Pod 控制器的使用方法,我们前面主要讲解的是 Deployment 这种对象资源的使用,接下来我们要讲解的是在特定场合下使用的控制器: DaemonSet 与 StatefulSet 。

1、DaemonSet 的使用

通过该控制器的名称我们可以看出它的用法:Daemon,就是用来部署守护进程的, DaemonSet 用于在每个 Kubernetes 节点中将守护进程的副本作为后台进程运行,说白了就是在每个节点部署一个 Pod 副本,当节点加入到 Kubernetes 集群中, Pod 会被调度到该节点上运行,当节点从集群中被移除后,该节点上的这个 Pod 也会被移除,当然,如果我们删除 DaemonSet ,所有和这个对象相关的 Pods 都会被删除。

在哪种情况下会需要用到这种业务场景呢?其实这种场景还是比较普通的,比如:

  • 集群存储守护程序,如 glusterd 、 ceph 要部署在每个节点上以提供持久性存储;
  • 节点监视守护进程,如 Prometheus 监控集群,需要在每个节点上运行⼀个 node-exporter 进程来收集监控节点的信息;
  • 日志收集守护程序,如 fluentd 或 logstash ,在每个节点上运行可以收集容器的日志。

这里需要特别说明的是关于 DaemonSet 运行的 Pod 的调度问题,正常情况下, Pod 运行在哪个节点上是由 Kubernetes 的调度器策略来决定的,然而,由 DaemonSet 控制器创建的 Pod 实际上提前已经确定了在哪个节点上了( Pod 创建时指定了 .spec.nodeName ),所以我们可以看出:

  • DaemonSet 并不关心节点的 unshedulable 字段;
  • DaemonSet 可以创建 Pod ,即使调度器还没有启动(这点非常重要)。

话不多说,下⾯我们直接通过⼀个示例来演示,在每个节点上部署⼀个 Nginx Pod :(nginx-ds.yaml)

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx
  labels:
    k8s-app: nginx
spec:
  selector: 
    matchLabels: 
      k8s-app: nginx
  template:
    metadata:
      labels:
        k8s-app: nginx
    spec:
      containers:
      - image: nginx:1.7.9
        name: nginx
        ports:
        - name: http
          containerPort: 80

我们直接创建,创建完成之后我们可以通过 kubectl get pods -o wide 命令看到、nginx pod被调度到了Node01和Node02上:

img

2、StatefulSet 的使用

在学习 StatefulSet 这种控制器之前,我们就得先弄明白⼀个概念:什么是有状态服务?什么是无状态服务?

无状态服务(Stateless Service):该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同⼀个请求响应的结果是完全⼀致的,比如前⾯我们讲解的 WordPress 实例,我们可以同时启动多个实例,但是我们访问任意⼀个实例得到的结果都是⼀样的。因为他唯⼀需要持久化的数据是存储在 MySQL 数据库中的,所以我们可以说 WordPress 这个应⽤是无状态服务,但是 MySQL 数据库就不是了,因为他需要把数据持久化到本地。

有状态服务(Stateful Service):和上⾯的概念是对立的了,该服务运行的实例需要在本地存储持久化数据,比如上⾯的 MySQL 数据库,现在运行在节点A,那么他的数据就存储在节点A上⾯的,如果这个时候你把该服务迁移到节点B去的话,那么就没有之前的数据了,因为他需要去对应的数据目录里面恢复数据,而此时没有任何数据。

现在我们对有状态和无状态有⼀定的认识了,比如我们常见的 WEB 应用,是通过 session 来保持用户登录状态的,如果我们将 session 持久化到节点上,那么该应用就是⼀个有状态的服务了,因为现在登录进来你把我的 session 持久化到节点A上了,下次我登录的时候可能会将请求路由到节点B上去了,但是节点B上根本就没有我当前的 session 数据,就会被认为是未登录状态了,这样就导致前后两次请求得到的结果不⼀致了。所以⼀般为了横向扩展,我们都会把这类 WEB 应用改成无状态的服务,怎么改?将 session 数据存入⼀个公共的地方⽅,比如存放到 redis 里面,对于⼀些客户端请求 API 的情况,我们就不使用 session 来保持用户状态,改成 token 也是可以的。

无状态服务利用我们前⾯的 Deployment 或者 RC 都可以很好的控制。而有状态服务,需要考虑的细节就要多很多了,容器化应用程序最困难的任务之⼀,就是设计有状态分布式组件的部署体系结构。由于无状态组件可能没有预定义的启动顺序、集群要求、点对点 TCP 连接、唯⼀的网络标识符、正常的启动和终止要求等,因此可以很容易地进行容器化。诸如数据库,大数据分析系统,分布式key/value 存储和 message brokers 可能有复杂的分布式体系结构,都可能会用到上述功能。为此, Kubernetes 引入了 StatefulSet 资源对象来支持这种复杂的需求。

StatefulSet 类似于 ReplicaSet ,但是它可以处理 Pod 的启动顺序,为保留每个Pod 的状态设置唯⼀标识,同时具有以下功能:

  • 稳定的、唯⼀的网络标识符
  • 稳定的、持久化的存储
  • 有序的、优雅的部署和缩放有
  • 序的、优雅的删除和终止
  • 有序的、⾃动滚动更新

3、创建StatefulSet

接下来我们来给大家演示下 StatefulSet 对象的使用方法,在开始之前,我们先准备两个1G的存储卷(PV),在后⾯的章节中我们也会和大家详细讲解 PV 和 PVC 的使用方法的,这里先不深究:(pv-demo.yaml)

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    release: stable
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: /tmp/data
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    release: stable
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: /tmp/data

然后创建,然后可以看到成功创建了两个 PV对象,状态是:Available:

img

然后我们使用 StatefulSet 来创建⼀个 Nginx 的 Pod,对于这种类型的资源,我们⼀般是通过创建⼀个 Headless Service 类型的服务来暴露服务,将 ClusterIP 设置为 None 就是⼀个无头的服务:(statefulset-demo.yaml)

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None # 这里不需要clusterIP来做负载均衡
  selector:
    app: nginx
    role: stateful
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector: 
    matchLabels:  
      app: nginx
  serviceName: "nginx"
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
        role: stateful
    spec:
      containers:
      - name: nginx
        image: cnych/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

注:上⾯的 YAML 文件中和 volumeMounts 进行关联的是⼀个新的属性:volumeClaimTemplates,该属性会自动声明⼀个 pvc 对象和 pv 进行管理。

然后我们开启两个终端窗口。在第⼀个终端中,使用 kubectl get 来查看 StatefulSet 的 Pods 的创建情况:

img

在另⼀个终端中,使用 kubectl create 来创建定义在 statefulset-demo.yaml 中的 Headless Service 和 StatefulSet:

img

4、检查Pod的顺序索引

对于一个拥有 N 个副本的 StatefulSet,Pod 被部署时是按照 {0..N-1}的序号顺序创建的。在第⼀个终端中我们可以看到如下的⼀些信息:

img

注:在 web-0 Pod 处于 Running 和 Ready 状态后 web-1 Pod 才会被启动。

如同 StatefulSets 概念中所提到的, StatefulSet 中的 Pod 拥有⼀个具有稳定的、独⼀无⼆的身份标志。这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯⼀顺序索引。 Pod 的名称的形式为 。web StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0 和 web-1。

上⾯的命令创建了两个 Pod,每个都运行了⼀个 NGINX web 服务器。获取 nginx Service 和 web StatefulSet 来验证是否成功的创建了它们:

img

5、使用稳定的网络身份标识

每个 Pod 都拥有⼀个基于其顺序索引的稳定的主机名。使用 kubectl exec 在每个 Pod 中执行hostname:

img

headless service 的 CNAME 指向 SRV 记录(记录每个 Running 和 Ready状态的 Pod)。SRV 记录指向⼀个包含 Pod IP 地址的记录表项。然后我们再来看下删除 StatefulSet 下面的 Pod,在⼀个终端中查看 StatefulSet 的 Pod,在另⼀个终端中使用 kubectl delete 删除 StatefulSet 中所有的 Pod,等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态:

img

我们可以看到Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能会发生改变。所以说这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要。⼀般情况下我们直接通过 SRV 记录连接就行:web-0.nginx、web-1.nginx,因为他们是稳定的,并且当你的 Pod 的状态变为 Running 和 Ready 时,你的应⽤就能够发现它们的地址。同样我们可以查看 PV、PVC的最终绑定情况:

img

关于存储卷的使用的,我们会在后面继续讲解。当然 StatefulSet 还拥有其他特性,在实际的项目中,我们还是很少会直接通过 StatefulSet 来部署有状态服务的,除非你自己能够完全能够 hold 住,对于⼀些特定的服务,我们可能会使用更加高级的 Operator 来部署,比如 etcd-operator、prometheus-operator 等等,这些应用都能够很好的来管理有状态的服务,而不是单纯的使用⼀个 StatefulSet 来部署⼀个 Pod就行,因为对于有状态的应用最重要的还是数据恢复、故障转移等等。

推荐文章