k8s之存储、SC、STS、DS篇(一些常见的存储方案)

2023-11-01

为什么要做持久化存储?

在k8s中部署的应用都是以pod容器的形式运行的,假如我们部署MySQL、Redis等数据库,需要对这些数据库产生的数据做备份。因为Pod是有生命周期的,如果pod不挂载数据卷,那pod被删除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到pod数据持久化存储。

k8s支持那些持久化存储

[root@master1 ~]# kubectl explain pods.spec.volumes

常用的如下:
emptyDir、hostPath、nfs、persistenVolumeClaim、glusterfs、cephfs、configMap、secret

如果我们使用持久化存储,需要以下步骤:

  • 定义pod的volume,这个volume要指明它关联到哪个存储上的
  • 在容器中要使用volumemounts挂载对应的存储

emptyDir持久化存储

emptyDir是最基础的Volume类型。正如其名字所示,一个 emptyDir Volume是Host上的一个空目录。

emptyDir Volume对于容器来说是持久的,对于Pod则不是。当 Pod从节点删除时,Volume的内容也会被删除。但如果只是容器被销 毁而Pod还在,则Volume不受影响。

也就是说:emptyDir Volume的生命周期与Pod一致

Pod中的所有容器都可以共享Volume,它们可以指定各自的 mount路径。

这里我们模拟了一个producer-consumer场景。Pod有两个容器 producer和consumer,它们共享一个Volume。producer负责往Volume 中写数据,consumer则是从Volume读取数据。
① 文件最底部volumes定义了一个emptyDir类型的Volume shared- volume。
② producer容器将shared-volume mount到/producer_dir目录。
③ producer通过echo将数据写到文件hello里。
④ consumer容器将shared-volume mount到/consumer_dir目录。
⑤ consumer通过cat从文件hello读数据

[root@master1 emptydir]# cat emptydir.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: producer-comsumer
spec:
  containers:
    - image: busybox
      name: producer
      volumeMounts:
        - mountPath: /producer_dir
          name: shared-volume
      args:
        - /bin/sh
        - -c
        - echo "hello world" > /producer_dir/hello ; sleep 3600
    - image: busybox
      name: comsumer
      volumeMounts:
        - mountPath: /comsumer_dir
          name: shared-volume
      args:
        - /bin/sh
        - -c
        - cat /comsumer_dir/hello; sleep 3600
  volumes:
    - name: shared-volume
      emptyDir: {}
[root@master1 emptydir]# kubectl apply -f emptydir.yaml
pod/producer-comsumer created
[root@master1 emptydir]# kubectl logs producer-comsumer comsumer
hello world

kubectl logs显示容器consumer成功读到了producer写入的数据, 验证了两个容器共享emptyDir Volume。

因为emptyDir是Docker Host文件系统里的目录,其效果相当于执 行了docker run -v/producer_dir和docker run -v /consumer_dir。通过 docker inspect查看容器的详细配置信息,我们发现两个容器都mount 了同一个目录,
在这里插入图片描述
在这里插入图片描述
emptyDir是Host上创建的临时目录,其优点是能够方便地为Pod 中的容器提供共享存储,不需要额外的配置。它不具备持久性,如果 Pod不存在了,emptyDir也就没有了。根据这个特性,emptyDir特别 适合Pod中的容器需要临时共享存储空间的场景,比如前面的生产者 消费者用例

注意: 存储的卷一般在宿主机的/var/lib/kubelet/pods目录下


hostpath持久化存储

hostPath Volume的作用是将Docker Host文件系统中已经存在的目 录mount给Pod的容器。大部分应用都不会使用hostPath Volume,因为 这实际上增加了Pod与节点的耦合,限制了Pod的使用。不过那些需 要访问Kubernetes或Docker内部数据(配置文件和二进制库)的应用 则需要使用hostPath。

hostPath Volume是指Pod挂载宿主机上的目录或文件。 hostPath Volume使得容器可以使用宿主机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的。

查看hostpath的用法

[root@master1 ~]#  kubectl explain pods.spec.volumes.hostPath

创建一个Pod,挂载HostPath存储卷

[root@master1 volume]# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-hostpath
spec:
  containers:
    - name: test-nginx
      image: nginx
      volumeMounts:
        - name: test-volume
          mountPath: /test-nginx
    - name: test-tomcat
      image: tomcat
      volumeMounts:
        - name: test-volume
          mountPath: /test-tomcat
  volumes:
    - name: test-volume
      hostPath:
        path: /data1
        type: DirectoryOrCreate 

注意:
DirectoryOrCreate表示本地有/data1目录,就用本地的,本地没有就会在pod调度到的节点自动创建一个
常见的type类型有:
在这里插入图片描述

  • 创建pod资源
[root@master1 volume]# kubectl apply -f hostpath.yaml
pod/test-hostpath created
  • 查看pod资源
[root@master1 volume]# kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE     IP            NODE    NOMINATED NODE   READINESS GATES
test-hostpath   2/2     Running   0          3m30s   10.1.104.12   node2   <none>           <none>
  • 登录到node2上,看是否存在/data1目录
[root@node2 ~]# ll /data1/
total 0
  • 测试卷是否能正常使用
[root@node2 data1]# mkdir test
[root@node2 data1]# ls
test

[root@master1 ~]# kubectl exec -it  test-hostpath -c test-nginx -- /bin/bash
root@test-hostpath:/# ls /test-nginx/
test
[root@master1 ~]# kubectl exec -it  test-hostpath -c test-tomcat -- /bin/bash
root@test-hostpath:/usr/local/tomcat# ls /test-tomcat/
test

