Kubernetes系列(四)StatefulSet

作者: LemonNan
原文地址: https://juejin.im/post/6870071267438329869

Kubernetes 系列

  • Kubernetes系列(一) Pod
  • Kubernetes系列(二) Service
  • Kubernetes系列(三) Deployment

StatefulSet

前言

前几篇讲解的都是关于无状态的应用, 今天这篇介绍 Kubernetes 中的另一种应用, 有状态应用, 而 有状态的应用是由 StatefulSet 来进行管理部署 , **StatefulSet 也是 kubernetes 中的一种资源类型, 作用类似于无状态应用的 Deployment **.

操作的基础

最近的文章都是基于 minikube 进行的操作, 所以时不时的需要进入到 minikube 里面进行一些操作的验证, 所以把进入 minikube 的操作记录在这.

# minikube 默认用户名:docker 密码:tcuser , 我的地址是 192.168.99.100
ssh [email protected]

概念介绍

无状态 VS 有状态

之前介绍的无状态应用, 看过的朋友一定有印象, 在 Deployment 控制器每次调度的时候, 比如关掉了一个 Pod, 然后再重新启动一个 Pod, 那 Pod 的名称一定是不相同的, 虽然前缀都一样, 但是后面的随机字符串每次都是不同的, 也就是如果之前对应服务保存了一些东西, 后面启动的 Pod 无法使用, 从这里就引申出了 能继续使用之前的比如持久化数据 , 也就是 有状态应用.

而有状态应用呢, 主要用于一些数据的保存和恢复, 简单来说就是, 可以恢复之前使用时的应用状态(信息及数据) .


Deployment VS StatefulSet

Deployment 中的Pod名称包含随机字符串, 索尼每次启动 Pod 的名称都不一样, 而 StatefulSet 每次启动的名称都会一样, Pod 名称是集群中的一个唯一标识.

基于 StatefulSet 创建的应用的启动顺序是有严格限制的, 其基于 init controller 实现. 当被创建的时候, StatefulSet 创建应用的顺序为 0 ~ N-1, 而删除的时候, 应用的终止顺序为 N-1 ~ 0. 而 Deployment 的创建是无序的.

当扩容的时候, 必须前面的 N 个容器都必须处于 Running 和 Ready 状态, 才能继续创建后面的容器.


Headless Service

在 StatefulSet 中, 使用的是 Headless Service (无头服务) , 与之前介绍的 Service 的区别就是它没有 Cluster IP, 解析它的名称的时候返回的是 该 Headless Service 对应的 Pod 的 Endpoint 列表 (也就是 Pod 列表, 在做微服务的时候, 使用它 - Headless Service 是个不错的选择).

Headless Service 在这里很重要, 因为它还会对 每个 Pod 都设置集群内部的一个 DNS 域名 , 集群中的 Pod 可以通过该域名相互通信.

Headless 下的 Pod 的 DNS 域名

$(podname).$(service name).$(namespace).svc.cluster.local

并且通过 statefulSet 创建的 有状态 Pod 的名称是在集群内部的唯一标识, 所以 通过这个域名访问到的, 可以确定就是集群内部的唯一的有状态的 Pod .


ConfigMap

本文会简单的使用到 Kubernetes 中的另一种资源类型 ConfigMap, 它可以作为环境变量、命令行参数或者存储卷中的配置文件, 它保存一些私密性不强的信息, 在这里我们使用它存储 redis 的一些简单配置.


配置文件 yaml

今天我们使用 redis 作为基础的容器镜像(这次放过nginx)


ConfigMap

在创建 StatefulSet 之前需要创建 存储redis配置的 configMap.

# ConfigMap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: 2020-09-07T12:01:49Z
  name:  lemonnan-redis-config
  namespace: default
  resourceVersion: "111"
  selfLink: /api/v1/namespaces/default/configmaps/lemonnan-redis-config
  uid: 11a0c66c-2d9a-4be9-b7ac-ec19037392a4
