Kubernetes实战
Kubernetes实战
第1部分 Kubernetes基础篇
第1章 Kubernetes介绍
几个概念
云计算
狭义上将是指IT基础设施的交付和使用模式,即通过网络以按需、易扩展的方式获取所需资源。
广义上则是指服务的交付和使用模式,通过网络以按需、易扩展的方式获取所需服务。
提供资源的网络被形象地比喻成“云”,其计算能力通常由分布式的大规模集群和虚拟化技术提供的。
“云”好比发电厂,互联网好比输电线路,只不过发电厂对外提供的是IT服务
业界根据云计算提供服务资源的类型将其划分为三大类:
- IaaS(基础设施即服务)
- PaaS(平台即服务)
- SaaS(软件即服务)
云计算三层架构图

IaaS(基础设施即服务)
白话:卖给你硬件设备,相比与传统的设备更易扩展而已,如云硬盘、云服务器、云主机
通过虚拟化和分布式存储等技术,实现了对包括服务器、存储设备、网络设备等各种物理资源的抽象,从而形成了一个可扩展、可按需分配的虚拟资源池。目前最具代表性的IaaS产品有Amazon AWS,提供虚拟机EC2和云存储S3等服务。
PaaS(平台即服务)
白话:卖给你一些开发组件,如云数据库、或是Tomcat运行环境等(Kubernetes也算在定义范畴内)。PaaS服务一般分为框架类服务和中间件服务,
框架类服务:Tomcat、Websphere、Node.js、Rubyon Rails、Ruby on Rack
中间件服务:数据库(Mysql、mongoDB、Redis)、消息队列(RabbitMQ)、缓存(Memcache)。
为开发者提供了应用的开发环境和运行环境,将开发者从繁琐的IT环境管理中解放出来,自动化部署和运维,使开发者能够集中精力于应用业务开发,提升应用的开发效率。PaaS主要面向的是软件专业人员,Google的GAE是PaaS的鼻祖。
SaaS(软件即服务)
白话:直接卖软件给你用
面向使用软件的终端用户。一般来说,SaaS将软件功能以特定的接口形式发布,终端用户通过网络浏览器就可以使用软件功能(那不就是Web应用嘛)。SaaS是应用最广的云计算模式,如在线使用的邮箱系统和各种管理系统都可以认为是SaaS的范畴。