通过上面测试可以看到,同一个pod里的test-nginx和test-tomcat这两个容器是共享存储卷的。

hostpath存储卷缺点:
单节点
pod删除之后重新创建必须调度到同一个node节点,数据才不会丢失


NFS持久化存储

hostPath存储,存在单点故障,pod挂载hostPath时,只有调度到同一个节点,数据才不会丢失。那可以使用nfs作为持久化存储。

搭建的nfs服务器

[root@master1 ~]# yum install nfs-utils -y
# 创建共享目录
[root@master1 ~]# mkdir /data/volumes -p
# 配置nfs共享服务器上的/data/volumes目录
[root@master1 ~]# vim /etc/exports
/data/volumes 192.168.0.0/24(rw,no_root_squash)
#no_root_squash: 用户具有根目录的完全管理访问权限

#使NFS配置生效
[root@master1 ~]# exportfs -arv
exporting 192.168.0.0/24:/data/nfs_pro
[root@master1 ~]# systemctl restart nfs
[root@master1 ~]# systemctl enable nfs

在pod中使用nfs存储作为持久化存储

[root@master1 volume]# cat nfs.yaml
apiVersion: v1
kind: Pod
metadata: 
  name: test-nfs-volume
spec:
  containers:
    - name: test-nfs
      image: nginx
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 80
          name: nginx-port
          protocol: TCP
      volumeMounts:
        - name: nfs-volumes
          mountPath: /usr/share/nginx/html
  volumes:
    - name: nfs-volumes
      nfs:
          path: /data/volumes
          server: 192.168.0.180
  • 创建nfs为存储的pod资源
[root@master1 volume]# kubectl apply -f nfs.yaml
pod/test-nfs-volume created
[root@master1 volume]# kubectl get pods -o wide | grep nfs
test-nfs-volume   1/1     Running   0          102s    10.1.166.166   node1   <none>           <none>
  • 测试数据的写入。是否nginx能够访问
[root@master1 volumes]# pwd
/data/volumes
[root@master1 volumes]# cat index.html 
hello, Nice to meet you! 
[root@master1 volume]# curl 10.1.166.166
hello, Nice to meet you! 

通过上面可以看到,在共享目录创建的index.html已经被pod挂载了,nfs支持多个客户端挂载,可以创建多个pod,挂载同一个nfs服务器共享出来的目录;但是nfs如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布式存储有glusterfs和cephfs


PVC持久化存储

参考官网:
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes

PV是什么?
PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像pod是k8s集群资源一样。 PV是容量插件,如Volumes,其生命周期独立于使用PV的任何单个pod。

PVC是什么?
PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建pod时可以定义这个类型的存储卷。 它类似于一个pod。 Pod消耗节点资源,PVC消耗PV资源。 Pod可以请求特定级别的资源(CPU和内存)。 pvc在申请pv的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次只读)。

特别说明:
PV是群集中的资源。 PVC是对这些资源的请求。
PV和PVC之间的相互作用遵循以下生命周期:

  • pv的供应方式
    • 静态的方式配置PV:

      集群管理员创建了许多PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于Kubernetes API中,可供使用。

    • 动态的方式配置PV:
      当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,群集可能会尝试为PVC专门动态配置卷。此配置基于StorageClasses,PVC必须请求存储类,管理员必须创建并配置该类,以便进行动态配置。

  • 绑定
    用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态
    • 常用访问模式(accessModes):
      • ReadWriteOnce表示PV能以read-write模式mount到单个节点
      • ReadOnlyMany表示PV能以read-only模式mount到多个节点
      • ReadWriteMany表示PV能以read-write模式mount到多个节点。
  • 使用
    • 需要找一个存储服务器,把它划分成多个存储空间
    • k8s管理员可以把这些存储空间定义成多个pv
    • 在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的访问模式,找到合适的pv
    • pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储卷
    • pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了
    • 我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处于pending状态
  • 回收策略
    当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会解除,解除之后和pvc绑定的pv卷里的数据需要怎么处理,目前,卷可以保留,回收或删除
    • Retain表示需要管理员手工回收
      当删除pvc的时候,pv仍然存在,处于released状态,但是它不能被其他pvc绑定使用,里面的数据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略
    • Recycle表示清除PV中的数据(不推荐使用,1.15可能被废弃了)
    • Delete表示删除Storage Provider上的对应存储资源
      删除pvc时即会从Kubernetes中移除PV,也会从相关的外部设施中删除存储资产

以nfs为存储服务器,使用pvc作为持久化存储卷

  • 配置共享目录的资源,用于创建pv
