容器技术使用rootfs机制和Mount Namespace,构建出一个同宿主机完全隔离开的文件系统环境

  那容器里进程新建的文件,怎么样才能让宿主机获取到?宿主机上的文件和目录,怎么样才能让容器里的进程访问到?

  Docker Volume就可以解决这个问题,它允许你将宿主机上指定的目录或文件挂载到容器里面进行读取和修改操作

  在Docker项目里,它支持两种Volume声明方式,可以把宿主机目录挂载进容器的/test目录当中

$docker run -v /test …  //docker没有显式声明宿主机目录,Docker会默认在宿主机上创建一个临时目录/var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载到容器的/test目录上
$docker run -v /home:/test … //直接把宿主机的/home目录挂载到容器的/test目录上

Docker如何将宿主机上的目录或文件挂载到容器里面呢?

  当容器进程被创建之后,尽管开启了Monut Namespace,但是在它执行chroot之前,容器进程一直可以看到宿主机上的整个文件系统(包括了要使用的容器镜像,这个镜像的各个层,保存在/var/lib/docker/aufs/diff目录下,在容器启动后,它们会被联合挂载在var/lib/docker/aufs/mnt中)。在执行chroot之前,把Volume指定的宿主机目录(如/home目录)挂载到指定的容器目录(如/test目录)在宿主机对应的目录(var/lib/docker/aufs/mnt/[可读写层ID]/test,这个Volume的挂载工作就完成了。由于执行了这个挂载操作,容器进程(Docker初始化进程dockerinit,负责完成根目录的准备、挂载设备和目录、配置hostname等一系列需要容器内进行的初始化操作,最后它通过execv()系统调用,让应用进程取代自己,成为容器里PID=1的进程)已经创建了,也就意味着此时Monut namespace 已经开启了。所以这个挂载事件只在容器里可见,在宿主机是看不见容器内部的这个挂载点的,这就保证了容器的隔离性不会被Volume打破。

  这里使用到的挂载技术,就是Linux的绑定挂载(bind mount)机制,它的主要作用是允许你将一个目录或者文件,而不是整个设备挂载到一个指定的目录上,并且这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而源挂载点的内容则会被隐藏起来且不受影响。

  绑定挂载实际上是一个inode替换的过程,在linux系统中,inode可以理解为存放文件内容的“对象”,而dentry,也叫目录项,就是访问这个inode所使用的指针。

  如上图所示,mount --bind /home/test,会将/home挂载到/test上。其实相当于将/test的dentry,重定向到了/home的inode。这样当修改/test目录时,实际修改的是/home目录的inode。这也就是为何一旦执行umount命令,/test目录原先的内容就会恢复:因为修改真正发送在/home目录里。因此在一个正确的时机进行一次绑定挂载,Docker就可以成功将一个宿主机上的目录或文件不动声色地挂载到容器中。这样进程在容器里对这个/test目录进行的所有操作都实际发生在宿主机的对应目录(如/home),而不会影响容器镜像的内容。

  那test目录里的内容既然挂载在容器rootfs的可读写层,它会不会被Docker commit提交掉呢?

  不会,容器的镜像操作是发生在宿主机空间的,而由于Mount Namespace的隔离作用,宿主机并不知道这个绑定挂载的存在。所以在宿主机看了,容器中可读写层的/test目录(var/lib/docker/aufs/mnt/[可读写层ID]/test),始终是空的。不过由于Docker一开始还是要创建/test这个目录作为挂载点,所以执行docker commit之后,会发现新产生的镜像里,会多出来一个空的/test目录。

如何在kubernetes集群部署时使用volume呢??

  在kubernetes中,Volume属于Pod对象的一部分,所以就需要修改YAML文件里的template.spec字段

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort:
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: nginx_vol
volumes:
- name: nginx-vol
emptuDir: {}

  上面在Deployment的Pod中添加了一个volumes字段,定义了这个pod声明的所有Volume。它的名字叫做nginx_vol,类型是emptyDir

  那什么是emptyDir类型?即不显式声明宿主机目录的Volume,。所有kubernetes也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所生命的volumes目录上。

  而Pod中的容器,使用的volumeMounts字段来声明自己要挂载哪个Volume,并通过mountPath字段来定义容器内的Volume目录,比如:/usr/share/nginx/html

  当然kubernetes也提供了显式的volume定义hostPath,如

……
volumes:
- name: nginx-vol
hostPath:
path: /var/data

  这样容器Volume挂载的宿主机目录,就变成了/var/data

  接下来可以使用kubectl apply 更新这个deployment

kubectl apply -f nginx-deployment.yaml

  最后还可以使用kubectl exec指令,进入到Pod当中,查看这个Vloume目录:

$kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash
#ls /usr/share/nginx/html

特殊的Volume——Project Volume(投射数据卷,kubernetes v1.11之后的特性)

  Project存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换,它们的作用是为容器提供预先定义好的数据。

  Kubernetes支持的Projected Volume一共有四种:

    • Secret
    • ConfigMap
    • Downward API
    • ServcieAccountToken

 1)、Secret

  它的作用是把Pod想要访问的加密数据,存放到Etcd中,然后就可以通过在Pod的容器里挂载Volume的方式,访问到这些Secret里保存的信息。Secret应用场景是存放数据库的Credential信息。

