官方介绍

k8s service 分为几种类型,分别为:

ClusterIp

(默认类型,每个Node分配一个集群内部的Ip,内部可以互相访问,外部无法访问集群内部)

NodePort

(基于ClusterIp,另外在每个Node上开放一个端口,可以从所有的位置访问这个地址)

LoadBalance

(基于NodePort,并且有云服务商在外部创建了一个负载均衡层,将流量导入到对应Port。要收费的,一般由云服务商提供,比如阿里云、AWS等均提供这种服务, k3s也默认提供了一个lbs - klipper-lb, 本地集群可以使用metallb, metallb解释文档

ExternalName

(将外部地址经过集群内部的再一次封装,实际上就是集群DNS服务器将CNAME解析到了外部地址上,实现了集群内部访问)

例如,以下 Service 定义将 prod 名称空间中的 my-service 服务映射到 my.database.example.com

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Service
metadata:
name: my-service # 不允许'.'
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com # 设置 IP 无效

当查找主机 my-service.prod.svc.cluster.local 时,群集DNS服务返回 CNAME 记录,其值为 my.database.example.com。 访问 my-service 的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。 如果以后您决定将数据库移到群集中,则可以启动其 Pod,添加适当的选择器或端点以及更改服务的类型

所以, externalName 的意义就是为域名设置一个别名,如为 www.baidu.com 设置别名 baiducom,以便 pod 内容器使用

Headless Service【较为特殊】

K8S Service支持headless模式,创建Service时,设置clusterIp=none时,k8s将不再为Service分配clusterIp,即开启headless模式。Headless-Service会将对应的每个 Pod IP 以 A 记录的形式存储。通过dsn lookup访问Headless-Service时候,可以获取到所有Pod的IP信息。

headless service会为其关联的资源分配一个域,通过dns解析该域能获取到每个pod的ip+port<service name>.$<namespace name>.svc.cluster.local

关于port

  • nodePort (NodePort 默认范围是 30000-32767)

外部机器可访问的端口。比如一个Web应用需要被其他用户访问,那么需要配置type=NodePort,而且配置nodePort=30001,那么其他机器就可以通过浏览器访问scheme://node:30001访问到该服务
例如(ClusterIp)MySQL数据库可能不需要被外界访问,只需被内部服务访问,那么不必设置 type=NodePort

1
k8s 能保证在任意 Pod 挂掉时自动启动一个新的,甚至是动态扩容,这就意味着 Pod IP 是会动态变化的。因此这个 Pod IP 你不适合暴露出去,而 Service 可以以标签的形式选定一组带有指定标签的 Pod,并监控和自动负载这些 Pod IP,因此对外只暴露 Service IP 就行了

如果在 Kubernetes 中为一个服务配置了类型为 NodePort 的 Service,但未为该服务配置 nodePort 属性,则会自动分配一个随机的端口号作为该服务的 NodePort。这个随机端口号默认将在 30000 到 32767 的范围内选择,并且与此服务相关的所有节点都会打开该端口。

  • targetPort

容器的端口(和container暴露出来的端口对应),与制作容器时暴露的端口一致(DockerFile中EXPOSE),例如docker.io官方的nginx暴露的是80端口。

1
如果这是一个字符串,它将在目标 Pod 的容器端口(name值)中作为命名端口进行查找。如果未指定,则使用“端口”字段的值
  • port(必须配置项, 用于集群内部服务之间可以访问的端口, [spec.ports[0].port: must be between 1 and 65535)

kubernetes集群内部中的服务之间访问的端口,尽管mysql容器暴露了3306端口, 但是集群内其他容器需要通过3306端口访问该服务,外部机器不能访问mysql服务,因为他没有配置 type=NodePort

  • hostPort
1
2
3
4
5
6
spec:
containers:
- name: xxx
ports:
- containerPort: 8086
hostPort: 10000

关于hostPort,是一种直接定义Pod网络的方式。hostPort是直接将容器的端口与所调度的节点上的端口路由,这样用户就可以通过宿主机的IP加上来访问Pod了

类似nodePort,但这样做有个缺点,因为Pod重新调度的时候该Pod被调度到的宿主机可能会变动,这样就变化了,用户必须自己维护一个Pod与所在宿主机的对应关系。
使用了 hostPort 的容器只能调度到端口不冲突的 Node 上,除非有必要(比如运行一些系统级的 daemon 服务),不建议使用端口映射功能。如果需要对外暴露服务,建议使用NodePort Service

  • 多说下, 关于service下每个port的name, 如果配置了多个port, name则是必须项,配置格式如 123-abcweb ,不要_

关于能否ping通service

ClusterIP/NodePort

这类 svc 都会分配 ClusterIP,这个 IP 地址是 VIP(虚拟 IP),是在所有 node 上添加一些 netfilter 规则,主要有 iptables 和 ipvs 两种方案,能不能 ping 通要看具体实现。

  • iptables:clusterIP 只是 iptables 中的规则,只会处理 ip:port 四层数据包,reject 了 icmp。不能 ping 通。
  • IPVS:clusterIP 会绑定到虚拟网卡 kube-ipvs0,配置了 route 路由到回环网卡,icmp 包是 lo 网卡回复的。可以 ping 通。

Headless: ClusterIP=None

Headless svc 不会分配 clusterIP,而是返回对应 DNS 的 A 记录,如果 svc 后端有3个 pod 则返回 3 个 pod IP。访问 svc 时会随机选择一个 IP,所以 headless svc 是可以 ping 通的。

Loadbalancer

Loadbalancer 类型 svc 也要看云厂商的具体实现。

  • 普通模式:基于 NodePort,LB -> node:nodePort -> pod。ping 的结果跟 NodePort 一致。
  • 直连模式:LB 和 pod 处于同一个 VPC 子网,LB -> pod。ping 的结果跟 Headless 一致。

ExternalName

ExternalName 对应 DNS 的 CNAME,如果配置的域名可以 ping 通则 svc 可以 ping 通。

参考

headless Service和普通Service的区别

headless不分配clusterIP
headless service可以通过解析service的DNS,返回所有Pod的地址和DNS(statefulSet部署的Pod才有DNS)
普通的service,只能通过解析service的DNS返回service的ClusterIP

附录: Kubernetes中的服务发现

创建Pod资源时,kubelet会将其所属名称空间内的每个活动的Service对象以一系列环境变量的形式注入其中。它支持使用Kubernetes Service环境变量以及与Docker的links兼容的变量。

简单来说,服务发现就是服务或者应用之间互相定位的过程。不过,服务发现并非什么新概念,传统的单体应用架构时代也会用到,只不过单体应用的动态性不强,更新和重新发布频度较低,通常以月甚至以年计,基本不会进行自动伸缩,因此服务发现的概念无须显性强调。在传统的单体应用网络位置发生变化时,由IT运维人员手工更新一下相关的配置文件基本就能解决问题。但在微服务应用场景中,应用被拆分成众多的小服务,它们按需创建且变动频繁,配置信息基本无法事先写入配置文件中并及时跟踪反映动态变化,服务发现的重要性便随之凸显。

服务发现机制的基本实现,一般是事先部署好一个网络位置较为稳定的服务注册中心(也称为服务总线),服务提供者(服务端)向注册中心注册自己的位置信息,并在变动后及时予以更新,相应地,服务消费者则周期性地从注册中心获取服务提供者的最新位置信息从而“发现”要访问的目标服务资源。复杂的服务发现机制还能够让服务提供者提供其描述信息、状态信息及资源使用信息等,以供消费者实现更为复杂的服务选择逻辑。

实践中,根据其发现过程的实现方式,服务发现还可分为两种类型:客户端发现和服务端发现。

客户端发现:由客户端到服务注册中心发现其依赖到的服务的相关信息,因此,它需要内置特定的服务发现程序和发现逻辑。

服务端发现:这种方式额外要用到一个称为中央路由器或服务均衡器的组件;服务消费者将请求发往中央路由器或者负载均衡器,由它们负责查询服务注册中心获取服务提供者的位置信息,并将服务消费者的请求转发给服务提供者。

由此可见,服务注册中心是服务发现得以落地的核心组件。事实上,DNS可以算是最为原始的服务发现系统之一,不过,在服务的动态性很强的场景中,DNS记录的传播速度可能会跟不上服务的变更速度,因此它不并适用于微服务环境。另外,传统实践中,常见的服务注册中心是ZooKeeper和etcd等分布式键值存储系统。不过,它们只能提供基本的数据存储功能,距离实现完整的服务发现机制还有大量的二次开发任务需要完成。另外,它们更注重数据一致性,这与有着更高的服务可用性要求的微服务发现场景中的需求不太相吻合。

Netflix的Eureka是目前较流行的服务发现系统之一,它是专门开发用来实现服务发现的系统,以可用性目前为先,可以在多种故障期间保持服务发现和服务注册的功能可用,其设计原则遵从“存在少量的错误数据,总比完全不可用要好”。另一个同级别的实现是Consul,它是由HashiCorp公司提供的商业产品,不过还有一个开源基础版本提供。它于服务发现的基础功能之外还提供了多数据中心的部署能力等一众出色的特性。

尽管传统的DNS系统不适于微服务环境中的服务发现,但SkyDNS项目(后来称kubedns)却是一个有趣的实现,它结合古老的DNS技术和时髦的Go语言、Raft算法并构建于etcd存储系统之上,为Kubernetes系统实现了一种服务发现机制。Service资源为Kubernetes提供了一个较为稳定的抽象层,这有点类似于服务端发现的方式,于是也就不存在DNS服务的时间窗口的问题。

Kubernetes自1.3版本开始,其用于服务发现的DNS更新为了kubeDNS,而类似的另一个基于较新的DNS的服务发现项目是由CNCF(Cloud Native Computing Foundation)孵化的CoreDNS,它基于Go语言开发,通过串接一组实现DNS功能的插件的插件链进行工作。自Kubernetes 1.11版本起,CoreDNS取代kubeDNS成为默认的DNS附件。不过,Kubernetes依然支持使用环境变量进行服务发现。