ljzsdut
GitHubToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

3.2 K8s如何引入外部服务

k8s访问外部服务的方式:

  • 直接访问ip+port,缺点:如果更换ip,应用需要修改配置文件并重载

  • ExternalName类型的Service,缺点:无法进行端口映射

  • 定义没有选择器的service+同名的endpoints,缺点:如果更换ip,需要更新endpoints资源配置

使用endpoint映射外部服务

针对k8s原生应用,k8s提供了一个简单的Endpoints API,当服务中的一组pod发生更改时,该API就会更新。对于非本机应用程序,Kubernetes提供了一个基于虚拟ip的服务桥接器,服务将重定向到后端pod。

endpoint 是k8s集群中一个资源对象,存储在etcd里面,用来记录一个service对应的所有pod的访问地址。service配置了selector,endpoint controller才会自动创建对应的endpoint对象,否则是不会生产endpoint 对象。services-without-selectors

一个service由一组后端的pod组成,这些后端的pod通过service endpoint暴露出来,如果有一个新的pod创建创建出来,且pod的标签名称(label:pod)跟service里面的标签(label selector 的label)一致会自动加入到service的endpoints 里面,如果pod对象终止后,pod 会自动从edponts 中移除。在集群中任意节点可以使用curl请求service的<CLUSTER-IP>:<PORT>

endpoints: 实际上servce服务后端的pod端点集合。

service不仅可以代理pod,还可以代理任意其它的后端,比如运行在k8s集群外部的服务mysql mysql (如果需要从k8s里面链接外部服务(mysql)需要定义同名的service和endpoint)。这种组合可以理解为是一种静态服务,如果后期需要将有状态服务迁移到k8s里面,则代码不需要任何修改,只需要修改svc即可。

使用场景:

在集群外部托管自己的数据库,例如在阿里云的ECS实例中。如果您在 Kubernetes 内部运行无状态的应用和外部运行一些数据库服务,并且希望未来某个时候您可以将所有服务都移入集群内,但在此之前将是“内外混用”的状态。幸运的是,您可以使用静态 Kubernetes 服务来缓解上述痛点。如此,根本不需要在代码中使用 IP 地址!如果以后 IP 地址发生变化,您可以为endpoints更新 IP 地址,而应用无需进行任何更改。

示例:创建service+endpoints关联外部服务

  • 创建service (mysql-service-extenal)

此服务没有 Pod 选择器。此操作将创建一个服务,但它不知道往哪里发送流量。这样一来,您可以手动创建一个将从此服务接收流量的 Endpoints 对象(条件是svc和ep的name是相同的,就会自动关联)。

kind: Service
apiVersion: v1
metadata:
  name: mysql
  namespace: default
spec:
  ports:
  - port: 3306
    name: mysql
    targetPort: 3306
  • 创建 endpoint(mysql-endpoint)

service 和endpoint的名称相同, 且在一个命名空间下面

kind: Endpoints
apiVersion: v1
metadata: 
  name: mysql
  namespace: default
subsets:
- addresses:
  - ip: 192.168.3.24
  ports:
   - port: 3306
     name: mysql
  • 创建静态service
[root@vm-3-30 aa]# kubectl  apply -f svc.yaml 
service/mysql created
[root@vm-3-30 aa]# kubectl describe svc mysql
Name:              mysql
Namespace:         default
Labels:            <none>
Annotations:       Selector:  <none>
Type:              ClusterIP
IP:                10.43.195.223
Port:              mysql  3306/TCP
TargetPort:        3306/TCP
Endpoints:         <none>   #因为没有标签选择器,此处为none
Session Affinity:  None
Events:            <none>
[root@vm-3-30 aa]# kubectl  apply -f ep.yaml 
endpoints/mysql created
[root@vm-3-30 aa]# kubectl  get ep mysql
NAME    ENDPOINTS           AGE
mysql   192.168.3.24:3306   9s
[root@vm-3-30 aa]# kubectl describe svc mysql
Name:              mysql
Namespace:         default
Labels:            <none>
Annotations:       Selector:  <none>
Type:              ClusterIP
IP:                10.43.195.223
Port:              mysql  3306/TCP
TargetPort:        3306
Endpoints:         192.168.3.24:3306  #配置同名的endpoints后,svc自动关联ep。
Session Affinity:  None
Events:            <none>

Kubernetes 将 Endpoints 中定义的所有 IP 地址视为与常规 Kubernetes Pod 一样。

使用ExternalName类型的Service引用外部服务

使用场景:

如果您使用的是来自第三方的托管数据库服务,例如阿里云的rds,它们可能会为您提供可用于连接的统一资源标识符 (URI)。这些数据库的连接字符串如下所示:

mongodb://:@ds149763.mlab.com:49763/devmongodb://:@ds145868.mlab.com:45868/prodmLab

可以看到上面的连接中提供了动态URI和动态端口。这种情景中。我们可以创建一个 “ExternalName” Kubernetes 服务,此服务为您提供将流量重定向到外部服务的静态 Kubernetes 服务。此服务在内核级别执行简单的 CNAME 重定向,因此对性能的影响非常小。

示例:“ExternalName” Kubernetes 服务

kind: Service
apiVersion: v1
metadata:
  name: mongo
spec:
  type: ExternalName
  externalName: ds149763.mlab.com   #必须是FQDN格式,不能是ip

现在,您可以使用更简化的连接字符串:

mongodb://:@mongo:/dev

由于 “ExternalName” 使用 CNAME 重定向,因此无法执行端口重映射。对于使用静态端口的服务来说,这可能不成问题,然而本例中使用的是动态端口。这意味着您需要对开发和生产数据库使用不同的连接字符串。但如果您可以获取 IP 地址,就可以执行端口重映射,详见下例。

示例:具有 URI 和端口重映射功能的远程托管数据库

首先我们对rds的连接字串中的host进行dns解析,获取到期其IP。然后创建一个重新映射rds端口的服务,并为此 IP 地址创建端点。

kind: Service
apiVersion: v1
metadata:
  name: mongo
spec:
  ports:
  – port: 27017
    targetPort: 49763
—--
kind: Endpoints
apiVersion: v1
metadata:
  name: mongo
subsets:
– addresses:
  – ip: 35.188.8.12
  ports:
  – port: 49763

示例:

apiVersion: v1
kind: Service
metadata:
  name: "baiducom" # 设置IP(14.215.177.39)和域名(www.baidu.com)错误,不允许使用'.'
spec:
  type: ExternalName
  externalName: "www.baidu.com" # 设置 IP 14.215.177.39 无效

# 所以 externalName 的意义就是为域名设置一个别名,如上为"www.baidu.com"设置别名"baiducom",以便pod内容器使用
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox
  labels:
    app: busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
    spec:
      containers:
        - name: busybox
          image: busybox
          command: ["/bin/sh", "-c", "curl baiducom"]

上例中,externalName除了设置为真实的域名"www.baidu.com"外,也可以使用svc的域名;可以通过这种方式实现为其他namespace的svc在自己的ns中定义一个svc的别名,实现用访问自己ns中的svc来访问其他ns的中svc。例如:

apiVersion: v1
kind: Service
metadata:
  name: "mysql"
  namespace: app-ns
spec:
  type: ExternalName
  externalName: "mysql.db-ns.svc.cluster.local"

db-ns名称空间中有一个服务为:mysql.db-ns.svc.cluster.local

app-ns名称空间中有一个app要访问上面的mysql,可以直接使用mysql -h mysql访问即可,无需使用mysql -h mysql.db-ns的方式。