apiVersion: v1
kind: Pod
metadata:
name: test-projected-volume
spec:
containers:
- name: test-secret-volume
image: busybox
args:
- sleep
- ""
volumeMounts:
- name: mysql-cred
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: mysql-cred
projected:
sources:
- secret:
name: user
- secret:
name: pass

  在这个Pod中,它声明挂载的Volume是projected类型,而这个Volume的数据来源,则是名为user和pass的Secret对象,分别对应的是数据库的用户名和密码

  这里用到的数据库的用户名、密码,正是以Secret对象的方式交给Kubernetes保存的,完成这个操作的指令是:

$ cat ./username.txt
admin
$ cat ./password.txt
c1oudc0w! $ kubectl create secret generic user --from-file=./username.txt
$ kubectl create secret generic pass --from-file=./password.txt

  其中,username.txt和password.txt文件里,存放的就是用户名和密码,而user和pass是为Secret对象指定的名字。可以通过kubectl get secrets查看这些对象

$ kubectl get secrets
NAME TYPE DATA AGE
user Opaque 51s
pass Opaque 51s

  除了使用kubectl create secret 指令外,也可以直接编写yaml文件的方式来创建这个Secret对象

apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
user: YWRtaW4=
pass: MWYyZDFlMmU2N2Rm

  通过编写YAML文件创建出来的Secret对象只有一个,但它的data字段,却以Key-Value的格式保存了两份Secret数据,其中,“user”就是第一份数据的key,“pass”是第二份数据的key。需要注意的是,Secret对象要求数据经过Base64转码,以免出现明文密码的安全隐患。

$ echo -n 'admin' | base64
YWRtaW4=
$ echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
$ kubectl create -f test-projected-volume.yaml

  当pod变成running状态后,再验证这些secret对象是不是已经在容器里

$ kubectl exec -it test-projected-volume -- /bin/sh
$ ls /projected-volume/
user
pass
$ cat /projected-volume/user
root
$ cat /projected-volume/pass
1f2d1e2e67df

  可以看到保存在Etcd里的用户名和密码信息,已经以文件的形式出现在了容器的Volume目录李曼,而这个文件的名字就是kubectl create secret指定的key。更重要的是通过挂载方式进入到容器里的Secret,一旦其对应的Etcd里的数据被更新,这里Volume里的文件内容同样也会被更新。这是kubectl组件在定时维护这些Volume,但是这个更新可能会有一定的延时,所以在写程序时,在发起数据库连接的代码处写好重试和超时的逻辑

  

2)、ConfigMap

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

  如一个Java应用程序所需的配置文件(.properties文件),就可以通过下面的方式保存在ConfigMap里

# .properties 文件的内容
$ cat example/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice # 从.properties 文件创建 ConfigMap
$ kubectl create configmap ui-config --from-file=example/ui.properties # 查看这个 ConfigMap 里保存的信息 (data)
$ kubectl get configmaps ui-config -o yaml
apiVersion: v1
data:
ui.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
kind: ConfigMap
metadata:
name: ui-config
...

3)、Downward API

  让Pod里的容器能够直接获取到这个Pod API对象本身的信息。

