Kubernetes
Kubernetes
K8s集群架构
Master主控节点
- API Server
是集群的统一入口,都以RestFul风格交给Etcd存储。提供认证、授权、访问控制、API注册和发现等机制
- Scheduler
节点调度,选择node节点应用部署
- controller-manager
处理集群中常规后台任务,一个资源对应一个控制器
- Etcd
储存系统,用于保存集群相关的数据
Node工作节点
- kubelet
Master节点指派到Node的代表,用于管理本机容器
- kube-proxy
提供网络代理,负载均衡等
K8s核心概念
Pod
- pod是K8s中最小的执行单元
- pod是一组容器的集合,其中的容器共享一个网络
- 生命周期十分短暂,服务器重启之后就找不到了。
Volume
- 申明在Pod容器中可访问的文件目录
- 可以挂载在Pod中多个容器的指定路径下
- 支持多种后端储存抽象,本地存储、分布式存储、云存储
Label
- 用于对象资源查询,筛选
Controller
- 确保预期的Pod副本数量
- 实现Pod的无状态部署(Deployment)和有状态部署(StatefulSet)
- 可以让多个Node运行同一个Pod
Deployment
- 定义一组Pod副本数目,版本等
- 通过控制器维持Pod数目(自动修复,回收失败的Pod)
- 通过控制器以指定的策略控制版本(回滚,滚动升级等)
Service
- 定义一组Pod的访问规则,为一个或多个Pod提供稳定的访问地址
K8s流程
- 通过Kubectl提交一个创建RC(Replication Controller)的请求,该请求通过APl server写入etcd
- 此时Controller Manager通过API Server的监听资源变化的接口监听到此RC事件
- Controller Manager分析之后,发现当前集群中还没有它所对应的Pod实例,于是根据RC里的Pod模板定义一个生成Pod对象,通过API Server写入etcd
- 此事件被Scheduler发现,它立即执行执行一个复杂的调度流程,为这个新的Pod选定一个落户的Node,然后通过API Server讲这一结果写入etcd中
- 目标Node上运行的Kubelet进程通过API Server监测到这个新生的Pod.并按照它的定义,启动该Pod并任劳任怨地负责它的下半生,直到Pod的生命结束
- 随后,我们通过Kubectl提交一个新的映射到该Pod的Service的创建请求
- ControllerManager通过Label标签查询到关联的Pod实例,然后生成Service的Endpoints信息,并通过APIServer写入到etcd中
- 接下来,所有Node上运行的Proxy进程通过API Server查询并监听Service对象与其对应的Endpoints信息,建立一个软件方式的负载均衡器来实现Service访问到后端Pod的流量转发功能
K8s集群搭建
视频搭建:k8s教程由浅入深-尚硅谷_哔哩哔哩_bilibili
博客借鉴:使用kubeadm方式搭建K8S集群 (moguit.cn)
注意,因为我穷买不起那么多服务器来搭建集群,于是通过VMWare软件创建了三个”服务器”,使用镜像为CentOS-7-x86_64-Minimal-2009.iso,分配硬件资源最好每台都能是2C4G往上,即使内存不够用可以降为2G,但处理器一定要2C往上,这是K8s的最低标准
有两种方式搭建集群,kubeadm和二进制包。Kubeadm 降低部署门槛,但屏蔽了很多细节,遇到问题很难排查。如果想更容易可控,推荐使用二进制包部署Kubernetes 集群,虽然手动部署麻烦点,期间可以学习很多工作原理,也利于后期维护。
使用kubeadm方式搭建K8S集群
kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。
这个工具能通过两条指令完成一个kubernetes集群的部署:
1 |
|
=以下ip请以虚拟机内分配的主机ip为准:==
输入“ip addr”并按回车键确定,发现无法获取IP(CentOS 7默认没有ifconfig命令),记录下网卡名称(本例中为ens33)。
1 |
|
1 |
|
安装要求
在开始之前,部署Kubernetes集群机器需要满足以下几个条件:
- 一台或多台机器,操作系统 CentOS7.x-86_x64
- 硬件配置:2GB或更多RAM,2个CPU或更多CPU,硬盘30GB或更多【注意master需要两核】
- 可以访问外网,需要拉取镜像,如果服务器不能上网,需要提前下载镜像并导入节点
- 禁止swap分区
准备环境
角色 | IP |
---|---|
master | 192.168.177.130 |
node1 | 192.168.177.131 |
node2 | 192.168.177.132 |
然后开始在每台机器上执行下面的命令
1 |
|
安装Docker/kubeadm/kubelet
所有节点安装Docker/kubeadm/kubelet ,Kubernetes默认CRI(容器运行时)为Docker,因此先安装Docker
安装Docker
首先配置一下Docker的阿里yum源
1 |
|
然后yum方式安装docker
1 |
|
配置docker的镜像源
1 |
|
然后重启docker
1 |
|
添加kubernetes软件源
然后我们还需要配置一下yum的k8s软件源
1 |
|
安装kubeadm,kubelet和kubectl
由于版本更新频繁,这里指定版本号部署:
1 |
|
部署Kubernetes Master【master节点】
在 192.168.177.130 执行,也就是master节点
1 |
|
由于默认拉取镜像地址k8s.gcr.io国内无法访问,这里指定阿里云镜像仓库地址,【执行上述命令会比较慢,因为后台其实已经在拉取镜像了】,我们 docker images 命令即可查看已经拉取的镜像
当我们出现下面的情况时,表示kubernetes的镜像已经安装成功
使用kubectl工具 【master节点操作】
1 |
|
执行完成后,我们使用下面命令,查看我们正在运行的节点
1 |
|
能够看到,目前有一个master节点已经运行了,但是还处于未准备状态
下面我们还需要在Node节点执行其它的命令,将node1和node2加入到我们的master节点上
加入Kubernetes Node【Slave节点】
下面我们需要到 node1 和 node2服务器,执行下面的代码向集群添加新节点
执行在Master节点Kubernetes初始化成功之后的命令:
1 |
|
注意,以上的命令是在master初始化完成后,每个人的都不一样!!!需要复制自己生成的
默认token有效期为24小时,当过期之后,该token就不可用了。这时就需要重新创建token,操作如下:
1 |
|
当我们把两个节点都加入进来后,我们就可以去Master节点 执行下面命令查看情况
1 |
|
token过期后,node还需要加入master就需要重新获得token和ca证书sha256编码hash值
1 |
|
在node上执行
1 |
|
部署CNI网络插件
上面的状态还是NotReady,下面我们需要网络插件,来进行联网访问
1 |
|
默认镜像地址无法访问,sed命令修改为docker hub镜像仓库。
1 |
|
运行后的结果
运行完成后,我们查看状态可以发现,已经变成了Ready状态了
如果上述操作完成后,还存在某个节点处于NotReady状态,可以在Master将该节点删除
1 |
|
测试kubernetes集群
我们都知道K8S是容器化技术,它可以联网去下载镜像,用容器的方式进行启动
在Kubernetes集群中的Master中创建一个pod,验证是否正常运行:
1 |
|
如果我们出现Running状态的时候,表示已经成功运行了
下面我们就需要将端口暴露出去,让其它外界能够访问
1 |
|
能够看到,我们已经成功暴露了 80端口 到 30529上
我们到我们的宿主机浏览器上,访问如下地址
1 |
|
发现我们的nginx已经成功启动了
到这里为止,我们就搭建了一个单master的k8s集群
错误汇总
错误一
在执行Kubernetes init方法的时候,出现这个问题
1 |
|
是因为VMware设置的核数为1,而K8S需要的最低核数应该是2,调整核数重启系统即可
错误二
我们在给node1节点使用 kubernetes join命令的时候,出现以下错误
1 |
|
错误原因是我们需要关闭swap
1 |
|
错误三
在给node1节点使用 kubernetes join命令的时候,出现以下错误
1 |
|
解决方法,首先需要到 master 节点,创建一个文件
1 |
|
然后删除刚刚创建的配置目录
1 |
|
然后 在master重新初始化
1 |
|
初始完成后,我们再到 node1节点,执行 kubeadm join命令,加入到master
1 |
|
添加完成后,我们使用下面命令,查看节点是否成功添加
1 |
|
错误四
我们再执行查看节点的时候, kubectl get nodes 会出现问题
1 |
|
这是因为我们之前创建的配置文件还存在,也就是这些配置
1 |
|
我们需要做的就是把配置文件删除,然后重新执行一下
1 |
|
然后再次创建一下即可
1 |
|
这个问题主要是因为我们在执行 kubeadm reset 的时候,没有把 $HOME/.kube 给移除掉,再次创建时就会出现问题了
错误五
安装的时候,出现以下错误
1 |
|
是因为yum上锁占用,解决方法
1 |
|
kubectl
1 |
|
- command:指定要对资源执行的操作,例如create、get、describe、delete
- type:指定资源类型,资源类型是大小写敏感的,开发者能够以单数 、复数 和 缩略的形式
- name:指定资源的名称,名称也是大小写敏感的,如果省略名称,则会显示所有的资源
- flags:指定可选的参数,例如,可用 -s 或者 -server参数指定Kubernetes API server的地址和端口
通过==kubectl –help==获取详细命令,==kubectl get –help==获取使用范例和详细用法
1 |
|
基础命令
常见的基础命令
命令 | 介绍 |
---|---|
create | 通过文件名或标准输入创建资源 |
expose | 将一个资源公开为一个新的Service |
run | 在集群中运行一个特定的镜像 |
set | 在对象上设置特定的功能 |
get | 显示一个或多个资源 |
explain | 文档参考资料 |
edit | 使用默认的编辑器编辑一个资源 |
delete | 通过文件名,标准输入,资源名称或标签来删除资源 |
部署命令
命令 | 介绍 |
---|---|
rollout | 管理资源的发布 |
rolling-update | 对给定的复制控制器滚动更新 |
scale | 扩容或缩容Pod数量,Deployment、ReplicaSet、RC或Job |
autoscale | 创建一个自动选择扩容或缩容并设置Pod数量 |
集群管理命令
命令 | 介绍 |
---|---|
certificate | 修改证书资源 |
cluster-info | 显示集群信息 |
top | 显示资源(CPU/M) |
cordon | 标记节点不可调度 |
uncordon | 标记节点可被调度 |
drain | 驱逐节点上的应用,准备下线维护 |
taint | 修改节点taint标记 |
故障和调试命令
命令 | 介绍 |
---|---|
describe | 显示特定资源或资源组的详细信息 |
logs | 在一个Pod中打印一个容器日志,如果Pod只有一个容器,容器名称是可选的 |
attach | 附加到一个运行的容器 |
exec | 执行命令到容器 |
port-forward | 转发一个或多个 |
proxy | 运行一个proxy到Kubernetes API Server |
cp | 拷贝文件或目录到容器中 |
auth | 检查授权 |
其它命令
命令 | 介绍 |
---|---|
apply | 通过文件名或标准输入对资源应用配置 |
patch | 使用补丁修改、更新资源的字段 |
replace | 通过文件名或标准输入替换一个资源 |
convert | 不同的API版本之间转换配置文件 |
label | 更新资源上的标签 |
annotate | 更新资源上的注释 |
completion | 用于实现kubectl工具自动补全 |
api-versions | 打印受支持的API版本 |
config | 修改kubeconfig文件(用于访问API,比如配置认证信息) |
help | 所有命令帮助 |
plugin | 运行一个命令行插件 |
version | 打印客户端和服务版本信息 |
Yaml
通过yaml文件可以创建一个pod,而yaml文件比较复杂,我们可以基于拉取的远程镜像的yaml或导出现有Pod的yaml进行自定义修改
1 |
|
==–dry-run==表明只是拉取而不是真正创建Pod,将其yaml文件导出至mynginx.yaml
1 |
|
这个则是直接导出现有的Pod的yaml文件到mynginx.yaml
Pod
Pod是K8S系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在K8S上运行容器化应用的资源对象,其它的资源对象都是用来支撑或者扩展Pod对象功能的,比如控制器对象是用来管控Pod对象的,Service或者Ingress资源对象是用来暴露Pod引用对象的,PersistentVolume资源对象是用来为Pod提供存储等等
K8S不会直接处理容器,而是Pod,Pod是由一个或多个container组成。其中有一个被称为“根容器”的Pause容器,其镜像来源于Kubernetes平台。
创建容器使用docker,一个docker对应一个容器,一个容器运行一个应用进程。Pod是多进程设计,运用多个应用程序,也就是一个Pod里面有多个容器,而一个容器里面运行一个应用程序
为什么不直接操作容器?
- Kubernetes并不是只支持Docker这一个容器运行。
Kubernetes通过CRI这个抽象层,支持除Docker之外的其他容器运行时,比如rkt甚至支持客户自定义容器运行时。因此,借助CRI这个抽象层,使得Kubernetes不依赖于底层某一种具体的容器运行时实现技术,而是直接操作pod,pod内部再管理多个业务上紧密相关的用户业务容器,这种架构便于Kubernetes做扩展
- 可以定义一组容器的状态
假设Kubernetes没有pod的概念,而是直接管理容器,那么一组容器作为一个单元,假设其中一个容器死亡了,此时这个单元的状态应该如何定义呢?应该理解成整体死亡,还是个别死亡?
这也是每个pod里都有一个Kubernetes系统自带的pause容器的原因,通过引入pause这个与业务无关并且作用类似于Linux操作系统守护进程的Kubernetes系统标准容器,以pause容器的状态来代表整个容器组的状态
- 共享网络,频繁通信
pod里所有的业务容器共享pause容器的IP地址,以及pause容器mount的Volume,通过这种设计,业务容器之间可以直接通信,文件也能够直接彼此共享。
Kubernetes里的每个pod都有唯一的IP地址。Pod的IP地址可以通过命令kubectl describe pod来查看。也就意味着Kubernetes的底层网络可以借助Flannel,openswitch等虚拟二层网络技术来实现集群内任意两个pod之间的TCP/IP通信。即使是不同主机间的Pod。
Pod配置清单
1 |
|
Pod类型
Pod是K8S集群中所有业务类型的基础,可以把Pod看作运行在K8S集群上的小机器人,不同类型的业务就需要不同类型的小机器人去执行。目前K8S的业务主要可以分为以下几种
- 长期伺服型:long-running
- 批处理型:batch
- 节点后台支撑型:node-daemon
- 有状态应用型:stateful application
上述的几种类型,分别对应的小机器人控制器为:Deployment、Job、DaemonSet 和 StatefulSet
Pod实现机制
共享网络
当Pod中的容器处于同一Namespace时即可实现网络共享。
以共享网络方式实现Pod,会先在Pod中创建一个根容器==Pause容器==,然后在创建业务容器时会把它放到==info容器==中,而info容器中会独立出ip、mac、port等信息,这样业务容器就会被划分到一个共享网络中。
共享存储
如果Node宕机,为了继续运行Pod的服务,需要在另一个Node里运行这个Pod。但是如果通过拉取新镜像来复原服务环境,就会丢失原Node的数据。因而,将Pod内的container数据实现持久化存储,即挂载数据卷。
Pod镜像拉取
- IfNotPresent:默认值,镜像在宿主机上不存在才拉取
- Always:每次创建Pod都会重新拉取一次镜像
- Never:Pod永远不会主动拉取这个镜像
Pod资源限制
新生的Pod要由Sheduler调度到合适的Node中,其中资源限制就是调度算法的考虑点。
- requests:容器正常运行最低要求的资源数,也就是对Node配置的最低要求。
- limits:限制容器扩张,是Node最多能分配给容器使用的资源量。
Pod重启机制
- Always:当容器终止退出后,总是重启容器,默认策略 【nginx等,需要不断提供服务】
- OnFailure:当容器异常退出(退出状态码非0)时,才重启容器。
- Never:当容器终止退出,从不重启容器 【批量任务,只执行一次】
Pod健康检查
两种检查后的处理策略:
- ==livenessProbe(存活检查)==:如果检查出错,将杀死容器,再根据Pod的重启策略操作
- ==readinessProbe(就绪检查)==:如果检查出错,K8s会将Pod从Service endpoint中剔除,并用一个相同的Pod代替。
三种检查方式:
- ==http Get==:发送HTTP请求,返回200 - 400 范围状态码为成功
- ==exec==:执行Shell命令返回状态码是0为成功
- ==tcpSocket==:发起TCP Socket建立成功
Pod调度
Pod节点选择器
Pod调度除了Sheduler分配,还能够人为调度。env_role
后面选择的是Node,dev
为Node的别名
1 |
|
Pod节点亲和性
除了Pod资源限制会影响调度,Pod还可以要求Node的其他属性符合。根据该要求的强硬程度分为硬亲和性和软亲和性。
- ==operator==:判断是否满足亲和性的操作符。
In
就要求Pod要调度到标签为dev、test的Node中。类似的操作符还有NotIn、Exists、Gt、Lt、DoesNotExists
污点和污点容忍
可以为Node设置污点值,但调度时就会通过污点值来判断能否分配Pod到该Node中。污点值有三:
- NoSchedule:一定不被调度
- PreferNoSchedule:尽量不被调度
- NoExecute:不会调度,并且还会驱逐Node已有Pod
查看Node污点:
1 |
|
添加污点:
1 |
|
删除污点:
1 |
|
我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍。
1 |
|
我们为Pod添加污点容忍,这样就可以分配到env_role=tzq:NoSchedule的Node中去了。
Controller
博客借鉴:yooome/LearningNotes
Pod控制器是管理pod的中间层,使用Pod控制器之后,只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它会创建出满足条件的Pod并确保每一个Pod资源处于用户期望的目标状态。如果Pod资源在运行中出现故障,它会基于指定策略重新编排Pod。
在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:
- ReplicaSet:保证副本数量一直维持在期望值,并支持pod数量扩缩容,镜像版本升级
- Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本
- Horizontal Pod Autoscaler:可以根据集群负载自动水平调整Pod的数量,实现削峰填谷
- DaemonSet:在集群中的指定Node上运行且仅运行一个副本,一般用于守护进程类的任务
- Job:它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务
- Cronjob:它创建的Pod负责周期性任务控制,不需要持续后台运行
- StatefulSet:管理有状态应用
Replication Controller
Replication Controller 保证了在所有时间内,都有特定数量的Pod副本正在运行,如果太多则会按照template新建几个。
Pod template
一个Replication Controller通过模版来创建pod,这个是Replication Controller对象内置的功能,但是我们计划要将这个功能从Replication Controller剥离开来
与其说Pod的模版是一个多有Pod副本的期望状态,Pod的模版更像是一个饼干的模具,一旦从模具中出来之后,饼干就和饼干模具没啥关系了,没有任何关联。而Replication Controller创建的pod可以在之后直接的修改。
Labels
由Replication Controller监控的Pod的数量是是由一个叫 selector(标签选择器)决定的,selector在Replication Controller和被控制的pod创建了一个松散耦合的关系,与pod相比,pod与他们的定义文件关系紧密。
因为Replication Controller是通过selector链接Pod的,因此由template创造出来的Pod必须拥有selector的标签。虽然Replication Controller也可以有自己的标签,这只是用来被Controller-manager标识的
所以,删除一个Pod可以通过改变其标签,令Replication Controller失去该Pod,这样改Pod就会被自动的替换掉。反过来,如果想要清空Replication Controller所管辖的Pods,可以将.spec.replicas
设置为零。这与直接删除Replication Controller不同,直接删除是不会影响已经被创造出来的Pods的
ReplicaSet(rs)
ReplicaSet的资源清单文件:
1 |
|
replicas:指定副本数量,其实就是当前rs创建出来的pod的数量,默认为1
selector:选择器,它的作用是建立pod控制器和pod之间的关联关系,采用的Label Selector机制
在pod模板上定义label,在控制器上定义选择器,就可以表明当前控制器能管理哪些pod了
template:模板,就是当前控制器创建pod所使用的模板
1 |
|
扩缩容
1 |
|
镜像升级
1 |
|
Deployment(deploy)
Deployment不直接管理pod,而是通过管理ReplicaSet来简介管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。
相比于ReplicaSet,Deployment多了以下配置:
1 |
|
1 |
|
扩缩容、镜像更新
扩缩容方式和ReplicSet一致,只是将操作符从rs
换成deploy
。
1 |
|
镜像更新则扩展了重建更新和滚动更新
,通过strategy.type
指定策略类型,支持两个属性RollingUpdate
和Recreate
1 |
|
==滚动更新==:更新时新版本的pod的创建不会影响旧版本pod的运行,新版本创建成功后才会删除旧版本
==重建更新==:旧版本被删除,一直等到新版本上线
版本回退
kubectl rollout: 版本升级相关功能,支持下面的选项:
- status 显示当前升级状态
- history 显示 升级历史记录
- pause 暂停版本升级过程
- resume 继续已经暂停的版本升级过程
- restart 重启版本升级过程
- undo 回滚到上一级版本(可以使用–to-revision回滚到指定版本)
1 |
|
金丝雀发布
比如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。
Job
Job的配置清单
1 |
|
Job负责批量处理短暂的一次性任务 (short lived one-off tasks),即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。
Kubernetes支持以下几种Job,通过根据.spec.completions
和.spec.Parallelism
的设置可以划分:
Job类型 | 使用示例 | 行为 | completions | Parallelism |
---|---|---|---|---|
一次性Job | 数据库迁移 | 创建一个Pod直至其成功结束 | 1 | 1 |
固定结束次数的Job | 处理工作队列的Pod | 依次创建一个Pod运行直至completions个成功结束 | 2+ | 1 |
固定结束次数的并行Job | 多个Pod同时处理工作队列 | 依次创建多个Pod运行直至completions个成功结束 | 2+ | 2+ |
并行Job | 多个Pod同时处理工作队列 | 创建一个或多个Pod直至有一个成功结束 | 1 | 2+ |
CronJob(CJ)
CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点及重复运行的方式。也就是说,CronJob可以在特定的时间点(反复的)去运行job任务。
CRonJob的资源清单相比于Job多了以下部分,将Job的配置转移至了.spec.jobTemplate
下
1 |
|
schedule:*用于指定任务的执行时间。 **多个时间可以用逗号隔开; 范围可以用连字符给出;\可以作为通配符; /表示每…
1 |
|
Service
每个pod都由自己的ip,这些IP也随着时间的变化也不能持续依赖。这样就引发了一个问题:如果一些Pods(让我们叫它作后台,后端)提供了一些功能供其它的Pod使用(让我们叫作前台),在kubernete集群中是如何实现让这些前台能够持续的追踪到这些后台的?
Kubernete Service 是一个定义了一组Pod的策略的抽象,我们也有时候叫做宏观服务。这些被服务标记的Pod都是(一般)通过label Selector决定的。Service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址。通过访问Service的入口地址就能访问到后面的pod服务。
kube-proxy
Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将最新的Service信息转换成对应的访问规则。
kube-proxy目前支持三种工作模式:
==userspace==
userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。 该模式下,kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率比较低。
==iptables==
iptables模式下,kube-proxy为service后端的每个Pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod IP。 该模式下kube-proxy不承担四层负责均衡器的角色,只负责创建iptables规则。该模式的优点是较userspace模式效率更高,但不能提供灵活的LB策略,当后端Pod不可用时也无法进行重试。
==ipvs==
ipvs模式和iptables类似,kube-proxy监控Pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此以外,ipvs支持更多的LB算法。
此模式必须开启ipvs内核,否则会降级成iptables模式
1 |
|
Service类型
Service资源清单:
1 |
|
.spec.type
的可选值:
- ClusterIP:默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问
- NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
- LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持
- ExternalName: 把集群外部的服务引入集群内部,直接使用
ClusterIP
创建deployment.yaml,内容如下:
1 |
|
创建改deployment后,查看pod分配:
1 |
|
配置service-clusterip.yaml
1 |
|
创建改service,会发现改service统一管理了标签为app: nginx-pod的pods。
1 |
|
我们可以看到,一个Service管理了三个Pod的通信,外部可以通过访问10.244.1.39:80来访问Pod1的服务,也可以通过10.97.97.97:80来访问Pod1的服务,只不过要注意Service采用的是轮询方式,第一次访问到Pod1,再一次就会访问Pod2了。
EndPoint
Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址。一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合
正如上面提到的,外部统一访问Service时,Service默认会通过轮询的方式把流量转发给后面的Pod,但也可以基于客户端地址保持会话,即同一个客户端的请求交给固定的一个Pod处理。
这种Service负载分发策略由属性.spec.sessionAffinity
决定,spec.sessionAffinity = ClusterIP
时表示基于客户端地址保持会话。
HeadLiness
ClusterIP提供的两种负载均衡方式难以满足显示要求,而HeadLiness能够自定义负载均衡策略。这类Service不会分配Cluster Ip,如果要访问service,只能通过service的域名进行查询。
创建HeadLiness类型Service,只需要在ClusterIP类型上将spec.clusterIP
设置为None即可
NodePort
在之前的样例中,创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。相比于CLusterIP,NodePort会将Service的一个端口映射到Node的真实端口上,这样访问Node对应端口就能访问到Service了
K8s的三种IP
- Node IP:Node节点IP地址
- Pod IP:Pod的IP地址
- Cluster IP:Service的IP地址
NodeIP是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通讯,不管他们中间是否含有不属于Kubernetes集群中的节点。想Kubernetes之外的节点访问Kubernetes集群内的节点或者TCP/IP服务时,必须通过Node IP
Pod IP是每个Pod的IP地址,通常是一个虚拟的二层网络,用于不同Pod间彼此直接通讯。所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信,而真实的TCP/IP流量则是通过Node IP所在的物理网卡流出
而ClusterIP仅仅是个用于统一管理PodIP的虚拟网络,更无法在Kubernets外访问。如果外网想要访问Pods就需要使用NodePort类型的Service来代理PodIP
配置NodePort
创建service-nodeport.yaml
1 |
|
创建Service后查看该NodePort-service可以看到被映射到了30002端口,这时使用任意NodeIP:30002都能访问到Pod
其实我们也可以发现,我们当初直接创建Pod时为了暴露端口,也用了NodePort
1 |
|
LoadBalancer
LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。
1 |
|
从外部负载均衡器的流量将会被引到后端的Pod,然而具体这个如何实现则要看云提供商。一些云提供商允许指定loadBalancerIP。如果字段loadBalancerIP没有指定,该负载均衡器会被指定一个短暂性的IP。如果指定了loadBalancerIP,但是云提供商不支持这个特性,这个字段会被忽略。
ExternalName
ExternalName类型的Service用于引入集群外部的服务,它通过externalName
属性指定外部一个服务的地址,然后在集群内部访问此service就可以访问到外部的服务了。
1 |
|
Ingress
- NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显
- LB方式的缺点是每个service需要一个LB,浪费、麻烦,并且需要kubernetes之外设备的支持
基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制大致如下图表示:
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!