Kubernetes基本概念与术语
在 k8s 中,Node、Pod、Replication Controller、Service 等概念都可以看作一种资源对象,通过k8s 提供的 kubectl 工具或者 API 调用进行操作,并保存在 etcd 中。
1.1 Node(节点)
Node 是 k8s 集群中相对于 Master 而言的工作主机,Node 可以是一台物理主机,也可以是一台虚拟机。在每个 Node 上运行用于启动和管理 Pod 的服务——Kubelet,并能够被 Master 管理。在 Node 上运行的服务进程包括 Kubelet、kube-proxy 和 docker daemon。
Node 的信息如下:
- Node地址:主机的 IP地址,或者 Node ID;
- Node运行状态:包括 Pending、Running、Terminated 三种状态。
- Node Condition(条件):描述Running状态Node的运行条件,目前只有一种条件——Ready,Ready表示Node处于健康状态,可以接收从 Master 发来的创建 Pod 的指令。
- Node系统容量:描述 Node 可用的系统资源,包括 CPU、内存数量、最大可调度 Pod 数量等。
- 其他:Node的其他信息,包括实例的内核版本号、K8s版本号、docker版本号、操作系统名称等。
可以通过 kubectl describe node <node_name>
来查看 Node 的详细信息
1.1.1 Node的管理
Node通常是物理机、虚拟机或者云服务商提供的资源,并不是由 k8s 创建的。虽然说 k8s创建了一个Node,只是表示 k8s 在系统内部创建了一个 Node 对象,创建后即会对其进行一系列健康检查,包括是否可以联通、服务是否正确启动、是否可以创建Pod等。如果检查未能通过,则该Node将会在集群中被标记为不可以(Not Ready)。
1.1.2 使用 Node Controller 对 Node 进行管理
Node Controller 是 kubernetes Master 中的一个组件,用于管理 Node对象。它的两个主要功能包括:集群范围内的Node信息同步,以及单个 Node 的生命周期管理。
Node 信息同步可以通过 kube-controller-manage 的启动参数 --node-sync-period
设置同步的时间周期。
1.1.3 Node 的自注册
当 Kubelet 的 --register-node
参数被设置额为 true (默认)时,Kubelet 会向 apiserver 注册自己。这也是 Kubernetes 推荐的 Node 管理方式。
Kubelet 进行自注册的启动参数如下:
- –apiservers=:apiserver 的地址;
- –kubeconfig=:登录 apiserver 所需凭证/证书的目录;
- –cloud_provider=:云服务商地址,用于获取自身的 metadata;
- –register-node=:设置true表示自动注册到apiserver
1.1.4 手动管理 Node
k8s 集群管理员也可以手工创建和修改Node对象。当需要这样操作时,先要将 Kubelet 启动参数中 --register-node
参数的值设置为false,这样,在 Node 上的 Kubelet 就不会自注册到apiserver中。
1.2 Pod
Pod是k8s的最基本的操作单元,包含一个或多个紧密相关的容器。一个Pod可以被一个容器化的环境看作应用层的“逻辑宿主机”(Logical Host)。一个Pod中的多个容器应用通常是紧耦合的。Pod在Node上被创建、启动或者销毁。
k8s使用Pod在容器之上再封装一层的一个重要原因是,Docker容器之间的通信受到Docker网络机制的限制,在Docker的世界中,一个容器需要通过link方式才能访问另一个容器提供的服务(端口)。大量容器之间的link将是一件非常繁重的工作。通过 Pod 的概念将多个容器组合在一个虚拟的“主机”内,可以实现容器之间仅需要通过 localhost 就能互相通信了。
Pod、容器与Node的关系如图:
一个Pod中的应用容器共享同一组资源,如下所述。
- PID命名空间:Pod中的不同应用程序可以看到其他应用程序的进程ID
- 网络命名空间:Pod中的多个容器能够访问同一个IP和端口范围
- IPC命名空间:Pod中的多个容器能够使用 SystemV IPC或POSIX消息队列进行通信
- UTS命名空间:Pod中的多个容器共享一个主机名
- Volumes(共享存储卷):Pod中的各个容器可以访问在Pod级别定义的Volumens
1.2.1 对 Pod 的定义
对 Pod 的定义通过 Yaml 或 Json 格式的配置文件来完成。下面的配置文件中定义了一个名为 redis-master 的 Pod,其中 kind 为 Pod。在 spec 中主要包含了对 Containers(容器)的定义,可以定义多个容器。
apiVersion: v1
kind: Pod
metadata:
name: redis-master
labels:
name: redis-master
spec:
containers:
- name: master
image: docker.io/redis
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 6379
Pod 的生命周期是通过 Replication Controller 来管理的。 Pod 的生命周期包括:通过模板进行定义,然后分配到一个 Node 上运行,在 Pod 所含容器运行结束后 Pod 也结束。 在整个过程中,Pod 处于以下 4种状态之一,
- Pending:Pod 定义正确,提交到 Master,但其所包含的容器镜像还未完全创建。通常 Master 对 Pod 进行调度需要一些时间,之后 Node 对镜像进行下载也需要一些时间。
- Running:Pod 已被分配到某个 Node 上,且其包含的所有容器镜像都已创建完成,并成功运行起来。
- Succeeded:Pod 中所有容器都成功结束,并且不会被重启,这是 Pod 的一种最终状态。
- Failed:Pod 中所有容器都结束了,但至少一个容器是以失败状态结束的,这也是 Pod 的一种最终状态。
k8s 为 Pod 设计了一套独特的网络配置,包括:为每个 Pod 分配一个 IP 地址,使用 Pod 名作为容器间通信的主机名等。另外,不建议在 k8s 的一个 Pod 内运行相同应用的多个实例
1.3 Label(标签)
Label 以 key/value 键值对的形式附加到各种对象上,如 Pod、Service、RC、Node 等。 Label 定义了这些对象的可识别属性,用来对它们进行管理和选择。Label 可以在创建对象时附加到对象上,也可以在对象创建后通过 Api 进行管理。
在为对象定义好 Label 后,其他对象就可以使用 Label Selector(选择器)来定义其作用的对象了。
Label Selector 的定义由多个逗号分隔的条件组成:
"labels": {
"key1" : "value1",
"key2" : "value2"
}
当前有两种 Label Selector:基于等式的(Equality-based)和基于集合的(Set-based),在使用时可以将多个 Label 进行组合来选择。
基于等式:
- name=redis-slave:选择所有包含 Label 中 key = “name” 且 value = “redis-slave” 的对象
- env != production:选择所有包括 Label 中 key = “env” 且 value 不等于 “production” 的对象
基于集合:
- name in (redis-master, redis-slave):选择所有包含 Label 中 key = “name” 且 value = “redis-master” 或 “redis-slave” 的对象
- name not in (redis-master):选择所有包含 Label 中 key = “name” 且 value 不等于 “redis-master” 的对象
在使用 Label Selector时,可以将其看作 SQL 查询语句中的 where 查询条件的语法
一般来说,我们会给一个 Pod (或其他对象)定义多个 Labels,以便于配置、部署等管理工作。通过对多个 Label 的设置,我们就可以 “多维度” 地对 Pod 或其他对象进行精细的管理。
一些常用的 Label 示例如下:
- release: stable, cannary …
- environment: dev, qa, production
- tier: frontend, backend, middleware
- partition: customerA, customerB …
- track: daily, weekly
RC 通过 Label Selector 来选择要管理的 Pod。在 RC 的定义中,Pod 部分的 template.metadata.labels
定义了 Pod 的 Label,即 name=redis-slave
然后在 spec.selector
中指定了 name=redis-slave
,表示将对所有包含该 Label 的 Pod 进行管理。
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-slave
labels:
name: redis-slave
spec:
replicas: 2
selector:
name: redis-slave
template:
metadata:
labels:
name: redis-slave
spec:
containers:
- name: slave
image: docker.io/redis
ports:
- containerPort: 6379
同样,在 Service 的定义中,,也通过定义 spec.selector
为 name=redis-slave
来选择将哪些具有该 Label 的 Pod 加入其 Load Balance 的后端列表中去。这样,当客户端访问请求到达该 Service 时,系统就能够将请求转发到后端具有该 Label 的一个 Pod 上去。
apiVersion: v1
kind: Service
metadata:
name: redis-slave
labels:
name: redis-slave
spec:
ports:
- port: 6379
selector:
name: redis-slave
使用 Label 可以给对象创建多组标签,Service、RC 等组件则通过 Label Selector 来选择对象范围,Label 和 Label Selector 共同构成了 Kubernetes 系统中最核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群地的高可用性。
1.4 Replication Controller(RC)
RC 用于定义 Pod 副本的数量。 在 Master 内,Controller Manage 进程通过 RC 的定义来完成 Pod 的创建、监控、启停等操作。
通过对 RC 的使用,k8s 实现了应用集群的高可用性,并且大大减少了系统管理员在传统 IT 环境中需要完成的许多手工运维工作(如主机监控脚本、应用监控脚本、故障恢复脚本等)。
RC 在 配置文件中通过 spec.template 定义 Pod 的属性, 设置 spec.replicas=2
来定义 Pod 副本的数量。
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-slave
labels:
name: redis-slave
spec:
replicas: 2
selector:
name: redis-slave
template:
metadata:
labels:
name: redis-slave
spec:
containers:
- name: slave
image: docker.io/redis
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 6379
通常 k8s 集群中不止一个 Node,假设一个集群有3个 Node,根据 RC 的定义,系统将可能在其中的两个 Node 上创建 Pod。如下图:
假设 Node2 上的 Pod2 意外终止,根据 RC 定义的 replicas 数量为 2,k8s 将会自动创建并启动一个新的 Pod,以保证整个集群中始终有两个 redis-slave Pod 在运行。
如图:系统可能选择 Node3 或 Node1 来创建一个新的 Pod。
在运行时,我们可以通过修改 RC 的副本数量,来实现 Pod 的动态缩放(scaling)。
kubectl scale rc redis-slave --replicas=3
执行结果如图
注意:删除 RC 并不会影响通过该 RC 已创建好的 Pod。为删除所有 Pod,可以设置 replicas 值为0,然后更新该 RC。Kubectl 提供了
stop
和delete
命令来完成一次性删除 RC 和 RC 控制的全部 Pod。另外,通过修改 RC 可以实现应用的滚动升级(Rolling Update),具体操作暂不介绍。
1.5 Service(服务)
每个 Pod 都会被分配一个单独的 IP 地址,但这个 IP 地址会随着 Pod 的销毁而消失。如果有一组 Pod 组成一个集群来提供服务,那么如何来访问它们呢?
Kubernetes 的 Service 就是用来解决这个问题的核心概念。
一个 Service 可以看作一组提供相同服务的 Pod 的对外访问接口。Service 作用于哪些 Pod 是通过 Label Selector 来定义的。
在1.4节中,redis-slave Pod 运行了两个副本(replica),这两个 Pod 对于前端程序(frontend)来说没有区别,所以前端程序并不关心是哪个后端副本在提供服务,并且后端 redis-slave Pod 在发生变化时,前端也无须跟踪这些变化。Service 就是用来实现这种解耦的抽象概念
1.5.1 对 Service 的定义
apiVersion: v1
kind: Service
metadata:
name: redis-slave
labels:
name: redis-slave
spec:
ports:
- port: 6379
selector:
name: redis-slave
通过该定义,k8s 将会创建一个名为 ”redis-slave“ 的服务,并在 6379 端口上监听。 spec.selector
的定义表示该 Service 将包含所有具有 name=redis-slave
Label 的 Pod。
在 Pod 正常启动后,系统将会根据 Service 的定义创建出与 Pod 对应的 Endpoint(断点)对象,以建立起 Service 与后端 Pod 的对应关系。随着 Pod 的创建、销毁,Endpoint 对象也将被更新。Endpoint 对象主要由 Pod 的 IP 地址和容器需要监听的端口号组成,通过 kubectl get endpoints
命令可以查看,显示为 IP:Port 的格式。
1.5.2 Pod 的 IP 地址和 Service 的 Cluster IP 地址
Pod 的 IP 地址是 Docker Daemon 根据 docker0 网桥的 IP 地址段进行分配的,但 Service 的 Cluster IP 地址是 K8s 系统中的虚拟 IP 地址,由系统动态分配。Service 的 Cluster IP地址相对于 Pod 的 IP 地址来说相对稳定,Service 被创建时即被分配一个 IP 地址,在销毁该 Service 之前这个 IP 地址都不会再变化了。而 Pod 在 k8s 集群中生命周期较端,可能被 RC 销毁、再次创建,新创建的 Pod 将会被分配一个新的 IP 地址。
1.5.3 外部访问 Service
由于 Service 对象再 Cluster IP Range 池中分配到的 IP 只能在内部访问,所以其他的 Pod 都可以无障碍地访问它。但如果这个 Service 作为前端服务,准备为集群外的客户端提供服务,我们就需要给这个服务提供公共 IP 了。
k8s 支持两种对外提供服务的 Service 的 type 定义:NodePort 和 LoadBalancer
-
NodePort
在定义Service 时指定
spec.type=NodePort
,并指定spec.ports.nodePort
的值,系统就会在 k8s 集群中的每个 Node 上打开一个主机上的真实端口号。这样,能够访问 Node 的客户端都就能通过这个端口号访问到内部的 Service了apiVersion: v1 kind: Service metadata: name: frontend labels: name: frontend spec: type: NodePort ports: - port: 80 # 在每一个启动了该 Pod 的 Node 节点上,都会打开80端口 nodePort: 30001 selector: name: frontend
假设由3个frontend Pod 运行在3个不同的Node上,客户端访问其中任意一个 Node都可以访问到这个 Service。
-
LoadBalancer
如果云服务商支持外接负载均衡器,则可以通过
spec.type=LoadBalance
定义 Service,同时需要指定负载均衡器的 IP 地址。使用这种类型需要指定 Service 的 nodePort 和 clusterIP。apiVersion: v1 kind: Service metadata: { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "my-service" }, "spec": { "type": "LoadBalancer", "clusterIP": "10.0.171.239", "selector": { "app": "myApp" }, "ports": [ { "protocol": "TCP", "port": 80, "targetPort": 9376, "nodePort": 30061 } ], }, "status": { "loadBalancer": { "ingress": [ { "ip": "146.148.47.155" } ] } } }
status.loadBalancer.ingress.ip
设置的 146.148.47.155 为云服务商提供的负载均衡的 IP 地址。对该 Service 的访问请求将会通过 LoadBalancer 转发到后端 Pod 上去,负载分发的实现方式则依赖于云服务商提供的 LoadBalancer 的实现机制。
1.5.4 多端口的服务
在很多情况下,一个服务都需要对外暴露多个端口号,在这种情况下,可以通过端口进行命名,使各 EndPoint 不会因重名而产生歧义。
kind: Service
apiVersion: v1,
metadata:
name: my-service
spec:
selector:
app: myApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
1.6 Namespace(命名空间)
通过将系统内部的对象”分配“到不同的 Namespace 中,形成逻辑上分组的不同项目、小组或用户组便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
k8s 集群在启动后,会创建一个名为 ”default“ 的 Namespace,如果不特别指明 namespace,则用户创建的 Pod、RC、Service 都将被系统创建到 ”default“ 的 namespace 中。
apiVersion: v1
kind: Namespace
metadata:
name: deveplopment
在创建 Pod 时,可以指定 Pod 属于哪个 Namespace:
...
metadata:
namespace: development
...
新创建的 Pod 将会属于 “development” 命名空间,此时,使用 kubectl get pods
命令将无法显示。因为该命令仅显示属于 “default” 命名空间的对象,需要加入 --namespace
参数来查看某个命名空间中的对象:kubectl get pods --namespace=development
使用 Namespace 来组织 K8s 的各种对象,可以实现对用户的分组,即“多租户”管理。对不同的租户还可以进行单独的资源配额设置和管理,使得整个集群的资源配置非常灵活、方便。
1.7 Annotation(注解)
Annotation 与 Label 类似,也使用 key/value 键值对的形式进行定义。Label 具有严格的命名规则,它定义的是 k8s 对象的元数据(metadata),并且用于 Label Selector。Annotation 则是用户任意定义的“附加”信息,以便于外部工具进行查找。
用 Annotation 来记录的信息包括:
- build信息、release信息、Docker镜像信息等,录入时间戳、release id号、PR号、镜像hash值、docker registry地址等;
- 日志库、监控库、分析库等资源库的地址信息;
- 程序调试工具信息,例如工具名称、版本号等;
- 团队的联系信息,例如电话号码、负责人名称、网址等。