Kubernetes 中的 Master 指的是集群控制节点,每个 Kubernetes 集群里需要有一个 Master 节点来负责整个集群的管理和控制,基本上 Kubectl 所有的命令都是发给它,它来负责具体的执行过程。
除了 Master,Kubernetes 集群中的其他物理主机或虚拟机被称为 Node 节点,是 Kubernetes 集群中的工作负载节点。当某个 Node 宕机时,该节点上的工作负载会被 Master 自动转移到其他节点上去。
# kubectl get nodes
NAME STATUS AGE
127.0.0.1 Ready 2d
# kubectl describe node 127.0.0.1
Name: 127.0.0.1
Role:
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/hostname=127.0.0.1
Taints:
CreationTimestamp: Mon, 14 Jan 2019 18:58:22 +0800
Phase:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
OutOfDisk False Fri, 18 Jan 2019 01:28:36 +0800 Mon, 14 Jan 2019 18:58:22 +0800 KubeletHasSufficientDisk kubelet has sufficient disk space available
MemoryPressure False Fri, 18 Jan 2019 01:28:36 +0800 Mon, 14 Jan 2019 18:58:22 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Fri, 18 Jan 2019 01:28:36 +0800 Mon, 14 Jan 2019 18:58:22 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
Ready True Fri, 18 Jan 2019 01:28:36 +0800 Mon, 14 Jan 2019 18:58:22 +0800 KubeletReady kubelet is posting ready status
Addresses: 127.0.0.1,127.0.0.1,127.0.0.1
Capacity:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 2055500Ki
pods: 110
Allocatable:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 2055500Ki
pods: 110
System Info:
Machine ID: d015a3d4bf38443f9bb8a6dc7ed5d96e
System UUID: 564D8B7C-B1F1-C296-92E0-41D925E1E4E5
Boot ID: 83f74ad3-7741-4ef5-a55a-b1b905532238
Kernel Version: 3.10.0-862.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://1.13.1
Kubelet Version: v1.5.2
Kube-Proxy Version: v1.5.2
ExternalID: 127.0.0.1
Non-terminated Pods: (4 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
default mysql-jdxz9 0 (0%) 0 (0%) 0 (0%) 0 (0%)
default myweb-814g3 0 (0%) 0 (0%) 0 (0%) 0 (0%)
default myweb-p74pl 0 (0%) 0 (0%) 0 (0%) 0 (0%)
default myweb-pfs34 0 (0%) 0 (0%) 0 (0%) 0 (0%)
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.
CPU Requests CPU Limits Memory Requests Memory Limits
------------ ---------- --------------- -------------
0 (0%) 0 (0%) 0 (0%) 0 (0%)
No events.
Pod 是 Kubernetes 中最基本也最重要的概念,每个 Pod 都有一个特殊的被称为“根容器”的 Pause 容器。Pause 容器对应的镜像术语 Kubernetes 平台的一部分,除了 Pause 容器,每个 Pod 还包含一个或多个紧密相关的用户业务容器。
Kubernetes 给每一个 Pod 都分配了唯一的 IP 地址,称之为 Pod IP,一个 Pod 里的多个容器共享 Pod IP。Kubernetes 要求底层网络支持集群内任意两个 Pod 之间的 TCP/IP 直接通信,这通常采用虚拟二层网络技术来实现,例如 Flannel、Openvswitch 等。所以在 Kubernetes 中,一个 Pod 里的容器与、另外主机上的 Pod 容器能直接通信。
Pod 其实有两种类型:
Kubernetes 里所有的资源对象都可以采用 yaml 或者 JSON 格式的文件来定义或描述。
apiVersion: v1
kind: Pod
metadata:
name: myweb
lables:
name: myweb
spec:
containers:
- name: myweb
image: kubeguide/tomcat-app:v1
ports:
- containerPort: 8080
env:
- name: MYSQL_SERVICE_HOST
value: 'mysql'
- name: MYSQL_SERVICE_PORT
value: '3306'
Pod 的 IP 加上定义的容器端口 (containerPort),就组成了一个新的概念 — Endpoint,它代表着此 Pod 里的一个服务进程的对外通信地址。一个 Pod 也可以存在多个 Endpoint,比如将 Tomcat 定义为一个 Pod 的时候,可以对外暴露管理端口和服务端口两个 Endpoint。
每个 Pod 都可以对能使用的服务器上的计算资源设置限额,当前可以设置限额的计算资源有 CPU 和 Memory 两种,其中 CPU 的资源单位为 CPU (Core) 的数量,是一个绝对值而非相对值。
在 Kubernetes 中,通常以千分之一的 CPU 配额为最小单位,用 m 来表示。通常一个容器的 CPU 配额被定义为 100~300m,即占用 0.1~0.3 个CPU。由于 CPU 配额是一个绝对值,所以无论在一个 Core 的机器上,还是在拥有48个 Core 的机器上,100m 这个配额代表的 CPU 的使用量都是一样的。与 CPU 配额类似,Memory 配额也是一个绝对值,它的单位是内存字节数。
spec:
containers:
- name: db
image: mysql
resources:
requests:
memory: "64Mi" # 表示容器最少申请64MiB内存
cpu: "250m" # 表示容器最少申请0.25个CPU
limits:
memory: "128Mi" # 表示容器最多申请128MiB内存
cpu: "500m" # 表示容器最多申请0.5个CPU
Event 是一个事件的记录,记录了事件的最早产生时间、最后重现时间、重复次数、发起者、类型以及导致此事件的原因等信息。Event 通常会关联到某个具体的资源对象上,是排查故障的重要参考信息,之前我们看到 Node 的描述信息中就含有 Event,而 Pod 同样有 Event 记录,当我们发现某个 Pod 迟迟无法创建时,可以用 kubectl describe pod < pod_name >
来查看它的描述信息,用来定义问题原因。
Label 是 Kubernetes 系统中另外一个核心概念。Lable 是一个 key=value 的键值对,Label 可以附加到各种资源对象上,例如 Node、Pod、Service、RC等,一个资源对象可以定义任意数量的 Label,一个 Label 也可以添加到任意数量的资源对象上。
Label Selector (标签选择器) 查询和筛选某些具有 Label 的资源对象时,可以实现类似 SQL 中的 where 子句的对象查询机制,例如具有 name=redis-slave 这个 Label 的 Pod,可以被类比为 select * from pod where pod’s name = ‘redis-slave’ 这样的语句。
可以通过 Label Selector 表达式的组合实现复杂的条件选择,多个表达式之间用 ‘,’ 分隔,类似 ‘AND’ 的关系,即同时满足多个条件。如: name = redis-slave,env != production。
RC 是 Kubernetes 系统中的核心概念之一,它声明某种 Pod 的副本数量在任意时刻都要符合某个预期值,所以 RC 的定义包括以下几个部分:
apiVersion: v1
kind: ReplicationController
metadata:
name: frontend
spec:
replicas: 1
selector:
tier: frontend
template:
metadata:
label:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
当我们定义了一个 RC 并提交到 Kubernetes 集群中以后,Master 节点上的 Controller Manager 组件就会得到通知,定期巡检系统中当前存活的目标 Pod,并确保 Pod 实例的数量刚好等于 RC 的预期值。此外,在运行时我们可以通过修改 RC 的副本数量,来实现 Pod 的动态缩放 (Scaling) 功能,这可以通过执行 kubectl scale 命令来完成:
$ kubectl scale rc redis-slave --replicas=3
scaled
另外 kubectl 提供了 stop 和 delete 命令来一次性删除 RC 和 RC 控制的全部 Pod。
Replica Set 是 RC 的升级版,它与 RC 存在的唯一区别是:RS 支持基于集合的 Label Selector (Set-based Selector),而 RC 只支持基于等式的 Label Selector (Equality-based Selector),这使得 RS 的功能更强,下面是等价于之前 RC 例子的 Replica Set:
apiVersion: extensions/v1beta1
kind: ReplicatSet
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
template:
metadata:
label:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
kubectl 命令行工具适用于 RC 的绝大部分命令同样也适用于 RS。此外,我们很少单独使用 RS,它主要被 Deployment 这个更高层的资源对象所用,从而形成一整套 Pod 创建、删除、更新的编排机制。当我们使用 Deployment 时,无需关心它是如何创建和维护 RS 的,这一切都是自动发生的。
Deployment 内部使用了 Replica Set 来实现目的,无论从作用与目的、它的 YAM 定义,还是从它的具体命令行操作来看,我们都可以把它视作 RS 的一次升级,两者相似度超过90%。
Deployment的典型使用场景:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
运行以下命令创建并查看 Deployment:
$ kubectl create -f tomcat-deployment.yaml
deployment "frontend" created
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
frontend 1 1 1 1 12s
查看 RS 信息:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
frontend-141477217 1 1 1 6m
查看 Pod 信息:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend-141477217-1hf7n 1/1 Running 0 8m
通过查看 Deployment、RS、Pod 的信息,我们可以发现 Pod、RS 和 Deployment 的对应关系。很清晰的了解到一个 Deployment 创建了哪个 RS,一个 RS 创建了哪些 Pod,这样很容器排查错误。
查看 deployment 更详细的状态信息:
$ kubectl describe deploy frontend
Name: frontend
Namespace: default
CreationTimestamp: Mon, 21 Jan 2019 20:02:11 +0800
Labels: app=app-demo
tier=frontend
Selector: tier=frontend,tier in (frontend)
Replicas: 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet: frontend-141477217 (1/1 replicas created)
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
12m 12m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set frontend-141477217 to 1
通过手工执行 kubectl scale 命令,我们可以实现 Pod 扩容或缩容。但是这一过程在分布式系统中可能是频繁发生的、不可预料的,所以手动控制的方式是不现实的。Horizontal Pod Autoscaling (HPA) 与之前的 RC、Deployment 一样,也属于一种资源对象。它可以通过追踪 RC 控制的所有 Pod 的负载变化情况,来确定是否需要针对性的调整 Pod 的副本数,来实现智能扩容的特性。
HPA 可以有以下两种方式作为 Pod 负载的度量指标:
CPUUtilizationPercentage 是目标 Pod 所有副本自身的 CPU 利用率的平均值。如果某一刻 CPUUtilizationPercentage 的值超过 80%,即表明当前 Pod 的副本数量很可能不足以支撑接下来的更多请求,需要进行动态扩容,而当请求高峰时段过去后,Pod 的 CPU 利用率又会降下来,此时目标 Pod 的副本数量又会自动减少到一个合理的水平。
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
maxReplicas: 10
minReplicas: 1
scaleTargetRef:
kind: Deployment
name: php-apache
targetCPUUtilizationPercentage: 90
根据上面的定义文件,HPA 会控制一个名叫 php-apache 的 Deployment 里的 Pod 副本,当这些 Pod 副本的 CPUUtilizationPercentage 的值超过90%时会触发自动动态扩容行为,扩容或缩容时必须满足的一个约束条件是 Pod 的副本数量要介于 1 与 10 之间。
我们也可以通过命令直接建立一个 HPA 资源对象:
$ kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10
作用与刚刚的 yaml 配置文件相同。
Service 是 Kubernetes 中最核心的资源对象之一,Kubernetes 中的每个 Service 其实就是我们经常提起的微服务架构中的一个“微服务”,之前我们所说的 Pod、RC 等资源对象其实都是为这项“微服务”— Kubernetes Service 提供服务的。
从图中我们看到,Kubernetes 的 Service 定义了一个服务的访问入口地址,前端的应用 (Pod) 通过这个入口地址访问到其背后的一组由 Pod 副本组成的集群实例,Service 与其后端 Pod 副本集群之间则是通过 Label Selector 来实现链接的。而 RC 的作用则是保证 Service 的服务能力和服务质量始终处于预期的标准。
由于 Pod 的 Endpoint 地址会随着 Pod 的销毁和重新创建而发生改变,而 Service 一旦创建,Kubernetes 就会自动为它分配一个可用的 Cluster IP,而且在 Service 的整个生命周期内,这个 Cluster IP 不会发生改变,这样每个服务就变成了具备唯一 IP 地址的可通信服务,服务调用也变成了最基础的 TCP 网络通信问题。
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
selector:
tier: frontend
上面的资源定义文件定义了一个名为“tomcat-service”的 Service,它的服务端口为 8080,拥有“tier=frontend”这个 Label 的所有 Pod 副本都属于它。
$ kubectl get svc tomcat-service -o yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2019-01-21T16:13:34Z
name: tomcat-service
namespace: default
resourceVersion: "466446"
selfLink: /api/v1/namespaces/default/services/tomcat-service
uid: 80be3db2-1d97-11e9-badc-000c29e1e4e5
spec:
clusterIP: 10.254.17.136
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
tier: frontend
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
在 spec.ports 定义中,targetPort 属性用来确定提供该服务的容器的暴露 (expose) 的端口,即具体业务进程在容器内的 targetPort 上提供 TCP/IP 接入,而 port 属性则定义了 Service 的虚拟端口。如果没有指定 targetPort,则默认 targetPort 与 port 相同。
如果服务需要存在多个 Endpoint 的情况下,需要给每个 Endpoint 命名来区分,我们可以这样定义:
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
name: service-port
- port: 8005
name: shutdown-port
selector:
tier: frontend
Node IP,Node 节点的 IP 地址,即 Kubernetes 集群中每个节点的物理网卡的 IP 地址,这是一个真实存在的物理网络,所有属于这个网络中的服务器之间都能通过这个网络直接通信。这也表明了 Kubernetes 集群之外的节点访问 Kubernetes 集群之内的某个节点或者 TCP/IP 服务的时候,必须要通过 Node IP 进行通信。
Pod IP,Pod 的 IP 地址,它是通过 Docker Engine 根据 docker0 网桥的 IP 地址段进行分配的,通常是一个虚拟的二层网络。Kubernetes 要求位于不同 Node 上的 Pod 能够彼此直接通信,所以 Kubernetes 里的一个 Pod 中的容器访问另外一个 Pod 中的容器时,就是通过 Pod IP 所在的虚拟二层网络进行通信的,而真实的 TCP/IP 流量则是通过 Node IP 所在的物理网卡流出的。
Cluster IP,它是一个虚拟的 IP,Cluster IP 仅仅作用于 Kubernetes Service 这个对象,并由 Kubernetes 管理和分配 IP 地址 (来源于 Cluster IP 地址池)。且 Cluster IP 无法被 Ping,只能结合 Service Port 组成一个具体的通信端口,单独的 Cluter IP 不具备 TCP/IP 通信的基础,并且它们属于 Kubernetes 集群这个封闭的空间内,集群之外的节点需要访问这个通信端口,则需要做一些额外的工作。
NodePort 是 Kubernetes 集群外部访问集群内部的 Service 的一个最直接、最有效、最常用的解决办法。以下就是我们如何在 Service 的资源定义文件中开启 NodePort 的具体方法:
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
type: NodePort
ports:
- port: 8080
nodePort: 31002
selector:
tier: frontend
上面的资源文件中,nodePort: 31002 这个属性表明我们手动指定 tomcat-service 的 NodePort 为 31002,如果我们不指定端口,Kubernetes 会自动分配一个可用的端口。然后我们就可以通过 Node IP + NodePort 的方式来访问 Service 了。
NodePort 的实现方式是在 Kubernetes 集群的每个 Node 上开启一个与 Service 对应的 TCP 监听端口,外部系统只要用任意一个 Node 的 IP 地址 + NodePort 即可访问该服务,但是这样无法解决 Service 的所有问题,比如负载均衡问题。我们需要结合集群外部的 Load balancer 负载均衡器来实现负载均衡,如 HAProxy 或 Nginx。于是 Kubernetes 提供了 type = LoadBalancer 的解决方案,不过需要我们的集群运行在谷歌的 GCE 公有云上才可以使用该功能。
Volume 是 Pod 中能够被多个容器访问的共享目录。Kubernetes 中的 Volume 定义在 Pod 上,然后被该 Pod 中的多个容器挂载到具体的文件目录下。Kubernetes 中的 Volume 的生命周期与 Pod 的生命周期相对应,与容器的生命周期不相关,当容器销毁或重启时,Volume 中的数据也不会丢失。且 Kubernetes 支持多种类型的 Volume,例如 GlusterFS、Ceph 等先进的分布式文件系统。
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
volumes:
- name: datavol
emptyDir: {}
containers:
- name: tomcat-demo
image: tomcat
volumeMounts:
- mountPath: /mydata-data
name: datavol
imagePullPolicy: IfNotPresent
emptyDir
emptyDir 是在 Pod 分配到 Node 时创建的。其初始内容为空,无需指定宿主机上的对应目录文件。当 Pod 从 Node 上移除时,emptyDir 中的数据也会被永久删除。
hostPath
hostPath 在 Pod 上挂载宿主机上的文件或目录,需注意的是在不同 Node 上具有相同配置的 Pod 可能会因为宿主机上的目录和文件不同而导致对 Volume 上目录和文件的访问结果不一致;且如果使用了资源配额管理,则 Kubernetes 无法将 hostPath 在宿主机上使用的资源纳入管理。
volumes:
- name: "persistent-storage"
hostPath:
path: "/data"
volumes:
- name: nfs
nfs:
server: -Server>
上文的 Volume 是定义在 Pod 上的,而 Persistent Volume (PV) 和与之相关的 Persistent Volume Claim (PVC) 则可以将网络存储的“网盘”挂载到集群中。PV 与 Volume 的区别如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
accessMode:
- ReadWriteOnce
nfs:
path: /somepath
server: 172.17.0.2
其中 accessMode 属性是 PV 的权限控制,分为:ReadWriteOnce (读写权限,只能被单个 Node 挂载)、ReadOnlyMany (只读权限,允许被多个 Node 挂载) 和 ReadWriteMany (读写权限,允许被多个 Node 挂载)。
如果某个 Pod 想使用某个 PV,我们还需要一个 PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessMode:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
然后,在 Pod 的 Volume 定义中引用 PVC 即可:
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
PV 状态分为以下几种:
Namespace 是 Kubernetes 系统中另一个非常重要的概念,Namespace 在很多情况下用于实现多租户的资源隔离。Namespace 通过将集群内部的资源对象“分配”到不同的 Namespace 中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
Kubernetes 集群在启动后,会创建一个名为“default”的Namespace,如果不特别指明 Namespace,则用户创建的 Pod、RC、Service 都将会被系统创建到这个默认的名为 default 的 Namespace 中。
apiVersion: v1
kind: Namespace
metadata:
name: development
只有 Namespace, 我们在创建资源对象时才能制定这个资源对象属于哪个 Namespace:
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: development
spec:
containers:
- image: busybox
command:
- sleep:
- "3600"
name: busybox
此时使用 kubectl get 命令将无法查看信息,因为 kubectl get 命令在没有参数的情况下默认显示 “default” 命名空间的资源对象。想要查看某个命名空间的资源对象时,可以加入 --namespace 参数来查看。
$ kubectl get pods --namespace=development
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 2m
Annotation 是用户任意定义的 “附加” 信息,以便于外部工具进行查找。很多时候,Kubernetes 的模块自身会通过 Annotation 的方式标记资源对象的一些特殊信息。
通常使用 Annotation 来记录如下信息: