【解构云原生】初识Kubernetes Service
编者按:云原生是网易杭州研究院(网易杭研)奉行的核心技术方向之一,开源容器平台Kubernetes作为云原生产业技术标准、云原生生态基石,在设计上不可避免有其复杂性,Kubernetes系列文章基于网易杭研资深工程师总结,多角度多层次介绍Kubernetes的原理及运用,如何解决生产中的实际需求及规避风险,希望与读者深入交流共同进步。
本文由作者授权发布,未经许可,请勿转载。
作者:李岚清,网易杭州研究院云计算技术中心资深工程师
为什么引入service
众所周知,pod的生命周期是不稳定的,可能会朝生夕死,这也就意味着pod的ip是不固定的。
比如我们使用三副本的deployment部署了nginx服务,每个pod都会被分配一个ip,由于pod的生命周期不稳定,pod可能会被删除重建,而重建的话pod的ip地址就会改变。也有一种场景,我们可能会对nginx deployment进行扩缩容,从3副本扩容为5副本或者缩容为2副本。当我们需要访问上述的nginx服务时,客户端对于nginx服务的ip地址就很难配置和管理。
因此,kubernetes社区就抽象出了service
这个资源对象或者说逻辑概念。
什么是service
service是kubernetes中最核心的资源对象之一,kubernetes中的每个service其实就是我们经常提到的“微服务”。
service定义了一个服务的入口地址,它通过label selector 关联后端的pod。service会被自动分配一个ClusterIP,service的生命周期是稳定的,它的ClusterIP也不会发生改变,用户通过访问service的ClusterIP来访问后端的pod。所以,不管后端pod如何扩缩容、如何删除重建,客户端都不需要关心。
(1)创建一个三副本的nginx deployment:
nginx.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
# kubectl create -f nginx.yaml
deployment.extensions/nginx created # kubectl get pods -o wide
nginx-5c7588df-5dmmp / Running 57s 10.120.49.230 pubt2-k8s-for-iaas4.dg..org <none> <none>
nginx-5c7588df-gb2d8 / Running 57s 10.120.49.152 pubt2-k8s-for-iaas4.dg..org <none> <none>
nginx-5c7588df-gdngk / Running 57s 10.120.49.23 pubt2-k8s-for-iaas4.dg..org <none> <none>
(2)创建service,通过label selector关联nginx pod:
svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- port:
protocol: TCP
targetPort:
# kubectl create -f svc.yaml
service/nginx created # kubectl get svc nginx -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx ClusterIP 10.178.4.2 <none> /TCP 23s app=nginx
(3)在k8s节点上访问service地址
# curl 10.178.4.2:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p> <p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p>
</body>
</html>
实现原理
service中有几个关键字段:
spec.selector
: 通过该字段关联属于该service的podspec.clusterIP
: k8s自动分配的虚拟ip地址spec.ports
: 定义了监听端口和目的端口。用户可以通过访问clusterip:监听端口
来访问后端的pod
当用户创建一个service时,kube-controller-manager会自动创建一个跟service同名的endpoints资源:
# kubectl get endpoints nginx
NAME ENDPOINTS AGE
nginx 10.120.49.152:80,10.120.49.23:80,10.120.49.230:80 12m
endpoints资源中,保存了该service关联的pod列表,这个列表是kube-controller-manager自动维护的,当发生pod的增删时,这个列表会被自动刷新。
比如,我们删除了其中的一个pod:
# kubectl delete pods nginx-5c7588df-5dmmp
pod "nginx-5c7588df-5dmmp" deleted # kubectl get pods
nginx-5c7588df-ctcml / Running 6s
nginx-5c7588df-gb2d8 / Running 18m
nginx-5c7588df-gdngk / Running 18m
可以看到kube-controller-manager立马补充了一个新的pod。然后我们再看一下endpoints资源,后端pod列表也被自动更新了:
# kubectl get endpoints nginx
NAME ENDPOINTS AGE
nginx 10.120.49.152:,10.120.49.23:,10.120.49.73: 16m
那么,当用户去访问clusterip:port
时,流量是如何负载均衡到后端pod的呢?
k8s在每个node上运行了一个kube-proxy
组件,kube-proxy
会watch service和endpoints资源,通过配置iptables规则(现在也支持ipvs,不过不在本文章讨论范围之内)来实现service的负载均衡。
可以在任一个k8s node上看一下上述nginx service的iptables规则:
# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */ # iptables -t nat -L KUBE-SERVICES
Chain KUBE-SERVICES ( references)
target prot opt source destination
KUBE-SVC-4N57TFCL4MD7ZTDA tcp -- anywhere 10.178.4.2 /* default/nginx: cluster IP */ tcp dpt:http # iptables -t nat -L KUBE-SVC-4N57TFCL4MD7ZTDA
Chain KUBE-SVC-4N57TFCL4MD7ZTDA ( references)
target prot opt source destination
KUBE-SEP-AHN4ALGUQHWJZNII all -- anywhere anywhere statistic mode random probability 0.33332999982
KUBE-SEP-BDD6UBFFJ4G2PJDO all -- anywhere anywhere statistic mode random probability 0.50000000000
KUBE-SEP-UR2OSKI3P5GEGC2Q all -- anywhere anywhere # iptables -t nat -L KUBE-SEP-AHN4ALGUQHWJZNII
Chain KUBE-SEP-AHN4ALGUQHWJZNII ( references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.120.49.152 anywhere
DNAT tcp -- anywhere anywhere tcp to:10.120.49.152:
当用户访问clusterip:port
时,iptables会通过iptables DNAT 均衡的负载均衡到后端pod。
service ClusterIP
service的ClusterIP是一个虚拟ip,它没有附着在任何的网络设备上,仅仅存在于iptables规则中,通过dnat实现访问clusterIP时的负载均衡。
当用户创建service时,k8s会自动从service网段中分配一个空闲ip设置到.spec.clusterIP
字段。当然,k8s也支持用户在创建svc时自己指定clusterIP。
service的网段是通过 kube-apiserver的命令行参数--service-cluster-ip-range
配置的,不允许变更。
service网段不能跟机房网络、docker网段、容器网段冲突,否则可能会导致网络不通。
service的clusterIP是k8s集群内的虚拟ip,不同的k8s集群可以使用相同的service网段,在k8s集群外是访问不通service的clusterIP的。
service域名
kubernetes是有自己的域名解析服务的。比如我们可以通过访问域名nginx.default.svc.cluster.local
来访问上述的nginx服务:
$ curl nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p> <p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p>
</body>
</html>
域名格式为: ${ServiceName}.${Namespace}.svc.${ClusterDomain}
. 其中${ClusterDomain}的默认值是cluster.local
,可以通过kubelet的命令行参数----cluster-domain
进行配置。
headless service
当不需要service ip的时候,可以在创建service的时候指定spec.clusterIP: None
,这种service即是headless service。由于没有分配service ip,kube-proxy也不会处理这种service。
DNS对这种service的解析:
- 当service里定义selector的时候:Endpoints controller会创建相应的endpoints。DNS里的A记录会将svc地址解析为这些pods的地址
- 当service里没有定义selector:Endpoints controller不会创建endpoints。DNS会这样处理:
- 首先CNAME到service里定义的ExternalName
- 没有定义ExternalName的话,会搜寻所有的和这个service共享name的Endpoints,然后将A记录解析到这些Endpoints的地址
service的不同类型
service支持多种不同的类型,包括ClusterIP
、NodePort
、LoadBalancer
,通过字段spec.type
进行配置。
ClusterIP service
默认类型。对于ClusterIP service, k8s会自动分配一个只在集群内可达的虚拟的ClusterIP,在k8s集群外无法访问。
NodePort service
k8s除了会给NodePort service自动分配一个ClusterIP,还会自动分配一个nodeport端口。集群外的客户端可以访问任一node的ip加nodeport,即可负载均衡到后端pod。
nodeport的端口范围可以通过kube-apiserver的命令行参数--service-node-port-range
配置,默认值是30000-32767
,当前我们的配置是30000-34999
。
但是客户端访问哪个node ip也是需要考虑的一个问题,需要考虑高可用。而且NodePort会导致访问后端服务时多了一跳,并且可能会做snat看不到源ip。
另外需要注意的是,service-node-port-range
不能够跟几个端口范围冲突:
- Linux的
net.ipv4.ip_local_port_range
,可以配置为35000-60999
- ingress nginx中的四层负载均衡,端口必须小于30000
- 其他普通业务的端口也需要小于30000
LoadBalancer service
LoadBalancer service需要对接云服务提供商的NLB服务。当用户创建一个LoadBalancer类型的sevice时,cloud-controller-manager
会调用NLB的API自动创建LB实例,并且将service后端的pod挂到LB实例后端。
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port:
protocol: TCP
targetPort:
type: LoadBalancer
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.178.8.216 10.194.73.147 :/TCP 3s
service中会话保持
用户可以通过配置spec.serviceAffinity=ClientIP
来实现基于客户端ip的会话保持功能。 该字段默认为None。
还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来设置最大会话停留时间。 (默认值为 10800 秒,即 3 小时)
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port:
protocol: TCP
targetPort:
type: ClusterIP
sessionAffinity: ClientIP
kubernetes
service
当我们部署好一个k8s集群之后,发现系统自动帮忙在default
namespace下创建了一个name为kubernetes
的service:
# kubectl get svc kubernetes -o yaml
apiVersion: v1
kind: Service
metadata:
labels:
component: apiserver
provider: kubernetes
name: kubernetes
namespace: default
spec:
clusterIP: 10.178.4.1
ports:
- name: https
port:
protocol: TCP
targetPort:
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
可以看到kubernetes
svc的ip是--service-cluster-ip-range
的第一个ip,并且该service没有设置spec.selector
。理论上来说,对于没有设置selector的svc,kube-controller-manager不会自动创建同名的endpoints资源出来。
但是我们看到是有同名的endpoints存在的,并且多个apiserver的地址也被保存在endpoints资源中:
# kubectl get ep kubernetes
NAME ENDPOINTS AGE
kubernetes 10.120.0.2:,10.120.0.3: 137d
具体是如何实现的,感兴趣的可以看下源码k8s.io/kubernetes/pkg/master/reconcilers
Frequently Asked Questions
问题一 为什么service clusterip无法ping通
因为service clusterip是一个k8s集群内部的虚拟ip,没有附着在任何网络设备上,仅仅存在于iptables nat规则中,用来实现负载均衡。
问题二 为什么service的网段不能跟docker网段、容器网段、机房网段冲突
假如service网段跟上述网段冲突,很容易导致容器或者在k8s node上访问上述网段时发生网络不通的情况。
问题三 为什么在k8s集群外无法访问service clusterip
service clusterip是k8s集群内可达的虚拟ip,集群外不可达。不同的k8s集群可以使用相同的service网段。
或者说,集群外的机器上没有本k8s集群的kube-proxy组件,没有创建对应的iptables规则,因此集群外访问不通service clusterip。
问题四 能否扩容service网段
原则上这个网段是不允许更改的,但是假如因为前期规划的问题分配的网段过小,实际可以通过比较hack的运维手段扩容service网段。
问题五 service是否支持七层的负载均衡
service仅支持四层的负载均衡,七层的负载均衡需要使用ingress
参考文档
作者简介
李岚清,网易杭州研究院云计算技术中心容器编排团队资深系统开发工程师,具有多年Kubernetes开发、运维经验,主导实现了容器网络管理、容器混部等生产级核心系统研发,推动网易集团内部电商、音乐、传媒、教育等多个业务的容器化。
【解构云原生】初识Kubernetes Service的更多相关文章
- 解构HE2E中的Kubernetes技术应用
摘要:我们从Kubernetes技术应用的角度解构华为云DevCloud HE2E DevOps实践. 本文分享自华为云社区<解构HE2E中的Kubernetes技术应用>,作者: 敏捷小 ...
- 云原生应用 Kubernetes 监控与弹性实践
前言 云原生应用的设计理念已经被越来越多的开发者接受与认可,而Kubernetes做为云原生的标准接口实现,已经成为了整个stack的中心,云服务的能力可以通过Cloud Provider.CRD C ...
- 从零搭建云原生技术kubernetes(K8S)环境-通过kubesPhere的AllInOne方式
前言 k8s云原生搭建,步骤有点多,但通过kubesphere,可以快速搭建k8s环境,同时有一个以 Kubernetes 为内核的云原生分布式操作系统-kubesphere,本文将从零开始进行kub ...
- 未来已来:云原生 Cloud Native
作者:天知,原文链接 前言 自 2013 年容器(虚拟)技术(Docker)成熟后,后端的架构方式进入快速迭代的阶段,出现了很多新兴概念: 微服务 k8s Serverless IaaS:基础设施服务 ...
- 云原生生态周报 Vol.9| K8s v1.15 版本发布
本周作者 | 衷源.心贵 业界要闻 1.Kubernetes Release v1.15 版本发布,新版本的两个主题是持续性改进和可扩展性.(https://github.com/kubernetes ...
- 云原生生态周报 Vol. 7 | Docker 再爆 CVE
业界要闻 Docker 基础镜像 Alpine 爆出提权漏洞(CVE-2019-5021):该CVE影响自 Alpine Linux 3.3 版本开始的所有 Docker 镜像.该漏洞的机制在于 Al ...
- waf 引擎云原生调研---扫盲
概念: lstio Istio是一个用于服务治理的开放平台 Istio是一个Service Mesh形态的用于服务治理的开放平台 Istio是一个与Kubernetes紧密结合的适用于云原生场景的Se ...
- 重大升级!灵雀云发布全栈云原生开放平台ACP 3.0
云原生技术的发展正在改变全球软件业的格局,随着云原生技术生态体系的日趋完善,灵雀云的云原生平台也进入了成熟阶段.近日,灵雀云发布重大产品升级,推出全栈云原生开放平台ACP 3.0.作为面向企业级用户的 ...
- 产品对话 | 愿云原生不再只有Kubernete
从2013年,云原生(Cloud Native)的概念由 Pivotal 的 MattStine 首次提出,到现在,其技术细节不断得到社区的完善.云原生逐渐演变出包括 DevOps.持续交付.微服务. ...
随机推荐
- centos 7.0运行docker出现内核报错解决方法
目前我这里docker是运行在centos 7.0系统里,使用1.5版本docker,最近一台服务器总是不定期死机,通过查看日志发现属于内核bug导致,报错信息如下 1 2 3 4 5 6 7 8 9 ...
- 图论--2-SAT--HDOJ/HDU 1824 Let's go home
Problem Description 小时候,乡愁是一枚小小的邮票,我在这头,母亲在那头. -- 余光中 集训是辛苦的,道路是坎坷的,休息还是必须的. ...
- MySQL 索引、视图
1.索引 什么是索引 一个索引是存储在表中的数据结构,索引在表的列名上创建.索引中包含了一个列的值,这些值保存在一个数据结构中 索引优缺点 索引大大提高了查询速度 会降低更新表的速度,如对表进行INS ...
- 06 __init__ 和 __new__的关系和不同
一. 双下new 和 双下init 关系 首先从__new__(cls,a,b,c)的参数说说起,__new__方法的第一个参数是这个类,而其余的参数会在调用成功后全部传递给__init__方法初始化 ...
- [转载] IE8+兼容小结
本文分享下我在项目中积累的IE8+兼容性问题的解决方法.根据我的实践经验,如果你在写HTML/CSS时候是按照W3C推荐的方式写的,然后下面的几点都关注过,那么基本上很大一部分IE8+兼容性问题都OK ...
- 播放音乐(mciSendString)
1.需要引用命名空间using System.Runtime.InteropServices; 这里只是做了个简单的播放功能,想了解更多查看它的官方文档 [DllImport("winmm. ...
- 记一次 spinor flash 读速度优化
背景 某个项目使用的介质是 spinor, 其 bootloader 需要从 flash 中加载 os. 启动速度是一个关键指标,需要深入优化.其他部分的优化暂且略过,此篇主要记录对 nor 读速度的 ...
- Day_12【集合】扩展案例1_利用集合的知识对长度为10的int数组进行去重,产生新数组,不能改变数组中原来数字的大小顺序
分析以下需求,并用代码实现 1.定义一个长度为10的int数组,并存入10个int类型的数据,其中有一些数据是重复的 2.利用集合的知识对数组进行去重,产生新数组,不能改变数组中原来数字的大小顺序 3 ...
- Centos 编译带调试信息的libevent
libevent编译过程 查看libevent文档即可 解决cmake编译出来的可执行文件没有调试信息(该方法未实验,暂时对cmake不熟悉) SET(CMAKE_BUILD_TYPE "D ...
- vtk学习记录(三)——初识vtkRenderer
目录 前言 vtkRenderer 引入vtk窗口 小结 前言 一场疫情打乱了好多人的节奏,我也一样,一不留神半年都快过去了,这期间虽说一直在鼓捣东西吧,不过确实是没啥实质性的进展,索性就继续把vtk ...