首先需要明确一件事,那就是Service、DNS与服务发现中说明的Service的访问信息只能在k8s集群当中使用。也可以说Service是集群当中的Pod互相访问使用的对象。

那么外界肯定是要与集群交互的,而我们当然也想要它能够通过Service来访问,这样才能够充分利用Service的功能。

所以,在使用 Kubernetes 的 Service 时,一个必须要面对和解决的问题就是:如何从外部(Kubernetes 集群之外),访问到 Kubernetes 里创建的 Service?

而解决这个问题的常见方法就是==NodePort==。

NodePort

在Service的定义里,声明类型是type=NodePort

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - nodePort: 8080
    targetPort: 80
    protocol: TCP
    name: http
  - nodePort: 443
    protocol: TCP
    name: https
  selector:
    run: my-nginx

在ports字段里声明了Service的8080端口代理Pod的80端口,Service的443端口代理Pod的443端口。如果你不显式地声明 nodePort 字段,Kubernetes 就会为你分配随机的可用端口来设置代理。这个端口的范围默认是 30000-32767,你可以通过 kube-apiserver 的–service-node-port-range 参数来修改它。

要想访问这个Service,只要

<任何一台宿主机的IP地址>:8080

就可以访问到某一个被代理的Pod的80端口了。

NodePort模式就是在每台宿主机上生成这样一条iptables规则:

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx: nodePort" -m tcp --dport 8080 -j KUBE-SVC-67RL4FN6JRUPOJYM

KUBE-SVC-67RL4FN6JRUPOJYM 其实就是一组随机模式的 iptables 规则。

NodePort模式下,k8s会在IP包离开宿主机发往Pod时,进行一次SNAT操作,将IP包的源IP地址替换成这台宿主机的IP或者cni网桥的IP。这是因为外界是与这个宿主机进行交互的,所以将IP包发送给Pod之后,响应需要先到达宿主机,再返回给外界客户端,否则外界客户端会发生错误。这种情景下也说明了

如果Pod需要明确知道请求来源,那么需要将 Service 的 spec.externalTrafficPolicy 字段设置为 local,这就保证了所有 Pod 通过 Service 收到请求之后,一定可以看到真正的、外部 client 的源地址。这个机制的实现原理也非常简单:这时候,一台宿主机上的 iptables 规则,会设置为只将 IP 包转发给运行在这台宿主机上的 Pod。所以这时候,Pod 就可以直接使用源地址将回复包发出,不需要事先进行 SNAT 了。这也意味着,如果请求的Node上没有代理的Pod,那么请求就会失败。

指定LoadBalancer类型的Service

这种方式适用于公有云上的k8s服务

---
kind: Service
apiVersion: v1
metadata:
  name: example-service
spec:
  ports:
  - port: 8765
    targetPort: 9376
  selector:
    app: example
  type: LoadBalancer

公有云上使用了一个叫做CloudProvider 的转接层,这个转接层是用来跟公有云本身的API进行对接的。当创建了一个LoadBalancer类型的Service后,k8s就会调用CloudProvider的接口在公有云上创建一个负载均衡服务,并把被代理的Pod的IP地址配置给负载均衡服务。

其实就是在公有云上启动了一个负载均衡服务,让外界能够通过这个服务获取到被Service代理的Pod的IP列表。

ExternalName

这是k8s在1.7之后推出的新特性。

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  type: ExternalName
  externalName: my.database.example.com

这里不需要指定selector。ExternalName 类型的 Service,其实是在 kube-dns 里为你添加了一条 CNAME 记录。这时,访问 my-service.default.svc.cluster.local 就和访问 my.database.example.com 这个域名是一个效果了。

相当于一个外部可以访问的DNS域名别名。还可以为Service分配共有IP地址。比如下面:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  externalIPs:
  - 80.11.12.10

可以通过访问 80.11.12.10:80 访问到被代理的 Pod 了。不过,在这里 Kubernetes 要求 externalIPs 必须是至少能够路由到一个 Kubernetes 的节点,不然如何与k8s的Node通信。