【k8s系列十五】k8s 存储

364 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第31天,点击查看活动详情

一. 存储的概述

k8s的存储一共有四种类型:configMap,Sercet,Persistent Volume,Volume.

1. configMap

configMap简称cm, cm主要是存储配置信息的, 比如:nginx的配置文件, pod的启动命令, nginx的端口号等, 我们都可以使用cm存储.

2. Sercet

只要是和安全有关的,需要加密保存的, 都通过sercet去存储, 例如: 进群认证的sa; 密码; 证书等

3. Volumn

Volumn就有点类似与docker中的卷, docker中的volumn可以保证容器死了以后, 目录还存在; 而k8s中的volumn可以保证pod死了, 目录还存在.

4. Persistent Volume

Persistent Volume简称pv, pv持久卷是一种抽象, 并不是真正的储存. 比如: 存一些网页数据

下面分别来详细研究.

二. Config Map的使用

ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制等对象.

研究configMap从两个方面入手:一个是创建, 一个是使用。

1. Config Map的创建

configMap的创建有3种方式

1)使用目录创建

使用目录创建,会把当前文件夹下所有的文件都转换文config map对象

目录中文件的格式是什么呢?其实没有强制要求,建议使用k-v对。

$ ls 
    game.file
    ui.file
​
$ cat game.file
version=1.17
name=dave
age=18
​
$ cat ui.properties
level=2
color=yellow

image

创建configMap命令

kubectl create configmap game-config --from-file=/root/configMap
  • create:创建命令
  • configmap:创建命令的类型
  • game-config:创建命令的名称
  • --from-file : 指定目录, 目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容

image

问题

k8s生成的文件通常都是yaml格式的,而这种方式生成的config map怎么是k-v的?其实没关系,k8s依然会将其转换为yaml格式。我们可以通过-o yaml 查看

kubectl create configmap game-config --from-file=/root/configMap --dry-run -o yaml
  • --dry-run: 本地(local)干运行而不与服务器通信:它没有服务器验证,也没有通过验证许可控制器(validating admission controller)

image

这样就生成了一个yaml格式的configmap。

通过命令查看config map

kubectl get cm

image

  • DATA:表示里面有两个字段

查看config map的详情

有两种方式

  • 方式一:
kubectl describe cm game-config

image

  • 方式二
kubectl get cm game-config -o yaml

image

2)使用文件创建

使用文件创建, 把单个文件转换为config map。

创建命令

kubectl create configmap game-config --from-file=/root/configMap

这个创建命令和文件夹创建config map命令是一模一样的,也就是说--from-file既可以指定文件夹,也可以指定文件

3)使用字面值创建

使用文字值创建,利用 —from-literal 参数传递配置信息,该参数可以使用多次,格式如下

kubectl create configmap literal-config --from-literal=name=dave --from-literal=password=pass
  • literal-config: 表示使用文字值创建
  • --from-literal: 指定创建的k-v对
  • 这里创建了两个字面量:一个是name=dave, 另一个是password=pass

image

使用describe查询cm 后者通过 -o yaml查询

image

我们看到,data一共有两个元素。

2. Config Map的使用

使用config map也有3种方式:

1)把 ConfigMap 替代为当前的环境变量

Config map 的第一种用法是使用Config Map来代替环境变量。下面举个例子

第一步:准备configMap资源清单1

apiVersion: v1
kind: ConfigMap
metadata:
  name: literal-config
  namespace: default
data:
  name: dave
  password: pass
  • 资源清单的类型是ConfigMap
  • 资源清单名称是literal-config
  • 资源清单中定义了两个数据:name=dave,password=pass

第二步:准备configMap资源清单2

apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
  namespace: default
data:
  log_level: INFO
  app: myapp
  • 创建了一个名为env-config的资源
  • 资源清单的类型是ConfigMap
  • 资源清单中有两组数据:log_level=INFO,app=myapp

第三步:准备pod资源清单

apiVersion: v1
kind: Pod
metadata:
  name: pod-configmap-env
