前言

本篇是Kubernetes第十二篇,大家一定要把环境搭建起来,看是解决不了问题的,必须实战。

Kubernetes系列文章:
  1. Kubernetes介绍
  2. Kubernetes环境搭建
  3. Kubernetes-kubectl介绍
  4. Kubernetes-Pod介绍(-)
  5. Kubernetes-Pod介绍(二)-生命周期
  6. Kubernetes-Pod介绍(三)-Pod调度
  7. Kubernetes-Pod介绍(四)-Deployment
  8. Kubernetes-Service介绍(一)-基本概念
  9. Kubernetes-Service介绍(二)-服务发现
  10. Kubernetes-Service介绍(三)-Ingress(含最新版安装踩坑实践)
  11. Kubernetes-网络

为什么需要存储

对于这个问题其实很简单,容器中持久化的文件生命周期是短暂的,如果容器中程序崩溃宕机,kubelet 就会重新启动,容器中的文件将会丢失,所以对于有状态的应用容器中持久化存储是至关重要的一个环节;另外很多时候一个 Pod 中可能包含多个 Docker 镜像,在 Pod 内数据也需要相互共享,Kubernetes 中 Pod 也可以增加副本数量,遇到故障时 Pod 可以转移到其它节点,为了浮动节点都能够访问统一的持久化存储以及容器间共享数据,Kubernetes 中定义了 Volume 来解决这些问题 ,从本质上讲,Volume 只是一个目录,可能包含一些数据,Pod 中的容器可以访问它。该目录是何种形式,是由所使用的 Volume 类型决定的。

Volume介绍

Kubernetes 支持很多 Volume类型,可以分为以下类型:

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘

img

我们重点介绍下资源对象映射为存储卷、Node本地存储卷和持久卷。

资源对象映射为存储卷

在 Kubernetes中ConfigMap、Serect、DownwardAPI和ServiceAccountToken四种存储卷,存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换,是为容器提供预先定义好的数据。从容器的角度来看,这些 Volume里的信息就是被映射到容器当中的。

ConfigMap

ConfigMap用于保存应用程序的配置信息,可以通过Volume的形式挂载到容器内部文件系统中,供容器内的应用程序读取。

使用
  1. 创建configMap文件;
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
data:
  env-key: test
  1. 创建Pod文件,在Pod使用configMap配置信息;
apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
  - name: configmap-pod
    image: busybox:1.28.3
    command: [ "/bin/sh", "-c", "env" ]
    env:
     - name: ENV
       valueFrom:
         configMapKeyRef:
           name: env-config
           key: env-key 
  restartPolicy: Never
  1. 启动configMap和Pod;
kubectl apply -f env-configMap.yaml
kubectl apply -f configmap-pod.yaml
  1. 查看启动日志;
kubectl logs configmap-pod

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_02

image.png

Secret

Secret功能与ConfigMap类似,它与ConfigMap的区别在于,Secret保存的是需要加密的、应用所需的配置信息。Secret与ConfigMap用法完全相同:你可以使用 kubectl create secret从文件或者目录创建 Secret,也可以直接编写Secret对象的YAML文件。

Downward API

Downward API的作用是让Pod里的容器能够直接获取到这个Pod API对象本身的元数据信息。

使用
  1. 创建Pod,该Pod声明了一个projected类型的Volume,Volume的数据来源为Downward API,而这个 Downward API Volume,要暴露Pod的metadata.labels 信息给容器,容器启动以后,则是不断打印出 /etc/podinfo/labels 里的内容;
apiVersion: v1
kind: Pod
metadata:
  name: downwardapi-demo-pod
  labels:
    cluster: demo-test
spec:
  containers:
    - name: downwardapi-demo
      image: busybox:1.28.3
      command: ["sh", "-c"]
      args:
      - while true; do
          if [[ -e /etc/podinfo/labels ]]; then
            echo -en '\n\n'; cat /etc/podinfo/labels; fi;
          sleep 5;
        done;
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
          readOnly: false
  volumes:
    - name: podinfo
      projected:
        sources:
        - downwardAPI:
            items:
              - path: "labels"
                fieldRef:
                  fieldPath: metadata.labels
  1. 启动Pod和查看日志;
kubectl apply -f downwardapi-demo-pod.yaml
kubectl logs downwardapi-demo-pod

kubernetes存储于本地磁盘 kubernetes 对象存储_Pod_03

