# Kubernetes存储

# 为什么需要存储卷

volume

容器部署过程中一般有以下三种数据:

  • 启动时需要的初始数据,例如配置文件
  • 启动过程中产生的临时数据,该临时数据需要多个容器间共享
  • 启动过程中产生的持久化数据,例如MySQL的data

# 数据卷概述

  • Kubernetes中的Volume提供了在容器中挂载外部存储的能力

  • Pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后才可以使用相应的Volume

# 常用的数据卷

  • 本地(hostPath,emptyDir)
  • 网络(NFS,Ceph,GlusterFS)
  • 公有云(AWS EBS)
  • K8S资源(configmap,secret)

# 使用数据卷的流程

镜像里程序写的目录 -> mountPath 挂载到写的目录 -> 引用的volumes对应的name -> 相关卷的配置

# 临时存储卷: emptyDir

# emptyDir卷

是一个临时存储卷,与Pod生命周期绑定一起,如果Pod删除了卷也会被删除。

# 应用场景

Pod中容器之间数据共享

# 一个简单的yaml示例

apiVersion:  v1
kind:  Pod
metadata: 
  name:  my-pod
spec: 
  containers: 
  - name:  wirte
    image:  centos
    command:  ["bash","-c","for i in {1..100}; do echo $i >> /data/hello; sleep 1; done"]
    volumeMounts: 
    - name:  data
      mountPath:  /data
  
  - name:  read
    image:  centos
    command:  ["bash","-c","tail -f /data/hello"]
    volumeMounts: 
      - name:  data
        mountPath:  /data
  
  volumes: 
  - name:  data
    emptyDir:  {}

# 简单说明

  • 创建一个名为my-pod的pod
  • pod中有两个容器,一个名为write,用于写数据,一个名为read,用于读取数据
  • 其中write使用镜像centos作为基础镜像,将1-100写入/data目录的hello文件中,通过名称name: data挂载到共享存储volume下
  • read也是用centos作为基础镜像,读取/data目录的hello文件中的内容,也是通过name: data挂载到共享存储volume下
  • volumes配置用于创建名为data的emptyDir卷

# 执行一下

$ kubectl apply -f emptydirep.yaml
pod/my-pod created

$ kubectl get pod
NAME                      READY   STATUS    RESTARTS   AGE
my-pod                    2/2     Running   0          63s
......

$ kubectl logs -f my-pod  -c write
$ kubectl logs -f my-pod  -c write --tail /data/hello
1
......
100
$ kubectl logs -f my-pod  -c read
1
......
100

# 执行结果说明

  • -c命令用于指定pod中查看的容器名称
  • write容器不会有任何输出
  • 查看write容器中,/data/hello的内容,可以正常输出1-100
  • read容器正常输出1-100

# 节点存储卷: hostPath

# hostPath卷

挂载Node文件系统(Pod所在节点)上文件或者目录到Pod中的容器。

# 一个简单的yaml示例

apiVersion:  v1
kind:  Pod
metadata:  
  name:  my-pod-host-path
spec: 
  containers: 
  - name:  busybox
    image:  busybox
    args: 
    - bin/sh
    - -c
    - sleep 3600
    volumeMounts: 
    - name:  data
      mountPath:  /data
  volumes: 
  - name:  data
    hostPath: 
      path:  /tmp
      type:  Directory

# yaml说明

  • volumeMounts指定数据卷来源,通过name: data挂载到存储卷
  • volumes用于创建名为data的数据卷,hostPath用于指定数据持久化的目录

# 执行一下

$ kubectl apply -f volume-host-path.yaml
pod/my-pod-host-path created

$ kubectl get pod -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP              NODE               NOMINATED NODE   READINESS GATES
my-pod-host-path          1/1     Running   0          10m   10.244.17.181   liuxiaolu-node     <none>           <none>
......

$ cd /tmp
$ ls
vmware-tools-distrib

$ kubectl exec -it my-pod-host-path -- sh
/ # cd /data
/data # ls
vmware-tools-distrib

/data # touch create-in-pod
/data # ls
create-in-pod         vmware-tools-distrib
$ cd /tmp
$ ls
create-in-pod  vmware-tools-distrib

