第三章 K8s存储与网络笔记
一、K8s存储
1、存储卷基础
在docker容器中,为了实现数据的持久性存储,在宿主机和容器内做映射,可以保证在容器的生命周期结束,数据依旧可以实现持久性存储。
但是在k8s中,由于pod分布在各个不同的节点之上,并不能实现不同节点之间持久性数据的共享,并且,在节点故障时,可能会导致数据的永久性丢失。为此,k8s就引入了外部存储卷的功能。
1.1、emptyDir目录
emptyDir:Pod挂载在本地的磁盘或者内存,被称为emptyDir,称为临时空目录,随着Pod删除,也会被删除。注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除pod.
做一个empty数据卷示例:pod-emp.yaml
命令:kubectl create -f pod-emp.yaml
进入容器:kubectl exec -it empty-pod /bin/sh
运行后查看,挂载描述如下:
使用场景,容器共享文件。在一个pod中有两类容器,一个是主容器,一个是辅助容器,两个容器使用同一个存储卷,辅助容器用来生成新的内容,主容器加载使用。
示例2:运行pod-empty.yaml文件
运行:
kubectl create -f pod-empty.yaml
查看容器:
kubectl get pods -o wide
可看到页面内容不断增加
1.2、gitrepo目录
gitrepo实际上并不是一个新的存储类型,只是emptydir上补添一个git命令来拉取文件而已。当pod创建时候,会拉取git(依赖于宿主机git命令驱动)仓库中数据克隆到本地,并且作为存储卷定义在pod之上。gitrepo基于emptyDir,此存储卷是emptyDir,git仓库中拉取的代码存放在emptyDir后被定义到pod。
因为拉取git仓库的动作,仅仅是在pod创建时触发一次,并不会待续更新文件,因此在工作中基本上无用处。有兴趣的同学可以自行研究一下。
1.3、hostPath目录
hostPath类型则是映射node文件系统中的文件或者目录到pod里。可实现针对某一节点的数据持久化,如果节点宕机了,那数据就丢失了
示例:pod-hostpath.yaml
创建容器:
kubectl create -f pod-hostpath.yaml
进入pod中创建一个测试文件:
kubectl exec -it host-pod /bin/sh
在挂在目录下,创建一个文件:
[[email protected] k8s-config]# kubectl exec -it host-pod /bin/sh
/data # echo 123 > 123file
/data # ls
123file
/data # cat 123file
123
/data #
回到pod运行的node机器上查看(leader192.168.30.161),是否存在了这个测试文件
[[email protected] ~]# ls /data
ls: cannot access /data: No such file or directory
查看pod信息发现该容器运行在work1上面
然后到work1机器上查看,果然有此文件
[[email protected] ~]# ls /data
123file
[[email protected] ~]#
此后,删除掉pod,此文件也依然存在,可自行去验证。
1.4、nfs共享存储卷
nfs使的我们可以挂在已经存在的共享到的我们的Pod中,nfs不会被删除,仅仅是解除挂在状态而已,这就意味着NFS能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递.并且,nfs可以同时被多个pod挂在并进行读写
1.5、节点安装配置nfs
1)这里在leader机器上安装nfs:
yum install -y nfs-utils
2)配置nfs:
vi /etc/exports
添加内容:/data/volumes *(rw,no_root_squash)
3)启动并查看:
[[email protected] ~]# systemctl start nfs
[[email protected] ~]# systemctl start rpcbind
[[email protected] ~]# showmount -e localhost
Export list for localhost:
/data/volumes *
[[email protected] ~]#
[[email protected] ~]# showmount -e
Export list for leader:
/data/volumes *
[[email protected] ~]#
1.6、node节点挂载
需要在所有节点安装`nfs-utils` 组件,否则当Pod被分配到没有组件的节点,会启动失败,因为没有`mount.nfs`。
1)安装nfs:
yum install -y nfs-utils
注意:
为了让leader与work节点能互相访问,需要在work节点的host配置里面添加leader节点的信息
2)挂载nfs:
mount -t nfs leader:/data/volumes /nfs
注意:没有则先在work机器新建nfs目录,leader机器新建目录: /data/volumes
解除挂载:umount /nfs
查看挂载效果:mount
在 leader节点/data/volumes目录下新建文件leader_create_file并输入内容its leader create by leader
[[email protected] ~]# cd /data/volumes/
[[email protected] volumes]# echo its leader create by leader > leader_create_file
[[email protected] volumes]# ls
leader_create_file
[[email protected] volumes]#
然后回到work节点的nfs目录下查看:
[[email protected] ~]# cd /nfs/
[[email protected] nfs]# ls
leader_create_file
[[email protected] nfs]# cat leader_create_file
its leader create by leader
[[email protected] nfs]#
进入node节点,可查看/mnt目录下文件与master已互通
1.7、测试NFS
先在nfs服务内,加一个html测试页面
echo '<h1>NFS success</h1>' > /data/volumes/index.html
创建一个pod测试:
Leader机器创建vol-nfs.yaml
运行pod后测试:
kubectl create -f vol-nfs.yaml
kubectl get pods -o wide
可看到nginx输出页面
你还可以测试pod的删除重建,丝毫不影响pod服务效果
2、k8s的namespace概念
一个namespaces可以理解为kubernetes集群中的一个虚拟化集群。在一个Kubernetes集群中可以拥有多个命名空间,它们在逻辑上彼此隔离。 这样可以方便我们区分各个团队的不同服务,增加安全甚至性能方面的帮助。K8S默认已经为了我们初始化了三个namespace:
- default:你的service和app默认被创建于此。
- kube-system:kubernetes系统组件使用。
- kube-public:公共资源使用。但实际上现在并不常用。
执行以下命令查看:
kubectl get namespaces
kubectl get pods --namespace kube-system
总而言之,namespace是一种为了你方便管理服务的辅助,你可以根据需要创建若干个namespace供业务服务分组治理。从另一方面来说,K8s的任何一个服务,都是归属于某一个namespace的。
3、PVC和PV
通过上面的存储卷的基础知识,我们大概了解了在K8S中,要持久化存储数据,最好还是存入到第三方服务里更安全,如NFS等。而在业界,这样的存储很多,使用方式五花八门,这就要求运维工程师必须要精通这些存储才能用好它。 PersistentVolume(pv)和PersistentVolumeClaim(pvc)就是k8s提供的两种API资源,用于抽象存储细节:
- 管理员关注于如何通过pv提供存储功能而无需如何使用
- 用户只需要挂载pvc到容器中而不需要关注存储卷采用何种技术实现。
pvc和pv的关系与pod和node关系类似,前者消耗后者的资源。pvc可以向pv申请指定大小的存储资源并设置访问模式。
接下来我们使用PV与PVC的方式,实现上面nfs模式的资源挂载效果,定义一个PV资源。
3.1、PV概念与使用
PersistentVolume(PV)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像节点是集群资源一样。因此PV是全局资源,不归属于任何namespace的。
查看vol-pv.yaml文件:
[[email protected] k8s-config]# cat vol-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs2 # pv的名称 与后面的PVC绑定
spec:
storageClassName: manual
capacity:
storage: 2Gi # 存储容量
accessModes:
- ReadWriteMany
nfs: # 引入nfs服务
server: 192.168.30.161
path: /data/volumes
创建PV:
kubectl create -f vol-pv.yaml
此处的RWX是PV资源的AccessModes(访问模式),有以下几种模式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载
此处的NFS资源,是支持多个pod同时挂载使用的,同时PV同一时间只能被一个PVC申请使用,这里有个状态变迁过程:
- Available(可用):表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定):表示 PV 已经被 PVC 绑定
- Released(已释放):PVC 被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该 PV 的自动回收失败
3.2、PVC的概念与使用
PersistentVolumeClaim(PVC)是由用户进行存储的请求。 它类似于pod。 Pod消耗节点资源,PVC消耗PV资源。因此PVC是归属于某个namespace的。
1)文件vol-pvc.yaml内容:
[[email protected] k8s-config]# cat vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs
namespace: default # 归属namespace
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi # 要求的容量
2)创建PVC:
kubectl create -f vol-pvc.yaml
kubectl get pvc
kubectl get pv
现次查看PV的状态,发现其已经被绑定使用了
测试一个pod的使用效果,将上面的pvc引入这里使用,这里我们创建两个资源(pod与PVC)
3)文件vol-pod.yaml内容:
[[email protected] k8s-config]# cat vol-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc
spec:
containers:
- name: nginx
image: nginx:1.9.7
ports:
- containerPort: 80
volumeMounts:
- name: pvc-nfs
mountPath: "/usr/share/nginx/html" # 将PVC挂载到路劲下
volumes:
- name: pvc-nfs
persistentVolumeClaim: # 挂载类型为PVC
claimName: pvc-nfs # 申请PVC的名字
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi
这里我们先删除之前创建的那个PVC:
kubectl delete pvc pvc-nfs
注意:如果pv的状态是released,那么这个pv也得删除重建。
创建新的PVC pod:
kubectl create -f vol-pod.yaml
查看效果:
3.3、PVC的阻塞
当PVC没有足够的PV来满足时,状态为挂起pending
如果有PV被释放,或者新建了PV能满足请求时,自动绑定占用
4、statefulset控制器
现实工作中,我们的应用,分两类,有状态和无状态:
- 无状态的,关注的是群体,如nginx,tomcat等
- 有状态的,关注的是个体,如redis,mysql等
我们前面的控制器,对无状态的应用操作很好,但对有状态的应用完全不实用。
有状态应用集的特点:
稳定且需要唯一的网络标识符,如Redis集群,在Redis集群中,它是通过槽位来存储数据的,假如第一个节点是01000,第二个节点是1001~2000,第三个节点2001~3000...等等,这就使得Redis集群中每个节点要通过ID来标识自己,如果第二个节点宕机了,重建后它必须还叫第二个节点,或者说第二个节点叫R2,它必须还叫R2,这样在获取1001~2000槽位的数据时,才能找到数据,否则Redis集群将无法找到这段数据。
稳定且持久的存储,可实现持久存储,新增或减少pod,存储不会随之发生变化;针对这样的特点,k8s推出StatefulSet。
4.1、PV资源配置
制做三个基础PV资源:pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: 192.168.30.161
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: 192.168.30.161
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: 192.168.30.161
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 3Gi
创建PV:
kubectl create -f pv.yaml
4.2、创建StatefulSet服务
StatefulSet资源清单包含三个部分:
- headless service 用于定义网络标识(DNS)
- StatefulSet 控制器,用于定义具体应用
- volumeClaimTemplate 存储卷申请模板,用于创建PVC
1)查看stateful.yaml文件内容
[[email protected] k8s-config]# cat stateful.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
namespace: default
labels:
app: myapp # service 名称
spec:
ports:
- port: 80
name: web
clusterIP: None # 配置headless service
selector:
app: myapp-pod # 匹配Pod 标签
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp
spec:
serviceName: myapp # 名称
replicas: 2
selector:
matchLabels:
app: myapp-pod # 匹配Pod
template:
metadata:
labels:
app: myapp-pod
spec:
containers:
- name: myapp
image: nginx:1.9.7
ports:
- containerPort: 80
name: web
volumeMounts:
- name: myappdata
mountPath: /usr/shar/nginx/html
volumeClaimTemplates:
- metadata:
name: myappdata # pvc名称
spec:
accessModes: ["ReadWriteOnce"] # 权限
resources:
requests:
storage: 1Gi # pv 大小
2)创建服务:
kubectl create -f stateful.yaml
查看详情:
查看pod,发现sts创建的pod集群,是有序编号的,从第0个开始,依次创建。
查看pvc列表(sts自动创建管理的),它与上面的pod是序号一一对应的。也就是说,此pod重启重建,对应的pvc都是一样的
3)服务扩容:
命令:kubectl scale sts myapp --replicas=3
可以看到pod扩展,会连带pvc一起扩容,但是缩容却不会
命令:kubectl scale sts myapp --replicas=2
删除pod继续查看:
可以看到,新建出来的pod名称/node都是不变的,对应的PVC当然也不变。
5、configmap与secret
secret和configmap可以理解为特殊的存储卷,但是它们不是给Pod提供存储功能的,而是提供了从集群外部向集群内部的应用注入配置信息的功能。ConfigMap扮演了K8S集群中配置中心的角色。
ConfigMap定义了Pod的配置信息,可以以存储卷的形式挂载至Pod中的应用程序配置文件目录,从configmap中读取配置信息;也可以基于环境变量的形式,从ConfigMap中获取变量注入到Pod容器中使用。
5.1、命令行方式创建ConfigMap
--from-literal=key=value格式直接赋值,示例如下:
创建命令:
kubectl create configmap web-config --from-literal=db.host=192.168.30.128 --from-literal=db.port='3306'
查看这个configmap信息:
[[email protected] k8s-config]# kubectl get configmaps -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
db.host: 192.168.30.128
db.port: "3306"
kind: ConfigMap
metadata:
creationTimestamp: "2020-04-11T11:32:07Z"
name: web-config
namespace: default
resourceVersion: "42780"
selfLink: /api/v1/namespaces/default/configmaps/web-config
uid: 4c914861-421d-4e4e-842c-594c6784c0a7
kind: List
metadata:
resourceVersion: ""
selfLink: ""
[[email protected] k8s-config]#
--from-file=path方式,将文件中信息存入configmap
先将值放入文件中:
[[email protected] k8s-config]# echo -n 192.168.30.128 > ./db.host
[[email protected] k8s-config]# echo -n 3306 > ./db.port
[[email protected] k8s-config]# cat db.port
3306
[[email protected] k8s-config]# cat db.host
192.168.30.128
再合建configmap:
kubectl create configmap config2 --from-literal=db.host=./db.host --from-literal=db.port=./db.port
查看信息,效果一样
5.2、YAML 配置文件方式创建ConfigMap
直接创建一个yaml文件,配置好信息
[[email protected] k8s-config]# cat db.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config3
data:
db.host: 192.168.30.128
db.port: "3306"
[[email protected] k8s-config]#
创建并查验信息:
kubectl get cm
kubectl get cm config3 -o yaml
5.3、环境变量方式使用configmap
使用valueFrom、configMapKeyRef、name、key指定要用的key。
1)文件 cm-pod.yaml内容如下:
[[email protected] k8s-config]# cat cm-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cmpod
spec:
containers:
- name: cmpod
image: busybox
args: [ "/bin/sh", "-c", "sleep 3000" ]
env:
- name: DB_HOST # 我们需要配置这个值
valueFrom:
configMapKeyRef:
name: config3 # 到config3中获取db.host的值
key: db.host #
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: config3
key: db.port
[[email protected] k8s-config]#
2)运行pod:
kubectl create -f cm-pod.yaml
进入pod容器中,查看环境信息已经引入:
kubectl exec -it cmpod -- /bin/sh
5.4、volume挂载使用
把configmap当作文件挂载进pod容器
1)文件cm2.yaml内容如下:
[[email protected] k8s-config]# cat cm2.yaml
apiVersion: v1
kind: Pod
metadata:
name: cmpod2
spec:
containers:
- name: cmpod2
image: busybox
args: [ "/bin/sh", "-c", "sleep 3000" ]
volumeMounts: # 文件挂载
- name: db
mountPath: "/etc/db" # 文件挂载目录
readOnly: true
volumes:
- name: db # 定义挂载数据
configMap: # 定义挂载数据
name: config3
[[email protected] k8s-config]#
2)创建容器:
kubectl create -f cm2.yaml
进入容器查看详情:kubectl get pod -o wide
kubectl exec -it cmpod2 /bin/sh
ls /etc/db/
二、K8S网络
K8S中包含三种网络:
container之间的通信。
Pod网络:Pod之间通信的网络。
节点网络: 各个节点主机之间的通信网络。
1、Pod内容器间的通讯
K8s的用处是容器的编排和管理,但最小组成却不是容器,是pod。物理机或者虚拟机叫node,pod是基础单元,pod里可以有多个容器,也可以只有一个容器,同一个pod的容器彼此是共享网络和主机配置的,换句话说,彼此是可以直接localhost通信的,类似于同一台机器上进行通信,所以这里面是无所谓隔离和安全一说,对外而言就是一个环境,所以pod就是这个环境的业务实体。如下图:
创建一个多容器的pod,在容器内,你可以查看到它们的ip地址是同一个。
1)文件net-pod.yaml内容如下:
[[email protected] k8s-config]# cat net-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: netpod
labels:
app: netpod #给自己打上标签
spec:
containers: #创建了3个容器
- name: nginx
image: nginx:1.7.9
- name: tomcat
image: tomcat:latest
- name: busybox
image: busybox
command: [ "sleep", "3600" ]
[[email protected] k8s-config]#
2)创建命令:
kubectl create -f net-pod.yaml
3)查看每个容器的ip地址:
kubectl exec -it netpod -c nginx /bin/sh
kubectl exec -it netpod -c tomcat /bin/sh
这也告诉我们,同一个pod内共享网络,容器间的port是互斥的,你无法让pod内的两个容器使用同一个port。
2、Pod间的容器通讯
两个pod间的网络通讯,可以直接理解为两台主机的ip通讯
只要能寻址到对方pod的ip,就轻松通讯了。在K8s的集群虚拟的ip中,ip网段是根据node节点来分配的,每个node节点得一个独立的ip子网段,如下例的3个pod集群:
可以看到,节点work2上的所有pod处理同一个网段,work3的pod处于另一个网段。很明显,work2上的两台pod处于同一个子网,网络天然是通的。但是K8s还为我们多做了一步,即work2与work3上的网络,相互之间也是畅通无阻的。这里就用到了k8s的网络插件的功能,常用的就是flannel。
3、flannel原理、跨node的通信
flannel组建一个大二层扁平网络,pod的ip分配由flannel统一分配,通讯过程也是走flannel的网桥。
每个node上面都会创建一个flannel0虚拟网卡,用于跨node之间通讯,所以容器直接可以直接使用pod id进行通讯。跨节点通讯时,发送端数据会从docker0路由到flannel0虚拟网卡,接收端数据会从flannel0路由到docker0。
跨Node的Pod,通信需要网络组件协调。