apiVersion: v1
kind: Pod
metadata:
name: test-downwardapi-volume
labels:
zone: us-est-coast
cluster: test-cluster1
rack: rack-
spec:
containers:
- name: client-container
image: k8s.gcr.io/busybox
command: ["sh", "-c"]
args:
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
echo -en '\n\n'; cat /etc/podinfo/labels; fi;
sleep ;
done;
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
readOnly: false
volumes:
- name: podinfo
projected:
sources:
- downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels

  上面的Pod的Downward API Volume声明了暴露Pod的metadata.labels信息给容器。通过这样的声明方式,当前Pod的Labels字段的值,就会被kubernetes自动挂载成为容器里的/etc/podinfo/labels文件。而这个容器的启动命令,则是不断打印出/etc/podinfo/labels里的内容

$ kubectl create -f dapi-volume.yaml
$ kubectl logs test-downwardapi-volume
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"

  目前Downward API支持的字段已经非常丰富了,比如

. 使用 fieldRef 可以声明使用:
spec.nodeName - 宿主机名字
status.hostIP - 宿主机 IP
metadata.name - Pod 的名字
metadata.namespace - Pod 的 Namespace
status.podIP - Pod 的 IP
spec.serviceAccountName - Pod 的 Service Account 的名字
metadata.uid - Pod 的 UID
metadata.labels['<KEY>'] - 指定 <KEY> 的 Label 值
metadata.annotations['<KEY>'] - 指定 <KEY> 的 Annotation 值
metadata.labels - Pod 的所有 Label
metadata.annotations - Pod 的所有 Annotation . 使用 resourceFieldRef 可以声明使用:
容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request

  需要注意的是Downward API能够获取到的信息,一定是Pod里的容器进程启动之前就能够确定下来的信息。如果想要获取Pod容器运行后才会出现的信息,应该考虑在Pod里定义一个sidecar容器

Secret、ConfigMap以及Downward API这三种Projected Volume定义的信息,大多可以通过环境变量的方式出现在容器里,但是通过环境变量获取这些信息的方式,不具备自动更新的能力,因此建议使用volume文件获取这些信息

4)ServiceAccountToken

  Service Account对象的作用就是kubernetes系统内置的一种服务账户,它是kubernets进程权限分配的对象。如Service Account A可以只被运行对kuberntes API进行GET操作,而Service Account B,则可以有kubernetes API所有操作的权限

  Service Account的授权信息和文件,实际上保存在它所绑定的一个特殊的Secret对象里,这个Secret对象,就是ServiceAccountToken,任何运行在kubernetes集群上的应用都必须使用这个ServiceAccountToken保存的授权信息,才可以合法的访问API Server。也即ServiceAccountToken是一种特殊的Secret。

  此外,kubernetes提供了一个默认的服务账户,任何一个运行在kubernetes里的pod,都可以直接使用这个默认的Service Account,而无需显示地声明挂载它。查看任意一个运行在kubernetes集群里的pod,都会发现每一个pod都已经自动声明一个类型是secret、名为default-token-xxxx的volume,然后自动挂载在每个容器的一个固定目录上

$ kubectl describe pod nginx-deployment-5c678cfb6d-lg9lw
Containers:
...
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-s8rbq (ro)
Volumes:
default-token-s8rbq:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-s8rbq
Optional: false

  即kubernets在每个pod创建时,都自动在它的spec.volumes部分添加上了默认ServiceAccountToken的定义,然后自动给每个容器加上了对于的volumeMounts字段,这个过程对于用户来说是完全透明的。一旦pod创建完成,容器里的应用就可以直接从这个默认的Service AccountToken的挂载目录里访问到授权信息和文件,这个容器内路径在kubernetes里是固定的

$ ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token

  应用程序只要直接加载这些授权文件,就可以访问并操作kubernetes API。这种把Kubernetes客户端以容器的方式运行在集群里,然后使用default Service Account自动授权的方式,被称作“IncluserConfig”

