搜索
您的当前位置:首页正文

k8s 中的服务如何沟通

来源:二三娱乐

本文将介绍 k8s 中的服务如何相互访问,例如后端服务访问数据库,不同类型的服务间的相互访问。并介绍用于实现这种访问的资源 服务service

挡在 pod 身前的 service

我们现在已经有了 pod ,那让他们相互访问不可以么?答案是,不可以,因为 pod 是有生命周期的,他可能随时被创建也可能随时被销毁,而 每次新建 pod 就会给其分配一个随机的 ip,并且 k8s 也会自动调控 pod 的数量。这就导致了 pod 之间直接访问是不现实的,如果有一个入口,可以动态绑定那些提供相同服务的 pod,并将其开放在固定端口上,这样访问起来不就方便很多了么?这个入口在 k8s 中被称为service

挡在 pod 前的 service

首先新建kubia-replicaset.yaml文件,并填入如下内容:

apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - containerPort: 8080

这个文件将创建 3 个 pod,每个 pod 都包含一个app: kubia的标签,并开放提供服务的8080端口。执行如下命令进行创建:

kubectl create -f kubia-replicaset.yaml

然后kubectl get po就可以看到创建出的三个 pod,接下来我们创建一个svc来提供对这三个 pod 的访问。新建kubia-svc.yaml文件,并填入如下内容:

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http # 请为所有端口指定名称
    port: 80 # 对外开放的服务端口
    targetPort: 8080 # 后方 pod 的服务端口
  selector:
    app: kubia

可以看到基本svc的配置非常简单,只定义了两个端口和一个选择器,我们在选择器中注明了app: kubia,意思就是让这个svc去将所有携带app: kubia标签的 pod 纳入自己的后方。其实ports.name字段并不是必填项,但是为了方便维护,请为每个端口指定名称。然后我们使用下面命令创建这个svc

kubectl create -f 

创建好了之后来看一下,执行kubectl get svc kubia,我们就可以看到他的信息了:

NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubia   ClusterIP   10.98.237.175   <none>        80/TCP    127m

可以在CLUSTER-IP列看到这个当前的 ip 地址。也就是说,其他 pod 就可以通过这个 ip 访问到其后面的 pod。接下来我们随便使用一个 pod 访问下这个服务。执行kubectl get po并选择一个顺眼的 pod。然后使用下面的命令进行访问svc注意修改 pod 名和 svc 的 ip 地址

kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 

这条命令里的--是一个分隔符,之前的部分是属于kubectl的,之后的是属于要在 pod 内部执行的命令的。

然后就可以看到来自svc后方 pod 的响应了:

root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-pxfw7
root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-7rt2n
root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-7rt2n
root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-fxqcc

可以看到,svc同时也实现了负载均衡,合理的将请求平摊到了每一个 pod 上。

service 对 pod 的动态绑定

因为svc是通过我们事先定义好的标签选择器来查找 pod 的,所以 pod 的 ip 地址变动对于svc毫无影响,其实在svcpod之间还包含了一个资源叫做endpointendpoint(简称ep)是一组地址及其端口的合集,如下图,只要一个svc有标签选择器的话,他就会自动创建一个同名的ep来标记出自己的要管理的 pod。

svc、ep、pod之间的关系

我们可以通过如下命令来查看我们刚才创建的kubia服务的ep

root@master1:~# kubectl describe svc kubia
Name:              kubia
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=kubia
Type:              ClusterIP
IP:                10.98.237.175
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         10.244.1.18:8080,10.244.2.14:8080,10.244.3.14:8080
Session Affinity:  None
Events:            <none>

然后就可以在Endpoints列中找到他包含的地址及端口号,这三个用,分隔的地址正是三个 pod 的地址。你可以使用kubectl get pod -o wide来查看 pod 的地址。你可以执行如下命令来重建所有的kubia pod,然后再来查看ep,会发现其ENDPOINTS也会自动更换成这三个 pod 的值。

