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
无法调度