35

学练结合,快速掌握 Kubernetes Service

 3 years ago
source link: https://mp.weixin.qq.com/s/VJIwipm5lR62uAPVkiUXWQ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今天这篇文章里我们来讲一下 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 ,同时会自动创建 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 -

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

qeMRvmN.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK