志在超越 發表於 2019-8-16 08:47:00

Kubernetes 系列六】Kubernetes 服务发现

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>什么是服务发现?</li><li>环境变量</li><li>DNS 服务<ul><li>Linux 中 DNS 查询原理</li><li>Kubernetes 中 DNS 查询原理</li><li>调试 DNS 服务</li><li>存根域及上游 DNS</li></ul></li></ul></div><p></p>
<h2 id="什么是服务发现">什么是服务发现?</h2>
<p><strong>服务发现就是一种提供服务发布和查找的服务</strong>,是基于服务架构(SOA)应用的核心服务,需具备以下关键特性:</p>
<ol>
<li>注册(Registration),新增服务到服务列表;</li>
<li>目录(Directory),即服务列表;</li>
<li>查找(Lookup),通过服务名找到服务。</li>
</ol>
<p><strong>服务发现的关键在于服务元数据(metadata)的存储</strong>,包括服务名、服务 IP、服务端口等信息。</p>
<p>Kubernetes 支持两种服务发现方式,环境变量和 DNS。</p>
<h2 id="环境变量">环境变量</h2>
<p>当 Pod 创建时,Kubernetes 会将每个活跃的 Service 的相关环境变量设置到 Pod 中。<strong>值得注意的是,这些环境变量不会因为相关 Service 改变而改变</strong>(笔者亲手试验过)。</p>
<p>Kubernetes 会设置两类环境变量,分别是:</p>
<ol>
<li>Kubernetes Service 环境变量</li>
<li>Docker Link 环境变量</li>
</ol>
<p>Kubernetes Service 环境变量形如(假定服务名为 latte,且访问端口为 8080):</p>
<pre><code class="language-shell">LATTE_SERVICE_HOST=10.100.251.57
LATTE_SERVICE_PORT=8080
</code></pre>
<p>Docker Link 环境变量形如(假定服务名为 latte,且访问端口为 8080):</p>
<pre><code class="language-shell">LATTE_PORT_8080_TCP_ADDR=10.100.251.57
LATTE_PORT_8080_TCP_PORT=8080
LATTE_PORT_8080_TCP_PROTO=tcp
LATTE_PORT=tcp://10.100.251.57:8080
LATTE_PORT_8080_TCP=tcp://10.100.251.57:8080
</code></pre>
<p>可以通过进入 Pod 的终端,执行 env 命令查看设置的环境变量验证。</p>
<pre><code class="language-shell">kubectl exec -ti &lt;pod-name&gt; env --namespace=&lt;my-namespace&gt;
</code></pre>
<p>此种方式的服务发现缺点很明显:</p>
<ol>
<li>依赖的服务必须先运行起来,否则 Pod 无法发现;</li>
<li>如依赖的服务宕机或绑定新地址,Pod 无法发现,仍然持有旧的地址。</li>
</ol>
<p>幸好,我们还有另一种服务发现机制。</p>
<h2 id="dns-服务">DNS 服务</h2>
<p>在讲述 Kubernetes 中使用 DNS 进行服务发现之前,我们不得不先了解下 Linux 中是如何进行 DNS 查询的。</p>
<h3 id="linux-中-dns-查询原理">Linux 中 DNS 查询原理</h3>
<p>在 Linux 的 <code>/etc/</code> 目录中,存在 3 个我们需要关注的文件,分别是(参考:http://man7.org/linux/man-pages/man5/host.conf.5.html):</p>
<ol>
<li><code>/etc/host.conf</code>:DNS 解析器配置,包含 trim、multi、order、reorder 和 nospoof 等等配置。</li>
<li><code>/etc/hosts</code>:本地 hosts 数据库,存放本地的域名到 IP 的配置。</li>
<li><code>/etc/resolv.conf</code>:DNS 解析器配置,包含 nameserver、domain、search、sortlist 和 options 等配置。</li>
</ol>
<p>下面是一台 Linux 服务器中 3 个相关文件的内容:</p>
<pre><code class="language-shell"># /etc/host.conf
multi on
# /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost6 localhost6.localdomain6
# /etc/resolv.conf
; generated by /usr/sbin/dhclient-script
search us-west-2.compute.internal
options timeout:2 attempts:5
nameserver 192.168.0.2
</code></pre>
<p><code>/etc/resolv.conf</code> 配置解释如下:</p>
<table>
<thead>
<tr>
<th>配置项</th>
<th>功能</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>nameserver</td>
<td>DNS 服务器</td>
<td>值必须是 IP 地址</td>
</tr>
<tr>
<td>domain</td>
<td>本地域名</td>
<td>域中的查询可以使用相对于本地域名的短名称</td>
</tr>
<tr>
<td>search</td>
<td>主机名查询列表</td>
<td>默认只包含本地域名。阈值为 6 个域名,256 个字符。</td>
</tr>
<tr>
<td>options</td>
<td>选项</td>
<td>修改内部 DNS 解析器变量值。</td>
</tr>
</tbody>
</table>
<p>options 中常见的配置项有:</p>
<table>
<thead>
<tr>
<th>配置项</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>ndots</td>
<td>所有查询中,如果<code>.</code>的个数少于给定的数值,则会根据<code>search</code>中配置的列表依次在对应域中先进行搜索,如果没有返回,则最后再直接查询域名本身。阈值为 15。</td>
</tr>
<tr>
<td>timeout</td>
<td>等待 DNS 服务器响应的超时时间,单位为秒。阈值为 30 s。</td>
</tr>
<tr>
<td>attempts</td>
<td>向同一个 DNS 服务器发起重试的次数,单位为次。阈值为 5。</td>
</tr>
</tbody>
</table>
<p>笔者在本地试验时发现,文件 <code>/etc/resolv.conf</code> 是网络连接时自动生成的,依据是:</p>
<ol>
<li>当本机处以断网状态时,<code>cat /etc/resolv.conf</code> 返回空文本;</li>
<li>当本机连上网络时,<code>cat /etc/resolv.conf</code> 返回以下内容:</li>
</ol>
<pre><code class="language-shell">#
# macOS Notice
#
# This file is not consulted for DNS hostname resolution, address
# resolution, or the DNS query routing mechanism used by most
# processes on this system.
#
# To view the DNS configuration used by this system, use:
#   scutil --dns
#
# SEE ALSO
#   dns-sd(1), scutil(8)
#
# This file is automatically generated.
#
nameserver 58.250.162.58
nameserver 8.8.8.8
</code></pre>
<p>第一个 DNS 服务器是中国联通的,通过访问 https://whois.domaintools.com/58.250.162.58 可知;</p>
<p>第二个 DNS服务器是 Google 的,通过 <code>nslookup 8.8.8.8</code>或者访问 https://whois.domaintools.com/8.8.8.8 可知。</p>
<h3 id="kubernetes-中-dns-查询原理">Kubernetes 中 DNS 查询原理</h3>
<p>Kubernetes 中有两个可选的 DNS 服务插件(处在 kube-system 命名空间):</p>
<table>
<thead>
<tr>
<th>插件</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>kube-dns</td>
<td>其代码已经从 kubernetes 库中分离到单独的仓库维护,见 https://github.com/kubernetes/dns</td>
</tr>
<tr>
<td>CoreDNS</td>
<td>支持 Kubernetes v1.9 及以上;Kubernetes v1.12 起,官方推荐使用来替换 kube-dns;Kubernetes v1.13 起,成为默认 DNS 服务;占用内存小,查询速度快。</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注意:<code>kube-dns</code> 在 Kubernetes 中有多重含义,要注意区别。</p>
<ol>
<li>与 CoreDNS 对比时,使用狭义,表示名为 <code>kube-dns</code> 的 DNS 服务;</li>
<li>当泛指时,表示 Kubernetes 中的 DNS 服务。</li>
</ol>
</blockquote>
<p>使用 <code>kubeadm</code> 创建 v1.11 及以后的 Kubernetes 集群,默认启用 CoreDNS(处于 GA 状态,见 Software release life cycle)。(来源)</p>
<p>笔者通过 aws 提供的 <code>eksctl</code> 工具创建的 v1.15 的集群默认也是启用了 CoreDNS,查阅 <code>eksctl</code> 源码可以获取其默认启用的插件。</p>
<p><strong>Kubernetes 通过修改每个 Pod 中每个容器的域名解析配置文件 <code>/etc/resolv.conf</code> 来达到服务发现的目的</strong>。在笔者创建的集群中获取其中一个容器的域名解析配置文件如下:</p>
<pre><code class="language-shell"># /etc/resolv.conf
nameserver 10.100.0.10
search cafe.svc.cluster.local svc.cluster.local cluster.local us-west-2.compute.internal
options ndots:5
</code></pre>
<p>其含义是:DNS 服务器为 10.100.0.10,当查询关键词中 <code>.</code> 的数量少于 5 个,则根据 search 中配置的域名进行查询,当查询都没有返回正确响应时再尝试直接查询关键词本身。</p>
<p>例如,执行 <code>host -v cn.bing.com</code> 后我们将会看到:</p>
<pre><code class="language-shell">Trying "cn.bing.com.cafe.svc.cluster.local"
Trying "cn.bing.com.svc.cluster.local"
Trying "cn.bing.com.cluster.local"
Trying "cn.bing.com.us-west-2.compute.internal"
Trying "cn.bing.com"
...
</code></pre>
<p>解析过程是如此缓慢,当对某些服务访问频繁时建议额外配置 DNS 记录。</p>
<blockquote>
<p>注:获取过程如下</p>
<pre><code class="language-shell"># 1) 查询指定命名空间中的所有 pod
kubectlget pods --namespace=cafe
# 2) 进入其中一个 pod 的交互终端
kubectl exec -ti macchiato-6746674bdd-5hmtw sh --namespace=cafe
# 3) 查看 /etc/resolv.conf
cat /etc/resolv.conf
</code></pre>
</blockquote>
<p>DNS 服务器会监听着集群内所有 Service API,以在服务不可用时移除记录,在新服务创建时插入新记录。</p>
<p><em>这些记录存放在哪里呢?</em></p>
<p>答案是:存储在 kube-dns 插件中的 cache 也可配置到 etcd。</p>
<p><em>存储的 DNS 记录有哪些种类呢?</em></p>
<p>我们过去或多或少了解到的 DNS 记录有以下几种:</p>
<table>
<thead>
<tr>
<th>类别</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>Address record,域名到 IP 地址的记录</td>
</tr>
<tr>
<td>CNAME</td>
<td>Canonical name record,别名记录,设置域名的别名</td>
</tr>
<tr>
<td>NS</td>
<td>Name server record,域名服务器记录</td>
</tr>
<tr>
<td>MX</td>
<td>Mail exchange record,邮件服务记录</td>
</tr>
<tr>
<td>TXT</td>
<td>Text record,为某条记录设置说明</td>
</tr>
<tr>
<td>AAAA</td>
<td>IPv6 address record,域名到 IPv6 地址 ( 128 = 32 * 4 )的记录</td>
</tr>
</tbody>
</table>
<p>这次我们要多认识一个称之为 <strong>SRV(Service locator)</strong>的 DNS 记录,用来通用化地定位服务。</p>
<p><strong>Kubernetes 的 DNS 服务(简称为 kube-dns)支持 Service 的 A 记录、 SRV 记录和 CNAME 记录</strong>。</p>
<p>我们知道 Kubernetes 中的 Service 是 Pod 的逻辑分组,有 Cluster IP 和 Label Selector 有无之别。没有设置 Cluster IP 的我们称之为 Headless Service,否则称之为 Normal Service。设置了 Label Selector 的会同时产生一个 Endpoints 对象,声明集群内部 Service 的访问端点。</p>
<p>假定有一个 cafe 命名空间下名为 latte 的 Normal Service,开放了名为 http 的 TCP 端口 8080,kube-dns 会为其生成以下的 A 记录和 SRV 记录:</p>
<pre><code class="language-shell">latte.cafe.svc.cluster.local. 5 IN A 10.100.71.56
_http._tcp.latte.cafe.svc.cluster.local. 30 IN SRV 10 100 443 latte.cafe.svc.cluster.local.
</code></pre>
<blockquote>
<p><strong>注:查询 DNS 记录的方法</strong></p>
<p>(1)安装 <code>dig</code> 工具</p>
<p>将下面的部署配置保存到文件 dnsutils.yaml,然后执行 <code>kubectl apply -f dnsutils.yaml</code> 部署。</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: tutum/dnsutils
command:
      - sleep
      - "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
</code></pre>
<p>(2)使用<code>dig</code>工具获取 DNS 记录</p>
<pre><code class="language-shell"># 用法
dig @&lt;DNS服务器&gt; &lt;记录类型&gt; &lt;域名&gt; &lt;可选值&gt;
# 示例
## 1)获取 DNS 服务地址
kubectl get svc kube-dns -n kube-system
NAME       TYPE      CLUSTER-IP    EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.100.0.10   &lt;none&gt;      53/UDP,53/TCP   8d
## 2)进入 dnsutils 的 shell 终端
kubectl exec -ti dnsutilssh
## 3)查询 latte 服务的 A 记录
dig @10.100.0.10 Alatte.cafe.svc.cluster.local+noall +answer
</code></pre>
</blockquote>
<p>假如有一个 cafe 命名空间下名为 mocha 的 Headless Service,kube-dns 会为其生成以下的 A 记录集(域名到 Pod IPs 的映射):</p>
<pre><code class="language-shell">mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.111
mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.112
mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.113
</code></pre>
<p>如若有一个 cafe 命名空间下名为 macchiato 的 Headless 但设置了以下 Endpoints 的 Service:</p>
<pre><code class="language-yaml">kind: Endpoints
apiVersion: v1
metadata:
name: macchiato
namespace: cafe
subsets:
- addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376
</code></pre>
<p>kube-dns 会为其生成以下的 A 记录:</p>
<pre><code class="language-shell">macchiato.cafe.svc.cluster.local. 4 IN A 1.2.3.4
</code></pre>
<p>如果有一个 cafe 命名空间下名为 cappuccino 的 Headless 但设置了以下 ExternalName 的 Service:</p>
<pre><code class="language-yaml">kind: Service
apiVersion: v1
metadata:
name: cappuccino
namespace: cafe
spec:
type: ExternalName
externalName: cappuccino.cafe.com
</code></pre>
<p>kube-dns 会为其生成以下的 CNAME 记录:</p>
<pre><code class="language-shell">cappuccino.cafe.svc.cluster.local. 10 IN CNAME cappuccino.cafe.com.
</code></pre>
<p>Kubernetes 的 DNS 服务除了支持 Service 的 DNS 记录外,还支持 Pod 的 A 记录,使用hostname + subdomain 方式实现。仔细阅读以下部署配置。</p>
<pre><code class="language-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
</code></pre>
<p>我们发现其创建了:</p>
<ol>
<li>name 为 busybox1,hostname 为 busybox-1,subdomain 为 default-subdomain 的 Pod;</li>
<li>name 为 busybox2,hostname 为 busybox-2,subdomain 为 default-subdomain 的 Pod;</li>
<li>name 为 default-subdomain 的 Headless Service。</li>
</ol>
<p>产生以下 A 记录:</p>
<pre><code class="language-shell">busybox-1.default-subdomain.svc.cluster.local. 4 IN A 192.168.51.119
busybox-2.default-subdomain.svc.cluster.local. IN A
busybox-2.default-subdomain.svc.cluster.local. 4 IN A 192.168.36.188
default-subdomain.svc.cluster.local. 4 IN A 192.168.62.187
default-subdomain.svc.cluster.local. 4 IN A 192.168.62.188
</code></pre>
<p><em>这些记录是怎样的一种格式呢?</em></p>
<p>参见:https://github.com/kubernetes/dns/blob/master/docs/specification.md</p>
<h3 id="调试-dns-服务">调试 DNS 服务</h3>
<p>使用 busybox 调试 DNS 服务,因为 busybox 中有 nslookup 工具可以使用。</p>
<p>(1)保存以下文本到文件 <code>busybox.yaml</code>(此处使用命名空间为 cafe )</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: cafe
spec:
containers:
- name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
restartPolicy: Always
</code></pre>
<p>(2)应用 <code>busybox.yaml</code>,并查看状态</p>
<pre><code class="language-shell">kubectl apply -f busybox.yaml --namespace=cafe
kubectl get pods busybox --namespace=cafe
</code></pre>
<p>(3)进入终端交互界面并支持 <code>nslookup</code> 查询服务 <code>latte</code></p>
<pre><code class="language-shell">kubectl exec -ti busybox sh --namespace=cafe
nslookup latte
</code></pre>
<h3 id="存根域及上游-dns">存根域及上游 DNS</h3>
<p>当无自定义配置时,不匹配的 DNS 查询(比如上文说的<code>cn.bing.com</code>)会使用从 Node 中继承的 nameserver 进行解析。</p>
<p>当有自定义的配置时,会在 DNS 缓存层查询无果后,根据查询名称后缀决定去往的 DNS 解析器:</p>
<ul>
<li>查询名称带有集群后缀的(比如 ".cluster.local"),转发到 kube-dns。</li>
<li>查询名称带有存根域名后缀的(比如 ".acme.local"),转发到 custom DNS。</li>
<li>查询名称不匹配的(比如 "widget.com"),转发到 upstream DNS。</li>
</ul>
<p><img src="https://img2018.cnblogs.com/blog/784520/201908/784520-20190816084536168-9381559.png"></p>
<p>以上配置使用 Kubernetes 的<code>ConfigMap</code> 插件,配置如下:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
stubDomains: |
    {"acme.local": ["1.2.3.4"]}
upstreamNameservers: |
    ["8.8.8.8","8.8.4.4"]
</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    <br/>
本作品地址: https://www.cnblogs.com/lshare/p/11361687.html,作者: 東籬老農,採用知識共享署名 4.0 國際許可協議進行許可。
<br><br><br>
来源:https://www.cnblogs.com/lshare/p/11361687.html
頁: [1]
查看完整版本: Kubernetes 系列六】Kubernetes 服务发现