学练结合,快速掌握Kubernetes Service
今天这篇文章里我们来讲一下Kubernetes
里的Service
对象。其实前面的文章《Kubernetes初体验--部署运行Go项目》里我们已经与Service
有过一次短暂接触了,在那篇文章里我说用Deployment
对象部署完应用后还需要向外界暴露入口才能通过HTTP访问到Kubernetes
集群里的应用Pod
,当时使用的是这样一条命令,其实就是创建的Service
对象:
kubectl expose deployment my-go-app --type=NodePort ...
那么在这篇文章里我们就来聊一下:
什么是 Service
对象,在Kubernetes
里它是干什么用的;Kubernetes
里怎么发现Service
;如何创建和使用 Service
;nodePort,port,targetPort都是啥;
文章前面半部分理论知识多一点,稍显枯燥,后半部分会用一个实践练习给之前用Deployment
部署好的应用Pod
们加上Service
,让外部请求能访问到Kubernetes
集群里的应用,并为Pod
提供负载均衡。
Kubernetes Service
和之前文章里介绍的Pod,ReplicaSet,Deployment一样,Service也是Kubernetes里的一个API对象,而 Kubernetes 之所以需要 Service,一方面是因为Pod
的 IP 不是固定的,另一方面则是因为一组Pod
实例需要Service
提供复杂均衡功能。所以Service
是在逻辑抽象层上定义了一组Pod
,为他们提供一个统一的固定IP和访问这组Pod
的负载均衡策略。
下面是Service
对象的常用属性设置:
使用label selector,在集群中查找目标 Pod
;ClusterIP设置Service的集群内IP让 kube-proxy
使用;通过prot和targetPort将访问端口与目标端口建议映射(不指定targetPort时默认值和port设置的值一样); Service支持多个端口映射 Service支持HTTP(默认),TCP和UDP协议;
下面是一个典型的Service
定义:
apiVersion: v1
kind: Service
metadata:
name: hostnames
spec:
selector:
app: hostnames
ports:
- name: default
protocol: TCP
port: 80
targetPort: 9376
都有哪些类型的Service
Kubernetes
中有四种Service类型:
ClusterIP。这是默认的Service类型,会将Service对象通过一个内部IP暴露给集群内部,这种类型的Service只能够在集群内部使用 <ClusterIP>:<port>
访问。NodePort。会在每个宿主机节点的一个指定的固定端口上暴露Service,与此同时还会自动创建一个ClusterIP类型的Service,NodePort类型的Service会将集群外部的请求路由给ClusterIP类型的Service。你可以使用 <NodeIP>:<NodePort>
访问NodePort类型的Service,NodePort的端口范围为30000-32767。LoadBalancer。适用于公有云上的 Kubernetes
服务,使用公有云服务的CloudProvider
创建LoadBalancer类型的Service,同时会自动创建NodePort和ClusterIP类型的Service,LoadBalancer会把请求路由到NodePort和ClusterIP类型的Service上。ExternalName。ExternalName 类型的 Service,是在 kube-dns 里添加了一条 CNAME 记录。这个CNAME记录是在Service的spec.externalName里指定的,
以上四种类型除了ExternalName
,Kubernetes
的kube-proxy
组件都会为Service
提供VIP(虚拟IP),kube-proxy
支持两种模式:iptables和ipvs。涉及到不少知识,感兴趣的可以去极客时间上看这篇文章:Service, DNS与服务发现[1]
上面的第三和第四种类型的Service
在本地试验不了,所以后面的例子我们主要通过NodePort
类型的Service
学习它的基本用法。
怎么发现Service
在Kubernetes
里的内部组件kube-dns
会监控Kubernetes API,当有新的Service
对象被创建出来后,kube-dns
会为Service
对象添加DNS A记录(从域名解析 IP 的记录)
对于 ClusterIP
模式的 Service
来说,它的 A 记录的格式是:
serviceName.namespace.svc.cluster.local,当你访问这条 A 记录的时候,它解析到的就是该 Service 的 VIP 地址。
对于指定了 clusterIP=None 的 Headless Service来说,它的A记录的格式跟上面一样,但是访问记录后返回的是Pod的IP地址集合。Pod 也会被分配对应的 DNS A 记录,格式为:podName.serviceName.namesapce.svc.cluster.local
我们会在后面的实践练习里通过nslookup
印证DNS记录是否符合这里说的格式
创建和使用Service
跟其他Kubernetes
里的API对象,Service
也是通过YAML
文件定义然后提交给Kubernetes
后由ApiManager
创建完成。一个典型的NodePort
类型的Service
的定义如下所示:
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
type: NodePort
selector:
app: go-app
ports:
- name: http
protocol: TCP
nodePort: 30080
port: 80
targetPort: 3000
这里定义的Service对象会去管控我们在之前的文章《K8s上的Go服务怎么扩容、发版更新、回滚、平滑重启?教你用Deployment全搞定!》里用Deployment
创建的Go
应用的三个Pod
副本。
➜ kubectl get pods -l app=go-app
NAME READY STATUS RESTARTS AGE
my-go-app-864496b67b-6hm7r 1/1 Running 1 16d
my-go-app-864496b67b-d87kl 1/1 Running 1 16d
my-go-app-864496b67b-qxrsr 1/1 Running 1 16d
➜
我们用kubectl apply -f service.yaml命令把定义好的Service
提交给Kubernetes
:
➜ kubectl apply -f service.yaml
service/app-service created
被Service
的selector
选中的Pod
,就称为Service
的 Endpoints
,可以使用 kubectl get ep 命令看到它们,如下所示:
➜ kubectl get ep app-service
NAME ENDPOINTS AGE
app-service 172.17.0.6:3000,172.17.0.7:3000,172.17.0.8:3000 8m38s
需要注意的是,只有处于Running
状态,且 readinessProbe 检查通过的Pod
,才会出现在Service
的 Endpoints
列表里。当某一个Pod
出现问题时,Kubernetes
会自动把它从 Service
里摘除掉。
使用 kubectl get svc可以查看到刚才看到的Service
的信息和状态。
➜ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
app-service NodePort 10.108.26.155 <none> 80:30080/TCP 116m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 89d
nodePort 、port、targetPort都是啥
上面我们创建了一个NodePort
类型的Service
,在下面的端口映射spec.ports配置里,每个端口映射里出现了三种port:nodePort、port、targetPort。那这三种port
都代表的什么意思呢?
port:指定在集群内部暴露 Service
所使用的端口,集群内部使用<ClusterIP>:<port>
访问Service
的EndPoints
(Service
选中的Pod)。nodePort:指定向集群外部暴露 Service
所使用的端口,从集群外部使用<NodeIp>:<NodePort>
访问Service
的EndPoints
。如果你不显式地声明nodePort
字段,会随机分配可用端口来设置代理。这个端口的范围默认是 30000-32767。targetPort: targetPort
是后面的Pod
监听的端口,容器里的应用也应该监听这个端口,Service
会把请求发送到这个端口。
所以结合刚才我们创建的app-service这个Service
的信息,在集群内部使用10.108.26.155:80 访问Pod
里的应用。因为我们试验使用的minikube
是个单节点的集群,NodeIP
可以通过 minikube ip命令获得。
➜ minikube ip
192.168.64.4
所以从集群外部,通过192.168.64.4:30080访问Pod
里的应用。
➜ curl 192.168.64.4:30080
Hello World
Hostname: my-go-app-75d6d768ff-mlqnh% ➜ curl 192.168.64.4:30080
Hello World
Hostname: my-go-app-75d6d768ff-4x8p8% ➜ curl 192.168.64.4:30080
Hello World
Hostname: my-go-app-75d6d768ff-vt7dx%
通过多次访问,我们可以看到请求会通过Service
发给不同的应用Pod
。Pod
里的应用就是在原来的文章里一直使用的例子的基础上加了一行获取系统Hostname
的代码:
...
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World")
hostname, _ := os.Hostname()
fmt.Fprintf(w, "Hostname: %s", hostname)
}
...
最后我们进到Pod
里看一下Service
创建后kube-dns
组件在集群里为app-service
这个Service
对象创建的DNS A记录,因为Service
定义里指定的名字是app-service
,命名空间的话因为没有指定就是默认的default
命名空间,所以我们使用nslookup app-service.default.svc.cluster.local 查看一下这条DNS
记录,进入到其中一个Pod
里,执行上述查询的结果如下:
nslookup app-service.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: app-service.default.svc.cluster.local
Address: 10.108.26.155
对于Service
的EndPoints
也是有DNS
记录的,因为不是Headless Service
,所以需要用nslookup *.app-service.default.svc.cluster.local查询DNS
记录。
nslookup *.app-service.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: *.app-service.default.svc.cluster.local
Address: 172.17.0.8
Name: *.app-service.default.svc.cluster.local
Address: 172.17.0.6
Name: *.app-service.default.svc.cluster.local
Address: 172.17.0.7
上面查询出来三条DNS
记录,正好跟Service
管控的Pod
数量能够对上。
总结
今天的文章里我结合实例讲述了Kubernetes
里Service
对象的基本使用方法和对象本身的一些原理,其实需要计算机网络知识掌握的好才能从更深层次了解各种模式的Service
的实现原理,这方面的内容推荐极客时间里的专栏文章深入剖析Kubernetes Service[2]。
到这里如果你认真看了我写的关于Kubernetes的这几篇文章,再回看我之前的文章Kubernetes入门实践--部署运行Go项目,就会觉得文章里的例子很好理解了。Kubernetes
的确是学习曲线比较陡峭,我也是在边学边练。希望我的这些入门文章能帮助到想学Kubernetes
的后端程序员们,大家一起进步。
看到这里了,如果喜欢我的文章可以帮我点个赞,我会每周通过技术文章分享我的所学所见,感谢你的支持。微信搜索关注公众号「网管叨bi叨」第一时间获取我的文章推送。
参考资料
Service, DNS与服务发现: https://time.geekbang.org/column/article/68636
[2]深入剖析Kubernetes Service: https://time.geekbang.org/column/article/68636
关注公众号,获取更多精选技术原创文章