$ touch create-in-host
$ kubectl exec -it my-pod-host-path -- sh
/ # cd /data
/data # ls
create-in-host        create-in-pod         vmware-tools-distrib

# 执行结果说明

  • 创建pod
  • 查看pod是否创建完成,同时查看pod所在的节点,我们发现pod在liuxiaolu-node这个节点
  • 验证pod中/data目录中的内容是否和liuxiaolu-node中/tmp目录的内容一致
    • 登录到liuxiaolu-node节点,并打开/tmp目录,ls查看这个目录下的文件
    • 进入pod,打开/data目录,ls查看这个目录下的文件
    • 验证结果是一致的,都只有vmware-tools-distrib这个文件夹
  • 验证在pod的/data目录中创建的文件,在liuxiaolu—node节点的/tmp是否能查询到
    • 在pod中使用touch命令创建文件touch create-in-pod,并使用ls命令查看是否创建成功
    • 在liuxiaolu-node节点打开tmp目录,使用ls命令查询这个目录下的文件
    • 验证通过,在pod中创建的文件,,在liuxiaolu—node节点的/tmp能查询到
  • 验证在liuxiaolu—node节点的/tmp目录中创建的文件,在pod的/data目录中是否能查询到
    • 在liuxiaolu—node节点的/tmp目录中使用touch命令创建文件create-in-host
    • 在pod的/data目录下,使用ls命令查看
    • 验证通过,在host中创建的文件,在pod中能够查询到
  • 结论是挂载成功

# 应用场景

Pod中容器需要访问宿主机文件

  • 比如一个监控节点,收集宿主机的信息

# 网络存储卷: NFS

  • NFS卷

    提供对NFS挂载支持,可以自动将NFS共享路径 挂载到Pod中

  • NFS

    是一个主流的文件共享服务器。

# 安装

一般来说,为了追求性能,都会在一台新的机器上去安装nfs共享服务器,但我们这里为了学习,就在liuxiaolu-node这个节点上去安装nfs服务器。

注意,每个节点都需要安装nfs-utils包。我们这里学习的时候,是两台服务器,一个master(liuxiaolu-master)节点,一个node(liuxiaolu-node)节点。

这个场景下,需要如下步骤:

  1. 在liuxiaolu-node这个节点执行下面的安装命令
  2. 在其他节点(liuxiaolu-master)执行yum install nfs-utils即可

# 安装命令

# 安装nfs-utils
$ yum install nfs-utils

# 创建共享目录/infs/kubernetes
$ mkdir -p /infs/kubernetes

# 设置nfs共享目录,*代表所有ip可访问,如果写一个ip,或者网段,那么只有指定的ip/网段可访问,括号中内容为读写权限
# rw:  可读写
# no_root_squash:  当登录NFS主机使用共享目录的使用者是root时,其权限将被转换成为匿名使用者
$ vi /etc/exports 
/infs/kubernetes *(rw,no_root_squash) 

# 启动nfs
$ systemctl start nfs

# 设置nfs开机启动
$ systemctl enable nfs 

# 安装测试

在liuxiaolu-master节点上执行命令

# 在本机创建需要使用nfs共享服务器的目录
$ mkdir /liuxiaolu-test-mount

# 将本机的liuxiaolu-test-mount目录挂载到liuxiaolu-node节点的/infs/kubernetes目录
# 注意,liuxiaolu-node是通过本地hosts域名映射到node节点上,如果没有配置域名银映射,这里需要替换为ip地址
$ mount -t nfs liuxiaolu-node: /infs/kubernetes /liuxiaolu-test-mount

# 打开本机的liuxiaolu-test-mount目录
$ cd /liuxiaolu-test-mount

# 任意创建一个测试文件
$ touch liuxiaolu-mount-test.text

在liuxiaolu-node节点上执行命令

# 打开共享目录
$ cd /infs/kubernetes

# 若能看到我们在master节点创建的测试文件liuxiaolu-mount-test.text即为挂载成功
$ ls

# 卸载

在挂载共享服务器的节点(liuxiaolu-master)中执行命令

