Kubernetes
Kubernetes集群的两种管理角色:Master和Node
Master
Kubernets里的Master指的是集群控制节点,每个Kubernets集群里需要一个Master节点来负责整个集群的管理和控制,基本上Kubernets的所有控制命令都发给它,它来负责具体执行过程。Master节点通常高可用建议3台服务器来完成。
Master节点上运行以下关键进程。
- Kubernets API Server(kube-apiserver):提供了HTTP Rest接口的关键服务进程,是Kubernets里所有资源的增、删、改、查等操作的唯一入口,也是集群控制的入口进程。
- Kubernets Controller Manager(kube-controller-manager):Kubernets里所有资源对象的自动化控制中心。
- Kubernets Scheduler(kube-scheduler):负责资源调度(Pod调度)的进程
另外,在Master节点上还需要启动一个etcd服务,因为Kubernets里的所有服务资源对象的数据全部是保存在etcd中的
Node
Node节点是Kubernets集群中的工作负载节点,每个Node都会被Master分配一些工作负载,当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上去。
Node节点上都运行着以下一组关键进程。
- kubelet:负责Pod对应的容器创建、启停等任务,同时与Master节点密切协作,实现集群管理的基本功能
- kube-proxy:实现Kubernetes Service的通信与负载均衡机制的重要组件
- Docker Engine: Docker引擎
查看集群有多少个node:
1 | kubectl get nodes |
查看某个node的详细信息:
1 | kubectl describe node <node name> |
资源对象
Pod
Pod是Kubernets的最重要也是最基本的概念,每个Pod都有一个特殊的被称为“根容器”的Pause容器。Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包括一个或者多个紧要相关的用户业务容器。
在一组容器作为一个单元的情况下,我们难以对‘整体’简单地进行判断及有效地进行行动。引入业务无关的并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,就简单、巧妙地解决了这个难题。
Pod里的多个容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样极简化了密切关联的业务容器之间的通信问题,也很好的解决了他们之间的文件共享问题。
1 | apiVersion: v1 |
2 | kind: Pod |
3 | metadata: |
4 | name: mysql |
5 | labels: |
6 | name: mysql |
7 | spec: |
8 | containers: |
9 | - name: mysql |
10 | image: mysql:latest |
11 | resources: |
12 | requests: |
13 | memory: "64Mi" |
14 | cpu: "250m" |
15 | limits: |
16 | memory: "128Mi" |
17 | cpu: "500m" |
18 | ports: |
19 | - containerPort: 3306 |
20 | hostPort: 3306 |
21 | env: |
22 | - name: MYSQL_ROOT_PASSWORD |
23 | value: "123456" |
resources:
- requests: 该资源的最小申量,系统必须满足
- limits: 该资源最大允许使用的量,不能被突破,当容器试图使用超过这个量的资源时,可能被kubernets kill并重启
Label
Label是kubernetes系统中的另一个核心概念。一个Label是一个key=value的键值对,我们可以通过指定的资源对象绑定一个或多个不同的label来实现多维度的资源分组管理功能,给某个资源对象定义一个Label,就相当于打了一个标签,随后通过Label Selector查询和筛选拥有某些Label的资源对象。
Pod,Label定义在其metadata中
1 | apiVersion: v1 |
2 | kind: Pod |
3 | metadata: |
4 | name: myweb |
5 | labels: |
6 | app: myweb |
管理对象RC和Service在spec中定义Selector与Pod进行关联:
1 | apiVersion: v1 |
2 | kind: ReplicationController |
3 | metadata: |
4 | name: myweb |
5 | spec: |
6 | replicas: 1 |
7 | selector: |
8 | app: myweb |
9 | template: |
10 | .... |
1 | apiVersion: v1 |
2 | kind: Service |
3 | metadata: |
4 | name: myweb |
5 | spec: |
6 | selector: |
7 | app: myweb |
8 | ports: |
9 | - port: 8080 |
10 | .... |
新出现的管理对象如Deployment、ReplicaSet、DaemonSet和Job则可以在Selector中使用给予集合的筛选条件定义:
1 | selector: |
2 | matchLabels: |
3 | app: myweb |
4 | matchExpressions: |
5 | - {key: tier,operator: In,values: [frontend]} |
6 | - {key: environment,operator: NotIn,values: [dev]} |
Label Selector在kubernetes中的重要使用场景有以下几处:
- kube-controller进程通过资源对象RC上定义的Label Selector来筛选要监控的Pod副本的数量,从而实现Pod副本的数量始终符合预期设定的全自动控制流程。
- kube-proxy进程通过Service的Label Selector来选择对应的Pod,自动建立起每个Service到对应Pod的请求转发路由表,从而实现Service的智能负载均衡机制。
- 通过对某些Node定义特定的Label,并且在Pod定义文件中使用NodeSelector这种标签调度策略,kube-scheduler进程可以实现Pod”定向调度”的特性。
Replication Controller
RC是kubernetes系统中的核心概念之一,简单来说,它其实是定义了一个期望的场景,RC的定义包含以下几部分:
- Pod期待的副本数(replicas)
- 用于筛选目标Pod的Label Selector
- 当Pod的副本数量小于预期数量时,用于创建新Pod的Pod模版
1 | apiVersion: v1 |
2 | kind: ReplicationController |
3 | metadata: |
4 | name: backend |
5 | spec: |
6 | replicas: 1 |
7 | selector: |
8 | tier: backend |
9 | template: |
10 | metadata: |
11 | labels: |
12 | app: mysql |
13 | tier: backend |
14 | spec: |
15 | containers: |
16 | - name: mysql-demo |
17 | image: mysql:latest |
18 | imagePullPolicy: IfNotPresent |
19 | resources: |
20 | requests: |
21 | memory: '64Mi' |
22 | cpu: '300m' |
23 | limits: |
24 | memory: '128Mi' |
25 | cpu: '500m' |
26 | env: |
27 | - name: MYSQL_ROOT_PASSWORD |
28 | value: 123456 |
29 | ports: |
30 | - containerPort: 3306 |
31 | hostPort: 3306 |
总结关于RC的一些特性与作用:
- 在大多数情况下,我们通过定义一个RC实现pod的创建过程及副本数量的自动控制
- RC里包括完成的Pod摸版
- RC通过Label Selector机制实现对Pod副本的自动控制
- 通过改变RC里的Pod副本数量,可以实现Pod的扩容或缩容功能
- 通过改变RC里Pod模版中的镜像版本,可以实现Pod的滚动升级功能
Replica Set
官方解释为下一代RC,它与RC当前存在的唯一区别是:Replica Set支持给予集合的Label Selector,而RC只支持基于等式的Label Selector:
1 | apiVersion: extensions/v1beta1 |
2 | kind: ReplicaSet |
3 | metadata: |
4 | name: mysql |
5 | spec: |
6 | replicas: 1 |
7 | selector: |
8 | matchLabels: |
9 | app: mysql |
10 | matchExpressions: |
11 | - {key: tier,operator: In,values: [backend]} |
12 | template: |
13 | metadata: |
14 | labels: |
15 | app: mysql |
16 | tier: backend |
17 | spec: |
18 | containers: |
19 | - name: mysql |
20 | image: mysql:latest |
21 | imagePullPolicy: IfNotPresent |
22 | resources: |
23 | requests: |
24 | memory: "64Mi" |
25 | cpu: "250m" |
26 | limits: |
27 | memory: "128Mi" |
28 | cpu: "500m" |
29 | ports: |
30 | - containerPort: 3306 |
31 | hostPort: 3306 |
32 | env: |
33 | - name: MYSQL_ROOT_PASSWORD |
34 | value: '123456' |
kubectl的RC的绝大部分命令同样适用于Replica Set。此外,当前我们很少单独使用Replica Set,它主要被Deployment这个更高层次的资源对象所使用,从而形成一整套Pod创建、删除、更新的编排机制。Replica Set和Deployment这两重要资源对象逐步替换了之前的RC的作用,是kubernets v1.3里Pod自动扩容(伸缩)这个告警功能实现的基础。
Deployment
Deployment的引入的目的是为了更好地解决Pod编排问题。为此,Deployment在内部使用了Replica Set来实现,Deployment相对于RC的最大升级是我们可以随时知道当前Pod部署的进度。
Deployment的典型使用场景有以下几个:
- 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建过程
- 检查Deployment的状态来看部署动作是否完成(Pod副本的数量是否达到预期的值)
- 更新Deployment以创建新的Pod(比如镜像升级)
- 如果当前Deployment不稳定,则回滚到早先的Deployment版本
- 暂停Deployment以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment,进行新的发布
- 扩展Deployment以应对高负载
- 查看Deployment的状态,以此作为发布是否成功的指标
- 清理不在需要的旧版本ReplicaSets
1 | apiVersion: extensions/v1beta1 |
2 | kind: Deployment |
3 | metadata: |
4 | name: mysql |
5 | spec: |
6 | replicas: 1 |
7 | selector: |
8 | matchLabels: |
9 | app: mysql |
10 | matchExpressions: |
11 | - {key: tier,operator: In,values: [backend]} |
12 | template: |
13 | metadata: |
14 | labels: |
15 | app: mysql |
16 | tier: backend |
17 | spec: |
18 | volumes: |
19 | - name: data |
20 | hostPath: |
21 | path: /data |
22 | containers: |
23 | - name: mysql-demo |
24 | image: mysql:latest |
25 | volumeMounts: |
26 | - mountPath: /mydata |
27 | name: data |
28 | imagePullPolicy: IfNotPresent |
29 | resources: |
30 | requests: |
31 | memory: "64Mi" |
32 | cpu: "250m" |
33 | limits: |
34 | memory: "128Mi" |
35 | cpu: "500m" |
36 | ports: |
37 | - containerPort: 3306 |
38 | hostPort: 3309 |
39 | env: |
40 | - name: MYSQL_ROOT_PASSWORD |
41 | value: '123456' |
Horizontal Pod Autoscaler
Pod横向扩容,简称HPA,与RC、Deployment一样,也属于一种Kubernetes资源对象,通过追踪分析RC控制的所有目标Pod的负载变化情况,来确定是否需要针对性地调整目标Pod的副本数,这是HPA的实现原理。当前,HPA可以有以下两种方式作为Pod负载的度量指标。
- CPUUtilizationPercentage
- 应用程序自定义的度量指标,比如服务每秒内的相应的请求数(TPS和QPS)
CPUUtilizationPercentage计算过程中使用的Pod的cpu使用量通常是1min内的平均值,目前通过查询Heapster扩展组件来得到这个值,所以需要安装部署Heapster,并且如果Pod没有定义Pod Request的值,则无法使用CPUUtilizationPercentage来实现Pod的横向扩展能力。Kubernetes从v1.2版本开始支持应用程序自定义度量指标。
1 | apiVersion: autoscaling/v1 |
2 | kind: HorizontalPodAutoscaler |
3 | metadata: |
4 | name: auto |
5 | namespace: default |
6 | spec: |
7 | maxReplicas: 10 |
8 | minReplicas: 1 |
9 | scaleTargetRef: |
10 | kind: Deployment |
11 | name: mysql |
12 | targetCPUUtilizationPercentage: 90 |
StatefulSet
在Kubernetes系统中,Pod的管理对象RC、Deployment、DaemonSet和Job都是无状态的服务。但现实中很多服务都是有状态的,特别是一些复杂的中间件集群,如mysql集群、mongodb集群等,这些集群有一个共同点:
- 每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互通信
- 集群的规模比较固定
- 集群中的每个节点都是有状态的,通常会持久化数据到永久存储中
- 如果磁盘损坏,则集群里的每个节点无法正常运行,集群功能受损
为了解决上述问题,引入StatefulSet,有如下特性:
- StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员,假设StatefulSet的名字叫kafka,那么第1个Pod叫kafka-0,第二个叫kafka-1
- StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态
- StatefulSet里的Pod采用稳定的持久化存储卷,通过PV/PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷
Service
Service也是Kubernetes里最核心的资源对象之一,Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个‘微服务’,之前我们所说的Pod、RC等资源对象其实都是为这说的服务。
运行在每个Node上的kube-proxy进程其实是一个智能的软件负载均衡器,它负责把对service的请求转发到后端的某个Pod实例上,并在内部实现负载均衡和会话保持,Service不是共用一个负载均衡器的IP地址,而是每个service分配一个全局唯一的IP地址,这个虚拟IP被称为Cluster IP,Pod的Endpoint地址会随着Pod的销毁和重组创建而发生改变,而Cluster IP不会发生改变,于是服务发现这个棘手的问题在kubernetes的整个架构中轻松解决。
1 | apiVersion: v1 |
2 | kind: Service |
3 | metadata: |
4 | name: mysql |
5 | spec: |
6 | ports: |
7 | - port: 3306 |
8 | selector: |
9 | name: mysql |
Kubernetes Service支持多个Endpoint,在存在多个Endpoint的情况下,要求每个Endpoint定义一个名字来区分:
1 | apiVersion: v1 |
2 | kind: Service |
3 | metadata: |
4 | name: tomcat-service |
5 | spec: |
6 | ports: |
7 | - port: 8080 |
8 | name: service-port |
9 | - port: 8005 |
10 | name: shutdown-port |
外部系统访问service,我们先要知道Cluster IP:
- cluster ip仅仅作用于kubernetes service对象,并由kubernetes管理和分配ip地址
- cluster ip无法被ping,因为没有一个‘实体网络对象’来响应
- cluster ip只能结合service port组成一个具体的通信接口,单独的cluster ip不具备tcp/ip通信基础,并且它们属于kubernetes集群这样一个封闭的空间,集群之外的节点如果要访问这个通信接口,则需要做一些额外的工作
- 在kubernetes集群之内,Node IP,Pod IP,Cluster IP网之间的通信,采用的是kubernetes自己设计的一种编程方式的特殊路由规则
想要集群外部直接访问,NodePort是解决上述问题最直接的方法:
1 | apiVersion: v1 |
2 | kind: Service |
3 | metadata: |
4 | name: tomcat-service |
5 | spec: |
6 | type: NodePort |
7 | ports: |
8 | - port: 8080 |
9 | nodePort: 31002 |
10 | selector: |
11 | tier: frontend |
Volume
Volume是pod中能够被多个容器访问的共享目录,Kubernets的Volume定义在Pod上,然后被一个Pod里的多个容器挂载到具体的目录下:其次,Kubernets中的Volume与Pod的生命周期相同,但与容器的生命周期不相关。
1 | apiVersion: v1 |
2 | kind: Pod |
3 | metadata: |
4 | name: mysql |
5 | labels: |
6 | name: mysql |
7 | spec: |
8 | volumes: |
9 | - name: datavol |
10 | emptyDir: {} |
11 | containers: |
12 | - name: mysql |
13 | image: mysql:latest |
14 | resources: |
15 | requests: |
16 | memory: "64Mi" |
17 | cpu: "250m" |
18 | limits: |
19 | memory: "128Mi" |
20 | cpu: "500m" |
21 | volumeMounts: |
22 | - mountPath: /mysqldata |
23 | name: datavol |
24 | ports: |
25 | - containerPort: 3306 |
26 | hostPort: 3306 |
27 | env: |
28 | - name: MYSQL_ROOT_PASSWORD |
29 | value: "123456" |
emptyDir
一个emptyDir Volume是在Pod分配到Node时创建的。初始内容为空,并且无需指定宿主机上的对应的目录文件,因为这是kubernetes自动分配的一个目录,emptyDir的一些用途:
- 临时空间,例如用于某些应用程序运行时的临时目录,且无需永久保存
- 长时间的中间过程CheckPoint的临时保存目录
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)
hostPath
hostPath为Pod上挂载宿主机上的文件或者目录,它通常可以用于以下几方面。
- 容器应用程序生成的日志文件需要多久保存时,可以使用宿主机的高速文件系统进行存储
- 需要访问宿主机上的Docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机/var/lib/docker
1 | volumes: |
2 | - name: 'hostpath' |
3 | hostPath: |
4 | path: /data |
NFS
1 | volumes: |
2 | - name: nfs |
3 | nfs: |
4 | server: nfs-server.address |
5 | path: / |
其他类型的Volume
- gcePersistentDisk: 谷歌公有云提供的永久磁盘(Persitent Disk,PD)存放Volume的数据
- awsElasticBlockStore: 亚马逊公有云提供的EBS Volume储存数据
- iscsi: 使用ISCSIc存储设备上的目录挂载到Pod中
- flocker: 使用Flocker来管理存储卷
- glusterfs: 使用开源GlusterFS网络文件系统的目录挂载到Pod中
- rdb: 使用Ceph块设备共享存储(Rados Block Device)挂载到Pod中
- gitRepo: 通过挂载一个空目录,并从GIT库clone一个git repository以供Pod使用
- secret: 一个secret volume用于为Pod提个加密的信息,secret volume是通过tmfs(内存文件系统)实现的,所以这种的volume总是不会持久化
Persistent Volume
网路存储是相对于计算资源而存在的一种实体资源,比如在使用虚拟机的情况下,我们通常会先定义一个网络存储,然后从中划出一个网盘并挂载到虚拟机上。Persistent Volume(简称PV)和与之相关联的Persistent Volume Chaim(简称PVC)也起到类似的作用。
PV可以理解成kubernetes集群中的某个网络存储中对应的一块存储,它与Volume很类似:
- PV只能是网络存储,不属于任何Node,但可以在每个Node上访问
- PV并不是定义在Pod上的,而是独立与Pod之外定义的
- PV目前支持的包括:gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibew Channel)、Flocker、NFS、iSCSI、RDB(Rados Block Device)、CephFS、Cinder、GlusterFS、VsphereVloume、Quobyte Volumes、VMware Photon、Portworx Volumes、ScaleIO Volumes和HostPath(单机测试)。
1 | apiVersion: v1 |
2 | kind: PersistentVolume |
3 | metadata: |
4 | name: pv0003 |
5 | spec: |
6 | capacity: |
7 | storage: 5Gi |
8 | accessModes: |
9 | - ReadWriteOnce |
10 | nfs: |
11 | path: /somepath |
12 | server: 172.16.2.3 |
- ReadWriteOnce: 读写权限、并且只能被单个node挂载
- ReadOnlyMany: 只读权限、允许被多个Node挂载
- ReadWriteMany: 读写权限、允许被多个node挂载
如果某个Pod想申请某种类型的PV,则首先需要定义一个PersistentVolumeChaim(PVC):
1 | apiVersion: v1 |
2 | kind: PersistentVolumeClaim |
3 | metadata: |
4 | name: myclaim |
5 | spec: |
6 | accessModes: |
7 | - ReadWriteOnce |
8 | resources: |
9 | requests: |
10 | storage: 8Gi |
然后在Pod的Volume定义中引用上述PVC即可:
1 | volumes: |
2 | - name: mypod |
3 | persistentVolumeClaim: |
4 | claimName: myclaim |
PV的状态:
- Available: 空闲状态
- Bound: 已经绑定到某个PVC上
- Released: 对应的PVC已经删除,但资源还没有被集群收回
- Failed: PV自动回收失败
Namespace
Namespace(命名空间)是kubernetes系统的另一个非常重要的概念,Namespace在很多情况下用于实现多租户的资源隔离。Namespace通过将集群内部的资源对象‘分配’到不同的Namespace中,形成逻辑上的分组的不同项目、小组或用户组。
kubernetes集群启动后,会创建一个名为‘default’的namespace:
1 | ~]# kubectl get namespaces |
1 | apiVersion: v1 |
2 | kind: Pod |
3 | metadata: |
4 | name: busybox |
5 | namespace: development |
6 | spec: |
7 | containers: |
8 | - image: busybox |
9 | command: |
10 | - sleep |
11 | - "3600" |
12 | name: busybox |
kubectl get命令将仅显示“default”命名空间的资源对象:
1 | ~]# kubectl get pods --namespace=development |
Annotation
Annotation与Label类似,也使用key/value键对值的形式进行定义。不同的是Label具有严格的命名规则,定义了kubernetes对象的元数据,并且用于label selector,而Annotation则是用户任意定义的“附加”信息。