45. Kubernetes深入Service-Service服务发现

释放双眼,带上耳机,听听看~!

微服务意味着存在更多的独立服务,但它们并非独立的个体,而是存在着复杂的依赖关系且彼此之间通常需要进行非常频繁地交互和通信的群体。然而,建立通信之前,服务和服务之间该如何得知彼此的地址呢?在Kubernetes系统上,Service为Pod中的服务应用提供来一个稳定的访问入口,但Pod客户端中的应用如何得知某个特定Service资源的IP和端口呢?这个时候就需要引入服务发现(Service Discovery)的机制。

服务发现概述

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

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

  • 客户端发现:由客户端到注册中心发现其依赖的服务相关信息,因此它需要内置特定的服务发现程序和发现逻辑。
  • 服务端发现:这种方式需要额外用到一个称为中央路由器或服务均衡器的组件;服务消费者将请求发往中央路由器或者负载均衡器,由它们负责查询服务注册中心获取服务提供者的位置信息,并将服务消费者的请求转发给服务提供者。

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

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

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

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

服务发现方式:环境变量

为了解决上面的问题,在之前的版本中,Kubernetes 采用了环境变量的方法,每个 Pod 启动的时候,会通过环境变量设置所有服务的 IP 和 port 信息,这样 Pod 中的应用可以通过读取环境变量来获取依赖服务的地址信息,这种方法使用起来相对简单,但是有一个很大的问题就是依赖的服务必须在 Pod 启动之前就存在,不然是不会被注入到环境变量中的。

  • {SVCNAME}_SERVICE_HOST
  • {SVCNAME}_SERVICE_PORT

如果SVCNAME使用了连接线,那么Kubernetes会在定义为环境变量时将其转换为下划线

如下,我们创建一个资源配置清单

cat nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    name: nginx-service
spec:
  ports:
  - port: 8080
    targetPort: 80
  selector:
    app: nginx

创建资源对象

kubectl apply -f nginx-deploy.yaml

查看创建的资源对象

kubectl get pods,deploy,svc
NAME                               READY   STATUS    RESTARTS   AGE
pod/nginx-deploy-cc7df4f8f-2j45s   1/1     Running   0          36s
pod/nginx-deploy-cc7df4f8f-rxxv4   1/1     Running   0          36s
pod/nginx-deploy-cc7df4f8f-vwhk9   1/1     Running   0          36s

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deploy   3/3     3            3           37s

NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP    35d
service/nginx-service   ClusterIP   10.110.69.11   <none>        8080/TCP   36s

可以看到如上,三个Pod和一个deploy控制器以及nginx-service已经创建完成,Service监听的端口是8080,同时会把流量转发至后端的Pod。

现在再来创建一个普通的Pod,观察下该 Pod 中的环境变量是否包含上面的 nginx-service 的服务信息,如下创建一个busybox的Pod,执行命令env输出当前Pod的环境变量。

cat busybox-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox-pod
spec:
  containers:
  - name: busybox
    image: busybox:latest
    command: ["/bin/sh", "-c", "env"]

创建Pod

kubectl apply -f busybox-pod.yaml

查看busybox-pod输出的日志

kubectl logs -f busybox-pod
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=busybox-pod
SHLVL=1
HOME=/root
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_SERVICE_SERVICE_HOST=10.110.69.11
NGINX_SERVICE_PORT_8080_TCP_ADDR=10.110.69.11
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
NGINX_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_SERVICE_PORT_8080_TCP_PROTO=tcp
NGINX_SERVICE_PORT=tcp://10.110.69.11:8080
NGINX_SERVICE_SERVICE_PORT=8080
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
NGINX_SERVICE_PORT_8080_TCP=tcp://10.110.69.11:8080
PWD=/

我们可以看到打印了很多环境变量信息,其中就包括我们刚刚创建的 nginx-service 这个服务,有 HOST、PORT、PROTO、ADDR 等,也包括其他已经存在的 Service 的环境变量,现在如果我们需要在这个 Pod 里面访问 nginx-service 的服务,就可以直接通过环境变量 NGINX_SERVICE_SERVICE_HOSTNGINX_SERVICE_SERVICE_PORT 就可以了,但是如果这个 Pod 启动起来的时候 nginx-service 服务还没启动起来,在环境变量中我们是无法获取到这些信息的,当然我们可以通过 initContainer 之类的方法来确保 nginx-service 启动后再启动 Pod,但是这种方法毕竟增加了 Pod 启动的复杂性,所以这不是最优的方法,局限性太多了。