# 创建用于存储的的共享目录
[root@master1 volume]# mkdir /data/volume_test/v{1,2,3,4,5,6,7,8,9,10} -p
# 修改配置文件,实目录成为共享
[root@master1 volume]#  cat /etc/exports
/data/volumes 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v1 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v2 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v3 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v4 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v5 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v6 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v7 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v8 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v9 192.168.0.0/24(rw,no_root_squash)
/data/volume_test/v10 192.168.0.0/24(rw,no_root_squash)
# 加载配置文件
[root@master1 volume]#  exportfs -arv
exporting 192.168.0.0/24:/data/nfs_pro
exporting 192.168.0.0/24:/data/volume_test/v10
exporting 192.168.0.0/24:/data/volume_test/v9
exporting 192.168.0.0/24:/data/volume_test/v8
exporting 192.168.0.0/24:/data/volume_test/v7
exporting 192.168.0.0/24:/data/volume_test/v6
exporting 192.168.0.0/24:/data/volume_test/v5
exporting 192.168.0.0/24:/data/volume_test/v4
exporting 192.168.0.0/24:/data/volume_test/v3
exporting 192.168.0.0/24:/data/volume_test/v2
exporting 192.168.0.0/24:/data/volume_test/v1
exporting 192.168.0.0/24:/data/volumes
# 重启服务
[root@master1 volume]# systemctl restart nfs
[root@master1 volume]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v1
spec:
  capacity:
    storage: 1Gi
  accessModes: ["ReadWriteOnce"]
  nfs:
    path: /data/volume_test/v1
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v2
spec:
  capacity:
    storage: 2Gi
  accessModes: ["ReadWriteMany"]
  nfs:
    path: /data/volume_test/v2
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v3
spec:
  capacity:
    storage: 3Gi
  accessModes: ["ReadOnlyMany"]
  nfs:
    path: /data/volume_test/v3
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v4
spec:
  capacity:
    storage: 4Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v4
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v5
spec:
  capacity:
    storage: 5Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v5
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v6
spec:
  capacity:
    storage: 6Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v6
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v7
spec:
  capacity:
    storage: 7Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v7
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v8
spec:
  capacity:
    storage: 8Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v8
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v9
spec:
  capacity:
    storage: 9Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v9
    server: 192.168.0.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: v10
spec:
  capacity:
    storage: 10Gi
  accessModes: ["ReadWriteOnce","ReadWriteMany"]
  nfs:
    path: /data/volume_test/v10
    server: 192.168.0.180
[root@master1 volume]# kubectl apply -f pv.yaml
persistentvolume/v1 created
persistentvolume/v2 created
persistentvolume/v3 created
persistentvolume/v4 created
persistentvolume/v5 created
persistentvolume/v6 created
persistentvolume/v7 created
persistentvolume/v8 created
persistentvolume/v9 created
persistentvolume/v10 created

# #STATUS是Available,表示pv是可用的  
[root@master1 volume]# kubectl get pv
v1                                         1Gi        RWO            Retain           Available                                                54s
v10                                        10Gi       RWO,RWX        Retain           Available                                                54s
v2                                         2Gi        RWX            Retain           Available                                                54s
v3                                         3Gi        ROX            Retain           Available                                                54s
v4                                         4Gi        RWO,RWX        Retain           Available                                                54s
v5                                         5Gi        RWO,RWX        Retain           Available                                                54s
v6                                         6Gi        RWO,RWX        Retain           Available                                                54s
v7                                         7Gi        RWO,RWX        Retain           Available                                                54s
v8                                         8Gi        RWO,RWX        Retain           Available                                                54s
v9                                         9Gi        RWO,RWX        Retain           Available                                                54s
  • 创建PVC,绑定到PV
[root@master1 volume]# cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 2Gi
[root@master1 volume]# kubectl apply -f pvc.yaml
persistentvolumeclaim/my-pvc created
  • 查看pv的绑定状态
[root@master1 volume]# kubectl get pv | grep v2
v2                                         2Gi        RWX            Retain           Bound         default/my-pvc                               10m
[root@master1 volume]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc      Bound    v2                                         2Gi        RWX                           87s
  • 创建Pod资源,使用Pvc持久化存储
[root@master1 volume]# cat pod-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-pvc
spec:
  containers:
    - name: nginx
      image: nginx
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - name: nginx-html
          mountPath: /usr/share/nginx/html
  volumes:
    - name: nginx-html
      persistentVolumeClaim:
         claimName: my-pvc
  • 更新Pod资源
[root@master1 volume]#  kubectl apply -f pod-pvc.yaml 
pod/pod-pvc created
[root@master1 volume]#  kubectl get pods -o wide | grep pod-pvc
pod-pvc           1/1     Running   0          111s    10.1.166.167   node1   <none>           <none>
  • 数据持久化存储测试
[root@master1 v2]# pwd
/data/volume_test/v2
[root@master1 v2]# cat index.html 
hello, test pv
[root@master1 v2]# curl 10.1.166.167
hello, test pv

pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。

[root@master1 volume]# kubectl delete -f pod-pvc.yaml 
pod "pod-pvc" deleted
[root@master1 volume]# kubectl delete -f pvc.yaml 
persistentvolumeclaim "my-pvc" deleted
You have new mail in /var/spool/mail/root
[root@master1 volume]# kubectl apply  -f pvc.yaml 
persistentvolumeclaim/my-pvc created
[root@master1 volume]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc      Bound    v4                                         4Gi        RWO,RWX                       4s
[root@master1 volume]# cat pv.yaml | grep persistentVolumeReclaimPolicy
  persistentVolumeReclaimPolicy: Delete
[root@master1 volume]# kubectl apply -f pv.yaml 
persistentvolume/v1 created
persistentvolume/v2 created
persistentvolume/v3 created
persistentvolume/v4 created
persistentvolume/v5 created
persistentvolume/v6 created
persistentvolume/v7 created
persistentvolume/v8 created
persistentvolume/v9 created
persistentvolume/v10 created
[root@master1 volume]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM                STORAGECLASS   REASON   AGE
v1                                         1Gi        RWO            Retain           Available                                                  6s
v10                                        10Gi       RWO,RWX        Retain           Available                                                  6s
v2                                         2Gi        RWX            Delete           Available                                                  6s
v3                                         3Gi        ROX            Retain           Available                                                  6s
v4                                         4Gi        RWO,RWX        Retain           Available                                                  6s
v5                                         5Gi        RWO,RWX        Retain           Available                                                  6s
v6                                         6Gi        RWO,RWX        Retain           Available                                                  6s
v7                                         7Gi        RWO,RWX        Retain           Available                                                  6s
v8                                         8Gi        RWO,RWX        Retain           Available                                                  6s
v9                                         9Gi        RWO,RWX        Retain           Available                                                  6s
[root@master1 volume]# kubectl apply -f pvc.yaml 
persistentvolumeclaim/my-pvc created