data:
  redis-conf: |
    appendonly yes  
    save 900 1  
    # cluster-enabled yes  # 单机测试的时候先去掉这几个 cluster 的东西
    # cluster-config-file /var/lib/redis/nodes.conf  
    # cluster-node-timeout 5000  
    dir /var/lib/redis    # 用于存放数据的路径
    port 6379  

Headless Service

# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: headless-service
spec:
  selector:
    app: label-redis  # 管理拥有 app: label-redis 标签的Pod
  ports:
    - protocol: TCP
      port: 6379
      targetPort: 6379
  clusterIP: None

StatefulSet

# Statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: statefulset-redis
spec:
  selector:
    matchLabels:
      app: label-redis
  serviceName: headless-service
  replicas: 1
  template:
    metadata:
      labels:
        app: label-redis
    spec:
      terminationGracePeriodSeconds: 30 # 默认30s
      containers:
      - name: pod-redis
        imagePullPolicy: Never
        image: redis:latest
        command:
          - redis-server                # 使用指定路径 conf 启动redis
          - "/etc/redis/redis.conf"     # 使用 configMap 后存放的配置文件的路径
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: redis-conf              # 配置文件 volume 名称
          mountPath: /etc/redis         # 容器中存储 redis.conf 的地址
        - name: redis-data              # 持久数据 volume 名称
          mountPath: /var/lib/redis     # 容器内的数据存放在这里(onfigMap 中的 dir 路径), 然后根据下面的 volume 模板进行数据持久化
      volumes:
      - name: redis-conf                # 
        configMap:
          name: lemonnan-redis-config   # configMap 名称
          items:
            - key: redis-conf           # configMap 名称
              path: redis.conf          # 这个是配置文件名称, 总的在容器中的路径就是 /etc/redis/redis.conf
  volumeClaimTemplates:      						# 用于持久化的模板              
  - metadata:
      name: redis-data
    spec:
      accessModes:
      - ReadWriteMany
      resources:
        requests:
          storage: 200M

redis 单机

根据上面的配置文件创建好ConfigMap、Service、StatefulSet 之后, 创建redis单机相对来说还算是比较简单的, 基本上可以一路畅通无阻.


创建资源

创建 ConfigMap、HeadlessService、StatefulSet

Kubernetes系列(四)StatefulSet_第1张图片


进入redis pod

Kubernetes系列(四)StatefulSet_第2张图片

添加数据

Kubernetes系列(四)StatefulSet_第3张图片

重启 redis pod

使用 delete 命令, StatefulSet 会检测到 Pod 终止并且自动开启新的 Pod, 这里(箭头所指)的容器ID已经不一样了.

Kubernetes系列(四)StatefulSet_第4张图片

查看数据恢复

最后进入到重启后的 redis Pod, 检查上一次 Pod 关闭前添加进去的数据, 有没有持久化下来.

Kubernetes系列(四)StatefulSet_第5张图片


redis 集群

如果要设置redis集群的话, 需要在单机的基础上修改一下东西

1.首先需要把 ConfigMap 中关于 cluster 的信息打开

2.在 StatefulSet.yaml 中设置 spec.replicas: 6(3主3从), 并且在所有 redis启动后, 设置相应的集群信息.


修改配置

下面是修改后的配置

ConfigMap.yaml

# Configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: 2020-09-07T12:01:49Z
  name:  lemonnan-redis-config
  namespace: default
  resourceVersion: "111"
  selfLink: /api/v1/namespaces/default/configmaps/lemonnan-redis-config
  uid: 11a0c66c-2d9a-4be9-b7ac-ec19037392a4
data:
  redis-conf: |
    appendonly yes  
    save 900 1  
    cluster-enabled yes  
    cluster-config-file /var/lib/redis/nodes.conf  
    cluster-node-timeout 5000  
    dir /var/lib/redis
    port 6379  

StatefulSet.yaml

