为什么80%的码农都做不了架构师?>>>
基本概念和术语
Master&Node
Kubernetes 集群的两种管理角色: Master 和 Node
Master
Master 只的是集群控制节点,每个集群需要一个检点来负责整个集群的管理和控制。 基本上所有控制命令都发给它,它来负责具体的执行过程。
Master 节点通常会占据一个独立的服务器。
Master 节点上运行着以下一组关键进程
- Kubernetes API Server (kube-apiserver): 提供了HTTP Rest接口的关键服务进程,是Kubernetes里所有资源的增、删、改、查等操作的唯一入口,也是集群控制的入口进程
- Kubernetes Controller Manager(kube-controller-manager): Kubernetes 里所有资源对象的自动化控制中心
- Kubernetes Scheduler (kube-scheduler): 负责资源调度(Pod 调度) 的进程
Master 节点上还需要启动一个etcd服务,k8s里的所有资源对象的数据全部保存在etcd中
Node
集群中除了Master其他机器被称为Node节点,Node可以是一台物理机,也可以是一台虚拟机。Node是k8s集群中工作负载节点,没个Node会被Master分配一些工作负载(docker 容器), 当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上去。
Node 节点运行以下一组关键进程
- kubelet: 负责Pod对应的容器的创建、启停等任务,同时与Master节点密切协作,实现集群管理的基本功能
- kube-proxy: 实现Kubernetes Service 的通信与负载均衡机制的重要组件。
- Docker Engine (docker): Docker 引擎,负责本机的容器创建和管理工作。
Node 可以在运行期间动态增加到Kubernetes集群中,前提是节点已正确安装、配置和启动上述关键进程。
默认情况kubelet会向Master注册自己, 这也是Kubernetes推荐的Node管理方式。
一旦Node纳入集群管理范围,kubelet进程就会定时向Master节点回报自身的情报,如操作系统、Docker版本、机器的CPU和内存情况,以及当前有哪些Pod在运行。这样Master可以获知每个Node的资源使用情况,并实现高效均衡的资源调度策略。
某个Node超过指定时间不上报信息,会被Master判定为“失联”,Node的状态被标记为不可用(Not Ready), 随后Master会触发“工作负载转移”的自动流程。
Pod
构成
- Pause 容器: 根容器
- User 容器: 一个或多个紧密相关的用户业务容器。
设计 Pause 原因:
- 以它的状态代表整个容器组的状态。
- Pod里多个容器共享Pause容器的IP, 共享Pause容器挂接的Volume, 简化容器通讯,文件共享的问题。
Pod IP: k8s 为每个Pod 分配了唯一IP地址,Pod里多个容器共享。
k8s要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,通常采用虚拟二层网络技术来实现,如 Flannel、Open vSwitch.
一个Pod里的容器与另外主机上的Pod容器能够直接通信。
分类:
- 普通Pod: 一旦被创建,就会放入到etcd中存储, 随后被master调度到某个具体的Node上并进行绑定(Binding),随后被Node的kubelet进程实例化成Docker容器启动。
- 静态Pod(Static Pod): 存放在某个具体的Node上的一个具体文件内,并且只在此Node上启动运行。
默认情况Pod里某个容器停止,k8s会自动检测到并重启这个Pod(重启Pod内所有容器),如果Pod所在Node宕机,将会将Node上所有Pod重新调度到其他节点上。
Endpoint: Pod的IP加上容器的端口(containerPort)
Pod Volume: 定义在Pod上, 被各个容器挂载到自己的文件系统中。
查看pod描述信息,定位问题。
kubectl describe pod
Event:是一个事件的记录, 记录了时间的最早产生时间、最后重现时间、重复次数、发起者、类型,以及导致此时间的原因等众多信息。Event 通常会关联到某个具体的资源对象上,是排查故障的重要参考信息。
Pod 对服务器上计算资源设置限额
- CPU : CPU的资源单位为CPU(Core) 的数量,是一个绝对值。
- Memory: 单位是内存字节数,也是一个绝对值
k8s里通常以千分之一的CPU配额为最小单位。用m来表示。通常一个容器的配额被定义为100~300m,既0.1~0.3个cpu.因为是一个绝对值,所以无论是一个Core还是48个Core的机器 100m所代表的使用量是一样的。
k8s里一个计算资源进行配额限定需要设定两个参数
- Requests: 该资源的最小申请量,系统必须满足要求。
- Limits 该资源最大循序使用的量,不能被突破,当容器试图使用超过这个量的资源时,可能会被k8s kill并重启。
通常将 Request 设置为一个比较小的值,符合容器平时的工作负载情况下的资源需求,把Limits设置为峰值负载下资源占用的最大值。。
如表明mysql容器最少申请0.25个CPU和64Mib内存,在运行时,mysql锁能使用的资源配额为 0.5个CPU及128Mib内存
spec:
containers:
- name: db
image: mysql
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Label 标签
Label 是一个键值对,可以附加到各种资源对象上,如 Node、Pod、Service、RC等。
一个资源可以定义任意数量的Lable, 同一个Lable也可以被添加到任意数量的资源对象上去。
可以在对象定义时确定,也可以在对象创建后动态添加或者删除。
通过制定的资源对象捆绑一个或多个不同的Label 来实现多维度的资源分组管理,方便的进行资源分配、调度、配置、部署等管理工作。
常用标签示例
- 版本标签: "release": "stable", "release": "canary" ...
- 环境标签: "environment": "dev", "environment": "qa", "environment": "production",
- 架构标签: "tier": "frontend", "tier": "backend", "tier": "middleware",
- 分区标签: "partition": "customerA",
- 质量管控标签: "track": "daily", "track": "weekly",
Label Selector 标签选择器
通过标签选择器(Label Selector) 查询和筛选拥有Label的资源对象。
Label Selector 表达式:
- 基于等式(Equality-based):
- name = redis 匹配所有具有标签的资源对象
- name != mysql 匹配不具有标签的对象
- 基于集合(Set-based):
- name in (redis, mysql) 匹配所有具有标签 name=redis 或 name=mysql的资源对象
- name not in (redis) 匹配所有不具有标签 name=redis的资源对象
可以使用多个Label Selector 表达式组合实现复杂的条件选择,多个表达式使用,
进行分隔,几个条件是AND关系
name=redis,env!=production
新出现的管理对象Deployment、ReplicaSet、DaemonSet和Job 可以使用Selector中使用基于集合筛选条件定义
selector:
matchLabels:
app: myweb
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
- {key: environment, operator: NotIn, values: [dev]}
- matchLabels: 定义一组Label, 与直接写在Selector作用相同
- matchExpressions: 定义一组筛选条件,可用条件运算符包括: In、NotIn、Exists和DoesNotExist。
同时设置了这两组,他们的关系是 AND 关系,所有条件都满足才能完成筛选。
Replication Controller
RC: 声明某种Pod的副本数量在任意时刻都符合某种预期值
- Pod 期待的副本数(replicas)
- 用于筛选目标Pod的Label Selector
- 当Pod的副本数量小于预期数量时,用于创建新Pod的模版(template)
定义RC提交到k8s集群后,master节点的Controller Manager组件获得通知,定期巡检系统中存活的目标Pod, 并确保Pod实例的数量刚好等于此RC的期望值。 如果过多的Pod运行,就停一些Pod,否则会创建一些Pod.
通过RC实现了用户应用集群的高可用,减少手工运维工作。
在运行时,可以通过修改RC的副本数量,来实现Pod的动态缩放(Scaling)。
kubectl scale rc redis-slave --replicas=3
注意 删除RC并不会影响通过该RC已创建好的Pod, 为了删除所有Pod,可以将replicas
的值为0
,然后更新该RC.
还提供了 stop 和 delete 命令来一次性删除RC和RC 控制的所有Pod.
通过RC可以实现 滚动升级(Rolling Update)
Kubernetes v1.2时,升级为新的概念 Replica Set, 下一代RC.
区别:
- Replica Sets 支持基于集合的Label selector
- RC 只支持基于等式的Label selector
apiVersion: v1
kind: ReplicaSet # 对比: 这里写的是 ReplicationController
metadata:
name: mysql
spec:
selector:
matchLabels:
app: myweb
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
当前我们很少单独使用,主要被Deployment这个高层对象所使用。我们在使用Deployment时,无须关心如何创建ReplicaSet.
- 改变RC 副本数量,可以实现Pod的扩容和缩容的功能
- 改变RC 里Pod 模版的镜像版本, 可以实现Pod的滚动升级功能。
Deployment
为了更好解决Pod编排问题,内部使员工ReplicaSet
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: dev
template:
metadata:
labels:
app: myapp
release: dev
spec:
containers:
- name: myapp-containers
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
创建:
kubectl create -f deployment.yaml
查看Deployment的信息
➜ k8s kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
myapp-deploy 1 1 1 1 1m
- DESIRED: Pod副本数量的期望值, 既Deployment里定义的Replica
- CURRENT: 当前Replica的值,这个值不断增加,直到达到DESIRED为止, 完成整个部署过程
- UP-TO-DATE: 最新版本的Pod的副本数量,用于指示在滚动升级的过程中,有多少Pod副本已成功升级
- AVAILABLE: 当前集群可用Pod副本数量, 既集群中当前存活的Pod数量。
查看对应的ReplicaSet, 命名和Deployment有关
➜ k8s kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp-deploy-f4bcc4799 1 1 1 10m
查看Pod, 以ReplicaSet的名称为前缀, 清晰表明那些rs创建了哪些Pod,对于滚动升级,可以容易排除错误。
➜ k8s kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-deploy-f4bcc4799-d9pqn 1/1 Running 0 14m
Horizontal Pod Autoscaler
Pod 横向自动扩容, 简称HPA
HPA有两种方式作为Pod负载的度量指标
- CPUUtilizationPercentage
- 应用程序自定义的度量指标,比如服务在每秒内的相应的请求数(TPS和QPS)
CPUUtilizationPercentage 是一个算数平均值,既目标Pod所有副本自身的CPU利用率的平均值。
一个Pod自身CPU利用率是该Pod当前CPU的使用量除以它的Pod Request的值。
如:一个Pod的PodRequest为0.4,当前CPU使用率是0.2, 那么它CPU使用率为50% (0.2/0.4=0.5)
这样我们就可以算出一个RC控制的Pod副本的CPU利用率的算数平均值。
如果某个时刻CPUUtilizationPercentage的值超过80%,则意味着当前的Pod副本数量可能不足以支撑接下来更多的请求,需要动态扩容。 当峰值时段过去,CPU利用率又降下来,此时对应的Pod副本应该自动减少到一个合理水平。
CPUUtilizationPercentage计算使用到的Pod的CPU使用量通常在1min内的平均值,目前通过Heapster扩展组件来得到这个值。所以需要安装Heapster.
如果目标没定义Pod Request,则无法使用CPUUtilizationPercentage
StatefulSet
提供有状态的服务。
- StatefulSet 里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。如 StatefulSet 的名字是kafka, 第一个pod叫kafka-0, 依次类推
- StatefulSet 控制的Pod副本的启停顺序是受控的,操作第n个Pod时, 前n-1个Pod已经运行且准备好的状态。
- StatefulSet 里的Pod采用稳定的持久化存储卷,通过PV/PVC来实现, 删除Pod时默认不会删除与StatefulSet相关的存储卷。
StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合,每个定义中要声明它属于哪个Headless Service。
Headless Service 与普通Service的区别在于,它没有Cluster IP, 如解析Headless Service的DNS域名, 则返回的是该Service对应的全部Pod的Endpoint列表。
StatefulSet在Headless Service的基础上又为控制的每个Pod实例创建了一个DNS域名:
$(podname).$(headless service name)
如: 3 个节点的kafka, Headless Service名字kafka,则3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、kafka-2.kafka
这些DNS名称可以直接在集群的配置文件中固定下来。
Service(服务)
每个Service相当于微服务架构中的 "微服务"
Service与其后端Pod副本集群通过Label Selector 来实现"无缝对接"
RC 保证 Service 的服务能力和服务质量始终处于预期的标准值。
运行在Node上的kube-proxy进程负责负载,把请求转发到后盾Pod实例上, 内部实现负载与会话保持。
Cluster IP : Service不是共用一个负载均衡器的IP地址,而是没个Service分配了一个全局唯一的虚拟IP地址, 这个IP叫Cluster IP
这样每个服务都变成了具有唯一IP地址的"通信节点",服务变成了基础的TCP网络通信问题。
Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址和旧的Pod的不同
Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP
, 而且在Service的整个生命周期内, 它的Cluster IP
不会发生改变。
所以服务发现使用Service的Name
与Cluster IP
地址做一个DNS域名映射就解决问题。
创建一个Service: tomcat-service.yaml
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
selector:
tier: frontend
创建
kubectl create -f tomcat-service.yaml
这时候就会去对应一个Pod, 使用下面的命令查看对应情况
kubectl get endpoints
查看Cluster IP
kubectl get svc tomcat-service -o yaml
服务多端口问题,存在多个Endpoint,定义例子如:
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
name: service-port
- port: 8005
name: shutdown-port
selector:
tier: frontend
服务发现机制
Service 都有一个唯一的Cluster IP 及唯一的名字。名字由开发者自己定义,部署也不需要改,所以完全可以固定在配置中。
最早使用环境变量(env),在每个Pod的容器在启动时,自动注入。但是不够直观。
通过Add-On增值包的方式引入了DNS系统, 把服务名作为DNS域名, 这样程序就可以直接使用服务名来简历通信连接了。
外部访问Service的问题
三种IP
- Node IP: Node节点的IP地址
- Pod IP: Pod的IP的地址
- Cluster IP: Service的IP地址
Node IP
Node IP是 k8s集群中没个节点的物理网卡的IP地址, 这是一个真实存在的物理网络。
这表明k8s集群之外的节点访问k8s集群某个节点或TCP/IP服务,必须通过Node IP 通信。
Pod IP
Pod IP 是每个Pod的IP地址,它是根据Docker Engine 根据 docker0 网桥IP地址段进行分配的,通常是一个虚拟的二层网络。
所以k8s里一个Pod里的容器访问另一个Pod里的容器,就通过Pod IP 所在的虚拟二层网络进行通信,真实流量则是通过Node IP所在的物理网卡流出。
Cluster IP
Cluster IP 也是虚拟IP,
- Cluster IP 只作用于 Kubernetes Service 这个对象,并由k8s管理和分配IP地址 (来源于 Cluster IP 地址池)
- 无法被Ping, 没有一个"实体网络对象"来响应
- 只能结合Service Port 组成一个具体的通信端口,单独的Cluster IP 不具备TCP/IP通信的基础,并且他们属于集群内部封闭空间,如集群外想访问,需要额外的工作。
- k8s 集群内 Node IP网、Pod IP网与 Cluster IP网之间通信,采用k8s自己设计的一套特殊路由规则,与我们熟悉的IP路由有很大不同。
Cluster IP 属于 k8s 内部的地址,无法在集群外部直接使用这个地址。
采用NodePort 解决上述问题
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
type: NodePort
ports:
- port: 8080
nodePort: 31002 # 手动指定NodePort 端口号,不然会自动分配。
selector:
tier: frontend
NodePort还没完全解决外部访问Service的所有问题,如负载均衡。最好使用一个负载均衡器,由负载均衡器负责转发流量到后面某个Node的NodePort.
如: 使用硬件 Load balancer 负载, 活 HAProxy 或者 Nginx.
谷歌GCE公有云上,将 type=NodePort 改为 type=LoadBalancer, k8s会自动创建一个对应的Load balancer 使用。其他公有云实现此驱动,也可使用。
Volume (存储卷)
Volume 是 Pod 中能够被多个容器访问的共享目录。
k8s中的 Volume 定义在Pod上, 然后被一个Pod里的多个容器挂载到具体的文件目录下。
k8s中的 Volume 与Pod的生命周期相同, 与容器的生命周期不相关。
当容器终止或者重启时,Volume 中的数据不会丢失。
k8s 支持多种文件类型的 Volume.
使用: 在Pod上声明一个 Volume, 然后在容器里引用Volume并Mount到容器的某个目录上。
如: 给 Pod 增加一个 名字为 datavol 的 Volume, 挂载到 /mydata-data上
template:
metadata:
labels:
app: mysql
spec:
volumes:
- name: datavol
emptyDir: {}
containers:
- name: mysql
image: mysql
volumeMounts:
- mountPath: /mydata-data
name: datavol
emptyDir
一个emptyDir Volume 是在Pod 分配到Node时创建的。它的内容为空,并且无须指定宿主机上对应的文件目录。这是k8s自动分配的一个目录。 Pod 从Node上移除, emptyDir 中的数据也会被永久删除。
用途:
- 临时空间, 例如用于某些应用程序运行时所需的零时目录,且无须永久保存。
- 长时间任务的中间过程Check Point的零时保存目录
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)
hostPath
hostPath 为在Pod上挂载宿主机上的文件或目录,
- 容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储。
- 需要访问宿主机Docker 引擎内部数据结构的容器应用时, 可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部可以直接访问Docker的文件系统。
使用注意:
- 在不同的Node 上具有相同配置的Pod 可能会因为宿主机上的目录和文件不同而导致对Volume上的目录和文件访问结果不一致。
- 如果使用了资源配额管理,则k8s无法将hostPath在宿主机上使用资源纳入管理。
volumes:
- name: datavol
hostPath:
path: "/data"
gcePersistentDisk (用不到)
使用这种类型的Volume 表示使用谷歌公开云提供的永久磁盘(Persisteent Disk, PD)存放Volume的数据,它与emptyDir不同,会永久保存。 当Pod被删除时,PD只会卸载,但不会被删除。你需要先创建一个永久磁盘(PD), 才能使用
gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk
volumes:
- name: datavol
gcePersistentDisk:
pdName: my-data-disk
fsType: ext4
awsElasticBlockStore (用不到)
亚马逊公有云提供的EBS Volume存储数据,需要先创建一个ESB Volume才能使用。
一些限制:
- Node(运行kubelet的节点) 需要是 AWS EC2实例
- 这些AWS EC2 实例需要与 EBS volume存在相同的 region 和 availability-zone中,
- EBS 只支持当个EC2 实例 mount 一个 volume
aws ec2 create-volume --availability-zone eu-west-1a --size 10 --volume-type gp2
volumes:
- name: datavol
awsElasticBlockStore:
volumeID: aws:///
fsType: ext4
NFS (用不到)
使用NFS网络文件系统提供的共享目录存储数据时,需要在系统部署一个NFS Server.
volumes:
- name: nfs
nfs:
server: nfs-server.localhost
path: "/"
其他 (用不到)
- iscsi: 使用iSCSI存储设备上的目录挂载到Pod中
- flocker: 使用Flocker来管理存储卷
- glusterfs: 使用 GlusterFS 网络文件系统的目录
- rbd: 使用Ceph块设备共享存储(Rados Block Device)
- gitRepo: 挂载一个空目录,并从git库clone一个git repository
- secret: 一个secret volume 用于为Pod 提供加密信息,可以将定义在k8s中的secret 直接挂载为文件让Pod访问。secret volume 是通过tmfs(内存文件系统实现的,所以这种类型的volume总是不会持久化)
Persistent Volume
之前说的Volume是定义在Pod上,属于"计算资源"的一部分
"网络存储" 是相对独立于"计算资源"而存在的一种实体资源
Persistent Volume: 简称 PV 。
Persistent Volume Claim (简称PVC) 。
可以认为是集群中某个网络存储对应的一块存储,与 Volume类似
区别:
- PV 只能是网络存储, 不属于Node, 但可以在每个Node 上访问。
- PV 并不是定义在Pod上的,而是独立于Pod之外定义。
例子: NFS类型的PV的yaml, 声明需要5Gi的空间
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
nfs:
path: /somepath
server: 127.17.0.2
PV 的 accessModes 属性
- ReadWriteOnce: 读写权限、并且只能被单个Node挂载
- ReadOnlyMany: 只读权限、允许多个Node挂载
- ReadWriteMany: 读写权限、允许多个Node挂载
如果Pod 想申请PV, 需要先定义一个PVC.
apiVersion: v1
kind: PersistentVolumeClain
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
然后在Pod 的 Volume 定义上引用上述PVC :
volumes:
- name: datavol
persistentVolumeClain:
claimName: myclaim
Namespace (命名空间)
namespace 在很多情况下用于多租户资源隔离。通过将集群内部的资源对象分配到不同的namespace 形成逻辑上分组的不同项目、小组、用户组。 便于不同分组在共享使用整个集群的资源的同时还能被分别管理。
k8s 启动后,会创建一个名为default的Namespace 通过
kubectl get namespaces
如果不特别指明,则用户创建的Pod、RC、Service都将被系统创建到这个默认namespace。
创建一个名为 development 的 Namespace:
apiVersion: v1
kind: Namespace
metadata:
name: development
创建后可以指定这个资源对象属于哪个Namespace.
定义一个名为busybox的Pod, 放入上面创建的:
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: development
spec:
...
这时使用命令将看不到上面创建的pod, 默认使用的是default
kubectl get pods
需要添加参数--namespace
来查看
kubectl get pods --namespace=development
Annotation 注释
使用key/value 键值对
Annotation 用来记录的信息如下:
- build信息、release信息、docker镜像消息等,如时间戳、release id 号、 PR号、
- 日志库、监控库、分析库、等资源库的地址信息。
- 程序调试工具,如工具名称、版本号、
- 团队的联系信息,例如:电话号、负责人名称、网址等。