kubernetes 权威指南学习笔记(2) -- 基本概念和术语

基本概念和术语

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 原因:

  1. 以它的状态代表整个容器组的状态。
  2. Pod里多个容器共享Pause容器的IP, 共享Pause容器挂接的Volume, 简化容器通讯,文件共享的问题。

Pod IP: k8s 为每个Pod 分配了唯一IP地址,Pod里多个容器共享。

k8s要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,通常采用虚拟二层网络技术来实现,如 Flannel、Open vSwitch.

一个Pod里的容器与另外主机上的Pod容器能够直接通信。

分类:

  1. 普通Pod: 一旦被创建,就会放入到etcd中存储, 随后被master调度到某个具体的Node上并进行绑定(Binding),随后被Node的kubelet进程实例化成Docker容器启动。
  2. 静态Pod(Static Pod): 存放在某个具体的Node上的一个具体文件内,并且只在此Node上启动运行。

默认情况Pod里某个容器停止,k8s会自动检测到并重启这个Pod(重启Pod内所有容器),如果Pod所在Node宕机,将会将Node上所有Pod重新调度到其他节点上。

Endpoint: Pod的IP加上容器的端口(containerPort)

Pod Volume: 定义在Pod上, 被各个容器挂载到自己的文件系统中。

查看pod描述信息,定位问题。

kubectl describe pod <pod name>

Event:是一个事件的记录, 记录了时间的最早产生时间、最后重现时间、重复次数、发起者、类型,以及导致此时间的原因等众多信息。Event 通常会关联到某个具体的资源对象上,是排查故障的重要参考信息。

Pod 对服务器上计算资源设置限额

  1. CPU : CPU的资源单位为CPU(Core) 的数量,是一个绝对值。
  2. Memory: 单位是内存字节数,也是一个绝对值

k8s里通常以千分之一的CPU配额为最小单位。用m来表示。通常一个容器的配额被定义为100~300m,既0.1~0.3个cpu.因为是一个绝对值,所以无论是一个Core还是48个Core的机器 100m所代表的使用量是一样的。

k8s里一个计算资源进行配额限定需要设定两个参数

  1. Requests: 该资源的最小申请量,系统必须满足要求。
  2. 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的NameCluster 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://<availability-zone>/<volume-id>
    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号、
  • 日志库、监控库、分析库、等资源库的地址信息。
  • 程序调试工具,如工具名称、版本号、
  • 团队的联系信息,例如:电话号、负责人名称、网址等。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券