# StatefulSet.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: statefulset-redis
spec:
  selector:
    matchLabels:
      app: label-redis
  serviceName: headless-service
  replicas: 6
  template:
    metadata:
      labels:
        app: label-redis
    spec:
      terminationGracePeriodSeconds: 30 # 默认30s
      containers:
      - name: pod-redis
        imagePullPolicy: Never
        image: redis:latest
        command:
          - redis-server                # 使用指定路径 conf 启动redis
          - "/etc/redis/redis.conf"     # 使用 configMap 后存放的配置文件的路径
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: redis-conf              # 配置文件 volume 名称
          mountPath: /etc/redis         # 容器中存储 redis.conf 的地址
        - name: redis-data              # 持久数据 volume 名称
          mountPath: /var/lib/redis     # 容器内的数据存放在这里(onfigMap 中的 dir 路径), 然后根据下面的 volume 模板进行数据持久化
      volumes:
      - name: redis-conf                # 
        configMap:
          name: lemonnan-redis-config   # configMap 名称
          items:
            - key: redis-conf           # configMap 名称
              path: redis.conf          # 这个是配置文件名称, 总的在容器中的路径就是 /etc/redis/redis.conf
  volumeClaimTemplates:                 # 用于持久化的模板       
  - metadata:
      name: redis-data
    spec:
      accessModes:
      - ReadWriteMany
      resources:
        requests:
          storage: 200M

创建完成之后,接下来开始配置 redis-cluster

首先根据之前 Headless 创建的 Pod 域名为

$(podname).$(service name).$(namespace).svc.cluster.local

so, 这里的话应该是

redis-app-0.headless-service.default.svc.cluster.local:6379
redis-app-1.headless-service.default.svc.cluster.local:6379
redis-app-2.headless-service.default.svc.cluster.local:6379
redis-app-3.headless-service.default.svc.cluster.local:6379
redis-app-4.headless-service.default.svc.cluster.local:6379
redis-app-5.headless-service.default.svc.cluster.local:6379

由于 redis-cluster 目前的版本还不支持域名的配置, so 这里我们还是使用 IP 进行搭建, 最终在redis Pod 中执行以下命令构建redis集群:

注意: 在执行下面的命令的时候, 如果是从上面过来的, 需要先将 /var/lib/redis 下的文件都删掉, 否则集群创建失败.

# 这几个是我本机上的redis pod 的ip地址
redis-cli --cluster create --cluster-replicas 1 \
172.17.0.2:6379 \
172.17.0.6:6379 \
172.17.0.7:6379 \
172.17.0.8:6379 \
172.17.0.9:6379 \
172.17.0.10:6379 

创建结果

Kubernetes系列(四)StatefulSet_第6张图片

集群创建成功了, 然后测试设置数据

redis-cli 启动不加 -c 参数的话, 会报 MOVED 错误, 需要自己找到对应的redis节点进行设置, 加了 -c 参数后会自动重定向到对应节点, 具体见下图

Kubernetes系列(四)StatefulSet_第7张图片

到这里redis集群的搭建也完成了

总结

本文介绍了有状态应用相关的东西, 看似挺有用的, 能持久化数据, 宕机恢复后还能继续使用之前已经持久化下来的数据, 但实际上, 一般比较重要的组件/数据不会使用容器运行, 比如数据库, 有以下几个重要的原因:

1.容器占用资源过多很有可能会被 OS kill 掉, 而像数据库这样的组件被 kill 掉基本上就意味着数据丢失.

2.容器是在 OS 层面上做的一个逻辑层, 有一定的性能损耗, 并且数据库的成本也是很高的. 像比如传统的关系型数据库, 一般配置不高的服务器 TPS 本身就不高, 再弄一个逻辑层损耗一下性能, TPS 就更低了( 钱突然就没了 ).

so, 有状态应用的话一般使用也是用于存储一些不太重要的数据, 数据库这种存储比较重要信息的组件基本上不会使用容器进行管理.

你可能感兴趣的:(Kubernetes,docker,Linux,kubernetes,docker,分布式)