从 docker 到 compose 再到 k8s,容器化是越来越火了,应该也有不少小伙伴和我一样跃跃欲试。但是马克思告诉我们要一分为二地看待问题。容器化固然好,不过也不是所有服务都适合这么搞。典型的,数据库服务是否适合放在 k8s 中就一直饱受争议。

这次的问题也起于此。通过 Service 暴露内部服务到外部已经轻车熟路了,倒过来则讨论少了许多。究其原因,我觉得是太简单,甚至不值得讨论——直接访问域名和 ip 不就行咯。

确实可以,但不够「优雅」, 尤其当我们不确定某个服务是否会在某天也容器化时。这非常符合处于过渡阶段的项目。

服务间通讯

首先来看一下集群内部服务之间的通讯。官方文档传送门🌀

这个太简单了,默认情况下,k8s 会根据服务名,为每个服务都创建一个内部的 FQDN,格式为 my-svc.my-namespace.svc.cluster-domain.example。集群内的其他服务可以通过此域名直接访问。

访问集群外服务

那么我们想,如果把这种思路用于外部服务,岂不是太好了。

这样一来,随着容器化的推进,就可以无缝切换,不需要重新调整服务地址的配置。

Service - ExternalName

搂一眼配置:

kind: Service
apiVersion: v1
metadata:
  name: pgsql
  namespace: default
  ports:
    - port: 5432
spec:
  type: ExternalName
  externalName: pgsql.mydomain.com

这也是一个 Service,只不过没有选择器。访问这个服务所对应的 FQDN 时,实际将解析到 externalName 所指定的域名对应的 IP。最常见的用例应该就是购买云计算厂商的数据库,通常会给我们一个地址用于连接。通过这种方式,相当于把外部地址和内部地址做了映射。

这种方式内部其实利用了 DNS 的 CNAME 技术。这就意味着 externalName 必须是一个有效的域名,而不能 IP 地址。 如果… 就是想要 IP 呢?

Endpoint

其实 Service 一直都有 Endpoint,只不过因为是自动的而被我们忽略。通常,Service 通过选择器与 Pod 关联,这时候 k8s 就会根据 Pod 的信息搞出一个 Endpoint 作为访问点。那… 我是不是可以手动指定它呢?

apiVersion: v1
kind: Service
metadata:
  name: pgsql
spec:
  ports:
    - port: 5432
---
apiVersion: v1
kind: Endpoints
metadata:
  name: pgsql
subsets:
  - addresses:
      - ip: 200.1.2.3
    ports:
      - port: 5432

这同样是一个没有选择器的 Service,但被手动指定了 Endpoint。可以类比地理解为,手动给 FQDN 设置 A 记录。

通过上面两种方式,我们成功在外部服务和 Pod 中创建了一个抽象层。未来如果外部服务也容器化,只需要改变 Service 的选择器即可,使用到这些服务的组件可以自动适配。