[root@master1 volume]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc      Bound    v2                                         2Gi        RWX                           4s

[root@master1 volume]# kubectl  delete pvc my-pvc
persistentvolumeclaim "my-pvc" deleted
You have new mail in /var/spool/mail/root
[root@master1 volume]# kubectl  delete pv v2
persistentvolume "v2" deleted
[root@master1 v2]# ls
index.html
[root@master1 volume]# kubectl apply -f pv.yaml 
[root@master1 volume]# kubectl apply -f pvc.yaml
[root@master1 volume]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc      Bound    v2                                         2Gi        RWX                           4s

从上面可以看到,即使pv和pvc删除了,也不会删除nfs共享目录下的数据,删除的过的v2pvc,再从重新生成新的pvc,还是会绑定到原来的v2上。持有原来的数据!


PV动态供给

动态供给是通过StorageClass实现的,StorageClass定义了如何创 建PV。
V和PVC模式都是需要先创建好PV,然后定义好PVC和pv进行一对一的Bond,但是如果PVC请求成千上万,那么就需要创建成千上万的PV,对于运维人员来说维护成本很高,Kubernetes提供一种自动创建PV的机制,叫StorageClass,它的作用就是创建PV的模板。k8s集群管理员通过创建storageclass可以动态生成一个存储卷pv供k8s pvc使用。

StorageClass会定义以下两部分:
1、PV的属性 ,比如存储的大小、类型等;
2、创建这种PV需要使用到的存储插件,比如Ceph、NFS等

有了这两部分信息,Kubernetes就能够根据用户提交的PVC,找到对应的StorageClass,然后Kubernetes就会调用 StorageClass声明的存储插件,创建出需要的PV。

StorageClass的provisioner字段

storageclass需要有一个供应者,用来确定我们使用什么样的存储来创建pv,常见的provisioner如下:
provisioner既可以由内部供应商提供,也可以由外部供应商提供,如果是外部供应商可以参考https://github.com/kubernetes-incubator/external-storage/下提供的方法创建。
在这里插入图片描述

StorageClass的reclaimPolicy字段: 回收策略

  • allowVolumeExpansion:允许卷扩展
  • PersistentVolume 可以配置成可扩展 将此功能设置为true时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当基础存储类的allowVolumeExpansion字段设置为 true 时,以下类型的卷支持卷扩展。
    在这里插入图片描述

安装nfs provisioner,用于配合存储类动态生成pv

  • 创建运行nfs-provisioner需要的sa( serviceaccount )账号
    • 什么是sa?
      serviceaccount是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。
      指定了serviceaccount之后,我们把pod创建出来了,我们在使用这个pod时,这个pod就有了我们指定的账户的权限了。
[root@master1 storageclass]# cat serviceaccount.yaml
apiVersion: v1 
kind: ServiceAccount
metadata:
  name: nfs-provisioner
[root@master1 storageclass]# kubectl apply -f serviceaccount.yaml
serviceaccount/nfs-provisioner created
  • 对 nfs-provisioner这个sa用户授权
[root@master1 storageclass]# kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner
  • 安装nfs-provisioner程序
# 创建提供sc自动创建pv的存储资源
[root@master1 storageclass]# mkdir /data/nfs_pro -p
[root@master1 storageclass]# cat /etc/exports
/data/nfs_pro 192.168.0.0/24(rw,no_root_squash)
[root@master1 storageclass]# exportfs -arv
exporting 192.168.0.0/24:/data/volumes
[root@master1 storageclass]# systemctl restart nfs
[root@master1 storageclass]# cat nfs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-provisioner
spec:
  selector:
    matchLabels:
       app: nfs-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-provisioner
      containers:
        - name: nfs-provisioner
          image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: example.com/nfs
            - name: NFS_SERVER
              value: 192.168.0.180
            - name: NFS_PATH
              value: /data/nfs_pro
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.0.180
            path: /data/nfs_pro

  • 查看提供商的pod资源
[root@master1 storageclass]# kubectl apply -f nfs-deployment.yaml
deployment.apps/nfs-provisioner created
[root@master1 storageclass]# kubectl get pods | grep nfs
nfs-provisioner-75c9ccfb8f-96b22   1/1     Running   0          40s
  • 创建storageclass,动态供给pv
[root@master1 storageclass]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
provisioner: example.com/nfs # provisioner处写的example.com/nfs应该跟安装nfs provisioner时候的env下的PROVISIONER_NAME的value值保持一致,
[root@master1 storageclass]# kubectl apply -f nfs-storageclass.yaml
storageclass.storage.k8s.io/nfs created
  • 创建pvc,通过storageclass动态生成pv
[root@master1 storageclass]# cat claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-claim
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs # 这里使用的是上一步创建的storageclass
[root@master1 storageclass]# kubectl apply -f claim.yaml
persistentvolumeclaim/test-claim created
  • 查看pvc的绑定情况
[root@master1 storageclass]# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-claim   Bound    pvc-49c4f612-8bca-462e-9648-654ac73ff5f1   1Gi        RWX            nfs            29s

通过上面可以看到test-claim1的pvc已经成功创建了,绑定的pv是pvc-49c4f612-8bca-462e-9648-654ac73ff5f1 ,这个pv是由storageclass调用nfs provisioner自动生成的。