注意:只有在同一个名称空间下的Pod才会自动载入当前名称空间下的所有环境变量

我们再次重新创建一个busybox Pod,然后进入busybox Pod测试通过环境变量访问nginx-service

cat busybox-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox-pod
spec:
  containers:
  - name: busybox
    image: busybox:latest
    command: ["/bin/sh", "-c", "tail -f /etc/passwd"]

创建一个新的busybox Pod

kubectl apply -f busybox-pod.yaml

进入Pod并查看当前Pod的环境变量

kubectl exec -it pods/busybox-pod -- /bin/sh
/ # env
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=busybox-pod
SHLVL=1
HOME=/root
TERM=xterm
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_SERVICE_SERVICE_HOST=10.110.69.11
NGINX_SERVICE_PORT_8080_TCP_ADDR=10.110.69.11
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
NGINX_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_SERVICE_PORT_8080_TCP_PROTO=tcp
NGINX_SERVICE_SERVICE_PORT=8080
NGINX_SERVICE_PORT=tcp://10.110.69.11:8080
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
NGINX_SERVICE_PORT_8080_TCP=tcp://10.110.69.11:8080
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/

通过环境变量访问nginx-service所关联的 Endpoints

/ # wget -q -O -  http://$NGINX_SERVICE_SERVICE_HOST:$NGINX_SERVICE_PORT_8080_TCP_PORT
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

服务发现方式:DNS

创建Service对象时,ClusterDNS会为Service自动创建资源记录用于解析和服务注册,于是,Pod资源可以直接使用标准的DNS名称来访问这些Service资源,每个Service对象相关的DNS记录包含如下两个:

  • {SVCNAME}.{NAMESPACE}.{CLUSTER_DOMAIN}
  • {SVCNAME}.{NAMESPACE}.svc.{CLUSTER_DOMAIN}

1.查看nginx-service状态

kubectl describe svc nginx-service
Name:              nginx-service
Namespace:         default
Labels:            name=nginx-service
Annotations:       Selector:  app=nginx
Type:              ClusterIP
IP:                10.110.69.11
Port:              <unset>  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.244.2.100:80,10.244.3.22:80,10.244.5.144:80
Session Affinity:  None
Events:            <none>

2.进入busybox-pod容器

kubectl exec -it pods/busybox-pod -- /bin/sh

3.通过CoreDNS所提供服务发现的方式访问Service
nginx-service.default.svc.cluster.local:8080
nginx-service:service名称
default:名称空间
svc:固定后缀,意思为服务
cluster.local 为当前kubernetes集群所在域的域名,在我们初始化k8s时可以指定–service-dns-domain= 参数来进行绑定,如果没有指定则为 cluster.local
8080:Service映射后端Pod的端口

/ # wget -q -O- nginx-service.default.svc.cluster.local:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

也可以简写为 service名称与名称空间名称

/ # wget -O - -q nginx-service.default:8080

查看当前容器的dns配置文件,可以看到如下dns为CoreDNS的地址,搜索域为cluster.local,这也是在容器中可以通过此种名称去访问Service的原因,可以简写就访问到Service的原因为容器中的search搜索域svc.cluster.local

/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

4.在宿主机上通过DNS发现名称去访问Service
这需要在集群中的宿主机上添加CoreDNS和搜索域

cat /etc/resolv.conf
nameserver 127.0.0.53
options edns0
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local

在宿主机添加完成后通过DNS发现名称访问Service

curl nginx-service.default:8080 -I
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Mon, 01 Jun 2020 08:22:04 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes

curl nginx-service.default.svc.cluster.local:8080 -I
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Mon, 01 Jun 2020 08:22:09 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes
Ops工具

44. Kubernetes深入Service-Service端口暴露

2020-6-22 10:28:27

Ops工具

46. Kubernetes深入Service-Headless类型的Service资源

2020-6-22 10:33:15

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索