# 注意,如果是待卸载的目录再进行卸载操作
$ cd ~
# 卸载
$ umount /liuxiaolu-test-mount
# 再次打开目录liuxiaolu-test-mount
$ cd /liuxiaolu-test-mount
# 查看当前文件夹下的内容,如果查询不到之前创建的liuxiaolu-mount-test.text文件,即为挂载成功。
# 我们也可以在nfs共享服务器查看共享目录下是否还有文件liuxiaolu-mount-test.text,如果有,也证明了,我们之前上传的文件确实上传到了共享服务器,而不是本地。
# 若执行命令重新挂载,也可以重新看到我们之前创建的文件liuxiaolu-mount-test.text
$ ls

说明 NFS卷使用过程中和本地文件夹使用没有任何区别,只是通过网络传输将文件存到了共享服务器中

# 使用k8s的NFS存储卷

# 使用命令创建一个标准的deployment的yaml
$ kubectl create deployment deploy-nfs --image=nginx --dry-run=client -o yaml > deploy-nfs.yaml

# 修改yaml文件,修改后的内容参考后面的给出的yaml
$ vi deploy-nfs.yaml

# 创建deploy-nfs
$ kubectl apply -f deploy-nfs.yaml
deployment.apps/deploy-nfs created

# 查看pod信息,可以看到三个节点都被拉起来了,我们随便找一个pod进行curl访问测试
$ kubectl get pod -o wide
NAME                          READY   STATUS    RESTARTS   AGE     IP              NODE               NOMINATED NODE   READINESS GATES
deploy-nfs-5fc65d6768-45cvg   1/1     Running   0          3m24s   10.244.17.184   liuxiaolu-node     <none>           <none>
deploy-nfs-5fc65d6768-rffwv   1/1     Running   0          3m24s   10.244.17.182   liuxiaolu-node     <none>           <none>
deploy-nfs-5fc65d6768-zqkbr   1/1     Running   0          3m24s   10.244.17.183   liuxiaolu-node     <none>           <none>
...

# 这里的访问结果是403无法访问
$ curl 10.244.17.184
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.21.0</center>
</body>
</html>

# 接下来我们进入这个pod的网站根目录,看下里面的内容会发现这是我们之前在master节点随便创建的文件
$ kubectl exec -it deploy-nfs-5fc65d6768-45cvg -- bash
$ cd /usr/share/nginx/html
$ ls
liuxiaolu-mount-test.text

# 在nfs服务器节点(liuxiaolu-node)执行命令删除之前创建的文件liuxiaolu-mount-test.text
[root@liuxiaolu-node kubernetes]# ls
liuxiaolu-mount-test.text
[root@liuxiaolu-node kubernetes]# rm liuxiaolu-mount-test.text
rm:  remove regular empty file 'liuxiaolu-mount-test.text'? y
[root@liuxiaolu-node kubernetes]# ls

# 再次在容器内部执行,就会发现文件没有了,这也从侧面印证我们挂载成功了
$ ls

# 接下来我们再nfs服务器上创建一个index.html
[root@liuxiaolu-node kubernetes] vi index.html
hello liuxiaolu

# 再次对之前的pod进行curl,会发现,可以正常响应结果了。这就是,我们在nfs服务器上中的任何操作,都会被同步到使用共享目录的节点中。
$ curl 10.244.17.184
hello liuxiaolu

# yaml

apiVersion:  apps/v1
kind:  Deployment
metadata: 
  labels: 
    app:  deploy-nfs
  name:  deploy-nfs
spec: 
  replicas:  3
  selector: 
    matchLabels: 
      app:  deploy-nfs
  template: 
    metadata: 
      labels: 
        app:  deploy-nfs
    spec: 
      containers: 
      - image:  nginx
        name:  nginx
        resources:  {}
        volumeMounts: 
        - name:  data
          mountPath:  /usr/share/nginx/html
      volumes: 
      - name:  data
        nfs: 
          server:  liuxiaolu-node
          path:  /infs/kubernetes

# 使用k8s的NFS存储卷的目标

  • 将pod启动后的数据持久化到远程存储
  • 可以实现pod多副本共享数据

# NFS的缺点

