pod设计解读
在Kubernetes中,能够被创建、调度和管理的最小单元是pod,而非单个容器。前面已经说过,一个pod是由若干个Docker容器构成的容器组(pod意为豆荚,里面容纳了多个豆子,很形象)。这里需要强调的是,pod里的容器共享network namespace,并通过volume机制共享一部分存储。
- pod是IP等网络资源的分配的基本单位,这个IP及其对应的network namespace是由pod里的容器共享的;
- pod内的所有容器也共享volume。当有一个volume被挂载在同属一个pod的多个Docker容器的文件系统上时,该volume可以被这些容器共享。
另外,从Linux namespace的角度看,同属一个pod的容器还共享以下namespace。
- IPC namespace,即同一个pod内的应用容器能够使用System V IPC或POSIX消息队列进行通信。
- UTS namespace,即同一个pod内的应用容器共享主机名。
同一个pod里的容器有如下两个特性:
- 通过Kubernetes volume机制,在容器之间共享存储;
- 可以通过localhost直接访问另一个容器。
pod的使用
使用Kubernetes的客户端工具kubectl来创建pod,该命令行工具支持对Kubernetes对象(pod,replication controller, service)的增、删、改、查操作以及其他对集群的管理操作。创建Kubernetes资源对象的一般方法如下所示。
代码语言:txt复制$ kubectl create -f obj.json
其中obj.json可以是定义pod, replication controller, service等Kubernetes对象的JSON格式的资源配置文件。
下面是一个pod类型的json文件。
以上配置信息描述了一个name为podtest的对象。而该配置信息的kind字段表明该对象是一个pod。 apiVersion字段表明客户端使用的服务端API版本是v1。
spec:containers字段描述了pod内的容器的属性,包括:容器名(name )、镜像(image )、端口映射(ports)等。
replication controller设计解读(新版本已用seplicaSet替代)
Kubernetes中第二个重要的概念就是replication controller,它决定了一个pod有多少同时运行的副本,并保证这些副本的期望状态与当前状态一致。所以,如果创建了一个pod,并且在希望该pod是持续运行的应用时即仅适用于重启策略(RestartPolicy)为Always的pod ,一般都推荐同时给pod创建一个replication controller,让这个controller一直守护pod,直到pod被删除。
replication controller在设计上依然体现出了“旁路控制”的思想,在Kubernetes中并没有像Cloud Foundry那样设置一个专门的健康检查组件,而是为每个pod“外挂”了一个控制器进程,从而避免了健康检查组件成为性能瓶颈;即使这个控制器进程失效,容器依然可以正常运行,pod和容器无需知道这个控制器,也不会把这个控制器作为依赖。
pod的状态转换
在Kubernetes中,pod的状态值(podStatus)的数量和定义是系统严格保留和规定的,如下表所示:
replication controller的描述文件
前面已经介绍过,replication controller是Kubernetes为解决“如何构造完全同质的pod副本”问题而引人的资源对象。与pod对象类似,Kubernetes使用一份JSON格式的资源配置文件来定义一个replication controlle树象。replication controller的资源配置文件主要邮个方面组成:一个用于创建pod的pod模板(pod template )、一个期望副本数和一个用于选择被控制的pod集合的label selector。replication controller会不断地监测控制的pod集合的数量并与期望的副本数量进行比较,根据实际情况进行创建和删除pod的操作。一份定义replication controller的资源配置文件示例如下所示,该replication controller例化了两个运行nginx的pod。
replication controller的典型场景
- 重调度。一旦当前的宿主机节点异常崩溃或pod运行终止Kubernetes就会进行相应pod重调度。
- 弹性伸缩。不论是通过手动还是自动弹性伸缩控制代理来修改副本数(replicas)字段,replication controller都能轻松实现pod数量的弹性伸缩。
- 滚动更新(灰度发布)。replication controller被设计成通过逐个替换pod的方式来进行副本增删操作,这使得容器的滚动更新会非常简单。
- 应用多版本release追踪。在生产环境中一个已经发布的应用程序同时在线多个release版本是一个很普遍的现象。通过replica selector机制,我们能很方便地实现对一个应用的多版本release进行管理。
service设计解读
service这个概念存在的意义颇为重要,首先由于重新调度等原因,pod在Kubernetes中的IP地址不是固定的,因此需要一个代理来确保需要使用pod的应用不需要知晓pod的真实IP地址。另一个原因是当使用replication controller创建了多个pod的副本时,需要一个代理来为这些pod做负载均衡。
service主要由一个IP地址和一个label selector组成。在创建之初,每个service便被分配了一个独一无二的IP地址,该IP地址与service的生命周期相同且不再更改(pod IP地址与此不同,会随着pod的生命周期产生及消亡)。
如何定义一个service
和pod一样,service也是一个Kubernetes REST对象,客户端可以通过向APIServe泼送一个http POST请求来创建一个新的service实例。假设有一个pod集,该pod集中所有pod均被贴上labels{"app": "MyApp"},且所有容器均对外暴露TCP 9376端口。那么以下配置信息指定新创建一个名为myapp的service对象。
该service会将外部流量转发到所有label匹配{"app" : "MyApp"}的pod的9376 TCP端口上。每个service会由系统分配一个虚拟IP地址作为service的人口IP地址(cluster IP ),然后监听上述文件中的port指定的端口(比如上述的80端口)。上述虚拟IP地址和port的组合称为service入口。而当名为my-service的service对象被创建后,系统就会随之创建一个同样名为my-service的Endpoints对象,该对象即保存了所有匹配label selector端pod的IP地址和端口。
使用service来代理遗留系统
需要注意的是,与replication controller不同,service对象的.spec.selector属性是可选项,即允许存在没有label selector的service。这类service有什么作用呢?它们是为了让用户能够使用service代理一些并非pod,或者得不到label的资源,比如:
- 访问Kubernetes集群外部的一个数据库;
- 访问其他namespace或者其他集群的service;
- 任何其他类型的外部遗留系统。
在上面提到的那些场景中,可以定义一个没有selector属性的service对象,如下所示。
注意,因为该service没有selector属性,所以系统不会自动创建Endpoints对象。此时,可以通过自定义一个Endpoints对象,显式地将上述service对象映射到一个或多个后端(例如被代理的遗留系统地址),如下所示。
这时访问该service,流量将会被分发给用户自定义的endpoints,在上面的例子中即1.2.3.4:9376。
service的自发现机制
Kubernetes中有一个很重要的服务自发现特性。一旦一个service被创建,该service的service IP和service port等信息都可以被注入到pod中供它们使用。Kubernetes主要支持两种service发现机制:环境变量和DNS,这与etcd集群启动时的自发现方法类似,现逐一分析如下。
- 环境变量方式
kubelet创建pod时会自动添加所有可用的service环境变量到该pod中,如有需要,这些环境变量就被注人pod内的容器里。这些环境变量是诸如{SVCNAME}SERVICE_HOS下和{SVCNAME} SERVICEPORT这样的变量,其中{SVCNAME}部分将service名字全部替换成大写且将破折号(-)替换成下划线()。
- DNS方式
Kubernetes集群现在支持增加一个可选的组件—DNS服务器。这个DNS服务器使用Kubernetes的watchAPI,不间断地监测新service的创建并为每个service新建一个DNS记录。如果DNS在整个集群范围内都可用,那么所有pod都能够自动解析service的域名。
service外部可路由性设计
为了实现service从外部可以路由,有以下3种常见的解决方案。
- NodePort
service通常分为3种类型,分别为ClusterIP, NodePort和LoadBalancer。其中,ClusterIP是最基本的类型,即在默认情况下只能在集群内部进行访问;另外两种则与实现从集群外部路由有着密不可分的联系。
如果将service的类型设为NodePort,那么系统会从service-node-port-range范围中为其分配一个端口,并且在每个工作节点上都打开该端口,使得访问该端口(.spec.ports.nodesPort)即可访问到这个service。当然,用户也可以选择自定义该端口,则需要自行解决潜在的端口冲突的问题。
特别地,此时在集群外部使用<NodeIP>:spec.ports*.nodePort或在集群内部spec.clusterIp:spec.ports*.port均可访问到该service。
- loadBalancer
类型为LoadBalancer的service较为特别,实际上它并不由Kubernetes集群维护,而需要云服务提供商的支持。如何将从外部loadbalancer接入的流量导到后端pod中的实现逻辑,也完全取决于具体的云服务提供商。
用户可以在manifest中自行定义spec.loadBalancerIP,如果运行的云服务商平台支持这一功能,则在创建loadbalancer时会依据这一字段分配IP,否则,该字段会被忽略。
- external IP
在这个场景中,用户需要维护一个外部IP地址池(externalIPs),并且在service的描述文件中添加externalIPs字段。注意,Kubernetes并不负责维护externalIPs的路由,而需要由集群admin或者IaaS平台等负责维护。externalIPs可以与任何service类型一起使用。
Deployment设计解读
Deployment多用于为pod和replica set提供更新,并且可以方便地跟踪观察其所属的replica set或者pod数量以及状态的变化。换言之,Deployment是为了应用的更新而设计的。
熟悉kubectl的读者可能会产生这样的疑惑:kubectl rolling-update不是也有类似的功能吗?为什么我们需要一个独立的resource来解决这一问题呢?原因在于,kubectl rolling-update是由命令行工具(类似于前端)本身实现更新逻辑,而Deployment则将这一负担移到了服务器端(类似于后端),由专门的controller来负责这部分工作。不仅在使用场景下得到了扩展,并且其健壮性也有了更好的保障。
Deployment使用样例
Deployment典型的应用场景包括:
- 定义Deployment来创建Pod和ReplicaSet
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续Deployment
比如一个简单的nginx应用可以定义为
代码语言:txt复制apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
DaemonSet设计解读
在生产环境中,我们可能会希望在每个工作节点上都运行某个相同的pod副本,如下所示。
- 在每个工作节点上运行一个存储daemon,如glusterd, ceph等。
- 在每个工作节点上运行一个日志收集daemon,如fluentd, logstash等。
- 在每个工作节点上运行一个监控daemon,如collectd, New Relic agant, Ganglia gmond等。
此时,我们当然可以在每个工作节点注册到集群时手动将pod绑定到节点上,或者采用其他daemon进程(如init, upstart或systemd等)直接进行管理,但是我们希望部署流程能够尽量地简单化。DaemonSet就提供这样的服务,每当一个新的工作节点加入到集群中,系统就会按照DaemonSet的配置在节点上运行相应的pod,负责这部分工作的是DaemonSet controller。
ConfigMap设计解读
很多生产环境中的应用程序配置较为复杂,可能需要多个config文件、命令行参数和环境量的组合。并且,这些配置信息应该从应用程序镜像中解耦出来,以保证镜像的可移植性以及配置信息不被泄露。社区引入了ConfigMap这个API资源来满足这一需求。
ConfigMap包含了一系列的键值对,用于存储被pod或者系统组件(如controller等)访问的信息。这与Secret的设计理念有异曲同工之妙,它们的主要区别在于ConfigMap通常不用于存储敏感信息,而只存储简单的文本信息。
创建ConfigMap
此时,可以通过如下命令创建包含该目录下所有文件内容的ConfigMap, from-file的参数是包含文件的目录。
代码语言:txt复制$ kubectl create configmap game_config --from-file=configmap
# game_config是要创建的ConfigMap的名字
# --from-file参数是指定数据模板文件,也可以是文件内的键值对
使用ConfigMap中的信息
在创建完ConfigMap后,如何使用存储在其中的信息呢?在这里介绍3种主要的方式。
- 通过环境变量调用
- 设置命令行参数
- volume plugin
Job设计解读
在Kubernetes诞生之初,其预设的服务场景基本上围绕long-running service,例如web service等。因此replication controller一开始获得了大多数用户的关注,它用来管理那些restart policy是Always的应用。
现在,Kubernetes已经有了专门支持batch job的资源—Job,我们可以简单地将其理解为run to completion的任务,它对应restart policy为OnFailure或者Never的应用。
Job的类型
根据Job中pod的数量和并发关系,我们主要将其分为3种类型。另外,在Job的定义中,有两个比较重要的参数会根据Job的不同类型有不同的配置要求,分别为.spec.completions和.spec.parallelism,也会在下文的介绍中一并展开。
Job的第一个适用场景非常容易想到,用户可以使用Job创建单个pod,一旦pod完成工作退出,则认为这个Job也就成功结束了。这样的Job被称为non-parellel job。对于这个类型的Job, .spec.completions和.spec.parallelism都可以不做设定,系统会默认将其设为1.
对于parallel job,则具体细分成两种类型,分别是有固定completion数值的parallel Job,以及有work queue的Parallel Job。
在前者中,Job成功的标志是有completion数量的pod运行成功并退出,用户需要指定.spec.completions字段。对于这种情况,用户可以指定.spec.parallelism,同样也可以不做设定,使用默认值1。
后者则是同时运行多个pod,其中任意一个pod成功停止,则说明该Job成功完成。所谓的work queue的含义在于,首先成功完成的pod对于Job的运行结果起着决定作用,而一旦有一个pod成功完成,系统不会再为这个Job试图创建新的pod。用户需要指定.spec.parallelism字段,表示在任一时刻同时运行的pod数目。如果该值被设为0,该Job不会被启动,直到该值被设为一个正值。注意,在Job的实际运行过程中,并发的pod数量可能会少于.spec.parallelism字段指定的数值。
Horizontal Pod Autoscaler设计解读
自动扩展主要分为两种,其一为水平扩展,即本节中即将详细介绍的内容,针对于实例数目的增减;其二为垂直扩展,即单个实例可以使用的资源的增减。Horizontal Pod Autoscaling ( HPA )属于前者。
Horizontal Pod Autoscaling如何工作
Horizontal Pod Autoscaling的操作对象是ReplicationController, ReplicaSet或Deployment对应的pod,根据观察到的CPU实际使用量与用户的期望值进行比对,做出是否需要增减实例数量的决策。
controller目前使用heapster来检测CPU使用量,检测周期可以通过horizontal-pod-autoscaler-sync-period参数进行调节,默认情况下是30秒。
Horizontal Pod Autoscaling的决策策略
在HPA controller测到CPU的实际使用量之后,会求出当前的CPU使用率(实际使用量与pod请求量的比率)。然后,HPA controller会对比该CPU使用率和.spec.cpuUtilization.targetPercentage,并且通过调整副本数量使得CPU使用率尽量向期望值靠近,副本数的允许范围是用户设定的minReplicas,maxR即licas之间的数值,期望副本数目下argetNumOfPods通过以下计算公式求得:
代码语言:txt复制TargetNumofPods=ceil(sum(CurrentPodsCPUUtilization)/Target)
其中,Target是用户在HPA的manifest中设定的.spec.cpuUtilization.targetPercentage,表示用户期望的CPU使用率,CurrentPodsCPUUtilization表示当前的CPU使用率,是HPA调节对象(可能是replication controller, deployment或者replicaSet)对应的所有的pod的平均CPU使用率。
另外,考虑到自动扩展的决策可能需要一段时间才会生效,甚至在短时间内会引入一些噪声,例如当pod所需要的CPU负荷过大,从而运行一个新的pod进行分流,在创建的过程中,系统的CPU使用量可能会有一个攀升的过程。所以,在每一次作出决策后的一段时间内,将不再进行扩展决策。对于scale up而言,这个时间段为3分钟, scale down为5分钟。