步骤总结:
1、供应商:创建一个nfs provisioner
2、创建storageclass,storageclass指定刚才创建的供应商
3、创建pvc,这个pvc指定storageclass

  • 创建pod,挂载storageclass动态生成的pvc:test-claim1
[root@master1 storageclass]# cat read-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: read-pod
spec:
  containers:
  - name: read-pod
    image: nginx
    volumeMounts:
      - name: nfs-pvc
        mountPath: /usr/share/nginx/html
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim
[root@master1 storageclass]# kubectl apply -f read-pod.yaml
pod/read-pod created
  • 写入数据,访问测试
[root@master1 storageclass]# kubectl get pods -o wide | grep read
read-pod                           1/1     Running   0          3m3s   10.1.104.19    node2   <none>           <none>
[root@master1 default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1]# pwd
/data/nfs_pro/default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1
[root@master1 default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1]# echo "this is test" > index.html
[root@master1 default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1]# curl 10.1.104.19
this is test

Statefulset控制器

StatefulSet是为了管理有状态服务的问题而设计的

什么是有状态服务?
StatefulSet是有状态的集合,管理有状态的服务,它所管理的Pod的名称不能随意变化。数据持久化的目录也是不一样,每一个Pod都有自己独有的数据持久化存储目录。比如MySQL主从、redis集群等。

什么是无状态服务
RC、Deployment、DaemonSet都是管理无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。个体对整体无影响,所有pod都是共用一个数据卷的,部署的tomcat就是无状态的服务,tomcat被删除,在启动一个新的tomcat,加入到集群即可,跟tomcat的名字无关。

StatefulSet由以下几个部分组成:

  • Headless Service:用来定义pod网路标识,生成可解析的DNS记录
  • volumeClaimTemplates:存储卷申请模板,创建pvc,指定pvc名称大小,自动创建pvc,且pvc由存储类供应。
  • StatefulSet:管理pod的

什么是Headless service?
Headless service不分配clusterIPheadless service可以通过解析service的DNS,返回所有Pod的dns和ip地址 (statefulSet部署的Pod才有DNS),普通的service,只能通过解析service的DNS返回service的ClusterIP。

  • headless service会为service分配一个域名
    service name.name spacename.svc.cluster.local

为什么要用headless service(没有service ip的service)?
在使用Deployment时,创建的Pod名称是没有顺序的,是随机字符串,在用statefulset管理pod时要求pod名称必须是有序的 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。因为pod IP是变化的,所以要用Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称。

  • StatefulSet会为关联的Pod保持一个不变的Pod Name
    statefulset中Pod的名字格式为(StatefulSet name)-(pod序号)

  • StatefulSet会为关联的Pod分配一个dnsName
    Pod name.service name.namespace name.svc.cluster.local

为什么要用volumeClaimTemplate?

对于有状态应用都会用到持久化存储,比如mysql主从,由于主从数据库的数据是不能存放在一个目录下的,每个mysql节点都需要有自己独立的存储空间。而在deployment中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,它们数据是同步的,而statefulset定义中的每一个pod都不能使用同一个存储卷,这就需要使用volumeClainTemplate,当在使用statefulset创建pod时,volumeClainTemplate会自动生成一个PVC,从而请求绑定一个PV,每一个pod都有自己专用的存储卷。
在这里插入图片描述

Statefulset使用案例

部署web站点

  • 创建存储类
[root@master1 statefulset]# cat class-web.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-web
provisioner: example.com/nfs
[root@master1 statefulset]# kubectl apply -f class-web.yaml
storageclass.storage.k8s.io/nfs-web created
  • 部署statefulsetful web服务
[root@master1 statefulset]# cat statefulset.yaml
apiVersion: v1
kind: Service
metadata: 
  name: nginx
  labels:
     app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec: 
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:   
  - metadata:
       name: www
    spec:
       accessModes: ["ReadWriteOnce"]
       storageClassName: "nfs-web"
       resources: 
         requests:
           storage: 1Gi
        
  • 查看pod资源是否创建成功
[root@master1 statefulset]# kubectl apply -f statefulset.yaml
service/nginx unchanged
statefulset.apps/web created
[root@master1 statefulset]# kubectl get statefulset -o wide
NAME   READY   AGE     CONTAINERS   IMAGES
web    2/2     2m17s   nginx        nginx

# 可以看见这个nginx service是没有ip的
[root@master1 statefulset]# kubectl get service -o wide | grep nginx
nginx        ClusterIP   None         <none>        80/TCP    3m25s   app=nginx
#可以看到创建的pod是有序的
[root@master1 statefulset]#  kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          5m12s
web-1   1/1     Running   0          5m10s
  • 查看pvc的绑定情况
[root@master1 statefulset]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-1a16870f-1ce8-466e-95e7-83a9806e3632   1Gi        RWO            nfs-web        3d5h
www-web-1   Bound    pvc-acc86b7d-ae3f-4d35-8972-01a652eb4ae6   1Gi        RWO            nfs-web        3d5h
  • 测试是否能通过域名解析出pod的IP
[root@master1 statefulset]# kubectl get pods -l app=nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          30m   10.1.104.20   node2   <none>           <none>
web-1   1/1     Running   0          30m   10.1.104.15   node2   <none>           <none>
[root@master1 ~]#  kubectl exec -it web-0 -- /bin/bash
root@web-0:/# apt-get update
root@web-0:/# apt-get install -y dnsutils
root@web-0:/# nslookup web-0.nginx.default.svc.cluster.local  
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-0.nginx.default.svc.cluster.local
Address: 10.1.104.20