需要定义服务器相关的信息和目录,现在一般部署是由开发者去进行安装部署应用,开发者一般不知道k8s的相关安装部署信息,如果将这部分信息同步给开发者也不利于k8s的安全运维。

# 持久卷概述

# PersistentVolume(PV)

对存储资源创建和使用的抽象,使得存储作为集群中的资源管理

# PersistentVolumeClaim(PVC)

让用户不需要关心具体的Volume实现细节

# PV与PVC使用流程

pv/pvc使用流程

# 简单搭建

# 准备deploy-pvc.yaml

# 复制之前的yaml文件
$ cp deploy-nfs.yaml deploy-pvc.yaml

# 编辑,将存储卷改为使用pvc,内容参考后面给出的yaml
$ vi deploy-pvc.yaml

# 创建
$ kubectl apply -f deploy-pvc.yaml
deployment.apps/deploy-pvc created
persistentvolumeclaim/my-pvc created

# 会发现三个pod一直处于pending状态,这是由于没有正确绑定pv导致,通过describe查看某一个pod也可以看到错误原因是
# 2 pod has unbound immediate PersistentVolumeClaims.
$ kubectl get pod 
NAME                          READY   STATUS    RESTARTS   AGE
deploy-pvc-6994b7ff4b-7p4lz   0/1     Pending   0          2m58s
deploy-pvc-6994b7ff4b-h78j4   0/1     Pending   0          2m58s
deploy-pvc-6994b7ff4b-zqq2p   0/1     Pending   0          2m58s

# 内容参考后面给出的yaml
$ vi deploy-pv.yaml

# 再次查看pod,会发现已经在运行中了,pv和pvc绑定成功
$ kubectl get pod
NAME                          READY   STATUS    RESTARTS   AGE
deploy-pvc-6994b7ff4b-7p4lz   1/1     Running   0          9m46s
deploy-pvc-6994b7ff4b-h78j4   1/1     Running   0          9m46s
deploy-pvc-6994b7ff4b-zqq2p   1/1     Running   0          9m46s

# YAML

# PVC

apiVersion:  apps/v1
kind:  Deployment
metadata: 
  labels: 
    app:  deploy-pvc
  name:  deploy-pvc
spec: 
  replicas:  3
  selector: 
    matchLabels: 
      app:  deploy-pvc
  template: 
    metadata: 
      labels: 
        app:  deploy-pvc
    spec: 
      containers: 
      - image:  nginx
        name:  nginx
        resources:  {}
        volumeMounts: 
        - name:  data
          mountPath:  /usr/share/nginx/html
      volumes: 
      - name:  data
        persistentVolumeClaim: 
          claimName:  my-pvc
---

apiVersion:  v1
kind:  PersistentVolumeClaim
metadata: 
  name:  my-pvc
spec: 
  accessModes: 
    - ReadWriteMany
  resources: 
    requests: 
      storage:  5Gi

# PV

apiVersion:  v1
kind:  PersistentVolume
metadata: 
  name:  my-pv0000001
spec: 
  capacity: 
    storage:  5Gi
  accessModes: 
    - ReadWriteMany
  nfs: 
    path:  /infs/kubernetes

# PV与PVC的绑定

  • 不通过name绑定,所以pv任意命名即可

  • pv与pvc一对一绑定,pv在绑定的情况下,不可再分配

  • pv与pvc绑是怎么绑定的?

    • 存储空间
    • 访问模式
  • pv与pvc通过存储空间匹配的匹配策略是怎么样的?

    • 匹配最接近的pv容量,如果没有匹配的容量,寻找超出容量需求的pv中,最接近的pv
    • 如果无法满足容量需求,pod会处于pending状态

    eg: 比如有四个pv分别是1号(5Gi),2号(20Gi),3号(30Gi),4号(50Gi)

    1. 首先来了一个需求5Gi的pvc,会绑定1号pv
    2. 然后来了一个需求35Gi的pvc,会绑定4号pv
    3. 然后来了一个需求18Gi的pvc,会绑定2号pv
  • 存储空间的字段是限制使用容量?

    • 主要用于做匹配用的,实际使用限制取决于后端存储

# PV生命周期

