前面几篇文章我们都是使用的 ClusterIP 供集群内部访问, 每个 Pod 都有一个自己的 IP 地址, 那么问题来了: 当控制器使用新的 Pod 替代发生故障的 Pod 时又或者增加新的副本 Pod 时, 新 Pod 会分配到新的 IP 地址, 那么想要对外提供服务时, 客户端如何找到并访问这个服务? 没关系, 别抠脑壳了, 本文介绍的 Service 就是解决方案.
一, 认识 Service
1.1 什么是 Service?
Service 是一个抽象概念, 它定义了逻辑集合下访问 Pod 组的策略. 通过使用 Service, 我们就可以不用关心这个服务下面的 Pod 的增加和减少, 故障重启等, 只需通过 Service 就能够访问到对应服务的容器, 即通过 Service 来暴露 Pod 的资源.
这样说可能还是有点难懂, 举个例子, 假设我们的一个服务 Service A 下面有 3 个 Pod, 我们都知道 Pod 的 IP 都不是持久化的, 重启之后就会有变化. 那么 Service B 想要访问 Service A 的 Pod, 它只需跟绑定了这 3 个 Pod 的 Service A 打交道就可以了, 无须关心下面的 3 个 Pod 的 IP 和端口等信息的变化. 换句话说, 就像一个 Service Discovery 服务发现的组件, 你无须关心具体服务的 URL, 只需知道它们在服务发现中注册的 Key 就可以通过类似 Consul,Eureka 之类的服务发现组件中获取它们的 URL 一样, 还是实现了负载均衡效果的 URL.
1.2 Service 的几种类型
(1)ClusterIP
ClusterIP 服务是 Kubernetes 的默认服务. 它给你一个集群内的服务, 集群内的其它应用都可以访问该服务, 但是集群外部无法访问它.
因此, 这种服务常被用于内部程序互相的访问, 且不需要外部访问, 那么这个时候用 ClusterIP 就比较合适, 如下面的 YAML 文件所示:
- apiVersion: v1
- kind: Service
- metadata:
- name: my-internal-service
- selector:
- App: my-App
- spec:
- type: ClusterIP
- ports:
- - name: http
- port: 80
- targetPort: 80
- protocol: TCP
那么, 如果需要从外部访问呢 (比如我们在开发模式下总得调试吧)? 可以启用 K8S 的代理模式:
$ kubectl proxy --port=8080
如此一来, 便可以通过 K8S 的 API 来访问了, 例如下面这个 URL 就可以访问在 YAML 中定义的这个 my-internal-service 了:
http://localhost:8080/API/v1/proxy/namespaces/default/services/my-internal-service:http/
PS:ClusterIP 是一个虚拟 IP, 由 K8S 节点上的 iptables 规则管理的. iptables 会将访问 Service 的流量转发到后端 Pod, 而且使用类似于轮询的负载均衡策略转发的.
(2)NodePort
除了只在内部访问的服务, 我们总有很多是需要暴露出来公开访问的服务吧. 在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口, 这样就可以通过 < NodeIP>:NodePort 来访问这些服务. 例如, 下面这个 YAML 中定义了服务为 NodePort 类型:
- apiVersion: v1
- kind: Service
- metadata:
- name: my-nodeport-service
- selector:
- App: my-App
- spec:
- type: NodePort
- ports:
- - name: http
- port: 80
- targetPort: 80
- nodePort: 30036
- protocol: TCP
PS: 这种方式顾名思义需要一个额外的端口来进行暴露, 且端口范围只能是 30000-32767, 如果节点 / VM 的 IP 地址发生变化, 你需要能处理这种情况.
(3)LoadBalancer
LoadBalancer 服务是暴露服务到 internet 的标准方式, 它借助 Cloud Provider 创建一个外部的负载均衡器, 并将请求转发到 < NodeIP>:NodePort(向节点导流).
例如下面这个 YAML 中:
- kind: Service
- apiVersion: v1
- metadata:
- name: my-service
- spec:
- selector:
- App: MyApp
- ports:
- - protocol: TCP
- port: 80
- targetPort: 9376
- clusterIP: 10.0.171.239
- loadBalancerIP: 78.11.24.19
- type: LoadBalancer
- status:
- loadBalancer:
- ingress:
- - ip: 146.148.47.155
PS: 每一个用 LoadBalancer 暴露的服务都会有它自己的 IP 地址, 每个用到的 LoadBalancer 都需要付费, 这将是比较昂贵的花费.
二, Service 的创建与运行
2.1 创建 Deployment
这里仍然以我们的一个 ASP.NET Core webAPI 项目为例, 准备一个 Deployment 的 YAML 文件:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: edc-webapi-deployment
- namespace: aspnetcore
- spec:
- replicas: 2
- selector:
- matchLabels:
- name: edc-webapi
- template:
- metadata:
- labels:
- name: edc-webapi
- spec:
- containers:
- - name: edc-webapi-container
- image: edisonsaonian/k8s-demo
- ports:
- - containerPort: 80
- imagePullPolicy: IfNotPresent
这里我们需要注意的就是给该 Deployment 标注 selector 的 matchLabels 以及 template 中的 labels, 这是一个 Key/Value 对, 用于后续 Service 来匹配要挑选哪些 Pod 作为 Service 的后端, 即需要给哪些 Pod 提供服务发现以及负载均衡的效果.
同样, 通过 kubectl 创建资源:
kubectl apply -f edc-API.YAML
然后, 通过 curl 命令验证一下:(这里的两个 IP 地址是 ClusterIP, 它们分别位于我的两个 K8S Node 节点上)
- curl 10.244.1.40/API/values
- curl 10.244.2.31/API/values
可以看到, 我们的 ASP.NET Core WebAPI 正常的返回了 JSON 数据.
2.2 创建 Service
接下来我们就为上面的两个 Pod 创建一个 Service:
- apiVersion: v1
- kind: Service
- metadata:
- name: edc-webapi-service
- namespace: aspnetcore
- spec:
- ports:
- - port: 8080
- targetPort: 80
- selector:
- name: edc-webapi
这里需要注意的几个点:
(1)port : 8080 => 指将 Service 的 8080 端口映射到 Pod 的对应端口上, 这里 Pod 的对应端口由 targetPort 指定.
(2)selector => 指将具有 name: edc-webapi 这个 label 的 Pod 作为我们这个 Service 的后端, 为这些 Pod 提供统一 IP 和端口.
这里我们来进行验证一下:
- kubectl get service -n aspnetcore
- curl 10.1.59.71:8080/API/values
可以看到, 默认情况下 Service 的类型时 ClusterIP, 只能提供集群内部的服务访问. 如果想要为外部提供访问, 那么需要改为 NodePort.
2.3 使用 NodePort
下面为 Service 增加 NodePort 访问方式:
- apiVersion: v1
- kind: Service
- metadata:
- name: edc-webapi-service
- namespace: aspnetcore
- spec:
- type: NodePort
- ports:
- - port: 8080
- targetPort: 80
- selector:
- name: edc-webapi
再次进行创建, 会覆盖已有配置:
kubectl apply -f edc-API-service.YAML
再次进行验证, 会发现已经改为了 NodePort 方式:
这里的 PORT 已经变为了 8080:32413, 意味着它将 Service 中的 8080 端口映射到了 Node 节点的 32413 端口, 我们可以通过访问 Node 节点的 32413 端口获取 ASP.NET Core WebAPI 返回的接口数据了.
访问 k8s-node1:
访问 k8s-node2:
2.4 指定特定端口
刚刚的 NodePort 默认情况下是随机选择一个端口 (30000-32767 范围内), 不过我们可以使用 nodePort 属性指定一个特定端口:
- apiVersion: v1
- kind: Service
- metadata:
- name: edc-webapi-service
- namespace: aspnetcore
- spec:
- type: NodePort
- ports:
- - nodePort: 31000
- port: 8080
- targetPort: 80
- selector:
- name: edc-webapi
这里我们自己指定了一个外部访问端口: 31000, 通过 kubectl 覆盖之后, 我们再次验证一下:
访问 k8s-node1:
访问 k8s-node2:
最后, 再次总结一下三个端口配置:
(1)nodePort => Node 节点上监听的端口, 也就是外部访问的 Service 端口
(2)port => ClusterIP 上监听的端口, 即内部访问的 Service 端口
(3)targetPort => Pod 上监听的端口, 即容器内部的端口
三, DNS 访问 Service
Kubernetes 默认安装了一个 dns 组件 coredns, 它位于 kube-system 命名空间中:
每当有新的 Service 被创建时, coredns 会添加该 Service 的 DNS 记录, 于是在 Cluster 中的 Pod 便可以通过 servicename.namespacename 来访问 Service 了, 从而可以做到一个服务发现的效果.
这里我们来验证一下, 通过临时创建一个 busybox Pod 来访问 edc-webapi-service.aspnetcore:8080:
kubectl run busybox --rm -ti --image=busybox /bin/sh
可以看到, coredns 组件为刚刚创建的 Service edc-webapi-service 创建了 DNS 记录, 在 Cluster 中的 Pod 无须知道 edc-webapi-service 的 IP 地址只需要知道 ServiceName 即可访问到该 Service.
四, 小结
本文介绍了 K8S 中 Service 的基本概念及常用类型, 然后通过一个具体的例子演示了如何创建 Service 和使用 NodePort 的方式对外提供访问, 最后介绍了如何通过 DNS 的方式访问 Service 从而实现服务发现的效果. 当然, 笔者也是初学, 很多东西没有介绍到, 也请大家多多参考其他资料更加深入了解.
参考资料
(1)CloudMan,《每天 5 分钟玩转 Kubernetes https://item.jd.com/12329528.html 》
(2) 李振良,《一天入门 Kubernets 教程 https://edu.51cto.com/course/16518.html 》
(3) 马哥 (马永亮),《Kubernetes 快速入门》
(4) 小黑老,《K8S 中 Service 的理解 https://www.jianshu.com/p/1555c9b7fccd 》
作者: 周旭龙 http://www.edisonchou.cn/
来源: https://www.cnblogs.com/edisonchou/p/aspnet_core_on_k8s_deepstudy_part4.html