root@web-0:/# nslookup web-1.nginx.default.svc.cluster.local  
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-1.nginx.default.svc.cluster.local
Address: 10.1.104.15

root@web-0:/# nslookup nginx.default.svc.cluster.local  
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx.default.svc.cluster.local
Address: 10.1.104.20
Name:   nginx.default.svc.cluster.local
Address: 10.1.104.15

通过上面可以看到可以成功解析出pod里

service 和headless service区别:

[root@master1 statefulset]# cat deploy-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: ClusterIP
  ports:
  - port: 80   #service的端口,暴露给k8s集群内部服务访问
    protocol: TCP
    targetPort: 80    #pod容器中定义的端口
  selector:
    run: my-nginx  #选择拥有run=my-nginx标签的pod
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: busybox
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        command:
          - sleep
          - "3600"
[root@master1 statefulset]#  kubectl apply -f deploy-service.yaml
service/my-nginx created
deployment.apps/my-nginx created
[root@master1 statefulset]# kubectl get svc -l run=my-nginx
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
my-nginx   ClusterIP   10.104.61.124   <none>        80/TCP    12s
[root@master1 statefulset]# kubectl get pods -l run=my-nginx
NAME                        READY   STATUS    RESTARTS   AGE
my-nginx-68f486d49b-sxzjt   1/1     Running   0          17s
my-nginx-68f486d49b-trvbt   1/1     Running   0          17s
# 通过上面可以看到deployment创建的pod是随机生成的
[root@master1 statefulset]#  kubectl exec -it web-1 -- /bin/bash
root@web-1:/# nslookup my-nginx.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   my-nginx.default.svc.cluster.local
Address: 10.104.61.124   #解析的是service的ip地址

root@web-1:/# exit

Statefulset管理pod:扩容、缩容、更新

  • 扩容
    • 方法1: 修改配置文件statefulset.yaml里的replicas的值即可,原来replicas: 2,现在变成replicaset: 3
[root@master1 statefulset]# vim statefulset.yaml 
[root@master1 statefulset]# kubectl apply -f statefulset.yaml
service/nginx unchanged
statefulset.apps/web configured
[root@master1 statefulset]# kubectl get pods -l app=nginx 
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          95m
web-1   1/1     Running   0          95m
web-2   1/1     Running   0          15s
    • 方法2: 直接编辑控制器实现扩容
      在这里插入图片描述
[root@master1 statefulset]# kubectl edit sts web
statefulset.apps/web edited
[root@master1 statefulset]# kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          97m
web-1   1/1     Running   0          97m
web-2   1/1     Running   0          2m26s
web-3   1/1     Running   0          42s
  • 缩容更扩容差不多,把数字改小就好了

  • Statefulset实现pod的更新
    在这里插入图片描述

[root@master1 statefulset]#  kubectl edit sts web
statefulset.apps/web edited
     <none>

[root@master1 statefulset]# kubectl get sts -o wide
NAME   READY   AGE    CONTAINERS   IMAGES
web    4/4     103m   nginx        ikubernetes/myapp:v2
[root@master1 statefulset]# kubectl get pods -o wide -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE    IP             NODE    NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          69s    10.1.104.18    node2   <none>           <none>
web-1   1/1     Running   0          79s    10.1.104.21    node2   <none>           <none>
web-2   1/1     Running   0          112s   10.1.166.173   node1   <none>           <none>
web-3   1/1     Running   0          2m7s   10.1.104.14    node2   <none>           <none>

DaemonSet概述

DaemonSet控制器能够确保k8s集群所有的节点都运行一个相同的pod副本,当向k8s集群中增加node节点时,这个node节点也会自动创建一个pod副本,当node节点从集群移除,这些pod也会自动删除;删除Daemonset也会删除它们创建的pod

DaemonSet工作原理:如何管理Pod?

daemonset的控制器会监听kuberntes的daemonset对象、pod对象、node对象,这些被监听的对象之变动,就会触发syncLoop循环让kubernetes集群朝着daemonset对象描述的状态进行演进。

Daemonset典型的应用场景

在集群的每个节点上运行存储,比如:glusterd 或 ceph。
在每个节点上运行日志收集组件,比如:flunentd 、 logstash、filebeat等。
在每个节点上运行监控组件,比如:Prometheus、 Node Exporter 、collectd等。

DaemonSet 与 Deployment 的区别

Deployment 部署的副本 Pod 会分布在各个 Node 上,每个 Node 都可能运行好几个副本。
DaemonSet 的不同之处在于:每个 Node 上最多只能运行一个副本。

资源清单文件编写技巧

查看定义Daemonset资源需要的字段有哪些?

[root@master1 ~]# kubectl explain daemonset
KIND:     DaemonSet
VERSION:  apps/v1

DESCRIPTION:
     DaemonSet represents the configuration of a daemon set.

FIELDS:
   apiVersion	<string> #当前资源使用的api版本,跟VERSION:  apps/v1保持一致
   kind	<string>  #资源类型,跟KIND:  DaemonSet保持一致
   metadata	<Object> #元数据,定义DaemonSet名字的
   spec	<Object>    #定义容器的
   status	<Object>  #状态信息,不能改

查看DaemonSet的spec字段如何定义?

[root@master1 ~]# kubectl explain ds.spec
KIND:     DaemonSet
VERSION:  apps/v1
RESOURCE: spec <Object>
DESCRIPTION:
     The desired behavior of this daemon set. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
     DaemonSetSpec is the specification of a daemon set.
