Tips:
自己初学的时候,有时候会搞混 Deployment 、SVC 匹配标签的字段,这里标记一下
Deployment 匹配 pod 标签的字段是
matchLabels
,yaml中spec.selector.matchLabels
SVC 匹配 pod 标签的字段是selector
,yaml中spec.selector
Service概念
:
Kubernetes以Pod作为应用部署的最小单位。kubernetes会根据Pod的声明对其进行调度,包括创建、销毁、迁移、水平伸缩等,因此Pod 的IP地址不是固定的,不方便直接采用
Pod 的 IP
对服务进行访问。为解决该问题,Kubernetes提供了Service资源,Service对提供同一个服务的多个Pod进行聚合。一个Service提供一个虚拟的Cluster IP,后端对应一个或者多个提供服务的Pod。在集群中访问该Service时,采用
Cluster IP
即可,Kube-proxy负责将发送到Cluster IP的请求转发到后端的Pod上。Kube-proxy是运行在每个节点上的go应用程序,支持三种工作模式:
userspace
,iptables
,ipvs
Kubernetes Service 定义了一个pod的逻辑分组,一种可以访问Pod组的策略--通常被称为微服务。
Service
通过指定的一组Label Selector
而匹配到这组Pod
也就是说,在Kubernetes中,每个节点都安装了kube-proxy,kube-proxy通过kubernetes中固有的watch请求方法持续监听apiserver。一旦有service资源发生变动(增删改查)kube-proxy可以及时转化为能够调度到后端Pod节点上的规则,这个规则可以是iptables也可以是ipvs,取决于service实现方式
如图所示,Deployment定义了3个副本数目的一组应用程序Pod,这组Pod有着一组指定Label Selector。同时还存在一个Service通过特定的标签
app=webapp、role=frontend
所匹配到这组Frontend v1 Pod
,所以匹配到的Pod的信息就会被写入到svc当中。当客户端访问的时候,就通过SVC以RR的调度方式(svc到pod的调度方式有且仅有一个RR算法
)来访问到这组Pod中的其中一个Pod。而且如果后期SVC匹配的Pod意外死掉了,Deployment根据副本数目创建出来的新的Pod就会被自动更新到SVC的负载策略中(同时也就是说只要被SVC的label Selector匹配的到的Pod,不管是意外退出、滚动更新、水平扩容,都会被更新到svc的负载策略中,并不会对上一层的服务造成影响
)。
CoreDNS作用:
Tips:
FQDN
:(Fully Qualified Domain Name)完全合格域名/全称域名,是指主机名加上全路径,全路径中列出了序列中所有域成员。全域名可以从逻辑上准确地表示出主机在什么地方,也可以说全域名是主机名的一种完全表示形式。从全域名中包含的信息可以看出主机在域名树中的位置。完全合格域名(FQDN):点结尾的域名,
例如bbs.cnblogs.com.就是一个完全合格域名。在一般的网络应用中,我们可以省略完全合格域名最右侧的点,但DNS对这个点不能随便省略。因为这个点代表了DNS的根,有了这个点,完全合格域名就可以表达为一个绝对路径,例如bbs.cnblogs.com.就可以表示为DNS根下的com子域下cnblogs.com域中一个名为bbs的主机。如果DNS发现一个域名不是以点结尾的完全合格域名,就会把这个域名加上当前的区域名称作为后缀,让其满足完全合格域名的形式需求。例如DNS会把域名bbs处理为bbs.blogs.com.
K8s中资源的全局FQDN格式
:
svc_name.namespace_name.Domain.LTD.
Domain.LTD. = svc.cluster.local. #这是k8s集群默认的域名。
svc_name.namespace_name.svc.cluster.local
CoreDNS可以把Service Name(的FQDN)解析成Cluste IP,所以当管理员为Deployment/RS/多个Pod添加一个Cluster IP类型的Service之后,client端就可以通过该Cluster IP访问到Deployment下的Pod,也可以通过该Service Name访问到Deployment下的Pod。而访问Service Name的时候,CoreDNS是会将Service Name解析成该Service对应的Cluster IP,进而通过负载均衡转发到后端的单个/多个Endpoint端点(一个Endpoint端点对应的就是后端一个Pod的IP:Port)。
CoreDNS仅能将Service的Name解析为对应的Cluster IP,做不到将Deployment/Pod的Name解析为其对应的IP
这是因为请看下面Service工作原理
Service 工作原理:
k8s在创建Service时,会根据标签选择器selector(lable selector)来查找Pod,据此创建与Service同名的endpoint对象,当Pod 地址发生变化时,endpoint也会随之发生变化,service接收前端client请求的时候,就会通过endpoint,找到转发到那个Pod进行访问的地址。(至于转发到哪个节点的Pod,由负载均衡kube-proxy起初就决定好的)
① endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址
② 只有当service配置selector(选择器),endpoint controller才会自动创建对应的endpoint对象,否则,不会生成endpoint对象
③ 在k8s集群中创建webapp的service,就会生成一个同名的endpoint对象,
endpoint就是service关联的Pod的ip地址和端口
Service能够提供负载均衡的能力,但是在使用上有以下限制:
默认只提供4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。
( 后期可以通过
Ingress
添加7层的负载均衡能力,后面详细讲 )
Service类型
ClusterIp
:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP
NodePort
( 常见的暴露k8s内部服务的方式 ):在ClusterIP
的基础上为 Service 在每台机器节点上绑定一个端口,这样就可以通过: NodePort
来访问该服务。访问节点的 IP + 特定端口就会访问到SVC绑定的端口,进而以rr的调度算法访问到Pod的端口上
,并且每一台机器节点上都是这样的流程,在集群多节点前加一个负载均衡器(nginx/LVS+keeplived等)的话就实现了一定程度的高可用,如下图
LoadBalancer
:在NodePort
的基础上,借助云服务商(Cloud Provider)
创建一个外部负载均衡器,并将请求转发到 NodePort ( LoadBalancer 与 Nodeport( 有负载均衡器的情形下 )的区别就是,LoadBalancer 的负载均衡器是云服务商所提供的 )
ExternalName
:把集群外部的服务引入到集群内部来,在集群内部直接使用。在这种模式下,k8s集群内如果想访问外部的服务,只用去访问svc即可,外部服务后期如果更改了IP,只用更新svc的配置即可。只有 kubernetes 1.7 或更高版本的 kube-dns 才支持
SVC的实现原理:
svc 的实现需要这么几个组件的配合,首先
ApiServer
通过kube-proxy
来负责监听所有的Service、Endpoint端点信息,而kube-proxy则监听Service 所指定Label Selector匹配到的Pod的信息,将其写入到 svc 中
,而其实 SVC 就是通过 iptables 来实现的,也就是把kube-proxy监测到pod的信息写入到iptables 规则中。当客户端访问SVC的时候,其实访问的就是iptables规则,然后iptables规则就根据RR算法将流量调度到后端的Pod上。(
整个过程其实就是:apiserver通过监控kube-proxy实现服务的更新、端点的维护,kube-proxy将规则写入到iptables,而iptables实现将客户端流量调度到具体的pod
)
VIP 和 Service 代理分类简介
在 Kubernetes 集群中,每个worker Node 都会运行一个kube-proxy进程,kube-proxy负责为Service实现了一个VIP(虚拟 IP)的形式,而不是ExternalName( k8s内部访问集群外部服务的形式 )的形式。在 Kubernetes v1.0 版本,kube-proxy 代理只有 userspace 模式。在Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。从 Kubernetes v1.2 起,默认就是iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理
在 Kubernetes 1.14 版本开始默认使用ipvs 代理
在 Kubernetes v1.0 版本,Service是 “4层”(TCP/UDP over IP)概念。在 Kubernetes v1.1 版本,新增了Ingress API(beta 版),用来表示 “7层”(HTTP)服务
Service代理模式的分类说明
Service代理模式总览
 的算法要快很多