[Kubernetes]Volume的更多相关文章

  1. Docker Kubernetes Volume 本地数据卷

    Docker Kubernetes Volume 本地数据卷 emptyDir 当Pod分配到Node时,首先创建一个空卷,并挂载到Pod中的容器. Pod中的容器可以读取和写入卷中的文件. 当Pod ...

  2. Docker Kubernetes Volume 网络数据卷

    Docker Kubernetes Volume 网络数据卷 由于支持网络数据卷众多 今天只拿nfs作为案例. 支持网络数据卷 nfs iscsi glusterfs awsElasticBlockS ...

  3. Kubernetes K8S之存储Volume详解

    K8S之存储Volume概述与说明,并详解常用Volume示例 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS7.7 2C ...

  4. kubernetes入门(05)kubernetes的核心概念(2)

    一.使用 kubectl run 创建 pod(容器) 命令 kubectl run类似于 docker run,可以方便的创建一个容器(实际上创建的是一个由deployment来管理的Pod): 等 ...

  5. kubernetes入门(04)kubernetes的核心概念(1)

    一.ReplicationController/ReplicaSet 在Kubernetes集群中,ReplicationController能够确保在任意时刻,指定数量的Pod副本正在运行.如果Po ...

  6. kubernetes系列10—存储卷详解

    本文收录在容器技术学习系列文章总目录 1.认识存储卷 1.1 背景 默认情况下容器中的磁盘文件是非持久化的,容器中的磁盘的生命周期是短暂的,这就带来了一系列的问题:第一,当一个容器损坏之后,kubel ...

  7. 理解OpenShift(5):从 Docker Volume 到 OpenShift Persistent Volume

    理解OpenShift(1):网络之 Router 和 Route 理解OpenShift(2):网络之 DNS(域名服务) 理解OpenShift(3):网络之 SDN 理解OpenShift(4) ...

  8. kubernetes 实战5_命令_Assign Pods to Nodes&Configure a Pod to Use a ConfigMap

    Assign Pods to Nodes how to assign a Kubernetes Pod to a particular node in a Kubernetes cluster. Ad ...

  9. kubernetes 一些基本的概念

    k8s 原理 kubernetes API server 作为集群的核心,负责集群各功能之间的通信, 集群内的各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据的时候 通 ...

随机推荐

  1. CentOS6.5下中文输入法的相关问题

    问题.点击Input Method Preferences没反应. 首先执行 yum install "@Chinese Support" yum install -yibus-t ...

  2. 分享几个自己喜欢的前端UI框架

    http://www.layui.com/ http://element-cn.eleme.io/#/zh-CN/component/installation

  3. sass+compass起步

    前言:Sass is an extension of CSS that adds power and elegance to the basic language. It allows you to ...

  4. linux的top下buffer与cache的区别

    buffer:    缓冲区,一个用于存储速度不同步的设备或优先级不同的设备之间传输数据 的区域.通过缓冲区,可以使进程之间的相互等待变少,从而使从速度慢的设备读入数据 时,速度快的设备的操作进程不发 ...

  5. MSDN值得学习的地方

    作者:朱金灿 来源:http://blog.csdn.net/clever101 我一直认为:如果你没有乔布斯那样的天才,能够从头脑中原创出好产品,那么最好先学习分析好的产品,它到底好在哪里?哪些地方 ...

  6. ZIP解压缩文件的工具类【支持多级目录|全】

    ZIP解压缩文件的工具类[支持多级目录|全] 作者:Vashon 网上有很多的加压缩示例代码,但是都只是支持一级目录的操作,如果存在多级目录的话就不行了.本解压缩工具类经过多次检查及重构,最终分享给大 ...

  7. Windows各个文件夹介绍

    windows文件介绍 总结 ├WINDOWS │ ├-system32(存放Windows的系统文件和硬件驱动程序) │ │ ├-config(用户配置信息和密码信息) │ │ │ └-system ...

  8. 浅析HashSet add() 方法存储自定义类型对象的过程

    一.自定义一个Student类 package date0504; public class Student { private String id; Student(String id){ this ...

  9. flask_第一个程序

    安装flask sudo pip3 install flask falsk最小应用 from flask import Flask app = Flask(__name__) @app.route(' ...

  10. 最小生成树 || HDU 1301 Jungle Roads

    裸的最小生成树 输入很蓝瘦 **并查集 int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } 找到x在并查集里的根结点,如果 ...