kubectl delete po -l app=kubia
root@master1:~# kubectl get endpoints kubia
NAME    ENDPOINTS                                            AGE
kubia   10.244.1.18:8080,10.244.2.14:8080,10.244.3.14:8080   169m

发现服务

root@master1:~# k exec kubia-5n2m2 -- curl -s http://kubia
You've hit kubia-bv2k8

这种方式可以访问同一命名空间中的服务,k8s 也支持访问其他命名空间的服务。不过域名要长很多。有兴趣的话可以自行了解。

如果你发现你访问不到服务的话请使用kubectl delete po -l app=kubia重建 pod。因为 k8s 只会在创建时间晚于服务的 pod 中注入服务域名。你可以在容器中查看/etc/resolv.conf文件来找到对应的解析。

顺带一提,这个功能是 k8s 的 dns 服务器coredns实现的,你可以在命名空间kube-system中找到它,让我们为它鼓掌!

访问集群外部的服务

现在问题又来了,对于一个集群内部的 pod 来说,如果他想访问一个集群外部的服务该怎么办呢?例如一个网站的公共 api,或者是一个云数据库。我们就没办法使用svc+标签选择器的方式来获取这些服务了,因为标签选择器只能监测集群内部的 pod 。而无法放眼外部。那么我们应该怎么做呢?

还记得我们之前介绍过的endpoint资源么,没错。我们可以 自定义一个endpoint资源,用它指定外部服务的 ip 及端口,然后绑定到一个svc,这样内部的 pod 不就通过完全一样的方式访问外部服务了么?

使用ep手动指定外部资源

你可以把这张图拿去和"service 对 pod 的动态绑定"小节中的图做对比,你会发现最根本的service > endpoint > 服务提供者的传递流程是没有变的。ok,现在我们来动手操作一下,首先新建一个什么都不会做的呆萌svc,它只告诉别人自己有一个端口80可以提供访问:

external-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  ports:
  - port: 80

然后通过kubectl create -f external-service.yaml创建它。然后查看一下他的信息:

root@master1: ~# kubectl describe svc external-service
Name:              external-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP:                10.103.101.18
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         <none>
Session Affinity:  None
Events:            <none>

因为没有标签选择器,所以它不会自己去创建endpoint,现在我们来手动为它创建一个endpoint,只要是和svc同名,那么他们两个就会自动绑定在一起:

external-endpoint.yaml

apiVersion: v1
kind: Endpoints
metadata:
  # 和 svc 相同的名称
  name: external-service
subsets:
  - addresses:
    # 这里指定了外部服务的 ip
    - ip: 11.11.11.11
    # 可以指定多个
    - ip: 22.22.22.22
    # 还要指定端口号
    ports:
    - port: 80

然后使用命令kubectl create -f external-endpoint.yaml创建endpoint,之后再查看svc的详情,你会发现他们已经完成了绑定。

root@master1:~/k8s-yaml# kubectl describe svc external-service       
Name:              external-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP:                10.103.101.18
Port:              <unset>  80/TCP
TargetPort:        80/TCP
# 看这里!
Endpoints:         11.11.11.11:80,22.22.22.22:80
Session Affinity:  None
Events:            <none>

其实还有个更简单的方法,可以 通过在svc里的配置项中指定spec.typeExternalName,然后在使用spec.externalName字段来指定外部服务的完全限定域名,如下:

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  # 要先指定 svc 的类型
  type: ExternalName
  # 再在这里指定外部服务的完全限定域名
  externalName: 
  ports:
  - port: 80

这样就可以免于新建一个endpoint来直接实现对外部服务的访问。而且使用方式也完全没有变化。

总结

本篇文章讲解了 k8s 中服务的访问入口资源service以及请求的”终点“endpoint。任何可以提供实际请求处理能力的pod最终都要封装成统一的service才能对外提供服务。而service中的标签选择器则可以一直监测pod的状态,从而实现无论是pod增加还是减少对外部请求者来说都是无感知的。

Top