spec:
  containers:
    - name: test-container
      image: wangyanglinux/myapp:v1
      command: [ "/bin/sh", "-c", "env" ]
      env:
        - name: USERNAME
          valueFrom:
            configMapKeyRef:
              name: literal-config
              key: name
        - name: PASSWORD
          valueFrom:
            configMapKeyRef:
              name: literal-config
              key: password
      envFrom:
        - configMapRef:
            name: env-config
  restartPolicy: Never
  • 创建一个pod, pod的名字是pod-configmap-env

  • spec中指定了两个元素

    • restartPolicy:重启策略, 这里是Never。表示pod结束后不再重启。

    • containers:指定pod中的容器

      • name:容器名称是test-container

      • image:镜像名称是wangyanglinux/myapp:v1

      • command: [ "/bin/sh", "-c", "env" ] 表示容器启动以后执行的命令, 这里执行env命令,也就是打印环境变量。后面,我们会定义环境变量的内容

      • env:表示定义环境变量

        • name:系统中环境变量的名称,这里名称叫USERNAME

        • valueFrom:环境变量的值,这里不是直接一个value值,而是值的来源。我们这里的值来源于configMap。

          • configMapKeyRef:指的是值来源于configMap

            • name:来源于哪一个config呢?也就是configMap的名字是什么。这里来源的configMap的名字是literal-config
            • key:指定的configMap中有多组数据。我们这里的变量引用的是哪一个数据呢?key指定的是数据的name。这里key指定的是name。
            • 表示的含义是:我们定义了一个系统环境变量,变量名叫 USERNAME。变量的值来源于名称为literal-config的configMap,data数据中key=name的字段的值,这个值是dave。因此,我们定义的这个系统变量是 USERNAME=dave
      • envFrom:表示引用一组环境变量。

        • configMapRef:引用环境变量来自 configMap

          • name:指定引用的 configMap 的名字。这里是 env-config
          • 在env-config中有两组数据,都会被引用进来。log_level: INFO 和 app: myapp

上面的代码的含义是什么呢?创建一个pod,里面定义一个容器,给容器创建了两组环境变量。第一组环境变量来源是名字为literal-config的configMap,变量内容是:USERNAME=dave 和PASSWORD=pass;第二组环境变量来源于名字是env-config的configMap,环境变量的内容是log_level=INFO 和app=myapp

第四步:执行资源文件

[root@master configMap]# touch literal-config-cm.yaml
[root@master configMap]# vi literal-config-cm.yaml[root@master configMap]# touch env-config-cm.yaml
[root@master configMap]# vi env-config-cm.yaml[root@master configMap]# touch pod-configmap-env.yaml
[root@master configMap]# vi pod-configmap-env.yaml[root@master configMap]# kubectl create -f literal-config-cm.yaml
configmap/literal-config created
​
[root@master configMap]# kubectl create -f env-config-cm.yaml
configmap/env-config created
​
[root@master configMap]# kubectl create -f pod-configmap-env.yaml
pod/pod-configmap-env created
​
# 查看pod的日志
[root@master configMap]# kubectl logs pod pod/pod-configmap-env

image

第五步:总结

这样,就成功将configMap中的数据注入到环境变量了。

2) 把 ConfigMap 设置为命令行参数

其实第二种方式设置为命令行参数和第一种有些类似,也是定义环境变量,然后将环境变量作为命令行的调用参数。用案例来解释:

第一步:准备资源清单

apiVersion: v1
kind: Pod
metadata:
  name: pod-command-cm
spec:
  containers:
    - name: test-container
      image: wangyanglinux/myapp:v1
      command: [ "/bin/sh", "-c", "echo $(USERNAME) $(PASSWORD)" ]
      env:
        - name: USERNAME
          valueFrom:
            configMapKeyRef:
              name: literal-config
              key: name
        - name: PASSWORD
          valueFrom:
            configMapKeyRef:
              name: literal-config
              key: password
  restartPolicy: Never
  • pod的名字是pod-command-cm
  • env:定义了环境变量,具体含义如上一个案例的解释。环境变量的内容是USERNAME=dave,PASSWORD=pass
  • comamnd:容器启动后执行的命令。这里启动命令带参数"echo (USERNAME)(USERNAME) (PASSWORD)",这个逻辑和在linux中的shell是一样的。我们已经定义了环境变量了,那么在命令行可以直接调用环境变量

第二步:执行资源清单

[root@master configMap]# kubectl apply -f pod-command-cm.yaml
pod/pod-command-cm created
​
# 查看日志
kubectl logs pod-command-cm

image

3)将configMap当成文件使用,需要数据卷插件配合

前两种方式都是服务启动环境变量就创建了,中途是不可以修改的。而第三种方式,我们可以在中途修改环境变量。这种方式需要通过数据卷的方式实现。

之前说过配置中心。我们来看看配置中心的原理:

image

当我们的系统越来越庞大,管理变量就成了一个问题。如果每次修改变量,都要重新发版,这样也将是一个很大的工作量,因此引入了nacos。nacos可以自动实现环境变量实时同步且在不启动服务的情况下生效。在k8s中,config Map就可以起到配置中心的作用。 环境变量修改,在不重启的情况下,立即生效。这就涉及了configMap的另一个功能,热更新。

下面举例说明:

第一步:准备资源清单

apiVersion: v1
kind: ConfigMap
metadata:
  name: log-config
  namespace: default