FIELDS:
   minReadySeconds	<integer>   #当新的pod启动几秒种后,再kill掉旧的pod。
   revisionHistoryLimit	<integer> #历史版本
   selector	<Object> -required-  #用于匹配pod的标签选择器
   template	<Object> -required- 
#定义Pod的模板,基于这个模板定义的所有pod是一样的
   updateStrategy	<Object> #daemonset的升级策略
#查看DaemonSet的spec.template字段如何定义?
#对于template而言,其内部定义的就是pod,pod模板是一个独立的对象

查看DaemonSet的spec字段如何定义?

[root@master1 ~]# kubectl explain daemonset.spec
KIND:     DaemonSet
VERSION:  apps/v1
RESOURCE: spec <Object>
DESCRIPTION:
     The desired behavior of this daemon set. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
     DaemonSetSpec is the specification of a daemon set.
FIELDS:
   minReadySeconds	<integer>   #当新的pod启动几秒种后,再kill掉旧的pod。
   revisionHistoryLimit	<integer> #历史版本
   selector	<Object> -required-  #用于匹配pod的标签选择器
   template	<Object> -required- 
#定义Pod的模板,基于这个模板定义的所有pod是一样的
   updateStrategy	<Object> #daemonset的升级策略
   

查看DaemonSet的spec.template字段如何定义?

[root@master1 ~]# kubectl explain ds.spec.template
KIND:     DaemonSet
VERSION:  apps/v1
RESOURCE: template <Object>
FIELDS:
   metadata	<Object>  # 定义pod的元数据
   spec<Object>   # 定义容器

日志收集案例实战

[root@master1 daemonset]# cat daemonset.yaml
apiVersion: apps/v1   # Daemonset使用api的版本
kind: DaemonSet    # 资源类型
metadata:
  name: fluentd-elasticsearch   # 资源的名字
  namespace: kube-system  # 资源所在的名称空间:
  labels:
    k8s-app: fluentd-logging  # 资源具有的标签
spec:
   selector:     # 标签选择器
     matchLabels:
       name: fluentd-elasticsearch
   template:
     metadata:
       labels:
          name: fluentd-elasticsearch   # 基于模版,定义pod的标签
     spec:
       tolerations:   # 定义容忍度
       - key: node-role.kubernetes.io/master
         effect: NoSchedule
       containers:   #定义容器
       - name: fluentd-elasticsearch
         image: fluentd
         imagePullPolicy: IfNotPresent
         resources:  # 容器的资源配额
           requests:
             cpu: 100m
             memory: 200Mi
           limits:
             memory: 200Mi
         volumeMounts:
           - name: varlog
             mountPath: /var/log  #把本地的/var/log/目录挂载到容器内
           - name: varlibdockercontainers
             mountPath: /var/lib/docker/containers
             readOnly: true   # 挂载目录制度权限
       terminationGracePeriodSeconds: 30   #优雅关闭服务
       volumes:
         - name: varlog   # 基于本地目录,创建一个卷 
           hostPath:
             path: /var/log
         - name: varlibdockercontainers
           hostPath:
             path: /var/lib/docker/containers

查看daemonset控制器的资源

[root@master1 daemonset]# kubectl apply -f daemonset.yaml
daemonset.apps/fluentd-elasticsearch unchanged
[root@master1 daemonset]# kubectl get ds -n kube-system | grep fluentd-elasticsearch 
fluentd-elasticsearch   3         3         3       3            3           <none>                   65s
[root@master1 daemonset]# kubectl get pods -n kube-system -o wide  | grep fluentd
fluentd-elasticsearch-9nttl                1/1     Running   0          2m59s   10.1.137.100    master1   <none>           <none>
fluentd-elasticsearch-fb4cb                1/1     Running   0          2m59s   10.1.166.158    node1     <none>           <none>
fluentd-elasticsearch-lkn7v                1/1     Running   0          2m59s   10.1.104.2      node2     <none>           <none>

通过上面可以看到在k8s的三个节点均创建了fluentd这个pod
pod的名字是由控制器的名字-随机数组成的

daemonset的滚动更新

查看滚动更新的字段定义

[root@master1 ~]# kubectl explain ds.spec.updateStrategy.rollingUpdate
KIND:     DaemonSet
VERSION:  apps/v1
RESOURCE: rollingUpdate <Object>
DESCRIPTION:
     Rolling update config params. Present only if type = "RollingUpdate".
     Spec to control the desired behavior of daemon set rolling update.

FIELDS:
   maxUnavailable	<string>

上面表示rollingUpdate更新策略只支持maxUnavailabe,先删除在更新;因为我们不支持一个节点运行两个pod,因此需要先删除一个,在更新一个。

  • 命令行更新镜像
[root@master1 daemonset]# kubectl set image daemonsets fluentd-elasticsearch fluentd-elasticsearch=ikubernetes/filebeat:5.6.6-alpine -n kube-system
daemonset.apps/fluentd-elasticsearch image updated
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

k8s之存储、SC、STS、DS篇(一些常见的存储方案) 的相关文章