image.png

Service Account Token

Service Account Token是一种针对Pod的账号,它是Kubernetes进行权限分配的对象。比如Service Account A,可以只被允许对 Kubernetes API 进行 GET 操作,而 Service Account B,则可以有 Kubernetes API 的所有操作权限。Service Account的授权信息和文件,实际上保存在它所绑定的一个特殊的 Secret 对象里的,对于Service Account Token可以理解为一种特殊的Secret对象。

Kubernetes对所有的Pod都提供了一个default Service Account,任何一个运行在 Kubernetes的Pod,都可以直接使用这个默认的 Service Account,而无需显示地声明挂载它。

原理探索

实现方式还是靠Projected Volume机制

  1. 查看该Pod的详细信息,可以看到该Po的默认挂载一个serviceaccount的路径;
kubectl describe pod downwardapi-demo-pod

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_04

image.png

  1. 我们进入容器内部查看下serviceaccount具体内容,我们可以看到包括3方面内容,分别是token、ca.crt、namespace;
#进入容器
kubectl exec -it downwardapi-demo-pod -- /bin/sh
#查看具体内容
ls /var/run/secrets/kubernetes.io/serviceaccount

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_05

image.png

  1. 接下来我们看下系统中的Service Account对象,可以看到有一个default-token-q4qts的Secret对象,该对象是一个Mountable secrets对象,说明此对象需要被挂载的;

kubernetes存储于本地磁盘 kubernetes 对象存储_API_06

image.png

  1. 接下来我们看下default-token-q4qts包含什么东西,我们可以看到包含了token、ca.crt、namespace三类对象;

kubernetes存储于本地磁盘 kubernetes 对象存储_Pod_07

image.png

通过Mountable secrets标识、Pod内容对象以及Secrets包含的对象,我们可以证明每个命名空间下面都会有一个名为default默认的Service Account,在Pod启动的时候,Secret对象会自动挂载到Pod指定的目录下,帮助Pod来完成API Server的身份鉴权。

使用
  1. 创建Service Account资源;
#创建Service Account资源
kubectl create serviceaccount sa-demo
#查看Service Account资源
kubectl get sa

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_08

image.png

  1. 查看下是否创建Secret;
#查看是否创建Secret
kubectl describe sa sa-demo、
#查看具体的Secret内容
kubectl describe secret sa-demo-token-k4t96

kubernetes存储于本地磁盘 kubernetes 对象存储_Pod_09

image.png

  1. 创建使用sa-demo的Pod;
apiVersion: v1
kind: Pod
metadata:
  name: sa-demo-pod
spec:  
  serviceAccountName: sa-demo
  containers:
  - image: nginx          
    name: sa-demo-pod               
    resources:                      
      limits:
        cpu: 100m
        memory: 200Mi
      requests:
        cpu: 100m
        memory: 200Mi
  1. 启动该Pod;
#创建pod资源
kubectl create -f sa-demo-pod.yaml
#查看Pod资源
kubectl get pods
#查看pod是否挂载serviceaccount
kubectl describe pod sa-demo-pod

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_10

image.png

kubernetes存储于本地磁盘 kubernetes 对象存储_数据_11

image.png

  1. 进入容器内部访问kubernetes服务的API,在Kubernetes集群中,默认为API Server创建了一个名为kubernetes的Service,通过这个Service可以访问API Server,使用curl命令直接访问会得到如下返回信息,表示并没有权限;
#查看服务列表
kubectl get svc
#进入容器内部
kubectl exec -it sa-demo-pod -- /bin/sh
#访问kubernetes服务
curl https://kubernetes

kubernetes存储于本地磁盘 kubernetes 对象存储_API_12

image.png

kubernetes存储于本地磁盘 kubernetes 对象存储_数据_13

image.png

  1. 使用ca.crt和Token做认证,先将ca.crt放到CURL_CA_BUNDLE这个环境变量中,curl命令使用CURL_CA_BUNDLE指定证书;再将Token的内容放到TOKEN中,然后带上TOKEN访问API Server;
#声明变量
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
#携带token访问服务
curl -H "Authorization: Bearer $TOKEN" https://kubernetes

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_14

image.png

Node本地存储卷

Kubernetes管理Node本地存储卷有是那种类型:

  1. EmptyDir: 与Pod同生命周期,Node临时存储;
  2. HostPath: Node目录;
  3. Local: 基于持久卷PV管理的Node目录;
