<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>程序印象</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://www.cn18k.com/"/>
  <updated>2020-08-07T14:21:00.000Z</updated>
  <id>http://www.cn18k.com/</id>
  
  <author>
    <name>Davad.di</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>eBPF 技术简介</title>
    <link href="http://www.cn18k.com/2020/08/07/bpf_intro_blog/"/>
    <id>http://www.cn18k.com/2020/08/07/bpf_intro_blog/</id>
    <published>2020-08-07T14:21:00.000Z</published>
    <updated>2020-08-07T14:21:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="eBPF-技术简介"><a href="#eBPF-技术简介" class="headerlink" title="eBPF 技术简介"></a>eBPF 技术简介</h1><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/linux-bpf-book.jpeg" style="zoom:50%;"></p><p>由范老师和我一起翻译的图书 《Linux内核观测技术BPF》 已经在 JD 上有现货，欢迎感兴趣 BPF 技术的同学选购。链接地址 <a href="https://item.jd.com/72110825905.html" target="_blank" rel="noopener">https://item.jd.com/72110825905.html</a></p><p>“eBPF 是我见过的 Linux 中最神奇的技术，没有之一，已成为 Linux 内核中顶级子模块，从 tcpdump 中用作网络包过滤的经典 cbpf，到成为通用 Linux 内核技术的 eBPF，已经完成华丽蜕变，为应用与神奇的内核打造了一座桥梁，在系统跟踪、观测、性能调优、安全和网络等领域发挥重要的角色。为 Service Mesh 打造了具备 API 感知和安全高效的容器网络方案 Cilium，其底层正是基于 eBPF 技术”</p><h2 id="1-BPF"><a href="#1-BPF" class="headerlink" title="1. BPF"></a>1. BPF</h2><p>BPF（Berkeley Packet Filter ），中文翻译为伯克利包过滤器，是类 Unix 系统上数据链路层的一种原始接口，提供原始链路层封包的收发。1992 年，Steven McCanne 和 Van Jacobson 写了一篇名为《BSD数据包过滤：一种新的用户级包捕获架构》的论文。在文中，作者描述了他们如何在 Unix 内核实现网络数据包过滤，这种新的技术比当时最先进的数据包过滤技术快 20 倍。BPF 在数据包过滤上引入了两大革新：</p><ul><li><p>一个新的虚拟机 (VM) 设计，可以有效地工作在基于寄存器结构的 CPU 之上；</p></li><li><p>应用程序使用缓存只复制与过滤数据包相关的数据，不会复制数据包的所有信息。这样可以最大程度地减少BPF 处理的数据；</p></li></ul><p>由于这些巨大的改进，所有的 Unix 系统都选择采用 BPF 作为网络数据包过滤技术，直到今天，许多 Unix 内核的派生系统中（包括 Linux 内核）仍使用该实现。</p><p>tcpdump 的底层采用 BPF 作为底层包过滤技术，我们可以在命令后面增加 ”-d“ 来查看 tcpdump 过滤条件的底层汇编指令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ tcpdump -d <span class="string">'ip and tcp port 8080'</span></span><br><span class="line">(000) ldh      [12]</span><br><span class="line">(001) jeq      <span class="comment">#0x800           jt 2jf 12</span></span><br><span class="line">(002) ldb      [23]</span><br><span class="line">(003) jeq      <span class="comment">#0x6             jt 4jf 12</span></span><br><span class="line">(004) ldh      [20]</span><br><span class="line">(005) jset     <span class="comment">#0x1fff          jt 12jf 6</span></span><br><span class="line">(006) ldxb     4*([14]&amp;0xf)</span><br><span class="line">(007) ldh      [x + 14]</span><br><span class="line">(008) jeq      <span class="comment">#0x1f90          jt 11jf 9</span></span><br><span class="line">(009) ldh      [x + 16]</span><br><span class="line">(010) jeq      <span class="comment">#0x1f90          jt 11jf 12</span></span><br><span class="line">(011) ret      <span class="comment">#262144</span></span><br><span class="line">(012) ret      <span class="comment">#0</span></span><br></pre></td></tr></table></figure><p>图 1-1  tcpdump 底层汇编指令</p><p>BPF 工作在内核层，BPF 的架构图如下 [来自于bpf-usenix93]：</p><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/image-20200419215511484.png" style="zoom:50%;"></p><p>图 1-2 tcpdump 运行架构 </p><h2 id="2-eBPF"><a href="#2-eBPF" class="headerlink" title="2. eBPF"></a>2. eBPF</h2><h3 id="2-1-eBPF-介绍"><a href="#2-1-eBPF-介绍" class="headerlink" title="2.1 eBPF 介绍"></a>2.1 eBPF 介绍</h3><p>2014 年初，Alexei Starovoitov 实现了 eBPF（extended Berkeley Packet Filter）。经过重新设计，eBPF 演进为一个通用执行引擎，可基于此开发性能分析工具、软件定义网络等诸多场景。eBPF 最早出现在 3.18 内核中，此后原来的 BPF 就被称为经典 BPF，缩写 cBPF（classic BPF），cBPF 现在已经基本废弃。现在，Linux 内核只运行 eBPF，内核会将加载的 cBPF 字节码透明地转换成 eBPF 再执行。</p><p>eBPF 新的设计针对现代硬件进行了优化，所以 eBPF 生成的指令集比旧的 BPF 解释器生成的机器码执行得更快。扩展版本也增加了虚拟机中的寄存器数量，将原有的 2 个 32 位寄存器增加到 10 个 64 位寄存器。由于寄存器数量和宽度的增加，开发人员可以使用函数参数自由交换更多的信息，编写更复杂的程序。总之，这些改进使 eBPF 版本的速度比原来的 BPF 提高了 4 倍。</p><table><thead><tr><th>维度</th><th>cBPF</th><th>eBPF</th></tr></thead><tbody><tr><td>内核版本</td><td>Linux 2.1.75（1997年）</td><td>Linux 3.18（2014年）[4.x for kprobe/uprobe/tracepoint/perf-event]</td></tr><tr><td>寄存器数目</td><td>2个：A, X</td><td>10个： R0–R9, 另外 R10 是一个只读的帧指针</td></tr><tr><td>寄存器宽度</td><td>32位</td><td>64位</td></tr><tr><td>存储</td><td>16 个内存位: M[0–15]</td><td>512 字节堆栈，无限制大小的 “map” 存储</td></tr><tr><td>限制的内核调用</td><td>非常有限，仅限于 JIT 特定</td><td>有限，通过 bpf_call 指令调用</td></tr><tr><td>目标事件</td><td>数据包、 seccomp-BPF</td><td>数据包、内核函数、用户函数、跟踪点 PMCs 等</td></tr></tbody></table><p>表格 1 cBPF 与 eBPF 对比</p><blockquote><p>eBPF 在 Linux 3.18 版本以后引入，并不代表只能在内核 3.18+ 版本上运行，低版本的内核升级到最新也可以使用 eBPF 能力，只是可能部分功能受限，比如我就是在 Linux 发行版本 CentOS Linux release 7.7.1908 内核版本 3.10.0-1062.9.1.el7.x86_64 上运行 eBPF 在生产环境上搜集和排查网络问题。</p></blockquote><p>eBPF 实现的最初目标是优化处理网络过滤器的内部 BPF 指令集。当时，BPF 程序仍然限于内核空间使用，只有少数用户空间程序可以编写内核处理的 BPF 过滤器，例如：tcpdump和 seccomp。时至今日，这些程序仍基于旧的 BPF 解释器生成字节码，但内核中会将这些指令转换为高性能的内部表示。                                 </p><p>2014 年 6 月，<strong>eBPF 扩展到用户空间，这也成为了 BPF 技术的转折点</strong>。 正如 Alexei 在提交补丁的注释中写到：“这个补丁展示了 eBPF 的潜力”。当前，eBPF 不再局限于网络栈，已经成为内核顶级的子系统。eBPF 程序架构强调安全性和稳定性，看上去更像内核模块，但与内核模块不同，eBPF 程序不需要重新编译内核，并且可以确保 eBPF 程序运行完成，而不会造成系统的崩溃。</p><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/bpf-basic-arch.png" alt=""></p><p>图 2-1 BPF 架构图</p><p>简述概括， eBPF 是一套通用执行引擎，提供了可基于系统或程序事件高效安全执行特定代码的通用能力，通用能力的使用者不再局限于内核开发者；eBPF 可由执行字节码指令、存储对象和 Helper 帮助函数组成，字节码指令在内核执行前必须通过 BPF 验证器 Verfier 的验证，同时在启用 BPF JIT 模式的内核中，会直接将字节码指令转成内核可执行的本地指令运行。</p><p>同时，eBPF 也逐渐在观测（跟踪、性能调优等）、安全和网络等领域发挥重要的角色。Facebook、NetFlix 、CloudFlare 等知名互联网公司内部广泛采用基于 eBPF 技术的各种程序用于性能分析、排查问题、负载均衡、防范 DDoS 攻击，据相关信息显示在 Facebook 的机器上内置一系列 eBPF 的相关工具。</p><p>相对于系统的性能分析和观测，eBPF 技术在网络技术中的表现，更是让人眼前一亮，BPF 技术与 XDP（eXpress Data Path） 和 TC（Traffic Control） 组合可以实现功能更加强大的网络功能，更可为 SDN 软件定义网络提供基础支撑。XDP 只作用与网络包的 Ingress 层面，BPF 钩子位于<strong>网络驱动中尽可能早的位置</strong>，<strong>无需进行原始包的复制</strong>就可以实现最佳的数据包处理性能，挂载的 BPF 程序是运行过滤的理想选择，可用于丢弃恶意或非预期的流量、进行 DDOS 攻击保护等场景；而 TC Ingress 比 XDP 技术处于更高层次的位置，BPF 程序在 L3 层之前运行，可以访问到与数据包相关的大部分元数据，是本地节点处理的理想的地方，可以用于流量监控或者 L3/L4 的端点策略控制，同时配合 TC egress 则可实现对于容器环境下更高维度和级别的网络结构。</p><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/packet-processor-xdp.png" alt="packet-processor-xdp.png" style="zoom:75%;"></p><p>图 2-2 XDP 技术架构</p><p>eBPF 相关的知名的开源项目包括但不限于以下：</p><ul><li>Facebook 高性能 4 层负载均衡器 <a href="https://github.com/facebookincubator/katran" target="_blank" rel="noopener">Katran</a>；</li><li><a href="https://cilium.io/" target="_blank" rel="noopener">Cilium</a> 为下一代微服务 ServiceMesh 打造了具备API感知和安全高效的容器网络方案；底层主要使用 XDP 和 TC 等相关技术；</li><li>IO Visor 项目开源的  <a href="https://github.com/iovisor/bcc" target="_blank" rel="noopener">BCC</a>、 <a href="https://github.com/iovisor/bpftrace" target="_blank" rel="noopener">BPFTrace</a> 和 <a href="https://github.com/iovisor/kubectl-trace" target="_blank" rel="noopener">Kubectl-Trace</a>：  <a href="https://github.com/iovisor/bcc" target="_blank" rel="noopener">BCC</a> 提供了更高阶的抽象，可以让用户采用 Python、C++ 和 Lua 等高级语言快速开发 BPF 程序；<a href="https://github.com/iovisor/bpftrace" target="_blank" rel="noopener">BPFTrace</a> 采用类似于 awk 语言快速编写 eBPF 程序；<a href="https://github.com/iovisor/kubectl-trace" target="_blank" rel="noopener">Kubectl-Trace</a> 则提供了在 kubernetes 集群中使用 BPF 程序调试的方便操作；</li><li>CloudFlare 公司开源的 <a href="https://github.com/cloudflare/ebpf_exporter" target="_blank" rel="noopener">eBPF Exporter</a> 和 <a href="https://github.com/cloudflare/bpftools" target="_blank" rel="noopener">bpf-tools</a>：<a href="https://github.com/cloudflare/ebpf_exporter" target="_blank" rel="noopener">eBPF Exporter</a> 将 eBPF 技术与监控 Prometheus 紧密结合起来；<a href="https://github.com/cloudflare/bpftools" target="_blank" rel="noopener">bpf-tools</a> 可用于网络问题分析和排查；</li></ul><p>越来越多的基于 eBPF 的项目如雨后脆笋一样开始蓬勃发展，而且逐步在社区中异军突起，成为一道风景线。比如 IO Visor 项目的 BCC 工具，为性能分析和观察提供了更加丰富的工具集：<a href="https://github.com/iovisor/bcc" target="_blank" rel="noopener">图片来源</a></p><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/bcc-tools.png" alt="image-20200423080103673" style="zoom:50%;"></p><p>图 2-3 Linux bcc/BPF 观测工具</p><p>同时，IO Visor 的 <a href="https://github.com/iovisor/bpf-docs" target="_blank" rel="noopener">bpf-docs</a> 包含了日常的文档，可以用于学习。</p><blockquote><p> 由于 eBPF 还在快速发展期，内核中的功能也日趋增强，一般推荐基于Linux 4.4+ (4.9 以上会更好) 内核的来使用 eBPF。部分 Linux Event 和 BPF 版本支持见下图：</p><p> <img src="https://www.do1618.com/wp-content/uploads/2020/08/linux_kernel_event_bpf.png" alt="linux_kernel_bpf" style="zoom:50%;"></p><p> 图 2-4 Linux 事件和 BPF 版本支持</p></blockquote><h3 id="2-2-eBPF-架构（观测）"><a href="#2-2-eBPF-架构（观测）" class="headerlink" title="2.2 eBPF 架构（观测）"></a>2.2 eBPF 架构（观测）</h3><p>基于 Linux 系统的观测工具中，eBPF 有着得天独厚的优势，高效、生产安全且内核中内置，特别的可以在内核中完成数据分析聚合比如直方图，与将数据发送到用户空间分析聚合相比，能够节省大量的数据复制传递带来的 CPU 消耗。</p><p>eBPF 整体结构图如下：</p><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/linux_ebpf_internals.png" alt="ebpf-arch" style="zoom:50%;"></p><p>图 2-5 eBPF 观测架构</p><p>eBPF 分为用户空间程序和内核程序两部分：</p><ul><li>用户空间程序负责加载 BPF 字节码至内核，如需要也会负责读取内核回传的统计信息或者事件详情；</li><li>内核中的 BPF 字节码负责在内核中执行特定事件，如需要也会将执行的结果通过 maps 或者 perf-event 事件发送至用户空间；</li></ul><p>其中用户空间程序与内核 BPF 字节码程序可以使用 map 结构实现双向通信，这为内核中运行的 BPF 字节码程序提供了更加灵活的控制。</p><p>用户空间程序与内核中的 BPF 字节码交互的流程主要如下：</p><ol><li>我们可以使用 LLVM 或者 GCC 工具将编写的 BPF 代码程序编译成 BPF 字节码；</li><li>然后使用加载程序 Loader 将字节码加载至内核；内核使用验证器（verfier） 组件保证执行字节码的安全性，以避免对内核造成灾难，在确认字节码安全后将其加载对应的内核模块执行；BPF 观测技术相关的程序程序类型可能是 kprobes/uprobes/tracepoint/perf_events 中的一个或多个，其中：<ul><li><strong>kprobes</strong>：实现内核中动态跟踪。 kprobes 可以跟踪到 Linux 内核中的函数入口或返回点，但是不是稳定 ABI 接口，可能会因为内核版本变化导致，导致跟踪失效。</li><li><strong>uprobes</strong>：用户级别的动态跟踪。与 kprobes 类似，只是跟踪的函数为用户程序中的函数。</li><li><strong>tracepoints</strong>：内核中静态跟踪。tracepoints 是内核开发人员维护的跟踪点，能够提供稳定的 ABI 接口，但是由于是研发人员维护，数量和场景可能受限。</li><li><strong>perf_events</strong>：定时采样和 PMC。</li></ul></li><li>内核中运行的 BPF 字节码程序可以使用两种方式将测量数据回传至用户空间<ul><li><strong>maps</strong> 方式可用于将内核中实现的统计摘要信息（比如测量延迟、堆栈信息）等回传至用户空间；</li><li><strong>perf-event</strong> 用于将内核采集的事件实时发送至用户空间，用户空间程序实时读取分析；</li></ul></li></ol><blockquote><p>如无特殊说明，本文中所说的 BPF 都是泛指 BPF 技术。</p></blockquote><h3 id="2-3-eBPF-的限制"><a href="#2-3-eBPF-的限制" class="headerlink" title="2.3 eBPF 的限制"></a>2.3 eBPF 的限制</h3><p>eBPF 技术虽然强大，但是为了保证内核的处理安全和及时响应，内核中的 eBPF 技术也给予了诸多限制，当然随着技术的发展和演进，限制也在逐步放宽或者提供了对应的解决方案。</p><ul><li><p>eBPF 程序不能调用任意的内核参数，只限于内核模块中列出的 BPF Helper 函数，函数支持列表也随着内核的演进在不断增加。（todo 添加个数说明）</p></li><li><p>eBPF 程序不允许包含无法到达的指令，防止加载无效代码，延迟程序的终止。</p></li><li><p>eBPF 程序中循环次数限制且必须在有限时间内结束，这主要是用来防止在 kprobes 中插入任意的循环，导致锁住整个系统；解决办法包括展开循环，并为需要循环的常见用途添加辅助函数。Linux 5.3 在 BPF 中包含了对有界循环的支持，它有一个可验证的运行时间上限。</p></li><li><p>eBPF 堆栈大小被限制在 MAX_BPF_STACK，截止到内核 Linux 5.8 版本，被设置为 512；参见 <a href="https://github.com/torvalds/linux/blob/v5.8/include/linux/filter.h" target="_blank" rel="noopener">include/linux/filter.h</a>，这个限制特别是在栈上存储多个字符串缓冲区时：一个char[256]缓冲区会消耗这个栈的一半。目前没有计划增加这个限制，解决方法是改用 bpf 映射存储，它实际上是无限的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* BPF program can access up to 512 bytes of stack space. */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MAX_BPF_STACK512</span></span><br></pre></td></tr></table></figure></li><li><p>eBPF 字节码大小最初被限制为 4096 条指令，截止到内核 Linux 5.8 版本， 当前已将放宽至 100 万指令（ BPF_COMPLEXITY_LIMIT_INSNS），参见：<a href="https://github.com/torvalds/linux/blob/v5.8/include/linux/bpf.h" target="_blank" rel="noopener">include/linux/bpf.h</a>，对于无权限的BPF程序，仍然保留4096条限制 ( BPF_MAXINSNS )；新版本的 eBPF 也支持了多个 eBPF 程序级联调用，虽然传递信息存在某些限制，但是可以通过组合实现更加强大的功能。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BPF_COMPLEXITY_LIMIT_INSNS      1000000 <span class="comment">/* yes. 1M insns */</span></span></span><br></pre></td></tr></table></figure></li></ul><h3 id="2-4-eBPF-与内核模块对比"><a href="#2-4-eBPF-与内核模块对比" class="headerlink" title="2.4 eBPF 与内核模块对比"></a>2.4 eBPF 与内核模块对比</h3><p>在 Linux 观测方面，eBPF 总是会拿来与 kernel 模块方式进行对比，eBPF 在安全性、入门门槛上比内核模块都有优势，这两点在观测场景下对于用户来讲尤其重要。</p><table><thead><tr><th>维度</th><th>Linux 内核模块</th><th>eBPF</th></tr></thead><tbody><tr><td>kprobes/tracepoints</td><td>支持</td><td>支持</td></tr><tr><td><strong>安全性</strong></td><td>可能引入安全漏洞或导致内核 Panic</td><td>通过验证器进行检查，可以保障内核安全</td></tr><tr><td>内核函数</td><td>可以调用内核函数</td><td>只能通过 BPF Helper 函数调用</td></tr><tr><td>编译性</td><td>需要编译内核</td><td>不需要编译内核，引入头文件即可</td></tr><tr><td>运行</td><td>基于相同内核运行</td><td>基于稳定 ABI 的 BPF 程序可以编译一次，各处运行</td></tr><tr><td>与应用程序交互</td><td>打印日志或文件</td><td>通过 perf_event 或 map 结构</td></tr><tr><td>数据结构丰富性</td><td>一般</td><td>丰富</td></tr><tr><td><strong>入门门槛</strong></td><td>高</td><td>低</td></tr><tr><td><strong>升级</strong></td><td>需要卸载和加载，可能导致处理流程中断</td><td>原子替换升级，不会造成处理流程中断</td></tr><tr><td>内核内置</td><td>视情况而定</td><td>内核内置支持</td></tr></tbody></table><p>表格 2 eBPF 与 Linux 内核模块方式对比</p><h2 id="3-应用案例"><a href="#3-应用案例" class="headerlink" title="3. 应用案例"></a>3. 应用案例</h2><p>大名鼎鼎的性能分析大师 Brendan Gregg 等编写了诸多的 BCC 或 BPFTrace 的工具集可以拿来直接使用，完全可以满足我们日常问题分析和排查。</p><p>BCC 在 CentOS 7 系统中可以通过 yum 快速安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># yum install bcc -y</span></span><br><span class="line">Resolving Dependencies</span><br><span class="line">--&gt; Running transaction check</span><br><span class="line">---&gt; Package bcc.x86_64 0:0.8.0-1.el7 will be updated</span><br><span class="line">--&gt; Processing Dependency: bcc(x86-64) = 0.8.0-1.el7 <span class="keyword">for</span> package: python-bcc-0.8.0-1.el7.x86_64</span><br><span class="line">---&gt; Package bcc.x86_64 0:0.10.0-1.el7 will be an update</span><br><span class="line">--&gt; Processing Dependency: bcc-tools = 0.10.0-1.el7 <span class="keyword">for</span> package: bcc-0.10.0-1.el7.x86_64</span><br><span class="line">--&gt; Running transaction check</span><br><span class="line">---&gt; Package bcc-tools.x86_64 0:0.8.0-1.el7 will be updated</span><br><span class="line">---&gt; Package bcc-tools.x86_64 0:0.10.0-1.el7 will be an update</span><br><span class="line">---&gt; Package python-bcc.x86_64 0:0.8.0-1.el7 will be updated</span><br><span class="line">---&gt; Package python-bcc.x86_64 0:0.10.0-1.el7 will be an update</span><br><span class="line">--&gt; Finished Dependency Resolution</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>其他系统的安装方式参见：<a href="https://github.com/iovisor/bcc/blob/master/INSTALL.md" target="_blank" rel="noopener">INSTALL.md</a></p><p>BCC 中每一个工具都有一个对应的使用样例，比如 <a href="https://github.com/iovisor/bcc/blob/master/tools/execsnoop.py" target="_blank" rel="noopener">execsnoop.py</a> 和 <a href="https://github.com/iovisor/bcc/blob/master/tools/execsnoop_example.txt" target="_blank" rel="noopener">execsnoop_example.txt</a>，在使用样例中有详细的使用说明，而且 BCC 中的工具使用的帮助文档格式基本类似，上手非常方便。</p><blockquote><p>BCC 的程序一般情况下都需要 root 用户来运行。</p></blockquote><h3 id="3-1-Linux-性能分析-60-秒-（BPF版本）"><a href="#3-1-Linux-性能分析-60-秒-（BPF版本）" class="headerlink" title="3.1 Linux 性能分析 60 秒 （BPF版本）"></a>3.1 Linux 性能分析 60 秒 （BPF版本）</h3><p>英文原文 <a href="https://netflixtechblog.com/linux-performance-analysis-in-60-000-milliseconds-accc10403c55" target="_blank" rel="noopener">Linux Performance Analysis in 60,000 Milliseconds</a>，<a href="http://www.brendangregg.com/blog/2015-12-03/linux-perf-60s-video.html" target="_blank" rel="noopener">视频地址</a> </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">uptime</span><br><span class="line">dmesg | tail</span><br><span class="line">vmstat 1</span><br><span class="line">mpstat -P ALL 1</span><br><span class="line">pidstat 1</span><br><span class="line">iostat -xz 1</span><br><span class="line">free -m</span><br><span class="line">sar -n DEV 1</span><br><span class="line">sar -n TCP,ETCP 1</span><br><span class="line">top</span><br></pre></td></tr></table></figure><p><strong>60s 系列 BPF 版本如下：</strong></p><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/ebpf_60s.png" alt="ebpf_60s" style="zoom:50%;"></p><p>图 3-1 60s 排查之 BPF 版本</p><p>对于在系统中运行的 “闪电侠” 程序，运行周期非常短，但是可能会带来系统的抖动延时，我们采用 <code>top</code> 命令查看一般情况下难以发现，我们可以使用 BCC 提供的工具 <code>execsnoop</code> 来进行排查：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Trace file opens with process and filename: opensnoop</span></span><br><span class="line"><span class="comment">#/usr/share/bcc/tools/execsnoop </span></span><br><span class="line">PCOMM            PID    PPID   RET ARGS</span><br><span class="line">sleep            3334   21029    0 /usr/bin/sleep 3</span><br><span class="line">sleep            3339   21029    0 /usr/bin/sleep 3</span><br><span class="line">conntrack        3341   1112     0 /usr/sbin/conntrack --stats</span><br><span class="line">conntrack        3342   1112     0 /usr/sbin/conntrack --count</span><br><span class="line">sleep            3344   21029    0 /usr/bin/sleep 3</span><br><span class="line">iptables-save    3347   9211     0 /sbin/iptables-save -t filter</span><br><span class="line">iptables-save    3348   9211     0 /sbin/iptables-save -t nat</span><br></pre></td></tr></table></figure><h3 id="3-2-slab-dentry-过大导致的网络抖动排查"><a href="#3-2-slab-dentry-过大导致的网络抖动排查" class="headerlink" title="3.2 slab dentry 过大导致的网络抖动排查"></a>3.2 <strong>slab dentry 过大导致的网络抖动排查</strong></h3><p><strong>现象</strong></p><p>网络 ping 的延时间歇性有规律出现抖动</p><p><strong>问题排查</strong></p><p>采用 <code>execsnoop</code>  分析发现，某个运行命令<code>cat /proc/slabinfo</code>的运行时间间隔与抖动的频率完全吻合，顺着这个的线索定位，我们发现云厂商提供的 Java 版本的云监控会定期调用 <code>cat /proc/slabinfo</code> 来获取内核缓存的信息；</p><p>通过命令 <code>slabtop</code> 发现系统中的 <code>dentry</code> 项的内存占用非常大，系统内存 128G，<code>dentry</code> 占用 70G 以上，所以问题很快就定位到是系统在打开文件方面可能有相关问题；</p><p><strong>根因分析</strong></p><p>我们使用对于打开文件跟踪的 BCC 工具 <code>opensnoop</code> 很快就定位到是某个程序频繁创建和删除临时文件，最终定位为某个 PHP 程序设置的调用方式存在问题，导致每次请求会创建和删除临时文件；代码中将 http 调用中的 <code>contentType</code> 设置成了 <code>Http::CONTENT_TYPE_UPLOAD</code>，导致每次请求都会生成临时文件，修改成 <code>application/x-www-form-urlencoded</code> 问题解决。</p><p>问题的原理可参考 <a href="https://developer.aliyun.com/article/697773" target="_blank" rel="noopener">记一次对网络抖动经典案例的分析</a> 和 <a href="https://yq.aliyun.com/articles/131870" target="_blank" rel="noopener">systemtap脚本分析系统中dentry SLAB占用过高问题</a></p><h3 id="3-3-生成火焰图"><a href="#3-3-生成火焰图" class="headerlink" title="3.3 生成火焰图"></a>3.3 生成火焰图</h3><p>火焰图是帮助我们对系统耗时进行可视化的图表，能够对程序中那些代码经常被执行给出一个清晰的展现。Brendan Gregg 是火焰图的创建者，他在 <a href="https://github.com/brendangregg/FlameGraph" target="_blank" rel="noopener">GitHub</a> 上维护了一组脚本可以轻松生成需要的可视化格式数据。使用 BCC 中的工具 <code>profile</code> 可很方面地收集道 CPU 路径的数据，基于数据采用工具可以轻松地生成火焰图，查找到程序的性能瓶颈。</p><blockquote><p>使用 <code>profile</code> 搜集火焰图的程序没有任何限制和改造</p></blockquote><p><code>profile</code> 工具可以让我们轻松对于系统或者程序的 CPU 性能路径进行可视化分析：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">/usr/share/bcc/tools/profile -h</span><br><span class="line">usage: profile [-h] [-p PID | -L TID] [-U | -K] [-F FREQUENCY | -c COUNT] [-d]</span><br><span class="line">               [-a] [-I] [-f] [--stack-storage-size STACK_STORAGE_SIZE]</span><br><span class="line">               [-C CPU]</span><br><span class="line">               [duration]</span><br><span class="line"></span><br><span class="line">Profile CPU stack traces at a timed interval</span><br><span class="line"></span><br><span class="line">positional arguments:</span><br><span class="line">  duration              duration of trace, in seconds</span><br><span class="line"></span><br><span class="line">optional arguments:</span><br><span class="line">  -h, --help            show this help message and exit</span><br><span class="line">  -p PID, --pid PID     profile process with this PID only</span><br><span class="line">  -L TID, --tid TID     profile thread with this TID only</span><br><span class="line">  -U, --user-stacks-only</span><br><span class="line">                        show stacks from user space only (no kernel space</span><br><span class="line">                        stacks)</span><br><span class="line">  -K, --kernel-stacks-only</span><br><span class="line">                        show stacks from kernel space only (no user space</span><br><span class="line">                        stacks)</span><br><span class="line">  -F FREQUENCY, --frequency FREQUENCY</span><br><span class="line">                        sample frequency, Hertz</span><br><span class="line">  -c COUNT, --count COUNT</span><br><span class="line">                        sample period, number of events</span><br><span class="line">  -d, --delimited       insert delimiter between kernel/user stacks</span><br><span class="line">  -a, --annotations     add _[k] annotations to kernel frames</span><br><span class="line">  -I, --include-idle    include CPU idle stacks</span><br><span class="line">  -f, --folded          output folded format, one line per stack (for flame</span><br><span class="line">                        graphs)</span><br><span class="line">  --stack-storage-size STACK_STORAGE_SIZE</span><br><span class="line">                        the number of unique stack traces that can be stored</span><br><span class="line">                        and displayed (default 16384)</span><br><span class="line">  -C CPU, --cpu CPU     cpu number to run profile on</span><br><span class="line"></span><br><span class="line">examples:</span><br><span class="line">    ./profile             # profile stack traces at 49 Hertz until Ctrl-C</span><br><span class="line">    ./profile -F 99       # profile stack traces at 99 Hertz</span><br><span class="line">    ./profile -c 1000000  # profile stack traces every 1 in a million events</span><br><span class="line">    ./profile 5           # profile at 49 Hertz for 5 seconds only</span><br><span class="line">    ./profile -f 5        # output in folded format for flame graphs</span><br><span class="line">    ./profile -p 185      # only profile process with PID 185</span><br><span class="line">    ./profile -L 185      # only profile thread with TID 185</span><br><span class="line">    ./profile -U          # only show user space stacks (no kernel)</span><br><span class="line">    ./profile -K          # only show kernel space stacks (no user)</span><br></pre></td></tr></table></figure><p><code>profile</code>  配合 <code>FlameGraph</code> 可以轻松帮我们绘制出 CPU 使用的火焰图。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ profile -af 30 &gt; out.stacks01</span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/brendangregg/FlameGraph</span><br><span class="line">$ <span class="built_in">cd</span> FlameGraph</span><br><span class="line">$ ./flamegraph.pl --color=java &lt; ../out.stacks01 &gt; out.svg</span><br></pre></td></tr></table></figure><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/flame.png" alt="image-20200806194324643" style="zoom: 67%;"></p><p>图 3-2 火焰图</p><h3 id="3-3-排查网络调用来源"><a href="#3-3-排查网络调用来源" class="headerlink" title="3.3 排查网络调用来源"></a>3.3 排查网络调用来源</h3><p>在生产场景下，会有些特定场景需要抓取连接到外网特定地址的程序，这时候我们可以采用 BCC 工具集中的 <code>tcplife</code> 来定位。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">/usr/share/bcc/tools/tcplife -h</span><br><span class="line">usage: tcplife [-h] [-T] [-t] [-w] [-s] [-p PID] [-L LOCALPORT]</span><br><span class="line">               [-D REMOTEPORT]</span><br><span class="line"></span><br><span class="line">Trace the lifespan of TCP sessions and summarize</span><br><span class="line"></span><br><span class="line">optional arguments:</span><br><span class="line">  -h, --help            show this help message and exit</span><br><span class="line">  -T, --time            include time column on output (HH:MM:SS)</span><br><span class="line">  -t, --timestamp       include timestamp on output (seconds)</span><br><span class="line">  -w, --wide            wide column output (fits IPv6 addresses)</span><br><span class="line">  -s, --csv             comma separated values output</span><br><span class="line">  -p PID, --pid PID     trace this PID only</span><br><span class="line">  -L LOCALPORT, --localport LOCALPORT</span><br><span class="line">                        comma-separated list of local ports to trace.</span><br><span class="line">  -D REMOTEPORT, --remoteport REMOTEPORT</span><br><span class="line">                        comma-separated list of remote ports to trace.</span><br><span class="line"></span><br><span class="line">examples:</span><br><span class="line">    ./tcplife           # trace all TCP connect()s</span><br><span class="line">    ./tcplife -t        # include time column (HH:MM:SS)</span><br><span class="line">    ./tcplife -w        # wider colums (fit IPv6)</span><br><span class="line">    ./tcplife -stT      # csv output, with times &amp; timestamps</span><br><span class="line">    ./tcplife -p 181    # only trace PID 181</span><br><span class="line">    ./tcplife -L 80     # only trace local port 80</span><br><span class="line">    ./tcplife -L 80,81  # only trace local ports 80 and 81</span><br><span class="line">    ./tcplife -D 80     # only trace remote port 80</span><br></pre></td></tr></table></figure><p>通过在机器上使用 <code>tcplife</code> 来获取的网络连接信息，我们可以看到包括了 PID、COMM、本地 IP 地址、本地端口、远程 IP 地址和远程端口，通过这些信息非常方便排查到连接到特定 IP 地址的程序，尤其是连接的过程非常短暂，通过 <code>netstat</code> 等其他工具不容易排查的场景。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /usr/share/bcc/tools/tcplife</span></span><br><span class="line">PID   COMM             IP LADDR                      LPORT RADDR                  RPORT  TX_KB  RX_KB MS</span><br><span class="line">1776  blackbox_export  4  169.254.20.10              35830 169.254.20.10          53       0      0 0.36</span><br><span class="line">27150 node-cache       4  169.254.20.10              53    169.254.20.10          35830    0      0 0.36</span><br><span class="line">12511 coredns          4  127.0.0.1                  58492 127.0.0.1              8080     0      0 0.32</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>如果我们想知道更加详细的 TCP 状态情况，那么 <code>tcptracer</code> 可展示更加详细的 TCP 状态，其中 C 代表 Connect X 表示关闭， A 代表 Accept。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /usr/share/bcc/tools/tcptracer </span></span><br><span class="line">Tracing TCP established connections. Ctrl-C to end.</span><br><span class="line">T  PID    COMM             IP SADDR            DADDR            SPORT  DPORT</span><br><span class="line">C  21066  ilogtail         4  10.81.128.12     100.100.49.128   40906  80</span><br><span class="line">X  21066  ilogtail         4  10.81.128.12     100.100.49.128   40906  80</span><br><span class="line">C  21066  ilogtail         4  10.81.128.12     100.100.49.128   40908  80</span><br><span class="line">X  21066  ilogtail         4  10.81.128.12     100.100.49.128   40908  80</span><br></pre></td></tr></table></figure><p><code>tcpstates</code> 还能够展示出来 TCP 状态机的流转情况：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /usr/share/bcc/tools/tcpstates</span></span><br><span class="line">SKADDR           C-PID C-COMM     LADDR           LPORT RADDR           RPORT OLDSTATE    -&gt; NEWSTATE    MS</span><br><span class="line">ffff9fd7e8192000 22384 curl       100.66.100.185  0     52.33.159.26    80    CLOSE       -&gt; SYN_SENT    0.000</span><br><span class="line">ffff9fd7e8192000 0     swapper/5  100.66.100.185  63446 52.33.159.26    80    SYN_SENT    -&gt; ESTABLISHED 1.373</span><br><span class="line">ffff9fd7e8192000 22384 curl       100.66.100.185  63446 52.33.159.26    80    ESTABLISHED -&gt; FIN_WAIT1   176.042</span><br></pre></td></tr></table></figure><p>同样，我们也可以实时获取到 TCP 连接超时或者重连的网络连接；也可以通过抓取 UDP包相关的连接信息，用于定位诸如 DNS 请求超时或者 DNS 请求的发起进程。</p><h2 id="4-编写-BPF-程序"><a href="#4-编写-BPF-程序" class="headerlink" title="4. 编写 BPF 程序"></a>4. 编写 BPF 程序</h2><p>对于大多数开发者而言，更多的是基于 BPF 技术之上编写解决我们日常遇到的各种问题，当前 BCC 和 BPFTrace 两个项目在观测和性能分析上已经有了诸多灵活且功能强大的工具箱，完全可以满足我们日常使用。</p><ul><li><a href="https://github.com/iovisor/bcc" target="_blank" rel="noopener">BCC</a> 提供了更高阶的抽象，可以让用户采用 Python、C++ 和 Lua 等高级语言快速开发 BPF 程序；</li><li><a href="https://github.com/iovisor/bpftrace" target="_blank" rel="noopener">BPFTrace</a> 采用类似于 awk 语言快速编写 eBPF 程序；</li></ul><p>更早期的工具则是使用 C 语言来编写 BPF 程序，使用 LLVM clang 编译成 BPF 代码，这对于普通使用者上手有不少门槛当前仅限于对于 eBPF 技术更加深入的学习场景。</p><h3 id="4-1-BCC-版本-HelloWorld"><a href="#4-1-BCC-版本-HelloWorld" class="headerlink" title="4.1 BCC 版本 HelloWorld"></a>4.1 BCC 版本 HelloWorld</h3><p><img src="https://www.do1618.com/wp-content/uploads/2020/08/bcc-internals.png" style="zoom: 75%;"></p><p>图 4-1 BCC 整体架构</p><p>使用 BCC 前端绑定语言 Python 编写的 Hello World 版本：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/python3</span><br><span class="line"></span><br><span class="line">from bcc import BPF</span><br><span class="line"></span><br><span class="line"># This may not work for 4.17 on x64, you need replace kprobe__sys_clone with kprobe____x64_sys_clone</span><br><span class="line">prog = &quot;&quot;&quot;</span><br><span class="line">int kprobe__sys_clone(void *ctx) &#123;</span><br><span class="line">bpf_trace_printk(&quot;Hello, World!\\n&quot;);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line"></span><br><span class="line">b = BPF(text=prog, debug=0x04)</span><br><span class="line">b.trace_print()</span><br></pre></td></tr></table></figure><p>运行程序前需要安装过 bcc 相关工具包，当运行正常的时候我们发现每当 <code>sys_clone</code> 系统调用时，运行的控制台上就会打印 “Hello, World!”，在打印文字前面还包含了调用程序的进程名称，进程 ID 等信息；</p><blockquote><p>如果运行报错，可能是缺少头文件，一般安装 kernel-devel 包即可。</p></blockquote><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># python ./hello.py</span><br><span class="line">         kubelet-8349  [006] d... 33637334.829981: : Hello, World!</span><br><span class="line">         kubelet-8349  [006] d... 33637334.838594: : Hello, World!</span><br><span class="line">         kubelet-8349  [006] d... 33637334.843788: : Hello, World!</span><br></pre></td></tr></table></figure><h3 id="4-3-BPFTrace"><a href="#4-3-BPFTrace" class="headerlink" title="4.3 BPFTrace"></a>4.3 BPFTrace</h3><p>BPFTrace 是基于 BPF 和 BCC 的开源项目，与 BCC 不同的是其提供了更高层次的抽象，可以使用类似 AWK 脚本语言来编写基于 BPF 的跟踪或者性能排查工具，更加易于入门和编写，该工具的主要灵感来自于 Solaris 的 D 语言。BPFTrace 更方便与编写单行的程序。BPFTrace 与 BCC 一样也是 IO Visor 组织下的项目，仓库参见 <a href="https://github.com/iovisor/bpftrace" target="_blank" rel="noopener">bpftrace</a>。更加深入的学习资料参见：<a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md" target="_blank" rel="noopener">Reference Guide</a> 和 <a href="https://github.com/iovisor/bpftrace/blob/master/docs/tutorial_one_liners.md" target="_blank" rel="noopener">One-Liner Tutorial</a>。</p><p>BPFTrace 使用 LLVM 将脚本编译成 BPF 二进制码，后续使用 BCC 与 Linux 内核进行交互。从功能层面上讲，BPFTrace 的定制性和灵活性不如 BCC，但是比 BCC 工具更加易于理解和使用，降低了 BPF 技术的使用门槛。</p><p>使用样例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 统计进程调用 sys_enter 的次数</span></span><br><span class="line"><span class="comment">#bpftrace -e 'tracepoint:raw_syscalls:sys_enter &#123; @[comm] = count(); &#125;'</span></span><br><span class="line">Attaching 1 probe...</span><br><span class="line">^C</span><br><span class="line"></span><br><span class="line">@[bpftrace]: 6</span><br><span class="line">@[systemd]: 24</span><br><span class="line">@[snmp-pass]: 96</span><br><span class="line">@[sshd]: 125</span><br><span class="line"></span><br><span class="line"><span class="comment"># 统计内核中函数堆栈的次数</span></span><br><span class="line"><span class="comment"># bpftrace -e 'profile:hz:99 &#123; @[kstack] = count(); &#125;'</span></span><br><span class="line">Attaching 1 probe...</span><br><span class="line">^C</span><br><span class="line"></span><br><span class="line">[...]</span><br><span class="line">@[</span><br><span class="line">filemap_map_pages+181</span><br><span class="line">__handle_mm_fault+2905</span><br><span class="line">handle_mm_fault+250</span><br><span class="line">__do_page_fault+599</span><br><span class="line">async_page_fault+69</span><br><span class="line">]: 12</span><br><span class="line">[...]</span><br><span class="line">@[</span><br><span class="line">cpuidle_enter_state+164</span><br><span class="line">do_idle+390</span><br><span class="line">cpu_startup_entry+111</span><br><span class="line">start_secondary+423</span><br><span class="line">secondary_startup_64+165</span><br><span class="line">]: 22122</span><br></pre></td></tr></table></figure><h3 id="4-3-C-语言原生方式"><a href="#4-3-C-语言原生方式" class="headerlink" title="4.3 C 语言原生方式"></a>4.3 C 语言原生方式</h3><p>采用 LLVM Clang 的方式编译会涉及到内核编译环境搭建，而且还需要自己编译 Makefile 等操作，属于高级用户使用：</p><p>bpf_program.c</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;linux/bpf.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SEC(NAME) __attribute__((section(NAME), used))</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="title">int</span> <span class="params">(*bpf_trace_printk)</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *fmt, <span class="keyword">int</span> fmt_size,</span></span></span><br><span class="line"><span class="function"><span class="params">                               ...)</span> </span>= (<span class="keyword">void</span> *)BPF_FUNC_trace_printk;</span><br><span class="line"></span><br><span class="line">SEC(<span class="string">"tracepoint/syscalls/sys_enter_execve"</span>)</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">bpf_prog</span><span class="params">(<span class="keyword">void</span> *ctx)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">char</span> msg[] = <span class="string">"Hello, BPF World!"</span>;</span><br><span class="line">  bpf_trace_printk(msg, <span class="keyword">sizeof</span>(msg));</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> _license[] SEC(<span class="string">"license"</span>) = <span class="string">"GPL"</span>;</span><br></pre></td></tr></table></figure><p>loader.c</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"bpf_load.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> **argv)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (load_bpf_file(<span class="string">"bpf_program.o"</span>) != <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"The kernel didn't load the BPF program\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  read_trace_pipe();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Makefile 文件（部分）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">build: <span class="variable">$&#123;BPFCODE.c&#125;</span> <span class="variable">$&#123;BPFLOADER&#125;</span></span><br><span class="line">$(CLANG) -O2 -target bpf -c $(BPFCODE:=.c) $(CCINCLUDE) -o <span class="variable">$&#123;BPFCODE:=.o&#125;</span></span><br></pre></td></tr></table></figure><p>其中 clang 编译中的选型 <code>-target bpf</code> 表明我们将代码编译成 bpf 的字节码。</p><p>完整的程序参见：<a href="https://github.com/DavadDi/linux-observability-with-bpf/tree/master/code/chapter-2/hello_world" target="_blank" rel="noopener">hello_world</a>；更多的样例代码可以参见对应内核中 <code>kernel-src/samples/bpf/</code> 下的样例代码。</p><h2 id="5-国内大厂-eBPF-实践经验"><a href="#5-国内大厂-eBPF-实践经验" class="headerlink" title="5. 国内大厂 eBPF 实践经验"></a>5. 国内大厂 eBPF 实践经验</h2><ul><li><p><a href="https://www.infoq.cn/article/OVCVwQijztA7JlexgDOc" target="_blank" rel="noopener">eBPF 在网易轻舟云原生的应用实践</a></p></li><li><p><a href="https://mp.weixin.qq.com/s?__biz=MzI5ODQ2MzI3NQ==&amp;mid=2247491111&amp;idx=2&amp;sn=db348d6f13e1df4b3b9aba2dce0ba0e4&amp;chksm=eca42763dbd3ae757530f6922ca1748736e42eb863e01076e94622c81be542e5582c9678874b&amp;scene=27#wechat_redirect" target="_blank" rel="noopener">性能提升40%: 腾讯 TKE 用 eBPF绕过 conntrack 优化K8s Service</a></p></li><li><p><a href="https://www.infoq.cn/article/Tc5Bugo5vBAkyaRb5CCU" target="_blank" rel="noopener">字节跳动：eBPF 技术实践：高性能 ACL</a></p></li><li><p><a href="https://www.infoq.cn/article/c6t2IL23O6EbdQgUpQhb" target="_blank" rel="noopener">阿里：eBPF Internal：Instructions and Runtime</a></p></li><li><p><a href="https://mp.weixin.qq.com/s?__biz=MzI5ODQ2MzI3NQ==&amp;mid=2247488831&amp;idx=1&amp;sn=3da3a976439d0134e3789a3e035ea1f0&amp;chksm=eca42c7bdbd3a56d35c482d07798ee9d48a2f1103724f78f0634953ab33d8bd1ab9700190fb6&amp;scene=27#wechat_redirect" target="_blank" rel="noopener">使用 ebpf 深入分析容器网络 dup 包问题</a></p></li><li><p><a href="https://www.infoq.cn/article/JmCbkA0XX9NqrcX6loIo" target="_blank" rel="noopener">eBay 云计算“网”事：网络超时篇</a>  <a href="https://mp.weixin.qq.com/s/IpUr3pnVgMInqKAkBfawtA" target="_blank" rel="noopener">eBay云计算“网”事｜网络丢包篇</a></p></li><li><p><a href="https://www.infoq.cn/article/mu-1bFHNmrdd0kybgPXx" target="_blank" rel="noopener">字节跳动容器化场景下的性能优化实践</a></p></li></ul><h2 id="6-参考资料"><a href="#6-参考资料" class="headerlink" title="6. 参考资料"></a>6. 参考资料</h2><ol><li><p><a href="https://www.tcpdump.org/papers/bpf-usenix93.pdf" target="_blank" rel="noopener">The BSD Packet Filter: A New Architecture for User-level Packet Capture</a></p></li><li><p><a href="http://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/" target="_blank" rel="noopener">[译] Cilium：BPF 和 XDP 参考指南（2019） </a>   <a href="https://docs.cilium.io/en/v1.8/bpf/" target="_blank" rel="noopener">Cillum BPF and XDP Reference Guide</a></p></li><li><p><a href="https://blog.cloudflare.com/zh/cloudflare-architecture-and-how-bpf-eats-the-world-zh/" target="_blank" rel="noopener">Cloudflare架构以及BPF如何占据世界</a></p></li><li><p><a href="https://www.luoow.com/dc_tw/106805603" target="_blank" rel="noopener">關於 BPF 和 eBPF 的筆記</a></p></li><li><p><a href="https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/" target="_blank" rel="noopener">Dive into BPF: a list of reading material</a>  <a href="https://blog.csdn.net/F8qG7f9YD02Pe/article/details/79815702" target="_blank" rel="noopener">中文</a></p></li><li><p><a href="https://www.ibm.com/developerworks/cn/linux/l-lo-eBPF-history/index.html" target="_blank" rel="noopener">eBPF 简史</a></p></li><li><p><a href="https://www.youtube.com/watch?v=znBGt7oHJyQ" target="_blank" rel="noopener">https://www.youtube.com/watch?v=znBGt7oHJyQ</a></p></li><li><p><a href="https://www.infradead.org/~mchehab/kernel_docs/bpf/index.html" target="_blank" rel="noopener">BPF Documentation</a>  <a href="https://www.kernel.org/doc/html/latest/bpf/bpf_devel_QA.html#q-how-do-i-indicate-which-tree-bpf-vs-bpf-next-my-patch-should-be-applied-to" target="_blank" rel="noopener">HOWTO interact with BPF subsystem</a></p></li><li><p><a href="https://www.infradead.org/~mchehab/kernel_docs/bpf/index.html" target="_blank" rel="noopener">Linux 内核 BPF 文档</a></p></li><li><p><a href="http://www.brendangregg.com/ebpf.html" target="_blank" rel="noopener">Linux Extended BPF (eBPF) Tracing Tools</a>  Brendan Gregg</p></li><li><p><a href="https://mp.weixin.qq.com/s/3BQU9AYh1ScZ_1V17BJ4wg" target="_blank" rel="noopener">性能提升40%: 腾讯 TKE 用 eBPF绕过 conntrack 优化K8s Service</a></p></li><li><p><a href="https://tonydeng.github.io/sdn-handbook/" target="_blank" rel="noopener">SDN handbook</a></p></li><li><p>Linux BPF 帮助文档 <a href="https://man7.org/linux/man-pages/man2/bpf.2.html" target="_blank" rel="noopener">bpf(2)</a>  <a href="https://man7.org/linux/man-pages/man7/bpf-helpers.7.html" target="_blank" rel="noopener">bpf-helpers(7)</a>  <a href="https://man7.org/linux/man-pages/man8/tc-bpf.8.html" target="_blank" rel="noopener">tc-bpf(8)</a> </p><blockquote><ol><li><p>user commands</p></li><li><p>system calls</p></li><li><p>library functions</p></li><li><p>special files</p></li><li><p>file formats and filesystems</p></li><li><p>games</p></li><li><p>overview and miscellany section</p></li><li><p>administration and privileged commands</p></li></ol><p>参考：<a href="https://man7.org/linux/man-pages/index.html" target="_blank" rel="noopener">https://man7.org/linux/man-pages/index.html</a></p></blockquote></li><li></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;eBPF-技术简介&quot;&gt;&lt;a href=&quot;#eBPF-技术简介&quot; class=&quot;headerlink&quot; title=&quot;eBPF 技术简介&quot;&gt;&lt;/a&gt;eBPF 技术简介&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://www.do1618.com/wp-conten
      
    
    </summary>
    
      <category term="bpf" scheme="http://www.cn18k.com/categories/bpf/"/>
    
    
      <category term="bpf" scheme="http://www.cn18k.com/tags/bpf/"/>
    
      <category term="ebpf" scheme="http://www.cn18k.com/tags/ebpf/"/>
    
  </entry>
  
  <entry>
    <title>Serverless 漫谈</title>
    <link href="http://www.cn18k.com/2019/12/02/serverless/"/>
    <id>http://www.cn18k.com/2019/12/02/serverless/</id>
    <published>2019-12-02T03:00:00.000Z</published>
    <updated>2019-12-02T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong> 正文 </strong><br><br><br><div class="row">    <embed src="/assets/serverless_intro-v1.pdf" width="100%" height="550" type="application/pdf"></div></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;strong&gt; 正文 &lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;

	&lt;div class=&quot;row&quot;&gt;
    &lt;embed src=&quot;/assets/serverless_intro-v1.pdf&quot; width=&quot;100%&quot; height=&quot;550&quot; type=&quot;ap
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="serverless" scheme="http://www.cn18k.com/tags/serverless/"/>
    
  </entry>
  
  <entry>
    <title>Kubelet - Pod 创建之 CRI 和 CNI 源码剖析</title>
    <link href="http://www.cn18k.com/2019/12/02/kubelet-cri-cni/"/>
    <id>http://www.cn18k.com/2019/12/02/kubelet-cri-cni/</id>
    <published>2019-12-02T03:00:00.000Z</published>
    <updated>2019-12-02T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文源码跟踪基于  1.12.6</p><p><img src="https://www.do1618.com/wp-content/uploads/2019/12/CRI-CNI.jpg" alt=""></p><h2 id="CRI"><a href="#CRI" class="headerlink" title="CRI"></a>CRI</h2><p><img src="https://www.do1618.com/wp-content/uploads/2019/12/cri_shim.png" alt=""></p><h3 id="创建-Pod-入口"><a href="#创建-Pod-入口" class="headerlink" title="创建 Pod 入口"></a>创建 Pod 入口</h3><p>k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_manager.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// kubeGenericRuntimeManager::runtimeService:  newInstrumentedRuntimeService(runtimeService)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// SyncPod syncs the running pod into the desired pod by executing following steps:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//  1. Compute sandbox and container changes.</span></span><br><span class="line"><span class="comment">//  2. Kill pod sandbox if necessary.</span></span><br><span class="line"><span class="comment">//  3. Kill any containers that should not be running.</span></span><br><span class="line"><span class="comment">//  4. Create sandbox if necessary.</span></span><br><span class="line"><span class="comment">//  5. Create init containers.</span></span><br><span class="line"><span class="comment">//  6. Create normal containers.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *kubeGenericRuntimeManager)</span> <span class="title">SyncPod</span><span class="params">(pod *v1.Pod, _ v1.PodStatus, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff)</span> <span class="params">(result kubecontainer.PodSyncResult)</span></span> &#123;</span><br><span class="line"><span class="comment">// Step 1: Compute sandbox and container changes.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 2: Kill the pod if the sandbox has changed.</span></span><br><span class="line"><span class="keyword">if</span> podContainerChanges.KillPod &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// Step 3: kill any running containers in this pod which are not to keep.</span></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Keep terminated init containers fairly aggressively controlled</span></span><br><span class="line"><span class="comment">// This is an optimization because container removals are typically handled</span></span><br><span class="line"><span class="comment">// by container garbage collector.</span></span><br><span class="line">m.pruneInitContainersBeforeStart(pod, podStatus)</span><br><span class="line"></span><br><span class="line"><span class="comment">// We pass the value of the podIP down to generatePodSandboxConfig and</span></span><br><span class="line"><span class="comment">// generateContainerConfig, which in turn passes it to various other</span></span><br><span class="line"><span class="comment">// functions, in order to facilitate functionality that requires this</span></span><br><span class="line"><span class="comment">// value (hosts file and downward API) and avoid races determining</span></span><br><span class="line"><span class="comment">// the pod IP in cases where a container requires restart but the</span></span><br><span class="line"><span class="comment">// podIP isn't in the status manager yet.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// We default to the IP in the passed-in pod status, and overwrite it if the</span></span><br><span class="line"><span class="comment">// sandbox needs to be (re)started.</span></span><br><span class="line">podIP := <span class="string">""</span></span><br><span class="line"><span class="keyword">if</span> podStatus != <span class="literal">nil</span> &#123;</span><br><span class="line">podIP = podStatus.IP</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 4: Create a sandbox for the pod if necessary.  </span></span><br><span class="line">  <span class="comment">// 创建使用 pause 镜像创建的 sandbox</span></span><br><span class="line">podSandboxID := podContainerChanges.SandboxID</span><br><span class="line"><span class="keyword">if</span> podContainerChanges.CreateSandbox &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">podSandboxID, msg, err = m.createPodSandbox(pod, podContainerChanges.Attempt)</span><br><span class="line">    <span class="comment">// 底层调用 m.runtimeService.RunPodSandbox(podSandboxConfig, runtimeHandler)</span></span><br><span class="line">    </span><br><span class="line">glog.V(<span class="number">4</span>).Infof(<span class="string">"Created PodSandbox %q for pod %q"</span>, podSandboxID, format.Pod(pod))</span><br><span class="line"></span><br><span class="line">podSandboxStatus, err := m.runtimeService.PodSandboxStatus(podSandboxID)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get podSandboxConfig for containers to start.</span></span><br><span class="line">podSandboxConfig, err := m.generatePodSandboxConfig(pod, podContainerChanges.Attempt)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 5: start the init container.  启动所有的 init container </span></span><br><span class="line"><span class="keyword">if</span> container := podContainerChanges.NextInitContainerToStart; container != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// Start the next init container.</span></span><br><span class="line">glog.V(<span class="number">4</span>).Infof(<span class="string">"Creating init container %+v in pod %v"</span>, container, format.Pod(pod))</span><br><span class="line"><span class="keyword">if</span> msg, err := m.startContainer(podSandboxID, podSandboxConfig, container, pod, podStatus, pullSecrets, podIP, kubecontainer.ContainerTypeInit); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Successfully started the container; clear the entry in the failure</span></span><br><span class="line">glog.V(<span class="number">4</span>).Infof(<span class="string">"Completed init container %q for pod %q"</span>, container.Name, format.Pod(pod))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 6: start containers in podContainerChanges.ContainersToStart. 启动 container </span></span><br><span class="line"><span class="keyword">for</span> _, idx := <span class="keyword">range</span> podContainerChanges.ContainersToStart &#123;</span><br><span class="line">container := &amp;pod.Spec.Containers[idx]</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">glog.V(<span class="number">4</span>).Infof(<span class="string">"Creating container %+v in pod %v"</span>, container, format.Pod(pod))</span><br><span class="line"><span class="keyword">if</span> msg, err := m.startContainer(podSandboxID, podSandboxConfig, container, pod, podStatus, pullSecrets, podIP, kubecontainer.ContainerTypeRegular); err != <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CRI-接口调用和实现"><a href="#CRI-接口调用和实现" class="headerlink" title="CRI 接口调用和实现"></a>CRI 接口调用和实现</h3><p>CRI 相关文档： <a href="https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/" target="_blank" rel="noopener">Introducing Container Runtime Interface (CRI) in Kubernetes</a></p><p>k8s.io/kubernetes/pkg/kubelet/dockershim/docker_sandbox.go</p><p>dockerService 类实现了 CRI 接口：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> dockerService <span class="keyword">struct</span> &#123;</span><br><span class="line">client           libdocker.Interface</span><br><span class="line">os               kubecontainer.OSInterface</span><br><span class="line">podSandboxImage  <span class="keyword">string</span></span><br><span class="line">streamingRuntime *streamingRuntime</span><br><span class="line">streamingServer  streaming.Server</span><br><span class="line"></span><br><span class="line">network *network.PluginManager   <span class="comment">// 实现了网络</span></span><br><span class="line"><span class="comment">// Map of podSandboxID :: network-is-ready</span></span><br><span class="line">networkReady     <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">bool</span></span><br><span class="line">networkReadyLock sync.Mutex</span><br><span class="line"></span><br><span class="line">containerManager cm.ContainerManager</span><br><span class="line"><span class="comment">// cgroup driver used by Docker runtime.</span></span><br><span class="line">cgroupDriver      <span class="keyword">string</span></span><br><span class="line">checkpointManager checkpointmanager.CheckpointManager</span><br><span class="line"><span class="comment">// caches the version of the runtime.</span></span><br><span class="line"><span class="comment">// To be compatible with multiple docker versions, we need to perform</span></span><br><span class="line"><span class="comment">// version checking for some operations. Use this cache to avoid querying</span></span><br><span class="line"><span class="comment">// the docker daemon every time we need to do such checks.</span></span><br><span class="line">versionCache *cache.ObjectCache</span><br><span class="line"><span class="comment">// startLocalStreamingServer indicates whether dockershim should start a</span></span><br><span class="line"><span class="comment">// streaming server on localhost.</span></span><br><span class="line">startLocalStreamingServer <span class="keyword">bool</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RuntimeService 接口定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RuntimeService interface should be implemented by a container runtime.</span></span><br><span class="line"><span class="comment">// The methods should be thread-safe.</span></span><br><span class="line"><span class="keyword">type</span> RuntimeService <span class="keyword">interface</span> &#123;</span><br><span class="line">RuntimeVersioner</span><br><span class="line">ContainerManager</span><br><span class="line">PodSandboxManager</span><br><span class="line">ContainerStatsManager</span><br><span class="line"></span><br><span class="line"><span class="comment">// UpdateRuntimeConfig updates runtime configuration if specified</span></span><br><span class="line">UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error</span><br><span class="line"><span class="comment">// Status returns the status of the runtime.</span></span><br><span class="line">Status() (*runtimeapi.RuntimeStatus, error)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>PodSandboxManager 接口定义：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PodSandboxManager contains methods for operating on PodSandboxes. The methods</span></span><br><span class="line"><span class="comment">// are thread-safe.</span></span><br><span class="line"><span class="keyword">type</span> PodSandboxManager <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure</span></span><br><span class="line"><span class="comment">// the sandbox is in ready state.</span></span><br><span class="line">RunPodSandbox(config *runtimeapi.PodSandboxConfig, runtimeHandler <span class="keyword">string</span>) (<span class="keyword">string</span>, error)</span><br><span class="line"><span class="comment">// StopPodSandbox stops the sandbox. If there are any running containers in the</span></span><br><span class="line"><span class="comment">// sandbox, they should be force terminated.</span></span><br><span class="line">StopPodSandbox(podSandboxID <span class="keyword">string</span>) error</span><br><span class="line"><span class="comment">// RemovePodSandbox removes the sandbox. If there are running containers in the</span></span><br><span class="line"><span class="comment">// sandbox, they should be forcibly removed.</span></span><br><span class="line">RemovePodSandbox(podSandboxID <span class="keyword">string</span>) error</span><br><span class="line"><span class="comment">// PodSandboxStatus returns the Status of the PodSandbox.</span></span><br><span class="line">PodSandboxStatus(podSandboxID <span class="keyword">string</span>) (*runtimeapi.PodSandboxStatus, error)</span><br><span class="line"><span class="comment">// ListPodSandbox returns a list of Sandbox.</span></span><br><span class="line">ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error)</span><br><span class="line"><span class="comment">// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.</span></span><br><span class="line">PortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>以 <code>RunPodSandbox</code> 接口为例：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure</span></span><br><span class="line"><span class="comment">// the sandbox is in ready state.</span></span><br><span class="line"><span class="comment">// For docker, PodSandbox is implemented by a container holding the network</span></span><br><span class="line"><span class="comment">// namespace for the pod.</span></span><br><span class="line"><span class="comment">// Note: docker doesn't use LogDirectory (yet).</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(ds *dockerService)</span> <span class="title">RunPodSandbox</span><span class="params">(ctx context.Context, r *runtimeapi.RunPodSandboxRequest)</span> <span class="params">(*runtimeapi.RunPodSandboxResponse, error)</span></span> &#123;</span><br><span class="line">config := r.GetConfig()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 1: Pull the image for the sandbox.</span></span><br><span class="line">image := defaultSandboxImage</span><br><span class="line"><span class="keyword">if</span> err := ensureSandboxImageExists(ds.client, image); err != <span class="literal">nil</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createConfig, err := ds.makeSandboxDockerConfig(config, image)</span><br><span class="line">createResp, err := ds.client.CreateContainer(*createConfig)</span><br><span class="line"></span><br><span class="line">resp := &amp;runtimeapi.RunPodSandboxResponse&#123;PodSandboxId: createResp.ID&#125;</span><br><span class="line"></span><br><span class="line">ds.setNetworkReady(createResp.ID, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 3: Create Sandbox Checkpoint.</span></span><br><span class="line"><span class="keyword">if</span> err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 4: Start the sandbox container.</span></span><br><span class="line"><span class="comment">// Assume kubelet's garbage collector would remove the sandbox later, if</span></span><br><span class="line"><span class="comment">// startContainer failed.</span></span><br><span class="line">err = ds.client.StartContainer(createResp.ID)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 设置 DNS config</span></span><br><span class="line"><span class="comment">// Do not invoke network plugins if in hostNetwork mode.</span></span><br><span class="line"><span class="keyword">if</span> config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtimeapi.NamespaceMode_NODE &#123;</span><br><span class="line"><span class="keyword">return</span> resp, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Step 5: Setup networking for the sandbox.</span></span><br><span class="line"><span class="comment">// All pod networking is setup by a CNI plugin discovered at startup time.</span></span><br><span class="line"><span class="comment">// This plugin assigns the pod ip, sets up routes inside the sandbox,</span></span><br><span class="line"><span class="comment">// creates interfaces etc. In theory, its jurisdiction ends with pod</span></span><br><span class="line"><span class="comment">// sandbox networking, but it might insert iptables rules or open ports</span></span><br><span class="line"><span class="comment">// on the host as well, to satisfy parts of the pod spec that aren't</span></span><br><span class="line"><span class="comment">// recognized by the CNI standard yet.</span></span><br><span class="line">cID := kubecontainer.BuildContainerID(runtimeName, createResp.ID)</span><br><span class="line">err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations)</span><br><span class="line"><span class="keyword">return</span> resp, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="CNI"><a href="#CNI" class="headerlink" title="CNI"></a>CNI</h2><h3 id="CNI-接口调用和实现"><a href="#CNI-接口调用和实现" class="headerlink" title="CNI 接口调用和实现"></a>CNI 接口调用和实现</h3><p>在 dockershim 模式下，<code>cniNetworkPlugin</code> 实现了 CNI 定义的接口，<code>SetUpPod</code> 函数定义如下：</p><p>k8s.io/kubernetes/pkg/kubelet/dockershim/network/cni/cni.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(plugin *cniNetworkPlugin)</span> <span class="title">SetUpPod</span><span class="params">(namespace <span class="keyword">string</span>, name <span class="keyword">string</span>, id kubecontainer.ContainerID, annotations <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">netnsPath, err := plugin.host.GetNetNS(id.ID)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Windows doesn't have loNetwork. It comes only with Linux</span></span><br><span class="line"><span class="keyword">if</span> plugin.loNetwork != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">if</span> _, err = plugin.addToNetwork(plugin.loNetwork, name, namespace, id, netnsPath, annotations); err != <span class="literal">nil</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">  </span><br><span class="line">_, err = plugin.addToNetwork(plugin.getDefaultNetwork(), name, namespace, id, netnsPath, annotations)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>network 相关的在函数 <code>NewDockerService</code> 中初始化：</p><p>k8s.io/kubernetes/pkg/kubelet/dockershim/docker_service.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewDockerService</span><span class="params">(...)</span></span>&#123;</span><br><span class="line">  <span class="comment">// dockershim currently only supports CNI plugins.</span></span><br><span class="line">  <span class="comment">// 使用传入的 cni 配置目录和bin目录，初始化插件，并供后续选择</span></span><br><span class="line">pluginSettings.PluginBinDirs = cni.SplitDirs(pluginSettings.PluginBinDirString)</span><br><span class="line">cniPlugins := cni.ProbeNetworkPlugins(pluginSettings.PluginConfDir, pluginSettings.PluginBinDirs)</span><br><span class="line">cniPlugins = <span class="built_in">append</span>(cniPlugins, kubenet.NewPlugin(pluginSettings.PluginBinDirs))</span><br><span class="line">  </span><br><span class="line">    <span class="comment">// cniPlugins 传入全部的系统 CNI 插件</span></span><br><span class="line">  plug, err := network.InitNetworkPlugin(cniPlugins, pluginSettings.PluginName, netHost, pluginSettings.HairpinMode, pluginSettings.NonMasqueradeCIDR, pluginSettings.MTU)</span><br><span class="line"></span><br><span class="line">ds.network = network.NewPluginManager(plug)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CNI-接口定义"><a href="#CNI-接口定义" class="headerlink" title="CNI 接口定义"></a>CNI 接口定义</h3><p>在 DockerShim 中使用 <code>cniNetworkPlugin</code> 实现了 CNI 接口，CNI 接口如下：</p><p>k8s.io/kubernetes/pkg/kubelet/dockershim/network/cni/cni.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> cniNetworkPlugin <span class="keyword">struct</span> &#123;</span><br><span class="line">network.NoopNetworkPlugin</span><br><span class="line"></span><br><span class="line">loNetwork *cniNetwork        <span class="comment">// 指向 lo CNI 的实现，参见后续分析</span></span><br><span class="line">  defaultNetwork *cniNetwork   <span class="comment">// 指向 default CNI 的实现，参见后续分析</span></span><br><span class="line"></span><br><span class="line">sync.RWMutex</span><br><span class="line">defaultNetwork *cniNetwork</span><br><span class="line"></span><br><span class="line">host        network.Host</span><br><span class="line">execer      utilexec.Interface</span><br><span class="line">nsenterPath <span class="keyword">string</span></span><br><span class="line">confDir     <span class="keyword">string</span></span><br><span class="line">binDirs     []<span class="keyword">string</span></span><br><span class="line">podCidr     <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CNI 接口如下：</p><p>k8s.io/kubernetes/pkg/kubelet/dockershim/network/plugins.go面向 Pod 的 <a href="https://github.com/kubernetes/kubernetes/tree/v1.16.0/pkg/kubelet/dockershim/network/plugins.go" target="_blank" rel="noopener"><code>NetworkPlugin</code> 接口</a> ，CNI 规范参见：<a href="https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration" target="_blank" rel="noopener">https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration</a></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Plugin is an interface to network plugins for the kubelet</span></span><br><span class="line"><span class="keyword">type</span> NetworkPlugin <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// Init initializes the plugin.  This will be called exactly once</span></span><br><span class="line"><span class="comment">// before any other methods are called.</span></span><br><span class="line">Init(host Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR <span class="keyword">string</span>, mtu <span class="keyword">int</span>) error</span><br><span class="line"></span><br><span class="line"><span class="comment">// Called on various events like:</span></span><br><span class="line"><span class="comment">// NET_PLUGIN_EVENT_POD_CIDR_CHANGE</span></span><br><span class="line">Event(name <span class="keyword">string</span>, details <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">interface</span>&#123;&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Name returns the plugin's name. This will be used when searching</span></span><br><span class="line"><span class="comment">// for a plugin by name, e.g.</span></span><br><span class="line">Name() <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Returns a set of NET_PLUGIN_CAPABILITY_*</span></span><br><span class="line">Capabilities() utilsets.Int</span><br><span class="line"></span><br><span class="line"><span class="comment">// SetUpPod is the method called after the infra container of</span></span><br><span class="line"><span class="comment">// the pod has been created but before the other containers of the</span></span><br><span class="line"><span class="comment">// pod are launched.</span></span><br><span class="line">SetUpPod(namespace <span class="keyword">string</span>, name <span class="keyword">string</span>, podSandboxID kubecontainer.ContainerID, annotations <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>) error</span><br><span class="line"></span><br><span class="line"><span class="comment">// TearDownPod is the method called before a pod's infra container will be deleted</span></span><br><span class="line">TearDownPod(namespace <span class="keyword">string</span>, name <span class="keyword">string</span>, podSandboxID kubecontainer.ContainerID) error</span><br><span class="line"></span><br><span class="line"><span class="comment">// GetPodNetworkStatus is the method called to obtain the ipv4 or ipv6 addresses of the container</span></span><br><span class="line">GetPodNetworkStatus(namespace <span class="keyword">string</span>, name <span class="keyword">string</span>, podSandboxID kubecontainer.ContainerID) (*PodNetworkStatus, error)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Status returns error if the network plugin is in error state</span></span><br><span class="line">Status() error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>cniNetworkPlugin</code> 接口中包含接口 <code>CNIConfig</code>，其实现了 <code>libcni.CNI</code> 接口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> cniNetworkPlugin <span class="keyword">struct</span> &#123;</span><br><span class="line">network.NoopNetworkPlugin</span><br><span class="line"></span><br><span class="line">loNetwork *cniNetwork        <span class="comment">// lo 本地端口</span></span><br><span class="line">  defaultNetwork *cniNetwork  <span class="comment">// 默认网络</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> cniNetwork <span class="keyword">struct</span> &#123;</span><br><span class="line">name          <span class="keyword">string</span></span><br><span class="line">NetworkConfig *libcni.NetworkConfigList</span><br><span class="line">CNIConfig     libcni.CNI   <span class="comment">// 指向 CNI 接口定义</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CNI 接口定义</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> CNI <span class="keyword">interface</span> &#123;</span><br><span class="line">    AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)</span><br><span class="line">    DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error</span><br><span class="line"></span><br><span class="line">    AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)</span><br><span class="line">    DelNetwork(net *NetworkConfig, rt *RuntimeConf) error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CNIConfig</code> 的初始化：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ProbeNetworkPlugins</span><span class="params">(confDir <span class="keyword">string</span>, binDirs []<span class="keyword">string</span>)</span> []<span class="title">network</span>.<span class="title">NetworkPlugin</span></span> &#123;</span><br><span class="line"></span><br><span class="line">plugin := &amp;cniNetworkPlugin&#123;</span><br><span class="line">defaultNetwork: <span class="literal">nil</span>,</span><br><span class="line">loNetwork:      getLoNetwork(binDirs),  <span class="comment">// lo 初始化</span></span><br><span class="line">execer:         utilexec.New(),</span><br><span class="line">confDir:        confDir,</span><br><span class="line">binDirs:        binDirs,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> []network.NetworkPlugin&#123;plugin&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>getLoNetwork 函数定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getLoNetwork</span><span class="params">(binDirs []<span class="keyword">string</span>)</span> *<span class="title">cniNetwork</span></span> &#123;</span><br><span class="line">loConfig, err := libcni.ConfListFromBytes([]<span class="keyword">byte</span>(<span class="string">`&#123;</span></span><br><span class="line"><span class="string">  "cniVersion": "0.2.0",</span></span><br><span class="line"><span class="string">  "name": "cni-loopback",</span></span><br><span class="line"><span class="string">  "plugins":[&#123;</span></span><br><span class="line"><span class="string">    "type": "loopback"</span></span><br><span class="line"><span class="string">  &#125;]</span></span><br><span class="line"><span class="string">&#125;`</span>))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// The hardcoded config above should always be valid and unit tests will</span></span><br><span class="line"><span class="comment">// catch this</span></span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">&#125;</span><br><span class="line">loNetwork := &amp;cniNetwork&#123;</span><br><span class="line">name:          <span class="string">"lo"</span>,</span><br><span class="line">NetworkConfig: loConfig,</span><br><span class="line">CNIConfig:     &amp;libcni.CNIConfig&#123;Path: binDirs&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> loNetwork</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>cniNetworkPlugin</code> 结构体中默认网络字段 <code>defaultNetwork</code> 的初始化如下，该函数在 <code>cni.ProbeNetworkPlugins</code> 函数中被调用：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(plugin *cniNetworkPlugin)</span> <span class="title">syncNetworkConfig</span><span class="params">()</span></span> &#123;</span><br><span class="line">network, err := getDefaultCNINetwork(plugin.confDir, plugin.binDirs)</span><br><span class="line">plugin.setDefaultNetwork(network)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>getDefaulgotCNINetwork</code> 的定义如下，主要工作是从 CNI 的 conf 目录中读取 conf 文件，完成默认 network 的配置初始化：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getDefaulgotCNINetwork</span><span class="params">(confDir <span class="keyword">string</span>, binDirs []<span class="keyword">string</span>)</span> <span class="params">(*cniNetwork, error)</span></span> &#123;</span><br><span class="line">files, err := libcni.ConfFiles(confDir, []<span class="keyword">string</span>&#123;<span class="string">".conf"</span>, <span class="string">".conflist"</span>, <span class="string">".json"</span>&#125;)</span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> err != <span class="literal">nil</span>:</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line"><span class="keyword">case</span> <span class="built_in">len</span>(files) == <span class="number">0</span>:</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, fmt.Errorf(<span class="string">"No networks found in %s"</span>, confDir)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">sort.Strings(files)</span><br><span class="line"><span class="keyword">for</span> _, confFile := <span class="keyword">range</span> files &#123;</span><br><span class="line"><span class="keyword">var</span> confList *libcni.NetworkConfigList</span><br><span class="line"><span class="keyword">if</span> strings.HasSuffix(confFile, <span class="string">".conflist"</span>) &#123;</span><br><span class="line">confList, err = libcni.ConfListFromFile(confFile)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">conf, err := libcni.ConfFromFile(confFile)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Ensure the config has a "type" so we know what plugin to run.</span></span><br><span class="line"><span class="comment">// Also catches the case where somebody put a conflist into a conf file.</span></span><br><span class="line"><span class="keyword">if</span> conf.Network.Type == <span class="string">""</span> &#123;</span><br><span class="line">glog.Warningf(<span class="string">"Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?"</span>, confFile)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">confList, err = libcni.ConfListFromConf(conf)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">glog.Warningf(<span class="string">"Error converting CNI config file %s to list: %v"</span>, confFile, err)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(confList.Plugins) == <span class="number">0</span> &#123;</span><br><span class="line">glog.Warningf(<span class="string">"CNI config list %s has no networks, skipping"</span>, confFile)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">glog.V(<span class="number">4</span>).Infof(<span class="string">"Using CNI configuration file %s"</span>, confFile)</span><br><span class="line"></span><br><span class="line">network := &amp;cniNetwork&#123;</span><br><span class="line">name:          confList.Name,</span><br><span class="line">NetworkConfig: confList,</span><br><span class="line">CNIConfig:     &amp;libcni.CNIConfig&#123;Path: binDirs&#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> network, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, fmt.Errorf(<span class="string">"No valid networks found in %s"</span>, confDir)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;本文源码跟踪基于  1.12.6&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.do1618.com/wp-content/uploads/2019/12/CRI-CNI.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;CRI&quot;&gt;&lt;a href=&quot;#CRI&quot; 
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="cri" scheme="http://www.cn18k.com/tags/cri/"/>
    
      <category term="kubelet" scheme="http://www.cn18k.com/tags/kubelet/"/>
    
      <category term="cni" scheme="http://www.cn18k.com/tags/cni/"/>
    
  </entry>
  
  <entry>
    <title>Kube-Scheduler 源码剖析</title>
    <link href="http://www.cn18k.com/2019/12/02/kube-scheduler-source/"/>
    <id>http://www.cn18k.com/2019/12/02/kube-scheduler-source/</id>
    <published>2019-12-02T03:00:00.000Z</published>
    <updated>2019-12-02T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p><h2 id="主要功能"><a href="#主要功能" class="headerlink" title="主要功能"></a>主要功能</h2><p><code>Kube-Scheduler</code> 主要工作是为需要运行的 Pod 选择合适的 Node，从阶段上讲分为两个阶段：</p><ol><li><p>预选 Predicates</p><p>挑选出符合调度条件的 Node 列表</p></li><li><p>优选 Prioritizing</p><p>从已经选择出来的 Node 列表中按照一定的算法选择出最优匹配的 Node，设置 Pod 对应的 NodeName </p></li></ol><p><img src="https://www.do1618.com/wp-content/uploads/2019/12/scheduler-sequence.png" alt=""></p><p>使用者可以使用自己定义的 config 文件，或直接使用系统提供的默认预选和优选算法；当用户可以自己通过扩展算法来实现自己的调度器；</p><p><img src="https://www.do1618.com/wp-content/uploads/2019/12/scheduler-source.png" alt="image-20191202143724661"></p><p>代码分析基于 v1.12.6</p><h2 id="核心主流程"><a href="#核心主流程" class="headerlink" title="核心主流程"></a>核心主流程</h2><h3 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h3><p>k8s.io/kubernetes/cmd/kube-scheduler/scheduler.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="string">"k8s.io/kubernetes/cmd/kube-scheduler/app"</span></span><br><span class="line">)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := command.Execute(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/cmd/kube-scheduler/app/server.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewSchedulerCommand creates a *cobra.Command object with default parameters</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewSchedulerCommand</span><span class="params">()</span> *<span class="title">cobra</span>.<span class="title">Command</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">cmd := &amp;cobra.Command&#123;</span><br><span class="line">Use: <span class="string">"kube-scheduler"</span>,</span><br><span class="line">Long: <span class="string">`...`</span>,</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">stopCh := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line"><span class="keyword">if</span> err := Run(c.Complete(), stopCh); err != <span class="literal">nil</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> cmd</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Run-函数入口"><a href="#Run-函数入口" class="headerlink" title="Run() 函数入口"></a>Run() 函数入口</h3><p>k8s.io/kubernetes/cmd/kube-scheduler/app/server.go</p><p>kube-scheduler 主函数在 <code>Run</code> 函数中，该函数主要的工作包括：</p><ol><li>调度算法的初始化</li><li>启动 Informer 监听，从 Kube-APIServer 同步需要的数据</li><li>开启调度的工作，调度的主函数 <code>sched.scheduleOne</code> 顾名思义就是每次串行调度一个 Pod</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Run runs the Scheduler.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Run</span><span class="params">(c schedulerserverconfig.CompletedConfig, stopCh &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 注册算法 参见流程 算法初始化</span></span><br><span class="line">algorithmprovider.ApplyFeatureGates()  </span><br><span class="line"></span><br><span class="line">  <span class="comment">// 配置初始化</span></span><br><span class="line">schedulerConfig, err := NewSchedulerConfig(c)  </span><br><span class="line">  </span><br><span class="line"><span class="comment">// Create the scheduler.</span></span><br><span class="line">sched := scheduler.NewFromConfig(schedulerConfig)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动 informer 监听</span></span><br><span class="line"><span class="keyword">go</span> c.PodInformer.Informer().Run(stopCh) </span><br><span class="line">c.InformerFactory.Start(stopCh)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Wait for all caches to sync before scheduling.</span></span><br><span class="line">c.InformerFactory.WaitForCacheSync(stopCh)</span><br><span class="line">controller.WaitForCacheSync(<span class="string">"scheduler"</span>, stopCh, c.PodInformer.Informer().HasSynced)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prepare a reusable run function.</span></span><br><span class="line">run := <span class="function"><span class="keyword">func</span><span class="params">(ctx context.Context)</span></span> &#123;</span><br><span class="line">sched.Run()  <span class="comment">// 主入口</span></span><br><span class="line">&lt;-ctx.Done()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  </span><br><span class="line">run(ctx)</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">"finished without leader elect"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/pkg/scheduler/scheduler.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Run begins watching and scheduling. It waits for cache to be synced, then starts a goroutine and returns immediately.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sched *Scheduler)</span> <span class="title">Run</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> !sched.config.WaitForCacheSync() &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 主入口函数 scheduleOne </span></span><br><span class="line"><span class="keyword">go</span> wait.Until(sched.scheduleOne, <span class="number">0</span>, sched.config.StopEverything)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="配置初始化-之-config-CompletedConfig"><a href="#配置初始化-之-config-CompletedConfig" class="headerlink" title="配置初始化-之 config.CompletedConfig"></a>配置初始化-之 config.CompletedConfig</h3><blockquote><p>如果对配置初始化过程不感兴趣，可以跳过，直接看调度算法</p></blockquote><p>配置初始化的工作主要流程如下：</p><p><code>args</code> -&gt; <code>options.Options</code> -&gt; <code>config.Config</code> -&gt; <code>config.CompleteConfig（仅包装了config.Config)</code> -&gt; <code>scheduler.Config</code> </p><p>其中 包含了 <code>config.Config</code> 包含了配置  <code>config.KubeSchedulerConfiguration</code>（调度器名称、调度器算法来源、是否禁止强制调度）</p><p>简略一点的路径是：</p><p><code>options.Options</code> -&gt;<code>KubeSchedulerConfiguration</code> -&gt;  <code>config.CompleteConfig</code> -&gt; <code>scheduler.Config</code></p><p>函数 <code>c.Complete()</code> 完成了 <code>config</code> 结构体的相关变量</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewSchedulerCommand creates a *cobra.Command object with default parameters</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewSchedulerCommand</span><span class="params">()</span> *<span class="title">cobra</span>.<span class="title">Command</span></span> &#123;</span><br><span class="line">opts, err := options.NewOptions()</span><br><span class="line"></span><br><span class="line">cmd := &amp;cobra.Command&#123;</span><br><span class="line">Use: <span class="string">"kube-scheduler"</span>,</span><br><span class="line">Long: <span class="string">``</span>,</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">c, err := opts.Config()  <span class="comment">// 初始化配置</span></span><br><span class="line"></span><br><span class="line">stopCh := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">Run(c.Complete(), stopCh)  <span class="comment">// 完成配置返回  config.CompletedConfig 结构</span></span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> cmd</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/cmd/kube-scheduler/app/options/options.go</p><p>初始化配置的函数  <code>opts.Config()</code> 主要是完成 <code>opt</code> 参数向  <code>config</code> 对象的各种初始化动作：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Config return a scheduler config object</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(o *Options)</span> <span class="title">Config</span><span class="params">()</span> <span class="params">(*schedulerappconfig.Config, error)</span></span> &#123;</span><br><span class="line">c := &amp;schedulerappconfig.Config&#123;&#125;</span><br><span class="line">  <span class="comment">// ApplyTo 函数完成 opt 参数或者configfile 变量到 schedulerappconfig.Config&#123;&#125; 对象</span></span><br><span class="line"><span class="keyword">if</span> err := o.ApplyTo(c); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// prepare kube clients.</span></span><br><span class="line">client, leaderElectionClient, eventClient, err := createClients(c.ComponentConfig.ClientConnection, o.Master, c.ComponentConfig.LeaderElection.RenewDeadline.Duration)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prepare event clients.</span></span><br><span class="line">eventBroadcaster := record.NewBroadcaster()</span><br><span class="line">recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, corev1.EventSource&#123;Component: c.ComponentConfig.SchedulerName&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set up leader election if enabled.</span></span><br><span class="line"><span class="keyword">var</span> leaderElectionConfig *leaderelection.LeaderElectionConfig</span><br><span class="line"><span class="keyword">if</span> c.ComponentConfig.LeaderElection.LeaderElect &#123;</span><br><span class="line">leaderElectionConfig, err = makeLeaderElectionConfig(c.ComponentConfig.LeaderElection, leaderElectionClient, recorder)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">c.Client = client</span><br><span class="line">c.InformerFactory = informers.NewSharedInformerFactory(client, <span class="number">0</span>)</span><br><span class="line">c.PodInformer = factory.NewPodInformer(client, <span class="number">0</span>)</span><br><span class="line">c.EventClient = eventClient</span><br><span class="line">c.Recorder = recorder</span><br><span class="line">c.Broadcaster = eventBroadcaster</span><br><span class="line">c.LeaderElection = leaderElectionConfig</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> c, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结构体为 <code>config.CompletedConfig</code> ，里面内嵌了 <code>config.Config</code> 结构体</p><p>k8s.io/kubernetes/cmd/kube-scheduler/app/config/config.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">type</span> completedConfig <span class="keyword">struct</span> &#123;</span><br><span class="line">   *Config</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// CompletedConfig same as Config, just to swap private object.</span></span><br><span class="line"> <span class="keyword">type</span> CompletedConfig <span class="keyword">struct</span> &#123;</span><br><span class="line">   <span class="comment">// Embed a private pointer that cannot be instantiated outside of this package.</span></span><br><span class="line">   <span class="comment">// 嵌入一个私有的指针，避免在包外初始化</span></span><br><span class="line">   *completedConfig</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Config has all the context to run a Scheduler</span></span><br><span class="line"> <span class="keyword">type</span> Config <span class="keyword">struct</span> &#123;</span><br><span class="line">   <span class="comment">// config is the scheduler server's configuration object.</span></span><br><span class="line">   ComponentConfig kubeschedulerconfig.KubeSchedulerConfiguration</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 认证相关的</span></span><br><span class="line">   Authentication         apiserver.AuthenticationInfo</span><br><span class="line">   Authorization          apiserver.AuthorizationInfo</span><br><span class="line"> </span><br><span class="line">   Client          clientset.Interface <span class="comment">// k8s client</span></span><br><span class="line">   InformerFactory informers.SharedInformerFactory  <span class="comment">// infromer factory</span></span><br><span class="line">   PodInformer     coreinformers.PodInformer        <span class="comment">// pod informer</span></span><br><span class="line">   EventClient     v1core.EventsGetter</span><br><span class="line">   Recorder        record.EventRecorder</span><br><span class="line">   Broadcaster     record.EventBroadcaster</span><br><span class="line"> </span><br><span class="line">   <span class="comment">// LeaderElection is optional.</span></span><br><span class="line">   LeaderElection *leaderelection.LeaderElectionConfig</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>其中 <code>config.Config</code> 内嵌了 <code>KubeSchedulerConfiguration</code> 的变量，结构体定义如下</p><p>k8s.io/kubernetes/pkg/scheduler/apis/config/types.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// KubeSchedulerConfiguration configures a scheduler</span></span><br><span class="line"><span class="keyword">type</span> KubeSchedulerConfiguration <span class="keyword">struct</span> &#123;</span><br><span class="line">metav1.TypeMeta</span><br><span class="line"></span><br><span class="line"><span class="comment">// SchedulerName is name of the scheduler, used to select which pods</span></span><br><span class="line"><span class="comment">// will be processed by this scheduler, based on pod's "spec.SchedulerName".</span></span><br><span class="line">  <span class="comment">// 调度器的名字，用于用户选择不同的调度器</span></span><br><span class="line">SchedulerName <span class="keyword">string</span></span><br><span class="line">  </span><br><span class="line"><span class="comment">// AlgorithmSource specifies the scheduler algorithm source.</span></span><br><span class="line">  <span class="comment">// 指定算法的来源</span></span><br><span class="line">AlgorithmSource SchedulerAlgorithmSource</span><br><span class="line">  </span><br><span class="line"><span class="comment">// RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule</span></span><br><span class="line"><span class="comment">// corresponding to every RequiredDuringScheduling affinity rule.</span></span><br><span class="line"><span class="comment">// HardPodAffinitySymmetricWeight represents the weight of implicit PreferredDuringScheduling affinity rule, in the range 0-100.</span></span><br><span class="line">HardPodAffinitySymmetricWeight <span class="keyword">int32</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// LeaderElection defines the configuration of leader election client.</span></span><br><span class="line">LeaderElection KubeSchedulerLeaderElectionConfiguration</span><br><span class="line"></span><br><span class="line"><span class="comment">// ClientConnection specifies the kubeconfig file and client connection</span></span><br><span class="line"><span class="comment">// settings for the proxy server to use when communicating with the apiserver.</span></span><br><span class="line">  </span><br><span class="line">ClientConnection apimachineryconfig.ClientConnectionConfiguration</span><br><span class="line"><span class="comment">// HealthzBindAddress is the IP address and port for the health check server to serve on,</span></span><br><span class="line"><span class="comment">// defaulting to 0.0.0.0:10251</span></span><br><span class="line">HealthzBindAddress <span class="keyword">string</span></span><br><span class="line">  </span><br><span class="line"><span class="comment">// MetricsBindAddress is the IP address and port for the metrics server to</span></span><br><span class="line"><span class="comment">// serve on, defaulting to 0.0.0.0:10251.</span></span><br><span class="line">MetricsBindAddress <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// DisablePreemption disables the pod preemption feature.</span></span><br><span class="line">  <span class="comment">// 是否禁止强制调度</span></span><br><span class="line">DisablePreemption <span class="keyword">bool</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 每次 Pod 调度的时候获取到整体 Node 的比例，函数 minFeasibleNodesToFind 负责处理，一般使用在</span></span><br><span class="line">  <span class="comment">// 大集群中, 低于 100 的总是返回全部 Node 进行匹配；如果 500 个 Node，设置 30，则搜索 500 * 30/100 = 150，每次搜索 150 个 Node 则停止</span></span><br><span class="line">PercentageOfNodesToScore <span class="keyword">int32</span></span><br><span class="line"></span><br><span class="line">BindTimeoutSeconds *<span class="keyword">int64</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>config.Complete</code> 函数针对了结构体的封装：</p><p>k8s.io/kubernetes/cmd/kube-scheduler/app/config/config.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *Config)</span> <span class="title">Complete</span><span class="params">()</span> <span class="title">CompletedConfig</span></span> &#123;</span><br><span class="line">  cc := completedConfig&#123;c&#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> c.InsecureServing != <span class="literal">nil</span> &#123;</span><br><span class="line">    c.InsecureServing.Name = <span class="string">"healthz"</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> c.InsecureMetricsServing != <span class="literal">nil</span> &#123;</span><br><span class="line">    c.InsecureMetricsServing.Name = <span class="string">"metrics"</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> CompletedConfig&#123;&amp;cc&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="配置初始化-之-scheduler-Config"><a href="#配置初始化-之-scheduler-Config" class="headerlink" title="配置初始化-之 scheduler.Config"></a>配置初始化-之 scheduler.Config</h3><p>切换整体，回到 Run 函数的初始化函数 <code>schedulerConfig, err := NewSchedulerConfig(c)</code></p><ul><li><code>configFactory</code> 对应工厂模式的工厂模型，根据不同的配置和参数生成 <code>config</code>，当然事先会准备好 <code>config</code> 需要的各种数据</li><li><code>config</code> 是调度器中最重要的组件，里面实现了调度的各个组件逻辑</li><li><code>scheduler</code> 使用 <code>config</code> 提供的功能来完成调度</li></ul><p>该函数完成了 <code>config.CompletedConfig</code> 向 <code>scheduler.Config</code> 的转换和初始化：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewSchedulerConfig creates the scheduler configuration. </span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewSchedulerConfig</span><span class="params">(s schedulerserverconfig.CompletedConfig)</span> <span class="params">(*scheduler.Config, error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set up the configurator which can create schedulers from configs.</span></span><br><span class="line">  <span class="comment">// 创建 configurator，用于生成各种需要的 config</span></span><br><span class="line">  <span class="comment">// 返回 configFactory， 文件定义在 k8s.io/kubernetes/pkg/scheduler/factory/factory.go</span></span><br><span class="line">  <span class="comment">//  NewConfigFactory() -&gt;  初始化各种 informer，比较重要的是 PodInformer</span></span><br><span class="line">  <span class="comment">// unscheduled pod queue</span></span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">args.PodInformer.Informer().AddEventHandler(</span></span><br><span class="line"><span class="comment">cache.FilteringResourceEventHandler&#123;</span></span><br><span class="line"><span class="comment">FilterFunc: func(obj interface&#123;&#125;) bool &#123; // 用于过滤 pod.Spec.NodeName 为空等条件 Pod</span></span><br><span class="line"><span class="comment">switch t := obj.(type) &#123;</span></span><br><span class="line"><span class="comment">case *v1.Pod:</span></span><br><span class="line"><span class="comment">return unassignedNonTerminatedPod(t) &amp;&amp; responsibleForPod(t, args.SchedulerName)</span></span><br><span class="line"><span class="comment">case cache.DeletedFinalStateUnknown:</span></span><br><span class="line"><span class="comment">if pod, ok := t.Obj.(*v1.Pod); ok &#123;</span></span><br><span class="line"><span class="comment">return unassignedNonTerminatedPod(pod) &amp;&amp; responsibleForPod(pod, args.SchedulerName)</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">runtime.HandleError(fmt.Errorf("unable to convert object %T to *v1.Pod in %T", obj, c))</span></span><br><span class="line"><span class="comment">return false</span></span><br><span class="line"><span class="comment">default:</span></span><br><span class="line"><span class="comment">runtime.HandleError(fmt.Errorf("unable to handle object in %T: %T", c, obj))</span></span><br><span class="line"><span class="comment">return false</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">&#125;,</span></span><br><span class="line"><span class="comment">Handler: cache.ResourceEventHandlerFuncs&#123;</span></span><br><span class="line"><span class="comment">AddFunc:    c.addPodToSchedulingQueue,</span></span><br><span class="line"><span class="comment">UpdateFunc: c.updatePodInSchedulingQueue,</span></span><br><span class="line"><span class="comment">DeleteFunc: c.deletePodFromSchedulingQueue,</span></span><br><span class="line"><span class="comment">&#125;,</span></span><br><span class="line"><span class="comment">&#125;,</span></span><br><span class="line"><span class="comment">)</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">configurator := factory.NewConfigFactory(&amp;factory.ConfigFactoryArgs&#123;</span><br><span class="line">SchedulerName:    s.ComponentConfig.SchedulerName,</span><br><span class="line">Client:           s.Client,</span><br><span class="line">NodeInformer:     s.InformerFactory.Core().V1().Nodes(),</span><br><span class="line">PodInformer:      s.PodInformer,</span><br><span class="line">PvInformer:       s.InformerFactory.Core().V1().PersistentVolumes(),</span><br><span class="line">PvcInformer:      s.InformerFactory.Core().V1().PersistentVolumeClaims(),</span><br><span class="line">ReplicationControllerInformer:  s.InformerFactory.Core().V1().ReplicationControllers(),</span><br><span class="line">ReplicaSetInformer:      s.InformerFactory.Apps().V1().ReplicaSets(),</span><br><span class="line">StatefulSetInformer:     s.InformerFactory.Apps().V1().StatefulSets(),</span><br><span class="line">ServiceInformer:         s.InformerFactory.Core().V1().Services(),</span><br><span class="line">PdbInformer:              s.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(),</span><br><span class="line">StorageClassInformer:           storageClassInformer,</span><br><span class="line">HardPodAffinitySymmetricWeight: s.ComponentConfig.HardPodAffinitySymmetricWeight,</span><br><span class="line">EnableEquivalenceClassCache:    utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache),</span><br><span class="line">DisablePreemption:              s.ComponentConfig.DisablePreemption,</span><br><span class="line">PercentageOfNodesToScore:       s.ComponentConfig.PercentageOfNodesToScore,</span><br><span class="line">BindTimeoutSeconds:             *s.ComponentConfig.BindTimeoutSeconds,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/</span></span><br><span class="line">source := s.ComponentConfig.AlgorithmSource  <span class="comment">// KubeSchedulerConfiguration`</span></span><br><span class="line"><span class="keyword">var</span> config *scheduler.Config</span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line">    <span class="comment">// 创建调度算法有两种方式  Provider 和 用户指定的 Ploicy 文件（文件或Configmap)</span></span><br><span class="line">  <span class="comment">// --algorithm-provider string</span></span><br><span class="line">  <span class="comment">// DEPRECATED: the scheduling algorithm provider to use, one of: </span></span><br><span class="line">  <span class="comment">// ClusterAutoscalerProvider | DefaultProvider</span></span><br><span class="line"><span class="keyword">case</span> source.Provider != <span class="literal">nil</span>:</span><br><span class="line"><span class="comment">// Create the config from a named algorithm provider.</span></span><br><span class="line">    <span class="comment">// 使用前面创建的 configurator 创建命名的算法 provider 配置</span></span><br><span class="line">    <span class="comment">// ClusterAutoscalerProvider | DefaultProvider</span></span><br><span class="line">sc, err := configurator.CreateFromProvider(*source.Provider)</span><br><span class="line">config = sc</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// --policy-config-file string</span></span><br><span class="line">  <span class="comment">//DEPRECATED: file with scheduler policy configuration. This file is used if policy ConfigMap is not provided or --use-legacy-policy-config=true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> source.Policy != <span class="literal">nil</span>:</span><br><span class="line"><span class="comment">// Create the config from a user specified policy source.</span></span><br><span class="line">policy := &amp;schedulerapi.Policy&#123;&#125;</span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> source.Policy.File != <span class="literal">nil</span>:</span><br><span class="line"><span class="comment">// Use a policy serialized in a file.</span></span><br><span class="line">policyFile := source.Policy.File.Path</span><br><span class="line">data, err := ioutil.ReadFile(policyFile)</span><br><span class="line">      </span><br><span class="line">err = runtime.DecodeInto(latestschedulerapi.Codec, []<span class="keyword">byte</span>(data), policy)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从 configmap 初始化</span></span><br><span class="line"><span class="keyword">case</span> source.Policy.ConfigMap != <span class="literal">nil</span>:</span><br><span class="line">   <span class="comment">// 处理流程同上</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">sc, err := configurator.CreateFromConfig(*policy)   </span><br><span class="line">config = sc</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, fmt.Errorf(<span class="string">"unsupported algorithm source: %v"</span>, source)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Additional tweaks to the config produced by the configurator.</span></span><br><span class="line">config.Recorder = s.Recorder</span><br><span class="line"></span><br><span class="line">config.DisablePreemption = s.ComponentConfig.DisablePreemption</span><br><span class="line"><span class="keyword">return</span> config, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>无论是函数 <code>CreateFromProvider</code> 还是 <code>CreateFromConfig</code> 底层都是调用 函数 <code>func (c *configFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*scheduler.Config, error)</code></p><p>该函数输入  <code>predicateKeys</code>/<code>priorityKeys</code>/<code>SchedulerExtender</code>，最终返回 <code>scheduler.Config</code>对象</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Creates a scheduler from a set of registered fit predicate keys and priority keys.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *configFactory)</span> <span class="title">CreateFromKeys</span><span class="params">(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender)</span> <span class="params">(*scheduler.Config, error)</span></span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">predicateFuncs, err := c.GetPredicates(predicateKeys)</span><br><span class="line">priorityConfigs, err := c.GetPriorityFunctionConfigs(priorityKeys)</span><br><span class="line">priorityMetaProducer, err := c.GetPriorityMetadataProducer()</span><br><span class="line">predicateMetaProducer, err := c.GetPredicateMetadataProducer()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// Init equivalence class cache</span></span><br><span class="line"><span class="keyword">if</span> c.enableEquivalenceClassCache &#123;</span><br><span class="line">c.equivalencePodCache = equivalence.NewCache()</span><br><span class="line">glog.Info(<span class="string">"Created equivalence class cache"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 完成真正算法的初始化</span></span><br><span class="line">algo := core.NewGenericScheduler(</span><br><span class="line">c.schedulerCache,</span><br><span class="line">c.equivalencePodCache,</span><br><span class="line">c.podQueue,</span><br><span class="line">predicateFuncs,</span><br><span class="line">predicateMetaProducer,</span><br><span class="line">priorityConfigs,</span><br><span class="line">priorityMetaProducer,</span><br><span class="line">extenders,</span><br><span class="line">c.volumeBinder,</span><br><span class="line">c.pVCLister,</span><br><span class="line">c.alwaysCheckAllPredicates,</span><br><span class="line">c.disablePreemption,</span><br><span class="line">c.percentageOfNodesToScore,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">podBackoff := util.CreateDefaultPodBackoff()</span><br><span class="line"><span class="keyword">return</span> &amp;scheduler.Config&#123;</span><br><span class="line">SchedulerCache: c.schedulerCache,</span><br><span class="line">Ecache:         c.equivalencePodCache,</span><br><span class="line"><span class="comment">// The scheduler only needs to consider schedulable nodes.</span></span><br><span class="line">NodeLister:          &amp;nodeLister&#123;c.nodeLister&#125;,</span><br><span class="line">Algorithm:           algo, <span class="comment">// 设置调度的算法</span></span><br><span class="line">GetBinder:           c.getBinderFunc(extenders),</span><br><span class="line">PodConditionUpdater: &amp;podConditionUpdater&#123;c.client&#125;,</span><br><span class="line">PodPreemptor:        &amp;podPreemptor&#123;c.client&#125;,</span><br><span class="line">WaitForCacheSync: <span class="function"><span class="keyword">func</span><span class="params">()</span> <span class="title">bool</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> cache.WaitForCacheSync(c.StopEverything, c.scheduledPodsHasSynced)</span><br><span class="line">&#125;,</span><br><span class="line">NextPod: <span class="function"><span class="keyword">func</span><span class="params">()</span> *<span class="title">v1</span>.<span class="title">Pod</span></span> &#123; <span class="comment">// 设定 NextPod d的</span></span><br><span class="line"><span class="keyword">return</span> c.getNextPod()</span><br><span class="line">&#125;,</span><br><span class="line">Error:           c.MakeDefaultErrorFunc(podBackoff, c.podQueue),</span><br><span class="line">StopEverything:  c.StopEverything,</span><br><span class="line">VolumeBinder:    c.volumeBinder,</span><br><span class="line">SchedulingQueue: c.podQueue,</span><br><span class="line">&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到此完成了 <code>scheduler.Config</code> 的核心数据的初始化，特别是调度算法  <code>Algorithm:algo</code></p><h3 id="scheduleOne"><a href="#scheduleOne" class="headerlink" title="scheduleOne"></a>scheduleOne</h3><p>k8s.io/kubernetes/pkg/scheduler/scheduler.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scheduleOne does the entire scheduling workflow for a single pod.  It is serialized on the scheduling algorithm's host fitting.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sched *Scheduler)</span> <span class="title">scheduleOne</span><span class="params">()</span></span> &#123;</span><br><span class="line">  <span class="comment">// 从队列中取出待调度的 Pod，最终调用的函数为 configFactory.getNextPod()</span></span><br><span class="line">pod := sched.config.NextPod()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 检查是否是被删除的，如果是则跳过</span></span><br><span class="line">  </span><br><span class="line"><span class="comment">// Synchronously attempt to find a fit for the pod.</span></span><br><span class="line">start := time.Now()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 获取待调度 Pod 匹配的主机名</span></span><br><span class="line">suggestedHost, err := sched.schedule(pod)  <span class="comment">// 见后续分析</span></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// schedule() 可能因为没有满足调度的 Node 而失败，后续进行 preempt,</span></span><br><span class="line"><span class="keyword">if</span> fitError, ok := err.(*core.FitError); ok &#123;</span><br><span class="line">preemptionStartTime := time.Now()</span><br><span class="line">sched.preempt(pod, fitError)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Tell the cache to assume that a pod now is running on a given node, even though it hasn't been bound yet.</span></span><br><span class="line"><span class="comment">// This allows us to keep scheduling without waiting on binding to occur.</span></span><br><span class="line">assumedPod := pod.DeepCopy()</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 判断是否需要VolumeScheduling特性</span></span><br><span class="line"><span class="comment">// Assume volumes first before assuming the pod. If all volumes are completely bound, then allBound is true and binding will be skipped. Otherwise, binding of volumes is started after the pod is assumed, but before pod binding. This function modifies 'assumedPod' if volume binding is required.</span></span><br><span class="line">allBound, err := sched.assumeVolumes(assumedPod, suggestedHost)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// assume modifies `assumedPod` by setting NodeName=suggestedHost</span></span><br><span class="line">  <span class="comment">// Pod 对应的 NodeName 写上主机名，存入缓存</span></span><br><span class="line">err = sched.assume(assumedPod, suggestedHost)</span><br><span class="line"></span><br><span class="line"><span class="comment">// bind the pod to its host asynchronously (we can do this b/c of the assumption step above).</span></span><br><span class="line">  <span class="comment">// 请求apiserver，异步处理最终的绑定，写入到etcd</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// Bind volumes first before Pod</span></span><br><span class="line"><span class="keyword">if</span> !allBound &#123;</span><br><span class="line">err = sched.bindVolumes(assumedPod)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">err := sched.bind(assumedPod, &amp;v1.Binding&#123;</span><br><span class="line">ObjectMeta: metav1.ObjectMeta&#123;Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID&#125;,</span><br><span class="line">Target: v1.ObjectReference&#123;</span><br><span class="line">Kind: <span class="string">"Node"</span>,</span><br><span class="line">Name: suggestedHost,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;)</span><br><span class="line">metrics.E2eSchedulingLatency.Observe(metrics.SinceInMicroseconds(start))</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// schedule implements the scheduling algorithm and returns the suggested host.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sched *Scheduler)</span> <span class="title">schedule</span><span class="params">(pod *v1.Pod)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">  <span class="comment">// 调用算法提供的调度器进行调度，为 GenericScheduler</span></span><br><span class="line">host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister)</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sched.config.Algorithm = core.NewGenericScheduler()</span></span><br><span class="line"><span class="comment">// k8s.io/kubernetes/pkg/scheduler/factory/factory.go</span></span><br><span class="line"><span class="comment">// 初始化过程参见 func (c *configFactory) CreateFromKeys()&#123;...&#125;</span></span><br><span class="line"><span class="comment">// </span></span><br><span class="line"><span class="comment">// k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go</span></span><br><span class="line"><span class="comment">// NewGenericScheduler creates a genericScheduler object.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewGenericScheduler</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">cache schedulercache.Cache,</span></span></span><br><span class="line"><span class="function"><span class="params">eCache *equivalence.Cache,</span></span></span><br><span class="line"><span class="function"><span class="params">podQueue SchedulingQueue,</span></span></span><br><span class="line"><span class="function"><span class="params">predicates <span class="keyword">map</span>[<span class="keyword">string</span>]algorithm.FitPredicate,</span></span></span><br><span class="line"><span class="function"><span class="params">predicateMetaProducer algorithm.PredicateMetadataProducer,</span></span></span><br><span class="line"><span class="function"><span class="params">prioritizers []algorithm.PriorityConfig,</span></span></span><br><span class="line"><span class="function"><span class="params">priorityMetaProducer algorithm.PriorityMetadataProducer,</span></span></span><br><span class="line"><span class="function"><span class="params">extenders []algorithm.SchedulerExtender,</span></span></span><br><span class="line"><span class="function"><span class="params">volumeBinder *volumebinder.VolumeBinder,</span></span></span><br><span class="line"><span class="function"><span class="params">pvcLister corelisters.PersistentVolumeClaimLister,</span></span></span><br><span class="line"><span class="function"><span class="params">alwaysCheckAllPredicates <span class="keyword">bool</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">disablePreemption <span class="keyword">bool</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">percentageOfNodesToScore <span class="keyword">int32</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> <span class="title">algorithm</span>.<span class="title">ScheduleAlgorithm</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;genericScheduler&#123;</span><br><span class="line">cache:                    cache,</span><br><span class="line">equivalenceCache:         eCache,</span><br><span class="line">schedulingQueue:          podQueue,</span><br><span class="line">predicates:               predicates,</span><br><span class="line">predicateMetaProducer:    predicateMetaProducer,</span><br><span class="line">prioritizers:             prioritizers,</span><br><span class="line">priorityMetaProducer:     priorityMetaProducer,</span><br><span class="line">extenders:                extenders,</span><br><span class="line">cachedNodeInfoMap:        <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]*schedulercache.NodeInfo),</span><br><span class="line">volumeBinder:             volumeBinder,</span><br><span class="line">pvcLister:                pvcLister,</span><br><span class="line">alwaysCheckAllPredicates: alwaysCheckAllPredicates,</span><br><span class="line">disablePreemption:        disablePreemption,</span><br><span class="line">percentageOfNodesToScore: percentageOfNodesToScore,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go</p><p>最终完成预选和优选的最终处理的地方，还是通过调用 genericScheduler:Schedule 函数完成</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Schedule tries to schedule the given pod to one of the nodes in the node list.</span></span><br><span class="line"><span class="comment">// If it succeeds, it will return the name of the node.</span></span><br><span class="line"><span class="comment">// If it fails, it will return a FitError error with reasons.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(g *genericScheduler)</span> <span class="title">Schedule</span><span class="params">(pod *v1.Pod, nodeLister algorithm.NodeLister)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">nodes, err := nodeLister.List()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Used for all fit and priority funcs.</span></span><br><span class="line">err = g.cache.UpdateNodeNameToInfoMap(g.cachedNodeInfoMap)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 1. 预选机器列表，获取到 filteredNodes 列表</span></span><br><span class="line">  <span class="comment">// 1.1 如果没有配置预选算法，则直接返回全部 Node 列表</span></span><br><span class="line">  <span class="comment">// 1.2 如果配置了预选算法，则对多个 Node 调用 checkNode 的方法，检查 Pod 是否可以调度到该 Node</span></span><br><span class="line">  <span class="comment">// 1.3 预选筛选之后，如果有扩展算法，则继续匹配筛选，返回最终匹配的 Node 列表</span></span><br><span class="line">filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 从预选的机器列表中获取优选机器列表</span></span><br><span class="line">metaPrioritiesInterface := g.priorityMetaProducer(pod, g.cachedNodeInfoMap)</span><br><span class="line">  </span><br><span class="line">priorityList, err := PrioritizeNodes(pod, g.cachedNodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 选择 Scores 最高的并返回</span></span><br><span class="line"><span class="keyword">return</span> g.selectHost(priorityList)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="预选函数主流程"><a href="#预选函数主流程" class="headerlink" title="预选函数主流程"></a>预选函数主流程</h3><p>k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Filters the nodes to find the ones that fit based on the given predicate functions</span></span><br><span class="line"><span class="comment">// Each node is passed through the predicate functions to determine if it is a fit</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(g *genericScheduler)</span> <span class="title">findNodesThatFit</span><span class="params">(pod *v1.Pod, nodes []*v1.Node)</span> <span class="params">([]*v1.Node, FailedPredicateMap, error)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> filtered []*v1.Node</span><br><span class="line">failedPredicateMap := FailedPredicateMap&#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果没有预选算法，则返回全部机器列表</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(g.predicates) == <span class="number">0</span> &#123;</span><br><span class="line">filtered = nodes</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">allNodes := <span class="keyword">int32</span>(g.cache.NodeTree().NumNodes)</span><br><span class="line">    <span class="comment">// 保证一次性不用返回过多的Node数量，避免数组过大，当前有个硬编码 100， 100 以内一般全部返回，否则</span></span><br><span class="line">    <span class="comment">// 按照 percentageOfNodesToScore 比例设置每次返回集群中多少个节点返回，以提升调度性能</span></span><br><span class="line">    <span class="comment">// percentageOfNodesToScore 为 0或者大于100，则返回集群全部 Node</span></span><br><span class="line">numNodesToFind := g.numFeasibleNodesToFind(allNodes)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create filtered list with enough space to avoid growing it</span></span><br><span class="line"><span class="comment">// and allow assigning.</span></span><br><span class="line">filtered = <span class="built_in">make</span>([]*v1.Node, numNodesToFind)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 用于检测 Pod 是否可以调度到 Node 的检查函数</span></span><br><span class="line">checkNode := <span class="function"><span class="keyword">func</span><span class="params">(i <span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> nodeCache *equivalence.NodeCache</span><br><span class="line">nodeName := g.cache.NodeTree().Next()</span><br><span class="line">      </span><br><span class="line">fits, failedPredicates, err := podFitsOnNode(</span><br><span class="line">pod,</span><br><span class="line">meta,</span><br><span class="line">g.cachedNodeInfoMap[nodeName],</span><br><span class="line">g.predicates,</span><br><span class="line">g.cache,</span><br><span class="line">nodeCache,</span><br><span class="line">g.schedulingQueue,</span><br><span class="line">g.alwaysCheckAllPredicates,</span><br><span class="line">equivClass,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> fits &#123;</span><br><span class="line">length := atomic.AddInt32(&amp;filteredLen, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> length &gt; numNodesToFind &#123;</span><br><span class="line">cancel()</span><br><span class="line">atomic.AddInt32(&amp;filteredLen, <span class="number">-1</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">filtered[length<span class="number">-1</span>] = g.cachedNodeInfoMap[nodeName].Node()</span><br><span class="line">&#125;</span><br><span class="line">&#125; </span><br><span class="line">      </span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Stops searching for more nodes once the configured number of feasible nodes</span></span><br><span class="line"><span class="comment">// are found.</span></span><br><span class="line">workqueue.ParallelizeUntil(ctx, <span class="number">16</span>, <span class="keyword">int</span>(allNodes), checkNode)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果配置了 extender 算法，则使用扩展算法继续过滤</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(filtered) &gt; <span class="number">0</span> &amp;&amp; <span class="built_in">len</span>(g.extenders) != <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">for</span> _, extender := <span class="keyword">range</span> g.extenders &#123;</span><br><span class="line">filteredList, failedMap, err := extender.Filter(pod, filtered, g.cachedNodeInfoMap)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">filtered = filteredList</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> filtered, failedPredicateMap, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// podFitsOnNode checks whether a node given by NodeInfo satisfies the given predicate functions.</span></span><br><span class="line"><span class="comment">// For given pod, podFitsOnNode will check if any equivalent pod exists and try to reuse its cached</span></span><br><span class="line"><span class="comment">// predicate results as possible.</span></span><br><span class="line"><span class="comment">// This function is called from two different places: Schedule and Preempt.</span></span><br><span class="line"><span class="comment">// When it is called from Schedule, we want to test whether the pod is schedulable</span></span><br><span class="line"><span class="comment">// on the node with all the existing pods on the node plus higher and equal priority</span></span><br><span class="line"><span class="comment">// pods nominated to run on the node.</span></span><br><span class="line"><span class="comment">// When it is called from Preempt, we should remove the victims of preemption and</span></span><br><span class="line"><span class="comment">// add the nominated pods. Removal of the victims is done by SelectVictimsOnNode().</span></span><br><span class="line"><span class="comment">// It removes victims from meta and NodeInfo before calling this function.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">podFitsOnNode</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">pod *v1.Pod,</span></span></span><br><span class="line"><span class="function"><span class="params">meta algorithm.PredicateMetadata,</span></span></span><br><span class="line"><span class="function"><span class="params">info *schedulercache.NodeInfo,</span></span></span><br><span class="line"><span class="function"><span class="params">predicateFuncs <span class="keyword">map</span>[<span class="keyword">string</span>]algorithm.FitPredicate,</span></span></span><br><span class="line"><span class="function"><span class="params">cache schedulercache.Cache,</span></span></span><br><span class="line"><span class="function"><span class="params">nodeCache *equivalence.NodeCache,</span></span></span><br><span class="line"><span class="function"><span class="params">queue SchedulingQueue,</span></span></span><br><span class="line"><span class="function"><span class="params">alwaysCheckAllPredicates <span class="keyword">bool</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">equivClass *equivalence.Class,</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> <span class="params">(<span class="keyword">bool</span>, []algorithm.PredicateFailureReason, error)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">eCacheAvailable  <span class="keyword">bool</span></span><br><span class="line">failedPredicates []algorithm.PredicateFailureReason</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">podsAdded := <span class="literal">false</span></span><br><span class="line"><span class="comment">// We run predicates twice in some cases. If the node has greater or equal priority</span></span><br><span class="line"><span class="comment">// nominated pods, we run them when those pods are added to meta and nodeInfo.</span></span><br><span class="line"><span class="comment">// If all predicates succeed in this pass, we run them again when these</span></span><br><span class="line"><span class="comment">// nominated pods are not added. This second pass is necessary because some</span></span><br><span class="line"><span class="comment">// predicates such as inter-pod affinity may not pass without the nominated pods.</span></span><br><span class="line"><span class="comment">// If there are no nominated pods for the node or if the first run of the</span></span><br><span class="line"><span class="comment">// predicates fail, we don't run the second pass.</span></span><br><span class="line"><span class="comment">// We consider only equal or higher priority pods in the first pass, because</span></span><br><span class="line"><span class="comment">// those are the current "pod" must yield to them and not take a space opened</span></span><br><span class="line"><span class="comment">// for running them. It is ok if the current "pod" take resources freed for</span></span><br><span class="line"><span class="comment">// lower priority pods.</span></span><br><span class="line"><span class="comment">// Requiring that the new pod is schedulable in both circumstances ensures that</span></span><br><span class="line"><span class="comment">// we are making a conservative decision: predicates like resources and inter-pod</span></span><br><span class="line"><span class="comment">// anti-affinity are more likely to fail when the nominated pods are treated</span></span><br><span class="line"><span class="comment">// as running, while predicates like pod affinity are more likely to fail when</span></span><br><span class="line"><span class="comment">// the nominated pods are treated as not running. We can't just assume the</span></span><br><span class="line"><span class="comment">// nominated pods are running because they are not running right now and in fact,</span></span><br><span class="line"><span class="comment">// they may end up getting scheduled to a different node.</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">2</span>; i++ &#123;</span><br><span class="line">metaToUse := meta</span><br><span class="line">nodeInfoToUse := info</span><br><span class="line">    <span class="comment">// 第一次调度，根据NominatedPods更新meta和nodeInfo信息，pod根据更新后的信息去预选</span></span><br><span class="line"><span class="comment">// 第二次调度，meta和nodeInfo信息不变，保证pod不完全依赖于NominatedPods（主要考虑到pod亲和性之类的）</span></span><br><span class="line"><span class="keyword">if</span> i == <span class="number">0</span> &#123;</span><br><span class="line">podsAdded, metaToUse, nodeInfoToUse = addNominatedPods(pod, meta, info, queue)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> !podsAdded || <span class="built_in">len</span>(failedPredicates) != <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Bypass eCache if node has any nominated pods.</span></span><br><span class="line"><span class="comment">// TODO(bsalamat): consider using eCache and adding proper eCache invalidations</span></span><br><span class="line"><span class="comment">// when pods are nominated or their nominations change.</span></span><br><span class="line">eCacheAvailable = equivClass != <span class="literal">nil</span> &amp;&amp; nodeCache != <span class="literal">nil</span> &amp;&amp; !podsAdded</span><br><span class="line"><span class="keyword">for</span> _, predicateKey := <span class="keyword">range</span> predicates.Ordering() &#123;</span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">fit     <span class="keyword">bool</span></span><br><span class="line">reasons []algorithm.PredicateFailureReason</span><br><span class="line">err     error</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> predicate, exist := predicateFuncs[predicateKey]; exist &#123;</span><br><span class="line"><span class="keyword">if</span> eCacheAvailable &#123;</span><br><span class="line">fit, reasons, err = nodeCache.RunPredicate(predicate, predicateKey, pod, metaToUse, nodeInfoToUse, equivClass, cache)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">fit, reasons, err = predicate(pod, metaToUse, nodeInfoToUse)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> !fit &#123;</span><br><span class="line"><span class="comment">// eCache is available and valid, and predicates result is unfit, record the fail reasons</span></span><br><span class="line">failedPredicates = <span class="built_in">append</span>(failedPredicates, reasons...)</span><br><span class="line"><span class="comment">// if alwaysCheckAllPredicates is false, short circuit all predicates when one predicate fails.</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">len</span>(failedPredicates) == <span class="number">0</span>, failedPredicates, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="优选函数主流程"><a href="#优选函数主流程" class="headerlink" title="优选函数主流程"></a>优选函数主流程</h3><p>使用与预选类似的多任务同步调用方式，采用 MapReduce的思想，Map 根据不同的优选算法获取对某一 Node 的值，根据 Reduce 统计最终的结果。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PrioritizeNodes prioritizes the nodes by running the individual priority functions in parallel.</span></span><br><span class="line"><span class="comment">// Each priority function is expected to set a score of 0-10</span></span><br><span class="line"><span class="comment">// 0 is the lowest priority score (least preferred node) and 10 is the highest</span></span><br><span class="line"><span class="comment">// Each priority function can also have its own weight</span></span><br><span class="line"><span class="comment">// The node scores returned by the priority function are multiplied by the weights to get weighted scores</span></span><br><span class="line"><span class="comment">// All scores are finally combined (added) to get the total weighted scores of all nodes</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrioritizeNodes</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">pod *v1.Pod,</span></span></span><br><span class="line"><span class="function"><span class="params">nodeNameToInfo <span class="keyword">map</span>[<span class="keyword">string</span>]*schedulercache.NodeInfo,</span></span></span><br><span class="line"><span class="function"><span class="params">meta <span class="keyword">interface</span>&#123;&#125;,</span></span></span><br><span class="line"><span class="function"><span class="params">priorityConfigs []algorithm.PriorityConfig,</span></span></span><br><span class="line"><span class="function"><span class="params">nodes []*v1.Node,</span></span></span><br><span class="line"><span class="function"><span class="params">extenders []algorithm.SchedulerExtender,</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> <span class="params">(schedulerapi.HostPriorityList, error)</span></span> &#123;</span><br><span class="line"><span class="comment">// If no priority configs are provided, then the EqualPriority function is applied</span></span><br><span class="line"><span class="comment">// This is required to generate the priority list in the required format</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(priorityConfigs) == <span class="number">0</span> &amp;&amp; <span class="built_in">len</span>(extenders) == <span class="number">0</span> &#123;</span><br><span class="line">result := <span class="built_in">make</span>(schedulerapi.HostPriorityList, <span class="number">0</span>, <span class="built_in">len</span>(nodes))</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> nodes &#123;</span><br><span class="line">hostPriority, err := EqualPriorityMap(pod, meta, nodeNameToInfo[nodes[i].Name])</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line">result = <span class="built_in">append</span>(result, hostPriority)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> result, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">mu   = sync.Mutex&#123;&#125;</span><br><span class="line">wg   = sync.WaitGroup&#123;&#125;</span><br><span class="line">errs []error</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">results := <span class="built_in">make</span>([]schedulerapi.HostPriorityList, <span class="built_in">len</span>(priorityConfigs), <span class="built_in">len</span>(priorityConfigs))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i, priorityConfig := <span class="keyword">range</span> priorityConfigs &#123;</span><br><span class="line"><span class="keyword">if</span> priorityConfig.Function != <span class="literal">nil</span> &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(index <span class="keyword">int</span>, config algorithm.PriorityConfig)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line"><span class="keyword">var</span> err error</span><br><span class="line">results[index], err = config.Function(pod, nodeNameToInfo, nodes)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">appendError(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(i, priorityConfig)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">results[i] = <span class="built_in">make</span>(schedulerapi.HostPriorityList, <span class="built_in">len</span>(nodes))</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">processNode := <span class="function"><span class="keyword">func</span><span class="params">(index <span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line">nodeInfo := nodeNameToInfo[nodes[index].Name]</span><br><span class="line"><span class="keyword">var</span> err error</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> priorityConfigs &#123;</span><br><span class="line">      <span class="comment">// 使用 map 函数计算过程数据</span></span><br><span class="line">results[i][index], err = priorityConfigs[i].Map(pod, meta, nodeInfo)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">workqueue.Parallelize(<span class="number">16</span>, <span class="built_in">len</span>(nodes), processNode)</span><br><span class="line"><span class="keyword">for</span> i, priorityConfig := <span class="keyword">range</span> priorityConfigs &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(index <span class="keyword">int</span>, config algorithm.PriorityConfig)</span></span> &#123;</span><br><span class="line">      <span class="comment">// 使用 reduce 函数结算结果</span></span><br><span class="line"><span class="keyword">if</span> err := config.Reduce(pod, meta, nodeNameToInfo, results[index]); err != <span class="literal">nil</span> &#123;</span><br><span class="line">appendError(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(i, priorityConfig)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Wait for all computations to be finished.</span></span><br><span class="line">wg.Wait()</span><br><span class="line"><span class="comment">// Summarize all scores.</span></span><br><span class="line">result := <span class="built_in">make</span>(schedulerapi.HostPriorityList, <span class="number">0</span>, <span class="built_in">len</span>(nodes))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> nodes &#123;</span><br><span class="line">result = <span class="built_in">append</span>(result, schedulerapi.HostPriority&#123;Host: nodes[i].Name, Score: <span class="number">0</span>&#125;)</span><br><span class="line"><span class="keyword">for</span> j := <span class="keyword">range</span> priorityConfigs &#123;</span><br><span class="line">result[i].Score += results[j][i].Score * priorityConfigs[j].Weight</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 继续使用 extenders 来进行优选 </span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(extenders) != <span class="number">0</span> &amp;&amp; nodes != <span class="literal">nil</span> &#123;</span><br><span class="line">combinedScores := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">int</span>, <span class="built_in">len</span>(nodeNameToInfo))</span><br><span class="line"><span class="keyword">for</span> _, extender := <span class="keyword">range</span> extenders &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(ext algorithm.SchedulerExtender)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">prioritizedList, weight, err := ext.Prioritize(pod, nodes)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// Prioritization errors from extender can be ignored, let k8s/other extenders determine the priorities</span></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">mu.Lock()</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> *prioritizedList &#123;</span><br><span class="line">host, score := (*prioritizedList)[i].Host, (*prioritizedList)[i].Score</span><br><span class="line">combinedScores[host] += score * weight</span><br><span class="line">&#125;</span><br><span class="line">mu.Unlock()</span><br><span class="line">&#125;(extender)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// wait for all go routines to finish</span></span><br><span class="line">wg.Wait()</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> result &#123;</span><br><span class="line">result[i].Score += combinedScores[result[i].Host]</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">  </span><br><span class="line"><span class="keyword">return</span> result, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="算法初始化"><a href="#算法初始化" class="headerlink" title="算法初始化"></a>算法初始化</h2><p>k8s.io/kubernetes/pkg/scheduler/algorithmprovider/plugins.go 函数 <code>ApplyFeatureGates</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> algorithmprovider</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ApplyFeatureGates applies algorithm by feature gates.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ApplyFeatureGates</span><span class="params">()</span></span> &#123;</span><br><span class="line">defaults.ApplyFeatureGates()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults/defaults.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// Register functions that extract metadata used by predicates and priorities computations.</span></span><br><span class="line">factory.RegisterPredicateMetadataProducerFactory(</span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(args factory.PluginFactoryArgs)</span> <span class="title">algorithm</span>.<span class="title">PredicateMetadataProducer</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> predicates.NewPredicateMetadataFactory(args.PodLister)</span><br><span class="line">&#125;)</span><br><span class="line">  </span><br><span class="line">factory.RegisterPriorityMetadataProducerFactory(</span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(args factory.PluginFactoryArgs)</span> <span class="title">algorithm</span>.<span class="title">PriorityMetadataProducer</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> priorities.NewPriorityMetadataFactory(args.ServiceLister, args.ControllerLister, args.ReplicaSetLister, args.StatefulSetLister)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 注册默认的预选算法和优选算法</span></span><br><span class="line">registerAlgorithmProvider(defaultPredicates(), defaultPriorities())</span><br><span class="line"></span><br><span class="line"><span class="comment">// IMPORTANT NOTES for predicate developers:</span></span><br><span class="line"><span class="comment">// We are using cached predicate result for pods belonging to the same equivalence class.</span></span><br><span class="line"><span class="comment">// So when implementing a new predicate, you are expected to check whether the result</span></span><br><span class="line"><span class="comment">// of your predicate function can be affected by related API object change (ADD/DELETE/UPDATE).</span></span><br><span class="line"><span class="comment">// If yes, you are expected to invalidate the cached predicate result for related API object change.</span></span><br><span class="line"><span class="comment">// For example:</span></span><br><span class="line"><span class="comment">// https://github.com/kubernetes/kubernetes/blob/36a218e/plugin/pkg/scheduler/factory/factory.go#L422</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Registers predicates and priorities that are not enabled by default, but user can pick when creating their</span></span><br><span class="line"><span class="comment">// own set of priorities/predicates.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// PodFitsPorts has been replaced by PodFitsHostPorts for better user understanding.</span></span><br><span class="line"><span class="comment">// For backwards compatibility with 1.0, PodFitsPorts is registered as well.</span></span><br><span class="line">factory.RegisterFitPredicate(<span class="string">"PodFitsPorts"</span>, predicates.PodFitsHostPorts)</span><br><span class="line"><span class="comment">// Fit is defined based on the absence of port conflicts.</span></span><br><span class="line"><span class="comment">// This predicate is actually a default predicate, because it is invoked from</span></span><br><span class="line"><span class="comment">// predicates.GeneralPredicates()</span></span><br><span class="line">factory.RegisterFitPredicate(predicates.PodFitsHostPortsPred, predicates.PodFitsHostPorts)</span><br><span class="line"><span class="comment">// Fit is determined by resource availability.</span></span><br><span class="line"><span class="comment">// This predicate is actually a default predicate, because it is invoked from</span></span><br><span class="line"><span class="comment">// predicates.GeneralPredicates()</span></span><br><span class="line">factory.RegisterFitPredicate(predicates.PodFitsResourcesPred, predicates.PodFitsResources)</span><br><span class="line"><span class="comment">// Fit is determined by the presence of the Host parameter and a string match</span></span><br><span class="line"><span class="comment">// This predicate is actually a default predicate, because it is invoked from</span></span><br><span class="line"><span class="comment">// predicates.GeneralPredicates()</span></span><br><span class="line">factory.RegisterFitPredicate(predicates.HostNamePred, predicates.PodFitsHost)</span><br><span class="line"><span class="comment">// Fit is determined by node selector query.</span></span><br><span class="line">factory.RegisterFitPredicate(predicates.MatchNodeSelectorPred, predicates.PodMatchNodeSelector)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ServiceSpreadingPriority is a priority config factory that spreads pods by minimizing</span></span><br><span class="line"><span class="comment">// the number of pods (belonging to the same service) on the same node.</span></span><br><span class="line"><span class="comment">// Register the factory so that it's available, but do not include it as part of the default priorities</span></span><br><span class="line"><span class="comment">// Largely replaced by "SelectorSpreadPriority", but registered for backward compatibility with 1.0</span></span><br><span class="line">factory.RegisterPriorityConfigFactory(</span><br><span class="line"><span class="string">"ServiceSpreadingPriority"</span>,</span><br><span class="line">factory.PriorityConfigFactory&#123;</span><br><span class="line">MapReduceFunction: <span class="function"><span class="keyword">func</span><span class="params">(args factory.PluginFactoryArgs)</span> <span class="params">(algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> priorities.NewSelectorSpreadPriority(args.ServiceLister, algorithm.EmptyControllerLister&#123;&#125;, algorithm.EmptyReplicaSetLister&#123;&#125;, algorithm.EmptyStatefulSetLister&#123;&#125;)</span><br><span class="line">&#125;,</span><br><span class="line">Weight: <span class="number">1</span>,</span><br><span class="line">&#125;,</span><br><span class="line">)</span><br><span class="line"><span class="comment">// EqualPriority is a prioritizer function that gives an equal weight of one to all nodes</span></span><br><span class="line"><span class="comment">// Register the priority function so that its available</span></span><br><span class="line"><span class="comment">// but do not include it as part of the default priorities</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"EqualPriority"</span>, core.EqualPriorityMap, <span class="literal">nil</span>, <span class="number">1</span>)</span><br><span class="line"><span class="comment">// Optional, cluster-autoscaler friendly priority function - give used nodes higher priority.</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"MostRequestedPriority"</span>, priorities.MostRequestedPriorityMap, <span class="literal">nil</span>, <span class="number">1</span>)</span><br><span class="line">factory.RegisterPriorityFunction2(</span><br><span class="line"><span class="string">"RequestedToCapacityRatioPriority"</span>,</span><br><span class="line">priorities.RequestedToCapacityRatioResourceAllocationPriorityDefault().PriorityMap,</span><br><span class="line"><span class="literal">nil</span>,</span><br><span class="line"><span class="number">1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="预选算法"><a href="#预选算法" class="headerlink" title="预选算法"></a>预选算法</h2><p>k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IMPORTANT <span class="doctag">NOTE:</span> this list contains the ordering of the predicates, if you develop a new predicate</span></span><br><span class="line"><span class="comment">// it is mandatory to add its name to this list.</span></span><br><span class="line"><span class="comment">// Otherwise it won't be processed, see generic_scheduler#podFitsOnNode().</span></span><br><span class="line"><span class="comment">// The order is based on the restrictiveness &amp; complexity of predicates.</span></span><br><span class="line"><span class="comment">// Design doc: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/predicates-ordering.md</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 预选算法的顺序</span></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">predicatesOrdering = []<span class="keyword">string</span>&#123;CheckNodeConditionPred, CheckNodeUnschedulablePred,</span><br><span class="line">GeneralPred, HostNamePred, PodFitsHostPortsPred,</span><br><span class="line">MatchNodeSelectorPred, PodFitsResourcesPred, NoDiskConflictPred,</span><br><span class="line">PodToleratesNodeTaintsPred, PodToleratesNodeNoExecuteTaintsPred, CheckNodeLabelPresencePred,</span><br><span class="line">CheckServiceAffinityPred, MaxEBSVolumeCountPred, MaxGCEPDVolumeCountPred, MaxCSIVolumeCountPred,</span><br><span class="line">MaxAzureDiskVolumeCountPred, CheckVolumeBindingPred, NoVolumeZoneConflictPred,</span><br><span class="line">CheckNodeMemoryPressurePred, CheckNodePIDPressurePred, CheckNodeDiskPressurePred, MatchInterPodAffinityPred&#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults/defaults.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">defaultPredicates</span><span class="params">()</span> <span class="title">sets</span>.<span class="title">String</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> sets.NewString(</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Fit is determined by node conditions: not ready, </span></span><br><span class="line">    <span class="comment">// network unavailable or out of disk.</span></span><br><span class="line">factory.RegisterMandatoryFitPredicate(predicates.CheckNodeConditionPred, predicates.CheckNodeConditionPredicate), <span class="comment">// 见下文函数分析</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go</p><p><code>CheckNodeConditionPredicate</code> 函数用于判断 Node 的状态是否符合预期：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CheckNodeConditionPredicate checks if a pod can be scheduled on a node reporting out of disk,</span></span><br><span class="line"><span class="comment">// network unavailable and not ready condition. Only node conditions are accounted in this predicate.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CheckNodeConditionPredicate</span><span class="params">(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *schedulercache.NodeInfo)</span> <span class="params">(<span class="keyword">bool</span>, []algorithm.PredicateFailureReason, error)</span></span> &#123;</span><br><span class="line">reasons := []algorithm.PredicateFailureReason&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> nodeInfo == <span class="literal">nil</span> || nodeInfo.Node() == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>, []algorithm.PredicateFailureReason&#123;ErrNodeUnknownCondition&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">node := nodeInfo.Node()</span><br><span class="line"><span class="keyword">for</span> _, cond := <span class="keyword">range</span> node.Status.Conditions &#123;</span><br><span class="line"><span class="comment">// We consider the node for scheduling only when its:</span></span><br><span class="line"><span class="comment">// - NodeReady condition status is ConditionTrue,</span></span><br><span class="line"><span class="comment">// - NodeOutOfDisk condition status is ConditionFalse,</span></span><br><span class="line"><span class="comment">// - NodeNetworkUnavailable condition status is ConditionFalse.</span></span><br><span class="line"><span class="keyword">if</span> cond.Type == v1.NodeReady &amp;&amp; cond.Status != v1.ConditionTrue &#123;</span><br><span class="line">reasons = <span class="built_in">append</span>(reasons, ErrNodeNotReady)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> cond.Type == v1.NodeOutOfDisk &amp;&amp; cond.Status != v1.ConditionFalse &#123;</span><br><span class="line">reasons = <span class="built_in">append</span>(reasons, ErrNodeOutOfDisk)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> cond.Type == v1.NodeNetworkUnavailable &amp;&amp; cond.Status != v1.ConditionFalse &#123;</span><br><span class="line">reasons = <span class="built_in">append</span>(reasons, ErrNodeNetworkUnavailable)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> node.Spec.Unschedulable &#123;</span><br><span class="line">reasons = <span class="built_in">append</span>(reasons, ErrNodeUnschedulable)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">len</span>(reasons) == <span class="number">0</span>, reasons, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中函数 <code>RegisterFitPredicate</code> 是对 <code>RegisterFitPredicateFactory</code> 的一层封装：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RegisterFitPredicate registers a fit predicate with the algorithm</span></span><br><span class="line"><span class="comment">// registry. Returns the name with which the predicate was registered.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RegisterFitPredicate</span><span class="params">(name <span class="keyword">string</span>, predicate algorithm.FitPredicate)</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> RegisterFitPredicateFactory(name, <span class="function"><span class="keyword">func</span><span class="params">(PluginFactoryArgs)</span> <span class="title">algorithm</span>.<span class="title">FitPredicate</span></span> &#123; <span class="keyword">return</span> predicate &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="优选算法-重点"><a href="#优选算法-重点" class="headerlink" title="优选算法 - 重点"></a>优选算法 - 重点</h2><p>k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults/defaults.go</p><table><thead><tr><th>序号</th><th>优选策略</th><th>策略说明</th></tr></thead><tbody><tr><td>1</td><td>SelectorSpreadPriority</td><td>尽量将属于同一 Service，StatefulSet 或 ReplicaSet 的 Pod 跨主机调度</td></tr><tr><td>2</td><td>InterPodAffinityPriority</td><td>基于 Pod 亲和情况打分, 通过循环计算 weightedPodAffinityTerm 的和，如果该节点满足相应的PodAffinityTerm，则在总和中添加 “权重” 来计算总和；总和最高的节点是最优选的</td></tr><tr><td>3</td><td>LeastRequestedPriority</td><td>计算 Pod 需要的 CPU 和内存资源与在 Node 可用资源的百分比，具有最小百分比的节点就是最优</td></tr><tr><td>4</td><td>BalancedResourceAllocation</td><td>根据 Node 上各项资源(CPU、内存) 使用率均衡情况进行打分</td></tr><tr><td>5</td><td>NodePreferAvoidPodsPriority</td><td>根据节点注释 scheduler.alpha.kubernetes.io/preferAvoidPods 对节点进行优先级排序。您可以使用它来暗示两个不同的Pod不应在同一节点上运行</td></tr><tr><td>6</td><td>NodeAffinityPriority</td><td>根据 PreferredDuringSchedulingIgnoredDuringExecution 中指示的节点相似性调度首选项对节点进行优先级排序。您可以在将Pod分配给节点中了解有关此内容的更多信息</td></tr><tr><td>7</td><td>TaintTolerationPriority</td><td>根据节点上无法忍受的污点数量，为所有节点准备优先级列表。该策略会根据该列表来调整节点的排名</td></tr><tr><td>8</td><td>ImageLocalityPriority</td><td>根据节点上无法忍受的污点数量，为所有节点准备优先级列表。该策略会根据该列表来调整节点的排名</td></tr><tr><td></td><td>以上是 default  priotirty 函数注册</td><td></td></tr><tr><td>9</td><td>ServiceSpreadingPriority</td><td>对于给定的服务的 Pod 分配到不同的节点上运行，它有利于安排到尚未在其中分配了服务的 Pod 的节点上进行调度。总体结果是，该服务对于单个节点故障变得更具弹性</td></tr><tr><td>10</td><td>EqualPriority</td><td>对所有节点给予相等的权重</td></tr><tr><td>11</td><td>MostRequestedPriority</td><td>根据 Node 上所提供的资源进行打分；使用请求最多的资源来支持节点。此策略将使计划的 Pod 适应运行整体工作负载所需的最少数量的节点</td></tr><tr><td>12</td><td>RequestedToCapacityRatioPriority</td><td>使用默认资源评分功能形状创建基于 requestToCapacity 的ResourceAllocationPriority</td></tr></tbody></table><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">defaultPriorities</span><span class="params">()</span> <span class="title">sets</span>.<span class="title">String</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> sets.NewString(</span><br><span class="line"><span class="comment">// spreads pods by minimizing the number of pods (belonging to the same service or replication controller) on the same node.</span></span><br><span class="line">factory.RegisterPriorityConfigFactory(</span><br><span class="line"><span class="string">"SelectorSpreadPriority"</span>,</span><br><span class="line">factory.PriorityConfigFactory&#123;</span><br><span class="line">MapReduceFunction: <span class="function"><span class="keyword">func</span><span class="params">(args factory.PluginFactoryArgs)</span> <span class="params">(algorithm.PriorityMapFunction, algorithm.PriorityReduceFunction)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> priorities.NewSelectorSpreadPriority(args.ServiceLister, args.ControllerLister, args.ReplicaSetLister, args.StatefulSetLister)</span><br><span class="line">&#125;,</span><br><span class="line">Weight: <span class="number">1</span>,</span><br><span class="line">&#125;,</span><br><span class="line">),</span><br><span class="line"><span class="comment">// pods should be placed in the same topological domain (e.g. same node, same rack, same zone, same power domain, etc.)</span></span><br><span class="line"><span class="comment">// as some other pods, or, conversely, should not be placed in the same topological domain as some other pods.</span></span><br><span class="line">factory.RegisterPriorityConfigFactory(</span><br><span class="line"><span class="string">"InterPodAffinityPriority"</span>,</span><br><span class="line">factory.PriorityConfigFactory&#123;</span><br><span class="line">Function: <span class="function"><span class="keyword">func</span><span class="params">(args factory.PluginFactoryArgs)</span> <span class="title">algorithm</span>.<span class="title">PriorityFunction</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> priorities.NewInterPodAffinityPriority(args.NodeInfo, args.NodeLister, args.PodLister, args.HardPodAffinitySymmetricWeight)</span><br><span class="line">&#125;,</span><br><span class="line">Weight: <span class="number">1</span>,</span><br><span class="line">&#125;,</span><br><span class="line">),</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prioritize nodes by least requested utilization.</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"LeastRequestedPriority"</span>, priorities.LeastRequestedPriorityMap, <span class="literal">nil</span>, <span class="number">1</span>),</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prioritizes nodes to help achieve balanced resource usage</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"BalancedResourceAllocation"</span>, priorities.BalancedResourceAllocationMap, <span class="literal">nil</span>, <span class="number">1</span>),</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set this weight large enough to override all other priority functions.</span></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> Figure out a better way to do this, maybe at same time as fixing #24720.</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"NodePreferAvoidPodsPriority"</span>, priorities.CalculateNodePreferAvoidPodsPriorityMap, <span class="literal">nil</span>, <span class="number">10000</span>),</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prioritizes nodes that have labels matching NodeAffinity</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"NodeAffinityPriority"</span>, priorities.CalculateNodeAffinityPriorityMap, priorities.CalculateNodeAffinityPriorityReduce, <span class="number">1</span>),</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prioritizes nodes that marked with taint which pod can tolerate.</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"TaintTolerationPriority"</span>, priorities.ComputeTaintTolerationPriorityMap, priorities.ComputeTaintTolerationPriorityReduce, <span class="number">1</span>),</span><br><span class="line"></span><br><span class="line"><span class="comment">// ImageLocalityPriority prioritizes nodes that have images requested by the pod present.</span></span><br><span class="line">factory.RegisterPriorityFunction2(<span class="string">"ImageLocalityPriority"</span>, priorities.ImageLocalityPriorityMap, <span class="literal">nil</span>, <span class="number">1</span>),</span><br><span class="line">)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://www.do1618.com/wp-content/uploads/2019/12/Schedule.jpeg" alt="img"> 来源<a href="https://ggaaooppeenngg.github.io/zh-CN/2017/09/26/kubernetes-%E6%8C%87%E5%8C%97/Schedule.jpeg" target="_blank" rel="noopener">https://ggaaooppeenngg.github.io/zh-CN/2017/09/26/kubernetes-%E6%8C%87%E5%8C%97/Schedule.jpeg</a></p><h2 id="Preempt"><a href="#Preempt" class="headerlink" title="Preempt"></a>Preempt</h2><p>当通过正常的调度流程如果没有找到合适的节点（主要是预选没有合适的节点），会判断需不需要进行<strong>抢占调度</strong>，具体的代码在<code>pkg/scheduler/scheduler.go</code>文件下，用到的方法<code>preempt</code>。</p><h2 id="自定义调度器"><a href="#自定义调度器" class="headerlink" title="自定义调度器"></a>自定义调度器</h2><p>kube-scheduler 在启动的时候可以通过 <code>--policy-config-file</code> 参数可以指定调度策略文件，用户可以根据需要组装 predicates 和 priority 函数。配置多个调度器参见文档：<a href="https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/" target="_blank" rel="noopener">Configure Multiple Schedulers</a></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="attr">"kind"</span> : <span class="string">"Policy"</span>,</span><br><span class="line"><span class="attr">"apiVersion"</span> : <span class="string">"v1"</span>,</span><br><span class="line"><span class="attr">"predicates"</span> : [</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"PodFitsHostPorts"</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"PodFitsResources"</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"NoDiskConflict"</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"NoVolumeZoneConflict"</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"MatchNodeSelector"</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"HostName"</span>&#125;</span><br><span class="line">    ],</span><br><span class="line"><span class="attr">"priorities"</span> : [</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"LeastRequestedPriority"</span>, <span class="attr">"weight"</span> : <span class="number">1</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"BalancedResourceAllocation"</span>, <span class="attr">"weight"</span> : <span class="number">1</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"ServiceSpreadingPriority"</span>, <span class="attr">"weight"</span> : <span class="number">1</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">"name"</span> : <span class="string">"EqualPriority"</span>, <span class="attr">"weight"</span> : <span class="number">1</span>&#125;</span><br><span class="line">    ],</span><br><span class="line"><span class="attr">"hardPodAffinitySymmetricWeight"</span> : <span class="number">10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然我们也可以自己编写  predicate 或 priority 函数，并完成注册</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// predicate </span></span><br><span class="line"><span class="keyword">type</span> FitPredicate <span class="function"><span class="keyword">func</span><span class="params">(pod *v1.Pod, meta <span class="keyword">interface</span>&#123;&#125;, nodeInfo *schedulercache.NodeInfo)</span> <span class="params">(<span class="keyword">bool</span>, []PredicateFailureReason, error)</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">// 完成注册</span></span><br><span class="line">factory.RegisterFitPredicate("MyFunc", predicates.PodFitsHostPorts)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义 policy 文件使用即可</span></span><br></pre></td></tr></table></figure><p>Pod 可以通过 spec.schedulername 字段指定特定的调度器；</p><blockquote><p>调取器的名字并没有统一保存在 apiserver 中进行统一管理，而是每个调取器去 apiserver 中获取和自己名字一直的 pod 来调度。也就是说，调度器是自己管理名字的，因此做到不冲突而且逻辑正确是每个调度器的工作。</p></blockquote><p>一个非常简单的 shell 调度器，它通过 <code>kubectl</code> 命令从 apiserver 获取未调度的 pod（<code>spec.schedulerName</code> 是 <code>my-scheduler</code>，并且<code>spec.nodeName</code> 为空），同样地，用 <code>kubectl</code> 从 apiserver 获取 nodes 的信息，然后随机选择一个 node 作为调度结果，并写入到 apiserver 中。更加详细的可以参见 <a href="https://github.com/kelseyhightower/scheduler" target="_blank" rel="noopener">https://github.com/kelseyhightower/scheduler</a> 和 <a href="https://banzaicloud.com/blog/k8s-custom-scheduler/" target="_blank" rel="noopener">Writing custom Kubernetes schedulers</a>，还有 <a href="https://github.com/martonsereg/random-scheduler" target="_blank" rel="noopener">https://github.com/martonsereg/random-scheduler</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">SERVER=<span class="string">'localhost:8001'</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    <span class="keyword">for</span> PODNAME <span class="keyword">in</span> $(kubectl --server <span class="variable">$SERVER</span> get pods -o json | jq <span class="string">'.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name'</span> | tr -d <span class="string">'"'</span>)</span><br><span class="line">;</span><br><span class="line">    <span class="keyword">do</span></span><br><span class="line">        NODES=($(kubectl --server <span class="variable">$SERVER</span> get nodes -o json | jq <span class="string">'.items[].metadata.name'</span> | tr -d <span class="string">'"'</span>))</span><br><span class="line">        NUMNODES=<span class="variable">$&#123;#NODES[@]&#125;</span></span><br><span class="line">        CHOSEN=<span class="variable">$&#123;NODES[$[ $RANDOM % $NUMNODES ]]&#125;</span></span><br><span class="line">        curl --header <span class="string">"Content-Type:application/json"</span> --request POST --data <span class="string">'&#123;"apiVersion":"v1", "kind": "Binding", "metadata": &#123;"name": "'</span><span class="variable">$PODNAME</span><span class="string">'"&#125;, "target": &#123;"apiVersion": "v1", "kind"</span></span><br><span class="line"><span class="string">: "Node", "name": "'</span><span class="variable">$CHOSEN</span><span class="string">'"&#125;&#125;'</span> http://<span class="variable">$SERVER</span>/api/v1/namespaces/default/pods/<span class="variable">$PODNAME</span>/binding/</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"Assigned <span class="variable">$PODNAME</span> to <span class="variable">$CHOSEN</span>"</span></span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line">    sleep 1</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://kubernetes.io/docs/concepts/scheduling/" target="_blank" rel="noopener">Kubernetes Scheduler</a></li><li><a href="https://juejin.im/post/5c889c2e5188257df700a732#heading-5" target="_blank" rel="noopener">Kubernetes源码分析之kube-scheduler</a></li><li><a href="https://cizixs.com/2017/07/19/kubernetes-scheduler-source-code-analysis/" target="_blank" rel="noopener">kubelet scheduler 源码分析：调度器的工作原理</a></li><li><a href="http://tang.love/2018/07/24/learning-kubernetes-source-code/" target="_blank" rel="noopener">Kubernetes Scheduler 源码全解析（附流程图）</a></li><li><a href="https://www.jianshu.com/p/aecdd7bf925c" target="_blank" rel="noopener">k8s-调度算法</a></li><li><a href="https://github.com/kelseyhightower/scheduler" target="_blank" rel="noopener">A toy kubernetes scheduler</a></li><li><a href="https://www.qikqiak.com/post/kube-scheduler-introduction/" target="_blank" rel="noopener">Kubernetes 调度器介绍</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;[toc]&lt;/p&gt;
&lt;h2 id=&quot;主要功能&quot;&gt;&lt;a href=&quot;#主要功能&quot; class=&quot;headerlink&quot; title=&quot;主要功能&quot;&gt;&lt;/a&gt;主要功能&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Kube-Scheduler&lt;/code&gt; 主要工作是为需要运行的 Pod 选择合适的
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="scheduler" scheme="http://www.cn18k.com/tags/scheduler/"/>
    
  </entry>
  
  <entry>
    <title>下一代微服务 ServiceMesh 介绍</title>
    <link href="http://www.cn18k.com/2019/03/05/service-mesh-intro/"/>
    <id>http://www.cn18k.com/2019/03/05/service-mesh-intro/</id>
    <published>2019-03-05T03:00:00.000Z</published>
    <updated>2019-03-05T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong> 正文 </strong><br><br><br><div class="row">    <embed src="https://www.do1618.com/wp-content/uploads/2019/03/ServiceMesh.pdf" width="100%" height="550" type="application/pdf"></div></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;strong&gt; 正文 &lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;

	&lt;div class=&quot;row&quot;&gt;
    &lt;embed src=&quot;https://www.do1618.com/wp-content/uploads/2019/03/ServiceMesh.pdf&quot; 
      
    
    </summary>
    
      <category term="service-mesh" scheme="http://www.cn18k.com/categories/service-mesh/"/>
    
    
      <category term="service mesh" scheme="http://www.cn18k.com/tags/service-mesh/"/>
    
  </entry>
  
  <entry>
    <title>Istio源码系列4：mixer 源码分析</title>
    <link href="http://www.cn18k.com/2019/02/15/istio-source-mixer/"/>
    <id>http://www.cn18k.com/2019/02/15/istio-source-mixer/</id>
    <published>2019-02-15T03:00:00.000Z</published>
    <updated>2019-02-15T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<hr><p><strong>本系列链接：</strong></p><ul><li><a href="/2019/02/02/istio-source-pilot-agent/" title="Istio源码系列1：pilot-agent 源码分析">Istio源码系列1：pilot-agent 源码分析</a></li><li><a href="/2019/02/06/istio-source-citadel/" title="Istio源码系列2：citadel 源码分析">Istio源码系列2：citadel 源码分析</a></li><li><a href="/2019/02/08/istio-source-pilot/" title="Istio源码系列3：pilot-discovery 源码分析">Istio源码系列3：pilot-discovery 源码分析</a></li><li><a href="/2019/02/15/istio-source-mixer/" title="Istio源码系列4：mixer 源码分析">Istio源码系列4：mixer 源码分析</a></li></ul><hr><p>TODO</p><hr><p><strong>除特别声明本站文章均属原创（翻译内容除外），如需要转载请事先联系，转载需要注明作者原文链接地址。</strong></p><hr>]]></content>
    
    <summary type="html">
    
      
      
        &lt;hr&gt;
&lt;p&gt;&lt;strong&gt;本系列链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2019/02/02/istio-source-pilot-agent/&quot; title=&quot;Istio源码系列1：pilot-agent 源码分析&quot;&gt;Istio源码系列1：
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="istio" scheme="http://www.cn18k.com/tags/istio/"/>
    
  </entry>
  
  <entry>
    <title>Istio源码系列3：pilot-discovery 源码分析</title>
    <link href="http://www.cn18k.com/2019/02/08/istio-source-pilot/"/>
    <id>http://www.cn18k.com/2019/02/08/istio-source-pilot/</id>
    <published>2019-02-08T03:00:00.000Z</published>
    <updated>2019-02-08T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<hr><p><strong>本系列链接：</strong></p><ul><li><a href="/2019/02/02/istio-source-pilot-agent/" title="Istio源码系列1：pilot-agent 源码分析">Istio源码系列1：pilot-agent 源码分析</a></li><li><a href="/2019/02/06/istio-source-citadel/" title="Istio源码系列2：citadel 源码分析">Istio源码系列2：citadel 源码分析</a></li><li><a href="/2019/02/08/istio-source-pilot/" title="Istio源码系列3：pilot-discovery 源码分析">Istio源码系列3：pilot-discovery 源码分析</a></li><li><a href="/2019/02/15/istio-source-mixer/" title="Istio源码系列4：mixer 源码分析">Istio源码系列4：mixer 源码分析</a></li></ul><hr><p>[TOC]</p><h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><p><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1ftppyenvgyj30la0fudhb.jpg" alt=""></p><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>完整的 yaml 文件参见 <a href="https://gist.github.com/DavadDi/3f515a972f33225cd5828ec47e31efe7" target="_blank" rel="noopener">pilot-yaml</a>。</p><p>Dockerfile</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FROM istionightly/base_debug</span><br><span class="line"></span><br><span class="line">ADD pilot-discovery /usr/<span class="built_in">local</span>/bin/</span><br><span class="line">ADD cacert.pem /cacert.pem</span><br><span class="line">ENTRYPOINT [<span class="string">"/usr/local/bin/pilot-discovery"</span>]</span><br></pre></td></tr></table></figure><p>启动命令行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ps -ef -www|grep pilot</span></span><br><span class="line">$/usr/<span class="built_in">local</span>/bin/pilot-discovery discovery</span><br></pre></td></tr></table></figure><p>命令行帮助</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl exec -ti istio-pilot-f9d78b7b9-fmhfb -n istio-system -c discovery -- /usr/local/bin/pilot-discovery --help</span></span><br><span class="line">Usage:</span><br><span class="line">  pilot-discovery [<span class="built_in">command</span>]</span><br><span class="line"></span><br><span class="line">Available Commands:</span><br><span class="line">  discovery   Start Istio proxy discovery service</span><br><span class="line">  <span class="built_in">help</span>        Help about any <span class="built_in">command</span></span><br><span class="line">  request     Makes an HTTP request to Pilot metrics/debug endpoint</span><br><span class="line">  version     Prints out build version information</span><br></pre></td></tr></table></figure><p>discovery 相关的帮助</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl exec -ti istio-pilot-f9d78b7b9-fmhfb -n istio-system -c discovery -- /usr/local/bin/pilot-discovery discovery --help</span></span><br><span class="line">Defaulting container name to discovery.</span><br><span class="line"></span><br><span class="line">Start Istio proxy discovery service</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line">  pilot-discovery discovery [flags]</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line">  -a, --appNamespace string                 Restrict the applications namespace the controller manages; <span class="keyword">if</span> not <span class="built_in">set</span>, controller watches all namespaces</span><br><span class="line">      --cfConfig string                     Cloud Foundry config file</span><br><span class="line">      --clusterRegistriesConfigMap string   ConfigMap map <span class="keyword">for</span> clusters config store</span><br><span class="line">      --clusterRegistriesNamespace string   Namespace <span class="keyword">for</span> ConfigMap <span class="built_in">which</span> stores clusters configs</span><br><span class="line">      --configDir string                    Directory to watch <span class="keyword">for</span> updates to config yaml files. If specified, the files will be used as the <span class="built_in">source</span> of config, rather than a CRD client.</span><br><span class="line">      --consulserverInterval duration       Interval (<span class="keyword">in</span> seconds) <span class="keyword">for</span> polling the Consul service registry (default 2s)</span><br><span class="line">      --consulserverURL string              URL <span class="keyword">for</span> the Consul server</span><br><span class="line">      --<span class="built_in">disable</span>-install-crds                Disable discovery service from verifying the existence of CRDs at startup and <span class="keyword">then</span> installing <span class="keyword">if</span> not detected.  It is recommended to be <span class="built_in">disable</span> <span class="keyword">for</span> highly available setups.</span><br><span class="line">      --discovery_cache                     Enable caching discovery service responses (default <span class="literal">true</span>)</span><br><span class="line">      --domain string                       DNS domain suffix (default <span class="string">"cluster.local"</span>)</span><br><span class="line">      --grpcAddr string                     Discovery service grpc address (default <span class="string">":15010"</span>)</span><br><span class="line">  -h, --<span class="built_in">help</span>                                <span class="built_in">help</span> <span class="keyword">for</span> discovery</span><br><span class="line">      --httpAddr string                     Discovery service HTTP address (default <span class="string">":8080"</span>)</span><br><span class="line">      --kubeconfig string                   Use a Kubernetes configuration file instead of <span class="keyword">in</span>-cluster configuration</span><br><span class="line">      --meshConfig string                   File name <span class="keyword">for</span> Istio mesh configuration. If not specified, a default mesh will be used. (default <span class="string">"/etc/istio/config/mesh"</span>)</span><br><span class="line">      --monitoringAddr string               HTTP address to use <span class="keyword">for</span> the exposing pilot self-monitoring information (default <span class="string">":9093"</span>)</span><br><span class="line">  -n, --namespace string                    Select a namespace <span class="built_in">where</span> the controller resides. If not <span class="built_in">set</span>, uses <span class="variable">$&#123;POD_NAMESPACE&#125;</span> environment variable</span><br><span class="line">      --plugins stringSlice                 comma separated list of networking plugins to <span class="built_in">enable</span> (default [authn,authz,health,mixer,envoyfilter])</span><br><span class="line">      --profile                             Enable profiling via web interface host:port/debug/pprof (default <span class="literal">true</span>)</span><br><span class="line">      --registries stringSlice              Comma separated list of platform service registries to <span class="built_in">read</span> from (choose one or more from &#123;Kubernetes, Consul, CloudFoundry, Mock, Config&#125;) (default [Kubernetes])</span><br><span class="line">      --resync duration                     Controller resync interval (default 1m0s)</span><br><span class="line">      --secureGrpcAddr string               Discovery service grpc address, with https (default <span class="string">":15012"</span>)</span><br><span class="line">      --webhookEndpoint string              Webhook API endpoint (supports http://sockethost, and unix:///absolute/path/to/socket</span><br><span class="line"></span><br><span class="line">Global Flags:</span><br><span class="line">   省略</span><br></pre></td></tr></table></figure><p>主要参数：</p><table><thead><tr><th>名称</th><th>默认值</th><th>备注</th></tr></thead><tbody><tr><td>–appNamespace</td><td>空</td><td>与 helm 安装中的 oneNamespace 对应</td></tr><tr><td>–configDir</td><td>空</td><td>表明 pilot 的两种来源：配置文件和 CRD</td></tr><tr><td>–discovery_cache</td><td>ture</td><td>启动 cache，有助于提升性能</td></tr><tr><td>–domain</td><td>“cluster.local”</td><td>k8s 中域后缀</td></tr><tr><td>–grpcAddr</td><td>:15010</td><td></td></tr><tr><td>–httpAddr</td><td>:8080</td><td></td></tr><tr><td>–meshConfig</td><td>“/etc/istio/config/mesh”</td><td></td></tr><tr><td>–registries</td><td>Kubernetes</td></tr></tbody></table><h2 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h2><p>函数入口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">discoveryCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">"discovery"</span>,</span><br><span class="line">Short: <span class="string">"Start Istio proxy discovery service."</span>,</span><br><span class="line">Args:  cobra.ExactArgs(<span class="number">0</span>),</span><br><span class="line">RunE: <span class="function"><span class="keyword">func</span><span class="params">(c *cobra.Command, args []<span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">           <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Create the stop channel for all of the servers.</span></span><br><span class="line">stop := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create the server for the discovery service.</span></span><br><span class="line">discoveryServer, err := bootstrap.NewServer(serverArgs)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">"failed to create discovery service: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Start the server</span></span><br><span class="line"><span class="keyword">if</span> err := discoveryServer.Start(stop); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">"failed to start discovery service: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">cmd.WaitSignal(stop)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>istio.io/istio/pilot/pkg/bootstrap/server.go</p><p>mesh 的默认配置参见：<a href="https://gist.github.com/DavadDi/f110459d339e260f818250287fc78ccc#file-mesh" target="_blank" rel="noopener">https://gist.github.com/DavadDi/f110459d339e260f818250287fc78ccc#file-mesh</a></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewServer creates a new Server instance based on the provided arguments.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewServer</span><span class="params">(args PilotArgs)</span> <span class="params">(*Server, error)</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">s := &amp;Server&#123;</span><br><span class="line">filewatcher: filewatcher.NewWatcher(),</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 省略错误处理</span></span><br><span class="line">s.initKubeClient(&amp;args) <span class="comment">// 初始化到 k8s 集群的客户端 s.kubeClient</span></span><br><span class="line">s.initMesh(&amp;args) <span class="comment">// 初始化配置，并添加到  filewatcher 中， /etc/istio/config/mesh</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1.0.5 版本的 cmd 中未包括，应该是 1.1 中添加 </span></span><br><span class="line">    <span class="comment">// serverArgs.NetworksConfigFile, "networksConfig", "/etc/istio/config/meshNetworks"</span></span><br><span class="line">    <span class="comment">// 初始化配置，并加入到 filewatcher 中监听</span></span><br><span class="line">s.initMeshNetworks(&amp;args)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// initMixerSan configures the mixerSAN configuration item. </span></span><br><span class="line">    <span class="comment">// The mesh must already have been configured.</span></span><br><span class="line">s.initMixerSan(&amp;args)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// creates the config controller in the pilotConfig.</span></span><br><span class="line">    <span class="comment">// 最终会创建一个 crd.NewController 实例</span></span><br><span class="line">s.initConfigController(&amp;args)</span><br><span class="line"> <span class="comment">/* 内部以 kube 为例，表明主要流程</span></span><br><span class="line"><span class="comment"> controller, err := s.makeKubeConfigController(args)</span></span><br><span class="line"><span class="comment">s.configController = controller</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">s.addStartFunc(func(stop &lt;-chan struct&#123;&#125;) error &#123;</span></span><br><span class="line"><span class="comment">go s.configController.Run(stop)  // 1. 第一个启动的 configController</span></span><br><span class="line"><span class="comment">&#125;)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// creates and initializes the service controllers</span></span><br><span class="line">s.initServiceControllers(&amp;args)</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">       s.createK8sServiceControllers(serviceControllers, args)</span></span><br><span class="line"><span class="comment">       --&gt; kube.NewController(s.kubeClient, args.Config.ControllerOptions)</span></span><br><span class="line"><span class="comment">       </span></span><br><span class="line"><span class="comment">       s.addStartFunc(func(stop &lt;-chan struct&#123;&#125;) error &#123;</span></span><br><span class="line"><span class="comment">go s.ServiceController.Run(stop)  // 2. 第二个启动的 ServiceController</span></span><br><span class="line"><span class="comment">return nil</span></span><br><span class="line"><span class="comment">&#125;)</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化 DiscoveryService gRPC 服务器，后面详细讲解</span></span><br><span class="line">    <span class="comment">// 添加2个 func 到 startFuncs 中</span></span><br><span class="line">s.initDiscoveryService(&amp;args)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// initializes the configuration for the pilot monitoring server.</span></span><br><span class="line">    <span class="comment">// 添加1个 func 到 startFuncs 中</span></span><br><span class="line">s.initMonitor(&amp;args)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// starts the secret controller to watch for remote</span></span><br><span class="line"><span class="comment">// clusters and initialize the multicluster structures.</span></span><br><span class="line">    <span class="comment">// 主要指定了，连接到远程集群的配置和需要监控的 namespace，并启动一个 secret controller</span></span><br><span class="line">    <span class="comment">// 监视 istio/multiCluster=true 的 secret </span></span><br><span class="line">    <span class="comment">// --clusterRegistriesConfigMap   ConfigMap map for clusters config store</span></span><br><span class="line">    <span class="comment">//  --clusterRegistriesNamespace string     Namespace for ConfigMap which stores clusters configs</span></span><br><span class="line">    <span class="comment">// 暂时不分析</span></span><br><span class="line">s.initClusterRegistries(&amp;args)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> s, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 NewServer 函数中初始化的函数依次启动起来</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Server)</span> <span class="title">Start</span><span class="params">(stop &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="comment">// Now start all of the components.</span></span><br><span class="line"><span class="keyword">for</span> _, fn := <span class="keyword">range</span> s.startFuncs &#123;</span><br><span class="line">fn(stop)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>istio.io/api/mesh/v1alpha1/network.pb.go</p><p>其中 MeshNetworks 结构体和定义说明如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MeshNetworks (config map) provides information about the set of networks</span></span><br><span class="line"><span class="comment">// inside a mesh and how to route to endpoints in each network. For example</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// MeshNetworks(file/config map):</span></span><br><span class="line"><span class="comment">// networks:</span></span><br><span class="line"><span class="comment">// - network1:</span></span><br><span class="line"><span class="comment">//   - endpoints:</span></span><br><span class="line"><span class="comment">//     - fromRegistry: registry1 #must match secret name in kubernetes</span></span><br><span class="line"><span class="comment">//     - fromCidr: 192.168.100.0/22 #a VM network for example</span></span><br><span class="line"><span class="comment">//     gateways:</span></span><br><span class="line"><span class="comment">//     - registryServiceName: istio-ingressgateway.istio-system.svc.cluster.local</span></span><br><span class="line"><span class="comment">//       port: 15443</span></span><br><span class="line"><span class="comment">//       locality: us-east-1a</span></span><br><span class="line"><span class="keyword">type</span> MeshNetworks <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// REQUIRED: The set of networks inside this mesh. Each network should</span></span><br><span class="line"><span class="comment">// have a unique name and information about how to infer the endpoints in</span></span><br><span class="line"><span class="comment">// the network as well as the gateways associated with the network.</span></span><br><span class="line">Networks <span class="keyword">map</span>[<span class="keyword">string</span>]*Network <span class="string">`protobuf:"bytes,1,rep,name=networks" json:"networks,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>经过对于 NewServer 函数的计划分析如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewServer</span><span class="params">(args PilotArgs)</span> <span class="params">(*Server, error)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 省略错误处理</span></span><br><span class="line">s.initKubeClient(&amp;args) <span class="comment">// 初始化到 k8s 集群的客户端 s.kubeClient</span></span><br><span class="line"></span><br><span class="line">   <span class="comment">// 初始化相关配置，并监视配置的变化情况</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// creates the config controller in the pilotConfig.</span></span><br><span class="line">    <span class="comment">// 最终会创建一个 crd.NewController 实例</span></span><br><span class="line">s.initConfigController(&amp;args)</span><br><span class="line"> <span class="comment">// s.makeKubeConfigController(args)</span></span><br><span class="line"><span class="comment">// s.configController.Run(stop)</span></span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// creates and initializes the service controllers</span></span><br><span class="line">s.initServiceControllers(&amp;args)</span><br><span class="line">    <span class="comment">// kube.NewController(s.kubeClient, args.Config.ControllerOptions)</span></span><br><span class="line">    <span class="comment">// go s.ServiceController.Run(stop)</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化 DiscoveryService gRPC 服务器，后面详细讲解</span></span><br><span class="line">    <span class="comment">// 添加 2 个 func 到 startFuncs 中</span></span><br><span class="line">s.initDiscoveryService(&amp;args)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> s, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ConfigController"><a href="#ConfigController" class="headerlink" title="ConfigController"></a>ConfigController</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// initConfigController creates the config controller in the pilotConfig.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Server)</span> <span class="title">initConfigController</span><span class="params">(args *PilotArgs)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="comment">// k8s 方式下初始化</span></span><br><span class="line">controller, err := s.makeKubeConfigController(args)</span><br><span class="line">s.configController = controller</span><br><span class="line"></span><br><span class="line"><span class="comment">// Defer starting the controller until after the service is created.</span></span><br><span class="line">s.addStartFunc(<span class="function"><span class="keyword">func</span><span class="params">(stop &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="keyword">go</span> s.configController.Run(stop)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Create the config store.</span></span><br><span class="line">s.istioConfigStore = model.MakeIstioStore(s.configController)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Server)</span> <span class="title">makeKubeConfigController</span><span class="params">(args *PilotArgs)</span> <span class="params">(model.ConfigStoreCache, error)</span></span> &#123;</span><br><span class="line">kubeCfgFile := s.getKubeCfgFile(args)</span><br><span class="line">configClient, err := crd.NewClient(</span><br><span class="line">        kubeCfgFile, <span class="string">""</span>, </span><br><span class="line">        model.IstioConfigTypes, <span class="comment">// 全部相关的 CRD 定义</span></span><br><span class="line">        args.Config.ControllerOptions.DomainSuffix)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> !args.Config.DisableInstallCRDs &#123;</span><br><span class="line">        <span class="comment">// 注册自定义的 CRD</span></span><br><span class="line">configClient.RegisterResources()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> crd.NewController(configClient, args.Config.ControllerOptions), <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 crd.NewClient 中的参数 model.IstioConfigTypes 包含了相关的全部 CRD 的定义:</p><p>istio.io/istio/pilot/pkg/model/config.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IstioConfigTypes lists all Istio config types with schemas and validation</span></span><br><span class="line">IstioConfigTypes = ConfigDescriptor&#123;</span><br><span class="line">VirtualService,</span><br><span class="line">Gateway,</span><br><span class="line">ServiceEntry,</span><br><span class="line">DestinationRule,</span><br><span class="line">EnvoyFilter,</span><br><span class="line">Sidecar,</span><br><span class="line">HTTPAPISpec,</span><br><span class="line">HTTPAPISpecBinding,</span><br><span class="line">QuotaSpec,</span><br><span class="line">QuotaSpecBinding,</span><br><span class="line">AuthenticationPolicy,</span><br><span class="line">AuthenticationMeshPolicy,</span><br><span class="line">ServiceRole,</span><br><span class="line">ServiceRoleBinding,</span><br><span class="line">RbacConfig,</span><br><span class="line">ClusterRbacConfig,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 crd.NewController 定义在 istio.io/istio/pilot/pkg/config/kube/crd/controller.go 文件中：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewController creates a new Kubernetes controller for CRDs</span></span><br><span class="line"><span class="comment">// Use "" for namespace to listen for all namespace changes</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewController</span><span class="params">(client *Client, options kube.ControllerOptions)</span> <span class="title">model</span>.<span class="title">ConfigStoreCache</span></span> &#123;</span><br><span class="line">log.Infof(<span class="string">"CRD controller watching namespaces %q"</span>, options.WatchedNamespace)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Queue requires a time duration for a retry delay after a handler error</span></span><br><span class="line">out := &amp;controller&#123;</span><br><span class="line">client: client,</span><br><span class="line">queue:  kube.NewQueue(<span class="number">1</span> * time.Second),</span><br><span class="line">kinds:  <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]cacheHandler),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// add stores for CRD kinds</span></span><br><span class="line"><span class="keyword">for</span> _, schema := <span class="keyword">range</span> client.ConfigDescriptor() &#123;</span><br><span class="line">out.addInformer(schema, options.WatchedNamespace, options.ResyncPeriod)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> out</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于 CRD 的监视，每一类资源需要启动一个单独的客户端连接，目前 CRD 的 Group 主要有 network/config/authentication/rbac 等；</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// controller is a collection of synchronized resource watchers.</span></span><br><span class="line"><span class="comment">// Caches are thread-safe</span></span><br><span class="line"><span class="keyword">type</span> controller <span class="keyword">struct</span> &#123;</span><br><span class="line">client *Client  <span class="comment">// 每类资源一个连接</span></span><br><span class="line">queue  kube.Queue</span><br><span class="line">kinds  <span class="keyword">map</span>[<span class="keyword">string</span>]cacheHandler</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> cacheHandler <span class="keyword">struct</span> &#123;</span><br><span class="line">informer cache.SharedIndexInformer</span><br><span class="line">handler  *kube.ChainHandler</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Client is a basic REST client for CRDs implementing config store</span></span><br><span class="line"><span class="keyword">type</span> Client <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// Map of apiVersion to restClient.</span></span><br><span class="line">clientset <span class="keyword">map</span>[<span class="keyword">string</span>]*restClient</span><br><span class="line"></span><br><span class="line"><span class="comment">// domainSuffix for the config metadata</span></span><br><span class="line">domainSuffix <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ServiceController"><a href="#ServiceController" class="headerlink" title="ServiceController"></a>ServiceController</h3><p>istio.io/istio/pilot/pkg/serviceregistry/kube/controller.go</p><p>结构定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Controller <span class="keyword">struct</span> &#123;</span><br><span class="line">domainSuffix <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line">client    kubernetes.Interface</span><br><span class="line">queue     Queue</span><br><span class="line">services  cacheHandler</span><br><span class="line">endpoints cacheHandler</span><br><span class="line">nodes     cacheHandler</span><br><span class="line"></span><br><span class="line">pods *PodCache</span><br><span class="line"></span><br><span class="line"><span class="comment">//....</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>NewController 函数定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewController creates a new Kubernetes controller</span></span><br><span class="line"><span class="comment">// Created by bootstrap and multicluster (see secretcontroler).</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewController</span><span class="params">(client kubernetes.Interface, options ControllerOptions)</span> *<span class="title">Controller</span></span> &#123;</span><br><span class="line">log.Infof(<span class="string">"Service controller watching namespace %q for services, endpoints, nodes and pods, refresh %s"</span>,</span><br><span class="line">options.WatchedNamespace, options.ResyncPeriod)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Queue requires a time duration for a retry delay after a handler error</span></span><br><span class="line">out := &amp;Controller&#123;</span><br><span class="line">domainSuffix:               options.DomainSuffix,</span><br><span class="line">client:                     client,</span><br><span class="line">queue:                      NewQueue(<span class="number">1</span> * time.Second),</span><br><span class="line">ClusterID:                  options.ClusterID,</span><br><span class="line">XDSUpdater:                 options.XDSUpdater,</span><br><span class="line">servicesMap:                <span class="built_in">make</span>(<span class="keyword">map</span>[model.Hostname]*model.Service),</span><br><span class="line">externalNameSvcInstanceMap: <span class="built_in">make</span>(<span class="keyword">map</span>[model.Hostname][]*model.ServiceInstance),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">sharedInformers := informers.NewSharedInformerFactoryWithOptions(client, options.ResyncPeriod, informers.WithNamespace(options.WatchedNamespace))</span><br><span class="line"></span><br><span class="line">svcInformer := sharedInformers.Core().V1().Services().Informer()</span><br><span class="line">out.services = out.createCacheHandler(svcInformer, <span class="string">"Services"</span>)</span><br><span class="line"></span><br><span class="line">epInformer := sharedInformers.Core().V1().Endpoints().Informer()</span><br><span class="line">out.endpoints = out.createEDSCacheHandler(epInformer, <span class="string">"Endpoints"</span>)</span><br><span class="line"></span><br><span class="line">nodeInformer := sharedInformers.Core().V1().Nodes().Informer()</span><br><span class="line">out.nodes = out.createCacheHandler(nodeInformer, <span class="string">"Nodes"</span>)</span><br><span class="line"></span><br><span class="line">podInformer := sharedInformers.Core().V1().Pods().Informer()</span><br><span class="line">out.pods = newPodCache(out.createCacheHandler(podInformer, <span class="string">"Pod"</span>), out)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> out</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上面代码可以很清晰看到，ServiceController 监听特定 namespace 下的以下资源：</p><ol><li>Services</li><li>Endpoints</li><li>Nodes</li><li>Pod</li></ol><h3 id="ServiceDiscovery"><a href="#ServiceDiscovery" class="headerlink" title="ServiceDiscovery"></a>ServiceDiscovery</h3><p>istio.io/istio/pilot/pkg/bootstrap/server.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Server)</span> <span class="title">initDiscoveryService</span><span class="params">(args *PilotArgs)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="comment">// 需要注意 env 保存了后面使用的变量包括 </span></span><br><span class="line">    <span class="comment">// s.istioConfigStore  s.ServiceController s.ServiceController</span></span><br><span class="line">environment := &amp;model.Environment&#123;</span><br><span class="line">Mesh:             s.mesh,</span><br><span class="line">MeshNetworks:     s.meshNetworks,</span><br><span class="line">IstioConfigStore: s.istioConfigStore, <span class="comment">// istio routing rules</span></span><br><span class="line">ServiceDiscovery: s.ServiceController, <span class="comment">// service list </span></span><br><span class="line">ServiceAccounts:  s.ServiceController,</span><br><span class="line">MixerSAN:         s.mixerSAN,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set up discovery service</span></span><br><span class="line">discovery, err := envoy.NewDiscoveryService(</span><br><span class="line">environment,</span><br><span class="line">args.DiscoveryOptions,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">s.mux = discovery.RestContainer.ServeMux</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建  envoyv2.NewDiscoveryServer 对应的 gRPC Server</span></span><br><span class="line">s.EnvoyXdsServer = envoyv2.NewDiscoveryServer(</span><br><span class="line">        environment,</span><br><span class="line">istio_networking.NewConfigGenerator(args.Plugins),</span><br><span class="line">s.ServiceController, </span><br><span class="line">        s.configController)</span><br><span class="line">    </span><br><span class="line">s.EnvoyXdsServer.InitDebug(s.mux, s.ServiceController)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// create grpc/http server</span></span><br><span class="line">s.initGrpcServer(args.KeepaliveOptions)</span><br><span class="line">s.httpServer = &amp;http.Server&#123;</span><br><span class="line">Addr:    args.DiscoveryOptions.HTTPAddr,</span><br><span class="line">Handler: s.mux,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// create http listener</span></span><br><span class="line">listener, err := net.Listen(<span class="string">"tcp"</span>, args.DiscoveryOptions.HTTPAddr)</span><br><span class="line">s.HTTPListeningAddr = listener.Addr()</span><br><span class="line"></span><br><span class="line"><span class="comment">// create grpc listener</span></span><br><span class="line">grpcListener, err := net.Listen(<span class="string">"tcp"</span>, args.DiscoveryOptions.GrpcAddr)</span><br><span class="line">s.GRPCListeningAddr = grpcListener.Addr()</span><br><span class="line"></span><br><span class="line">s.addStartFunc(<span class="function"><span class="keyword">func</span><span class="params">(stop &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="comment">// 启动 http  server goroutine</span></span><br><span class="line"><span class="comment">// 启动 gRPC server goroutine</span></span><br><span class="line"><span class="comment">// 等待关闭 goroutine</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run secure grpc server</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>istio.io/istio/pilot/pkg/proxy/envoy/v2/discovery.go，<code>envoyv2.NewDiscoveryServer</code> 函数定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// s.EnvoyXdsServer = envoyv2.NewDiscoveryServer(</span></span><br><span class="line"><span class="comment">//        environment,</span></span><br><span class="line"><span class="comment">//  istio_networking.NewConfigGenerator(args.Plugins),</span></span><br><span class="line"><span class="comment">//  s.ServiceController, </span></span><br><span class="line"><span class="comment">//        s.configController)</span></span><br><span class="line"><span class="comment">// NewDiscoveryServer creates DiscoveryServer that sources data from Pilot's internal mesh data structures</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewDiscoveryServer</span><span class="params">(env *model.Environment, generator core.ConfigGenerator, ctl model.Controller, configCache model.ConfigStoreCache)</span> *<span class="title">DiscoveryServer</span></span> &#123;</span><br><span class="line">    <span class="comment">// 创建 DiscoveryServer 对象</span></span><br><span class="line">out := &amp;DiscoveryServer&#123;</span><br><span class="line">Env:                     env,</span><br><span class="line">ConfigGenerator:         generator, <span class="comment">// generates xDS responses</span></span><br><span class="line">ConfigController:        configCache,</span><br><span class="line">EndpointShardsByService: <span class="keyword">map</span>[<span class="keyword">string</span>]*EndpointShards&#123;&#125;,</span><br><span class="line">WorkloadsByID:           <span class="keyword">map</span>[<span class="keyword">string</span>]*Workload&#123;&#125;,</span><br><span class="line">edsUpdates:              <span class="keyword">map</span>[<span class="keyword">string</span>]*EndpointShards&#123;&#125;,</span><br><span class="line">concurrentPushLimit:     <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;, <span class="number">20</span>), </span><br><span class="line">updateChannel:           <span class="built_in">make</span>(<span class="keyword">chan</span> *updateReq, <span class="number">10</span>),</span><br><span class="line">&#125;</span><br><span class="line">env.PushContext = model.NewPushContext()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理相关的更新操作</span></span><br><span class="line">    <span class="comment">// handleUpdates处理来自 updateChannel 的事件它确保自上次事件处理之前至少已经过了minQuiet时间。</span></span><br><span class="line">    <span class="comment">// 它还确保在接收事件和处理事件之间最多经过 maxDelay。</span></span><br><span class="line">    <span class="comment">// 最后调用 doPush 函数进行推送，根据全量推送标记，将最近更新的 eds 相关信息通过 </span></span><br><span class="line">    <span class="comment">// XDS Incremental Push 或者 全量推送出去</span></span><br><span class="line"><span class="keyword">go</span> out.handleUpdates()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以下三种清空 DiscoveryServer 的本地缓存，并注册清理缓存的函数</span></span><br><span class="line">    <span class="comment">// 1. service 相关的信息有变化的时候，</span></span><br><span class="line">    <span class="comment">// 2. jwt public key 发生变化</span></span><br><span class="line">    <span class="comment">// 3. Istio CRD 有变化的</span></span><br><span class="line">    <span class="comment">// 当以上三种任一情况发生的时候，会设置信息到 out.handleUpdates() 函数</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 周期性更新</span></span><br><span class="line"><span class="keyword">go</span> out.periodicRefresh()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 周期性更新 metrics</span></span><br><span class="line"><span class="keyword">go</span> out.periodicRefreshMetrics()</span><br><span class="line"></span><br><span class="line">out.DebugConfigs = pilot.DebugConfigs</span><br><span class="line"></span><br><span class="line">pushThrottle := intEnv(pilot.PushThrottle, <span class="number">10</span>)</span><br><span class="line">pushBurst := intEnv(pilot.PushBurst, <span class="number">100</span>)</span><br><span class="line"></span><br><span class="line">adsLog.Infof(<span class="string">"Starting ADS server with rateLimiter=%d burst=%d"</span>, pushThrottle, pushBurst)</span><br><span class="line">out.rateLimiter = rate.NewLimiter(rate.Limit(pushThrottle), pushBurst)</span><br><span class="line">out.initRateLimiter = rate.NewLimiter(rate.Limit(pushThrottle*<span class="number">2</span>), pushBurst*<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> out</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ADS 相关定义：</p><p><a href="https://github.com/envoyproxy/data-plane-api/blob/master/envoy/service/discovery/v2/ads.proto" target="_blank" rel="noopener">https://github.com/envoyproxy/data-plane-api/blob/master/envoy/service/discovery/v2/ads.proto</a></p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">// See https://github.com/lyft/envoy-api#apis for a description of the role of</span><br><span class="line">// ADS and how it is intended to be used by a management server. ADS requests</span><br><span class="line">// have the same structure as their singleton xDS counterparts, but can</span><br><span class="line">// multiplex many resource types on a single stream. The type_url in the</span><br><span class="line">// DiscoveryRequest/DiscoveryResponse provides sufficient information to recover</span><br><span class="line">// the multiplexed singleton APIs at the Envoy instance and management server.</span><br><span class="line">service AggregatedDiscoveryService &#123;</span><br><span class="line">  // This is a gRPC-only API.</span><br><span class="line">  rpc StreamAggregatedResources(stream envoy.api.v2.DiscoveryRequest)</span><br><span class="line">      returns (stream envoy.api.v2.DiscoveryResponse) &#123;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  rpc IncrementalAggregatedResources(stream envoy.api.v2.IncrementalDiscoveryRequest)</span><br><span class="line">      returns (stream envoy.api.v2.IncrementalDiscoveryResponse) &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/discovery.proto" target="_blank" rel="noopener">https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/discovery.proto</a></p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line">// A DiscoveryRequest requests a set of versioned resources of the same type for</span><br><span class="line">// a given Envoy node on some API.</span><br><span class="line">message DiscoveryRequest &#123;</span><br><span class="line">  // The version_info provided in the request messages will be the version_info</span><br><span class="line">  // received with the most recent successfully processed response or empty on</span><br><span class="line">  // the first request. It is expected that no new request is sent after a</span><br><span class="line">  // response is received until the Envoy instance is ready to ACK/NACK the new</span><br><span class="line">  // configuration. ACK/NACK takes place by returning the new API config version</span><br><span class="line">  // as applied or the previous API config version respectively. Each type_url</span><br><span class="line">  // (see below) has an independent version associated with it.</span><br><span class="line">  string version_info = 1;</span><br><span class="line"></span><br><span class="line">  // The node making the request.</span><br><span class="line">  core.Node node = 2;</span><br><span class="line"></span><br><span class="line">  // List of resources to subscribe to, e.g. list of cluster names or a route</span><br><span class="line">  // configuration name. If this is empty, all resources for the API are</span><br><span class="line">  // returned. LDS/CDS expect empty resource_names, since this is global</span><br><span class="line">  // discovery for the Envoy instance. The LDS and CDS responses will then imply</span><br><span class="line">  // a number of resources that need to be fetched via EDS/RDS, which will be</span><br><span class="line">  // explicitly enumerated in resource_names.</span><br><span class="line">  repeated string resource_names = 3;</span><br><span class="line"></span><br><span class="line">  // Type of the resource that is being requested, e.g.</span><br><span class="line">  // &quot;type.googleapis.com/envoy.api.v2.ClusterLoadAssignment&quot;. This is implicit</span><br><span class="line">  // in requests made via singleton xDS APIs such as CDS, LDS, etc. but is</span><br><span class="line">  // required for ADS.</span><br><span class="line">  string type_url = 4;</span><br><span class="line"></span><br><span class="line">  // nonce corresponding to DiscoveryResponse being ACK/NACKed. See above</span><br><span class="line">  // discussion on version_info and the DiscoveryResponse nonce comment. This</span><br><span class="line">  // may be empty if no nonce is available, e.g. at startup or for non-stream</span><br><span class="line">  // xDS implementations.</span><br><span class="line">  string response_nonce = 5;</span><br><span class="line"></span><br><span class="line">  // This is populated when the previous :ref:`DiscoveryResponse &lt;envoy_api_msg_DiscoveryResponse&gt;`</span><br><span class="line">  // failed to update configuration. The *message* field in *error_details* provides the Envoy</span><br><span class="line">  // internal exception related to the failure. It is only intended for consumption during manual</span><br><span class="line">  // debugging, the string provided is not guaranteed to be stable across Envoy versions.</span><br><span class="line">  google.rpc.Status error_detail = 6;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">message DiscoveryResponse &#123;</span><br><span class="line">  // The version of the response data.</span><br><span class="line">  string version_info = 1;</span><br><span class="line"></span><br><span class="line">  // The response resources. These resources are typed and depend on the API being called.</span><br><span class="line">  repeated google.protobuf.Any resources = 2 [(gogoproto.nullable) = false];</span><br><span class="line"></span><br><span class="line">  // [#not-implemented-hide:]</span><br><span class="line">  // Canary is used to support two Envoy command line flags:</span><br><span class="line">  //</span><br><span class="line">  // * --terminate-on-canary-transition-failure. When set, Envoy is able to</span><br><span class="line">  //   terminate if it detects that configuration is stuck at canary. Consider</span><br><span class="line">  //   this example sequence of updates:</span><br><span class="line">  //   - Management server applies a canary config successfully.</span><br><span class="line">  //   - Management server rolls back to a production config.</span><br><span class="line">  //   - Envoy rejects the new production config.</span><br><span class="line">  //   Since there is no sensible way to continue receiving configuration</span><br><span class="line">  //   updates, Envoy will then terminate and apply production config from a</span><br><span class="line">  //   clean slate.</span><br><span class="line">  // * --dry-run-canary. When set, a canary response will never be applied, only</span><br><span class="line">  //   validated via a dry run.</span><br><span class="line">  bool canary = 3;</span><br><span class="line"></span><br><span class="line">  // Type URL for resources. This must be consistent with the type_url in the</span><br><span class="line">  // Any messages for resources if resources is non-empty. This effectively</span><br><span class="line">  // identifies the xDS API when muxing over ADS.</span><br><span class="line">  string type_url = 4;</span><br><span class="line"></span><br><span class="line">  // For gRPC based subscriptions, the nonce provides a way to explicitly ack a</span><br><span class="line">  // specific DiscoveryResponse in a following DiscoveryRequest. Additional</span><br><span class="line">  // messages may have been sent by Envoy to the management server for the</span><br><span class="line">  // previous version on the stream prior to this DiscoveryResponse, that were</span><br><span class="line">  // unprocessed at response send time. The nonce allows the management server</span><br><span class="line">  // to ignore any further DiscoveryRequests for the previous version until a</span><br><span class="line">  // DiscoveryRequest bearing the nonce. The nonce is optional and is not</span><br><span class="line">  // required for non-stream based xDS implementations.</span><br><span class="line">  string nonce = 5;</span><br><span class="line"></span><br><span class="line">  // [#not-implemented-hide:]</span><br><span class="line">  // The control plane instance that sent the response.</span><br><span class="line">  core.ControlPlane control_plane = 6;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于 ADS 保证一致性的内容参见：<a href="https://www.do1618.com/archives/1474/envoy-%E7%9A%84-xds-rest-%E5%92%8C-grpc-%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%EF%BC%88%E8%AF%91%EF%BC%89/" target="_blank" rel="noopener">envoy-的-xds-rest-和-grpc-协议详解</a> 中的 ”最终一致性考虑“  章节：</p><blockquote><p>一般来说，为避免流量丢弃，更新的顺序应该遵循 make before break 模型，其中</p><ul><li>必须始终先推送 CDS 更新（如果有）。</li><li>EDS 更新（如果有）必须在相应集群的 CDS 更新后到达。</li><li>LDS 更新必须在相应的 CDS/EDS 更新后到达。</li><li>与新添加的监听器相关的 RDS 更新必须在最后到达。</li><li>最后，删除过期的 CDS 集群和相关的 EDS 端点（不再被引用的端点）。</li></ul><p>ADS 允许单一管理服务器通过单个 gRPC 流，提供所有的 API 更新。配合仔细规划的更新顺序，ADS 可规避更新过程中流量丢失。</p></blockquote><p>pilot/pkg/proxy/envoy/v2/ads.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// StreamAggregatedResources implements the ADS interface.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">StreamAggregatedResources</span><span class="params">(stream ads.AggregatedDiscoveryService_StreamAggregatedResourcesServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">// InitContext returns immediately if the context was already initialized.</span></span><br><span class="line">    <span class="comment">// InitContext 从 env 从取出需要发送到客户端的数据，后续会继续分析</span></span><br><span class="line">    <span class="comment">// 1. initServiceRegistry</span></span><br><span class="line">    <span class="comment">// 2. initVirtualServices</span></span><br><span class="line">    <span class="comment">// 3. initDestinationRules</span></span><br><span class="line">    <span class="comment">// 4. initAuthorizationPolicies</span></span><br><span class="line">    <span class="comment">// 5. InitSidecarScopes</span></span><br><span class="line">err := s.globalPushContext().InitContext(s.Env)</span><br><span class="line">con := newXdsConnection(peerAddr, stream)</span><br><span class="line"></span><br><span class="line">reqChannel := <span class="built_in">make</span>(<span class="keyword">chan</span> *xdsapi.DiscoveryRequest, <span class="number">1</span>)</span><br><span class="line">    <span class="comment">// 启动一个新的 goroutine 来从客户端接受相关的数据 xdsapi.DiscoveryRequest</span></span><br><span class="line"><span class="keyword">go</span> receiveThread(con, reqChannel, &amp;receiveError)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="comment">// Block until either a request is received or a push is triggered.</span></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> discReq, ok := &lt;-reqChannel:</span><br><span class="line">  err = s.initConnectionNode(discReq, con)</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> discReq.TypeUrl &#123;</span><br><span class="line"><span class="keyword">case</span> ClusterType:</span><br><span class="line">                <span class="comment">// ...</span></span><br><span class="line"><span class="comment">// CDS REQ is the first request an envoy makes. This shows up</span></span><br><span class="line"><span class="comment">// immediately after connect. It is followed by EDS REQ as</span></span><br><span class="line"><span class="comment">// soon as the CDS push is returned.</span></span><br><span class="line">adsLog.Infof(<span class="string">"ADS:CDS: REQ %v %s %v raw: %s"</span>, peerAddr, con.ConID, time.Since(t0), discReq.String())</span><br><span class="line">con.CDSWatch = <span class="literal">true</span></span><br><span class="line">err := s.pushCds(con, s.globalPushContext(), versionInfo())</span><br><span class="line">                </span><br><span class="line"><span class="keyword">case</span> ListenerType:</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">adsLog.Debugf(<span class="string">"ADS:LDS: REQ %s %v"</span>, con.ConID, peerAddr)</span><br><span class="line">con.LDSWatch = <span class="literal">true</span></span><br><span class="line">err := s.pushLds(con, s.globalPushContext(), <span class="literal">true</span>, versionInfo())</span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> RouteType:</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">adsLog.Debugf(<span class="string">"ADS:RDS: REQ %s %s  routes: %d"</span>, peerAddr, con.ConID, <span class="built_in">len</span>(con.Routes))</span><br><span class="line">err := s.pushRoute(con, s.globalPushContext())</span><br><span class="line">                </span><br><span class="line"><span class="keyword">case</span> EndpointType:</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="comment">// 各种错误处理</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, cn := <span class="keyword">range</span> con.Clusters &#123;</span><br><span class="line">s.removeEdsCon(cn, con.ConID, con)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, cn := <span class="keyword">range</span> clusters &#123;</span><br><span class="line">s.addEdsCon(cn, con.ConID, con)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">con.Clusters = clusters</span><br><span class="line">adsLog.Debugf(<span class="string">"ADS:EDS: REQ %s %s clusters: %d"</span>, peerAddr, con.ConID, <span class="built_in">len</span>(con.Clusters))</span><br><span class="line">err := s.pushEds(s.globalPushContext(), con, <span class="literal">true</span>, <span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">adsLog.Warnf(<span class="string">"ADS: Unknown watched resources %s"</span>, discReq.String())</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">case</span> pushEv := &lt;-con.pushChannel:</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">err := s.pushConnection(con, pushEv)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>istio.io/istio/pilot/pkg/model/push_context.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PushContext tracks the status of a push - metrics and errors.</span></span><br><span class="line"><span class="comment">// Metrics are reset after a push - at the beginning all</span></span><br><span class="line"><span class="comment">// values are zero, and when push completes the status is reset.</span></span><br><span class="line"><span class="comment">// The struct is exposed in a debug endpoint - fields public to allow</span></span><br><span class="line"><span class="comment">// easy serialization as json.</span></span><br><span class="line"><span class="keyword">type</span> PushContext <span class="keyword">struct</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// privateServices are reachable within the same namespace.</span></span><br><span class="line">privateServicesByNamespace <span class="keyword">map</span>[<span class="keyword">string</span>][]*Service</span><br><span class="line"><span class="comment">// publicServices are services reachable within the mesh.</span></span><br><span class="line">publicServices []*Service</span><br><span class="line"></span><br><span class="line">privateVirtualServicesByNamespace <span class="keyword">map</span>[<span class="keyword">string</span>][]Config</span><br><span class="line">publicVirtualServices             []Config</span><br><span class="line"></span><br><span class="line"><span class="comment">// destination rules are of three types:</span></span><br><span class="line"><span class="comment">// namespaceLocalDestRules: all public/private dest rules pertaining to a service defined in a given namespace</span></span><br><span class="line"><span class="comment">//  namespaceExportedDestRules: all public dest rules pertaining to a service defined in a namespace</span></span><br><span class="line"><span class="comment">//  allExportedDestRules: all (public) dest rules across all namespaces</span></span><br><span class="line"><span class="comment">// We need the allExportedDestRules in addition to namespaceExportedDestRules because we select</span></span><br><span class="line"><span class="comment">// the dest rule based on the most specific host match, and not just any destination rule</span></span><br><span class="line">namespaceLocalDestRules    <span class="keyword">map</span>[<span class="keyword">string</span>]*processedDestRules</span><br><span class="line">namespaceExportedDestRules <span class="keyword">map</span>[<span class="keyword">string</span>]*processedDestRules</span><br><span class="line">allExportedDestRules       *processedDestRules</span><br><span class="line"></span><br><span class="line"><span class="comment">// sidecars for each namespace</span></span><br><span class="line">sidecarsByNamespace <span class="keyword">map</span>[<span class="keyword">string</span>][]*SidecarScope</span><br><span class="line"><span class="comment">////////// END ////////</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// The following data is either a global index or used in the inbound path.</span></span><br><span class="line"><span class="comment">// Namespace specific views do not apply here.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ServiceByHostname has all services, indexed by hostname.</span></span><br><span class="line">ServiceByHostname <span class="keyword">map</span>[Hostname]*Service <span class="string">`json:"-"`</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// AuthzPolicies stores the existing authorization policies in the cluster. Could be nil if there</span></span><br><span class="line"><span class="comment">// are no authorization policies in the cluster.</span></span><br><span class="line">AuthzPolicies *AuthorizationPolicies <span class="string">`json:"-"`</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ServicePort2Name is used to keep track of service name and port mapping.</span></span><br><span class="line"><span class="comment">// This is needed because ADS names use port numbers, while endpoints use</span></span><br><span class="line"><span class="comment">// port names. The key is the service name. If a service or port are not found,</span></span><br><span class="line"><span class="comment">// the endpoint needs to be re-evaluated later (eventual consistency)</span></span><br><span class="line">ServicePort2Name <span class="keyword">map</span>[<span class="keyword">string</span>]PortList <span class="string">`json:"-"`</span></span><br><span class="line"></span><br><span class="line">initDone <span class="keyword">bool</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// InitContext will initialize the data structures used for code generation.</span></span><br><span class="line"><span class="comment">// This should be called before starting the push, from the thread creating</span></span><br><span class="line"><span class="comment">// the push context.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(ps *PushContext)</span> <span class="title">InitContext</span><span class="params">(env *Environment)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">ps.Mutex.Lock()</span><br><span class="line"><span class="keyword">defer</span> ps.Mutex.Unlock()</span><br><span class="line"><span class="keyword">if</span> ps.initDone &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line">ps.Env = env</span><br><span class="line"><span class="keyword">var</span> err error</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Caches list of services in the registry, and creates a map</span></span><br><span class="line">    <span class="comment">// of hostname to service -&gt; ServicePort2Name map[string]PortList `json:"-"`</span></span><br><span class="line">ps.initServiceRegistry(env)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Caches list of virtual services -&gt; publicVirtualServices []Config</span></span><br><span class="line">ps.initVirtualServices(env)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Split out of DestinationRule expensive conversions - once per push.</span></span><br><span class="line">    <span class="comment">// 最后保存到以下三个变量中：</span></span><br><span class="line">    <span class="comment">// * namespaceLocalDestRules    map[string]*processedDestRules</span></span><br><span class="line"><span class="comment">//  * namespaceExportedDestRules map[string]*processedDestRules</span></span><br><span class="line"><span class="comment">//  * allExportedDestRules       *processedDestRules</span></span><br><span class="line">ps.initDestinationRules(env)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Get the ClusterRbacConfig -&gt; AuthzPolicies *AuthorizationPolicies</span></span><br><span class="line">ps.initAuthorizationPolicies(env)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Must be initialized in the end -&gt; sidecarsByNamespace map[string][]*SidecarScope</span></span><br><span class="line">ps.InitSidecarScopes(env)</span><br><span class="line"></span><br><span class="line">ps.initDone = <span class="literal">true</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CDS 为 ADS 中第一个发送的信息，后续我们以  CDS 为例进行详细分析</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// StreamAggregatedResources implements the ADS interface.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">StreamAggregatedResources</span><span class="params">(stream ads.AggregatedDiscoveryService_StreamAggregatedResourcesServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">// InitContext returns immediately if the context was already initialized.</span></span><br><span class="line">    <span class="comment">// InitContext 从 env 从取出需要发送到客户端的数据，后续会继续分析</span></span><br><span class="line">    <span class="comment">// 1. initServiceRegistry</span></span><br><span class="line">    <span class="comment">// 2. initVirtualServices</span></span><br><span class="line">    <span class="comment">// 3. initDestinationRules</span></span><br><span class="line">    <span class="comment">// 4. initAuthorizationPolicies</span></span><br><span class="line">    <span class="comment">// 5. InitSidecarScopes</span></span><br><span class="line">err := s.globalPushContext().InitContext(s.Env)</span><br><span class="line">con := newXdsConnection(peerAddr, stream)</span><br><span class="line"></span><br><span class="line">reqChannel := <span class="built_in">make</span>(<span class="keyword">chan</span> *xdsapi.DiscoveryRequest, <span class="number">1</span>)</span><br><span class="line">    <span class="comment">// 启动一个新的 goroutine 来从客户端接受相关的数据 xdsapi.DiscoveryRequest</span></span><br><span class="line"><span class="keyword">go</span> receiveThread(con, reqChannel, &amp;receiveError)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="comment">// Block until either a request is received or a push is triggered.</span></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> discReq, ok := &lt;-reqChannel:</span><br><span class="line">            <span class="comment">// 主要是调用 `ParseServiceNodeWithMetadata` 函数，</span></span><br><span class="line">            <span class="comment">// 从 Req 的消息中获取到各种信息，并生产 `Proxy` 对象</span></span><br><span class="line">            <span class="comment">// 当前 discReq.Node.Id 格式为 Type~IPAddress~ID~Domain</span></span><br><span class="line">err = s.initConnectionNode(discReq, con)</span><br><span class="line">            </span><br><span class="line"><span class="keyword">switch</span> discReq.TypeUrl &#123;</span><br><span class="line"><span class="keyword">case</span> ClusterType:</span><br><span class="line">                <span class="comment">// 如果已经发送过 CDS 数据后的响应消息的处理</span></span><br><span class="line"><span class="keyword">if</span> con.CDSWatch &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// CDS REQ is the first request an envoy makes. This shows up</span></span><br><span class="line"><span class="comment">// immediately after connect. It is followed by EDS REQ as</span></span><br><span class="line"><span class="comment">// soon as the CDS push is returned.</span></span><br><span class="line">adsLog.Infof(<span class="string">"ADS:CDS: REQ %v %s %v raw: %s"</span>, peerAddr, con.ConID, time.Since(t0), discReq.String())</span><br><span class="line">con.CDSWatch = <span class="literal">true</span></span><br><span class="line">err := s.pushCds(con, s.globalPushContext(), versionInfo())</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="core-Node"><a href="#core-Node" class="headerlink" title="core.Node"></a><a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/base.proto#envoy-api-msg-core-node" target="_blank" rel="noopener">core.Node</a></h4><blockquote><p>Identifies a specific Envoy instance. The node identifier is presented to the management server, which may use this identifier to distinguish per Envoy configuration for serving.{<br>  “id”: “…”,<br>  “cluster”: “…”,<br>  “metadata”: “{…}”,<br>  “locality”: “{…}”,<br>  “build_version”: “…”<br>}</p><p><strong>id</strong><br>(string) An opaque node identifier for the Envoy node. This also provides the local service node name. It should be set if any of the following features are used: statsd, CDS, and HTTP tracing, either in this message or via –service-node.</p><p><strong>cluster</strong><br>(string) Defines the local service cluster name where Envoy is running. Though optional, it should be set if any of the following features are used: statsd, health check cluster verification, runtime override directory, user agent addition, HTTP global rate limiting, CDS, and HTTP tracing, either in this message or via –service-cluster.</p><p><strong>metadata</strong><br>(Struct) Opaque metadata extending the node identifier. Envoy will pass this directly to the management server.</p><p><strong>locality</strong><br>(core.Locality) Locality specifying where the Envoy instance is running.</p><p><strong>build_version</strong><br>(string) This is motivated by informing a management server during canary which version of Envoy is being tested in a heterogeneous fleet. This will be set by Envoy in management server RPCs.</p></blockquote><p>在 <code>initConnectionNode</code> 函数中，主要是调用 <code>ParseServiceNodeWithMetadata</code> 函数，从 Req 的消息中获取到各种信息，并生产 <code>Proxy</code> 对象；</p><p>istio.io/istio/pilot/pkg/model/context.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ParseServiceNodeWithMetadata</span><span class="params">(s <span class="keyword">string</span>, metadata <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>)</span> <span class="params">(*Proxy, error)</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Proxy contains information about an specific instance of a proxy (envoy sidecar, gateway,</span></span><br><span class="line"><span class="comment">// etc). The Proxy is initialized when a sidecar connects to Pilot, and populated from</span></span><br><span class="line"><span class="comment">// 'node' info in the protocol as well as data extracted from registries.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// In current Istio implementation nodes use a 4-parts '~' delimited ID.</span></span><br><span class="line"><span class="comment">// Type~IPAddress~ID~Domain</span></span><br><span class="line"><span class="keyword">type</span> Proxy <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// ClusterID specifies the cluster where the proxy resides.</span></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> clarify if this is needed in the new 'network' model, likely needs to</span></span><br><span class="line"><span class="comment">// be renamed to 'network'</span></span><br><span class="line">ClusterID <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Type specifies the node type. First part of the ID.</span></span><br><span class="line">Type NodeType</span><br><span class="line"></span><br><span class="line"><span class="comment">// IPAddresses is the IP addresses of the proxy used to identify it and its</span></span><br><span class="line"><span class="comment">// co-located service instances. Example: "10.60.1.6". In some cases, the host</span></span><br><span class="line"><span class="comment">// where the poxy and service instances reside may have more than one IP address</span></span><br><span class="line">IPAddresses []<span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ID is the unique platform-specific sidecar proxy ID. For k8s it is the pod ID and</span></span><br><span class="line"><span class="comment">// namespace.</span></span><br><span class="line">ID <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Locality is the location of where Envoy proxy runs.</span></span><br><span class="line">Locality Locality</span><br><span class="line"></span><br><span class="line"><span class="comment">// DNSDomain defines the DNS domain suffix for short hostnames (e.g.</span></span><br><span class="line"><span class="comment">// "default.svc.cluster.local")</span></span><br><span class="line">DNSDomain <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TrustDomain defines the trust domain of the certificate</span></span><br><span class="line">TrustDomain <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ConfigNamespace defines the namespace where this proxy resides</span></span><br><span class="line"><span class="comment">// for the purposes of network scoping.</span></span><br><span class="line"><span class="comment">// <span class="doctag">NOTE:</span> DO NOT USE THIS FIELD TO CONSTRUCT DNS NAMES</span></span><br><span class="line">ConfigNamespace <span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Metadata key-value pairs extending the Node identifier</span></span><br><span class="line">Metadata <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// the sidecarScope associated with the proxy</span></span><br><span class="line">SidecarScope *SidecarScope</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>推送 CDS 的核心实现通过函数 <code>s.pushCds(con, s.globalPushContext(), versionInfo())</code></p><p>istio.io/istio/pilot/pkg/proxy/envoy/v2/cds.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">pushCds</span><span class="params">(con *XdsConnection, push *model.PushContext, version <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="comment">// 通过当前的 con.modelNode 和 push 的上下文生成对应的 rawClusters 对象 []*xdsapi.Cluster</span></span><br><span class="line">rawClusters, err := s.generateRawClusters(con.modelNode, push)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// DebugConfigs controls saving snapshots of configs for /debug/adsz.</span></span><br><span class="line"><span class="comment">// Defaults to false, can be enabled with PILOT_DEBUG_ADSZ_CONFIG=1</span></span><br><span class="line">    <span class="comment">// 如果通过 env 开启了此选项，则可以使用 9093 端口 /debug/adsz 查看详细信息，会增加内存开销</span></span><br><span class="line">   <span class="keyword">if</span> s.DebugConfigs &#123;</span><br><span class="line">con.CDSClusters = rawClusters</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response := con.clusters(rawClusters)</span><br><span class="line">err = con.send(response)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因此 <code>s.generateRawClusters(con.modelNode, push)</code> 的作用不言而喻，就是将  <code>push</code> 上下文中与 CDS  相关的数据整理并封装成 CDS  Reponse 的格式。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">generateRawClusters</span><span class="params">(node *model.Proxy, push *model.PushContext)</span> <span class="params">([]*xdsapi.Cluster, error)</span></span> &#123;</span><br><span class="line">rawClusters, err := s.ConfigGenerator.BuildClusters(s.Env, node, push)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 对 rawClusters 中的信息进行 Validate 验证</span></span><br><span class="line"><span class="keyword">return</span> rawClusters, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对象 <code>rawClusters</code> 是通过 <code>s.ConfigGenerator.BuildClusters</code> 函数基于  <code>s.Env, node, push</code> 三者的信息组合而生成出来的：</p><ul><li><p>s.Env 保存了 ServiceController 和 ConfigController 等资源的本地缓存信息</p></li><li><p>node 为本次 Req 请求中生成的包含 Node 相关信息的对象</p></li><li><p>push 为本次推送的上下文，已经将本次推送过程中需要的信息完成了初步的整理（从 s.Env 中生成出来的）</p></li></ul><p>istio.io/istio/pilot/pkg/networking/core/v1alpha3/cluster.go</p><p>BuildClusters 函数的实现如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// BuildClusters returns the list of clusters for the given proxy. This is the CDS output</span></span><br><span class="line"><span class="comment">// For outbound: Cluster for each service/subset hostname or cidr with SNI set to service hostname</span></span><br><span class="line"><span class="comment">// Cluster type based on resolution</span></span><br><span class="line"><span class="comment">// For inbound (sidecar only): Cluster for each inbound endpoint port and for each service port</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">BuildClusters</span><span class="params">(env *model.Environment, proxy *model.Proxy, push *model.PushContext)</span> <span class="params">([]*apiv2.Cluster, error)</span></span> &#123;</span><br><span class="line">clusters := <span class="built_in">make</span>([]*apiv2.Cluster, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> proxy.Type &#123;</span><br><span class="line"><span class="keyword">case</span> model.SidecarProxy:</span><br><span class="line">        <span class="comment">// GetProxyServiceInstances returns service instances co-located with the proxy</span></span><br><span class="line">        <span class="comment">// 获取与 proxy 所在主机上的 service instance，包括 headless 服务</span></span><br><span class="line">instances, err := env.GetProxyServiceInstances(proxy)</span><br><span class="line"></span><br><span class="line">sidecarScope := proxy.SidecarScope</span><br><span class="line">recomputeOutboundClusters := <span class="literal">true</span></span><br><span class="line">        <span class="comment">// 追加 OutboundClusters</span></span><br><span class="line"><span class="keyword">if</span> recomputeOutboundClusters &#123;</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, configgen.buildOutboundClusters(env, proxy, push)...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Let ServiceDiscovery decide which IP and Port are used for management if</span></span><br><span class="line"><span class="comment">// there are multiple IPs</span></span><br><span class="line">managementPorts := <span class="built_in">make</span>([]*model.Port, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">for</span> _, ip := <span class="keyword">range</span> proxy.IPAddresses &#123;</span><br><span class="line">managementPorts = <span class="built_in">append</span>(managementPorts, env.ManagementPorts(ip)...)</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 追加与 proxy ip 上 managementPorts 相关的 InboundClusters</span></span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, configgen.buildInboundClusters(env, proxy, push, instances, managementPorts)...)</span><br><span class="line"></span><br><span class="line"><span class="keyword">default</span>: <span class="comment">// Gateways</span></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add a blackhole and passthrough cluster for catching traffic to unresolved routes</span></span><br><span class="line"><span class="comment">// DO NOT CALL PLUGINS for these two clusters.</span></span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, buildBlackHoleCluster())</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, buildDefaultPassthroughCluster())</span><br><span class="line"></span><br><span class="line">    <span class="comment">// resolves cluster name conflicts. </span></span><br><span class="line"><span class="keyword">return</span> normalizeClusters(push, proxy, clusters), <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>临时的调试方法，挂载到了 9093 端口，但是是否保存 ads 相关的信息，还会受到 pilot 相关选项的限制，参见</p><p>istio.io/istio/pkg/features/pilot/pilot.go</p><blockquote><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&gt; // DebugConfigs controls saving snapshots of configs for /debug/adsz.</span><br><span class="line">&gt; // Defaults to false, can be enabled with PILOT_DEBUG_ADSZ_CONFIG=1</span><br><span class="line">&gt; // For larger clusters it can increase memory use and GC - useful for small tests.</span><br><span class="line">&gt; DebugConfigs = os.Getenv(&quot;PILOT_DEBUG_ADSZ_CONFIG&quot;) == &quot;1&quot;</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></blockquote><p>istio.io/istio/pilot/pkg/proxy/envoy/v2/debug.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// InitDebug initializes the debug handlers and adds a debug in-memory registry.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">InitDebug</span><span class="params">(mux *http.ServeMux, sctl *aggregate.Controller)</span></span> &#123;</span><br><span class="line"><span class="comment">// For debugging and load testing v2 we add an memory registry.</span></span><br><span class="line">s.MemRegistry = NewMemServiceDiscovery(</span><br><span class="line"><span class="keyword">map</span>[model.Hostname]*model.Service&#123; <span class="comment">// mock.HelloService.Hostname: mock.HelloService,</span></span><br><span class="line">&#125;, <span class="number">2</span>)</span><br><span class="line">s.MemRegistry.EDSUpdater = s</span><br><span class="line">s.MemRegistry.ClusterID = <span class="string">"v2-debug"</span></span><br><span class="line"></span><br><span class="line">sctl.AddRegistry(aggregate.Registry&#123;</span><br><span class="line">ClusterID:        <span class="string">"v2-debug"</span>,</span><br><span class="line">Name:             serviceregistry.ServiceRegistry(<span class="string">"memAdapter"</span>),</span><br><span class="line">ServiceDiscovery: s.MemRegistry,</span><br><span class="line">ServiceAccounts:  s.MemRegistry,</span><br><span class="line">Controller:       s.MemRegistry.controller,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">mux.HandleFunc(<span class="string">"/ready"</span>, s.ready)</span><br><span class="line"></span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/edsz"</span>, s.edsz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/adsz"</span>, s.adsz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/cdsz"</span>, cdsz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/syncz"</span>, Syncz)</span><br><span class="line"></span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/registryz"</span>, s.registryz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/endpointz"</span>, s.endpointz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/endpointShardz"</span>, s.endpointShardz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/workloadz"</span>, s.workloadz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/configz"</span>, s.configz)</span><br><span class="line"></span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/authenticationz"</span>, s.authenticationz)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/config_dump"</span>, s.ConfigDump)</span><br><span class="line">mux.HandleFunc(<span class="string">"/debug/push_status"</span>, s.PushStatusHandler)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="PushContext-初始化"><a href="#PushContext-初始化" class="headerlink" title="PushContext 初始化"></a>PushContext 初始化</h3><p>  istio.io/istio/pilot/pkg/model/push_context.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(ps *PushContext)</span> <span class="title">InitContext</span><span class="params">(env *Environment)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="comment">// 只会初始化一次，如果已经初始化了则直接返回</span></span><br><span class="line"><span class="keyword">if</span> ps.initDone &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    ps.initServiceRegistry(env)</span><br><span class="line">    ps.initVirtualServices(env)</span><br><span class="line">    ps.initDestinationRules(env)</span><br><span class="line">    ps.initAuthorizationPolicies(env)</span><br><span class="line">    ps.InitSidecarScopes(env)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 env 保存了能够使用的全部信息，包括 ServiceDiscovery 接口。</p><p>istio.io/istio/pilot/pkg/model/context.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Environment provides an aggregate environmental API for Pilot</span></span><br><span class="line"><span class="keyword">type</span> Environment <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// Discovery interface for listing services and instances.</span></span><br><span class="line">ServiceDiscovery</span><br><span class="line">    </span><br><span class="line">ServiceAccounts</span><br><span class="line">IstioConfigStore</span><br><span class="line"></span><br><span class="line">Mesh *meshconfig.MeshConfig</span><br><span class="line">MixerSAN []<span class="keyword">string</span></span><br><span class="line">PushContext *PushContext</span><br><span class="line">MeshNetworks *meshconfig.MeshNetworks</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><strong>initServiceRegistry</strong></p><ul><li>从 env.Services() -&gt; ServiceDiscovery::Services</li><li>涉及操作的变量包括： <ul><li><code>privateServicesByNamespace</code> ns 为 key </li><li><code>publicServices</code> 列表</li><li><code>ServiceByHostname</code>  s.Hostname -&gt; Service</li><li><code>ServicePort2Name</code> s.Hostname -&gt; Ports</li></ul></li></ul></li><li><p><strong>initVirtualServices</strong></p><ul><li><p>从 env.List(VirtualService.Type, NamespaceAll) -&gt; <strong>IstioConfigStore::ConfigStore::List</strong></p></li><li><p>将  virtual services 的 host shortnames  转换成 FQDNs</p></li><li><p>涉及操作的变量包括：</p><ul><li><code>privateVirtualServicesByNamespace</code> ns 为 key</li><li><code>publicVirtualServices</code> 列表</li></ul></li><li><p>相关定义</p><p>VirtualService -&gt; (Hosts, []*HTTPRoute) -&gt; (HTTPMatchRequest, HTTPRouteDestination)</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">networking.istio.io/v1alpha3</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">VirtualService</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"><span class="attr">  name:</span> <span class="string">reviews-route</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"><span class="attr">  hosts:</span></span><br><span class="line"><span class="bullet">  -</span> <span class="string">reviews.prod.svc.cluster.local</span></span><br><span class="line"><span class="attr">  http:</span> <span class="comment"># HTTPRoute</span></span><br><span class="line"><span class="attr">  - match:</span></span><br><span class="line"><span class="attr">    - uri:</span></span><br><span class="line"><span class="attr">        prefix:</span> <span class="string">"/wpcatalog"</span></span><br><span class="line"><span class="attr">    - uri:</span></span><br><span class="line"><span class="attr">        prefix:</span> <span class="string">"/consumercatalog"</span></span><br><span class="line"><span class="attr">    rewrite:</span></span><br><span class="line"><span class="attr">      uri:</span> <span class="string">"/newcatalog"</span></span><br><span class="line"><span class="attr">    route:</span></span><br><span class="line"><span class="attr">    - destination:</span>  <span class="comment"># HTTPRouteDestination</span></span><br><span class="line"><span class="attr">        host:</span> <span class="string">reviews.prod.svc.cluster.local</span></span><br><span class="line"><span class="attr">        subset:</span> <span class="string">v2</span></span><br><span class="line"><span class="attr">  - route:</span></span><br><span class="line"><span class="attr">    - destination:</span></span><br><span class="line"><span class="attr">        host:</span> <span class="string">reviews.prod.svc.cluster.local</span></span><br><span class="line"><span class="attr">        subset:</span> <span class="string">v1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">networking.istio.io/v1alpha3</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">DestinationRule</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"><span class="attr">  name:</span> <span class="string">reviews-destination</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"><span class="attr">  host:</span> <span class="string">reviews.prod.svc.cluster.local</span></span><br><span class="line"><span class="attr">  subsets:</span></span><br><span class="line"><span class="attr">  - name:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">    labels:</span></span><br><span class="line"><span class="attr">      version:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">  - name:</span> <span class="string">v2</span></span><br><span class="line"><span class="attr">    labels:</span></span><br><span class="line"><span class="attr">      version:</span> <span class="string">v2</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>initDestinationRules</strong></p><ul><li>env.List(DestinationRule.Type, NamespaceAll) -&gt; <strong>IstioConfigStore::ConfigStore::List</strong></li><li>操作变量涉及<ul><li><code>namespaceLocalDestRules</code>  ns -&gt;  processedDestRules,  ConfigScope == PRIVATE</li><li><code>namespaceExportedDestRules</code>  ns -&gt;  processedDestRules,  ConfigScope == PUBLIC</li><li><code>allExportedDestRules</code> 列表</li></ul></li></ul></li><li><p><strong>initAuthorizationPolicies</strong></p><ul><li>env.IstioConfigStore.ClusterRbacConfig()</li><li>操作变量<ul><li><code>AuthzPolicies</code></li></ul></li></ul></li><li><p><strong>InitSidecarScopes</strong></p><ul><li>env.List(Sidecar.Type, NamespaceAll)  -&gt; env.List(Sidecar.Type, NamespaceAll)</li><li>操作的变量<ul><li><code>sidecarsByNamespace</code>   ns -&gt; SidecarScope</li></ul></li></ul></li></ul><h3 id="CDS-初始化流程详解"><a href="#CDS-初始化流程详解" class="headerlink" title="CDS 初始化流程详解"></a>CDS 初始化流程详解</h3><p><code>DiscoveryServer::pushCds</code> -&gt; <code>DiscoveryServer::generateRawClusters</code> -&gt; <code>ConfigGenerator.BuildClusters</code>，最终的函数主体在函数 <code>BuildClusters</code> 中实现</p><h4 id="BuildClusters"><a href="#BuildClusters" class="headerlink" title="BuildClusters"></a>BuildClusters</h4><p>istio.io/istio/pilot/pkg/networking/core/v1alpha3/cluster.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// BuildClusters returns the list of clusters for the given proxy. This is the CDS output</span></span><br><span class="line"><span class="comment">// For outbound: Cluster for each service/subset hostname or cidr with SNI set to service hostname</span></span><br><span class="line"><span class="comment">// Cluster type based on resolution: For inbound (sidecar only): </span></span><br><span class="line"><span class="comment">// Cluster for each inbound endpoint port and for each service port</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">BuildClusters</span><span class="params">(env *model.Environment, proxy *model.Proxy, push *model.PushContext)</span> <span class="params">([]*apiv2.Cluster, error)</span></span> &#123;</span><br><span class="line">    clusters := <span class="built_in">make</span>([]*apiv2.Cluster, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> proxy.Type &#123;</span><br><span class="line"><span class="keyword">case</span> model.SidecarProxy:</span><br><span class="line">        <span class="comment">// 1. 获取 Proxy 所在 Pod 上的监听的服务名，在 k8s 中是通过 proxyIP 进行相关的 Pod 查找</span></span><br><span class="line">        <span class="comment">// 主要用于生成 Inbound 相关的集群</span></span><br><span class="line">        <span class="comment">// GetProxyServiceInstances returns the service instances that </span></span><br><span class="line">        <span class="comment">// co-located(in the same network namespace and security context) </span></span><br><span class="line">        <span class="comment">// with a given Proxy</span></span><br><span class="line">instances, err := env.GetProxyServiceInstances(proxy)</span><br><span class="line"></span><br><span class="line">sidecarScope := proxy.SidecarScope</span><br><span class="line">recomputeOutboundClusters := <span class="literal">true</span></span><br><span class="line"><span class="keyword">if</span> configgen.CanUsePrecomputedCDS(proxy) &#123;</span><br><span class="line"><span class="comment">// 如果已经缓存过了，则直接使用</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 2. 如果没有缓存，则直接计算 configgen.buildOutboundClusters(env, proxy, push)</span></span><br><span class="line"><span class="keyword">if</span> recomputeOutboundClusters &#123;</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, configgen.buildOutboundClusters(env, proxy, push)...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Let ServiceDiscovery decide which IP and Port are used for management if</span></span><br><span class="line"><span class="comment">// there are multiple IPs</span></span><br><span class="line">    <span class="comment">// managementPorts 主要是从 Pod 中获取 Liveness &amp;&amp; Readiness probes 的端口</span></span><br><span class="line">managementPorts := <span class="built_in">make</span>([]*model.Port, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">for</span> _, ip := <span class="keyword">range</span> proxy.IPAddresses &#123;</span><br><span class="line">managementPorts = <span class="built_in">append</span>(managementPorts, env.ManagementPorts(ip)...)</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// </span></span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, configgen.buildInboundClusters(env, proxy, push, instances, managementPorts)...) </span><br><span class="line">        </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">        </span><br><span class="line">    <span class="comment">// Add a blackhole and passthrough cluster for catching traffic to unresolved routes</span></span><br><span class="line"><span class="comment">// DO NOT CALL PLUGINS for these two clusters.</span></span><br><span class="line">    <span class="comment">// 添加 blackhole 和 passthrough cluster</span></span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, buildBlackHoleCluster())</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, buildDefaultPassthroughCluster())</span><br><span class="line">   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以多版本的 HelloWorld 为例，最终生成的样例如下，完整样例参见 <a href="https://gist.github.com/DavadDi/3aea82343790e795cce066aab79e9c2c#file-istio_helloworld_v1_v2-json" target="_blank" rel="noopener"><strong>istio_helloworld_v1_v2.json</strong></a></p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line">  "clusters": &#123;   </span><br><span class="line">"dynamic_active_clusters": [</span><br><span class="line">     &#123;</span><br><span class="line">     <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T09:28:16Z/7"</span>,</span><br><span class="line">     <span class="attr">"cluster"</span>: &#123;</span><br><span class="line">      <span class="attr">"name"</span>: <span class="string">"BlackHoleCluster"</span>,</span><br><span class="line">      <span class="attr">"connect_timeout"</span>: <span class="string">"1s"</span></span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T09:28:37.971Z"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">     <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T09:28:16Z/7"</span>,</span><br><span class="line">     <span class="attr">"cluster"</span>: &#123;</span><br><span class="line">      <span class="attr">"name"</span>: <span class="string">"BlackHoleCluster"</span>,</span><br><span class="line">      <span class="attr">"connect_timeout"</span>: <span class="string">"1s"</span></span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T09:28:37.971Z"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">     <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T09:28:16Z/7"</span>,</span><br><span class="line">     <span class="attr">"cluster"</span>: &#123;</span><br><span class="line">               // fmt.Sprintf("%s|%d|%s|%s", direction, port, subsetName, hostname)</span><br><span class="line">      "name": "inbound|5000||helloworld.default.svc.cluster.local",</span><br><span class="line">      "connect_timeout": "1s",</span><br><span class="line">      "hosts": [</span><br><span class="line">       &#123;</span><br><span class="line">        <span class="attr">"socket_address"</span>: &#123;</span><br><span class="line">         <span class="attr">"address"</span>: <span class="string">"127.0.0.1"</span>,</span><br><span class="line">         <span class="attr">"port_value"</span>: <span class="number">5000</span></span><br><span class="line">        &#125;</span><br><span class="line">       &#125;</span><br><span class="line">      ],</span><br><span class="line">      "circuit_breakers": &#123;</span><br><span class="line">       "thresholds": [</span><br><span class="line">        &#123;&#125;</span><br><span class="line">       ]</span><br><span class="line">      &#125;</span><br><span class="line">     &#125;,</span><br><span class="line">     "last_updated": "2019-02-13T09:28:37.971Z"</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    &#123;</span><br><span class="line">     <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T12:41:46Z/10"</span>,</span><br><span class="line">     <span class="attr">"cluster"</span>: &#123;</span><br><span class="line">      <span class="attr">"name"</span>: <span class="string">"outbound|5000|v1|helloworld.default.svc.cluster.local"</span>,</span><br><span class="line">      <span class="attr">"type"</span>: <span class="string">"EDS"</span>,</span><br><span class="line">      <span class="attr">"eds_cluster_config"</span>: &#123;</span><br><span class="line">       <span class="attr">"eds_config"</span>: &#123;</span><br><span class="line">        <span class="attr">"ads"</span>: &#123;&#125; // ESS 类型的，需要通过 EDS 来进行获取 ！！！</span><br><span class="line">       &#125;,</span><br><span class="line">       <span class="attr">"service_name"</span>: <span class="string">"outbound|5000|v1|helloworld.default.svc.cluster.local"</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">"connect_timeout"</span>: <span class="string">"1s"</span>,</span><br><span class="line">      <span class="attr">"circuit_breakers"</span>: &#123;</span><br><span class="line">       <span class="attr">"thresholds"</span>: [</span><br><span class="line">        &#123;&#125;</span><br><span class="line">       ]</span><br><span class="line">      &#125;</span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T12:41:46.640Z"</span></span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    &#123;</span><br><span class="line">     <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T12:41:46Z/10"</span>,</span><br><span class="line">     <span class="attr">"cluster"</span>: &#123;</span><br><span class="line">      <span class="attr">"name"</span>: <span class="string">"outbound|5000|v2|helloworld.default.svc.cluster.local"</span>,</span><br><span class="line">      <span class="attr">"type"</span>: <span class="string">"EDS"</span>,</span><br><span class="line">      <span class="attr">"eds_cluster_config"</span>: &#123;</span><br><span class="line">       <span class="attr">"eds_config"</span>: &#123;</span><br><span class="line">        <span class="attr">"ads"</span>: &#123;&#125;</span><br><span class="line">       &#125;,</span><br><span class="line">       <span class="attr">"service_name"</span>: <span class="string">"outbound|5000|v2|helloworld.default.svc.cluster.local"</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">"connect_timeout"</span>: <span class="string">"1s"</span>,</span><br><span class="line">      <span class="attr">"circuit_breakers"</span>: &#123;</span><br><span class="line">       <span class="attr">"thresholds"</span>: [</span><br><span class="line">        &#123;&#125;</span><br><span class="line">       ]</span><br><span class="line">      &#125;</span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T12:41:46.641Z"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">     <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T09:28:16Z/7"</span>,</span><br><span class="line">     <span class="attr">"cluster"</span>: &#123;</span><br><span class="line">      <span class="attr">"name"</span>: <span class="string">"outbound|5000||helloworld.default.svc.cluster.local"</span>,</span><br><span class="line">      <span class="attr">"type"</span>: <span class="string">"EDS"</span>,</span><br><span class="line">      <span class="attr">"eds_cluster_config"</span>: &#123;</span><br><span class="line">       <span class="attr">"eds_config"</span>: &#123;</span><br><span class="line">        <span class="attr">"ads"</span>: &#123;&#125;</span><br><span class="line">       &#125;,</span><br><span class="line">       <span class="attr">"service_name"</span>: <span class="string">"outbound|5000||helloworld.default.svc.cluster.local"</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">"connect_timeout"</span>: <span class="string">"1s"</span>,</span><br><span class="line">      <span class="attr">"circuit_breakers"</span>: &#123;</span><br><span class="line">       <span class="attr">"thresholds"</span>: [</span><br><span class="line">        &#123;&#125;</span><br><span class="line">       ]</span><br><span class="line">      &#125;</span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T09:28:37.928Z"</span></span><br><span class="line">    &#125;,</span><br><span class="line">   ]</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="GetProxyServiceInstances"><a href="#GetProxyServiceInstances" class="headerlink" title="GetProxyServiceInstances"></a><strong>GetProxyServiceInstances</strong></h4><p>GetProxyServiceInstances 函数在 kube 中的实现通过  ProxyIP 所在的 Pod 查找对应的服务，代码如下：</p><p>istio.io/istio/pilot/pkg/serviceregistry/kube/controller.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GetProxyServiceInstances returns service instances co-located with a given proxy</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *Controller)</span> <span class="title">GetProxyServiceInstances</span><span class="params">(proxy *model.Proxy)</span> <span class="params">([]*model.ServiceInstance, error)</span></span> &#123;</span><br><span class="line">out := <span class="built_in">make</span>([]*model.ServiceInstance, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// There is only one IP for kube registry</span></span><br><span class="line">proxyIP := proxy.IPAddresses[<span class="number">0</span>]</span><br><span class="line">proxyNamespace := <span class="string">""</span></span><br><span class="line"></span><br><span class="line">pod := c.pods.getPodByIP(proxyIP)</span><br><span class="line"><span class="keyword">if</span> pod != <span class="literal">nil</span> &#123;</span><br><span class="line">proxyNamespace = pod.Namespace</span><br><span class="line"><span class="comment">// 1. find proxy service by label selector, </span></span><br><span class="line">        <span class="comment">// if not any, there may exist headless service</span></span><br><span class="line"><span class="comment">// failover to 2</span></span><br><span class="line">svcLister := listerv1.NewServiceLister(c.services.informer.GetIndexer())</span><br><span class="line"><span class="keyword">if</span> services, err := svcLister.GetPodServices(pod); err != <span class="literal">nil</span> &amp;&amp; <span class="built_in">len</span>(services) &gt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">for</span> _, svc := <span class="keyword">range</span> services &#123;</span><br><span class="line">item, exists, err := c.endpoints.informer.GetStore().GetByKey(KeyFunc(svc.Namespace, svc.Name))</span><br><span class="line"></span><br><span class="line">ep := *item.(*v1.Endpoints)</span><br><span class="line">out = <span class="built_in">append</span>(out, c.getProxyServiceInstancesByEndpoint(ep, proxy)...)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> out, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. Headless service</span></span><br><span class="line">endpointsForPodInSameNS := <span class="built_in">make</span>([]*model.ServiceInstance, <span class="number">0</span>)</span><br><span class="line">endpointsForPodInDifferentNS := <span class="built_in">make</span>([]*model.ServiceInstance, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">for</span> _, item := <span class="keyword">range</span> c.endpoints.informer.GetStore().List() &#123;</span><br><span class="line">ep := *item.(*v1.Endpoints)</span><br><span class="line">endpoints := &amp;endpointsForPodInSameNS</span><br><span class="line"><span class="keyword">if</span> ep.Namespace != proxyNamespace &#123;</span><br><span class="line">endpoints = &amp;endpointsForPodInDifferentNS</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">*endpoints = <span class="built_in">append</span>(*endpoints, c.getProxyServiceInstancesByEndpoint(ep, proxy)...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Put the endpointsForPodInSameNS in front of endpointsForPodInDifferentNS so that Pilot will</span></span><br><span class="line"><span class="comment">// first use endpoints from endpointsForPodInSameNS. This makes sure if there are two endpoints</span></span><br><span class="line"><span class="comment">// referring to the same IP/port, the one in endpointsForPodInSameNS will be used. (The other one</span></span><br><span class="line"><span class="comment">// in endpointsForPodInDifferentNS will thus be rejected by Pilot).</span></span><br><span class="line">out = <span class="built_in">append</span>(endpointsForPodInSameNS, endpointsForPodInDifferentNS...)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(out) == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">if</span> c.Env != <span class="literal">nil</span> &#123;</span><br><span class="line">c.Env.PushContext.Add(model.ProxyStatusNoService, proxy.ID, proxy, <span class="string">""</span>)</span><br><span class="line">status := c.Env.PushContext</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> out, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="buildOutboundClusters"><a href="#buildOutboundClusters" class="headerlink" title="buildOutboundClusters"></a><strong>buildOutboundClusters</strong></h4><p>查找出 proxy 可见的 Service 和 公开的 Service，设置成对应的 Cluster。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">buildOutboundClusters</span><span class="params">(env *model.Environment, proxy *model.Proxy, push *model.PushContext)</span> []*<span class="title">apiv2</span>.<span class="title">Cluster</span></span> &#123;</span><br><span class="line">clusters := <span class="built_in">make</span>([]*apiv2.Cluster, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">inputParams := &amp;plugin.InputParams&#123;</span><br><span class="line">Env:  env,</span><br><span class="line">Push: push,</span><br><span class="line">Node: proxy,</span><br><span class="line">&#125;</span><br><span class="line">networkView := model.GetNetworkView(proxy)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// push.Services(proxy) 会返回对于Proxy 可见的 PrivateService 和 PublicServices</span></span><br><span class="line"><span class="keyword">for</span> _, service := <span class="keyword">range</span> push.Services(proxy) &#123;</span><br><span class="line">config := push.DestinationRule(proxy, service)</span><br><span class="line"><span class="keyword">for</span> _, port := <span class="keyword">range</span> service.Ports &#123;</span><br><span class="line"><span class="keyword">if</span> port.Protocol == model.ProtocolUDP &#123;</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">inputParams.Service = service</span><br><span class="line">inputParams.Port = port</span><br><span class="line"></span><br><span class="line">lbEndpoints := buildLocalityLbEndpoints(env, networkView, service, port.Port, <span class="literal">nil</span>)</span><br><span class="line"><span class="comment">// create default cluster</span></span><br><span class="line">            <span class="comment">// outbound|5000||helloworld.default.svc.cluster.local</span></span><br><span class="line">discoveryType := convertResolution(service.Resolution)</span><br><span class="line">clusterName := model.BuildSubsetKey(model.TrafficDirectionOutbound, <span class="string">""</span>, service.Hostname, port.Port)</span><br><span class="line">serviceAccounts := env.ServiceAccounts.GetIstioServiceAccounts(service.Hostname, []<span class="keyword">int</span>&#123;port.Port&#125;)</span><br><span class="line">defaultCluster := buildDefaultCluster(env, clusterName, discoveryType, lbEndpoints, model.TrafficDirectionOutbound)</span><br><span class="line"></span><br><span class="line">updateEds(defaultCluster)</span><br><span class="line">setUpstreamProtocol(defaultCluster, port)</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, defaultCluster)</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 如果 service 存在对应的 destinationRule 存在，则需要将 destinationRule </span></span><br><span class="line">            <span class="comment">// 中定义的 subset 也添加到 outbound 类型的 cluster 集合中</span></span><br><span class="line">            <span class="comment">// outbound|5000|v1|helloworld.default.svc.cluster.local</span></span><br><span class="line">            <span class="comment">// outbound|5000|v2|helloworld.default.svc.cluster.local</span></span><br><span class="line">            <span class="comment">// v1 和 v2 为 subset name</span></span><br><span class="line"><span class="keyword">if</span> config != <span class="literal">nil</span> &#123;</span><br><span class="line">destinationRule := config.Spec.(*networking.DestinationRule)</span><br><span class="line">defaultSni := model.BuildDNSSrvSubsetKey(model.TrafficDirectionOutbound, <span class="string">""</span>, service.Hostname, port.Port)</span><br><span class="line">applyTrafficPolicy(env, defaultCluster, destinationRule.TrafficPolicy, port, serviceAccounts,</span><br><span class="line">defaultSni, DefaultClusterMode, model.TrafficDirectionOutbound)</span><br><span class="line">setLocalityPriority := <span class="literal">false</span></span><br><span class="line"><span class="keyword">if</span> defaultCluster.OutlierDetection != <span class="literal">nil</span> &#123;</span><br><span class="line">setLocalityPriority = <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line">applyLocalityLBSetting(proxy, defaultCluster.LoadAssignment, env.Mesh.LocalityLbSetting, setLocalityPriority)</span><br><span class="line"><span class="keyword">for</span> _, subset := <span class="keyword">range</span> destinationRule.Subsets &#123;</span><br><span class="line">inputParams.Subset = subset.Name</span><br><span class="line">subsetClusterName := model.BuildSubsetKey(model.TrafficDirectionOutbound, subset.Name, service.Hostname, port.Port)</span><br><span class="line">defaultSni := model.BuildDNSSrvSubsetKey(model.TrafficDirectionOutbound, subset.Name, service.Hostname, port.Port)</span><br><span class="line"></span><br><span class="line"><span class="comment">// clusters with discovery type STATIC, STRICT_DNS or LOGICAL_DNS rely on cluster.hosts field</span></span><br><span class="line"><span class="comment">// ServiceEntry's need to filter hosts based on subset.labels in order to perform weighted routing</span></span><br><span class="line"><span class="keyword">if</span> discoveryType != apiv2.Cluster_EDS &amp;&amp; <span class="built_in">len</span>(subset.Labels) != <span class="number">0</span> &#123;</span><br><span class="line">lbEndpoints = buildLocalityLbEndpoints(env, networkView, service, port.Port, []model.Labels&#123;subset.Labels&#125;)</span><br><span class="line">&#125;</span><br><span class="line">subsetCluster := buildDefaultCluster(env, subsetClusterName, discoveryType, lbEndpoints, model.TrafficDirectionOutbound)</span><br><span class="line">updateEds(subsetCluster)</span><br><span class="line">setUpstreamProtocol(subsetCluster, port)</span><br><span class="line">applyTrafficPolicy(env, subsetCluster, destinationRule.TrafficPolicy, port, serviceAccounts, defaultSni,</span><br><span class="line">DefaultClusterMode, model.TrafficDirectionOutbound)</span><br><span class="line">applyTrafficPolicy(env, subsetCluster, subset.TrafficPolicy, port, serviceAccounts, defaultSni,</span><br><span class="line">DefaultClusterMode, model.TrafficDirectionOutbound)</span><br><span class="line">setLocalityPriority = <span class="literal">false</span></span><br><span class="line"><span class="keyword">if</span> subsetCluster.OutlierDetection != <span class="literal">nil</span> &#123;</span><br><span class="line">setLocalityPriority = <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line">applyLocalityLBSetting(proxy, subsetCluster.LoadAssignment, env.Mesh.LocalityLbSetting, setLocalityPriority)</span><br><span class="line"><span class="comment">// call plugins</span></span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> configgen.Plugins &#123;</span><br><span class="line">p.OnOutboundCluster(inputParams, subsetCluster)</span><br><span class="line">&#125;</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, subsetCluster)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// call plugins for the default cluster</span></span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> configgen.Plugins &#123;</span><br><span class="line">p.OnOutboundCluster(inputParams, defaultCluster)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> clusters</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="buildInboundClusters"><a href="#buildInboundClusters" class="headerlink" title="buildInboundClusters"></a><strong>buildInboundClusters</strong></h4><p>在 <code>buildInboundClusters</code> 中需要添加 k8s 用于管理端口相关的信息，<code>ManagementPorts</code> 函数则是通过 proxyIP 查找到与 ProxyIP 相同的 Pod，然后基于 Pod 的规范获取到 Liveness 和 Readiness probes 中定义的相关端口；</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ManagementPorts implements a service catalog operation</span></span><br><span class="line"><span class="comment">// addr 为 proxy 的 IP 地址</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *Controller)</span> <span class="title">ManagementPorts</span><span class="params">(addr <span class="keyword">string</span>)</span> <span class="title">model</span>.<span class="title">PortList</span></span> &#123;</span><br><span class="line">pod := c.pods.getPodByIP(addr)</span><br><span class="line"><span class="keyword">if</span> pod == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">managementPorts, err := convertProbesToPorts(&amp;pod.Spec)</span><br><span class="line"><span class="keyword">return</span> managementPorts</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// convertProbesToPorts</span></span><br><span class="line"><span class="comment">// convertProbesToPorts returns a PortList consisting of the ports where the</span></span><br><span class="line"><span class="comment">// pod is configured to do Liveness and Readiness probes</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">convertProbesToPorts</span><span class="params">(t *v1.PodSpec)</span> <span class="params">(model.PortList, error)</span></span> &#123;</span><br><span class="line">set := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]*model.Port)</span><br><span class="line"><span class="keyword">for</span> _, container := <span class="keyword">range</span> t.Containers &#123;</span><br><span class="line"><span class="keyword">for</span> _, probe := <span class="keyword">range</span> []*v1.Probe&#123;container.LivenessProbe, container.ReadinessProbe&#125; &#123;</span><br><span class="line"></span><br><span class="line">p, err := convertProbePort(&amp;container, &amp;probe.Handler)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">errs = multierror.Append(errs, err)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> p != <span class="literal">nil</span> &amp;&amp; set[p.Name] == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// Deduplicate along the way. We don't differentiate between HTTP vs TCP mgmt ports</span></span><br><span class="line">set[p.Name] = p</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">mgmtPorts := <span class="built_in">make</span>(model.PortList, <span class="number">0</span>, <span class="built_in">len</span>(set))</span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> set &#123;</span><br><span class="line">mgmtPorts = <span class="built_in">append</span>(mgmtPorts, p)</span><br><span class="line">&#125;</span><br><span class="line">sort.Slice(mgmtPorts, <span class="function"><span class="keyword">func</span><span class="params">(i, j <span class="keyword">int</span>)</span> <span class="title">bool</span></span> &#123; <span class="keyword">return</span> mgmtPorts[i].Port &lt; mgmtPorts[j].Port &#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> mgmtPorts, errs</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>buildInboundClusters 函数的主体实现如下：</p><p>istio.io/istio/pilot/pkg/networking/core/v1alpha3/cluster.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">buildInboundClusters</span><span class="params">(env *model.Environment, proxy *model.Proxy,</span></span></span><br><span class="line"><span class="function"><span class="params">push *model.PushContext, instances []*model.ServiceInstance, managementPorts []*model.Port)</span> []*<span class="title">apiv2</span>.<span class="title">Cluster</span></span> &#123;</span><br><span class="line"></span><br><span class="line">clusters := <span class="built_in">make</span>([]*apiv2.Cluster, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">sidecarScope := proxy.SidecarScope</span><br><span class="line">noneMode := proxy.GetInterceptionMode() == model.InterceptionNone</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 如果没有设置  sidecarScope 和 ingressListener</span></span><br><span class="line"><span class="keyword">if</span> sidecarScope == <span class="literal">nil</span> || !sidecarScope.HasCustomIngressListeners &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">       <span class="comment">// 为传入的 instances 建立 inbound 相关的集群</span></span><br><span class="line"><span class="keyword">for</span> _, instance := <span class="keyword">range</span> instances &#123;</span><br><span class="line">pluginParams := &amp;plugin.InputParams&#123;</span><br><span class="line">Env:             env,</span><br><span class="line">Node:            proxy,</span><br><span class="line">ServiceInstance: instance,</span><br><span class="line">Port:            instance.Endpoint.ServicePort,</span><br><span class="line">Push:            push,</span><br><span class="line">Bind:            LocalhostAddress, <span class="comment">// 设定 Hosts 为 127.0.0.1</span></span><br><span class="line">&#125;</span><br><span class="line">localCluster := configgen.buildInboundClusterForPortOrUDS(pluginParams)</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, localCluster)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 添加管理端口， clusterName 格式为 inbound|port_name|mgmCluster|port</span></span><br><span class="line"><span class="comment">// Add a passthrough cluster for traffic to management ports (health check ports)</span></span><br><span class="line"><span class="keyword">for</span> _, port := <span class="keyword">range</span> managementPorts &#123;</span><br><span class="line">clusterName := model.BuildSubsetKey(model.TrafficDirectionInbound, port.Name,</span><br><span class="line">ManagementClusterHostname, port.Port)</span><br><span class="line">            </span><br><span class="line">localityLbEndpoints := buildInboundLocalityLbEndpoints(LocalhostAddress, port.Port)</span><br><span class="line">mgmtCluster := buildDefaultCluster(env, clusterName, apiv2.Cluster_STATIC, localityLbEndpoints,</span><br><span class="line">model.TrafficDirectionInbound)</span><br><span class="line">setUpstreamProtocol(mgmtCluster, port)</span><br><span class="line">clusters = <span class="built_in">append</span>(clusters, mgmtCluster)</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// ....</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> clusters</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着调用将各种信息拼装成 <code>Plugin</code> 结构，传入 <code>buildInboundClusterForPortOrUDS</code> 来进行最终的拼装，</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Plugin is called during the construction of a xdsapi.Listener which may alter the Listener in any</span></span><br><span class="line"><span class="comment">// way. Examples include AuthenticationPlugin that sets up mTLS authentication on the inbound Listener</span></span><br><span class="line"><span class="comment">// and outbound Cluster, the mixer plugin that sets up policy checks on the inbound listener, etc.</span></span><br><span class="line"><span class="keyword">type</span> Plugin <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// OnOutboundListener is called whenever a new outbound listener is added to the LDS output for a given service.</span></span><br><span class="line"><span class="comment">// Can be used to add additional filters on the outbound path.</span></span><br><span class="line">OnOutboundListener(in *InputParams, mutable *MutableObjects) error</span><br><span class="line"></span><br><span class="line"><span class="comment">// OnInboundListener is called whenever a new listener is added to the LDS output for a given service</span></span><br><span class="line"><span class="comment">// Can be used to add additional filters.</span></span><br><span class="line">OnInboundListener(in *InputParams, mutable *MutableObjects) error</span><br><span class="line"></span><br><span class="line"><span class="comment">// OnOutboundCluster is called whenever a new cluster is added to the CDS output.</span></span><br><span class="line"><span class="comment">// This is called once per push cycle, and not for every sidecar/gateway, except for gateways with non-standard</span></span><br><span class="line"><span class="comment">// operating modes.</span></span><br><span class="line">OnOutboundCluster(in *InputParams, cluster *xdsapi.Cluster)</span><br><span class="line"></span><br><span class="line"><span class="comment">// OnInboundCluster is called whenever a new cluster is added to the CDS output.</span></span><br><span class="line"><span class="comment">// Called for each sidecar</span></span><br><span class="line">OnInboundCluster(in *InputParams, cluster *xdsapi.Cluster)</span><br><span class="line"></span><br><span class="line"><span class="comment">// OnOutboundRouteConfiguration is called whenever a new set of virtual hosts (a set of virtual hosts with routes) is</span></span><br><span class="line"><span class="comment">// added to RDS in the outbound path.</span></span><br><span class="line">OnOutboundRouteConfiguration(in *InputParams, routeConfiguration *xdsapi.RouteConfiguration)</span><br><span class="line"></span><br><span class="line"><span class="comment">// OnInboundRouteConfiguration is called whenever a new set of virtual hosts are added to the inbound path.</span></span><br><span class="line">OnInboundRouteConfiguration(in *InputParams, routeConfiguration *xdsapi.RouteConfiguration)</span><br><span class="line"></span><br><span class="line"><span class="comment">// OnInboundFilterChains is called whenever a plugin needs to setup the filter chains, including relevant filter chain</span></span><br><span class="line"><span class="comment">// configuration, like FilterChainMatch and TLSContext.</span></span><br><span class="line">OnInboundFilterChains(in *InputParams) []FilterChain</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>函数<code>buildInboundClusterForPortOrUDS</code>完成了最后拼装 cluster 对象的接力赛：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">for _, instance := range instances &#123;</span></span><br><span class="line"><span class="comment">pluginParams := &amp;plugin.InputParams&#123;</span></span><br><span class="line"><span class="comment">Env:             env,</span></span><br><span class="line"><span class="comment">Node:            proxy,</span></span><br><span class="line"><span class="comment">ServiceInstance: instance,</span></span><br><span class="line"><span class="comment">Port:            instance.Endpoint.ServicePort,</span></span><br><span class="line"><span class="comment">Push:            push,</span></span><br><span class="line"><span class="comment">Bind:            LocalhostAddress,</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">localCluster := configgen.buildInboundClusterForPortOrUDS(pluginParams)</span></span><br><span class="line"><span class="comment">clusters = append(clusters, localCluster)</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">buildInboundClusterForPortOrUDS</span><span class="params">(pluginParams *plugin.InputParams)</span> *<span class="title">apiv2</span>.<span class="title">Cluster</span></span> &#123;</span><br><span class="line">instance := pluginParams.ServiceInstance</span><br><span class="line">clusterName := model.BuildSubsetKey(model.TrafficDirectionInbound, instance.Endpoint.ServicePort.Name,</span><br><span class="line">instance.Service.Hostname, instance.Endpoint.ServicePort.Port)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 设置本地绑定的地址信息</span></span><br><span class="line">localityLbEndpoints := buildInboundLocalityLbEndpoints(pluginParams.Bind, instance.Endpoint.Port)</span><br><span class="line">    </span><br><span class="line">localCluster := buildDefaultCluster(pluginParams.Env, clusterName, apiv2.Cluster_STATIC, localityLbEndpoints,</span><br><span class="line">model.TrafficDirectionInbound)</span><br><span class="line">setUpstreamProtocol(localCluster, instance.Endpoint.ServicePort)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// call plugins，用于通知到相关的处理函数调用</span></span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> configgen.Plugins &#123;</span><br><span class="line">p.OnInboundCluster(pluginParams, localCluster)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// When users specify circuit breakers, they need to be set on the receiver end</span></span><br><span class="line"><span class="comment">// (server side) as well as client side, so that the server has enough capacity</span></span><br><span class="line"><span class="comment">// (not the defaults) to handle the increased traffic volume</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// DestinationRule returns a destination rule for a service name in a given domain.</span></span><br><span class="line">config := pluginParams.Push.DestinationRule(pluginParams.Node, instance.Service)</span><br><span class="line"><span class="keyword">if</span> config != <span class="literal">nil</span> &#123;</span><br><span class="line">destinationRule := config.Spec.(*networking.DestinationRule)</span><br><span class="line"><span class="keyword">if</span> destinationRule.TrafficPolicy != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// only connection pool settings make sense on the inbound path.</span></span><br><span class="line"><span class="comment">// upstream TLS settings/outlier detection/load balancer don't apply here.</span></span><br><span class="line">applyConnectionPool(pluginParams.Env, localCluster, destinationRule.TrafficPolicy.ConnectionPool,</span><br><span class="line">model.TrafficDirectionInbound)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> localCluster</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>istio.io/istio/pilot/pkg/networking/core/v1alpha3/cluster.go</p><p><code>applyConnectionPool</code> 会根据 service 对应的 <code>destinationRule</code> 信息完成 <code>ConnectionPool</code> 部分的补齐，  函数定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// <span class="doctag">FIXME:</span> there isn't a way to distinguish between unset values and zero values</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">applyConnectionPool</span><span class="params">(env *model.Environment, cluster *apiv2.Cluster, settings *networking.ConnectionPoolSettings, direction model.TrafficDirection)</span></span> &#123;</span><br><span class="line">threshold := GetDefaultCircuitBreakerThresholds(direction)</span><br><span class="line"><span class="keyword">if</span> settings.Http != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">if</span> settings.Http.Http2MaxRequests &gt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// Envoy only applies MaxRequests in HTTP/2 clusters</span></span><br><span class="line">threshold.MaxRequests = &amp;types.UInt32Value&#123;Value: <span class="keyword">uint32</span>(settings.Http.Http2MaxRequests)&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> settings.Http.Http1MaxPendingRequests &gt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// Envoy only applies MaxPendingRequests in HTTP/1.1 clusters</span></span><br><span class="line">threshold.MaxPendingRequests = &amp;types.UInt32Value&#123;Value: <span class="keyword">uint32</span>(settings.Http.Http1MaxPendingRequests)&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> settings.Http.MaxRequestsPerConnection &gt; <span class="number">0</span> &#123;</span><br><span class="line">cluster.MaxRequestsPerConnection = &amp;types.UInt32Value&#123;Value: <span class="keyword">uint32</span>(settings.Http.MaxRequestsPerConnection)&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">FIXME:</span> zero is a valid value if explicitly set, otherwise we want to use the default</span></span><br><span class="line"><span class="keyword">if</span> settings.Http.MaxRetries &gt; <span class="number">0</span> &#123;</span><br><span class="line">threshold.MaxRetries = &amp;types.UInt32Value&#123;Value: <span class="keyword">uint32</span>(settings.Http.MaxRetries)&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> settings.Tcp != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">if</span> settings.Tcp.ConnectTimeout != <span class="literal">nil</span> &#123;</span><br><span class="line">cluster.ConnectTimeout = util.GogoDurationToDuration(settings.Tcp.ConnectTimeout)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> settings.Tcp.MaxConnections &gt; <span class="number">0</span> &#123;</span><br><span class="line">threshold.MaxConnections = &amp;types.UInt32Value&#123;Value: <span class="keyword">uint32</span>(settings.Tcp.MaxConnections)&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">applyTCPKeepalive(env, cluster, settings)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">cluster.CircuitBreakers = &amp;v2Cluster.CircuitBreakers&#123;</span><br><span class="line">Thresholds: []*v2Cluster.CircuitBreakers_Thresholds&#123;threshold&#125;,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="LDS-初始化流程详解"><a href="#LDS-初始化流程详解" class="headerlink" title="LDS 初始化流程详解"></a>LDS 初始化流程详解</h3><p><code>DiscoveryServer::pushLds</code> -&gt; <code>DiscoveryServer::generateRawListeners</code> -&gt; <code>ConfigGenerator.BuildListeners</code> 的流程进行，主要的实现在函数 <code>BuildListeners</code> 中。</p><p>仍然以 HelloWorld  为例，只保留了2个相关的样例：<a href="https://gist.github.com/DavadDi/8be9826009ded90dfe70cdd2a884d7c0" target="_blank" rel="noopener">envoy_listeners.json</a></p><p>相关信息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl get pod -n istio-system -o wide</span></span><br><span class="line">NAME                                READY   STATUS      RESTARTS   AGE    IP            NODE       NOMINATED NODE</span><br><span class="line">helloworld-v1-8f8dd85-d59lh         2/2     Running     0          155m   10.128.5.4    node06     &lt;none&gt;</span><br><span class="line">helloworld-v2-f9cf47df4-w9mfn       2/2     Running     0          155m   10.128.13.2   node04     &lt;none&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># kubectl get svc -n istio-system -o wide</span></span><br><span class="line">NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                                 AGE    SELECTOR</span><br><span class="line">helloworld      ClusterIP   10.0.40.71    &lt;none&gt;        5000/TCP                                155m   app=helloworld</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># kubectl -n istio-system exec -ti  helloworld-v1-8f8dd85-d59lh -c istio-proxy -- netstat -t -anlp</span></span><br><span class="line">Active Internet connections (servers and established)</span><br><span class="line">Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name</span><br><span class="line">tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN      -</span><br><span class="line">tcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN      23/envoy</span><br><span class="line">tcp        0      0 127.0.0.1:15000         0.0.0.0:*               LISTEN      23/envoy</span><br><span class="line">tcp        0      0 0.0.0.0:15001           0.0.0.0:*               LISTEN      23/envoy</span><br></pre></td></tr></table></figure><table><thead><tr><th>端口号</th><th>服务名</th><th>说明</th></tr></thead><tbody><tr><td>5000</td><td>helloworld</td><td>endpoint 监听端口</td></tr><tr><td>15090</td><td>静态监听</td><td>通过 /stats/prometheus，转发到 15000 管理端口</td></tr><tr><td>15000</td><td>管理端口</td><td>监听在本机 127.0.0.1</td></tr><tr><td>15001</td><td>virtual 端口</td><td>BlackHoleCluster，用于接受 iptable 的重定向</td></tr></tbody></table><p>envoy 打印的日志，可以查看到对应的加载的信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[lds_api.cc:80] lds: add/update listener &apos;10.128.69.4_5000&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;10.0.79.108_15011&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_8080&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_9093&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_8060&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_5000&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_15010&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;virtual&apos;</span><br></pre></td></tr></table></figure><p>会从相关的 namespace 中查找出对应暴露出去的对应端口，生成相关记录，并针对本地 endpoint 端口增加一条记录，有去重的功能。</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line">"listeners": &#123;</span><br><span class="line"> "dynamic_active_listeners": [</span><br><span class="line">  // 生成一个 endpoint_ip:5000 端口的 hellworld 的记录</span><br><span class="line">  &#123;</span><br><span class="line">   <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T09:28:16Z/7"</span>,</span><br><span class="line">   <span class="attr">"listener"</span>: &#123;</span><br><span class="line">    <span class="attr">"name"</span>: <span class="string">"10.128.5.4_5000"</span>,</span><br><span class="line">    <span class="attr">"address"</span>: &#123;</span><br><span class="line">     <span class="attr">"socket_address"</span>: &#123;</span><br><span class="line">      <span class="attr">"address"</span>: <span class="string">"10.128.5.4"</span>,</span><br><span class="line">      <span class="attr">"port_value"</span>: <span class="number">5000</span></span><br><span class="line">     &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"filter_chains"</span>: [</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"deprecated_v1"</span>: &#123;</span><br><span class="line">     <span class="attr">"bind_to_port"</span>: <span class="literal">false</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"listener_filters"</span>: [</span><br><span class="line">     &#123;</span><br><span class="line">      <span class="attr">"name"</span>: <span class="string">"envoy.listener.tls_inspector"</span>,</span><br><span class="line">      <span class="attr">"config"</span>: &#123;&#125;</span><br><span class="line">     &#125;</span><br><span class="line">    ]</span><br><span class="line">   &#125;,</span><br><span class="line">   <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T09:28:38.027Z"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  // 生成一个 0.0.0.0:5000 端口的 hellworld 的记录</span><br><span class="line">  &#123;</span><br><span class="line">   <span class="attr">"version_info"</span>: <span class="string">"2019-02-15T05:44:18Z/6"</span>,</span><br><span class="line">   <span class="attr">"listener"</span>: &#123;</span><br><span class="line">    <span class="attr">"name"</span>: <span class="string">"0.0.0.0_5000"</span>,</span><br><span class="line">    <span class="attr">"address"</span>: &#123;</span><br><span class="line">     <span class="attr">"socket_address"</span>: &#123;</span><br><span class="line">      <span class="attr">"address"</span>: <span class="string">"0.0.0.0"</span>,</span><br><span class="line">      <span class="attr">"port_value"</span>: <span class="number">5000</span></span><br><span class="line">     &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"filter_chains"</span>: [</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"deprecated_v1"</span>: &#123;</span><br><span class="line">     <span class="attr">"bind_to_port"</span>: <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">   &#125;,</span><br><span class="line">   <span class="attr">"last_updated"</span>: <span class="string">"2019-02-15T05:44:18.648Z"</span></span><br><span class="line">  &#125;,</span><br><span class="line">     </span><br><span class="line">  // 为可见的其他 service 暴露出来的端口生成对应的记录</span><br><span class="line"></span><br><span class="line">  // BlackHoleCluster</span><br><span class="line">  &#123;</span><br><span class="line">   <span class="attr">"version_info"</span>: <span class="string">"2019-02-13T09:28:16Z/7"</span>,</span><br><span class="line">   <span class="attr">"listener"</span>: &#123;</span><br><span class="line">    <span class="attr">"name"</span>: <span class="string">"virtual"</span>,</span><br><span class="line">    <span class="attr">"address"</span>: &#123;</span><br><span class="line">     <span class="attr">"socket_address"</span>: &#123;</span><br><span class="line">      <span class="attr">"address"</span>: <span class="string">"0.0.0.0"</span>,</span><br><span class="line">      <span class="attr">"port_value"</span>: <span class="number">15001</span></span><br><span class="line">     &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"filter_chains"</span>: [</span><br><span class="line">     &#123;</span><br><span class="line">      <span class="attr">"filters"</span>: [</span><br><span class="line">       &#123;</span><br><span class="line">        <span class="attr">"name"</span>: <span class="string">"envoy.tcp_proxy"</span>,</span><br><span class="line">        <span class="attr">"config"</span>: &#123;</span><br><span class="line">         <span class="attr">"stat_prefix"</span>: <span class="string">"BlackHoleCluster"</span>,</span><br><span class="line">         <span class="attr">"cluster"</span>: <span class="string">"BlackHoleCluster"</span></span><br><span class="line">        &#125;</span><br><span class="line">       &#125;</span><br><span class="line">      ]</span><br><span class="line">     &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"use_original_dst"</span>: <span class="literal">true</span></span><br><span class="line">   &#125;,</span><br><span class="line">   <span class="attr">"last_updated"</span>: <span class="string">"2019-02-13T09:28:38.080Z"</span></span><br><span class="line">  &#125;,</span><br><span class="line"> ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>BuildListeners</code> 函数实现如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// BuildListeners produces a list of listeners and referenced clusters for all proxies</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">BuildListeners</span><span class="params">(env *model.Environment, node *model.Proxy, push *model.PushContext)</span> <span class="params">([]*xdsapi.Listener, error)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> node.Type &#123;</span><br><span class="line"><span class="keyword">case</span> model.SidecarProxy:</span><br><span class="line">        <span class="comment">// 获取本机上监听服务的 endpoint，然后根据 endpoint.ip 和 port 生成对应的记录</span></span><br><span class="line"><span class="keyword">return</span> configgen.buildSidecarListeners(env, node, push)</span><br><span class="line"><span class="keyword">case</span> model.Router, model.Ingress:</span><br><span class="line"><span class="keyword">return</span> configgen.buildGatewayListeners(env, node, push)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>buildSidecarListeners</code> 函数主要流程如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// buildSidecarListeners produces a list of listeners for sidecar proxies</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">buildSidecarListeners</span><span class="params">(env *model.Environment, node *model.Proxy,</span></span></span><br><span class="line"><span class="function"><span class="params">push *model.PushContext)</span> <span class="params">([]*xdsapi.Listener, error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">mesh := env.Mesh</span><br><span class="line"></span><br><span class="line">proxyInstances, err := env.GetProxyServiceInstances(node)</span><br><span class="line"></span><br><span class="line">noneMode := node.GetInterceptionMode() == model.InterceptionNone</span><br><span class="line">listeners := <span class="built_in">make</span>([]*xdsapi.Listener, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> mesh.ProxyListenPort &gt; <span class="number">0</span> &#123;</span><br><span class="line">        <span class="comment">// 建立 inbound 相关的 Listeners</span></span><br><span class="line">inbound := configgen.buildSidecarInboundListeners(env, node, push, proxyInstances)</span><br><span class="line"></span><br><span class="line"><span class="comment">// outbound 相关的 Listeners</span></span><br><span class="line"><span class="comment">// buildSidecarOutboundListeners generates http and tcp listeners for</span></span><br><span class="line"><span class="comment">// outbound connections from the proxy based on the sidecar scope associated with the proxy.</span></span><br><span class="line"><span class="comment">// TODO(github.com/istio/pilot/issues/237)</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Sharing tcp_proxy and http_connection_manager filters on the same port for</span></span><br><span class="line"><span class="comment">// different destination services doesn't work with Envoy (yet). When the</span></span><br><span class="line"><span class="comment">// tcp_proxy filter's route matching fails for the http service the connection</span></span><br><span class="line"><span class="comment">// is closed without falling back to the http_connection_manager.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Temporary workaround is to add a listener for each service IP that requires</span></span><br><span class="line"><span class="comment">// TCP routing</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Connections to the ports of non-load balanced services are directed to</span></span><br><span class="line"><span class="comment">// the connection's original destination. This avoids costly queries of instance</span></span><br><span class="line"><span class="comment">// IPs and ports, but requires that ports of non-load balanced service be unique.</span></span><br><span class="line">        outbound := configgen.buildSidecarOutboundListeners(env, node, push, proxyInstances)</span><br><span class="line"></span><br><span class="line">listeners = <span class="built_in">append</span>(listeners, inbound...)</span><br><span class="line">listeners = <span class="built_in">append</span>(listeners, outbound...)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Do not generate any management port listeners if the user has specified a SidecarScope object</span></span><br><span class="line"><span class="comment">// with ingress listeners. Specifying the ingress listener implies that the user wants</span></span><br><span class="line"><span class="comment">// to only have those specific listeners and nothing else, in the inbound path.</span></span><br><span class="line">generateManagementListeners := <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">sidecarScope := node.SidecarScope</span><br><span class="line"><span class="keyword">if</span> sidecarScope != <span class="literal">nil</span> &amp;&amp; sidecarScope.HasCustomIngressListeners ||</span><br><span class="line">noneMode &#123;</span><br><span class="line">generateManagementListeners = <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> generateManagementListeners &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">tcpProxy := &amp;tcp_proxy.TcpProxy&#123;</span><br><span class="line">StatPrefix:       util.BlackHoleCluster,</span><br><span class="line">ClusterSpecifier: &amp;tcp_proxy.TcpProxy_Cluster&#123;Cluster: util.BlackHoleCluster&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> mesh.OutboundTrafficPolicy.Mode == meshconfig.MeshConfig_OutboundTrafficPolicy_ALLOW_ANY &#123;</span><br><span class="line"><span class="comment">// We need a passthrough filter to fill in the filter stack for orig_dst listener</span></span><br><span class="line">tcpProxy = &amp;tcp_proxy.TcpProxy&#123;</span><br><span class="line">StatPrefix:       util.PassthroughCluster,</span><br><span class="line">ClusterSpecifier: &amp;tcp_proxy.TcpProxy_Cluster&#123;Cluster: util.PassthroughCluster&#125;,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> transparent *google_protobuf.BoolValue</span><br><span class="line"><span class="keyword">if</span> node.GetInterceptionMode() == model.InterceptionTproxy &#123;</span><br><span class="line">transparent = proto.BoolTrue</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// add an extra listener that binds to the port that is the recipient of the iptables redirect</span></span><br><span class="line">listeners = <span class="built_in">append</span>(listeners, &amp;xdsapi.Listener&#123;</span><br><span class="line">Name:           VirtualListenerName,</span><br><span class="line">Address:        util.BuildAddress(WildcardAddress, <span class="keyword">uint32</span>(mesh.ProxyListenPort)),</span><br><span class="line">Transparent:    transparent,</span><br><span class="line">UseOriginalDst: proto.BoolTrue,</span><br><span class="line">FilterChains: []listener.FilterChain&#123;</span><br><span class="line">&#123;</span><br><span class="line">Filters: []listener.Filter&#123;</span><br><span class="line">&#123;</span><br><span class="line">Name: xdsutil.TCPProxy,</span><br><span class="line">ConfigType: &amp;listener.Filter_Config&#123;</span><br><span class="line">Config: util.MessageToStruct(tcpProxy),</span><br><span class="line">&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">httpProxyPort := mesh.ProxyHttpPort</span><br><span class="line"><span class="keyword">if</span> httpProxyPort == <span class="number">0</span> &amp;&amp; noneMode &#123; <span class="comment">// make sure http proxy is enabled for 'none' interception.</span></span><br><span class="line">httpProxyPort = <span class="keyword">int32</span>(pilot.DefaultPortHTTPProxy)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// enable HTTP PROXY port if necessary; this will add an RDS route for this port</span></span><br><span class="line"><span class="keyword">if</span> httpProxyPort &gt; <span class="number">0</span> &#123;</span><br><span class="line">useRemoteAddress := <span class="literal">false</span></span><br><span class="line">traceOperation := http_conn.EGRESS</span><br><span class="line">listenAddress := LocalhostAddress</span><br><span class="line"></span><br><span class="line">opts := buildListenerOpts&#123;</span><br><span class="line">env:            env,</span><br><span class="line">proxy:          node,</span><br><span class="line">proxyInstances: proxyInstances,</span><br><span class="line">bind:           listenAddress,</span><br><span class="line">port:           <span class="keyword">int</span>(httpProxyPort),</span><br><span class="line">filterChainOpts: []*filterChainOpts&#123;&#123;</span><br><span class="line">httpOpts: &amp;httpListenerOpts&#123;</span><br><span class="line">rds:              RDSHttpProxy,</span><br><span class="line">useRemoteAddress: useRemoteAddress,</span><br><span class="line">direction:        traceOperation,</span><br><span class="line">connectionManager: &amp;http_conn.HttpConnectionManager&#123;</span><br><span class="line">HttpProtocolOptions: &amp;core.Http1ProtocolOptions&#123;</span><br><span class="line">AllowAbsoluteUrl: proto.BoolTrue,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;&#125;,</span><br><span class="line">bindToPort:      <span class="literal">true</span>,</span><br><span class="line">skipUserFilters: <span class="literal">true</span>,</span><br><span class="line">&#125;</span><br><span class="line">l := buildListener(opts)</span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> plugins for HTTP_PROXY mode, envoyfilter needs another listener match for SIDECAR_HTTP_PROXY</span></span><br><span class="line"><span class="comment">// there is no mixer for http_proxy</span></span><br><span class="line">mutable := &amp;plugin.MutableObjects&#123;</span><br><span class="line">Listener:     l,</span><br><span class="line">FilterChains: []plugin.FilterChain&#123;&#123;&#125;&#125;,</span><br><span class="line">&#125;</span><br><span class="line">pluginParams := &amp;plugin.InputParams&#123;</span><br><span class="line">ListenerProtocol: plugin.ListenerProtocolHTTP,</span><br><span class="line">ListenerCategory: networking.EnvoyFilter_ListenerMatch_SIDECAR_OUTBOUND,</span><br><span class="line">Env:              env,</span><br><span class="line">Node:             node,</span><br><span class="line">ProxyInstances:   proxyInstances,</span><br><span class="line">Push:             push,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err := buildCompleteFilterChain(pluginParams, mutable, opts); err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Warna(<span class="string">"buildSidecarListeners "</span>, err.Error())</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">listeners = <span class="built_in">append</span>(listeners, l)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> need inbound listeners in HTTP_PROXY case, with dedicated ingress listener.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> listeners, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="EDS-初始化流程详解"><a href="#EDS-初始化流程详解" class="headerlink" title="EDS 初始化流程详解"></a>EDS 初始化流程详解</h3><p><strong>eds_clusters</strong> 跟踪的架构大体如下：</p><p><code>cluster_name</code> -&gt;  <code>proxy.LocalityA</code>  -&gt; <code>eds_clusterA</code>  -&gt; (node1，node2….)<br>                                 <code>proxy.LocalityB</code> -&gt; <code>eds_clusterB</code> -&gt;   (node1，node2….)</p><p><code>EdsCluster</code> 和 <code>ClusterLoadAssignment</code> 核心结构如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// EdsCluster tracks eds-related info for monitored clusters. In practice it'll include</span></span><br><span class="line"><span class="comment">// all clusters until we support on-demand cluster loading.</span></span><br><span class="line"><span class="keyword">type</span> EdsCluster <span class="keyword">struct</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">LoadAssignment *xdsapi.ClusterLoadAssignment</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 记录当前 cluster 被那些 proxy 使用</span></span><br><span class="line">    <span class="comment">// EdsClients keeps track of all nodes monitoring the cluster.</span></span><br><span class="line">EdsClients <span class="keyword">map</span>[<span class="keyword">string</span>]*XdsConnection <span class="string">`json:"-"`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ClusterLoadAssignment <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// Name of the cluster. This will be the :ref:`service_name</span></span><br><span class="line"><span class="comment">// &lt;envoy_api_field_Cluster.EdsClusterConfig.service_name&gt;` value if specified</span></span><br><span class="line"><span class="comment">// in the cluster :ref:`EdsClusterConfig</span></span><br><span class="line"><span class="comment">// &lt;envoy_api_msg_Cluster.EdsClusterConfig&gt;`.</span></span><br><span class="line">ClusterName <span class="keyword">string</span> </span><br><span class="line"><span class="comment">// List of endpoints to load balance to.</span></span><br><span class="line">Endpoints []endpoint.LocalityLbEndpoints </span><br><span class="line"><span class="comment">// Map of named endpoints that can be referenced in LocalityLbEndpoints.</span></span><br><span class="line">NamedEndpoints <span class="keyword">map</span>[<span class="keyword">string</span>]*endpoint.Endpoint </span><br><span class="line"><span class="comment">// Load balancing policy settings.</span></span><br><span class="line">Policy               *ClusterLoadAssignment_Policy </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于 EDS 推送的主体函数如下：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> EndpointType:</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="comment">// 将当前已经存在的 cluster 从当前 eds_cluster 资源前跟踪的 Node 信息删除掉</span></span><br><span class="line"><span class="keyword">for</span> _, cn := <span class="keyword">range</span> con.Clusters &#123;</span><br><span class="line">s.removeEdsCon(cn, con.ConID, con)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将当前请求的 cluster 信息和 node 信息添加到，cluster 跟踪的 Node 列表中</span></span><br><span class="line"><span class="keyword">for</span> _, cn := <span class="keyword">range</span> clusters &#123;</span><br><span class="line">s.addEdsCon(cn, con.ConID, con)</span><br><span class="line">                  <span class="comment">// addEdsCon 函数会处理不存在的情况，并初始化 eds_cluster</span></span><br><span class="line">                  <span class="comment">// -&gt; s.getOrAddEdsCluster(connection.modelNode, clusterName)</span></span><br><span class="line">                  <span class="comment">//c := edsClusters[clusterName]</span></span><br><span class="line"><span class="comment">//  if c == nil &#123;</span></span><br><span class="line"><span class="comment">//c := &amp;EdsCluster&#123;</span></span><br><span class="line"><span class="comment">//   discovery:  s,</span></span><br><span class="line"><span class="comment">//         EdsClients: map[string]*XdsConnection&#123;&#125;,</span></span><br><span class="line">        <span class="comment">//          FirstUse:   time.Now(),</span></span><br><span class="line"><span class="comment">//  &#125;</span></span><br><span class="line"><span class="comment">// edsClusters[clusterName] = map[model.Locality]*EdsCluster&#123;</span></span><br><span class="line"><span class="comment">// proxy.Locality: c,</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line"><span class="comment">//return c</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">con.Clusters = clusters</span><br><span class="line">adsLog.Debugf(<span class="string">"ADS:EDS: REQ %s %s clusters: %d"</span>, peerAddr, con.ConID, <span class="built_in">len</span>(con.Clusters))</span><br><span class="line">err := s.pushEds(s.globalPushContext(), con, <span class="literal">true</span>, <span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 配置发生变化，如果有更新则出发增量更新</span></span><br><span class="line"><span class="keyword">case</span> pushEv := &lt;-con.pushChannel:</span><br><span class="line"><span class="comment">// It is called when config changes.</span></span><br><span class="line"><span class="comment">// This is not optimized yet - we should detect what changed based on event and only</span></span><br><span class="line"><span class="comment">// push resources that need to be pushed.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> possible race condition: if a config change happens while the envoy</span></span><br><span class="line"><span class="comment">// was getting the initial config, between LDS and RDS, the push will miss the</span></span><br><span class="line"><span class="comment">// monitored 'routes'. Same for CDS/EDS interval.</span></span><br><span class="line"><span class="comment">// It is very tricky to handle due to the protocol - but the periodic push recovers</span></span><br><span class="line"><span class="comment">// from it.</span></span><br><span class="line"></span><br><span class="line">err := s.pushConnection(con, pushEv)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>函数 <code>pushEds</code> 负责主体的推送：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// pushEds is pushing EDS updates for a single connection. Called the first time</span></span><br><span class="line"><span class="comment">// a client connects, for incremental updates and for full periodic updates.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">pushEds</span><span class="params">(push *model.PushContext, con *XdsConnection,</span></span></span><br><span class="line"><span class="function"><span class="params">   full <span class="keyword">bool</span>, edsUpdatedServices <span class="keyword">map</span>[<span class="keyword">string</span>]*EndpointShards)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">   loadAssignments := []*xdsapi.ClusterLoadAssignment&#123;&#125;</span><br><span class="line"></span><br><span class="line">   emptyClusters := <span class="number">0</span></span><br><span class="line">   endpoints := <span class="number">0</span></span><br><span class="line"></span><br><span class="line">   <span class="comment">// 根据 conn 连接上发送过来的 clusters name 循环拉取相关的信息</span></span><br><span class="line">   <span class="keyword">for</span> _, clusterName := <span class="keyword">range</span> con.Clusters &#123;</span><br><span class="line">      _, _, hostname, _ := model.ParseSubsetKey(clusterName)</span><br><span class="line">      <span class="keyword">if</span> edsUpdatedServices != <span class="literal">nil</span> &amp;&amp; edsUpdatedServices[<span class="keyword">string</span>(hostname)] == <span class="literal">nil</span> &#123;</span><br><span class="line">         <span class="comment">// Cluster was not updated, skip recomputing.</span></span><br><span class="line">         <span class="keyword">continue</span></span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">     <span class="comment">// 根据 cluster_name + con.modelNode 获取到对应的 EdsCluster 对象</span></span><br><span class="line">      c := s.getEdsCluster(con.modelNode, clusterName)</span><br><span class="line"></span><br><span class="line">      l := c.LoadAssignment</span><br><span class="line">       </span><br><span class="line">      <span class="comment">// 如果存在，则重新更新 Cluster 中 LoadAssignment 相关的信息</span></span><br><span class="line">      <span class="keyword">if</span> l == <span class="literal">nil</span> &#123; <span class="comment">// fresh cluster</span></span><br><span class="line">         edsClusters := <span class="keyword">map</span>[model.Locality]*EdsCluster&#123;</span><br><span class="line">            con.modelNode.Locality: c,</span><br><span class="line">         &#125;</span><br><span class="line">         s.updateCluster(push, clusterName, edsClusters)</span><br><span class="line">         l = loadAssignment(c)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// If networks are set (by default they aren't) apply the Split Horizon</span></span><br><span class="line">      <span class="comment">// EDS filter on the endpoints</span></span><br><span class="line">      <span class="comment">// 根据 networks 进行的 eds 水平切分，则再进行一次过滤</span></span><br><span class="line">      <span class="keyword">if</span> s.Env.MeshNetworks != <span class="literal">nil</span> &amp;&amp; <span class="built_in">len</span>(s.Env.MeshNetworks.Networks) &gt; <span class="number">0</span> &#123;</span><br><span class="line">         endpoints := EndpointsByNetworkFilter(l.Endpoints, con, s.Env)</span><br><span class="line">         endpoints = LoadBalancingWeightNormalize(endpoints)</span><br><span class="line">         filteredCLA := &amp;xdsapi.ClusterLoadAssignment&#123;</span><br><span class="line">            ClusterName: l.ClusterName,</span><br><span class="line">            Endpoints:   endpoints,</span><br><span class="line">            Policy:      l.Policy,</span><br><span class="line">         &#125;</span><br><span class="line">         l = filteredCLA</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      endpoints += <span class="built_in">len</span>(l.Endpoints)</span><br><span class="line">      loadAssignments = <span class="built_in">append</span>(loadAssignments, l)</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   response := endpointDiscoveryResponse(loadAssignments)</span><br><span class="line">   err := con.send(response)</span><br><span class="line">   <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="RDS-初始化流程详解"><a href="#RDS-初始化流程详解" class="headerlink" title="RDS 初始化流程详解"></a>RDS 初始化流程详解</h3><p>结合 LDS 中加载的日志信息</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[lds_api.cc:80] lds: add/update listener &apos;10.128.69.4_5000&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;10.0.79.108_15011&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_8080&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_9093&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_8060&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_5000&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;0.0.0.0_15010&apos;</span><br><span class="line">[lds_api.cc:80] lds: add/update listener &apos;virtual&apos;</span><br></pre></td></tr></table></figure><p>在 RDS 的请求中会包括 LDS 接口中的 IP 地址为 0.0.0.0 表明是外部访问的端口，将端口号作为请求的 route_names 向 ADS Server 发起请求，比如 ”8080,9093,8060,5000,15010“ 共 5 个发起请求，响应结果仅以 helloworld 5000 端口为例：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">   &quot;dynamic_route_configs&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">     &quot;version_info&quot;: &quot;2019-02-15T05:53:49Z/8&quot;,</span><br><span class="line">     &quot;route_config&quot;: &#123;</span><br><span class="line">      &quot;name&quot;: &quot;5000&quot;,</span><br><span class="line">      &quot;virtual_hosts&quot;: [</span><br><span class="line">       &#123;</span><br><span class="line">        &quot;name&quot;: &quot;helloworld.istio-system.svc.cluster.local:5000&quot;,</span><br><span class="line">        &quot;domains&quot;: [</span><br><span class="line">         &quot;helloworld.istio-system.svc.cluster.local&quot;,</span><br><span class="line">         &quot;helloworld.istio-system.svc.cluster.local:5000&quot;,</span><br><span class="line">         &quot;helloworld&quot;,</span><br><span class="line">         &quot;helloworld:5000&quot;,</span><br><span class="line">         &quot;helloworld.istio-system.svc.cluster&quot;,</span><br><span class="line">         &quot;helloworld.istio-system.svc.cluster:5000&quot;,</span><br><span class="line">         &quot;helloworld.istio-system.svc&quot;,</span><br><span class="line">         &quot;helloworld.istio-system.svc:5000&quot;,</span><br><span class="line">         &quot;helloworld.istio-system&quot;,</span><br><span class="line">         &quot;helloworld.istio-system:5000&quot;,</span><br><span class="line">         &quot;10.0.40.71&quot;,</span><br><span class="line">         &quot;10.0.40.71:5000&quot;</span><br><span class="line">        ],</span><br><span class="line">        &quot;routes&quot;: [</span><br><span class="line">         &#123;</span><br><span class="line">          &quot;match&quot;: &#123;</span><br><span class="line">           &quot;prefix&quot;: &quot;/&quot;</span><br><span class="line">          &#125;,</span><br><span class="line">          &quot;route&quot;: &#123;</span><br><span class="line">           &quot;weighted_clusters&quot;: &#123;</span><br><span class="line">            &quot;clusters&quot;: [</span><br><span class="line">             &#123;</span><br><span class="line">              &quot;name&quot;: &quot;outbound|5000|v1|helloworld.istio-system.svc.cluster.local&quot;,</span><br><span class="line">              &quot;weight&quot;: 90,</span><br><span class="line">              &quot;per_filter_config&quot;: &#123;</span><br><span class="line">              &#125;</span><br><span class="line">             &#125;,</span><br><span class="line">             &#123;</span><br><span class="line">              &quot;name&quot;: &quot;outbound|5000|v2|helloworld.istio-system.svc.cluster.local&quot;,</span><br><span class="line">              &quot;weight&quot;: 10,</span><br><span class="line">              &quot;per_filter_config&quot;: &#123;</span><br><span class="line">              &#125;</span><br><span class="line">             &#125;</span><br><span class="line">            ]</span><br><span class="line">           &#125;,</span><br><span class="line">           &quot;timeout&quot;: &quot;0s&quot;,</span><br><span class="line">           &quot;max_grpc_timeout&quot;: &quot;0s&quot;</span><br><span class="line">          &#125;,</span><br><span class="line">          &quot;decorator&quot;: &#123;</span><br><span class="line">           &quot;operation&quot;: &quot;helloworld:5000/*&quot;</span><br><span class="line">          &#125;,</span><br><span class="line">          &quot;per_filter_config&quot;: &#123;</span><br><span class="line">           &quot;mixer&quot;: &#123;</span><br><span class="line">            &quot;disable_check_calls&quot;: true</span><br><span class="line">           &#125;</span><br><span class="line">          &#125;</span><br><span class="line">         &#125;</span><br><span class="line">        ]</span><br><span class="line">       &#125;</span><br><span class="line">      ],</span><br><span class="line">      &quot;validate_clusters&quot;: false</span><br><span class="line">     &#125;,</span><br><span class="line">     &quot;last_updated&quot;: &quot;2019-02-15T05:53:49.910Z&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>处理入口代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> RouteType:</span><br><span class="line">routes := discReq.GetResourceNames()</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> sortedRoutes == <span class="literal">nil</span> &#123;</span><br><span class="line">sort.Strings(routes)</span><br><span class="line">sortedRoutes = routes</span><br><span class="line">&#125;</span><br><span class="line">con.Routes = sortedRoutes</span><br><span class="line">adsLog.Debugf(<span class="string">"ADS:RDS: REQ %s %s  routes: %d"</span>, peerAddr, con.ConID, <span class="built_in">len</span>(con.Routes))</span><br><span class="line">err := s.pushRoute(con, s.globalPushContext())</span><br></pre></td></tr></table></figure><p><code>pushRoute</code> 函数实现如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">pushRoute</span><span class="params">(con *XdsConnection, push *model.PushContext)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">rawRoutes, err := s.generateRawRoutes(con, push)</span><br><span class="line"></span><br><span class="line">response := routeDiscoveryResponse(rawRoutes)</span><br><span class="line">err = con.send(response)</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>generateRawRoutes</code> 主要调用函数 <code>ConfigGenerator.BuildHTTPRoutes</code> 完成数据的准备：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *DiscoveryServer)</span> <span class="title">generateRawRoutes</span><span class="params">(con *XdsConnection, push *model.PushContext)</span> <span class="params">([]*xdsapi.RouteConfiguration, error)</span></span> &#123;</span><br><span class="line">   rc := <span class="built_in">make</span>([]*xdsapi.RouteConfiguration, <span class="number">0</span>)</span><br><span class="line">   <span class="keyword">for</span> _, routeName := <span class="keyword">range</span> con.Routes &#123;</span><br><span class="line">      r, err := s.ConfigGenerator.BuildHTTPRoutes(s.Env, con.modelNode, push, routeName)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 验证并追加</span></span><br><span class="line">      rc = <span class="built_in">append</span>(rc, r)</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> rc, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ConfigGeneratorImpl</code> 函数根据 <code>model.SidecarProxy</code> 的类型调用对应的函数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// BuildHTTPRoutes produces a list of routes for the proxy</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">BuildHTTPRoutes</span><span class="params">(env *model.Environment, node *model.Proxy, push *model.PushContext,</span></span></span><br><span class="line"><span class="function"><span class="params">routeName <span class="keyword">string</span>)</span> <span class="params">(*xdsapi.RouteConfiguration, error)</span></span> &#123;</span><br><span class="line">proxyInstances, err := env.GetProxyServiceInstances(node)</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> node.Type &#123;</span><br><span class="line"><span class="keyword">case</span> model.SidecarProxy:</span><br><span class="line"><span class="keyword">return</span> configgen.buildSidecarOutboundHTTPRouteConfig(env, node, push, proxyInstances, routeName), <span class="literal">nil</span></span><br><span class="line"><span class="keyword">case</span> model.Router, model.Ingress:</span><br><span class="line"><span class="keyword">return</span> configgen.buildGatewayHTTPRouteConfig(env, node, push, proxyInstances, routeName)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终工作，会基于 <code>routeName</code> 和相对应的 <code>VirtualService</code> 共同生成最终的 <code>Route</code> 配置，格式上面已经给出：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// buildSidecarOutboundHTTPRouteConfig builds an outbound HTTP Route for sidecar.</span></span><br><span class="line"><span class="comment">// Based on port, will determine all virtual hosts that listen on the port.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(configgen *ConfigGeneratorImpl)</span> <span class="title">buildSidecarOutboundHTTPRouteConfig</span><span class="params">(env *model.Environment, node *model.Proxy, push *model.PushContext,</span></span></span><br><span class="line"><span class="function"><span class="params">proxyInstances []*model.ServiceInstance, routeName <span class="keyword">string</span>)</span> *<span class="title">xdsapi</span>.<span class="title">RouteConfiguration</span></span> &#123;</span><br><span class="line"></span><br><span class="line">listenerPort := <span class="number">0</span></span><br><span class="line">listenerPort, err = strconv.Atoi(routeName)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> virtualServices []model.Config</span><br><span class="line"><span class="keyword">var</span> services []*model.Service</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get the list of services that correspond to this egressListener from the sidecarScope</span></span><br><span class="line">sidecarScope := node.SidecarScope</span><br><span class="line"><span class="comment">// sidecarScope should never be nil</span></span><br><span class="line"><span class="keyword">if</span> sidecarScope != <span class="literal">nil</span> &amp;&amp; sidecarScope.Config != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// egress 相关的</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123; <span class="comment">// 从默认的 meshGateway 中获取</span></span><br><span class="line">meshGateway := <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">bool</span>&#123;model.IstioMeshGateway: <span class="literal">true</span>&#125;</span><br><span class="line">services = push.Services(node)</span><br><span class="line">virtualServices = push.VirtualServices(node, meshGateway)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">nameToServiceMap := <span class="built_in">make</span>(<span class="keyword">map</span>[model.Hostname]*model.Service)</span><br><span class="line"><span class="keyword">for</span> _, svc := <span class="keyword">range</span> services &#123;</span><br><span class="line"><span class="keyword">if</span> listenerPort == <span class="number">0</span> &#123;</span><br><span class="line">nameToServiceMap[svc.Hostname] = svc</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">if</span> svcPort, exists := svc.Ports.GetByPort(listenerPort); exists &#123;</span><br><span class="line"></span><br><span class="line">nameToServiceMap[svc.Hostname] = &amp;model.Service&#123;</span><br><span class="line">Hostname:     svc.Hostname,</span><br><span class="line">Address:      svc.Address,</span><br><span class="line">MeshExternal: svc.MeshExternal,</span><br><span class="line">Ports:        []*model.Port&#123;svcPort&#125;,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collect all proxy labels for source match</span></span><br><span class="line"><span class="keyword">var</span> proxyLabels model.LabelsCollection</span><br><span class="line"><span class="keyword">for</span> _, w := <span class="keyword">range</span> proxyInstances &#123;</span><br><span class="line">proxyLabels = <span class="built_in">append</span>(proxyLabels, w.Labels)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get list of virtual services bound to the mesh gateway</span></span><br><span class="line">virtualHostWrappers := istio_route.BuildSidecarVirtualHostsFromConfigAndRegistry(node, push, nameToServiceMap, proxyLabels, virtualServices, listenerPort)</span><br><span class="line">vHostPortMap := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">int</span>][]route.VirtualHost)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, virtualHostWrapper := <span class="keyword">range</span> virtualHostWrappers &#123;</span><br><span class="line">virtualHosts := <span class="built_in">make</span>([]route.VirtualHost, <span class="number">0</span>, <span class="built_in">len</span>(virtualHostWrapper.VirtualServiceHosts)+<span class="built_in">len</span>(virtualHostWrapper.Services))</span><br><span class="line"><span class="keyword">for</span> _, host := <span class="keyword">range</span> virtualHostWrapper.VirtualServiceHosts &#123;</span><br><span class="line">virtualHosts = <span class="built_in">append</span>(virtualHosts, route.VirtualHost&#123;</span><br><span class="line">Name:    fmt.Sprintf(<span class="string">"%s:%d"</span>, host, virtualHostWrapper.Port),</span><br><span class="line">Domains: []<span class="keyword">string</span>&#123;host, fmt.Sprintf(<span class="string">"%s:%d"</span>, host, virtualHostWrapper.Port)&#125;,</span><br><span class="line">Routes:  virtualHostWrapper.Routes,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, svc := <span class="keyword">range</span> virtualHostWrapper.Services &#123;</span><br><span class="line">virtualHosts = <span class="built_in">append</span>(virtualHosts, route.VirtualHost&#123;</span><br><span class="line">Name:    fmt.Sprintf(<span class="string">"%s:%d"</span>, svc.Hostname, virtualHostWrapper.Port),</span><br><span class="line">Domains: generateVirtualHostDomains(svc, virtualHostWrapper.Port, node),</span><br><span class="line">Routes:  virtualHostWrapper.Routes,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">vHostPortMap[virtualHostWrapper.Port] = <span class="built_in">append</span>(vHostPortMap[virtualHostWrapper.Port], virtualHosts...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> virtualHosts []route.VirtualHost</span><br><span class="line"><span class="keyword">if</span> listenerPort == <span class="number">0</span> &#123;</span><br><span class="line">virtualHosts = mergeAllVirtualHosts(vHostPortMap)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">virtualHosts = vHostPortMap[listenerPort]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">util.SortVirtualHosts(virtualHosts)</span><br><span class="line">out := &amp;xdsapi.RouteConfiguration&#123;</span><br><span class="line">Name:             routeName,</span><br><span class="line">VirtualHosts:     virtualHosts,</span><br><span class="line">ValidateClusters: proto.BoolFalse,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// call plugins</span></span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> configgen.Plugins &#123;</span><br><span class="line">in := &amp;plugin.InputParams&#123;</span><br><span class="line">ListenerProtocol: plugin.ListenerProtocolHTTP,</span><br><span class="line">Env:              env,</span><br><span class="line">Node:             node,</span><br><span class="line">Push:             push,</span><br><span class="line">&#125;</span><br><span class="line">p.OnOutboundRouteConfiguration(in, out)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> out</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Envoy-重启的-Pilot-连接日志"><a href="#Envoy-重启的-Pilot-连接日志" class="headerlink" title="Envoy 重启的 Pilot 连接日志"></a>Envoy 重启的 Pilot 连接日志</h3><p>CDS -&gt; EDS -&gt; LDS -&gt; RDS</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">2019-02-16T06:27:14.131219ZinfoadsADS:CDS: REQ 10.128.69.0:48816 sidecar~10.128.69.4~helloworld-v1-8f8dd85-f99wk.istio-system~istio-system.svc.cluster.local-29 66.451µs raw: node:&lt;id:<span class="string">"sidecar~10.128.69.4~helloworld-v1-8f8dd85-f99wk.istio-system~istio-system.svc.cluster.local"</span> cluster:<span class="string">"helloworld"</span> metadata:&lt;fields:&lt;key:<span class="string">"INTERCEPTION_MODE"</span> value:&lt;string_value:<span class="string">"REDIRECT"</span> &gt; &gt; fields:&lt;key:<span class="string">"ISTIO_PROXY_SHA"</span> value:&lt;string_value:<span class="string">"istio-proxy:930841ca88b15365737acb7eddeea6733d4f98b9"</span> &gt; &gt; fields:&lt;key:<span class="string">"ISTIO_PROXY_VERSION"</span> value:&lt;string_value:<span class="string">"1.0.2"</span> &gt; &gt; fields:&lt;key:<span class="string">"ISTIO_VERSION"</span> value:&lt;string_value:<span class="string">"1.0.5"</span> &gt; &gt; fields:&lt;key:<span class="string">"POD_NAME"</span> value:&lt;string_value:<span class="string">"helloworld-v1-8f8dd85-f99wk"</span> &gt; &gt; fields:&lt;key:<span class="string">"app"</span> value:&lt;string_value:<span class="string">"helloworld"</span> &gt; &gt; fields:&lt;key:<span class="string">"istio"</span> value:&lt;string_value:<span class="string">"sidecar"</span> &gt; &gt; fields:&lt;key:<span class="string">"version"</span> value:&lt;string_value:<span class="string">"v1"</span> &gt; &gt; &gt; build_version:<span class="string">"0/1.8.0-dev//RELEASE"</span> &gt; type_url:<span class="string">"type.googleapis.com/envoy.api.v2.Cluster"</span></span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:14.131444ZinfoadsCDS: PUSH 2019-02-15T05:53:49Z/8 <span class="keyword">for</span> helloworld-v1-8f8dd85-f99wk.istio-system <span class="string">"10.128.69.0:48816"</span>, Clusters: 11, Services 3</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:14.141574ZinfoadsEDS: PUSH <span class="keyword">for</span> sidecar~10.128.69.4~helloworld-v1-8f8dd85-f99wk.istio-system~istio-system.svc.cluster.local-29 clusters 9 endpoints 9 empty 0</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:14.142920ZinfoUses TLS multiplexing <span class="keyword">for</span> helloworld.istio-system.svc.cluster.local &#123;http 5000 HTTP&#125;</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:14.149656ZinfoadsLDS: PUSH <span class="keyword">for</span> node:helloworld-v1-8f8dd85-f99wk.istio-system addr:<span class="string">"10.128.69.0:48816"</span> listeners:8 10981</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:14.163397ZinfoadsADS: RDS: PUSH <span class="keyword">for</span> node: helloworld-v1-8f8dd85-f99wk.istio-system addr:10.128.69.0:48816 routes:5 ver:2019-02-15T05:53:49Z/8</span><br></pre></td></tr></table></figure><p>envoy </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line">2019-02-16T06:27:13.308594ZinfoVersion root@6f6ea1061f2b-docker.io/istio-1.0.5-c1707e45e71c75d74bf3a5dec8c7086f32f32fad-Clean</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:13.308678ZinfoProxy role: model.Proxy&#123;ClusterID:<span class="string">""</span>, Type:<span class="string">"sidecar"</span>, IPAddress:<span class="string">"10.128.69.4"</span>, ID:<span class="string">"helloworld-v1-8f8dd85-f99wk.istio-system"</span>, Domain:<span class="string">"istio-system.svc.cluster.local"</span>, Metadata:map[string]string(nil)&#125;</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:13.309241ZinfoEffective config: binaryPath: /usr/<span class="built_in">local</span>/bin/envoy</span><br><span class="line">configPath: /etc/istio/proxy</span><br><span class="line">connectTimeout: 10s</span><br><span class="line">discoveryAddress: istio-pilot.istio-system:15007</span><br><span class="line">discoveryRefreshDelay: 1s</span><br><span class="line">drainDuration: 45s</span><br><span class="line">parentShutdownDuration: 60s</span><br><span class="line">proxyAdminPort: 15000</span><br><span class="line">serviceCluster: helloworld</span><br><span class="line">zipkinAddress: zipkin.istio-system:9411</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:13.309274ZinfoMonitored certs: []envoy.CertSource&#123;envoy.CertSource&#123;Directory:<span class="string">"/etc/certs/"</span>, Files:[]string&#123;<span class="string">"cert-chain.pem"</span>, <span class="string">"key.pem"</span>, <span class="string">"root-cert.pem"</span>&#125;&#125;&#125;</span><br><span class="line"></span><br><span class="line">2019-02-16T06:27:13.309457ZinfoStarting proxy agent</span><br><span class="line">2019-02-16T06:27:13.309573ZinfoReceived new config, resetting budget</span><br><span class="line">2019-02-16T06:27:13.310745ZinfoReconciling configuration (budget 10)</span><br><span class="line">2019-02-16T06:27:13.310805ZinfoEpoch 0 starting</span><br><span class="line">2019-02-16T06:27:13.311883ZinfoEnvoy <span class="built_in">command</span>: [-c /etc/istio/proxy/envoy-rev0.json --restart-epoch 0 --drain-time<span class="_">-s</span> 45 --parent-shutdown-time<span class="_">-s</span> 60 --service-cluster helloworld --service-node sidecar~10.128.69.4~helloworld-v1-8f8dd85-f99wk.istio-system~istio-system.svc.cluster.local --max-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only]</span><br><span class="line"></span><br><span class="line">// ...</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:13.347][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:130] cm init: initializing cds</span><br><span class="line">[2019-02-16 06:27:14.133][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|15010||istio-pilot.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.134][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|15011||istio-pilot.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.134][24][info][upstream] </span><br><span class="line">external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|8080||istio-pilot.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.135][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|9093||istio-pilot.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.136][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|8060||istio-citadel.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.137][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|9093||istio-citadel.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.138][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|5000||helloworld.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.138][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|5000|v1|helloworld.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.139][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster outbound|5000|v2|helloworld.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.140][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster inbound|5000||helloworld.istio-system.svc.cluster.local during init</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.141][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:494] add/update cluster BlackHoleCluster during init</span><br><span class="line">[2019-02-16 06:27:14.141][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:111] cm init: initializing secondary clusters</span><br><span class="line"></span><br><span class="line">[2019-02-16 06:27:14.142][24][info][upstream] external/envoy/<span class="built_in">source</span>/common/upstream/cluster_manager_impl.cc:134] cm init: all clusters initialized</span><br><span class="line">[2019-02-16 06:27:14.142][24][info][main] external/envoy/<span class="built_in">source</span>/server/server.cc:401] all clusters initialized. initializing init manager</span><br><span class="line">[2019-02-16 06:27:14.155][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'10.128.69.4_5000'</span></span><br><span class="line">[2019-02-16 06:27:14.156][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'10.0.79.108_15011'</span></span><br><span class="line">[2019-02-16 06:27:14.157][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'0.0.0.0_8080'</span></span><br><span class="line">[2019-02-16 06:27:14.158][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'0.0.0.0_9093'</span></span><br><span class="line">[2019-02-16 06:27:14.159][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'0.0.0.0_8060'</span></span><br><span class="line">[2019-02-16 06:27:14.160][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'0.0.0.0_5000'</span></span><br><span class="line">[2019-02-16 06:27:14.160][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'0.0.0.0_15010'</span></span><br><span class="line">[2019-02-16 06:27:14.161][24][info][upstream] external/envoy/<span class="built_in">source</span>/server/lds_api.cc:80] lds: add/update listener <span class="string">'virtual'</span></span><br><span class="line">[2019-02-16 06:27:14.165][24][info][config] external/envoy/<span class="built_in">source</span>/server/listener_manager_impl.cc:908] all dependencies initialized. starting workers</span><br></pre></td></tr></table></figure><h3 id="configScope"><a href="#configScope" class="headerlink" title="configScope"></a>configScope</h3><p>Istio 1.1 代码中实现，参见 <a href="https://github.com/istio/istio/pull/10287" target="_blank" rel="noopener">PR 10278</a>，早期的版本实现是通过注解的方式来进行的，打在 service 上，注解如下：</p><blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="comment">// ServiceConfigScopeAnnotation configs the scope the service visible to.</span></span><br><span class="line">&gt; <span class="comment">//   "PUBLIC" which is the default, indicates it is reachable within the mesh</span></span><br><span class="line">&gt; <span class="comment">//   "PRIVATE" indicates it is reachable within its namespace</span></span><br><span class="line">&gt; ServiceConfigScopeAnnotation = <span class="string">"networking.istio.io/configScope"</span></span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></blockquote><p>ServiceEntry，VirtualService，Gateway， DestinationRule等都可以通过spec.configScope设置作用范围。ConfigScope 可以设置为”PUBLIC”，”PRIVATE”类型：</p><ul><li><p>“PUBLIC” 表示规则对网格内所有的工作负载可见，这也是默认值。</p></li><li><p>“PRIVATE” 表示规则仅对同一namespace下面的工作负载可见。</p></li></ul><p><a href="https://github.com/istio/istio/blob/d93e9eb86a104c77275e84077fd20435ab1c9cfa/tests/e2e/tests/pilot/testdata/networking/v1alpha3/virtualservice-http-scope-private.yaml" target="_blank" rel="noopener">样例</a></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">networking.istio.io/v1alpha3</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">VirtualService</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"><span class="attr">  name:</span> <span class="string">virtual-service-scope-private</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"><span class="attr">  configScope:</span> <span class="string">PRIVATE</span></span><br><span class="line"><span class="attr">  hosts:</span></span><br><span class="line"><span class="bullet">  -</span> <span class="string">"bookinfo.com"</span></span><br><span class="line"><span class="attr">  http:</span></span><br><span class="line"><span class="attr">  - route:</span></span><br><span class="line"><span class="attr">    - destination:</span></span><br><span class="line"><span class="attr">        host:</span> <span class="string">"bookinfo.com"</span></span><br><span class="line"><span class="attr">    headers:</span></span><br><span class="line"><span class="attr">      request:</span></span><br><span class="line"><span class="attr">        add:</span> </span><br><span class="line"><span class="attr">          scope:</span> <span class="string">private</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://zhaohuabing.com/istio-practice/content/debug-istio/%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%B0%83%E8%AF%95%E4%BF%A1%E6%81%AF.html" target="_blank" rel="noopener">debug-istio</a></li></ol><hr><p><strong>除特别声明本站文章均属原创（翻译内容除外），如需要转载请事先联系，转载需要注明作者原文链接地址。</strong></p><hr>]]></content>
    
    <summary type="html">
    
      
      
        &lt;hr&gt;
&lt;p&gt;&lt;strong&gt;本系列链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2019/02/02/istio-source-pilot-agent/&quot; title=&quot;Istio源码系列1：pilot-agent 源码分析&quot;&gt;Istio源码系列1：
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="istio" scheme="http://www.cn18k.com/tags/istio/"/>
    
  </entry>
  
  <entry>
    <title>Istio源码系列2：citadel 源码分析</title>
    <link href="http://www.cn18k.com/2019/02/06/istio-source-citadel/"/>
    <id>http://www.cn18k.com/2019/02/06/istio-source-citadel/</id>
    <published>2019-02-06T03:00:00.000Z</published>
    <updated>2019-02-06T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<hr><p><strong>本系列链接：</strong></p><ul><li><a href="/2019/02/02/istio-source-pilot-agent/" title="Istio源码系列1：pilot-agent 源码分析">Istio源码系列1：pilot-agent 源码分析</a></li><li><a href="/2019/02/06/istio-source-citadel/" title="Istio源码系列2：citadel 源码分析">Istio源码系列2：citadel 源码分析</a></li><li><a href="/2019/02/08/istio-source-pilot/" title="Istio源码系列3：pilot-discovery 源码分析">Istio源码系列3：pilot-discovery 源码分析</a></li><li><a href="/2019/02/15/istio-source-mixer/" title="Istio源码系列4：mixer 源码分析">Istio源码系列4：mixer 源码分析</a></li></ul><hr><h2 id="安全整体架构"><a href="#安全整体架构" class="headerlink" title="安全整体架构"></a>安全整体架构</h2><p><img src="https://istio.io/docs/concepts/security/architecture.svg" alt=""> From: <a href="https://istio.io/zh/docs/concepts/security/" target="_blank" rel="noopener">Istio 安全</a></p><p>源码位于 <a href="https://github.com/istio/istio/tree/master/security" target="_blank" rel="noopener">security</a>，编译后名称为 citadel。</p><h2 id="命令行介绍"><a href="#命令行介绍" class="headerlink" title="命令行介绍"></a>命令行介绍</h2><p>Dockerfile istio.io/istio/security/docker/Dockerfile.citadel</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">FROM scratch</span><br><span class="line"></span><br><span class="line"><span class="comment"># obtained from debian ca-certs deb using fetch_cacerts.sh</span></span><br><span class="line">ADD ca-certificates.tgz /</span><br><span class="line"><span class="comment"># All containers need a /tmp directory</span></span><br><span class="line">WORKDIR /tmp/</span><br><span class="line">ADD istio_ca /usr/<span class="built_in">local</span>/bin/istio_ca</span><br><span class="line"></span><br><span class="line">ENTRYPOINT [ <span class="string">"/usr/local/bin/istio_ca"</span>, <span class="string">"--self-signed-ca"</span> ]</span><br></pre></td></tr></table></figure><p>查看版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl exec -ti istio-citadel-55cdfdd57c-bh7dk -n istio-system -- /usr/local/bin/istio_ca version</span></span><br><span class="line">Version: 1.0.5</span><br><span class="line">GitRevision: c1707e45e71c75d74bf3a5dec8c7086f32f32fad</span><br><span class="line">User: root@6f6ea1061f2b</span><br><span class="line">Hub: docker.io/istio</span><br><span class="line">GolangVersion: go1.10.4</span><br><span class="line">BuildStatus: Clean</span><br></pre></td></tr></table></figure><p>命令行帮助：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl exec -ti istio-citadel-55cdfdd57c-bh7dk -n istio-system -- /usr/local/bin/istio_ca --help</span></span><br><span class="line">Istio Certificate Authority (CA)</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line">  istio_ca [flags]</span><br><span class="line">  istio_ca [<span class="built_in">command</span>]</span><br><span class="line"></span><br><span class="line">Available Commands:</span><br><span class="line">  <span class="built_in">help</span>        Help about any <span class="built_in">command</span></span><br><span class="line">  probe       Check the liveness or readiness of a locally-running server</span><br><span class="line">  version     Prints out build version information</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line">      --append-dns-names                           Append DNS names to the certificates <span class="keyword">for</span> webhook services. (default <span class="literal">true</span>)</span><br><span class="line">      --cert-chain string                          Path to the certificate chain file</span><br><span class="line">      --citadel-storage-namespace string           Namespace <span class="built_in">where</span> the Citadel pod is running. Will not be used <span class="keyword">if</span> explicit file or other storage mechanism is specified. (default <span class="string">"istio-system"</span>)</span><br><span class="line">      --custom-dns-names string                    The list of account.namespace:customdns names, separated by comma.</span><br><span class="line">      --<span class="built_in">enable</span>-profiling                           Enabling profiling when monitoring Citadel.</span><br><span class="line">      --grpc-host-identities string                The list of hostnames <span class="keyword">for</span> istio ca server, separated by comma. (default <span class="string">"istio-ca,istio-citadel"</span>)</span><br><span class="line">      --grpc-hostname string                       DEPRECATED, use --grpc-host-identites. (default <span class="string">"istio-ca"</span>)</span><br><span class="line">      --grpc-port int                              The port number <span class="keyword">for</span> Citadel GRPC server. If unspecified, Citadel will not serve GRPC requests. (default 8060)</span><br><span class="line">  -h, --<span class="built_in">help</span>                                       <span class="built_in">help</span> <span class="keyword">for</span> istio_ca</span><br><span class="line">      --key-size int                               Size of generated private key (default 2048)</span><br><span class="line">      --kube-config string                         Specifies path to kubeconfig file. This must be specified when not running inside a Kubernetes pod.</span><br><span class="line">      --listened-namespace string                  Select a namespace <span class="keyword">for</span> the CA to listen to. If unspecified, Citadel tries to use the <span class="variable">$&#123;NAMESPACE&#125;</span> environment variable. If neither is <span class="built_in">set</span>, Citadel listens to all namespaces.</span><br><span class="line">      --liveness-probe-interval duration           Interval of updating file <span class="keyword">for</span> the liveness probe.</span><br><span class="line">      --liveness-probe-path string                 Path to the file <span class="keyword">for</span> the liveness probe.</span><br><span class="line">      --log_as_json                                Whether to format output as JSON or <span class="keyword">in</span> plain console-friendly format</span><br><span class="line">      --log_caller string                          Comma-separated list of scopes <span class="keyword">for</span> <span class="built_in">which</span> to include <span class="built_in">caller</span> information, scopes can be any of [default, model]</span><br><span class="line">      --log_output_level string                    Comma-separated minimum per-scope logging level of messages to output, <span class="keyword">in</span> the form of &lt;scope&gt;:&lt;level&gt;,&lt;scope&gt;:&lt;level&gt;,... <span class="built_in">where</span> scope can be one of [default, model] and level can be one of [debug, info, warn, error, none] (default <span class="string">"default:info"</span>)</span><br><span class="line">      --log_rotate string                          The path <span class="keyword">for</span> the optional rotating <span class="built_in">log</span> file</span><br><span class="line">      --log_rotate_max_age int                     The maximum age <span class="keyword">in</span> days of a <span class="built_in">log</span> file beyond <span class="built_in">which</span> the file is rotated (0 indicates no <span class="built_in">limit</span>) (default 30)</span><br><span class="line">      --log_rotate_max_backups int                 The maximum number of <span class="built_in">log</span> file backups to keep before older files are deleted (0 indicates no <span class="built_in">limit</span>) (default 1000)</span><br><span class="line">      --log_rotate_max_size int                    The maximum size <span class="keyword">in</span> megabytes of a <span class="built_in">log</span> file beyond <span class="built_in">which</span> the file is rotated (default 104857600)</span><br><span class="line">      --log_stacktrace_level string                Comma-separated minimum per-scope logging level at <span class="built_in">which</span> stack traces are captured, <span class="keyword">in</span> the form of &lt;scope&gt;:&lt;level&gt;,&lt;scope:level&gt;,... <span class="built_in">where</span> scope can be one of [default, model] and level can be one of [debug, info, warn, error, none] (default <span class="string">"default:none"</span>)</span><br><span class="line">      --log_target stringArray                     The <span class="built_in">set</span> of paths <span class="built_in">where</span> to output the <span class="built_in">log</span>. This can be any path as well as the special values stdout and stderr (default [stdout])</span><br><span class="line">      --max-workload-cert-ttl duration             The max TTL of issued workload certificates (default 2160h0m0s)</span><br><span class="line">      --monitoring-port int                        The port number <span class="keyword">for</span> monitoring Citadel. If unspecified, Citadel will <span class="built_in">disable</span> monitoring. (default 9093)</span><br><span class="line">      --org string                                 Organization <span class="keyword">for</span> the cert</span><br><span class="line">      --probe-check-interval duration              Interval of checking the liveness of the CA. (default 30s)</span><br><span class="line">      --requested-ca-cert-ttl duration             The requested TTL <span class="keyword">for</span> the workload (default 8760h0m0s)</span><br><span class="line">      --root-cert string                           Path to the root certificate file</span><br><span class="line">      --self-signed-ca                             Indicates whether to use auto-generated self-signed CA certificate. When <span class="built_in">set</span> to <span class="literal">true</span>, the <span class="string">'--signing-cert'</span> and <span class="string">'--signing-key'</span> options are ignored.</span><br><span class="line">      --self-signed-ca-cert-ttl duration           The TTL of self-signed CA root certificate (default 8760h0m0s)</span><br><span class="line">      --self-signed-ca-org string                  The issuer organization used <span class="keyword">in</span> self-signed CA certificate (default to k8s.cluster.local) (default <span class="string">"k8s.cluster.local"</span>)</span><br><span class="line">      --sign-ca-certs                              Whether Citadel signs certificates <span class="keyword">for</span> other CAs</span><br><span class="line">      --signing-cert string                        Path to the CA signing certificate file</span><br><span class="line">      --signing-key string                         Path to the CA signing key file</span><br><span class="line">      --upstream-ca-address string                 The IP:port address of the upstream CA. When <span class="built_in">set</span>, the CA will rely on the upstream Citadel to provision its own certificate.</span><br><span class="line">      --workload-cert-grace-period-ratio float32   The workload certificate rotation grace period, as a ratio of the workload certificate TTL. (default 0.5)</span><br><span class="line">      --workload-cert-min-grace-period duration    The minimum workload certificate rotation grace period. (default 10m0s)</span><br><span class="line">      --workload-cert-ttl duration                 The TTL of issued workload certificates (default 2160h0m0s)</span><br><span class="line"></span><br><span class="line">Use <span class="string">"istio_ca [command] --help"</span> <span class="keyword">for</span> more information about a <span class="built_in">command</span>.</span><br></pre></td></tr></table></figure><p>容器内部启动添加的命令行如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- --append-dns-names=<span class="literal">true</span></span><br><span class="line">- --grpc-port=8060</span><br><span class="line">- --grpc-hostname=citadel</span><br><span class="line">- --citadel-storage-namespace=istio-system</span><br><span class="line">- --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system,istio-ingressgateway-service-account.istio-system:istio-ingressgateway.istio-system</span><br><span class="line">- --self-signed-ca=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>可以在其运行的 node 节点上通过命令查看</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ /usr/<span class="built_in">local</span>/bin/istio_ca --self-signed-ca --append-dns-names=<span class="literal">true</span> --grpc-port=8060 --grpc-hostname=citadel --citadel-storage-namespace=istio-system --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system,istio-ingressgateway-service-account.istio-system:istio-ingressgateway.istio-system --self-signed-ca=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>istio-citadel 启动的 yaml 文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl get pod istio-citadel-55cdfdd57c-bh7dk -n istio-system -o yaml</span></span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Pod</span><br><span class="line">metadata:</span><br><span class="line">  annotations:</span><br><span class="line">    scheduler.alpha.kubernetes.io/critical-pod: <span class="string">""</span></span><br><span class="line">    sidecar.istio.io/inject: <span class="string">"false"</span></span><br><span class="line">  creationTimestamp: 2019-01-15T08:24:24Z</span><br><span class="line">  generateName: istio-citadel-55cdfdd57c-</span><br><span class="line">  labels:</span><br><span class="line">    istio: citadel</span><br><span class="line">    pod-template-hash: 55cdfdd57c</span><br><span class="line">  name: istio-citadel-55cdfdd57c-bh7dk</span><br><span class="line">  namespace: istio-system</span><br><span class="line">  ownerReferences:</span><br><span class="line">  - apiVersion: apps/v1</span><br><span class="line">    blockOwnerDeletion: <span class="literal">true</span></span><br><span class="line">    controller: <span class="literal">true</span></span><br><span class="line">    kind: ReplicaSet</span><br><span class="line">    name: istio-citadel-55cdfdd57c</span><br><span class="line">    uid: f6db1f80-189e-11e9-ab53-00163e0c1552</span><br><span class="line">  resourceVersion: <span class="string">"16685125"</span></span><br><span class="line">  selfLink: /api/v1/namespaces/istio-system/pods/istio-citadel-55cdfdd57c-bh7dk</span><br><span class="line">  uid: f710ae31-189e-11e9-ab53-00163e0c1552</span><br><span class="line">spec:</span><br><span class="line">  affinity:</span><br><span class="line">    nodeAffinity:</span><br><span class="line">      preferredDuringSchedulingIgnoredDuringExecution:</span><br><span class="line">      - preference:</span><br><span class="line">          matchExpressions:</span><br><span class="line">          - key: beta.kubernetes.io/arch</span><br><span class="line">            operator: In</span><br><span class="line">            values:</span><br><span class="line">            - amd64</span><br><span class="line">        weight: 2</span><br><span class="line">      - preference:</span><br><span class="line">          matchExpressions:</span><br><span class="line">          - key: beta.kubernetes.io/arch</span><br><span class="line">            operator: In</span><br><span class="line">            values:</span><br><span class="line">            - ppc64le</span><br><span class="line">        weight: 2</span><br><span class="line">      - preference:</span><br><span class="line">          matchExpressions:</span><br><span class="line">          - key: beta.kubernetes.io/arch</span><br><span class="line">            operator: In</span><br><span class="line">            values:</span><br><span class="line">            - s390x</span><br><span class="line">        weight: 2</span><br><span class="line">      requiredDuringSchedulingIgnoredDuringExecution:</span><br><span class="line">        nodeSelectorTerms:</span><br><span class="line">        - matchExpressions:</span><br><span class="line">          - key: beta.kubernetes.io/arch</span><br><span class="line">            operator: In</span><br><span class="line">            values:</span><br><span class="line">            - amd64</span><br><span class="line">            - ppc64le</span><br><span class="line">            - s390x</span><br><span class="line">  containers:</span><br><span class="line">  - args:</span><br><span class="line">    - --append-dns-names=<span class="literal">true</span></span><br><span class="line">    - --grpc-port=8060</span><br><span class="line">    - --grpc-hostname=citadel</span><br><span class="line">    - --citadel-storage-namespace=istio-system</span><br><span class="line">    - --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system,istio-ingressgateway-service-account.istio-system:istio-ingressgateway.istio-system</span><br><span class="line">    - --self-signed-ca=<span class="literal">true</span></span><br><span class="line">    image: docker.io/istio/citadel:1.0.5</span><br><span class="line">    imagePullPolicy: IfNotPresent</span><br><span class="line">    name: citadel</span><br><span class="line">    resources:</span><br><span class="line">      requests:</span><br><span class="line">        cpu: 10m</span><br><span class="line">    terminationMessagePath: /dev/termination-log</span><br><span class="line">    terminationMessagePolicy: File</span><br><span class="line">    volumeMounts:</span><br><span class="line">    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount</span><br><span class="line">      name: istio-citadel-service-account-token-gdxfk</span><br><span class="line">      readOnly: <span class="literal">true</span></span><br><span class="line">  dnsPolicy: ClusterFirst</span><br><span class="line">  nodeName: node02</span><br><span class="line">  priority: 0</span><br><span class="line">  restartPolicy: Always</span><br><span class="line">  schedulerName: default-scheduler</span><br><span class="line">  securityContext: &#123;&#125;</span><br><span class="line">  serviceAccount: istio-citadel-service-account</span><br><span class="line">  serviceAccountName: istio-citadel-service-account</span><br><span class="line">  terminationGracePeriodSeconds: 30</span><br><span class="line">  tolerations:</span><br><span class="line">  - effect: NoExecute</span><br><span class="line">    key: node.kubernetes.io/not-ready</span><br><span class="line">    operator: Exists</span><br><span class="line">    tolerationSeconds: 10</span><br><span class="line">  - effect: NoExecute</span><br><span class="line">    key: node.kubernetes.io/unreachable</span><br><span class="line">    operator: Exists</span><br><span class="line">    tolerationSeconds: 10</span><br><span class="line">  volumes:</span><br><span class="line">  - name: istio-citadel-service-account-token-gdxfk</span><br><span class="line">    secret:</span><br><span class="line">      defaultMode: 420</span><br><span class="line">      secretName: istio-citadel-service-account-token-gdxfk</span><br></pre></td></tr></table></figure><h2 id="代码流程分析"><a href="#代码流程分析" class="headerlink" title="代码流程分析"></a>代码流程分析</h2><h3 id="整体架构"><a href="#整体架构" class="headerlink" title="整体架构"></a>整体架构</h3><p><img src="https://www.do1618.com/wp-content/uploads/2019/02/citadel_arch.png" alt="image-20190205170654001"></p><p>istio.io/istio/security/cmd/istio_ca/main.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// /usr/local/bin/istio_ca </span></span><br><span class="line"><span class="comment">//--self-signed-ca </span></span><br><span class="line"><span class="comment">//--append-dns-names=true </span></span><br><span class="line"><span class="comment">//  --grpc-port=8060 </span></span><br><span class="line"><span class="comment">//  --grpc-hostname=citadel </span></span><br><span class="line"><span class="comment">//  --citadel-storage-namespace=istio-system </span></span><br><span class="line"><span class="comment">//  --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system,</span></span><br><span class="line"><span class="comment">//     istio-ingressgateway-service-account.istio-system:istio-ingressgateway.istio-system //  --self-signed-ca=true</span></span><br><span class="line"></span><br><span class="line">rootCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">"istio_ca"</span>,</span><br><span class="line">Short: <span class="string">"Istio Certificate Authority (CA)."</span>,</span><br><span class="line">Args:  cobra.ExactArgs(<span class="number">0</span>),</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">runCA()</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>runCA 的主函数流程如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">runCA</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"><span class="comment">// --listened-namespace 设置 CA 监控的 namespace，如果没有指定会从 $&#123;NAMESPACE&#125;  环境变量中获取，如果都没有设置，Citadel 则会监听全部的 namespace.</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> value, exists := os.LookupEnv(cmd.ListenedNamespaceKey); exists &#123;</span><br><span class="line"><span class="comment">// When -namespace is not set, try to read the namespace from environment variable.</span></span><br><span class="line"><span class="keyword">if</span> opts.listenedNamespace == <span class="string">""</span> &#123;</span><br><span class="line">opts.listenedNamespace = value</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Use environment variable for istioCaStorageNamespace if it exists</span></span><br><span class="line">opts.istioCaStorageNamespace = value</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 验证命令行</span></span><br><span class="line">verifyCommandLineOptions()</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> webhooks <span class="keyword">map</span>[<span class="keyword">string</span>]controller.DNSNameEntry</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果设置了添加 DNS 名字的后缀</span></span><br><span class="line"><span class="keyword">if</span> opts.appendDNSNames &#123;</span><br><span class="line">webhooks = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]controller.DNSNameEntry)</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">       // ServiceAccount/DNS pair for generating DNS names in certificates.</span></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> move it to a configmap later when we have more services to support.</span></span><br><span class="line"><span class="comment">webhookServiceAccounts = []string&#123;</span></span><br><span class="line"><span class="comment">"istio-sidecar-injector-service-account",</span></span><br><span class="line"><span class="comment">"istio-galley-service-account",</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">webhookServiceNames = []string&#123;</span></span><br><span class="line"><span class="comment">"istio-sidecar-injector",</span></span><br><span class="line"><span class="comment">"istio-galley",</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line"><span class="keyword">for</span> i, svcAccount := <span class="keyword">range</span> webhookServiceAccounts &#123; </span><br><span class="line">            <span class="comment">// istio-sidecar-injector-service-account</span></span><br><span class="line">            <span class="comment">// istio-galley-service-account</span></span><br><span class="line">webhooks[svcAccount] = controller.DNSNameEntry&#123;</span><br><span class="line">ServiceName: webhookServiceNames[i],</span><br><span class="line">Namespace:   opts.istioCaStorageNamespace, </span><br><span class="line">                <span class="comment">// opts.istioCaStorageNamespace 运行的 namespace，默认为  istio-system</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建连接到集群中的 client</span></span><br><span class="line">cs := createClientset()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 返回 ca.IstioCA，用于管理证书链和签新的证书</span></span><br><span class="line">ca := createCA(cs.CoreV1())</span><br><span class="line">    </span><br><span class="line"><span class="comment">// For workloads in K8s, we apply the configured workload cert TTL.</span></span><br><span class="line">    <span class="comment">// 1. 创建 NewSecretController 来完成对于 API Server 中的 ServiceAccount 和 Secret 的创建</span></span><br><span class="line">sc, err := controller.NewSecretController(ca,</span><br><span class="line">opts.workloadCertTTL,</span><br><span class="line">opts.workloadCertGracePeriodRatio, opts.workloadCertMinGracePeriod, opts.dualUse,</span><br><span class="line">cs.CoreV1(), opts.signCACerts, opts.listenedNamespace, webhooks)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fatalf(<span class="string">"Failed to create secret controller: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">stopCh := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">    <span class="comment">// !!! 运行 NewSecretController</span></span><br><span class="line">sc.Run(stopCh)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 如果设置了 grpcPort，则启动相关 server</span></span><br><span class="line"><span class="keyword">if</span> opts.grpcPort &gt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line"><span class="comment">// monitor service objects with "alpha.istio.io/kubernetes-serviceaccounts" and</span></span><br><span class="line"><span class="comment">// "alpha.istio.io/canonical-serviceaccounts" annotations</span></span><br><span class="line">        <span class="comment">// 2.1 NewServiceController</span></span><br><span class="line">serviceController := kube.NewServiceController(cs.CoreV1(), opts.listenedNamespace, reg)</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ServiceController</span></span><br><span class="line">serviceController.Run(ch)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2.2 NewServiceAccountController</span></span><br><span class="line"><span class="comment">// monitor service account objects for istio mesh expansion</span></span><br><span class="line">serviceAccountController := kube.NewServiceAccountController(cs.CoreV1(), opts.listenedNamespace, reg)</span><br><span class="line">serviceAccountController.Run(ch)</span><br><span class="line"></span><br><span class="line"><span class="comment">// The CA API uses cert with the max workload cert TTL.</span></span><br><span class="line">hostnames := <span class="built_in">append</span>(strings.Split(opts.grpcHosts, <span class="string">","</span>), fqdn())</span><br><span class="line">caServer, startErr := caserver.New(ca, opts.maxWorkloadCertTTL, opts.signCACerts, hostnames, opts.grpcPort, spiffe.GetTrustDomain())</span><br><span class="line"><span class="keyword">if</span> startErr != <span class="literal">nil</span> &#123;</span><br><span class="line">fatalf(<span class="string">"Failed to create istio ca server: %v"</span>, startErr)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> serverErr := caServer.Run(); serverErr != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// stop the registry-related controllers</span></span><br><span class="line">ch &lt;- <span class="keyword">struct</span>&#123;&#125;&#123;&#125;</span><br><span class="line"></span><br><span class="line">log.Warnf(<span class="string">"Failed to start GRPC server with error: %v"</span>, serverErr)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">monitorErrCh := <span class="built_in">make</span>(<span class="keyword">chan</span> error)</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 3. Start the monitoring server.</span></span><br><span class="line"><span class="keyword">if</span> opts.monitoringPort &gt; <span class="number">0</span> &#123;</span><br><span class="line">monitor, mErr := monitoring.NewMonitor(opts.monitoringPort, opts.enableProfiling)</span><br><span class="line"><span class="keyword">if</span> mErr != <span class="literal">nil</span> &#123;</span><br><span class="line">fatalf(<span class="string">"Unable to setup monitoring: %v"</span>, mErr)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">go</span> monitor.Start(monitorErrCh)</span><br><span class="line">log.Info(<span class="string">"Citadel monitor has started."</span>)</span><br><span class="line"><span class="keyword">defer</span> monitor.Close()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">log.Info(<span class="string">"Citadel has started"</span>)</span><br><span class="line"></span><br><span class="line">rotatorErrCh := <span class="built_in">make</span>(<span class="keyword">chan</span> error)</span><br><span class="line"><span class="comment">// Start CA client if the upstream CA address is specified.</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(opts.cAClientConfig.CAAddress) != <span class="number">0</span> &#123;</span><br><span class="line">config := &amp;opts.cAClientConfig</span><br><span class="line">config.Env = <span class="string">"onprem"</span></span><br><span class="line">config.Platform = <span class="string">"vm"</span></span><br><span class="line">config.ForCA = <span class="literal">true</span></span><br><span class="line">config.CertFile = opts.signingCertFile</span><br><span class="line">config.KeyFile = opts.signingKeyFile</span><br><span class="line">config.CertChainFile = opts.certChainFile</span><br><span class="line">config.RootCertFile = opts.rootCertFile</span><br><span class="line">config.CSRGracePeriodPercentage = cmd.DefaultCSRGracePeriodPercentage</span><br><span class="line">config.CSRMaxRetries = cmd.DefaultCSRMaxRetries</span><br><span class="line">config.CSRInitialRetrialInterval = cmd.DefaultCSRInitialRetrialInterval</span><br><span class="line">rotator, creationErr := caclient.NewKeyCertBundleRotator(config, ca.GetCAKeyCertBundle())</span><br><span class="line"><span class="keyword">if</span> creationErr != <span class="literal">nil</span> &#123;</span><br><span class="line">fatalf(<span class="string">"Failed to create key cert bundle rotator: %v"</span>, creationErr)</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. rotator 启动</span></span><br><span class="line"><span class="keyword">go</span> rotator.Start(rotatorErrCh)</span><br><span class="line">log.Info(<span class="string">"Key cert bundle rotator has started."</span>)</span><br><span class="line"><span class="keyword">defer</span> rotator.Stop()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Blocking until receives error.</span></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-monitorErrCh:</span><br><span class="line">fatalf(<span class="string">"Monitoring server error: %v"</span>, err)</span><br><span class="line"><span class="keyword">case</span> &lt;-rotatorErrCh:</span><br><span class="line">fatalf(<span class="string">"Key cert bundle rotator error: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="NewSecretController"><a href="#NewSecretController" class="headerlink" title="NewSecretController"></a>NewSecretController</h3><p>SecretController 内部会创建两个 Controller：</p><ol><li>ServiceAccount 的监听，如果设置了 listened-namespace，则监听该 namespace 下，否则是全部；</li><li>Secret 的监听，namespace 同上，但是 Controller 只会监听自己创建的类型，即：type:”istio.io/key-and-cert”</li></ol><p>实现的主要功能是为 ServiceAccount 创建对应的 Secret，Secret 中设置了相关的证书，在对应的 Pod 启动的时候进行加载；</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewSecretController returns a pointer to a newly constructed SecretController instance.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewSecretController</span><span class="params">(ca ca.CertificateAuthority, certTTL time.Duration,</span></span></span><br><span class="line"><span class="function"><span class="params">gracePeriodRatio <span class="keyword">float32</span>, minGracePeriod time.Duration, dualUse <span class="keyword">bool</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">core corev1.CoreV1Interface, forCA <span class="keyword">bool</span>, namespace <span class="keyword">string</span>, dnsNames <span class="keyword">map</span>[<span class="keyword">string</span>]DNSNameEntry)</span> <span class="params">(*SecretController, error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"></span><br><span class="line">c := &amp;SecretController&#123;</span><br><span class="line">ca:               ca,</span><br><span class="line">certTTL:          certTTL,</span><br><span class="line">gracePeriodRatio: gracePeriodRatio,</span><br><span class="line">minGracePeriod:   minGracePeriod,</span><br><span class="line">dualUse:          dualUse,</span><br><span class="line">core:             core,</span><br><span class="line">forCA:            forCA,</span><br><span class="line">dnsNames:         dnsNames,</span><br><span class="line">monitoring:       newMonitoringMetrics(),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听特定 namespace 下的 ServiceAccount</span></span><br><span class="line">c.saStore, c.saController = cache.NewInformer(saLW, &amp;v1.ServiceAccount&#123;&#125;, time.Minute, rehf)</span><br><span class="line"></span><br><span class="line">istioSecretSelector := fields.SelectorFromSet(<span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>&#123;<span class="string">"type"</span>: IstioSecretType&#125;).String()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听 type:”istio.io/key-and-cert” 的 secret </span></span><br><span class="line">c.scrtStore, c.scrtController =</span><br><span class="line">cache.NewInformer(scrtLW, &amp;v1.Secret&#123;&#125;, secretResyncPeriod, cache.ResourceEventHandlerFuncs&#123;</span><br><span class="line">DeleteFunc: c.scrtDeleted,</span><br><span class="line">UpdateFunc: c.scrtUpdated,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Run starts the SecretController until a value is sent to stopCh.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sc *SecretController)</span> <span class="title">Run</span><span class="params">(stopCh <span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line"><span class="keyword">go</span> sc.scrtController.Run(stopCh)</span><br><span class="line"></span><br><span class="line"><span class="comment">// saAdded calls upsertSecret to update and insert secret</span></span><br><span class="line"><span class="comment">// it throws error if the secret cache is not synchronized, but the secret exists in the system</span></span><br><span class="line">cache.WaitForCacheSync(stopCh, sc.scrtController.HasSynced)</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> sc.saController.Run(stopCh)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="gRPC-Server-启动"><a href="#gRPC-Server-启动" class="headerlink" title="gRPC Server 启动"></a>gRPC Server 启动</h3><p>如果设置了 gRPC 相关的参数，则会启动相关的服务，同上也会启动两个 Controller 和 一个 gRPC Server，Controller 监听的 namespace 由 listened-namespace 设置，同上：</p><ol><li><p><strong>NewServiceController</strong>：用于监听添加了注解 <code>alpha.istio.io/kubernetes-serviceaccounts</code> 和     <code>alpha.istio.io/canonical-serviceaccounts</code> 的 Service 对象；从注解中解出来对应的用户名对应的 Reg 注册表的映射关系中，当前 key 和 value 都是相同 <code>c.reg.AddMapping(svcAcct, svcAcct)</code>；</p><p>istio.io/istio/security/pkg/registry/kube/service.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// KubeServiceAccountsOnVMAnnotation is to specify the K8s service accounts that are allowed to run</span></span><br><span class="line"><span class="comment">// this service on the VMs</span></span><br><span class="line">KubeServiceAccountsOnVMAnnotation = <span class="string">"alpha.istio.io/kubernetes-serviceaccounts"</span></span><br><span class="line">   </span><br><span class="line"><span class="comment">// CanonicalServiceAccountsAnnotation is to specify the non-Kubernetes service accounts that</span></span><br><span class="line"><span class="comment">// are allowed to run this service.</span></span><br><span class="line">CanonicalServiceAccountsAnnotation = <span class="string">"alpha.istio.io/canonical-serviceaccounts"</span></span><br></pre></td></tr></table></figure><p>结构体定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ServiceController monitors the service definition changes in a namespace. If a</span></span><br><span class="line"><span class="comment">// new service is added with "alpha.istio.io/kubernetes-serviceaccounts" or</span></span><br><span class="line"><span class="comment">// "alpha.istio.io/canonical-serviceaccounts" annotations enabled,</span></span><br><span class="line"><span class="comment">// the corresponding service account will be added to the identity registry</span></span><br><span class="line"><span class="comment">// for whitelisting.</span></span><br><span class="line"><span class="keyword">type</span> ServiceController <span class="keyword">struct</span> &#123;</span><br><span class="line">core corev1.CoreV1Interface</span><br><span class="line"></span><br><span class="line"><span class="comment">// identity registry object</span></span><br><span class="line">reg registry.Registry</span><br><span class="line"></span><br><span class="line"><span class="comment">// controller for service objects</span></span><br><span class="line">controller cache.Controller</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><ol start="2"><li><p><strong>NewServiceAccountController</strong>： 监听 ServiceAccount 对象；对于获取到 sa 信息，生成相对应的 <code>SpiffeID</code> 保存到 Reg 注册表的映射关系中，当前 key 和 value 都是相同 <code>c.reg.DeleteMapping(id, id)</code>；</p><p>结构体定义如下：</p><p>istio.io/istio/security/pkg/registry/kube/serviceaccount.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ServiceAccountController monitors service account definition changes in a namespace.</span></span><br><span class="line"><span class="comment">// For each service account object, its SpiffeID is added to identity registry for</span></span><br><span class="line"><span class="comment">// whitelisting purpose.</span></span><br><span class="line"><span class="keyword">type</span> ServiceAccountController <span class="keyword">struct</span> &#123;</span><br><span class="line">core corev1.CoreV1Interface</span><br><span class="line"></span><br><span class="line"><span class="comment">// identity registry object</span></span><br><span class="line">reg registry.Registry</span><br><span class="line"></span><br><span class="line"><span class="comment">// controller for service objects</span></span><br><span class="line">controller cache.Controller</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><strong>IstioCAServiceServer</strong>：主要提供证书的生成和验证功能；</p><p>istio.io/istio/security/pkg/server/ca/server.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CreateCertificate handles an incoming certificate signing request (CSR). It does</span></span><br><span class="line"><span class="comment">// authentication and authorization. Upon validated, signs a certificate that:</span></span><br><span class="line"><span class="comment">// the SAN is the identity of the caller in authentication result.</span></span><br><span class="line"><span class="comment">// the subject public key is the public key in the CSR.</span></span><br><span class="line"><span class="comment">// the validity duration is the ValidityDuration in request, or default value if the given duration is invalid.</span></span><br><span class="line"><span class="comment">// it is signed by the CA signing key.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Server)</span> <span class="title">CreateCertificate</span><span class="params">(ctx context.Context, request *pb.IstioCertificateRequest)</span> <span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">*pb.IstioCertificateResponse, error)</span></span> &#123;</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 根据请求生成对应的证书</span></span><br><span class="line">_, _, certChainBytes, rootCertBytes := s.ca.GetCAKeyCertBundle().GetAll()</span><br><span class="line">cert, signErr := s.ca.Sign(</span><br><span class="line">[]<span class="keyword">byte</span>(request.Csr), caller.Identities, time.Duration(request.ValidityDuration)*time.Second, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">respCertChain := []<span class="keyword">string</span>&#123;<span class="keyword">string</span>(cert)&#125;</span><br><span class="line">respCertChain = <span class="built_in">append</span>(respCertChain, <span class="keyword">string</span>(rootCertBytes))</span><br><span class="line">response := &amp;pb.IstioCertificateResponse&#123;</span><br><span class="line">CertChain: respCertChain,</span><br><span class="line">&#125;</span><br><span class="line">log.Debug(<span class="string">"CSR successfully signed."</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> response, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// HandleCSR handles an incoming certificate signing request (CSR). It does</span></span><br><span class="line"><span class="comment">// proper validation (e.g. authentication) and upon validated, signs the CSR</span></span><br><span class="line"><span class="comment">// and returns the resulting certificate. If not approved, reason for refusal</span></span><br><span class="line"><span class="comment">// to sign is returned as part of the response object.</span></span><br><span class="line"><span class="comment">// [TODO](myidpt): Deprecate this function.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Server)</span> <span class="title">HandleCSR</span><span class="params">(ctx context.Context, request *pb.CsrRequest)</span> <span class="params">(*pb.CsrResponse, error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">csr, err := util.ParsePemEncodedCSR(request.CsrPem)</span><br><span class="line"></span><br><span class="line">_, err = util.ExtractIDs(csr.Extensions)</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> Call authorizer.</span></span><br><span class="line"></span><br><span class="line">_, _, certChainBytes, _ := s.ca.GetCAKeyCertBundle().GetAll()</span><br><span class="line">cert, signErr := s.ca.Sign(request.CsrPem, []<span class="keyword">string</span>&#123;&#125;, time.Duration(request.RequestedTtlMinutes)*time.Minute, s.forCA)</span><br><span class="line"></span><br><span class="line">response := &amp;pb.CsrResponse&#123;</span><br><span class="line">IsApproved: <span class="literal">true</span>,</span><br><span class="line">SignedCert: cert,</span><br><span class="line">CertChain:  certChainBytes,</span><br><span class="line">&#125;</span><br><span class="line">log.Debug(<span class="string">"CSR successfully signed."</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> response, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="Monitor"><a href="#Monitor" class="headerlink" title="Monitor"></a>Monitor</h3><p>   Monitor 服务主要用于对外输出检查，为 HttpServer, 主要提供 <code>/metrics</code> 和 <code>/version</code> ，如果启用了 <code>enableProfiling</code> 还会启用 <code>/debug/pprof/</code> 相关的路径；当 Monitor 启动以后， Citadel 则任务已经启动成功，打印以下信息：<code>log.Info(&quot;Citadel has started&quot;)</code>；</p><h3 id="CA-client"><a href="#CA-client" class="headerlink" title="CA client"></a>CA client</h3><p>如果指定了 upstream CA Server，还会启动一个 CA Client， 创建一个 rotator go routine，定期用于证书的轮转替换；</p><p>istio.io/istio/security/pkg/caclient/keycertbundlerotator.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Start periodically rotates the KeyCertBundle by interacting with the upstream CA.</span></span><br><span class="line"><span class="comment">// It is a blocking function that should run as a go routine. Thread safe.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *KeyCertBundleRotator)</span> <span class="title">Start</span><span class="params">(errCh <span class="keyword">chan</span>&lt;- error)</span></span> &#123;</span><br><span class="line">c.stoppedMutex.Lock()</span><br><span class="line"><span class="keyword">if</span> !c.stopped &#123;</span><br><span class="line">errCh &lt;- fmt.Errorf(<span class="string">"rotator already started"</span>)</span><br><span class="line">c.stoppedMutex.Unlock()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.stopped = <span class="literal">false</span></span><br><span class="line">c.stoppedMutex.Unlock()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Make sure we mark rotator stopped after this method finishes.</span></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">c.stoppedMutex.Lock()</span><br><span class="line">c.stopped = <span class="literal">true</span></span><br><span class="line">c.stoppedMutex.Unlock()</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">certBytes, _, _, _ := c.keycert.GetAllPem()</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(certBytes) != <span class="number">0</span> &#123;</span><br><span class="line">waitTime, ttlErr := c.certUtil.GetWaitTime(certBytes, time.Now())</span><br><span class="line"><span class="keyword">if</span> ttlErr != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Errorf(<span class="string">"Error getting TTL from cert: %v. Rotate immediately."</span>, ttlErr)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">timer := time.NewTimer(waitTime)</span><br><span class="line">log.Infof(<span class="string">"Will rotate key and cert in %v."</span>, waitTime)</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-c.stopCh:</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">case</span> &lt;-timer.C:</span><br><span class="line"><span class="comment">// Continue in the loop.</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">co, coErr := c.keycert.CertOptions()</span><br><span class="line"><span class="keyword">if</span> coErr != <span class="literal">nil</span> &#123;</span><br><span class="line">err := fmt.Errorf(<span class="string">"failed to extact CertOptions from bundle: %v, abort auto rotation"</span>, coErr)</span><br><span class="line">log.Errora(err)</span><br><span class="line">errCh &lt;- err</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">certBytes, certChainBytes, privKeyBytes, rErr := c.retriever.Retrieve(co)</span><br><span class="line"><span class="keyword">if</span> rErr != <span class="literal">nil</span> &#123;</span><br><span class="line">err := fmt.Errorf(<span class="string">"error retrieving the key and cert: %v, abort auto rotation"</span>, rErr)</span><br><span class="line">log.Errora(err)</span><br><span class="line">errCh &lt;- err</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">_, _, _, rootCertBytes := c.keycert.GetAllPem()</span><br><span class="line"><span class="keyword">if</span> vErr := c.keycert.VerifyAndSetAll(certBytes, privKeyBytes, certChainBytes, rootCertBytes); vErr != <span class="literal">nil</span> &#123;</span><br><span class="line">err := fmt.Errorf(<span class="string">"cannot verify the retrieved key and cert: %v, abort auto rotation"</span>, vErr)</span><br><span class="line">log.Errora(err)</span><br><span class="line">errCh &lt;- err</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">log.Infof(<span class="string">"Successfully retrieved new key and certs."</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>TODO: istio-security-post-install 和 istio-cleanup-secrets Job 作用</p><hr><p><strong>除特别声明本站文章均属原创（翻译内容除外），如需要转载请事先联系，转载需要注明作者原文链接地址。</strong></p><hr>]]></content>
    
    <summary type="html">
    
      
      
        &lt;hr&gt;
&lt;p&gt;&lt;strong&gt;本系列链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2019/02/02/istio-source-pilot-agent/&quot; title=&quot;Istio源码系列1：pilot-agent 源码分析&quot;&gt;Istio源码系列1：
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="istio" scheme="http://www.cn18k.com/tags/istio/"/>
    
  </entry>
  
  <entry>
    <title>Istio源码系列1：pilot-agent 源码分析</title>
    <link href="http://www.cn18k.com/2019/02/02/istio-source-pilot-agent/"/>
    <id>http://www.cn18k.com/2019/02/02/istio-source-pilot-agent/</id>
    <published>2019-02-02T03:00:00.000Z</published>
    <updated>2019-02-02T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<hr><p><strong>本系列链接：</strong></p><ul><li><a href="/2019/02/02/istio-source-pilot-agent/" title="Istio源码系列1：pilot-agent 源码分析">Istio源码系列1：pilot-agent 源码分析</a></li><li><a href="/2019/02/06/istio-source-citadel/" title="Istio源码系列2：citadel 源码分析">Istio源码系列2：citadel 源码分析</a></li><li><a href="/2019/02/08/istio-source-pilot/" title="Istio源码系列3：pilot-discovery 源码分析">Istio源码系列3：pilot-discovery 源码分析</a></li><li><a href="/2019/02/15/istio-source-mixer/" title="Istio源码系列4：mixer 源码分析">Istio源码系列4：mixer 源码分析</a></li></ul><hr><p>本文分析基于 Istio 1.1 版本，但是日志或者流程是基于 1.0.5 版本。</p><h2 id="整体架构"><a href="#整体架构" class="headerlink" title="整体架构"></a>整体架构</h2><p>pilot 的代码仓库位于 <a href="https://github.com/istio/istio/tree/master/pilot" target="_blank" rel="noopener">pilot repo</a>，当前主要实现了 3 个命令：</p><ol><li><a href="https://github.com/istio/istio/tree/master/pilot/cmd/pilot-agent" target="_blank" rel="noopener">pilot-agent</a> 充当 Proxy 节点上与 API-Server 和 <a href="https://github.com/istio/proxy" target="_blank" rel="noopener">proxy</a> 的桥梁，负责生成 envoy 初始配置文件和管理envoy 生命周期；</li><li><a href="https://github.com/istio/istio/tree/master/pilot/cmd/pilot-discovery" target="_blank" rel="noopener">pilot-discovery</a> 为  <a href="https://github.com/istio/proxy" target="_blank" rel="noopener">proxy</a>  提供集群地址服务发现服务；</li><li><a href="https://github.com/istio/istio/tree/master/pilot/cmd/sidecar-injector" target="_blank" rel="noopener">sidecar-injector</a> 自动注入提供的 Webhook 服务；</li></ol><p><img src="https://camo.githubusercontent.com/919e2e3cd8e4267a00035b813df53902864a3388/68747470733a2f2f63646e2e7261776769742e636f6d2f697374696f2f70696c6f742f6d61737465722f646f632f70696c6f742e737667" alt=""></p><blockquote><p>上图为旧版本的结构图，与新版本可能有差异，但是整体架构类似</p></blockquote><p>在 istio-proxy 容器 pilot-agent 启动的命令行参数如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ps -ef -www</span><br><span class="line">istio-p+     1     0  0 Jan14 ?        00:01:00 /usr/<span class="built_in">local</span>/bin/pilot-agent proxy sidecar --configPath /etc/istio/proxy --binaryPath /usr/<span class="built_in">local</span>/bin/envoy --serviceCluster helloworld --drainDuration 45s --parentShutdownDuration 1m0s --discoveryAddress istio-pilot.istio-system:15007 --discoveryRefreshDelay 1s --zipkinAddress zipkin.istio-system:9411 --connectTimeout 10s --proxyAdminPort 15000 --controlPlaneAuthPolicy NONE</span><br><span class="line"></span><br><span class="line">istio-p+   608     1  0 Jan15 ?        03:31:24 /usr/<span class="built_in">local</span>/bin/envoy -c /etc/istio/proxy/envoy-rev13.json --restart-epoch 13 --drain-time<span class="_">-s</span> 45 --parent-shutdown-time<span class="_">-s</span> 60 --service-cluster helloworld --service-node sidecar~10.128.5.4~helloworld-v1-8f8dd85-cfz42.default~default.svc.cluster.local --max-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only</span><br></pre></td></tr></table></figure><p>其中 envoy 进程为 pilot-agent 的子进程，配置文件的初始化和启动监护由 pilot-agent 来管理。</p><h2 id="pilot-agent-命令行"><a href="#pilot-agent-命令行" class="headerlink" title="pilot-agent 命令行"></a>pilot-agent 命令行</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /usr/local/bin/pilot-agent --help</span></span><br><span class="line">Istio Pilot agent runs <span class="keyword">in</span> the side car or gateway container and bootstraps envoy.</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line">  pilot-agent [<span class="built_in">command</span>]</span><br><span class="line"></span><br><span class="line">Available Commands:</span><br><span class="line">  <span class="built_in">help</span>        Help about any <span class="built_in">command</span></span><br><span class="line">  proxy       Envoy proxy agent</span><br><span class="line">  request     Makes an HTTP request to the Envoy admin API</span><br><span class="line">  version     Prints out build version information</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line">  -h, --<span class="built_in">help</span>                          <span class="built_in">help</span> <span class="keyword">for</span> pilot-agent</span><br><span class="line">      --log_as_json                   Whether to format output as JSON or <span class="keyword">in</span> plain console-friendly format</span><br><span class="line">      --log_caller string             Comma-separated list of scopes <span class="keyword">for</span> <span class="built_in">which</span> to include <span class="built_in">caller</span> information, scopes can be any of [default, model]</span><br><span class="line">      --log_output_level string       Comma-separated minimum per-scope logging level of messages to output, <span class="keyword">in</span> the form of &lt;scope&gt;:&lt;level&gt;,&lt;scope&gt;:&lt;level&gt;,... <span class="built_in">where</span> scope can be one of [default, model] and level can be one of [debug, info, warn, error, none] (default <span class="string">"default:info"</span>)</span><br><span class="line">      --log_rotate string             The path <span class="keyword">for</span> the optional rotating <span class="built_in">log</span> file</span><br><span class="line">      --log_rotate_max_age int        The maximum age <span class="keyword">in</span> days of a <span class="built_in">log</span> file beyond <span class="built_in">which</span> the file is rotated (0 indicates no <span class="built_in">limit</span>) (default 30)</span><br><span class="line">      --log_rotate_max_backups int    The maximum number of <span class="built_in">log</span> file backups to keep before older files are deleted (0 indicates no <span class="built_in">limit</span>) (default 1000)</span><br><span class="line">      --log_rotate_max_size int       The maximum size <span class="keyword">in</span> megabytes of a <span class="built_in">log</span> file beyond <span class="built_in">which</span> the file is rotated (default 104857600)</span><br><span class="line">      --log_stacktrace_level string   Comma-separated minimum per-scope logging level at <span class="built_in">which</span> stack traces are captured, <span class="keyword">in</span> the form of &lt;scope&gt;:&lt;level&gt;,&lt;scope:level&gt;,... <span class="built_in">where</span> scope can be one of [default, model] and level can be one of [debug, info, warn, error, none] (default <span class="string">"default:none"</span>)</span><br><span class="line">      --log_target stringArray        The <span class="built_in">set</span> of paths <span class="built_in">where</span> to output the <span class="built_in">log</span>. This can be any path as well as the special values stdout and stderr (default [stdout])</span><br><span class="line"></span><br><span class="line">Use <span class="string">"pilot-agent [command] --help"</span> <span class="keyword">for</span> more information about a <span class="built_in">command</span>.</span><br></pre></td></tr></table></figure><p>关于子命令 proxy 的帮助内容如下：（省略了公共的参数）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">$ /usr/<span class="built_in">local</span>/bin/pilot-agent proxy --<span class="built_in">help</span></span><br><span class="line">Envoy proxy agent</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line">  pilot-agent proxy [flags]</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line">      --applicationPorts stringSlice      Ports exposed by the application. Used to determine that Envoy is configured and ready to receive traffic.</span><br><span class="line">      --availabilityZone string           Availability zone</span><br><span class="line">      --binaryPath string                 Path to the proxy binary (default <span class="string">"/usr/local/bin/envoy"</span>)</span><br><span class="line">      --bootstrapv2                       Use bootstrap v2 - DEPRECATED (default <span class="literal">true</span>)</span><br><span class="line">      --concurrency int                   number of worker threads to run</span><br><span class="line">      --configPath string                 Path to the generated configuration file directory (default <span class="string">"/etc/istio/proxy"</span>)</span><br><span class="line">      --connectTimeout duration           Connection timeout used by Envoy <span class="keyword">for</span> supporting services (default 1s)</span><br><span class="line">      --controlPlaneAuthPolicy string     Control Plane Authentication Policy (default <span class="string">"NONE"</span>)</span><br><span class="line">      --customConfigFile string           Path to the custom configuration file</span><br><span class="line">      --disableInternalTelemetry          Disable internal telemetry</span><br><span class="line">      --discoveryAddress string           Address of the discovery service exposing xDS (e.g. istio-pilot:8080) (default <span class="string">"istio-pilot:15007"</span>)</span><br><span class="line">      --discoveryRefreshDelay duration    Polling interval <span class="keyword">for</span> service discovery (used by EDS, CDS, LDS, but not RDS) (default 1s)</span><br><span class="line">      --domain string                     DNS domain suffix. If not provided uses <span class="variable">$&#123;POD_NAMESPACE&#125;</span>.svc.cluster.local</span><br><span class="line">      --drainDuration duration            The time <span class="keyword">in</span> seconds that Envoy will drain connections during a hot restart (default 2s)</span><br><span class="line">  -h, --<span class="built_in">help</span>                              <span class="built_in">help</span> <span class="keyword">for</span> proxy</span><br><span class="line">      --id string                         Proxy unique ID. If not provided uses <span class="variable">$&#123;POD_NAME&#125;</span>.<span class="variable">$&#123;POD_NAMESPACE&#125;</span> from environment variables</span><br><span class="line">      --ip string                         Proxy IP address. If not provided uses <span class="variable">$&#123;INSTANCE_IP&#125;</span> environment variable.</span><br><span class="line">      --parentShutdownDuration duration   The time <span class="keyword">in</span> seconds that Envoy will <span class="built_in">wait</span> before shutting down the parent process during a hot restart (default 3s)</span><br><span class="line">      --proxyAdminPort uint16             Port on <span class="built_in">which</span> Envoy should listen <span class="keyword">for</span> administrative commands (default 15000)</span><br><span class="line">      --proxyLogLevel string              The <span class="built_in">log</span> level used to start the Envoy proxy (choose from &#123;trace, debug, info, warn, err, critical, off&#125;) (default <span class="string">"warn"</span>)</span><br><span class="line">      --serviceCluster string             Service cluster (default <span class="string">"istio-proxy"</span>)</span><br><span class="line">      --serviceregistry string            Select the platform <span class="keyword">for</span> service registry, options are &#123;Kubernetes, Consul, CloudFoundry, Mock, Config&#125; (default <span class="string">"Kubernetes"</span>)</span><br><span class="line">      --statsdUdpAddress string           IP Address and Port of a statsd UDP listener (e.g. 10.75.241.127:9125)</span><br><span class="line">      --statusPort uint16                 HTTP Port on <span class="built_in">which</span> to serve pilot agent status. If zero, agent status will not be provided.</span><br><span class="line">      --templateFile string               Go template bootstrap config</span><br><span class="line">      --zipkinAddress string              Address of the Zipkin service (e.g. zipkin:9411)</span><br></pre></td></tr></table></figure><p>如果启动方式为 <code>/usr/local/bin/pilot-agent proxy</code> 则后面可以选的启动类型分为：</p><ul><li><p>pilot-agent proxy sidecar</p></li><li><p>pilot-agent proxy router， 用在 istio-gateway 方式下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ingressgateway 或者 egressgateway</span></span><br><span class="line">$ ps -ef -www</span><br><span class="line">UID        PID  PPID  C STIME TTY          TIME CMD</span><br><span class="line">root         1     0  0 Jan15 ?        00:00:52 /usr/<span class="built_in">local</span>/bin/pilot-agent proxy router -v 2 --discoveryRefreshDelay 1s --drainDuration 45s --parentShutdownDuration 1m0s --connectTimeout 10s --serviceCluster istio-ingressgateway --zipkinAddress zipkin:9411 --proxyAdminPort 15000 --controlPlaneAuthPolicy NONE --discoveryAddress istio-pilot:8080</span><br><span class="line">root        64     1  0 Jan15 ?        03:37:59 /usr/<span class="built_in">local</span>/bin/envoy -c /etc/istio/proxy/envoy-rev1.json --restart-epoch 1 --drain-time<span class="_">-s</span> 45 --parent-shutdown-time<span class="_">-s</span> 60 --service-cluster istio-ingressgateway --service-node router~10.128.45.4~istio-ingressgateway-78c6d8b8d7-sxvpx.istio-system~istio-system.svc.cluster.local --max-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only</span><br></pre></td></tr></table></figure></li><li><p>pilot-agent proxy ingress，仅用于 ingress 模式下</p></li></ul><h2 id="pilot-agent-代码流程分析"><a href="#pilot-agent-代码流程分析" class="headerlink" title="pilot-agent 代码流程分析"></a>pilot-agent 代码流程分析</h2><p>pilot-agent 需要监视相关的证书。</p><ul><li>sidecar模式，监视 <code>/etc/certs/</code> 目录下的 <code>cert-chain.pem/key.pem/root-cert.pem</code> 三个文件；</li><li>ingress 模式，监控 <code>/etc/istio/ingress-certs/</code> 目录下的 <code>tls.crt/tls.key</code> 两个文件；</li></ul><p>在 pilot-agent 启动的初始日志里面，会打印出来当前的配置和监控的证书，如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># kubectl logs helloworld-v1-8f8dd85-cfz42 -c istio-proxy |more</span></span><br><span class="line">2019-01-14T06:35:21.726068ZinfoVersion root@6f6ea1061f2b-docker.io/istio-1.0.5-c1707e45e71c75d74bf3a5dec8c7086f32f32fad-Clean</span><br><span class="line">2019-01-14T06:35:21.726179ZinfoProxy role: model.Proxy&#123;ClusterID:<span class="string">""</span>, Type:<span class="string">"sidecar"</span>, IPAddress:<span class="string">"10.128.5.4"</span>, ID:<span class="string">"helloworld-v1-8f8dd8</span></span><br><span class="line"><span class="string">5-cfz42.default"</span>, Domain:<span class="string">"default.svc.cluster.local"</span>, Metadata:map[string]string(nil)&#125;</span><br><span class="line">2019-01-14T06:35:21.726865ZinfoEffective config: binaryPath: /usr/<span class="built_in">local</span>/bin/envoy</span><br><span class="line">configPath: /etc/istio/proxy</span><br><span class="line">connectTimeout: 10s</span><br><span class="line">discoveryAddress: istio-pilot.istio-system:15007</span><br><span class="line">discoveryRefreshDelay: 1s</span><br><span class="line">drainDuration: 45s</span><br><span class="line">parentShutdownDuration: 60s</span><br><span class="line">proxyAdminPort: 15000</span><br><span class="line">serviceCluster: helloworld</span><br><span class="line">zipkinAddress: zipkin.istio-system:9411</span><br><span class="line"></span><br><span class="line">2019-01-14T06:35:21.726902ZinfoMonitored certs: []envoy.CertSource&#123;envoy.CertSource&#123;Directory:<span class="string">"/etc/certs/"</span>, Files:[]string&#123;<span class="string">"cert-cha</span></span><br><span class="line"><span class="string">in.pem"</span>, <span class="string">"key.pem"</span>, <span class="string">"root-cert.pem"</span>&#125;&#125;&#125;</span><br><span class="line">2019-01-14T06:35:21.727115ZinfoStarting proxy agent</span><br><span class="line">2019-01-14T06:35:21.728269ZinfoReceived new config, resetting budget</span><br><span class="line">2019-01-14T06:35:21.728587ZinfoReconciling configuration (budget 10)</span><br><span class="line">2019-01-14T06:35:21.728626ZinfoEpoch 0 starting</span><br><span class="line">2019-01-14T06:35:21.729334ZinfoEnvoy <span class="built_in">command</span>: [-c /etc/istio/proxy/envoy-rev0.json --restart-epoch 0 --drain-time<span class="_">-s</span> 45 --parent-shutd</span><br><span class="line">own-time<span class="_">-s</span> 60 --service-cluster helloworld --service-node sidecar~10.128.5.4~helloworld-v1-8f8dd85-cfz42.default~default.svc.cluster.local --m</span><br><span class="line">ax-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only]</span><br></pre></td></tr></table></figure><p>本文主要分析 proxy sidecar 方式下的主要逻辑：</p><p><img src="https://www.do1618.com/wp-content/uploads/2019/02/pilot-agent-arch.png" alt="image-20190201172553563"></p><p><a href="https://blog.envoyproxy.io/envoy-hot-restart-1d16b14555b5" target="_blank" rel="noopener">Envoy hot restart</a></p><p>主函数入口：</p><p>istio.io/istio/pilot/cmd/pilot-agent/main.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := rootCmd.Execute(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Errora(err)</span><br><span class="line">os.Exit(<span class="number">-1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于使用的命令行为  <code>pilot-agent proxy sidecar</code>，最终调用的子命令的函数入口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">proxyCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">"proxy"</span>,</span><br><span class="line">Short: <span class="string">"Envoy proxy agent"</span>,</span><br><span class="line">RunE: <span class="function"><span class="keyword">func</span><span class="params">(c *cobra.Command, args []<span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">           <span class="comment">// 用于设置默认配置文件的默认配置相关参数</span></span><br><span class="line">           proxyConfig := model.DefaultProxyConfig()</span><br><span class="line"></span><br><span class="line"><span class="comment">// set all flags</span></span><br><span class="line">proxyConfig.CustomConfigFile = customConfigFile</span><br><span class="line">proxyConfig.ConfigPath = configPath</span><br><span class="line">proxyConfig.BinaryPath = binaryPath</span><br><span class="line">proxyConfig.ServiceCluster = serviceCluster</span><br><span class="line">proxyConfig.DrainDuration = types.DurationProto(drainDuration)</span><br><span class="line">proxyConfig.ParentShutdownDuration = types.DurationProto(parentShutdownDuration)</span><br><span class="line">proxyConfig.DiscoveryAddress = discoveryAddress</span><br><span class="line">proxyConfig.ConnectTimeout = types.DurationProto(connectTimeout)</span><br><span class="line">proxyConfig.StatsdUdpAddress = statsdUDPAddress</span><br><span class="line">proxyConfig.ProxyAdminPort = <span class="keyword">int32</span>(proxyAdminPort)</span><br><span class="line">proxyConfig.Concurrency = <span class="keyword">int32</span>(concurrency)</span><br><span class="line">           </span><br><span class="line">           </span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">           <span class="comment">// 1. 启动 status server</span></span><br><span class="line">           <span class="comment">// If a status port was provided, start handling status probes.</span></span><br><span class="line"><span class="keyword">if</span> statusPort &gt; <span class="number">0</span> &#123;</span><br><span class="line">parsedPorts, err := parseApplicationPorts()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">statusServer := status.NewServer(status.Config&#123;</span><br><span class="line">AdminPort:        proxyAdminPort,</span><br><span class="line">StatusPort:       statusPort,</span><br><span class="line">ApplicationPorts: parsedPorts,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">go</span> statusServer.Run(ctx)</span><br><span class="line">&#125;</span><br><span class="line">           </span><br><span class="line">           <span class="comment">// 初始化 envoyProxy 对象</span></span><br><span class="line">envoyProxy := envoy.NewProxy(proxyConfig, role.ServiceNode(), proxyLogLevel, pilotSAN, role.IPAddresses)</span><br><span class="line"></span><br><span class="line">agent := proxy.NewAgent(envoyProxy, proxy.DefaultRetry)</span><br><span class="line">watcher := envoy.NewWatcher(certs, agent.ConfigCh())</span><br><span class="line"></span><br><span class="line">           <span class="comment">// 2. 启动 agent </span></span><br><span class="line"><span class="keyword">go</span> agent.Run(ctx)</span><br><span class="line">           </span><br><span class="line">           <span class="comment">// 3. 启动 watcher</span></span><br><span class="line"><span class="keyword">go</span> watcher.Run(ctx)</span><br><span class="line"></span><br><span class="line">           <span class="comment">// 4. 主 goroutine 等待信号量</span></span><br><span class="line">stop := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">cmd.WaitSignal(stop)</span><br><span class="line">&lt;-stop</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h3 id="status-server"><a href="#status-server" class="headerlink" title="status server"></a>status server</h3><p>如果 statusPort 端口进行了设置，则会启动  statusServer。</p><ul><li>对于 ready 检查，调用的路径为<code>/healthz/ready</code>， 并配合设置的端口 <code>applicationPorts</code> 通过 envoy 的 admin 端口进行对应的端口进行检查，用于决定 envoy 是否已经 ready 接受相对应的流量。</li></ul><blockquote><p>–statusPort uint16                 HTTP Port on which to serve pilot agent status. If zero, agent status will not be provided.</p></blockquote><blockquote><p>–applicationPorts stringSlice      Ports exposed by the application. Used to determine that Envoy is configured and ready to receive traffic.</p></blockquote><p>检查原理是通过本地管理端口，如 <code>http://127.0.0.1:15000/listeners</code> 获取 envoy 当前监听的全部端口，然后将配置的端口 <code>applicationPorts</code> 在监听的端口中进行查找，来决定 envoy 是否 ready。</p><ul><li><p>应用端口检查 </p><p>检查的路径为 <code>/url</code> 路径，在 header 中设置 <code>istio-app-probe-port</code> 端口，使用 访问路径中的 <code>url</code> 来进行检查，最终调用的是 <code>http://127.0.0.1:istio-app-probe-port/url</code>，头部设置的全部参数也都会传递到别检测的服务端口上；</p></li></ul><h3 id="agent"><a href="#agent" class="headerlink" title="agent"></a>agent</h3><p>agent 的函数入口代码位于 <code>istio.io/istio/pilot/pkg/proxy/agent.go</code> 文件中。</p><p>interface 定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Agent <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// ConfigCh returns the config channel used to send configuration updates.</span></span><br><span class="line"><span class="comment">// Agent compares the current active configuration to the desired state and</span></span><br><span class="line"><span class="comment">// initiates a restart if necessary. If the restart fails, the agent attempts</span></span><br><span class="line"><span class="comment">// to retry with an exponential back-off.</span></span><br><span class="line">ConfigCh() <span class="keyword">chan</span>&lt;- <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Run starts the agent control loop and awaits for a signal on the input</span></span><br><span class="line"><span class="comment">// channel to exit the loop.</span></span><br><span class="line">Run(ctx context.Context)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>agent 结构定义为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> agent <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// proxy commands</span></span><br><span class="line">proxy Proxy</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 记录 envoy 重启的各种参数，包括 time、budget、MaxRetries 和 InitialInterval 间隔</span></span><br><span class="line">    <span class="comment">// 对于异常退出的进程，一般来说抢救 10 次，中间使用退步算法，如果 10 次仍然不好，</span></span><br><span class="line">    <span class="comment">// 则会退出 proxy 容器，启动新的容器</span></span><br><span class="line"><span class="comment">// retry configuration</span></span><br><span class="line">retry Retry</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 期望使用的配置文件，当前为对应证书的 sha256 的值</span></span><br><span class="line"><span class="comment">// desired configuration state</span></span><br><span class="line">desiredConfig <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 用来保存全部对应的 epoch 对应的证书 sha256 的值</span></span><br><span class="line"><span class="comment">// active epochs and their configurations</span></span><br><span class="line">epochs <span class="keyword">map</span>[<span class="keyword">int</span>]<span class="keyword">interface</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当前使用的配置文件，为对应证书的 sha256 的值</span></span><br><span class="line"><span class="comment">// current configuration is the highest epoch configuration</span></span><br><span class="line">currentConfig <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 读取从 watcher 监护到证书变化的 channel</span></span><br><span class="line"><span class="comment">// channel for posting desired configurations</span></span><br><span class="line">configCh <span class="keyword">chan</span> <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 用于监护管理 Envoy 的 channel</span></span><br><span class="line"><span class="comment">// channel for proxy exit notifications</span></span><br><span class="line">statusCh <span class="keyword">chan</span> exitStatus</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 记录 epoch 对应的  abortCh channel，当前最大为10个，最大允许10个正在重启中的 proxy </span></span><br><span class="line"><span class="comment">// channel for aborting running instances</span></span><br><span class="line">abortCh <span class="keyword">map</span>[<span class="keyword">int</span>]<span class="keyword">chan</span> error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>agent  接口体中外部的控制主要是通过 channel 来实现的：</p><ul><li><p>configCh 用于接受到是否有配置文件发生变化，当前主要是有 watcher goroutine 来监视相关的证书，如果证书发生了变化或者定时（当前为10s），configCh 就会节后到 watcher 发送的 sha256 摘要值；</p></li><li><p>statusCh 用于管理启动 envoy 后的状态通道，用于监视 envoy 进程的状态；</p></li><li><p>proxy 对象则是实现了对于 envoy 管理的主要工作，在 proxyCmd 的函数中初始化：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">envoyProxy := envoy.NewProxy(proxyConfig, role.ServiceNode(), proxyLogLevel, pilotSAN, role.IPAddresses)</span><br></pre></td></tr></table></figure></li></ul><p>其中 Proxy 为接口定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Proxy defines command interface for a proxy</span></span><br><span class="line"><span class="keyword">type</span> Proxy <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// Run command for a config, epoch, and abort channel</span></span><br><span class="line">Run(<span class="keyword">interface</span>&#123;&#125;, <span class="keyword">int</span>, &lt;-<span class="keyword">chan</span> error) error</span><br><span class="line"></span><br><span class="line"><span class="comment">// Cleanup command for an epoch</span></span><br><span class="line">Cleanup(<span class="keyword">int</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Panic command is invoked with the desired config when all retries to</span></span><br><span class="line"><span class="comment">// start the proxy fail just before the agent terminating</span></span><br><span class="line">Panic(<span class="keyword">interface</span>&#123;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>agent 的主入口函数为: </p><p>istio.io/istio/pilot/pkg/proxy/agent.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(a *agent)</span> <span class="title">Run</span><span class="params">(ctx context.Context)</span></span> &#123;</span><br><span class="line">log.Info(<span class="string">"Starting proxy agent"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Throttle processing up to smoothed 1 qps with bursts up to 10 qps.</span></span><br><span class="line"><span class="comment">// High QPS is needed to process messages on all channels.</span></span><br><span class="line">rateLimiter := rate.NewLimiter(<span class="number">1</span>, <span class="number">10</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> reconcileTimer *time.Timer</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">err := rateLimiter.Wait(ctx)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">a.terminate()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// maximum duration or duration till next restart</span></span><br><span class="line"><span class="keyword">var</span> delay time.Duration = <span class="number">1</span>&lt;&lt;<span class="number">63</span> - <span class="number">1</span></span><br><span class="line"><span class="keyword">if</span> a.retry.restart != <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="comment">// 如果设置了下次重启的时间间隔，则 delay 设置为该值</span></span><br><span class="line">delay = time.Until(*a.retry.restart)</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 停止原有的 reconcileTimer， 并设置成当前的 delay 值</span></span><br><span class="line"><span class="keyword">if</span> reconcileTimer != <span class="literal">nil</span> &#123;</span><br><span class="line">reconcileTimer.Stop()</span><br><span class="line">&#125;</span><br><span class="line">reconcileTimer = time.NewTimer(delay)</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line">        <span class="comment">//  1. 如果相关的配置发生了变化，如果没有变化则忽略</span></span><br><span class="line"><span class="keyword">case</span> config := &lt;-a.configCh:</span><br><span class="line"><span class="keyword">if</span> !reflect.DeepEqual(a.desiredConfig, config) &#123;</span><br><span class="line">log.Infof(<span class="string">"Received new config, resetting budget"</span>)</span><br><span class="line">a.desiredConfig = config</span><br><span class="line"></span><br><span class="line"><span class="comment">// reset retry budget if and only if the desired config changes</span></span><br><span class="line">                <span class="comment">// 因为配置发生了变化，把下一次重启时间间隔设置为最大</span></span><br><span class="line">a.retry.budget = a.retry.MaxRetries</span><br><span class="line">a.reconcile()</span><br><span class="line">&#125;</span><br><span class="line">            </span><br><span class="line">        <span class="comment">// 默认的重试策略值为 </span></span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">DefaultRetry = Retry&#123;</span></span><br><span class="line"><span class="comment">MaxRetries:      10,</span></span><br><span class="line"><span class="comment">InitialInterval: 200 * time.Millisecond,</span></span><br><span class="line"><span class="comment">&#125;*/</span></span><br><span class="line">            </span><br><span class="line">        <span class="comment">// 2. 如果 proxy-envoy 的状态发生了变化</span></span><br><span class="line"><span class="keyword">case</span> status := &lt;-a.statusCh:</span><br><span class="line"><span class="comment">// delete epoch record and update current config</span></span><br><span class="line"><span class="comment">// avoid self-aborting on non-abort error</span></span><br><span class="line"><span class="built_in">delete</span>(a.epochs, status.epoch)</span><br><span class="line"><span class="built_in">delete</span>(a.abortCh, status.epoch)</span><br><span class="line">a.currentConfig = a.epochs[a.latestEpoch()]</span><br><span class="line"></span><br><span class="line">            <span class="comment">// errAbort 为被正常取消情况下的退出，比如 &lt;-ctx.Done() 情况下调用 a.terminate()</span></span><br><span class="line"><span class="keyword">if</span> status.err == errAbort &#123;</span><br><span class="line">log.Infof(<span class="string">"Epoch %d aborted"</span>, status.epoch)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> status.err != <span class="literal">nil</span> &#123; <span class="comment">// 异常情况下的退出</span></span><br><span class="line">log.Warnf(<span class="string">"Epoch %d terminated with an error: %v"</span>, status.epoch, status.err)</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">NOTE:</span> due to Envoy hot restart race conditions, an error from the</span></span><br><span class="line"><span class="comment">// process requires aggressive non-graceful restarts by killing all</span></span><br><span class="line"><span class="comment">// existing proxy instances</span></span><br><span class="line">                <span class="comment">//  Envoy热重启竞争条件，进程中的错误需要通过终止所有现有代理实例来进行积极的非正常重启</span></span><br><span class="line">a.abortAll()</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 正常情况下的退出</span></span><br><span class="line">log.Infof(<span class="string">"Epoch %d exited normally"</span>, status.epoch)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// cleanup for the epoch</span></span><br><span class="line">            <span class="comment">// 删除当前 epoch 对应的配置文件</span></span><br><span class="line">a.proxy.Cleanup(status.epoch)</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 设置出错后的重试，由于 proxy 可能已中止，因此当前配置可能已过期。当前配置</span></span><br><span class="line">            <span class="comment">// 将在中止时更改，因此在中止之前重试将不会进行。</span></span><br><span class="line">            <span class="comment">// 如果重新启动的计划尚未安排，需要重新安排相关重启。</span></span><br><span class="line"><span class="keyword">if</span> status.err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// skip retrying twice by checking retry restart delay</span></span><br><span class="line">                <span class="comment">// a.retry.restart nil 表示还未安排相关的重启进程</span></span><br><span class="line"><span class="keyword">if</span> a.retry.restart == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">if</span> a.retry.budget &gt; <span class="number">0</span> &#123;</span><br><span class="line">delayDuration := a.retry.InitialInterval * (<span class="number">1</span> &lt;&lt; <span class="keyword">uint</span>(a.retry.MaxRetries-a.retry.budget))</span><br><span class="line">restart := time.Now().Add(delayDuration)</span><br><span class="line">a.retry.restart = &amp;restart</span><br><span class="line">a.retry.budget = a.retry.budget - <span class="number">1</span></span><br><span class="line">log.Infof(<span class="string">"Epoch %d: set retry delay to %v, budget to %d"</span>, status.epoch, delayDuration, a.retry.budget)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="comment">// 耗费了所有的重启次数尝试，仍然不能正常启动，退出容器</span></span><br><span class="line">log.Error(<span class="string">"Permanent error: budget exhausted trying to fulfill the desired configuration"</span>)</span><br><span class="line">a.proxy.Panic(status.epoch)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123; <span class="comment">// 重启已经安排过了</span></span><br><span class="line">log.Debugf(<span class="string">"Epoch %d: restart already scheduled"</span>, status.epoch)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. reconcileTimer 时间到了</span></span><br><span class="line"><span class="keyword">case</span> &lt;-reconcileTimer.C:</span><br><span class="line">a.reconcile()</span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> _, more := &lt;-ctx.Done():</span><br><span class="line"><span class="keyword">if</span> !more &#123; <span class="comment">// 表明被关闭了</span></span><br><span class="line">a.terminate()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简化一下为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="comment">// 根据当前的重启策略设置 reconcileTimer 定时器的时间</span></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 接收到的配置如果和当前使用的而配置不相同，则调用, a.reconcile(); 相同则忽略</span></span><br><span class="line"><span class="keyword">case</span> config := &lt;-a.configCh:</span><br><span class="line">a.reconcile()</span><br><span class="line">   </span><br><span class="line">   <span class="comment">// 检测各种错误值，如果是特定的 errAbort 退出或者 根据退出的各种参数检查判断是预期安排的重启</span></span><br><span class="line">   <span class="comment">// 还是异常退出；同时根据重启的策略设置相关的重启策略，在后续的循环中设置 reconcileTimer 时间</span></span><br><span class="line">   <span class="keyword">case</span> status := &lt;-a.statusCh:</span><br><span class="line">        <span class="comment">// 非预期错误的错误处理</span></span><br><span class="line">        <span class="comment">// 如果是重启策略失效了，则直接退出当前循环</span></span><br><span class="line">        <span class="comment">// 特定的重启策略内，设置下次重启的时间</span></span><br><span class="line">        <span class="comment">// 特定的重启策略内，设置下次重启的时间</span></span><br><span class="line">        </span><br><span class="line">   <span class="comment">// 设置的重启时间到达</span></span><br><span class="line">   <span class="keyword">case</span> &lt;-reconcileTimer.C:</span><br><span class="line">a.reconcile()</span><br><span class="line">    </span><br><span class="line">   <span class="comment">// 如果是取消，则全部退出</span></span><br><span class="line">   <span class="keyword">case</span> _, more := &lt;-ctx.Done():</span><br><span class="line">  <span class="keyword">if</span> !more &#123;</span><br><span class="line">a.terminate()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>在配置发生变化或者异常重启设置重启策略后，最终调用的函数为 <code>reconcile</code>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(a *agent)</span> <span class="title">reconcile</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// cancel any scheduled restart</span></span><br><span class="line">a.retry.restart = <span class="literal">nil</span></span><br><span class="line"></span><br><span class="line">log.Infof(<span class="string">"Reconciling retry (budget %d)"</span>, a.retry.budget)</span><br><span class="line"></span><br><span class="line"><span class="comment">// check that the config is current</span></span><br><span class="line"><span class="keyword">if</span> reflect.DeepEqual(a.desiredConfig, a.currentConfig) &#123;</span><br><span class="line">log.Infof(<span class="string">"Desired configuration is already applied"</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// discover and increment the latest running epoch</span></span><br><span class="line">epoch := a.latestEpoch() + <span class="number">1</span></span><br><span class="line"><span class="comment">// buffer aborts to prevent blocking on failing proxy</span></span><br><span class="line">abortCh := <span class="built_in">make</span>(<span class="keyword">chan</span> error, maxAborts)</span><br><span class="line">a.epochs[epoch] = a.desiredConfig</span><br><span class="line">a.abortCh[epoch] = abortCh</span><br><span class="line">a.currentConfig = a.desiredConfig</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 最终的调用，会将相关相关结果放到 abortCh channel 中</span></span><br><span class="line"><span class="keyword">go</span> a.runWait(a.desiredConfig, epoch, abortCh)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>reconcile</code> 设置相关参数后，最终启动一个新的 goroutine 来进行启动最终的程序</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// runWait runs the start-up command as a go routine and waits for it to finish</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(a *agent)</span> <span class="title">runWait</span><span class="params">(config <span class="keyword">interface</span>&#123;&#125;, epoch <span class="keyword">int</span>, abortCh &lt;-<span class="keyword">chan</span> error)</span></span> &#123;</span><br><span class="line">log.Infof(<span class="string">"Epoch %d starting"</span>, epoch)</span><br><span class="line">err := a.proxy.Run(config, epoch, abortCh)</span><br><span class="line">a.statusCh &lt;- exitStatus&#123;epoch: epoch, err: err&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>runWait</code> 函数中，最终调用 <code>a.proxy.Run(config, epoch, abortCh)</code>，并将其返回的错误值放到 agent 的 <code>statusCh</code> channel 中。</p><p>对于 envoy 的启动过程可以通过 <code>proxy.run</code> 来进行总结分析： </p><p>envoy 的结构体定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> envoy <span class="keyword">struct</span> &#123;</span><br><span class="line">config    meshconfig.ProxyConfig  <span class="comment">// 配置文件</span></span><br><span class="line">node      <span class="keyword">string</span></span><br><span class="line">extraArgs []<span class="keyword">string</span></span><br><span class="line">pilotSAN  []<span class="keyword">string</span></span><br><span class="line">opts      <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">errChan   <span class="keyword">chan</span> error</span><br><span class="line">nodeIPs   []<span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>istio.io/istio/pilot/pkg/proxy/envoy/proxy.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *envoy)</span> <span class="title">Run</span><span class="params">(config <span class="keyword">interface</span>&#123;&#125;, epoch <span class="keyword">int</span>, abort &lt;-<span class="keyword">chan</span> error)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> fname <span class="keyword">string</span></span><br><span class="line"><span class="comment">// Note: the cert checking still works, the generated file is updated if certs are changed.</span></span><br><span class="line"><span class="comment">// We just don't save the generated file, but use a custom one instead. Pilot will keep</span></span><br><span class="line"><span class="comment">// monitoring the certs and restart if the content of the certs changes.</span></span><br><span class="line">    <span class="comment">// 1. 如果指定了模板文件，则使用用户指定的，否则则使用默认的</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(e.config.CustomConfigFile) &gt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// there is a custom configuration. Don't write our own config - but keep watching the certs.</span></span><br><span class="line">fname = e.config.CustomConfigFile</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">out, err := bootstrap.WriteBootstrap(&amp;e.config, e.node, epoch, e.pilotSAN, e.opts, os.Environ(), e.nodeIPs)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Errora(<span class="string">"Failed to generate bootstrap config"</span>, err)</span><br><span class="line">os.Exit(<span class="number">1</span>) <span class="comment">// Prevent infinite loop attempting to write the file, let k8s/systemd report</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">fname = out</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// spin up a new Envoy process</span></span><br><span class="line">args := e.args(fname, epoch)</span><br><span class="line">log.Infof(<span class="string">"Envoy command: %v"</span>, args)</span><br><span class="line"></span><br><span class="line"><span class="comment">/* #nosec */</span></span><br><span class="line">cmd := exec.Command(e.config.BinaryPath, args...)</span><br><span class="line">cmd.Stdout = os.Stdout</span><br><span class="line">cmd.Stderr = os.Stderr</span><br><span class="line"><span class="keyword">if</span> err := cmd.Start(); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set if the caller is monitoring envoy, for example in tests or if envoy runs in same</span></span><br><span class="line"><span class="comment">// container with the app.</span></span><br><span class="line"><span class="keyword">if</span> e.errChan != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// Caller passed a channel, will wait itself for termination</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">e.errChan &lt;- cmd.Wait()</span><br><span class="line">&#125;()</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过 done channel 来获取 evnoy 启动的最终状态</span></span><br><span class="line">done := <span class="built_in">make</span>(<span class="keyword">chan</span> error, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">done &lt;- cmd.Wait()</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 等待 abort channel 和 done，用于结束 envoy 和正确返回当前的启动状态</span></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> err := &lt;-abort:</span><br><span class="line">log.Warnf(<span class="string">"Aborting epoch %d"</span>, epoch)</span><br><span class="line"><span class="keyword">if</span> errKill := cmd.Process.Kill(); errKill != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Warnf(<span class="string">"killing epoch %d caused an error %v"</span>, epoch, errKill)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line"><span class="keyword">case</span> err := &lt;-done:</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>函数 <code>bootstrap.WriteBootstrap</code> 用来生成 envoy 使用的初始配置文件，默认的配置模板文件路径为 <code>/var/lib/istio/envoy/envoy_bootstrap_tmpl.json</code>，该文件也可以通过参数 <code>templateFile</code> 传递进来。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// istio.io/istio/pkg/bootstrap/bootstrap_config.go</span></span><br><span class="line">DefaultCfgDir     = <span class="string">"/var/lib/istio/envoy/envoy_bootstrap_tmpl.json"</span></span><br></pre></td></tr></table></figure><p>整体内容参见 <a href="https://gist.github.com/DavadDi/be65927559c4d9cef023cf1e63918079#file-envoy_bootstrap_tmpl-json" target="_blank" rel="noopener">file-envoy_bootstrap_tmpl-json</a></p><p>envoy_bootstrap_tmpl.json + meshconfig.ProxyConfig + epoch = envoy 初始化文件，文件名格式为 ”envoy-rev%d.json“， 其中 <code>%d</code> 会被替换成 <code>epoch</code> 的值。</p><p>在 envoy 默认配置输出成功以后，就接着构造 envoy 启动的参数，主要函数如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *envoy)</span> <span class="title">args</span><span class="params">(fname <span class="keyword">string</span>, epoch <span class="keyword">int</span>)</span> []<span class="title">string</span></span> &#123;</span><br><span class="line">startupArgs := []<span class="keyword">string</span>&#123;<span class="string">"-c"</span>, fname,</span><br><span class="line"><span class="string">"--restart-epoch"</span>, fmt.Sprint(epoch),</span><br><span class="line"><span class="string">"--drain-time-s"</span>, fmt.Sprint(<span class="keyword">int</span>(convertDuration(e.config.DrainDuration) / time.Second)),</span><br><span class="line"><span class="string">"--parent-shutdown-time-s"</span>, fmt.Sprint(<span class="keyword">int</span>(convertDuration(e.config.ParentShutdownDuration) / time.Second)),</span><br><span class="line"><span class="string">"--service-cluster"</span>, e.config.ServiceCluster,</span><br><span class="line"><span class="string">"--service-node"</span>, e.node,</span><br><span class="line"><span class="string">"--max-obj-name-len"</span>, fmt.Sprint(e.config.StatNameLength),</span><br><span class="line"><span class="string">"--allow-unknown-fields"</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">startupArgs = <span class="built_in">append</span>(startupArgs, e.extraArgs...)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> e.config.Concurrency &gt; <span class="number">0</span> &#123;</span><br><span class="line">startupArgs = <span class="built_in">append</span>(startupArgs, <span class="string">"--concurrency"</span>, fmt.Sprint(e.config.Concurrency))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> startupArgs</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到此为止，envoy 的配置文件和启动命令行已经构造完成，后续就需要采用 <code>exec.Command</code>  命令来进行启动。</p><p>至此 agent 对于 envoy 的启动和管理流程结束。</p><h3 id="watcher"><a href="#watcher" class="headerlink" title="watcher"></a>watcher</h3><p>watcher 的整体逻辑相对比较简单，就是 watch 相关的证书变化，当证书有变化或者定期（10s），向 agent 发送配置当前的 sha256 摘要，agent 如果发现配置已经变化，则会触发 agent 重启 envoy，并增加 epoch 的值。</p><p>istio.io/istio/pilot/pkg/proxy/envoy/watcher.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(w *watcher)</span> <span class="title">Run</span><span class="params">(ctx context.Context)</span></span> &#123;</span><br><span class="line"><span class="comment">// kick start the proxy with partial state (in case there are no notifications coming)</span></span><br><span class="line">w.SendConfig()  <span class="comment">// 用于向 agent 发送相关的摘要值</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// monitor certificates</span></span><br><span class="line">certDirs := <span class="built_in">make</span>([]<span class="keyword">string</span>, <span class="number">0</span>, <span class="built_in">len</span>(w.certs))</span><br><span class="line"><span class="keyword">for</span> _, cert := <span class="keyword">range</span> w.certs &#123;</span><br><span class="line">certDirs = <span class="built_in">append</span>(certDirs, cert.Directory)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> watchCerts(ctx, certDirs, watchFileEvents, defaultMinDelay, w.SendConfig)</span><br><span class="line"></span><br><span class="line">&lt;-ctx.Done()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>watchCerts 的函数主体：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// watchCerts watches all certificate directories and calls the provided</span></span><br><span class="line"><span class="comment">// `updateFunc` method when changes are detected. This method is blocking</span></span><br><span class="line"><span class="comment">// so it should be run as a goroutine.</span></span><br><span class="line"><span class="comment">// updateFunc will not be called more than one time per minDelay.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">watchCerts</span><span class="params">(ctx context.Context, certsDirs []<span class="keyword">string</span>, watchFileEventsFn watchFileEventsFn,</span></span></span><br><span class="line"><span class="function"><span class="params">minDelay time.Duration, updateFunc <span class="keyword">func</span>()</span>)</span> &#123;</span><br><span class="line">fw, err := fsnotify.NewWatcher()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Warnf(<span class="string">"failed to create a watcher for certificate files: %v"</span>, err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := fw.Close(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Warnf(<span class="string">"closing watcher encounters an error %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// watch all directories</span></span><br><span class="line"><span class="keyword">for</span> _, d := <span class="keyword">range</span> certsDirs &#123;</span><br><span class="line"><span class="keyword">if</span> err := fw.Watch(d); err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Warnf(<span class="string">"watching %s encounters an error %v"</span>, d, err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">watchFileEventsFn(ctx, fw.Event, minDelay, updateFunc)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于监听的相关证书，则来自于注入时从命名空间中 secret 中加载到 pod 中的文件，deployment文件如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">extensions/v1beta1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"><span class="attr">  creationTimestamp:</span> <span class="literal">null</span></span><br><span class="line"><span class="attr">  name:</span> <span class="string">helloworld-v2</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"><span class="attr">  replicas:</span> <span class="number">1</span></span><br><span class="line"><span class="attr">  strategy:</span> <span class="string">&#123;&#125;</span></span><br><span class="line"><span class="attr">  template:</span></span><br><span class="line"><span class="attr">    metadata:</span></span><br><span class="line"><span class="attr">      labels:</span></span><br><span class="line"><span class="attr">        app:</span> <span class="string">helloworld</span></span><br><span class="line"><span class="attr">        version:</span> <span class="string">v2</span></span><br><span class="line"><span class="attr">    spec:</span></span><br><span class="line"><span class="attr">      containers:</span></span><br><span class="line"><span class="attr">      - image:</span> <span class="string">istio/examples-helloworld-v2</span></span><br><span class="line"><span class="attr">        imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line"><span class="attr">        name:</span> <span class="string">helloworld</span></span><br><span class="line"><span class="attr">        ports:</span></span><br><span class="line"><span class="attr">        - containerPort:</span> <span class="number">5000</span></span><br><span class="line"><span class="attr">        resources:</span></span><br><span class="line"><span class="attr">          requests:</span></span><br><span class="line"><span class="attr">            cpu:</span> <span class="number">100</span><span class="string">m</span></span><br><span class="line"><span class="attr">      - args:</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">proxy</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">sidecar</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--configPath</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">/etc/istio/proxy</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--binaryPath</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">/usr/local/bin/envoy</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--serviceCluster</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">helloworld</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--drainDuration</span></span><br><span class="line"><span class="bullet">        -</span> <span class="number">45</span><span class="string">s</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--parentShutdownDuration</span></span><br><span class="line"><span class="bullet">        -</span> <span class="number">1</span><span class="string">m0s</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--discoveryAddress</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">istio-pilot.istio-system:15007</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--discoveryRefreshDelay</span></span><br><span class="line"><span class="bullet">        -</span> <span class="number">1</span><span class="string">s</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--zipkinAddress</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">zipkin.istio-system:9411</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--connectTimeout</span></span><br><span class="line"><span class="bullet">        -</span> <span class="number">10</span><span class="string">s</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--proxyAdminPort</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">"15000"</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">--controlPlaneAuthPolicy</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">NONE</span></span><br><span class="line"><span class="attr">        env:</span></span><br><span class="line"><span class="attr">        - name:</span> <span class="string">POD_NAME</span></span><br><span class="line"><span class="attr">          valueFrom:</span></span><br><span class="line"><span class="attr">            fieldRef:</span></span><br><span class="line"><span class="attr">              fieldPath:</span> <span class="string">metadata.name</span></span><br><span class="line"><span class="attr">        - name:</span> <span class="string">POD_NAMESPACE</span></span><br><span class="line"><span class="attr">          valueFrom:</span></span><br><span class="line"><span class="attr">            fieldRef:</span></span><br><span class="line"><span class="attr">              fieldPath:</span> <span class="string">metadata.namespace</span></span><br><span class="line"><span class="attr">        - name:</span> <span class="string">INSTANCE_IP</span></span><br><span class="line"><span class="attr">          valueFrom:</span></span><br><span class="line"><span class="attr">            fieldRef:</span></span><br><span class="line"><span class="attr">              fieldPath:</span> <span class="string">status.podIP</span></span><br><span class="line"><span class="attr">        - name:</span> <span class="string">ISTIO_META_POD_NAME</span></span><br><span class="line"><span class="attr">          valueFrom:</span></span><br><span class="line"><span class="attr">            fieldRef:</span></span><br><span class="line"><span class="attr">              fieldPath:</span> <span class="string">metadata.name</span></span><br><span class="line"><span class="attr">        - name:</span> <span class="string">ISTIO_META_INTERCEPTION_MODE</span></span><br><span class="line"><span class="attr">          value:</span> <span class="string">REDIRECT</span></span><br><span class="line"><span class="attr">        - name:</span> <span class="string">ISTIO_METAJSON_LABELS</span></span><br><span class="line"><span class="attr">          value:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            &#123;"app":"helloworld","version":"v2"&#125;</span></span><br><span class="line"><span class="string"></span><span class="attr">        image:</span> <span class="string">docker.io/istio/proxyv2:1.0.5</span></span><br><span class="line"><span class="attr">        imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line"><span class="attr">        name:</span> <span class="string">istio-proxy</span></span><br><span class="line"><span class="attr">        ports:</span></span><br><span class="line"><span class="attr">        - containerPort:</span> <span class="number">15090</span></span><br><span class="line"><span class="attr">          name:</span> <span class="string">http-envoy-prom</span></span><br><span class="line"><span class="attr">          protocol:</span> <span class="string">TCP</span></span><br><span class="line"><span class="attr">        resources:</span></span><br><span class="line"><span class="attr">          requests:</span></span><br><span class="line"><span class="attr">            cpu:</span> <span class="number">10</span><span class="string">m</span></span><br><span class="line"><span class="attr">        securityContext:</span></span><br><span class="line"><span class="attr">          readOnlyRootFilesystem:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">          runAsUser:</span> <span class="number">1337</span></span><br><span class="line"><span class="attr">        volumeMounts:</span></span><br><span class="line"><span class="attr">        - mountPath:</span> <span class="string">/etc/istio/proxy</span></span><br><span class="line"><span class="attr">          name:</span> <span class="string">istio-envoy</span></span><br><span class="line"><span class="attr">        - mountPath:</span> <span class="string">/etc/certs/</span>   <span class="comment"># 将证书挂载的目录， watcher 会监视该目录</span></span><br><span class="line"><span class="attr">          name:</span> <span class="string">istio-certs</span></span><br><span class="line"><span class="attr">          readOnly:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">      initContainers:</span></span><br><span class="line"><span class="attr">      - args:</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-p</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">"15001"</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-u</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">"1337"</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-m</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">REDIRECT</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-i</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">'*'</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-x</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">""</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-b</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">"5000"</span></span><br><span class="line"><span class="bullet">        -</span> <span class="bullet">-d</span></span><br><span class="line"><span class="bullet">        -</span> <span class="string">""</span></span><br><span class="line"><span class="attr">        image:</span> <span class="string">docker.io/istio/proxy_init:1.0.5</span></span><br><span class="line"><span class="attr">        imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line"><span class="attr">        name:</span> <span class="string">istio-init</span></span><br><span class="line"><span class="attr">        resources:</span> <span class="string">&#123;&#125;</span></span><br><span class="line"><span class="attr">        securityContext:</span></span><br><span class="line"><span class="attr">          capabilities:</span></span><br><span class="line"><span class="attr">            add:</span></span><br><span class="line"><span class="bullet">            -</span> <span class="string">NET_ADMIN</span></span><br><span class="line"><span class="attr">          privileged:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">      volumes:</span></span><br><span class="line"><span class="attr">      - emptyDir:</span></span><br><span class="line"><span class="attr">          medium:</span> <span class="string">Memory</span></span><br><span class="line"><span class="attr">        name:</span> <span class="string">istio-envoy</span></span><br><span class="line"><span class="attr">      - name:</span> <span class="string">istio-certs</span>    <span class="comment"># 来自于 istio.default 的 secret</span></span><br><span class="line"><span class="attr">        secret:</span></span><br><span class="line"><span class="attr">          optional:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">          secretName:</span> <span class="string">istio.default</span></span><br></pre></td></tr></table></figure><p>使用 kubectl 命令验证：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubectl get secrets istio.default</span></span><br><span class="line">NAME            TYPE                    DATA   AGE</span><br><span class="line">istio.default   istio.io/key-and-cert   3      17d</span><br><span class="line"></span><br><span class="line"><span class="comment"># kubectl get secrets istio.default -o yaml</span></span><br><span class="line">apiVersion: v1</span><br><span class="line">data:</span><br><span class="line">  cert-chain.pem: xxxx</span><br><span class="line">  key.pem: xxxx==</span><br><span class="line">  root-cert.pem: xxxx==</span><br><span class="line">kind: Secret</span><br><span class="line">metadata:</span><br><span class="line">  annotations:</span><br><span class="line">    istio.io/service-account.name: default</span><br><span class="line">  creationTimestamp: 2019-01-15T08:24:26Z</span><br><span class="line">  name: istio.default</span><br><span class="line">  namespace: default</span><br><span class="line">  resourceVersion: <span class="string">"16685160"</span></span><br><span class="line">  selfLink: /api/v1/namespaces/default/secrets/istio.default</span><br><span class="line">  uid: f863ce6f-189e-11e9-ab53-00163e0c1552</span><br><span class="line"><span class="built_in">type</span>: istio.io/key-and-cert</span><br></pre></td></tr></table></figure><p>证书的生成和管理是由 Citadel 负责的，具体细节可以参见 <a href="https://istio.io/help/ops/security/keys-and-certs/" target="_blank" rel="noopener">Keys and Certificates</a> ，可以使用以下命令来检查证书的详细信息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># jq 为命令下处理 json 的工具，参见 </span></span><br><span class="line"><span class="comment"># https://www.ibm.com/developerworks/cn/linux/1612_chengg_jq/index.html</span></span><br><span class="line">$ sudo yum install -y jq  </span><br><span class="line"></span><br><span class="line">$ kubectl get secret -o json istio.default | jq -r <span class="string">'.data["cert-chain.pem"]'</span> | base64 --decode | openssl x509 -noout -text</span><br><span class="line"></span><br><span class="line">Certificate:</span><br><span class="line">    Data:</span><br><span class="line">        Version: 3 (0x2)</span><br><span class="line">        Serial Number:</span><br><span class="line">            07:d1:25:13:1c:38:db:ef:89:8e:95:2e:d1:6c:b5:4d</span><br><span class="line">    Signature Algorithm: sha256WithRSAEncryption</span><br><span class="line">        Issuer: O=k8s.cluster.local</span><br><span class="line">        Validity</span><br><span class="line">            Not Before: Jan 15 08:24:26 2019 GMT</span><br><span class="line">            Not After : Apr 15 08:24:26 2019 GMT</span><br><span class="line">        Subject: O=</span><br><span class="line">        Subject Public Key Info:</span><br><span class="line">            Public Key Algorithm: rsaEncryption</span><br><span class="line">                Public-Key: (2048 bit)</span><br><span class="line">                Modulus:</span><br><span class="line">                    00:d1:13:34:58:e7:b0:6e:b5:07:0e:bd:7f:d5:a0:</span><br><span class="line">                    66:d9:4a:2a:6d:ec:bd:26:ab:22:26:31:c7:c9:48:</span><br><span class="line">                    da:57:9a:5a:91:b3:7c:78:2c:c4:8d:14:4b:b1:b4:</span><br><span class="line">                    a4:29:3d:26:d1:ad:8d:6e:6f:b0:27:64:31:93:cf:</span><br><span class="line">                    43:be:f4:04:0a:d2:0f:e6:dc:45:4a:5d:38:65:c0:</span><br><span class="line">                    08:44:25:5f:e8:2d:c3:2a:9a:6b:82:bb:27:81:59:</span><br><span class="line">                    c7:f5:38:66:b1:f2:06:eb:94:46:34:47:c5:b1:9d:</span><br><span class="line">                    01:59:04:e7:8e:df:bf:ed:17:f8:16:06:9d:85:c0:</span><br><span class="line">                    e9:43:0f:3a:a0:b6:b9:64:50:3e:e1:26:e3:03:d4:</span><br><span class="line">                    dc:43:08:ef:de:af:56:5c:d5:6c:c1:72:72:7b:f5:</span><br><span class="line">                    05:f8:09:15:08:2d:f3:5b:c7:57:7d:1a:15:72:90:</span><br><span class="line">                    3e:df:0a:e6:a1:e3:d9:81:9f:bb:9c:f2:c5:da:7f:</span><br><span class="line">                    48:a6:4d:12:f7:5e:e8:21:99:1d:f0:95:d7:c5:1a:</span><br><span class="line">                    34:d5:a7:56:79:4b:dd:82:a1:39:cc:d5:0b:e3:fa:</span><br><span class="line">                    92:04:21:38:89:41:7c:9b:12:aa:c4:5f:93:c1:1d:</span><br><span class="line">                    <span class="built_in">fc</span>:bc:5a:6d:e5:3d:98:e1:9c:28:38:58:75:c6:e3:</span><br><span class="line">                    27:5f:77:4b:10:b6:53:70:7b:25:fd:5c:44:28:67:</span><br><span class="line">                    54:51</span><br><span class="line">                Exponent: 65537 (0x10001)</span><br><span class="line">        X509v3 extensions:</span><br><span class="line">            X509v3 Key Usage: critical</span><br><span class="line">                Digital Signature, Key Encipherment</span><br><span class="line">            X509v3 Extended Key Usage:</span><br><span class="line">                TLS Web Server Authentication, TLS Web Client Authentication</span><br><span class="line">            X509v3 Basic Constraints: critical</span><br><span class="line">                CA:FALSE</span><br><span class="line">            X509v3 Subject Alternative Name:</span><br><span class="line">                URI:spiffe://cluster.local/ns/default/sa/default</span><br><span class="line">    Signature Algorithm: sha256WithRSAEncryption</span><br><span class="line">         68:fb:87:12:c6:d1:fb:c1:69:fa:ec:2e:30:ea:f7:4d:8f:9c:</span><br><span class="line">         5b:54:a1:f9:a3:5f:ff:83:3f:76:c5:d6:9c:2b:cb:55:6a:66:</span><br><span class="line">         49:b5:a2:bd:dd:71:06:7f:a4:f8:18:<span class="built_in">cd</span>:0d:7a:3f:bf:4c:56:</span><br><span class="line">         e0:35:5b:68:2d:71:72:e3:a2:7d:b9:90:f3:86:d0:1a:87:f6:</span><br><span class="line">         31:7e:24:db:00:a8:69:df:54:7f:8b:b0:1d:7d:02:03:c0:26:</span><br><span class="line">         1a:87:53:aa:e4:66:76:e9:80:1e:28:61:53:0c:53:c6:1e:80:</span><br><span class="line">         7e:b0:2d:71:75:1b:42:9f:51:f4:5c:7b:53:ee:06:02:31:5d:</span><br><span class="line">         71:2d:b5:4a:dd:58:25:a6:c4:24:ad:19:86:ac:24:87:99:ad:</span><br><span class="line">         6b:be:c8:ae:84:7e:7d:86:0a:1d:44:a0:50:62:2d:8b:d6:79:</span><br><span class="line">         ff:db:43:40:de:3b:ec:4e:2b:80:87:e5:1a:cb:1e:cf:e6:12:</span><br><span class="line">         8e:96:10:46:f7:fa:ed:1c:bb:0b:11:35:41:c8:69:43:64:79:</span><br><span class="line">         44:ea:a8:72:b7:27:2a:0d:a6:39:bb:34:b0:8b:e3:86:ba:3c:</span><br><span class="line">         1e:b9:ee:b5:61:bb:c8:65:b0:8d:bd:a1:9c:29:64:7d:0b:2c:</span><br><span class="line">         f9:9b:34:18:98:38:24:ad:85:b8:1e:59:41:09:1f:2e:a8:6d:</span><br><span class="line">         ef:ee:0d:a8</span><br></pre></td></tr></table></figure><p>特别是，Subject Alternative Name 字段应为 <code>URI:spiffe://cluster.local/ns/default/sa/default</code>。 </p><h2 id="Envoy-hot-restart"><a href="#Envoy-hot-restart" class="headerlink" title="Envoy hot restart"></a>Envoy hot restart</h2><p>经过上述代码分析，我们得知 pilot-agent 基本上就是准备 Envoy 初始配置文件和管理 Envoy 的生命周期（包括初始化启动、异常退出重启、终止），但是对于 hot restart 的过程则是完全不参与，Envoy 的 hot restart 的过程则是由 Envoy 自己来负责管理的。详细参见：<a href="https://blog.Envoyproxy.io/Envoy-hot-restart-1d16b14555b5" target="_blank" rel="noopener">Envoy hot restart</a></p><p>当前 host restart 的方式主要有以下两种：</p><ol><li>Rollling deploy，部署多个服务实例，通过切换流量的方式来逐步迁移，方案需要基础设施的配合支持；</li><li>host  restart deploy，在同一个主机或者容器内启动多个实例，由服务自身完成相关的工作；</li></ol><p>方式1提供了更安全可控的流量迁移方式，能够提供各种复杂情况下的流量迁移技巧，但是对于基础设置的要求也比较高；Envoy 采用方式2，主要原因是方式1需要基础实施的配合支持，比较重，而且在 lyft 公司内部也没有容器化，所以采用轻量方式2，让 Envoy 的 host restart 方案具备了更多的适用场景。</p><p><img src="https://www.do1618.com/wp-content/uploads/2019/02/hot_restart_methord.png" alt=""></p><p>Envoy hot restart 的目标有以下几点：</p><ol><li>整个进程 reload（不是配置）不能丢失连接；</li><li>在 reload 过程中，统计相关的信息需要保持一致；重启过程中的多个实例统计要保持步调一致；</li><li>使用基于容器的不可变部署仍然可以进行热重启； 同一个主机上不可变容器的方式只能通过共享内存和网络的方式进行；</li><li>老的进程驱逐连接的速率和销毁进度可以配置；</li></ol><p>Envoy 的 host restart 的方式，在工程方面考虑的更加周全，在保证流量不丢失且逐步驱逐的前提下，更多考虑了对于 Envoy 整体的可观测性。重启过程中的多个实例在外部看来是一个逻辑的整体，通过共享内存共享 stats 统计信息和全局 locks。多实例的进程间通过 UDS(unix domain sockets) 协议进行通信。新老实例是通过其拥有的 epoch 值来进行区分，epoch == 0 的进程负责创建共享内存。<br><img src="https://www.do1618.com/wp-content/uploads/2019/02/envoy_hot_restart_arch.png" alt=""></p><p>Envoy hot restart 过程中的交互流程如下图，即使图中的两个进程位于同一主机上两个不同的容器内，仍然能够正常工作；</p><p><img src="https://www.do1618.com/wp-content/uploads/2019/02/envoy_hot_restart_flow.png" alt=""></p><ol><li>secondary 进程要求 primary 进程关闭其管理端口。secondary 进程将接管所有管理职责，包括统计刷新。从运维人员的角度来看，两者只有一个逻辑 Envoy 进程。</li><li>secondary 加载其配置并开始绑定到侦听套接字。在此阶段，其通过 UDS 从 primary 获取 listen sockets。 （在当前的实现中，Envoy 没有使用 SO_REUSEPORT 套接字选项。这主要是历史原因，因为套接字选项仅适用于相对较新的内核。在某些时候，可以删除对旧内核的支持，我们将代码切换为使用这个套接字选项）。</li><li>一旦 secondary 进程完全初始化，其通知 primary 进程停止接受新连接并开始 drain。drain 时间可配置，默认为 15 分钟。在这15分钟内（或配置的任何时间），primary 进程将开始正常关闭连接。drain 耗尽的时间越长，关闭率就越高。实现平滑地关闭旧连接，然后重新在 secondary 进程建立新连接。</li><li>在 drain 阶段，secondary 进程负责刷新统计数据。大多数统计数据存储在共享内存中，不需要通过 RPC 获取。但是，一些特殊的统计数据仅用于 drain 过程中 primary 进程。例如，primary 进程仍然打开了多少连接以及它分配了多少内存。这些统计数据由 secondary 获取并统计，因此可以更容易地观察到 drain 速率。</li><li>最后，在 drain 时间过去之后，secondary 告诉 primary 关闭。 打开的任何剩余连接都将关闭。此时，secondary 服务器成为主服务器，并且运行一个 Envoy 进程。</li><li>重复以上过程。</li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="http://www.servicemesher.com/blog/istio-service-mesh-source-code-pilot-agent-deepin/" target="_blank" rel="noopener">istio源码分析之pilot-agent模块分析</a></li><li><a href="Istio实战系列-Envoy Proxy构建分析">Istio实战系列-Envoy Proxy构建分析</a></li><li><a href="https://juejin.im/post/5bc9ddc56fb9a05cd7777936" target="_blank" rel="noopener">Istio Proxy【Envoy扩展】详解</a></li><li><a href="https://skyao.io/publication/201811-service-mesh-step-by-step/" target="_blank" rel="noopener">蚂蚁金服Service Mesh渐进式迁移方案</a></li><li><a href="http://www.servicemesher.com/Envoy/" target="_blank" rel="noopener">Envoy 官方文档中文版</a></li><li><a href="https://blog.Envoyproxy.io/Envoy-hot-restart-1d16b14555b5" target="_blank" rel="noopener">Envoy hot restart</a></li></ol><hr><p><strong>除特别声明本站文章均属原创（翻译内容除外），如需要转载请事先联系，转载需要注明作者原文链接地址。</strong></p><hr>]]></content>
    
    <summary type="html">
    
      
      
        &lt;hr&gt;
&lt;p&gt;&lt;strong&gt;本系列链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2019/02/02/istio-source-pilot-agent/&quot; title=&quot;Istio源码系列1：pilot-agent 源码分析&quot;&gt;Istio源码系列1：
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="istio" scheme="http://www.cn18k.com/tags/istio/"/>
    
  </entry>
  
  <entry>
    <title>从百家争鸣的微服务生态到服务网格（译）</title>
    <link href="http://www.cn18k.com/2018/12/13/from-fragmented-microservices-ecosystem-to-service-mesh/"/>
    <id>http://www.cn18k.com/2018/12/13/from-fragmented-microservices-ecosystem-to-service-mesh/</id>
    <published>2018-12-13T03:00:00.000Z</published>
    <updated>2018-12-13T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>作者：<a href="https://blog.avinetworks.com/from-fragmented-microservices-ecosystem-to-service-mesh" target="_blank" rel="noopener">Manish Chugtu</a> OCTOBER 16, 2017<br>翻译：狄卫华</p><p>原文：From Fragmented Microservices Ecosystem to Service Mesh</p><p>原文链接：<a href="https://raw.githubusercontent.com/servicemesher/trans/master/201812/from-fragmented-microservices-ecosystem-to-service-mesh.md" target="_blank" rel="noopener">https://raw.githubusercontent.com/servicemesher/trans/master/201812/from-fragmented-microservices-ecosystem-to-service-mesh.md</a><br>GitHub地址： <a href="https://raw.githubusercontent.com/servicemesher/trans/master/201812/from-fragmented-microservices-ecosystem-to-service-mesh.md" target="_blank" rel="noopener">https://raw.githubusercontent.com/servicemesher/trans/master/201812/from-fragmented-microservices-ecosystem-to-service-mesh.md</a></p><p>在过去几年中，我们注意到应用程序架构正在迅速转变为分布式微服务架构——单体和庞大的应用程序被分解为更小的单个服务，其可被独立修改、构建、部署和管理。这种模式的主要优点就是简洁和快速，同时由于其对其他服务的依赖性很小或者完全没有依赖，更易于升级和独立扩展。这与敏捷和DevOps理念非常吻合，这种模式也已经被许多规模化的Web公司成功采用。过去的许多年中，这些公司中的大多数都能够很好地采用这种模式，但是近几年中成功将这种模式发扬光大的两大推手非Docker和Kubernetes莫属。Docker简化了将微服务构建为Linux容器的过程，Kubernetes则能够以资源优化的方式来部署、管理和扩展服务。</p><h2 id="应用架构演进"><a href="#应用架构演进" class="headerlink" title="应用架构演进"></a>应用架构演进</h2><p>在这篇博客中，我们不会花太多时间讨论微服务架构的优缺点。相反，我们将专注于在向基于微服务构建的云原生架构的重大转变上。</p><p>虽然微服务架构提供了灵活性，但其也带有复杂性。Kubernetes在部署和管理微服务方面发挥了非常重要的作用，但我们需要的不仅仅是单一的运行在生产环境中的云原生应用程序——还需要在服务发现、安全性、流量管理等方面需要更加深入的了解。尤其是在相互通信的成千上百个服务经常被删除、生产、扩展和更新的复杂环境下，深入的了解更加有必要性。</p><p><img src="https://wx3.sinaimg.cn/mw1024/7e0ee03agy1fy53fs6ze4j20qf0b9gnq.jpg" alt="img"></p><h2 id="微服务架构面临的挑战"><a href="#微服务架构面临的挑战" class="headerlink" title="微服务架构面临的挑战"></a>微服务架构面临的挑战</h2><p>这种规模化和动态化对于早期运行单体程序和管理应用程序的基础设施带来了具体的转变。为支持这种动态环境，新一代架构需要在生态系统中补充大量的新技术。为了交付所有的用户场景，我们需要在基础架构栈的每个级别上提供多个解决方案。根据需要，基础架构人员开始将这些技术集成到平台上，但这也意味着程序开发人员需要额外的负担来支持这些技术。</p><p><img src="https://wx2.sinaimg.cn/mw1024/7e0ee03agy1fy53fsh5iej20rd0cejup.jpg" alt="img"></p><h2 id="基础架构栈高层视图"><a href="#基础架构栈高层视图" class="headerlink" title="基础架构栈高层视图"></a>基础架构栈高层视图</h2><p>这不是人们所期望的，并且也绝对不是微服务架构做出的的敏捷性、易于开发和部署的承诺。</p><p>此后出现了服务网格的理念，这也是Avi Networks在此术语被创造之前一直专注于为客户提供的内容，并且由Istio和Linkerd等开源项目推动下形成了事实上的标准。我们很高兴看到社区热情拥抱了服务网格，而且我们也认为服务网格是微服务基础架构的必要组成部分。</p><p>那么什么是 “服务网格” ，其如何帮助解决这些问题的呢？服务网格实质上是提供了上面图中在基础架构中的多层服务，与此同时程序开发者无需集成或修改代码就可以利用这些服务。它不仅使服务之间的通信快速可靠，而且服务网络还提供细粒度的流量管理、故障恢复、安全（加密、授权和认证）和可观察性（如跟踪、日志和监控）。所有这些都是从使用某种架构的开发人员中抽象出来的，其中所有服务间的通信都流经sidecar代理，代理与每个服务一起部署，从而创建一个服务网格。Sidecar由集中控制平面管理配置，用于流量路由和策略实施。尽管运行与应用程序容器一样多的sidecar容器一直是争论的焦点，但服务网格的优势和功能似乎超过了运维问题。</p><p>在本博客系列的其余部分，我将深入探讨如何实现服务网格，并使用Istio的参考架构来完成旅程，因为Istio是当前最广泛使用和最知名的服务网格解决方案之一。但Istio是否解决了所有问题，并且在处理当今微服务世界中存在的重要场景方面是否完整？我们将深入探讨这一点，并在本系列的后续部分讨论所有内容。 敬请关注！</p><p>Manish Chugtu - CTO Cloud Infrastructure和Microservices@Avi Networks，是一位创新思想领军人物，在架构，设计和产品开发方面拥有 18 年以上的经验，在架构和开发高度可扩展的企业解决方案方面拥有丰富的经验。目前，他致力于推动Avi在容器和云基础架构领域的战略， <a href="https://www.linkedin.com/in/manishchugtu/" target="_blank" rel="noopener">他的 LinkedIn</a>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;作者：&lt;a href=&quot;https://blog.avinetworks.com/from-fragmented-microservices-ecosystem-to-service-mesh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Manish C
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="service_mesh" scheme="http://www.cn18k.com/tags/service-mesh/"/>
    
      <category term="istio" scheme="http://www.cn18k.com/tags/istio/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes DNS 入门指南</title>
    <link href="http://www.cn18k.com/2018/10/25/k8s-dns/"/>
    <id>http://www.cn18k.com/2018/10/25/k8s-dns/</id>
    <published>2018-10-25T03:00:00.000Z</published>
    <updated>2018-10-25T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong> 正文 </strong><br><br><br><div class="row">    <embed src="/assets/k8s-dns-v2.pdf" width="100%" height="550" type="application/pdf"></div></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;strong&gt; 正文 &lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;

	&lt;div class=&quot;row&quot;&gt;
    &lt;embed src=&quot;/assets/k8s-dns-v2.pdf&quot; width=&quot;100%&quot; height=&quot;550&quot; type=&quot;application
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="coredns" scheme="http://www.cn18k.com/tags/coredns/"/>
    
      <category term="dns" scheme="http://www.cn18k.com/tags/dns/"/>
    
      <category term="clusterfirst" scheme="http://www.cn18k.com/tags/clusterfirst/"/>
    
  </entry>
  
  <entry>
    <title>容器技术与 K8S 集成介绍</title>
    <link href="http://www.cn18k.com/2018/10/09/container-runtime-k8s/"/>
    <id>http://www.cn18k.com/2018/10/09/container-runtime-k8s/</id>
    <published>2018-10-09T03:00:00.000Z</published>
    <updated>2018-10-09T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong> 正文 </strong><br><br><br><div class="row">    <embed src="/assets/container-k8s-cri.pdf" width="100%" height="550" type="application/pdf"></div></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;strong&gt; 正文 &lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;

	&lt;div class=&quot;row&quot;&gt;
    &lt;embed src=&quot;/assets/container-k8s-cri.pdf&quot; width=&quot;100%&quot; height=&quot;550&quot; type=&quot;appl
      
    
    </summary>
    
      <category term="kubernetes" scheme="http://www.cn18k.com/categories/kubernetes/"/>
    
    
      <category term="kubernetes" scheme="http://www.cn18k.com/tags/kubernetes/"/>
    
      <category term="cri" scheme="http://www.cn18k.com/tags/cri/"/>
    
      <category term="container" scheme="http://www.cn18k.com/tags/container/"/>
    
      <category term="runtime" scheme="http://www.cn18k.com/tags/runtime/"/>
    
  </entry>
  
  <entry>
    <title>为 Kubernetes 选择合适的 container runtime（译）</title>
    <link href="http://www.cn18k.com/2018/09/29/kubernets-container-runtime/"/>
    <id>http://www.cn18k.com/2018/09/29/kubernets-container-runtime/</id>
    <published>2018-09-29T03:00:00.000Z</published>
    <updated>2018-09-29T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>原文地址：<a href="https://joejulian.name/post/kubernetes-container-engine-comparison" target="_blank" rel="noopener">https://joejulian.name/post/kubernetes-container-engine-comparison</a><br>作者：Joe Julian<br>翻译：狄卫华<br>发表时间： 2018/3/23</p><p>Kubelet 可通过配置项 <code>container-runtime</code>，<code>container-runtime-endpoint</code> 和 <code>image-service-endpoint</code> 来选择使用 docker，rkt（不建议使用）或任何 CRI API 兼容的 <code>container-runtime</code> 。</p><h2 id="意见"><a href="#意见" class="headerlink" title="意见"></a>意见</h2><p>虽然基于虚拟机的机制提供了额外的安全隔离，但资源限制太多，需要较多的开销才能将软件在生产环境上部署使用，但是为了方案的完整性，本文也涵盖其相关的实现，但在整体解决方案章节将不再完整描述或分析。</p><h2 id="Kubernetes-接口"><a href="#Kubernetes-接口" class="headerlink" title="Kubernetes 接口"></a>Kubernetes 接口</h2><h3 id="Native"><a href="#Native" class="headerlink" title="Native"></a>Native</h3><h4 id="Docker"><a href="#Docker" class="headerlink" title="Docker"></a>Docker</h4><p>Docker 是迄今为止最常用的容器引擎，主要得益于其容器仓库和社区。Docker 是 dockerd 、containerd、containerd-shim 和 runc 的组合，其中 runc 作为 <code>container-runtime</code>。 由于 kubernetes 移除对于 <code>container-runtime</code> 的直接和非 CRI 接口的支持，因此 Native 支持的方式的后续将会被移除。</p><h4 id="rktnetes"><a href="#rktnetes" class="headerlink" title="rktnetes"></a>rktnetes</h4><p>截至 kubernetes 1.10.0，直接集成 rkt（或 rktnetes）支持已被弃用。</p><h3 id="CRI"><a href="#CRI" class="headerlink" title="CRI"></a>CRI</h3><p>容器运行时接口（CRI）的出现是因为 CoreOS 想要为 kubernetes 添加 rkt 支持。Kubernetes 初期提供了对于 docker 的支持，但在后续添加 rkt 支持的补丁后，代码变得丑陋务必，充斥着大量的 “if this do rkt else do docker”。为后续出现的 runtimes 添加支持将无法再继续维护，基于此，CRI 诞生了。</p><p>CRI 是一个基于 protobuf 定义的 api，包括两个 gRPC 服务，<code>ImageService</code> 和 <code>RuntimeService</code>。 <code>ImageServic</code>e 提供 RPC 以从仓库中提取镜像，检查和删除镜像。<code>RuntimeService</code> 包含用于管理 pod 和容器生命周期的 RPC，也提供与容器交互的调用（exec/attach/port-forward）。</p><h4 id="cri-containerd"><a href="#cri-containerd" class="headerlink" title="cri-containerd"></a>cri-containerd</h4><p><img src="https://ws1.sinaimg.cn/large/7e0ee03agy1fvqixozwzbj20j70oqgn2.jpg" alt="cri-containerd-interaction"></p><p><code>cri-containerd</code> 是一种向 <code>containerd</code> 添加 CRI 支持的服务，<code>containerd</code>是由 Docker 创建并捐赠给 CNCF ，提供 runtime 管理和镜像服务。后续  <code>containerd</code> 与 Docker 分离，将 runtime 管理器与 docker 其余的工具分离，以利于不断增长的容器管理工具生态系统在 docker api上实现标准化。这有助于 docker 社区的扩展并帮助在 docker 仓库中提供丰富的容器镜像。</p><p>cri-containerd 在kubernetes 1.9 中处于 beta 阶段。</p><h4 id="rktlet"><a href="#rktlet" class="headerlink" title="rktlet"></a>rktlet</h4><p><code>rktlet</code> 是一个使用 rkt 作为容器 runtime 的 Kubernetes 接口实现。</p><p><img src="https://ws1.sinaimg.cn/large/7e0ee03agy1fvqiy2t9f9j20hl0qe75o.jpg" alt="rktlet-interaction"></p><p>当 kubelet 请求创建一个 pod 时，rktlet 将启动一个 systemd 服务，该服务将通过运行 rkt 二进制文件创建一个新的 rkt sandbox。 创建 sandbox 后，kubelet 可以请求为 pod 添加/删除容器等。然后 rktlet 将使用相应的 rkt app 命令（app add，app rm，app start 或 app stop）运行 rkt 二进制文件。 其余的 CRI 方法也通过执行 rkt 二进制文件来处理。</p><p>rktlet 在 kubernetes 1.9 中为 alpha 状态。</p><h4 id="cri-o"><a href="#cri-o" class="headerlink" title="cri-o"></a>cri-o</h4><p>cri-o 是一个 CRI 实现，旨在为 kubernetes 提供 CRI 接口支撑。 cri-o 提供了一组最小的工具和接口来下载、提取和管理镜像、维护容器生命周期、并提供满足 CRI 所需的监控和日志记录。 它可以使用任何实现 OCI 运行时规范的 runtime，默认使用 runc。</p><p><img src="https://ws1.sinaimg.cn/large/7e0ee03agy1fvqiyghnigj20ix0lo0tw.jpg" alt="cri-o-interaction"></p><p>cri-o 使用配置的 runtime（默认情况下为runc）创建容器 sandbox（pod），然后再次使用 runtime 在该 pod 中创建容器。</p><p>从kubernetes 1.9开始，cri-o 被认为是 stable 的。</p><h4 id="frakti"><a href="#frakti" class="headerlink" title="frakti"></a>frakti</h4><p>Frakti 允许 Kubernetes 通过 runV 直接在虚拟机管理程序中运行 pod 和容器 。</p><h2 id="OCI-Open-Container-Initiative-兼容-Runtimes"><a href="#OCI-Open-Container-Initiative-兼容-Runtimes" class="headerlink" title="OCI (Open Container Initiative) 兼容 Runtimes"></a>OCI (Open Container Initiative) 兼容 Runtimes</h2><h3 id="Containers"><a href="#Containers" class="headerlink" title="Containers"></a>Containers</h3><h4 id="bwrap-oci"><a href="#bwrap-oci" class="headerlink" title="bwrap-oci"></a>bwrap-oci</h4><p><code>bwrap-oci</code> 是为容器工具 bubblewrap 提供 OCI 兼容的包装器，bubblewrap 是一个无特权的容器工具。作者为：Per Giuseppe Scrivano，<code>bwrap-oci</code> 不满足e2e Kubernetes测试所需的许多功能，例如：无法指定绑定装载的选项，也无法通过 cgroup 限制资源。</p><h4 id="crun"><a href="#crun" class="headerlink" title="crun"></a>crun</h4><p><code>crun</code>是用 C 编写的 OCI 运行时规范实现。配合 cri-o，作者 Giuseppe Scrivano 用它来通过了完整的 e2e 套件测试。它比同类型功能中的任何其他工具更小更轻。</p><h4 id="railcar"><a href="#railcar" class="headerlink" title="railcar"></a>railcar</h4><p>railcar 是 OCI runtime-spec 规范的 rust 语言实现。参考并实现了 runc 的部分功能。一般来说，railcar 与 runc 非常相似，但不支持某些 runc 命令。截至目前不受支持的命令列表为：checkpoint，events，exec，init，list，pause，restore，resume，spec。 railcar 始终运行与容器进程分开的 init 进程。 railcar 在其研发过程号称发现了 OCI 运行时规范中的一些<a href="https://blogs.oracle.com/developers/building-a-container-runtime-in-rust" target="_blank" rel="noopener">缺陷</a>。通过使用 rust 语言重写 rust，作者能够消除在 go 语言实现过程中对于 c 语言中介层的的需要。</p><p>我尝试向编写缺陷那篇文章的作者询问了有关缺陷的具体内容，但是未得到回复。</p><h4 id="rkt"><a href="#rkt" class="headerlink" title="rkt"></a>rkt</h4><p>rkt 是一个在 LInux 上运行容器而编写的 CLI 工具。它采用多层设计，允许 runtime 可根据实现者的需求更改 。 默认情况下，rkt 结合使用 <code>systemd</code>和 <code>systemd-nspawn</code> 来创建容器。 systemd-nspawn 用于管理执行 systemd 以管理 cgroup 的命名空间。容器应用程序作为 systemd 单元运行。通过使用不同与 “1阶段” 镜像，该工具可将运行应用程序的工具可以从 systemd 工具更改为其他任何工具。</p><p>rkt 包含与 runc 相同的功能，但是并不使用 OCI runtime-spec。相反，rkt 提供了一个命令行接口来提供类似的功能集。</p><h4 id="runc"><a href="#runc" class="headerlink" title="runc"></a>runc</h4><p><a href="https://github.com/opencontainers/runc" target="_blank" rel="noopener">runc</a> 是一个主要用 go 编写的 CLI 工具（需要 c 语言的中介层 shim 来完成某些 go 不能完成的功能）。runc 是最受欢迎的 OCI runtime，被 containerd 和 cri-o 使用。</p><p>在大多数情况下，所有 <code>runc</code> 都会在生成进程时配置 namespace 和 cgroup。 这实际上就是是一个容器，包括 namespace 和 cgroups。</p><p>runc 依赖并跟踪 OCI runtime-spec，以保证 runc 和 OCI 规范主要版本保持同步。这意味着 runc 1.0.0 实现了 OCI 1.0 版本的规范。</p><h4 id="runlxc"><a href="#runlxc" class="headerlink" title="runlxc"></a>runlxc</h4><p><code>runlxc</code> 是阿里巴巴即将开源的 OCI 兼容 runtime。 runlxc 目前尚未对外发布，与  <a href="https://github.com/alibaba/pouch" target="_blank" rel="noopener">pouch</a> 配合使用。</p><h3 id="Virtual-Machines"><a href="#Virtual-Machines" class="headerlink" title="Virtual Machines"></a>Virtual Machines</h3><h4 id="Clear-Containers"><a href="#Clear-Containers" class="headerlink" title="Clear Containers"></a>Clear Containers</h4><p><a href="https://github.com/clearcontainers/runtime/" target="_blank" rel="noopener">cc-runtime</a> 兼容 OCI runtime，其通过启动一个 Intel VT-x 安全 <a href="https://github.com / clearcontainers / agent" target="_blank" rel="noopener">Clear Containers</a> 管理程序，而不是标准的Linux 容器。</p><h4 id="runv"><a href="#runv" class="headerlink" title="runv"></a>runv</h4><p><a href="https://github.com/hyperhq/runv" target="_blank" rel="noopener">runv</a> 是基于 hypervisor 的 OCI runtime，使用 KVM，Xen 或 QEMU 来运行  OCI 镜像。它不会创建容器。</p><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><h3 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h3><h4 id="containerd"><a href="#containerd" class="headerlink" title="containerd"></a>containerd</h4><p>containerd 是 CRI 实现中最复杂的，提供镜像和 runtime 服务所需的 3 个组件，采用 Go 编写。</p><p>这些组件包含两个独立的项目 <a href="https://github.com/containerd/cri.git" target="_blank" rel="noopener">cri</a>，提供了 19,000 行 Go 代码和 <a href="https://github.com/containerd/containerd.git" target="_blank" rel="noopener">containerd</a> 提供 runtime 的守护进程，运行程序和中介层代码一共 112,000 行。 containerd 默认使用它自己的 runc 分支，但是可以配置任何 OCI runtime 规范兼容的实现。</p><p>Docker/containerd 对 CRI 支持处于 beta 阶段。</p><p>我无法在 Arch Linux 中编译。由于时间有限，我没有花很多时间在这上面。依据 README.md 中的构建指令无法成功完成 <code>make install.deps</code> 阶段。</p><h4 id="rkt-1"><a href="#rkt-1" class="headerlink" title="rkt"></a>rkt</h4><p>rkt 需要两个组件，rkt 和 rktlet。 尽管如此，它并没有遵循标准，而是以自己的方式做事情，尽管它的 github 描述说：“它是可组合的，安全的，并且建立在标准之上”。 它也是用 Go 编写的。</p><p>rkt 组件是 <a href="https://github.com/rkt/rkt.git" target="_blank" rel="noopener">rkt</a>，包含 71,000 行 Go 和 <a href="https://github.com/kubernetes-incubator/rktlet.git" target="_blank" rel="noopener">rktlet</a>，包含 4,000 行。</p><p>rktlet 对 CRI 支持是 alpha 版。</p><h4 id="cri-o-1"><a href="#cri-o-1" class="headerlink" title="cri-o"></a>cri-o</h4><p>坐在中间是 <a href="https://github.com/kubernetes-incubator/cri-o.git" target="_blank" rel="noopener">cri-o</a>。cri-o 为容器编排工具 Kubernetes 定制而生。 凭借其聚焦的单一性，它很快就获得了 “稳定” 状态，仅用了 14,000 行 Go。 它与任何 OCI 运行时规范实现接口，默认为 runc。</p><p>cri-o 对 CRI 支持是稳定。</p><h3 id="可用性"><a href="#可用性" class="headerlink" title="可用性"></a>可用性</h3><p>选择 CRI 实现的一个明显因素是可用性。 您应该能够运行完整的 e2e 测试并能够运行任何 OCI 容器。 如果出现问题，应该能够快速诊断问题。</p><h4 id="cri-o-2"><a href="#cri-o-2" class="headerlink" title="cri-o"></a>cri-o</h4><p>cri-o 可以作为包装安装在大多数发行版中。 使用 cri-o 非常简单。 只需更改 kubelet 标志即可使用默认值以使用cri-o 套接字。可以应用其他配置来添加其他功能，例如 repo 限制，selinux，apparmor seccomp，镜像签名等。调试问题很简单，因为只有两个部分，cri-o 守护程序和 <code>conmon</code> 控制台监视器。</p><p>使用 runc 与kubernetes 1.9 我很容易通过完整的 e2e 测试。</p><h4 id="cri-containerd-1"><a href="#cri-containerd-1" class="headerlink" title="cri-containerd"></a>cri-containerd</h4><p>由于无法编译，我无法测试其可用性。</p><h4 id="rktlet-1"><a href="#rktlet-1" class="headerlink" title="rktlet"></a>rktlet</h4><p>rktlet 可以在大多数发行版中作为包安装。使用 rkt 可能需要一点学习曲线。有许多有效的方法来配置它，并且学习哪一个适合的用例并不是非常简单。 rkt 使用 “阶段”，并且很少有关于阶段1 镜像像用于什么目的的文档。你<em>可以</em>创建自己的阶段1 镜像，但似乎有适当的文档可能会提供这种需要。</p><p>它只有三个部分，诊断问题通常非常简单，因为它与 systemd 集成，将所有输出留在日志中。</p><p>在尝试使用 rktlet 作为我的 CRI 提供程序时，某些容器不能够正常工作。不幸的是，由于时间限制，所以我无法正确诊断。 我希望使用 rkt 就像更改 kubelet 标志一样简单。</p><h3 id="社区"><a href="#社区" class="headerlink" title="社区"></a>社区</h3><p>作为开源社区的成员，社区的健康也很重要。 一个好的社区应该寻求积极参与，快速审查贡献并有一个记录良好的贡献过程。 它应该有一个积极和有效的用户群，在其中提出问题并获得答案。</p><h4 id="cri-o-3"><a href="#cri-o-3" class="headerlink" title="cri-o"></a>cri-o</h4><p>自 2016 年 9 月 10 日以来已有 1354 Pull Requests，其中 23 个是 Open 的，最早的是 2017 年 9 月 19 日创建的，最后一次 Commit 是在 9 天前。 共有 75 名贡献者，其中 20 人在上个月活跃。 过去 30 天提交的 75％ 来自Red Hat 员工。 在过去的 30 天里，已经添加了 5,141 行代码和 删除了 694 行。 5,141 行代表了 37％ 的代码变化。 cri-o 有一个积极的贡献者基础，其中大部分是红帽员工，但有红帽以外的员工。 拉取请求需要合并 4 天的平均值。 问题大致相同。有一个 IRC 频道，开发人员在北美工作时间内回答用户问题，响应时间约为 4 分钟。</p><h4 id="cri-containerd-containerd"><a href="#cri-containerd-containerd" class="headerlink" title="cri-containerd/containerd"></a>cri-containerd/containerd</h4><p>自 2017 年 4 月 14 日以来，已有 484 次针对 containerd/cri 的 Pull Requests，其中 6 次 是 Open 的。最早的Open PR 创建于 2017 年 12 月 8 日，最后一次 Comment 时间是 2018 年 1 月 18 日。共有 31 位贡献者，其中 8 位活跃于上个月。在过去的 30 天里，IBM，Docker，中兴通讯，谷歌和英特尔最终贡献了很多。在过去的 30天里，已经添加了 2,123 行代码和 删除了 1,217 行。 2,123 行代表了 11％ 的代码变化。对于 containerd /cri，并没有一个非常活跃的贡献者基础，但是这代表了社区的一个更健康的体现。</p><p>自 2015 年 12月 7 日起，共向 containerd/containerd 提交了1,662 个 Pull Requests，其中 13 个是 Open 状态。 最早的 Open PR 创建于 2017 年 8 月 24 日，最后一次 Commit 于 2017 年 9 月 19 日。共有 120 名贡献者，其中 12人在上个月活跃。 过去 30 天中有 46％ 来自 Docker 员工，NTT 和 IBM 分别增加了31％。 在过去的 30 天里，已经添加了 4,444 行代码 和删除了 1,724 行。 4,444 行代表 3％ 的代码变化。 对于 containerd，有一个非常活跃的贡献者基础，但它仍然是多样化的。</p><p>获得对这两者之一的支持需要注册为 docker 社区成员。</p><h4 id="rktlet-rkt"><a href="#rktlet-rkt" class="headerlink" title="rktlet/rkt"></a>rktlet/rkt</h4><p>自 2014 年11月13 日起，共 2 ,406 次 Pull Requests，其中 49 份是开放的。 最旧的 Open PR 创建于 2015 年 8 月25 日，最后一次 Commente 记录时间是 2016 年 8 月 9 日。我认为 rkt 维护者可以做得更好。 接受的 PR 通常在一周内进行审核和合并。 共有 195 名贡献者，上个月没有任何活动。 rkt 的活动急剧下降。 我不确定这是不是一个活跃的项目。</p><p>在看了 rkt 的活跃状况后，我选择忽略了继续查看 rktlet 社区的健康状况。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>标准和其相关的 apis 定义提供了非常大的灵活性，并有助于防止锁定在特定的累积技术债中。通过使用 OCI（Open Container Initiative） 容器和 rumtime 标准以及 Kubernetes 的 CRI（Container Runtime Interface ）API，可以选择演进任何任何特定选项而不用更整个堆技术栈。</p><p>为此，应该弃用 kubernetes 原生支持的 docker 机制，并且只应使用 CRI。</p><p>基于 hypervisor 的解决方案，我并没有在选择继续分析，因为在本文章编写的时候，runlxc 尚未开源。</p><p>对于 CRI 接口，rktlet/rkt 没有长期的社区支持，而且经过了很长的时间才演变成 beta 状态；cri-containerd 并没有提供一个良好的构建过程，也没有发行版的包装，我无法测试。cri-containerd 仍然只是一个 beta 版本，获得支持有障碍。cri-o 相对稳定，快速，轻松地构建，而且更适合在生产环境稳定运行，并且具备非常好的社区支持。我的结论是，cri-o 是目前最好的选择。</p><p>对于 runtime，虽然  railcar 和 crun在技术上可能更优越，但我没有时间测试它们。由于它们是可替代品，因此预先为此工具选择最佳技术并不重要。我建议使用当前默认的 runc，直到可以彻底评估它们为止。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;原文地址：&lt;a href=&quot;https://joejulian.name/post/kubernetes-container-engine-comparison&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://joejulian.name/p
      
    
    </summary>
    
      <category term="kubernets" scheme="http://www.cn18k.com/categories/kubernets/"/>
    
    
      <category term="container" scheme="http://www.cn18k.com/tags/container/"/>
    
      <category term="runtime" scheme="http://www.cn18k.com/tags/runtime/"/>
    
  </entry>
  
  <entry>
    <title>xDS REST 和 gRPC 协议（译）</title>
    <link href="http://www.cn18k.com/2018/09/26/xds-protocol/"/>
    <id>http://www.cn18k.com/2018/09/26/xds-protocol/</id>
    <published>2018-09-26T03:00:00.000Z</published>
    <updated>2018-09-26T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>原文地址： <a href="https://github.com/envoyproxy/data-plane-api/blob/master/XDS_PROTOCOL.md" target="_blank" rel="noopener">https://github.com/envoyproxy/data-plane-api/blob/master/XDS_PROTOCOL.md</a><br>翻译：狄卫华<br>校对：宋净超</p><p>Envoy 通过查询文件或管理服务器来动态发现资源。概括地讲，对应的发现服务及其相应的 API 被称作 <em>xDS</em>。Envoy 通过订阅（<em>subscription</em>）方式来获取资源，如监控指定路径下的文件、启动 gRPC 流或轮询 REST-JSON URL。后两种方式会发送 <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/discovery.proto#discoveryrequest" target="_blank" rel="noopener"><code>DiscoveryRequest</code></a> 请求消息，发现的对应资源则包含在响应消息 <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/discovery.proto#discoveryresponse" target="_blank" rel="noopener"><code>DiscoveryResponse</code></a> 中。下面，我们将具体讨论每种订阅类型。</p><h2 id="文件订阅"><a href="#文件订阅" class="headerlink" title="文件订阅"></a>文件订阅</h2><p>发现动态资源的最简单方式就是将其保存于文件，并将路径配置在 <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/config_source.proto#core-configsource" target="_blank" rel="noopener">ConfigSource</a> 中的 <code>path</code> 参数中。Envoy 使用 <code>inotify</code>（Mac OS X 上为 <code>kqueue</code>）来监控文件的变化，在文件被更新时，Envoy 读取保存的 <code>DiscoveryResponse</code> 数据进行解析，数据格式可以为二进制 protobuf、JSON、YAML 和协议文本等。</p><blockquote><p>译者注：core.ConfigSource 配置格式如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt; &#123;</span><br><span class="line">&gt;   &quot;path&quot;: &quot;...&quot;,</span><br><span class="line">&gt;   &quot;api_config_source&quot;: &quot;&#123;...&#125;&quot;,</span><br><span class="line">&gt;   &quot;ads&quot;: &quot;&#123;...&#125;&quot;</span><br><span class="line">&gt; &#125;</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></blockquote><p>文件订阅方式可提供统计数据和日志信息，但是缺少 ACK/NACK 更新的机制。如果更新的配置被拒绝，xDS API 则继续使用最后一个的有效配置。</p><h2 id="gRPC-流式订阅"><a href="#gRPC-流式订阅" class="headerlink" title="gRPC 流式订阅"></a>gRPC 流式订阅</h2><h3 id="单资源类型发现"><a href="#单资源类型发现" class="headerlink" title="单资源类型发现"></a>单资源类型发现</h3><p>每个 xDS API 可以单独配置 <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/config_source.proto#core-apiconfigsource" target="_blank" rel="noopener"><code>ApiConfigSource</code></a>，指向对应的上游管理服务器的集群地址。每个 xDS 资源类型会启动一个独立的双向 gRPC 流，可能对应不同的管理服务器。API 交付方式采用最终一致性。可以参考后续聚合服务发现（ADS） 章节来了解必要的显式控制序列。</p><blockquote><p>译者注：core.ApiConfigSource 配置格式如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&gt; &#123;</span><br><span class="line">&gt;   "api_type": "...",</span><br><span class="line">&gt;   "cluster_names": [],</span><br><span class="line">&gt;   "grpc_services": [],</span><br><span class="line">&gt;   "refresh_delay": "&#123;...&#125;",</span><br><span class="line">&gt;   "request_timeout": "&#123;...&#125;"</span><br><span class="line">&gt; &#125;</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></blockquote><h4 id="类型-URL"><a href="#类型-URL" class="headerlink" title="类型 URL"></a>类型 URL</h4><p>每个 xDS API 都与给定的资源的类型存在 1:1 对应。关系如下：</p><ul><li><a href="https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/lds.proto" target="_blank" rel="noopener">LDS： <code>envoy.api.v2.Listener</code></a></li><li><a href="https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/rds.proto" target="_blank" rel="noopener">RDS： <code>envoy.api.v2.RouteConfiguration</code></a></li><li><a href="https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/cds.proto" target="_blank" rel="noopener">CDS： <code>envoy.api.v2.Cluster</code></a></li><li><a href="https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/eds.proto" target="_blank" rel="noopener">EDS： <code>envoy.api.v2.ClusterLoadAssignment</code></a></li></ul><p><a href="https://developers.google.com/protocol-buffers/docs/proto3#any" target="_blank" rel="noopener"><em>类型 URL</em></a> 的概念如下所示，其采用 <code>type.googleapis.com/&lt;resource type&gt;</code> 的形式，例如 CDS 对应于  <code>type.googleapis.com/envoy.api.v2.Cluster</code>。在 Envoy 的请求和管理服务器的响应中，都包括了资源类型 URL。</p><h4 id="ACK-NACK-和版本"><a href="#ACK-NACK-和版本" class="headerlink" title="ACK/NACK 和版本"></a>ACK/NACK 和版本</h4><p>每个 Envoy 流以  <code>DiscoveryRequest</code> 开始，包括了列表订阅的资源、订阅资源对应的类型 URL、节点标识符和空的 <code>version_info</code>。EDS 请求示例如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version_info:</span></span><br><span class="line"><span class="attr">node:</span> <span class="string">&#123;</span> <span class="attr">id:</span> <span class="string">envoy</span> <span class="string">&#125;</span></span><br><span class="line"><span class="attr">resource_names:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">foo</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">bar</span></span><br><span class="line"><span class="attr">type_url:</span> <span class="string">type.googleapis.com/envoy.api.v2.ClusterLoadAssignment</span></span><br><span class="line"><span class="attr">response_nonce:</span></span><br></pre></td></tr></table></figure><p>管理服务器可立刻或等待资源就绪时发送 <code>DiscoveryResponse</code>作为响应，示例如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version_info:</span> <span class="string">X</span></span><br><span class="line"><span class="attr">resources:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">foo</span> <span class="string">ClusterLoadAssignment</span> <span class="string">proto</span> <span class="string">encoding</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">bar</span> <span class="string">ClusterLoadAssignment</span> <span class="string">proto</span> <span class="string">encoding</span></span><br><span class="line"><span class="attr">type_url:</span> <span class="string">type.googleapis.com/envoy.api.v2.ClusterLoadAssignment</span></span><br><span class="line"><span class="attr">nonce:</span> <span class="string">A</span></span><br></pre></td></tr></table></figure><p>Envoy 在处理 <code>DiscoveryResponse</code> 响应后，将通过流发送一个新的请求，请求包含应用成功的最后一个版本号和管理服务器提供的 <code>nonce</code>。如果本次更新已成功应用，则 <code>version_info</code> 的值设置为 <strong>X</strong>，如下序列图所示：</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxs5aod1j20cc06y74c.jpg" alt="Version update after ACK"></p><p>在此序列图及后续中，将统一使用以下缩写格式：</p><ul><li><code>DiscoveryRequest</code>： (V=<code>version_info</code>，R=<code>resource_names</code>，N=<code>response_nonce</code>，T=<code>type_url</code>)</li><li><code>DiscoveryResponse</code>： (V=<code>version_info</code>，R=<code>resources</code>，N=<code>nonce</code>，T=<code>type_url</code>)</li></ul><blockquote><p>译者注：在<a href="https://zh.wikipedia.org/wiki/%E8%B3%87%E8%A8%8A%E5%AE%89%E5%85%A8" target="_blank" rel="noopener">信息安全</a>中，<strong>Nonce</strong>是一个在加密通信只能使用一次的数字。在认证协议中，它往往是一个<a href="https://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA" target="_blank" rel="noopener">随机</a>或<a href="https://zh.wikipedia.org/wiki/%E4%BC%AA%E9%9A%8F%E6%9C%BA" target="_blank" rel="noopener">伪随机</a>数，以避免<a href="https://zh.wikipedia.org/wiki/%E9%87%8D%E6%94%BE%E6%94%BB%E5%87%BB" target="_blank" rel="noopener">重放攻击</a>。Nonce也用于<a href="https://zh.wikipedia.org/wiki/%E6%B5%81%E5%AF%86%E7%A0%81" target="_blank" rel="noopener">流密码</a>以确保安全。如果需要使用相同的密钥加密一个以上的消息，就需要Nonce来确保不同的消息与该密钥加密的密钥流不同。（引用自<a href="https://zh.wikipedia.org/wiki/Nonce" target="_blank" rel="noopener">维基百科</a>）在本文中<code>nonce</code>是每次更新的数据包的唯一标识。</p></blockquote><p>版本为 Envoy 和管理服务器提供了共享当前应用配置的概念和通过 ACK/NACK 来进行配置更新的机制。如果 Envoy 拒绝配置更新 <strong>X</strong>，则回复 <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/discovery.proto#envoy-api-field-discoveryrequest-error-detail" target="_blank" rel="noopener"><code>error_detail</code></a> 及前一个的版本号，在当前情况下为空的初始版本号，<code>error_detail</code> 包含了有关错误的更加详细的信息：</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxtjqtcsj20cc06y0ss.jpg" alt="No version update after NACK"></p><p>后续，API 更新可能会在新版本 <strong>Y</strong> 上成功：</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxtwzc96j20cc0923yp.jpg" alt="ACK after NACK"></p><p>每个流都有自己的版本概念，但不存在跨资源类型的共享版本。在不使用 ADS 的情况下，每个资源类型可能具有不同的版本，因为 Envoy API 允许指向不同的 EDS/RDS 资源配置并对应不同的 <code>ConfigSources</code>。</p><h4 id="何时发送更新"><a href="#何时发送更新" class="headerlink" title="何时发送更新"></a>何时发送更新</h4><p>管理服务器应该只向 Envoy 客户端发送上次 <code>DiscoveryResponse</code> 后更新过的资源。Envoy 则会根据接受或拒绝 <code>DiscoveryResponse</code> 的情况，立即回复包含 ACK/NACK 的  <code>DiscoveryRequest</code> 请求。如果管理服务器每次发送相同的资源集结果，而不是根据其更新情况，则会导致 Envoy 和管理服务器通讯效率大打折扣。</p><p>在同一个流中，新的 <code>DiscoveryRequests</code> 将取代此前具有相同的资源类型 <code>DiscoveryRequest</code> 请求。<strong>这意味着管理服务器只需要响应给定资源类型最新的 <code>DiscoveryRequest</code> 请求即可。</strong></p><h4 id="资源提示"><a href="#资源提示" class="headerlink" title="资源提示"></a>资源提示</h4><p><code>DiscoveryRequest</code> 中的 <code>resource_names</code> 信息作为资源提示出现。一些资源类型，例如 <code>Cluster</code> 和 <code>Listener</code> 将使用一个空的 <code>resource_names</code>，因为 Envoy 需要获取管理服务器对应于节点标识的所有 <code>Cluster</code>（CDS）和  <code>Listener</code>（LDS）。对于其他资源类型，如 <code>RouteConfigurations</code>（RDS）和 <code>ClusterLoadAssignments</code>（EDS），则遵循此前的 CDS/LDS 更新，Envoy 能够明确地枚举这些资源。</p><p>LDS/CDS 资源提示信息将始终为空，并且期望管理服务器的每个响应都提供 <code>LDS/CDS</code> 资源的完整状态。缺席的 <code>Listener</code> 或 <code>Cluster</code> 将被删除。</p><p>对于 EDS/RDS，管理服务器并不需要为每个请求的资源进行响应，而且还可能提供额外未请求的资源。<code>resource_names</code> 只是一个提示。Envoy 将默默地忽略返回的多余资源。如果请求的资源中缺少相应的 RDS 或 EDS 更新，Envoy 将保留对应资源的最后的值。管理服务器可能会依据  <code>DiscoveryRequest</code> 中 <code>node</code> 标识推断其所需的 EDS/RDS 资源，在这种情况下，提示信息可能会被丢弃。从相应的角度来看，空的 EDS/RDS <code>DiscoveryResponse</code> 响应实际上是表明在 Envoy 中为一个空的资源。</p><p>当 <code>Listener</code> 或 <code>Cluster</code> 被删除时，其对应的 EDS 和 RDS 资源也需要在 Envoy 实例中删除。为使 EDS 资源被 Envoy 已知或跟踪，就必须存在应用过的 <code>Cluster</code> 定义（如通过 CDS 获取）。RDS 和 <code>Listeners</code> 之间存在类似的关系（如通过 LDS 获取）。</p><p>对于 EDS/RDS ，Envoy 可以为每个给定类型的资源生成不同的流（如每个 <code>ConfigSource</code> 都有自己的上游管理服务器的集群）或当指定资源类型的请求发送到同一个管理服务器的时候，允许将多个资源请求组合在一起发送。虽然可以单个实现，但管理服务器应具备处理每个给定资源类型中对单个或多个 <code>resource_names</code>  请求的能力。下面的两个序列图对于获取两个 EDS 资源都是有效的 <code>{foo，bar}</code>：</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxuviiqsj20eh06ymx9.jpg" alt="Multiple EDS requests on the same stream"><br><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxv7cv21j20j20a4wet.jpg" alt="Multiple EDS requests on distinct streams"></p><h4 id="资源更新"><a href="#资源更新" class="headerlink" title="资源更新"></a>资源更新</h4><p>如上所述，Envoy 可能会更新  <code>DiscoveryRequest</code> 中出现的 <code>resource_names</code> 列表，其中 <code>DiscoveryRequest</code>  是用来 ACK/NACK 管理服务器的特定的 <code>DiscoveryResponse</code> 。此外，Envoy 后续可能会发送额外的 <code>DiscoveryRequests</code> ，用于在特定 <code>version_info</code> 上使用新的资源提示来更新管理服务器。例如，如果 Envoy 在 EDS 版本 <strong>X</strong> 时仅知道集群 <code>foo</code>，但在随后收到的 CDS 更新时额外获取了集群 <code>bar</code> ，它可能会为版本 <strong>X</strong> 发出额外的 <code>DiscoveryRequest</code> 请求，并将 <code>{foo，bar}</code> 作为请求的 <code>resource_names</code> 。</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxvj18dpj20ij080q34.jpg" alt="CDS response leads to EDS resource hint update"></p><p>这里可能会出现竞争状况；如果 Envoy 在版本 <strong>X</strong> 上发布了资源提示更新请求，但在管理服务器处理该请求之前发送了新的版本号为 <strong>Y</strong>  的响应，针对 <code>version_info</code> 为 <strong>X</strong> 的版本，资源提示更新可能会被解释为拒绝  <strong>Y</strong> 。为避免这种情况，通过使用管理服务器提供的 <code>nonce</code>，Envoy 可用来保证每个 <code>DiscoveryRequest</code> 对应到相应的 <code>DiscoveryResponse</code> ：</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxvumt0kj20of0bzq3f.jpg" alt="EDS update race motivates nonces"></p><p>管理服务器不应该为含有过期 <code>nonce</code> 的 <code>DiscoveryRequest</code> 发送 <code>DiscoveryResponse</code> 响应。在向 Envoy 发送的 <code>DiscoveryResponse</code>  中包含了的新 <code>nonce</code> ，则此前的 <code>nonce</code> 将过期。在资源新版本就绪之前，管理服务器不需要向 Envoy 发送更新。同版本的早期请求将会过期。在新版本就绪时，管理服务器可能会处理同一个版本号的多个 <code>DiscoveryRequests</code>请求。</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxw4h4buj20h70csjrs.jpg" alt="Requests become stale"></p><p>上述资源更新序列表明 Envoy 并不能期待其发出的每个 <code>DiscoveryRequest</code> 都得到 <code>DiscoveryResponse</code> 响应。</p><h4 id="最终一致性考虑"><a href="#最终一致性考虑" class="headerlink" title="最终一致性考虑"></a>最终一致性考虑</h4><p>由于 Envoy 的 xDS API 采用最终一致性，因此在更新期间可能导致流量被丢弃。例如，如果通过 CDS/EDS 仅获取到了集群 <strong>X</strong>，而且 <code>RouteConfiguration</code> 引用了集群  <strong>X</strong>；在 CDS/EDS 更新集群  <strong>Y</strong>  配置之前，如果将 <code>RouteConfiguration</code> 将引用的集群调整为 <strong>Y</strong> ，那么流量将被吸入黑洞而丢弃，直至集群 <strong>Y</strong> 被 Envoy 实例获取。</p><p>对某些应用程序，可接受临时的流量丢弃，客户端重试或其他 Envoy sidecar 会掩盖流量丢弃。那些对流量丢弃不能容忍的场景，可以通过以下方式避免流量丢失，CDS/EDS 更新同时携带 <strong>X</strong> 和 <strong>Y</strong> ，然后发送 RDS 更新从 <strong>X</strong> 切换到 <strong>Y</strong> ，此后发送丢弃 <strong>X</strong> 的 CDS/EDS 更新。</p><p>一般来说，为避免流量丢弃，更新的顺序应该遵循 <code>make before break</code> 模型，其中</p><ul><li>必须始终先推送 CDS 更新（如果有）。</li><li>EDS 更新（如果有）必须在相应集群的 CDS 更新后到达。</li><li>LDS 更新必须在相应的 CDS/EDS 更新后到达。</li><li>与新添加的监听器相关的 RDS 更新必须在最后到达。</li><li>最后，删除过期的 CDS 集群和相关的 EDS 端点（不再被引用的端点）。</li></ul><p>如果没有新的集群/路由/监听器或者允许更新时临时流量丢失的情况下，可以独立推送 xDS 更新。请注意，在 LDS 更新的情况下，监听器须在接收流量之前被预热，例如如其配置了依赖的路由，则先需先从 RDS 进行获取。添加/删除/更新集群信息时，集群也需要进行预热。另一方面，如果管理平面确保路由更新时所引用的集群已经准备就绪，路由可以不用预热。</p><h3 id="聚合服务发现（ADS）"><a href="#聚合服务发现（ADS）" class="headerlink" title="聚合服务发现（ADS）"></a>聚合服务发现（ADS）</h3><p>当管理服务器进行资源分发时，通过上述保证交互顺序的方式来避免流量丢弃是一项很有挑战的工作。ADS 允许单一管理服务器通过单个 gRPC 流，提供所有的 API 更新。配合仔细规划的更新顺序，ADS 可规避更新过程中流量丢失。使用 ADS，在单个流上可通过类型 URL 来进行复用多个独立的 <code>DiscoveryRequest</code>/<code>DiscoveryResponse</code> 序列。对于任何给定类型的 URL，以上 <code>DiscoveryRequest</code> 和 <code>DiscoveryResponse</code> 消息序列都适用。 更新序列可能如下所示：</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxwgdcehj20cc0a474j.jpg" alt="EDS/CDS multiplexed on an ADS stream"></p><p>每个 Envoy 实例可使用单独的 ADS 流。</p><p>最小化 ADS 配置的 <code>bootstrap.yaml</code> 片段示例如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">node:</span></span><br><span class="line"><span class="attr">  id:</span> <span class="string">&lt;node</span> <span class="string">identifier&gt;</span></span><br><span class="line"><span class="attr">dynamic_resources:</span></span><br><span class="line"><span class="attr">  cds_config:</span> <span class="string">&#123;ads:</span> <span class="string">&#123;&#125;&#125;</span></span><br><span class="line"><span class="attr">  lds_config:</span> <span class="string">&#123;ads:</span> <span class="string">&#123;&#125;&#125;</span></span><br><span class="line"><span class="attr">  ads_config:</span></span><br><span class="line"><span class="attr">    api_type:</span> <span class="string">GRPC</span></span><br><span class="line"><span class="attr">    grpc_services:</span></span><br><span class="line"><span class="attr">      envoy_grpc:</span></span><br><span class="line"><span class="attr">        cluster_name:</span> <span class="string">ads_cluster</span></span><br><span class="line"><span class="attr">static_resources:</span></span><br><span class="line"><span class="attr">  clusters:</span></span><br><span class="line"><span class="attr">  - name:</span> <span class="string">ads_cluster</span></span><br><span class="line"><span class="attr">    connect_timeout:</span> <span class="string">&#123;</span> <span class="attr">seconds:</span> <span class="number">5</span> <span class="string">&#125;</span></span><br><span class="line"><span class="attr">    type:</span> <span class="string">STATIC</span></span><br><span class="line"><span class="attr">    hosts:</span></span><br><span class="line"><span class="attr">    - socket_address:</span></span><br><span class="line"><span class="attr">        address:</span> <span class="string">&lt;ADS</span> <span class="string">management</span> <span class="string">server</span> <span class="string">IP</span> <span class="string">address&gt;</span></span><br><span class="line"><span class="attr">        port_value:</span> <span class="string">&lt;ADS</span> <span class="string">management</span> <span class="string">server</span> <span class="string">port&gt;</span></span><br><span class="line"><span class="attr">    lb_policy:</span> <span class="string">ROUND_ROBIN</span></span><br><span class="line"><span class="attr">    http2_protocol_options:</span> <span class="string">&#123;&#125;</span></span><br><span class="line"><span class="attr">admin:</span></span><br><span class="line">  <span class="string">...</span></span><br></pre></td></tr></table></figure><h3 id="增量-xDS"><a href="#增量-xDS" class="headerlink" title="增量 xDS"></a>增量 xDS</h3><p>增量 xDS 是可用于允许的 ADS、CDS 和 RDS 单独 xDS 端点：</p><ul><li>xDS 客户端对跟踪资源列表进行增量更新。这支持 Envoy 按需/惰性地请求额外资源。例如，当与未知集群相对应的请求到达时，可能会发生这种情况。</li><li>xDS 服务器可以增量更新客户端上的资源。这支持 xDS 资源可伸缩性的目标。管理服务器只需交付更改的单个集群，而不是在修改单个集群时交付所有上万个集群。</li></ul><p>xDS 增量会话始终位于 gRPC 双向流的上下文中。这允许 xDS 服务器能够跟踪到连接的 xDS 客户端的状态。xDS REST 版本不支持增量。</p><p>在增量 xDS 中，nonce 字段是必需的，用于匹配  <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/discovery.proto#discoveryrequest" target="_blank" rel="noopener"><code>IncrementalDiscoveryResponse</code></a> 关联的 ACK 或 NACK <a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/discovery.proto#discoveryrequest" target="_blank" rel="noopener"><code>IncrementalDiscoveryRequest</code></a>。可选地，存在响应消息级别的 system_version_info，但仅用于调试目的。</p><p><code>IncrementalDiscoveryRequest</code> 可在以下 3 种情况下发送：</p><ol><li>xDS 双向 gRPC 流的初始消息。</li><li>作为对先前的 <code>IncrementalDiscoveryResponse</code> 的 ACK 或 NACK 响应。在这种情况下，<code>response_nonce</code> 被设置为响应中的 nonce 值。ACK 或 NACK 由可由 <code>error_detail</code> 字段是否出现来区分。</li><li>客户端自发的 <code>IncrementalDiscoveryRequest</code>。此场景下可以采用动态添加或删除被跟踪的 <code>resource_names</code> 集。这种场景下，必须忽略 <code>response_nonce</code>。</li></ol><p>在第一个示例中，客户端连接并接收它的第一个更新并 ACK。第二次更新失败，客户端发送 NACK 拒绝更新。xDS客户端后续会自发地请求 “wc” 相关资源。</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxws84urj20u30ewmxi.jpg" alt="Incremental session example"><br>在重新连接时，支持增量的 xDS 客户端可能会告诉服务器其已知资源从而避免通过网络重新发送它们。</p><p><img src="https://ws1.sinaimg.cn/mw690/7e0ee03agy1fvmxx33zf5j20kc09c74c.jpg" alt="Incremental reconnect example"></p><h2 id="REST-JSON-轮询订阅"><a href="#REST-JSON-轮询订阅" class="headerlink" title="REST-JSON 轮询订阅"></a>REST-JSON 轮询订阅</h2><p>单个 xDS  API 可对 REST 端点进行的同步（长）轮询。除了无持久流与管理服务器交互外，消息顺序与上述相似。在任何时间点，只存在一个未完成的请求，因此响应消息中的 <code>nonce</code> 在 REST-JSON 中是可选的。<code>DiscoveryRequest</code> 和 <code>DiscoveryResponse</code> 的消息编码遵循 <a href="https://developers.google.com/protocol-buffers/docs/proto3#json" target="_blank" rel="noopener">JSON 变换 proto3</a> 规范。ADS 不支持 REST-JSON 轮询。</p><p>当轮询期间设置为较小的值时，则可以等同于长轮询，这时要求避免发送 <code>DiscoveryResponse</code>，<a href="#何时发送更新">除非对请求的资源发生了更改</a>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;原文地址： &lt;a href=&quot;https://github.com/envoyproxy/data-plane-api/blob/master/XDS_PROTOCOL.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.co
      
    
    </summary>
    
      <category term="envoy" scheme="http://www.cn18k.com/categories/envoy/"/>
    
    
      <category term="envoy" scheme="http://www.cn18k.com/tags/envoy/"/>
    
      <category term="grpc" scheme="http://www.cn18k.com/tags/grpc/"/>
    
      <category term="xds" scheme="http://www.cn18k.com/tags/xds/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus Discovery 之 K8S 代码分析</title>
    <link href="http://www.cn18k.com/2018/09/13/Prometheus-Discovery-K8S/"/>
    <id>http://www.cn18k.com/2018/09/13/Prometheus-Discovery-K8S/</id>
    <published>2018-09-13T03:00:00.000Z</published>
    <updated>2018-09-13T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Service-Discovery-interface"><a href="#Service-Discovery-interface" class="headerlink" title="Service Discovery interface"></a>Service Discovery interface</h2><p>Service Discovery 必须实现 Discovery 接口，定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Discoverer <span class="keyword">interface</span> &#123;</span><br><span class="line">   Run(ctx context.Context, ch <span class="keyword">chan</span>&lt;- []*targetgroup.Group)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Prometheus 支持了众多的 SD 发现机制，代码位于  discovery 目录下。</p><p>Group 的结构定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Group is a set of targets with a common label set(production , test, staging etc.).</span></span><br><span class="line"><span class="keyword">type</span> Group <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// Targets is a list of targets identified by a label set. Each target is</span></span><br><span class="line"><span class="comment">// uniquely identifiable in the group by its address label.</span></span><br><span class="line">Targets []model.LabelSet</span><br><span class="line"><span class="comment">// Labels is a set of labels that is common across all targets in the group.</span></span><br><span class="line">Labels model.LabelSet</span><br><span class="line"></span><br><span class="line"><span class="comment">// Source is an identifier that describes a group of targets.</span></span><br><span class="line">Source <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>K8S 的 Discoverer 定义在文件 <code>gprometheus/discovery/kubernetes/kubernetes.go</code> 中。</p><p>在 init 函数中注册了metrics  <code>prometheus_sd_kubernetes_events_total</code>，用于分析发现过程中的事件接受数量：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">prometheus.MustRegister(eventCount)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Initialize metric vectors.</span></span><br><span class="line"><span class="keyword">for</span> _, role := <span class="keyword">range</span> []<span class="keyword">string</span>&#123;<span class="string">"endpoints"</span>, <span class="string">"node"</span>, <span class="string">"pod"</span>, <span class="string">"service"</span>&#125; &#123;</span><br><span class="line"><span class="keyword">for</span> _, evt := <span class="keyword">range</span> []<span class="keyword">string</span>&#123;<span class="string">"add"</span>, <span class="string">"delete"</span>, <span class="string">"update"</span>&#125; &#123;</span><br><span class="line">eventCount.WithLabelValues(role, evt)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第一次可以全量的将全部事件发送到接口中定义的 <code>ch</code> 中，后续的更新事件，只需要发送更新的事件信息内容即可，如果信息被删除了则可以发送一个为空的事件内容（包含 <code>Source</code>），所有事件通过 <code>Source</code> 字段作为唯一  key。 </p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Discovery implements the discoverer interface for discovering</span></span><br><span class="line"><span class="comment">// targets from Kubernetes.</span></span><br><span class="line"><span class="comment">// 每个 role 会启动一个单独的 Discovery 进行跟踪</span></span><br><span class="line"><span class="keyword">type</span> Discovery <span class="keyword">struct</span> &#123;</span><br><span class="line">sync.RWMutex</span><br><span class="line">client             kubernetes.Interface  <span class="comment">// 连接到 k8s 的 client</span></span><br><span class="line">role               Role</span><br><span class="line">logger             log.Logger</span><br><span class="line">namespaceDiscovery *NamespaceDiscovery <span class="comment">// 保存需要监控的 namespace </span></span><br><span class="line">discoverers        []discoverer        <span class="comment">// 每个 role 按照 namespace 进行划分，单独跟踪的 sd</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 <code>discoverers</code> 分不同的 namespace，每个 namespace 会单独起一个内部的 discoverer 来进行单独的跟踪。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// This is only for internal use.</span></span><br><span class="line"><span class="keyword">type</span> discoverer <span class="keyword">interface</span> &#123;</span><br><span class="line">Run(ctx context.Context, up <span class="keyword">chan</span>&lt;- []*targetgroup.Group)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="初始化和运行"><a href="#初始化和运行" class="headerlink" title="初始化和运行"></a>初始化和运行</h2><p>main 函数入口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="comment">// 初始化 </span></span><br><span class="line">discoveryManagerScrape  = discovery.NewManager(ctxScrape, log.With(logger, <span class="string">"component"</span>, <span class="string">"discovery manager scrape"</span>))</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Notify 的作用待定？</span></span><br><span class="line">    discoveryManagerNotify  = discovery.NewManager(ctxNotify, log.With(logger, <span class="string">"component"</span>, <span class="string">"discovery manager notify"</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">reloaders := []<span class="function"><span class="keyword">func</span><span class="params">(cfg *config.Config)</span> <span class="title">error</span></span>&#123;</span><br><span class="line"><span class="comment">// JobName 为 key ，对应到相关配置</span></span><br><span class="line">        <span class="comment">// v.JobName] = v.ServiceDiscoveryConfig</span></span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(cfg *config.Config)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">c := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]sd_config.ServiceDiscoveryConfig)</span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> cfg.ScrapeConfigs &#123;</span><br><span class="line">c[v.JobName] = v.ServiceDiscoveryConfig</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> discoveryManagerScrape.ApplyConfig(c)</span><br><span class="line">&#125;,</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">        </span><br><span class="line">     &#123;</span><br><span class="line"><span class="comment">// Scrape discovery manager.</span></span><br><span class="line">g.Add(</span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line">               <span class="comment">// 调用 Run 启动</span></span><br><span class="line">err := discoveryManagerScrape.Run()</span><br><span class="line">level.Info(logger).Log(<span class="string">"msg"</span>, <span class="string">"Scrape discovery manager stopped"</span>)</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;,</span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(err error)</span></span> &#123;</span><br><span class="line">level.Info(logger).Log(<span class="string">"msg"</span>, <span class="string">"Stopping scrape discovery manager..."</span>)</span><br><span class="line">cancelScrape()</span><br><span class="line">&#125;,</span><br><span class="line">)</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">   <span class="keyword">if</span> err := g.Run(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">level.Error(logger).Log(<span class="string">"err"</span>, err)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>整个 DS 的入口在 <code>prometheus/discovery/manager.go</code> 中</p><p>NewManager 函数用于生成 DS Mgr 对象：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">// NewManager is the Discovery Manager constructor</span><br><span class="line">func NewManager(ctx context.Context, logger log.Logger) *Manager &#123;</span><br><span class="line">   if logger == nil &#123;</span><br><span class="line">      logger = log.NewNopLogger()</span><br><span class="line">   &#125;</span><br><span class="line">   return &amp;Manager&#123;</span><br><span class="line">      logger:         logger,</span><br><span class="line">      syncCh:         make(chan map[string][]*targetgroup.Group),</span><br><span class="line">      targets:        make(map[poolKey]map[string]*targetgroup.Group),</span><br><span class="line">      discoverCancel: []context.CancelFunc&#123;&#125;,</span><br><span class="line">      ctx:            ctx,</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ApplyConfig removes all running discovery providers and starts new ones using the provided config.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">ApplyConfig</span><span class="params">(cfg <span class="keyword">map</span>[<span class="keyword">string</span>]sd_config.ServiceDiscoveryConfig)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">m.mtx.Lock()</span><br><span class="line"><span class="keyword">defer</span> m.mtx.Unlock()</span><br><span class="line"></span><br><span class="line">m.cancelDiscoverers()</span><br><span class="line">    <span class="comment">// 对于配置文件组织管理</span></span><br><span class="line"><span class="keyword">for</span> name, scfg := <span class="keyword">range</span> cfg &#123;</span><br><span class="line">m.registerProviders(scfg, name)</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 全部启动</span></span><br><span class="line"><span class="keyword">for</span> _, prov := <span class="keyword">range</span> m.providers &#123;</span><br><span class="line">m.startProvider(m.ctx, prov)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将每个 JobName 为 key 的结构进行注册管理</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// provider 用于管理可能相同配置的不同 job 任务</span></span><br><span class="line"><span class="comment">// provider holds a Discoverer instance, its configuration and its subscribers.</span></span><br><span class="line"><span class="keyword">type</span> provider <span class="keyword">struct</span> &#123;</span><br><span class="line">name   <span class="keyword">string</span>       <span class="comment">// "kubernetes_sd_configs/[0-n]"</span></span><br><span class="line">d      Discoverer   <span class="comment">// 对应的 Discoverer </span></span><br><span class="line">subs   []<span class="keyword">string</span>     <span class="comment">// 配置相关情况下的，可能是不同的 JobName</span></span><br><span class="line">config <span class="keyword">interface</span>&#123;&#125;  <span class="comment">// 对应的相关配置</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Manager 结构如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Manager maintains a set of discovery providers and sends each update to a map channel.</span></span><br><span class="line"><span class="comment">// Targets are grouped by the target set name.</span></span><br><span class="line"><span class="keyword">type</span> Manager <span class="keyword">struct</span> &#123;</span><br><span class="line">   logger         log.Logger</span><br><span class="line">   mtx            sync.RWMutex</span><br><span class="line">   ctx            context.Context</span><br><span class="line">   discoverCancel []context.CancelFunc</span><br><span class="line"></span><br><span class="line">   <span class="comment">// Some Discoverers(eg. k8s) send only the updates for a given target group</span></span><br><span class="line">   <span class="comment">// so we use map[tg.Source]*targetgroup.Group to know which group to update.</span></span><br><span class="line">    <span class="comment">// poolkey: job_name + provider_n, 将事件放到各个 job 任务的队列中</span></span><br><span class="line">   targets <span class="keyword">map</span>[poolKey]<span class="keyword">map</span>[<span class="keyword">string</span>]*targetgroup.Group </span><br><span class="line">   <span class="comment">// providers keeps track of SD providers.</span></span><br><span class="line">   providers []*provider</span><br><span class="line">   <span class="comment">// The sync channels sends the updates in map[targetSetName] where targetSetName is the job value from the scrape config.</span></span><br><span class="line">    <span class="comment">// 经过聚合后，将需要更新的对象信息发送到 jobName 的队列中</span></span><br><span class="line">   syncCh <span class="keyword">chan</span> <span class="keyword">map</span>[<span class="keyword">string</span>][]*targetgroup.Group</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>registerProviders：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">registerProviders</span><span class="params">(cfg sd_config.ServiceDiscoveryConfig, setName <span class="keyword">string</span>)</span></span>&#123;</span><br><span class="line">    <span class="comment">// setName 为 JobName</span></span><br><span class="line">add := <span class="function"><span class="keyword">func</span><span class="params">(cfg <span class="keyword">interface</span>&#123;&#125;, newDiscoverer <span class="keyword">func</span>()</span> <span class="params">(Discoverer, error)</span>)</span> &#123;</span><br><span class="line">t := reflect.TypeOf(cfg).String() <span class="comment">// kubernetes_sd_configs</span></span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> m.providers &#123;</span><br><span class="line"><span class="keyword">if</span> reflect.DeepEqual(cfg, p.config) &#123;</span><br><span class="line">p.subs = <span class="built_in">append</span>(p.subs, setName)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// call kubernetes.New(log.With(m.logger, "discovery", "k8s"), cfg)</span></span><br><span class="line">d, err := newDiscoverer()</span><br><span class="line">provider := provider&#123;</span><br><span class="line">            <span class="comment">// t = "kubernetes_sd_configs"</span></span><br><span class="line">name:   fmt.Sprintf(<span class="string">"%s/%d"</span>, t, <span class="built_in">len</span>(m.providers)),</span><br><span class="line">d:      d,</span><br><span class="line">config: cfg,</span><br><span class="line">subs:   []<span class="keyword">string</span>&#123;setName&#125;,</span><br><span class="line">&#125;</span><br><span class="line">m.providers = <span class="built_in">append</span>(m.providers, &amp;provider)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">    <span class="comment">// 循环处理 k8s 相关的配置</span></span><br><span class="line"><span class="keyword">for</span> _, c := <span class="keyword">range</span> cfg.KubernetesSDConfigs &#123;</span><br><span class="line">add(c, <span class="function"><span class="keyword">func</span><span class="params">()</span> <span class="params">(Discoverer, error)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> kubernetes.New(log.With(m.logger, <span class="string">"discovery"</span>, <span class="string">"k8s"</span>), c)</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>整体结构如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Manager --&gt; []provider -&gt; provider[k8s/0] --&gt;  Discovery -&gt;  [roleA]discoverys -&gt; ns1, ns2 </span><br><span class="line">                          provider[k8s/n]                -&gt;  [roleB]discoverys -&gt; ns1, ns2</span><br><span class="line">                          provider[xxx/n]         discovery: Service, Endpoints, Service..</span><br></pre></td></tr></table></figure><p>启动：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">startProvider</span><span class="params">(ctx context.Context, p *provider)</span></span> &#123;</span><br><span class="line">   level.Debug(m.logger).Log(<span class="string">"msg"</span>, <span class="string">"Starting provider"</span>, <span class="string">"provider"</span>, p.name, <span class="string">"subs"</span>, fmt.Sprintf(<span class="string">"%v"</span>, p.subs))</span><br><span class="line">   ctx, cancel := context.WithCancel(ctx)</span><br><span class="line">    </span><br><span class="line">   <span class="comment">// updates 为 SD 对外输出目标的通道，需要重点关注</span></span><br><span class="line">   updates := <span class="built_in">make</span>(<span class="keyword">chan</span> []*targetgroup.Group)</span><br><span class="line"></span><br><span class="line">   m.discoverCancel = <span class="built_in">append</span>(m.discoverCancel, cancel)</span><br><span class="line"></span><br><span class="line">   <span class="keyword">go</span> p.d.Run(ctx, updates) <span class="comment">// 循环启动</span></span><br><span class="line">   <span class="keyword">go</span> m.updater(ctx, p, updates)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> <code>updater(ctx, p, updates)</code> 负责从 SD 发送的通道中读取数据:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">updater</span><span class="params">(ctx context.Context, p *provider, updates <span class="keyword">chan</span> []*targetgroup.Group)</span></span> &#123;</span><br><span class="line">   ticker := time.NewTicker(<span class="number">5</span> * time.Second)</span><br><span class="line">   <span class="keyword">defer</span> ticker.Stop()</span><br><span class="line"></span><br><span class="line">   triggerUpdate := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">   <span class="keyword">for</span> &#123;</span><br><span class="line">      <span class="keyword">select</span> &#123;</span><br><span class="line">      <span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">         <span class="keyword">return</span></span><br><span class="line">      <span class="keyword">case</span> tgs, ok := &lt;-updates:</span><br><span class="line">         <span class="keyword">if</span> !ok &#123;</span><br><span class="line">            level.Debug(m.logger).Log(<span class="string">"msg"</span>, <span class="string">"discoverer channel closed, sending the last update"</span>, <span class="string">"provider"</span>, p.name)</span><br><span class="line">            <span class="keyword">select</span> &#123;</span><br><span class="line">            <span class="keyword">case</span> m.syncCh &lt;- m.allGroups(): <span class="comment">// Waiting until the receiver can accept the last update.</span></span><br><span class="line">               level.Debug(m.logger).Log(<span class="string">"msg"</span>, <span class="string">"discoverer exited"</span>, <span class="string">"provider"</span>, p.name)</span><br><span class="line">               <span class="keyword">return</span></span><br><span class="line">            <span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">               <span class="keyword">return</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">         &#125;</span><br><span class="line">          </span><br><span class="line">          <span class="comment">// s: job_name, provider: k8s/n</span></span><br><span class="line">          <span class="comment">// 针对可能的订阅者(包括相同配置的订阅者) 发送事件</span></span><br><span class="line">         <span class="keyword">for</span> _, s := <span class="keyword">range</span> p.subs &#123;</span><br><span class="line">            m.updateGroup(poolKey&#123;setName: s, provider: p.name&#125;, tgs)</span><br><span class="line">         &#125;</span><br><span class="line"></span><br><span class="line">         <span class="keyword">select</span> &#123;</span><br><span class="line">         <span class="keyword">case</span> triggerUpdate &lt;- <span class="keyword">struct</span>&#123;&#125;&#123;&#125;:</span><br><span class="line">         <span class="keyword">default</span>:</span><br><span class="line">         &#125;</span><br><span class="line">      <span class="keyword">case</span> &lt;-ticker.C: <span class="comment">// Some discoverers send updates too often so we throttle these with the ticker.</span></span><br><span class="line">         <span class="keyword">select</span> &#123;</span><br><span class="line">         <span class="keyword">case</span> &lt;-triggerUpdate:</span><br><span class="line">            <span class="keyword">select</span> &#123;</span><br><span class="line">                <span class="comment">// m.allGroups() 按照 pkey.SetName (job_name 进行聚合)</span></span><br><span class="line">            <span class="keyword">case</span> m.syncCh &lt;- m.allGroups(): </span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">               level.Debug(m.logger).Log(<span class="string">"msg"</span>, <span class="string">"discovery receiver's channel was full so will retry the next cycle"</span>, <span class="string">"provider"</span>, p.name)</span><br><span class="line">               <span class="keyword">select</span> &#123;</span><br><span class="line">               <span class="keyword">case</span> triggerUpdate &lt;- <span class="keyword">struct</span>&#123;&#125;&#123;&#125;:</span><br><span class="line">               <span class="keyword">default</span>:</span><br><span class="line">               &#125;</span><br><span class="line">            &#125;</span><br><span class="line">         <span class="keyword">default</span>:</span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 读取 channel 数据后，放入到相对应的 poolkey 中进行更新</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">updateGroup</span><span class="params">(poolKey poolKey, tgs []*targetgroup.Group)</span></span> &#123;</span><br><span class="line">m.mtx.Lock()</span><br><span class="line"><span class="keyword">defer</span> m.mtx.Unlock()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, tg := <span class="keyword">range</span> tgs &#123;</span><br><span class="line"><span class="keyword">if</span> tg != <span class="literal">nil</span> &#123; <span class="comment">// Some Discoverers send nil target group so need to check for it to avoid panics.</span></span><br><span class="line"><span class="keyword">if</span> _, ok := m.targets[poolKey]; !ok &#123;</span><br><span class="line">m.targets[poolKey] = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]*targetgroup.Group)</span><br><span class="line">&#125;</span><br><span class="line">            <span class="comment">// poolkey: job_name + provider_n, </span></span><br><span class="line">m.targets[poolKey][tg.Source] = tg</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终  Manager 将数据汇总到了  <code>syncCh chan map[string][]*targetgroup.Group</code> 中的定义的 JobName 对应的队列中，通过其 <code>SyncCh</code> 函数将该通道返回出去</p><h2 id="处理更新后的事件"><a href="#处理更新后的事件" class="headerlink" title="处理更新后的事件"></a>处理更新后的事件</h2><p>main 函数中，scrapeManager 负责从 DS Manager 的输出队列中读取数据：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">int main()&#123;</span><br><span class="line">    </span><br><span class="line">// ...</span><br><span class="line">&#123;</span><br><span class="line">// Scrape manager.</span><br><span class="line">g.Add(</span><br><span class="line">func() error &#123;</span><br><span class="line">// When the scrape manager receives a new targets list</span><br><span class="line">// it needs to read a valid config for each job.</span><br><span class="line">// It depends on the config being in sync with the discovery manager so</span><br><span class="line">// we wait until the config is fully loaded.</span><br><span class="line">&lt;-reloadReady.C</span><br><span class="line"></span><br><span class="line">err := scrapeManager.Run(discoveryManagerScrape.SyncCh())</span><br><span class="line">level.Info(logger).Log(&quot;msg&quot;, &quot;Scrape manager stopped&quot;)</span><br><span class="line">return err</span><br><span class="line">&#125;,</span><br><span class="line">func(err error) &#123;</span><br><span class="line">// Scrape manager needs to be stopped before closing the local TSDB</span><br><span class="line">// so that it doesn&apos;t try to write samples to a closed storage.</span><br><span class="line">level.Info(logger).Log(&quot;msg&quot;, &quot;Stopping scrape manager...&quot;)</span><br><span class="line">scrapeManager.Stop()</span><br><span class="line">&#125;,</span><br><span class="line">)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    // ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>scraple/manager.go</code> 中定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Manager maintains a set of scrape pools and manages start/stop cycles</span></span><br><span class="line"><span class="comment">// when receiving new target groups form the discovery manager.</span></span><br><span class="line"><span class="keyword">type</span> Manager <span class="keyword">struct</span> &#123;</span><br><span class="line">logger    log.Logger</span><br><span class="line"><span class="built_in">append</span>    Appendable</span><br><span class="line">graceShut <span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">mtxTargets     sync.Mutex <span class="comment">// Guards the fields below.</span></span><br><span class="line">targetsActive  []*Target</span><br><span class="line">targetsDropped []*Target</span><br><span class="line">targetsAll     <span class="keyword">map</span>[<span class="keyword">string</span>][]*Target</span><br><span class="line"></span><br><span class="line">mtxScrape     sync.Mutex <span class="comment">// Guards the fields below.</span></span><br><span class="line">scrapeConfigs <span class="keyword">map</span>[<span class="keyword">string</span>]*config.ScrapeConfig</span><br><span class="line">scrapePools   <span class="keyword">map</span>[<span class="keyword">string</span>]*scrapePool</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Run starts background processing to handle target updates and reload the scraping loops.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">Run</span><span class="params">(tsets &lt;-<span class="keyword">chan</span> <span class="keyword">map</span>[<span class="keyword">string</span>][]*targetgroup.Group)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> ts := &lt;-tsets:</span><br><span class="line">m.reload(ts)</span><br><span class="line"><span class="keyword">case</span> &lt;-m.graceShut:</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>reload 函数定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m *Manager)</span> <span class="title">reload</span><span class="params">(t <span class="keyword">map</span>[<span class="keyword">string</span>][]*targetgroup.Group)</span></span> &#123;</span><br><span class="line">m.mtxScrape.Lock()</span><br><span class="line"><span class="keyword">defer</span> m.mtxScrape.Unlock()</span><br><span class="line"></span><br><span class="line">tDropped := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>][]*Target)</span><br><span class="line">tActive := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>][]*Target)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> tsetName, tgroup := <span class="keyword">range</span> t &#123;</span><br><span class="line"><span class="keyword">var</span> sp *scrapePool</span><br><span class="line"><span class="keyword">if</span> existing, ok := m.scrapePools[tsetName]; !ok &#123;</span><br><span class="line">scrapeConfig, ok := m.scrapeConfigs[tsetName]</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line">level.Error(m.logger).Log(<span class="string">"msg"</span>, <span class="string">"error reloading target set"</span>, <span class="string">"err"</span>, fmt.Sprintf(<span class="string">"invalid config id:%v"</span>, tsetName))</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">sp = newScrapePool(scrapeConfig, m.<span class="built_in">append</span>, log.With(m.logger, <span class="string">"scrape_pool"</span>, tsetName))</span><br><span class="line">m.scrapePools[tsetName] = sp</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">sp = existing</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Sync 函数中用于过滤相关符合条件的 Target</span></span><br><span class="line">tActive[tsetName], tDropped[tsetName] = sp.Sync(tgroup)</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更新获取和丢弃的目标，可以通过界面查询到对应的结果 </span></span><br><span class="line">m.targetsUpdate(tActive, tDropped)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>sync 定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Sync converts target groups into actual scrape targets and synchronizes</span></span><br><span class="line"><span class="comment">// the currently running scraper with the resulting set and returns all scraped and dropped targets.</span></span><br><span class="line"><span class="comment">// 同步将目标组转换为实际的抓取目标，并将当前运行的抓取与结果集同步，并返回所有刮取和删除的目标。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sp *scrapePool)</span> <span class="title">Sync</span><span class="params">(tgs []*targetgroup.Group)</span> <span class="params">(tActive []*Target, tDropped []*Target)</span></span> &#123;</span><br><span class="line">start := time.Now()</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> all []*Target</span><br><span class="line">sp.mtx.Lock()</span><br><span class="line">sp.droppedTargets = []*Target&#123;&#125;</span><br><span class="line"><span class="keyword">for</span> _, tg := <span class="keyword">range</span> tgs &#123;</span><br><span class="line">        <span class="comment">// targetsFromGroup 函数通过相关配置完成转换</span></span><br><span class="line">targets, err := targetsFromGroup(tg, sp.config)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, t := <span class="keyword">range</span> targets &#123;</span><br><span class="line">            <span class="comment">// 返回目标的标签，不是处理后的，不以 “__” 为前缀</span></span><br><span class="line"><span class="keyword">if</span> t.Labels().Len() &gt; <span class="number">0</span> &#123; <span class="comment">// 不存在以 “__" 为前缀匹配的标签</span></span><br><span class="line">all = <span class="built_in">append</span>(all, t)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> t.DiscoveredLabels().Len() &gt; <span class="number">0</span> &#123;</span><br><span class="line">sp.droppedTargets = <span class="built_in">append</span>(sp.droppedTargets, t)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">sp.mtx.Unlock()</span><br><span class="line">sp.sync(all)</span><br><span class="line"></span><br><span class="line">targetSyncIntervalLength.WithLabelValues(sp.config.JobName).Observe(</span><br><span class="line">time.Since(start).Seconds(),</span><br><span class="line">)</span><br><span class="line">targetScrapePoolSyncsCounter.WithLabelValues(sp.config.JobName).Inc()</span><br><span class="line"></span><br><span class="line">sp.mtx.RLock()</span><br><span class="line"><span class="keyword">for</span> _, t := <span class="keyword">range</span> sp.targets &#123;</span><br><span class="line">tActive = <span class="built_in">append</span>(tActive, t)</span><br><span class="line">&#125;</span><br><span class="line">tDropped = sp.droppedTargets</span><br><span class="line">sp.mtx.RUnlock()</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> tActive, tDropped</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>targetsFromGroup函数根据输入的对象信息和相关配置完成过滤的整体工作：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// targetsFromGroup builds targets based on the given TargetGroup and config.</span></span><br><span class="line"><span class="comment">// targetsFromGroup 根据给定的 TargetGroup 和 config 构建目标</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">targetsFromGroup</span><span class="params">(tg *targetgroup.Group, cfg *config.ScrapeConfig)</span> <span class="params">([]*Target, error)</span></span> &#123;</span><br><span class="line">targets := <span class="built_in">make</span>([]*Target, <span class="number">0</span>, <span class="built_in">len</span>(tg.Targets))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i, tlset := <span class="keyword">range</span> tg.Targets &#123;</span><br><span class="line">        <span class="comment">// 将每个数组中的标签和通用标签合并进行过滤</span></span><br><span class="line">lbls := <span class="built_in">make</span>([]labels.Label, <span class="number">0</span>, <span class="built_in">len</span>(tlset)+<span class="built_in">len</span>(tg.Labels))</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 合并发现的标签</span></span><br><span class="line"><span class="keyword">for</span> ln, lv := <span class="keyword">range</span> tlset &#123;</span><br><span class="line">lbls = <span class="built_in">append</span>(lbls, labels.Label&#123;Name: <span class="keyword">string</span>(ln), Value: <span class="keyword">string</span>(lv)&#125;)</span><br><span class="line">&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 合并通用标签</span></span><br><span class="line"><span class="keyword">for</span> ln, lv := <span class="keyword">range</span> tg.Labels &#123;</span><br><span class="line"><span class="keyword">if</span> _, ok := tlset[ln]; !ok &#123;</span><br><span class="line">lbls = <span class="built_in">append</span>(lbls, labels.Label&#123;Name: <span class="keyword">string</span>(ln), Value: <span class="keyword">string</span>(lv)&#125;)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 复制一份合并后的标签列表</span></span><br><span class="line">lset := labels.New(lbls...)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 根据 cfg 配置来进行过滤， lset 为本次的全量标签; lbls 为根据配置处理后的标签集合，origLabels 为处理之前的原始标签集</span></span><br><span class="line">lbls, origLabels, err := populateLabels(lset, cfg)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果 lbls 或者 origLabels 有一个不为空，则加入</span></span><br><span class="line"><span class="keyword">if</span> lbls != <span class="literal">nil</span> || origLabels != <span class="literal">nil</span> &#123;    <span class="comment">// cfg.Params 配置中添加到 url 后的参数</span></span><br><span class="line">targets = <span class="built_in">append</span>(targets, NewTarget(lbls, origLabels, cfg.Params))</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> targets, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>过滤后的 Target 结构定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Target refers to a singular HTTP or HTTPS endpoint.</span></span><br><span class="line"><span class="keyword">type</span> Target <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// Labels before any processing.</span></span><br><span class="line">discoveredLabels labels.Labels</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Any labels that are added to this target and its metrics.</span></span><br><span class="line">labels labels.Labels</span><br><span class="line">    </span><br><span class="line"><span class="comment">// Additional URL parmeters that are part of the target URL.</span></span><br><span class="line">params url.Values</span><br><span class="line"></span><br><span class="line">mtx        sync.RWMutex</span><br><span class="line">lastError  error</span><br><span class="line">lastScrape time.Time</span><br><span class="line">health     TargetHealth</span><br><span class="line">metadata   metricMetadataStore</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>populateLabels 根据给定的标签集和 scrape 配置构建标签集。<br>会在重新标记应用之前返回标签集作为第二个返回值。<br>如果在重新标记期间丢弃目标，则返回在应用重新标记之前找到的原始发现标签集。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// populateLabels builds a label set from the given label set and scrape configuration.</span></span><br><span class="line"><span class="comment">// It returns a label set before relabeling was applied as the second return value.</span></span><br><span class="line"><span class="comment">// Returns the original discovered label set found before relabelling was applied if the target is dropped during relabeling.</span></span><br><span class="line"><span class="comment">// 函数根据给定的 labels 和 相关配置的选项，来进行 relabel 的处理，返回的第一个参数为匹配后的结果集，第二个参数返回应用之前的 labels， 如果第一个参数为空，则表示该目标被丢失，比如 action:drop </span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">populateLabels</span><span class="params">(lset labels.Labels, cfg *config.ScrapeConfig)</span> <span class="params">(res, orig labels.Labels, err error)</span></span> &#123;</span><br><span class="line">   <span class="comment">// Copy labels into the labelset for the target if they are not set already.</span></span><br><span class="line">   scrapeLabels := []labels.Label&#123;</span><br><span class="line">      &#123;Name: model.JobLabel, Value: cfg.JobName&#125;,</span><br><span class="line">      &#123;Name: model.MetricsPathLabel, Value: cfg.MetricsPath&#125;,</span><br><span class="line">      &#123;Name: model.SchemeLabel, Value: cfg.Scheme&#125;,</span><br><span class="line">   &#125;</span><br><span class="line">   lb := labels.NewBuilder(lset)</span><br><span class="line"></span><br><span class="line">   <span class="keyword">for</span> _, l := <span class="keyword">range</span> scrapeLabels &#123;</span><br><span class="line">      <span class="keyword">if</span> lv := lset.Get(l.Name); lv == <span class="string">""</span> &#123;</span><br><span class="line">         lb.Set(l.Name, l.Value)</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="comment">// Encode scrape query parameters as labels.</span></span><br><span class="line">   <span class="keyword">for</span> k, v := <span class="keyword">range</span> cfg.Params &#123;</span><br><span class="line">      <span class="keyword">if</span> <span class="built_in">len</span>(v) &gt; <span class="number">0</span> &#123;</span><br><span class="line">         lb.Set(model.ParamLabelPrefix+k, v[<span class="number">0</span>])</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   preRelabelLabels := lb.Labels()</span><br><span class="line">   <span class="comment">// relabel.Process 进程返回给定标签集的重新标记的副本。 relabel 按输入顺序应用。</span></span><br><span class="line">   <span class="comment">// 如果删除标签集，则返回nill</span></span><br><span class="line">   <span class="comment">// 可以返回修改的输入 labelSet。</span></span><br><span class="line">   <span class="comment">// Process 会自动添加 job 和 instance 两个lable， 如果 lset 为空这说明不是监控的目标</span></span><br><span class="line">   lset = relabel.Process(preRelabelLabels, cfg.RelabelConfigs...)</span><br><span class="line"></span><br><span class="line">   <span class="comment">// Check if the target was dropped.</span></span><br><span class="line">   <span class="keyword">if</span> lset == <span class="literal">nil</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">nil</span>, preRelabelLabels, <span class="literal">nil</span></span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">if</span> v := lset.Get(model.AddressLabel); v == <span class="string">""</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span>, fmt.Errorf(<span class="string">"no address"</span>)</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   lb = labels.NewBuilder(lset)</span><br><span class="line"></span><br><span class="line">   <span class="comment">// addPort checks whether we should add a default port to the address.</span></span><br><span class="line">   <span class="comment">// If the address is not valid, we don't append a port either.</span></span><br><span class="line">   addPort := <span class="function"><span class="keyword">func</span><span class="params">(s <span class="keyword">string</span>)</span> <span class="title">bool</span></span> &#123;</span><br><span class="line">      <span class="comment">// If we can split, a port exists and we don't have to add one.</span></span><br><span class="line">      <span class="keyword">if</span> _, _, err := net.SplitHostPort(s); err == <span class="literal">nil</span> &#123;</span><br><span class="line">         <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// If adding a port makes it valid, the previous error</span></span><br><span class="line">      <span class="comment">// was not due to an invalid address and we can append a port.</span></span><br><span class="line">      _, _, err := net.SplitHostPort(s + <span class="string">":1234"</span>)</span><br><span class="line">      <span class="keyword">return</span> err == <span class="literal">nil</span></span><br><span class="line">   &#125;</span><br><span class="line">   addr := lset.Get(model.AddressLabel)</span><br><span class="line">   <span class="comment">// If it's an address with no trailing port, infer it based on the used scheme.</span></span><br><span class="line">   <span class="keyword">if</span> addPort(addr) &#123;</span><br><span class="line">      <span class="comment">// Addresses reaching this point are already wrapped in [] if necessary.</span></span><br><span class="line">      <span class="keyword">switch</span> lset.Get(model.SchemeLabel) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">"http"</span>, <span class="string">""</span>:</span><br><span class="line">         addr = addr + <span class="string">":80"</span></span><br><span class="line">      <span class="keyword">case</span> <span class="string">"https"</span>:</span><br><span class="line">         addr = addr + <span class="string">":443"</span></span><br><span class="line">      <span class="keyword">default</span>:</span><br><span class="line">         <span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span>, fmt.Errorf(<span class="string">"invalid scheme: %q"</span>, cfg.Scheme)</span><br><span class="line">      &#125;</span><br><span class="line">      lb.Set(model.AddressLabel, addr)</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> err := config.CheckTargetAddress(model.LabelValue(addr)); err != <span class="literal">nil</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span>, err</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// Meta labels are deleted after relabelling. Other internal labels propagate to</span></span><br><span class="line">   <span class="comment">// the target which decides whether they will be part of their label set.</span></span><br><span class="line">   <span class="keyword">for</span> _, l := <span class="keyword">range</span> lset &#123;</span><br><span class="line">      <span class="keyword">if</span> strings.HasPrefix(l.Name, model.MetaLabelPrefix) &#123;</span><br><span class="line">         lb.Del(l.Name)</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// Default the instance label to the target address.</span></span><br><span class="line">   <span class="keyword">if</span> v := lset.Get(model.InstanceLabel); v == <span class="string">""</span> &#123;</span><br><span class="line">      lb.Set(model.InstanceLabel, addr)</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   res = lb.Labels()</span><br><span class="line">   <span class="keyword">for</span> _, l := <span class="keyword">range</span> res &#123;</span><br><span class="line">      <span class="comment">// Check label values are valid, drop the target if not.</span></span><br><span class="line">      <span class="keyword">if</span> !model.LabelValue(l.Value).IsValid() &#123;</span><br><span class="line">         <span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span>, fmt.Errorf(<span class="string">"invalid label value for %q: %q"</span>, l.Name, l.Value)</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> res, preRelabelLabels, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于 prometheus_client 相关的测试样例参见： <a href="https://github.com/DavadDi/Kubernetes_study/tree/master/prometheus_client" target="_blank" rel="noopener">https://github.com/DavadDi/Kubernetes_study/tree/master/prometheus_client</a> </p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Service-Discovery-interface&quot;&gt;&lt;a href=&quot;#Service-Discovery-interface&quot; class=&quot;headerlink&quot; title=&quot;Service Discovery interface&quot;&gt;&lt;/a&gt;Servi
      
    
    </summary>
    
      <category term="prometheus" scheme="http://www.cn18k.com/categories/prometheus/"/>
    
    
      <category term="kubenetes" scheme="http://www.cn18k.com/tags/kubenetes/"/>
    
      <category term="prometheus" scheme="http://www.cn18k.com/tags/prometheus/"/>
    
  </entry>
  
  <entry>
    <title>gRPC 之 LoadBalancer</title>
    <link href="http://www.cn18k.com/2018/09/07/grpc-loadbanlancer/"/>
    <id>http://www.cn18k.com/2018/09/07/grpc-loadbanlancer/</id>
    <published>2018-09-07T13:00:00.000Z</published>
    <updated>2018-09-07T13:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>基于 Etcd 实现的服务注册和发现的 grpclb 参见： <a href="https://github.com/DavadDi/grpclb" target="_blank" rel="noopener">grpclb</a></p><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><p>gRPC 负载均衡是针对每次请求，而不是连接，这样可以保证服务端负载的均衡性。负载均衡器按照实现侧不同一般分为两种：</p><h3 id="1-Proxy"><a href="#1-Proxy" class="headerlink" title="1. Proxy"></a>1. Proxy</h3><p><img src="https://grpc.io/img/image_0.png" alt=""></p><h3 id="2-Client-Side"><a href="#2-Client-Side" class="headerlink" title="2. Client Side"></a>2. Client Side</h3><p>   <img src="https://grpc.io/img/image_1.png" alt=""></p><h4 id="Thick-Client"><a href="#Thick-Client" class="headerlink" title="Thick Client"></a>Thick Client</h4><p>所有负载均衡算法实现都在客户端。</p><h4 id="Lookaside-Load-Balancing"><a href="#Lookaside-Load-Balancing" class="headerlink" title="Lookaside Load Balancing"></a>Lookaside Load Balancing</h4><p>也称单臂路由。</p><p><img src="https://grpc.io/img/image_2.png" alt=""></p><h3 id="Etcd-Loadbanlacer-实现"><a href="#Etcd-Loadbanlacer-实现" class="headerlink" title="Etcd Loadbanlacer 实现"></a>Etcd Loadbanlacer 实现</h3><p>gPRC 当前最新版本为 <a href="https://github.com/grpc/grpc-go/releases/tag/v1.14.0" target="_blank" rel="noopener">Release 1.14.0</a>，由于 Etcd 不同版本的 Banlancer 实现方式不仅相同，可以参考：<a href="https://etcd.readthedocs.io/en/latest/client-architecture.html" target="_blank" rel="noopener">client-architecture</a>/ Etcd v3.4.0 (TBD 2018-09)。组合条件有以下几类算法：</p><ul><li><p>clientv3-grpc1.0</p><ul><li><p>客户端同时保留与服务端 Nodes 多个连接，在出现错误时候，可以快速重试其他的连接</p></li><li><p>limit： 保持多个连接浪费资源；对于 Nodes 的健康状态或者集群 Membership 关系未知，在某个 Node 出现问题时可能不能正常工作。</p><p><img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-01.png" alt=""></p></li></ul></li><li><p>clientv3-grpc1.7  （可选者方案）</p><ul><li><p>与服务端中的某一个 Node 保持连接。当集群中有多个endpoints 时候，尝试连接到所有的 endpoints，一旦选中了一个连接（pinned address），关闭其他的连接，直至该连接被关闭。如果调用中间出现错误，则进入错误处理流程。</p><p><img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-02.png" alt="client-architecture-balancer-figure-02"><br> <img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-03.png" alt="client-architecture-balancer-figure-03"><br> <img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-04.png" alt="client-architecture-balancer-figure-04"><br> <img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-05.png" alt="client-architecture-balancer-figure-05"><br> <img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-06.png" alt="client-architecture-balancer-figure-06"></p></li><li><p>限制：使用 HTTP/2 keepalives 来保持心跳，可能会出现脑裂的情况。</p><p>  <img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-07.png" alt=""><br><img src="https://etcd.readthedocs.io/en/latest/_images/client-architecture-balancer-figure-08.png" alt=""></p></li></ul></li><li>clientv3-grpc1.14  （v3.4.0 开发中）</li></ul><h2 id="gRPC-1-14-LD-分析"><a href="#gRPC-1-14-LD-分析" class="headerlink" title="gRPC 1.14 LD 分析"></a>gRPC 1.14 LD 分析</h2><p><img src="https://github.com/grpc/proposal/raw/master/L9_graphics/bar_after.png" alt=""></p><h3 id="dnsResolver"><a href="#dnsResolver" class="headerlink" title="dnsResolver"></a>dnsResolver</h3><p>注册 dnsResolver：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">   resolver.Register(NewBuilder())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Resolver 的 Builder 接口。Builder 会创建一个用于监视名称解析更新的 resolver 。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Builder creates a resolver that will be used to watch name resolution updates.</span></span><br><span class="line"><span class="keyword">type</span> Builder <span class="keyword">interface</span> &#123;</span><br><span class="line">   <span class="comment">// Build creates a new resolver for the given target.</span></span><br><span class="line">   <span class="comment">//</span></span><br><span class="line">   <span class="comment">// gRPC dial calls Build synchronously, and fails if the returned error is</span></span><br><span class="line">   <span class="comment">// not nil.</span></span><br><span class="line">   Build(target Target, cc ClientConn, opts BuildOption) (Resolver, error)</span><br><span class="line">   <span class="comment">// Scheme returns the scheme supported by this resolver.</span></span><br><span class="line">   <span class="comment">// Scheme is defined at https://github.com/grpc/grpc/blob/master/doc/naming.md.</span></span><br><span class="line">   Scheme() <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Resover 的接口定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Resolver watches for the updates on the specified target.</span></span><br><span class="line"><span class="comment">// Updates include address updates and service config updates.</span></span><br><span class="line"><span class="keyword">type</span> Resolver <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// ResolveNow will be called by gRPC to try to resolve the target name</span></span><br><span class="line"><span class="comment">// again. It's just a hint, resolver can ignore this if it's not necessary.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// It could be called multiple times concurrently.</span></span><br><span class="line">ResolveNow(ResolveNowOption)</span><br><span class="line"><span class="comment">// Close closes the resolver.</span></span><br><span class="line">Close()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码分析，首先 <code>NewBuilder</code> 作为一个工厂来创建一个 <code>resolver.Builder</code>，供 gRPC 程序来进行调用：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NewBuilder creates a dnsBuilder which is used to factory DNS resolvers.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewBuilder</span><span class="params">()</span> <span class="title">resolver</span>.<span class="title">Builder</span></span> &#123;</span><br><span class="line">   <span class="keyword">return</span> &amp;dnsBuilder&#123;minFreq: defaultFreq&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>dnsResolver 结构定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// dnsResolver watches for the name resolution update for a non-IP target.</span></span><br><span class="line"><span class="keyword">type</span> dnsResolver <span class="keyword">struct</span> &#123;</span><br><span class="line">freq       time.Duration</span><br><span class="line">backoff    backoff.Exponential</span><br><span class="line">retryCount <span class="keyword">int</span></span><br><span class="line">host       <span class="keyword">string</span></span><br><span class="line">port       <span class="keyword">string</span></span><br><span class="line">ctx        context.Context</span><br><span class="line">cancel     context.CancelFunc</span><br><span class="line">cc         resolver.ClientConn</span><br><span class="line">    <span class="comment">// rn 是一个 channel， 用于 ResolveNow() 调用的时候强制进行解析</span></span><br><span class="line"><span class="comment">// rn channel is used by ResolveNow() to force an immediate resolution of the target.</span></span><br><span class="line">rn <span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">t  *time.Timer</span><br><span class="line">    <span class="comment">// wg 用于等待 watcher 的 goroutine 结束，否则可能导致 data race</span></span><br><span class="line"><span class="comment">// wg is used to enforce Close() to return after the watcher() goroutine has finished.</span></span><br><span class="line"><span class="comment">// Otherwise, data race will be possible. [Race Example] in dns_resolver_test we</span></span><br><span class="line"><span class="comment">// replace the real lookup functions with mocked ones to facilitate testing.</span></span><br><span class="line"><span class="comment">// If Close() doesn't wait for watcher() goroutine finishes, race detector sometimes</span></span><br><span class="line"><span class="comment">// will warns lookup (READ the lookup function pointers) inside watcher() goroutine</span></span><br><span class="line"><span class="comment">// has data race with replaceNetFunc (WRITE the lookup function pointers).</span></span><br><span class="line">wg                   sync.WaitGroup</span><br><span class="line">disableServiceConfig <span class="keyword">bool</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Build 创建并启动一个 DNS 解析器，用于监视目标的名称解析。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Build creates and starts a DNS resolver that watches the name resolution of the target.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *dnsBuilder)</span> <span class="title">Build</span><span class="params">(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption)</span> <span class="params">(resolver.Resolver, error)</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">host, port, err := parseTarget(target.Endpoint)</span><br><span class="line"></span><br><span class="line"><span class="comment">// IP address.</span></span><br><span class="line"><span class="comment">// 省略分析 ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// DNS address (non-IP).</span></span><br><span class="line">    <span class="comment">// 创建一个用于跟踪的 ctx 和 cancel </span></span><br><span class="line">ctx, cancel := context.WithCancel(context.Background())</span><br><span class="line">d := &amp;dnsResolver&#123;</span><br><span class="line">freq:                 b.minFreq,</span><br><span class="line">backoff:              backoff.Exponential&#123;MaxDelay: b.minFreq&#125;,</span><br><span class="line">host:                 host,</span><br><span class="line">port:                 port,</span><br><span class="line">ctx:                  ctx,</span><br><span class="line">cancel:               cancel,</span><br><span class="line">cc:                   cc,</span><br><span class="line">t:                    time.NewTimer(<span class="number">0</span>),</span><br><span class="line">rn:                   <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;, <span class="number">1</span>),</span><br><span class="line">disableServiceConfig: opts.DisableServiceConfig,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">d.wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> d.watcher()  <span class="comment">// 启动 watcher 进行监听</span></span><br><span class="line"><span class="keyword">return</span> d, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>传入参数 <code>cc resolver.ClientConn</code>，ClientConn 包含 <code>resolver</code> 的回调，以通知 <code>gRPC</code> <code>ClientConn</code> 的任何更新。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ClientConn contains the callbacks for resolver to notify any updates</span></span><br><span class="line"><span class="comment">// to the gRPC ClientConn.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// This interface is to be implemented by gRPC. Users should not need a</span></span><br><span class="line"><span class="comment">// brand new implementation of this interface. For the situations like</span></span><br><span class="line"><span class="comment">// testing, the new implementation should embed this interface. This allows</span></span><br><span class="line"><span class="comment">// gRPC to add new methods to this interface.</span></span><br><span class="line"><span class="keyword">type</span> ClientConn <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// NewAddress is called by resolver to notify ClientConn a new list</span></span><br><span class="line"><span class="comment">// of resolved addresses.</span></span><br><span class="line"><span class="comment">// The address list should be the complete list of resolved addresses.</span></span><br><span class="line">NewAddress(addresses []Address)</span><br><span class="line"><span class="comment">// NewServiceConfig is called by resolver to notify ClientConn a new</span></span><br><span class="line"><span class="comment">// service config. The service config should be provided as a json string.</span></span><br><span class="line">NewServiceConfig(serviceConfig <span class="keyword">string</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>watcher</code> 函数的实现如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(d *dnsResolver)</span> <span class="title">watcher</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> d.wg.Done()</span><br><span class="line">    <span class="comment">// 设置一个 watcher 死循环，一直在监听</span></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-d.ctx.Done(): <span class="comment">// 如果被取消，通过 ctx，则直接结束 for 循环</span></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">case</span> &lt;-d.t.C: <span class="comment">// 定时器触发，第一次为0，所以直接通过</span></span><br><span class="line"><span class="keyword">case</span> &lt;-d.rn: <span class="comment">// rn 是一个 channel， 用于 ResolveNow() 调用的时候强制进行解析</span></span><br><span class="line">                             <span class="comment">// ResolveNow 的时候操作  ase d.rn &lt;- struct&#123;&#125;&#123;&#125;:</span></span><br><span class="line">&#125;</span><br><span class="line">        <span class="comment">// 开始异步进行解析</span></span><br><span class="line">result, sc := d.lookup()</span><br><span class="line"><span class="comment">// Next lookup should happen within an interval defined by d.freq. It may be</span></span><br><span class="line"><span class="comment">// more often due to exponential retry on empty address list.</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(result) == <span class="number">0</span> &#123;</span><br><span class="line">d.retryCount++</span><br><span class="line">d.t.Reset(d.backoff.Backoff(d.retryCount))</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">d.retryCount = <span class="number">0</span></span><br><span class="line">d.t.Reset(d.freq)</span><br><span class="line">&#125;</span><br><span class="line">d.cc.NewServiceConfig(sc)   <span class="comment">// 回调进行通知</span></span><br><span class="line">d.cc.NewAddress(result)     <span class="comment">// 回调进行通知</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 Close 函数的调用来达到停止 watcher 的效果：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Close closes the dnsResolver.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(d *dnsResolver)</span> <span class="title">Close</span><span class="params">()</span></span> &#123;</span><br><span class="line">d.cancel()</span><br><span class="line">d.wg.Wait()</span><br><span class="line">d.t.Stop()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="RR-Balancer"><a href="#RR-Balancer" class="headerlink" title="RR Balancer"></a>RR Balancer</h3><p><img src="https://github.com/grpc/proposal/raw/master/L9_graphics/bar_after.png" alt=""></p><p>Banlancer Builder 接口定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Builder creates a balancer.</span></span><br><span class="line"><span class="keyword">type</span> Builder <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// Build creates a new balancer with the ClientConn.</span></span><br><span class="line">Build(cc ClientConn, opts BuildOptions) Balancer</span><br><span class="line"><span class="comment">// Name returns the name of balancers built by this builder.</span></span><br><span class="line"><span class="comment">// It will be used to pick balancers (for example in service config).</span></span><br><span class="line">Name() <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Banlancer 接口定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Balancer takes input from gRPC, manages SubConns, and collects and aggregates</span></span><br><span class="line"><span class="comment">// the connectivity states.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// It also generates and updates the Picker used by gRPC to pick SubConns for RPCs.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// HandleSubConnectionStateChange, HandleResolvedAddrs and Close are guaranteed</span></span><br><span class="line"><span class="comment">// to be called synchronously from the same goroutine.</span></span><br><span class="line"><span class="comment">// There's no guarantee on picker.Pick, it may be called anytime.</span></span><br><span class="line"><span class="keyword">type</span> Balancer <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// HandleSubConnStateChange is called by gRPC when the connectivity state</span></span><br><span class="line"><span class="comment">// of sc has changed.</span></span><br><span class="line"><span class="comment">// Balancer is expected to aggregate all the state of SubConn and report</span></span><br><span class="line"><span class="comment">// that back to gRPC.</span></span><br><span class="line"><span class="comment">// Balancer should also generate and update Pickers when its internal state has</span></span><br><span class="line"><span class="comment">// been changed by the new state.</span></span><br><span class="line">HandleSubConnStateChange(sc SubConn, state connectivity.State)</span><br><span class="line"><span class="comment">// HandleResolvedAddrs is called by gRPC to send updated resolved addresses to</span></span><br><span class="line"><span class="comment">// balancers.</span></span><br><span class="line"><span class="comment">// Balancer can create new SubConn or remove SubConn with the addresses.</span></span><br><span class="line"><span class="comment">// An empty address slice and a non-nil error will be passed if the resolver returns</span></span><br><span class="line"><span class="comment">// non-nil error to gRPC.</span></span><br><span class="line">HandleResolvedAddrs([]resolver.Address, error)</span><br><span class="line"><span class="comment">// Close closes the balancer. The balancer is not required to call</span></span><br><span class="line"><span class="comment">// ClientConn.RemoveSubConn for its existing SubConns.</span></span><br><span class="line">Close()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>banlancer/base 中实现了大多数 banlancer 功能，不同选择算法的实现，基于 base 实现 pickBuilder 与 picker 接口即可。</p><p><code>base.NewBalancerBuilder</code> 函数定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Base 实现的函数</span></span><br><span class="line"><span class="comment">// NewBalancerBuilder returns a balancer builder. The balancers</span></span><br><span class="line"><span class="comment">// built by this builder will use the picker builder to build pickers.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewBalancerBuilder</span><span class="params">(name <span class="keyword">string</span>, pb PickerBuilder)</span> <span class="title">balancer</span>.<span class="title">Builder</span></span> &#123;</span><br><span class="line">   <span class="keyword">return</span> &amp;baseBuilder&#123;</span><br><span class="line">      name:          name,</span><br><span class="line">      pickerBuilder: pb,</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RR Banlancer 的实现基于 balancer/base 基础实现，核心功能主体在 balancer/base 中实现，而 RR Banlancer 基于 <code>base.NewBalancerBuilder</code> 实现了 balancer.Builder 接口，可以用于注册。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Name is the name of round_robin balancer.</span></span><br><span class="line"><span class="keyword">const</span> Name = <span class="string">"round_robin"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// newBuilder creates a new roundrobin balancer builder.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">newBuilder</span><span class="params">()</span> <span class="title">balancer</span>.<span class="title">Builder</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> base.NewBalancerBuilder(Name, &amp;rrPickerBuilder&#123;&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">balancer.Register(newBuilder())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>PickerBuilder 接口定义如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PickerBuilder creates balancer.Picker.</span></span><br><span class="line"><span class="keyword">type</span> PickerBuilder <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// Build takes a slice of ready SubConns, and returns a picker that will be</span></span><br><span class="line"><span class="comment">// used by gRPC to pick a SubConn.</span></span><br><span class="line">Build(readySCs <span class="keyword">map</span>[resolver.Address]balancer.SubConn) balancer.Picker</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RoundRobin Banlancer Builder 实现了  <code>PickerBuilder</code> 的接口：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*rrPickerBuilder)</span> <span class="title">Build</span><span class="params">(readySCs <span class="keyword">map</span>[resolver.Address]balancer.SubConn)</span> <span class="title">balancer</span>.<span class="title">Picker</span></span> &#123;</span><br><span class="line">grpclog.Infof(<span class="string">"roundrobinPicker: newPicker called with readySCs: %v"</span>, readySCs)</span><br><span class="line">    <span class="comment">// 将 map 接口转化成 slice[] 结构，并使用期构造 rrPicker 并返回</span></span><br><span class="line"><span class="keyword">var</span> scs []balancer.SubConn</span><br><span class="line"><span class="keyword">for</span> _, sc := <span class="keyword">range</span> readySCs &#123;</span><br><span class="line">scs = <span class="built_in">append</span>(scs, sc)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;rrPicker&#123;</span><br><span class="line">subConns: scs,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>banlancer.Picker 由 rrPicker 来实现：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> rrPicker <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// subConns is the snapshot of the roundrobin balancer when this picker was</span></span><br><span class="line"><span class="comment">// created. The slice is immutable. Each Get() will do a round robin</span></span><br><span class="line"><span class="comment">// selection from it and return the selected SubConn.</span></span><br><span class="line">subConns []balancer.SubConn</span><br><span class="line"></span><br><span class="line">mu   sync.Mutex</span><br><span class="line">    <span class="comment">// 用于记录下一个位移量</span></span><br><span class="line">next <span class="keyword">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p *rrPicker)</span> <span class="title">Pick</span><span class="params">(ctx context.Context, opts balancer.PickOptions)</span> <span class="params">(balancer.SubConn, <span class="keyword">func</span>(balancer.DoneInfo)</span>, <span class="title">error</span>)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(p.subConns) &lt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, <span class="literal">nil</span>, balancer.ErrNoSubConnAvailable</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">p.mu.Lock()</span><br><span class="line">sc := p.subConns[p.next]</span><br><span class="line">p.next = (p.next + <span class="number">1</span>) % <span class="built_in">len</span>(p.subConns)</span><br><span class="line">p.mu.Unlock()</span><br><span class="line"><span class="keyword">return</span> sc, <span class="literal">nil</span>, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Etcd-服务注册与发现"><a href="#Etcd-服务注册与发现" class="headerlink" title="Etcd 服务注册与发现"></a>Etcd 服务注册与发现</h2><p><em>clientv3-grpc1.14</em>: Official client implementation, with <a href="https://github.com/grpc/grpc-go/releases/tag/v1.14.0" target="_blank" rel="noopener">grpc-go v1.14.x</a>, which is used in latest etcd v3.4.</p><p>etcdv3Client  -&gt; autoSync()  -&gt; Sync() -&gt; c.SetEndpoints(eps…)  -&gt; gc.resolverGroup.SetEndpoints(eps) -&gt; EveryResover  -&gt; ClientConn update</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://grpc.io/blog/loadbalancing" target="_blank" rel="noopener">gRPC Load Balancing</a></li><li><a href="https://github.com/grpc/grpc/blob/master/doc/load-balancing.md" target="_blank" rel="noopener">Load Balancing in gRPC</a></li><li><a href="https://segmentfault.com/a/1190000008672912" target="_blank" rel="noopener">gRPC服务发现&amp;负载均衡</a></li><li><a href="https://segmentfault.com/a/1190000010471761" target="_blank" rel="noopener">go语言gRPC负载均衡库grpc-lb的使用</a></li><li><a href="https://github.com/bsm/grpcl" target="_blank" rel="noopener">bsm/grpclb</a> <a href="https://github.com/bsm/grpclb" target="_blank" rel="noopener">External Load Balancing Service solution for gRPC written in Go</a></li><li><a href="https://medium.com/@shijuvar/writing-grpc-interceptors-in-go-bf3e7671fe48" target="_blank" rel="noopener">Writing gRPC Interceptors in Go</a></li><li><a href="https://github.com/grpc/proposal/blob/master/L9-go-resolver-balancer-API.md" target="_blank" rel="noopener"><a href="https://github.com/grpc/proposal" target="_blank" rel="noopener">proposal</a>/<strong>L9-go-resolver-balancer-API.md</strong></a></li><li><a href="https://github.com/DavadDi/wonaming" target="_blank" rel="noopener">https://github.com/DavadDi/wonaming</a></li><li><a href="http://ralphbupt.github.io/2017/11/27/etcd%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" target="_blank" rel="noopener">etcd学习笔记（etcdv3， gRPC服务发现和负载均衡）</a></li><li><a href="https://www.cnblogs.com/sevenPP/p/8149890.html" target="_blank" rel="noopener">etcd v3 服务注册与发现 Go代码</a></li><li><a href="https://godoc.org/github.com/coreos/etcd/clientv3/namespace" target="_blank" rel="noopener">https://godoc.org/github.com/coreos/etcd/clientv3/namespace</a>  自动使用前缀</li><li><a href="http://chen-tao.github.io/2017/09/14/Use-gvm-manage-golang-version/" target="_blank" rel="noopener">使用gvm管理多版本golang</a>  gvm 管理多个 golang 环境，类似于 python virtural env 方式</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;基于 Etcd 实现的服务注册和发现的 grpclb 参见： &lt;a href=&quot;https://github.com/DavadDi/grpclb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;grpclb&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;前提&quot;&gt;&lt;a h
      
    
    </summary>
    
      <category term="golang" scheme="http://www.cn18k.com/categories/golang/"/>
    
    
      <category term="grpc" scheme="http://www.cn18k.com/tags/grpc/"/>
    
  </entry>
  
  <entry>
    <title>gRPC 之 Interceptors</title>
    <link href="http://www.cn18k.com/2018/09/01/grpc-interceptors/"/>
    <id>http://www.cn18k.com/2018/09/01/grpc-interceptors/</id>
    <published>2018-09-01T13:00:00.000Z</published>
    <updated>2018-09-01T13:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="gRPC-环境安装-Mac"><a href="#gRPC-环境安装-Mac" class="headerlink" title="gRPC 环境安装 Mac"></a>gRPC 环境安装 Mac</h2><p>由于仓库已经转到 github，命令 <code>go get -u google.golang.org/grpc</code> 已经不能正常工作。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">$ brew install libtool</span><br><span class="line">$ brew install automake</span><br><span class="line"></span><br><span class="line"><span class="comment"># install proto3</span></span><br><span class="line">$ mkdir tmp</span><br><span class="line">$ <span class="built_in">cd</span> tmp</span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/google/protobuf</span><br><span class="line">$ <span class="built_in">cd</span> protobuf</span><br><span class="line">$ ./autogen.sh</span><br><span class="line">$ ./configure</span><br><span class="line">$ make</span><br><span class="line">$ make check</span><br><span class="line">$ sudo make install</span><br><span class="line"></span><br><span class="line"><span class="comment"># install go-grpc, 由于仓库已经转到 github，因此下面命令不能正常工作</span></span><br><span class="line"><span class="comment"># $ go get -u google.golang.org/grpc</span></span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/grpc/grpc-go.git <span class="variable">$GOPATH</span>/src/google.golang.org/grpc</span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/golang/net.git <span class="variable">$GOPATH</span>/src/golang.org/x/net</span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/golang/text.git <span class="variable">$GOPATH</span>/src/golang.org/x/text</span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/google/go-genproto.git <span class="variable">$GOPATH</span>/src/google.golang.org/genproto</span><br><span class="line"></span><br><span class="line"><span class="comment"># $ git clone https://github.com/golang/crypto.git $GOPATH/src/golang.org/x/crypto</span></span><br><span class="line"><span class="comment"># $ git clone https://github.com/golang/sys.git $GOPATH/src/golang.org/x/sys</span></span><br><span class="line"></span><br><span class="line">$ <span class="built_in">cd</span> <span class="variable">$GOPATH</span>/src/ &amp;&amp; go install google.golang.org/grpc</span><br><span class="line"></span><br><span class="line">$ go get -u github.com/golang/protobuf/&#123;proto,protoc-gen-go&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway</span></span><br><span class="line"><span class="comment"># go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger</span></span><br><span class="line">$ go get -u github.com/golang/protobuf/protoc-gen-go</span><br></pre></td></tr></table></figure><h2 id="gPRC-Interceptors"><a href="#gPRC-Interceptors" class="headerlink" title="gPRC Interceptors"></a>gPRC Interceptors</h2><p>当前实现的 Interceptors 包括 Auth/Log/Monitor/Trace 等。（auth, logging , trace，message, validation, retries or monitoring），大本营： <a href="https://github.com/grpc-ecosystem/go-grpc-middleware" target="_blank" rel="noopener">https://github.com/grpc-ecosystem/go-grpc-middleware</a></p><p>类型两种：</p><ul><li>UnaryInterceptor</li><li>StreamInterceptor</li></ul><p>常见拦截器列表如下：</p><ol><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware" target="_blank" rel="noopener">Go gRPC Middleware</a>: 提供了拦截器的interceptor链式的功能，可以将多个拦截器组合成一个拦截器链，当然它还提供了其它的功能，所以以gRPC中间件命名。</li><li><a href="https://github.com/kazegusuri/grpc-multi-interceptor" target="_blank" rel="noopener">grpc-multi-interceptor</a>: 是另一个interceptor链式功能的库，也可以将单向的或者流式的拦截器组合。</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth" target="_blank" rel="noopener">grpc_auth</a>: 身份验证拦截器</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/tags" target="_blank" rel="noopener">grpc_ctxtags</a>: 为上下文增加<code>Tag</code> map对象</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/zap" target="_blank" rel="noopener">grpc_zap</a>: 支持<code>zap</code>日志框架</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/logrus" target="_blank" rel="noopener">grpc_logrus</a>: 支持<code>logrus</code>日志框架</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-prometheus" target="_blank" rel="noopener">grpc_prometheus</a>: 支持 <code>prometheus</code></li><li><a href="https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc" target="_blank" rel="noopener">otgrpc</a>: 支持opentracing/zipkin</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/tracing/opentracing" target="_blank" rel="noopener">grpc_opentracing</a>:支持opentracing/zipkin</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/retry" target="_blank" rel="noopener">grpc_retry</a>: 为客户端增加重试的功能</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/validator" target="_blank" rel="noopener">grpc_validator</a>: 为服务器端增加校验的功能</li><li><a href="https://github.com/mercari/go-grpc-interceptor/tree/master/xrequestid" target="_blank" rel="noopener">xrequestid</a>: 将request id 设置到context中</li><li><a href="https://github.com/mercari/go-grpc-interceptor/tree/master/acceptlang" target="_blank" rel="noopener">go-grpc-interceptor</a>: 解析<code>Accept-Language</code>并设置到context</li><li><a href="https://github.com/mercari/go-grpc-interceptor/tree/master/requestdump" target="_blank" rel="noopener">requestdump</a>: 输出request/response</li></ol><p>Prometheus 样例如下：</p><p><img src="http://www.do1618.com/wp-content/uploads/2018/09/image-20180901102309480.png" alt=""></p><p>添加了 Zipkin 与 Logger 的代码样例：</p><p>client</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"log"</span></span><br><span class="line"><span class="string">"os"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"golang.org/x/net/context"</span></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line">pb <span class="string">"google.golang.org/grpc/examples/helloworld/helloworld"</span></span><br><span class="line"></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">address     = <span class="string">"localhost:50051"</span></span><br><span class="line">defaultName = <span class="string">"world"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set up a connection to the server.</span></span><br><span class="line">conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBalancerName())</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">"did not connect: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line">c := pb.NewGreeterClient(conn)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Contact the server and print out its response.</span></span><br><span class="line">name := defaultName</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(os.Args) &gt; <span class="number">1</span> &#123;</span><br><span class="line">name = os.Args[<span class="number">1</span>]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ctx, cancel := context.WithTimeout(context.Background(), time.Second)</span><br><span class="line"><span class="keyword">defer</span> cancel()</span><br><span class="line">r, err := c.SayHello(ctx, &amp;pb.HelloRequest&#123;Name: name&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">"could not greet: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">log.Printf(<span class="string">"Greeting: %s"</span>, r.Message)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>server.go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"log"</span></span><br><span class="line"><span class="string">"net"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"golang.org/x/net/context"</span></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line">pb <span class="string">"google.golang.org/grpc/examples/helloworld/helloworld"</span></span><br><span class="line"><span class="string">"google.golang.org/grpc/reflection"</span></span><br><span class="line"><span class="string">"github.com/sirupsen/logrus"</span></span><br><span class="line"><span class="string">"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"</span></span><br><span class="line"><span class="string">"github.com/grpc-ecosystem/go-grpc-middleware"</span></span><br><span class="line"><span class="string">"github.com/grpc-ecosystem/go-grpc-middleware/tags"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"github.com/opentracing/opentracing-go"</span></span><br><span class="line">zipkin <span class="string">"github.com/openzipkin-contrib/zipkin-go-opentracing"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"os"</span></span><br><span class="line"><span class="string">"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">port = <span class="string">":50051"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// server is used to implement helloworld.GreeterServer.</span></span><br><span class="line"><span class="keyword">type</span> server <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SayHello implements helloworld.GreeterServer</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *server)</span> <span class="title">SayHello</span><span class="params">(ctx context.Context, in *pb.HelloRequest)</span> <span class="params">(*pb.HelloReply, error)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;pb.HelloReply&#123;Message: <span class="string">"Hello "</span> + in.Name&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">logrusLogger *logrus.Logger</span><br><span class="line">customFunc   grpc_logrus.CodeToLevel</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="comment">// docker run -d -p 9411:9411 -p 9410:9410 openzipkin/zipkin</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// create collector.</span></span><br><span class="line">collector, err := zipkin.NewHTTPCollector(<span class="string">"http://localhost:9411/api/v1/spans"</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">"unable to create Zipkin HTTP collector: %+v\n"</span>, err)</span><br><span class="line">os.Exit(<span class="number">-1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// create recorder.</span></span><br><span class="line">recorder := zipkin.NewRecorder(collector, <span class="literal">false</span>, <span class="string">"127.0.0.1:50051"</span>, <span class="string">"greeter_server"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// create tracer.</span></span><br><span class="line">tracer, err := zipkin.NewTracer(</span><br><span class="line">recorder,</span><br><span class="line">zipkin.ClientServerSameSpan(<span class="literal">true</span>),</span><br><span class="line">zipkin.TraceID128Bit(<span class="literal">true</span>),</span><br><span class="line">)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">"unable to create Zipkin tracer: %+v\n"</span>, err)</span><br><span class="line">os.Exit(<span class="number">-1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// explicitly set our tracer to be the default tracer.</span></span><br><span class="line">opentracing.InitGlobalTracer(tracer)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Logrus entry is used, allowing pre-definition of certain fields by the user.</span></span><br><span class="line">logrusLogger = logrus.New()</span><br><span class="line">logrusLogger.SetFormatter(&amp;logrus.JSONFormatter&#123;&#125;)</span><br><span class="line">logrusEntry := logrus.NewEntry(logrusLogger)</span><br><span class="line"><span class="comment">// Shared options for the logger, with a custom gRPC code to log level function.</span></span><br><span class="line"></span><br><span class="line">customFunc = grpc_logrus.DefaultCodeToLevel</span><br><span class="line">opts := []grpc_logrus.Option&#123;</span><br><span class="line">grpc_logrus.WithLevels(customFunc),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Shared options for the logger, with a custom duration to log field function.</span></span><br><span class="line"><span class="comment">//opts := []grpc_logrus.Option&#123;</span></span><br><span class="line"><span class="comment">//grpc_logrus.WithDurationField(func(duration time.Duration) (key string, value interface&#123;&#125;) &#123;</span></span><br><span class="line"><span class="comment">//return "grpc.time_ns", duration.Nanoseconds()</span></span><br><span class="line"><span class="comment">//&#125;),</span></span><br><span class="line"><span class="comment">//&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Make sure that log statements internal to gRPC library are logged using the logrus Logger as well.</span></span><br><span class="line">grpc_logrus.ReplaceGrpcLogger(logrusEntry)</span><br><span class="line"></span><br><span class="line">lis, err := net.Listen(<span class="string">"tcp"</span>, port)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">"failed to listen: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create a server, make sure we put the grpc_ctxtags context before everything else.</span></span><br><span class="line">s := grpc.NewServer(</span><br><span class="line">grpc_middleware.WithUnaryServerChain(</span><br><span class="line">grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),</span><br><span class="line">grpc_logrus.UnaryServerInterceptor(logrusEntry, opts...),</span><br><span class="line">grpc_opentracing.UnaryServerInterceptor(),</span><br><span class="line">),</span><br><span class="line">grpc_middleware.WithStreamServerChain(</span><br><span class="line">grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),</span><br><span class="line">grpc_logrus.StreamServerInterceptor(logrusEntry, opts...),</span><br><span class="line">grpc_opentracing.StreamServerInterceptor(),</span><br><span class="line"></span><br><span class="line">),</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// s := grpc.NewServer()</span></span><br><span class="line">pb.RegisterGreeterServer(s, &amp;server&#123;&#125;)</span><br><span class="line"><span class="comment">// Register reflection service on gRPC server.</span></span><br><span class="line">reflection.Register(s)</span><br><span class="line"><span class="keyword">if</span> err := s.Serve(lis); err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">"failed to serve: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>server 端的产奖配置可以如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"github.com/grpc-ecosystem/go-grpc-middleware"</span></span><br><span class="line"></span><br><span class="line">myServer := grpc.NewServer(</span><br><span class="line">    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(</span><br><span class="line">        grpc_ctxtags.StreamServerInterceptor(),</span><br><span class="line">        grpc_opentracing.StreamServerInterceptor(),</span><br><span class="line">        grpc_prometheus.StreamServerInterceptor,</span><br><span class="line">        grpc_zap.StreamServerInterceptor(zapLogger),</span><br><span class="line">        grpc_auth.StreamServerInterceptor(myAuthFunction),</span><br><span class="line">        grpc_recovery.StreamServerInterceptor(),</span><br><span class="line">    )),</span><br><span class="line">    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(</span><br><span class="line">        grpc_ctxtags.UnaryServerInterceptor(),</span><br><span class="line">        grpc_opentracing.UnaryServerInterceptor(),</span><br><span class="line">        grpc_prometheus.UnaryServerInterceptor,</span><br><span class="line">        grpc_zap.UnaryServerInterceptor(zapLogger),</span><br><span class="line">        grpc_auth.UnaryServerInterceptor(myAuthFunction),</span><br><span class="line">        grpc_recovery.UnaryServerInterceptor(),</span><br><span class="line">    )),</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>分类如下：</p><h4 id="Auth"><a href="#Auth" class="headerlink" title="Auth"></a>Auth</h4><ul><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth" target="_blank" rel="noopener"><code>grpc_auth</code></a> - a customizable (via <code>AuthFunc</code>) piece of auth middleware</li></ul><h4 id="Logging"><a href="#Logging" class="headerlink" title="Logging"></a>Logging</h4><ul><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/tags" target="_blank" rel="noopener"><code>grpc_ctxtags</code></a> - a library that adds a <code>Tag</code> map to context, with data populated from request body</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/zap" target="_blank" rel="noopener"><code>grpc_zap</code></a> - integration of <a href="https://github.com/uber-go/zap" target="_blank" rel="noopener">zap</a> logging library into gRPC handlers.</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/logrus" target="_blank" rel="noopener"><code>grpc_logrus</code></a> - integration of <a href="https://github.com/sirupsen/logrus" target="_blank" rel="noopener">logrus</a> logging library into gRPC handlers.</li></ul><h4 id="Monitoring"><a href="#Monitoring" class="headerlink" title="Monitoring"></a>Monitoring</h4><ul><li><a href="https://github.com/grpc-ecosystem/go-grpc-prometheus" target="_blank" rel="noopener"><code>grpc_prometheus</code>⚡</a> - Prometheus client-side and server-side monitoring middleware</li><li><a href="https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc" target="_blank" rel="noopener"><code>otgrpc</code>⚡</a> - <a href="http://opentracing.io/" target="_blank" rel="noopener">OpenTracing</a> client-side and server-side interceptors</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/tracing/opentracing" target="_blank" rel="noopener"><code>grpc_opentracing</code></a> - <a href="http://opentracing.io/" target="_blank" rel="noopener">OpenTracing</a> client-side and server-side interceptors with support for streaming and handler-returned tags</li></ul><h4 id="Client"><a href="#Client" class="headerlink" title="Client"></a>Client</h4><ul><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/retry" target="_blank" rel="noopener"><code>grpc_retry</code></a> - a generic gRPC response code retry mechanism, client-side middleware</li></ul><h4 id="Server"><a href="#Server" class="headerlink" title="Server"></a>Server</h4><ul><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/validator" target="_blank" rel="noopener"><code>grpc_validator</code></a> - codegen inbound message validation from <code>.proto</code> options</li><li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/recovery" target="_blank" rel="noopener"><code>grpc_recovery</code></a> - turn panics into gRPC errors</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://segmentfault.com/a/1190000007997759" target="_blank" rel="noopener">Golang gRPC实践 连载五 拦截器 Interceptor</a></li><li><a href="https://medium.com/@shijuvar/writing-grpc-interceptors-in-go-bf3e7671fe48" target="_blank" rel="noopener">Writing gRPC Interceptors in Go</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;gRPC-环境安装-Mac&quot;&gt;&lt;a href=&quot;#gRPC-环境安装-Mac&quot; class=&quot;headerlink&quot; title=&quot;gRPC 环境安装 Mac&quot;&gt;&lt;/a&gt;gRPC 环境安装 Mac&lt;/h2&gt;&lt;p&gt;由于仓库已经转到 github，命令 &lt;code&gt;g
      
    
    </summary>
    
      <category term="golang" scheme="http://www.cn18k.com/categories/golang/"/>
    
    
      <category term="grpc" scheme="http://www.cn18k.com/tags/grpc/"/>
    
  </entry>
  
  <entry>
    <title>gRPC 之 Gateway</title>
    <link href="http://www.cn18k.com/2018/09/01/grpc_gateway/"/>
    <id>http://www.cn18k.com/2018/09/01/grpc_gateway/</id>
    <published>2018-09-01T13:00:00.000Z</published>
    <updated>2018-09-01T13:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>调研对象：</p><ul><li>Kong</li><li>Traefix</li><li>Envoy</li></ul><h2 id="Kong-CE-版本"><a href="#Kong-CE-版本" class="headerlink" title="Kong CE 版本"></a>Kong CE 版本</h2><p><a href="https://konghq.com/kong-community-edition/" target="_blank" rel="noopener">官网</a> 和 <a href="https://github.com/Kong/kong" target="_blank" rel="noopener">GitHub Repo</a>。</p><p>Kong 版本：</p><ul><li><a href="https://docs.konghq.com/" target="_blank" rel="noopener">Community Edition (CE)</a></li><li><a href="https://docs.konghq.com/enterprise" target="_blank" rel="noopener">Enterprise Edition (EE)</a></li></ul><p>CE 当前版本 0.14.1。Kong 底层采用 OpenRestry 开发。</p><blockquote><p>Kong is a cloud-native, fast, scalable, and distributed Microservice Abstraction Layer <em>(also known as an API Gateway, API Middleware or in some cases Service Mesh)</em>. Made available as an open-source project in 2015, its core values are high performance and extensibility</p></blockquote><p><img src="https://camo.githubusercontent.com/d4d0dcb22c223db0bf2e301aab0dddb3015f1729/68747470733a2f2f6b6f6e6768712e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031382f30352f6b6f6e672d62656e65666974732d6769746875622d726561646d652e706e67" alt="Arch"></p><h3 id="Kong-CE-Features"><a href="#Kong-CE-Features" class="headerlink" title="Kong CE Features"></a>Kong CE Features</h3><ul><li><strong>Cloud-Native</strong>: Platform agnostic, Kong can run from bare metal to<br>Kubernetes.</li><li><strong>Dynamic Load Balancing</strong>: Load balance traffic across multiple upstream<br>services.</li><li><strong>Hash-based Load Balancing</strong>: Load balance with consistent hashing/sticky<br>sessions.</li><li><strong>Circuit-Breaker</strong>: Intelligent tracking of unhealthy upstream services.</li><li><strong>Health Checks:</strong> Active and passive monitoring of your upstream services.</li><li><strong>Service Discovery</strong>: Resolve SRV records in third-party DNS resolvers like<br>Consul.</li><li><strong>Serverless</strong>: Invoke and secure AWS Lambda or OpenWhisk functions directly<br>from Kong.</li><li><strong>WebSockets</strong>: Communicate to your upstream services via WebSockets.</li><li><strong>OAuth2.0</strong>: Easily add OAuth2.0 authentication to your APIs.</li><li><strong>Logging</strong>: Log requests and responses to your system over HTTP, TCP, UDP,<br>or to disk.</li><li><strong>Security</strong>: ACL, Bot detection, whitelist/blacklist IPs, etc…</li><li><strong>Syslog</strong>: Logging to System log.</li><li><strong>SSL</strong>: Setup a Specific SSL Certificate for an underlying service or API.</li><li><strong>Monitoring</strong>: Live monitoring provides key load and performance server<br>metrics.</li><li><strong>Forward Proxy</strong>: Make Kong connect to intermediary transparent HTTP proxies.</li><li><strong>Authentications</strong>: HMAC, JWT, Basic, and more.</li><li><strong>Rate-limiting</strong>: Block and throttle requests based on many variables.</li><li><strong>Transformations</strong>: Add, remove, or manipulate HTTP requests and responses.</li><li><strong>Caching</strong>: Cache and serve responses at the proxy layer.</li><li><strong>CLI</strong>: Control your Kong cluster from the command line.</li><li><strong>REST API</strong>: Kong can be operated with its RESTful API for maximum<br>flexibility.</li><li><strong>Geo-Replicated</strong>: Configs are always up-to-date across different regions.</li><li><strong>Failure Detection &amp; Recovery</strong>: Kong is unaffected if one of your Cassandra<br>nodes goes down.</li><li><strong>Clustering</strong>: All Kong nodes auto-join the cluster keeping their config<br>updated across nodes.</li><li><strong>Scalability</strong>: Distributed by nature, Kong scales horizontally by simply<br>adding nodes.</li><li><strong>Performance</strong>: Kong handles load with ease by scaling and using NGINX at<br>the core.</li><li><strong>Plugins</strong>: Extendable architecture for adding functionality to Kong and<br>APIs.</li></ul><h3 id="OpenRestry"><a href="#OpenRestry" class="headerlink" title="OpenRestry"></a>OpenRestry</h3><p><a href="https://openresty.org/" target="_blank" rel="noopener">官网</a> 和 <a href="https://github.com/openresty/openresty" target="_blank" rel="noopener">GitHub Repo</a>。<br>作者章亦春 (agentzh)，底层实现采用 Nginx + Lua 结合的方式进行，非常灵活，易于扩展。<br>Nginx 在 2018 年 4 月 17 号发布的版本 1.13.10 已经支持增加了对于 gRPC 的支持，<br>参见：<a href="https://www.nginx.com/blog/nginx-1-13-10-grpc/" target="_blank" rel="noopener">Introducing gRPC Support with NGINX 1.13.10</a>。</p><p>OpenRestry 当前支持最新版为 2018 年 5 月 14 号发布的  <a href="1.13.6">1.13.6.x</a> ，还不支持 Nginx 1.13.10，其版本列表参见：<a href="https://openresty.org/cn/changes.html" target="_blank" rel="noopener">changes.html</a>。 倒数第二个版本 1.11.2 发布时间为 2017 年 8月 18 号。</p><p>Nginx 对于 HTTP 2 支持的相关情况可以参见 <a href="https://www.nginx.com/?s=http2" target="_blank" rel="noopener">http2</a> , gRPC 支持的情况参见 <a href="https://www.nginx.com/?s=grpc" target="_blank" rel="noopener">gRPC</a>。</p><h2 id="Traefik"><a href="#Traefik" class="headerlink" title="Traefik"></a><a href="https://traefik.io/" target="_blank" rel="noopener">Traefik</a></h2><p><a href="https://traefik.io/" target="_blank" rel="noopener">官网地址</a> 和 <a href="https://github.com/containous/traefik" target="_blank" rel="noopener">GitHub Repo</a>。</p><blockquote><p>Træfik is a modern HTTP reverse proxy and load balancer that makes<br>deploying microservices easy. Træfik integrates with your existing infrastructure<br>components (<a href="https://www.docker.com/" target="_blank" rel="noopener">Docker</a>, <a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener">Swarm mode</a>,<a href="https://kubernetes.io/" target="_blank" rel="noopener">Kubernetes</a>, <a href="https://mesosphere.github.io/marathon/" target="_blank" rel="noopener">Marathon</a>, <a href="https://www.consul.io/" target="_blank" rel="noopener">Consul</a>, <a href="https://coreos.com/etcd/" target="_blank" rel="noopener">Etcd</a>, <a href="https://rancher.com/" target="_blank" rel="noopener">Rancher</a>, <a href="https://aws.amazon.com/ecs" target="_blank" rel="noopener">Amazon ECS</a>, …)<br>and configures itself automatically and dynamically. Pointing Træfik at your orchestrator<br>should be the <em>only</em>configuration step you need.</p></blockquote><p><img src="https://d33wubrfki0l68.cloudfront.net/7c5fd7d38c371e23cdff059e6cebb10292cd441c/7d420/assets/img/traefik-architecture.svg" alt="arch"></p><h3 id="Traefik-Features"><a href="#Traefik-Features" class="headerlink" title="Traefik Features"></a>Traefik Features</h3><ul><li>Continuously updates its configuration (No restarts!)</li><li>Supports multiple load balancing algorithms</li><li>Provides HTTPS to your microservices by leveraging <a href="https://letsencrypt.org" target="_blank" rel="noopener">Let’s Encrypt</a>  (wildcard certificates support)</li><li>Circuit breakers, retry</li><li>High Availability with cluster mode (beta)</li><li>See the magic through its clean web UI</li><li><strong>Websocket, HTTP/2, GRPC ready</strong></li><li>Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)</li><li>Keeps access logs (JSON, CLF)</li><li>Fast</li><li>Exposes a Rest API</li><li>Packaged as a single binary file (made with :heart: with go) and available as a <a href="https://microbadger.com/images/traefik" target="_blank" rel="noopener">tiny</a> <a href="https://hub.docker.com/r/_/traefik/" target="_blank" rel="noopener">official</a> docker image</li></ul><h2 id="Ambassador-Envoy-Gateway"><a href="#Ambassador-Envoy-Gateway" class="headerlink" title="Ambassador (Envoy Gateway)"></a><a href="https://www.getambassador.io/" target="_blank" rel="noopener">Ambassador</a> (Envoy Gateway)</h2><p><a href="https://www.getambassador.io/" target="_blank" rel="noopener">官网</a> <a href="https://github.com/datawire/ambassador" target="_blank" rel="noopener">GitHub Repo</a>。 Ambassador 底层基于 <a href="https://www.envoyproxy.io/" target="_blank" rel="noopener">Envoy</a>。由于 Envoy 天然支持 gRPC， 因此 Ambassador 也完全支持 gRPC 代理。</p><blockquote><p><a href="https://www.getambassador.io/" target="_blank" rel="noopener">Ambassador</a> is an open source Kubernetes-native API Gateway built on <a href="https://www.envoyproxy.io/" target="_blank" rel="noopener">Envoy</a>, designed for microservices. Ambassador essentially serves as an Envoy ingress controller, but with many more features.</p></blockquote><h3 id="Ambassador-Features"><a href="#Ambassador-Features" class="headerlink" title="Ambassador Features"></a>Ambassador Features</h3><ul><li>Self-service configuration, via Kubernetes annotations</li><li><strong>First class <a href="https://www.getambassador.io/user-guide/grpc" target="_blank" rel="noopener">gRPC and HTTP/2 support</a></strong></li><li>Support for CORS, timeouts, weighted round robin (<a href="https://www.getambassador.io/reference/canary" target="_blank" rel="noopener">canary</a>), <a href="https://www.getambassador.io/reference/services/rate-limit-service" target="_blank" rel="noopener">rate limiting</a></li><li><a href="https://www.getambassador.io/user-guide/with-istio" target="_blank" rel="noopener">Istio integration</a></li><li><a href="https://www.getambassador.io/reference/services/auth-service" target="_blank" rel="noopener">Authentication</a></li><li>Robust TLS support, including TLS client-certificate authentication</li></ul><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><ul><li><a href="https://konghq.com/kong-community-edition/" target="_blank" rel="noopener">Kong CE</a> 对于 gRPC 的支持取决于 OpenRestry 对于 Nginx 版本的支持情况，按照 OpenRestry 的开发进度，预计年底可能会支持 gRPC，可以持续关注。</li><li><a href="https://traefik.io/" target="_blank" rel="noopener">Traefix</a> 对于 WebSocket、HTTP 2 和 GRPC 都提供了支持，可以作为重点测试对象。</li><li><a href="https://www.getambassador.io/" target="_blank" rel="noopener">Ambassador</a> 基于 Envoy 实现，将 <strong>gRPC</strong> 和 HTTP/2 作为一等公民来进行对待，对于 gRPC 的支持应该是最好的，但是考虑到其底层强依赖于 Kubernetes，因此可以在使用 Istio 的时候进行集成比较方面，持续关注。</li></ul><table><thead><tr><th>特性\方案</th><th>Nginx</th><th>Kong（CE）</th><th>Traefik</th><th>Envoy（Ambassador）</th></tr></thead><tbody><tr><td>性能</td><td>转发效率高</td><td>转发效率高</td><td><a href="http://docs.traefik.io/benchmarks" target="_blank" rel="noopener">吞吐量 Nginx 85%</a></td><td>底层 C++，转发效率高</td></tr><tr><td>配置难易度</td><td>简单</td><td>简单</td><td>简单，与微服务对接方便</td><td>简单，ServiceMesh 原生支持</td></tr><tr><td>负载均衡机制</td><td>一般</td><td>可以灵活扩展</td><td>会话保持和健康机制</td><td>会话保持和健康机制</td></tr><tr><td>社区活跃度</td><td>高</td><td>一般</td><td>一般</td><td>一般</td></tr><tr><td>功能适用性</td><td>代理或者web/mail 服务器</td><td>代理或者负载均衡器</td><td>代理或负载均衡器</td><td>负载均衡器</td></tr><tr><td>支持平台</td><td>各种平台</td><td>各种平台</td><td>各种平台</td><td>主要是 Kubernets</td></tr><tr><td>K8S Ingress</td><td><a href="https://github.com/kubernetes/ingress-nginx" target="_blank" rel="noopener">ingress-nginx</a> 成熟度高</td><td><a href="https://github.com/Kong/kubernetes-ingress-controller" target="_blank" rel="noopener">kubernetes-ingress-controller</a> 成熟度低</td><td>天然支持，成熟度高</td><td>天然支持，成熟度中</td></tr><tr><td>HTTP 2支持情况</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td></tr><tr><td><strong>gRPC 代理</strong></td><td><strong>支持 &gt;=1.13.10</strong></td><td><strong>暂时不支持</strong></td><td><strong>支持</strong></td><td><strong>原生支持</strong></td></tr><tr><td>与 ServiceMesh 集成度</td><td>良好</td><td>良好</td><td>良好</td><td>优秀，原生支持</td></tr></tbody></table><h2 id="Traefik-gRPC-代理测试"><a href="#Traefik-gRPC-代理测试" class="headerlink" title="Traefik gRPC 代理测试"></a>Traefik gRPC 代理测试</h2><p>本文以 <a href="https://traefik.io/" target="_blank" rel="noopener">traefik</a> 作为测试对象，来作为 gRPC 支持情况。完整代码库参见：<a href="https://github.com/DavadDi/go_study/tree/master/src/google.golang.org/grpc/examples" target="_blank" rel="noopener">examples</a></p><p><img src="https://docs.traefik.io/img/grpc.svg" alt=""></p><blockquote><p>特别备注： gRPC 在 Mac 环境下测试过程中，如果将 <code>backend.local</code> 写入到 <code>/etc/hosts</code> 中，使用 gRPC Client 使用 <code>backend.local</code> 地址进行访问的时候，不能够正常解析出来地址，问题待确认。后切换到 Linux 下则无此问题。</p></blockquote><h3 id="gRPC-依赖准备"><a href="#gRPC-依赖准备" class="headerlink" title="gRPC 依赖准备"></a>gRPC 依赖准备</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># gRPC requires Go 1.6 or higher.</span></span><br><span class="line">$ go version</span><br><span class="line">go version go1.11</span><br><span class="line"></span><br><span class="line"><span class="comment"># Install gRPC 需要翻墙</span></span><br><span class="line">$ go get -u google.golang.org/grpc</span><br><span class="line"></span><br><span class="line"><span class="comment"># install the protoc plugin for Go</span></span><br><span class="line">$ go get -u github.com/golang/protobuf/protoc-gen-go</span><br></pre></td></tr></table></figure><p>gRPC 测试样例参见 <a href="https://grpc.io/docs/quickstart/go.html" target="_blank" rel="noopener">go quickstart</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> <span class="variable">$GOPATH</span>/src/google.golang.org/grpc/examples/helloworld)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动服务端</span></span><br><span class="line">$ go run greeter_server/main.go</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动客户端</span></span><br><span class="line">$ go run greeter_client/main.go</span><br><span class="line"></span><br><span class="line"><span class="comment"># 期待能够正常工作</span></span><br></pre></td></tr></table></figure><h3 id="Traefik-配置"><a href="#Traefik-配置" class="headerlink" title="Traefik 配置"></a>Traefik 配置</h3><p>traefix grpc 样例配置参考 <a href="https://docs.traefik.io/user-guide/grpc/" target="_blank" rel="noopener">user-guide</a></p><blockquote><p>As gRPC needs HTTP2, we need HTTPS certificates on both gRPC Server and Træfik.</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># download traefix</span></span><br><span class="line">$ wget https://github.com/containous/traefik/releases/download/v1.6.6/traefik_linux-amd64</span><br></pre></td></tr></table></figure><p>生成服务端证书：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \</span><br><span class="line">-keyout ./certs/backend.key \</span><br><span class="line">-out ./certs/backend.crt \</span><br><span class="line">-subj <span class="string">"/CN=backend.local/O=backend.local"</span></span><br></pre></td></tr></table></figure><p>生成客户端证书</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \</span><br><span class="line">-keyout ./certs/frontend.key \</span><br><span class="line">-out ./certs/frontend.crt \</span><br><span class="line">-subj <span class="string">"/CN=frontend.local/O=frontend.local"</span></span><br></pre></td></tr></table></figure><p>修改 grpc server 和 client 添加证书支持，同时修改 client 代码的连接地址，在本地 hosts 添加以下记录：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1 frontend.local</span><br><span class="line">127.0.0.1 backend.local</span><br></pre></td></tr></table></figure><p>traefik.toml 配置文件样例：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">$ cat traefik.toml</span><br><span class="line"><span class="attr">defaultEntryPoints</span> = [<span class="string">"https"</span>]</span><br><span class="line"></span><br><span class="line"><span class="attr">logLevel</span> = <span class="string">"INFO"</span></span><br><span class="line"></span><br><span class="line"><span class="attr">RootCAs</span> = [ <span class="string">"../certs/backend.crt"</span> ]</span><br><span class="line"></span><br><span class="line"><span class="section">[entryPoints]</span></span><br><span class="line"><span class="section">  [entryPoints.https]</span></span><br><span class="line">  address = ":4443"</span><br><span class="line"><span class="section">    [entryPoints.https.tls]</span></span><br><span class="line"><span class="section">     [[entryPoints.https.tls.certificates]]</span></span><br><span class="line">     certFile = "../certs/frontend.crt"</span><br><span class="line">     keyFile  = "../certs/frontend.key"</span><br><span class="line"></span><br><span class="line"><span class="section">[api]</span></span><br><span class="line"></span><br><span class="line"><span class="section">[file]</span></span><br><span class="line"></span><br><span class="line"><span class="section">[backends]</span></span><br><span class="line"><span class="section">  [backends.backend1]</span></span><br><span class="line"><span class="section">    [backends.backend1.servers.server1]</span></span><br><span class="line">    url = "https://backend.local:50051"</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="section">[frontends]</span></span><br><span class="line"><span class="section">  [frontends.frontend1]</span></span><br><span class="line">  backend = "backend1"</span><br><span class="line"><span class="section">    [frontends.frontend1.routes.test_1]</span></span><br><span class="line">    rule = "Host:frontend.local"</span><br><span class="line"></span><br><span class="line"><span class="section">[traefikLog]</span></span><br><span class="line"></span><br><span class="line"><span class="section">[metrics]</span></span><br><span class="line"><span class="section">  [metrics.prometheus]</span></span><br><span class="line">    entryPoint = "traefik"</span><br></pre></td></tr></table></figure><p>为方便启动，增加 <code>start.sh</code> 脚本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ cat start.sh</span><br><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">./traefik --configFile=traefik.toml</span><br></pre></td></tr></table></figure><p><code>traefik</code> server 启动成功后可以，会启动一个 dashboard 地址，可以通过地址访问 <code>http://172.16.71.9:8080/dashboard/</code>，其中 <code>172.16.71.9</code> 为启动的 IP 地址。</p><p><code>greeter_client</code> 必须使用域名访问，如果使用 IP 地址访问，会导致证书不匹配的错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ go run greeter_client/main.go</span><br><span class="line">2018/08/29 10:01:56 could not greet: rpc error: code = Unavailable desc = all SubConns are <span class="keyword">in</span> TransientFailure, latest connection error: connection error: desc = <span class="string">"transport: authentication handshake failed: x509: certificate is valid for backend.local, not localhost"</span></span><br><span class="line"><span class="built_in">exit</span> status 1</span><br></pre></td></tr></table></figure><h3 id="gRPC-配置证书"><a href="#gRPC-配置证书" class="headerlink" title="gRPC 配置证书"></a>gRPC 配置证书</h3><p>gRPC HelloWorld 样例程序采用非证书模式，为配合 traefik 测试，需要将服务端与客户端代码增加证书支持。</p><h4 id="greet-server"><a href="#greet-server" class="headerlink" title="greet_server"></a>greet_server</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Read cert and key file</span></span><br><span class="line">BackendCert, _ := ioutil.ReadFile(<span class="string">"./backend.cert"</span>)</span><br><span class="line">BackendKey, _ := ioutil.ReadFile(<span class="string">"./backend.key"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Generate Certificate struct</span></span><br><span class="line">cert, err := tls.X509KeyPair(BackendCert, BackendKey)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">  log.Fatalf(<span class="string">"failed to parse certificate: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create credentials</span></span><br><span class="line">creds := credentials.NewServerTLSFromCert(&amp;cert)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Use Credentials in gRPC server options</span></span><br><span class="line">serverOption := grpc.Creds(creds)</span><br><span class="line"><span class="keyword">var</span> s *grpc.Server = grpc.NewServer(serverOption)</span><br><span class="line"><span class="keyword">defer</span> s.Stop()</span><br><span class="line"></span><br><span class="line">pb.RegisterGreeterServer(s, &amp;server&#123;&#125;)</span><br><span class="line">err := s.Serve(lis)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><h4 id="greet-client"><a href="#greet-client" class="headerlink" title="greet_client"></a>greet_client</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Read cert file</span></span><br><span class="line">FrontendCert, _ := ioutil.ReadFile(<span class="string">"./frontend.cert"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create CertPool</span></span><br><span class="line">roots := x509.NewCertPool()</span><br><span class="line">roots.AppendCertsFromPEM(FrontendCert)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create credentials</span></span><br><span class="line">credsClient := credentials.NewClientTLSFromCert(roots, <span class="string">""</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Dial with specific Transport (with credentials)</span></span><br><span class="line">conn, err := grpc.Dial(<span class="string">"frontend.local:4443"</span>, grpc.WithTransportCredentials(credsClient))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    log.Fatalf(<span class="string">"did not connect: %v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line">client := pb.NewGreeterClient(conn)</span><br><span class="line"></span><br><span class="line">name := <span class="string">"World"</span></span><br><span class="line">r, err := client.SayHello(context.Background(), &amp;pb.HelloRequest&#123;Name: name&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>TODO 性能相关的数据收集</p><blockquote><p>mdl -r~MD013 grpc_gateway.md  验证 md 的时候去除长度限制。</p></blockquote><h2 id="其他-Ingress-对比图"><a href="#其他-Ingress-对比图" class="headerlink" title="其他 Ingress 对比图"></a>其他 Ingress 对比图</h2><p><img src="https://i0.wp.com/kubedex.com/wp-content/uploads/2018/09/ingresses-updated-1.png?resize=1024%2C465&amp;ssl=1" alt=""></p><p>完整地址参见：<a href="https://kubedex.com/nginx-ingress-vs-kong-vs-traefik-vs-haproxy-vs-voyager-vs-contour-vs-ambassador/" target="_blank" rel="noopener">ingress</a></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://microservices.io/patterns/apigateway.html" target="_blank" rel="noopener">Pattern: API Gateway / Backend for Front-End</a></li><li><a href="https://zhaohuabing.com/2018/04/11/service-mesh-vs-api-gateway/" target="_blank" rel="noopener">Service Mesh 和 API Gateway的关系探讨</a></li><li><a href="https://www.nginx.com/blog/building-microservices-using-an-api-gateway/" target="_blank" rel="noopener">Building Microservices: Using an API Gateway</a></li><li><a href="https://smartbear.com/learn/api-design/api-gateways-in-microservices/" target="_blank" rel="noopener">Using an API Gateway in Your Microservices Architecture</a></li><li><a href="https://github.com/vaporz/turbo" target="_blank" rel="noopener">Turbo</a> 能生成一个反向代理服务器，把HTTP请求转换为 grpc 或者 Thrift 格式的请求。  <a href="https://zhuanlan.zhihu.com/p/29350695" target="_blank" rel="noopener">grpc-gateway的替代品–Turbo</a></li><li><a href="https://www.datawire.io/envoyproxy/envoy-as-api-gateway/" target="_blank" rel="noopener">Part 3: Deploying Envoy as an API Gateway for Microservices</a></li><li><a href="https://medium.com/@DazWilkin/grpc-through-traefik-envoy-651bdc3b37e9" target="_blank" rel="noopener">gRPC through Traefik || Envoy</a></li><li><a href="https://github.com/grpc/grpc/blob/master/doc/naming.md" target="_blank" rel="noopener">gRPC naming</a></li><li><a href="https://bbengfort.github.io/programmer/2017/03/03/secure-grpc.html" target="_blank" rel="noopener">Secure gRPC with TLS/SSL</a></li><li><a href="http://blog.51cto.com/devingeng/2154041" target="_blank" rel="noopener">深入玩转K8S之如何访问业务应用（Traefik-ingress配置https篇）</a></li><li><a href="https://blog.csdn.net/u013066244/article/details/78725842" target="_blank" rel="noopener">OpenSSL自签发配置有多域名或ip地址的证书</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;调研对象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kong&lt;/li&gt;
&lt;li&gt;Traefix&lt;/li&gt;
&lt;li&gt;Envoy&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Kong-CE-版本&quot;&gt;&lt;a href=&quot;#Kong-CE-版本&quot; class=&quot;headerlink&quot; title=&quot;Ko
      
    
    </summary>
    
      <category term="golang" scheme="http://www.cn18k.com/categories/golang/"/>
    
    
      <category term="grpc" scheme="http://www.cn18k.com/tags/grpc/"/>
    
  </entry>
  
  <entry>
    <title>Serverless 之 OpenFaaS</title>
    <link href="http://www.cn18k.com/2018/08/03/serverless-openfaas/"/>
    <id>http://www.cn18k.com/2018/08/03/serverless-openfaas/</id>
    <published>2018-08-03T03:00:00.000Z</published>
    <updated>2018-08-03T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>OpenFaaS - Serverless Functions Made Simple for Docker &amp; Kubernetes <a href="https://docs.openfaas.com/" target="_blank" rel="noopener">https://docs.openfaas.com/</a>，当前支持以下语言：csharp、go、python、node、java、ruby等。</p><p><img src="https://camo.githubusercontent.com/08bc7c0c4f882ef5eadaed797388b27b1a3ca056/68747470733a2f2f7062732e7477696d672e636f6d2f6d656469612f4446726b46344e586f41414a774e322e6a7067" alt=""></p><p>在 OpenFaaS 的UI 中可以通过指定 Docker Image等相关信息添加一个新的 Function，具体界面如下：</p><p><img src="https://www.do1618.com/wp-content/uploads/2018/08/openfaas_new_fun.png" alt=""></p><p>从原来上来讲，在我们部署的 Docker Image 中，在编译中会自动加入 <em>Function Watchdog</em> 的程序，该程序是基于 Go 开发的 Http Server，负责将本地镜像中的包含 Function 的可执行程序与 API Gateway 进行一个串联。</p><p><img src="http://www.do1618.com/wp-content/uploads/2018/08/call_serverless_function.png" alt=""></p><h2 id="安装-OpenFaaS"><a href="#安装-OpenFaaS" class="headerlink" title="安装 OpenFaaS"></a>安装 OpenFaaS</h2><p>安装过程参考：<a href="https://github.com/openfaas/workshop，为方便进行环境搭建和测试，本文采用" target="_blank" rel="noopener">https://github.com/openfaas/workshop，为方便进行环境搭建和测试，本文采用</a> Docker Swarm 的方式。MiniKube 的方式可以参见 <a href="https://medium.com/devopslinks/getting-started-with-openfaas-on-minikube-634502c7acdf" target="_blank" rel="noopener">Getting started with OpenFaaS on minikube</a></p><h3 id="安装-OpenFaaS-CLI"><a href="#安装-OpenFaaS-CLI" class="headerlink" title="安装 OpenFaaS CLI"></a>安装 OpenFaaS CLI</h3><p>参见：<a href="https://github.com/openfaas/workshop/blob/master/lab1.md" target="_blank" rel="noopener">Prepare for OpenFaaS</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Docker Swarm</span></span><br><span class="line">$ docker swarm init</span><br><span class="line"></span><br><span class="line"><span class="comment"># OpenFaaS CLI</span></span><br><span class="line">$ curl -sL cli.openfaas.com | sudo sh</span><br><span class="line"></span><br><span class="line">$ faas-cli <span class="built_in">help</span></span><br><span class="line">$ faas-cli version</span><br></pre></td></tr></table></figure><h3 id="部署-OpenFaaS"><a href="#部署-OpenFaaS" class="headerlink" title="部署 OpenFaaS"></a>部署 OpenFaaS</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> https://github.com/openfaas/faas</span><br><span class="line">$ <span class="built_in">cd</span> faas &amp;&amp; git checkout master</span><br><span class="line">$ ./deploy_stack.sh --no-auth  <span class="comment"># 部署 faas-swarm 相关的 provider</span></span><br><span class="line">$ docker service ls</span><br><span class="line">docker service ls</span><br><span class="line">ID                  NAME                MODE                REPLICAS            IMAGE                              PORTS</span><br><span class="line">jauscdc7kxsi        base64              replicated          1/1                 <span class="built_in">functions</span>/alpine:latest</span><br><span class="line">tqo6mkpf3xg6        echoit              replicated          1/1                 <span class="built_in">functions</span>/alpine:latest</span><br><span class="line">prmtd3len7hs        func_alertmanager   replicated          1/1                 prom/alertmanager:v0.15.0-rc.0</span><br><span class="line">i2p4m4187lkg        func_faas-swarm     replicated          1/1                 openfaas/faas-swarm:0.4.0</span><br><span class="line">vizszwmlekg6        func_gateway        replicated          1/1                 openfaas/gateway:0.8.9             *:8080-&gt;8080/tcp</span><br><span class="line">07p9rusy7hiu        func_nats           replicated          1/1                 nats-streaming:0.6.0</span><br><span class="line">wxftz5yscpiz        func_prometheus     replicated          1/1                 prom/prometheus:v2.2.0             *:9090-&gt;9090/tcp</span><br><span class="line">zcpqvvj64tv4        func_queue-worker   replicated          1/1                 openfaas/queue-worker:0.4.8</span><br><span class="line"></span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>当部署完成后，我们可以通过 <a href="http://127.0.0.1:8080/ui/" target="_blank" rel="noopener">http://127.0.0.1:8080/ui/</a> 参看到已经部署的 Function 并可以进行相关测试。</p><h2 id="Go-语言"><a href="#Go-语言" class="headerlink" title="Go 语言"></a>Go 语言</h2><p>Go 语言的静态编译方式，可以打造出来体积比较小的镜像出来，非常适用于在 OpenFaaS 中来进行使用。</p><h3 id="创建-Hello-World-程序"><a href="#创建-Hello-World-程序" class="headerlink" title="创建 Hello World 程序"></a>创建 Hello World 程序</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir -p <span class="variable">$GOPATH</span>/src/<span class="built_in">functions</span> &amp;&amp; <span class="built_in">cd</span> <span class="variable">$GOPATH</span>/src/<span class="built_in">functions</span></span><br><span class="line"></span><br><span class="line">$ faas-cli new --lang go gohash</span><br><span class="line">Folder: gohash created.</span><br><span class="line">  ___                   ___              ___  </span><br><span class="line"> / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___| </span><br><span class="line">| | | | <span class="string">'_ \ / _ \ '</span>_ \| |_ / _` |/ _` \___ \ </span><br><span class="line">| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |</span><br><span class="line"> \___/| .__/ \___|_| |_|_|  \__,_|\__,_|___ / </span><br><span class="line">      |_|                                     </span><br><span class="line"></span><br><span class="line">Function created <span class="keyword">in</span> folder: gohash</span><br><span class="line">Stack file written: gohash.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建后生成以下目录结构</span></span><br><span class="line">$ tree</span><br><span class="line">.</span><br><span class="line">├── build</span><br><span class="line">│   └── gohash</span><br><span class="line">│       ├── Dockerfile</span><br><span class="line">│       ├── <span class="keyword">function</span></span><br><span class="line">│       │   └── handler.go</span><br><span class="line">│       ├── main.go</span><br><span class="line">│       └── template.yml</span><br><span class="line">├── gohash</span><br><span class="line">│   └── handler.go  <span class="comment"># 用于编写主逻辑的函数入口</span></span><br><span class="line">├── gohash.yml      <span class="comment"># 配置文件</span></span><br><span class="line">└── template</span><br><span class="line">    ......</span><br></pre></td></tr></table></figure><p><code>gohash.yml</code> 格式：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">provider:</span></span><br><span class="line"><span class="attr">  name:</span> <span class="string">faas</span></span><br><span class="line"><span class="attr">  gateway:</span> <span class="attr">http://127.0.0.1:8080</span></span><br><span class="line"></span><br><span class="line"><span class="attr">functions:</span></span><br><span class="line"><span class="attr">  gohash:</span></span><br><span class="line"><span class="attr">    lang:</span> <span class="string">go</span></span><br><span class="line"><span class="attr">    handler:</span> <span class="string">./gohash</span></span><br><span class="line"><span class="attr">    image:</span> <span class="attr">gohash:latest</span></span><br></pre></td></tr></table></figure><p><code>gohash/handler.go</code> 内容如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> function</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Handle a serverless request</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Handle</span><span class="params">(req []<span class="keyword">byte</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Sprintf(<span class="string">"Hello, Go. You said: %s"</span>, <span class="keyword">string</span>(req))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="编译和部署"><a href="#编译和部署" class="headerlink" title="编译和部署"></a>编译和部署</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 编译程序</span></span><br><span class="line">$ faas-cli build -f gohash.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 部署程序</span></span><br><span class="line">$ faas-cli deploy -f gohash.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 调用并测试</span></span><br><span class="line">$ <span class="built_in">echo</span> -n <span class="string">"test"</span> | faas-cli invoke gohash</span><br><span class="line">Hello, Go. You said: <span class="built_in">test</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除</span></span><br><span class="line">$ <span class="built_in">echo</span> -n <span class="string">"test"</span> | faas-cli delete gohash</span><br></pre></td></tr></table></figure><h2 id="镜像细节探究"><a href="#镜像细节探究" class="headerlink" title="镜像细节探究"></a>镜像细节探究</h2><p><code>build/gohash</code> 目录下文件列表如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ tree</span><br><span class="line">├── Dockerfile</span><br><span class="line">├── function</span><br><span class="line">│   └── handler.go</span><br><span class="line">├── main.go</span><br><span class="line">└── template.yml</span><br></pre></td></tr></table></figure><h3 id="main-go"><a href="#main-go" class="headerlink" title="main.go"></a>main.go</h3><p>首先我们分析一下 <code>main.go</code>，内容如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"io/ioutil"</span></span><br><span class="line"><span class="string">"log"</span></span><br><span class="line"><span class="string">"os"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"handler/function"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">input, err := ioutil.ReadAll(os.Stdin)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">"Unable to read standard input: %s"</span>, err.Error())</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fmt.Println(function.Handle(input))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过对于 <code>main.go</code>源码分析，我们可以得知，<code>main</code>函数主要是从 <code>os.Stdin</code> 读取数据，并调用我们 <code>function.Handle</code> 并将调用的结果打印到 <code>os.Stdout</code>。<code>main.go</code> 起到了一个包装器的作用。</p><h3 id="template-yml"><a href="#template-yml" class="headerlink" title="template.yml"></a>template.yml</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">language: <span class="keyword">go</span></span><br><span class="line">fprocess: ./handler</span><br><span class="line">welcome_message: |</span><br><span class="line">  You have created a <span class="built_in">new</span> function which uses Golang <span class="number">1.9</span><span class="number">.7</span>.</span><br><span class="line">  To include third-party dependencies, use a vendoring tool like dep:</span><br><span class="line">  dep documentation: https:<span class="comment">//github.com/golang/dep#installation</span></span><br></pre></td></tr></table></figure><h3 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">$ cat Dockerfile</span><br><span class="line">FROM golang:1.9.7-alpine3.7 as builder</span><br><span class="line"></span><br><span class="line">RUN apk --no-cache add curl \</span><br><span class="line">    &amp;&amp; <span class="built_in">echo</span> <span class="string">"Pulling watchdog binary from Github."</span> \</span><br><span class="line">    &amp;&amp; curl -sSL https://github.com/openfaas/faas/releases/download/0.8.9/fwatchdog &gt; /usr/bin/fwatchdog \</span><br><span class="line">    &amp;&amp; chmod +x /usr/bin/fwatchdog \</span><br><span class="line">    &amp;&amp; apk del curl --no-cache</span><br><span class="line"></span><br><span class="line">WORKDIR /go/src/handler</span><br><span class="line">COPY . .</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run a gofmt and exclude all vendored code.</span></span><br><span class="line">RUN <span class="built_in">test</span> -z <span class="string">"<span class="variable">$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./function/vendor/*")</span>)"</span> || &#123; <span class="built_in">echo</span> <span class="string">"Run \"gofmt -s -w\" on your Golang code"</span>; <span class="built_in">exit</span> 1; &#125;</span><br><span class="line"></span><br><span class="line">RUN CGO_ENABLED=0 GOOS=linux \</span><br><span class="line">    go build --ldflags <span class="string">"-s -w"</span> -a -installsuffix cgo -o handler . &amp;&amp; \</span><br><span class="line">    go <span class="built_in">test</span> $(go list ./... | grep -v /vendor/) -cover</span><br><span class="line"></span><br><span class="line">FROM alpine:3.7</span><br><span class="line">RUN apk --no-cache add ca-certificates</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add non root user</span></span><br><span class="line">RUN addgroup -S app &amp;&amp; adduser -S -g app app</span><br><span class="line">RUN mkdir -p /home/app</span><br><span class="line"></span><br><span class="line">WORKDIR /home/app</span><br><span class="line"></span><br><span class="line">COPY --from=builder /usr/bin/fwatchdog         .</span><br><span class="line"></span><br><span class="line">COPY --from=builder /go/src/handler/<span class="keyword">function</span>/  .</span><br><span class="line">COPY --from=builder /go/src/handler/handler    .</span><br><span class="line"></span><br><span class="line">RUN chown -R app /home/app</span><br><span class="line"></span><br><span class="line">USER app</span><br><span class="line"></span><br><span class="line">ENV fprocess=<span class="string">"./handler"</span></span><br><span class="line"></span><br><span class="line">HEALTHCHECK --interval=2s CMD [ -e /tmp/.lock ] || <span class="built_in">exit</span> 1</span><br><span class="line"></span><br><span class="line">CMD [<span class="string">"./fwatchdog"</span>]</span><br></pre></td></tr></table></figure><p>在生成的 <code>gohash:latest</code> 的镜像中目录结构如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">~ $ <span class="built_in">pwd</span></span><br><span class="line">/home/app</span><br><span class="line">~ $ ls -hl</span><br><span class="line">total 5644</span><br><span class="line">-rwxr-xr-x    1 app      root        4.2M Aug  2 05:16 fwatchdog</span><br><span class="line">-rwxr-xr-x    1 app      root        1.3M Aug  2 05:16 handler</span><br><span class="line">-rw-r--r--    1 app      root         163 Aug  2 05:15 handler.go</span><br></pre></td></tr></table></figure><h3 id="watch-dog"><a href="#watch-dog" class="headerlink" title="watch dog"></a>watch dog</h3><p>watch dog 对于我们编写的 <code>function</code> 函数套上了一层 http 的外壳（通过创建子进程，写入子进程的 stdiin，然后从子进程 stdout 接受响应数据）。</p><p><img src="https://camo.githubusercontent.com/61c169ab5cd01346bc3dc7a11edc1d218f0be3b4/68747470733a2f2f7062732e7477696d672e636f6d2f6d656469612f4447536344626c554941416f34482d2e6a70673a6c61726765" alt=""></p><p>Wtachdog，作为镜像的对外代理程序，必须作为启动的入口，一个简单的 Dockerfile 文件如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">FROM alpine:3.7</span><br><span class="line"></span><br><span class="line">ADD https://github.com/openfaas/faas/releases/download/0.8.0/fwatchdog /usr/bin</span><br><span class="line">RUN chmod +x /usr/bin/fwatchdog</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define your binary here</span></span><br><span class="line">ENV fprocess=<span class="string">"/bin/cat"</span>     <span class="comment"># 通过环境变量到处 watchdog 需要派生的子进程二进制</span></span><br><span class="line"></span><br><span class="line">CMD [<span class="string">"fwatchdog"</span>]           <span class="comment"># 必须将 watchdog 作为镜像运行的入口</span></span><br></pre></td></tr></table></figure><p>对于 watchdog 的配置，主要是通过环境变量的方式进行，可以配置的值如下：</p><table><thead><tr><th>Option</th><th>Usage</th></tr></thead><tbody><tr><td><code>fprocess</code></td><td>The process to invoke for each function call (function process). This must be a UNIX binary and accept input via STDIN and output via STDOUT</td></tr><tr><td><code>cgi_headers</code></td><td>HTTP headers from request are made available through environmental variables - <code>Http_X_Served_By</code>etc. See section: <em>Handling headers</em> for more detail. Enabled by default</td></tr><tr><td><code>marshal_request</code></td><td>Instead of re-directing the raw HTTP body into your fprocess, it will first be marshalled into JSON. Use this if you need to work with HTTP headers and do not want to use environmental variables via the <code>cgi_headers</code> flag.</td></tr><tr><td><code>content_type</code></td><td>Force a specific Content-Type response for all responses</td></tr><tr><td><code>write_timeout</code></td><td>HTTP timeout for writing a response body from your function (in seconds)</td></tr><tr><td><code>read_timeout</code></td><td>HTTP timeout for reading the payload from the client caller (in seconds)</td></tr><tr><td><code>suppress_lock</code></td><td>The watchdog will attempt to write a lockfile to /tmp/ for swarm healthchecks - set this to true to disable behaviour.</td></tr><tr><td><code>exec_timeout</code></td><td>Hard timeout for process exec’d for each incoming request (in seconds). Disabled if set to 0</td></tr><tr><td><code>write_debug</code></td><td>Write all output, error messages, and additional information to the logs. Default is false</td></tr><tr><td><code>combine_output</code></td><td>True by default - combines stdout/stderr in function response, when set to false <code>stderr</code> is written to the container logs and stdout is used for function response</td></tr></tbody></table><p>更加具体的功能或者使用说明，参考：<a href="https://github.com/openfaas/faas/tree/master/watchdog" target="_blank" rel="noopener">https://github.com/openfaas/faas/tree/master/watchdog</a></p><p>watchdog 的主流程：</p><p><a href="https://github.com/openfaas/faas/blob/master/watchdog/main.go#L22" target="_blank" rel="noopener"><code>main.go</code></a></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">s := &amp;http.Server&#123;</span><br><span class="line">Addr:           fmt.Sprintf(<span class="string">":%d"</span>, config.port),</span><br><span class="line">ReadTimeout:    readTimeout,</span><br><span class="line">WriteTimeout:   writeTimeout,</span><br><span class="line">MaxHeaderBytes: <span class="number">1</span> &lt;&lt; <span class="number">20</span>, <span class="comment">// Max header of 1MB</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">http.HandleFunc(<span class="string">"/_/health"</span>, makeHealthHandler())  <span class="comment">// 用于健康检查</span></span><br><span class="line">http.HandleFunc(<span class="string">"/"</span>, makeRequestHandler(&amp;config))  <span class="comment">// 处理请求</span></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://github.com/openfaas/faas/blob/master/watchdog/handler.go#L292" target="_blank" rel="noopener"><code>handler.go</code></a></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">makeRequestHandler</span><span class="params">(config *WatchdogConfig)</span> <span class="title">func</span><span class="params">(http.ResponseWriter, *http.Request)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> r.Method &#123;</span><br><span class="line"><span class="keyword">case</span></span><br><span class="line">http.MethodPost,</span><br><span class="line">http.MethodPut,</span><br><span class="line">http.MethodDelete,</span><br><span class="line">http.MethodGet:</span><br><span class="line">pipeRequest(config, w, r, r.Method)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">w.WriteHeader(http.StatusMethodNotAllowed)</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://github.com/openfaas/faas/blob/master/watchdog/handler.go#L62:6" target="_blank" rel="noopener"><code>handler.go</code></a></p><p>主要通过调用 <code>os.exec</code> 相关的函数来实现。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">pipeRequest</span><span class="params">(config *WatchdogConfig, w http.ResponseWriter, r *http.Request, method <span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">    parts := strings.Split(config.faasProcess, <span class="string">" "</span>)</span><br><span class="line">ri := &amp;requestInfo&#123;&#125;</span><br><span class="line">log.Println(<span class="string">"Forking fprocess."</span>)</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 执行目标二进制文件</span></span><br><span class="line">targetCmd := exec.Command(parts[<span class="number">0</span>], parts[<span class="number">1</span>:]...)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取目标子进程的 Stdin，后续将请求信息解码有写入</span></span><br><span class="line">    <span class="comment">// func (c *Cmd) StdoutPipe() (io.ReadCloser, error)</span></span><br><span class="line">writer, _ := targetCmd.StdinPipe()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 根据配置的各种参数，来进行处理写入，并采用 waitgroup 来读取响应</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="comment">// func (c *Cmd) CombinedOutput() ([]byte, error) </span></span><br><span class="line">    out, err = targetCmd.Output()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 将读取到的写入 w http.ResponseWriter 中</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Gateway-核心功能"><a href="#Gateway-核心功能" class="headerlink" title="Gateway 核心功能"></a><a href="https://github.com/openfaas/faas/tree/master/gateway" target="_blank" rel="noopener">Gateway</a> 核心功能</h2><p><img src="https://raw.githubusercontent.com/openfaas/faas/master/docs/of-overview.png" alt=""></p><h3 id="Backend-交互"><a href="#Backend-交互" class="headerlink" title="Backend 交互"></a>Backend 交互</h3><p>OpenFaas 的 Backend 是通过 Provider 来进行的一层抽象，达到了与 Docker Swarm 或 Kubernets 容器编排平台的解耦，这两者是官方提供支持的。对于 OpenFaas 如何与 Backend 进行交互，可以参见 <a href="https://github.com/openfaas/faas/blob/master/guide/backends.md" target="_blank" rel="noopener">backend.md</a>。</p><p>OpenFaas 与 Docker Swam 集群的交互是通过  <a href="https://github.com/openfaas/faas-swarm" target="_blank" rel="noopener">faas-swarm</a>, 与 Kubernets 平台的集成是通过 <a href="https://github.com/openfaas/faas-netes" target="_blank" rel="noopener">faas-netes</a> 。其他的 Provider 也都有社区进行开发。例如 AWS <a href="https://github.com/ewilde/faas-fargate" target="_blank" rel="noopener">faas-fargate</a>， Nomad <a href="https://github.com/hashicorp/faas-nomad" target="_blank" rel="noopener">faas-nomad</a>, Rancher <a href="https://github.com/kenfdev/faas-rancher" target="_blank" rel="noopener">faas-rancher</a> 等。</p><p><strong>部署函数</strong></p><p><img src="https://camo.githubusercontent.com/14fca0665b637e7e20b0677a8e636dea8b014dd4/68747470733a2f2f7062732e7477696d672e636f6d2f6d656469612f44497946466e73586b41416135476a2e6a7067" alt=""></p><p><strong>调用函数</strong></p><p><img src="https://camo.githubusercontent.com/1ce383bf1c7076fd83afaf9865df082d6a952900/68747470733a2f2f7062732e7477696d672e636f6d2f6d656469612f44497946466e71586741414d7943682e6a7067" alt=""></p><p>与 Kubernets 的交互图如下:</p><p><img src="https://camo.githubusercontent.com/888f9106de92978615b16231e6a0801e0a5b6f77/68747470733a2f2f696d6775722e636f6d2f646f776e6c6f61642f525847344e3162" alt=""></p><h3 id="Gateway-与-Provider-交互"><a href="#Gateway-与-Provider-交互" class="headerlink" title="Gateway 与 Provider 交互"></a>Gateway 与 Provider 交互</h3><p>Gateway 通过读取环境变量 <code>functions_provider_url</code> 来与 Provider 进行交互。</p><p><code>gateway/server.go</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">osEnv := types.OsEnv&#123;&#125;</span><br><span class="line">readConfig := types.ReadConfig&#123;&#125;</span><br><span class="line">config := readConfig.Read(osEnv)</span><br><span class="line"></span><br><span class="line">log.Printf(<span class="string">"HTTP Read Timeout: %s"</span>, config.ReadTimeout)</span><br><span class="line">log.Printf(<span class="string">"HTTP Write Timeout: %s"</span>, config.WriteTimeout)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> !config.UseExternalProvider() &#123;</span><br><span class="line">log.Fatalln(<span class="string">"You must provide an external provider via 'functions_provider_url' env-var."</span>)</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="comment">// 在配置文件类 config 中 FunctionsProviderURL 作为 Provider 的 URL 使用</span></span><br><span class="line">    reverseProxy := types.NewHTTPClientReverseProxy(config.FunctionsProviderURL, config.UpstreamTimeout)</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">urlResolver := handlers.SingleHostBaseURLResolver&#123;BaseURL: config.FunctionsProviderURL.String()&#125; <span class="comment">// config.FunctionsProviderURL</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Read fetches config from environmental variables.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(ReadConfig)</span> <span class="title">Read</span><span class="params">(hasEnv HasEnv)</span> <span class="title">GatewayConfig</span></span> &#123;</span><br><span class="line">cfg := GatewayConfig&#123;</span><br><span class="line">PrometheusHost: <span class="string">"prometheus"</span>,</span><br><span class="line">PrometheusPort: <span class="number">9090</span>,</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(hasEnv.Getenv(<span class="string">"functions_provider_url"</span>)) &gt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">var</span> err error</span><br><span class="line">cfg.FunctionsProviderURL, err = url.Parse(hasEnv.Getenv(<span class="string">"functions_provider_url"</span>))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(<span class="string">"If functions_provider_url is provided, then it should be a valid URL."</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Docker-Swarm-Provider"><a href="#Docker-Swarm-Provider" class="headerlink" title="Docker Swarm Provider"></a>Docker Swarm Provider</h3><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://blog.alexellis.io/serverless-golang-with-openfaas/" target="_blank" rel="noopener">Build a Serverless Golang Function with OpenFaaS</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;介绍&quot;&gt;&lt;a href=&quot;#介绍&quot; class=&quot;headerlink&quot; title=&quot;介绍&quot;&gt;&lt;/a&gt;介绍&lt;/h2&gt;&lt;p&gt;OpenFaaS - Serverless Functions Made Simple for Docker &amp;amp; Kubernete
      
    
    </summary>
    
      <category term="serverless" scheme="http://www.cn18k.com/categories/serverless/"/>
    
    
      <category term="serverless" scheme="http://www.cn18k.com/tags/serverless/"/>
    
      <category term="openfaas" scheme="http://www.cn18k.com/tags/openfaas/"/>
    
  </entry>
  
  <entry>
    <title>Goroutine 泄露 (译)</title>
    <link href="http://www.cn18k.com/2018/07/31/goroutine_leak/"/>
    <id>http://www.cn18k.com/2018/07/31/goroutine_leak/</id>
    <published>2018-07-31T03:21:00.000Z</published>
    <updated>2018-07-31T03:21:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://www.do1618.com/wp-content/uploads/2018/07/1_WekvakWGg1P-0l-0xNBWuw.jpeg" alt=""></p><p>作者：<a href="https://medium.com/@mlowicki?source=post_header_lockup" target="_blank" rel="noopener">Michał Łowicki</a></p><p>原文地址： <a href="https://medium.com/golangspec/goroutine-leak-400063aef468" target="_blank" rel="noopener">https://medium.com/golangspec/goroutine-leak-400063aef468</a></p><p>Go 中的并发以 goroutines（独立活动）和 channels（用于通信）的方式实现。然而处理 goroutines 程序员仍然需要小心避免泄漏。 如果最终在 I/O上像 channel 通信那样永久阻塞或者陷入无限循环，则会产生泄漏。 即使是阻塞的 goroutine 也会消耗资源，因此程序可能会使用比实际需要更多的内存，可能最终耗尽内存并导致崩溃。 让我们看看它可能发生的几个例子。 然后我们将专注于如何检测程序是否受到这种泄露的影响。</p><h2 id="发送到没有接收方的-channel"><a href="#发送到没有接收方的-channel" class="headerlink" title="发送到没有接收方的 channel"></a>发送到没有接收方的 channel</h2><p><img src="https://www.do1618.com/wp-content/uploads/2018/07/1_7Ii5H_ld1frQDFa4VuUm5g.jpeg" alt=""></p><p>假设为了冗余，程序向许多后端发送请求，只使用接受到第一个响应，丢弃后面的响应。 下面的代码将模拟通过等待一个随机的毫秒数向下游服务器发送请求：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">    <span class="string">"math/rand"</span></span><br><span class="line">    <span class="string">"runtime"</span></span><br><span class="line">    <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">query</span><span class="params">()</span> <span class="title">int</span></span> &#123;</span><br><span class="line">    n := rand.Intn(<span class="number">100</span>)</span><br><span class="line">    time.Sleep(time.Duration(n) * time.Millisecond)</span><br><span class="line">    <span class="keyword">return</span> n</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">queryAll</span><span class="params">()</span> <span class="title">int</span></span> &#123;</span><br><span class="line">    ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">int</span>)</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; ch &lt;- query() &#125;()</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; ch &lt;- query() &#125;()</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; ch &lt;- query() &#125;()</span><br><span class="line">    <span class="keyword">return</span> &lt;-ch</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">4</span>; i++ &#123;</span><br><span class="line">        queryAll()</span><br><span class="line">        fmt.Printf(<span class="string">"#goroutines: %d\n"</span>, runtime.NumGoroutine())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">#goroutines: <span class="number">3</span></span><br><span class="line">#goroutines: <span class="number">5</span></span><br><span class="line">#goroutines: <span class="number">7</span></span><br><span class="line">#goroutines: <span class="number">9</span></span><br></pre></td></tr></table></figure><p>每次调用 <code>queryAll</code> ，都会导致 <code>goroutines</code> 数量的增长。 问题是，在收到第一个响应后，”较慢” 的 goroutines 将发送到另一侧没有接收的 channel 中。</p><p>如果预先知道后端服务器的数量，则可能的解决方法是使用缓冲通道。 或者我们可以使用一个 goroutine 从 channel 接收数据，这样仍然需要至少有一个 goroutine 进行相关工作。 其他选项可能是使用 <a href="https://golang.org/pkg/context/" target="_blank" rel="noopener">context</a> 取消其他请求的一些机制（<a href="http://golang.rakyll.org/leakingctx/" target="_blank" rel="noopener">示例</a>）。</p><h2 id="从没有发送方的-channel-接收"><a href="#从没有发送方的-channel-接收" class="headerlink" title="从没有发送方的 channel 接收"></a>从没有发送方的 channel 接收</h2><p>此方案类似于在没有任何接收方的情况下发送到 channel 。 <a href="http://openmymind.net/Leaking-Goroutines/" target="_blank" rel="noopener">泄漏的 goroutine</a> 帖子包含一个例子。</p><h3 id="nil-channel"><a href="#nil-channel" class="headerlink" title="nil channel"></a>nil channel</h3><p>写入到 nil channel 会导致永久阻塞：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> ch <span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">    ch &lt;- <span class="keyword">struct</span>&#123;&#125;&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以它导致死锁：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fatal error: all goroutines are asleep - deadlock!</span><br><span class="line">goroutine 1 [chan send (nil chan)]:</span><br><span class="line">main.main()</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><p>从 nil channel 读取时也是如此：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ch <span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">&lt;-ch</span><br></pre></td></tr></table></figure><p>这可能在传递尚未初始化的 channel 时发生：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">    <span class="string">"runtime"</span></span><br><span class="line">    <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> ch <span class="keyword">chan</span> <span class="keyword">int</span></span><br><span class="line">    <span class="keyword">if</span> <span class="literal">false</span> &#123;</span><br><span class="line">        ch = <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">int</span>, <span class="number">1</span>)</span><br><span class="line">        ch &lt;- <span class="number">1</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(ch <span class="keyword">chan</span> <span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line">        &lt;-ch</span><br><span class="line">    &#125;(ch)</span><br><span class="line">    </span><br><span class="line">    c := time.Tick(<span class="number">1</span> * time.Second)</span><br><span class="line">    <span class="keyword">for</span> <span class="keyword">range</span> c &#123;</span><br><span class="line">        fmt.Printf(<span class="string">"#goroutines: %d\n"</span>, runtime.NumGoroutine())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这个例子中有一个明显的错误 - <code>if false {</code> 在更复杂的程序中它更容易忽略，将 channel 设置为了 nil。</p><h3 id="无限循环"><a href="#无限循环" class="headerlink" title="无限循环"></a>无限循环</h3><p>Goroutine 泄漏不仅仅是由于错误使用 channel 造成的。 原因可能是阻塞的 I/O 操作，例如在没有超时的情况下向 API 服务器发送请求。 另一个选择是程序可以简单地陷入无限循环。</p><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p><img src="http://www.do1618.com/wp-content/uploads/2018/07/1_AzMvKBxKAmQQxCU7sqK4DA.jpeg" alt=""></p><p><strong>runtime.NumGoroutine</strong></p><p>简单的方法是使用 <a href="https://golang.org/pkg/runtime/#NumGoroutine" target="_blank" rel="noopener">runtime.NumGoroutine</a> 查看 goroutine 数量。</p><p><strong>net/http/pprof</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"log"</span></span><br><span class="line">    <span class="string">"net/http"</span></span><br><span class="line">    _ <span class="string">"net/http/pprof"</span></span><br><span class="line">)</span><br><span class="line">...</span><br><span class="line">log.Println(http.ListenAndServe(<span class="string">"localhost:6060"</span>, <span class="literal">nil</span>))</span><br></pre></td></tr></table></figure><p>我们可以在  <a href="http://localhost:6060/debug/pprof/goroutine?debug=1" target="_blank" rel="noopener">http://localhost:6060/debug/pprof/goroutine?debug=1</a> 上查看到 goroutine 列表及其堆栈跟踪。</p><p><strong>runtime/pprof</strong></p><p>要将现有 goroutine 的堆栈跟踪打印到 stdout：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"os"</span></span><br><span class="line">    <span class="string">"runtime/pprof"</span></span><br><span class="line">)</span><br><span class="line">...</span><br><span class="line">pprof.Lookup(<span class="string">"goroutine"</span>).WriteTo(os.Stdout, <span class="number">1</span>)</span><br></pre></td></tr></table></figure><p><strong><a href="https://github.com/google/gops" target="_blank" rel="noopener">gops</a></strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="keyword">go</span> get -u github.com/google/gops</span><br></pre></td></tr></table></figure><p>与程序集成：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"github.com/google/gops/agent"</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">if</span> err := agent.Start(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">    log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">time.Sleep(time.Hour)</span><br><span class="line">&gt; ./bin/gops</span><br><span class="line"><span class="number">12365</span>   gops    (/Users/mlowicki/projects/golang/spec/bin/gops)</span><br><span class="line"><span class="number">12336</span>*  lab     (/Users/mlowicki/projects/golang/spec/bin/lab)</span><br><span class="line">&gt; ./bin/gops vitals -p=<span class="number">12336</span></span><br><span class="line">goroutines: <span class="number">14</span></span><br><span class="line">OS threads: <span class="number">9</span></span><br><span class="line">GOMAXPROCS: <span class="number">4</span></span><br><span class="line">num CPU: <span class="number">4</span></span><br></pre></td></tr></table></figure><p><strong><a href="https://github.com/fortytw2/leaktest" target="_blank" rel="noopener">leaktest</a></strong></p><p>这是自动检测 goroutine 泄漏的方法之一。 它基本上在测试的开始和结束时通过 <a href="https://golang.org/pkg/runtime/#Stack" target="_blank" rel="noopener"><code>runtime.Stack</code></a> 获取活动goroutine 的堆栈跟踪。 如果在测试完成后仍然存在新的 goroutine，那么它被归类为泄漏。</p><p>对于 goroutines 管理非常重要，即使是已经在运行中的程序，因为 goroutines 的泄露最终可能导致内存不足。</p><p>问题通常会在程序在生产环境中运行数天后才被发现，因此可能会造成真正的损害。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://golang.org/pkg/" target="_blank" rel="noopener">https://golang.org/pkg/</a></li><li><a href="https://github.com/google/gops" target="_blank" rel="noopener">https://github.com/google/gops</a></li><li><a href="https://github.com/golang/go/issues/5308" target="_blank" rel="noopener">https://github.com/golang/go/issues/5308</a></li><li><a href="https://github.com/fortytw2/leaktest" target="_blank" rel="noopener">https://github.com/fortytw2/leaktest</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;img src=&quot;https://www.do1618.com/wp-content/uploads/2018/07/1_WekvakWGg1P-0l-0xNBWuw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;作者：&lt;a href=&quot;https://medium.com/
      
    
    </summary>
    
      <category term="golang" scheme="http://www.cn18k.com/categories/golang/"/>
    
    
      <category term="golang" scheme="http://www.cn18k.com/tags/golang/"/>
    
  </entry>
  
</feed>