随机推荐

  • 使用jstack排查线上故障:高CPU占用

    1 前言 一个应用占用CPU很高 除了确实是计算密集型应用之外 通常原因都是出现了死循环 我们以当时出现的实际故障为例 来介绍怎么定位和解决这类问题 2 排查步骤 思路 找出tomcat 进程中使用CPU最高 时间最长的线程 分析堆栈信息
  • Java中栈的实现(1)-使用顺序存储结构(数组)以及实现

    栈和队列其实是与普通的线性发展而来的 为普通的线性表增加一些特殊的限制就可以得到栈和队列了 从功能上看 栈和队列比普通的线性表功能相对弱一点 但是在特殊的场合下 使用栈和队列更有利 例如 编译器在实现函数的调用的时候需要使用栈来存储断点 实
  • js中apply方法的使用详细解析_宿雪家的小镇_新浪博客

    1 对象的继承 一般的做法是复制 Object extendprototype js的实现方式是 复制代码代码如下 Object extend function destination source for property in sour
  • Android平台实现ping功能方案----避过ping数据构建需要root权限的限制

    PingForAndroid C库采用SOCK DGRAM方式构建icmp包 避开raw socket必须root权限的限制 实现ping功能 Github https github com bgylde PingForAndroid pi
  • linux shell 等待输入_shell中获得用户的输入

    有时我们需要shell脚本有更多的交互性 比如我们安装某个linux命令的时候 会提示 N Y 选择安装或者放弃 这时就需要用到shell的read命令 read命令的基本使用 演示代码如下 其中 n表示字符串输出尾端不换行 还可以直接在r
  • R语言 回归诊断几种方法

    回归诊断技术提供了评价回归模型使用性的必要工具 能帮助发现并且纠正问题 有几种方法进行回归诊断 分别是标准方法 car包中的函数 gvlma函数 建议先通过gvlma函数进行验证 如果违反假设条件 再使用其他方法来判断哪些假设没有满足并进行
  • Cloud Run min实例:最小化无服务器的冷启动

    作者 Kelsey Hightower Vinod Ramachandran 无服务器最为优异的一点在于其按需付费的操作模型 可以让用户将服务规模缩减为零 但是对于某些应用程序来说 无服务器对于他们来说没那么重要的原因也在于其模型可以按需缩
  • 春秋云镜 CVE-2022-24112

    春秋云镜 CVE 2022 24112 Apache APISIX batch requests SSRF RCE 漏洞 靶标介绍 Apache Apisix是美国阿帕奇 Apache 基金会的一个云原生的微服务API网关服务 该软件基于O
  • opencv 轮廓放大_使用OpenCV和Python构建自己的车辆检测模型

    概述 你对智慧城市领域有兴趣吗 如果是的话 你会喜欢这个关于实现你自己的车辆检测系统的教程 在深入实现部分之前 我们将首先了解如何检测视频中的移动目标 我们将使用OpenCV和Python构建自动车辆检测器 介绍 关于智慧城市的理念 以及自
  • 三分钟了解腾讯云DDOS基础防护

    三分钟了解腾讯云DDOS基础防护 DDoS 基础防护应用场景包括哪些 腾讯云 DDoS 基础防护应用于攻击频率不高且攻击峰值不超过基础防护阈值的 DDoS 攻击防护场景 当攻击流量超过一定流量时 将自动启动 DDoS 清洗设备进行流量清洗
  • nettty

    https www jianshu com p e58674eb4c7a 1 Netty 异步和事件驱动 1 Netty 能够帮助搭建允许系统能够扩展到支持150000名并发用户 2 Netty 设计关键 异步 事件驱动 1 1 Java网
  • Linux下JDK1.8安装及配置

    进入Linux系统Ubuntu debian或centos安装JDK1 8并配置环境变量 通过终端在 opt目录下新建base文件夹 命令行 sudo mkdir opt base 然后进入java目录 命令行 cd opt base 到官
  • Ubuntu安装Nvidia显卡驱动

    原来没那么复杂 https linuxconfig org how to install the nvidia drivers on ubuntu 18 04 bionic beaver linux 我用的第一种方法 安装途中遇到了一个 v
  • php ajax右边悬浮购物车,一个简单的php+Ajax购物车程序代码(1/2)_PHP教程

    cart name name this gt items SESSION this gt cart name setItemQuantity Set the quantity of an item param string order co
  • 远程服务器docker配置并启动jupyter lab

    前提摘要 在远程服务器docker中存在镜像 但想通过jupyter lab来更好的可视化代码结果 首先登录远程服务器ssh 并拉取镜像 p是端口映射 22给ssh连接 jupyter lab的默认端口是8889 8888是自己的端口号 n
  • Ubuntu下OpenResty 搭建高性能服务端

    Socke 介绍 Linux Socket 编程领域为了处理大量连接请求场景 需要使用非阻塞 I O 和复用 select poll epoll 是 Linux API 提供的 I O 复用方式 自从 Linux2 6 中加入了 epoll
  • 函数式,F#都做了哪些优化?

    非函数式语言中使用函数式风格的缺点 函数式的优点 想必大家都已经非常了解了 我们来看看 一般语言使用函数式风格可能带来的问题 变量默认是可变的 为了实现不可变性 开发者只能人为的规范不去改变变量的值 没有明确的变量修改提示 容易因失误改变变
  • 手撸软件测试框架——lua版(四)

    本文简单介绍一下开发测试用例用到的几个关键接口 1 断言 在测试用例中少不了对结果进行校验 校验的方法一般称为断言 assert 也就是说 在进行一系列的操作之后 断定会出现某个确定性的结果 如果这个确定性的结果如期出现 则断言成功 被测试
  • spring读取resources文件夹下的文件报错:cannot be resolved to absolute file path because it dose not reside in th

    spring springboot读取resources文件夹下的文件报错 cannot be resolved to absolute file path because it dose not reside in the file sy
  • k8s之存储、SC、STS、DS篇(一些常见的存储方案)

    为什么要做持久化存储 在k8s中部署的应用都是以pod容器的形式运行的 假如我们部署MySQL Redis等数据库 需要对这些数据库产生的数据做备份 因为Pod是有生命周期的 如果pod不挂载数据卷 那pod被删除或重启后这些数据会随之消失