# AccessModes(访问模式)

AccessModes是用来对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限

访问权限包括下面几种方式

  • ReadWriteOnce(RWO): 读写权限,但是只能被单个节点挂载
  • ReadOnlyMany(ROX): 只读权限,可以被多个节点挂载
  • ReadWriteMany(RWX): 读写权限,可以被多个节点挂载

# RECLAIM POLICY(回收策略)

目前 PV 支持的策略有三种

  • Retain(保留): 保留数据,需要管理员手工清理数据
  • Recycle(回收): 清除PV中的数据,效果相当于执行rm -rf /infs/kuberneres/*
  • Delete(删除): 与PV相连的后端存储同时删除

# STATUS(状态)

一个PV的生命周期中,可能会处于4中不同的阶段

  • Available(可用): 表示可用状态,还未被任何PVC绑定
  • Bound(已绑定): 表示PV已经被PVC绑定
  • Released(已释放): PVC被删除,但是资源还未被集群重新声明
  • Failed(失败): 表示该PV的自动回收失败

# 静态供给

现在PV使用方式称为静态供给,需要K8s运维工程师提前创建一堆PV,供开发者使用,模式比较僵化。 pv静态供给

# PV动态供给

# 图示

pv动态供给

# NFS动态供给流程图

NFS动态供给

PV静态供给明显的缺点是维护成本太高了! 因此,K8s开始支持PV动态供给,使用StorageClass对象实现。

查看支持动态供给的StorageClass (opens new window)

K8s默认不支持NFS动态供给,需要单独部署社区开发的插件。

点击查看项目地址 (opens new window)

# 部署

# 创建并进入动态供给目录
$ mkdir nfs-subdir-external-provisioner  && cd nfs-subdir-external-provisioner 

# 创建本次所需的三个yaml,内容参考后面给出的yaml
$ vi class.yaml
$ vi deployment.yaml
$ vi rbac.yaml

# 授权访问apiserver
$ kubectl apply -f rbac.yaml 
# 创建存储类
$ kubectl apply -f class.yaml 
# 部署插件,需修改里面NFS服务器地址与共享目录
$ kubectl apply -f deployment.yaml 
# 查看存储类
$ kubectl get sc
 # 查看是否创建完成
$ kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-86d547fbb-tlgqr   1/1     Running   0          2m21s
...

# YAML

# class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false"

# deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: lizhenliang/nfs-subdir-external-provisioner:v4.0.1
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
              value: liuxiaolu-node 
            - name: NFS_PATH
              value: /infs/kubernetes
      volumes:
        - name: nfs-client-root
          nfs:
            server: liuxiaolu-node
            path: /infs/kubernetes

# rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

# 验证

# 复制之前静态供给的yaml
$ cp deploy-pvc.yaml deploy-pv-sl.yaml
# 修改yaml,内容在后面
$ vi deploy-pv-sl.yaml
$ kubectl apply -f deploy-pv.sl.yaml

# 查看pv,pvc发现已经动态生成了一个pv,pvc
$ kubectl get pv,pvc
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS          REASON   AGE
persistentvolume/my-pv0000001                               5Gi        RWX            Retain           Bound    default/my-pvc                                     102m
persistentvolume/pvc-e52e7a5d-3675-41d8-b9a4-adb7fd6de3bb   5Gi        RWX            Delete           Bound    default/my-pvc-sl   managed-nfs-storage            92s

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/my-pvc      Bound    my-pv0000001                               5Gi        RWX                                  110m
persistentvolumeclaim/my-pvc-sl   Bound    pvc-e52e7a5d-3675-41d8-b9a4-adb7fd6de3bb   5Gi        RWX            managed-nfs-storage   92s

# 再次查看共享目录,发现生成了一个默认路径,这个路径就是新的deploy所使用的共享目录
[root@liuxiaolu-node kubernetes]# ls
default-my-pvc-sl-pvc-e52e7a5d-3675-41d8-b9a4-adb7fd6de3bb  index.html

# YAML

# deploy-pv.sl.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: deploy-pvc-sl
  name: deploy-pvc-sl
spec:
  replicas: 3
  selector:
    matchLabels:
      app: deploy-pvc-sl
  template:
    metadata:
      labels:
        app: deploy-pvc-sl
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: my-pvc-sl
---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc-sl
spec:
  # kubectl get sc to describe sc name exists
  storageClassName: "managed-nfs-storage"
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

# 有状态应用部署: StatefulSet工作负载控制器

# 无状态与有状态

# Deployment控制器设计原则

管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运行,可随意扩容和缩容,这种应用称为“无状态”。

例如Web服务,在实际的场景中,这并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群

# StatefulSet

# 解决的问题

  • 部署有状态应用
  • 解决Pod独立生命周期,保持Pod启动顺序和唯一性
    • 稳定,唯一的网络标识符,持久存储
    • 有序,优雅的部署和扩展、删除和终止
    • 有序,滚动更新

# 应用场景

  • 分布式应用
  • 数据库集群
    • 比如部署一个mysql主从,可能要考虑以下问题
    1. 主和从实例启动顺序
    2. 主和从实例数据目录必须唯一
    3. 从连接主实例需要指定IP或者主机名

# 优点

  • 稳定的网络ID

    使用Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份。 并且添加serviceName: “nginx”字段指定StatefulSet控制器要使用这个Headless Service。 DNS解析名称:<statefulsetName-index>.<service-name> .<namespace-name>.svc.cluster.local

  • 稳定的存储

    StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用 VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。

# 部署

# 预生成两个文件,基于文件进行修改创建StatefulSet的yaml
$ kubectl create deployment deploy-sts --image=nginx --dry-run=client -o yaml >  deploy-sts.yaml
$ kubectl expose deployment deploy-pvc --port=80 --target-port=80 --dry-run=client -o yaml > expose-sts.yaml

# 将预生成的expose-sts.yaml合并到deploy-sts.yaml,并修改yaml
$ vi deploy-sts.yaml

# 执行创建命令
$ kubectl apply -f deploy-sts.yaml
statefulset.apps/deploy-sts created
service/deploy-sts created

# 查看age可以看到,pod启动的时间不一致,这是由于是有序启动的,0启动完成之后,才会启动1,1启动完成之后,才会启动2,与pod编号相同
$ kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
deploy-sts-0                             1/1     Running   0          35s
deploy-sts-1                             1/1     Running   0          16s
deploy-sts-2                             1/1     Running   0          11s
...

# 查看nfs服务器的地址会发现,每个pod使用的不同的存储,这也印证了独立的存储这一点
[root@liuxiaolu-node kubernetes]# ls
default-data-deploy-sts-0-pvc-12eea52c-5c2d-4d41-afbc-81047e1d4ed7  default-data-deploy-sts-2-pvc-ff971b3f-ccf9-4e86-a9b3-6b9b4230c6de
default-data-deploy-sts-1-pvc-4c1b4a3b-960e-4686-b174-2d90c0ba9a7e  
...

$ kubectl get pv,pvc
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                       STORAGECLASS          REASON   AGE
persistentvolume/pvc-12eea52c-5c2d-4d41-afbc-81047e1d4ed7   1Gi        RWO            Delete           Bound    default/data-deploy-sts-0   managed-nfs-storage            10m
persistentvolume/pvc-4c1b4a3b-960e-4686-b174-2d90c0ba9a7e   1Gi        RWO            Delete           Bound    default/data-deploy-sts-1   managed-nfs-storage            10m
persistentvolume/pvc-ff971b3f-ccf9-4e86-a9b3-6b9b4230c6de   1Gi        RWO            Delete           Bound    default/data-deploy-sts-2   managed-nfs-storage            9m55s

NAME                                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/data-deploy-sts-0   Bound    pvc-12eea52c-5c2d-4d41-afbc-81047e1d4ed7   1Gi        RWO            managed-nfs-storage   10m
persistentvolumeclaim/data-deploy-sts-1   Bound    pvc-4c1b4a3b-960e-4686-b174-2d90c0ba9a7e   1Gi        RWO            managed-nfs-storage   10m
persistentvolumeclaim/data-deploy-sts-2   Bound    pvc-ff971b3f-ccf9-4e86-a9b3-6b9b4230c6de   1Gi        RWO            managed-nfs-storage   9m55s

# YMAL

# deploy-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: deploy-sts
  name: deploy-sts
spec:
  serviceName: "deploy-sts"
  replicas: 3
  selector:
    matchLabels:
      app: deploy-sts
  template:
    metadata:
      labels:
        app: deploy-sts
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi
---

apiVersion: v1
kind: Service
metadata:
  labels:
    app: deploy-sts
  name: deploy-sts
spec:
  clusterIP: None
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: deploy-sts

# StatefulSet与Deployment区别:有身份的!

身份三要素:

  • 域名
  • 主机名
  • 存储(PVC)

# 应用程序配置文件存储:ConfigMap

创建ConfigMap后,数据实际会存储在K8s中Etcd,然后通过创建Pod时引用该数据。

# 应用场景

应用程序配置

# Pod使用configmap数据有两种方式

  • 变量注入
  • 数据卷挂载

# 简单使用

# 创建configmap

apiVersion: v1
kind: ConfigMap
metadata:
  name: config-demo
data:
  # 类属性键;每一个键都映射到一个简单的值
  name: "test"
  breif: "xx"

  # 类文件键
  redis.properties: |
    port: 6379
    host: 127.0.0.1  

# 创建pod使用configmap的配置

apiVersion: v1
kind: Pod 
metadata:
  name: configmap-demo-pod 
spec:
  containers:
  - name: demo
    image: nginx 
    env:
      # 这里是直接读取configmap
      - name: NAME 
        valueFrom:
          configMapKeyRef:
            name: config-demo 
            key: name
      - name: BREIF 
        valueFrom:
          configMapKeyRef:
            name: config-demo 
            key: breif
    # 这后面的部分是通过数据卷挂载的方式读取configmap
    volumeMounts: 
      - name: config
        mountPath: "/config"
        readOnly: true 
  volumes:
    - name: config 
      configMap:
        name: config-demo 
        items:
        - key: "redis.properties"
          path: "redis.properties"

# 验证

$ kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
configmap-demo-pod                       1/1     Running   0          39s

$ kubectl exec -it configmap-demo-pod -- bash
$ echo $BREIF
xx
$ echo $NAME
test
$ cat /config/redis.properties
port: 6379
host: 127.0.0.1

# 生效

如果需要修改configmap,重启pod之后会生效

# 应用程序配置文件存储:Secret

与ConfigMap类似,区别在于Secret主要存储敏感数据,所有的数据要经过base64编码。

# 应用场景

凭据

# kubectl create secret 支持三种数据类型

  • docker-registry:存储镜像仓库认证信息
  • generic:从文件、目录或者字符串创建,例如存储用户名密码
  • tls:存储证书,例如HTTPS证书

# 简单使用

# 创建一个用户名密码的凭据

# 将用户名密码进行编码:
$ echo -n 'admin' | base64
$ echo -n '1f2d1e2e67df' | base64
apiVersion: v1 
kind: Secret 
metadata:
  name: db-user-pass 
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

# 读取

apiVersion: v1
kind: Pod 
metadata:
  name: secret-demo-pod 
spec:
  containers:
  - name: demo
    image: nginx 
    env:
      # 这里是直接读取configmap
      - name: username 
        valueFrom:
          secretKeyRef:
            name: db-user-pass
            key: username
      - name: password
        valueFrom:
          secretKeyRef:
            name: db-user-pass
            key: password
    # 这后面的部分是通过数据卷挂载的方式读取configmap
    volumeMounts: 
      - name: config
        mountPath: "/config"
        readOnly: true 
  volumes:
    - name: config
      secret: 
        secretName: db-user-pass
        items:
        - key: "username"
          path: "username"
        - key: "password"
          path: "password"

# 验证

$ kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
secret-demo-pod                          1/1     Running   0          23s

$ kubectl exec -it secret-demo-pod -- bash
$ echo $username
admin
$ echo $password
1f2d1e2e67df
$ cd /config
$ ls
password  username
$ cat password
1f2d1e2e67df
$ cat username
admin
Last Updated: 6/26/2022, 4:44:06 PM