EmptyDir

当Pod被调度到某个节点上时,在Node上创建的是一个emptyDir卷,此卷刚开始是一个空目录,并且只要 Pod在该节点上运行,卷就一直存在,与Pod生命周期相同, 当Pod被销毁的时候,Node上的目录也会被销毁。同一个Pod的多个容器都可以挂载到改卷上。

emptyDir可以在以下几种场景下使用:

  1. 临时空间,例如基于磁盘的合并排序;
  2. 设置检查点以从崩溃事件中恢复未执行完毕的长计算;
  3. 保存内容管理器容器从Web服务器容器提供数据时所获取的文件;
使用
  1. 创建一个使用emptyDir volume的Pod;
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec:
  containers:
  - image: busybox:1.28.3
    name: emptydir-pod
    command: [ "sleep", "3600" ]
    volumeMounts:
    - mountPath: /data
      name: data-volume
  volumes:
  - name: data-volume
    emptyDir: {}
  1. 创建完成之后我们通过describe命令查看该Pod的详情,我们可以看到上面创建的Pod Volumes 类型是:EmptyDir;
kubectl describe pod emptydir-pod

kubernetes存储于本地磁盘 kubernetes 对象存储_数据_15

image.png

  1. 进入容器中查看挂载情况;
kubectl exec -it emptydir-pod /bin/sh

kubernetes存储于本地磁盘 kubernetes 对象存储_API_16

image.png

HostPath

hostPath类型则是映射Node节点文件系统中的文件或者目录到Pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。

hostPath可以在以下几种场景使用:

  1. 容器应用中关键的数据需要持久化到宿主机上;
  2. 需要使用运行一个访问 Docker内部数据Pod, 可以将主机的/var/lib/docker目录挂载到容器内;
  3. 监控系统,例如在容器中运行cAdvisor需要以hostPath方式挂载/sys;
  4. Pod启动需要依赖宿主机某个目录或者文件信息;
使用
  1. 创建一个使用hostPath volume的Pod;
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
  - image: busybox:1.28.3
    name: hostpath-pod
    command: [ "sleep", "3600" ]
    volumeMounts:
    - mountPath: /test-data
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory
  1. 创建完成之后我们通过describe命令查看该Pod的详情,我们可以看到上面创建的Pod Volumes 类型是:HostPath,如果发现有个hostPath type check failed: /data is not a directory的告警,你需要在每个节点创建/data目录;
kubectl describe pod hostpath-pod

kubernetes存储于本地磁盘 kubernetes 对象存储_数据_17

image.png

kubernetes存储于本地磁盘 kubernetes 对象存储_数据_18

image.png

  1. 接下来我们进入容器,在容器中创建文件;
#进入容器
kubectl exec -it hostpath-pod /bin/sh
#编辑文件
vi 123.txt
#输出文件
cat 123.txt

kubernetes存储于本地磁盘 kubernetes 对象存储_Pod_19

image.png

  1. 查看Pod被调度到那个节点;
kubectl get pods -o wide

kubernetes存储于本地磁盘 kubernetes 对象存储_kubernetes存储于本地磁盘_20

image.png

  1. 进入demo-slave-2查看/data目录下是否增加文件;

kubernetes存储于本地磁盘 kubernetes 对象存储_API_21

image.png

  1. 销毁hostpath-pod,检查文件是否存在,我们会发现Pod已经被删除了,volume卷中的数据仍然还在;
kubectl delete -f hostpath-pod.yaml

kubernetes存储于本地磁盘 kubernetes 对象存储_Pod_22

image.png

emptyDir和hostPath都可以实现本地存储卷的功能、但是二者在功能上还是有异同的:

  1. 二者都是node节点的本地存储卷方式;
  2. emptyDir可以选择把数据存到tmpfs类型的本地文件系统中去,hostPath并不支持这一点;
  3. hostPath除了支持挂载目录外,还支持File、Socket、CharDevice和BlockDevice,既支持把已有的文件和目录挂载到容器中,也提供了“如果文件或目录不存在,就创建一个”的功能;
  4. emptyDir是临时存储空间,完全不提供持久化支持;
  5. hostPath的卷数据是持久化在Node节点的文件系统中的,即便pod已经被删除了,volume卷中的数据还会留存在Node节点上;