实战:k8s之服务发现-2021.12.21

实战:k8s之服务发现-2021.12.21

image-20211212152405943

目录

实验环境

实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.22.2
   containerd://1.5.5

实验软件

1、服务发现

上面我们讲解了 Service 的用法,我们可以通过 Service 生成的 ClusterIP(VIP) 来访问 Pod 提供的服务,但是在使用的时候还有一个问题:我们怎么知道某个应用的 VIP 呢?比如我们有两个应用,一个是 api 应用,一个是 db 应用,两个应用都是通过 Deployment 进行管理的,并且都通过 Service 暴露出了端口提供服务。api 需要连接到 db 这个应用,我们只知道 db 应用的名称和 db 对应的 Service 的名称,但是并不知道它的 VIP 地址,我们前面的 Service 课程中是不是学习到我们通过 ClusterIP 就可以访问到后面的 Pod 服务,如果我们知道了 VIP 的地址是不是就行了?

⚠️ 注意:在实际工作应用中,我们很少去把访问地址用ip固定下来。我们要解决这种问题,一般有2种方法。
第一种:环境变量。
第二种:DNS。

1.1 环境变量

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

📍 案例测试

比如我们首先创建一个 Nginx 服务:(test-nginx.yaml)

[root@master1 ~]#vim test-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    name: nginx-service
spec:
  ports:
  - port: 5000
    targetPort: 80
  selector:
    app: nginx

创建上面的服务并查看:

[root@master1 ~]#kubectl apply -f test-nginx.yaml 
deployment.apps/nginx-deploy created
service/nginx-service created
[root@master1 ~]#kubectl get po,deploy,svc -owide
NAME                                READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
pod/nginx-deploy-5d59d67564-7bzf7   1/1     Running   0          51s   10.244.2.210   node2   <none>           <none>
pod/nginx-deploy-5d59d67564-rdglk   1/1     Running   0          51s   10.244.1.89    node1   <none>           <none>

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES        SELECTOR
deployment.apps/nginx-deploy   2/2     2            2           51s   nginx        nginx:1.7.9   app=nginx

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE   SELECTOR
service/kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP    42d   <none>
service/nginx-service   ClusterIP   10.105.30.147   <none>        5000/TCP   51s   app=nginx
[root@master1 ~]#

我们可以看到两个 Pod 和一个名为 nginx-service 的服务创建成功了,该 Service 监听的端口是 5000,同时它会把流量转发给它代理的所有 Pod(我们这里就是拥有 app: nginx 标签的两个 Pod)。

现在我们再来创建一个普通的 Pod,观察下该 Pod 中的环境变量是否包含上面的 nginx-service 的服务信息:(test-pod.yaml)

[root@master1 ~]#vim test-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: test-service-pod
    image: busybox:1.28.3
    command: ["/bin/sh", "-c", "env"]

然后创建该测试的 Pod:

[root@master1 ~]#kubectl apply -f test-pod.yaml 
pod/test-pod created

等 Pod 创建完成后,我们查看日志信息:

[root@master1 ~]#kubectl logs test-pod 
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT=443
HOSTNAME=test-pod
SHLVL=1
HOME=/root
NGINX_SERVICE_PORT_5000_TCP_ADDR=10.105.30.147
NGINX_SERVICE_PORT_5000_TCP_PORT=5000
NGINX_SERVICE_PORT_5000_TCP_PROTO=tcp
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.105.30.147
NGINX_SERVICE_PORT_5000_TCP=tcp://10.105.30.147:5000
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
NGINX_SERVICE_SERVICE_PORT=5000
NGINX_SERVICE_PORT=tcp://10.105.30.147:5000
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_HOST=10.96.0.1
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 启动的复杂性,所以这不是最优的方法,局限性太多了。

⚠️ 注意:kubernetes这个service这个是谁创建的?

[root@master1 ~]#kubectl get svc
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP    42d
nginx-service   ClusterIP   10.98.166.23   <none>        5000/TCP   5m57s

这个kubernetes主服务(service)是系统自己创建的,这个service是必须要有的,不能随便被删除的。相当与是我们集群内部的一些应用,他们要通过这个service去访问apiserver。这个其实就是去关联到我们的apiserver而已。

我们可以去describe看下:

[root@master1 ~]#kubectl describe svc kubernetes 
Name:              kubernetes
Namespace:         default
Labels:            component=apiserver
                   provider=kubernetes
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.0.1
IPs:               10.96.0.1
Port:              https  443/TCP
TargetPort:        6443/TCP
Endpoints:         172.29.9.51:6443 #care
Session Affinity:  None
Events:            <none>

可以看到这里的Endpoints,其实就是到我们的apiserver那里去的。

其实就是给我们集群内部的一些pod去使用这个apiserver的,因为一般来说 我们不太可能在集群内部去写上这个172.29.9.51:6443地址。

实验结束。😘😘

1.2 DNS

由于上面环境变量这种方式的局限性,我们需要一种更加智能的方案,其实我们可以自己思考一种比较理想的方案:那就是可以直接使用 Service 的名称因为 Service 的名称不会变化(我们当然不会在线上随意更改这个名称了哈哈🤣),我们不需要去关心分配的 ClusterIP 的地址,因为这个地址并不是固定不变的,所以如果我们直接使用 Service 的名字,然后对应的 ClusterIP 地址的转换能够自动完成就很好了。我们知道名字和 IP 直接的转换是不是和我们平时访问的网站非常类似啊?他们之间的转换功能通过 DNS 就可以解决了,同样的,Kubernetes 也提供了 DNS 的方案来解决上面的服务发现的问题

DNS 服务不是一个独立的系统服务,而是作为一种 addon 插件而存在,现在比较推荐的两个插件:kube-dns 和 CoreDNS,实际上在比较新点的版本中已经默认是 CoreDNS 了因为 kube-dns 默认一个 Pod 中需要3个容器配合使用,CoreDNS 只需要一个容器即可,我们在前面使用 kubeadm 搭建集群的时候直接安装的就是 CoreDNS 插件:

[root@master1 ~]#kubectl get po -A -l k8s-app=kube-dns -owide
NAMESPACE     NAME                       READY   STATUS    RESTARTS     AGE   IP           NODE      NOMINATED NODE   READINESS GATES
kube-system   coredns-7568f67dbd-2ztgw   1/1     Running   1 (8d ago)   17d   10.244.0.9   master1   <none>           <none>
kube-system   coredns-7568f67dbd-9dls5   1/1     Running   1 (8d ago)   38d   10.244.0.8   master1   <none>           <none>

# 注意:这2个pod都是被调度到master节点上的。

CoreDns 是用 GO 写的高性能,高扩展性的 DNS 服务,基于 HTTP/2 Web 服务 Caddy 进行编写的。CoreDns 内部采用插件机制,所有功能都是插件形式编写,用户也可以扩展自己的插件,以下是 Kubernetes 部署 CoreDns 时的默认配置:

➜  ~ kubectl get cm coredns -n kube-system -o yaml
apiVersion: v1
data:
  Corefile: | #注意:你可以认为,下面{}里的每一行相当于一个插件!!!
    .:53 {
        errors  # 启用错误记录
        health  # 启用健康检查检查端点,8080:health
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {  # 处理 k8s 域名解析
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153  # 启用 metrics 指标,9153:metrics
        forward . /etc/resolv.conf  # 通过 resolv.conf 内的 nameservers 解析
        cache 30  # 启用缓存,所有内容限制为 30s 的TTL (all的解析都有一个缓存的)
        loop  # 检查简单的转发循环并停止服务
        reload  # 运行自动重新加载 corefile,热更新
        loadbalance  # 负载均衡,默认 round_robin
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2019-11-08T11:59:49Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "188"
  selfLink: /api/v1/namespaces/kube-system/configmaps/coredns
  uid: 21966186-c2d9-467a-b87f-d061c5c9e4d7
  • 每个 {} 代表一个 zone,格式是 “Zone:port{}”, 其中"."代表默认zone

  • {} 内的每个名称代表插件的名称,只有配置的插件才会启用,当解析域名时,会先匹配 zone(都未匹配会执行默认 zone),然后 zone 内的插件从上到下依次执行(这个顺序并不是配置文件内谁在前面的顺序,而是core/dnsserver/zdirectives.go内的顺序),匹配后返回处理(执行过的插件从下到上依次处理返回逻辑),不再执行下一个插件

CoreDNS 的 Service 地址一般情况下是固定的,类似于 kubernetes 这个 Service 地址一般就是第一个 IP 地址 10.96.0.1,CoreDNS 的 Service 地址就是 10.96.0.10。该 IP 被分配后,kubelet 会将使用 --cluster-dns=<dns-service-ip> 参数配置的 DNS 传递给每个容器。DNS 名称也需要域名,本地域可以使用参数--cluster-domain = <default-local-domain> 在 kubelet 中配置:

➜  ~ cat /var/lib/kubelet/config.yaml
......
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local #当然,这个可以改,但一般是不会去修改的,是约定俗成的。
......

⚠️注意:这里的解释如下。(这里的2点有点晦涩)

注意点1:

当我们去启动一个容器的时候,它会把这里的clusterDNS地址给写入到我们容器的nameserver里面去,那么当我们容器里面的namesrver是这个地址,然后去做域名解析的时候,是不是都会进入到这个10.96.0.10里面去,而这个地址刚好是我们core dns的地址。所以最终解析,还是进入到这个coredns里面来做的。

[root@master1 ~]#kubectl get svc -nkube-system
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
kube-dns         ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP   49d
metrics-server   ClusterIP   10.106.125.106   <none>        443/TCP                  27d

注意点2:

文件/var/lib/kubelet/config.yaml里的clusterDomain: cluster.local就相当于在我们k8s集群里面,就是我们service的一个域名后缀。all的以dns的形式访问service的时候,它的一个后缀都是cluster.local

我们前面说了如果我们建立的 Service 如果支持域名形式进行解析,就可以解决我们的服务发现的功能,那么利用 kubedns 可以将 Service 生成怎样的 DNS 记录呢?

  • 普通的 Service:会生成 servicename.namespace.svc.cluster.local 的域名,会解析到 Service 对应的 ClusterIP 上,在 Pod 之间的调用可以简写成 servicename.namespace,如果处于同一个命名空间下面,甚至可以只写成 servicename 即可访问

    假如说我们有个svc叫做nginx-service:
    那么,该service在dns里的域名可以按如下来写:
    	nginx-service.default.svc.cluster.local
    	nginx-service.default.svc
    	nginx-service.default
    如果你的Pod和nigx-service在同一个namesapce,也可以写成nginx-service
    
  • Headless Service:无头服务,就是把 clusterIP 设置为 None 的,会被解析为指定 Pod 的 IP 列表,同样还可以通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod。

📍 案例测试

接下来我们来使用一个简单 Pod 来测试下 Service 的域名访问:

[root@master1 ~]#kubectl run -it --image busybox:1.28.3 test-dns --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
/ #

我们进入到 Pod 中,查看 /etc/resolv.conf 中的内容,可以看到 nameserver 的地址 10.96.0.10,该 IP 地址即是在安装 CoreDNS 插件的时候集群分配的一个固定的静态 IP 地址,我们可以通过下面的命令进行查看:

[root@master1 ~]#kubectl get svc -nkube-system
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
kube-dns         ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP   50d
metrics-server   ClusterIP   10.106.125.106   <none>        443/TCP                  28d

也就是说我们这个 Pod 现在默认的 nameserver 就是 kube-dns 的地址,现在我们来访问下前面我们创建的 nginx-service 服务:

/ # wget -q -O- nginx-service.default.svc.cluster.local

可以看到上面我们使用 wget 命令去访问 nginx-service 服务的域名的时候被 hang 住了,没有得到期望的结果,这是因为上面我们建立 Service 的时候暴露的端口是 5000:

/ # wget -q -O- nginx-service.default.svc.cluster.local:5000
<!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>
/ #

加上 5000 端口,就正常访问到服务,再试一试访问:nginx-service.default.svcnginx-service.defaultnginx-service,不出意外这些域名都可以正常访问到期望的结果。

到这里我们是不是就实现了在集群内部通过 Service 的域名形式进行互相通信了,大家下去试着看看访问不同 namespace 下面的服务呢?

测试结束。😋

⚠️ 注意:我这里的现象和老师的有些不同:测试过程如下

不加Port直接会报错的:

/ # wget -q -O- nginx-service.default.svc.cluster.local
wget: can't connect to remote host (10.98.166.23): Connection refused

奇怪的是:使用wget命令测试的时候,第一次可以,但第二次就卡主了,很奇怪。老师当时是直接卡主了的。。。😥

/ # wget -q -O- nginx-service.default.svc.cluster.local:5000
<!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>
/ # wget -q -O- nginx-service.default.svc.cluster.local:5000

测试域名:

/ # nslookup nginx-service.default.svc.cluster.local
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-service.default.svc.cluster.local
Address 1: 10.98.166.23 nginx-service.default.svc.cluster.local
/ # nslookup nginx-service.default.svc
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-service.default.svc
Address 1: 10.98.166.23 nginx-service.default.svc.cluster.local
/ # nslookup nslookup nginx-service.default
Server:    10.98.166.23
Address 1: 10.98.166.23

/ # nslookup nginx-service.default
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-service.default
Address 1: 10.98.166.23 nginx-service.default.svc.cluster.local
/ # nslookup nginx-service
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-service
Address 1: 10.98.166.23 nginx-service.default.svc.cluster.local



或使用ping测试:
/ # ping nginx-service
PING nginx-service (10.98.166.23): 56 data bytes
64 bytes from 10.98.166.23: seq=0 ttl=64 time=0.134 ms
64 bytes from 10.98.166.23: seq=1 ttl=64 time=0.141 ms
^C
--- nginx-service ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.134/0.137/0.141 ms
/ # ping nginx-service.default
PING nginx-service.default (10.98.166.23): 56 data bytes
64 bytes from 10.98.166.23: seq=0 ttl=64 time=0.101 ms
^C
--- nginx-service.default ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.101/0.101/0.101 ms
/ # ping nginx-service.default.svc
PING nginx-service.default.svc (10.98.166.23): 56 data bytes
64 bytes from 10.98.166.23: seq=0 ttl=64 time=0.108 ms
^C
--- nginx-service.default.svc ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.108/0.108/0.108 ms
/ # ping nginx-service.default.svc.cluster.local
PING nginx-service.default.svc.cluster.local (10.98.166.23): 56 data bytes
64 bytes from 10.98.166.23: seq=0 ttl=64 time=0.146 ms
^C
--- nginx-service.default.svc.cluster.local ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.146/0.146/0.146 ms
/ #

当然在节点直接测试也是没问题的:

[root@master1 ~]#kubectl get svc
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP    50d
nginx-service   ClusterIP   10.98.166.23   <none>        5000/TCP   8d
[root@master1 ~]#curl 10.98.166.23:5000
<!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>
[root@master1 ~]#

实验结束。😘😘

2、给 Pod 添加 DNS 记录

我们都知道 StatefulSet 中的 Pod 是拥有单独的 DNS 记录的,比如一个 StatefulSet 名称为 etcd,而它关联的 Headless SVC 名称为 etcd-headless,那么 CoreDNS 就会为它的每个 Pod 解析如下的记录:

etcd-0.etcd-headless.default.svc.cluster.local
etcd-1.etcd-headless.default.svc.cluster.local
......

那么除了 StatefulSet 管理的 Pod 之外,其他的 Pod 是否也可以生成 DNS 记录呢?

如下所示,我们这里只有一个 Headless 的 SVC,并没有 StatefulSet 管理的 Pod,而是 ReplicaSet 管理的 Pod,我们可以看到貌似也生成了类似于 StatefulSet 中的解析记录。

解析记录

这是怎么做到的呢?按照我们常规的理解会认为这是一个 StatefulSet 管理的 Pod,但其实这里是不同的 ReplicaSet 而已。这里的实现其实是因为 Pod 自己本身也是可以有自己的 DNS 记录的,所以我们是可以去实现一个类似于 StatefulSet 的 Pod 那样的解析记录的。

📍 案例测试

首先我们来部署一个 Deployment 管理的普通应用,其定义如下:

[root@master1 ~]#vim nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

部署后创建了两个 Pod:

[root@master1 ~]#kubectl apply -f nginx.yaml
deployment.apps/nginx created
[root@master1 ~]#kubectl get po -l app=nginx -owide                                                                  
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
nginx-5d59d67564-lcjw8   1/1     Running   0          29s   10.244.2.215   node2   <none>           <none>
nginx-5d59d67564-nxnbv   1/1     Running   0          29s   10.244.2.216   node2   <none>           <none>

然后定义如下的 Headless Service:

[root@master1 ~]#vim service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  clusterIP: None
  ports:
  - name: http
    port: 80
    protocol: TCP
  selector:
    app: nginx
  type: ClusterIP

创建该 service,并尝试解析 service DNS:

[root@master1 ~]#kubectl apply -f service.yaml 
service/nginx created
[root@master1 ~]#kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   51d
nginx        ClusterIP   None         <none>        80/TCP    10s

[root@master1 ~]#dig @10.96.0.10 nginx.default.svc.cluster.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.8 <<>> @10.96.0.10 nginx.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41513
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx.default.svc.cluster.local. IN    A

;; ANSWER SECTION:
nginx.default.svc.cluster.local. 30 IN  A       10.244.2.215
nginx.default.svc.cluster.local. 30 IN  A       10.244.2.216

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Dec 21 19:43:55 CST 2021
;; MSG SIZE  rcvd: 154

[root@master1 ~]#

然后我们对 nginx 的 FQDN 域名进行 dig 操作,可以看到返回了多条 A 记录,每一条对应一个 Pod。上面 dig 命令中使用的 10.96.0.10 就是 kube-dns 的 cluster IP,可以在 kube-system namespace 中查看:

[root@master1 ~]#kubectl get svc kube-dns -nkube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   51d

接下来我们试试在 service 名字前面加上 Pod 名字交给 kube-dns 做解析:

[root@master1 ~]#dig @10.96.0.10 nginx-5d59d67564-lcjw8.nginx.default.svc.cluster.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.8 <<>> @10.96.0.10 nginx-5d59d67564-lcjw8.nginx.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48912
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx-5d59d67564-lcjw8.nginx.default.svc.cluster.local.        IN A

;; AUTHORITY SECTION:
cluster.local.          30      IN      SOA     ns.dns.cluster.local. hostmaster.cluster.local. 1640086465 7200 1800 86400 30

;; Query time: 2 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Dec 21 19:47:33 CST 2021
;; MSG SIZE  rcvd: 176

[root@master1 ~]#

可以看到并没有得到解析结果。

官方文档中有一段 Pod’s hostname and subdomain fields 说明:

1.Pod 规范中包含一个可选的 hostname 字段,可以用来指定 Pod 的主机名。当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。举个例子,给定一个 hostname 设置为 “my-host” 的 Pod, 该 Pod 的主机名将被设置为 “my-host”。
2.Pod 规约还有一个可选的 subdomain 字段,可以用来指定 Pod 的子域名

举个例子,某 Pod 的 hostname 设置为 “foo”,subdomain 设置为 “bar”, 在名字空间 “my-namespace” 中对应的完全限定域名为 “foo.bar.my-namespace.svc.cluster-domain.example”。

现在我们编辑一下 nginx.yaml 加上 subdomain 测试下看看:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      subdomain: nginx
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

更新部署再尝试解析 Pod DNS:

[root@master1 ~]#kubectl apply -f nginx.yaml
deployment.apps/nginx configured
[root@master1 ~]#kubectl get po -l app=nginx -owide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
nginx-78f58d8bcb-498sp   1/1     Running   0          15s   10.244.1.91    node1   <none>           <none>
nginx-78f58d8bcb-4pfxk   1/1     Running   0          14s   10.244.2.217   node2   <none>           <none>
[root@master1 ~]#dig @10.96.0.10 nginx-78f58d8bcb-498sp.nginx.default.svc.cluster.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.8 <<>> @10.96.0.10 nginx-78f58d8bcb-498sp.nginx.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 12912
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx-78f58d8bcb-498sp.nginx.default.svc.cluster.local.        IN A

;; AUTHORITY SECTION:
cluster.local.          30      IN      SOA     ns.dns.cluster.local. hostmaster.cluster.local. 1640088249 7200 1800 86400 30

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Dec 21 20:04:54 CST 2021
;; MSG SIZE  rcvd: 176

[root@master1 ~]#

可以看到依然不能解析。

那就试试官方文档中的例子 ,不用 Deployment 直接创建 Pod 吧。

[root@master1 ~]#vim individual-pods-example.yaml

# individual-pods-example.yaml
apiVersion: v1
kind: Service
metadata:
  name: default-subdomain
spec:
  selector:
    name: busybox
  clusterIP: None
  ports:
  - name: foo # Actually, no port is needed.
    port: 1234
    targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  labels:
    name: busybox
spec:
  hostname: busybox-1
  subdomain: default-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox2
  labels:
    name: busybox
spec:
  hostname: busybox-2
  subdomain: default-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox

部署然后尝试解析 Pod DNS (注意这里 hostname 和 pod 的名字有区别,中间多了减号):

[root@master1 ~]#kubectl apply -f individual-pods-example.yaml
service/default-subdomain created
pod/busybox1 created
pod/busybox2 created
[root@master1 ~]#kubectl get po -l name=busybox -owide
NAME       READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
busybox1   1/1     Running   0          79s   10.244.2.218   node2   <none>           <none>
busybox2   1/1     Running   0          79s   10.244.1.92    node1   <none>           <none>

[root@master1 ~]#dig @10.96.0.10 busybox-1.default-subdomain.default.svc.cluster.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.8 <<>> @10.96.0.10 busybox-1.default-subdomain.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33058
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;busybox-1.default-subdomain.default.svc.cluster.local. IN A

;; ANSWER SECTION:
busybox-1.default-subdomain.default.svc.cluster.local. 30 IN A 10.244.2.218

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Dec 21 20:10:23 CST 2021
;; MSG SIZE  rcvd: 151

[root@master1 ~]#

现在我们看到有 ANSWER 记录回来了,hostname 和 subdomain 二者都必须显式指定,缺一不可。一开始我们的截图中的实现方式其实也是这种方式。

现在我们修改一下之前的 nginx deployment 加上 hostname,重新解析:

[root@master1 ~]#vim nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      subdomain: nginx
      hostname: nginx
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
[root@master1 ~]#kubectl apply -f nginx.yaml
deployment.apps/nginx configured
[root@master1 ~]#kubectl get po -owide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
nginx-6ffdd87d4d-42m7q   1/1     Running   0          13s   10.244.2.223   node2   <none>           <none>
nginx-6ffdd87d4d-5cdh2   1/1     Running   0          11s   10.244.2.224   node2   <none>           <none>


[root@master1 ~]#dig @10.96.0.10 nginx.nginx.default.svc.cluster.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.8 <<>> @10.96.0.10 nginx.nginx.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46249
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx.nginx.default.svc.cluster.local. IN A

;; ANSWER SECTION:
nginx.nginx.default.svc.cluster.local. 30 IN A  10.244.2.223
nginx.nginx.default.svc.cluster.local. 30 IN A  10.244.2.224

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Dec 21 20:19:11 CST 2021
;; MSG SIZE  rcvd: 172

[root@master1 ~]#

可以看到解析成功了,但是因为 Deployment 中无法给每个 Pod 指定不同的 hostname,所以两个 Pod 有同样的 hostname,解析出来两个 IP,跟我们的本意就不符合了。不过知道了这种方式过后我们就可以自己去写一个 Operator 去直接管理 Pod 了,**给每个 Pod 设置不同的 hostname 和一个 Headless SVC 名称的 subdomain,这样就相当于实现了 StatefulSet 中的 Pod 解析。**😋

  • 注意:我们来看下注释

[root@master1 ~]#kubectl explain pod.spec

   hostname     <string>
     Specifies the hostname of the Pod If not specified, the pod's hostname will
     be set to a system-defined value.

   subdomain    <string>
     If specified, the fully qualified Pod hostname will be
     "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". If not
     specified, the pod will not have a domainname at all.

实验结束。😘😘

3、Pod 的 DNS 策略

DNS 策略可以单独对 Pod 进行设定,目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。这些策略可以在 Pod 规范中的 dnsPolicy 字段设置:

  • Default: 有人说 Default 的方式,是使用宿主机的方式,这种说法并不准确。这种方式其实是让 kubelet 来决定使用何种 DNS 策略。而 kubelet 默认的方式,就是使用宿主机的 /etc/resolv.conf(可能这就是有人说使用宿主机的 DNS 策略的方式吧),但是,kubelet 是可以灵活来配置使用什么文件来进行DNS策略的,我们完全可以使用 kubelet 的参数 –resolv-conf=/etc/resolv.conf 来决定你的 DNS 解析文件地址。

    [root@master1 ~]#kubelet --help|grep resolv
          --resolv-conf string                                       Resolver configuration file used as the basis for the container DNS resolution configuration. (default "/etc/resolv.conf") (DEPRECATED: This parameter should be set via the config file specified by the Kubelet's --config flag. See https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/ for more information.)
    [root@master1 ~]#
    
  • ClusterFirst: 这种方式,表示 Pod 内的 DNS 使用集群中配置的 DNS 服务,简单来说,就是使用 Kubernetes 中 kubedns 或 coredns 服务进行域名解析。如果解析不成功,才会使用宿主机的 DNS 配置进行解析。

  • ClusterFirstWithHostNet:在某些场景下,我们的 Pod 是用 HostNetwork 模式启动的,一旦用 HostNetwork 模式,表示这个 Pod 中的所有容器,都要使用宿主机的 /etc/resolv.conf 配置进行 DNS 查询,但如果你还想继续使用 Kubernetes 的DNS服务,那就将 dnsPolicy 设置为 ClusterFirstWithHostNet

  • None: 表示空的 DNS 设置,这种方式一般用于想要自定义 DNS 配置的场景,往往需要和 dnsConfig 配合一起使用达到自定义 DNS 的目的。(下面会讲到的)

⚠️ 注意:

Default 并不是默认的 DNS 策略,如果未明确指定 dnsPolicy,则使用 ClusterFirst

注意:[root@master1 ~]#kubectl explain pod.spec

   dnsPolicy    <string>
     Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are
     'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS
     parameters given in DNSConfig will be merged with the policy selected with
     DNSPolicy. To have DNS options set along with hostNetwork, you have to
     specify DNS policy explicitly to 'ClusterFirstWithHostNet'.

📍 案例测试

下面的示例显示了一个 Pod,其 DNS 策略设置为 ClusterFirstWithHostNet,因为它已将 hostNetwork 设置为 true。

编写资源清单文件:

[root@master1 ~]#vim pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox:1.28.3
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet

部署并进入到pod查看nameserver:

[root@master1 ~]#kubectl apply -f pod.yaml
pod/busybox created
[root@master1 ~]#kubectl get po
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          7s
[root@master1 ~]#kubectl exec busybox -- cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
[root@master1 ~]#kubectl get svc kube-dns -nkube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   51d

可以看到,当pod里使用了hostNetwork模式,且dnsPolicy配置为ClusterFirstWithHostNet的之后,此时容器里的nameserver使用的是Kubernetes 的DNS服务。

但此时,若把dnsPolicy配置为ClusterFirst,按理说,此时的容器里的nameserver应该使用的是宿主机的/etc/resolv.conf 配置进行 DNS 查询,我们来测试下:

[root@master1 ~]#vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox1 #这里改个名称!
  namespace: default
spec:
  containers:
  - image: busybox:1.28.3
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always
  hostNetwork: true
  dnsPolicy: ClusterFirst #将上面的ClusterFirstWithHostNet修改为ClusterFirst。 

修改完后,重新部署并观察现象:

[root@master1 ~]#kubectl apply -f pod.yaml
pod/busybox1 created
[root@master1 ~]#kubectl get po busybox1
NAME       READY   STATUS    RESTARTS   AGE
busybox1   1/1     Running   0          7s
[root@master1 ~]#kubectl exec busybox1 -- cat /etc/resolv.conf
nameserver 223.6.6.6
[root@master1 ~]#

可以发现,此时的容器里的nameserver应该使用的是宿主机的/etc/resolv.conf 配置进行 DNS 查询,符合预期效果,测试结束。

实验结束。😘😘

  • 注意

我们的k8s集群的kube-proxy组件,它开启了hostNetwork:true,但是它的的dnsPolicy却是ClusterFirst,难道这里的kube-proxy pod不使用coredns服务吗?而是直接使用的宿主机的dns服务,这个自己有些疑问??😢😢

image-20211220214007658

image-20211220213834796

4、Pod 的 DNS 配置

Pod 的 DNS 配置可让用户对 Pod 的 DNS 设置进行更多控制。dnsConfig 字段是可选的,它可以与任何 dnsPolicy 设置一起使用。 但是,当 Pod 的 dnsPolicy 设置为 “None” 时,必须指定 dnsConfig 字段

用户可以在 dnsConfig 字段中指定以下属性:

  • nameservers:将用作于 Pod 的 DNS 服务器的 IP 地址列表。 最多可以指定 3 个 IP 地址。当 Pod 的 dnsPolicy 设置为 “None” 时,列表必须至少包含一个 IP 地址,否则此属性是可选的。所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。
  • searches:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。 指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。重复的域名将被删除,Kubernetes 最多允许 6 个搜索域
  • options:可选的对象列表,其中每个对象可能具有 **name 属性(必需)**和 value 属性(可选)。此属性中的内容将合并到从指定的 DNS 策略生成的选项。重复的条目将被删除。

📍 案例测试

以下是具有自定义 DNS 设置的 Pod 示例:

[root@master1 ~]#vim dnsconfig.yaml

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: dns-example
spec:
  containers:
    - name: test
      image: nginx
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 1.2.3.4
    searches:
      - ns1.svc.cluster-domain.example
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

创建上面的 Pod 后,容器 test 会在其 /etc/resolv.conf 文件中获取以下内容:

[root@master1 ~]#kubectl apply -f dnsconfig.yaml 
pod/dns-example created
[root@master1 ~]#kubectl get po
]NAME          READY   STATUS    RESTARTS   AGE
dns-example   1/1     Running   0          40s
[root@master1 ~]#kubectl exec dns-example -- cat /etc/resolv.conf
search ns1.svc.cluster-domain.example my.dns.search.suffix
nameserver 1.2.3.4
options ndots:2 edns0
[root@master1 ~]#

符合预期效果。

实验结束。😘

  • 注意:可以看下dnsConfig的帮助信息。

[root@master1 ~]#kubectl explain pod.spec

   dnsConfig    <Object>
     Specifies the DNS parameters of a pod. Parameters specified here will be
     merged to the generated DNS configuration based on DNSPolicy.
[root@master1 ~]#kubectl explain pod.spec.dnsConfig
KIND:     Pod
VERSION:  v1

RESOURCE: dnsConfig <Object>:

DESCRIPTION:
     Specifies the DNS parameters of a pod. Parameters specified here will be
     merged to the generated DNS configuration based on DNSPolicy.

     PodDNSConfig defines the DNS parameters of a pod in addition to those
     generated from DNSPolicy.

FIELDS:
   nameservers  <[]string>
     A list of DNS name server IP addresses. This will be appended to the base
     nameservers generated from DNSPolicy. Duplicated nameservers will be
     removed.

   options      <[]Object>
     A list of DNS resolver options. This will be merged with the base options
     generated from DNSPolicy. Duplicated entries will be removed. Resolution
     options given in Options will override those that appear in the base
     DNSPolicy.

   searches     <[]string>
     A list of DNS search domains for host-name lookup. This will be appended to
     the base search paths generated from DNSPolicy. Duplicated search paths
     will be removed.

注意事项

1.pod的镜像拉取策略

https://kubernetes.io/zh/docs/concepts/containers/images/

image-20211220225729159

 [root@master1 ~]#kubectl explain pod.spec.containers
 ……
 imagePullPolicy      <string>
     Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always
     if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated.
     More info:
     https://kubernetes.io/docs/concepts/containers/images#updating-images
……     

image-20211221204818374

关于我

我的博客主旨:我希望每一个人拿着我的博客都可以做出实验现象,先把实验做出来,然后再结合理论知识更深层次去理解技术点,这样学习起来才有乐趣和动力。并且,我的博客内容步骤是很完整的,也分享源码和实验用到的软件,希望能和大家一起共同进步!

各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人免费帮您解决问题:

  1. 个人微信二维码:x2675263825 (舍得), qq:2675263825。

    image-20211002091450217

  2. 个人博客地址:www.onlyonexl.cn

    image-20211002092057988

  3. 个人微信公众号:云原生架构师实战

    image-20211002141739664

  4. 个人csdn

    https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421

    image-20211002092344616

  5. 个人github主页:https://github.com/OnlyOnexl

    image-20211208201714773

最后

​ 好了,关于服务发现实验就到这里了,感谢大家阅读,最后贴上我女神的photo,祝大家生活快乐,每天都过的有意义哦,我们下期见!

image-20211221204956715

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值