data:
  log_level: INFO
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hot-update
spec: 
  selector: 
    matchLabels: 
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: wangyanglinux/myapp:v1
        ports:
        - containerPort: 80
        volumeMounts:
        - name: config-volume
          mountPath: /etc/config
      volumes:
        - name: config-volume
          configMap:
            name: log-config
  • 这里定义了两个资源清单, 一个是configMap,数据内容是log_level = INFO;另一个是Deployment,在这里我们会定义数据卷挂载到config map。

  • Deployment的资源清单基础的就不多说了,说一下我们第一次见到的,从容器开始看

    • 在template中定义了两个个性化资。一个是volumes,另一个是containers。

      • volumes:定义了资源数据卷。

        • name:数据卷的名字是config-volume

        • configMap:指定了数据卷数据来源的类型是configMap

          • name:当前k8s中有很多cm,到底是哪一个呢?指定cm的名字是log-config
      • containers

        • volumeMounts:为当前容器指定挂载的数据卷

          • name:挂载数据卷的名字
          • mountPath:数据挂载的路径

总结:这个含义是,定义了一个数据卷,数据卷的名字是config-volume,数据来源是名字为log-config的cm。然后为名字为my-nginx的容器挂载数据卷,并指定数据卷挂载到容器的/etc/config目录下。

第二步:容器挂载的原理

这里分析一下容器挂载的原理

image

来分析一下pod是如何跟cm交互了。pod可不可以实时去cm读取数据?可以,但这样cm的压力会很大,而且交互会很频繁。最好的办法是在每个pod中缓存一份变量,当cm中变量变化了,同步更新本地的缓存。这样,变量的更新可能会产生延迟。

configMap配置文件在本地如何保存呢?之前我们在创建cm的时候可以从文件创建,那么保存的时候也会保存为文件。比如这个资源文件

[root@master configMap]# kubectl get cm env-config -o yaml
apiVersion: v1
data:
  app: myapp
  log_level: INFO
kind: ConfigMap
metadata:
  creationTimestamp: "2022-04-07T07:07:11Z"
  name: env-config
  namespace: default
  resourceVersion: "1204949"
  selfLink: /api/v1/namespaces/default/configmaps/env-config
  uid: 5192cf76-d5b1-4dfe-b058-cd137ccfed5f

保存为本地文件的时候,会保存为2个文件,

  • 文件1:文件名是app,文件内容是myapp
  • 文件2:文件名是log_level,文件内容是INFO

第三步:执行资源清单

[root@master configMap]# kubectl create -f hot-update.yaml
deployment.apps/hot-update created

执行结果

[root@master configMap]# kubectl get pod
NAME                                   READY   STATUS      RESTARTS       AGE
hot-update-75b6496495-2ff6h            1/1     Running     0              28s

第四步:cm热更新

我们写一个for循环,不停的打印环境变量log_level: INFO。

while 2>1; do kubectl exec hot-update-75b6496495-2ff6h -- cat /etc/config/log_level; sleep 2; date; done
[root@master configMap]# while 2>1; do kubectl exec hot-update-75b6496495-2ff6h -- cat /etc/config/log_level; sleep 2; date; done
INFOThu Apr  7 16:27:51 CST 2022
INFOThu Apr  7 16:27:53 CST 2022
INFOThu Apr  7 16:27:55 CST 2022
INFOThu Apr  7 16:27:57 CST 2022
INFOThu Apr  7 16:27:59 CST 2022
INFOThu Apr  7 16:28:02 CST 2022
INFOThu Apr  7 16:28:04 CST 2022

每隔两秒打印一下挂载数据卷中log_level文件的内容

然后我们修改cm中的内容, 将log_level的值从INFO改为DEBUG

kubectl edit cm log-config

image

来看结果

image

从图中可以看出,16:35:12 文件内容改为了DEBUG。其实我是16:34:39 就修改完文件了,经过了31s才变过来。这也说明,这里环境变量不是共享的,而是缓存(注入)到本地了。

注意:在edit文件的时候,要注意有没有annotations,如果有,那么这里面的内容也要修改,否则会不生效。

image

我们修改了cm的内容,容器挂载的数据卷也可以同步到修改的内容。但是,这个修改对应用生效么?很显然是不生效的。容器的镜像没变。所以,要想cm修改的配置最终在应用中生效,还要重载镜像。什么意思呢? 如下表格

配置生效实现方式
修改配置文件Config Map
重启容器?

如果想要实现修改配置文件,不手动重启, 自动生效, 需要有两步: 第一步: 配置文件实时更新, 这个通过cm可以实现 第二步: 重启容器. 如果容器不重启, 那配置还是原来的配置. 如何重启容器呢? 修改镜像的版本号

docker tag wangyanglinux/myapp:v1 wangyanglinux/myapp:v1.1

这样就会重启了。可是在k8s中怎么操作呢?cm提供了滚动更新策略。

第五步 Config Map的滚动更新策略

更新 ConfigMap 目前并不会触发相关 Pod 的滚动更新,可以通过修改 pod annotations 的方式强制触发滚动更新。