查看原文
其他

学练结合,快速掌握Kubernetes Service

网管 网管叨bi叨 2022-09-06

今天这篇文章里我们来讲一下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

和之前文章里介绍的PodReplicaSetDeployment一样,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,同时会自动创建NodePortClusterIP类型的ServiceLoadBalancer会把请求路由到NodePortClusterIP类型的Service上。
  • ExternalName。ExternalName 类型的 Service,是在 kube-dns 里添加了一条 CNAME 记录。这个CNAME记录是在Service的spec.externalName里指定的,

以上四种类型除了ExternalNameKuberneteskube-proxy组件都会为Service提供VIP(虚拟IP),kube-proxy支持两种模式:iptablesipvs。涉及到不少知识,感兴趣的可以去极客时间上看这篇文章: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

Serviceselector选中的Pod,就称为ServiceEndpoints,可以使用 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,才会出现在ServiceEndpoints 列表里。当某一个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>访问ServiceEndPointsService选中的Pod)。
  • nodePort:指定向集群外部暴露Service 所使用的端口,从集群外部使用<NodeIp>:<NodePort>访问ServiceEndPoints。如果你不显式地声明 nodePort 字段,会随机分配可用端口来设置代理。这个端口的范围默认是 30000-32767。
  • targetPorttargetPort是后面的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发给不同的应用PodPod里的应用就是在原来的文章里一直使用的例子的基础上加了一行获取系统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

对于ServiceEndPoints 也是有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数量能够对上。

总结

今天的文章里我结合实例讲述了KubernetesService对象的基本使用方法和对象本身的一些原理,其实需要计算机网络知识掌握的好才能从更深层次了解各种模式的Service的实现原理,这方面的内容推荐极客时间里的专栏文章深入剖析Kubernetes Service[2]

到这里如果你认真看了我写的关于Kubernetes的这几篇文章,再回看我之前的文章Kubernetes入门实践--部署运行Go项目,就会觉得文章里的例子很好理解了。Kubernetes的确是学习曲线比较陡峭,我也是在边学边练。希望我的这些入门文章能帮助到想学Kubernetes的后端程序员们,大家一起进步。

看到这里了,如果喜欢我的文章可以帮我点个赞,我会每周通过技术文章分享我的所学所见,感谢你的支持。微信搜索关注公众号「网管叨bi叨」第一时间获取我的文章推送。

参考资料

[1]

Service, DNS与服务发现: https://time.geekbang.org/column/article/68636

[2]

深入剖析Kubernetes Service: https://time.geekbang.org/column/article/68636

- END -


关注公众号,获取更多精选技术原创文章



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存