Kubernetes是什么
是Google开源的容器集群管理系统。构建在Docker技术之上,为容器化的应用提供资源调度、部署运行、服务发现、扩容缩容等一整套功能。
特性:
容器编排能力
容器组合、标签选择和服务发现等
轻量级
遵循微服务架构理论,整个系统划分出各个功能独立的组件,组件之间边界清晰,部署简单,可以轻易地运行在各种系统和环境中。同时,Kubernetes中的许多功能都实现了插件化,可以非常方便地进行扩展和替换。
开放开源
Kubernetes核心概念
Pod
Pod是若干相关容器的组合,Pod包含的容器运行在同一台宿主机上,这些容器使用相同的网络命名空间、IP地址和端口,相互之间能通过localhost来发现和通信。另外,这些容器还可共享一块存储卷空间。在Kubernetes中创建、调度和管理的最小单位是Pod,而不是容器,Pod通过提供更高层次的抽象,提供了更加灵活的部署和管理模式。
若干运行在同一台宿主机上的相关容器的组合
使用相同网络命名空间、IP地址和端口
共享一块存储卷空间
Replication Controller
用来控制管理Pod副本(Replica,或者称为实例),Relication Controller确保任何时候Kubernetes集群中有指定数量的Pod副本在运行。如果少于指定数量的Pod副本,它会启动新的Pod副本,反之会杀死多余的副本以保证数量不变。Replication Controller是弹性伸缩、滚动升级的实现核心。
- 控制副本数量
- 弹性伸缩、滚动升级的实现核心
Service
是真实应用服务的抽象,定义了Pod的逻辑集合和访问这个Pod集合的策略。它将代理Pod对外表现为一个单一访问的接口,外部不需要了解后端Pod如何运行,对扩展和维护有好处,提供了一套简化的服务代理和发现机制。
- 定义了Pod的逻辑集合和访问这个Pod集合的策略
- 将代理Pod对外表现为一个单一访问的接口
- 提供服务代理和发现机制
Label
用来区分Pod、Service、Replication Controller的Key/Value对,Kubernetes中任意API对象都可以通过Label标识。每个API对象可以有多个Label,但是每个Label的Key只能对应一个Value。Label是Service和Replication Controller运行的基础,它们都通过Label来关联Pod,是一种松耦合关系。
- Key/Value对,标识API对象
- 每个API对象可以有多个Label,但是每个Label的Key只能对应一个Value
Node
K8s属于主从分布式集群架构,Node运行并管理容器。Node作为K8s的操作单元,用来分配给Pod(或者说容器)进行绑定,Pod最终运行在Node上,Node可以认为是Pod的宿主机。
- K8s操作单元,分配给Pod进行绑定
- 可以认为是Pod的宿主机
第2章 K8s的架构和部署
K8s的架构和组件
K8s属于主从分布式架构,节点在角色上分为Master和Node
K8s使用Etcd作为存储组件
Etcd是一个高可用的键值存储系统,灵感来自ZooKeeper和Doozer,通过Raft一致性算法处理日志复制以保证强一致性。
K8s使用Etcd作为系统的配置存储中心,K8s中的重要数据都是持久化在Etcd中的,这使得K8s架构的各个组件属于无状态,可以更简单实施分布式集群部署。
K8s Master作为控制节点,调度管理整个系统,包含以下组件
K8s API Server:作为K8s系统的入口,其封装了核心对象的增删改查操作,以REST API接口方式提供给外部客户和内部组件调用。它维护的REST对象将持久化到
Etcd中。K8s Scheduler:负责集群的资源调度,为新建的Pod分配机器。这部分工作分出来一个组件,意味着可以很方便地替换成其他的调度器。
K8s Controller Manager:负责执行各种控制器,目前已经实现很多控制器来保证K8s的正常运行
控制器 说明
K8s Node是运行节点,用于运行管理业务的容器,包含以下组件
- Kubelet:负责管控容器,Kubelet会从K8s API Server接受Pod的创建请求,启动和停止容器,监控容器运行状态并汇报给K8s API Server。
- K8s Proxy:负责为Pod创建代理服务,K8s Proxy会从K8s API Server获取所有的Service,并根据Service信息创建代理服务,实现Service到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络。
- Docker:K8s Node是容器运行节点,需要运行Docker服务,K8s也支持其他容器引擎
部署K8s
环境准备
K8s是一个分布式架构,可以灵活进行安装部署,可以部署在单机,也可以分布式部署。但是需要运行在Linux(x86_64)系统上,至少1核CPU和1GB内存。
(使用4台虚拟机用于部署k8s运行环境,一个etcd、一个K8s Master和三个K8s Node
运行etcd
获取K8s发行包
运行K8s Master组件
运行K8s Node组件
查询K8s的健康状态
创建K8s覆盖网络
安装K8s扩展插件
安装Cluster DNS
Cluster DNS用于支持K8s的服务发现机制,主要包含如下几项:
- SkyDNS:提供DNS解析服务
- Etcd:用于SkyDNS的存储
- Kube2sky:监听K8s,当有新的Service创建时,生成相应记录到SkyDNS
安装Cluster Monitoring
安装Cluster Logging
安装Kube UI
第3章 K8s快速入门
K8s是容器集群管理系统,为容器化的应用提供资源调度、部署运行、容灾容错和服务发现等功能。
示例应用Guestbook
Guestbook是一个典型的Web应用

Guestbook包含两部分
Frontend
Web前端,无状态节点,可以方便伸缩,本例中将运行3个实例
Redis
存储部分,Redis采用主备模式,即运行1个Redis Master、2个Redis Slave,Redis Slave会从Redis Master同步数据
Guestbook提供的功能:在Frontend页面提交数据,Frontend则将数据保存到Redis Master,然后从Redis Slave读取数据显示到页面上。
运行Redis
在K8s上部署Redis,包括Master和Slave
创建Redis Master Pod
Pod是K8s的基本处理单元,Pod包含一个或多个相关的容器,应用以Pod的形式运行在K8s中(本质上是容器)。Replication Controller能够控制Pod按照指定的副本数目持续运行,一般情况下是通过Replication Controller来创建Pod来保证Pod的可靠性。
定义redis-master-controller.yaml
1 | apiVersion: v1 |
K8s中通过文件定义API对象,文件格式支持JSON和YAML
API对象基本属性:
API版本(
.apiVersion)API对象类型(
.kind)元数据(
.metadata)redis-master-controller.yaml定义了V1版本下一个名称为redis-master的Replication Controller,另外配置了规格(.spec),其中设置了Pod的副本数(.spec.replicas)和Pod模板(.spec.template)Pod模板中说明了Pod包含了一个容器,该容器使用镜像redis,即运行Redis Master,该Replication Controller将关联一个这样的Pod,而Replication Controller和Pod的关联是通过Label来实现的(.spec.selector和.spec.template.metadata.labels)
通过定义文件创建Replication Controller
1 | $ kubectl create -f redis-master-controller.yaml |
创建Redis Master Service
K8s中Pod是变化的,特别是受到Replication Controller控制的时候,当Pod发生变化的时候,Pod的IP也是变化的。
由此衍生出一个问题,就是集群中的
Pod如何互相发现并访问的?
K8s提供Service实现服务发现
Service是真实应用的抽象,将用来代理Pod,对外提供固定IP作为访问入口,通过访问Service来访问相应的Pod,访问者只需要知道Service的访问地址,而不需要感知Pod的变化
创建Redis Master Service来代理Redis Master Pod
Redis Master Service的定义文件redis-master-service.yaml:
1 | apiVersion: v1 |
创建Service
1 | kubectl create -f redis-master-service.yaml |
Service通过Label关联Pod,在Service的定义中,设置.spec.selector为name=redis-master将关联redis master pod(创建时我们指定了selector.name为redis-master
通过命令查询Service
1 | $ kubectl get service redis-master |
CLUSTER_IP:K8s分配给Serivce的虚拟IP
PORT(S):6379/TCP,是Service会转发的端口(通过Service定义文件中的.spec.ports[0].port指定),K8s会将访问该端口的TCP请求转发到Redis Master Pod中,目标端口为6379/TCP(通过Service定义文件中的.spec.ports[0].targetPort指定)
因为创建Redis Master Service来代理Redis Master Pod,所以Redis Slave Pod通过Service的虚拟IP就可以访问到Redis Master Pod,但是如果只是硬配置Service的虚拟IP到Redis Slave Pod中,还不是真正的服务发现,K8s提供了两种发现Service的方法
环境变量
当Pod运行的时候,K8s会将之前存在的Service的信息通过环境变量写到Pod中,以Redis Master Service为例,它的信息会被写到Pod中:
1
2
3
4
5
6
7REDIS_MASTER_SERVICE_HOST=10.254.233.212
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.254.233.212:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.254.233.212:6379
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.254.233.212缺点:Pod必须在Service之后启动,采用DNS方式就没有这种限制
DNS
当有新的Service创建时,就会自动生成一条DNS记录,以Redis Master Service为例,有一条DNS记录:
redis-master => 10.254.233.212使用这种方法,K8s需要安装Cluster DNS插件
创建Redis Slave Pod
通过Replication Controller可创建Redis Slave Pod,将创建两个Redis Slave Pod。定义文件redis-slave-controller.yaml:
1 | apiVersion: v1 |
定义文件中设置了Pod的副本数为2,Pod模板中包含一个容器,容器使用镜像gcr.io/google_samples/gb-redisslave:v1,该镜像实际是基于redis镜像,重写了启动脚本,将其作为Redis Master的备用节点启动,启动脚本如下:
1 | if [[ ${GET_HOSTS_FROM:-dns} == "env" ]]; then |
其实就是通过GET_HOSTS_FROM环境变量控制服务发现的
扩展:${GET_HOSTS_FROM:-dns}
shell中对变量赋默认值,这里的意思是如果
GET_HOSTS_FROM未定义或为空串,则赋予默认值dns语法格式:${变量名:-默认值}
另一种写法是只有未定义才赋予默认值
语法格式:${变量名-默认值}
创建Pod
1 | kubectl create -f redis-slave-controller.yaml |
创建Redis Slave Service
redis-slave-service.yaml:
1 | apiVersion: v1 |
创建Service
1 | kubectl create -f redis-slave-service.yaml |
运行Frontend
创建Frontend Pod
通过Frontend Replication Controller来创建Frontend Pod,将创建3个Frontend Pod
frontend-controller.yaml
1 | apiVersion: v1 |
定义文件中设置Pod副本数为3,Pod模板包含一个容器,容器使用镜像gcr.io/google_samples/gb-frontend:v3,这是一个PHP实现的Web应用,写数据到Redis Master,并从Redis Slave中读取数据。内部也是通过GET_HOSTS_FROM环境变量控制服务发现方式的。
创建Pod
1 | kubectl create -f frontend-controller.yaml |
创建Frontend Service
frontend-service.yaml
1 | apiVersion: v1 |
设置Guestbook外网访问
现在Guestbook已经运行在K8s上了,但是只有内部网络能访问,外部网络的用户是无法访问的,我们需要增加一层网络转发,即外网到内网的转发。实现方式有很多种,我们这里采用NodePort的方式实现。
即K8s会在每个节点上设置端口,称为NodePort,通过NodePort可以访问到Pod
修改frontend-service.yaml,设置.spec.type为NodePort:
1 | apiVersion: v1 |
重新创建Service:
1 | $ kubectl replace -f front-service.yaml --force |
可以看到frontend的TYPE已经是NodePort了,并且可以看到PORT(S)有端口映射(80:32443/TCP),外部可以通过32443访问
清理Guestbook
1 | kubectl delete rc redis-master redis-slave frontend |
第4章 Pod
Hello World
创建一个简单的Hello World Pod,运行一个输出Hello World的容器
定义文件hello-world-pod.yaml:
1 | apiVersion: v1 |
定义文件中描述了Pod的属性和行为
apiVersion:声明K8s的API版本,目前是v1kind:声明API对象的类型,这里的类型是Podmetadata:设置Pod的元数据name:指定Pod的名称,Pod的名称必须Namespace内唯一
spec:配置Pod的具体规格restartPolicy:设置Pod的重启策略containers:设置Pod中容器的规格,数组格式,每一项定义一个容器name:指定容器名称,在Pod的定义中唯一image:设置容器镜像command:设置容器的启动命令
这里的Pod定义效果和以下docker命令运行的容器效果一样
1 | $ docker run --name hello-world ubuntu:18.04 /bin/echo 'hello world' |
需要注意,因为容器输出完之后就会退出,这是一次性执行的,所以Pod的定义中把.spec.restartPolicy设置为OnFailure,即容器正常退出不会重新创建容器
创建Pod
1 | $ kubectl create -f hello-world-pod.yaml |
Pod的基本操作
创建Pod
K8s中大部分的API对象都是通过kubectl create命令创建的
如果Pod的定义有误,kubectl create会打印出错误信息
查询Pod
1 | kubectl get pod (pod name) |
不指定pod则查询全部pod
字段含义:
NAME:Pod名称READY:Pod的准备状况,右边的数字表示Pod包含的容器总数,左边的数字表示准备就绪的容器数目STATUS:Pod的状态RESTARTS:Pod的重启次数AGE:Pod的运行时间
其中Pod的准备状况指的是Pod是否准备就绪以接受请求,Pod的准备状况取决于容器,即所有容器都准备就绪了,Pod才准备就绪。这个时候K8s的代理服务才会添加Pod作为分发后端,而一旦Pod的准备状况变为false(至少一个容器的准备状况变为false),K8s会将Pod从代理服务的分发后端移除。
默认情况下,kubectl get只显示Pod的简要信息
1 | kubectl get pod (pod name) --output json # json格式显示,--output可以简写为-o |
也支持Go Template方式过滤指定的信息,比如查询Pod的运行状态:
1 | kubectl get pod (pod name) -o go-template --template={{.status.phase}} |
kubectl describe查询Pod的状态和生命事件:
1 | kubectl describe pod (pod name) |
字段含义:
Name:Pod名称Namespace:Pod的NamespaceImage(s):Pod使用的镜像Node:Pod所在的NodeStart Time:Pod的起始时间Labels:Pod的LabelStatus:Pod的状态Reason:Pod处于当前状态的原因Message:Pod处于当前状态的信息IP:Pod的PodIPReplication Controllers:Pod对应的Replication ControllerContainers:Pod中容器的信息Container ID:容器IDImage:容器的镜像Image ID:镜像IDState:容器的状态Ready:容器的准备状态Restart Count:容器的重启次数统计Environment Variables:容器的环境变量
Conditions:Pod的条件,包含Pod的准备状况Volumes:Pod的数据卷Events:与Pod相关的事件列表
删除Pod
1 | kubectl delete pod [pod name] |
更新Pod
如果想要更新Pod,可以在修改定义文件后执行:
1 | kubectl replace [file] |
但是很多属性没办法修改,比如容器镜像,这时候可以通过--force参数强制更新,等于重建Pod
Pod与容器
在Docker中,容器是最小处理单位,隔离是基于Linux Namespace实现的,Linux内核中提供了6种Linux Namespace隔离的系统调用
| Linux Namespace | 系统调用参数 | 隔离内容 |
|---|---|---|
| UTS | CLONE_NEWUTS | 主机名与域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
| PID | CLONE_NEWPID | 进程编号 |
| Network | CLONE_NEWNET | 网络设备、网格栈、端口等 |
| Mount | CLONE_NEWNS | 挂载点(文件系统) |
| User | CLONE_NEWUSER | 用户和用户组 |
在K8s中,Pod包含一个或多个相关容器,Pod可以认为是容器的一种延伸扩展,一个Pod也是一个隔离体,而Pod包含的一组容器又是共享的(当前共享的Linux Namespace包括:PID、Network、IPC和UTS)。除此之外,Pod中的容器可以访问共同的数据卷来实现文件系统的共享,所以K8s的数据卷是Pod级别的
Pod是容器的集合,容器是真正的执行体。Pod的设计不是为了运行同一个应用的多个实例,而是运行一个应用、多个紧密联系的程序。而每个程序运行在单独的容器中,以Pod的形式组合成一个应用。相比于单个容器中运行多个程序,这样设计的好处有:
- 透明性:将Pod内的容器向基础设施可见,底层系统就能向容器提供如进程管理和资源监控等服务
- 解绑软件的依赖:单个容器可以独立重建和重新部署,实现独立容器的实时更新
- 易用性:用户不需要运行自己的进程管理器,也不需要负责信号量和退出码的传递等
- 高效性:因为底层设备负责更多的管理,容器因而更轻量化
在Pod中可以详细配置如何运行一个容器
镜像
运行容器必须先指定镜像,镜像名称遵循Docker的命名规范。
如果镜像不存在,会从Docker镜像仓库下载。K8s中可以选择镜像的下载策略,支持的策略有:
- Always:每次都下载最新的镜像
- Never:只使用本地镜像,从不下载
- IfNotPresent:只有本地没有的时候才下载
通过imagePullPolicy设置镜像下载策略
1 | name: hello |
启动命令
启动命令用来说明容器是如何运行的,在Pod的定义中可以设置容器启动命令和参数
例:
1 | ... |
也可以配置为:
1 | ... |
在Pod定义中command和args都是可选项,将和Docker镜像的ENTRYPOINT和CMD相互作用,生成最终容器的启动命令
规则:
- 只要指定了command,就不会使用镜像中的命令
- 没指定command,指定了args,则将args作为参数使用,命令使用镜像中的命令
环境变量
Pod中可以设置容器运行时的环境变量:
1 | env: |
在一些场景下,Pod中的容器希望获取本身的信息,比如Pod的名称,Pod所在的Namespace等,在K8s中提供了Downward API获取这些信息,并且可以通过环境变量告诉容器
- Pod的名称:metadata.name
- Pod的Namespace:metadata.namespace
- Pod的PodIP:status.podIP
现在创建一个Pod并通过环境变量获取Downward API
定义文件downwardapi-env.yaml:
1 | apiVersion: v1 |
1 | # 创建Pod |
端口
在Docker中运行容器通过-p/--publish参数设置端口映射规则
在Pod的定义中也可以设置端口映射规则
1 | apiVersion: v1 |
在Pod的定义中,通过.spec.containers[].ports[]设置容器的端口,数组形式,参数含义:
- name:端口名称,Pod内唯一,当只配置一个端口的时候,这是可选的,当配置多个端口的时候,这是必选的
- containerPort:必选,设置在容器内的端口
- protocol:可选,设置端口协议,TCP或UDP,默认是TCP
- hostIP:可选,设置在宿主机上的IP,默认绑定到所有可用的IP接口上,即0.0.0.0
- hostPort:可选,设置在宿主机上的端口,如果设置则进行端口映射
使用宿主机端口需要考虑端口冲突问题,幸运的是,K8s在调度Pod的时候,会检查宿主机端口是否冲突。比如两个Pod都需要使用宿主机80端口,那么调度的时候会将两个Pod调度到不同的Node上。如果所有Node的端口都被占用了,那么Pod调度失败。
数据持久化和共享
容器是临时存在的,如果容器被销毁,容器中的数据将会丢失
为了能够持久化数据以及共享容器间的数据,Docker提出了数据卷(Volume)的概念
数据卷就是目录或者文件,它可以绕过联合文件系统,以正常的文件或者目录的形式存在与宿主机上
使用docker run运行容器的时候,我们经常使用参数--volume/-v创建数据卷,将宿主机上的目录或者文件挂载到容器中。即使容器被销毁,数据卷中的数据仍然保存在宿主机上。
K8s对Docker数据卷进行了扩展,支持对接第三方存储系统。且K8s中的数据卷是Pod级别的,Pod中的容器可以访问共同的数据卷,实现容器间的数据共享。
我们对HelloWorldPod进行改造:
在Pod中声明创建数据卷,Pod中的两个容器将共享数据卷,容器write写入数据,容器read读出数据
1 | apiVersion: v1 |
Pod的网络
Pod中所有容器网络都是共享的,一个Pod中的所有容器的网络是一致的,它们能够通过本地地址访问其他用户容器的端口
K8s网络模型中,每一个Pod都拥有一个扁平化共享网络命令空间的IP,称为PodIP。
1 | $ kubectl get pod my-app --template={{.status.podIP}} |
这是Docker为容器进行网络虚拟化隔离而分配的内部IP。也可以设置Pod为Host网络模式,即直接使用宿主机网络,不进行网络虚拟化隔离。Pod的PodIP就是其所在Node的IP。
通过.spec.hostNetwork设置Pod为Host网络模式
1 | apiVersion: v1 |
注意:
- 不存在网络隔离,所以容易发生端口冲突
- Pod可以直接访问宿主机上的所有网络设备和服务,从安全性上来说是不可控的
Pod的重启策略
重启策略通过.spec.restartPolicy设置,目前支持3种策略
- Always:当容器终止退出后,总是重启容器,默认
- OnFailure:当容器终止异常退出时,才重启容器
- Never:当容器终止退出时,从不重启
Pod中的容器重启次数统计,实际并不是非常精确,只能作为一个参考
Pod的状态和生命周期
容器状态
Pod的本质是一组容器
K8s对Pod中的容器进行了状态的记录
- Waiting:容器正在等待创建,比如下载镜像
- Reason:等待原因
- Running:容器已经创建并且正在运行
- startedAt:容器创建时间
- Terminated:容器终止退出
- exitCode:退出码
- signal:容器退出信号
- reason:容器退出原因
- message:容器退出信息
- startedAt:容器创建时间
- finishedAt:容器退出时间
- containerID:容器的ID
1 | # 查询Pod状态 |
Pod的生命周期阶段
Pod一旦被分配到Node后,就不会离开这个Node
Pod的生命周期阶段:
- Pending:Pod已经被创建,但是有容器未被创建
- Running:Pod已经被调度到Node,所有容器已经创建,并且至少一个容器在运行或者正在重启
- Succeeded:Pod中所有容器正常退出
- Failed:Pod中所有容器退出,至少一个容器是异常退出的
生命周期回调函数
K8s提供了回调函数,在容器的生命周期的特定阶段执行调用,比如容器在停止前希望执行某项操作,就可以注册相应的钩子函数。
- PostStart:在容器创建成功后调用
- PreStop:在容器被终止前调用
钩子函数的实现方式有以下两种
Exec
执行指定命令
配置参数:
command:需要执行的命令,字符串数组
示例
1
2
3
4exec:
command:
- cat
- /tmp/healthHTTP
发起一个HTTP调用请求
配置参数:
path:请求的URL路径,可选
port:请求的端口,必选
host:请求的IP,可选,默认是Pod的PodIP
scheme:请求的协议,可选,默认是HTTP
示例:
1
2
3
4httpGet:
host: 192.168.1.1
path: /notify
port: 8080
现定义一个Pod,包含一个Java的Web应用服务器,其中设置了PostStart和PreStop回调函数。在容器创建成功后,复制/sample.war到/app目录。而在容器被终止前,发送HTTP请求到http://monitor.com:8080/warning,往监控系统发送一个警告,Pod的定义如下:
1 | apiVersion: v1 |
自定义检查Pod
对于Pod是否健康,默认情况下只是检查容器是否正常运行。但有的时候容器正常运行并不代表健康,可能有的应用进程已经阻塞住无法正常处理请求,所以为了提供更加健壮的应用,往往需要定制化的健康检查。
K8s提供Probe机制,有两种类型的Probe
- Liveness Probe:用于容器的自定义健康检查,如果检查失败,K8s将会杀死Pod,然后根据重启策略重启Pod
- Readiness Probe:用于检查容器的自定义准备状况检查,如果检查失败,K8s会将Pod从服务代理的分发后端移除,即不会分发请求给该Pod
Probe支持以下三种检查方法
ExecAction
在容器中执行指定的命令进行检查,当命令执行成功(返回码为0),检查成功。
配置参数
command:检查命令,字符串数组
示例
1
2
3
4exec:
command:
- cat
- /tmp/healthTCPSocketAction
对Pod中的指定TCP端口进行检查,当TCP端口被占用,检查成功
配置参数
port:检查的TCP端口
示例
1
2tcpSocket:
port: 8080HTTPGetAction
发送一个HTTP请求,当返回码介于200~400之间时,检查成功
配置参数
path:请求的URI路径,可选
port:请求的端口,必选
host:请求的IP,可选,默认为Pod的PodIP
scheme:请求的协议,可选,默认为HTTP
示例
1
2
3httpGet:
path: /health
port: 8080
Pod的健康检查
定义一个Pod,使用Liveness Probe通过ExecAction方式检查容器的健康状态,Pod的定义文件liveness-exec-pod.yaml:
1 | apiVersion: v1 |
1 | $ kubectl describe pod liveness-exec-pod | grep Unhealthy |
可以看到Liveness Probe检查失败并重启了Pod
Pod的准备状况检查
定义一个Pod,使用Readiness Probe通过ExecAction方式检查容器的准备状况,Pod的定义文件readiness-exec-pod.yaml:
1 | apiVersion: v1 |
查看Pod,大概一分钟后可以看到Pod的ready数目变为0
1 | $ kubectl get pod |
调度Pod
Pod调度指的是Pod在创建之后分配到哪一个Node上,调度算法分为两个步骤
- 筛选出符合条件的Node
- 选择最优的Node
对于所有的Node,首先K8s通过过滤函数去除不符合条件的Node,K8s v1.1.1支持的过滤函数如下:
- NoDiskConflict:检查Pod请求的数据卷是否与Node上已存在Pod挂载的数据卷存在冲突,如果存在冲突,则过滤掉该Node
- PodFitsResources:检查Node的可用资源(CPU和内存)是否满足Pod的资源请求
- PodFitsPorts:检查Pod设置的HostPorts在Node上是否已经被其他Pod占用
- PodFitsHost:如果Pod设置了NodeName属性,则筛选出指定的Node
- PodSelectorMatches:如果Pod设置了NodeSelector属性,则筛选出符合的Node
- CheckNodeLabelPresence:检查Node是否存在
Kubernetes Scheduler配置的标签
筛选出符合条件的Node来运行Pod,如果存在多个符合条件的Node,那么需要选择出最优的Node。
K8s通过优先级函数来评估出最优的Node,对于每个Node,优先级函数给出一个分数:0~10(10表示最优,0表示最差),每个优先级函数设置有权重值,Node最终分数就是每个优先级函数给出的分数的加权和。
K8s v1.1.1提供的优先级函数有:
- LeastRequestedPriority:优先选择有最多可用资源的Node
- CalculateNodeLabelPriority:优先选择含有指定Label的Node
- BalancedResourceAllocation:优先选择资源使用均衡的Node
如何进行Node选择?
在定义Pod时通过设置
.spec.nodeSelector来选择Node1
2
3
4
5
6
7
8
9
10
11apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
...
nodeSelector:
env: testPod创建成功后大概率会被分配到有
env=test标签的Node上在定义Pod时通过
.spec.nodeName直接指定Node1
2
3
4
5
6
7
8
9
10apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
...
nodeName: kube-node-0还是建议使用
Node Selector,因为通过Label选择是一种弱绑定,而直接指定Node Name是一种强绑定,Node失效时会导致Pod无法调度