① userspace代理模式
在
userspace
代理模式下,client pod如果想要访问Server Pod( 就好比LNMP中的php想要访问mysql ),首先需要访问到 Service IP,再由实际的 iptables 访问到kube-proxy,再由kube-proxy访问到具体的Server pod。这种代理模式下,kube-proxy的压力是比较大的。
② iptables代理模式
在 iptables 的这种模式下,不需要kube-proxy去进行流量的调度了,client pod 的访问,直接就被 iptables 经过RR算法调度到某个server pod上
③ ipvs代理模式
什么是IPVS?
IPVS (IP Virtual Server,IP虚拟服务器)是基于Netfilter的、作为linux内核的一部分实现传输层负载均衡的技术,通常称为第4层LAN交换。
IPVS集成在LVS(Linux Virtual Server)中,它在主机中运行,并在真实服务器集群前充当负载均衡器。IPVS可以将对TCP/UDP服务的请求转发给后端的真实服务器,并使真实服务器的服务在单个IP地址上显示为虚拟服务。因此IPVS天然支持Kubernetes Service。
由于ipvs 无法提供包过滤、SNAT、masquared(伪装)等功能。因此在某些场景(如Node Port的实现)下还是要与iptables搭配使用,ipvs 将使用ipset来存储需要DROP或masquared的流量的源或目标地址,以确保 iptables 规则的数量是恒定的。假设要禁止上万个IP访问我们的服务器,则用iptables的话,就需要一条一条地添加规则,会在iptables中生成大量的规则;但是使用ipset的话,只需要将相关的IP地址(网段)加入到ipset集合中即可,这样只需要设置少量的iptables规则即可实现目标。
为什么选择IPVS
随着kubernetes使用量的增长,其资源的可扩展性变得越来越重要。特别是对于使用kubernetes运行大型工作负载的开发人员或者公司来说,service的可扩展性至关重要。
kube-proxy是为service构建路由规则的模块,之前依赖iptables来实现主要service类型的支持,比如(ClusterIP和NodePort)。但是iptables很难支持上万级的service,因为iptables纯粹是为防火墙而设计的,并且底层数据结构是内核规则的列表。
kubernetes早在1.6版本就已经有能力支持5000多节点,这样基于iptables的kube-proxy就成为集群扩容到5000节点的瓶颈。举例来说,如果在一个5000节点的集群,我们创建2000个service,并且每个service有10个pod,那么我们就会在每个节点上有至少20000条iptables规则,这会导致内核非常繁忙。
基于IPVS的集群内负载均衡就可以完美的解决这个问题。IPVS是专门为负载均衡设计的,并且底层使用哈希表这种非常高效的数据结构,几乎可以允许无限扩容。
IPVS vs. Iptables区别
IPVS模式和IPTABLES模式之间的差异如下:
- IPVS为大型集群提供了更好的可扩展性和性能。(规则的存储方式使用的数据结构更高效)
- IPVS支持比iptables更复杂的负载平衡算法(最小负载,最少连接,位置,加权等)。
- IPVS支持服务器健康检查和连接重试等。
此外,ipvs 为负载均衡算法提供了更多选项,例如:
✦ rr
轮询调度
✦ lc
最小连接数
✦ dh
目标哈希
✦ sh
源哈希
✦ sed
最短期望延迟
✦ nq
不排队调度注意:ipvs模式假定在
运行kube-proxy之前
已经在节点上都安装了IPVS内核模块
,当kube-proxy以ipvs代理模式
启动时,kube-proxy将验证
节点上是否安装了IPVS模块
,如果未安装,则kube-proxy
将回退
到iptables代理模式
ClusterIP说明与演示
:
clusterIP 主要在每个 node 节点使用 iptables,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口
为了实现图上的功能,主要需要以下几个组件的协同工作:
✦
apiserver,用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中
✦
kube-proxy,kubernetes的每个woker节点中都有一个kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables规则中
✦
iptables/ipvs 使用NAT等技术将virtualIP的流量转至endpoint中1、apiserver 先将信息写入到 etcd数据库中, kube-proxy检测 etcd数据库中信息的变化,得到变化的信息后会将信息写入到本地(每一个woker节点都会有自己的kube-proxy进程,所以说是本地)的 iptables/ipvs/userspace中
clusterIP 演示:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deploy namespace: default spec: replicas: 3 selector: matchLabels: app: myapp release: stabel template: metadata: labels: app: myapp release: stabel env: test spec: containers: - name: myapp image: nginx imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 # 创建完deployment之后,是可以直接通过pod的ip进行集群内访问的 # 但是这样的方式太过繁琐,且如果pod意外停止,新创建的Pod的IP的也是会发生改变的,所以接下来创建一个svc
① 首先创建一个Deployment,副本数为3
apiVersion: v1 kind: Service metadata: name: myapp namespace: default spec: type: ClusterIP selector: app: myapp release: stabel ports: - name: http port: 80 targetPort: 80 # 创建完service之后,可以结合本地的环境,查看是否有相应的转发规则 # ipvsadm -Ln / iptables -t nat -nvL # 如果创建的 service 通过 selector 设定的标签与后端的pod关联起来了,就可以通过访问svc直接访问后端的pod了
② 创建完Deployment之后创建一个Service ClusterIP:
③ 修改nginx镜像的default.index文件,测试访问ClusterIP
Headless Service
:
有时不需要或不想要负载均衡以及单独的 Service IP ,遇到这种情况,可以通过指定 ClusterIP(spec.clusterIP) 的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们指定负载均衡和路由
Headless Service 不会被分配Cluster IP(集群内部IP),
apiVersion: v1 kind: Service metadata: name: myapp-headless namespace: default spec: selector: app: myapp clusterIP: "None" ports: - port: 80 targetPort: 80
# svc创建之后会被写入到coreDNS中,写入的格式就是 " SVC名称.名称空间.域名. " # 域名默认是 " svc.cluster.local. " # 然后我们就可以通过下面的方式来访问Headless SVC # dig -t a SVC名称.名称空间.域名. @dns
CoreDNS作用:
Tips:
FQDN
:(Fully Qualified Domain Name)完全合格域名/全称域名,是指主机名加上全路径,全路径中列出了序列中所有域成员。全域名可以从逻辑上准确地表示出主机在什么地方,也可以说全域名是主机名的一种完全表示形式。从全域名中包含的信息可以看出主机在域名树中的位置。完全合格域名(FQDN):点结尾的域名,
例如bbs.cnblogs.com.就是一个完全合格域名。在一般的网络应用中,我们可以省略完全合格域名最右侧的点,但DNS对这个点不能随便省略。因为这个点代表了DNS的根,有了这个点,完全合格域名就可以表达为一个绝对路径,例如bbs.cnblogs.com.就可以表示为DNS根下的com子域下cnblogs.com域中一个名为bbs的主机。如果DNS发现一个域名不是以点结尾的完全合格域名,就会把这个域名加上当前的区域名称作为后缀,让其满足完全合格域名的形式需求。例如DNS会把域名bbs处理为bbs.blogs.com.
K8s中资源的全局FQDN格式
:
svc_name.namespace_name.Domain.LTD.
Domain.LTD. = svc.cluster.local.
#这是k8s集群默认的域名。CoreDNS 会把Service( Kubernetes的FQDN )解析成Cluster IP,所以当管理员为Deployment等控制器添加一个Cluster IP类型的Service之后,client端既可以通过该Cluster IP访问到Deployment下的Pod,也可以通过该Service FQDN访问到Deployment下的Pod。而访问Service FQDN的时候,CoreDNS是会将Service FQDN解析成该Service对应的Cluster IP,进而通过负载均衡转发到后端的单个/多个Endpoint端点(一个Endpoint端点对应的就是后端一个Pod的IP:Port)。
CoreDNS能将Service的Name解析为对应的Cluster IP,做不到将Deployment/Pod的Name解析为其对应的IP。倒是可以为Pod定义一个Headless Service,因为Headless Service没有Cluster IP,所以CoreDNS中Headless Service对应的解析就直接是后端对应的Pod IP
CoreDNS的记录格式(来源coreDNS Github):
一个Pod可以对应多个svc,而一个svc也可以对应多个pod,也就是说它们之间的对应关系是n对m的
NodePort
:
nodePort 的原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的 pod
apiVersion: v1 kind: Service metadata: name: myapp namespace: default spec: type: NodePort selector: app: myapp release: stabel ports: - name: http port: 80 targetPort: 80
查询流程:
# iptables -t nat -nvL
LoadBanlance:
客户端只用访问到云供应商的负载均衡器,流量从该负载均衡器指向k8s集群的NodePort(worker node),由NodePort指向后端的Pod
ExternalName
:
externalName SVC是比较特殊的不使用Label Seclector的一个SVC
这个模式是为了将外部的服务引入k8s集群内部,比如将外部读写分离的mysql集群引入集群内部。
首先需要知道的是,每创建一个svc就会在k8s的coreDNS中添加一条解析记录,该值格式为svc_name.namespace_name.svc.cluster.local.
,而Externalname SVC的工作流程是,当客户端访问到svc_name.namespace_name.svc.cluster.local.
这条解析的时候,流量会被导向yaml资源清单中定义的externalName
上,从而将流量指向集群外对应的服务上。externalName其实就相当于在coreDns中加了一个Cname解析
问:
集群外的服务如何以externalName指定的字段向k8s集群提供服务的?
传统svc仅支持四层代理,并不支持七层代理,如果要用到七层代理,那就需要用到ingress
了
Ingress负载网络:
在通常情况下,service和pod的IP仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到service在节点暴露的NodePort上,然后再由kube-proxy将其转发给相关的Pod。而Ingress就是为进入集群的请求提供路由规则的集合。
Ingress可以给service提供集群外部访问的URL、负载均衡、SSL、HTTP路由等。
为了配置这些Ingress规则,集群管理员需要部署一个IngressController
,它监昕Ingress和service的变化,并根据规则配置负载均衡并提供访问入口。为什么需要Ingress,因为在对外访问的时候,NodePort类型需要在外部搭建额外的负载均衡,而LoadBalancer要求kubernetes必须跑在特定的云服务提供商上面。
Ingress的实现方案有很多,例如Ingress-nginx、Ingress-Haproxy等
定义一个Ingress如下:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress spec: rules: - http: paths: - path: /testpath backend: serviceName: test servicePort: 80
每个Ingress都需要配置rules,目前Kubernetes仅支持http规则。上面的示例表示请求/testpath时转发到服务test的80端口中。根据IngressSpec配置的不同,Ingress可以分为以下几种类型.
1、单服务Ingress
单服务Ingress即该Ingress仅指定一个没有任何规则的后端服务
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress spec: backend: serviceName: testsvc servicePort: 80
单服务还可以通过设置Service.Type=NodePort或者Service.Type=LoadBalancer来对外暴露
2、多服务Ingress
路由到多服务的Ingress即根据请求路径的不同转发到不同的后端服务上,比如可以通过下面的Ingress来定义:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test spec: rules: - host: foo.bar.com http: paths: - path: /foo backend: serviceName: s1 servicePort: 80 - path: /bar backend: serviceName: s2 servicePort: 80
上面例子中,如果访问的是/foo,则路由转发到s1服务,如果是/bar则转发到s2服务
3、虚拟主机Ingress
虚拟主机,Ingress即根据名字的不同转发到不同的后端服务上,而它们共用同一个IP地址,基于Hostheader路由请求的Ingress如下:
aiVersion: extensions/v1beta1 kind: Ingress metadata: name: test spec: rules: - host: foo.bar.com http: paths: - backend: serviceName: s1 servicePort: 80 - host: bar.foe.com http: paths: - backend: serviceName: s2 servicePort: 80
根据不同的域名路由到不同的后端服务
4、更新Ingress
可以通过
kubectl edit ingress_name
的方法来更新Ingress# kubectl edit Ingress名称 -f yaml
这条命令会使用编辑器打开一个已有Ingress的yaml定义文件,修改并保存就会将其更新到KubemetesAPI server,进而触发IngressController重新配置负载均衡
spec: rules: - host: foe.bar.com http: paths: - backend: serviceName: s1 servicePort: 80 path: /foo - host: bar.baz.com http: paths: - backend: serviceName: s2 servicePort: 80 path: /foo
当然,也可以使用
kubectl replace - f new_ingress.yaml
来更新,ingress的yaml文件可以由命令kubectl get Resources_Type Name -o yaml
获得
Ingress HTTP代理访问
deployment、Service、Ingress Yaml文件
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-dm
spec:
replicas: 2
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx
image: ishells/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
name: nginx
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-test
spec:
rules:
- host: www1.test.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80
Ingress HTTPS代理访问
创建证书、以及cert存储方式
# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj"/CN=nginxsvc/O=nginxsvc"
# kubectl create secret tls tls-secret --key tls.key --cert tls.crt
deployment、Service、Ingress Yaml文件:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-test
spec:
tls:
- hosts:
- foo.bar.com
secretName: tls-secret
rules:
- host: foo.bar.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80
Nginx进行BasicAuth
# yum -y install httpd
# htpasswd -c auth foo
# kubectl create secret generic basic-auth --from-file=auth
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-with-auth
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
spec:
rules:
- host: foo2.bar.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80