编者按:云原生是网易杭州研究院(网易杭研)奉行的核心技术方向之一,开源容器平台Kubernetes作为云原生产业技术标准、云原生生态基石,在设计上不可避免有其复杂性,Kubernetes系列文章基于网易杭研资深工程师总结,多角度多层次介绍Kubernetes的原理及运用,如何解决生产中的实际需求及规避风险,希望与读者深入交流共同进步。
本文由作者授权发布,未经许可,请勿转载。
作者:李岚清,网易杭州研究院云计算技术中心资深工程师
众所周知,pod的生命周期是不稳定的,可能会朝生夕死,这也就意味着pod的ip是不固定的。
比如我们使用三副本的deployment部署了nginx服务,每个pod都会被分配一个ip,由于pod的生命周期不稳定,pod可能会被删除重建,而重建的话pod的ip地址就会改变。也有一种场景,我们可能会对nginx deployment进行扩缩容,从3副本扩容为5副本或者缩容为2副本。当我们需要访问上述的nginx服务时,客户端对于nginx服务的ip地址就很难配置和管理。
因此,kubernetes社区就抽象出了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: 3
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 1/1 Running 0 57s 10.120.49.230 pubt2-k8s-for-iaas4.dg.163.org
nginx-5c7588df-gb2d8 1/1 Running 0 57s 10.120.49.152 pubt2-k8s-for-iaas4.dg.163.org
nginx-5c7588df-gdngk 1/1 Running 0 57s 10.120.49.23 pubt2-k8s-for-iaas4.dg.163.org
(2)创建service,通过label selector关联nginx pod:
svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- port: 80
protocol: TCP
targetPort: 80
# 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 80/TCP 23s app=nginx
(3)在k8s节点上访问service地址
# curl 10.178.4.2:80
Welcome to nginx!
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
For online documentation and support please refer to
nginx.org.
Commercial support is available at
nginx.com.
Thank you for using nginx.
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 1/1 Running 0 6s
nginx-5c7588df-gb2d8 1/1 Running 0 18m
nginx-5c7588df-gdngk 1/1 Running 0 18m
可以看到kube-controller-manager立马补充了一个新的pod。然后我们再看一下endpoints资源,后端pod列表也被自动更新了:
# kubectl get endpoints nginx
NAME ENDPOINTS AGE
nginx 10.120.49.152:80,10.120.49.23:80,10.120.49.73:80 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 (2 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 (1 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 (1 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:80
当用户访问clusterip:port
时,iptables会通过iptables DNAT 均衡的负载均衡到后端pod。
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的。
kubernetes是有自己的域名解析服务的。比如我们可以通过访问域名nginx.default.svc.cluster.local
来访问上述的nginx服务:
$ curl nginx.default.svc.cluster.local
Welcome to nginx!
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
For online documentation and support please refer to
nginx.org.
Commercial support is available at
nginx.com.
Thank you for using nginx.
域名格式为: ${ServiceName}.${Namespace}.svc.${ClusterDomain}
. 其中${ClusterDomain}的默认值是cluster.local
,可以通过kubelet的命令行参数----cluster-domain
进行配置。
当不需要service ip的时候,可以在创建service的时候指定spec.clusterIP: None
,这种service即是headless service。由于没有分配service ip,kube-proxy也不会处理这种service。
DNS对这种service的解析:
service支持多种不同的类型,包括ClusterIP
、NodePort
、LoadBalancer
,通过字段spec.type
进行配置。
默认类型。对于ClusterIP service, k8s会自动分配一个只在集群内可达的虚拟的ClusterIP,在k8s集群外无法访问。
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
不能够跟几个端口范围冲突:
net.ipv4.ip_local_port_range
,可以配置为 35000-60999
LoadBalancer service需要对接云服务提供商的NLB服务。当用户创建一个LoadBalancer类型的sevice时,cloud-controller-manager
会调用NLB的API自动创建LB实例,并且将service后端的pod挂到LB实例后端。
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
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 80:32514/TCP 3s
用户可以通过配置spec.serviceAffinity=ClientIP
来实现基于客户端ip的会话保持功能。 该字段默认为None。
还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来设置最大会话停留时间。 (默认值为 10800 秒,即 3 小时)
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
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: 443
protocol: TCP
targetPort: 6443
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:6443,10.120.0.3:6443 137d
具体是如何实现的,感兴趣的可以看下源码k8s.io/kubernetes/pkg/master/reconcilers
问题一 为什么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开发、运维经验,主导实现了容器网络管理、容器混部等生产级核心系统研发,推动网易集团内部电商、音乐、传媒、教育等多个业务的容器化。