<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Clay 的技术空间</title>
  
  <subtitle>用进废退 | 艺不压身</subtitle>
  <link href="https://www.techgrow.cn/atom.xml" rel="self"/>
  
  <link href="https://www.techgrow.cn/"/>
  <updated>2026-01-30T13:03:50.000Z</updated>
  <id>https://www.techgrow.cn/</id>
  
  <author>
    <name>Clay</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>电⼦元器件零基础⼊⻔教程之二</title>
    <link href="https://www.techgrow.cn/posts/c1303b78.html"/>
    <id>https://www.techgrow.cn/posts/c1303b78.html</id>
    <published>2026-01-30T13:03:50.000Z</published>
    <updated>2026-01-30T13:03:50.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/14c20f01.html">电⼦元器件零基础⼊⻔教程之一</a></li><li><a href="/posts/c1303b78.html">电⼦元器件零基础⼊⻔教程之二</a></li></ul><span id="more"></span><h2 id="常见电子元器件"><a href="#常见电子元器件" class="headerlink" title="常见电子元器件"></a>常见电子元器件</h2>]]></content>
    
    
    <summary type="html">本文主要介绍电⼦元器件零基础⼊⻔教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="嵌入式开发" scheme="https://www.techgrow.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>电⼦元器件零基础⼊⻔教程之一</title>
    <link href="https://www.techgrow.cn/posts/14c20f01.html"/>
    <id>https://www.techgrow.cn/posts/14c20f01.html</id>
    <published>2026-01-11T13:03:50.000Z</published>
    <updated>2026-01-11T13:03:50.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/14c20f01.html">电⼦元器件零基础⼊⻔教程之一</a></li><li><a href="/posts/c1303b78.html">电⼦元器件零基础⼊⻔教程之二</a></li></ul><span id="more"></span><h2 id="前序知识"><a href="#前序知识" class="headerlink" title="前序知识"></a>前序知识</h2><p>这里先了解⼀些原⼦相关的知识 ，会对电学中很多的概念和结论有更好的理解。</p><h3 id="原子（Atomic）"><a href="#原子（Atomic）" class="headerlink" title="原子（Atomic）"></a>原子（Atomic）</h3><ul><li>原⼦是构成物质的最⼩单位，它由原⼦核和电⼦组成，其中原⼦核又由质⼦和中⼦组成。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-1.png"></p><ul><li>下图为碳原⼦的简易模型图：</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-2.png"></p><div class="admonition note"><p class="admonition-title">提示</p><p>这里提及原⼦结构，是为了给后续的：电流、电压、⼆极管、三极管等知识做铺垫，但也会忽略掉⼀些与电学⽆关的知识，⽐如：有些元素（氢的同位素氕）没有中⼦，再⽐如：质⼦和中⼦的⽐例关系，会对元素的放射性有⼀定影响等，这些知识不在本教程中体现。</p></div><h3 id="质⼦（Proton）"><a href="#质⼦（Proton）" class="headerlink" title="质⼦（Proton）"></a>质⼦（Proton）</h3><ul><li>质⼦带⼀个单位的正电荷，且质⼦数量决定着元素身份，改变了质⼦数量，也就改变了原⼦的类型。</li><li>举个例⼦：如果能从铅的原⼦核（82 个质子）⾥移出 3 个质⼦，那就成功的⽤铅原⼦造出了⼀个⾦原⼦（79 个质子）。</li><li>⽽实际上：质⼦之间结合地⾮常紧密，⼏乎⽆法分开，这也是炼⾦术未能实现的原因。</li><li>拓展内容：现代物理学中的核反应堆、粒⼦加速器等可以实现上述过程，但所需能量和成本⾮常⾼，已经远超⻩⾦的价值了，⽽且这种反应通常很难精确控制，产出的黄⾦量可能很少。</li></ul><h3 id="中⼦（Neutron）"><a href="#中⼦（Neutron）" class="headerlink" title="中⼦（Neutron）"></a>中⼦（Neutron）</h3><ul><li>中⼦不带电，且中⼦对原⼦的影响远⼩于质⼦。</li><li>电学领域通常不对中⼦进⾏深⼊探讨，因为它不带电荷，不直接影响电势、电流的形成。</li></ul><h3 id="电⼦（Electron）"><a href="#电⼦（Electron）" class="headerlink" title="电⼦（Electron）"></a>电⼦（Electron）</h3><ul><li>电⼦带⼀个单位的负电荷，它围绕原⼦核做⽆规则的运动。<ul><li>早期的『波尔模型』，将电⼦的运动⽐作成⾏星的 “轨道运动”；</li><li>但随着量⼦⼒学的发展，这种⽐喻并不完全正确，所谓的 “轨道” 其实是电⼦出现的概率区域，并不不是⼀个具体的路径。</li></ul></li><li>通常来说原⼦的质⼦数和电⼦数相等，其内部的正电荷与负电荷互相抵消，这时候原⼦呈电中性。</li></ul><hr><ul><li>电⼦层：电⼦围绕原⼦核活动的范围。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-3.png"></p><ul><li><p>最外层电⼦数为 8 个时，通常是较稳定的状态。</p><ul><li>许多元素在化学反应中，会倾向于通过：获得电⼦、失去电⼦、共享电⼦，来尽可能达到 8 个电⼦的稳定结构，这个现象被称为：⼋隅规则。</li><li>当然也有⼀些例外的元素，例如：氢 (H) 、氦 (He) 只需要 2 个电⼦就能达到稳定状态。</li></ul></li><li><p>不同电荷之间互相吸引，相同电荷之间互相排斥，具体计算参考库伦定律：</p></li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-4.png"></p><ul><li>最外层的电⼦更容易脱离原⼦，因为它们与原⼦核的距离较远，受到的引⼒较弱。</li></ul><h3 id="⽯墨与⾦刚⽯"><a href="#⽯墨与⾦刚⽯" class="headerlink" title="⽯墨与⾦刚⽯"></a>⽯墨与⾦刚⽯</h3><ul><li>⽯墨：每个碳原⼦与周边 3 个碳原⼦分别建⽴共价键，最终外层剩余 1 个 未成键的电⼦，这个电⼦可以在不同的原⼦之间⾃由移动，所以⽯墨是导体。</li><li>⽯墨的层与层之间是靠『范德华⼒』结合在⼀起的，范德华⼒是⼀种弱的分⼦间作⽤⼒，因此⽯墨的各个层之间可以很容易地滑动，所以⽯墨可以作为：润滑剂、铅笔芯等。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-5.png"></p><hr><ul><li>⾦刚⽯：每个碳原⼦与周边 4 个 碳原⼦分别建⽴共价键，形成⼀个⾮常坚硬的⽴体⽹状结构，没有⾃由电⼦，所以⾦刚⽯是绝缘体。</li><li>⾦刚⽯的碳原⼦之间共价键内的电⼦活动范围相对较⼩，且共价键内的电⼦被两个原⼦核共同吸引，所以碳原⼦之间共价键内的电⼦相对稳定，不会轻易脱落，除⾮有⾜够的能量来打破这个键。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-6.png"></p><h2 id="电学基础"><a href="#电学基础" class="headerlink" title="电学基础"></a>电学基础</h2><h3 id="电场、电势、电势能"><a href="#电场、电势、电势能" class="headerlink" title="电场、电势、电势能"></a>电场、电势、电势能</h3><p>为了能更好的理解⼀些抽象的概念，这里⽤重⼒场中的相关概念，去类⽐电场中的相关概念。</p><h4 id="重⼒场、电场"><a href="#重⼒场、电场" class="headerlink" title="重⼒场、电场"></a>重⼒场、电场</h4><ul><li>重⼒场：在地球周围存在，有质量的物体进⼊该区域后，会受到被拉向中⼼的⼒。</li><li>电场：在电荷周围存在，带电的粒⼦进⼊这个区域后，会受到吸引⼒或排斥⼒。<ul><li>沿着电场线⽅向，电场的强度会逐渐减弱，且⽆论正电荷还是负电荷，⽆穷远处的电场强度为 0。</li></ul></li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-9.png"></p><h4 id="重⼒势能、电势能"><a href="#重⼒势能、电势能" class="headerlink" title="重⼒势能、电势能"></a>重⼒势能、电势能</h4><ul><li>重⼒势能：描述物体在重⼒场中所具备的能量，与所处位置、物体质量均有关。</li><li>电势能：描述带电粒⼦在电场中所具备的能量，与所处位置、粒⼦带电量均有关。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-10.png"></p><h4 id="重⼒势、电势"><a href="#重⼒势、电势" class="headerlink" title="重⼒势、电势"></a>重⼒势、电势</h4><p><img data-src="../../../asset/2026/01/embedded-dev-11.png"></p><p><img data-src="../../../asset/2026/01/embedded-dev-12.png"></p><h3 id="电流"><a href="#电流" class="headerlink" title="电流"></a>电流</h3><ul><li><p>电流的概念：</p><ul><li>电荷的定向移动，形成了电流，衡量电流的⼤⼩，要看单位时间内通过导体横截⾯的电荷量。</li><li>注意：<ul><li>电学上定义的『电流⽅向』是正电荷的流动的⽅向，这个概念在 19 世纪初就先被定电学上定义的『电流⽅向』是正电荷的流动的⽅向，这个概念在 19 世纪初就先被定义了；</li><li>后来虽然发现了是电⼦在移动，但为了保持定义的统⼀性，『电流⽅向』仍然⽤正电荷的流动去定义，<strong>所以『电流⽅向』与『电⼦移动⽅向』是相反的</strong>。</li></ul></li></ul></li><li><p>电流的单位：安培 (A)，简称安</p><ul><li>1A 的含义：1 秒钟内有 <code>6.242*10^18</code> 个单位电荷（电⼦、质⼦、都属于单位电荷）通过了导体横截⾯。</li><li><code>6.242*10^18</code> 个单位电荷所带的电荷总量，为 1 库伦 (C)，所以 1A 的含义也能这样描述：在 1 秒钟内，有 1 库仑的电荷通过了导体的横截⾯。</li></ul></li><li><p>电流的单位换算：</p><ul><li>进位为 1000，例如 1A = 1000mA</li><li> 常⽤的单位是：安 (A)、毫安 (mA)<br><img data-src="../../../asset/2026/01/embedded-dev-7.png"></li></ul></li><li><p>常⻅电器的⼯作电流：<br>  <img data-src="../../../asset/2026/01/embedded-dev-8.png"></p></li></ul><h3 id="电压"><a href="#电压" class="headerlink" title="电压"></a>电压</h3><ul><li><p>电压的概念：</p><ul><li>⼜称电势差，是两点之间电势的差值，衡量电压的⼤⼩，要看两点电势差的⼤⼩。</li><li>有了电压，电子才能持续且定向地移动起来，所以电压是形成电流的必要条件之一。</li><li>电压越大，能定向移动起来的电子就越多，电流就会越大。</li></ul></li><li><p>电压的单位：伏特 (V)，简称伏</p><ul><li>1V 的含义是：电场 对 1 库伦 (C) 电荷做了 1 焦⽿ (J) 的功。<br><img data-src="../../../asset/2026/01/embedded-dev-17.png"></li></ul></li><li><p>电压的单位换算：</p><ul><li>进位为 1000，例如 1kV = 1000V<br><img data-src="../../../asset/2026/01/embedded-dev-13.png"></li></ul></li><li><p>常⻅电源的电压：<br>  <img data-src="../../../asset/2026/01/embedded-dev-14.png"></p></li></ul><h3 id="电阻"><a href="#电阻" class="headerlink" title="电阻"></a>电阻</h3><ul><li><p>电阻的概念：</p><ul><li>电阻是指材料或元器件对电流流动的阻碍程度。</li><li>电阻的大小不仅与材料有关，还与材料的粗细、长短等因素有关。</li></ul></li><li><p>电阻的单位：欧姆（简称欧），符号是 Ω。</p><ul><li>1Ω 的含义：给导体施加 1V 电压，此时如果导体的电流为 1A，那这个导体的电阻就是 1Ω。</li></ul></li><li><p>电阻的单位换算：</p><ul><li>进位为 1000，例如 1KΩ = 1000Ω<br><img data-src="../../../asset/2026/01/embedded-dev-15.png"></li></ul></li><li><p>电阻的决定式：</p><ul><li>$R = \rho \cdot \frac{L}{A}$<ul><li><strong>R</strong>：电阻</li><li><strong> L</strong>：导体的⻓度</li><li><strong> A</strong>：导体的横截⾯积</li><li><strong> ρ</strong>：材料的电阻率，表示材料对电流的阻碍能⼒</li></ul></li></ul></li><li><p>电阻的补充内容：</p><ul><li>后续会讲解⼀种电⼦元器件 — 『电阻器』，它的简称也叫『电阻』，是⼀种专⻔⽤于控制电流大小的电⼦元器件（如下图所示），其的作⽤是：为电路提供电阻，来限制电流的⼤⼩。<br><img data-src="../../../asset/2026/01/embedded-dev-16.png"></li></ul></li></ul><h3 id="电路"><a href="#电路" class="headerlink" title="电路"></a>电路</h3><ul><li>电源：物理学中将提供电能的装置叫做电源。</li><li>⽤电器：将灯泡、电动机、等这类消耗电能的装置叫做⽤电器。</li><li>电路：电源、⽤电器，再加上导线、开关等，就组成了电流可以流过的路径，这就是电路。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-18.png"></p><ul><li><p>电路图：</p><ul><li>在绘制电路图时，为了绘制简单且⽅便研究，通常⽤图形符号来表示元件。</li><li>此处先只列举了三个电路符号（如下图所示），其他的电路符号以后会随着教程讲解。<br><img data-src="../../../asset/2026/01/embedded-dev-19.png"></li></ul></li><li><p>电路状态：</p><ul><li>断路：电路中某处被切断，电路中没有电流流过。</li><li>通路：正常接通的电路，⽤电器能够正常⼯作的电路。</li><li>短路：比如直接⽤导线将电源的正、负极连接起来（⚠️危险操作）。<br><img data-src="../../../asset/2026/01/embedded-dev-20.png"></li></ul></li></ul><h3 id="欧姆定律"><a href="#欧姆定律" class="headerlink" title="欧姆定律"></a>欧姆定律</h3><ul><li><p>概念：</p><ul><li>导体中的电流，与导体两端的电压成正⽐，与导体的电阻成反⽐。</li><li>在电阻不变的情况下，电压越⼤，电流也会越⼤。</li></ul></li><li><p>公式：</p><ul><li>$I = \frac{U}{R}$<ul><li><strong>U</strong> 为电压，单位为 <strong>V</strong></li><li><strong>I</strong> 为电流，单位为 <strong>A</strong></li><li><strong>R</strong> 为电阻，单位为 <strong>Ω</strong></li></ul></li><li> 变形公式一：<ul><li>$R = \frac{U}{I}$</li></ul></li><li> 变形公式二：<ul><li>$U = IR$</li></ul></li></ul></li><li><p> 决定式 vs 定义式：<br>  <img data-src="../../../asset/2026/01/embedded-dev-21.png"></p></li><li><p>使⽤场景：<br>  <img data-src="../../../asset/2026/01/embedded-dev-22.png"></p></li><li><p>局限性：</p><ul><li><strong>欧姆定律有局限性，仅适用于纯电阻电路（或者说纯电阻元器件、纯电阻设备）！</strong></li><li>纯电阻电路：消耗的电能仅转化为热能，没有其他形式的能量转换。</li></ul></li></ul><h3 id="直流电-VS-交流电"><a href="#直流电-VS-交流电" class="headerlink" title="直流电 VS 交流电"></a>直流电 VS 交流电</h3><h4 id="直流电"><a href="#直流电" class="headerlink" title="直流电"></a>直流电</h4><ul><li><p>定义：</p><ul><li>有固定的正负极，且电流⽅向始终『不变』的电流，全称：Direct Current，简称 DC。</li></ul></li><li><p>举例：</p><ul><li>⼲电池、锂电池、光伏发电板等。<br><img data-src="../../../asset/2026/01/embedded-dev-23.png"></li></ul></li></ul><h4 id="交流电"><a href="#交流电" class="headerlink" title="交流电"></a>交流电</h4><ul><li><p>定义：</p><ul><li>电流⽅向随时间做『周期性变化』的电流，全称：Alternating Current，简称 AC。</li><li>交流电的电流⽅向⼀定做周期性变化，但电流⼤⼩不⼀定做周期性变化！</li></ul></li><li><p>举例：</p><ul><li>家庭⽤电<br><img data-src="../../../asset/2026/01/embedded-dev-24.png"></li></ul></li></ul><h4 id="扩展内容"><a href="#扩展内容" class="headerlink" title="扩展内容"></a>扩展内容</h4><p>家庭⽤电为的 220V / 50Hz 的正弦交流电，下⾯是关于家⽤交流电的⼀些总结性内容。</p><ul><li><p>周期：</p><ul><li>交流电完成⼀个完整波动循环所需的时间（参考下图）。<br><img data-src="../../../asset/2026/01/embedded-dev-25.png"></li></ul></li><li><p>零线与⽕线：</p><ul><li>交流电⽆正负极之分，但有零⽕线之分。</li><li>零线的电势始终为 0，⽕线的电势不断变化，有时⾼于 0，有时低于 0，这样就形成了交变电流。</li></ul></li><li><p>对于 220V / 50Hz 的理解：</p><ul><li>220V 指的是交流电的有效电压<ul><li>什么是有效电压？—— 让交流电和直流电，分别通过同⼀个电阻，在交流电的⼀个周期内，若⼆者产⽣的热量相等，那么这个直流电的电压，就是交流电的有效电压。</li><li>我们平时聊天所说到的、⽤电器说明书上写的、电表所测到的，都是有效电压，即：220V。家庭用电的 220V 指的是交流电的有效电压。</li><li>什么是峰值电压？—— 交流电在⼀个周期中达到的最⼤电压值，220V 交流电的峰值电压为：$200\sqrt {2}$，约为：311V，<a href="../../../asset/2026/01/embedded-dev-30.png">如图所示</a>。</li></ul></li><li>50hz 指的是交流电的频率<ul><li> 50hz 的含义是交流电在 1 秒钟内完成了 50 个周期（每个周期包含两次电流⽅向变化，所以对于 50Hz 的交流电来说，1 秒钟内电流的⽅向变化了 100 次）。</li></ul></li></ul></li><li><p>整流与逆变：</p><ul><li>什么是整流？—— 交流（AC） → 直流（DC）</li><li>什么是逆变？—— 直流（DC） → 交流（AC）</li><li>在⽣活中很多的电器，都是将交流转换成直流后⼯作的（参考下图），例如：电脑、⼿机、路由器等，因为直流电更加稳定，适合精密电路。<br><img data-src="../../../asset/2026/01/embedded-dev-26.png"></li></ul></li></ul><h3 id="弱电与强电"><a href="#弱电与强电" class="headerlink" title="弱电与强电"></a>弱电与强电</h3><ul><li><p>弱电：</p><ul><li>弱电的电压⼀般较低，⾏业规定安全电压为不⾼于 36V。</li><li>弱电通常⽤于直流电路（3.3V、5V、12V），弱电也能于信号传递，例如：⾳频和视频线路、⽹络线路、电话线等（参考下图）。<br><img data-src="../../../asset/2026/01/embedded-dev-27.png"></li></ul></li><li><p>强电：</p><ul><li>强电的电压⼀般很⾼，对人体危害较大，必须采取防护措施，例如：220V 的家⽤电、1000V 及以上的⾼压电。</li><li>强电常⽤于能源传输或者高功率供电（参考下图）。<br><img data-src="../../../asset/2026/01/embedded-dev-28.png"></li></ul></li><li><p>通过人体电流的大小，直接决定了电击的危险性：</p><ul><li>感知电流：能引起人感觉的最小电流，约 <strong>0.7mA ~ 1.1mA</strong> 左右，有很轻微的刺痛感，但不会造成伤害。</li><li>摆脱电流：能自主摆脱的最大电流，约 <strong>10mA ~ 16mA</strong> 左右，会有明显的肌肉收缩，不要持续接触。</li><li>致命电流：能在短时间内危及生命的最小电流，约为 <strong>50mA</strong> 左右，明显的呼吸困难、心脏产生室颤。</li></ul></li></ul><div class="admonition warning"><p class="admonition-title">特别注意</p><p><strong>⼤家⼀定要注意⽤电安全，⼀般弱电平台⽤⼿直接触摸不会造成⼈身危险；但是强电平台 ⼀定不能⽤⼿直接触摸，会有⽣命危险！</strong></p></div><h3 id="家庭电路"><a href="#家庭电路" class="headerlink" title="家庭电路"></a>家庭电路</h3><p>本节属于电⼯学内容，但也属于电学相关的常识性内容，不必深究，了解⼀下就可以。</p><p><img data-src="../../../asset/2026/01/embedded-dev-29.png"></p><ul><li>家庭电路中的『保护接地』，是如何保护我们的？<ul><li>(1) 发电⼚系统会通过⼀个接地点，将零线连接到⼤地，这是电⼒系统中⼀个重要的设计， 其⽬的是：<ul><li>保证电压稳定：将零线电压锁定为地电位（0V）。</li><li>提供保护：在发⽣故障时，接地可以快速引导电流回到地，触发保护装置（如断路器）。</li></ul></li><li>(2) 为什么触摸⽕线很危险？<ul><li>⽕线为 220V，地为 0V，身体与地之间构成回路，导致电流流过身体，很危险！</li><li>人体触电时，电流的整体路径是：⽕线（220V）→ 人体 → 地 → 零线（0V，接地）→ 发电⼚。</li></ul></li></ul></li></ul><hr><ul><li>无地线 + 人体触电（致命危险）</li></ul><p><img data-src="/gif/alternating-current-1.gif"></p><ul><li>接地线 + 人体触电（非致命危险）</li></ul><p><img data-src="/gif/alternating-current-2.gif"></p><ul><li>断路器 + 接地线 + 人体触电（安全）</li></ul><p><img data-src="/gif/alternating-current-3.gif"></p><div class="admonition warning"><p class="admonition-title">特别注意</p><p><strong>特别注意，家庭⽤电属于强电！禁⽌⾃⼰操作强电！也禁⽌⽤⾃⼰的身体去验证各种结论！</strong></p></div><h3 id="串联与并联"><a href="#串联与并联" class="headerlink" title="串联与并联"></a>串联与并联</h3><h4 id="串联电路"><a href="#串联电路" class="headerlink" title="串联电路"></a>串联电路</h4><p><img data-src="../../../asset/2026/01/embedded-dev-31.png"></p><h4 id="并联电路"><a href="#并联电路" class="headerlink" title="并联电路"></a>并联电路</h4><p><img data-src="../../../asset/2026/01/embedded-dev-32.png"></p><h4 id="串并联练习"><a href="#串并联练习" class="headerlink" title="串并联练习"></a>串并联练习</h4><ul><li>电路图 1，请分析 A、B、C 三点电压分别是多少？答案是：A 点 9V，B 点 3V，C 点 0V，<a href="../../../asset/2026/01/embedded-dev-41.png">点击查看图解分析</a>。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-33.png"></p><ul><li>电路图 2，请分析 A、B、C 三点电压分别是多少？答案是：A 点 4V，B 点 1V，C 点 0V，<a href="../../../asset/2026/01/embedded-dev-42.png">点击查看图解分析</a>。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-34.png"></p><ul><li>电路图 3，请分析 A、B、C 三点电压分别是多少？答案是：A 点 10V，B 点 8V，C 点 4V，<a href="../../../asset/2026/01/embedded-dev-44.png">点击查看图解分析</a>。</li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-43.png"></p><div class="admonition note"><p class="admonition-title">电路仿真软件</p><ul><li>串联电路和并联电路可以使用开源电路仿真软件 <a href="https://github.com/sharpie7/circuitjs1">CircuitJS1</a> 进行模拟和分析。CircuitJS1 是基于 Paul Falstad 的 <a href="https://www.falstad.com/circuit/">Circuit Simulator</a> 开发的 JavaScript 版本。</li><li>其他的 CircuitJS1 开源衍生版本有：<a href="https://github.com/pfalstad/circuitjs1">pfalstad/circuitjs1</a>、<a href="https://github.com/SEVA77/circuitjs1">SEVA77/circuitjs1</a>。</li></ul></div><h3 id="电功率"><a href="#电功率" class="headerlink" title="电功率"></a>电功率</h3><ul><li><p>概念：</p><ul><li>电功率⽤于衡量电流在单位时间内所做的功（⽤来表示⽤电器消耗电能快慢的物理量）。</li></ul></li><li><p>单位：</p><ul><li>瓦特（简称瓦），符号是 W。</li></ul></li><li><p>单位换算：</p><ul><li>进位为 1000，例如： 1kW = 1000W。<br><img data-src="../../../asset/2026/01/embedded-dev-36.png"></li></ul></li><li><p>公式：</p><ul><li>$P = U I$<ul><li><code>U</code> 为电压，单位为 <code>V</code>（伏）</li><li><code>I</code> 为电流，单位为 <code>A</code>（安）</li><li><code>P</code> 为功率，单位为 <code>W</code>（瓦）</li></ul></li></ul></li><li><p>常⻅⽤电器的功率</p></li></ul><p><img data-src="../../../asset/2026/01/embedded-dev-37.png"></p><hr><ul><li><p>瓦时 VS 千瓦时</p><ul><li>⼆者都是表示电能的⼀种单位。</li><li>瓦时：<ul><li>瓦时是功率（W）与时间（h）的乘积，即：<code>瓦时 = 功率 × 时间</code></li><li>⼀个 1W 的⽤电器，⼯作 1h ，它消耗的电量就是 1 瓦时（1Wh）。</li></ul></li><li>千瓦时：<ul><li>千瓦时是瓦时的千倍，通常⽤于描述⼤型电⼒设备，如电表读数、新能源汽⻋等。</li><li>⼀个 1kW 的电器⼯作 1h ，它消耗的电量就是 1 千瓦时（1kWh，俗称 1 度电）。<br><img data-src="../../../asset/2026/01/embedded-dev-38.png"></li></ul></li></ul></li><li><p>安时 VS 毫安时</p><ul><li>⼆者都是描述电池容量的单位。</li><li>安时：<ul><li>表示该电池能以 1A 的电流持续⼯作 1h，通常⽤于描述⼤容量的电池。</li></ul></li><li>毫安时：<ul><li>安时的千分之⼀，通常⽤于描述⼩容量的电池。</li></ul></li><li>例如：<ul><li>华为『Mate70 Pro』⼿机电池的容量为 5700mAh ，充电宝的电池容量为 20000mAh。</li><li>有些时候我们也省略 <code>h</code>，直接说：⼿机的电池容量和为 5700mA，充电宝电池容量为：20000mA。<br><img data-src="../../../asset/2026/01/embedded-dev-39.png"></li></ul></li></ul></li></ul><h3 id="焦⽿定律"><a href="#焦⽿定律" class="headerlink" title="焦⽿定律"></a>焦⽿定律</h3><ul><li><p>概念：</p><ul><li>电流通过导体产⽣的热量与电流的平⽅成正⽐，跟导体的电阻成正⽐，跟通电时间成正⽐。</li></ul></li><li><p>单位：</p><ul><li>单位：焦⽿（简称焦），简称 J。</li></ul></li><li><p>公式：</p><ul><li>$Q = I^2 R t$<ul><li><code>Q</code> 为热量，单位为：<code>J</code>（焦⽿）</li><li><code>I</code> 为电流，单位为：<code>A</code>（安培）</li><li><code>R</code> 为电阻，单位为：<code>Ω</code>（欧姆）</li><li><code>t</code> 为时间，单位为：<code>s</code>（秒）</li></ul></li></ul></li><li><p>例如：</p><ul><li>⼀根 60Ω 的电阻丝接在 36V 的电源两端，在 5 分钟内共产⽣多少热量？答案是：$ \left (\frac {36\ \text {V}}{60\ \Omega} \right)^2 \times 60\ \Omega \times 5\ \times 60\ = 6480\ \text {J} $<br>  <img data-src="../../../asset/2026/01/embedded-dev-40.png"></li></ul></li><li><p>注意：</p><ul><li>电路通过导体时，如果电能全部转化为内能，⽽没有同时转化为其他形式的能量，那么电流产⽣的热量就等于消耗的电能，但实际上：<strong>只有纯电阻电路，才能把电能完全⽤于产⽣热量</strong>！</li><li>对于<strong>纯电阻电路</strong>来说，消耗的电能全部⽤于转化热量，所以可以推导出如下公式：<ul><li>功率计算公式：$P = \frac {U^2}{R}$，$P = I^2 R$</li><li> 热量计算公式：$Q = U I t$，$Q = P t$</li></ul></li></ul></li></ul><h3 id="公式总结"><a href="#公式总结" class="headerlink" title="公式总结"></a>公式总结</h3><p><img data-src="../../../asset/2026/01/embedded-dev-45.png"></p><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>功率计算公式（$P = U I$）与焦耳定律（$Q = I^2 R t$）是原始公式，适用于所有电路。</li><li>欧姆定律（$I = \frac {U}{R}$）有局限性，仅适用于纯电阻电路（或者说纯电阻元器件、纯电阻设备）。</li></ul></div>]]></content>
    
    
    <summary type="html">本文主要介绍电⼦元器件零基础⼊⻔教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="嵌入式开发" scheme="https://www.techgrow.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>基于 ESP32、SI4732 开发收音机</title>
    <link href="https://www.techgrow.cn/posts/c350c4dd.html"/>
    <id>https://www.techgrow.cn/posts/c350c4dd.html</id>
    <published>2026-01-10T13:03:50.000Z</published>
    <updated>2026-01-10T13:03:50.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="学习路线"><a href="#学习路线" class="headerlink" title="学习路线"></a>学习路线</h2><p>基于 ESP32 + SI4732 + ATS MINI 的生态开发收音机，开发者并不需要从 51 单片机、寄存器操作等传统嵌入式底层知识开始学习，而可以利用现有的开源项目成果，以 “应用集成和修改” 为核心来切入，这会大大降低门槛。</p><table><thead><tr><th>个人的兴趣 / 目标</th><th>推荐入口</th><th>需要优先掌握的核心技能</th><th>学习难度</th><th>可能成果</th></tr></thead><tbody><tr><td>只想使用 / 小改现有功能（如改界面、调参数）</td><td>作为高级用户</td><td><br> - 1. Arduino IDE 基础使用 <br> - 2. 代码编译与烧录 <br> - 3. 配置文件修改</td><td>⭐⭐ 入门</td><td>成功编译并烧录官方固件，修改频率范围、显示语言等。</td></tr><tr><td>想深度定制功能（如增加新模块、改交互逻辑）</td><td>作为项目开发者</td><td><br> - 1. Arduino 框架下 C++ 基础 <br> - 2. ESP32 基础外设使用（I2C， 旋钮编码器，显示屏）<br> - 3. 阅读理解开源代码的能力</td><td>⭐⭐⭐ 中等</td><td>为收音机增加蓝牙音频输出、修改菜单结构、支持新的显示屏。</td></tr><tr><td>想彻底重写 / 从底层理解（如研究算法、追求极致性能）</td><td>作为系统开发者</td><td><br> - 1. 扎实的 C/C++ 语言和数据结构 <br> - 2. ESP-IDF 框架体系 <br> - 3. 嵌入式操作系统基础 <br> - 4. 射频电路基础知识</td><td>⭐⭐⭐⭐⭐ 困难</td><td>独立架构收音机软件，优化 DSP 音频处理算法，或移植到其他平台。</td></tr></tbody></table><span id="more"></span><div class="admonition note"><p class="admonition-title">学习路线选择</p><ul><li>对于绝大多数爱好者来说，前两个入口（"高级用户" 和 "项目开发者"）是最实际、最有成就感的起点。</li></ul></div><h2 id="必备知识"><a href="#必备知识" class="headerlink" title="必备知识"></a>必备知识</h2><p>基于 ESP32 + SI4732 + ATS MINI 的生态开发收音机，开发者不需要学习 “底层嵌入式全家桶”（寄存器、启动流程、裸机 RTOS 移植等），会 Arduino API 就能开发，但理解 I2C、GPIO、库结构、编译烧录流程是必须的。</p><ul><li><p>必须掌握的知识（重点）</p><ul><li>ESP32 基础使用<ul><li>会刷固件（USB / 串口）</li><li>会选开发板（ESP32 / ESP32-S3）</li><li>会看 GPIO 引脚定义</li></ul></li><li> Arduino 开发方式<ul><li>开发工具：Arduino IDE / PlatformIO-IDE</li><li> 开发框架：Arduino Core for ESP32</li><li> 使用 <code>setup()</code> / <code>loop()</code></li><li>编译 → 烧录 → 串口调试</li><li>修改现有 <code>.ino</code> / <code>.cpp</code></li></ul></li><li>I2C 基本概念<ul><li> SDA / SCL</li><li>SI4732 是 I2C 设备</li><li>会填正确的引脚即可（不需要写 I2C 驱动）</li></ul></li><li>读文档与改配置<ul><li>屏幕分辨率</li><li>按键 / 编码器引脚</li><li> FM / AM / SW 频段参数</li></ul></li><li>到这里，就已经能跑 ATS MINI 固件了</li></ul></li><li><p>不需要掌握的知识（忽略）</p><ul><li>裸机开发</li><li>寄存器级 GPIO / I2C</li><li>FreeRTOS 调度原理</li><li>链接脚本 / 启动代码</li><li>芯片 DataSheet 逐页啃</li></ul></li><li><p>只有在下面情况之一，才建议系统学习嵌入式</p><ul><li>自己写 SI4732 驱动</li><li>自己做低功耗管理</li><li>不用 Arduino IDE，改用 ESP-IDF</li><li> 做商业级产品 / 深度定制</li></ul></li></ul><div class="admonition note"><p class="admonition-title">ESP32 与 SI4732 的关系</p><ul><li>ESP32 是主控芯片（MCU），SI4732 是收音机芯片（射频 + 解调）。</li><li>ESP32：负责程序逻辑、按键处理、屏幕显示、存储、UI 界面、控制流程等。</li><li>SI4732：负责真正的收音工作（AM / FM / SW / SSB 的射频接收与解调等）。</li><li>两者之间，通过 I²C 通信，ESP32 向 SI4732 发送控制命令（调频、切台、模式切换等），SI4732 返回状态和信号质量数据。</li></ul></div><h2 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h2><ul><li>ESP32 开发的工具链：<ul><li>IDE：Arduino IDE</li><li>SDK：Arduino Core for ESP32</li><li> 硬件：ESP32 开发板 + SI4732 芯片 + 屏幕</li></ul></li></ul><hr><ul><li> Arduino 官方提供的 Arduino Core for ESP32 支持以下芯片：</li></ul><table><thead><tr><th>芯片</th><th> Arduino Core for ESP32 的支持</th><th>说明</th></tr></thead><tbody><tr><td> ESP32</td><td>✅ 完全支持</td><td>最成熟、资料最多</td></tr><tr><td> ESP32-S2</td><td>✅</td><td>USB 原生</td></tr><tr><td> ESP32-S3</td><td>✅ 完全支持</td><td>新一代，常用于 ATS MINI 项目</td></tr><tr><td> ESP32-C3</td><td>✅</td><td>RISC-V</td></tr></tbody></table><ul><li>Arduino Core for ESP32 对比 ESP-IDF（乐鑫官方原生 SDK）</li></ul><table><thead><tr><th>对比</th><th> Arduino Core for ESP32</th><th>ESP-IDF</th></tr></thead><tbody><tr><td> 上手速度</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐</td></tr><tr><td>ATS MINI 项目适配</td><td>⭐⭐⭐⭐⭐</td><td>❌</td></tr><tr><td>驱动成熟度</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>学习成本</td><td>低</td><td>高</td></tr></tbody></table><div class="admonition note"><p class="admonition-title">ESP-IDF 是什么</p><ul><li>ESP-IDF（Espressif IoT Development Framework）是乐鑫官方提供的 ESP32 系列底层开发框架（SDK）。</li><li>ESP-IDF 的特点是官方原生（乐鑫自己用），直接使用 FreeRTOS，多任务控制更细，代码更复杂。</li><li>ESP-IDF 适用于专业、灵活、产品级开发，比如做商业级产品 / 深度定制才会使用 ESP-IDF。</li></ul></div><div class="admonition note"><p class="admonition-title">Arduino IDE + ESP32 是怎么连起来的？</p><ul><li>使用 Arduino IDE + ESP32 进行开发时，使用的硬件仍然是 ESP32 开发板，并不是 Arduino 开发板。Arduino IDE 只是一个开发工具（IDE），用于编写、编译和烧录程序，ESP32 才是真正运行程序的硬件平台。</li><li>Arduino IDE 中使用的 Arduino Core for ESP32 相当于一层 "适配层 / 抽象层"，它基于 ESP-IDF 实现，将 ESP32 的底层能力封装为 Arduino 风格的 API，使开发者可以通过熟悉的 Arduino API 来开发 ESP32，而无需直接接触 ESP-IDF。</li><li>在 Arduino IDE 里，选的是 ESP32 开发板，写的是 Arduino API 风格代码，跑的是 ESP32 开发板（芯片）。</li><li>Arduino IDE 完全支持 ESP32 开发板，而且 ATS MINI、SI4732 项目本身就是基于 Arduino 体系开发的。</li></ul></div><h2 id="入门步骤"><a href="#入门步骤" class="headerlink" title="入门步骤"></a>入门步骤</h2><ul><li><p>第一步：搭建环境，跑通示例</p><ul><li>行动：安装 Arduino IDE，添加 ESP32 开发板支持；导入 ATS MINI 的源代码，尝试在电脑上成功编译。</li><li>学习点：嵌入式开发的第一步就是工具链的搭建，这能让你熟悉最基本的流程。</li></ul></li><li><p>第二步：购买开发板，点亮第一盏灯</p><ul><li>行动：购买一块 ESP32 开发板（如 ESP32-DevKitC），不接 SI4732，先学习如何控制一个 LED 闪烁，读取一个按键。</li><li>学习点：这是嵌入式开发的 <code>Hello World</code>，让你建立 “编写代码 -&gt; 硬件响应” 的直接概念，消除对硬件的陌生感。</li></ul></li><li><p>第三步：连接核心部件，理解通信</p><ul><li>行动：将 SI4732 模块通过 I2C 接口连接到 ESP32。不要急于用 ATS MINI 完整代码，而是先寻找 SI4732 的 Arduino 基础库，尝试写几行代码，让 ESP32 命令 SI4732 收一个已知的 FM 电台，并读出信号强度。</li><li>学习点：这是项目的核心，你将学会如何通过查阅芯片数据手册和使用现有驱动库来控制关键器件。I2C 通信是嵌入式开发中最常见的技能之一。</li></ul></li><li><p>第四步：深入研究 ATS MINI 源码</p><ul><li>行动：在完成了前三步的基础上，再打开 ATS MINI 的工程。此时你看代码的心态会完全不同。你能找到初始化 SI4732 的代码、扫描频段的函数、处理编码器输入的逻辑。</li><li>学习点：从 “读天书” 变成 “看图说话”。你能结合自己的实践经验，理解开源项目的架构设计，并知道在哪里修改以实现自己的功能。</li></ul></li></ul><div class="admonition note"><p class="admonition-title">学习顺序推荐</p><ul><li>对于 ESP32 + SI4732 + ATS MINI 开源收音机项目，"在实践中遇到问题，然后针对性地学习理论" 是最高效的方法。推荐的学习顺序如下：</li><li>购买硬件 -&gt; 搭建开发环境 -&gt; 运行 Demo -&gt; 修改小功能 -&gt; 遇到具体问题（如：I2C 通信失败、显示乱码）-&gt; 搜索问题、学习相关知识 -&gt; 解决问题 -&gt; 继续迭代。</li></ul></div><h2 id="学习资源"><a href="#学习资源" class="headerlink" title="学习资源"></a>学习资源</h2><ul><li>ESP32：乐鑫官方文档是最好资源，重点关注 Arduino Core for ESP32 部分。</li><li>SI4732：Silicon Labs 官方的 AN383（应用笔记） 和 SI4732 数据手册是必读文档，它详细说明了所有命令。</li><li>ATS MINI 项目：仔细阅读其 GitHub 仓库的 README 和 Wiki（如果有），这里面通常包含了硬件连接图、编译说明和常见问题。</li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/posts/32f038a6.html">开源收音机开发技术调研</a></li><li><a href="https://esp32-si4732.github.io/ats-mini/hardware.html">esp32-si4732/ats-mini 官方文档</a></li></ul>]]></content>
    
    
    <summary type="html">基于 ESP32、SI4732 开发收音机。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="嵌入式开发" scheme="https://www.techgrow.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>开源收音机开发技术调研</title>
    <link href="https://www.techgrow.cn/posts/32f038a6.html"/>
    <id>https://www.techgrow.cn/posts/32f038a6.html</id>
    <published>2026-01-09T13:03:50.000Z</published>
    <updated>2026-01-09T13:03:50.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="技术路线"><a href="#技术路线" class="headerlink" title="技术路线"></a>技术路线</h2><p>为了开发一款 DIY 收音机，非嵌入式开发者通常可以选择以下任意一种技术路线。</p><h3 id="三种技术路线"><a href="#三种技术路线" class="headerlink" title="三种技术路线"></a>三种技术路线</h3><ul><li>技术路线一：模块化开发（推荐起点）<ul><li>怎么做：购买调频（FM）收音机模块（如 RDA5807、TEA5767）或网络收音机模块，用 Arduino 或 ESP32 这类开发板通过 I2C 等简单接口控制，甚至可以用树莓派做网络收音机。</li><li>学习重点：基础的电子连接、Arduino IDE 的使用、调用现成的库函数。</li><li>优点：最快出成果，能建立信心，适合所有初学者。</li></ul></li></ul><span id="more"></span><ul><li><p>技术路线二：基于开源项目二次开发（平衡学习与成果）</p><ul><li>怎么做：在 GitHub 等平台找到开源收音机项目（例如，搜索 “Open Source Radio ESP32”）。下载其原理图、PCB 文件、源代码进行研究、焊接、烧录，并尝试修改功能（如增加显示屏、改变 UI）。</li><li>学习重点：阅读和理解他人代码、学习使用 PCB 设计软件（如 KiCad）、掌握基本的焊接和调试技能。</li><li>优点：在真实项目中高效学习，是成为合格开发者的最佳途径。</li></ul></li><li><p>技术路线三：从零开始（不推荐新手直接尝试）</p><ul><li>涉及内容：从芯片 DataSheet 开始设计收音机高频头、中放电路；为微控制器编写底层寄存器代码驱动音频解码芯片；实现复杂的用户界面和网络协议栈。</li><li>挑战：需要深厚的模拟 / 射频电路知识和嵌入式系统开发能力，极易因调试困难导致项目流产。</li></ul></li></ul><h3 id="技术路线推荐"><a href="#技术路线推荐" class="headerlink" title="技术路线推荐"></a>技术路线推荐</h3><p>对于绝大多数个人开发者，强烈推荐从路线一或者路线二开始。<strong>尤其是路线二，它让你在一个高起点上站上巨人的肩膀，既能做出有成就感的作品，又能通过实践快速学习嵌入式开发中最实用的部分。</strong>一个有效的行动思路是：</p><ul><li>(1) 明确核心：先想清楚最想要的是 “一台自己做的收音机”，还是 “深入掌握嵌入式开发全过程”。如果是前者，绝对不要从零开始。</li><li>(2) 寻找项目：立刻去 GitHub 等平台，用 “open source radio pcb” 或 “arduino radio” 等关键词搜索，找一个 star 数多、文档齐全的项目。</li><li>(3) 动手复刻：按照项目指导，购买现成的 PCB（或打样）、采购元件、焊接、烧录程序，这个过程本身就是最好的学习。</li><li>(4) 迭代升级：成功运行后，再尝试修改代码、增加功能，逐步深入到嵌入式开发中。</li></ul><h3 id="技术路线总结"><a href="#技术路线总结" class="headerlink" title="技术路线总结"></a>技术路线总结</h3><table><thead><tr><th>项目目标</th><th>推荐技术路线</th><th>嵌入式开发需求</th><th>难度评估</th><th>核心工作重心</th></tr></thead><tbody><tr><td>快速实现一个能用的收音机，验证想法</td><td>使用现成模块 / 开发板（如 ESP32 + 网络模块）</td><td>极低。主要进行拼接和配置，类似搭积木。</td><td>★☆☆☆☆（入门）</td><td>软件配置、外壳设计、系统集成</td></tr><tr><td>制作一个功能完整、有学习价值的收音机</td><td>基于成熟开源项目进行修改和优化</td><td>中等。需要理解、修改现有代码和硬件。</td><td>★★☆☆☆（进阶）</td><td>代码阅读与调试、电路调试、功能增删</td></tr><tr><td>从晶体管 / 芯片级打造，追求极致控制或学术研究</td><td>从零设计电路并编写底层驱动</td><td>极高。需精通电路设计、MCU 架构、通信协议、嵌入式 C 语言。</td><td>★★★★★（专业）</td><td>芯片选型、原理图 / PCB 设计、固件开发</td></tr></tbody></table><h2 id="开源固件项目"><a href="#开源固件项目" class="headerlink" title="开源固件项目"></a>开源固件项目</h2><div class="admonition note"><p class="admonition-title">提示</p><p>在 GitHub、Gitee 等代码托管平台，使用 <code>SI4732</code>、<code>ESP32</code>、<code>mini radio</code> 等关键词组合搜索，能找到更多优秀的开源收音机项目。</p></div><h3 id="ATS-MINI"><a href="#ATS-MINI" class="headerlink" title="ATS MINI"></a>ATS MINI</h3><h4 id="项目简单介绍"><a href="#项目简单介绍" class="headerlink" title="项目简单介绍"></a>项目简单介绍</h4><ul><li>在开源硬件与软件领域，ATS MINI 项目无疑是一个值得关注的技术瑰宝。它是一款专为 SI4732（ESP32-S3）Mini/Pocket Receiver 设计的固件，旨在为用户提供一个高度集成、易于使用的收音机解决方案。</li><li>ATS MINI 项目的核心是一个功能齐全的收音机固件，它基于多个开源项目的经验与智慧，例如 Volos Projects、PU2CLR、Ralph Xavier、Goshante 以及 G8PTN 等。这些开源项目的合并，使得 ATS MINI 在性能与稳定性上有着坚实的基础。</li></ul><h4 id="项目主要特点"><a href="#项目主要特点" class="headerlink" title="项目主要特点"></a>项目主要特点</h4><ul><li>高度集成<ul><li> ATS MINI 项目将 ESP32-S3 与 SI4732 模块的优势高度集成，用户无需担心复杂的硬件兼容性问题，只需关注于功能的实现和优化。</li></ul></li><li>灵活配置<ul><li>项目支持多种硬件配置，用户可以根据自己的需求自由搭配，实现个性化定制。</li></ul></li><li>开源共享<ul><li> ATS MINI 项目遵循开源协议，用户可以自由使用、修改和分发，促进了技术的交流与共享。</li></ul></li><li>完善文档<ul><li>项目提供了详细的硬件、软件以及刷机文档，即使是新手也能快速上手，降低了使用门槛。</li></ul></li></ul><h4 id="项目技术分析"><a href="#项目技术分析" class="headerlink" title="项目技术分析"></a>项目技术分析</h4><p>ATS MINI 项目采用 ESP32-S3 芯片作为主控制器，结合 SI4732 收音机模块，实现了 AM/FM/DAB+ 等多种广播信号的接收。其技术亮点如下：</p><ul><li>高性能处理器：ESP32-S3 提供了强大的处理能力和丰富的接口，使得收音机在处理信号和用户交互时游刃有余。</li><li>模块化设计：项目支持多种硬件配置，用户可以根据自己的需求选择不同的硬件模块，实现个性化定制。</li><li>丰富接口支持：ESP32-S3 的丰富接口使得 ATS MINI 能够支持屏幕显示、按键输入等多种交互方式。</li></ul><h4 id="项目应用场景"><a href="#项目应用场景" class="headerlink" title="项目应用场景"></a>项目应用场景</h4><p>ATS MINI 项目不仅适用于个人爱好者进行 DIY 创作，也适用于教育、科普等领域。以下是几个典型的应用场景：</p><ul><li>个人娱乐：用户可以自定义收音机的外观和功能，打造独一无二的个人娱乐设备。</li><li>教育工具：ATS MINI 可以作为一个教学工具，帮助学生了解无线信号传输原理，提高实践操作能力。</li><li>科普展示：在科技馆、展览馆等场所，ATS MINI 可以作为展示无线信号传输技术的展品，供参观者体验。</li></ul><h4 id="项目开源地址"><a href="#项目开源地址" class="headerlink" title="项目开源地址"></a>项目开源地址</h4><h5 id="上游开源项目"><a href="#上游开源项目" class="headerlink" title="上游开源项目"></a>上游开源项目</h5><table><thead><tr><th>名称</th><th> GitHub 地址</th><th>描述</th></tr></thead><tbody><tr><td> Volos Projects — TEmbedFMRadio</td><td><a href="https://github.com/VolosR/TEmbedFMRadio">https://github.com/VolosR/TEmbedFMRadio</a></td><td> 一个围绕 SI473x 系列芯片的嵌入式 FM 收音机示例项目，主要实现基础 FM 收音功能，是多个 ATS MINI 及相关固件项目参考和借鉴的早期实现之一。</td></tr><tr><td>PU2CLR SI4735 Library for Arduino</td><td><a href="https://github.com/pu2clr/SI4735">https://github.com/pu2clr/SI4735</a></td><td> 面向 SI473x 系列收音芯片的 Arduino 库与 Radio 实现，支持 AM、FM、SSB 等多种模式，接口设计清晰，是 SI473x 生态中被广泛引用的基础项目。</td></tr><tr><td>Goshante — ats20_ats_ex</td><td><a href="https://github.com/goshante/ats20_ats_ex">https://github.com/goshante/ats20_ats_ex</a></td><td> 面向 ATS-20 接收机的增强固件项目，基于 SI4732 / SI4735 芯片，对界面和功能进行了扩展，是 ATS MINI 系列固件的重要参考来源之一。</td></tr></tbody></table><h5 id="下游开源项目"><a href="#下游开源项目" class="headerlink" title="下游开源项目"></a>下游开源项目</h5><h6 id="调频收音机"><a href="#调频收音机" class="headerlink" title="调频收音机"></a>调频收音机</h6><ul><li><p>ATS MINI 原始版本 / 老版本固件：<a href="https://github.com/G8PTN/ATS_MINI">G8PTN/ATS_MINI</a></p><ul><li><code>G8PTN/ATS_MINI</code> 是最早的原始 ATS MINI 固件来源之一。</li><li>这是由作者 <em>Dave (G8PTN)</em> 发布的最初或早期的 ATS MINI 固件代码，围绕 ESP32-S3 + SI4732 搭建的固件实现。</li><li>该项目包含基础的功能、菜单扩展、电池电压显示等，可作为项目的一个核心实现版本。</li></ul></li><li><p>ATS MINI 社区 Fork 版本 / 发展版本固件：<a href="https://github.com/esp32-si4732/ats-mini">esp32-si4732/ats-mini</a></p><ul><li><code>esp32-si4732/ats-mini</code> 是一个 “ATS MINI Firmware Fork（ATS MINI 固件派生版）”，基于包括 <code>G8PTN/ATS_MINI</code> 在内的多个开源项目来开发，例如 Volos Projects、PU2CLR、Ralph Xavier、Goshante 以及 G8PTN 自己的 ATS_MINI。</li><li>该项目围绕 ESP32-S3 + SI4732 搭建的固件实现，它集成了更多贡献者的改善、修复、特性扩展和新功能，代码更活跃、更新频率更高，并且形成了自己的版本体系（比如 <code>v2.x</code> 系列）。</li></ul></li><li><p>SI4732 Radio 固件：<a href="https://github.com/joaquimorg/si4732-radio">si4732-radio</a></p><ul><li><code>si4732-radio</code> 是基于 <code>G8PTN/ATS_MINI</code> 的派生实现，偏向个人维护 / 定制化方向，围绕 <strong>ESP32-S2</strong> + SI4732 搭建的固件实现。</li><li>该项目由 <em>Joaquim Org</em> 维护，定位更偏向 通用型 / 工程示例型固件，适合作为 SI4732 芯片的软件参考实现与二次开发基础。</li></ul></li><li><p>SI4732 / SI4735 Radio 固件：<a href="https://github.com/ralphxavier/SI4735">ralphxavier/SI4735</a></p><ul><li><code>ralphxavier/SI4735</code> 是一个面向 SI4732 / SI4735 收音芯片的开源固件与示例项目，旨在为基于这些芯片的收音机开发提供功能实现和驱动参考。</li><li>该项目支持在 ESP32 系列 MCU（包括 ESP32-S2 / ESP32-S3）平台上运行，通过 I²C 接口控制 SI4732 / SI4735，实现 FM、AM、SW 等广播波段的接收与基础操作，并提供基本界面交互示例代码。</li><li>项目主要特点包括对硬件模块的收音控制逻辑封装、示例功能展示及对常见显示与按键硬件的支持，是一个适合作为 SI473x 系列芯片学习、移植与二次开发的基础实现仓库。</li></ul></li><li><p>SI4732 Mini Radio 资源汇总：<a href="https://github.com/Ramsin/SI4732-Mini-Radio-Files">Ramsin/SI4732-Mini-Radio-Files</a></p><ul><li>SI4732 Mini Radio 的固件代码、硬件设计（PCB）、资源链接等汇总。</li></ul></li></ul><h6 id="网络收音机"><a href="#网络收音机" class="headerlink" title="网络收音机"></a>网络收音机</h6><ul><li><p>yoRadio / 网络收音机固件: <a href="https://github.com/e2002/yoradio">e2002/yoradio</a></p><ul><li><code>e2002/yoradio</code> 是一个基于 ESP32 + I2S DAC 或 VS1053 音频解码模块的网络互联网电台播放器固件项目，用于构建可播放网络电台流（Web Radio）的硬件网络收音机，不支持 FM 调频，完全依赖 Wi-Fi 网络流媒体。</li><li>该项目定位为高度可定制的通用网络收音机固件，支持多种 ESP32 开发板和音频输出方案，适合 DIY 桌面网络收音机、智能音箱类设备。</li><li>功能特点包括：<ul><li>支持 MP3、AAC 等常见网络电台流格式，通过 I2S DAC（如 PCM5102、ES9023 等）或 VS1053 输出音频。</li><li>支持多种显示屏（如 SSD1306、ST7735、ILI9341 等），可显示电台名称、播放状态、时间、菜单等信息。</li><li>内置 Web 管理界面，可通过浏览器配置 Wi-Fi、电台列表、系统参数，无需反复刷固件。</li><li>支持多种人机交互方式：按键、旋转编码器、触摸屏等，硬件适配性强。</li><li>支持 OTA 在线升级、电台列表导入导出、配置持久化存储。</li><li>可选支持 MQTT / Home Assistant 集成，适合智能家居场景使用。</li></ul></li></ul></li><li><p>ESP32 Radio Evo3 / 网络收音机固件: <a href="https://github.com/dzikakuna/ESP32_radio_evo3">dzikakuna/ESP32_radio_evo3</a></p><ul><li><code>dzikakuna/ESP32_radio_evo3</code> 是一个基于 ESP32-S3 + PCM5102A 音频 DAC 的网络互联网电台播放器项目，称为 Evo (Evolution 3)，主要用于构建可播放全球在线电台流的硬件网络无线电设备，不支持 FM 调频，完全依赖 Wi-Fi 网络流媒体。</li><li>该项目支持 MP3、AAC、VORBIS 和 FLAC（最高 1.5 Mbit/s）等多种流媒体格式，并集成了 OLED 显示、旋转编码器、IR 遥控等交互控制功能，让用户能方便进行调台、调音量等操作。</li><li>功能特点包括：<ul><li>从预设的 “电台库” 文件获取网络电台流（支持 SD 卡或 GitHub 下载）。</li><li>使用 I2S 与 PCM5102A DAC 解码音频并输出。</li><li>内置完整的 Web 服务器，可通过浏览器在电脑或手机上控制电台、音量等设置。</li><li>OLED 显示屏显示当前电台信息、音量、VU 表等。</li><li>使用单旋转编码器实现菜单操作（包括音量、台号、库切换等），同时支持 NEC 标准红外遥控器操作。</li><li>支持 OTA（网页直接更新）、SD 卡浏览、3 种显示模式、睡眠定时器、3 点均衡器 等扩展功能。</li></ul></li></ul></li></ul><h6 id="二合一收音机"><a href="#二合一收音机" class="headerlink" title="二合一收音机"></a>二合一收音机</h6><h2 id="开源硬件资源"><a href="#开源硬件资源" class="headerlink" title="开源硬件资源"></a>开源硬件资源</h2><h3 id="硬件博客资源"><a href="#硬件博客资源" class="headerlink" title="硬件博客资源"></a>硬件博客资源</h3><table><thead><tr><th>资源名称</th><th>资源链接</th><th>描述</th></tr></thead><tbody><tr><td> esp32-si4732/ats-mini Documentation</td><td><a href="https://esp32-si4732.github.io/ats-mini/hardware.html">https://esp32-si4732.github.io/ats-mini/hardware.html</a></td><td><code>esp32-si4732/ats-mini</code> 项目的官方文档，包含硬件架构说明、引脚定义、参考设计等相关硬件资料。</td></tr><tr><td>Xtronic - ESP32 S3 SI4732 Pocket Multiband Receiver</td><td><a href="https://xtronic.org/circuit/rf/radio-receiver/esp32-s3-si4732-pocket-multiband-receiver/">https://xtronic.org/circuit/rf/radio-receiver/esp32-s3-si4732-pocket-multiband-receiver/</a></td><td>Xtronic 网站上的硬件资料，提供 ESP32 S3 SI4732 口袋多波段接收机相关电路说明、部件清单和设计参考。</td></tr><tr><td>PCBWay - ESP32 S3 SI4732 Pocket Multiband Radio Receiver</td><td><a href="https://www.pcbway.com/project/shareproject/ESP32_S3_SI4732_pocket_multiband_radio_receiver_40ce2b94.html">https://www.pcbway.com/project/shareproject/ESP32_S3_SI4732_pocket_multiband_radio_receiver_40ce2b94.html</a></td><td>PCBWay 社区分享的 ESP32 S3 SI4732 项目，展示 PCB 设计截图、硬件资料及项目概览，可用于了解板子外观与布局。</td></tr><tr><td>MakerWorld - SI4732 Project</td><td><a href="https://makerworld.com/en/models/785921">https://makerworld.com/en/models/785921</a></td><td>MakerWorld 上的 SI4732 项目模型，包含 3D 打印视图及硬件模型，有助于从机械和装配角度理解项目结构。</td></tr></tbody></table><h3 id="立创硬件资源"><a href="#立创硬件资源" class="headerlink" title="立创硬件资源"></a>立创硬件资源</h3><h4 id="调频收音机-1"><a href="#调频收音机-1" class="headerlink" title="调频收音机"></a>调频收音机</h4><table><thead><tr><th>资源名称</th><th>资源链接</th><th>描述</th><th>开源固件源码</th></tr></thead><tbody><tr><td> OSHWHub - ESP32S3 + SI4732 多波段收音机 </td><td><a href="https://oshwhub.com/sunnygold/esp32s3-si4732-shou-yin-ji">sunnygold/esp32s3-si4732-shou-yin-ji</a></td><td> 基于 ESP32-S3 + SI4732，提供完整的原理图、PCB、BOM 等可下载资源，适合 DIY 和二次开发。</td><td><a href="https://github.com/ralphxavier/SI4735">ralphxavier/SI4735</a></td></tr></tbody></table><h4 id="网络收音机-1"><a href="#网络收音机-1" class="headerlink" title="网络收音机"></a>网络收音机</h4><table><thead><tr><th>资源名称</th><th>资源链接</th><th>描述</th><th>开源固件源码</th></tr></thead><tbody><tr><td> OSHWHub - ESP32 + Wi-Fi 网络收音机 </td><td><a href="https://oshwhub.com/twist_66/i-can-listen-to-the-undamaged-in">twist_66/i-can-listen-to-the-undamaged-in</a></td><td> 基于 ESP32 + PCM5102 的网络播放器开源项目，支持蓝牙、流媒体网络电台，可播放 FLAC / AAC / MP3 等格式，通过 Web 页面进行控制，兼具实用性与可玩性。</td><td><a href="https://github.com/dzikakuna/ESP32_radio_evo3">dzikakuna/ESP32_radio_evo3</a></td></tr><tr><td>OSHWHub - ESP32 + I2S DAC 网络收音机 </td><td><a href="https://oshwhub.com/hudiesudie/chao-ji-jian-dan-ban-wang-luo-shou-yin-ji-2025-nian-1-yue-6-ri-ban">hodiesudie / 超级简单版网络收音机</a></td><td>极简 ESP32 网络收音机开源硬件项目，侧重 “低成本 + 易制作”，通常搭配 I2S DAC 使用，用于播放网络电台流，不包含 FM/AM 硬件。</td><td><a href="https://github.com/e2002/yoradio">yoradio</a></td></tr></tbody></table><h4 id="二合一收音机-1"><a href="#二合一收音机-1" class="headerlink" title="二合一收音机"></a>二合一收音机</h4><table><thead><tr><th>资源名称</th><th>资源链接</th><th>描述</th><th>开源固件源码</th></tr></thead><tbody><tr><td> OSHWHub - ESP32S3 + SI4732 多波段 + 网络二合一收音机 </td><td><a href="https://oshwhub.com/abcdef2022/full-band-network-2-in--1-radio">abcdef2022/full-band-network-2-in–1-radio</a></td><td> 在传统 SI4732 多波段收音机基础上，增加了 <strong>WiFi 网络功能</strong>，提供原理图、PCB、BOM 等完整硬件资料。其 v2.2 版本增加了网络收音机解码和 PCM5102 音频部分等改进。</td><td></td></tr><tr><td>OSHWHub - ESP32S3 + SI4732 多波段 + 网络二合一收音机 </td><td><a href="https://oshwhub.com/yaobaling/yoradio-radio">yaobaling/yoradio-radio</a></td><td> 基于 ESP32-S3 和 SI4732 的开源多波段 / 网络收音机项目，在 SI4732 多波段收音机设计上增加了耳放和网络收音机相关元件，并适配了网络收音机功能（固件需另行配合）。</td><td></td></tr></tbody></table>]]></content>
    
    
    <summary type="html">开源收音机开发技术调研。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="嵌入式开发" scheme="https://www.techgrow.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>近期开发计划</title>
    <link href="https://www.techgrow.cn/posts/29d74d90.html"/>
    <id>https://www.techgrow.cn/posts/29d74d90.html</id>
    <published>2026-01-03T14:38:21.000Z</published>
    <updated>2026-01-03T14:38:21.000Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="密码错误, 请重新输入." data-whm="文章校验失败, 但不影响阅读解密后的内容.">  <script id="hbeData" type="hbeData" data-hmacdigest="7aa038b85c2e84e72ab1db689533070f5573ed05f84a9c955d7977b52ef5538e">4cfab7e4fdfd9afdbf1592f9652b8a199f3cb3b67d053a3a2b7db0e886465ca1ea14179f28e40c4d184809b24d2f909058d2606747febc969140bf1db3dbbb1a7542b98eb3d7de26ec2c86ec81828b35913853b3ca6e01e7cdd106a9462dcbff705b54ac505b28447538b61c3ec4400b8cb84079a99816fe59006d71ef364f10dfb4d6a52fa3c40030b50b35a957f58eeaf772e9c44236a3270411d3be05e2b07d30b9bac2b2c9e4a597c4d551dde647547269d613d69e329a68cd1dcba6a2bae36bd33e5e11d2a5d518899e43e6fbe0524ed9b9c309564739dcbd3851cc07f0cf45fdf977054a7183a76498682d3bcbe26762ba095458de76d41fd18e8353e46f9e6a744945ca97a1e13148bb3f9423d01ee13a6b4834facb1fd3008b9bc0b23815838f409203118497fae9e3c50332fdcc1f781ba863ad73325b42ab0feec4ff52515efcaae7b170dbd491c656284a85ddf836e73fcccf8e7ca5401292a93019511a0678c1a62a4f9d524748fa16eb444d4ba9b9709af6db0fd8358faffb7d0c03a8d01f503646851d8ec0c9f69f5e075714dcc796357f1870f6f0dca899a9bf358860381dd91e102a52d8499852d404fc84364601414cf25f113952a4e8453feca610d559c44f8a12d16bfedd77987b81e1710f415778084a5bb8f4a5c6c2aab5f7ed9a2212789eea24995e31f21d19c0f60d3d46829a35a209ad5a64ba59a81bdcf152f450d14cd1a48d46a1387566700476d2ecdb9d8353d4cbf63e855586f66ae706f65dc4955184b9a65a836128c63f24689bd56daa7a178ce5757d9d01dc84779600b82e6a988dab8b740a61c2ff52336f31143d91d739de64ac0009731caf857040616087028acc4ed0f44ce7db4b06867cc89fbeb38e768587f29842144388051bf5a9d0e6d50c34f302fd7baa802f9f9c168ffda3ebac1d2050694c4497144e66768873510e3bd1c5331c36ce81277ca2b8b28a332997ea2d4a0c033d8734e7f53a93fd0f7d93befa1e2ca440424342540b5fb5dafb58bae19439fc52008da933c4ab1c09c9397180751c729410b4e33e4b42e2ca464123092a013681dc189d3c6c9b568c9ef209dd27aa322816b62fdb4d438cbb725b076f448c90ab1226f482c58e4a439618e5544ad0b035ece5da84f39b6e91cc187ad85bba9ad3d1df678e401029e9de1b70b3eb57536d7dd879dfcf8c3e624a38256e95a99af821624daa673e38bd8747b26b94d5348d0d27779b447f343f87368acb8e537276216e2e5e6561d9ccaa46b82bd680bce7c4f4cc56d82439b2024cb26c26c284a9d8f5ce24f35b52d335f8b62fc257b04b8ebac321d9785d90e835961fd2c723e3eaf1e9e9f98ee5db63e0521cac3e574a777bb5f1b4b5dbb8ba3d93a4168cc2dfcc0b9a0ae2442d97a01fa711ef780b41f63d893f1f9f6e88ed61953c66ef74636546b60e5f3b74dedf7826adb9aafc045a4d081cc0c6c46f7d99ecc3ac20e73e1b55a401ba5aa22f530c892de0f7961c766fc616288952010e6da9b886d01b932b09af54700ca58efd3d810c1b6e44c3d4f523cd5bf484023309b809e90de2882acb79c237a7a53594e54cd663039a5c74d97b93a5552c24ef2db394e2bca465056620ea92087125ac05f012fe376adaad2684a73e829406a0537e0c721990efafb49abc2d2573fef95878899b9b3bc40aebb4dacfc78dedfe5f8c510f95fc8cb0c645e44a85a438a33e3abfae5b91404b5baaa760e533120ae51b8a1b45d03db0bb7de2afeb3c68cd14915e3aa2cf82d4006b4925c5c061e4e67f779c38b0398feb23efe1b4af81aa7c13957cee35779cb2baf9aaef4eb0074ba9856601131144f3f32b06b8576ebaeaea8467d965e248dec371e7458f974280e534d7df0ed9d0bd01e8d8c8699fab51b573418372ce4c097c62b155d034eab1fb62d37e66d1931e99c8c49eff493573b2f81085af4de971a5b6c8798d3aae32abc0058307e4eacb6d4c20a714d8025ef4a7aabca790b41248b65894b64b5844136de8c10b9a51a96137dfdae9fbe2f8aa1bec41ec1b99bd9e00772946d236131f0e967f002172ee52b95cd5294ee434c95d941684c440616e2d692c46ce12482b1b975fea4822b6c403a9044b94723fb0f8c340d69c4280cdc99c7ad5bfd27a961d195ab077396fb346fbf60a83f70fd162efc7dea1b6b0aab01b08a18fd862d646ecee92cc5b4aac63b70a87e5374a6b2ad3732c63be3b7fec49225659ec8661145100469d3368056e1c5059b25129d28f297492af8aa0d955560b368ccaf6da9b4b74726223aa8713bc5d07c0683dec0ecdd18c1eb5758f6c9fb7f2b378bb0f0e9158c5b1f4e9965ea2dd715b871e0a4b17b315e9212509bb190ba53daefac614109d530a6c6a72a403193252f64cde247028c19e65b05e2aa5a628efed935c6ac9c288ea9b946bebbcfea053b7abf03b3fa9a8bbf594f4a364281fdddbb6404a71011f4bf85ad4eb9042d591b74b9b827ef705ea7fb47c6503cba8dd79b7d1a33e8e3a15bb2d8e2ca8a36eb685c94b2d78215b71c98b2c0b3ee37e035bafbbafef30c0983a823efe8e7117435dd296db8e79ee3f3a744a2538dfde388f61d0a6d130734c4e44be686c870afdb7a1caa863bdabc994fce857ebf7972073d4426ebf9405f5926b4a25db7461119b55bd9ec4bd07931eb8c7d447e022e17a2df094a1dc00dcacd1fc064f1054276eed105893619605c48d576be245c5b87c97301e98a03f4e7214bb91ccb684917f6d96f2fd3488a7eca5ee845751e15fa94e41092d5e55efeab282dcdd9e11323b8a3946b92b5a59100d1e1491e329296363557a9af9262b745f52b40442e617efb7736c99a740dca8af13ebddcd673eb4a534f72e933973592758b63bbd367081a651b5caa7693a2a0ebda3c9f381a2ba519351c4c1d716001a1848bdd3202c31f564397ceb9eb3e4e70041673ccfaa66bd757cea040a014a4391e18d126faec37a2ee964edd824d8c5d2de2c3ead3f410961b608cad747e2bc14687da33756d6de87cc5f8dff3547d7e63964d64eafa0cb410cf2690800747f38b1a7921cfb554ea6bfff93e4207b42461762ef62c1bdc669e1899d1bb55eacf78d9996258735b9a96d7632068e0736297bda947ac28572609247d9f0e1c88c57b891ae7e97a36b4332161e91c23a37f07780aeb2ec85d85e9dea91110a395c7b162dcc3995b684d10eb28d8c55aa78a45b50171f6b23658357003d77d76c00f69027d812e5a3278649dd7f510af0d5eb652abdc305daab3743709e2dad44b171ddc5be2200541a923d4fb3b83785916e92604a89b9f17fc4b838abb234a80e9e037601fc00ef31188b3464ae33435dfb10ab5c973090ac8f01badd332ec2e904e605cc4a3385be2648132d8718fa5f8fb37c035144517ab367a704133c9644c5d1dbcccd2cb46bf88d63527a0116c8282a3f90f9c7a142d29ee89e95e272aa7063df9143ab4ddba067e8d9fb8e84e1ed83e648e0d238ee55f89ac10c0d62477d0f7c123e9c3d88d6e94e2fdc0b162ac345a16e8a23a0c02f82b4e358a37eca0f550842074acdf047d912df3390745af62d280f49d6292bf2f2c09c0a824f6b8636cdb1638a362e2652aea98475ad25b689189ed0940d539736cadb54cb971531dd44ccb04dc1ae651569af62e117d836931dca4e0895fd6eeb4bca0d59f7c421dbe4e44b4cb69b59b67356219451c11b8d7dd5bf20e013a1ffe68d983d1f722c28bc1d7919d49391c48bf1f7ad94a8950d4cc574db236fdd0bae29273aff2931b6b0b981d3e8b6031c1e6f29a5bc4bbd788723eb8b77b8d311fbf17f848d7709988df6130a9050950aa3dea53fee05c55ce0129f5d50dc4bb80243fd3dcfd8ea416b51808c78d2b2a48f9295cb0c48d850f4d9ea9754f770976bcf621dd13892580768a72d15472a8cdc8025628dfbd380c5be7a4f9d36dc72cc0a5a5281119618c23394885a86b6e0fcf909f2fe8964b41f729a21ea6fe351b1057ae08b3e1afc0d3ef3e0a8e6ea83f57ffcf9269dcd4e5a25369630477074a1243e17561171b83664124025196b8581d3daacf8bf6630fe9bc0026fd074b6661e53781f90a757827dff0df9294f141d088c3209794d875c4fac17e0d476f8a760608d272c6e44cb59ba224e27b2df0bf92d167b9c88d7654110876536879bd3fff1db399662dbbe65bb8451b63dfefba2c0cc6511f4d0bd7135cd679d571b0f8278fb6d16883a843ac8feef8a31fe0484d3a70ae7e9e264437e831c618323b273e75b07cc94a4942b7abee0e629f1d1afefdff8bb8d1e92b9d256a9b8270f7ed297bae1cecd45422e80e518f7911d1a0fd78b969d016e08d330fa115d88ce949c137b9cd4f094428fdb499bf6d554a1bf5a89e15cff3a2ca75b42c4e45593dec554558a68993f38206a93ed9f0108dda3554077915127d450d5517f8b63e2274614f61f8df13b408a990f9946d710005fe09a1ee277c47cf38c4358e2d3263791a611a975c8d10ab60b427b5cb5a8551f564bbf59daab665729f836398fdf8dead68c8a2f6c540a3b70fbac21f8730386c18d0e24212731760555c6f60b23e4bc8054b59868ff124046064aedb65b38d9c17f7cffe6efe3c9dbc79f95771675913101fecb789a17a362750f9d1d3f23d0293554e9a5364a3f57a57f33924e50a836a1e448f0074c7c1d99a42bfb695109fdd3462da7fe1c0f953a6028ba576dc5f0bd3b8b69782888a326b4c23d48d9ef531ff8a74cc96f7ae17b926bb76e45598d4f4d186485d107985561cbc8f7eb5361ea6ea16dabfa2506c0bf67b316d547735432ae0c1d549c359d932b3866f0d366c9be1ded3c3073af4943144b8f8f5a81a51e9ed6a812b6bfe57c9288a01360741989c98f4a5f814da3bf74171fa964c630020d35fe88e46467f9dd37c0bcdd9c54ad21b5630df463bcffdc9b5086439fbd90cd5feebd21fbf19858a0bff3f959cf4c8aa546625becce865924cbca2047d82cb3451a2054d8896936256978eab4d6708896210809c05f87d4a1f6412ea838e6a22ae451e61c7a0cc0d15dca8897b8c04e8a8f9555ab8c26f8bea71412c44ee056cb8b1be5878d616b5127f581ea01974ac9e8765f48a3d2291685f878cfe2c710c1224aea84f4544b60fe75e20747ea69d919e5438ee2c46f09a3a829a33f8d344cdba5a0d4f8a6be912ee9dc2073e64ca73bb6cf8a18df311c05c8e1cadff10de8bf4284b4195c05729b5daa7d27077042fc43e20b4aa2271d673cfab972be594036dd6add32bfdd752066ba423c74fc701ef431b52eef8908df6526566f8f3e9662dc00f198ee669d37676b2a538b5ca4299f8e982cae65320161f6e43f38a25115091bed6696b1f2d7b2cc5e0199ffb5dbee9b63f219e1ba13da38735677e30cf4336cd8656efac6dc6afade3773ba1104d218d8f3b2020835336c8d7ac1e15e5ce10dc5811c6f52efb493fc5e4dc69304e44b4f753d9e3beebe013acea970a32144a91e53f2049b786e68389162b6337a4bb41e367c15389458e1af8ac7da2d943bd7ca97c8a3b17ce7538b095f15a86b6159e68709b65e196b5d4b44cf844bc6176a4b8ef844464f3987a211a93e24dfa199f8f34b19166e626cce8c8e9dbc116b0fcb62c4cd88b4aeeb0119b76eb38f624cd9b8a1cbfd5f43214f8f45034aeed30c89435f44bdfcd2adf832089f9716f02808677410dad61df3da105822adec82a11bf601a2b14289cbf8fce26b01ae6802dfcd8d0592027b83135734e4147bb453814b708231a72d6d12a58022dc7c8e546fc90f95b6102c5d650925e9613efa2b7a520ffb62bc526cd6602b6d30178ca55c18efc7b058c64d9c3f199390f507c18429710b5d52cf5a3fc655e2eb7688c11df912756dd978dd262293f702824caf505fcd6e551ceef8282d230e2768a63457ecadd3a7f8597dbf914ac64e1235003b652037d5045dddec13e1e31192cbd3072744e593e4f2bb49b136ada88e09ba5576ce8d38e8335f8f4f3290dd837de1c8a51e3acb0877936f072366c1151dc70033c28816f6e9a6efc83ad3425f88f78d62ce100cb5a7723f24374567f5653c46cb8323c69571aa468834974110f5dc82a30875bab673be32e95062212ff48ee5e856672f35710fcefcf29362ea983f223d5ca3d01789ff6450a19f93a521acdb652884c351c56f222a03e9c49e45319c9942d717c30101654f3c51031bb677fbcd5a7a4355c62ccb933b7ce3c3611dfb12234b152bcc270d5d3a57f6ce3bfa9a910f4ff1e2799dcfea466e5ac7f9365acc05ac0c2e9edfa3d0b8f084b5f402dbc4f9941cdbcb8ccf46e1776333e5a2bb6df995be4e3b87f10ec5bb4f04c538d087fc9f5dbc9eb683f1971e53016afc07fa5ecaf402068e19b7b2029215d7b8b2defef4a172968a7ab3dee2cb19cef50825e0e915ebd73dbec0cce52d1944e6aad535af0957f425d5ffe17d51638f241ff13bebb8f1358333bd5f1c9e9d9a560611cfd917e14b53e2a5d653980f581504b29f6ead070e64230ab9dae5410176b8744855e1c165235bffa9b3460dc763afca48d7c41aa892fcf38cf6b3d20ba7a20dad3f604894d404d83e74dcae2f9144843c1a81d6767f55ad82c55b3556d81301baf67529a1de8b33b18822c2a7338059682b196ec7eba1a3baf268ec6869f127437ee6d6ef4a5c518b40523e0674e0ffbf1917dc5ca27deeef6d41a6600b1193e0f0ebe6bd93d6b1bea9943617596c4726e9d9f3aa7cbab91c5a9fc9049c84b4bb9ea41d0c6f1cdfe3f72b1a03ce13344e61e9f8b1f19311d9a7d907243ed69be76f6331fa81f69b09b7533748d7b2a05c9f1474e014c06436d960c68263dd2b6901762151d628e06526bf38fda01d348277169cca7394c10737bce7df1a1336c517683f6a905f0e2bf5e0f9bdea98c4ac933345fc74560d88c4ecd2f4776ffec9b599d5b4366a2dcbf9e8ba78fd2f258141e900ad8f3de5c491595fedb26fd1a4fd4bc88de3735c0390ab8bfd211cde9f6575fdfe227bd67e9cff80241f75cd27bf2d549bb4c03d5887becd8345bee94b40770f09678175698a3d67bf8afaeecdcd2ac2a7e1a9db5fdc81b837cf76b964fdca7c35e527a41f3c32be359c4ad173df3085b78986b28100865a760d7ff0fb80d93eb470a57e9a1667d64c576da0898479cc8f54b74b2c895289fc321081df00400a686d75a116754ea4cb29ce5b6e5917cd7db174df72c4f7cc5ceca1e0b53cdce9779ad6bd511c31e7d5bb73b8f0cb2452b0f4f7d1bf73740aa1806ab113df1c82901d59db26690a388a3d17be3bc88c880cb92501e2dfdd76d1681c7e30a34e4469f06b67670cf626da64b3bdc160c82d4f4427217e1bb3baac2f6004ffe4f8da116e1336eb2c217957defd6c15af86195715fc5ac9132d339e754d00b308fd090316c37439e743277ab213484e5a68f4fcb2c65be736e26fda553648ba748f1a17b21001bfd5f02d54d395b50dc4ed81302c5cedd11bd5d9b75ba57cfe4a358a0ad3ca5e1952d402c72a7647322c931a2d913bb537d331ac1858694a7611d07e5154783d6d1c18efe984d82294203c3278d59f5d6a524e295ecc6120714350b9989be5c49fce14982491cd3ad781bedbd82e</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-surge">      <input class="hbe hbe-input-field hbe-input-field-surge" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-surge" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-surge" data-content="请 输 入 阅 读 密 码.">请 输 入 阅 读 密 码.</span>      </label>      <svg class="hbe hbe-graphic hbe-graphic-surge" width="300%" height="100%" viewBox="0 0 1200 60" preserveAspectRatio="none">        <path d="M1200,9c0,0-305.005,0-401.001,0C733,9,675.327,4.969,598,4.969C514.994,4.969,449.336,9,400.333,9C299.666,9,0,9,0,9v43c0,0,299.666,0,400.333,0c49.002,0,114.66,3.484,197.667,3.484c77.327,0,135-3.484,200.999-3.484C894.995,52,1200,52,1200,52V9z"></path>      </svg>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">本文主要记录近期的近期开发计划。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="加密博客" scheme="https://www.techgrow.cn/tags/%E5%8A%A0%E5%AF%86%E5%8D%9A%E5%AE%A2/"/>
    
  </entry>
  
  <entry>
    <title>近期学习计划</title>
    <link href="https://www.techgrow.cn/posts/860c6c02.html"/>
    <id>https://www.techgrow.cn/posts/860c6c02.html</id>
    <published>2026-01-02T14:38:21.000Z</published>
    <updated>2026-01-02T14:38:21.000Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="密码错误, 请重新输入." data-whm="文章校验失败, 但不影响阅读解密后的内容.">  <script id="hbeData" type="hbeData" data-hmacdigest="c8f00ac8144051377ba307fe38694385a15024411505406615450724ca4bd16a">4cfab7e4fdfd9afdbf1592f9652b8a199f3cb3b67d053a3a2b7db0e886465ca1ea14179f28e40c4d184809b24d2f909084075e7e2b3392f33fc9e77db5e7d5c8c75c9f5edb502c44c97d2936037834eaab784e9ab6bd875cbc56679f666329099d4ab89202683abbb75c823d1c8424df846096a5e220e8d947e3237c2079fccffd2ebb407025d74a55454dc8b9b915621dc936a1785358ea4563fc1d860f7b27f4f2efc4497cc955ee3c3c79ef2468b8f5f705ef1c1b0146ff27d98aadb9f9b269a7fea8268b36b7a8eafdcec14f94cf3bdaa5d35f236a25c23f0a8ec54a67b6768c47cc3a69f53ca3630b03cdda249c1862a61de00df4546ed43a441e16c36e2c05a9e0f8cd4e144c55b719c7a697bac7e90957c47087d36dc98afc80b75e748562c6d0e6b4e47ca91575f1758619420cd697b1e042ace49b1e60bd9e1269bf493a3cd90be137138bfa5ff2ba9dbf9c7e88905743edd9faf92b2d8a5726b56afeb5a5eb15f7734a4c4af977045589863cf1dea44b3aae59cc9c1fd517822a14c8e47e7cadd5aa24669b2d02d01078fcbeda47f35d2da2a353ca716ac21de1f4691e5136d7463bec82cb5b58a72e8dfe86153d1893c17369835af18bd66501e6d4559dee343028c34f87525b36beddfd38053a4a2833546621a8845c1132d4808ad89133bed187f171916da2091b4973511701c16b37d026e1c67f27cb0e8046579ccdee9b44f014f413d952254ffc173125f214dde79cbb04f0556811bf311086ed694d656a234bcb8f983ac41836ba4aa20363eba544121d736809b5fe9d80a1c17c99b0d1651b885321a4ae62a288af89150c66fe9dd6f9ddccd7a5fd78ae15f994e65743948fe8b46a5230d20258a412d3ecc33800abeffa33f4b3408f197e06adb2eee6c10b227ff486e3f94818c8069957f8d50128216cede6220ae004687cfe759c7ae4a7e52c5f3a372374a34c7141e248b768167747c31ca73251a4077ab7f7da1bdaa81b30dbfa534b700543a34f32454debaacd801d2359437bcb8269dba80e630ed619e5ea3c57b6b1c896bf5fce44a78b6e42bde3bbde6932dc73af25db63da5ad65ad6eafe1b6f00331f135da50e8e0fc2987275e05d41a86b007cf771746971c5cf4308b9e743949a89b54449b96a7db81e014363c4b29167d86a31373163e92919c114e6d3d0ff60c248f78dc7aa400545d28aad057f381f1a104c9d4becf6c42e8074baddc87c0711efd811bedc215fef0ffe19e9f767d582cae3089d68a0f539301341b62b70ae509b56f7bc46f6d6b7de2d4131b51ac5a280bc7807db539bb8ba4a3f33d9935df2ed6c44a8844eb9d33cb882c97ee5a84315691c86b8dfc111bb1902f9b89b1c3f369af52feece8baa7d4b7ed8cc8e664fa33e7bd646dd1ee1f1fce68c4d9771e9917953e4efeb5278dd11328d6db2f2a58682c6ac8cca3a8e13f9d1bd907a5916d03ca29d243a4f75855c7d5fc8e05dd97b2021e4c0ef4d26646edb0d493588bc22e915b3cadad638336b1f947f4f4d77f2a63858570d82276af131c7a81c1b11ce162dd214da3d73f4a75f2d76a730fa360f3659e2092caa6e1207a1e134d77b28e3b5ad3c09f48de0ed3ff31d28348b42e74b81a76fec467e5d8e792c660407f5c89a16ba5fe34bbb1e22d15aa15df6d7cdeb7fe6bb888d636da0dfba1457d687917f45cf271b863e8009046c7d6f1a6e91478d9bf08bf4036631086a19b52d750828ba3ddc049dc1d90507624f7e1f27d247617fa052bd2c6ed14cdfd025c431f8a384733142d57f2175de452b541b7164f909ee751212434d8c807b2cb23b59bae401422103c8212e9b6b7e4a13862e084306ec88c8f9e361ac1166d73d7fcf545cf98982e47fb3dfc6d04a47b3eaaaf3d48146a93609583380a0fef09b4043f73e8c6b6ffd2717785b7b2d7bfe497019bdc80d1c2efad129c6c00ccc80e9e915effacddd182f033324e01bf08631d07aadb692f5eb1cf99b13e0bbd3fa9fe170ab3a1a07f7c2cc7a22300c6dfb7ce27499a9e0dadb21351ab7fbd802b11b113d7b3618c4bbf2a484fed5c4c341ea07716fcbd2246535ab9077f28b018081d2452f79b8f2930f50d2e85f572a81546099e30575e1a357f22596e8db39f8e3a802f8cc32b4d1e81130d984e5b6f74f4d8e47a98b522dcb4d6e9a1713177e1b10c8d794649564e0829ad73cf2c890b4aff2d3f4a95f3f57ed16257261ed14b8fe8698d32a3fda73df5a20c2ba3695eac5eeae2b1497d13622b05353d5a63e20668f3632253524c81f6e9e5b376529630fe169774ed4f6098383010b01688d0123636c4bc4df0892d0c9978bf892c5d07950d1080c405e8f9edf6facc0cb0720bb0b4ea69b7490fae6d809c2c4b647724ca5cca621890d654e0054d2d9482ff380cb9ce02564d9c4cb83367bedb156a34c65ae7dba1b3a7030c98d51098b4a90b93b941d9f6d941a7fc9a56e96c8281f70ffc59230bab686b0ff94dd052d96672f83e6e1f20240394a89de78a563e055d3c1e2230789270edbc6e4871b30e038eb6e2d70c9756739f9abeafb4cf4763dd401afaab6e8b7d28eaa946938ef2224fba7ebee3c0955e0bfc177b64826dbda44291765079976b352dd800f2ec099d9d96a41ba984363b8ccc03cd7c1e67618c1f916759cadb1795b396e81a03591dd0cb88d06bbd935bf96819a0b3f899677ee20545543b2b62705b187a710e3f1e558403ee7e8885c3d60a31d53ccef36f7b80ef2d98beb0cf864f40f64b6b35af29e8842b350cc55520ec31b0f98382ff7d5e1396b0f835096e6da4f44e293f860d9a61fcd874c3515d0e9c970d9c2849f8feea51b6c5ad300165bc2904f84961d01d0d56fc2d9c725c43b17f94d9f1bb882b9a9bb4d5fd94c25423e9490f36a607bae8c1545f85e3fed4f4f437be53dc368d55cb4785cfc504c5ee0c78b4aec8fb67d84c9dcddaaa6f706eba6fc22377879827930942c2e5d49c6c35a2c1036e081dbf4bd6df0a2d4dfe492a434e85278f945ff84cf170519d33a5d4913823a7610a1a06d4c08e6fb56e634f6dff45835193185de8c44d78e4e9941c93ced089324fd70bd38b13864f83482c0e01ea36f55f90f1b2f752cac8178ca5439505630710a3bdda6b37c5e2227c73e53da2bb1b827f018bdfd6ce135a6e71b377580536e84e861caff81872d9541f3d864263c4c2d94aab9246a7b15a762116966ffb7f656ec1bc0af79d17a8c5459ded5bb7be392abba048e005748614da06452ad7c17ab4a0d7a71fa7f92b45ad1c06d637bbf674c4c7134fae11086c52b88429f728f20df23c76402e1ec3ef8ffd4edb6da08d19aa9af550e2600f633766a146e0ab5e1aa7ae5c5d6f2d22ba68f8e156b69218faaa613305b79fe226afb2b76c1d8f9ffc513379d588d11ca2183fdd232e69514029dd16e81fd94d852c333ac4c4491f83eb39731014679fae09d06a990c9fef72e9d3a2ca4bec17e2d9c186c3f41e8b8efbdbbb96f3b3f515bd70599c10b2dc7ed621342cb852f2d8c63d614d68d963dc3ae9afb0f233c7db2557f2c231c6f94fd7f482ecdda94a58d965b76e10c16b7b00efbc92768b5fe59fdb8a7f763e7947352d955ee12b71bbccf5d01728e73dca3baca3d5093d8aafb686bcce50933f5398f2342d010645014ed95de5371fb611e472746a9fa5346aaf497b2f2768dc628e433edd2769db9ea95882b6da8448d5cd87c2efed530d92511309352986d39a289c8d8db256516f6c37731d432c6702dc7dc577ad7d9386509f3fc885fe3d6327ac962b59d204376db69924cef74a7a9973d4f752667066ed47d53f35fa65c4bcbd025452a4adc8349679ef411646fed07c4dca3ea3399c61fe0f16c3deb7c13b8ef6bad453b4fb495bb3d79f59d70497773eecdb6eefe158f4634b7dc9e0e1ddc07aeae3f7d49ad0e96483c9319a8062eb7a4299b0835cd3fdd5875aa10ab484ff3a5a86c0e25f1e434e1fcd1f0a421aefba4c2ac26a5377c8fb069492b72533638cfa473c5bfd2111a6c277e07248d77e647f23b7c250853c6ea7c1af148a756184cf07acd171cb2b97778a0f8a8dbcf6c0442775a238ad39782eddd9079b224bd533411f3308fce227a2f837a598feea0cbd90867cc94b6245ffc5b21e2029c18bfe2561e0352706c717750e649e14da517742a5a3656b0bff2a7df3453042580b703f6c6cdf45743ef0e34eecc7a4204898eb6cb8c1a9a71aa60ba433f0438d4a977198186f69d854ee762c0d498d1cc6000ec3b0e441eafc89ec9f70af36dc73bb6d9030cbd97f915bcd11e268bc04ab69068ee9039a79ea2f114eebb195d04c77e729f24274db59c6ad3cba01d95aa14330a1e4f4f88ba13aa6b5463a6164fc4cffa1d526286cfba1b1b421f56ce42ca90fff2f2cfb70dfa11ffde21712103d74e19a2809b0f8b75da921d19ea91e6f71152f7b04d406bbbcecea68c51c1d46b045f78f84b0f464d41341bab2d456ed189585e4d5816572b7105f55bef91a2fe1b29bc8966703007b5a542b785146dae149d2ade3340989919359a5e89a496a42a9451cc82a343a9d6e8b545c6efb68618a7515fc7484b07b69825457883fbac5b59f962cb010643260d1043432e7dd71094a26a8f59d79cbec99a99ba805d81fe3386edf76acfc45fea7ea66dff9808d58c92b7cadb9b577f33472faa31457cb085d0245587a11c59d562678f30be18b2863d2f49a62274d520f57a3abf17f8dc60053f8e29671cd0b650f360b08d670d9f44c563f118412a2fd7cecf7fdf87e5a4e75ae4ab54ce62a251305148d73b2417a7b5431523c0169e9b4662d7e84aad66a904e5cec46ff61b1f70a31f87dd0f2a3e613cd4a0b167275bff2bc1117e7ee17e2e87070b9cbb9fd4255f6219513597566d15065d8bebf9f59d7d1ffb0e513f57c703bbe80c54b66a4859343cdb9b411fc2e67375b893cde020168a4ba4fca30c2cbba96bda18a27f91df60c25336a1ecd707668d1dd840b63ff62aff5f6be2d817bd62f155a07ef084d52c571ada2bd6f5114855dacf11b26de64b09f3121919bf017a3f045d09f52c5c05bd5098647f9efeb2b13bdd6cff7457460d9e8fc832de10c3c0b9dc8e9f039dde61c300567200fcd797a660e08518c64c08ff97e2339969b02d8d015053d8b087c5aaa06e4f60478d20418106802e29903205d07527daca79010151e3ea989748f9d00301904edeb40f816b0c9d8cce77c5e3d14c0df244697ed942f68f4c1fb52d9610419b5c980ad72caaa4cb6a0a0523ff61b619a2594267719ec686c67261cfad3d56f2abbdd50e97e1f6fd028e066eab86b1ce99ad3b28fdb05284e6fae46ce8f145391b0f6aeba63cd60e1b53df6ccd8cf2a9428d7845f45bc44ec9d1e6f20838775cdff18d4d4369919390dfb20f9c0de9c7a2cfd30992f7173943c43c6a16e41a93820481584362574165e8589b3e38425585eb124d186e9ae401ab9c1740568d362abf892696064c76e3f91faf49dfae8bb8a8260e0c33b18da0bd4e78fe7fdfc3f167e4968a94d878bcb9904758c8b6f2ecdcbe122ca21394bc5f2dbd185d3dcc3f16b2b7397ad4dfc40594dbe4040dd14f83ddd9f48dee8bb20a123b07ceeff8d93f24b5ee6a5cd0a3762d2f324a7a459a9a044f95d25e8b3027973082da46cd8cbce165e1eabef80464ece16e9b846ed51430d643d8c8cd34db43e4e925f2a4c560d5290316dcf8e42ff8325ebecc87e4e8bb8b553a4a81b16c7e31efa5514d3493c8919c633055699e567d6e36c032d278742f2af37fe198796a9b72d3da605e7d95773935aa8fb984f66026cf82356619d7bda944560684bcb29b82214cc75f0d2ed2e072ad212004e20ba8b9b92632edcfd9d54bc19f65c805fa05aeac1c77bd00281a30e86898d7ba53d58be2c4d353245f5c3bd096f330cb7cfef320fbe271c587b8401f0af8b28aa9a08892d265d814ed27f196b114da6efc60be8cfcadff4f960bc109421245382d46eb4c082eed695478dd93f4a32c72f912950ea48785787872bbc51c8dc2dfd592300183637260d94683e4340c2831bb8ca202dd8718283551258985bbfbf0edc4500eab33d833cbbe937cdcab566805b1906e1162a47b9258f4bd17bc3c62098d4ffdad7d0c0e2fb069a850ee08af5376cf2ac6319ee141049d650ea3ba0bb4e4bfc5adba802130556919a3c1d94112307750fbbb8776166f625aa8731fd9f56dfbb9d611e06106b944c0052db30496ad5526521688be65bfb889abb861cb0769b1eaab2a50e6073b2504ec5e9e497249c3027d2861f71cd2c13e47dd5bf1cab33a3f4a5a4e708c6fdbb7e9fb48fb96ddcebe084375edfbc45c5b8f1bb1dfd965e0a099d347c5aeaf76f8a47c86df1be7f3d7e2ab276b68912cb45067a423fffb720cfbe1908c6d2b0f4cb8c68c093eb8cedc3b852d535e90b63cd3b26e12de71fcaf30dfc0e7ddb9bdb4dcd2defaaf5a66150e2e6e6555289238beb8e899f8a319e239f051d470fe2cc881d5aa763c9d4acc235d3a1fb5463cf21a190aaee42c78fd4290c70aa7c86b57513d6b80d3dd66cbb8312a17632037e482bc90983c691639674a76935ca8db1023bd29018490281be92aeb3f69a7ac4d0b0c3733b27022a41b4022949e398d6f3904454bed1c6febe19278665b5ab25aa1bf3782b30c1a9b7c3027b490dbf0b661c9d8c6be9f8c5904ce44de817653ab99cc7eec09c941eece83bf9bc21a0cfc6fa929ac7979f3840c6805d30fc3cbb8f49bc4977992dc044aec8640354b2a3e2aa9899aa338f4dd9670f1aac84988a2e55df90c40cc3ddefb8e78da73b94d57342851e232e1bc38b912ed4dac037fd9fa2317ffe5bfd8344e3877e93d3b0fc86c3e69e87facd4f787fd508d656f414dd10d2ac2e803f95f0373abcd84793b379a2e530f3e851828dbc7ba5d77f847c1fa493999137e81ffc9f03311773b8c9d8bdded185f0b98d727fe339f68a80becdd48738100c41f53da3ff5f35f346d33902409896b288eb2c148885de93ff037017f068f7683dae067d882ca1935a6ea77486a848dbebae30000d765511d5bd4ac5ef0ec0438ce09bc8a48ab1eb5e3f9e9c4e504f8c99d1720e38fd5a62a93ab75c76ce760eef31c2adc7bc0025c6766847f51275ccd6d315f8377c93c69b0b1c31b0eee40da41b0ae782a61ca91eb9a305c6ee5117195cb99e7f3648ae49bd584f8156e798681f559aa28f58348724839d56d8b5167ef9424ba08555687e04fad27fa6abc29a8b77f7e0143e9634bbd7fa22a1213614e71e9323e79aa6c59538b866a05f07898d05eaba0f41de2812b9a2d71a5d0f36d6bc0c72d73b8c81cbc6b108dbc3c777bb801a7a98c20cfa930a39f90cce56830056790860e04c9f79418411acabbcaf8c630146841bc322b913b591cd73e380b3d7a3fe1c9d7e3c5b76356a56541ffae0982b0bdf4ccd06eae3af86fb30a4382acc456ec202c2f6546c0c818cde7b519691e4e74469864b7b2894ff7c46ab9850c9a52f690a84a7231ae191310cb41052704a519ff9358846d4f3387042fcbeef9b4b6253eb987b718b6d84b1c871e9dd9d317c4275a6d79619aa6d4626910dc75b49e34b2e2499755544476d79cc5129fbe6015676ff2c7ca0a924079987baa140055361a807541f26a5163231e80f687d55f90f9308831650f6ea646bfa45b93a4b454f69976930300f7d7a675195bfcefa3da9b4163dfb58bc00ee5162924547215e6c9b60a0b1d7d457ddf0d2a31706fdd4c4952d8df34242477045dac7d13d24cb0f0475de1afe6b4f709d68996e1694ed6a57b99bd8097a04eb7d4050bbec524486b040e1d7b6f3c7f57c2b79c2ae6c89f31a75dbc7d028cd10c3a60b19edd5e9a6afd6d34799001a1d477573467c7acf9589c09c0769e3f63373e3d4f6cfff7a46b25709b4effd5f0f6da2de9b8d8926c99fc5548ca035cccefac170eb542f7c42a6e0f99fc0a139b16be64e6485c2632ad17d5300952309fec2c43c8e098be2ae3ad544e8531e8b24c98afc57b19f1aa2b3af647e4bd35a5d88f8a42595c5a640d116ea14d8ea60dcc986775085adccc01b2ad520e516342e0a22b24d45fd367691d87bf8d8166193f5d16424a18829526c184a27a169e5b1b347da391b47d055a5f40a9d9d012f718278cbf60c59173fa60c9ba868d15aabdbdb15a26f71ab52df240a920943235db5209346c55916884722c3069ab2e085790de378bcb970246e0ab8f5058fdcd792dbd4ed25dccfcb84f345ff3000e24cc5369ea2c74a2b46e029e3ba172e27892f8cd858d9953347f8481ab8e6d714343e26b126aa3182aa7436c8d02fd4026cb19915731a4015ae85607e50173981aee3a86dddffbfea02521f35f8948dfe3569420ed17469f8288ac1cf824ff208248d18df6729c098061a5ac9a5b8970b879cf533abdfa57e0bf36e1aacfee7a18959a1c09477dc28b120bc2544698031e63818c038628858dbf66c35ef1146f03f6f146fbf3994347eb070f2dd481ae889c7b5a17335ff5760b56dfc5a46fef3dcd3cfe72cd677f93548a4e7f454b120de3c01e8dae8379b0901f30c3561cdf122a9ddb2ab7f53b0ec4d8de5b75b05c7ae34d1f013eb64afb9a1339143abbb8d449b862c42e1c040f99a370927a2ad564765d790cd4b271e3e7ed63b87b8438d7de8896e7d5cab351953311fa82e5b5651784a3eabbd2b9acc0704aed12796715cfa0b8b48a0f64cf3b78292ba025d0b9306fb0cd7856d4d0907c1b89aecb976540c4ae849190ba61c0a66f196c616348a37a30f5e2e2c7ea6623401daab7701f5d17ccf854eae1c1947041bea46e06312d75061c3382e84ded73348e8683b23f8ea5f69e1e8e767775b077e0f6b477c05f766fe85938dbe96d50ce0a543f59d1ecea4f82d81a4c6143dc5634c3558f184c225ff1ef0b6f5642438515a1f83eb65cc33a6aa16e3ef67584f2e23970575a09e8b97eaa92bf0f2567841839a86738ec5df90e5985678e768ffc2cf68407210a580ae5eb69d925af254fa7818b140083f21a127c8dd01b43c85ce11c4582001cd1b6b6a648e5d416d593ac4599cf57a9ae85fb409054715572e4d65c01d40b199642ed7e57cf99e1f0c67a33a7b681b87349b158b1ebb63fd54a71e35f02f2124d96edbe72279410037e5af67717a522f9ad0ee6f9612c017463b494e3f2ce00b63ba960c3b9e339fd512a3f1de4e4ae678fdb42092178a1e88a09b0ed8d3958922a1f8ab0a7e69339d0e9d0aee0858a9b796661a5ca3a3766857209f41648428041bb01e3c81051cb4bf20edf787ad658e137a143250972a377081585adcb7f4860ec28f05a1973f6f2878d53b4b60d7c70863f937f2a9044d9b717c9dd935e1672fd2c22972cc49f365c988b5f3df8c7a1acfe09dc79b7cb7180dfc45894414dc2e6850d597abaef8e3dfc541d6ee0248950501014024f840d37008b0efddcbd72d53013e654d6accf49a4b02551ce8ab52e65f44b665a6a3e621a3f1e4e1e212dbf9ed134c27769c35e3888c7249556ab425f164afd78937fad13ff93f20ae82a5d2b8da9174a610eddf0683d84ec3a816016f7781a5c50298eac9f8018ba06ef6cd9fa87e046405dc235e02424d2bd2a3e80672aeff775126c24c64afeac789297a62ae0bfe3cfbe78747d8d4f8e1f6ca21bdb4430d059cf11d3d94437a3969e03643c074846f9e1426d7f4ab4a4761d638838801b9cca8e0e9580a066cabdcdf1d9f99bfa74a893bd9fc9500a5da15174a6cd661bf926bc1d87b3cde4744c64457aed0685f86a0b9512f4cd1cf9504d97297b4492a87817979535bcce1a59126ce5943eb52dca082794d110844b6a8ce0c0c0169edcfc641a597b8d3506b8eb440ff6a3e1d2450b119a6db87c856304bb03bbd4cbc0174395429c95271d9fdd70448cfa607a982566417b7feec128e76948cd0543f4843cfff09e47c5281d193409180f465d19d92fda3e77918639ea166673d917680e9d15cdc6dae5f6ef3c824cc3ec631ac509369edba38497b075f6aa6da83b79d83c29aba7cbae1429f75385b15d035570bad7ba305c8be662a18236b2de5f26bff643dfa5fc5644f81192dcc0b8d2b4a85116de94a96cff9623906cd2b754f43264b46f2c5e76895478876c3586cd607fb09b0409cfbfb14ce25b0017eb516fa4b23f7929d41dfc2040095e29076f36d694a528346452911d10c3dbe2d8532ae8fe9ab98f62f9590eec28b96356cc6ab1ae9ba765188ba699ed4cfcc1a21980ec8c37c61ea7c961fb6d58fe7fa70cc14f23180ab8590e8c94fec43b128043064d407cd95481370622385291cc54485230eba4d22185eb548484a82c6d6496551936fd8446b2a3c23d04364fd36b743f5f43fc2a5a658ed327af36e5fa94ac69082cd572695dc5b02d00b12b6ce8c3e6e12a4bd5a005dbf68bc410fa9ecf84d204af54ae17fc5ce8d0db0cab3675706366d1d3b1c182901f07fb19dbd715322abe95a82a08b2111da057bf3a434e14123cbe6325be562861d1ead9d8e9b0bf140b0effe18f044218d31dccd72f9ef8f8d8587a60955885635942854b04e4106e69c6d04f57e7f7a4f8e86ff435c1f76e506e845decf2a488c4875f24d1c8781a9904533ac2c7892fa68ad32dce7bf8a93950b923a0de00594b1cb1c3c4cb01b1b1976cc15538c6fa60f05350738f4165068d7c13ef4404d6ff15fcc195b2e29fd2a9801345d0da4d037ace24bd639c90f8648334ac5364b30dbe58e341d6c695866c8c1ae57f9f4bb134cc330f5f717f2927a65c73a9ce764bd6a91dfea104805da8b5d65d44b44f728028889054eeaa035e81fe4b6ed92003aa08a61d57e783b360ff8885a1840beb19a7f984051fefd10c75ebc0da910267d1f7e197cfa93e1ee7587dbbba860b6fc9dce5f15e93a4ddebfc96ba785cc61f3e5fabc972654ffebd078b453d87aba5d6e2908d9ea107726a70cd04c6b59f601899b11c8714aeea998065dee17b0d194ed5dbf4c430258cb879e743f7bc8dddfc3ccd572281990ed68b9d225e3a12d92638c2e79efd909d4a2211d6897c36780e94266188de5eb243574ea0ebc7ffcc2621788aef7c30ea125f3dbc4d0d597ca1cac266b8e6d463f579fd7dbda5bbe6471f121ed08444620d479c0260a748c9811c61b48e74a404c3879be8e3a6fb99e1f253428f0d0eb67a0abb247dcfcdd906b4ef64f637851990be7c407e142ac3858c1d0fc874982f5d810deae418854df9ff16c1fa29023491acdc334f3fa775b48f86c1a7f2252acd4924e39b7db5fb97cdd829e0371e12d4b93c7d3a01a35dacf4b1a47a94f988ee42cb0d2e57e655d04aab5a7f8059668012caa4734e99124a7d0a6e24c7b2edea4766d4820c3d7413f2a4c7452af1bb663a11815819002e9ad6de9ed21bc02a147e8910bd91c03410ebb2206f8c42fdcd8f04286b58698d54183747d0bbd668c50f1865faa54f287ce24468e03e6414359af25b8ae3d7a9c518b641859511cf0748c454f7eefa0af222899948c4f16cc14ae930f08b40f19e9176c1745600b30a317bb6314705f60584b0e0258e1292d9291d4499dd92593bfa7802cb81415bbd67a4c33e73415018d3f7c19a64b0cf19e0ed0ccea4865e89bb1041af9b039d4edd5d67fdc489914ac619f7f91a8913d310c58287f16cc3ae20198b975085408f39448599cc9cea496fcc4a18f8b8d67cfc5669df03f82fc64f901223b92bc6b3ea687efe23b2e851ad7f454ee0ecfc419b4fd08a86c714369ca092b39ae1c81545f9cb583348a142201e7d8d223ab1fcefe8f8e2dc16ca998fcfa71e935a139a80b4ef6199f421b7f0d38b77696ae04bcd527b49cb4c11f7e344a9b9e4aa5b5f6581d8acbc38938d7b4f0846e789de18759aabe83c663a01956099c47dde52b2f876164e7f8284abcec8e64581add3861a329dab768255198b2400394a40df6f63c07d1db1fb2c1b0ae69f3f687219f73aef361386a5e583e8525052d8ef58485576006eb38267e43f13c339e37d49a000a851bbe5489407a01b0a8539e333d7cf48bab344e3c544922ef234c3ecc6744c62b3095276d907f96d6c0c3720b8a877b5f73581fd5f3906d5fbcebf00f3295160c5834ed48b602caa497883141a77bc358afafa2dec6e5c25d92ec89fb52e7433b976c624ca805871b6ad482d4fb62c0ba89a3607de08f7051d1397bb21b1421f4ea2a1c2c0f44419f0b365b43c29f3393ffa7e9967d9c4ae973766ba62aa3fc0e5500db4aea871fe8ad0ab8d801ae027fcf2071d751a84cfbc9a4c7bcb206cb71df3474648fbd3126995f9bc70fd8889f239ada3cadb92d4e16bb4a801908211f60d28700b49e3de012f63f4f85badb7770af4dc126575d395ba13b699953c38d9c33aa6237d23df42cbb2490cfc19e381a68cc1d63f22ffb092c2b7bcea2c897f94f270fa2779de75e0d9e169a8cead160c4b37e76cbfc5e1ae55ea0ae8f19d7630f07569c301e1e33d24a244d8729a0842aeb67eae9bd0521e7b970f38923edc9c215e8e17f3a948a4e94a27758bf175a993387a0d961e9e664cf0711de03b4e986d8b9f72f203163ab42a38e5dc572b17b6366da9ce824c8137bd3dea9644d5c97d02ed9624754e34c267c1c1801fb988ff1b24ad57a085f267d04c024bc38c1fcb144697e6001048ffbfee4cf21c11af71daaf859a230712a39f6aeba56e072d6a68ada249cdcc9ed47925c39095fb7b945e7b81482ef664be37e1710f534c588307a5a643bcc2537cc64fdfa83029509d94ad75c9075497782d7d2bf8f22ae8950c6b90c631b35ae617d04af0e7d03d5d6ec03b4b9564566d1519378d5d9e982169f156459098f9bc7bdb6383bfb92e94f0a2d159085114763429e38fcf203943b881b0dc22280f535107da0464e668f855938592d6c92caf83ebfc81ccd58729689c399ac3a9988c41eea25ecb81ed95e26998abe99cd23a02b8c60c11adc8cebb9dec6c6b208dcd36dc8943c0172538123fa9486b3f264d4c7900f10ea9cb3e75a92b165ddbdbf558615b31ae20d94c405177759bbcac043b687ab68cebcefdeb5f9e656ed109d5ae7dc6fc72fa7ecd4d328177b17b77d7152bf0cc2a3aba78420728d8c8e85462471c4757fec32a1fd5bcd27922ef3a6c43177633169a5866a09173fb592c9301b772eda2b65657bf966b9e56374bbae071d37f7427ac9743a9d24f23a63d872c12f1c6b0b5514e7fe7b54928b41b78dd2636173221cd931f9fb26978c664528cb1e5a68e895961c78312cbd394d12965789e471ad01d1da3ad21c0bbef2902b9ba96086f3cf70652b9993db25ef15b20fff90b2dd87665bcced620d501951d5a0ac4e27bb26259784bd6afc2b40102b64ede24eabea82d2adf931d500fcb581eccde05a7c0ecf29cf5e5c1f052d25c1a10a6d8059bd4894a19f51d2da05632a43b45883fbc6846e85c5ac6a983ba88c9b7bc96142f642ce70cec5bbec388adb3aa0df85bc611346c4c3bbd908132bbe8b9709dc3b25aa6ff9b6a28c46e7d00bac7e6d5751c2d2e819da1bb302df576ca32c2433a7443196c1ab81d8cf091051e448ca31d181ffe6b1f0b4d8d087109edcb68d96835a35beaba4f017963a7728666ae1fa676b85235ac65c113cfdb6c8097d5823452d6b786b992027ece31553186a17a1a89303e22d8b6ac1c995394cff2cf5a23662cf6036da1d5043189f66efdc9a3569e91f4f806c52aad17e037123a622a9bc5e3753bea0b12900d053f950fcb6ed2d0c6bdfb515d289e77b6b3284077f3b1fc8cdf1ef5ffe423c468612f8bef45af8782ad84c9d8389357e6dc78381c97f185a035d9f6fa6ee7ed832ea16efb0c8c0278ce61416980c9f867171528240208c671e22e664716d0224cb738897c7d91ca929d5165d4879ce5025a67b87502b1e7783dfa9392612c757d7fcb8027eee9fd74d0c24d37f4d509a58c0e15731a433c306ca7786d6fe613ee9ab1fa73382f616c6d2530225d6530373c050a5e34ce060dae83100ee1de76a5a10c6c8b9cbd241e7b1ed02cc0ff820b4be6be1bebce82b5fc429d977aa3d297fc5f69dcd62caf6dfe612fbf7dd1d2afec3f519552df9a6f475ff3a21fd7d49f20ce6f826d476fa56d922132aedc99ff0ea3bc75999019dccc74c6befdad121ef5b80c3609498aad2dded07d2c755cffde617403e1f418404126e1fb29a875a0cffff899bad3af0c1790de2a9bea8fb041462c6698840ec0a351a47f1ef09d7282d30f11dfa45155d439179619fc6914c03f8ec04553792b6d219de622912147504e7b45b957c5868838129a3af2301f27b95e812993ddcba68141677d652b1f21b179a51cbd66d98596d52e65a9788d8baf08d1e26503a1e85d9f4752d719b0d543556a1a1c8b431cc7cbd5983e5aa9b728dc685a45be754bd1fa2aa20225ef8f773264386bfce4f601807b94976055c5a0248f30ddfae4f903858bf06b797abf5e849b34e6d49a2486bc646759cb6b7786dde89588a95153ea7b90efceb0597740e72a8ce589645786d903259ef3a61ffa0eb363c330ef2b0384a906c84cb732854b6605775b10a2725c2f342c263dc9af26cd07cef3c8f09de64480a984f6faf780b8fbad17f8e2d9163a9e3cca6c0b41138bc0eca36e8ff6d0f20dc295ee065977cc2aedef603f64f03fef72ad37c7a4dc645b08d64f9b25f812333ce4252fd2ade441f6c7d0ccd0acc18909085b14455bbcc1a5ed9065fc5d5b1096b71dc03f73d1d7d7c6d4c13a5f6a9b145ce25d1574d597439d8b791b6da4d1bebf3949c8de1e0b864cca80b4e49388d57fd5279c09e496b9a9a34148b72fe18653ebec72cfce20f8a1c96e060f23165335b1ef8e1e952eb0b092ad2e54d0a094e40e7b805cfe9e039b3d1c79a1eccd243b05711cf435cc675b4decbea012744b5fa05b10f43491ad2fc929ec5f9edc688784fa72d152625b29cada2d8f5fffa94af4ff492787d4b5d7386de8dfabdeb02d788e1224b5111e5452d5ba1475d2e83e7e6a0e7e501db0cfe417a368bd0098f3a7dc2eb80bdfece6c0b35ccd8a96c17d4da33ca9b3cbc9dd7bda9fef32fce5af74186c863e0de11bfd8da6cd230aaf497528ea380addc41c82e56156f55ecd728a46b544e15d1cdf58059886805c563292939697cbafccdcf944e81f9e29f38781e5c09929291dc150e3b125b2c537bfbb5362c94a0b0a673382cc8406d9e3be373b4a2e945f0dd824a99da75b3e271a6af34eccbe0c24ef6354b602402b147493cadcc802b532bbecd0258d7e421920faf71113557a43fd19ef7a1ee2201702420610cc7c89b0a9b1784150986b31483c03f46613a57eb2e313c32272ae28dcfa360198e237f4329b9515580c67a4c277014cf79fed64842e4ab9b2685baecc8b68f4f7034bb0e6500e58f244c923386600ec158f21855288a02714561ef8c10a97d92f4ce2b69a5c74b0d8beed74a93aa2878b23f5fa5c271d3ec8137f06d92981974b486e2cb103cca3042de53c5eab4b8adef7669c0aeaefcd1eda848111a411ff79b9a26ecfff0e001a296cb108da8be3cade81456c22b27c5e35545ea13ee02358a29a0eac56b14c977793bbc1b0c245b4e99a1593024b555c92ce9263d9a847ab8b29d65ab45b20830ebc8ace237502cff34572361d0117d2940c282e4ceff6d9f17a86fc633d553e47fca0fd18efd79923bcc05dd84ba13a254da1e507867250d6834951e1a50ba30a76b52b7f3aee234d2ecfa07147d7e43598806bb7f771feba0d478b6e916da6f5b16d5788141c3f43b99b53b3f6b96e7fdb9a043cb78b3d447f79231206e323c58f616776b4ca096da1cab696afbc3c0e34e31929ab01e6ed7275ecf2de44f3ba13423f86d16b71241d010548078649260e75fce7cf45a48d08c54f6f8d836b79967aa8405cfed6a78f16d76e2cfc0de3dc608e9b03db3aeb088864c46834c9bcf351c09aaeabbffe3b5a40501b31ad570a637ca2cad1e027673304a6b25f1f3157bf3fb375980303acda0532cbfe4aae7331e6debb3a2c8eff75ffbfd189aae49fe5c99c6152ad3edb421f5263695665221ce4d155a2861d6ccd999c3e1e00b803ae1a036f7bcb9394e2262434f86445fb4e4f07ab73a0bcd857c5afc1b034ff0b1e51d4d679206052f8c0acceb3c043e3f677a7c5acf6a6690a9523bb009373926d1e234ca4126aa425f26a1bc225543992c7a328421016b3d671cdc627fa506bcaadddb08a7deba6a2f33129fc3f67b0fc44a9a9e4b690bd0244b436e60b0ea3e89b0c41771d441c671812a441a1e770ffbe1e94585bd40f18c3459e4cb6f098fcb168c850426fb9943d1b76684f9383e53a12075b4d6072b18e76148ad6b67871b80d2f96de7a63851c801a193e316724ba06b96c1903d614c71ff0cc32e72443dec9dcc674837cf286c56b5799a83f406cabb969a33a4652f546c0b9ee41736bdecc63ccd78a1d566acb50cd10d0ca6cc4a203a6c9841d2c74576fdad0c2cff519d277ef17f7cd7b4246f3f37b36200f4959925facd3ae75ce9ebc3f3bda24c6febc53aaf15228d4ee32955297d48af0386d7b66916a4b78838d2224468d06d0057d544ed4238ba368e1bb9f22f1ac9b1a0d8d3787e553373cdefc51356615070812694e84eda750312979bdcca940f1e35edf483659c47443f0eaceb864802b0117458dfc791b044806f69088fc457e250b6071ad53253ddf60e1161f63452b52111cd11c81628f98115a97116d6c8a96d19d5812dc1d1b87d02716207c9fe4c144d09b29bd5bdff5102357121182b8703b1d3d86b397f7f546471d271d08d0af0888f20bc80fb6e80f95f2fa12a6324b5df7573afa31b50c5ffe1267d89ba390a2ece50e0d3b783ef7b208266f2d32eb8695b113201f9976ace0cb261b0de4c3e65e86de12b80f703d503f41eb29a1d44f3fc660284bead92b2c9cfa4b63f442929dd21c1cb57d1f7eca476f2806b63c6703c419317cdf4f18efe87b6ea67fca742d43dc77b838f0d92519380b29aeb6a4391d0c01181c7ecafc67dbc1a62919afb7c6da78e613560e548a317ae45d70e680d4ac79824a56473558bd0f56607f60d19c6f0558e62151b7e07d60a10b0e5b77d18c61f4bda0a5478e8e9d6e3a3fdd223543decd900c1bf679a7fd4e3d0f64e12d11b11902fa300b3f679cdb91c1879f2c2445a9c93163b7f1d874b345796b3e0dfe66759c42875e65bc2f16850664c2773b508e34377453f4b1a27fe6ab3dc75c054a76b8cc6ec89629cfa605be5d9cd94e5974bdf012131faaa7df9c542e0adb2444cda5f203a5256b189d8b41a9e7f4b86a900a05892e1ea1a682a689eba570ab7767d0394ecbbbc32f56161e2e60cce2776ac18bd090020d78451ac86f7063689fa3af68dfefc153b44aae16fb28ff47cd41a321f6f09d568033f53943806ba0f8d06ae87210adb65bfbb0794653438a51b52aa915fa5c10bdf23571f8f3a5592fba7f4c7a8556279d0c72c5eb9e05927320a2ed572395224a6ace58f131f9a05531aa0111bddf8b13f418881033f406266a546a662872e477f48ff2b0250bc8f908558f319a59fabca25c0926a5f258f9ae8982bf4087655faa2b856fa0ed8f15c39a0011d3b79b22381bab09eaf9280d7b68a1889d6223d728267db0b7660ec62ce3d2c082c8353198a96ac84c411d5eec7b56b2f4a2628cc2dc1b2bf7b1059c7cef181bae3cdc0d626c1c9c3a0d4f2f56f0042d551d92f57463be1aaa402f41623d9b612bdacac779e8fbd94fe6cc020e85b2241f10504dbcfba78595e98454919123ae1a62ec8591c2e6dc5ed3c6643fd7da75864118146b4ff94fe3835afea72dfb98cdd6d3659b8e7c067c4635b9b230fad51f1362f5644177fa9a721ba70b0a9ec27d411400d795fda1f59b7ebfb59940ab2441551803f94318509c944e463afc9e30dd1a9406d22c340e92e133acacf0f8c920aa46a16dff02e50238e0f5e9435e2a4e135681b2d4c65264320d01ad72b4320fb3009b73ec80523d79bb234454f40e9b3e40381ae30c160da96d6cde44d5fa5c87a60ac4bfd8ded9561c52059ae826725c02c6d4fb04684dc576f3acb76440e33771f85ef4d620b478ffdb712d374d7132bc9dd45df314541cea11dd3596c76896041cca3d9a100c6188d5b5aebba2e7a066a849cc06e6a2373c737ef7d5e96c57e94bd9537d1e619f3cd837def7d114e1f36e62e6df49685ee955d71c4fd7c38c949663dc5cf4d51192dd8893d98e9d2a712423d93a299c31411d2f40f4e5aeaea0a82cd0a7b7c53fcb1868c053d0b80962f1e3af2c2252610a76331ad58d5dc737c6509c5274dc2c60e6d8d6160d0761d1de0fc52e2b805e414345d22b4384287935654480e38f1c9e2fd815470af6279158ee2d897a4534bf8cb3fb5e4261db86ba8708c2d741446a53291d8cbd2997807d2adb44fc72a08eb10c4fd13a872ab8cc89f05785f8f2d26c26e78689794b2e032ecbe4087cbe9c213c3be632a52d0281526da3ec29e2fdd2782c0e007ac4c3649ffa3ee63c955665f9d1accc77db03b4a004793b79cd672ea71ce42ca246e0d29ee5e901c2aed7f7b06811fe2c062e5096a62e6f52d35499c4</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-surge">      <input class="hbe hbe-input-field hbe-input-field-surge" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-surge" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-surge" data-content="请 输 入 阅 读 密 码.">请 输 入 阅 读 密 码.</span>      </label>      <svg class="hbe hbe-graphic hbe-graphic-surge" width="300%" height="100%" viewBox="0 0 1200 60" preserveAspectRatio="none">        <path d="M1200,9c0,0-305.005,0-401.001,0C733,9,675.327,4.969,598,4.969C514.994,4.969,449.336,9,400.333,9C299.666,9,0,9,0,9v43c0,0,299.666,0,400.333,0c49.002,0,114.66,3.484,197.667,3.484c77.327,0,135-3.484,200.999-3.484C894.995,52,1200,52,1200,52V9z"></path>      </svg>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">本文主要记录近期开发相关的学习计划。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="加密博客" scheme="https://www.techgrow.cn/tags/%E5%8A%A0%E5%AF%86%E5%8D%9A%E5%AE%A2/"/>
    
  </entry>
  
  <entry>
    <title>Linux 使用内存缓存减少磁盘 I/O</title>
    <link href="https://www.techgrow.cn/posts/3d17ec47.html"/>
    <id>https://www.techgrow.cn/posts/3d17ec47.html</id>
    <published>2025-12-30T11:50:21.000Z</published>
    <updated>2025-12-30T11:50:21.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 Linux 系统中，磁盘 I/O 往往是性能瓶颈。当应用程序频繁读取大文件（例如数据库文件、日志文件、多媒体文件）时，系统可能因为 I/O 等待而变得非常卡顿。为了减少磁盘压力、提升性能，Linux 提供了多种利用内存作为缓存的机制，例如：tmpfs、ramfs、Page Cache（页缓存）。本文将详细介绍这些机制的使用方法及区别，并介绍常用的磁盘 I/O 诊断工具。</p><span id="more"></span><h2 id="磁盘-I-O-诊断工具"><a href="#磁盘-I-O-诊断工具" class="headerlink" title="磁盘 I/O 诊断工具"></a>磁盘 I/O 诊断工具</h2><p>在 Linux 下，查看磁盘的吞吐量（每秒读 / 写的数据量）或者负载（IOPS - 每秒读 / 写的请求数），最常用、最直观的工具有下面几种。</p><table><thead><tr><th>工具</th><th>是否需要安装</th><th>能看具体磁盘</th><th>是否推荐</th></tr></thead><tbody><tr><td><code>iostat</code></td><td>需要</td><td>✅</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><code>iotop</code></td><td>需要</td><td>间接</td><td>⭐⭐⭐⭐</td></tr><tr><td><code>vmstat</code></td><td>不需要</td><td>❌</td><td>⭐⭐⭐</td></tr></tbody></table><h3 id="iostat"><a href="#iostat" class="headerlink" title="iostat"></a>iostat</h3><p><code>iostat</code> 是最快速的磁盘工具（强烈推荐）。</p><h4 id="iostat-安装"><a href="#iostat-安装" class="headerlink" title="iostat 安装"></a>iostat 安装</h4><figure class="highlight shell"><table><tbody><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"># CentOS</span> / RHEL</span><br><span class="line">sudo yum install sysstat</span><br><span class="line"><span class="keyword"></span></span><br><span class="line"><span class="keyword"># Debian</span> / Ubuntu</span><br><span class="line">sudo apt install sysstat</span><br></pre></td></tr></tbody></table></figure><h4 id="iostat-使用"><a href="#iostat-使用" class="headerlink" title="iostat 使用"></a>iostat 使用</h4><ul><li><code>iostat</code> 的使用 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">iostat<span class="params"> -x</span> 1</span><br></pre></td></tr></tbody></table></figure><ul><li>输出示例如下：</li></ul><figure class="highlight plaintext"><table><tbody><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">avg-cpu:  %user   %nice %system %iowait  %steal   %idle</span><br><span class="line">           3.59    0.02    1.32    0.33    0.00   94.74</span><br><span class="line"></span><br><span class="line">Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util</span><br><span class="line">sdc               0.09     0.67   33.60   14.91  2100.28  1028.30   128.99     0.32    6.53    2.01   16.73   0.43   2.09</span><br><span class="line">sdb               0.00     0.00    0.17    0.00     9.39     0.00   110.24     0.00    6.75    6.75    0.00   4.22   0.07</span><br><span class="line">sda               0.03     0.31    5.14    0.97   403.47    30.99   142.36     0.09   15.25   11.69   34.10   4.71   2.88</span><br></pre></td></tr></tbody></table></figure><ul><li>重点关注字段</li></ul><table><thead><tr><th>字段</th><th>含义</th><th>如何判断</th></tr></thead><tbody><tr><td><code>%util</code></td><td>磁盘忙碌时间百分比</td><td><strong>接近 100% = 磁盘 I/O 被打满（最重要）</strong>，<code>%util</code> 最高的磁盘 = 当前 I/O 压力最大的磁盘</td></tr><tr><td><code>await</code></td><td>平均 I/O 等待时间（ms）</td><td>等待时间高 = I/O 排队严重</td></tr><tr><td><code>r/s</code></td><td>每秒读的次数</td><td>读的频率，高表示随机读多</td></tr><tr><td><code>w/s</code></td><td>每秒写的次数</td><td>写的频率，高表示随机写多</td></tr><tr><td><code>rkB/s</code></td><td>每秒读取的数据量</td><td>读吞吐量，顺序读场景通常很高</td></tr><tr><td><code>wkB/s</code></td><td>每秒写入的数据量</td><td>写吞吐量，顺序写场景通常很高</td></tr></tbody></table><ul><li>其他字段说明</li></ul><table><thead><tr><th>字段</th><th>含义</th><th>如何判断</th></tr></thead><tbody><tr><td><code>rrqm/s</code></td><td>每秒被合并的读请求数</td><td>值高说明内核在合并顺序读</td></tr><tr><td><code>wrqm/s</code></td><td>每秒被合并的写请求数</td><td>值高说明顺序写、写合并效果好</td></tr><tr><td><code>avgrq-sz</code></td><td>平均每次 I/O 请求大小（KB）</td><td>值小（如 &lt; 16KB）多为随机 I/O；值大多为顺序 I/O</td></tr><tr><td><code>avgqu-sz</code></td><td>平均 I/O 队列长度</td><td>值大于 1 则说明开始排队；持续偏大 = 磁盘处理不过来</td></tr><tr><td><code>r_await</code></td><td>读请求平均等待时间（ms）</td><td>读延迟高，说明读取受阻（数据库、随机读场景重点看）</td></tr><tr><td><code>w_await</code></td><td>写请求平均等待时间（ms）</td><td>写延迟高，常见于日志、落盘、fsync 场景</td></tr><tr><td><code>svctm</code></td><td>平均 I/O 服务时间（ms）</td><td>单次 I/O 被磁盘处理的时间（老指标，仅作参考）</td></tr></tbody></table><ul><li>实战判断口诀<ul><li>磁盘是否被打满？<ul><li>看 <code>%util</code></li></ul></li><li>磁盘为什么慢？<ul><li><code>%util</code> + <code>await</code> + <code>avgqu-sz</code></li></ul></li><li>随机 I/O 还是顺序 I/O？<ul><li><code>avgrq-sz</code> + <code>r/s</code> / <code>w/s</code></li></ul></li><li>是读慢还是写慢？<ul><li><code>r_await</code> vs <code>w_await</code></li></ul></li><li>吞吐瓶颈还是 IOPS 瓶颈？<ul><li><code>rkB/s</code> / <code>wkB/s</code> vs <code>r/s</code> / <code>w/s</code></li></ul></li></ul></li></ul><h3 id="iotop"><a href="#iotop" class="headerlink" title="iotop"></a>iotop</h3><p><code>iotop</code> 是磁盘交互式神器（看进程 + 磁盘）。</p><h4 id="iotop-安装"><a href="#iotop-安装" class="headerlink" title="iotop 安装"></a>iotop 安装</h4><figure class="highlight shell"><table><tbody><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"># CentOS</span> / RHEL</span><br><span class="line">sudo yum install iotop</span><br><span class="line"><span class="keyword"></span></span><br><span class="line"><span class="keyword"># Debian</span> / Ubuntu</span><br><span class="line">sudo apt install iotop</span><br></pre></td></tr></tbody></table></figure><h4 id="iotop-使用"><a href="#iotop-使用" class="headerlink" title="iotop 使用"></a>iotop 使用</h4><ul><li><code>iotop</code> 的使用（需要 <code>root</code> 权限）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo iotop</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 或者</span></span><br><span class="line"><span class="comment"># -o 只显示有 I/O 的进程</span></span><br><span class="line"><span class="comment"># -p 显示进程而非线程</span></span><br><span class="line"><span class="comment"># -a 累计 I/O</span></span><br><span class="line">sudo iotop<span class="params"> -oPa</span></span><br></pre></td></tr></tbody></table></figure><ul><li>输出示例如下：</li></ul><figure class="highlight plaintext"><table><tbody><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">Total DISK READ:  12.34 M/s | Total DISK WRITE: 56.78 M/s</span><br><span class="line">Actual DISK READ: 12.10 M/s | Actual DISK WRITE: 55.90 M/s</span><br><span class="line">  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN   IO&gt;    COMMAND</span><br><span class="line">12345 be/4  mysql    1.20 M/s    35.60 M/s    0.00 %  92.34 %  mysqld</span><br><span class="line">23456 be/4  root     8.50 M/s     0.00 B/s    0.00 %  48.12 %  tar czf backup.tar.gz</span><br><span class="line">34567 be/4  www-data 0.00 B/s     15.80 M/s   0.00 %  61.45 %  php-fpm</span><br><span class="line">  987 be/4  root     0.00 B/s     2.10 M/s    0.00 %   5.23 %  jbd2/sda1-8</span><br></pre></td></tr></tbody></table></figure><ul><li>字段说明</li></ul><table><thead><tr><th>字段</th><th>含义</th><th>说明</th></tr></thead><tbody><tr><td><code>Total DISK READ</code></td><td>总读请求速率</td><td>所有进程发起的磁盘读取速率（总读请求速率）</td></tr><tr><td><code>Total DISK WRITE</code></td><td>总写请求速率</td><td>所有进程发起的磁盘写入速率（总写请求速率）</td></tr><tr><td><code>Actual DISK READ</code></td><td>实际读速率</td><td>实际落盘的磁盘读取速率（受页缓存命中情况影响）</td></tr><tr><td><code>Actual DISK WRITE</code></td><td>实际写速率</td><td>实际落盘的磁盘写入速率（受页缓存、写合并等机制影响）</td></tr><tr><td><code>TID</code></td><td>Thread ID</td><td> 线程 ID（比 PID 更细粒度）</td></tr><tr><td><code>PRIO</code></td><td>I/O 优先级</td><td><code>be</code> = best effort（普通优先级）</td></tr><tr><td><code>USER</code></td><td>进程所属用户</td><td>进程运行的用户</td></tr><tr><td><code>DISK READ</code></td><td>磁盘读取速率</td><td>该进程每秒从磁盘读取的数据量</td></tr><tr><td><code>DISK WRITE</code></td><td>磁盘写入速率</td><td>该进程每秒写入磁盘的数据量</td></tr><tr><td><code>SWAPIN</code></td><td>换页占比</td><td>因内存不足导致的换页 I/O 百分比</td></tr><tr><td><code>IO&gt;</code></td><td>I/O 等待占比</td><td><strong>最重要：进程被磁盘 I/O 阻塞的时间比例</strong></td></tr><tr><td><code>COMMAND</code></td><td>命令名</td><td>进程 / 线程名称</td></tr></tbody></table><ul><li>排查磁盘 I/O 瓶颈时重点关注的字段<ul><li><code>IO&gt;</code>（最重要）<ul><li>举例：  <figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IO&gt;  92.34 %</span><br></pre></td></tr></tbody></table></figure></li><li>含义：<ul><li>进程 92% 的时间在等磁盘 I/O</li><li> 几乎可以确定：磁盘是瓶颈</li></ul></li><li>判断规则：<ul><li><code>IO&gt; &gt; 30%</code> → I/O 明显慢</li><li><code>IO&gt; &gt; 60%</code> → 严重 I/O 阻塞</li><li><code>IO&gt; &gt; 80%</code> → 磁盘被打爆</li></ul></li></ul></li><li><code>DISK READ / DISK WRITE</code><ul><li>举例：  <figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DISK WRITE 35.60 M/s</span><br></pre></td></tr></tbody></table></figure></li><li>用来判断：<ul><li>磁盘是读取多，还是写入多</li><li>磁盘是否有异常的大流量进程</li></ul></li><li>特别注意：<ul><li>磁盘吞吐量高 ≠ 磁盘一定慢</li><li>必须结合 <code>IO&gt;</code> 来判断才有意义</li></ul></li></ul></li><li><code>Total / Actual</code> 顶部汇总行（系统级）<ul><li>举例：  <figure class="highlight plaintext"><table><tbody><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">Total DISK READ:  12.34 M/s | Total DISK WRITE: 56.78 M/s</span><br><span class="line">Actual DISK READ: 12.10 M/s | Actual DISK WRITE: 55.90 M/s</span><br></pre></td></tr></tbody></table></figure></li><li>字段：<ul><li><code>Total DISK READ</code>：所有进程发起的磁盘读取速率（总读请求速率）</li><li><code>Total DISK WRITE</code>：所有进程发起的磁盘写入速率（总写请求速率）</li><li><code>Actual DISK READ</code>：实际落盘的磁盘读取速率（受页缓存命中情况影响）</li><li><code>Actual DISK WRITE</code>：实际落盘的磁盘写入速率（受页缓存、写合并等机制影响）</li></ul></li><li><code>Total</code> 与 <code>Actual</code> 两者差距大的可能原因<ul><li> Page Cache（页缓存）命中高</li><li>写请求被缓存 / 合并</li></ul></li></ul></li></ul></li></ul><h3 id="vmstat"><a href="#vmstat" class="headerlink" title="vmstat"></a>vmstat</h3><p><code>vmstat</code> 无需安装，缺点只能看整体的磁盘 I/O 吞吐量，看不到具体的磁盘。</p><h4 id="vmstat-概述"><a href="#vmstat-概述" class="headerlink" title="vmstat 概述"></a>vmstat 概述</h4><ul><li><code>vmstat</code> 适合用来判断的事情</li></ul><table><thead><tr><th>适用场景</th><th>用法</th></tr></thead><tbody><tr><td>是否有磁盘读写行为</td><td>看 <code>bi</code> / <code>bo</code> 是否为 0</td></tr><tr><td> 是否突然爆发 I/O</td><td> 看是否瞬间飙升</td></tr><tr><td>写入型 / 读取型的吞吐量</td><td>对比 <code>bi</code> vs <code>bo</code></td></tr></tbody></table><ul><li><code>vmstat</code> 不适合用来判断的事情</li></ul><table><thead><tr><th>不适用场景</th><th>原因</th></tr></thead><tbody><tr><td>哪块磁盘 I/O 最高</td><td>看不到具体磁盘</td></tr><tr><td>磁盘是否打满</td><td>没有 <code>%util</code></td></tr><tr><td>I/O 是否排队</td><td>没有 <code>await</code></td></tr></tbody></table><h4 id="vmstat-使用"><a href="#vmstat-使用" class="headerlink" title="vmstat 使用"></a>vmstat 使用</h4><ul><li><code>vmstat</code> 的使用 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vmstat 1</span><br></pre></td></tr></tbody></table></figure><ul><li>输出示例如下：</li></ul><figure class="highlight plaintext"><table><tbody><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">procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----</span><br><span class="line"> r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st</span><br><span class="line"> 1  0      0 24642772 6851660 22099404    0    0    50    16  133   71  3  1 95  0  0</span><br><span class="line"> 0  0      0 24644776 6851660 22096364    0    0     0    37 10080 9298  1  0 98  0  0</span><br><span class="line"> 0  0      0 24644308 6851660 22096384    0    0     0   409 3063 8706  1  0 99  0  0</span><br><span class="line"> 0  0      0 24652476 6851660 22096388    0    0     0   226 3145 8780  1  0 99  0  0</span><br><span class="line"> 0  0      0 24640804 6851660 22096400    0    0     0     0 4060 10007  1  1 98  0  0</span><br></pre></td></tr></tbody></table></figure><ul><li><p>重点关注字段</p><ul><li><code>bi</code>（Block In）<ul><li>每秒从块设备读取的数据量（磁盘读流量），单位：KB/s</li><li> 不是针对某一块磁盘，而是所有块设备的读吞吐量总和</li><li><code>bi</code> 高，表示系统正在大量从磁盘读取数据</li></ul></li><li><code>bo</code>（Block Out）<ul><li>每秒写入块设备的数据量（磁盘写流量），单位：KB/s</li><li> 不是针对某一块磁盘，而是所有块设备的写吞吐量总和</li><li><code>bo</code> 高，表现系统正在大量向磁盘写数据</li></ul></li></ul></li><li><p>使用注意事项</p><ul><li><code>vmstat</code> 关注的是「磁盘吞吐量」，而不是「磁盘负载」</li><li><code>bi / bo</code> 高 ≠ 磁盘一定忙</li><li>磁盘顺序读写时：<ul><li><code>bi / bo</code> 很高</li><li>但磁盘可能很轻松</li></ul></li><li>磁盘随机读写时：<ul><li><code>bi / bo</code> 不高</li><li>磁盘却可能被打满</li></ul></li><li>不能用 <code>vmstat</code> 来判断磁盘 I/O 瓶颈<ul><li><code>bi / bo</code> 是全系统汇总值</li><li>只能判断 “有没有磁盘读写”</li><li> 不能判断 “哪个磁盘最忙”</li><li> 不能判断 “某个磁盘是否打满”</li></ul></li></ul></li></ul><h2 id="磁盘-I-O-优化方案"><a href="#磁盘-I-O-优化方案" class="headerlink" title="磁盘 I/O 优化方案"></a>磁盘 I/O 优化方案</h2><h3 id="tmpfs"><a href="#tmpfs" class="headerlink" title="tmpfs"></a>tmpfs</h3><h4 id="tmpfs-的概述"><a href="#tmpfs-的概述" class="headerlink" title="tmpfs 的概述"></a>tmpfs 的概述</h4><ul><li><p><code>tmpfs</code> 是一种基于内存的文件系统：</p><ul><li>文件实际存储在内存 + 交换分区（Swap）中</li><li>支持容量限制（挂载时可指定大小）</li><li>内存不足时，tmpfs 文件可以被交换到 Swap</li><li>tmpfs 的文件内容本质上就是 Page Cache</li><li> 不会导致 OOM（Out of Memory）</li><li>适合存放临时文件、缓存数据</li><li>典型的挂载点：<code>/dev/shm</code>、<code>/run</code></li></ul></li><li><p><code>tmpfs</code> 的主要特点</p><ul><li>支持创建目录（无限层级）</li><li>支持普通文件</li><li>支持符号链接、硬链接</li><li>支持权限、UID/GID</li><li> 支持文件删除（立即释放内存）</li><li>支持调整大小（动态扩缩）</li><li><strong>系统重启后会丢失所有数据</strong></li></ul></li><li><p><code>tmpfs</code> 的适用场景</p><ul><li>程序临时文件</li><li>加速频繁读写的小文件</li><li>构建系统（<code>make</code>、<code>npm</code>、<code>cargo</code> 等）</li><li>OBS、FFmpeg 等读取多媒体文件减少磁盘压力</li></ul></li></ul><h4 id="tmpfs-的使用"><a href="#tmpfs-的使用" class="headerlink" title="tmpfs 的使用"></a>tmpfs 的使用</h4><ul><li>创建挂载点 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir<span class="params"> -p</span> /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><ul><li>挂载 tmpfs</li></ul><figure class="highlight shell"><table><tbody><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"># 比如限制 4GB 容量</span></span><br><span class="line">sudo mount<span class="params"> -t</span> tmpfs<span class="params"> -o</span> size=4G tmpfs /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><ul><li>验证挂载 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">df<span class="params"> -h</span> /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><ul><li>开机自动挂载（可选）</li></ul><figure class="highlight shell"><table><tbody><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"># 编辑 fstab 系统配置文件，添加以下配置内容</span></span><br><span class="line">sudo vim /etc/fstab</span><br><span class="line"></span><br><span class="line"><span class="comment"># 挂载 fstab 中所有自动挂载的文件系统，并检测配置是否正确</span></span><br><span class="line">sudo mount<span class="params"> -a</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tmpfs /mnt/ramdisk tmpfs defaults,size=4G 0 0</span><br></pre></td></tr></tbody></table></figure><ul><li>卸载挂载（可选）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo umount /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><h3 id="ramfs"><a href="#ramfs" class="headerlink" title="ramfs"></a>ramfs</h3><h4 id="ramfs-的概述"><a href="#ramfs-的概述" class="headerlink" title="ramfs 的概述"></a>ramfs 的概述</h4><ul><li><p><code>ramfs</code> 是最简单的内存文件系统：</p><ul><li>数据存储在纯内存中</li><li>没有容量限制</li><li>内存写满后系统不会阻止继续写入，会持续占用内存</li><li>内存耗尽就会 OOM，最终导致系统宕机</li><li>更像一个危险，但读写速度极快的文件系统</li></ul></li><li><p><code>ramfs</code> 的主要特点</p><ul><li>支持创建目录（无限层级）</li><li>支持普通文件</li><li>支持符号链接、硬链接</li><li>支持权限、UID/GID</li><li> 支持文件删除（立即释放内存）</li><li>不支持大小限制（不能设置 Size，容量可无限增长）</li><li>数据页不可回收（不会被内核回收）</li><li>不支持 Swap Out（永远驻留物理内存中）</li><li>容易导致系统 OOM（写入越多，内存占用越多）</li><li><strong>系统重启后会丢失所有数据</strong></li></ul></li><li><p><code>ramfs</code> 的适用场景</p><ul><li>极端场景下的高性能缓存</li><li>只用于开发 / 研究，不建议生产环境使用</li></ul></li></ul><h4 id="ramfs-的使用"><a href="#ramfs-的使用" class="headerlink" title="ramfs 的使用"></a>ramfs 的使用</h4><ul><li>创建挂载点 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir<span class="params"> -p</span> /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><ul><li>挂载 ramfs</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mount<span class="params"> -t</span> ramfs ramfs /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><ul><li>开机自动挂载（可选）</li></ul><figure class="highlight shell"><table><tbody><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"># 编辑 fstab 系统配置文件，添加以下配置内容</span></span><br><span class="line">sudo vim /etc/fstab</span><br><span class="line"></span><br><span class="line"><span class="comment"># 挂载 fstab 中所有自动挂载的文件系统，并检测配置是否正确</span></span><br><span class="line">sudo mount<span class="params"> -a</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ramfs   /mnt/ramdisk   ramfs   defaults   0   0</span><br></pre></td></tr></tbody></table></figure><ul><li>卸载挂载（可选）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo umount /mnt/ramdisk</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><p>由于 ramfs 没有容量限制，如果程序写入大量数据会导致系统 OOM。</p></div><h3 id="Page-Cache"><a href="#Page-Cache" class="headerlink" title="Page Cache"></a>Page Cache</h3><h4 id="Page-Cache-的概述"><a href="#Page-Cache-的概述" class="headerlink" title="Page Cache 的概述"></a>Page Cache 的概述</h4><p>即使用户不主动使用 tmpfs 或 ramfs 文件系统，Linux 内核本身也会自动利用内存作为 “磁盘缓存”。当用户读取一个文件时：</p><ul><li>内核将数据加载到操作系统的 Page Cache（页缓存）</li><li>下次读取同一个文件时，不再触发磁盘 I/O，直接从内存返回结果</li></ul><h4 id="Page-Cache-的使用"><a href="#Page-Cache-的使用" class="headerlink" title="Page Cache 的使用"></a>Page Cache 的使用</h4><ul><li>将文件写入 Page Cache</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat video.mp4 &gt; /dev/null</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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><br><span class="line">- 强制把 `video.mp4` 文件从磁盘加载进内存</span><br><span class="line">- 但不输出任何内容</span><br><span class="line">- 后续程序读取 `video.mp4` 文件会命中缓存，不再产生磁盘 I/O</span><br></pre></td></tr></tbody></table></figure><ul><li>查看缓存使用情况 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看内存使用情况，会看到 buff/cache 部分增大</span></span><br><span class="line">free<span class="params"> -h</span></span><br></pre></td></tr></tbody></table></figure><ul><li>清除缓存（<strong>慎用，仅测试使用</strong>）</li></ul><figure class="highlight shell"><table><tbody><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"># 将所有文件系统的缓存数据写回磁盘</span></span><br><span class="line">sudo sync</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理页缓存、目录缓存和 inode 缓存</span></span><br><span class="line">sudo sh<span class="params"> -c</span> <span class="string">'echo 3 &gt; /proc/sys/vm/drop_caches'</span></span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><p>在 Linux 生产环境中，不建议随意清缓存，否则会影响系统性能。</p></div><h3 id="优化方案总结"><a href="#优化方案总结" class="headerlink" title="优化方案总结"></a>优化方案总结</h3><ul><li><code>tmpfs</code>、<code>ramfs</code>、Page Cache 的优缺点</li></ul><table><thead><tr><th>技术</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td> tmpfs</td><td> 快、可限制容量、安全</td><td>占用系统内存</td><td>缓存文件、临时空间、多媒体文件读取</td></tr><tr><td> ramfs</td><td> 极快</td><td>不限容量，OOM 风险高</td><td>测试、高速缓存（不推荐生产环境使用）</td></tr><tr><td>Page Cache</td><td> 自动、透明、无需修改程序</td><td>缓存不可控、可能被挤掉</td><td>文件预加载（热加载）</td></tr></tbody></table><ul><li><code>tmpfs</code> 与 <code>ramfs</code> 的主要区别</li></ul><table><thead><tr><th>特性</th><th> tmpfs</th><th>ramfs</th></tr></thead><tbody><tr><td> 存储位置</td><td>内存 + Swap</td><td> 纯内存（永远不使用 Swap）</td></tr><tr><td>内存限制</td><td>可限制大小（推荐）</td><td>不可限制，体积可无限增长</td></tr><tr><td>系统内存压力大时</td><td>会将部分数据 Swap 出去</td><td>继续占用内存，最终导致系统 OOM</td></tr><tr><td> 默认缓存行为</td><td>文件内容存放在 Page Cache 中，这些页是可回收的，内核在内存压力大时可以回收或 Swap Out</td><td> 文件内容同样存放在 Page Cache 中，但被标记为不可回收，不会 Swap Out，也不会被回收，因此会永久占用物理内存</td></tr><tr><td> OOM 风险</td><td>低（可回收）</td><td>极高（不可回收）</td></tr><tr><td>性能</td><td>与 ramfs 速度差异极小，几乎相同</td><td>比如 tmpfs 略快，但差距可以忽略</td></tr><tr><td>适用场景</td><td>安全缓存、高性能临时存储</td><td>特殊场景、嵌入式、需要可预测行为</td></tr></tbody></table>]]></content>
    
    
    <summary type="html">本文主要介绍 Linux 使用内存缓存减少磁盘 I/O，包括 tmpfs、ramfs、Page Cache 的使用。</summary>
    
    
    
    
    <category term="Linux运维" scheme="https://www.techgrow.cn/tags/Linux%E8%BF%90%E7%BB%B4/"/>
    
  </entry>
  
  <entry>
    <title>DevOps 的技术选型介绍</title>
    <link href="https://www.techgrow.cn/posts/54626c2e.html"/>
    <id>https://www.techgrow.cn/posts/54626c2e.html</id>
    <published>2025-11-13T13:12:19.000Z</published>
    <updated>2025-11-13T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="主流-DevOps-技术栈"><a href="#主流-DevOps-技术栈" class="headerlink" title="主流 DevOps 技术栈"></a>主流 DevOps 技术栈</h2><span id="more"></span><blockquote><p>GitHub + Jenkins + Docker</p></blockquote><p><img data-src="../../../asset/2025/11/devops-1.png"></p><blockquote><p>GitLab + Jenkins + Harbor + Kubernetes + Docker </p></blockquote><p><img data-src="../../../asset/2025/11/devops-3.png"></p><blockquote><p>基于 Kubernetes 的微服务部署与监控运维架构</p></blockquote><p><img data-src="../../../asset/2025/11/devops-4.png"></p><blockquote><p>Jenkins 作为 CD / DevOps 生态的核心</p></blockquote><p><img data-src="../../../asset/2025/11/devops-2.png"></p>]]></content>
    
    
    <summary type="html">本文主要介绍 DevOps 的技术选型介绍。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="微服务" scheme="https://www.techgrow.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
    <category term="Linux运维" scheme="https://www.techgrow.cn/tags/Linux%E8%BF%90%E7%BB%B4/"/>
    
    <category term="知识图谱" scheme="https://www.techgrow.cn/tags/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/"/>
    
  </entry>
  
  <entry>
    <title>基于 C++ 手写 Muduo 高性能网络库</title>
    <link href="https://www.techgrow.cn/posts/dbb10768.html"/>
    <id>https://www.techgrow.cn/posts/dbb10768.html</id>
    <published>2025-11-01T13:55:33.000Z</published>
    <updated>2025-11-28T13:55:33.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/def1afc3.html">C++ 网络编程 Muduo 库使用</a></li><li><a href="/posts/e635f0aa.html">基于 C++ 开发集群聊天服务器</a></li><li><a href="/posts/5e6aa28a.html">C++ 实现 RPC 分布式网络通信框架</a></li><li><a href="/posts/dbb10768.html">基于 C++ 手写 Muduo 高性能网络库</a></li></ul><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本文将基于 C++ 开发一个类似 <a href="https://github.com/chenshuo/muduo">Muduo</a> 的高性能网络库，项目代码大部分都是从 Muduo 移值过来，同时去掉 Boost 依赖，并使用 C++ 11 进行代码重构，重点是学习 Muduo 的底层设计思想（尤其是 Multiple Reactors 模型）。</p><span id="more"></span><h3 id="学习目标"><a href="#学习目标" class="headerlink" title="学习目标"></a>学习目标</h3><ul><li>1、理解阻塞、非阻塞、同步、异步</li><li> 2、理解 Unix/Linux 上的五种 I/O 模型</li><li> 3、epoll 的原理以及优势</li><li> 4、深刻理解 Reactor 模型（基于 I/O 的事件驱动模型）</li><li>5、从开源 C++ 网络库 Muduo 的源码中，学习优秀的代码设计</li><li> 6、掌握基于事件驱动和事件回调的 epoll + 线程池的面向对象编程</li><li> 7、通过深入理解 Muduo 源码，加深对于网络相关项目的深刻理解</li><li> 8、改造 Muduo，不依赖 Boost，使用 C++ 11 进行代码重构</li></ul><h3 id="知识储备"><a href="#知识储备" class="headerlink" title="知识储备"></a>知识储备</h3><p>在使用 C++ 开发高性能的网路库之前，要求先掌握以下前置知识：</p><ul><li>1、TCP 协议和 UDP 协议</li><li> 2、Linux 的 TCP 网络编程和 UDP 网络编程</li><li> 3、I/O 多路复用编程，包括 select、poll、epoll 库的使用</li><li> 4、Linux 的多线程编程（<code>pthread</code>）、进程和线程模型</li><li> 5、C++ 20 标准新加入的协程支持</li></ul><div class="admonition note"><p class="admonition-title">推荐阅读的书籍</p><p>《UNIX 环境高级编程》、《Linux 高性能服务器编程》、《Linux 多线程服务端编程 - 使用 Muduo C++ 网络库》《鸟哥的 Linux 私房菜》</p></div><h3 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h3><table><thead><tr><th>软件</th><th>版本</th><th>说明</th></tr></thead><tbody><tr><td> C++ 标准</td><td><code>11</code></td><td>C++ 标准的版本</td></tr><tr><td> G++（GCC）</td><td><code>12.2.0</code></td><td>建议使用 <code>9</code> 版本的 G++（GCC） 编译器</td></tr><tr><td> CMake</td><td><code>3.25.1</code></td><td>C/C++ 项目构建工具</td></tr><tr><td> Linux</td><td><code>Debian 12</code></td><td>Muduo 库不支持 Windows 平台</td></tr><tr><td> Visual Studio Code</td><td><code>1.100.2</code></td><td>使用 VSCode 远程开发特性</td></tr></tbody></table><h2 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h2><h3 id="阻塞、非阻塞、同步、异步"><a href="#阻塞、非阻塞、同步、异步" class="headerlink" title="阻塞、非阻塞、同步、异步"></a>阻塞、非阻塞、同步、异步</h3><div class="admonition note"><p class="admonition-title">提示</p><ul><li>下面提到的 "I/O 操作" 并不局限于网络 I/O，而是一个广义的 I/O 概念，它既包含网络 I/O（Socket 读写），也包含磁盘 I/O（文件读写）等所有涉及内核态与用户态之间数据交换的操作。</li><li>I/O 模型（如阻塞、非阻塞、同步、异步）更多用于讨论网络 I/O，原因是磁盘 I/O 的异步化由操作系统内核自动管理（页缓存 + 异步调度），应用层很少直接干预。</li></ul></div><ul><li><p>同步与异步的区别</p><ul><li>同步：<ul><li>请求方 A 发起 I/O 调用后，由 A 自身完成数据的读写；</li><li>无论阻塞与否，A 都要亲自执行数据的读写，将数据从内核缓冲区拷贝到用户空间（或反之）。</li></ul></li><li>异步：<ul><li>请求方 A 发起 I/O 调用后，仅仅发出请求，并由操作系统内核来完成数据的读写；</li><li>A 不需要等待操作完成，可以继续做其他事情；当操作系统内核完成读写操作后，会通过回调、事件通知等机制通知 A 结果。</li></ul></li></ul></li><li><p>阻塞与非阻塞的区别</p><ul><li>阻塞：<ul><li>调用未完成前，调用线程会一直等待；</li></ul></li><li>非阻塞：<ul><li>调用立即返回，即使操作未完成，也会返回错误码或状态提示（例如 <code>EAGAIN</code>）。</li></ul></li></ul></li><li><p>典型的一次 I/O 操作可以分为两个阶段</p><ul><li>数据准备（阶段一）：该阶段取决于系统 I/O 操作的就绪状态，即数据是否已经可以被读写<ul><li>阻塞：调用会等待数据准备好后再继续执行。</li><li>非阻塞：调用会立即返回，无论数据是否就绪。</li></ul></li><li>数据读写（阶段二）：该阶段取决于应用程序与操作系统内核之间的交互方式<ul><li>同步：由应用程序主动完成数据的读写，将数据从内核缓冲区拷贝到用户空间（或反之）。</li><li>异步：由操作系统内核完成数据的读写，并在操作完成后通知应用程序。</li></ul></li></ul></li></ul><div class="admonition warning"><p class="admonition-title">总结</p><ul><li>同步 / 异步区分的是谁来完成 I/O 读写（调用方自己还是操作系统内核来完成数据读写）。</li><li>阻塞 / 非阻塞区分的是调用方等待的方式（是否挂起等待处理结果）。</li></ul></div><blockquote><p><strong>常见的四种 I/O 模型</strong></p></blockquote><table><thead><tr><th>I/O 模型</th><th>数据准备阶段</th><th>数据读写阶段</th><th>调用方行为</th><th>示例说明</th></tr></thead><tbody><tr><td>同步阻塞</td><td>阻塞等待数据准备好</td><td>调用方执行读写</td><td>整个过程会阻塞当前线程</td><td><code>int size = recv(fd, buf, 1024, 0);</code>（若无数据则阻塞等待）</td></tr><tr><td>同步非阻塞</td><td>非阻塞轮询数据准备好</td><td>调用方执行读写</td><td>调用立即返回，但需要反复尝试调用</td><td>设置 <code>O_NONBLOCK</code>，多次调用 <code>recv()</code> 检查是否有数据可读</td></tr><tr><td>异步阻塞</td><td>阻塞等待事件完成</td><td>操作系统内核完成读写</td><td>等待通知，但数据读写由操作系统内核完成</td><td>例如 Windows <code>OVERLAPPED</code> I/O + <code>GetOverlappedResult</code> 阻塞等待</td></tr><tr><td>异步非阻塞</td><td>非阻塞提交请求</td><td>操作系统内核完成读写并通知</td><td>完全不阻塞，结果通过回调 / 事件返回</td><td>例如 Linux <code>aio_read()</code> 或 <code>io_uring</code> 提交请求后立即返回</td></tr></tbody></table><blockquote><p><strong>陈硕大神的原话：在处理 I/O 的时候，阻塞和非阻塞都是同步 I/O，只有使用了特殊的 API 才是异步 I/O（如下图所示）。</strong></p></blockquote><p><img data-src="../../../asset/2025/11/cplusplus-muduo-1.png"></p><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li><code>select</code> / <code>poll</code> / <code>epoll</code> 本身只是事件就绪通知机制，它们并不直接完成数据读写，调用它们的线程仍然需要自己去 <code>read()</code> 或 <code>write()</code> 数据。</li><li>因此，从严格意义上看，它们属于同步 I/O 实现方式，因为最终的 I/O 读写（即数据读写）是由调用线程自己完成的。</li><li>但它们提供了非阻塞的事件等待，使得一个线程可以同时监听多个 <code>fd</code>，而不用一个线程阻塞在一个 <code>fd</code> 上。</li><li>真正的异步 I/O 实现，在 Linux 上需要使用 <code>aio_*</code> 系列系统函数或者使用 <code>io_uring</code>。</li></ul></div><h3 id="Unix-Linux-的五种-I-O-模型"><a href="#Unix-Linux-的五种-I-O-模型" class="headerlink" title="Unix/Linux 的五种 I/O 模型"></a>Unix/Linux 的五种 I/O 模型</h3><p>Unix/Linux 支持以下五种 I/O 模型：</p><table><thead><tr><th>I/O 模型</th><th>阻塞 / 非阻塞</th><th>事件通知方式</th><th>适用场景</th></tr></thead><tbody><tr><td>阻塞 I/O</td><td> 阻塞</td><td>同步返回</td><td>简单程序，低并发</td></tr><tr><td>非阻塞 I/O</td><td> 非阻塞</td><td>轮询</td><td>少量 I/O，CPU 可支撑</td></tr><tr><td> I/O 多路复用</td><td>阻塞或非阻塞</td><td>操作系统内核返回就绪事件列表</td><td>高并发网络服务器</td></tr><tr><td>信号驱动 I/O</td><td> 非阻塞</td><td>信号</td><td>小规模异步通知</td></tr><tr><td>异步 I/O</td><td> 非阻塞</td><td>回调 / 事件</td><td>高并发、对延迟敏感场景</td></tr></tbody></table><blockquote><p><strong>阻塞 I/O（Blocking I/O）</strong></p></blockquote><ul><li>特征：应用程序调用 I/O 函数后，如果数据未就绪，调用线程会被阻塞，直到数据准备完成。</li><li>优点：编程实现简单、逻辑直观。</li><li>缺点：线程无法同时处理多个 I/O，吞吐量受限。</li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-2.png"></p><blockquote><p><strong>非阻塞 I/O（Non-Blocking I/O）</strong></p></blockquote><ul><li>特征：I/O 调用立即返回，即使数据未就绪也不会阻塞。应用程序需要通过轮询（Polling）或循环检查，目的是不断检测数据是否已经就绪，以便及时进行数据读写操作。</li><li>优点：单线程可以处理多个 I/O。</li><li>缺点：轮询会浪费 CPU 资源，逻辑较复杂。</li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-3.png"></p><blockquote><p><strong>I/O 多路复用（I/O Multiplexing）</strong></p></blockquote><ul><li>典型机制：<code>select</code>、<code>poll</code>、<code>epoll</code>。</li><li>特征：单个线程可以同时监听多个 <code>fd</code>，通过操作系统内核返回就绪事件列表，再进行读写操作。</li><li>优点：高效管理大量并发连接，避免轮询浪费。</li><li>缺点：处理非常大量 <code>fd</code> 时，某些实现（如 <code>select</code>、<code>poll</code>）效率有限。</li><li>注意：在 I/O 多路复用中，复用的线程而不是 TCP 连接。由于最终的 I/O 读写（即数据读写）是由调用线程自己完成的，因此从严格意义上看，I/O 多路复用属于同步 I/O 实现方式。</li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-4.png"></p><blockquote><p><strong>信号驱动 I/O（Signal-Driven I/O）</strong></p></blockquote><ul><li>特征：应用程序注册信号处理函数（如 <code>SIGIO</code>），当 <code>fd</code> 可读或可写时，操作系统内核发送信号通知。</li><li>优点：异步通知，无需轮询。</li><li>缺点：信号处理复杂，信号丢失或竞态问题较多，不易大规模使用。</li><li>注意：操作系统内核在第一个阶段（数据准备）是异步，在第二个阶段（数据读写）是同步；与非阻塞 I/O 的区别在于它提供了消息通知机制，不需要用户进程不断的轮询检查，减少了系统 API 的调用次数，提高了效率。</li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-5.png"></p><blockquote><p><strong>异步 I/O（Asynchronous I/O）</strong></p></blockquote><ul><li>特征：应用程序发起 I/O 调用后，立即返回；当数据准备好后，由操作系统内核完成数据读写；当数据读写操作完成后，通过信号、回调函数或事件机制通知应用程序。</li><li>优点：真正的异步，高效利用 CPU，可处理大量并发 I/O。</li><li>缺点：编程复杂，Linux 支持有限（传统 AIO 对网络 I/O 支持不好，<code>io_uring</code> 是新方案）。</li><li>注意：这是真正的异步 I/O 实现，在 Linux 上需要使用 <code>aio_*</code> 系列系统函数或者使用 <code>io_uring</code>，Node.js 采用了该 I/O 模型。</li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-6.png"></p><h3 id="优秀的网络服务器设计"><a href="#优秀的网络服务器设计" class="headerlink" title="优秀的网络服务器设计"></a>优秀的网络服务器设计</h3><p>在这个 CPU 多核时代，服务端网络编程如何选择线程模型呢？赞同 <code>libev</code> 作者的观点：”one loop perthread is usually a good model”，这样多线程服务端编程的问题就转换为如何设计一个高效且易于使用的 Event Loop， 然后每个线程运行一个 Event Loop 就行了（当然，线程间的同步、互斥少不了，还有其它的耗时事件需要起另外的线程来做）。Event Loop 是 Non-Blocking 网络编程的核心，可以简单理解为 Non-Blocking + epoll + thread-pool 的结合。在实际应用中，Non-Blocking 几乎总是与 I/O Multiplexing 一起使用，原因有以下两点：</p><ul><li>实际上没有人会采用轮询（Busy-Polling）方式不断检查某个 Non-Blocking I/O 操作是否完成，因为这会严重浪费 CPU 资源。</li><li>I/O Multiplexing 通常无法与 Blocking I/O 一起使用，因为在 Blocking I/O 中，<code>accept()</code>、<code>connect()</code>、<code>read()</code>、<code>write()</code> 等调用都有可能阻塞当前线程，从而导致线程无法继续处理其他 Socket 上的 I/O 事件。</li></ul><p><strong>所以，当日常提到 Non-Blocking I/O 时，实际上指的是 Non-Blocking + I/O Multiplexing（如 epoll + thread-pool） 的组合，如何单独使用其中任意一种，都无法很好地实现高效的网络 I/O。</strong></p><hr><p>在网络编程领域中，主流的网络 I/O 模型有以下几种（不限于），Muduo 采用的是第四种（<code>reactors in threads - one loop per thread</code>）。</p><ul><li><p>(1) <code>accept + read/write</code></p><ul><li>不适用于并发服务器</li></ul></li><li><p> (2) <code>accept + fork - process-pre-connection</code></p><ul><li>适合并发连接数不大，计算任务工作量大于 Fork 的开销。</li></ul></li><li><p>(3) <code>accept + thread - thread-pre-connection</code></p><ul><li>比第二种网络 I/O 模型的开销小了一点，但是并发造成的线程堆积过多。</li></ul></li><li><p>(4) <code>reactors in threads - one loop per thread</code></p><ul><li>这是 Muduo 库的网络设计方案，底层实质上是基于 Linux 的 <code>epoll</code> + <code>pthread</code> 线程池实现，且依赖了 Boost 库，适用于并发连接数较大的场景。</li><li>有一个 Main Reactor 负载 Accept 连接，然后将连接分发给某个 SubReactor（采用轮询的方式来选择 SubReactor），该连接的所用操作都在那个 SubReactor 所处的线程中完成。多个连接可能被分派到多个线程中被处理，以充分利用 CPU。</li><li>Main Reactor 中有一个 Base I/O Thread 负责 Accept 新的连接，接收到新的连接以后，使用轮询的方式在 Reactor Pool 中找到合适的 SubReactor 将这个连接挂载上去，这个连接上的所有任务都在这个 SubReactor  所处的线程中完成。</li><li>Reactor Poll 的大小是固定的，根据 CPU 的核心数量来确定。如果有过多的耗费 CPU 资源的计算任务，可以提交到 ThreadPool 线程池中专门处理耗时的计算任务。</li></ul></li><li><p>(5) <code>reactors in process - one loop pre process</code></p><ul><li>这是 Nginx 服务器的网络设计方案，基于进程设计，采用多个 Reactors 充当 I/O 进程和工作进程，通过一个 <code>accept</code> 锁，完美解决多个 Reactors 之间的 “惊群现象”。</li></ul></li></ul><div class="admonition note"><p class="admonition-title">reactors in process + fork 不如 reactors in threads 吗？</p><p>答案肯定是否定的，强大的 Nginx 服务器采用了 <code>reactors in process</code> 模型作为网络模块的架构设计，实现了简单好用的负载算法，使各个 fork 网络进程不会忙的越忙、闲的越闲，并且通过引入一把乐观锁解决了该模型导致的服务器惊群现象，功能十分强大。</p></div><h3 id="Reactor-网络-I-O-模型"><a href="#Reactor-网络-I-O-模型" class="headerlink" title="Reactor 网络 I/O 模型"></a>Reactor 网络 I/O 模型</h3><blockquote><p><strong>Reactor 模型的介绍</strong></p></blockquote><div class="admonition warning"><p class="admonition-title">维基百科对 Reactor 的描述</p><p>The reactor design pattern is an event handling pattern for handling service requestsdelivered concurrently to a service handler by one or more inputs. The service handlerthen demultiplexes the incoming requests and dispatches them synchronously to theassociated request handlers. 翻译后：Reactor（反应器）设计模式是一种事件处理模式，用于处理由一个或多个输入并发传递到服务处理器的服务请求。然后，服务处理器对传入的请求进行多路分解，并同步地将它们分派给相应的请求处理器。</p></div><ul><li><p>Reactor 是一种基于事件驱动（Event Driven）的网络 I/O 模型，核心思想是：</p><ul><li>主线程（或 I/O 线程）通过 I/O 多路复用（I/O Multiplexing）机制（如 <code>select</code>、<code>poll</code>、<code>epoll</code>），监听多个连接的 I/O 事件。</li><li>当某个事件就绪后，再分发（Dispatch）给对应的事件处理器（EventHandler）进行处理。</li></ul></li><li><p>Reactor 虽然是网络 I/O 模型，但它通常与线程模型结合使用：</p><ul><li>单线程 Reactor：所有 I/O 事件的监听与处理都在同一个线程中完成。</li><li>多线程 Reactor：I/O 事件的监听与业务处理分离，通常用线程池来处理业务逻辑。</li><li>主从 Reactor：主 Reactor（即 MainReactor）负责连接建立，从 Reactor（即 SubReactor）负责 I/O 读写（即数据读写），结合多线程提升并发性能。</li></ul></li><li><p>Reactor 的五大核心组件：</p><table><thead><tr><th>核心组件</th><th>作用</th><th> Muduo 网络库中对应的核心类</th></tr></thead><tbody><tr><td> Event（事件）</td><td>表示 I/O 事件的抽象，如连接建立、可读、可写等，用于描述发生了什么类型的网络事件。</td><td><code>Channel</code></td></tr><tr><td>Demultiplexer（事件分离器）</td><td>负责监听并检测多个 I/O 事件的就绪状态（通常由 <code>select</code>、<code>poll</code>、<code>epoll</code> 等系统调用实现），并将已就绪的事件返回给 Reactor。</td><td><code>Poller</code>、<code>EPollPoller</code></td></tr><tr><td>Reactor（反应堆）</td><td>事件分发器，负责从 Demultiplexer 获取就绪事件，并将事件分发给对应的 EventHandler 处理。</td><td><code>EventLoop</code></td></tr><tr><td>EventHandler（事件处理器）</td><td>负责具体的事件处理逻辑，如读、写、连接、业务处理等，是应用层的回调逻辑。</td><td>回调函数 + <code>TcpConnection</code> 的 <code>handleRead()</code> / <code>handleWrite()</code> 等</td></tr><tr><td> Acceptor（连接接收器）</td><td>负责监听服务器端口并接收新的客户端连接，在多 Reactor 模型中通常独立运行，仅负责建立连接并将连接交给子 Reactor 处理。</td><td><code>Acceptor</code></td></tr></tbody></table></li><li><p>Reactor 核心组件的工作流程：</p></li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-7.png"></p><ul><li>Muduo 库的 Multiple Reactors 模型：</li></ul><p><img data-src="../../../asset/2025/11/cplusplus-muduo-8.png"></p><blockquote><p><strong>Reactor 模型与 Proactor 模型的区别</strong></p></blockquote><ul><li>Reactor 模型与 Proactor 模型的主要区别</li></ul><table><thead><tr><th>模型</th><th>内核通知的事件</th><th>谁负责实际 I/O 读写</th><th>用户线程需要做什么</th></tr></thead><tbody><tr><td> Reactor</td><td> 可以读 / 可以写</td><td>用户线程做读写</td><td>用户线程收到可读 / 可写通知后，调用 <code>read</code> / <code>write</code>，并处理数据</td></tr><tr><td> Proactor</td><td> 读完了 / 写完了</td><td>内核做读写 （异步完成读 / 写后，再通知用户线程）</td><td>用户线程收到读 / 写完成通知后，直接处理已读 / 已写的数据</td></tr></tbody></table><ul><li>常见库 / 系统采用模型对比</li></ul><table><thead><tr><th>库 / 系统</th><th>模型</th><th>平台</th></tr></thead><tbody><tr><td> Muduo</td><td>Reactor</td><td>Linux</td></tr><tr><td>Netty（NIO）</td><td>Reactor</td><td>Linux</td></tr><tr><td>libevent / libev</td><td>Reactor</td><td>Linux</td></tr><tr><td>Boost.Asio（Linux）</td><td>Reactor</td><td>Linux</td></tr><tr><td>IOCP</td><td>Proactor</td><td>Windows</td></tr><tr><td>Boost.Asio（Windows）</td><td>Proactor</td><td>Windows</td></tr></tbody></table><ul><li> 为什么 Linux 基本用不到 Proactor？<ul><li>因为 Linux 的 <code>aio</code> 不是真正意义上的内核异步 I/O：<ul><li>文件 I/O 是异步的</li><li>网络 I/O 仍然是阻塞式的（内核不自动读取）</li></ul></li><li>所以 Linux 上的高性能网络库几乎都是：<ul><li>epoll（Reactor）</li><li>epoll + thread pool（高级 Reactor）</li></ul></li></ul></li></ul><h3 id="I-O-多路复用技术概述"><a href="#I-O-多路复用技术概述" class="headerlink" title="I/O 多路复用技术概述"></a>I/O 多路复用技术概述</h3><blockquote><p><strong>跨平台特性的对比</strong></p></blockquote><table><thead><tr><th>技术</th><th>是否支持跨平台</th><th>支持的平台</th><th>特点</th></tr></thead><tbody><tr><td><code>select</code></td><td>✅ 广泛跨平台</td><td> Linux / macOS / BSD / Windows / Unix</td><td> 最老的接口，POSIX 标准定义</td></tr><tr><td><code>poll</code></td><td>⚠️ 支持类 Unix 跨平台（不支持 Windows）</td><td>Linux /macOS/ BSD / Solaris 等</td><td><code>select</code> 的改进版 ，无 <code>fd</code> 数量限制</td></tr><tr><td><code>epoll</code></td><td>❌ Linux 独有</td><td>仅 Linux（2.6+）</td><td>高性能 I/O 多路复用技术</td></tr><tr><td><code>kqueue</code></td><td>❌ BSD /macOS 独有</td><td> FreeBSD / macOS / NetBSD / OpenBSD</td><td><code>epoll</code> 的 BSD 平台对应物</td></tr></tbody></table><blockquote><p><strong>select 与 poll 的缺点</strong></p></blockquote><p>I/O 多路复用技术 <code>select</code> 有以下缺点：</p><ul><li><p>(1) 文件描述符数量限制：</p><ul><li>单个进程可监视的文件描述符数量存在上限，通常为 1024（可修改）。但由于 <code>select</code> 采用轮询扫描方式检查文件描述符，随着监视数量的增加，性能会明显下降。</li><li>在 Linux 内核头文件中有如下定义：<code>#define __FD_SETSIZE 1024</code>。</li></ul></li><li><p>(2) 内核与用户空间的数据拷贝开销大：</p><ul><li>每次调用 <code>select</code> 都需要在内核空间与用户空间之间复制大量的文件描述符集合，这会造成显著的性能开销。</li></ul></li><li><p>(3) 结果集遍历效率低：</p><ul><li><code>select</code> 返回的是一个包含所有文件描述符的数组，应用程序需要遍历整个数组才能判断哪些描述符处于就绪状态，效率较低。</li></ul></li><li><p>(4) 水平触发机制（Level Trigger）：</p><ul><li><code>select</code> 采用水平触发方式，如果应用程序没有及时处理已就绪的文件描述符，那么在后续的每次 <code>select</code> 调用中，这些描述符仍会被重复通知。</li></ul></li></ul><p>I/O 多路复用技术 <code>poll</code> 跟 <code>select</code> 相比，使用链表来保存文件描述符，不再受文件描述符数量上限的限制，但仍然存在与 <code>select</code> 相同的其他三个缺点（数据拷贝开销大、结果集遍历效率低、水平触发），这里不再累述。</p><div class="admonition warning"><p class="admonition-title">select 无法支持高并发连接</p><p>以 <code>select</code> 为例，若服务器需支持 100 万并发连接，在 <code>__FD_SETSIZE</code> 为 1024 的情况下，至少需要创建约 1000 个进程才能满足要求。如此不仅会带来大量的进程上下文切换开销，还会因频繁的内核空间 / 用户空间句柄拷贝与数组遍历操作，导致系统性能急剧下降。因此，基于 <code>select</code> 模型的服务器要实现百万级并发几乎是不可能的。</p></div><blockquote><p><strong>epoll 的原理以及优势</strong></p></blockquote><p>设想这样一个场景：有 100 万个客户端同时与一个服务器进程保持 TCP 连接，但在任意时刻，通常只有几百到上千个连接是活跃的（这也是现实中最常见的情况）。如何高效地支撑如此庞大的并发连接呢？在 <code>select</code> / <code>poll</code> 时代，服务器每次调用都需要将这 100 万个连接的文件描述符从用户态复制到内核态，让内核轮询这些套接字上是否有事件发生；轮询完成后，再将结果从内核态复制回用户态，供应用程序继续遍历处理。这种方式带来了巨大的内存拷贝和遍历开销，因此基于 <code>select</code> / <code>poll</code> 通常只能处理几千个并发连接。</p><hr><p><code>epoll</code> 的设计思想与 <code>select</code> 完全不同，因此它们的缺点在 <code>epoll</code> 中已不复存在。<code>epoll</code> 在 Linux 内核中引入了一种专用的事件管理机制，通过红黑树（用于管理所有已注册的文件描述符）和就绪链表（用于管理已触发事件的文件描述符）来组织事件，大幅降低了事件查找和分发的开销，使大规模并发连接的事件管理更加高效。</p><ul><li>(1) <code>epoll_create()</code>：创建一个 <code>epoll</code> 对象（内核在 <code>epoll</code> 文件系统中为该对象分配资源）。</li><li>(2) <code>epoll_ctl()</code>：向 <code>epoll</code> 对象中添加、修改或删除需要监听的套接字（例如 100 万个 TCP 连接）。</li><li>(3) <code>epoll_wait()</code>：等待并收集有事件发生的文件描述符。</li></ul><p>其中 <code>epoll_create()</code> 在内核上创建的 <code>eventpoll</code> 结构如下：</p><figure class="highlight c"><table><tbody><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="class"><span class="keyword">struct</span> <span class="title">eventpoll</span> {</span></span><br><span class="line">    ....(省略)</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 红黑树的根节点，这颗树中存储着所有添加到 epoll 中的需要监控的事件 */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">rb_root</span>  <span class="title">rbr</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 双链表中则存放着将要通过 epoll_wait() 返回给用户的满足条件的事件 */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">rdlist</span>;</span></span><br><span class="line"></span><br><span class="line">    ....(省略)</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>得益于这种设计，只需在服务器启动时创建一次 <code>epoll</code> 对象，然后在连接建立或关闭时动态地添加或移除对应的套接字即可。更重要的是，<code>epoll_wait()</code> 的调用效率极高：</p><ul><li>它不需要在每次调用时复制所有文件描述符。</li><li>内核也无需遍历全部连接，而是通过回调机制主动将就绪的文件描述符加入到就绪队列中。</li></ul><p>因此，<code>epoll</code> 能够在单进程中轻松支撑数十万甚至上百万级的并发连接，这正是它区别于 <code>select</code> / <code>poll</code> 的根本优势所在。</p><blockquote><p><strong>epoll 的 LT 模式与 ET 模式</strong></p></blockquote><p><code>epoll</code> 支持 LT（水平触发）与 ET（边缘触发），而 <code>select</code>、<code>poll</code> 在设计上只支持 LT（水平触发），没有 ET（边缘触发）的概念。</p><ul><li><p>LT 模式（Level Triggered，水平触发）</p><ul><li>语义：只要 <code>fd</code> 上有数据未被读取完，就会一直被 <code>epoll</code> 通知。</li><li>特点：更 “宽松”，即使一次没读完，下次还会被提醒。</li><li>行为示例：<ul><li>缓冲区有 100 字节可读；</li><li>应用程序只读了 60 字节；</li><li>下次 <code>epoll_wait()</code> 还会再次返回该 <code>fd</code>。</li></ul></li><li>优点：编程简单、不易漏数据。</li><li>缺点：频繁触发，效率略低。</li></ul></li><li><p>ET 模式（Edge Triggered，边缘触发）</p><ul><li>语义：只有当状态发生变化（从无到有）时才触发一次事件。</li><li>特点：仅在 “边缘” 通知，比如缓冲区从空变为非空。</li><li>行为示例：<ul><li>缓冲区变为可读时触发；</li><li>应用程序必须一次性读完所有数据（直到返回 <code>EAGAIN</code>）；</li><li>如果应用程序没读完，下次不会再收到通知。</li></ul></li><li>优点：减少系统调用次数，效率高。</li><li>缺点：编程复杂，稍有疏忽就可能会 “丢事件”。</li></ul></li><li><p>Muduo 采用的是 LT（水平触发）模式</p><ul><li>不会丢失数据或者消息<ul><li>应用程序没有读取完数据，内核是会不断上报数据的</li></ul></li><li>低延迟处理<ul><li>每次读数据只需要一次系统调用，照顾了多个连接的公平性，不会因为某个连接上的数据量过大而影响其他连接处理消息</li></ul></li><li>跨平台处理<ul><li>像 <code>select</code> 一样可以跨平台使用</li></ul></li></ul></li></ul><h2 id="项目介绍"><a href="#项目介绍" class="headerlink" title="项目介绍"></a>项目介绍</h2><h3 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h3><figure class="highlight plaintext"><table><tbody><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></pre></td><td class="code"><pre><span class="line">c++-project-mymuduo</span><br><span class="line">├── autobuild.sh</span><br><span class="line">├── bin</span><br><span class="line">├── build</span><br><span class="line">├── CMakeLists.txt</span><br><span class="line">├── example</span><br><span class="line">│&nbsp;&nbsp; ├── CMakeLists.txt</span><br><span class="line">│&nbsp;&nbsp; ├── epoll</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── CMakeLists.txt</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; └── main.cc</span><br><span class="line">│&nbsp;&nbsp; └── mymuduo</span><br><span class="line">│&nbsp;&nbsp;     ├── ChatClient.cc</span><br><span class="line">│&nbsp;&nbsp;     ├── ChatClient.h</span><br><span class="line">│&nbsp;&nbsp;     ├── ChatServer.cc</span><br><span class="line">│&nbsp;&nbsp;     ├── ChatServer.h</span><br><span class="line">│&nbsp;&nbsp;     ├── CMakeLists.txt</span><br><span class="line">│&nbsp;&nbsp;     └── main.cc</span><br><span class="line">├── lib</span><br><span class="line">├── README.md</span><br><span class="line">├── src</span><br><span class="line">│&nbsp;&nbsp; ├── include</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Acceptor.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Buffer.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Callbacks.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Channel.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Connector.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── copyable.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── CurrentThread.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── EPollPoller.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── EventLoop.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── EventLoopThread.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── EventLoopThreadPool.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── InetAddress.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Logger.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── noncopyable.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Poller.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Socket.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── SocketsOps.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── TcpClient.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── TcpConnection.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── TcpServer.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; ├── Thread.h</span><br><span class="line">│&nbsp;&nbsp; │&nbsp;&nbsp; └── Timestamp.h</span><br><span class="line">│&nbsp;&nbsp; ├── Acceptor.cc</span><br><span class="line">│&nbsp;&nbsp; ├── Buffer.cc</span><br><span class="line">│&nbsp;&nbsp; ├── Channel.cc</span><br><span class="line">│&nbsp;&nbsp; ├── CMakeLists.txt</span><br><span class="line">│&nbsp;&nbsp; ├── Connector.cc</span><br><span class="line">│&nbsp;&nbsp; ├── CurrentThread.cc</span><br><span class="line">│&nbsp;&nbsp; ├── DefaultPoller.cc</span><br><span class="line">│&nbsp;&nbsp; ├── EPollPoller.cc</span><br><span class="line">│&nbsp;&nbsp; ├── EventLoop.cc</span><br><span class="line">│&nbsp;&nbsp; ├── EventLoopThread.cc</span><br><span class="line">│&nbsp;&nbsp; ├── EventLoopThreadPool.cc</span><br><span class="line">│&nbsp;&nbsp; ├── InetAddress.cc</span><br><span class="line">│&nbsp;&nbsp; ├── Logger.cc</span><br><span class="line">│&nbsp;&nbsp; ├── Poller.cc</span><br><span class="line">│&nbsp;&nbsp; ├── Socket.cc</span><br><span class="line">│&nbsp;&nbsp; ├── SocketsOps.cc</span><br><span class="line">│&nbsp;&nbsp; ├── TcpClient.cc</span><br><span class="line">│&nbsp;&nbsp; ├── TcpConnection.cc</span><br><span class="line">│&nbsp;&nbsp; ├── TcpServer.cc</span><br><span class="line">│&nbsp;&nbsp; ├── Thread.cc</span><br><span class="line">│&nbsp;&nbsp; └── Timestamp.cc</span><br><span class="line">└── test</span><br><span class="line">    ├── CMakeLists.txt</span><br><span class="line">    └── main.cc</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>目录名称</th><th>目录说明</th></tr></thead><tbody><tr><td><code>build</code></td><td>CMake 编译构建项目的目录（项目首次编译后才会有）</td></tr><tr><td><code>bin</code></td><td>存放项目编译生成的可执行文件的目录（项目首次编译后才会有）</td></tr><tr><td><code>lib</code></td><td>存放项目编译生成的 MyMuduo 动态链接库的目录（项目首次编译后才会有）</td></tr><tr><td><code>src</code></td><td>MyMuduo 网络库的源码</td></tr><tr><td><code>src/include</code></td><td>MyMuduo 网络库的头文件</td></tr><tr><td><code>test</code></td><td>MyMuduo 网络库的的测试代码</td></tr><tr><td><code>example</code></td><td>各种案例代码</td></tr><tr><td><code>example/epoll</code></td><td>epoll 的使用案例代码</td></tr><tr><td><code>example/mymuduo</code></td><td>MyMuduo 网络库的使用案例代码</td></tr><tr><td><code>autobuild.sh</code></td><td>项目一键编译构建的脚本文件</td></tr></tbody></table><h3 id="项目技术栈"><a href="#项目技术栈" class="headerlink" title="项目技术栈"></a>项目技术栈</h3><p>基于 C++ 开发网络库时，使用到以下技术：</p><ul><li>单例设计模式</li><li> epoll 等 I/O 多路复用技术</li><li> Linux 网络编程基础（<code>socket()</code>、<code>bind()</code>、<code>listen()</code>、<code>accept()</code>、<code>readv()</code>、<code>write()</code> 等）</li><li>C++ 11 多线程编程（<code>std::thread</code>、<code>std::unique_lock</code>、<code>std::mutex</code>、<code>std::condition_variable</code> 等）</li><li>使用 CMake 构建与集成项目的编译环境</li></ul><h3 id="项目整体架构"><a href="#项目整体架构" class="headerlink" title="项目整体架构"></a>项目整体架构</h3><p><img data-src="../../../asset/2025/11/muduo-arch-process.png"></p><div class="admonition note"><p class="admonition-title">架构图说明</p><p>在上述的架构图中，mainLoop 运行在主线程，负责监听新 TCP 连接并分发给 subLoop；而 subLoop（也称 ioLoop）运行在子线程，负责处理 TCP 连接的具体 I/O 事件（比如，读和写等）。mainLoop 与 subLoop 通过 <code>pendingFunctors</code> 异步任务队列进行线程间通信，禁止直接跨线程操作，这是为了保证某个 TCP 连接的所有 I/O 事件和连接状态操作都在同一个线程中执行，从而保证线程安全。在 Muduo 库的 <a href="../../../asset/2025/11/cplusplus-muduo-8.png">Multiple Reactors</a> 模型中，mainLoop 对应的就是 <code>mainReactor</code>（主 Reactor），而 subLoop 对应的就是 <code>subReactor</code>（子 Reactor）。</p></div><h2 id="项目代码"><a href="#项目代码" class="headerlink" title="项目代码"></a>项目代码</h2><div class="admonition note"><p class="admonition-title">代码下载</p><p>本文开发的 MyMuduo 网络库只实现了 Muduo 的核心功能，并不支持 Muduo 的定时事件机制（<code>TimerQueue</code>）、IPV6 / DNS / HTTP / RPC 协议等，完整的项目代码可以在 <a href="https://github.com/rqh656418510/c-cplusplus-study/tree/main/c%2B%2B-projects/c%2B%2B-project-mymuduo">这里</a> 下载得到。</p></div><h3 id="copyable"><a href="#copyable" class="headerlink" title="copyable"></a>copyable</h3><ul><li><code>copyable.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * copyable 类被继承以后，派生类对象可以正常地执行构造和析构操作，同时派生类对象还可以进行拷贝构造和赋值操作</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">copyable</span> {</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="built_in">copyable</span>() = <span class="keyword">default</span>;</span><br><span class="line">    ~<span class="built_in">copyable</span>() = <span class="keyword">default</span>;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><h3 id="noncopyable"><a href="#noncopyable" class="headerlink" title="noncopyable"></a>noncopyable</h3><ul><li><code>noncopyable.h</code></li></ul><figure class="highlight c++"><table><tbody><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">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * noncopyable 类被继承以后，派生类对象可以正常地执行构造和析构操作，但是派生类对象不能进行拷贝构造和赋值操作</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">noncopyable</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">noncopyable</span>(<span class="keyword">const</span> noncopyable&amp;) = <span class="keyword">delete</span>;</span><br><span class="line">    <span class="keyword">void</span> <span class="keyword">operator</span>=(<span class="keyword">const</span> noncopyable&amp;) = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="built_in">noncopyable</span>() = <span class="keyword">default</span>;</span><br><span class="line">    ~<span class="built_in">noncopyable</span>() = <span class="keyword">default</span>;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><h3 id="Logger"><a href="#Logger" class="headerlink" title="Logger"></a>Logger</h3><ul><li><code>Logger.h</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"CurrentThread.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义宏</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOG_DEBUG(logmsgformat, ...)                        \</span></span><br><span class="line"><span class="meta">    do {                                                    \</span></span><br><span class="line"><span class="meta">        Logger&amp; logger = Logger::instance();                \</span></span><br><span class="line"><span class="meta">        <span class="meta-keyword">if</span> (logger.getLogLevel() &lt;= DEBUG) {                \</span></span><br><span class="line"><span class="meta">            char c[1024] = {0};                             \</span></span><br><span class="line"><span class="meta">            snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \</span></span><br><span class="line"><span class="meta">            int tid = CurrentThread::tid();                 \</span></span><br><span class="line"><span class="meta">            LogMessage msg = {DEBUG, c, tid};               \</span></span><br><span class="line"><span class="meta">            logger.log(msg);                                \</span></span><br><span class="line"><span class="meta">        }                                                   \</span></span><br><span class="line"><span class="meta">    } while (0)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOG_INFO(logmsgformat, ...)                         \</span></span><br><span class="line"><span class="meta">    do {                                                    \</span></span><br><span class="line"><span class="meta">        Logger&amp; logger = Logger::instance();                \</span></span><br><span class="line"><span class="meta">        <span class="meta-keyword">if</span> (logger.getLogLevel() &lt;= INFO) {                 \</span></span><br><span class="line"><span class="meta">            char c[1024] = {0};                             \</span></span><br><span class="line"><span class="meta">            snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \</span></span><br><span class="line"><span class="meta">            int tid = CurrentThread::tid();                 \</span></span><br><span class="line"><span class="meta">            LogMessage msg = {INFO, c, tid};                \</span></span><br><span class="line"><span class="meta">            logger.log(msg);                                \</span></span><br><span class="line"><span class="meta">        }                                                   \</span></span><br><span class="line"><span class="meta">    } while (0)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOG_WARN(logmsgformat, ...)                         \</span></span><br><span class="line"><span class="meta">    do {                                                    \</span></span><br><span class="line"><span class="meta">        Logger&amp; logger = Logger::instance();                \</span></span><br><span class="line"><span class="meta">        <span class="meta-keyword">if</span> (logger.getLogLevel() &lt;= WARN) {                 \</span></span><br><span class="line"><span class="meta">            char c[1024] = {0};                             \</span></span><br><span class="line"><span class="meta">            snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \</span></span><br><span class="line"><span class="meta">            int tid = CurrentThread::tid();                 \</span></span><br><span class="line"><span class="meta">            LogMessage msg = {WARN, c, tid};                \</span></span><br><span class="line"><span class="meta">            logger.log(msg);                                \</span></span><br><span class="line"><span class="meta">        }                                                   \</span></span><br><span class="line"><span class="meta">    } while (0)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOG_ERROR(logmsgformat, ...)                        \</span></span><br><span class="line"><span class="meta">    do {                                                    \</span></span><br><span class="line"><span class="meta">        Logger&amp; logger = Logger::instance();                \</span></span><br><span class="line"><span class="meta">        <span class="meta-keyword">if</span> (logger.getLogLevel() &lt;= ERROR) {                \</span></span><br><span class="line"><span class="meta">            char c[1024] = {0};                             \</span></span><br><span class="line"><span class="meta">            snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \</span></span><br><span class="line"><span class="meta">            int tid = CurrentThread::tid();                 \</span></span><br><span class="line"><span class="meta">            LogMessage msg = {ERROR, c, tid};               \</span></span><br><span class="line"><span class="meta">            logger.log(msg);                                \</span></span><br><span class="line"><span class="meta">        }                                                   \</span></span><br><span class="line"><span class="meta">    } while (0)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOG_FATAL(logmsgformat, ...)                              \</span></span><br><span class="line"><span class="meta">    do {                                                          \</span></span><br><span class="line"><span class="meta">        Logger&amp; logger = Logger::instance();                      \</span></span><br><span class="line"><span class="meta">        <span class="meta-keyword">if</span> (logger.getLogLevel() &lt;= FATAL) {                      \</span></span><br><span class="line"><span class="meta">            char c[1024] = {0};                                   \</span></span><br><span class="line"><span class="meta">            snprintf(c, 1024, logmsgformat, ##__VA_ARGS__);       \</span></span><br><span class="line"><span class="meta">            int tid = CurrentThread::tid();                       \</span></span><br><span class="line"><span class="meta">            LogMessage msg = {FATAL, c, tid};                     \</span></span><br><span class="line"><span class="meta">            logger.log(msg);                                      \</span></span><br><span class="line"><span class="meta">            std::this_thread::sleep_for(std::chrono::seconds(1)); \</span></span><br><span class="line"><span class="meta">            exit(-1);                                             \</span></span><br><span class="line"><span class="meta">        }                                                         \</span></span><br><span class="line"><span class="meta">    } while (0)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日志级别（DEBUG &lt; INFO &lt; WARN &lt; ERROR &lt; FATAL）</span></span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">LogLevel</span> {</span></span><br><span class="line">    DEBUG,  <span class="comment">// 调试日志信息</span></span><br><span class="line">    INFO,   <span class="comment">// 普通日志信息</span></span><br><span class="line">    WARN,   <span class="comment">// 警告日志信息</span></span><br><span class="line">    ERROR,  <span class="comment">// 错误日志信息</span></span><br><span class="line">    FATAL   <span class="comment">// 致命错误信息</span></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="class"><span class="keyword">struct</span> <span class="title">LogMessage</span> {</span></span><br><span class="line">    LogLevel logLevel_;       <span class="comment">// 日志级别</span></span><br><span class="line">    std::string logContent_;  <span class="comment">// 日志内容</span></span><br><span class="line">    <span class="keyword">int</span> threadId_;            <span class="comment">// 打印日志的线程的 ID</span></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="class"><span class="keyword">class</span> <span class="title">Logger</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 获取单例对象</span></span><br><span class="line">    <span class="function"><span class="keyword">static</span> Logger&amp; <span class="title">instance</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 输出日志信息</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">log</span><span class="params">(<span class="keyword">const</span> LogMessage&amp; message)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取日志级别</span></span><br><span class="line">    <span class="function">LogLevel <span class="title">getLogLevel</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置日志级别</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setLogLevel</span><span class="params">(LogLevel level)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 私有构造函数</span></span><br><span class="line">    <span class="built_in">Logger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 私有析构函数</span></span><br><span class="line">    ~<span class="built_in">Logger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取日志级别的名称</span></span><br><span class="line">    <span class="function">std::string <span class="title">logLevelToString</span><span class="params">(LogLevel level)</span></span>;</span><br><span class="line"></span><br><span class="line">    LogLevel logLevel_;  <span class="comment">// 记录日志级别</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Logger.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sstream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Timestamp.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义宏（设置 Debug 模式）</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> MYMUDUO_DEBUG</span></span><br><span class="line">    <span class="keyword">constexpr</span> <span class="keyword">bool</span> kIsDebugMode = <span class="literal">true</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">    <span class="keyword">constexpr</span> <span class="keyword">bool</span> kIsDebugMode = <span class="literal">false</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义宏（跨平台获取当前调用的函数名称）</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(__GNUC__) || defined(__clang__)</span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">define</span> FUNC_NAME __PRETTY_FUNCTION__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> defined(_MSC_VER)</span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">define</span> FUNC_NAME __FUNCSIG__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">define</span> FUNC_NAME __func__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Logger::<span class="built_in">Logger</span>() {</span><br><span class="line">    <span class="comment">// 设置默认的日志级别</span></span><br><span class="line">    <span class="keyword">this</span>-&gt;logLevel_ = !kIsDebugMode ? INFO : DEBUG;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Logger::~<span class="built_in">Logger</span>() {</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="function">Logger&amp; <span class="title">Logger::instance</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 静态局部变量（线程安全）</span></span><br><span class="line">    <span class="keyword">static</span> Logger logger;</span><br><span class="line">    <span class="keyword">return</span> logger;</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="function"><span class="keyword">void</span> <span class="title">Logger::log</span><span class="params">(<span class="keyword">const</span> LogMessage&amp; message)</span> </span>{</span><br><span class="line">    <span class="comment">// 首先在外面构建好完整的字符串（避免多次 &lt;&lt; 竞争）</span></span><br><span class="line">    std::ostringstream oss;</span><br><span class="line">    oss &lt;&lt; Timestamp::<span class="built_in">now</span>().<span class="built_in">toString</span>() &lt;&lt; <span class="string">" =&gt; "</span> &lt;&lt; message.threadId_ &lt;&lt; <span class="string">" ["</span> &lt;&lt; <span class="built_in">logLevelToString</span>(message.logLevel_)</span><br><span class="line">        &lt;&lt; <span class="string">"] "</span> &lt;&lt; message.logContent_ &lt;&lt; <span class="string">'\n'</span>;</span><br><span class="line"></span><br><span class="line">    std::string s = oss.<span class="built_in">str</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 然后一次性写入，不使用 std::endl（避免隐式 flush）</span></span><br><span class="line">    std::<span class="built_in">fwrite</span>(s.<span class="built_in">data</span>(), <span class="number">1</span>, s.<span class="built_in">size</span>(), stdout);</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="function"><span class="keyword">void</span> <span class="title">Logger::setLogLevel</span><span class="params">(LogLevel level)</span> </span>{</span><br><span class="line">    <span class="keyword">this</span>-&gt;logLevel_ = level;</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="function">LogLevel <span class="title">Logger::getLogLevel</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>-&gt;logLevel_;</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="function">std::string <span class="title">Logger::logLevelToString</span><span class="params">(LogLevel level)</span> </span>{</span><br><span class="line">    <span class="built_in"><span class="keyword">switch</span></span> (level) {</span><br><span class="line">        <span class="keyword">case</span> DEBUG:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"DEBUG"</span>;</span><br><span class="line">        <span class="keyword">case</span> INFO:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"INFO"</span>;</span><br><span class="line">        <span class="keyword">case</span> WARN:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"WARN"</span>;</span><br><span class="line">        <span class="keyword">case</span> ERROR:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"ERROR"</span>;</span><br><span class="line">        <span class="keyword">case</span> FATAL:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"FATAL"</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"UNKNOWN"</span>;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Timestamp"><a href="#Timestamp" class="headerlink" title="Timestamp"></a>Timestamp</h3><ul><li><code>Timestamp.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"copyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 时间戳类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Timestamp</span> :</span> <span class="keyword">public</span> copyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 默认构造函数，初始化为 0 微秒</span></span><br><span class="line">    <span class="built_in">Timestamp</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数，使用微秒数进行初始化</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">Timestamp</span><span class="params">(<span class="keyword">int64_t</span> microSecondsSinceEpochArg)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将时间戳转换为字符串表示（比如 2025-11-16 17:45:30）</span></span><br><span class="line">    <span class="function">std::string <span class="title">toString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前时间戳</span></span><br><span class="line">    <span class="function"><span class="keyword">static</span> Timestamp <span class="title">now</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int64_t</span> microSecondsSinceEpoch_;  <span class="comment">// 自纪元（1970年1月1日）以来的微秒数</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Timestamp.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Timestamp.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认构造函数，初始化为 0 微秒</span></span><br><span class="line">Timestamp::<span class="built_in">Timestamp</span>()</span><br><span class="line">    : <span class="built_in">microSecondsSinceEpoch_</span>(<span class="number">0</span>){</span><br><span class="line"></span><br><span class="line">      };</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数，使用微秒数进行初始化</span></span><br><span class="line">Timestamp::<span class="built_in">Timestamp</span>(<span class="keyword">int64_t</span> microSecondsSinceEpochArg)</span><br><span class="line">    : <span class="built_in">microSecondsSinceEpoch_</span>(microSecondsSinceEpochArg){</span><br><span class="line"></span><br><span class="line">      };</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将时间戳转换为字符串表示（比如 2025-11-16 17:45:30）</span></span><br><span class="line"><span class="function">std::string <span class="title">Timestamp::toString</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">128</span>] = {<span class="number">0</span>};</span><br><span class="line">    tm *tm_time = <span class="built_in">localtime</span>(&amp;microSecondsSinceEpoch_);</span><br><span class="line">    <span class="built_in">snprintf</span>(buf, <span class="number">128</span>, <span class="string">"%4d-%02d-%02d %02d:%02d:%02d"</span>, tm_time-&gt;tm_year + <span class="number">1900</span>, tm_time-&gt;tm_mon + <span class="number">1</span>, tm_time-&gt;tm_mday,</span><br><span class="line">             tm_time-&gt;tm_hour, tm_time-&gt;tm_min, tm_time-&gt;tm_sec);</span><br><span class="line">    <span class="keyword">return</span> buf;</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="function">Timestamp <span class="title">Timestamp::now</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Timestamp</span>(<span class="built_in">time</span>(<span class="literal">NULL</span>));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="InetAddress"><a href="#InetAddress" class="headerlink" title="InetAddress"></a>InetAddress</h3><ul><li><code>InetAddress.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netinet/in.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"copyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 网络地址类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InetAddress</span> :</span> <span class="keyword">public</span> copyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">InetAddress</span><span class="params">(<span class="keyword">uint16_t</span> port = <span class="number">0</span>, std::string ip = <span class="string">"127.0.0.1"</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">InetAddress</span><span class="params">(<span class="keyword">const</span> sockaddr_in&amp; addr)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 IP 地址字符串</span></span><br><span class="line">    <span class="function">std::string <span class="title">toIp</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 IP 地址和端口号字符串（比如 127.0.0.1:8080）</span></span><br><span class="line">    <span class="function">std::string <span class="title">toIpPort</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取端口号</span></span><br><span class="line">    <span class="function"><span class="keyword">uint16_t</span> <span class="title">toPort</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取底层的 sockaddr_in 结构体指针</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> sockaddr_in* <span class="title">getSockAddr</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置底层的 sockaddr_in 结构体</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setSockAddr</span><span class="params">(<span class="keyword">const</span> sockaddr_in&amp; addr)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    sockaddr_in addr_;  <span class="comment">// 底层的 sockaddr_in 结构体</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>InetAddress.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"InetAddress.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;arpa/inet.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;strings.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">InetAddress::<span class="built_in">InetAddress</span>(<span class="keyword">uint16_t</span> port, std::string ip) {</span><br><span class="line">    <span class="built_in">bzero</span>(&amp;addr_, <span class="keyword">sizeof</span> addr_);</span><br><span class="line">    addr_.sin_family = AF_INET;</span><br><span class="line">    addr_.sin_port = <span class="built_in">htons</span>(port);</span><br><span class="line">    addr_.sin_addr.s_addr = <span class="built_in">inet_addr</span>(ip.<span class="built_in">c_str</span>());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">InetAddress::<span class="built_in">InetAddress</span>(<span class="keyword">const</span> sockaddr_in&amp; addr) {</span><br><span class="line">    <span class="keyword">this</span>-&gt;addr_ = addr;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 IP 地址字符串</span></span><br><span class="line"><span class="function">std::string <span class="title">InetAddress::toIp</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">64</span>] = {<span class="number">0</span>};</span><br><span class="line">    ::<span class="built_in">inet_ntop</span>(AF_INET, &amp;addr_.sin_addr, buf, <span class="keyword">sizeof</span> buf);</span><br><span class="line">    <span class="keyword">return</span> buf;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 IP 地址和端口号字符串（比如 127.0.0.1:8080）</span></span><br><span class="line"><span class="function">std::string <span class="title">InetAddress::toIpPort</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">64</span>] = {<span class="number">0</span>};</span><br><span class="line">    ::<span class="built_in">inet_ntop</span>(AF_INET, &amp;addr_.sin_addr, buf, <span class="keyword">sizeof</span> buf);</span><br><span class="line">    <span class="keyword">size_t</span> end = <span class="built_in">strlen</span>(buf);</span><br><span class="line">    <span class="keyword">uint16_t</span> port = <span class="built_in">ntohs</span>(addr_.sin_port);</span><br><span class="line">    <span class="built_in">sprintf</span>(buf + end, <span class="string">":%u"</span>, port);</span><br><span class="line">    <span class="keyword">return</span> buf;</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="function"><span class="keyword">uint16_t</span> <span class="title">InetAddress::toPort</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">ntohs</span>(addr_.sin_port);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取底层的 sockaddr_in 结构体指针</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> sockaddr_in* <span class="title">InetAddress::getSockAddr</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> &amp;addr_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置底层的 sockaddr_in 结构体</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">InetAddress::setSockAddr</span><span class="params">(<span class="keyword">const</span> sockaddr_in&amp; addr)</span> </span>{</span><br><span class="line">    addr_ = addr;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="SocketsOps"><a href="#SocketsOps" class="headerlink" title="SocketsOps"></a>SocketsOps</h3><ul><li><code>SocketsOps.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netinet/in.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建非阻塞的 Socket</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">createNonblockingSocket</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 Socket 错误码</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getSocketError</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断是否为自连接</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">isSelfConnect</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取本端地址</span></span><br><span class="line"><span class="function">sockaddr_in <span class="title">getLocalAddr</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取对端地址</span></span><br><span class="line"><span class="function">sockaddr_in <span class="title">getPeerAddr</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br></pre></td></tr></tbody></table></figure><ul><li><code>SocketsOps.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"SocketsOps.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;strings.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建非阻塞的 Socket</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">createNonblockingSocket</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> sockfd = ::<span class="built_in">socket</span>(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);</span><br><span class="line">    <span class="keyword">if</span> (sockfd &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; create nonblock sockfd failed, errno:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> sockfd;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 Socket 错误码</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getSocketError</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> optval;</span><br><span class="line">    <span class="keyword">socklen_t</span> optlen = <span class="keyword">sizeof</span> optval;</span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">getsockopt</span>(sockfd, SOL_SOCKET, SO_ERROR, &amp;optval, &amp;optlen) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="keyword">return</span> errno;</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="keyword">return</span> optval;</span><br><span class="line">    }</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="function">sockaddr_in <span class="title">getLocalAddr</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    sockaddr_in localaddr;</span><br><span class="line">    <span class="built_in">bzero</span>(&amp;localaddr, <span class="built_in"><span class="keyword">sizeof</span></span>(localaddr));</span><br><span class="line">    <span class="keyword">socklen_t</span> addrlen = <span class="built_in"><span class="keyword">sizeof</span></span>(localaddr);</span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">getsockname</span>(sockfd, (sockaddr*)(&amp;localaddr), &amp;addrlen) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; get socket name failed, errno:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> localaddr;</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="function">sockaddr_in <span class="title">getPeerAddr</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    sockaddr_in peeraddr;</span><br><span class="line">    <span class="built_in">bzero</span>(&amp;peeraddr, <span class="built_in"><span class="keyword">sizeof</span></span>(peeraddr));</span><br><span class="line">    <span class="keyword">socklen_t</span> addrlen = <span class="built_in"><span class="keyword">sizeof</span></span>(peeraddr);</span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">getpeername</span>(sockfd, (sockaddr*)(&amp;peeraddr), &amp;addrlen) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; get peer name failed, errno:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> peeraddr;</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="function"><span class="keyword">bool</span> <span class="title">isSelfConnect</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    sockaddr_in localaddr;</span><br><span class="line">    sockaddr_in peeraddr;</span><br><span class="line">    <span class="keyword">socklen_t</span> addrlen = <span class="built_in"><span class="keyword">sizeof</span></span>(sockaddr_in);</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">getsockname</span>(sockfd, (sockaddr*)&amp;localaddr, &amp;addrlen) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</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="keyword">if</span> (<span class="built_in">getpeername</span>(sockfd, (sockaddr*)&amp;peeraddr, &amp;addrlen) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 必须都是 IPv4</span></span><br><span class="line">    <span class="keyword">if</span> (localaddr.sin_family != AF_INET || peeraddr.sin_family != AF_INET) {</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检查 IP + 端口是否完全相同</span></span><br><span class="line">    <span class="keyword">return</span> (localaddr.sin_port == peeraddr.sin_port) &amp;&amp; (localaddr.sin_addr.s_addr == peeraddr.sin_addr.s_addr);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Channel"><a href="#Channel" class="headerlink" title="Channel"></a>Channel</h3><ul><li><code>Channel.h</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Timestamp.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Channel 可以理解为通道，封装了 socket fd 和其感兴趣的 event（事件），比如 EPOLLIN、EPOLLOUT 事件，还绑定了 Poller，返回的具体事件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Channel</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 事件回调函数类型定义</span></span><br><span class="line">    <span class="keyword">using</span> EventCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>()&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 读事件的回调函数类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ReadEventCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>(Timestamp)&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">Channel</span>(EventLoop* loop, <span class="keyword">int</span> fd);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">Channel</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// fd 得到 poller 通知以后，处理事件的函数</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleEvent</span><span class="params">(Timestamp receiveTime)</span></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="function"><span class="keyword">void</span> <span class="title">setReadCallback</span><span class="params">(ReadEventCallback cb)</span> </span>{</span><br><span class="line">        readCallback_ = std::<span class="built_in">move</span>(cb);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setWriteCallback</span><span class="params">(EventCallback cb)</span> </span>{</span><br><span class="line">        writeCallback_ = std::<span class="built_in">move</span>(cb);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setCloseCallback</span><span class="params">(EventCallback cb)</span> </span>{</span><br><span class="line">        closeCallback_ = std::<span class="built_in">move</span>(cb);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setErrorCallback</span><span class="params">(EventCallback cb)</span> </span>{</span><br><span class="line">        errorCallback_ = std::<span class="built_in">move</span>(cb);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/********** 获取和设置 fd 和 events **********/</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 socket 的 fd</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">fd</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> fd_;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 fd 感兴趣的事件</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">events</span><span class="params">()</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> events_;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 fd 上发生的具体事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_revents</span><span class="params">(<span class="keyword">int</span> revent)</span> </span>{</span><br><span class="line">        revents_ = revent;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/********** 设置 fd 相应的事件状态 **********/</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 开启监听 fd 上的读事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">enableReading</span><span class="params">()</span> </span>{</span><br><span class="line">        events_ |= kReadEvent;</span><br><span class="line">        <span class="built_in">update</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭监听 fd 上的读事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">disableReading</span><span class="params">()</span> </span>{</span><br><span class="line">        events_ &amp;= ~kReadEvent;</span><br><span class="line">        <span class="built_in">update</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 开启监听 fd 上的写事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">enableWriting</span><span class="params">()</span> </span>{</span><br><span class="line">        events_ |= kWriteEvent;</span><br><span class="line">        <span class="built_in">update</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭监听 fd 上的写事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">disableWriting</span><span class="params">()</span> </span>{</span><br><span class="line">        events_ &amp;= ~kWriteEvent;</span><br><span class="line">        <span class="built_in">update</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 禁止监听 fd 上的所有事件（读 + 写）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">disableAll</span><span class="params">()</span> </span>{</span><br><span class="line">        events_ = kNoneEvent;</span><br><span class="line">        <span class="built_in">update</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/********** 获取 fd 当前的事件状态 **********/</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断当前是否没有监听任何事件（既不读也不写）</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">isNoneEvent</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> events_ == kNoneEvent;</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="function"><span class="keyword">bool</span> <span class="title">isWriting</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> events_ &amp; kWriteEvent;</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="function"><span class="keyword">bool</span> <span class="title">isReading</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> events_ &amp; kReadEvent;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回当前 Channel 在 Poller 中的状态</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">index</span><span class="params">()</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> index_;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置当前 Channel 在 Poller 中的状态</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_index</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line">        index_ = index;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 防止当 Channel 被手动 remove 掉后，Channel 还在执行事件的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">tie</span><span class="params">(<span class="keyword">const</span> std::shared_ptr&lt;<span class="keyword">void</span>&gt;&amp; obj)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从 Poller 中删除当前 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 更新 Channel 状态到 Poller 中</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">update</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理事件，有了 guard 之后，Channel 就不会在被手动 remove 掉后还继续执行事件的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleEventWithGuard</span><span class="params">(Timestamp receiveTime)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 定义 Channel 支持的事件类型</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kNoneEvent;   <span class="comment">// 无事件</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kReadEvent;   <span class="comment">// 读事件</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kWriteEvent;  <span class="comment">// 写事件</span></span><br><span class="line"></span><br><span class="line">    EventLoop* loop_;  <span class="comment">// Channel 所属的事件循环</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">int</span> fd_;     <span class="comment">// fd，是 Poller 监听的对象</span></span><br><span class="line">    <span class="keyword">int</span> events_;       <span class="comment">// 注册 fd 上感兴趣的事件</span></span><br><span class="line">    <span class="keyword">int</span> revents_;      <span class="comment">// poller 返回的 fd 上具体发生的事件</span></span><br><span class="line">    <span class="keyword">int</span> index_;        <span class="comment">// 标记 Channel 在 Poller 中的状态</span></span><br><span class="line"></span><br><span class="line">    std::weak_ptr&lt;<span class="keyword">void</span>&gt; tie_;  <span class="comment">// 用于防止 Channel 被手动 remove 掉后，Channel 还在执行事件的回调操作</span></span><br><span class="line">    <span class="keyword">bool</span> tied_;                <span class="comment">// 标记是否已绑定 tie_</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Channel 里面能够获知 fd 上最终发生的具体事件（revents_），所以由它负责调用具体事件的回调操作（即事件分发）</span></span><br><span class="line">    ReadEventCallback readCallback_;  <span class="comment">// 读事件的回调函数</span></span><br><span class="line">    EventCallback writeCallback_;     <span class="comment">// 写事件的回调函数</span></span><br><span class="line">    EventCallback closeCallback_;     <span class="comment">// 关闭事件的回调函数</span></span><br><span class="line">    EventCallback errorCallback_;     <span class="comment">// 错误事件的回调函数</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Channel.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/epoll.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义 Channel 支持的事件类型（与 Epoll 兼容）</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Channel::kNoneEvent = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Channel::kReadEvent = EPOLLIN | EPOLLPRI;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Channel::kWriteEvent = EPOLLOUT;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Channel::<span class="built_in">Channel</span>(EventLoop* loop, <span class="keyword">int</span> fd) : <span class="built_in">loop_</span>(loop), <span class="built_in">fd_</span>(fd), <span class="built_in">events_</span>(<span class="number">0</span>), <span class="built_in">revents_</span>(<span class="number">0</span>), <span class="built_in">index_</span>(<span class="number">-1</span>), <span class="built_in">tied_</span>(<span class="literal">false</span>) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Channel::~<span class="built_in">Channel</span>() {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 防止当 Channel 被手动 remove 掉后，Channel 还在执行事件的回调操作</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Channel::tie</span><span class="params">(<span class="keyword">const</span> std::shared_ptr&lt;<span class="keyword">void</span>&gt;&amp; obj)</span> </span>{</span><br><span class="line">    tie_ = obj;</span><br><span class="line">    tied_ = <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从 Poller 中删除当前 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Channel::remove</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 通过 Channel 所属的 EventLoop，将当前的 Channel 删除掉</span></span><br><span class="line">    loop_-&gt;<span class="built_in">removeChannel</span>(<span class="keyword">this</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新 Channel 状态到 Poller 中</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Channel::update</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 通过 Channel 所属的 EventLoop，调用 Poller 相应的方法，注册 fd 的感兴趣的事件（events_）</span></span><br><span class="line">    loop_-&gt;<span class="built_in">updateChannel</span>(<span class="keyword">this</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// fd 得到 poller 通知以后，处理事件的函数</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Channel::handleEvent</span><span class="params">(Timestamp receiveTime)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (tied_) {</span><br><span class="line">        std::shared_ptr&lt;<span class="keyword">void</span>&gt; guad = tie_.<span class="built_in">lock</span>();</span><br><span class="line">        <span class="keyword">if</span> (guad) {</span><br><span class="line">            <span class="built_in">handleEventWithGuard</span>(receiveTime);</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">handleEventWithGuard</span>(receiveTime);</span><br><span class="line">    }</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"> * 处理事件，有了 guard 之后，Channel 就不会在被手动 remove 掉后还继续执行事件的回调操作了</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * EPOLLIN：可读，文件描述符有数据可读且读取不会阻塞（如 socket 或 pipe 有数据）。</span></span><br><span class="line"><span class="comment"> * EPOLLOUT：可写，文件描述符可以写入且不会阻塞（如 socket 可发送数据、pipe 可写入）。</span></span><br><span class="line"><span class="comment"> * EPOLLERR：错误，文件描述符发生错误，无法正常读写（如 TCP reset、I/O 错误）。</span></span><br><span class="line"><span class="comment"> * EPOLLHUP：挂断，文件描述符被挂断（如对端关闭连接）。注意：通常与 EPOLLIN 一起出现。</span></span><br><span class="line"><span class="comment"> * EPOLLPRI：紧急数据，文件描述符有优先数据（TCP OOB 或特殊设备的紧急数据）。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Channel::handleEventWithGuard</span><span class="params">(Timestamp receiveTime)</span> </span>{</span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"channel handle event, revents: %d"</span>, revents_);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发生挂断事件且没有读事件发生</span></span><br><span class="line">    <span class="keyword">if</span> ((revents_ &amp; EPOLLHUP) &amp;&amp; !(revents_ &amp; EPOLLIN)) {</span><br><span class="line">        <span class="keyword">if</span> (closeCallback_) {</span><br><span class="line">            <span class="built_in">closeCallback_</span>();</span><br><span class="line">        }</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="keyword">if</span> (revents_ &amp; EPOLLERR) {</span><br><span class="line">        <span class="keyword">if</span> (errorCallback_) {</span><br><span class="line">            <span class="built_in">errorCallback_</span>();</span><br><span class="line">        }</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="keyword">if</span> (revents_ &amp; (EPOLLIN | EPOLLPRI | EPOLLHUP)) {</span><br><span class="line">        <span class="keyword">if</span> (readCallback_) {</span><br><span class="line">            <span class="built_in">readCallback_</span>(receiveTime);</span><br><span class="line">        }</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="keyword">if</span> (revents_ &amp; EPOLLOUT) {</span><br><span class="line">        <span class="keyword">if</span> (writeCallback_) {</span><br><span class="line">            <span class="built_in">writeCallback_</span>();</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Poller"><a href="#Poller" class="headerlink" title="Poller"></a>Poller</h3><ul><li><code>Poller.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unordered_map&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Timestamp.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Channel</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * I/O 多路复用器抽象类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Poller</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// Channel 列表类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ChannelList = std::vector&lt;Channel*&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">Poller</span>(EventLoop* loop);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 虚析构函数</span></span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">Poller</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/********** 统一定义所有 I/O 多路复用器的接口 **********/</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听就绪事件，返回活跃的 Channel 列表</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> Timestamp <span class="title">poll</span><span class="params">(<span class="keyword">int</span> timeoutMs, ChannelList* activeChannels)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">updateChannel</span><span class="params">(Channel* channel)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">removeChannel</span><span class="params">(Channel* channel)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断 Poller 中是否存在某个 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">hasChannel</span><span class="params">(Channel* channel)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/********** 创建 I/O 多路复用器实例 **********/</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> Poller* <span class="title">newDefaultPoller</span><span class="params">(EventLoop* loop)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="comment">// Channel 集合的类型定义，key 是 fd，而 value 是 fd 所属的 Channel</span></span><br><span class="line">    <span class="keyword">using</span> ChannelMap = std::unordered_map&lt;<span class="keyword">int</span>, Channel*&gt;;</span><br><span class="line"></span><br><span class="line">    ChannelMap channels_;  <span class="comment">// 保存所有的 Channel</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    EventLoop* owerLoop_;  <span class="comment">// Poller 所属的事件循环</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Poller.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Poller.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Poller::<span class="built_in">Poller</span>(EventLoop* loop) : <span class="built_in">owerLoop_</span>(loop) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 虚析构函数</span></span><br><span class="line">Poller::~<span class="built_in">Poller</span>() {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断 Poller 中是否存在某个 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">Poller::hasChannel</span><span class="params">(Channel* channel)</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">auto</span> iterator = channels_.<span class="built_in">find</span>(channel-&gt;<span class="built_in">fd</span>());</span><br><span class="line">    <span class="keyword">return</span> iterator != channels_.<span class="built_in">end</span>() &amp;&amp; iterator-&gt;second == channel;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li><code>DefaultPoller.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EPollPoller.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Poller.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建默认的 I/O 多路复用器</span></span><br><span class="line"><span class="function">Poller* <span class="title">Poller::newDefaultPoller</span><span class="params">(EventLoop* loop)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">getenv</span>(<span class="string">"MYMUDUO_USE_POLL"</span>)) {</span><br><span class="line">        <span class="comment">// 创建 Poll 的实例</span></span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"not support poll, only support epoll"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 创建 Epoll 的实例</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">EPollPoller</span>(loop);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Epoller"><a href="#Epoller" class="headerlink" title="Epoller"></a>Epoller</h3><ul><li><code>EPollPoller.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/epoll.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Poller.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Timestamp.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 基于 Epoll 的 I/O 多路复用器</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EPollPoller</span> :</span> <span class="keyword">public</span> Poller {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">EPollPoller</span>(EventLoop* loop);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">EPollPoller</span>() <span class="keyword">override</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听就绪事件，返回活跃的 Channel 列表</span></span><br><span class="line">    <span class="function">Timestamp <span class="title">poll</span><span class="params">(<span class="keyword">int</span> timeoutMs, ChannelList* activeChannels)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">updateChannel</span><span class="params">(Channel* channel)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">removeChannel</span><span class="params">(Channel* channel)</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// Epoll 事件列表的初始大小</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kInitEventListSize;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 填充活跃的 Channel 列表</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">fillActiveChannels</span><span class="params">(<span class="keyword">int</span> numEvents, ChannelList* activeChannels)</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 更新 Channel，其中 operation 参数的值有以下几种</span></span><br><span class="line"><span class="comment">     * EPOLL_CTL_ADD   添加 fd 到 Epoll 实例</span></span><br><span class="line"><span class="comment">     * EPOLL_CTL_DEL   从 Epoll 实例中删除 fd</span></span><br><span class="line"><span class="comment">     * EPOLL_CTL_MOD   修改 fd 的监听事件</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">int</span> operation, Channel* channel)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Epoll 事件列表类型定义</span></span><br><span class="line">    <span class="keyword">using</span> EventList = std::vector&lt;::epoll_event&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> epollfd_;       <span class="comment">// Epoll 文件描述符（Epoll 监听的对象）</span></span><br><span class="line">    EventList events_;  <span class="comment">// Epoll 事件列表</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>EPollPoller.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EPollPoller.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;strings.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"error.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"unistd.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义 Epoll 事件列表的初始大小</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> EPollPoller::kInitEventListSize = <span class="number">16</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义 Channel 在 Epoll 中的状态</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> kNew = <span class="number">-1</span>;     <span class="comment">// 新创建的 Channel</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> kAdded = <span class="number">1</span>;    <span class="comment">// 已经添加到 Epoll 中的 Channel</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> kDeleted = <span class="number">2</span>;  <span class="comment">// 已经从 Epoll 中移除的 Channel</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">EPollPoller::<span class="built_in">EPollPoller</span>(EventLoop* loop)</span><br><span class="line">    : <span class="built_in">Poller</span>(loop), <span class="built_in">epollfd_</span>(::<span class="built_in">epoll_create1</span>(EPOLL_CLOEXEC)), <span class="built_in">events_</span>(kInitEventListSize) {</span><br><span class="line">    <span class="comment">// 如果创建 Epoll 文件描述符失败，则记录日志并终止程序</span></span><br><span class="line">    <span class="keyword">if</span> (epollfd_ &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; epoll_create1() error:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">EPollPoller::~<span class="built_in">EPollPoller</span>() {</span><br><span class="line">    <span class="comment">// 关闭 Epoll 文件描述符</span></span><br><span class="line">    ::<span class="built_in">close</span>(epollfd_);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听就绪事件，返回活跃的 Channel 列表</span></span><br><span class="line"><span class="function">Timestamp <span class="title">EPollPoller::poll</span><span class="params">(<span class="keyword">int</span> timeoutMs, ChannelList* activeChannels)</span> </span>{</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; fd total count:%lu"</span>, __PRETTY_FUNCTION__, channels_.<span class="built_in">size</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听就绪事件，会阻塞当前线程，超时等待返回 0（表示本次等待期间没有任何就绪事件发生）</span></span><br><span class="line">    <span class="keyword">int</span> numEvents = ::<span class="built_in">epoll_wait</span>(epollfd_, &amp;*events_.<span class="built_in">begin</span>(), <span class="keyword">static_cast</span>&lt;<span class="keyword">int</span>&gt;(events_.<span class="built_in">size</span>()), timeoutMs);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 保存错误码</span></span><br><span class="line">    <span class="keyword">int</span> savedErrno = errno;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前时间戳</span></span><br><span class="line">    <span class="function">Timestamp <span class="title">now</span><span class="params">(Timestamp::now())</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果有就绪事件发生</span></span><br><span class="line">    <span class="keyword">if</span> (numEvents &gt; <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; epoll happend %d events"</span>, __PRETTY_FUNCTION__, numEvents);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 填充活跃的 Channel 列表</span></span><br><span class="line">        <span class="built_in">fillActiveChannels</span>(numEvents, activeChannels);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果本次监听返回的就绪事件数量等于当前 Epoll 事件列表的大小，则将 Epoll 事件列表的容量扩大一倍</span></span><br><span class="line">        <span class="keyword">if</span> (numEvents == events_.<span class="built_in">size</span>()) {</span><br><span class="line">            events_.<span class="built_in">resize</span>(events_.<span class="built_in">size</span>() * <span class="number">2</span>);</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="keyword">else</span> <span class="keyword">if</span> (numEvents == <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; epoll wait timeout, nothing happened"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 如果监听出错</span></span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 只有在错误码不是 EINTR（系统调用被中断）时，才记录错误日志</span></span><br><span class="line">        <span class="keyword">if</span> (savedErrno != EINTR) {</span><br><span class="line">            <span class="comment">// 恢复错误码</span></span><br><span class="line">            errno = savedErrno;</span><br><span class="line">            <span class="comment">// 打印日志信息</span></span><br><span class="line">            <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; epoll wait error"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> now;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 填充活跃的 Channel 列表</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EPollPoller::fillActiveChannels</span><span class="params">(<span class="keyword">int</span> numEvents, ChannelList* activeChannels)</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="comment">// 遍历所有就绪的事件</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; numEvents; ++i) {</span><br><span class="line">        <span class="comment">// 获取就绪的 Channel</span></span><br><span class="line">        Channel* channel = <span class="keyword">static_cast</span>&lt;Channel*&gt;(events_[i].data.ptr);</span><br><span class="line">        <span class="comment">// 设置 Channel 上发生的具体事件</span></span><br><span class="line">        channel-&gt;<span class="built_in">set_revents</span>(events_[i].events);</span><br><span class="line">        <span class="comment">// 将就绪的 Channel 添加到活跃的 Channel 列表中</span></span><br><span class="line">        activeChannels-&gt;<span class="built_in">push_back</span>(channel);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EPollPoller::updateChannel</span><span class="params">(Channel* channel)</span> </span>{</span><br><span class="line">    <span class="comment">// 获取 Channel 在 Epoll 中的状态</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">int</span> index = channel-&gt;<span class="built_in">index</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; fd=%d events=%d index=%d"</span>, __PRETTY_FUNCTION__, channel-&gt;<span class="built_in">fd</span>(), channel-&gt;<span class="built_in">events</span>(), index);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (index == kNew || index == kDeleted) {</span><br><span class="line">        <span class="keyword">if</span> (index == kNew) {</span><br><span class="line">            <span class="comment">// 获取 socket 的 fd</span></span><br><span class="line">            <span class="keyword">int</span> fd = channel-&gt;<span class="built_in">fd</span>();</span><br><span class="line">            <span class="comment">// 将 Channel 添加到 Channel 集合中</span></span><br><span class="line">            channels_[fd] = channel;</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 更新 Channel 在 Epoll 中的状态</span></span><br><span class="line">        channel-&gt;<span class="built_in">set_index</span>(kAdded);</span><br><span class="line">        <span class="built_in">update</span>(EPOLL_CTL_ADD, channel);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 获取 socket 的 fd</span></span><br><span class="line">        <span class="keyword">int</span> fd = channel-&gt;<span class="built_in">fd</span>();</span><br><span class="line">        <span class="comment">// 如果当前没有任何事件感兴趣，则将 Channel 从 Epoll 中删除</span></span><br><span class="line">        <span class="keyword">if</span> (channel-&gt;<span class="built_in">isNoneEvent</span>()) {</span><br><span class="line">            <span class="built_in">update</span>(EPOLL_CTL_DEL, channel);</span><br><span class="line">            channel-&gt;<span class="built_in">set_index</span>(kDeleted);</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 否则，更新 Channel 的状态</span></span><br><span class="line">        <span class="keyword">else</span> {</span><br><span class="line">            <span class="built_in">update</span>(EPOLL_CTL_MOD, channel);</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">// 更新 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EPollPoller::update</span><span class="params">(<span class="keyword">int</span> operation, Channel* channel)</span> </span>{</span><br><span class="line">    <span class="comment">// 获取 socket 的 fd</span></span><br><span class="line">    <span class="keyword">int</span> fd = channel-&gt;<span class="built_in">fd</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Epoll 事件</span></span><br><span class="line">    ::epoll_event event;</span><br><span class="line">    <span class="built_in">bzero</span>(&amp;event, <span class="keyword">sizeof</span> event);</span><br><span class="line">    event.data.ptr = channel;</span><br><span class="line">    event.events = channel-&gt;<span class="built_in">events</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 fd 相应的 Epoll 事件（使用 Channel 中记录的 interests）</span></span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">epoll_ctl</span>(epollfd_, operation, fd, &amp;event) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="keyword">if</span> (operation == EPOLL_CTL_DEL) {</span><br><span class="line">            <span class="built_in">LOG_ERROR</span>(<span class="string">"epoll_ctl delete error:%d"</span>, errno);</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="built_in">LOG_FATAL</span>(<span class="string">"epoll_ctl add or mod error:%d"</span>, errno);</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">// 移除 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EPollPoller::removeChannel</span><span class="params">(Channel* channel)</span> </span>{</span><br><span class="line">    <span class="comment">// 获取 socket 的 fd</span></span><br><span class="line">    <span class="keyword">int</span> fd = channel-&gt;<span class="built_in">fd</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从 Channel 集合中将 fd 对应的 Channel 移除掉</span></span><br><span class="line">    channels_.<span class="built_in">erase</span>(fd);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; fd=%d"</span>, __PRETTY_FUNCTION__, fd);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 Channel 在 Epoll 中的状态</span></span><br><span class="line">    <span class="keyword">int</span> index = channel-&gt;<span class="built_in">index</span>();</span><br><span class="line">    <span class="keyword">if</span> (index == kAdded) {</span><br><span class="line">        <span class="comment">// 更新 Channel</span></span><br><span class="line">        <span class="built_in">update</span>(EPOLL_CTL_DEL, channel);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新 Channel 在 Epoll 中的状态</span></span><br><span class="line">    channel-&gt;<span class="built_in">set_index</span>(kNew);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="EventLoop"><a href="#EventLoop" class="headerlink" title="EventLoop"></a>EventLoop</h3><ul><li><code>EventLoop.h</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;mutex&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Timestamp.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Channel</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Poller</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 事件循环类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 回调函数类型定义</span></span><br><span class="line">    <span class="keyword">using</span> Functor = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>()&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">EventLoop</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">EventLoop</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 开启事件循环</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">loop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 退出事件循环</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">quit</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 Poller 返回发生事件的时间点</span></span><br><span class="line">    <span class="function">Timestamp <span class="title">pollReturnTime</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在当前 EventLoop 所在的线程执行回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">runInLoop</span><span class="params">(Functor cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将回调操作添加到队列中，唤醒 EventLoop 所在的线程执行回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">queueInLoop</span><span class="params">(Functor cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒 EventLoop 所在的线程</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">wakeup</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">updateChannel</span><span class="params">(Channel* channel)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">removeChannel</span><span class="params">(Channel* channel)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断 EventLoop 中是否存在某个 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">hasChannel</span><span class="params">(Channel* channel)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断当前线程是否是 EventLoop 所在的线程</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">isInLoopThread</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果当前线程不是 EventLoop 所在的线程，则触发断言失败</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">assertInLoopThread</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果当前线程不是 EventLoop 所在的线程，则中止程序运行</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">abortNotInLoopThread</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 处理 Wakeup Channel 的读事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleRead</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行当前 EventLoop 需要执行的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">doPendingFunctors</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Channel 列表的类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ChannelList = std::vector&lt;Channel*&gt;;</span><br><span class="line"></span><br><span class="line">    std::<span class="keyword">atomic_bool</span> looping_;  <span class="comment">// 事件循环状态</span></span><br><span class="line">    std::<span class="keyword">atomic_bool</span> quit_;     <span class="comment">// 标识退出 EventLoop 循环</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">pid_t</span> threadId_;            <span class="comment">// 记录当前 EventLoop 所在的线程的 ID</span></span><br><span class="line">    Timestamp pollReturnTime_;        <span class="comment">// 记录 Poller 返回发生事件的时间点</span></span><br><span class="line">    std::unique_ptr&lt;Poller&gt; poller_;  <span class="comment">// EventLoop 使用的 Poller（I/O 多路复用器）</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> wakeupFd_;                            <span class="comment">// 用于唤醒 EventLoop 所在的线程的 fd</span></span><br><span class="line">    std::unique_ptr&lt;Channel&gt; wakeupChannel_;  <span class="comment">// 用于唤醒 EventLoop 所在的线程的 Channel</span></span><br><span class="line"></span><br><span class="line">    ChannelList activeChannels_;  <span class="comment">// 保存 Poller 返回的活跃的 Channel 列表</span></span><br><span class="line"></span><br><span class="line">    std::<span class="keyword">atomic_bool</span> callingPendingFunctors_;  <span class="comment">// 标识当前 EventLoop 是否正在执行回调操作</span></span><br><span class="line">    std::vector&lt;Functor&gt; pendingFunctors_;     <span class="comment">// 保存当前 EventLoop 需要执行的所有回调操作</span></span><br><span class="line">    std::mutex mutex_;                         <span class="comment">// 保证 pendingFunctors_ 容器线程安全的互斥锁</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>EventLoop.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/eventfd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"CurrentThread.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Poller.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义线程局部变量（thread-local），用于防止一个线程创建多个 EventLoop</span></span><br><span class="line">__thread EventLoop* t_loopInThisThread = <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义 Poller（I/O 多路复用器）的默认超时时间，比如 10 秒</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> kPollTimeMs = <span class="number">10000</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 wakeupFd，用来 Notify（唤醒）SubReactor 处理新来的 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">createEventFd</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> evtfd = ::<span class="built_in">eventfd</span>(<span class="number">0</span>, EFD_NONBLOCK | EFD_CLOEXEC);</span><br><span class="line">    <span class="keyword">if</span> (evtfd &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; eventfd error:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> evtfd;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">EventLoop::<span class="built_in">EventLoop</span>()</span><br><span class="line">    : <span class="built_in">looping_</span>(<span class="literal">false</span>),</span><br><span class="line">      <span class="built_in">quit_</span>(<span class="literal">false</span>),</span><br><span class="line">      <span class="built_in">callingPendingFunctors_</span>(<span class="literal">false</span>),</span><br><span class="line">      <span class="built_in">threadId_</span>(CurrentThread::<span class="built_in">tid</span>()),</span><br><span class="line">      <span class="built_in">poller_</span>(Poller::<span class="built_in">newDefaultPoller</span>(<span class="keyword">this</span>)),</span><br><span class="line">      <span class="built_in">wakeupFd_</span>(<span class="built_in">createEventFd</span>()),</span><br><span class="line">      <span class="built_in">wakeupChannel_</span>(<span class="keyword">new</span> <span class="built_in">Channel</span>(<span class="keyword">this</span>, wakeupFd_)) {</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; EventLoop created %p in thread %d"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>, threadId_);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 防止一个线程创建多个 EventLoop</span></span><br><span class="line">    <span class="keyword">if</span> (t_loopInThisThread) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; Another EventLoop existed in this thread %d"</span>, __PRETTY_FUNCTION__, threadId_);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 将当前 EventLoop 对象赋值给线程局部变量</span></span><br><span class="line">        t_loopInThisThread = <span class="keyword">this</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 Wakeup Channel 的读事件回调函数</span></span><br><span class="line">    wakeupChannel_-&gt;<span class="built_in">setReadCallback</span>(std::<span class="built_in">bind</span>(&amp;EventLoop::handleRead, <span class="keyword">this</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启用 Wakeup Channel 的读事件监听</span></span><br><span class="line">    wakeupChannel_-&gt;<span class="built_in">enableReading</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">EventLoop::~<span class="built_in">EventLoop</span>() {</span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; EventLoop %p of thread %d destructs in thread"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>, CurrentThread::<span class="built_in">tid</span>());</span><br><span class="line">    <span class="comment">// 关闭 Wakeup Channel</span></span><br><span class="line">    wakeupChannel_-&gt;<span class="built_in">disableAll</span>();</span><br><span class="line">    <span class="comment">// 移除 Wakeup Channel</span></span><br><span class="line">    wakeupChannel_-&gt;<span class="built_in">remove</span>();</span><br><span class="line">    <span class="comment">// 关闭 wakeupFd_</span></span><br><span class="line">    ::<span class="built_in">close</span>(wakeupFd_);</span><br><span class="line">    <span class="comment">// 重置线程局部变量</span></span><br><span class="line">    t_loopInThisThread = <span class="literal">nullptr</span>;</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="function"><span class="keyword">void</span> <span class="title">EventLoop::loop</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记事件循环开始</span></span><br><span class="line">    looping_ = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标记退出事件循环的状态</span></span><br><span class="line">    quit_ = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; EventLoop %p start looping"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!quit_) {</span><br><span class="line">        activeChannels_.<span class="built_in">clear</span>();</span><br><span class="line">        <span class="comment">// Poller 会阻塞监听有哪些 Channel 发生了事件，然后上报给 EventLoop，通知 Channel 处理相应的事件</span></span><br><span class="line">        pollReturnTime_ = poller_-&gt;<span class="built_in">poll</span>(kPollTimeMs, &amp;activeChannels_);</span><br><span class="line">        <span class="keyword">for</span> (Channel* channel : activeChannels_) {</span><br><span class="line">            channel-&gt;<span class="built_in">handleEvent</span>(pollReturnTime_);</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 执行当前 EventLoop 需要处理的回调操作</span></span><br><span class="line">        <span class="built_in">doPendingFunctors</span>();</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="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; EventLoop %p stop looping"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标记事件循环结束</span></span><br><span class="line">    looping_ = <span class="literal">false</span>;</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="function"><span class="keyword">void</span> <span class="title">EventLoop::quit</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记退出事件循环的状态</span></span><br><span class="line">    quit_ = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果不是在当前 EventLoop 所在的线程上调用的 quit() 方法，则需要唤醒 EventLoop 所在的线程</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">isInLoopThread</span>()) {</span><br><span class="line">        <span class="built_in">wakeup</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 唤醒 EventLoop 所在的线程</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::wakeup</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">uint64_t</span> one = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 向 wakeupFd_ 写一个数据，wakeupChannel_ 就会发生读事件，当前的 EventLoop 就会被唤醒</span></span><br><span class="line">    <span class="keyword">ssize_t</span> n = ::<span class="built_in">write</span>(wakeupFd_, &amp;one, <span class="keyword">sizeof</span> one);</span><br><span class="line">    <span class="keyword">if</span> (n != <span class="keyword">sizeof</span> one) {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s write %zd bytes instead of 8"</span>, __PRETTY_FUNCTION__, n);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 Poller 返回发生事件的时间点</span></span><br><span class="line"><span class="function">Timestamp <span class="title">EventLoop::pollReturnTime</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> pollReturnTime_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断当前线程是否是 EventLoop 所在的线程</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">EventLoop::isInLoopThread</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> threadId_ == CurrentThread::<span class="built_in">tid</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果当前线程不是 EventLoop 所在的线程，则触发断言失败</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::assertInLoopThread</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">isInLoopThread</span>()) {</span><br><span class="line">        <span class="built_in">abortNotInLoopThread</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果当前线程不是 EventLoop 所在的线程，则中止程序运行</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::abortNotInLoopThread</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; EventLoop %p was created in threadId_ = %d, current thread id = %d"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>,</span><br><span class="line">              threadId_, CurrentThread::<span class="built_in">tid</span>());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在当前 EventLoop 所在的线程上执行回调操作</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::runInLoop</span><span class="params">(Functor cb)</span> </span>{</span><br><span class="line">    <span class="comment">// 如果在 EventLoop 所在的线程上执行回调操作</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">isInLoopThread</span>()) {</span><br><span class="line">        <span class="comment">// 则直接执行回调操作</span></span><br><span class="line">        <span class="built_in">cb</span>();</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 否则，将回调操作添加到队列中，并唤醒 EventLoop 所在的线程执行回调操作</span></span><br><span class="line">        <span class="built_in">queueInLoop</span>(std::<span class="built_in">move</span>(cb));</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将回调操作添加到队列中，并唤醒 EventLoop 所在的线程执行回调操作</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::queueInLoop</span><span class="params">(Functor cb)</span> </span>{</span><br><span class="line">    {</span><br><span class="line">        <span class="comment">// 将回调操作添加到队列中（需要保证线程安全）</span></span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        pendingFunctors_.<span class="built_in">emplace_back</span>(cb);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果不是在当前 EventLoop 所在的线程上执行回调操作，或者当前 EventLoop 正在执行回调操作</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">isInLoopThread</span>() || callingPendingFunctors_) {</span><br><span class="line">        <span class="comment">// 则唤醒当前 EventLoop 所在的线程去执行回调操作</span></span><br><span class="line">        <span class="built_in">wakeup</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::updateChannel</span><span class="params">(Channel* channel)</span> </span>{</span><br><span class="line">    poller_-&gt;<span class="built_in">updateChannel</span>(channel);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::removeChannel</span><span class="params">(Channel* channel)</span> </span>{</span><br><span class="line">    poller_-&gt;<span class="built_in">removeChannel</span>(channel);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断 EventLoop 中是否存在某个 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">EventLoop::hasChannel</span><span class="params">(Channel* channel)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> poller_-&gt;<span class="built_in">hasChannel</span>(channel);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理 Wakeup Channel 的读事件</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::handleRead</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">uint64_t</span> one = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">ssize_t</span> n = ::<span class="built_in">read</span>(wakeupFd_, &amp;one, <span class="keyword">sizeof</span> one);</span><br><span class="line">    <span class="keyword">if</span> (n != <span class="keyword">sizeof</span> one) {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s reads %zd bytes instead of 8"</span>, __PRETTY_FUNCTION__, n);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行当前 EventLoop 需要执行的回调操作</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">EventLoop::doPendingFunctors</span><span class="params">()</span> </span>{</span><br><span class="line">    std::vector&lt;Functor&gt; functors;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标记当前 EventLoop 正在执行回调操作</span></span><br><span class="line">    callingPendingFunctors_ = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    {</span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        <span class="comment">// 将需要执行的回调操作交换到局部变量 functors 中，以减少锁的持有时间，提高运行效率</span></span><br><span class="line">        functors.<span class="built_in">swap</span>(pendingFunctors_);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行当前 EventLoop 需要执行的回调操作</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> Functor&amp; functor : functors) {</span><br><span class="line">        <span class="built_in">functor</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标记当前 EventLoop 已经执行完回调操作</span></span><br><span class="line">    callingPendingFunctors_ = <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Thread"><a href="#Thread" class="headerlink" title="Thread"></a>Thread</h3><ul><li><code>Thread.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Thread</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 线程执行函数的类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ThreadFunc = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>()&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">Thread</span><span class="params">(ThreadFunc func, <span class="keyword">const</span> std::string&amp; name = std::string())</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">Thread</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动线程</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 等待线程执行结束</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">join</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取线程 ID</span></span><br><span class="line">    <span class="function"><span class="keyword">pid_t</span> <span class="title">tid</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取线程名称</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">name</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取已创建的线程数量</span></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">numCreated</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 设置线程的默认名称</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setDefaultName</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">bool</span> started_;                         <span class="comment">// 标记线程是否已启动</span></span><br><span class="line">    <span class="keyword">bool</span> joined_;                          <span class="comment">// 标记线程是否已经 join，防止重复 join 或析构时未 join</span></span><br><span class="line">    std::shared_ptr&lt;std::thread&gt; thread_;  <span class="comment">// 线程对象</span></span><br><span class="line">    <span class="keyword">pid_t</span> tid_;                            <span class="comment">// 线程 ID</span></span><br><span class="line">    ThreadFunc func_;                      <span class="comment">// 线程执行函数</span></span><br><span class="line">    std::string name_;                     <span class="comment">// 线程名称</span></span><br><span class="line">    <span class="keyword">static</span> std::<span class="keyword">atomic_int</span> numCreated_;    <span class="comment">// 已创建的线程数量</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Thread.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Thread.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"CurrentThread.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">std::<span class="keyword">atomic_int</span> <span class="title">Thread::numCreated_</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Thread::<span class="built_in">Thread</span>(ThreadFunc func, <span class="keyword">const</span> std::string&amp; name)</span><br><span class="line">    : <span class="built_in">started_</span>(<span class="literal">false</span>), <span class="built_in">joined_</span>(<span class="literal">false</span>), <span class="built_in">tid_</span>(<span class="number">0</span>), <span class="built_in">func_</span>(std::<span class="built_in">move</span>(func)), <span class="built_in">name_</span>(name) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Thread::~<span class="built_in">Thread</span>() {</span><br><span class="line">    <span class="comment">// 如果线程已启动且未被 join</span></span><br><span class="line">    <span class="keyword">if</span> (started_ &amp;&amp; !joined_) {</span><br><span class="line">        <span class="comment">// 设置分离线程（避免资源泄露）</span></span><br><span class="line">        thread_-&gt;<span class="built_in">detach</span>();</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Thread::start</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记线程为已启动</span></span><br><span class="line">    started_ = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 声明信号量</span></span><br><span class="line">    <span class="keyword">sem_t</span> sem;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化信号量</span></span><br><span class="line">    <span class="built_in">sem_init</span>(&amp;sem, <span class="literal">false</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动新的线程</span></span><br><span class="line">    thread_ = std::shared_ptr&lt;std::thread&gt;(<span class="keyword">new</span> std::<span class="built_in">thread</span>([&amp;]() {</span><br><span class="line">        <span class="comment">// 获取新线程的 ID</span></span><br><span class="line">        tid_ = CurrentThread::<span class="built_in">tid</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 通知主线程已获取新线程的 ID</span></span><br><span class="line">        <span class="built_in">sem_post</span>(&amp;sem);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 新线程执行线程函数</span></span><br><span class="line">        <span class="built_in">func_</span>();</span><br><span class="line">    }));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 阻塞等待新线程获取线程 ID</span></span><br><span class="line">    <span class="built_in">sem_wait</span>(&amp;sem);</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="function"><span class="keyword">void</span> <span class="title">Thread::join</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 如果线程已启动且未被 join</span></span><br><span class="line">    <span class="keyword">if</span> (started_ &amp;&amp; !joined_) {</span><br><span class="line">        <span class="comment">// 标记线程已 join</span></span><br><span class="line">        joined_ = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 等待线程执行结束</span></span><br><span class="line">        thread_-&gt;<span class="built_in">join</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取线程 ID</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">Thread::tid</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> tid_;</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="function"><span class="keyword">const</span> std::string&amp; <span class="title">Thread::name</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> name_;</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="function"><span class="keyword">int</span> <span class="title">Thread::numCreated</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> numCreated_.<span class="built_in">load</span>();</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="function"><span class="keyword">void</span> <span class="title">Thread::setDefaultName</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> num = ++numCreated_;</span><br><span class="line">    <span class="keyword">if</span> (name_.<span class="built_in">empty</span>()) {</span><br><span class="line">        <span class="keyword">char</span> buf[<span class="number">32</span>] = {<span class="number">0</span>};</span><br><span class="line">        <span class="built_in">snprintf</span>(buf, <span class="keyword">sizeof</span> buf, <span class="string">"Thread%d"</span>, num);</span><br><span class="line">        name_ = buf;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="CurrentThread"><a href="#CurrentThread" class="headerlink" title="CurrentThread"></a>CurrentThread</h3><ul><li><code>CurrentThread.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> CurrentThread {</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 声明线程局部变量（thread-local），用于缓存当前线程的 ID</span></span><br><span class="line">    <span class="keyword">extern</span> __thread <span class="keyword">int</span> t_cachedTid;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 声明缓存当前线程的 ID 的函数</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">cacheTid</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前线程的 ID</span></span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="keyword">int</span> <span class="title">tid</span><span class="params">()</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (__builtin_expect(t_cachedTid == <span class="number">0</span>, <span class="number">0</span>)) {</span><br><span class="line">            <span class="built_in">cacheTid</span>();</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> t_cachedTid;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li><code>CurrentThread.cc</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"CurrentThread.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/syscall.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> CurrentThread {</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 定义线程局部变量（thread-local），用于缓存当前线程的 ID</span></span><br><span class="line">    __thread <span class="keyword">int</span> t_cachedTid = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 定义缓存当前线程的 ID 的函数</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">cacheTid</span><span class="params">()</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (t_cachedTid == <span class="number">0</span>) {</span><br><span class="line">            <span class="comment">// 通过 Linux 系统调用，获取当前线程的 ID</span></span><br><span class="line">            t_cachedTid = <span class="keyword">static_cast</span>&lt;<span class="keyword">pid_t</span>&gt;(::<span class="built_in">syscall</span>(SYS_gettid));</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="EventLoopThread"><a href="#EventLoopThread" class="headerlink" title="EventLoopThread"></a>EventLoopThread</h3><ul><li><code>EventLoopThread.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;condition_variable&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;mutex&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Thread.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 事件循环线程类，封装了 EventLoop 与 Thread</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoopThread</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 线程初始化回调操作的类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ThreadInitCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>(EventLoop *)&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">EventLoopThread</span>(<span class="keyword">const</span> ThreadInitCallback &amp;cb = <span class="built_in">ThreadInitCallback</span>(), <span class="keyword">const</span> std::string &amp;name = std::<span class="built_in">string</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">EventLoopThread</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在对应的线程中启动事件循环</span></span><br><span class="line">    <span class="function">EventLoop *<span class="title">startLoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 线程执行函数</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">threadFunc</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    EventLoop *loop_;               <span class="comment">// 事件循环</span></span><br><span class="line">    <span class="keyword">bool</span> exiting_;                  <span class="comment">// 标记线程是否正在退出</span></span><br><span class="line">    Thread thread_;                 <span class="comment">// 线程对象（EventLoop 所在的线程）</span></span><br><span class="line">    std::mutex mutex_;              <span class="comment">// 互斥锁</span></span><br><span class="line">    std::condition_variable cond_;  <span class="comment">// 条件变量</span></span><br><span class="line">    ThreadInitCallback callback_;   <span class="comment">// 线程初始化回调操作</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>EventLoopThread.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoopThread.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;EventLoop.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">EventLoopThread::<span class="built_in">EventLoopThread</span>(<span class="keyword">const</span> ThreadInitCallback &amp;cb, <span class="keyword">const</span> std::string &amp;name)</span><br><span class="line">    : <span class="built_in">loop_</span>(<span class="literal">nullptr</span>),</span><br><span class="line">      <span class="built_in">exiting_</span>(<span class="literal">false</span>),</span><br><span class="line">      <span class="built_in">thread_</span>(std::<span class="built_in">bind</span>(&amp;EventLoopThread::threadFunc, <span class="keyword">this</span>), name),</span><br><span class="line">      <span class="built_in">mutex_</span>(),</span><br><span class="line">      <span class="built_in">cond_</span>(),</span><br><span class="line">      <span class="built_in">callback_</span>(cb) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">EventLoopThread::~<span class="built_in">EventLoopThread</span>() {</span><br><span class="line">    <span class="comment">// 标记线程正在退出</span></span><br><span class="line">    exiting_ = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (loop_ != <span class="literal">nullptr</span>) {</span><br><span class="line">        <span class="comment">// 退出线程循环</span></span><br><span class="line">        loop_-&gt;<span class="built_in">quit</span>();</span><br><span class="line">        <span class="comment">// 等待线程执行结束</span></span><br><span class="line">        thread_.<span class="built_in">join</span>();</span><br><span class="line">    }</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="function">EventLoop *<span class="title">EventLoopThread::startLoop</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 启动底层新创建的线程</span></span><br><span class="line">    thread_.<span class="built_in">start</span>();</span><br><span class="line"></span><br><span class="line">    EventLoop *loop = <span class="literal">nullptr</span>;</span><br><span class="line">    {</span><br><span class="line">        <span class="comment">// 等待线程函数 threadFunc() 创建好 EventLoop 对象</span></span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        <span class="keyword">while</span> (loop_ == <span class="literal">nullptr</span>) {</span><br><span class="line">            cond_.<span class="built_in">wait</span>(lock);</span><br><span class="line">        }</span><br><span class="line">        loop = loop_;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> loop;</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="function"><span class="keyword">void</span> <span class="title">EventLoopThread::threadFunc</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 新创建一个独立的事件循环，和上面底层新创建的线程一一对应</span></span><br><span class="line">    EventLoop loop;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行线程初始化回调操作</span></span><br><span class="line">    <span class="keyword">if</span> (callback_) {</span><br><span class="line">        <span class="built_in">callback_</span>(&amp;loop);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    {</span><br><span class="line">        <span class="comment">// 将新创建的事件循环对象赋值给成员变量 loop_，需要保证线程安全</span></span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        loop_ = &amp;loop;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 通知 startLoop() 成员函数，成员变量 loop_ 已经赋值完毕</span></span><br><span class="line">        cond_.<span class="built_in">notify_one</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 开启事件循环</span></span><br><span class="line">    loop.<span class="built_in">loop</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 事件循环退出后，重置成员变量 loop_</span></span><br><span class="line">    <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">    loop_ = <span class="literal">nullptr</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="EventLoopThreadPool"><a href="#EventLoopThreadPool" class="headerlink" title="EventLoopThreadPool"></a>EventLoopThreadPool</h3><ul><li><code>EventLoopThreadPool.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoopThread</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 事件循环线程池类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoopThreadPool</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 线程初始化回调操作的类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ThreadInitCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>(EventLoop*)&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">EventLoopThreadPool</span>(EventLoop* baseLoop, <span class="keyword">const</span> std::string&amp; nameArg);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">EventLoopThreadPool</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置线程池的线程数量</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setThreadNum</span><span class="params">(<span class="keyword">int</span> numThreads)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动线程池</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">(<span class="keyword">const</span> ThreadInitCallback&amp; cb = ThreadInitCallback())</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取下一个被选中的事件循环（如果工作在多线程中，baseLoop 默认以轮询的方式分配 Channel 给 subLoop）</span></span><br><span class="line">    <span class="function">EventLoop* <span class="title">getNextLoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回所有事件循环</span></span><br><span class="line">    <span class="function">std::vector&lt;EventLoop*&gt; <span class="title">getAllLoops</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回线程池是否已启动</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">started</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回线程池的名称</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">name</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    EventLoop* baseLoop_;  <span class="comment">// 基础事件循环（通常是主线程上的事件循环，也称作 mainLoop）</span></span><br><span class="line">    std::string name_;     <span class="comment">// 线程池名称</span></span><br><span class="line">    <span class="keyword">bool</span> started_;         <span class="comment">// 标记线程池是否已启动</span></span><br><span class="line">    <span class="keyword">int</span> numThreads_;       <span class="comment">// 线程数量</span></span><br><span class="line">    <span class="keyword">int</span> next_;             <span class="comment">// 下一个被选中的事件循环的索引</span></span><br><span class="line">    std::vector&lt;std::unique_ptr&lt;EventLoopThread&gt;&gt; threads_;  <span class="comment">// 事件循环线程对象的集合</span></span><br><span class="line">    std::vector&lt;EventLoop*&gt; loops_;                          <span class="comment">// 事件循环对象的集合</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>EventLoopThreadPool.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoopThreadPool.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoopThread.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">EventLoopThreadPool::<span class="built_in">EventLoopThreadPool</span>(EventLoop* baseLoop, <span class="keyword">const</span> std::string&amp; nameArg)</span><br><span class="line">    : <span class="built_in">baseLoop_</span>(baseLoop), <span class="built_in">name_</span>(nameArg), <span class="built_in">started_</span>(<span class="literal">false</span>), <span class="built_in">numThreads_</span>(<span class="number">0</span>), <span class="built_in">next_</span>(<span class="number">0</span>) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">EventLoopThreadPool::~<span class="built_in">EventLoopThreadPool</span>() {</span><br><span class="line">    <span class="comment">// 析构时不需要删除 loop，因为它是栈变量</span></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="function"><span class="keyword">void</span> <span class="title">EventLoopThreadPool::setThreadNum</span><span class="params">(<span class="keyword">int</span> numThreads)</span> </span>{</span><br><span class="line">    numThreads_ = numThreads;</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="function"><span class="keyword">void</span> <span class="title">EventLoopThreadPool::start</span><span class="params">(<span class="keyword">const</span> ThreadInitCallback&amp; cb)</span> </span>{</span><br><span class="line">    <span class="comment">// 标记线程池已启动</span></span><br><span class="line">    started_ = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当整个服务端有多个线程（负责运行一个 baseLoop 和多个 subLoop）</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; numThreads_; ++i) {</span><br><span class="line">        <span class="comment">// 拼接线程的名称</span></span><br><span class="line">        std::string tname = name_ + std::<span class="built_in">to_string</span>(i);</span><br><span class="line">        <span class="comment">// 创建事件循环线程</span></span><br><span class="line">        EventLoopThread* t = <span class="keyword">new</span> <span class="built_in">EventLoopThread</span>(cb, tname);</span><br><span class="line">        <span class="comment">// 将事件循环线程添加到线程池中</span></span><br><span class="line">        threads_.<span class="built_in">push_back</span>(std::unique_ptr&lt;EventLoopThread&gt;(t));</span><br><span class="line">        <span class="comment">// 启动事件循环线程，并获取该线程对应的事件循环对象，将其添加到事件循环对象的集合中</span></span><br><span class="line">        loops_.<span class="built_in">push_back</span>(t-&gt;<span class="built_in">startLoop</span>());</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当整个服务端只有一个线程（负责运行 baseLoop），就执行初始化回调操作</span></span><br><span class="line">    <span class="keyword">if</span> (numThreads_ == <span class="number">0</span> &amp;&amp; cb) {</span><br><span class="line">        <span class="built_in">cb</span>(baseLoop_);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取下一个被选中的事件循环（如果工作在多线程中，baseLoop 默认以轮询的方式分配 Channel 给 subLoop）</span></span><br><span class="line"><span class="function">EventLoop* <span class="title">EventLoopThreadPool::getNextLoop</span><span class="params">()</span> </span>{</span><br><span class="line">    EventLoop* loop = baseLoop_;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过轮询方式获取一下个处理事件的 EventLoop</span></span><br><span class="line">    <span class="keyword">if</span> (!loops_.<span class="built_in">empty</span>()) {</span><br><span class="line">        loop = loops_[next_];</span><br><span class="line">        ++next_;</span><br><span class="line">        <span class="keyword">if</span> (next_ &gt;= loops_.<span class="built_in">size</span>()) {</span><br><span class="line">            next_ = <span class="number">0</span>;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> loop;</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="function">std::vector&lt;EventLoop*&gt; <span class="title">EventLoopThreadPool::getAllLoops</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (loops_.<span class="built_in">empty</span>()) {</span><br><span class="line">        <span class="keyword">return</span> std::vector&lt;EventLoop*&gt;(<span class="number">1</span>, baseLoop_);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="keyword">return</span> loops_;</span><br><span class="line">    }</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="function"><span class="keyword">bool</span> <span class="title">EventLoopThreadPool::started</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> started_;</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="function"><span class="keyword">const</span> std::string&amp; <span class="title">EventLoopThreadPool::name</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> name_;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Socket"><a href="#Socket" class="headerlink" title="Socket"></a>Socket</h3><ul><li><code>Socket.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InetAddress</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 套接字类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Socket</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">Socket</span><span class="params">(<span class="keyword">int</span> sockFd)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">Socket</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 socket 的文件描述符</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">fd</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 绑定地址</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">bindAddress</span><span class="params">(<span class="keyword">const</span> InetAddress&amp; localaddr)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听连接请求</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">listen</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 接受连接请求</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">accept</span><span class="params">(InetAddress* peeraddr)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭写入</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">shutdownWrite</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否开启 TCP_NODELAY，开启后关闭 Nagle 算法，减少延迟</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setTcpNoDelay</span><span class="params">(<span class="keyword">bool</span> on)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否开启地址重用，允许端口在短时间内被重复绑定</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setReuseAddr</span><span class="params">(<span class="keyword">bool</span> on)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否开启端口重用，让多个进程/线程可以绑定同一端口</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setReusePort</span><span class="params">(<span class="keyword">bool</span> on)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否开启 TCP 保活，用于检测对端是否还存活</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setKeepAlive</span><span class="params">(<span class="keyword">bool</span> on)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">int</span> sockFd_;  <span class="comment">// socket 的文件描述符</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Socket.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Socket.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netinet/tcp.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"InetAddress.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Socket::<span class="built_in">Socket</span>(<span class="keyword">int</span> sockFd) : <span class="built_in">sockFd_</span>(sockFd) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Socket::~<span class="built_in">Socket</span>() {</span><br><span class="line">    ::<span class="built_in">close</span>(sockFd_);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 socket 的文件描述符</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">Socket::fd</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> sockFd_;</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="function"><span class="keyword">void</span> <span class="title">Socket::bindAddress</span><span class="params">(<span class="keyword">const</span> InetAddress&amp; localaddr)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (<span class="number">0</span> != ::<span class="built_in">bind</span>(sockFd_, (sockaddr*)localaddr.<span class="built_in">getSockAddr</span>(), <span class="built_in"><span class="keyword">sizeof</span></span>(sockaddr_in))) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; bind socketFd:%d failed, errno:%d"</span>, __PRETTY_FUNCTION__, sockFd_, errno);</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Socket::listen</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (<span class="number">0</span> != ::<span class="built_in">listen</span>(sockFd_, SOMAXCONN)) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; listen socketFd:%d failed, errno:%d"</span>, __PRETTY_FUNCTION__, sockFd_, errno);</span><br><span class="line">    }</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="function"><span class="keyword">int</span> <span class="title">Socket::accept</span><span class="params">(InetAddress* peeraddr)</span> </span>{</span><br><span class="line">    sockaddr_in addr;</span><br><span class="line">    <span class="keyword">socklen_t</span> len = <span class="keyword">sizeof</span> addr;</span><br><span class="line">    <span class="built_in">bzero</span>(&amp;addr, <span class="keyword">sizeof</span> addr);</span><br><span class="line">    <span class="comment">// 接受客户端新连接，返回新连接对应的 socket fd（非阻塞的），用来和客户端进行读写</span></span><br><span class="line">    <span class="keyword">int</span> connfd = ::<span class="built_in">accept4</span>(sockFd_, (sockaddr*)&amp;addr, &amp;len, SOCK_NONBLOCK | SOCK_CLOEXEC);</span><br><span class="line">    <span class="keyword">if</span> (connfd &gt;= <span class="number">0</span>) {</span><br><span class="line">        peeraddr-&gt;<span class="built_in">setSockAddr</span>(addr);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> connfd;</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="function"><span class="keyword">void</span> <span class="title">Socket::shutdownWrite</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">shutdown</span>(sockFd_, SHUT_WR) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; shutdown write socketFd:%d failed, errno:%d"</span>, __PRETTY_FUNCTION__, sockFd_, errno);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否开启 TCP_NODELAY，开启后关闭 Nagle 算法，减少延迟</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Socket::setTcpNoDelay</span><span class="params">(<span class="keyword">bool</span> on)</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> optval = on ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">    ::<span class="built_in">setsockopt</span>(sockFd_, IPPROTO_TCP, TCP_NODELAY, &amp;optval, <span class="keyword">static_cast</span>&lt;<span class="keyword">socklen_t</span>&gt;(<span class="keyword">sizeof</span> optval));</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="function"><span class="keyword">void</span> <span class="title">Socket::setReuseAddr</span><span class="params">(<span class="keyword">bool</span> on)</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> optval = on ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">    ::<span class="built_in">setsockopt</span>(sockFd_, SOL_SOCKET, SO_REUSEADDR, &amp;optval, <span class="keyword">static_cast</span>&lt;<span class="keyword">socklen_t</span>&gt;(<span class="keyword">sizeof</span> optval));</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="function"><span class="keyword">void</span> <span class="title">Socket::setReusePort</span><span class="params">(<span class="keyword">bool</span> on)</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> optval = on ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">int</span> ret = ::<span class="built_in">setsockopt</span>(sockFd_, SOL_SOCKET, SO_REUSEPORT, &amp;optval, <span class="keyword">static_cast</span>&lt;<span class="keyword">socklen_t</span>&gt;(<span class="keyword">sizeof</span> optval));</span><br><span class="line">    <span class="keyword">if</span> (ret &lt; <span class="number">0</span> &amp;&amp; on) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; set reuse port failed, errno:%d"</span>, __PRETTY_FUNCTION__, sockFd_, errno);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否开启 TCP 保活，用于检测对端是否还存活</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Socket::setKeepAlive</span><span class="params">(<span class="keyword">bool</span> on)</span> </span>{</span><br><span class="line">    <span class="keyword">int</span> optval = on ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">    ::<span class="built_in">setsockopt</span>(sockFd_, SOL_SOCKET, SO_KEEPALIVE, &amp;optval, <span class="keyword">static_cast</span>&lt;<span class="keyword">socklen_t</span>&gt;(<span class="keyword">sizeof</span> optval));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h3><ul><li><code>Buffer.h</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"copyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// @code</span></span><br><span class="line"><span class="comment">/// +-------------------+------------------+------------------+</span></span><br><span class="line"><span class="comment">/// | prependable bytes |  readable bytes  |  writable bytes  |</span></span><br><span class="line"><span class="comment">/// |                   |     (CONTENT)    |                  |</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">/// 0      &lt;=      readerIndex   &lt;=   writerIndex    &lt;=     size</span></span><br><span class="line"><span class="comment">/// @endcode</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Buffer</span> :</span> <span class="keyword">public</span> copyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">size_t</span> kCheapPrepend = <span class="number">8</span>;    <span class="comment">// 预留空间大小</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">size_t</span> kInitialSize = <span class="number">1024</span>;  <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="function"><span class="keyword">explicit</span> <span class="title">Buffer</span><span class="params">(<span class="keyword">size_t</span> initialSize = kInitialSize)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">Buffer</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取缓冲区中可读的字节数</span></span><br><span class="line">    <span class="function"><span class="keyword">size_t</span> <span class="title">readableBytes</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取缓冲区中可写的字节数</span></span><br><span class="line">    <span class="function"><span class="keyword">size_t</span> <span class="title">writableBytes</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取缓冲区中可预留的字节数</span></span><br><span class="line">    <span class="function"><span class="keyword">size_t</span> <span class="title">prependableBtes</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回缓冲区中可读数据的起始地址</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">peek</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移动读指针</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">retrieve</span><span class="params">(<span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重置读指针与写指针</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">retrieveAll</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将缓冲区中所有可读数据以字符串形式返回</span></span><br><span class="line">    <span class="function">std::string <span class="title">retrieveAllAsString</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将缓冲区中指定长度的可读数据以字符串形式返回</span></span><br><span class="line">    <span class="function">std::string <span class="title">retrieveAsString</span><span class="params">(<span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 确保缓冲区有足够的可写空间</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">ensureWritableBytes</span><span class="params">(<span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 扩容缓冲区以容纳更多数据</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">makeSpace</span><span class="params">(<span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向缓冲区追加数据</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">append</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span>* data, <span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通知缓冲区已写入数据</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">hasWritten</span><span class="params">(<span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回缓冲区中可写数据的起始地址</span></span><br><span class="line">    <span class="function"><span class="keyword">char</span>* <span class="title">beginWrite</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回缓冲区中可写数据的起始地址</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">beginWrite</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从 fd 上读取数据，并写到缓冲区中（返回值：n &gt; 0：读取成功；n == 0：连接关闭；n &lt; 0：读取出错）</span></span><br><span class="line">    <span class="function"><span class="keyword">ssize_t</span> <span class="title">readFd</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span>* saveErrno)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从缓冲区中读取数据，并写到 fd 上（返回值：n &gt; 0：写入成功；n == 0：没有数据可写入；n &lt; 0：写入出错）</span></span><br><span class="line">    <span class="function"><span class="keyword">ssize_t</span> <span class="title">writeFd</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span>* saveErrno)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 返回 vector 底层数组的首元素地址（即数组的起始地址）</span></span><br><span class="line">    <span class="function"><span class="keyword">char</span>* <span class="title">begin</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回 vector 底层数组的首元素地址（即数组的起始地址）</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">begin</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    std::vector&lt;<span class="keyword">char</span>&gt; buffer_;  <span class="comment">// 底层缓冲区</span></span><br><span class="line">    <span class="keyword">size_t</span> readerIndex_;        <span class="comment">// 读指针位置</span></span><br><span class="line">    <span class="keyword">size_t</span> writerIndex_;        <span class="comment">// 写指针位置</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Buffer.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Buffer.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;assert.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/uio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Buffer::<span class="built_in">Buffer</span>(<span class="keyword">size_t</span> initialSize)</span><br><span class="line">    : <span class="built_in">buffer_</span>(kCheapPrepend + initialSize), <span class="built_in">readerIndex_</span>(kCheapPrepend), <span class="built_in">writerIndex_</span>(kCheapPrepend) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Buffer::~<span class="built_in">Buffer</span>() {</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="function"><span class="keyword">size_t</span> <span class="title">Buffer::readableBytes</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> writerIndex_ - readerIndex_;</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="function"><span class="keyword">size_t</span> <span class="title">Buffer::writableBytes</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> buffer_.<span class="built_in">size</span>() - writerIndex_;</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="function"><span class="keyword">size_t</span> <span class="title">Buffer::prependableBtes</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> readerIndex_;</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="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">Buffer::peek</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">begin</span>() + readerIndex_;</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="function"><span class="keyword">void</span> <span class="title">Buffer::retrieve</span><span class="params">(<span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    <span class="built_in">assert</span>(len &lt;= <span class="built_in">readableBytes</span>());</span><br><span class="line">    <span class="keyword">if</span> (len &lt; <span class="built_in">readableBytes</span>()) {</span><br><span class="line">        readerIndex_ += len;</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">retrieveAll</span>();</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Buffer::retrieveAll</span><span class="params">()</span> </span>{</span><br><span class="line">    readerIndex_ = kCheapPrepend;</span><br><span class="line">    writerIndex_ = kCheapPrepend;</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="function">std::string <span class="title">Buffer::retrieveAllAsString</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">retrieveAsString</span>(<span class="built_in">readableBytes</span>());</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="function">std::string <span class="title">Buffer::retrieveAsString</span><span class="params">(<span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    <span class="built_in">assert</span>(len &lt;= <span class="built_in">readableBytes</span>());</span><br><span class="line">    <span class="comment">// 构造字符串</span></span><br><span class="line">    <span class="function">std::string <span class="title">result</span><span class="params">(peek(), len)</span></span>;</span><br><span class="line">    <span class="comment">// 移动读指针</span></span><br><span class="line">    <span class="built_in">retrieve</span>(len);</span><br><span class="line">    <span class="keyword">return</span> result;</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="function"><span class="keyword">void</span> <span class="title">Buffer::ensureWritableBytes</span><span class="params">(<span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">writableBytes</span>() &lt; len) {</span><br><span class="line">        <span class="comment">// 缓冲区扩容</span></span><br><span class="line">        <span class="built_in">makeSpace</span>(len);</span><br><span class="line">    }</span><br><span class="line">    <span class="built_in">assert</span>(<span class="built_in">writableBytes</span>() &gt;= len);</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="function"><span class="keyword">void</span> <span class="title">Buffer::makeSpace</span><span class="params">(<span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    <span class="comment">// 判断是否需要通过移动数据来腾出空间</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">writableBytes</span>() + <span class="built_in">prependableBtes</span>() &lt; len + kCheapPrepend) {</span><br><span class="line">        <span class="comment">// 没有空闲的空间，直接扩容</span></span><br><span class="line">        buffer_.<span class="built_in">resize</span>(writerIndex_ + len);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 有空闲的空间，通过移动数据来腾出空间</span></span><br><span class="line">        <span class="built_in">assert</span>(kCheapPrepend &lt; readerIndex_);</span><br><span class="line">        <span class="keyword">size_t</span> readable = <span class="built_in">readableBytes</span>();</span><br><span class="line">        std::<span class="built_in">copy</span>(<span class="built_in">begin</span>() + readerIndex_, <span class="built_in">begin</span>() + writerIndex_, <span class="built_in">begin</span>() + kCheapPrepend);</span><br><span class="line">        readerIndex_ = kCheapPrepend;</span><br><span class="line">        writerIndex_ = readerIndex_ + readable;</span><br><span class="line">        <span class="built_in">assert</span>(readable == <span class="built_in">readableBytes</span>());</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Buffer::append</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span>* data, <span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    <span class="built_in">ensureWritableBytes</span>(len);</span><br><span class="line">    std::<span class="built_in">copy</span>(data, data + len, <span class="built_in">beginWrite</span>());</span><br><span class="line">    <span class="built_in">hasWritten</span>(len);</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="function"><span class="keyword">void</span> <span class="title">Buffer::hasWritten</span><span class="params">(<span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    <span class="built_in">assert</span>(len &lt;= <span class="built_in">writableBytes</span>());</span><br><span class="line">    writerIndex_ += len;</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="function"><span class="keyword">char</span>* <span class="title">Buffer::beginWrite</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">begin</span>() + writerIndex_;</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="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">Buffer::beginWrite</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">begin</span>() + writerIndex_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从 fd 上读取数据，并写到缓冲区中（返回值：n &gt; 0：读取成功；n == 0：连接关闭；n &lt; 0：读取出错）</span></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">Buffer::readFd</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span>* saveErrno)</span> </span>{</span><br><span class="line">    <span class="comment">// 在栈上分配内存空间（64KB）</span></span><br><span class="line">    <span class="keyword">char</span> extrabuf[<span class="number">65536</span>] = {<span class="number">0</span>};</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 主缓冲区可写的字节数</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">size_t</span> writable = <span class="built_in">writableBytes</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 采用 scatter-gather 读技术，同时将数据读入主缓冲区和 extrabuf</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">iovec</span> <span class="title">vec</span>[2];</span></span><br><span class="line">    vec[<span class="number">0</span>].iov_base = <span class="built_in">begin</span>() + writerIndex_;</span><br><span class="line">    vec[<span class="number">0</span>].iov_len = writable;</span><br><span class="line">    vec[<span class="number">1</span>].iov_base = extrabuf;</span><br><span class="line">    vec[<span class="number">1</span>].iov_len = <span class="keyword">sizeof</span> extrabuf;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当主缓冲区 writable 小于 extrabuf（64KB）时，说明主缓冲区的空间可能不够装下数据，</span></span><br><span class="line">    <span class="comment">// 需要使用两个 iovec：第一个写入 buffer_，第二个写入 extrabuf，从而尽可能读完内核中的数据。</span></span><br><span class="line">    <span class="comment">// 否则，如果主缓冲区足够大，只需一个 iovec。</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">int</span> iovcnt = (writable &lt; <span class="keyword">sizeof</span> extrabuf) ? <span class="number">2</span> : <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 class="keyword">const</span> <span class="keyword">ssize_t</span> n = ::<span class="built_in">readv</span>(fd, vec, iovcnt);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果发生错误</span></span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0</span>) {</span><br><span class="line">        *saveErrno = errno;</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 如果只写入了主缓冲区，没有写入了 extrabuf</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (n &lt;= writable) {</span><br><span class="line">        writerIndex_ += n;</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 如果不仅写入了主缓冲区，还写入了 extrabuf</span></span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        writerIndex_ = buffer_.<span class="built_in">size</span>();</span><br><span class="line">        <span class="built_in">append</span>(extrabuf, n - writable);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> n;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从缓冲区中读取数据，并写到 fd 上（返回值：n &gt; 0：写入成功；n == 0：没有数据可写入；n &lt; 0：写入出错）</span></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">Buffer::writeFd</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span>* saveErrno)</span> </span>{</span><br><span class="line">    <span class="keyword">ssize_t</span> n = ::<span class="built_in">write</span>(fd, <span class="built_in">peek</span>(), <span class="built_in">readableBytes</span>());</span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 写入出错，记录错误码</span></span><br><span class="line">        *saveErrno = errno;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> n;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回 vector 底层数组的首元素地址（即数组的起始地址）</span></span><br><span class="line"><span class="function"><span class="keyword">char</span>* <span class="title">Buffer::begin</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> &amp;*buffer_.<span class="built_in">begin</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回 vector 底层数组的首元素地址（即数组的起始地址）</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">Buffer::begin</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> &amp;*buffer_.<span class="built_in">begin</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="TcpConnection"><a href="#TcpConnection" class="headerlink" title="TcpConnection"></a>TcpConnection</h3><ul><li><code>TcpConnection.h</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Buffer.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Callbacks.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"InetAddress.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Channel</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Socket</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP 连接类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TcpConnection</span> :</span> noncopyable, <span class="keyword">public</span> std::enable_shared_from_this&lt;TcpConnection&gt; {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">TcpConnection</span>(EventLoop* loop, <span class="keyword">const</span> std::string&amp; nameArg, <span class="keyword">int</span> sockfd, <span class="keyword">const</span> InetAddress&amp; localAddr,</span><br><span class="line">                  <span class="keyword">const</span> InetAddress&amp; peerAddr);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">TcpConnection</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 TCP 连接所在的事件循环</span></span><br><span class="line">    <span class="function">EventLoop* <span class="title">getLoop</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 TCP 连接的名称</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">name</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 TCP 连接的本地网络地址</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> InetAddress&amp; <span class="title">localAddress</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 TCP 连接的远程网络地址</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> InetAddress&amp; <span class="title">peerAddress</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断 TCP 连接是否处于已连接状态</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">connected</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断 TCP 连接是否处于断开状态</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">disconnected</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送数据到输出缓冲区</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">send</span><span class="params">(<span class="keyword">const</span> std::string&amp; message)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭 TCP 连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">shutdown</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 强制关闭连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">forceClose</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置连接建立/关闭时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setConnectionCallback</span><span class="params">(<span class="keyword">const</span> ConnectionCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置有数据到来时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setMessageCallback</span><span class="params">(<span class="keyword">const</span> MessageCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置数据发送完成时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setWriteCompleteCallback</span><span class="params">(<span class="keyword">const</span> WriteCompleteCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置触发高水位时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setHighWaterMarkCallback</span><span class="params">(<span class="keyword">const</span> HighWaterMarkCallback&amp; cb, <span class="keyword">size_t</span> highWaterMark)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置连接关闭时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setCloseCallback</span><span class="params">(<span class="keyword">const</span> CloseCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取输入缓冲区</span></span><br><span class="line">    <span class="function">Buffer* <span class="title">inputBuffer</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取输出缓冲区</span></span><br><span class="line">    <span class="function">Buffer* <span class="title">outputBuffer</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 连接建立</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connectEstablished</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 连接销毁</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connectDestroyed</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// TCP 连接的状态</span></span><br><span class="line">    <span class="class"><span class="keyword">enum</span> <span class="title">StateE</span> {</span> kDisconnected, kConnecting, kConnected, kDisconnecting };</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理读事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleRead</span><span class="params">(Timestamp receiveTime)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理写事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleWrite</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理关闭事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleClose</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理错误事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleError</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在事件循环（EventLoop）中发送数据到输出缓冲区</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">sendInLoop</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span>* message, <span class="keyword">size_t</span> len)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在事件循环（EventLoop）中关闭 TCP 连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">shutdownInLoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在事件循环（EventLoop）中强制关闭 TCP 连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">forceCloseInLoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 TCP 连接的状态</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setState</span><span class="params">(StateE state)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将 TCP 连接的状态转换为字符串</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">stateToString</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    EventLoop* loop_;         <span class="comment">// TCP 连接所在的事件循环，TCP 连接运行在 subLoop 中</span></span><br><span class="line">    <span class="keyword">const</span> std::string name_;  <span class="comment">// TCP 连接的名称</span></span><br><span class="line">    std::<span class="keyword">atomic_int</span> state_;   <span class="comment">// TCP 连接的状态</span></span><br><span class="line">    <span class="keyword">bool</span> reading_;            <span class="comment">// 标记是否正在读数据</span></span><br><span class="line"></span><br><span class="line">    std::unique_ptr&lt;Socket&gt; socket_;    <span class="comment">// TCP 连接对应的 Socket 对象</span></span><br><span class="line">    std::unique_ptr&lt;Channel&gt; channel_;  <span class="comment">// TCP 连接对应的 Channel 对象</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> InetAddress localAddr_;  <span class="comment">// TCP 连接的本地网络地址</span></span><br><span class="line">    <span class="keyword">const</span> InetAddress peerAddr_;   <span class="comment">// TCP 连接的远程网络地址</span></span><br><span class="line"></span><br><span class="line">    ConnectionCallback connectionCallback_;        <span class="comment">// 连接建立/关闭时的回调操作</span></span><br><span class="line">    MessageCallback messageCallback_;              <span class="comment">// 有数据到来时的回调操作</span></span><br><span class="line">    WriteCompleteCallback writeCompleteCallback_;  <span class="comment">// 数据发送完成时的回调操作</span></span><br><span class="line">    HighWaterMarkCallback highWaterMarkCallback_;  <span class="comment">// 触发高水位时的回调操作</span></span><br><span class="line">    CloseCallback closeCallback_;                  <span class="comment">// 连接关闭时的回调操作</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">size_t</span> highWaterMark_;  <span class="comment">// 高水位的大小（默认 64M）</span></span><br><span class="line">    Buffer inputBuffer_;    <span class="comment">// 输入缓冲区（用于接收数据的缓冲区）</span></span><br><span class="line">    Buffer outputBuffer_;   <span class="comment">// 输出缓冲区（用于发送数据的缓冲区）</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>TcpConnection.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpConnection.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;assert.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;error.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Socket.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"SocketsOps.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查 EventLoop 指针是否为空</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> EventLoop* <span class="title">CheckLoopNotNull</span><span class="params">(EventLoop* loop)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (loop == <span class="literal">nullptr</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; eventloop is null"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> loop;</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="function"><span class="keyword">void</span> <span class="title">defaultConnectionCallback</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span> </span>{</span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; %s -&gt; %s is %s"</span>, __PRETTY_FUNCTION__, conn-&gt;<span class="built_in">localAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>(),</span><br><span class="line">              conn-&gt;<span class="built_in">peerAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>(), (conn-&gt;<span class="built_in">connected</span>() ? <span class="string">"UP"</span> : <span class="string">"DOWN"</span>));</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="function"><span class="keyword">void</span> <span class="title">defaultMessageCallback</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp;, Buffer* buf, Timestamp)</span> </span>{</span><br><span class="line">    buf-&gt;<span class="built_in">retrieveAll</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">TcpConnection::<span class="built_in">TcpConnection</span>(EventLoop* loop, <span class="keyword">const</span> std::string&amp; nameArg, <span class="keyword">int</span> sockfd, <span class="keyword">const</span> InetAddress&amp; localAddr,</span><br><span class="line">                             <span class="keyword">const</span> InetAddress&amp; peerAddr)</span><br><span class="line">    : <span class="built_in">loop_</span>(<span class="built_in">CheckLoopNotNull</span>(loop)),</span><br><span class="line">      <span class="built_in">name_</span>(nameArg),</span><br><span class="line">      <span class="built_in">state_</span>(kConnecting),</span><br><span class="line">      <span class="built_in">socket_</span>(<span class="keyword">new</span> <span class="built_in">Socket</span>(sockfd)),</span><br><span class="line">      <span class="built_in">channel_</span>(<span class="keyword">new</span> <span class="built_in">Channel</span>(loop, sockfd)),</span><br><span class="line">      <span class="built_in">localAddr_</span>(localAddr),</span><br><span class="line">      <span class="built_in">peerAddr_</span>(peerAddr),</span><br><span class="line">      <span class="built_in">connectionCallback_</span>(defaultConnectionCallback),</span><br><span class="line">      <span class="built_in">messageCallback_</span>(defaultMessageCallback),</span><br><span class="line">      <span class="built_in">highWaterMark_</span>(<span class="number">64</span> * <span class="number">1024</span> * <span class="number">1024</span>) {</span><br><span class="line">    <span class="comment">// 给 Channel 设置相应的回调函数，Poller 会通知 Channel 它感兴趣的事件发生了，然后 Channel 会回调相应的操作函数</span></span><br><span class="line">    channel_-&gt;<span class="built_in">setReadCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::handleRead, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line">    channel_-&gt;<span class="built_in">setWriteCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::handleWrite, <span class="keyword">this</span>));</span><br><span class="line">    channel_-&gt;<span class="built_in">setCloseCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::handleClose, <span class="keyword">this</span>));</span><br><span class="line">    channel_-&gt;<span class="built_in">setErrorCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::handleError, <span class="keyword">this</span>));</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; create tcp connection [%s] at %p, fd=%d"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), <span class="keyword">this</span>, sockfd);</span><br><span class="line">    <span class="comment">// 开启 TCP 保活机制</span></span><br><span class="line">    socket_-&gt;<span class="built_in">setKeepAlive</span>(<span class="literal">true</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">TcpConnection::~<span class="built_in">TcpConnection</span>() {</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; destruct tcp connection [%s] at %p, fd=%d, state=%s"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), <span class="keyword">this</span>,</span><br><span class="line">              channel_-&gt;<span class="built_in">fd</span>(), <span class="built_in">stateToString</span>());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 TCP 连接所在的事件循环</span></span><br><span class="line"><span class="function">EventLoop* <span class="title">TcpConnection::getLoop</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> loop_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 TCP 连接的名称</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">TcpConnection::name</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> name_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 TCP 连接的本地网络地址</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> InetAddress&amp; <span class="title">TcpConnection::localAddress</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> localAddr_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 TCP 连接的远程网络地址</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> InetAddress&amp; <span class="title">TcpConnection::peerAddress</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> peerAddr_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断 TCP 连接是否处于已连接状态</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">TcpConnection::connected</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> state_ == kConnected;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断 TCP 连接是否处于断开状态</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">TcpConnection::disconnected</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> state_ == kDisconnected;</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::send</span><span class="params">(<span class="keyword">const</span> std::string&amp; message)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnected) {</span><br><span class="line">        <span class="comment">// 如果当前线程是 loop_ 所在的线程</span></span><br><span class="line">        <span class="keyword">if</span> (loop_-&gt;<span class="built_in">isInLoopThread</span>()) {</span><br><span class="line">            <span class="comment">// 直接将数据发送到输出缓冲区</span></span><br><span class="line">            <span class="built_in">sendInLoop</span>(message.<span class="built_in">c_str</span>(), message.<span class="built_in">size</span>());</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="comment">// 唤醒 loop_ 对应的线程将数据发送到输出缓冲区</span></span><br><span class="line">            loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::sendInLoop, <span class="keyword">this</span>, message.<span class="built_in">c_str</span>(), message.<span class="built_in">size</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"><span class="comment">// 关闭 TCP 连接</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpConnection::shutdown</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnected) {</span><br><span class="line">        <span class="comment">// 设置 TCP 连接的状态</span></span><br><span class="line">        <span class="built_in">setState</span>(kDisconnecting);</span><br><span class="line">        <span class="comment">// 唤醒 loop_ 对应的线程去关闭 TCP 连接</span></span><br><span class="line">        loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::shutdownInLoop, <span class="keyword">this</span>));</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::forceClose</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 判断 TCP 连接的状态</span></span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnected || state_ == kDisconnecting) {</span><br><span class="line">        <span class="comment">// 设置连接状态</span></span><br><span class="line">        <span class="built_in">setState</span>(kDisconnecting);</span><br><span class="line">        <span class="comment">// 唤醒 loop_ 对应的线程去强制关闭 TCP 连接</span></span><br><span class="line">        loop_-&gt;<span class="built_in">queueInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::forceCloseInLoop, <span class="built_in">shared_from_this</span>()));</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::setConnectionCallback</span><span class="params">(<span class="keyword">const</span> ConnectionCallback&amp; cb)</span> </span>{</span><br><span class="line">    connectionCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::setMessageCallback</span><span class="params">(<span class="keyword">const</span> MessageCallback&amp; cb)</span> </span>{</span><br><span class="line">    messageCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::setWriteCompleteCallback</span><span class="params">(<span class="keyword">const</span> WriteCompleteCallback&amp; cb)</span> </span>{</span><br><span class="line">    writeCompleteCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::setHighWaterMarkCallback</span><span class="params">(<span class="keyword">const</span> HighWaterMarkCallback&amp; cb, <span class="keyword">size_t</span> highWaterMark)</span> </span>{</span><br><span class="line">    highWaterMarkCallback_ = cb;</span><br><span class="line">    highWaterMark_ = highWaterMark;</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::setCloseCallback</span><span class="params">(<span class="keyword">const</span> CloseCallback&amp; cb)</span> </span>{</span><br><span class="line">    closeCallback_ = cb;</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="function">Buffer* <span class="title">TcpConnection::inputBuffer</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> &amp;inputBuffer_;</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="function">Buffer* <span class="title">TcpConnection::outputBuffer</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> &amp;outputBuffer_;</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::connectEstablished</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="built_in">assert</span>(state_ == kConnecting);</span><br><span class="line">    <span class="comment">// 设置 TCP 连接的状态</span></span><br><span class="line">    <span class="built_in">setState</span>(kConnected);</span><br><span class="line">    <span class="comment">// Channel 绑定 TCP 连接</span></span><br><span class="line">    channel_-&gt;<span class="built_in">tie</span>(<span class="built_in">shared_from_this</span>());</span><br><span class="line">    <span class="comment">// Channel 开启监听 fd 上的读事件</span></span><br><span class="line">    channel_-&gt;<span class="built_in">enableReading</span>();</span><br><span class="line">    <span class="comment">// 调用用户设置的回调操作</span></span><br><span class="line">    <span class="built_in">connectionCallback_</span>(<span class="built_in">shared_from_this</span>());</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::connectDestroyed</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnected) {</span><br><span class="line">        <span class="comment">// 设置 TCP 连接的状态</span></span><br><span class="line">        <span class="built_in">setState</span>(kDisconnected);</span><br><span class="line">        <span class="comment">// Channel 禁止监听 fd 上的所有事件</span></span><br><span class="line">        channel_-&gt;<span class="built_in">disableAll</span>();</span><br><span class="line">        <span class="comment">// 调用用户设置的回调操作</span></span><br><span class="line">        <span class="built_in">connectionCallback_</span>(<span class="built_in">shared_from_this</span>());</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 从 Poller 中删除 Channel</span></span><br><span class="line">    channel_-&gt;<span class="built_in">remove</span>();</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::handleRead</span><span class="params">(Timestamp receiveTime)</span> </span>{</span><br><span class="line">    <span class="comment">// 临时错误码</span></span><br><span class="line">    <span class="keyword">int</span> saveErrno = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从 fd 上读取数据，并写入到输入缓冲区中</span></span><br><span class="line">    <span class="keyword">ssize_t</span> n = inputBuffer_.<span class="built_in">readFd</span>(channel_-&gt;<span class="built_in">fd</span>(), &amp;saveErrno);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 已建立连接的客户端，有可读事件发生了，调用用户设置的回调操作</span></span><br><span class="line">        <span class="built_in">messageCallback_</span>(<span class="built_in">shared_from_this</span>(), &amp;inputBuffer_, receiveTime);</span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (n == <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 处理连接关闭</span></span><br><span class="line">        <span class="built_in">handleClose</span>();</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 设置错误码</span></span><br><span class="line">        errno = saveErrno;</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; read fd error, fd=%d, errno=%d"</span>, __PRETTY_FUNCTION__, channel_-&gt;<span class="built_in">fd</span>(), errno);</span><br><span class="line">        <span class="comment">// 处理连接错误</span></span><br><span class="line">        <span class="built_in">handleError</span>();</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::handleWrite</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 判断 Channel 是否正在监听写事件</span></span><br><span class="line">    <span class="keyword">if</span> (channel_-&gt;<span class="built_in">isWriting</span>()) {</span><br><span class="line">        <span class="comment">// 临时错误码</span></span><br><span class="line">        <span class="keyword">int</span> saveErrno = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 从输出缓冲区读取数据，并写入到 fd 上</span></span><br><span class="line">        <span class="keyword">ssize_t</span> n = outputBuffer_.<span class="built_in">writeFd</span>(channel_-&gt;<span class="built_in">fd</span>(), &amp;saveErrno);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (n &gt; <span class="number">0</span>) {</span><br><span class="line">            <span class="comment">// 移动输出缓冲区的读指针（标记有哪些数据被发送了）</span></span><br><span class="line">            outputBuffer_.<span class="built_in">retrieve</span>(n);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 如果输出缓冲区中的所有数据都发送完了</span></span><br><span class="line">            <span class="keyword">if</span> (outputBuffer_.<span class="built_in">readableBytes</span>() == <span class="number">0</span>) {</span><br><span class="line">                <span class="comment">// 关闭监听 fd 上的写事件</span></span><br><span class="line">                channel_-&gt;<span class="built_in">disableWriting</span>();</span><br><span class="line">                <span class="comment">// 调用用户设置的回调操作</span></span><br><span class="line">                <span class="keyword">if</span> (writeCompleteCallback_) {</span><br><span class="line">                    <span class="comment">// 唤醒 loop_ 所在的线程去执行用户设置的回调操作</span></span><br><span class="line">                    loop_-&gt;<span class="built_in">queueInLoop</span>(std::<span class="built_in">bind</span>(writeCompleteCallback_, <span class="built_in">shared_from_this</span>()));</span><br><span class="line">                }</span><br><span class="line">                <span class="comment">// 如果正在断开 TCP 连接，则关闭 TCP 连接</span></span><br><span class="line">                <span class="keyword">if</span> (state_ == kDisconnecting) {</span><br><span class="line">                    <span class="built_in">shutdownInLoop</span>();</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        } <span class="keyword">else</span> <span class="keyword">if</span> (n &lt; <span class="number">0</span>) {</span><br><span class="line">            <span class="comment">// 打印日志信息</span></span><br><span class="line">            <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; write fd error, fd=%d, errno=%d"</span>, __PRETTY_FUNCTION__, channel_-&gt;<span class="built_in">fd</span>(), errno);</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; tcp connection [%s] is down, no more writing, fd=%d"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(),</span><br><span class="line">                  channel_-&gt;<span class="built_in">fd</span>());</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::handleClose</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; tcp connection [%s] is close, fd=%d, state=%s"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), channel_-&gt;<span class="built_in">fd</span>(),</span><br><span class="line">              <span class="built_in">stateToString</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 TCP 连接的状态</span></span><br><span class="line">    <span class="built_in">setState</span>(kDisconnected);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 禁止 Channel 监听 fd 上的所有事件</span></span><br><span class="line">    channel_-&gt;<span class="built_in">disableAll</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前的 TCP 连接</span></span><br><span class="line">    <span class="function">TcpConnectionPtr <span class="title">guardThis</span><span class="params">(shared_from_this())</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用用户设置的连接建立/关闭时的回调操作</span></span><br><span class="line">    <span class="built_in">connectionCallback_</span>(guardThis);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用用户设置的连接关闭时的回调操作</span></span><br><span class="line">    <span class="keyword">if</span> (closeCallback_) {</span><br><span class="line">        <span class="built_in">closeCallback_</span>(guardThis);</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpConnection::handleError</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 获取 Socket 错误码</span></span><br><span class="line">    <span class="keyword">int</span> savedErrno = <span class="built_in">getSocketError</span>(channel_-&gt;<span class="built_in">fd</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; tcp connection [%s] occurred error, fd=%d, SO_ERROR:%d"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(),</span><br><span class="line">              channel_-&gt;<span class="built_in">fd</span>(), savedErrno);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在事件循环（EventLoop）中发送数据到输出缓冲区</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpConnection::sendInLoop</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span>* message, <span class="keyword">size_t</span> len)</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 已发送数据的字节数</span></span><br><span class="line">    <span class="keyword">ssize_t</span> nwrote = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 剩下未发送数据的字节数</span></span><br><span class="line">    <span class="keyword">size_t</span> remaining = len;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否发生致命错误</span></span><br><span class="line">    <span class="keyword">bool</span> faultError = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果 TCP 连接已断开，则放弃发送数据</span></span><br><span class="line">    <span class="keyword">if</span> (state_ == kDisconnected) {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; tcp connection [%s] disconnected, give up writing"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>());</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果 Channel 是第一次写入数据，且输出缓冲区里面没有待发送的数据</span></span><br><span class="line">    <span class="keyword">if</span> (!channel_-&gt;<span class="built_in">isWriting</span>() &amp;&amp; outputBuffer_.<span class="built_in">readableBytes</span>() == <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 直接发送数据（成功：返回已发送的字节数，失败：返回小于零的数字）</span></span><br><span class="line">        nwrote = ::<span class="built_in">write</span>(channel_-&gt;<span class="built_in">fd</span>(), message, len);</span><br><span class="line">        <span class="comment">// 发送数据成功</span></span><br><span class="line">        <span class="keyword">if</span> (nwrote &gt;= <span class="number">0</span>) {</span><br><span class="line">            <span class="comment">// 剩下未发送的字节数</span></span><br><span class="line">            remaining = len - nwrote;</span><br><span class="line">            <span class="comment">// 如果所有数据都发送完</span></span><br><span class="line">            <span class="keyword">if</span> (remaining == <span class="number">0</span> &amp;&amp; writeCompleteCallback_) {</span><br><span class="line">                <span class="comment">// 唤醒 loop_ 所在的线程去执行用户设置的回调操作</span></span><br><span class="line">                loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(writeCompleteCallback_, <span class="built_in">shared_from_this</span>()));</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="keyword">else</span> {</span><br><span class="line">            nwrote = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">if</span> (errno != EWOULDBLOCK) {</span><br><span class="line">                <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; occurred error"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">                <span class="keyword">if</span> (errno == EPIPE || errno == ECONNRESET) {</span><br><span class="line">                    faultError = <span class="literal">true</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"></span><br><span class="line">    <span class="built_in">assert</span>(remaining &lt;= len);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果发送数据没有发生致命错误，且有剩下的数据未发送</span></span><br><span class="line">    <span class="keyword">if</span> (!faultError &amp;&amp; remaining &gt; <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 输出缓冲区中原先未发送数据的字节数</span></span><br><span class="line">        <span class="keyword">size_t</span> oldLen = outputBuffer_.<span class="built_in">readableBytes</span>();</span><br><span class="line">        <span class="comment">// 判断所有未发送数据的大小是否触及了高水位线</span></span><br><span class="line">        <span class="keyword">if</span> (oldLen + remaining &gt;= highWaterMark_ &amp;&amp; oldLen &lt; highWaterMark_ &amp;&amp; highWaterMarkCallback_) {</span><br><span class="line">            <span class="comment">// 唤醒 loop_ 所在的线程去执行用户设置的回调操作</span></span><br><span class="line">            loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(highWaterMarkCallback_, <span class="built_in">shared_from_this</span>(), oldLen + remaining));</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 往输出缓冲区中写入上面未发送完的数据</span></span><br><span class="line">        outputBuffer_.<span class="built_in">append</span>(<span class="keyword">static_cast</span>&lt;<span class="keyword">const</span> <span class="keyword">char</span>*&gt;(message) + nwrote, remaining);</span><br><span class="line">        <span class="comment">// 让 Channel 开启监听 fd 上的写事件</span></span><br><span class="line">        <span class="keyword">if</span> (!channel_-&gt;<span class="built_in">isWriting</span>()) {</span><br><span class="line">            channel_-&gt;<span class="built_in">enableWriting</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"><span class="comment">// 在事件循环（EventLoop）中关闭 TCP 连接</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpConnection::shutdownInLoop</span><span class="params">()</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="comment">// 如果输出缓冲区中的所有数据都发送完</span></span><br><span class="line">    <span class="keyword">if</span> (!channel_-&gt;<span class="built_in">isWriting</span>()) {</span><br><span class="line">        <span class="comment">// Socket 关闭写入</span></span><br><span class="line">        socket_-&gt;<span class="built_in">shutdownWrite</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在事件循环（EventLoop）中强制关闭 TCP 连接</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpConnection::forceCloseInLoop</span><span class="params">()</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="comment">// 判断 TCP 连接的状态</span></span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnected || state_ == kDisconnecting) {</span><br><span class="line">        <span class="comment">// 处理关闭事件</span></span><br><span class="line">        <span class="built_in">handleClose</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置 TCP 连接的状态</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpConnection::setState</span><span class="params">(StateE state)</span> </span>{</span><br><span class="line">    state_ = state;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 TCP 连接的状态转换为字符串</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">char</span>* <span class="title">TcpConnection::stateToString</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="built_in"><span class="keyword">switch</span></span> (state_) {</span><br><span class="line">        <span class="keyword">case</span> kDisconnected:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"kDisconnected"</span>;</span><br><span class="line">        <span class="keyword">case</span> kConnecting:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"kConnecting"</span>;</span><br><span class="line">        <span class="keyword">case</span> kConnected:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"kConnected"</span>;</span><br><span class="line">        <span class="keyword">case</span> kDisconnecting:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"kDisconnecting"</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"unknown state"</span>;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Acceptor"><a href="#Acceptor" class="headerlink" title="Acceptor"></a>Acceptor</h3><ul><li><code>Acceptor.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Socket.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InetAddress</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP 连接接受器类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Acceptor</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 有新连接到来时的回调操作类型定义</span></span><br><span class="line">    <span class="keyword">using</span> NewConnectionCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>(<span class="keyword">int</span> sockFd, <span class="keyword">const</span> InetAddress&amp;)&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">Acceptor</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; listenAddr, <span class="keyword">bool</span> reuseport);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">Acceptor</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置有新连接到来时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setNewConnectionCallback</span><span class="params">(<span class="keyword">const</span> NewConnectionCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听连接请求（即监听有新的客户端连接进来）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">listen</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取是否正在监听连接请求</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">listenning</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 处理读事件（即处理有新客户端连接进来）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleRead</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    EventLoop* loop_;        <span class="comment">// Acceptor 使用的就是用户自定义的那个 baseLoop，也称作 mainLoop</span></span><br><span class="line">    Socket acceptSocket_;    <span class="comment">// 用于监听的 socket</span></span><br><span class="line">    Channel acceptChannel_;  <span class="comment">// 用于监听 acceptSocket_ 上的可读事件（即有新连接到来）</span></span><br><span class="line">    NewConnectionCallback newConnectionCallback_;  <span class="comment">// 有新连接到来时的回调操作</span></span><br><span class="line">    <span class="keyword">bool</span> listenning_;                              <span class="comment">// 标记是否正在监听连接请求</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Acceptor.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Acceptor.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"InetAddress.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"SocketsOps.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Acceptor::<span class="built_in">Acceptor</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; listenAddr, <span class="keyword">bool</span> reuseport)</span><br><span class="line">    : <span class="built_in">loop_</span>(loop),</span><br><span class="line">      <span class="built_in">acceptSocket_</span>(<span class="built_in">createNonblockingSocket</span>()),</span><br><span class="line">      <span class="built_in">acceptChannel_</span>(loop, acceptSocket_.<span class="built_in">fd</span>()),</span><br><span class="line">      <span class="built_in">listenning_</span>(<span class="literal">false</span>) {</span><br><span class="line">    acceptSocket_.<span class="built_in">setReuseAddr</span>(<span class="literal">true</span>);</span><br><span class="line">    acceptSocket_.<span class="built_in">setReusePort</span>(reuseport);</span><br><span class="line">    acceptSocket_.<span class="built_in">bindAddress</span>(listenAddr);</span><br><span class="line">    <span class="comment">// 设置 acceptChannel_ 的读事件回调操作为 Acceptor::handleRead 方法</span></span><br><span class="line">    acceptChannel_.<span class="built_in">setReadCallback</span>(std::<span class="built_in">bind</span>(&amp;Acceptor::handleRead, <span class="keyword">this</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Acceptor::~<span class="built_in">Acceptor</span>() {</span><br><span class="line">    <span class="comment">// 关闭 acceptChannel_ 上的所有事件监听</span></span><br><span class="line">    acceptChannel_.<span class="built_in">disableAll</span>();</span><br><span class="line">    <span class="comment">// 从 Poller 中删除 acceptChannel_</span></span><br><span class="line">    acceptChannel_.<span class="built_in">remove</span>();</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="function"><span class="keyword">void</span> <span class="title">Acceptor::setNewConnectionCallback</span><span class="params">(<span class="keyword">const</span> NewConnectionCallback&amp; cb)</span> </span>{</span><br><span class="line">    newConnectionCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">Acceptor::listen</span><span class="params">()</span> </span>{</span><br><span class="line">    listenning_ = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// 监听客户端的连接请求</span></span><br><span class="line">    acceptSocket_.<span class="built_in">listen</span>();</span><br><span class="line">    <span class="comment">// 启用 acceptChannel_ 的读事件监听（即监听有新连接到来）</span></span><br><span class="line">    acceptChannel_.<span class="built_in">enableReading</span>();</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="function"><span class="keyword">bool</span> <span class="title">Acceptor::listenning</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> listenning_;</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="function"><span class="keyword">void</span> <span class="title">Acceptor::handleRead</span><span class="params">()</span> </span>{</span><br><span class="line">    InetAddress peerAddr;</span><br><span class="line">    <span class="comment">// 接受客户端新连接，返回新连接对应的 socket fd，用来和客户端进行读写</span></span><br><span class="line">    <span class="keyword">int</span> connfd = acceptSocket_.<span class="built_in">accept</span>(&amp;peerAddr);</span><br><span class="line">    <span class="keyword">if</span> (connfd &gt;= <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 有客户端新连接到来，执行回调操作（如果存在）</span></span><br><span class="line">        <span class="keyword">if</span> (newConnectionCallback_) {</span><br><span class="line">            <span class="comment">// 回调操作的职责：轮询找到 subLoop，将新客户端的 fd 分发给 subLoop，然后唤醒 subLoop 以处理该新客户端的连接</span></span><br><span class="line">            <span class="built_in">newConnectionCallback_</span>(connfd, peerAddr);</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            ::<span class="built_in">close</span>(connfd);</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; accept failed, errno:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">        <span class="keyword">if</span> (errno == EMFILE) {</span><br><span class="line">            <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; sockfd reached limit"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="TcpServer"><a href="#TcpServer" class="headerlink" title="TcpServer"></a>TcpServer</h3><ul><li><code>TcpServer.h</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Acceptor.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Callbacks.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoopThreadPool.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"InetAddress.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpConnection.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"atomic"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"unordered_map"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP 服务器类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TcpServer</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 线程初始化回调操作类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ThreadInitCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>(EventLoop*)&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 端口复用选项枚举类型定义</span></span><br><span class="line">    <span class="class"><span class="keyword">enum</span> <span class="title">Option</span> {</span></span><br><span class="line">        kNoReusePort,</span><br><span class="line">        kReusePort,</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="built_in">TcpServer</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; listenAddr, <span class="keyword">const</span> std::string nameArg, Option option = kNoReusePort);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">TcpServer</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取服务器监听的 IP 和端口信息</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">ipPort</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取服务器名称</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">name</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取服务器的事件循环</span></span><br><span class="line">    <span class="function">EventLoop* <span class="title">getLoop</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置线程池的线程数量（即底层 subLoop 的数量）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setThreadNum</span><span class="params">(<span class="keyword">int</span> numThreads)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动服务器（线程安全）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置线程初始化回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setThreadInitCallback</span><span class="params">(<span class="keyword">const</span> ThreadInitCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置有新连接到来时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setConnectionCallback</span><span class="params">(<span class="keyword">const</span> ConnectionCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置有数据到来时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setMessageCallback</span><span class="params">(<span class="keyword">const</span> MessageCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置数据发送完成时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setWriteCompleteCallback</span><span class="params">(<span class="keyword">const</span> WriteCompleteCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// TCP 连接集合类型定义</span></span><br><span class="line">    <span class="keyword">using</span> ConnectionMap = std::unordered_map&lt;std::string, TcpConnectionPtr&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建 TCP 连接（在 baseLoop 上执行）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">newConnection</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> InetAddress&amp; peerAddr)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 TCP 连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">removeConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 TCP 连接（在 baseLoop 上执行）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">removeConnectionInLoop</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span></span>;</span><br><span class="line"></span><br><span class="line">    EventLoop* loop_;  <span class="comment">// 用户自定义的 EventLoop（即 baseLoop，也称作 mainLoop，运行在主线程上）</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> std::string name_;              <span class="comment">// 服务器名称</span></span><br><span class="line">    <span class="keyword">const</span> std::string ipPort_;            <span class="comment">// 服务器监听的 IP 和端口信息</span></span><br><span class="line">    std::unique_ptr&lt;Acceptor&gt; acceptor_;  <span class="comment">// 用于监听新连接的 Acceptor 对象，运行在 baseLoop 上</span></span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;EventLoopThreadPool&gt; threadPool_;  <span class="comment">// 事件循环线程池</span></span><br><span class="line"></span><br><span class="line">    ConnectionCallback connectionCallback_;        <span class="comment">// 有新连接到来时的回调操作</span></span><br><span class="line">    MessageCallback messageCallback_;              <span class="comment">// 有数据到来时的回调操作</span></span><br><span class="line">    WriteCompleteCallback writeCompleteCallback_;  <span class="comment">// 数据发送完成时的回调操作</span></span><br><span class="line">    ThreadInitCallback threadInitCallback_;        <span class="comment">// 线程初始化回调操作</span></span><br><span class="line"></span><br><span class="line">    std::<span class="keyword">atomic_int</span> started_;    <span class="comment">// 标记服务器是否已经启动</span></span><br><span class="line">    <span class="keyword">int</span> nextConnId_;             <span class="comment">// 下一个 TCP 连接的 ID</span></span><br><span class="line">    ConnectionMap connections_;  <span class="comment">// 保存所有的 TCP 连接</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>TcpServer.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpServer.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;assert.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpConnection.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> EventLoop* <span class="title">CheckLoopNotNull</span><span class="params">(EventLoop* loop)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (loop == <span class="literal">nullptr</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; baseLoop is null"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> loop;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">TcpServer::<span class="built_in">TcpServer</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; listenAddr, <span class="keyword">const</span> std::string nameArg, Option option)</span><br><span class="line">    : <span class="built_in">loop_</span>(<span class="built_in">CheckLoopNotNull</span>(loop)),</span><br><span class="line">      <span class="built_in">ipPort_</span>(listenAddr.<span class="built_in">toIpPort</span>()),</span><br><span class="line">      <span class="built_in">name_</span>(nameArg),</span><br><span class="line">      <span class="built_in">acceptor_</span>(<span class="keyword">new</span> <span class="built_in">Acceptor</span>(loop, listenAddr, option == kReusePort)),</span><br><span class="line">      <span class="built_in">threadPool_</span>(<span class="keyword">new</span> <span class="built_in">EventLoopThreadPool</span>(loop, name_)),</span><br><span class="line">      <span class="built_in">connectionCallback_</span>(defaultConnectionCallback),</span><br><span class="line">      <span class="built_in">messageCallback_</span>(defaultMessageCallback),</span><br><span class="line">      <span class="built_in">nextConnId_</span>(<span class="number">1</span>),</span><br><span class="line">      <span class="built_in">started_</span>(<span class="number">0</span>) {</span><br><span class="line">    <span class="comment">// 当有新客户端连接进来时，会调用 TcpServer::newConnection() 函数</span></span><br><span class="line">    acceptor_-&gt;<span class="built_in">setNewConnectionCallback</span>(</span><br><span class="line">        std::<span class="built_in">bind</span>(&amp;TcpServer::newConnection, <span class="keyword">this</span>, std::placeholders::_1, std::placeholders::_2));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">TcpServer::~<span class="built_in">TcpServer</span>() {</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; tcp server [%s] destructing"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历所有 TCP 连接</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; item : connections_) {</span><br><span class="line">        <span class="comment">// 这个局部的智能指针对象出了右括号后，会自动释放掉对应的 TcpConnection 资源</span></span><br><span class="line">        <span class="function">TcpConnectionPtr <span class="title">conn</span><span class="params">(item.second)</span></span>;</span><br><span class="line">        <span class="comment">// 重置原有的智能指针</span></span><br><span class="line">        item.second.<span class="built_in">reset</span>();</span><br><span class="line">        <span class="comment">// 唤醒 TCP 连接所在的 EventLoop 去执行 TcpConnection::connectDestroyed() 函数</span></span><br><span class="line">        conn-&gt;<span class="built_in">getLoop</span>()-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::connectDestroyed, conn));</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取服务器监听的 IP 和端口信息</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">TcpServer::ipPort</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> ipPort_;</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="function"><span class="keyword">const</span> std::string&amp; <span class="title">TcpServer::name</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> name_;</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="function">EventLoop* <span class="title">TcpServer::getLoop</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> loop_;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置线程池的线程数量（即底层 subLoop 的数量）</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpServer::setThreadNum</span><span class="params">(<span class="keyword">int</span> numThreads)</span> </span>{</span><br><span class="line">    threadPool_-&gt;<span class="built_in">setThreadNum</span>(numThreads);</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="function"><span class="keyword">void</span> <span class="title">TcpServer::start</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 防止 TcpServer 被多次启动</span></span><br><span class="line">    <span class="keyword">if</span> (started_++ == <span class="number">0</span>) {</span><br><span class="line">        <span class="comment">// 启动多个子线程，并各自运行一个 subLoop</span></span><br><span class="line">        threadPool_-&gt;<span class="built_in">start</span>(threadInitCallback_);</span><br><span class="line">        <span class="comment">// 在 baseLoop（运行在主线程）上监听连接请求（即监听有新的客户端连接进来）</span></span><br><span class="line">        loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;Acceptor::listen, acceptor_.<span class="built_in">get</span>()));</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpServer::setThreadInitCallback</span><span class="params">(<span class="keyword">const</span> ThreadInitCallback&amp; cb)</span> </span>{</span><br><span class="line">    threadInitCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">TcpServer::setConnectionCallback</span><span class="params">(<span class="keyword">const</span> ConnectionCallback&amp; cb)</span> </span>{</span><br><span class="line">    connectionCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">TcpServer::setMessageCallback</span><span class="params">(<span class="keyword">const</span> MessageCallback&amp; cb)</span> </span>{</span><br><span class="line">    messageCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">TcpServer::setWriteCompleteCallback</span><span class="params">(<span class="keyword">const</span> WriteCompleteCallback&amp; cb)</span> </span>{</span><br><span class="line">    writeCompleteCallback_ = cb;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 TCP 连接（在 baseLoop 上执行）</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpServer::newConnection</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> InetAddress&amp; peerAddr)</span> </span>{</span><br><span class="line">    <span class="comment">// 通过轮询算法，获取下一个 subLoop（也称作 ioLoop）</span></span><br><span class="line">    EventLoop* ioLoop = threadPool_-&gt;<span class="built_in">getNextLoop</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 拼接 TCP 连接的名称</span></span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">64</span>] = {<span class="number">0</span>};</span><br><span class="line">    <span class="built_in">snprintf</span>(buf, <span class="keyword">sizeof</span> buf, <span class="string">"-%s#%d"</span>, ipPort_.<span class="built_in">c_str</span>(), nextConnId_);</span><br><span class="line">    ++nextConnId_;</span><br><span class="line">    std::string connName = name_ + buf;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; tcp server [%s] new connection [%s] from %s"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), connName.<span class="built_in">c_str</span>(),</span><br><span class="line">              ipPort_.<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取本地网络地址</span></span><br><span class="line">    sockaddr_in local;</span><br><span class="line">    ::<span class="built_in">bzero</span>(&amp;local, <span class="keyword">sizeof</span> local);</span><br><span class="line">    <span class="keyword">socklen_t</span> addrlen = <span class="keyword">sizeof</span> local;</span><br><span class="line">    <span class="keyword">if</span> (::<span class="built_in">getsockname</span>(sockfd, (sockaddr*)&amp;local, &amp;addrlen) &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; fail to get local internet address"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">    }</span><br><span class="line">    <span class="function">InetAddress <span class="title">localAddr</span><span class="params">(local)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据连接成功的 sockfd，创建 TCP 连接对象</span></span><br><span class="line">    <span class="function">TcpConnectionPtr <span class="title">conn</span><span class="params">(<span class="keyword">new</span> TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr))</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将新创建的 TCP 连接对象放进集合中</span></span><br><span class="line">    connections_[connName] = conn;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 TCP 连接的回调操作（由用户自定义）</span></span><br><span class="line">    conn-&gt;<span class="built_in">setConnectionCallback</span>(connectionCallback_);</span><br><span class="line">    conn-&gt;<span class="built_in">setMessageCallback</span>(messageCallback_);</span><br><span class="line">    conn-&gt;<span class="built_in">setWriteCompleteCallback</span>(writeCompleteCallback_);</span><br><span class="line">    conn-&gt;<span class="built_in">setCloseCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpServer::removeConnection, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒 ioLoop 所在的线程去执行 TcpConnection::connectEstablished() 函数</span></span><br><span class="line">    ioLoop-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::connectEstablished, conn));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除 TCP 连接</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpServer::removeConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span> </span>{</span><br><span class="line">    <span class="comment">// 唤醒 baseLoop 所在的线程去执行 TcpServer::removeConnectionInLoop() 函数</span></span><br><span class="line">    loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpServer::removeConnectionInLoop, <span class="keyword">this</span>, conn));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除 TCP 连接（在 baseLoop 上执行）</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpServer::removeConnectionInLoop</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; tcp server [%s] remove connection [%s]"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), conn-&gt;<span class="built_in">name</span>().<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 TCP 连接</span></span><br><span class="line">    <span class="keyword">size_t</span> n = connections_.<span class="built_in">erase</span>(conn-&gt;<span class="built_in">name</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒 TCP 连接所在的 EventLoop 去执行 TcpConnection::connectDestroyed() 函数</span></span><br><span class="line">    <span class="built_in">assert</span>(n == <span class="number">1</span>);</span><br><span class="line">    EventLoop* ioLoop = conn-&gt;<span class="built_in">getLoop</span>();</span><br><span class="line">    ioLoop-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::connectDestroyed, conn));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Connector"><a href="#Connector" class="headerlink" title="Connector"></a>Connector</h3><ul><li><code>Connector.h</code></li></ul><figure class="highlight c++"><table><tbody><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="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"InetAddress.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Channel</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventLoop</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP 连接器类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Connector</span> :</span> noncopyable, <span class="keyword">public</span> std::enable_shared_from_this&lt;Connector&gt; {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 有新连接建立时的回调操作类型定义</span></span><br><span class="line">    <span class="keyword">using</span> NewConnectionCallback = std::function&lt;<span class="built_in"><span class="keyword">void</span></span>(<span class="keyword">int</span> sockfd)&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">Connector</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; serverAddr);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">Connector</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置有新连接建立时的回调操作类型定义</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setNewConnectionCallback</span><span class="params">(<span class="keyword">const</span> NewConnectionCallback&amp; cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动连接器</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重启连接器（必须在 EventLoop 所处的线程上执行）</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">restart</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 停止连接器</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取服务器地址</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> InetAddress&amp; <span class="title">serverAddress</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 连接器的状态</span></span><br><span class="line">    <span class="class"><span class="keyword">enum</span> <span class="title">States</span> {</span> kDisconnected, kConnecting, kConnected };</span><br><span class="line">    <span class="comment">// 最大重试延迟时间（毫秒）</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kMaxRetryDelayMs;</span><br><span class="line">    <span class="comment">// 初始重试延迟时间（毫秒）</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kInitRetryDelayMs;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置连接状态</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setState</span><span class="params">(States s)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在 EventLoop 所处的线程上启动连接器</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">startInLoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在 EventLoop 所处的线程上停止连接器</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">stopInLoop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发起连接操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connect</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理正在连接的 Socket</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connecting</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理写事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleWrite</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理错误事件</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handleError</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重试连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">retry</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除并重置 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">removeAndResetChannel</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重置 Channel</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">resetChannel</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    EventLoop* loop_;                              <span class="comment">// 连接器所在的事件循环</span></span><br><span class="line">    InetAddress serverAddr_;                       <span class="comment">// 服务器地址</span></span><br><span class="line">    std::<span class="keyword">atomic_int</span> connect_;                      <span class="comment">// 标记是否需要连接</span></span><br><span class="line">    States state_;                                 <span class="comment">// 连接状态</span></span><br><span class="line">    std::unique_ptr&lt;Channel&gt; channel_;             <span class="comment">// 连接器对应的 Channel</span></span><br><span class="line">    NewConnectionCallback newConnectionCallback_;  <span class="comment">// 新连接建立时的回调操作</span></span><br><span class="line">    <span class="keyword">int</span> retryDelayMs_;                             <span class="comment">// 重试连接的延迟时间（毫秒）</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>Connector.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Connector.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;assert.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;error.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;chrono&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Channel.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"SocketsOps.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义初始重试延迟时间（毫秒）</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Connector::kInitRetryDelayMs = <span class="number">500</span>;</span><br><span class="line"><span class="comment">// 定义最大重试延迟时间（毫秒）</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Connector::kMaxRetryDelayMs = <span class="number">30</span> * <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">Connector::<span class="built_in">Connector</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; serverAddr)</span><br><span class="line">    : <span class="built_in">loop_</span>(loop), <span class="built_in">serverAddr_</span>(serverAddr), <span class="built_in">connect_</span>(<span class="literal">false</span>), <span class="built_in">state_</span>(kDisconnected), <span class="built_in">retryDelayMs_</span>(kInitRetryDelayMs) {</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; create connector at %p"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">Connector::~<span class="built_in">Connector</span>() {</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; destruct connector at %p"</span>, __PRETTY_FUNCTION__, <span class="keyword">this</span>);</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="function"><span class="keyword">void</span> <span class="title">Connector::setNewConnectionCallback</span><span class="params">(<span class="keyword">const</span> NewConnectionCallback&amp; cb)</span> </span>{</span><br><span class="line">    newConnectionCallback_ = cb;</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="function"><span class="keyword">void</span> <span class="title">Connector::start</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记需要连接</span></span><br><span class="line">    connect_ = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// 唤醒 loop_ 对应的线程去启动连接器</span></span><br><span class="line">    loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;Connector::startInLoop, <span class="keyword">this</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在 EventLoop 所处的线程上启动连接器</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Connector::startInLoop</span><span class="params">()</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="built_in">assert</span>(state_ == kDisconnected);</span><br><span class="line">    <span class="comment">// 判断是否需要连接</span></span><br><span class="line">    <span class="keyword">if</span> (connect_) {</span><br><span class="line">        <span class="comment">// 发起连接操作</span></span><br><span class="line">        <span class="built_in">connect</span>();</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; do not connect"</span>);</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Connector::stop</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记不再连接</span></span><br><span class="line">    connect_ = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 唤醒 loop_ 对应的线程去关闭连接器</span></span><br><span class="line">    loop_-&gt;<span class="built_in">queueInLoop</span>(std::<span class="built_in">bind</span>(&amp;Connector::stopInLoop, <span class="keyword">this</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在 EventLoop 所处的线程上停止连接器</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Connector::stopInLoop</span><span class="params">()</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnecting) {</span><br><span class="line">        <span class="comment">// 设置连接状态为已断开</span></span><br><span class="line">        <span class="built_in">setState</span>(kDisconnected);</span><br><span class="line">        <span class="comment">// 移除并重置 Channel</span></span><br><span class="line">        <span class="keyword">int</span> sockfd = <span class="built_in">removeAndResetChannel</span>();</span><br><span class="line">        <span class="comment">// 重试连接</span></span><br><span class="line">        <span class="built_in">retry</span>(sockfd);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 重启连接器（必须在 EventLoop 所处的线程上执行）</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Connector::restart</span><span class="params">()</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="comment">// 设置连接状态</span></span><br><span class="line">    <span class="built_in">setState</span>(kDisconnected);</span><br><span class="line">    <span class="comment">// 重置重试延迟时间</span></span><br><span class="line">    retryDelayMs_ = kInitRetryDelayMs;</span><br><span class="line">    <span class="comment">// 标记需要连接</span></span><br><span class="line">    connect_ = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// 启动连接器</span></span><br><span class="line">    <span class="built_in">startInLoop</span>();</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="function"><span class="keyword">const</span> InetAddress&amp; <span class="title">Connector::serverAddress</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> serverAddr_;</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="function"><span class="keyword">void</span> <span class="title">Connector::setState</span><span class="params">(States s)</span> </span>{</span><br><span class="line">    state_ = s;</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="function"><span class="keyword">void</span> <span class="title">Connector::connect</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 创建非阻塞的 Socket</span></span><br><span class="line">    <span class="keyword">int</span> sockfd = <span class="built_in">createNonblockingSocket</span>();</span><br><span class="line">    <span class="comment">// 连接 TCP 服务器</span></span><br><span class="line">    <span class="keyword">int</span> ret = ::<span class="built_in">connect</span>(sockfd, (sockaddr*)serverAddr_.<span class="built_in">getSockAddr</span>(), <span class="built_in"><span class="keyword">sizeof</span></span>(sockaddr_in));</span><br><span class="line">    <span class="comment">// 处理连接结果</span></span><br><span class="line">    <span class="keyword">int</span> savedErrno = (ret == <span class="number">0</span>) ? <span class="number">0</span> : errno;</span><br><span class="line">    <span class="built_in"><span class="keyword">switch</span></span> (savedErrno) {</span><br><span class="line">        <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">case</span> EINPROGRESS:</span><br><span class="line">        <span class="keyword">case</span> EINTR:</span><br><span class="line">        <span class="keyword">case</span> EISCONN:</span><br><span class="line">            <span class="comment">// 处理正在连接的 Socket</span></span><br><span class="line">            <span class="built_in">connecting</span>(sockfd);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">case</span> EAGAIN:</span><br><span class="line">        <span class="keyword">case</span> EADDRINUSE:</span><br><span class="line">        <span class="keyword">case</span> EADDRNOTAVAIL:</span><br><span class="line">        <span class="keyword">case</span> ECONNREFUSED:</span><br><span class="line">        <span class="keyword">case</span> ENETUNREACH:</span><br><span class="line">            <span class="comment">// 重新连接</span></span><br><span class="line">            <span class="built_in">retry</span>(sockfd);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">case</span> EACCES:</span><br><span class="line">        <span class="keyword">case</span> EPERM:</span><br><span class="line">        <span class="keyword">case</span> EAFNOSUPPORT:</span><br><span class="line">        <span class="keyword">case</span> EALREADY:</span><br><span class="line">        <span class="keyword">case</span> EBADF:</span><br><span class="line">        <span class="keyword">case</span> EFAULT:</span><br><span class="line">        <span class="keyword">case</span> ENOTSOCK:</span><br><span class="line">            <span class="comment">// 打印日志信息</span></span><br><span class="line">            <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; connect error, errno:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">            <span class="comment">// 关闭连接</span></span><br><span class="line">            ::<span class="built_in">close</span>(sockfd);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="comment">// 打印日志信息</span></span><br><span class="line">            <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; unexpected error, errno:%d"</span>, __PRETTY_FUNCTION__, errno);</span><br><span class="line">            <span class="comment">// 关闭连接</span></span><br><span class="line">            ::<span class="built_in">close</span>(sockfd);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理正在连接的 Socket</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Connector::connecting</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    <span class="comment">// 设置连接状态为正在连接</span></span><br><span class="line">    <span class="built_in">setState</span>(kConnecting);</span><br><span class="line">    <span class="comment">// 创建 Channel 并注册写事件和错误事件的回调操作</span></span><br><span class="line">    channel_.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">Channel</span>(loop_, sockfd));</span><br><span class="line">    channel_-&gt;<span class="built_in">setWriteCallback</span>(std::<span class="built_in">bind</span>(&amp;Connector::handleWrite, <span class="keyword">this</span>));</span><br><span class="line">    channel_-&gt;<span class="built_in">setErrorCallback</span>(std::<span class="built_in">bind</span>(&amp;Connector::handleError, <span class="keyword">this</span>));</span><br><span class="line">    <span class="comment">// Channel 开启监听 fd 上的写事件</span></span><br><span class="line">    channel_-&gt;<span class="built_in">enableWriting</span>();</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="function"><span class="keyword">void</span> <span class="title">Connector::handleWrite</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; state:%d"</span>, __PRETTY_FUNCTION__, state_);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnecting) {</span><br><span class="line">        <span class="comment">// 移除并重置 Channel</span></span><br><span class="line">        <span class="keyword">int</span> sockfd = <span class="built_in">removeAndResetChannel</span>();</span><br><span class="line">        <span class="comment">// 获取 Socket 错误码</span></span><br><span class="line">        <span class="keyword">int</span> savedErrno = <span class="built_in">getSocketError</span>(sockfd);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 发生错误</span></span><br><span class="line">        <span class="keyword">if</span> (savedErrno) {</span><br><span class="line">            <span class="comment">// 打印日志信息</span></span><br><span class="line">            <span class="built_in">LOG_WARN</span>(<span class="string">"%s =&gt; SO_ERROR=%d"</span>, __PRETTY_FUNCTION__, savedErrno);</span><br><span class="line">            <span class="comment">// 重新连接</span></span><br><span class="line">            <span class="built_in">retry</span>(sockfd);</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 发生自连接</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">isSelfConnect</span>(sockfd)) {</span><br><span class="line">            <span class="comment">// 打印日志信息</span></span><br><span class="line">            <span class="built_in">LOG_WARN</span>(<span class="string">"%s =&gt; self connect"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">            <span class="comment">// 重新连接</span></span><br><span class="line">            <span class="built_in">retry</span>(sockfd);</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 连接成功</span></span><br><span class="line">        <span class="keyword">else</span> {</span><br><span class="line">            <span class="comment">// 设置连接状态为已连接</span></span><br><span class="line">            <span class="built_in">setState</span>(kConnected);</span><br><span class="line">            <span class="comment">// 判断是否需要连接</span></span><br><span class="line">            <span class="keyword">if</span> (connect_) {</span><br><span class="line">                <span class="comment">// 需要连接，执行有新连接建立时的回调操作</span></span><br><span class="line">                <span class="built_in">newConnectionCallback_</span>(sockfd);</span><br><span class="line">            } <span class="keyword">else</span> {</span><br><span class="line">                <span class="comment">// 不需要连接，关闭该连接</span></span><br><span class="line">                ::<span class="built_in">close</span>(sockfd);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">assert</span>(state_ == kDisconnected);</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Connector::handleError</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_ERROR</span>(<span class="string">"%s =&gt; occurred error, state:%d"</span>, __PRETTY_FUNCTION__, state_);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (state_ == kConnecting) {</span><br><span class="line">        <span class="comment">// 移除并重置 Channel</span></span><br><span class="line">        <span class="keyword">int</span> sockfd = <span class="built_in">removeAndResetChannel</span>();</span><br><span class="line">        <span class="comment">// 获取 Socket 错误码</span></span><br><span class="line">        <span class="keyword">int</span> savedErrno = <span class="built_in">getSocketError</span>(sockfd);</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; SO_ERROR:%d"</span>, __PRETTY_FUNCTION__, savedErrno);</span><br><span class="line">        <span class="comment">// 重新连接</span></span><br><span class="line">        <span class="built_in">retry</span>(sockfd);</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">Connector::retry</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    <span class="comment">// 关闭连接</span></span><br><span class="line">    ::<span class="built_in">close</span>(sockfd);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置连接状态</span></span><br><span class="line">    <span class="built_in">setState</span>(kDisconnected);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断是否需要连接</span></span><br><span class="line">    <span class="keyword">if</span> (connect_) {</span><br><span class="line">        <span class="comment">// 获取当前的重试延迟时间</span></span><br><span class="line">        <span class="keyword">int</span> delay = retryDelayMs_;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取 shared_ptr 指向的自身对象</span></span><br><span class="line">        <span class="keyword">auto</span> self = <span class="built_in">shared_from_this</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_INFO</span>(<span class="string">"%s =&gt; retry connecting to %s in %d milliseconds"</span>, __PRETTY_FUNCTION__, serverAddr_.<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>(),</span><br><span class="line">                 delay);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 在一个独立的线程中等待一段时间后启动连接器</span></span><br><span class="line">        std::<span class="built_in">thread</span>([self, delay]() {</span><br><span class="line">            <span class="comment">// 等待一段时间</span></span><br><span class="line">            std::this_thread::<span class="built_in">sleep_for</span>(std::chrono::<span class="built_in">milliseconds</span>(delay));</span><br><span class="line">            <span class="comment">// 唤醒 loop_ 对应的线程去启动连接器</span></span><br><span class="line">            self-&gt;loop_-&gt;<span class="built_in">queueInLoop</span>([self]() { self-&gt;<span class="built_in">startInLoop</span>(); });</span><br><span class="line">        }).<span class="built_in">detach</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 指数退避算法，增加重试延迟时间</span></span><br><span class="line">        retryDelayMs_ = std::<span class="built_in">min</span>(retryDelayMs_ * <span class="number">2</span>, kMaxRetryDelayMs);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">LOG_DEBUG</span>(<span class="string">"%s =&gt; do not connect"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除并重置 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">Connector::removeAndResetChannel</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 禁用 Channel 的所有事件监听</span></span><br><span class="line">    channel_-&gt;<span class="built_in">disableAll</span>();</span><br><span class="line">    <span class="comment">// 从 Poller 中删除 Channel</span></span><br><span class="line">    channel_-&gt;<span class="built_in">remove</span>();</span><br><span class="line">    <span class="comment">// 获取 Channel 对应的 sockfd</span></span><br><span class="line">    <span class="keyword">int</span> sockfd = channel_-&gt;<span class="built_in">fd</span>();</span><br><span class="line">    <span class="comment">// 唤醒 loop_ 对应的线程去重置 Channel</span></span><br><span class="line">    loop_-&gt;<span class="built_in">queueInLoop</span>(std::<span class="built_in">bind</span>(&amp;Connector::resetChannel, <span class="keyword">this</span>));</span><br><span class="line">    <span class="keyword">return</span> sockfd;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 重置 Channel</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Connector::resetChannel</span><span class="params">()</span> </span>{</span><br><span class="line">    channel_.<span class="built_in">reset</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="TcpClient"><a href="#TcpClient" class="headerlink" title="TcpClient"></a>TcpClient</h3><ul><li><code>TcpClient.h</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;mutex&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpConnection.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"noncopyable.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类前置声明</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Connector</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP 连接器智能指针类型定义</span></span><br><span class="line"><span class="keyword">using</span> ConnectorPtr = std::shared_ptr&lt;Connector&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TCP 客户端</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TcpClient</span> :</span> noncopyable {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">TcpClient</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; serverAddr, <span class="keyword">const</span> std::string&amp; nameArg);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">TcpClient</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发起连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connect</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 断开连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">disconnect</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭客户端</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前的 TCP 连接</span></span><br><span class="line">    <span class="function">TcpConnectionPtr <span class="title">connection</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取事件循环</span></span><br><span class="line">    <span class="function">EventLoop* <span class="title">getLoop</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否允许重试连接</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">retry</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 允许重试连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">enableRetry</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取客户端名称</span></span><br><span class="line">    <span class="function"><span class="keyword">const</span> std::string&amp; <span class="title">name</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置连接建立/关闭时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setConnectionCallback</span><span class="params">(ConnectionCallback cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置有数据到来时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setMessageCallback</span><span class="params">(MessageCallback cb)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置数据发送完成时的回调操作</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setWriteCompleteCallback</span><span class="params">(WriteCompleteCallback cb)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 创建新连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">newConnection</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">removeConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span></span>;</span><br><span class="line"></span><br><span class="line">    EventLoop* loop_;                              <span class="comment">// 事件循环</span></span><br><span class="line">    ConnectorPtr connector_;                       <span class="comment">// 连接器</span></span><br><span class="line">    <span class="keyword">const</span> std::string name_;                       <span class="comment">// 客户端名称</span></span><br><span class="line">    ConnectionCallback connectionCallback_;        <span class="comment">// 连接建立/关闭时的回调操作</span></span><br><span class="line">    MessageCallback messageCallback_;              <span class="comment">// 有数据到来时的回调操作</span></span><br><span class="line">    WriteCompleteCallback writeCompleteCallback_;  <span class="comment">// 数据发送完成时的回调操作</span></span><br><span class="line">    std::<span class="keyword">atomic_bool</span> retry_;                       <span class="comment">// 是否允许重试连接（即断线重连）</span></span><br><span class="line">    std::<span class="keyword">atomic_bool</span> connect_;                     <span class="comment">// 是否需要连接</span></span><br><span class="line">    <span class="keyword">int</span> nextConnId_;                               <span class="comment">// 下一个 TCP 连接的 ID</span></span><br><span class="line">    std::mutex mutex_;                             <span class="comment">// 互斥锁</span></span><br><span class="line">    TcpConnectionPtr connection_;                  <span class="comment">// TCP 连接</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>TcpClient.cc</code></li></ul><figure class="highlight c++"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpClient.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;assert.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;chrono&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;functional&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;memory&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Connector.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"EventLoop.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"SocketsOps.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查 EventLoop 指针是否为空</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> EventLoop* <span class="title">CheckLoopNotNull</span><span class="params">(EventLoop* loop)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (loop == <span class="literal">nullptr</span>) {</span><br><span class="line">        <span class="built_in">LOG_FATAL</span>(<span class="string">"%s =&gt; eventloop is null"</span>, __PRETTY_FUNCTION__);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> loop;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> detail {</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移除 TCP 连接</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">removeConnection</span><span class="params">(EventLoop* loop, <span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span> </span>{</span><br><span class="line">        loop-&gt;<span class="built_in">queueInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::connectDestroyed, conn));</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="function"><span class="keyword">void</span> <span class="title">removeConnector</span><span class="params">(<span class="keyword">const</span> ConnectorPtr&amp; connector)</span> </span>{</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}  <span class="comment">// namespace detail</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">TcpClient::<span class="built_in">TcpClient</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; serverAddr, <span class="keyword">const</span> std::string&amp; nameArg)</span><br><span class="line">    : <span class="built_in">loop_</span>(<span class="built_in">CheckLoopNotNull</span>(loop)),</span><br><span class="line">      <span class="built_in">connector_</span>(<span class="keyword">new</span> <span class="built_in">Connector</span>(loop_, serverAddr)),</span><br><span class="line">      <span class="built_in">name_</span>(nameArg),</span><br><span class="line">      <span class="built_in">connectionCallback_</span>(defaultConnectionCallback),</span><br><span class="line">      <span class="built_in">messageCallback_</span>(defaultMessageCallback),</span><br><span class="line">      <span class="built_in">retry_</span>(<span class="literal">false</span>),</span><br><span class="line">      <span class="built_in">connect_</span>(<span class="literal">true</span>),</span><br><span class="line">      <span class="built_in">nextConnId_</span>(<span class="number">1</span>) {</span><br><span class="line">    <span class="comment">// 设置有新连接建立时的回调操作</span></span><br><span class="line">    connector_-&gt;<span class="built_in">setNewConnectionCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpClient::newConnection, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_INFO</span>(<span class="string">"%s =&gt; crate tcp client [%s] - connector %p"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), connector_.<span class="built_in">get</span>());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">TcpClient::~<span class="built_in">TcpClient</span>() {</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_INFO</span>(<span class="string">"%s =&gt; destruct tcp client [%s] - connector %p"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(), connector_.<span class="built_in">get</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前 TcpConnection 的智能指针副本，并判断它是否是唯一拥有者</span></span><br><span class="line">    TcpConnectionPtr conn;</span><br><span class="line">    <span class="keyword">bool</span> unique = <span class="literal">true</span>;</span><br><span class="line">    {</span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        unique = connection_.<span class="built_in">unique</span>();</span><br><span class="line">        conn = connection_;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (conn) {</span><br><span class="line">        <span class="built_in">assert</span>(loop_ == conn-&gt;<span class="built_in">getLoop</span>());</span><br><span class="line">        <span class="comment">// 设置 TCP 连接关闭时的回调操作</span></span><br><span class="line">        CloseCallback cb = std::<span class="built_in">bind</span>(&amp;detail::removeConnection, loop_, std::placeholders::_1);</span><br><span class="line">        loop_-&gt;<span class="built_in">runInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::setCloseCallback, conn, cb));</span><br><span class="line">        <span class="comment">// 如果 TCP 连接唯一</span></span><br><span class="line">        <span class="keyword">if</span> (unique) {</span><br><span class="line">            <span class="comment">// 强制关闭 TCP 连接</span></span><br><span class="line">            conn-&gt;forceClose();</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 关闭连接器</span></span><br><span class="line">        connector_-&gt;<span class="built_in">stop</span>();</span><br><span class="line">        <span class="comment">// 获取当前的连接器</span></span><br><span class="line">        <span class="keyword">auto</span> connector = connector_;</span><br><span class="line">        <span class="comment">// 唤醒 loop_ 所在的线程去移除连接器</span></span><br><span class="line">        loop_-&gt;<span class="built_in">runInLoop</span>([connector]() { detail::<span class="built_in">removeConnector</span>(connector); });</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">TcpClient::connect</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_INFO</span>(<span class="string">"%s =&gt; connect to %s"</span>, __PRETTY_FUNCTION__, connector_-&gt;<span class="built_in">serverAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="comment">// 标记需要连接</span></span><br><span class="line">    connect_ = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// 启动连接器</span></span><br><span class="line">    connector_-&gt;<span class="built_in">start</span>();</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="function"><span class="keyword">void</span> <span class="title">TcpClient::disconnect</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记不需要连接</span></span><br><span class="line">    connect_ = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 关闭当前 TCP 连接</span></span><br><span class="line">    {</span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        <span class="keyword">if</span> (connection_) {</span><br><span class="line">            connection_-&gt;<span class="built_in">shutdown</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"><span class="comment">// 关闭客户端</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TcpClient::stop</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 标记不需要连接</span></span><br><span class="line">    connect_ = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 关闭连接器</span></span><br><span class="line">    connector_-&gt;<span class="built_in">stop</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取当前的 TCP 连接</span></span><br><span class="line"><span class="function">TcpConnectionPtr <span class="title">TcpClient::connection</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">    <span class="keyword">return</span> connection_;</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="function">EventLoop* <span class="title">TcpClient::getLoop</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> loop_;</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="function"><span class="keyword">bool</span> <span class="title">TcpClient::retry</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> retry_;</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="function"><span class="keyword">void</span> <span class="title">TcpClient::enableRetry</span><span class="params">()</span> </span>{</span><br><span class="line">    retry_ = <span class="literal">true</span>;</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="function"><span class="keyword">const</span> std::string&amp; <span class="title">TcpClient::name</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> name_;</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="function"><span class="keyword">void</span> <span class="title">TcpClient::setConnectionCallback</span><span class="params">(ConnectionCallback cb)</span> </span>{</span><br><span class="line">    connectionCallback_ = std::<span class="built_in">move</span>(cb);</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="function"><span class="keyword">void</span> <span class="title">TcpClient::setMessageCallback</span><span class="params">(MessageCallback cb)</span> </span>{</span><br><span class="line">    messageCallback_ = std::<span class="built_in">move</span>(cb);</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="function"><span class="keyword">void</span> <span class="title">TcpClient::setWriteCompleteCallback</span><span class="params">(WriteCompleteCallback cb)</span> </span>{</span><br><span class="line">    writeCompleteCallback_ = std::<span class="built_in">move</span>(cb);</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="function"><span class="keyword">void</span> <span class="title">TcpClient::newConnection</span><span class="params">(<span class="keyword">int</span> sockfd)</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 远端地址</span></span><br><span class="line">    <span class="function">InetAddress <span class="title">peerAddr</span><span class="params">(getPeerAddr(sockfd))</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 拼接 TCP 连接的名称</span></span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">32</span>] = {<span class="number">0</span>};</span><br><span class="line">    <span class="built_in">snprintf</span>(buf, <span class="keyword">sizeof</span> buf, <span class="string">":%s#%d"</span>, peerAddr.<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>(), nextConnId_);</span><br><span class="line">    ++nextConnId_;</span><br><span class="line">    std::string connName = name_ + buf;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 本端地址</span></span><br><span class="line">    <span class="function">InetAddress <span class="title">localAddr</span><span class="params">(getLocalAddr(sockfd))</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建 TCP 连接对象</span></span><br><span class="line">    <span class="function">TcpConnectionPtr <span class="title">conn</span><span class="params">(<span class="keyword">new</span> TcpConnection(loop_, connName, sockfd, localAddr, peerAddr))</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置 TCP 连接的回调操作</span></span><br><span class="line">    conn-&gt;<span class="built_in">setConnectionCallback</span>(connectionCallback_);</span><br><span class="line">    conn-&gt;<span class="built_in">setMessageCallback</span>(messageCallback_);</span><br><span class="line">    conn-&gt;<span class="built_in">setWriteCompleteCallback</span>(writeCompleteCallback_);</span><br><span class="line">    conn-&gt;<span class="built_in">setCloseCallback</span>(std::<span class="built_in">bind</span>(&amp;TcpClient::removeConnection, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置当前的 TCP 连接</span></span><br><span class="line">    {</span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        connection_ = conn;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 建立连接</span></span><br><span class="line">    conn-&gt;<span class="built_in">connectEstablished</span>();</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="function"><span class="keyword">void</span> <span class="title">TcpClient::removeConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span> </span>{</span><br><span class="line">    loop_-&gt;<span class="built_in">assertInLoopThread</span>();</span><br><span class="line">    <span class="built_in">assert</span>(loop_ == conn-&gt;<span class="built_in">getLoop</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重置当前的 TCP 连接</span></span><br><span class="line">    {</span><br><span class="line">        <span class="function">std::unique_lock&lt;std::mutex&gt; <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">        <span class="built_in">assert</span>(connection_ == conn);</span><br><span class="line">        connection_.<span class="built_in">reset</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒 loop_ 所在的线程去销毁 TCP 连接</span></span><br><span class="line">    loop_-&gt;<span class="built_in">queueInLoop</span>(std::<span class="built_in">bind</span>(&amp;TcpConnection::connectDestroyed, conn));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果允许重试连接，且需要连接</span></span><br><span class="line">    <span class="keyword">if</span> (retry_ &amp;&amp; connect_) {</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_INFO</span>(<span class="string">"%s =&gt; tcp client [%s] reconnecting to %s"</span>, __PRETTY_FUNCTION__, name_.<span class="built_in">c_str</span>(),</span><br><span class="line">                 connector_-&gt;<span class="built_in">serverAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line">        <span class="comment">// 重启连接器</span></span><br><span class="line">        connector_-&gt;<span class="built_in">restart</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="项目测试"><a href="#项目测试" class="headerlink" title="项目测试"></a>项目测试</h2><h3 id="测试代码"><a href="#测试代码" class="headerlink" title="测试代码"></a>测试代码</h3><ul><li><code>ChatClient.h</code></li></ul><figure class="highlight c++"><table><tbody><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">/**</span></span><br><span class="line"><span class="comment"> * 基于 MyMuduo 网络库开发 TCP 客户端程序</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpClient.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 聊天客户端</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChatClient</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">ChatClient</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; serverAddr, <span class="keyword">const</span> std::string&amp; nameArg);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">ChatClient</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 连接服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connect</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 客户端绑定连接回调函数，当连接或者断开服务器时调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 客户端绑定消息回调函数，当有数据接收时调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn, Buffer* buf, Timestamp time)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// TCP 客户端</span></span><br><span class="line">    TcpClient client_;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// EventLoop 事件循环</span></span><br><span class="line">    EventLoop* loop_;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>ChatClient.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 基于 MyMuduo 网络库开发 TCP 客户端程序</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ChatClient.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">ChatClient::<span class="built_in">ChatClient</span>(EventLoop* loop, <span class="keyword">const</span> InetAddress&amp; serverAddr, <span class="keyword">const</span> std::string&amp; nameArg)</span><br><span class="line">    : <span class="built_in">client_</span>(loop, serverAddr, nameArg), <span class="built_in">loop_</span>(loop) {</span><br><span class="line">    <span class="comment">// 允许重试连接</span></span><br><span class="line">    client_.<span class="built_in">enableRetry</span>();</span><br><span class="line">    <span class="comment">// 设置客户端TCP连接的回调</span></span><br><span class="line">    client_.<span class="built_in">setConnectionCallback</span>(std::<span class="built_in">bind</span>(&amp;ChatClient::onConnection, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line">    <span class="comment">// 设置客户端接收数据的回调</span></span><br><span class="line">    client_.<span class="built_in">setMessageCallback</span>(</span><br><span class="line">        std::<span class="built_in">bind</span>(&amp;ChatClient::onMessage, <span class="keyword">this</span>, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">ChatClient::~<span class="built_in">ChatClient</span>() {</span><br><span class="line">    <span class="comment">// 发起断开连接</span></span><br><span class="line">    client_.<span class="built_in">disconnect</span>();</span><br><span class="line">    <span class="comment">// 停止内部 Connector 的重连机制，避免异步行为</span></span><br><span class="line">    client_.<span class="built_in">stop</span>();</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="function"><span class="keyword">void</span> <span class="title">ChatClient::connect</span><span class="params">()</span> </span>{</span><br><span class="line">    client_.<span class="built_in">connect</span>();</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="function"><span class="keyword">void</span> <span class="title">ChatClient::onConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn)</span> </span>{</span><br><span class="line">    <span class="comment">// 连接创建</span></span><br><span class="line">    <span class="keyword">if</span> (conn-&gt;<span class="built_in">connected</span>()) {</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_INFO</span>(<span class="string">"ChatClient - new connection [%s] -&gt; [%s], state: connected"</span>, conn-&gt;<span class="built_in">localAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>(),</span><br><span class="line">                 conn-&gt;<span class="built_in">peerAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line">        <span class="comment">// 发送消息</span></span><br><span class="line">        conn-&gt;<span class="built_in">send</span>(<span class="string">"I'm "</span> + client_.<span class="built_in">name</span>());</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 连接断开</span></span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 打印日志信息</span></span><br><span class="line">        <span class="built_in">LOG_INFO</span>(<span class="string">"ChatClient - close connection [%s] -&gt; [%s], state: disconnected"</span>,</span><br><span class="line">                 conn-&gt;<span class="built_in">localAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>(), conn-&gt;<span class="built_in">peerAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">ChatClient::onMessage</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr&amp; conn, Buffer* buf, Timestamp time)</span> </span>{</span><br><span class="line">    <span class="comment">// 获取服务器发送的消息</span></span><br><span class="line">    std::string message = buf-&gt;<span class="built_in">retrieveAllAsString</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 去掉消息末尾的 '\r' 和 '\n' 字符（nc 命令会发送 CRLF）</span></span><br><span class="line">    <span class="keyword">while</span> (!message.<span class="built_in">empty</span>() &amp;&amp; (message.<span class="built_in">back</span>() == <span class="string">'\n'</span> || message.<span class="built_in">back</span>() == <span class="string">'\r'</span>)) {</span><br><span class="line">        message.<span class="built_in">pop_back</span>();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="built_in">LOG_INFO</span>(<span class="string">"ChatClient - receive message: [%s], time: %s"</span>, message.<span class="built_in">c_str</span>(), time.<span class="built_in">toString</span>().<span class="built_in">c_str</span>());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li><code>ChatServer.h</code></li></ul><figure class="highlight c++"><table><tbody><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">/**</span></span><br><span class="line"><span class="comment"> * 基于 MyMuduo 网络库开发 TCP 服务器程序</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"TcpServer.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 聊天服务器</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChatServer</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="built_in">ChatServer</span>(EventLoop *loop, <span class="keyword">const</span> InetAddress &amp;listenAddr, <span class="keyword">const</span> std::string &amp;nameArg);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 析构函数</span></span><br><span class="line">    ~<span class="built_in">ChatServer</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 处理用户的连接创建和断开</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr &amp;conn)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 服务器绑定消息回调函数，当有数据接收时调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr &amp;conn, Buffer *buffer, Timestamp time)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// TCP 服务器</span></span><br><span class="line">    TcpServer server_;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// EventLoop 事件循环</span></span><br><span class="line">    EventLoop *loop_;</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><ul><li><code>ChatServer.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 基于 MyMuduo 网络库开发 TCP 服务器程序</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ChatServer.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造函数</span></span><br><span class="line">ChatServer::<span class="built_in">ChatServer</span>(EventLoop *loop, <span class="keyword">const</span> InetAddress &amp;listenAddr, <span class="keyword">const</span> std::string &amp;nameArg)</span><br><span class="line">    : <span class="built_in">server_</span>(loop, listenAddr, nameArg), <span class="built_in">loop_</span>(loop) {</span><br><span class="line">    <span class="comment">// 设置服务器注册用户连接的创建和断开回调</span></span><br><span class="line">    server_.<span class="built_in">setConnectionCallback</span>(std::<span class="built_in">bind</span>(&amp;ChatServer::onConnection, <span class="keyword">this</span>, std::placeholders::_1));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置服务器注册用户读写事件的回调</span></span><br><span class="line">    server_.<span class="built_in">setMessageCallback</span>(</span><br><span class="line">        std::<span class="built_in">bind</span>(&amp;ChatServer::onMessage, <span class="keyword">this</span>, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置线程池的线程数量（比如：1个I/O线程，3个Worker线程）</span></span><br><span class="line">    server_.<span class="built_in">setThreadNum</span>(<span class="number">4</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 析构函数</span></span><br><span class="line">ChatServer::~<span class="built_in">ChatServer</span>() {</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="function"><span class="keyword">void</span> <span class="title">ChatServer::start</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 开启事件循环处理</span></span><br><span class="line">    server_.<span class="built_in">start</span>();</span><br><span class="line">    <span class="comment">// 打印日志信息</span></span><br><span class="line">    <span class="built_in">LOG_INFO</span>(<span class="string">"ChatServer - start success, listening on %s"</span>, server_.<span class="built_in">ipPort</span>().<span class="built_in">c_str</span>());</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="function"><span class="keyword">void</span> <span class="title">ChatServer::onConnection</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr &amp;conn)</span> </span>{</span><br><span class="line">    <span class="comment">// 连接创建</span></span><br><span class="line">    <span class="keyword">if</span> (conn-&gt;<span class="built_in">connected</span>()) {</span><br><span class="line">        <span class="built_in">LOG_INFO</span>(<span class="string">"ChatServer - Connection UP : %s"</span>, conn-&gt;<span class="built_in">peerAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 连接断开</span></span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">LOG_INFO</span>(<span class="string">"ChatServer - Connection DOWN : %s"</span>, conn-&gt;<span class="built_in">peerAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    }</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="function"><span class="keyword">void</span> <span class="title">ChatServer::onMessage</span><span class="params">(<span class="keyword">const</span> TcpConnectionPtr &amp;conn, Buffer *buffer, Timestamp time)</span> </span>{</span><br><span class="line">    <span class="comment">// 获取客户端发送的消息</span></span><br><span class="line">    std::string message = buffer-&gt;<span class="built_in">retrieveAllAsString</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 去掉消息末尾的 '\r' 和 '\n' 字符（telnet 命令会发送 CRLF）</span></span><br><span class="line">    <span class="keyword">while</span> (!message.<span class="built_in">empty</span>() &amp;&amp; (message.<span class="built_in">back</span>() == <span class="string">'\n'</span> || message.<span class="built_in">back</span>() == <span class="string">'\r'</span>)) {</span><br><span class="line">        message.<span class="built_in">pop_back</span>();</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="built_in">LOG_INFO</span>(<span class="string">"ChatServer - receive message: [%s], time: %s, ip: %s"</span>, message.<span class="built_in">c_str</span>(), time.<span class="built_in">toString</span>().<span class="built_in">c_str</span>(),</span><br><span class="line">             conn-&gt;<span class="built_in">peerAddress</span>().<span class="built_in">toIpPort</span>().<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送数据给客户端</span></span><br><span class="line">    conn-&gt;<span class="built_in">send</span>(<span class="string">"You just said: "</span> + message + <span class="string">"\n"</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li><code>main.cc</code></li></ul><figure class="highlight c++"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * MyMuduo 网络库的使用案例</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Linux 上运行程序：./bin/mymuduo_example</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;chrono&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ChatClient.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ChatServer.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"Logger.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动聊天服务器</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">startChatServer</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 创建服务器</span></span><br><span class="line">    EventLoop loop;</span><br><span class="line">    <span class="function">InetAddress <span class="title">addr</span><span class="params">(<span class="number">6000</span>, <span class="string">"127.0.0.1"</span>)</span></span>;</span><br><span class="line">    <span class="function">ChatServer <span class="title">server</span><span class="params">(&amp;loop, addr, <span class="string">"ChatServer"</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动服务器</span></span><br><span class="line">    server.<span class="built_in">start</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以阻塞方式等待新客户端的连接、已连接客户端的读写事件等</span></span><br><span class="line">    loop.<span class="built_in">loop</span>();</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="function"><span class="keyword">void</span> <span class="title">startChatClient</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">// 创建客户端</span></span><br><span class="line">    EventLoop loop;</span><br><span class="line">    <span class="function">InetAddress <span class="title">addr</span><span class="params">(<span class="number">6000</span>, <span class="string">"127.0.0.1"</span>)</span></span>;</span><br><span class="line">    <span class="function">ChatClient <span class="title">client</span><span class="params">(&amp;loop, addr, <span class="string">"ChatClient"</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 连接服务器</span></span><br><span class="line">    client.<span class="built_in">connect</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以阻塞方式等待服务器发送过来的数据</span></span><br><span class="line">    loop.<span class="built_in">loop</span>();</span><br><span class="line">}</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> </span>{</span><br><span class="line">    <span class="comment">// 设置日志级别</span></span><br><span class="line">    Logger::<span class="built_in">instance</span>().<span class="built_in">setLogLevel</span>(LogLevel::INFO);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在独立的线程上启动聊天服务器</span></span><br><span class="line">    <span class="function">std::thread <span class="title">serverThread</span><span class="params">([]() { startChatServer(); })</span></span>;</span><br><span class="line">    serverThread.<span class="built_in">detach</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 等待一段时间，让聊天服务器先启动（可选，因为聊天客户端会自动重连）</span></span><br><span class="line">    std::this_thread::<span class="built_in">sleep_for</span>(std::chrono::<span class="built_in">milliseconds</span>(<span class="number">200</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在独立的线程上启动聊天客户端</span></span><br><span class="line">    <span class="function">std::thread <span class="title">clientThrad</span><span class="params">([]() { startChatClient(); })</span></span>;</span><br><span class="line">    clientThrad.<span class="built_in">detach</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 阻塞等待用户按下任意键，然后结束程序运行</span></span><br><span class="line">    <span class="built_in">getchar</span>();</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">}</span><br></pre></td></tr></tbody></table></figure><h3 id="测试步骤"><a href="#测试步骤" class="headerlink" title="测试步骤"></a>测试步骤</h3><ul><li>编译项目代码 </li></ul><figure class="highlight sh"><table><tbody><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"># 进入项目根目录</span></span><br><span class="line"><span class="built_in">cd</span> c++-project-mymuduo</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行项目自动构建脚本</span></span><br><span class="line">./autobuild.sh</span><br></pre></td></tr></tbody></table></figure><ul><li>运行测试程序 </li></ul><figure class="highlight shell"><table><tbody><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"># 执行 MyMuduo 网络库使用案例的可执行文件</span></span><br><span class="line">./bin/mymuduo_example</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行 telnet 命令连接 TCP 服务器（成功连接后，输入任意字符，按回车键即可发送消息给服务器，之后服务器会返回相应的消息内容）</span></span><br><span class="line">telnet 127.0.0.1 6000</span><br></pre></td></tr></tbody></table></figure><ul><li>测试程序输出的日志信息如下：</li></ul><figure class="highlight plaintext"><table><tbody><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">2025-11-15 22:10:01 =&gt; 6609 [INFO] ChatServer - start success, listening on 127.0.0.1:6000</span><br><span class="line">2025-11-15 22:10:01 =&gt; 6614 [INFO] TcpClient::TcpClient(EventLoop*, const InetAddress&amp;, const std::string&amp;) =&gt; crate tcp client [ChatClient] - connector 0x7f52b8000e20</span><br><span class="line">2025-11-15 22:10:01 =&gt; 6614 [INFO] void TcpClient::connect() =&gt; connect to 127.0.0.1:6000</span><br><span class="line">2025-11-15 22:10:01 =&gt; 6614 [INFO] ChatClient - new connection [127.0.0.1:42170] -&gt; [127.0.0.1:6000], state: connected</span><br><span class="line">2025-11-15 22:10:01 =&gt; 6610 [INFO] ChatServer - Connection UP : 127.0.0.1:42170</span><br><span class="line">2025-11-15 22:10:01 =&gt; 6610 [INFO] ChatServer - receive message: [I'm ChatClient], time: 2025-11-15 22:10:01, ip: 127.0.0.1:42170</span><br><span class="line">2025-11-15 22:10:01 =&gt; 6614 [INFO] ChatClient - receive message: [You just said: I'm ChatClient], time: 2025-11-15 22:10:01</span><br></pre></td></tr></tbody></table></figure><h2 id="项目扩展"><a href="#项目扩展" class="headerlink" title="项目扩展"></a>项目扩展</h2><p>上面的 MyMuduo 网络库代码只实现了 Muduo 的核心功能，并不支持 Muduo 的定时事件机制（<code>TimerQueue</code>）、IPV6 / DNS / HTTP / RPC 协议等，日后可以从以下几方面继续对其进行扩展：</p><ul><li><p>(1) 定时事件机制</p><ul><li>TimerQueue：支持 EventLoop 内的定时任务调度，常见实现方式包括：<ul><li>链表队列：实现简单，但不适合大量定时器场景（需要线性扫描）。</li><li>红黑树（如 <code>nginx</code>）：按照到期时间排序，可快速找到最早到期的定时器，插入 / 删除的时间复杂度为 <code>O(logN)</code>。</li><li>时间轮（如 <code>libevent</code>）：适合大量、定时精度要求不高的场景，插入 / 删除的时间复杂度为 <code>O(1)</code>，整体性能出色。</li></ul></li></ul></li><li><p>(2) IPV6 / DNS / HTTP / RPC 协议支持</p><ul><li>IPV6：支持 IPv6 套接字、地址解析与双栈接入，确保网络库的所有连接与事件处理流程均可透明兼容 IPv6。</li><li>DNS：实现异步域名解析（如 <code>getaddrinfo_a</code>），将域名解析和网络事件循环结合，避免阻塞 I/O。</li><li>HTTP：构建基础的 HTTP 请求解析、响应封装，可扩展为简单的 Web 服务器或客户端；需要支持 Keep-Alive、Chunked 等机制。</li><li>RPC：在已有 TCP 框架上封装请求 / 响应协议，实现序列化、服务注册、方法调用、超时与重试等功能（可仿照 gRPC 实现）。</li></ul></li><li><p>(3) 服务器性能测试</p><ul><li>为了验证网络库的性能，需要进行专业的性能压测和系统配置优化：</li><li>系统性能优化<ul><li> Linux 最大文件描述符数设置：包括<ul><li><code>/proc/sys/fs/file-max</code>（系统级限制）</li><li><code>/etc/security/limits.conf</code>（用户 / 进程级限制）</li><li><code>ulimit -n</code>（当前会话限制）</li></ul></li></ul></li><li>性能测试工具 <ul><li><a href="https://github.com/apache/jmeter?utm_source=chatgpt.com">JMeter</a>：可压测 HTTP 服务与自定义 TCP 服务，能够生成聚合报告和可视化图表。</li><li><a href="https://github.com/wg/wrk?utm_source=chatgpt.com">wrk</a>：高性能 HTTP 压测工具，支持多线程 + <code>epoll</code>，需要手动编译安装，仅支持 HTTP 协议。</li></ul></li></ul></li></ul><h2 id="项目问答"><a href="#项目问答" class="headerlink" title="项目问答"></a>项目问答</h2><h3 id="新-TCP-连接的派发问题"><a href="#新-TCP-连接的派发问题" class="headerlink" title="新 TCP 连接的派发问题"></a>新 TCP 连接的派发问题</h3><p>在 Muduo 网络库中，mainLoop 是如何将新来的 TCP 连接派发给 subLoop 的，同时还让新 TCP 连接的所有 I/O 事件回调操作都在 subLoop 所在的线程上执行？</p><ul><li>(1) Acceptor 在 mainLoop（运行在主线程）上监听 <code>listenfd</code></li><li>(2) mainLoop 在收到新连接事件时，会调用 <code>Acceptor::handleRead()</code>，得到 <code>connfd</code>（新连接的文件描述符）</li><li>(3) mainLoop 选择一个 subLoop（通过 <code>EventLoopThreadPool</code> 的轮询）</li><li>(4) mainLoop 创建 <code>TcpConnection</code>，并把它的所有回调操作注册到 subLoop</li><li>(5) mainLoop 调用 <code>subLoop-&gt;runInLoop()</code>，将注册 connfd 读写事件到 subLoop 的 Poller 的任务丢给 subLoop</li><li>(6) subLoop 线程最终向自己的 Poller 注册事件，使得 <code>connfd</code> 的所有读写事件（包括 I/O 事件、回调处理等）永远在 subLoop 上执行 </li></ul><figure class="highlight plaintext"><table><tbody><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">Acceptor::listen()</span><br><span class="line">         |  </span><br><span class="line">         | 1</span><br><span class="line">         v</span><br><span class="line">Acceptor::handleRead()</span><br><span class="line">         |</span><br><span class="line">         | 2</span><br><span class="line">         v</span><br><span class="line">TcpServer::newConnection(connfd)</span><br><span class="line">         |</span><br><span class="line">         | 3</span><br><span class="line">         v</span><br><span class="line">选中一个 subLoop (ioLoop)</span><br><span class="line">         |</span><br><span class="line">         | 4</span><br><span class="line">         v</span><br><span class="line">创建 TcpConnection(subLoop)</span><br><span class="line">         |</span><br><span class="line">         | 5</span><br><span class="line">         v</span><br><span class="line">subLoop-&gt;runInLoop(connectEstablished)</span><br><span class="line">         |</span><br><span class="line">         | 6</span><br><span class="line">         v</span><br><span class="line">----------------------------------------------------</span><br><span class="line">↓ subLoop (I/O 线程) 被唤醒后执行 connectEstablished()</span><br><span class="line">----------------------------------------------------</span><br><span class="line">         |</span><br><span class="line">         | 7</span><br><span class="line">         v</span><br><span class="line">channel_-&gt;enableReading()</span><br><span class="line">         |</span><br><span class="line">         | 8</span><br><span class="line">         v</span><br><span class="line">事件到来 → Poller 触发 → 执行 TcpConnection 的回调操作 (全部都会在 subLoop 线程上执行)</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>在 Muduo 中，新连接的建立仅发生在 mainLoop：它负责监听 <code>listenfd</code>，并在有新连接到来时调用 <code>accept()</code>。mainLoop 只负责接受连接，不参与任何与该连接相关的后续 I/O 操作（读和写等）。在 mainLoop 完成 <code>accept()</code> 后，Muduo 会将得到的新连接文件描述符 <code>connfd</code> 分发给某个 subLoop（由 <code>EventLoopThreadPool</code> 按轮询算法选择）。之后，该新连接的所有读写事件（包括 I/O 事件、回调处理等）都由对应的 subLoop 独立处理，与 mainLoop 无关。</li></ul></div><h3 id="EventLoop-之间的通信问题"><a href="#EventLoop-之间的通信问题" class="headerlink" title="EventLoop 之间的通信问题"></a>EventLoop 之间的通信问题</h3><p>mainLoop 与 subLoop 分别运行在不同的线程上，它们之间是如何进行通信的，也就是说 mainLoop 是如何将新来的 TCP 连接派发给 subLoop 的，还有 mainLoop 是如何唤醒 subLoop 的？</p><ul><li>(1) mainLoop 与 subLoop 分别运行在不同线程中，每个 EventLoop 拥有自己独立的线程与 Poller。  </li><li>(2) 它们之间通过 EventLoop 的异步任务队列（<code>pendingFunctors</code>）进行通信，任何跨线程的操作，都会封装成回调函数投递到目标 EventLoop 的异步任务队列中。  </li><li>(3) mainLoop 接收（<code>accept()</code>）到新连接后，调用 <code>subLoop-&gt;runInLoop()</code>，将 TcpConnection 的初始化任务（如 <code>connectEstablished()</code>）投递给指定的 subLoop 执行。  </li><li>(4) mainLoop 向 subLoop 的任务队列中插入新任务后，会向 subLoop 的 <code>wakeupFd</code> 写入一个字节，目的是唤醒 subLoop 去执行 <code>pendingFunctors</code> 队列中的任务。  </li><li>(5) 写入 <code>wakeupFd</code> 会触发 subLoop 的 <code>wakeupChannel</code> 可读事件，wakeupChannel 是注册在 subLoop 上的一个 Channel，用来专门处理 “被唤醒” 事件。  </li><li>(6) 被唤醒的 subLoop 从阻塞的 <code>epoll_wait()</code> 中立即返回，然后执行 <code>wakeupChannel</code> 的读事件回调。  </li><li>(7) subLoop 随后继续执行其 <code>pendingFunctors</code> 队列中的任务，包括由 mainLoop 投递过来的 TcpConnection 初始化操作。  </li><li>(8) 从此以后，该 TcpConnection 的所有 I/O 事件都由该 subLoop 负责处理，包括读写事件回调、关闭回调、错误回调等全部在 subLoop 所在线程执行。</li></ul><figure class="highlight plaintext"><table><tbody><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></pre></td><td class="code"><pre><span class="line">Acceptor::listen()  </span><br><span class="line">        | </span><br><span class="line">        | 1</span><br><span class="line">        v  </span><br><span class="line">Acceptor::handleRead()</span><br><span class="line">        | </span><br><span class="line">        | 2</span><br><span class="line">        v  </span><br><span class="line">TcpServer::newConnection(connfd)  </span><br><span class="line">        | </span><br><span class="line">        | 3</span><br><span class="line">        v  </span><br><span class="line">EventLoopThreadPool::getNextLoop()  </span><br><span class="line">        | </span><br><span class="line">        | 4</span><br><span class="line">        v  </span><br><span class="line">new TcpConnection(subLoop, connfd)  </span><br><span class="line">        | </span><br><span class="line">        | 5</span><br><span class="line">        v  </span><br><span class="line">subLoop-&gt;runInLoop(std::bind(&amp;TcpConnection::connectEstablished, conn))  </span><br><span class="line">        | </span><br><span class="line">        | 6</span><br><span class="line">        v  </span><br><span class="line">EventLoop::queueInLoop(cb)  </span><br><span class="line">        | </span><br><span class="line">        | 7</span><br><span class="line">        v  </span><br><span class="line">EventLoop::wakeup()  </span><br><span class="line">        | </span><br><span class="line">        | 8</span><br><span class="line">        v  </span><br><span class="line">----------------------------------------------  </span><br><span class="line">↓ subLoop 所在线程（I/O 线程）被唤醒执行  </span><br><span class="line">----------------------------------------------  </span><br><span class="line">        | </span><br><span class="line">        | 9</span><br><span class="line">        v  </span><br><span class="line">wakeupChannel-&gt;handleEvent()  </span><br><span class="line">        | </span><br><span class="line">        | 10</span><br><span class="line">        v  </span><br><span class="line">EventLoop::handleRead()  </span><br><span class="line">        | </span><br><span class="line">        | 11</span><br><span class="line">        v  </span><br><span class="line">EventLoop::doPendingFunctors()  </span><br><span class="line">        | </span><br><span class="line">        | 12</span><br><span class="line">        v  </span><br><span class="line">TcpConnection::connectEstablished()  </span><br><span class="line">        | </span><br><span class="line">        | 13</span><br><span class="line">        v  </span><br><span class="line">Channel::enableReading()  </span><br><span class="line">        | </span><br><span class="line">        | 14</span><br><span class="line">        v  </span><br><span class="line">Poller::updateChannel(channel)  </span><br><span class="line">        | </span><br><span class="line">        | 15</span><br><span class="line">        v  </span><br><span class="line">事件到来 → Poller 触发 → 返回活跃事件 → 调用 channel-&gt;handleEvent() 处理活跃事件</span><br><span class="line">        | </span><br><span class="line">        | 16</span><br><span class="line">        v  </span><br><span class="line">回调操作在 subLoop 线程执行，保证线程安全  </span><br></pre></td></tr></tbody></table></figure><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://zhuanlan.zhihu.com/p/717586901">深入分析 Muduo 网络库核心代码</a></li><li><a href="https://zhuanlan.zhihu.com/p/683396341">Muduo 库核心代码及优秀编程细节剖析</a></li><li><a href="https://www.ituring.com.cn/article/504549">从抄书到开源之巅：章亦春的程序人生</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍如何基于 C++ 开发一款高性能网络库（类似 Muduo），采用 Multiple Reactors 模型。</summary>
    
    
    
    
    <category term="Linux系统编程" scheme="https://www.techgrow.cn/tags/Linux%E7%B3%BB%E7%BB%9F%E7%BC%96%E7%A8%8B/"/>
    
    <category term="C++" scheme="https://www.techgrow.cn/tags/C/"/>
    
    <category term="网络编程" scheme="https://www.techgrow.cn/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>Java 与 Dubbo 的 SPI 机制介绍</title>
    <link href="https://www.techgrow.cn/posts/ef54a41c.html"/>
    <id>https://www.techgrow.cn/posts/ef54a41c.html</id>
    <published>2025-10-12T15:42:35.000Z</published>
    <updated>2025-10-12T15:42:35.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="Java-SPI-机制"><a href="#Java-SPI-机制" class="headerlink" title="Java SPI 机制"></a>Java SPI 机制</h2><h3 id="概念介绍"><a href="#概念介绍" class="headerlink" title="概念介绍"></a>概念介绍</h3><p>Java 原生支持 SPI 机制，具体介绍如下：</p><ul><li><strong>核心概念</strong><ul><li> SPI（Service Provider Interface）是一种服务发现机制。</li><li>SPI 的核心思想是定义一个接口，由多个实现类提供不同的实现方式，在系统运行时根据配置或者默认策略，动态加载并使用具体的实现类。</li><li>SPI 的本质是将接口实现类的全限定名配置在文件中，并由服务加载器读取配置文件，加载实现类。这样就可以在运行时，动态为接口替换实现类。</li></ul></li></ul><span id="more"></span><ul><li><p><strong>工作原理</strong></p><ul><li>(1) 接口与实现类<ul><li>假设有一个接口 <code>A</code>，它有多个实现类：<code>A -&gt; A1、A2、A3</code>。</li></ul></li><li>(2) 配置实现类<ul><li>可以在配置文件中指定接口 <code>A</code> 对应使用哪个实现类。</li></ul></li><li>(3) 运行时加载<ul><li>程序启动时，会读取配置文件，根据配置信息找到对应的实现类，实例化并使用该对象。</li></ul></li><li>(4) Java 原生 SPI 机制<ul><li> Java 原生 SPI 机制的使用要求：<ul><li>在 <code>resources/META-INF/services/</code> 目录下，创建一个与接口全限定名相同的文件，例如：<code>resources/META-INF/services/com.example.service.A</code></li><li>文件内容的格式是一行一个实现类的全限定名（可以有多行，即支持多个不同的实现类），例如：<code>com.example.service.impl.A1</code>。</li><li>运行时通过 <code>ServiceLoader</code> 等工具扫描依赖的 Jar 包，在其中查找该文件，并加载指定的实现类，比如：<code>ServiceLoader&lt;HelloService&gt; loader = ServiceLoader.load(HelloService.class);</code></li></ul></li></ul></li></ul></li><li><p><strong>应用场景</strong></p><ul><li>SPI 机制常用于插件式扩展。</li><li>比如：如果你在开发一个框架，可以通过 SPI 让外部开发者编写插件，扩展框架的功能，而不必修改框架的源码。</li></ul></li><li><p><strong>典型案例</strong></p><ul><li>JDBC<ul><li>Java 标准库只定义了一套 JDBC 接口，并没有真正的实现。</li><li>数据库厂商（如 MySQL、Oracle）会提供自己的实现，并通过 SPI 机制声明在 <code>resources/META-INF/services/</code> 目录中。</li><li>运行时，Java 会根据项目引入的数据库驱动 Jar 包，自动找到对应的 JDBC 实现类。</li></ul></li></ul></li></ul><h2 id="Dubbo-SPI-机制"><a href="#Dubbo-SPI-机制" class="headerlink" title="Dubbo SPI 机制"></a>Dubbo SPI 机制</h2><div class="admonition note"><p class="admonition-title">扩展阅读</p><ul><li><a href="/posts/ea9ce835.html#SPI-%E6%89%A9%E5%B1%95%E6%9C%BA%E5%88%B6">Dubbo 深入理解 - SPI 扩展机制</a></li></ul></div><h3 id="概念介绍-1"><a href="#概念介绍-1" class="headerlink" title="概念介绍"></a>概念介绍</h3><p>Dubbo 借鉴了 SPI 思想，但没有直接使用 Java 原生的 SPI 机制，而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 <code>ExtensionLoader</code> 类中，通过 <code>ExtensionLoader</code> 类可以加载指定的实现类。</p><ul><li><p><strong>工作原理</strong></p><ul><li>(1) 接口声明<ul><li>在 Dubbo 中，如果某个接口需要支持 SPI 扩展，就会加上 <code>@SPI</code> 注解，比如 <code>Protocol</code> 接口：<figure class="highlight java"><table><tbody><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">// 接口定义</span></span><br><span class="line"><span class="meta">@SPI("dubbo")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Protocol</span> </span>{</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">int</span> <span class="title">getDefaultPort</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Adaptive</span></span><br><span class="line">   &lt;T&gt; <span class="function">Exporter&lt;T&gt; <span class="title">export</span><span class="params">(Invoker&lt;T&gt; invoker)</span> <span class="keyword">throws</span> RpcException</span>;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Adaptive</span></span><br><span class="line">   &lt;T&gt; <span class="function">Invoker&lt;T&gt; <span class="title">refer</span><span class="params">(Class&lt;T&gt; type, URL url)</span> <span class="keyword">throws</span> RpcException</span>;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">void</span> <span class="title">destroy</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载实现类</span></span><br><span class="line">Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();</span><br></pre></td></tr></tbody></table></figure></li><li><code>@SPI("dubbo")</code> 表示默认实现是 <code>dubbo</code>。</li><li><code>@Adaptive</code> 表示该方法会生成代理逻辑，运行时根据参数动态选择实现类。</li></ul></li><li>(2) 实现类配置<ul><li> Dubbo 在自己 Jar 包中的 <code>resources/META-INF/dubbo/internal/</code> 路径下提供了一个配置文件，文件名是接口的全限定名，比如：<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol</span><br></pre></td></tr></tbody></table></figure></li><li>配置文件的内容是 <code>key=实现类的全限定名</code>，key 对应 <code>@SPI</code> 注解中的扩展名称，比如：<figure class="highlight plaintext"><table><tbody><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">dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol</span><br><span class="line">http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol</span><br><span class="line">hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol</span><br></pre></td></tr></tbody></table></figure></li></ul></li><li>(3) 默认实现加载<ul><li>如果用户没有配置扩展，Dubbo 会根据 <code>@SPI("dubbo")</code> 的默认值 <code>dubbo</code>，从配置文件中加载对应的实现类 <code>DubboProtocol</code>。</li><li>这也是 Dubbo 默认使用 Dubbo 协议作为 RPC 通信协议的原因。</li></ul></li><li>(4) 动态切换实现类<ul><li>在 <code>Protocol</code> 接口中，有两个方法加了 <code>@Adaptive</code> 注解。</li><li>Dubbo 会在运行时生成代理类，在代理方法内部根据传入的 URL 参数的 <code>protocol</code> 值决定使用哪个实现类。</li><li>如果 URL 参数中没指定协议，就用默认的 <code>dubbo</code>；如果指定了其他值（如 <code>http</code>），则加载对应的实现类。</li></ul></li></ul></li><li><p><strong>实现特点</strong></p><ul><li>微内核 + 可插拔：保留一个接口和多个实现，运行时可替换。</li><li>组件化：如 <code>Protocol</code> 负责 RPC 调用，可以替换为自定义的 RPC 组件。</li><li>动态扩展：可通过 URL 参数或配置文件，在运行时动态切换实现类。</li><li>增强的 SPI：相比 Java 原生的 SPI 机制，Dubbo 的实现支持：<ul><li>支持指定默认的实现类（<code>@SPI</code> 注解的默认值）</li><li>运行时动态选择实现类（<code>@Adaptive</code>）</li><li>接口定义（扩展点）自动生成代理类</li></ul></li></ul></li><li><p><strong>使用总结</strong></p><ul><li>Dubbo SPI 本质上是一个运行时可扩展、可替换的组件机制。</li><li>Dubbo 大量核心组件（如 <code>Protocol</code>、<code>Cluster</code>、<code>Registry</code> 等）都是用这种 SPI 机制实现扩展的。</li><li>通过 <code>@SPI</code> 注解 + 配置文件来确定默认的实现类，通过 <code>@Adaptive</code> 注解 + URL 参数来实现动态切换实现类。</li><li>Dubbo SPI 的扩展文件路径（<a href="../../../asset/2025/11/dubbo-spi-extension-file.png">点击查看源码定义</a>）<ul><li>与 Java SPI 的不一样，Dubbo SPI 提供给开发者使用的扩展文件是 <code>resources/META-INF/dubbo/接口全限定名</code>，文件内容是 <code>key=实现类的全限定名</code>；</li><li>Dubbo 自己内部使用的扩展文件是 <code>resources/META-INF/dubbo/internal/接口全限定名</code>，文件内容是 <code>key=实现类的全限定名</code>；</li></ul></li></ul></li></ul><h3 id="加载流程"><a href="#加载流程" class="headerlink" title="加载流程"></a>加载流程</h3><p>Dubbo SPI 机制加载扩展的核心步骤：</p><ul><li>(1) 读取并解析配置文件</li><li> (2) 缓存所有扩展实现类</li><li> (3) 基于用户执行的扩展名，实例化对应的扩展实现类</li><li> (4) 执行扩展实例属性的 IOC 注入（基于 <code>Setter</code> 注入），以及实例化扩展的包装类，实现 AOP 特性</li></ul><p>Dubbo SPI 机制加载扩展的整个流程：</p><p><img data-src="../../../asset/2025/09/dubbo-spi-1.png"></p><h3 id="核心注解"><a href="#核心注解" class="headerlink" title="核心注解"></a>核心注解</h3><p>Dubbo SPI 机制有两个核心注解，分别是 <code>@Adaptive</code> 和 <code>@Activate</code>。</p><ul><li><strong><code>@Adaptive</code> 注解（自适应）</strong><ul><li>主要作用<ul><li>表示该扩展类或接口方法需要自适应扩展，Dubbo 会在运行时根据 URL 或其他条件动态选择具体扩展实现。</li></ul></li><li>常见使用方式<ul><li><code>@Adaptive</code> 标注在接口方法上（最常见）<ul><li>表示由 Dubbo 自动生成 <code>Xxx$Adaptive</code> 代理类</li><li>方法内部会根据 URL 参数在运行时选择具体扩展实现</li><li>使用例子：<figure class="highlight java"><table><tbody><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">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Protocol</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Adaptive({"protocol", "defaultProtocol"})</span></span><br><span class="line">    <span class="function">Exporter <span class="title">export</span><span class="params">(Invoker invoker)</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>当上面调用 <code>export()</code> 方法时，Dubbo 会根据 URL 的 <code>protocol</code> 或 <code>defaultProtocol</code> 参数决定使用哪个协议实现</li></ul></li></ul></li><li><code>@Adaptive</code> 标注在扩展类上（较少使用）<ul><li>表示该扩展类是一个手工编写的自适应扩展类</li><li> Dubbo 将直接使用这个扩展类，而不会再自动生成代理类</li><li>一般用于非常复杂的场景，比如需要手写逻辑替代 Dubbo 自动生成代理类</li><li>目前 Dubbo 中仅有两个扩展类标注了 <code>@Adaptive</code>，分别是 <code>AdaptiveCompiler</code> 和 <code>AdaptiveExtensionFactory</code>，表示这些扩展类的加载逻辑由人工编码（静态编码）完成</li><li>使用例子：<figure class="highlight java"><table><tbody><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="meta">@Adaptive</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomAdaptiveCompiler</span> <span class="keyword">implements</span> <span class="title">Compiler</span> </span>{</span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Class&lt;?&gt; compile(String code, ClassLoader loader) {</span><br><span class="line">        <span class="comment">// 自定义代码编译逻辑</span></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul></li><li>使用注意事项<ul><li>接口上不能标注 <code>@Adaptive</code></li><li>接口方法上可以标注 <code>@Adaptive</code>，表示使用 Dubbo 自动生成的代理类，适用大多数场景</li><li>扩展类上可以标注 <code>@Adaptive</code>，表示使用手写的自适应逻辑，适用特殊复杂场景</li></ul></li></ul></li></ul><hr><ul><li><strong><code>@Activate</code> 注解（自动激活）</strong><ul><li>主要作用<ul><li>表示当扩展类被自动加载时，满足 <code>group</code>、<code>value</code>、<code>order</code> 等条件，该扩展会自动加入扩展链，无需手动在配置中指定扩展名。</li></ul></li><li>常见使用方式<ul><li><code>@Activate</code> 标注在扩展类上（最常见）<ul><li>扩展类在满足 Activate 条件时自动激活</li><li>一般用于 Filter、Router、ExporterListener、Registry 等扩展点</li><li>使用例子：<figure class="highlight java"><table><tbody><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="meta">@Activate(group = {"provider"}, order = 10)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyProviderFilter</span> <span class="keyword">implements</span> <span class="title">Filter</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Result <span class="title">invoke</span><span class="params">(Invoker&lt;?&gt; invoker, Invocation invocation)</span> </span>{</span><br><span class="line">        <span class="comment">// 自定义过滤逻辑</span></span><br><span class="line">        <span class="keyword">return</span> invoker.invoke(invocation);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure></li></ul></li><li><code>@Activate</code> 标注在接口方法上（较少使用）<ul><li>表示该接口方法返回的扩展类实例在调用时可自动激活</li><li>可结合 URL 参数或 <code>group</code>、<code>value</code>、<code>order</code> 条件进行动态控制</li><li>使用例子：<figure class="highlight java"><table><tbody><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">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SomeFactory</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Activate(value = {"feature"}, group = "consumer")</span></span><br><span class="line">    <span class="function">Extension <span class="title">create</span><span class="params">()</span></span>;</span><br><span class="line">    </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul></li><li>典型使用 <code>@Activate</code> 自动激活的扩展点（最常见场景）<ul><li>Filter 自动生效（过滤器）</li><li>Router 自动生效（路由）</li><li>ExporterListener 自动生效（监听服务暴露）</li><li>Registry（如注册中心相关监听、通知）自动生效</li></ul></li><li>可使用 <code>@Activate</code> 激活，但通常是通过 URL 指定或者明确选择的的扩展点<ul><li> Cluster（有些装饰逻辑可能自动封装，但多数是由 Dubbo 主流程显式选择）</li><li>Protocol（支持 <code>@Activate</code>，但较少用于自动激活，一般是 URL 或显式选择）</li><li>ProxyFactory（可自动激活，但通常通过指定实现选择）</li></ul></li><li>使用注意事项<ul><li>接口上不能标注 <code>@Activate</code></li></ul></li></ul></li></ul><div class="admonition note"><p class="admonition-title">总结</p><ul><li><code>@Adaptive</code>（自适应）的作用：让扩展在运行时根据 URL 或条件动态选择具体扩展实现；标注在方法上，则表示由 Dubbo 自动生成代理类；标注在类上，则表示使用手写的自适应逻辑。</li><li><code>@Activate</code>（自动激活）的作用：让扩展类在满足 Activate 条件时自动激活加入调用链；可以省略，但省略后不会自动激活，必须通过扩展名主动加载，例如： <code>extensionLoader.getExtension("customizedProtocol");</code>。</li></ul></div><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/spi/description/">Dubbo 官方文档 - 部分重点 SPI 使用说明</a></li><li><a href="https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/spi/">Dubbo 官方文档 - SPI 插件扩展点使用手册</a></li><li><a href="https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/dubbo-spi/">Dubbo 官方文档 - SPI 源码分析</a>，<a href="../../../asset/2025/11/dubbo2-docs-spi-1.png">附文档截图</a></li><li><a href="https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/dubbo-spi/">Dubbo 官方文档 - 扩展点开发指南</a>，<a href="../../../asset/2025/11/dubbo-docs-spi-2.png">附文档截图</a></li><li><a href="https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/tasks/extensibility/spi/">Dubbo 官方文档 - 自定义 SPI 扩展的基本步骤</a>，<a href="../../../asset/2025/11/dubbo-docs-spi-3.png">附文档截图</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Java 与 Dubbo 的 SPI 机制。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="Java" scheme="https://www.techgrow.cn/tags/Java/"/>
    
    <category term="RPC" scheme="https://www.techgrow.cn/tags/RPC/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之八</title>
    <link href="https://www.techgrow.cn/posts/723af70c.html"/>
    <id>https://www.techgrow.cn/posts/723af70c.html</id>
    <published>2025-10-08T13:12:19.000Z</published>
    <updated>2025-10-08T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><h3 id="持久化存储"><a href="#持久化存储" class="headerlink" title="持久化存储"></a>持久化存储</h3><h4 id="Volume"><a href="#Volume" class="headerlink" title="Volume"></a>Volume</h4><h5 id="Volume-的概述"><a href="#Volume-的概述" class="headerlink" title="Volume 的概述"></a>Volume 的概述</h5><p>Volume（卷）是 Pod 中可被多个容器共同访问的共享目录。Kubernetes 的 Volume 定义在 Pod 上，并可由该 Pod 内的多个容器挂载到各自的文件路径下。Volume 的生命周期与 Pod 相同，但独立于容器的生命周期。当容器终止或重启时，Volume 中的数据不会丢失。在使用 Volume 时，Pod 需要指定 Volume 的类型和内容（<code>volumes</code> 字段），以及在容器中挂载的位置（<code>volumeMounts</code> 字段）。Kubernetes 支持多种类型的 Volume，包括：<code>emptyDir</code>、<code>hostPath</code>、<code>gcePersistentDisk</code>、<code>awsElasticBlockStore</code>、<code>nfs</code>、<code>iscsi</code>、<code>flocker</code>、<code>glusterfs</code>、<code>rbd</code>、<code>cephfs</code>、<code>gitRepo</code>、<code>secret</code>、<code>persistentVolumeClaim</code>、<code>downwardAPI</code>、<code>azureFile</code>、<code>azureDisk</code>、<code>vsphereVolume</code>、<code>quobyte</code>、<code>portworxVolume</code>、<code>scaleIO</code> 等。</p><div class="admonition warning"><p class="admonition-title">特别注意</p><p>在 Kubernetes 中，Volume 是定义在 Pod 层级上的，而不是容器层级的。这意味着同一个 Pod 内的多个容器可以通过挂载同一个 Volume 来共享数据。几乎所有类型的 Volume（如 <code>emptyDir</code>、<code>hostPath</code>、<code>nfs</code>、<code>configMap</code>、<code>secret</code> 等）都支持在同一个 Pod 内被多个容器同时访问和使用。</p></div><h5 id="emptyDir-的使用"><a href="#emptyDir-的使用" class="headerlink" title="emptyDir 的使用"></a>emptyDir 的使用</h5><p><code>emptyDir</code> 类型的 Volume 会在 Pod 被调度到某个节点（宿主机）时创建，Pod 内的所有容器都可以读写该目录中的数据。一旦 Pod 被删除或从该节点（宿主机）迁移，<code>emptyDir</code> 中的数据会被永久清除。因此，<code>emptyDir</code> 可以理解为本地存储，通常用于存放临时数据，例如 Web 服务器的日志文件或应用运行时的临时目录。<code>emptyDir</code> 类型的 Volume 的配置示例如下：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">test-pod</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="bullet">-</span> <span class="attr">name:</span> <span class="string">test-container</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">docker.io/nazarpc/webserver</span></span><br><span class="line">      <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">cache-volume</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/cache</span></span><br><span class="line">  <span class="attr">volumes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">cache-volume</span></span><br><span class="line">      <span class="attr">emptyDir:</span> {}</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置字段</th><th>说明</th></tr></thead><tbody><tr><td><code>volumeMounts</code></td><td>用于将 Pod 中定义的 Volume 挂载到容器内的指定路径。</td></tr><tr><td><code>emptyDir: {}</code></td><td>表示创建一个临时目录，当 Pod 删除或迁移时，该目录中的数据会被永久清除。</td></tr></tbody></table><h5 id="hostPath-的使用"><a href="#hostPath-的使用" class="headerlink" title="hostPath 的使用"></a>hostPath 的使用</h5><p><code>hostPath</code> 类型的 Volume 允许容器访问所在宿主机上的指定目录。例如，当需要运行一个访问 Docker 系统目录的容器时，可以将宿主机的 <code>/var/lib/docker</code> 目录挂载为一个 <code>hostPath</code> 类型的 Volume；或者在容器中运行 cAdvisor 时，可以将 <code>/dev/cgroups</code> 目录挂载为 <code>hostPath</code> Volume。需要注意的是，当 Pod 从当前宿主机上删除或迁移时，<code>hostPath</code> 中的数据不会被删除，但也不会随 Pod 一同迁移到新的宿主机上。此外，由于不同宿主机的文件系统结构和内容可能存在差异，相同的 Pod 在不同宿主机上使用 <code>hostPath</code> 时，可能会出现不同的行为。<code>hostPath</code> 类型的 Volume 的配置示例如下：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">test-pod</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="bullet">-</span> <span class="attr">name:</span> <span class="string">test-container</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">docker.io/nazarpc/webserver</span></span><br><span class="line">      <span class="comment"># 指定在容器中挂载路径</span></span><br><span class="line">      <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">test-volume</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/test-data</span></span><br><span class="line">  <span class="comment"># 指定所提供的存储卷</span></span><br><span class="line">  <span class="attr">volumes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">test-volume</span></span><br><span class="line">      <span class="comment"># 宿主机上的目录</span></span><br><span class="line">      <span class="attr">hostPath:</span></span><br><span class="line">        <span class="comment"># 宿主机上的目录路径</span></span><br><span class="line">        <span class="attr">path:</span> <span class="string">/data</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置字段</th><th>说明</th></tr></thead><tbody><tr><td><code>volumeMounts</code></td><td>定义容器内部的挂载路径 <code>/test-data</code>。</td></tr><tr><td><code>volumes.hostPath.path</code></td><td>指定宿主机上对应的物理目录 <code>/data</code>。</td></tr><tr><td><code>hostPath</code></td><td>该类型的卷允许容器直接访问宿主机上的文件系统资源。</td></tr></tbody></table><h5 id="nfs-的使用"><a href="#nfs-的使用" class="headerlink" title="nfs 的使用"></a>nfs 的使用</h5><p><code>nfs</code> 类型的 Volume 允许将已有的 NFS（Network File System，网络文件系统）存储挂载到 Pod 中，这样同一个 Pod 内的多个容器就可以共享使用。通过 NFS，可以让不同节点上的 Pod 访问同一个远程存储目录，从而实现跨主机的数据共享与持久化。与 <code>emptyDir</code> 或 <code>hostPath</code> 不同，<code>nfs</code> 的数据存储在远程服务器上，不会因 Pod 或节点的重建、迁移而丢失，非常适合需要共享存储或持久化数据的场景，比如数据库、缓存或日志存储。<code>nfs</code> 类型的 Volume 的配置示例如下：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">redis</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">revisionHistoryLimit:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">redis</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">redis</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="bullet">-</span> <span class="attr">name:</span> <span class="string">redis</span></span><br><span class="line">          <span class="comment"># 应用的镜像</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">redis</span></span><br><span class="line">          <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line">          <span class="comment"># 应用的内部端口</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis-6379</span></span><br><span class="line">              <span class="attr">containerPort:</span> <span class="number">6379</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">ALLOW_EMPTY_PASSWORD</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">"yes"</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">REDIS_PASSWORD</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">"redis"</span></span><br><span class="line">          <span class="comment"># 持久化挂载位置（容器内路径）</span></span><br><span class="line">          <span class="attr">volumeMounts:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis-persistent-storage</span></span><br><span class="line">              <span class="attr">mountPath:</span> <span class="string">/data</span></span><br><span class="line">      <span class="comment"># 定义存储卷</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis-persistent-storage</span></span><br><span class="line">          <span class="attr">nfs:</span>                             <span class="comment"># 使用 NFS 网络存储</span></span><br><span class="line">            <span class="attr">server:</span> <span class="number">192.168</span><span class="number">.126</span><span class="number">.112</span>        <span class="comment"># NFS 服务器的 IP 地址</span></span><br><span class="line">            <span class="attr">path:</span> <span class="string">/k8s-nfs/redis/data</span>      <span class="comment"># NFS 服务器上的共享目录</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置字段</th><th>说明</th></tr></thead><tbody><tr><td><code>revisionHistoryLimit: 2</code></td><td>仅保留最近 2 个历史版本的 ReplicaSet。</td></tr><tr><td><code>mountPath: /data</code></td><td>容器内 Redis 的数据存储路径。</td></tr><tr><td><code>nfs</code></td><td>通过 NFS 网络存储提供持久化数据目录。</td></tr></tbody></table><div class="admonition warning"><p class="admonition-title">特别注意</p><p>在 Kubernetes 集群中，如果需要使用 <code>nfs</code> 类型的 Volume（卷），则需要先在所有集群节点上分别手动安装 NFS 客户端，否则 Volume（卷）会无法正常挂载。比如，CentOS 系统安装 NFS 客户端，可以使用命令 <code>sudo yum install -y nfs-utils</code>。</p></div><h4 id="PV-与-PVC"><a href="#PV-与-PVC" class="headerlink" title="PV 与 PVC"></a>PV 与 PVC</h4><h5 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h5><ul><li><p>(1) 在 Kubernetes 中，存储管理是计算管理中的一个重要问题。为此，Kubernetes 提供了 PersistentVolume（PV）子系统，为用户和管理员提供了一个抽象层，用于屏蔽底层存储实现的复杂性，并通过统一的 API 管理存储资源的使用。该子系统引入了两个新的 API 资源：PersistentVolume（PV） 和 PersistentVolumeClaim（PVC）。</p></li><li><p>(2) PersistentVolume（PV）是由集群管理员预先配置的一块网络存储，它是集群级别的资源，就像节点（Node）一样。PV 可以看作是一种容量插件（类似于 Volume），但其生命周期独立于使用它的任何 Pod。PV 对象中包含了底层存储实现的详细信息，例如 NFS、iSCSI，或特定云服务提供商的存储系统。</p></li><li><p>(3) PersistentVolumeClaim（PVC）是用户发起的存储资源请求，类似于 Pod 对节点资源的使用。Pod 消耗节点资源（如 CPU、内存），而 PVC 消耗 PV 资源。用户可以在 PVC 中指定所需的存储大小以及访问模式（例如：单次读写或多次只读）。</p></li><li><p>(4) 虽然 PVC 让用户能够以抽象的方式使用存储资源，但在实际应用中，不同场景往往对存储有不同的特性需求（如性能、可靠性、备份策略等）。为满足这种灵活性，Kubernetes 提供了 StorageClass 资源。StorageClass 允许管理员定义存储的 “类别”，用以描述不同类型的存储服务。不同的存储类可以对应不同的服务质量（QoS）等级、备份策略或其他由管理员定义的策略。Kubernetes 本身并不限定这些类别的具体含义，这一概念在其他系统中有时被称为 “存储配置文件（Profile）”。</p></li><li><p>(5) 在实际使用中，PVC 与 PV 通常是一一对应的，PVC 会自动绑定到满足其需求的 PV 上，从而实现持久化存储的自动化管理。</p></li></ul><h5 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h5><p>PersistentVolume（PV）是集群中的资源，PersistentVolumeClaim（PVC）是用户对这些资源的请求并充当对资源的检查。PV 与 PVC 之间的交互遵循下面的生命周期阶段：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Provisioning → Binding → Using → Releasing → Recycling</span><br></pre></td></tr></tbody></table></figure><ul><li><p>(1) Provisioning（供应 / 准备）</p><ul><li>通过集群外的存储系统或者云平台来提供持久化存储支持，有两类方式：<ul><li>静态提供（Static）：集群管理员事先创建若干 PV，并在每个 PV 中描述底层真实存储的详细信息（例如 NFS、iSCSI、云盘等）。这些 PV 以资源对象存在于 Kubernetes API 中，供用户通过 PVC 消费。</li><li>动态提供（Dynamic）：当没有现成的、满足 PVC 要求的静态 PV 时，Kubernetes 可以基于 StorageClass 自动为 PVC 动态创建 PV（即动态配置卷）。要使用动态提供：<ul><li>PVC 必须指定某个 <code>storageClassName</code>（或者使用默认 StorageClass）。</li><li>对应的 StorageClass 必须已由管理员创建并配置好相应的 Provisioner（即外部存储插件）。</li><li>如果 PVC 明确请求一个不存在的类，则视为禁用动态配置（不会触发动态 Provisioning）。</li></ul></li></ul></li></ul></li><li><p>(2) Binding（绑定）</p><ul><li>用户创建 PVC 并在其中指定所需的容量和访问模式（Access Modes）。</li><li>Kubernetes 会查找符合 PVC 要求的 PV，并将其绑定（Bind）到该 PVC。</li><li>在找到合适的 PV 之前，PVC 处于未绑定（Pending）状态。</li></ul></li><li><p>(3) Using（使用）</p><ul><li>一旦 PVC 与 PV 绑定，用户可以在 Pod 的 <code>volumes</code> 中使用 PVC，就像使用普通 Volume 一样，Pod 内的容器可以通过 <code>volumeMounts</code> 挂载并访问该存储卷。</li></ul></li><li><p>(4) Releasing（释放）</p><ul><li>当用户删除 PVC（释放对存储的请求）时，PV 会进入 Released（已释放）状态。</li><li>注意：被释放的 PV 上可能仍然保留有先前使用者的数据。在这种状态下，如果不对数据做处理，该 PV 通常不能直接被新的 PVC 使用（取决于回收策略）。</li></ul></li><li><p>(5) Recycling（回收）</p><ul><li>PV 上可以设置回收策略，用于指定在 PVC 删除后如何处理底层存储资源，常见策略包括：<ul><li>Retain（保留）：默认回收策略，保留底层存储与数据，管理员需手动处理（例如备份或清理），然后手动将 PV 重新配置为可供新的 PVC 使用。</li><li>Delete（删除）：删除 PV 对象，并同时删除外部存储资源（删除操作需要底层存储插件支持）。</li><li>Recycle（回收）：旧版本 Kubernetes 支持（现已废弃），对底层卷执行简单的清理（比如 <code>rm -rf /thevolume/*</code>），清理后该 PV 可再次被新的 PVC 使用（回收操作需要相应插件支持或实现）。</li></ul></li></ul></li></ul><div class="admonition warning"><p class="admonition-title">总结</p><ul><li><strong>PV 是集群级别的存储资源，生命周期独立于单个 Pod。</strong></li><li><strong>PVC 是对 PV 的请求，用于记录存储容量与存储访问模式等需求。</strong></li><li>生命周期的完整流程为：准备（静态 / 动态）→ 绑定 → 使用 → 释放 → 回收 / 删除 / 保留，回收策略由 PV 的 <code>reclaimPolicy</code> 决定，管理员需要根据实际场景选择合适策略并配置相应的 StorageClass / Provisioner。</li></ul></div><h5 id="PV-的类型"><a href="#PV-的类型" class="headerlink" title="PV 的类型"></a>PV 的类型</h5><p>在 Kubernetes 中，PersistentVolume（PV）的类型有以下几种：</p><table><thead><tr><th>PV 类型</th><th>说明</th></tr></thead><tbody><tr><td> GCEPersistentDisk</td><td> 使用 Google Compute Engine 提供的持久磁盘（Persistent Disk）作为存储卷。</td></tr><tr><td>AWSElasticBlockStore</td><td> 使用 AWS 的 EBS（Elastic Block Store）卷作为存储卷。</td></tr><tr><td>AzureFile</td><td> 使用 Azure File 存储（基于 SMB 协议）作为共享文件卷。</td></tr><tr><td>AzureDisk</td><td> 使用 Azure 的托管磁盘（Managed Disk）或非托管磁盘作为块存储卷。</td></tr><tr><td>FC (Fibre Channel)</td><td> 通过光纤通道（Fibre Channel）协议连接的块存储设备。</td></tr><tr><td>FlexVolume</td><td> 可扩展的卷插件机制，允许用户通过外部驱动程序自定义存储挂载逻辑。</td></tr><tr><td>Flocker</td><td> 已弃用的存储方案，原用于容器与外部数据卷的动态关联。</td></tr><tr><td>NFS</td><td> 使用网络文件系统（Network File System，NFS）协议挂载远程共享存储，支持多容器共享访问。</td></tr><tr><td>iSCSI</td><td> 通过 iSCSI 协议访问远程块存储设备。</td></tr><tr><td>RBD (Ceph Block Device)</td><td> 使用 Ceph 提供的 RADOS 块设备（RBD）作为存储卷。</td></tr><tr><td>CephFS</td><td> 使用 Ceph 提供的分布式文件系统（CephFS）作为共享文件卷。</td></tr><tr><td>Cinder (OpenStack block storage)</td><td> 使用 OpenStack 的 Cinder 服务提供的块存储。</td></tr><tr><td>Glusterfs</td><td> 使用 GlusterFS 提供的分布式文件系统存储，支持多节点共享访问。</td></tr><tr><td>VsphereVolume</td><td> 使用 VMware vSphere 平台提供的虚拟磁盘（vmdk）作为存储卷。</td></tr><tr><td>Quobyte Volumes</td><td> 使用 Quobyte 提供的分布式文件系统作为存储卷。</td></tr><tr><td>HostPath</td><td> 将宿主机上的目录或文件挂载到 Pod 中（仅适用于单节点测试环境，不支持集群环境）。</td></tr><tr><td>Portworx Volumes</td><td> 使用 Portworx 存储解决方案提供的高可用分布式块存储。</td></tr><tr><td>ScaleIO Volumes</td><td> 使用 Dell EMC 的 ScaleIO（现 PowerFlex）分布式块存储。</td></tr><tr><td>StorageOS</td><td> 使用 StorageOS 提供的容器原生分布式存储系统。</td></tr></tbody></table><h5 id="PV-的阶段状态"><a href="#PV-的阶段状态" class="headerlink" title="PV 的阶段状态"></a>PV 的阶段状态</h5><p>在 Kubernetes 中，PersistentVolume（PV）的生命周期会经历多个阶段（Phase），用于描述其当前的使用状态。PV 的阶段状态有以下几个：</p><table><thead><tr><th>状态</th><th>说明</th></tr></thead><tbody><tr><td> Available</td><td>PV 资源尚未被任何 PVC（PersistentVolumeClaim）绑定，可供新的 Claim 使用。</td></tr><tr><td>Bound</td><td>PV 已经成功绑定到某个 PVC，正在被使用。</td></tr><tr><td>Released</td><td> 与该 PV 绑定的 PVC 已被删除，卷已释放但尚未被回收，此时卷中的数据可能仍然存在。</td></tr><tr><td>Failed</td><td>PV 自动回收失败，需要管理员手动干预或清理。</td></tr></tbody></table><div class="admonition note"><p class="admonition-title">提示</p><ul><li>PV 的状态转换是由 Kubernetes 控制器自动管理的。</li><li>如果存储类（StorageClass）指定回收策略为保留（<code>reclaimPolicy: Retain</code>），那么在 PVC 删除后 PV 会保持 <code>Released</code> 状态，需管理员手动处理。</li></ul></div><h5 id="PV-的使用案例"><a href="#PV-的使用案例" class="headerlink" title="PV 的使用案例"></a>PV 的使用案例</h5><div class="admonition note"><p class="admonition-title">提示</p><p>本节将演示在 Kubernetes 集群中，如何配合使用 PV + PVC + Pod，使用的 PV 类型是 <code>nfs</code>。</p></div><h6 id="创建-PV"><a href="#创建-PV" class="headerlink" title="创建 PV"></a>创建 PV</h6><ul><li>通过 YAML 文件（比如 <code>pv-demo.yaml</code>）创建 5 个 PersistentVolume（PV），类型都为 <code>nfs</code>，但存储大小各不相同，是否可读也不相同（<strong>请自行更改 NFS 服务器的 IP 地址，并在 NFS 服务器上提前创建好相应的共享目录</strong>）</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolume</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pv001</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">pv001</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">capacity:</span></span><br><span class="line">    <span class="attr">storage:</span> <span class="string">2Gi</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteMany</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteOnce</span></span><br><span class="line">  <span class="attr">persistentVolumeReclaimPolicy:</span> <span class="string">Retain</span></span><br><span class="line">  <span class="attr">nfs:</span></span><br><span class="line">    <span class="attr">server:</span> <span class="number">192.168</span><span class="number">.2</span><span class="number">.188</span>       <span class="comment"># NFS 服务器的 IP 地址</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/data/volumes/v1</span>      <span class="comment"># NFS 服务器上的共享目录</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolume</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pv002</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">pv002</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">capacity:</span></span><br><span class="line">    <span class="attr">storage:</span> <span class="string">3Gi</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteOnce</span></span><br><span class="line">  <span class="attr">persistentVolumeReclaimPolicy:</span> <span class="string">Retain</span></span><br><span class="line">  <span class="attr">nfs:</span></span><br><span class="line">    <span class="attr">server:</span> <span class="number">192.168</span><span class="number">.2</span><span class="number">.188</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/data/volumes/v2</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolume</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pv003</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">pv003</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">capacity:</span></span><br><span class="line">    <span class="attr">storage:</span> <span class="string">5Gi</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteMany</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteOnce</span></span><br><span class="line">  <span class="attr">persistentVolumeReclaimPolicy:</span> <span class="string">Retain</span></span><br><span class="line">  <span class="attr">nfs:</span></span><br><span class="line">    <span class="attr">server:</span> <span class="number">192.168</span><span class="number">.2</span><span class="number">.188</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/data/volumes/v3</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolume</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pv004</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">pv004</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">capacity:</span></span><br><span class="line">    <span class="attr">storage:</span> <span class="string">10Gi</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteMany</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteOnce</span></span><br><span class="line">  <span class="attr">persistentVolumeReclaimPolicy:</span> <span class="string">Retain</span></span><br><span class="line">  <span class="attr">nfs:</span></span><br><span class="line">    <span class="attr">server:</span> <span class="number">192.168</span><span class="number">.2</span><span class="number">.188</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/data/volumes/v4</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolume</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pv005</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">pv005</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">capacity:</span></span><br><span class="line">    <span class="attr">storage:</span> <span class="string">15Gi</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteMany</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteOnce</span></span><br><span class="line">  <span class="attr">persistentVolumeReclaimPolicy:</span> <span class="string">Retain</span></span><br><span class="line">  <span class="attr">nfs:</span></span><br><span class="line">    <span class="attr">server:</span> <span class="number">192.168</span><span class="number">.2</span><span class="number">.188</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/data/volumes/v5</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件（比如 <code>pv-demo.yaml</code>）中定义的 PV 对象 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> pv-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 PV</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pv</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE</span><br><span class="line">pv001   2Gi        RWO,RWX        Retain           Available                                   17s</span><br><span class="line">pv002   3Gi        RWO            Retain           Available                                   17s</span><br><span class="line">pv003   5Gi        RWO,RWX        Retain           Available                                   17s</span><br><span class="line">pv004   10Gi       RWO,RWX        Retain           Available                                   17s</span><br><span class="line">pv005   15Gi       RWO,RWX        Retain           Available                                   17s</span><br></pre></td></tr></tbody></table></figure><h6 id="创建-PVC-并绑定-PV"><a href="#创建-PVC-并绑定-PV" class="headerlink" title="创建 PVC 并绑定 PV"></a>创建 PVC 并绑定 PV</h6><ul><li>通过 YAML 文件（比如 <code>pvc-demo.yaml</code>）创建一个 PersistentVolumeClaim（PVC），需要 6G 存储空间，所以不会匹配上面的 <code>pv001</code>、<code>pv002</code>、<code>pv003</code></li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PersistentVolumeClaim</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mypvc</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">accessModes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">ReadWriteMany</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">storage:</span> <span class="string">6Gi</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置字段</th><th>含义</th></tr></thead><tbody><tr><td><code>namespace</code></td><td>PVC 所在命名空间为 <code>default</code>。</td></tr><tr><td><code>accessModes</code></td><td>设置访问模式，这里为 <code>ReadWriteMany</code>，表示允许多个节点同时读写。</td></tr><tr><td><code>resources.requests.storage</code></td><td>申请的存储大小，这里为 <code>6Gi</code>。</td></tr></tbody></table><ul><li>创建或更新 YAML 文件（比如 <code>pvc-demo.yaml</code>）中定义的 PVC 对象 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> pvc-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 PVC</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pvc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE</span><br><span class="line">mypvc   Bound    pv004    10Gi       RWO,RWX                       5s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 PV</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pv</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON   AGE</span><br><span class="line">pv001   2Gi        RWO,RWX        Retain           Available                                           4m11s</span><br><span class="line">pv002   3Gi        RWO            Retain           Available                                           4m11s</span><br><span class="line">pv003   5Gi        RWO,RWX        Retain           Available                                           4m11s</span><br><span class="line">pv004   10Gi       RWO,RWX        Retain           Bound       default/mypvc                           4m11s</span><br><span class="line">pv005   15Gi       RWO,RWX        Retain           Available                                           4m11s</span><br></pre></td></tr></tbody></table></figure><h6 id="创建-Pod-并挂载-PVC"><a href="#创建-Pod-并挂载-PVC" class="headerlink" title="创建 Pod 并挂载 PVC"></a>创建 Pod 并挂载 PVC</h6><ul><li>通过 YAML 文件（比如 <code>pod-demo.yaml</code>）创建一个 Deployment 和 Service，并挂载 PVC</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-svc</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>                 <span class="comment"># Service 类型为 NodePort，可通过节点 IP 访问</span></span><br><span class="line">  <span class="attr">selector:</span>                      <span class="comment"># 选择器，匹配后端 Pod 的标签（labels）</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-pod</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>                   <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span>             <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-deploy</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-pod</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">nginx-pod</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line">          <span class="attr">volumeMounts:</span>               <span class="comment"># 声明要挂载的卷（volume）</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">html</span></span><br><span class="line">              <span class="attr">mountPath:</span> <span class="string">/usr/share/nginx/html/</span></span><br><span class="line">      <span class="attr">volumes:</span>                        <span class="comment"># 定义 Pod 级别的卷（volume）</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">html</span></span><br><span class="line">          <span class="attr">persistentVolumeClaim:</span>      <span class="comment"># 指定使用已有的 PersistentVolumeClaim（PVC）</span></span><br><span class="line">            <span class="attr">claimName:</span> <span class="string">mypvc</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置字段</th><th>含义</th></tr></thead><tbody><tr><td><code>containers.volumeMounts.mountPath</code></td><td>指定容器内的挂载路径 <code>/usr/share/nginx/html/</code>。</td></tr><tr><td><code>volumes.persistentVolumeClaim.claimName</code></td><td>绑定前面创建的 PVC 名称 <code>mypvc</code>。</td></tr></tbody></table><ul><li>创建或更新 YAML 文件（比如 <code>pod-demo.yaml</code>）中定义的 Deployment 和 Service 对象 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> pod-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pod<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                            READY   STATUS    RESTARTS   AGE    IP            NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-deploy-7ccc7cd487-77mmd   1/1     Running   0          8m9s   10.244.0.11   k8s-node1    &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes   ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        92d</span><br><span class="line">nginx-svc    NodePort    10.0.0.62    &lt;none&gt;        80:31566/TCP   3m29s</span><br></pre></td></tr></tbody></table></figure><ul><li>在 NFS 的共享目录中，创建 Nginx 的首页文件（<code>index.html</code>），避免挂载卷（Volume）后覆盖了 Nginx 镜像原有的默认首页，导致 Nginx 首页访问出现 403 错误 </li></ul><figure class="highlight shell"><table><tbody><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"># 连接进 Nginx 容器内，在 NFS 共享目录中创建 Nginx 首页的 HTML 文件</span></span><br><span class="line">kubectl <span class="built_in">exec</span><span class="params"> -it</span> nginx-deploy-7ccc7cd487-77mmd -- bash<span class="params"> -c</span> <span class="string">'echo "&lt;h1&gt;Hello from NFS Volume&lt;/h1&gt;" &gt; /usr/share/nginx/html/index.html'</span></span><br></pre></td></tr></tbody></table></figure><ul><li>最后通过任意一个集群节点的 IP 与 Service 对外暴露的端口（比如 <code>http://192.168.2.191:31566</code>），就可以在 Kubernetes 集群外部通过浏览器访问 Nginx 的首页（如下图所示）</li></ul><p><img data-src="../../../asset/2025/11/k8s-volume-nfs-nginx.png"></p><h6 id="手动回收-Released-PV"><a href="#手动回收-Released-PV" class="headerlink" title="手动回收 Released PV"></a>手动回收 Released PV</h6><p>在<a href="/posts/723af70c.html#PV-%E7%9A%84%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B">上面的案例中</a>，当 Pod 和 PVC 都被删除后，PV 会处于 <code>Released</code> 状态，对应的底层存储与数据会保留下来。此时，集群管理员需要手动处理（例如备份或清理数据），然后手动将 PV 重新配置，这样该 PV 才可以供新的 PVC 使用。</p><ul><li>删除 Pod 与 PVC</li></ul><figure class="highlight shell"><table><tbody><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"># 删除 Pod</span></span><br><span class="line">kubectl delete<span class="params"> -f</span> pod-demo.yaml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除 PVC</span></span><br><span class="line">kubectl delete<span class="params"> -f</span> pvc-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 PV</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pv</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON   AGE</span><br><span class="line">pv001   2Gi        RWO,RWX        Retain           Available                                           3h35m</span><br><span class="line">pv002   3Gi        RWO            Retain           Available                                           3h35m</span><br><span class="line">pv003   5Gi        RWO,RWX        Retain           Available                                           3h35m</span><br><span class="line">pv004   10Gi       RWO,RWX        Retain           Released    default/mypvc                           3h35m</span><br><span class="line">pv005   15Gi       RWO,RWX        Retain           Available                                           3h35m</span><br></pre></td></tr></tbody></table></figure><p>从上面的输出信息，可以看到 <code>pv004</code> 的状态为 <code>STATUS: Released</code>，表示该 PV 原先被某个 PVC（这里是 <code>default/mypvc</code>）绑定过，但 PVC 已被删除。由于该 PV 的回收策略是 <code>Retain</code>，Kubernetes 不会自动清理其中的数据，也不会重新将它标记为可用（<code>Available</code>）。要让处于 <code>Released</code> 状态的 PV 再次可用（允许重新绑定新的 PVC），必须手动回收该 PV，以下是标准的做法。</p><hr><ul><li><p>1、删除旧 PVC 产生的残留数据</p><ul><li>通常 PV 对应一个存储路径（比如 NFS、<code>hostPath</code>、或本地目录等）。</li><li>(1) 先找到 PV 对应的存储目录路径：<figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl describe pv pv004</span><br></pre></td></tr></tbody></table></figure></li><li>(2) 然后在输出内容中找到：<figure class="highlight yml"><table><tbody><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="attr">Source:</span></span><br><span class="line">    <span class="attr">Server:</span> <span class="number">192.168</span><span class="number">.2</span><span class="number">.188</span></span><br><span class="line">    <span class="attr">Path:</span> <span class="string">/data/volumes/v4</span></span><br></pre></td></tr></tbody></table></figure></li><li>(3) 最后手动删除存储目录路径中的所有文件：<figure class="highlight shell"><table><tbody><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"># 这一步骤会删除上一个 PVC 的所有数据，请谨慎执行</span></span><br><span class="line">sudo rm<span class="params"> -rf</span> /data/volumes/v4/*</span><br></pre></td></tr></tbody></table></figure></li></ul></li><li><p>2、移除 PV 上的旧 Claim 信息</p><ul><li>因为 PV 仍然绑定了旧的 PVC（<code>claimRef</code> 字段），所以必须解除旧 PVC 的绑定。</li><li>Kubernetes 不允许直接编辑已绑定的 PV<ul><li> 需要强制修改 PV<ul><li> 强制修改 PV<figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl patch pv pv004<span class="params"> -p</span> <span class="string">'{"spec":{"claimRef": null}}'</span></span><br></pre></td></tr></tbody></table></figure></li><li>修改完成后，PV 的状态会改变为 <code>Available</code></li></ul></li><li>或者手动编辑 PV<ul><li> 手动编辑 PV<figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl edit pv pv004</span><br></pre></td></tr></tbody></table></figure></li><li>删除如下字段（<code>claimRef</code>）内容 <figure class="highlight yml"><table><tbody><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">claimRef:</span></span><br><span class="line">  <span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">PersistentVolumeClaim</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mypvc</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line">  <span class="attr">resourceVersion:</span> <span class="string">"473636"</span></span><br><span class="line">  <span class="attr">uid:</span> <span class="string">1be643da-a7fe-4957-b8ec-887952ae7763</span></span><br></pre></td></tr></tbody></table></figure></li><li>保存退出后，PV 的状态会改变为 <code>Available</code></li></ul></li></ul></li></ul></li><li><p>3、确认 PV 可用</p><ul><li>查看所有 PV<figure class="highlight shell"><table><tbody><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"># 查看 PV 列表</span></span><br><span class="line">kubectl get pv</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE</span><br><span class="line">pv001   2Gi        RWO,RWX        Retain           Available                                   3h56m</span><br><span class="line">pv002   3Gi        RWO            Retain           Available                                   3h56m</span><br><span class="line">pv003   5Gi        RWO,RWX        Retain           Available                                   3h56m</span><br><span class="line">pv004   10Gi       RWO,RWX        Retain           Available                                   3h56m</span><br><span class="line">pv005   15Gi       RWO,RWX        Retain           Available                                   3h56m</span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/posts/35310aec.html">Centos7 搭建 NFS 服务器</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之九</title>
    <link href="https://www.techgrow.cn/posts/cfb1715d.html"/>
    <id>https://www.techgrow.cn/posts/cfb1715d.html</id>
    <published>2025-10-08T13:12:19.000Z</published>
    <updated>2025-10-08T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><h3 id="配置管理"><a href="#配置管理" class="headerlink" title="配置管理"></a>配置管理</h3><h4 id="Secret"><a href="#Secret" class="headerlink" title="Secret"></a>Secret</h4><h5 id="Secret-的介绍"><a href="#Secret-的介绍" class="headerlink" title="Secret 的介绍"></a>Secret 的介绍</h5><ul><li><p>Secret 的概述</p><ul><li>Secret 是 Kubernetes 中一种用于存储敏感数据的对象类型。</li><li>Secret 会将敏感数据存储在 Etcd 里面，让 Pod 容器以环境变量或者挂载 Volume（卷）的方式进行访问。</li><li>Secret 的主要设计目标是：避免将敏感信息直接写入 Pod 的镜像或配置文件（如 Deployment、ConfigMap）中。</li><li>这些敏感数据包括（不限于）：<ul><li>数据库的用户名和密码</li><li> API Token 或访问密钥</li><li> SSL/TLS 私钥和证书</li><li> SSH 密钥</li></ul></li></ul></li><li><p> Secret 的作用</p></li></ul><table><thead><tr><th>作用</th><th>说明</th></tr></thead><tbody><tr><td>保护敏感信息</td><td>通过 Base64 编码的形式保存机密数据，防止在 YAML 文件中明文出现。</td></tr><tr><td>与 Pod 解耦</td><td>应用不直接携带凭证，Secret 可独立管理、更新和分发。</td></tr><tr><td>灵活挂载</td><td>可作为环境变量或 Volume（卷）文件挂载到容器中。</td></tr><tr><td>与 ServiceAccount 结合使用</td><td>可用于保存访问 API Server 的 Token 等认证信息。</td></tr><tr><td>支持自动轮换和更新</td><td>可结合控制器或外部系统（如 Vault）可实现密钥动态更新。</td></tr></tbody></table><ul><li>Secret 的类型</li></ul><table><thead><tr><th> Secret 类型</th><th>用途说明</th></tr></thead><tbody><tr><td> Opaque</td><td> 默认类型，用于存放任意用户定义的键值对。</td></tr><tr><td>kubernetes.io/dockerconfigjson</td><td> 存放 Docker Registry 的认证信息，用于拉取私有镜像。</td></tr><tr><td>kubernetes.io/service-account-token</td><td> 系统自动创建，用于 ServiceAccount 与 API Server 通信。</td></tr><tr><td>kubernetes.io/tls</td><td> 存放 TLS 证书与私钥，用于 HTTPS、Ingress 等场景。</td></tr><tr><td>bootstrap.kubernetes.io/token</td><td> 集群引导时 Kubelet 注册节点所用的临时令牌。</td></tr></tbody></table><ul><li>Secret 的使用场景</li></ul><table><thead><tr><th>使用场景</th><th>使用说明</th></tr></thead><tbody><tr><td>应用访问数据库</td><td>存放数据库的账号密码，通过环境变量注入。</td></tr><tr><td>拉取私有镜像</td><td>创建 Docker Registry Secret 供 <code>imagePullSecrets</code> 使用。</td></tr><tr><td>HTTPS 服务</td><td>存放 TLS 证书，用于 Ingress 或自签服务。</td></tr><tr><td>外部 API 调用</td><td>存放第三方服务的 API Token。</td></tr><tr><td>集群内部通信认证</td><td> ServiceAccount Token 类型 Secret。</td></tr></tbody></table><ul><li>Secret 的注意事项<ul><li>虽然 Secret 可以用于保护敏感信息，但它并非绝对安全：<ul><li>默认仅使用 Base64 编码，并未将信息加密存储；</li><li>通常需要启用 Kubernetes Encryption at Rest，确保在 Etcd 中加密存储；</li><li>合理配置集群安全机制（RBAC），限制访问 Secret 的权限；</li><li>避免将 Secret 信息直接输出到日志文件或终端；</li><li>建议结合外部安全系统（如 HashiCorp Vault、Sealed Secrets、External Secrets Operator）进行管理。</li></ul></li><li>Secret 以 Volume（卷）的方式挂载时支持热更新<ul><li><strong> Secret 更新后，不会自动更新 Pod 中容器内的环境变量，也不会触发容器重启；</strong></li><li><strong>但是，如果 Secret 是以 Volume（卷）的方式挂载，并且该挂载未使用 <code>subPath</code>，则 Pod 中容器内挂载的文件会在 60 秒内自动更新（热更新）；</strong></li><li>另外，应用程序需要在运行时重新读取这些文件（例如通过文件监听或定时重新加载配置）才能真正实现热更新。</li></ul></li><li>通过控制器（如 Deployment）触发 Pod 重启可以实现 Secret 更新生效<ul><li>更改 Secret 后，可以手动触发 Deployment 滚动重启（比如执行 <code>kubectl rollout restart deployment &lt;deployment-name&gt;</code>）；</li><li>Deployment 滚动重启时，Kubernetes 会让该 Deployment 下的所有 Pod 重新创建，但不会更改镜像版本；</li><li>当 Pod 重启后，容器启动时会重新加载 Secret，从而使最新的机密配置生效。</li></ul></li></ul></li></ul><div class="admonition note"><p class="admonition-title">subPath 的作用</p><ul><li><code>subPath</code> 是 Kubernetes 在 Volume 挂载中的一个重要机制，用于只挂载卷里的单个文件或者子目录，而不是整个目录。</li><li><code>subPath</code> 不支持热更新，即使底层 Volume（如 ConfigMap 或 Secret）更新后，挂载到 <code>subPath</code> 的文件也不会自动刷新（热更新）。</li><li><code>subPath</code> 不能用于挂载整个目录时的热更新场景，如果需要实时更新配置（比如热更新 Nginx 配置），不可以使用 <code>subPath</code>。</li></ul></div><h5 id="Secret-的创建"><a href="#Secret-的创建" class="headerlink" title="Secret 的创建"></a>Secret 的创建</h5><p>Secret 的创建通常有以下三种方式：</p><ul><li>通过命令行创建 Secret</li></ul><figure class="highlight shell"><table><tbody><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"># 手动创建 Secret</span></span><br><span class="line">kubectl create secret generic my-secret<span class="params"> --from</span>-literal=username=admin<span class="params"> --from</span>-literal=password=123456</span><br></pre></td></tr></tbody></table></figure><ul><li>通过文件创建 Secret</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create secret generic db-secret<span class="params"> --from</span>-file=username.txt<span class="params"> --from</span>-file=password.txt</span><br></pre></td></tr></tbody></table></figure><ul><li>通过 YAML 文件（比如 <code>my-secret.yaml</code>）创建 Secret</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Secret</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">my-secret</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">Opaque</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">username:</span> <span class="string">YWRtaW4=</span>      <span class="comment"># Base64(admin)</span></span><br><span class="line">  <span class="attr">password:</span> <span class="string">MTIzNDU2</span>      <span class="comment"># Base64(123456)</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Secret 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> my-secret.yaml</span><br></pre></td></tr></tbody></table></figure><h5 id="Secret-的查看"><a href="#Secret-的查看" class="headerlink" title="Secret 的查看"></a>Secret 的查看</h5><ul><li>查看 Secret 的详情 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl describe secret my-secret</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Secret 的完整内容 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看 Secret 的内容（注意：内容经过 Base64 编码）</span></span><br><span class="line">kubectl get secret my-secret<span class="params"> -o</span> yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Secret 列表 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看默认命名空间下的所有 Secret</span></span><br><span class="line">kubectl get secrets</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看特定命名空间下的所有 Secret</span></span><br><span class="line">kubectl get secrets<span class="params"> -n</span> dev</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看所有命名空间下的 Secret</span></span><br><span class="line">kubectl get secrets<span class="params"> --all</span>-namespaces</span><br></pre></td></tr></tbody></table></figure><h5 id="Secret-的更改"><a href="#Secret-的更改" class="headerlink" title="Secret 的更改"></a>Secret 的更改</h5><blockquote><p>Secret 的更改有以下几种方式</p></blockquote><ul><li>(1) 使用 <code>kubectl edit secret</code>（最常用）直接编辑 Secret，默认会打开一个临时编辑器（比如 <code>vi</code> 或 <code>nano</code>），编辑后保存退出即可，保存后会自动更新 Secret。这种方式需要手动对配置内容进行 Base64 编码，比如 <code>echo -n 'newuser' | base64</code>。</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl edit secret my-secret</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 使用 <code>kubectl apply</code>（声明式更新），如果有一个用于定义 Secret 的 YAML 文件（例如 <code>my-secret.yaml</code>），可以执行以下命令更新 Secret。这种方式可以直接在 YAML 文件中使用未经过 Base64 编码的配置内容，K8s 会自动将其转换成 Base64 编码。</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> my-secret.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 使用 <code>kubectl patch secret</code>（部分字段更新），只更新指定的字段（无需编辑整个 YAML）。这种方式 K8s 会自动将配置内容转换成 Base64 编码再存入 Etcd 中，不需要手动处理 Base64 编码。</li></ul><figure class="highlight shell"><table><tbody><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"># 更新单个字段（Key）</span></span><br><span class="line">kubectl patch secret my-secret<span class="params"> -p</span> <span class="string">'{"stringData":{"password":"123456"}}'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 同时改两个字段（Key）</span></span><br><span class="line">kubectl patch secret my-secret<span class="params"> -p</span> <span class="string">'{"stringData":{"username":"root","password":"123456"}}'</span></span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 直接重新创建 Secret（简单粗暴），也就是先删除旧的 Secret，然后创建新的 Secret。这种方式 K8s 会自动将配置内容转换成 Base64 编码再存入 Etcd 中，不需要手动处理 Base64 编码。</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create secret generic my-secret<span class="params"> --from</span>-literal=username=root<span class="params"> --from</span>-literal=password=123456<span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml | kubectl apply<span class="params"> -f</span> -</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">Secret 热更新说明</p><ul><li>上面介绍的四种 Secret 更新方式，都不会自动更新相关 Pod 中容器内的环境变量，也不会触发相关 Pod 的 滚动更新（即不会重启 Pod，不会重启容器）。</li><li>但是，如果 Secret 是以 Volume（卷）的方式挂载，并且该挂载未使用 <code>subPath</code>，那么在 Secret 更新后，Pod 中容器内挂载的文件会在 60 秒内自动刷新（热更新）。</li></ul></div><blockquote><p>Secret 更新后滚动更新 Pod</p></blockquote><ul><li>上面介绍的四种 Secret 更新方式，都不会触发相关 Pod 的滚动更新（Rolling Update），也就是 Pod 不会自动重启，但可以通过手动修改 Pod Annotations 的方式强制触发 Pod 的滚动更新。比如：</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl patch deployment my-nginx<span class="params"> --patch</span> <span class="string">'{"spec": {"template": {"metadata":{"annotations": {"version/config": "20190411" }}}}}'</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在这个例子中，往 <code>spec.template.metadata.annotations</code> 中添加了 <code>version/config</code>，每次在 Secret 更新后，可以通过手动修改 <code>version/config</code> 来触发 Pod 的滚动更新。</li><li>这里的 <code>spec.template.metadata.annotations</code> 是 Pod 模板（<code>spec.template</code>）元数据中的注解字段，当该字段的内容发生变化时，Kubernetes 会认为 Pod 模板被修改，于是触发新的 Replica Set（RS）创建，从而滚动替换所有旧的 Pod。</li></ul><div class="admonition warning"><p class="admonition-title">Pod 滚动更新方案</p><ul><li>在更新 Secret 后，除了可以通过手动修改 Pod Annotations 的方式强制触发 Pod 的滚动更新，还可以手动触发 Deployment 的滚动重启，从而让 Pod 重启，比如执行命令 <code>kubectl rollout restart deployment &lt;deployment-name&gt;</code>。</li><li>更推荐使用自动检测 Secret 变更的方案（更高级），例如借助 Stakater Reloader 等第三方工具监控 Secret 的变化。一旦检测到更新，就会自动触发相关 Pod 的滚动更新，从而确保配置自动生效。</li></ul></div><h5 id="Secret-的删除"><a href="#Secret-的删除" class="headerlink" title="Secret 的删除"></a>Secret 的删除</h5><figure class="highlight shell"><table><tbody><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"># 删除默认命名空间下的单个 Secret</span></span><br><span class="line">kubectl delete secret my-secret</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除默认命名空间下的多个 Secret</span></span><br><span class="line">kubectl delete secret my-secret db-secret api-token</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除默认命名空间下的所有 Secret（慎用），系统自动生成的 ServiceAccount Token Secret 也会被删掉，从而影响 K8s 集群的正常运行</span></span><br><span class="line">kubectl delete secret<span class="params"> --all</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除特定命名空间下的单个 Secret</span></span><br><span class="line">kubectl delete secret my-secret<span class="params"> -n</span> dev</span><br></pre></td></tr></tbody></table></figure><h5 id="在-Pod-中使用-Secret"><a href="#在-Pod-中使用-Secret" class="headerlink" title="在 Pod 中使用 Secret"></a>在 Pod 中使用 Secret</h5><p>Secret 创建后，可以通过以下两种方式供 Pod 容器使用：</p><ul><li>挂载为环境变量，K8s 会将 Secret 中的键值对映射为系统环境变量 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">secret-env-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo $DB_USER $DB_PASS; tail -f /dev/null"</span>]</span><br><span class="line">    <span class="attr">env:</span>                      <span class="comment"># 定义环境变量</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_USER</span>           <span class="comment"># 环境变量名</span></span><br><span class="line">      <span class="attr">valueFrom:</span>              <span class="comment"># 值来源于外部引用</span></span><br><span class="line">        <span class="attr">secretKeyRef:</span>         <span class="comment"># 引用类型为 Secret</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">my-secret</span>     <span class="comment"># Secret 的名称（需事先创建）</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">username</span>       <span class="comment"># Secret 中对应的键</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_PASS</span></span><br><span class="line">      <span class="attr">valueFrom:</span></span><br><span class="line">        <span class="attr">secretKeyRef:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">my-secret</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">password</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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"># 或者使用 Deployment 管理 Pod</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">secret-env-demo</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">secret-env-demo</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">secret-env-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo $DB_USER $DB_PASS; tail -f /dev/null"</span>]</span><br><span class="line">        <span class="attr">env:</span>                      <span class="comment"># 定义环境变量</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_USER</span>           <span class="comment"># 环境变量名</span></span><br><span class="line">          <span class="attr">valueFrom:</span>              <span class="comment"># 值来源于外部引用</span></span><br><span class="line">            <span class="attr">secretKeyRef:</span>         <span class="comment"># 引用类型为 Secret</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">my-secret</span>     <span class="comment"># Secret 的名称（需事先创建）</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">username</span>       <span class="comment"># Secret 中对应的键</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_PASS</span></span><br><span class="line">          <span class="attr">valueFrom:</span></span><br><span class="line">            <span class="attr">secretKeyRef:</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">my-secret</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">password</span></span><br></pre></td></tr></tbody></table></figure><ul><li>挂载为 Volume（卷），K8s 会自动将 Secret 中每个键映射为文件名（最终会自动创建多个文件），文件内容为键对应的值 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">secret-volume-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"cat /etc/secret-data/username; tail -f /dev/null"</span>]</span><br><span class="line">    <span class="attr">volumeMounts:</span>                     <span class="comment"># 定义容器内要挂载的卷</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">secret-volume</span>             <span class="comment"># 对应下面 volumes 中的卷名称（必须一致）</span></span><br><span class="line">      <span class="attr">mountPath:</span> <span class="string">/etc/secret-data</span>     <span class="comment"># 将 Secret 内容挂载到容器内的该目录下</span></span><br><span class="line">      <span class="attr">readOnly:</span> <span class="literal">true</span>                  <span class="comment"># 设置为只读（推荐），防止容器内误修改</span></span><br><span class="line">  <span class="attr">volumes:</span>                            <span class="comment"># 在 Pod 层定义卷（Volume）</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">secret-volume</span>               <span class="comment"># 指定卷的名称</span></span><br><span class="line">    <span class="attr">secret:</span>                           <span class="comment"># 指定卷的类型为 Secret</span></span><br><span class="line">      <span class="attr">secretName:</span> <span class="string">my-secret</span>           <span class="comment"># 指定引用的 Secret 名称（需事先创建）</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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"><span class="comment"># 或者使用 Deployment 管理 Pod</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">secret-volume-demo</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">secret-volume-demo</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">secret-volume-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"cat /etc/secret-data/username; tail -f /dev/null"</span>]</span><br><span class="line">        <span class="attr">volumeMounts:</span>                     <span class="comment"># 定义容器内要挂载的卷</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">secret-volume</span>             <span class="comment"># 对应下面 volumes 中的卷名称（必须一致）</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/etc/secret-data</span>     <span class="comment"># 将 Secret 内容挂载到容器内的该目录下</span></span><br><span class="line">          <span class="attr">readOnly:</span> <span class="literal">true</span>                  <span class="comment"># 设置为只读（推荐），防止容器内误修改</span></span><br><span class="line">      <span class="attr">volumes:</span>                            <span class="comment"># 在 Pod 层定义卷（Volume）</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">secret-volume</span>               <span class="comment"># 指定卷的名称</span></span><br><span class="line">        <span class="attr">secret:</span>                           <span class="comment"># 指定卷的类型为 Secret</span></span><br><span class="line">          <span class="attr">secretName:</span> <span class="string">my-secret</span>           <span class="comment"># 指定引用的 Secret 名称（需事先创建）</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Secret-的完整使用案例"><a href="#Secret-的完整使用案例" class="headerlink" title="Secret 的完整使用案例"></a>Secret 的完整使用案例</h5><ul><li>通过 YAML 文件（比如 <code>secret-env-demo.yaml</code>）定义 Secret 和 Pod，并使用环境变量的方式引用 Secret</li></ul><figure class="highlight yml"><table><tbody><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"># 定义 Secret</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Secret</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">my-secret</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">Opaque</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">username:</span> <span class="string">YWRtaW4=</span>      <span class="comment"># Base64(admin)</span></span><br><span class="line">  <span class="attr">password:</span> <span class="string">MTIzNDU2</span>      <span class="comment"># Base64(123456)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment"># 定义 Deployment</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">secret-env-demo</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">secret-env-demo</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">secret-env-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo $DB_USER $DB_PASS; tail -f /dev/null"</span>]</span><br><span class="line">        <span class="attr">env:</span>                      <span class="comment"># 定义环境变量</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_USER</span>           <span class="comment"># 环境变量名</span></span><br><span class="line">          <span class="attr">valueFrom:</span>              <span class="comment"># 值来源于外部引用</span></span><br><span class="line">            <span class="attr">secretKeyRef:</span>         <span class="comment"># 引用类型为 Secret</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">my-secret</span>     <span class="comment"># Secret 的名称（需事先创建）</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">username</span>       <span class="comment"># Secret 中对应的键</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_PASS</span></span><br><span class="line">          <span class="attr">valueFrom:</span></span><br><span class="line">            <span class="attr">secretKeyRef:</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">my-secret</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">password</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Secret 和 Deployment 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> secret-env-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">secret-env-demo-6c74c9dd76-9rbgr   1/1     Running   0          39s   10.244.0.13   k8s-master   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 的日志信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl logs secret-env-demo-6c74c9dd76-9rbgr</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">admin 123456</span><br></pre></td></tr></tbody></table></figure><h4 id="ConfigMap"><a href="#ConfigMap" class="headerlink" title="ConfigMap"></a>ConfigMap</h4><p>在 Kubernetes 中，ConfigMap（配置映射）是一种非常重要的配置管理对象，用于将配置数据与应用程序代码分离。它的设计初衷是：应用程序镜像保持通用性，而不同环境下的配置信息（如开发、测试、生产）通过 ConfigMap 动态注入。</p><h5 id="ConfigMap-的介绍"><a href="#ConfigMap-的介绍" class="headerlink" title="ConfigMap 的介绍"></a>ConfigMap 的介绍</h5><ul><li><p>ConfigMap 的概述</p><ul><li>ConfigMap 是一种用于存储非敏感配置信息的键值对集合的 Kubernetes 资源对象。</li><li>ConfigMap 会将非敏感数据存储在 Etcd 里面，让 Pod 容器以环境变量和挂载 Volume（卷）的方式进行访问。</li><li>ConfigMap 的核心思想是：配置应该与镜像解耦，应用部署时再注入配置，换句话说：<ul><li>用户可以在不修改镜像的前提下更改配置；</li><li>用户可以让相同的容器镜像在不同环境中以不同的方式运行。</li></ul></li></ul></li><li><p>ConfigMap 的作用</p><ul><li>配置解耦<ul><li>应用程序不再依赖镜像内置的配置，而是从外部（ConfigMap）加载配置。</li></ul></li><li>集中化管理配置<ul><li>所有 Pod 的环境变量、配置文件都可以统一由 ConfigMap 管理。</li></ul></li><li>灵活的注入方式<ul><li> ConfigMap 可通过以下方式注入到 Pod 容器中：<ul><li>作为环境变量；</li><li>以卷（Volume）挂载的方式出现在容器文件系统中。</li></ul></li></ul></li></ul></li><li><p>ConfigMap 的注意事项</p><ul><li>ConfigMap 不适合存储敏感信息<ul><li>因为信息是明文保存的，敏感信息应该使用 Secret 进行存储。</li></ul></li><li>ConfigMap 以 Volume（卷）的方式挂载时支持热更新<ul><li><strong> ConfigMap 更新后，不会自动更新 Pod 中容器内的环境变量，也不会触发容器重启；</strong></li><li><strong>但是，如果 ConfigMap 是以 Volume（卷）的方式挂载，并且该挂载未使用 <code>subPath</code>，则 Pod 中容器内挂载的文件会在 60 秒内自动更新（热更新）；</strong></li><li>另外，应用程序需要在运行时重新读取这些文件（例如通过文件监听或定时重新加载配置）才能真正实现热更新。</li></ul></li><li>通过控制器（如 Deployment）触发 Pod 重启可以实现 ConfigMap 更新生效<ul><li>更改 ConfigMap 后，手动触发 Deployment 滚动重启（比如执行 <code>kubectl rollout restart deployment &lt;deployment-name&gt;</code>）；</li><li>Deployment 滚动重启时，K8s 会让 Deployment 下的 Pod 全部重新创建，但不会改变镜像版本；</li><li>当 Pod 重启后，容器启动时会重新加载 ConfigMap，从而使最新的配置生效。</li></ul></li><li>配置信息的大小限制<ul><li>单个 ConfigMap 的大小不能超过 1MB。</li></ul></li></ul></li><li><p>ConfigMap 的使用场景</p></li></ul><table><thead><tr><th>使用场景</th><th>示例</th></tr></thead><tbody><tr><td>多环境配置</td><td>开发、测试、生产等环境使用不同的 ConfigMap</td></tr><tr><td> 应用启动参数</td><td>通过环境变量动态配置应用的启动参数</td></tr><tr><td>配置文件注入</td><td>将配置文件挂载进容器内部，如 Nginx、Tomcat 的配置文件</td></tr><tr><td>滚动更新配置</td><td>更改 ConfigMap 后，可以通过 Deployment 滚动重启 Pod，动态加载新的配置</td></tr><tr><td>与 Secret 搭配使用</td><td> ConfigMap 负责管理非敏感配置信息，Secret 负责管理敏感配置信息（如密码、Token）</td></tr></tbody></table><ul><li>ConfigMap 的最佳实践</li></ul><table><thead><tr><th>使用场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>应用支持热更新（如 Nginx、Envoy）</td><td>挂载 ConfigMap 文件，监控文件变更，感知配置更新</td></tr><tr><td>应用不支持热更新（如 Java Spring Boot）</td><td>更改 ConfigMap 后，触发 Deployment 滚动重启（<code>kubectl rollout restart deployment &lt;deployment-name&gt;</code>），不会改变镜像版本</td></tr><tr><td>同时包含敏感与非敏感配置</td><td>使用 Secret + ConfigMap 分开挂载</td></tr></tbody></table><ul><li> ConfigMap 与 Secret 对比</li></ul><table><thead><tr><th>对比项</th><th> ConfigMap</th><th>Secret</th></tr></thead><tbody><tr><td> 内容</td><td>非敏感配置信息</td><td>敏感配置信息（如密码、证书）</td></tr><tr><td>编码</td><td>明文</td><td> Base64 编码</td></tr><tr><td>用途</td><td>普通配置文件、应用启动参数</td><td>密钥、Token、证书</td></tr><tr><td>安全性</td><td>低</td><td>高</td></tr><tr><td>存储方式</td><td> Etcd 明文存储</td><td> Etcd 加密存储（需要额外配置）</td></tr></tbody></table><div class="admonition note"><p class="admonition-title">subPath 的作用</p><ul><li><code>subPath</code> 是 Kubernetes 在 Volume 挂载中的一个重要机制，用于只挂载卷里的单个文件或者子目录，而不是整个目录。</li><li><code>subPath</code> 不支持热更新，即使底层 Volume（如 ConfigMap 或 Secret）更新后，挂载到 <code>subPath</code> 的文件也不会自动刷新（热更新）。</li><li><code>subPath</code> 不能用于挂载整个目录时的热更新场景，如果需要实时更新配置（比如热更新 Nginx 配置），不可以使用 <code>subPath</code>。</li></ul></div><h5 id="ConfigMap-的创建"><a href="#ConfigMap-的创建" class="headerlink" title="ConfigMap 的创建"></a>ConfigMap 的创建</h5><p>ConfigMap 的创建通常有以下几种方式：</p><ul><li>通过命令行创建 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create configmap app-config<span class="params"> --from</span>-literal=app_mode=production<span class="params"> --from</span>-literal=app_debug=<span class="literal">false</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过文件创建 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create configmap app-config<span class="params"> --from</span>-file=app.properties</span><br></pre></td></tr></tbody></table></figure><ul><li>通过目录创建 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create configmap app-config<span class="params"> --from</span>-file=./config/</span><br></pre></td></tr></tbody></table></figure><ul><li>通过 YAML 文件（比如 <code>app-config.yaml</code>）创建 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><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">apiVersion: v1</span><br><span class="line">kind: ConfigMap</span><br><span class="line">metadata:</span><br><span class="line">  name: app-config</span><br><span class="line">data:</span><br><span class="line">  app_mode: <span class="string">"production"</span></span><br><span class="line">  app_debug: <span class="string">"false"</span></span><br><span class="line">  database.conf: |</span><br><span class="line">    host=127.0.0.1</span><br><span class="line">    port=3306</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 ConfigMap 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> app-config.yaml</span><br></pre></td></tr></tbody></table></figure><h5 id="ConfigMap-的查看"><a href="#ConfigMap-的查看" class="headerlink" title="ConfigMap 的查看"></a>ConfigMap 的查看</h5><ul><li>查看 ConfigMap 的详情 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl describe configmap app-config</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 ConfigMap 的完整内容 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get configmap app-config<span class="params"> -o</span> yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 ConfigMap 列表 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看默认命名空间下的所有 ConfigMap</span></span><br><span class="line">kubectl get configmaps</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看特定命名空间下的所有 ConfigMap</span></span><br><span class="line">kubectl get configmaps<span class="params"> -n</span> dev</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看所有命名空间下的 ConfigMap</span></span><br><span class="line">kubectl get configmaps<span class="params"> --all</span>-namespaces</span><br></pre></td></tr></tbody></table></figure><h5 id="ConfigMap-的更改"><a href="#ConfigMap-的更改" class="headerlink" title="ConfigMap 的更改"></a>ConfigMap 的更改</h5><blockquote><p>ConfigMap 的更改有以下几种方式</p></blockquote><ul><li>(1) 使用 <code>kubectl edit configmap</code>（最常用）直接编辑 ConfigMap，默认会打开一个临时编辑器（比如 <code>vi</code> 或 <code>nano</code>），编辑后保存退出即可，保存后会自动更新 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl edit configmap app-config</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 使用 <code>kubectl apply</code>（声明式更新），如果有一个用于定义 ConfigMap 的 YAML 文件（例如 <code>app-config.yaml</code>），可以执行以下命令更新 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> app-config.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 使用 <code>kubectl patch configmap</code>（部分字段更新），只更新指定的字段（无需编辑整个 YAML）</li></ul><figure class="highlight shell"><table><tbody><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"># 更新单个字段（Key）</span></span><br><span class="line">kubectl patch configmap app-config<span class="params"> -p</span> <span class="string">'{"data":{"app_mode":"development"}}'</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 同时改两个字段（Key）</span></span><br><span class="line">kubectl patch configmap app-config<span class="params"> -p</span> <span class="string">'{"data":{"app_mode":"development","app_debug":"true"}}'</span></span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 直接重新创建 ConfigMap（简单粗暴），也就是先删除旧的 ConfigMap，然后创建新的 ConfigMap</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create configmap app-config<span class="params"> --from</span>-literal=app_mode=development<span class="params"> --from</span>-literal=app_debug=<span class="literal">true</span><span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml | kubectl apply<span class="params"> -f</span> -</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">ConfigMap 热更新说明</p><ul><li>上面介绍的四种 ConfigMap 更新方式，都不会自动更新相关 Pod 中容器内的环境变量，也不会触发相关 Pod 的 滚动更新（即不会重启 Pod，不会重启容器）。</li><li>但是，如果 ConfigMap 是以 Volume（卷）的方式挂载，并且该挂载未使用 <code>subPath</code>，那么在 ConfigMap 更新后，Pod 中容器内挂载的文件会在 60 秒内自动刷新（热更新）。</li></ul></div><blockquote><p>ConfigMap 更新后滚动更新 Pod</p></blockquote><ul><li>上面介绍的四种 ConfigMap 更新方式，都不会触发相关 Pod 的滚动更新（Rolling Update），也就是 Pod 不会自动重启，但可以通过手动修改 Pod Annotations 的方式强制触发 Pod 的滚动更新。比如：</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl patch deployment my-nginx<span class="params"> --patch</span> <span class="string">'{"spec": {"template": {"metadata":{"annotations": {"version/config": "20190411" }}}}}'</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在这个例子中，往 <code>spec.template.metadata.annotations</code> 中添加了 <code>version/config</code>，每次在 ConfigMap 更新后，可以通过手动修改 <code>version/config</code> 来触发 Pod 的滚动更新。</li><li>这里的 <code>spec.template.metadata.annotations</code> 是 Pod 模板（<code>spec.template</code>）元数据中的注解字段，当该字段的内容发生变化时，Kubernetes 会认为 Pod 模板被修改，于是触发新的 Replica Set（RS）创建，从而滚动替换所有旧的 Pod。</li></ul><div class="admonition warning"><p class="admonition-title">Pod 滚动更新方案</p><ul><li>在更新 ConfigMap 后，除了可以通过手动修改 Pod Annotations 的方式强制触发 Pod 的滚动更新，还可以手动触发 Deployment 的滚动重启，从而让 Pod 重启，比如执行命令 <code>kubectl rollout restart deployment &lt;deployment-name&gt;</code>。</li><li>更推荐使用自动检测 ConfigMap 变更的方案（更高级），例如借助 Stakater Reloader 等第三方工具监控 ConfigMap 的变化。一旦检测到更新，就会自动触发相关 Pod 的滚动更新，从而确保配置自动生效。</li></ul></div><h5 id="ConfigMap-的删除"><a href="#ConfigMap-的删除" class="headerlink" title="ConfigMap 的删除"></a>ConfigMap 的删除</h5><figure class="highlight shell"><table><tbody><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"># 删除默认命名空间下的单个 ConfigMap</span></span><br><span class="line">kubectl delete configmap app-config</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除默认命名空间下的多个 ConfigMap</span></span><br><span class="line">kubectl delete configmap cm1 cm2 cm3</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除默认命名空间下的所有 ConfigMap（慎用），系统自动生成的 ConfigMap 也会被删掉，从而影响 K8s 集群的正常运行</span></span><br><span class="line">kubectl delete configmap<span class="params"> --all</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除特定命名空间下的单个 ConfigMap</span></span><br><span class="line">kubectl delete configmap nginx-config<span class="params"> -n</span> dev</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>删除 ConfigMap 不会立即影响已运行的 Pod，除非该 ConfigMap 是以挂载卷（Volume）或环境变量形式注入，并且 Pod 被重新启动或重新加载。</li></ul></div><h5 id="在-Pod-中使用-ConfigMap"><a href="#在-Pod-中使用-ConfigMap" class="headerlink" title="在 Pod 中使用 ConfigMap"></a>在 Pod 中使用 ConfigMap</h5><p>ConfigMap 创建后，可以通过以下两种方式供 Pod 容器使用：</p><ul><li>挂载为环境变量：K8s 会将 ConfigMap 中的键值对映射为系统环境变量 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">configmap-env-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo $APP_MODE $APP_DEBUG; tail -f /dev/null"</span>]</span><br><span class="line">    <span class="attr">env:</span>                       <span class="comment"># 定义环境变量</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_MODE</span>           <span class="comment"># 环境变量名</span></span><br><span class="line">      <span class="attr">valueFrom:</span>               <span class="comment"># 值来源于外部引用</span></span><br><span class="line">        <span class="attr">configMapKeyRef:</span>       <span class="comment"># 引用类型为 ConfigMap</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">app-config</span>     <span class="comment"># ConfigMap 的名称（需事先创建）</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">app_mode</span>        <span class="comment"># ConfigMap 中对应的键</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_DEBUG</span></span><br><span class="line">      <span class="attr">valueFrom:</span></span><br><span class="line">        <span class="attr">configMapKeyRef:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">app-config</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">app_debug</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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"># 或者使用 Deployment 管理 Pod</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">configmap-env-demo</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">configmap-env-demo</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">configmap-env-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo $APP_MODE $APP_DEBUG; tail -f /dev/null"</span>]</span><br><span class="line">        <span class="attr">env:</span>                       <span class="comment"># 定义环境变量</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_MODE</span>           <span class="comment"># 环境变量名</span></span><br><span class="line">          <span class="attr">valueFrom:</span>               <span class="comment"># 值来源于外部引用</span></span><br><span class="line">            <span class="attr">configMapKeyRef:</span>       <span class="comment"># 引用类型为 ConfigMap</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">app-config</span>     <span class="comment"># ConfigMap 的名称（需事先创建）</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">app_mode</span>        <span class="comment"># ConfigMap 中对应的键</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_DEBUG</span></span><br><span class="line">          <span class="attr">valueFrom:</span></span><br><span class="line">            <span class="attr">configMapKeyRef:</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">app-config</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">app_debug</span></span><br></pre></td></tr></tbody></table></figure><ul><li>挂载为 Volume（卷）：K8s 会自动将 ConfigMap 中每个键映射为文件名（最终会自动创建多个文件），文件内容为键对应的值 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">configmap-volume-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"cat /etc/config-data/app_mode; tail -f /dev/null"</span>]</span><br><span class="line">    <span class="attr">volumeMounts:</span>                     <span class="comment"># 定义容器内要挂载的卷</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span>             <span class="comment"># 对应下面 volumes 中的卷名称（必须一致）</span></span><br><span class="line">      <span class="attr">mountPath:</span> <span class="string">/etc/config-data</span>     <span class="comment"># 将 ConfigMap 内容挂载到容器内的该目录下</span></span><br><span class="line">      <span class="attr">readOnly:</span> <span class="literal">true</span>                  <span class="comment"># 设置为只读（推荐），防止容器内误修改</span></span><br><span class="line">  <span class="attr">volumes:</span>                            <span class="comment"># 在 Pod 层定义卷（Volume）</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span>               <span class="comment"># 指定卷的名称</span></span><br><span class="line">    <span class="attr">configMap:</span>                        <span class="comment"># 指定卷的类型为 ConfigMap</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">app-config</span>                <span class="comment"># 指定引用的 ConfigMap 名称（需事先创建）</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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"><span class="comment"># 或者使用 Deployment 管理 Pod</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">configmap-volume-demo</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">configmap-volume-demo</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">configmap-volume-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"cat /etc/config-data/app_mode; tail -f /dev/null"</span>]</span><br><span class="line">        <span class="attr">volumeMounts:</span>                     <span class="comment"># 定义容器内要挂载的卷</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span>             <span class="comment"># 对应下面 volumes 中的卷名称（必须一致）</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/etc/config-data</span>     <span class="comment"># 将 ConfigMap 内容挂载到容器内的该目录下</span></span><br><span class="line">          <span class="attr">readOnly:</span> <span class="literal">true</span>                  <span class="comment"># 设置为只读（推荐），防止容器内误修改</span></span><br><span class="line">      <span class="attr">volumes:</span>                            <span class="comment"># 在 Pod 层定义卷（Volume）</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span>               <span class="comment"># 指定卷的名称</span></span><br><span class="line">        <span class="attr">configMap:</span>                        <span class="comment"># 指定卷的类型为 ConfigMap</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">app-config</span>                <span class="comment"># 指定引用的 ConfigMap 名称（需事先创建）</span></span><br></pre></td></tr></tbody></table></figure><h5 id="ConfigMap-的完整使用案例"><a href="#ConfigMap-的完整使用案例" class="headerlink" title="ConfigMap 的完整使用案例"></a>ConfigMap 的完整使用案例</h5><ul><li>通过 YAML 文件（比如 <code>configmap-env-demo.yaml</code>）定义 ConfigMap 和 Pod，并使用环境变量的方式引用 ConfigMap</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义 ConfigMap</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">app-config</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">app_mode:</span> <span class="string">"production"</span></span><br><span class="line">  <span class="attr">app_debug:</span> <span class="string">"false"</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment"># 定义 Deployment</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">configmap-env-demo</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">configmap-env-demo</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">configmap-env-demo</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demo</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo $APP_MODE $APP_DEBUG; tail -f /dev/null"</span>]</span><br><span class="line">        <span class="attr">env:</span>                       <span class="comment"># 定义环境变量</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_MODE</span>           <span class="comment"># 环境变量名</span></span><br><span class="line">          <span class="attr">valueFrom:</span>               <span class="comment"># 值来源于外部引用</span></span><br><span class="line">            <span class="attr">configMapKeyRef:</span>       <span class="comment"># 引用类型为 ConfigMap</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">app-config</span>     <span class="comment"># ConfigMap 的名称（需事先创建）</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">app_mode</span>        <span class="comment"># ConfigMap 中对应的键</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_DEBUG</span></span><br><span class="line">          <span class="attr">valueFrom:</span></span><br><span class="line">            <span class="attr">configMapKeyRef:</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">app-config</span></span><br><span class="line">              <span class="attr">key:</span> <span class="string">app_debug</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 ConfigMap 和 Deployment 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> configmap-env-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                                  READY   STATUS    RESTARTS   AGE</span><br><span class="line">configmap-env-demo-698c84b677-625sk   1/1     Running   0          49s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 的日志信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl logs configmap-env-demo-698c84b677-625sk</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">production false</span><br></pre></td></tr></tbody></table></figure><h5 id="ConfigMap-实现-Nginx-自动热更新"><a href="#ConfigMap-实现-Nginx-自动热更新" class="headerlink" title="ConfigMap 实现 Nginx 自动热更新"></a>ConfigMap 实现 Nginx 自动热更新</h5><ul><li><a href="/posts/8639f08c.html">Kubernetes 中实现 Nginx 配置自动热更新</a></li></ul><h3 id="集群安全机制"><a href="#集群安全机制" class="headerlink" title="集群安全机制"></a>集群安全机制</h3><h4 id="RBAC-的基本概念"><a href="#RBAC-的基本概念" class="headerlink" title="RBAC 的基本概念"></a>RBAC 的基本概念</h4><p>RBAC（Role Based Access Control，基于角色的访问控制）在 Kubernetes <code>v1.5</code> 中首次引入，并在 <code>v1.6</code> 版本升级为 Beta，成为 Kubeadm 安装方式下的默认授权模式。RBAC 的核心思想是：通过角色定义权限，通过角色绑定将权限授予给特定的主体（比如 User、Group、ServiceAccount），从而实现精细化的访问控制。<strong>Kubernetes 集群要启用 RBAC 授权模式，需要在 API Server 的启动参数中添加 <code>--authorization-mode=RBAC</code>。</strong></p><hr><ul><li><p>在 RBAC（基于角色的访问控制）中，主要包含以下四个核心概念：</p><ul><li>角色（Role / ClusterRole）：<ul><li>定义一组可执行的权限规则，即允许对哪些资源执行哪些操作。</li></ul></li><li>角色绑定（RoleBinding / ClusterRoleBinding）：<ul><li>将角色与主体进行关联，从而使主体获得该角色定义的权限。</li></ul></li><li>主体（Subject）：<ul><li>表示可以被授予权限的实体，包括用户（User）、用户组（Group）和服务账户（ServiceAccount）。</li></ul></li><li>权限规则（Policy Rules）：<ul><li>具体描述允许执行的操作，包括对哪些资源（<code>resources</code>）、在哪些命名空间（<code>namespace</code>）、执行哪些动作（<code>verbs</code>）等。</li></ul></li></ul></li><li><p>相比其他访问控制方式，RBAC（基于角色的访问控制）具有以下优势：</p><ul><li>权限覆盖全面：<ul><li>对集群中的资源和非资源类型的访问权限均提供完整支持。</li></ul></li><li>动态调整：<ul><li>可在集群运行时更改权限配置，无需重启 API Server。</li></ul></li><li>API 原生支持：<ul><li>RBAC 由若干 API 对象构成，可像其他 Kubernetes 资源一样通过 <code>kubectl</code> 或 API 进行管理。</li></ul></li></ul></li></ul><hr><ul><li>在访问 Kubernetes 集群的时候，需要经过以下三个步骤：<ul><li>第一步：认证<ul><li>核心目标：<ul><li>用于确认访问者的身份。</li></ul></li><li>访问端口：<ul><li>对外不暴露 <code>8080</code> 端口（仅供集群内部组件访问）；</li><li>对外提供的访问端口为 <code>6443</code>（HTTPS 端口）。</li></ul></li><li>认证方式：<ul><li>HTTPS 证书认证：基于 CA 证书验证客户端身份；</li><li>HTTP Token 认证：通过 Token 标识用户身份；</li><li>HTTP Basic 认证：基于用户名和密码进行身份校验。</li></ul></li></ul></li><li>第二步：鉴权（授权）<ul><li>在确认用户身份后，判断其是否具备执行该操作的权限；</li><li>Kubernetes 主要基于 RBAC（基于角色的访问控制）机制进行鉴权；</li><li>权限通过角色（Role / ClusterRole）与角色绑定（RoleBinding / ClusterRoleBinding）来定义和分配。</li></ul></li><li>第三步：准入控制<ul><li>用于在请求通过认证和鉴权后，对请求内容进行进一步的策略检查；</li><li>本质上是一个由多个准入控制器（Admission Controllers）组成的列表；</li><li>如果请求被这些准入控制器中的规则允许，则放行请求；否则，请求会被拒绝。</li></ul></li></ul></li></ul><div class="admonition note"><p class="admonition-title">提示</p><p>在访问 Kubernetes 的过程中，都需要经过 API Server，由 API Server 做统一协调。比如，访问过程中需要 CA 证书、Token、或者用户名和密码；如果访问 Pod，则需要 Service Account。</p></div><h4 id="RBAC-的-API-资源对象"><a href="#RBAC-的-API-资源对象" class="headerlink" title="RBAC 的 API 资源对象"></a>RBAC 的 API 资源对象</h4><p>在 Kubernetes 中，RBAC 引入了 4 个新的顶级资源对象：Role、ClusterRole、RoleBinding、ClusterRoleBinding。同其他 API 资源对象一样，用户可以使用 <code>kubectl</code> 命令或者 API 调用等方式操作这些资源对象。</p><h5 id="Role（角色）"><a href="#Role（角色）" class="headerlink" title="Role（角色）"></a>Role（角色）</h5><blockquote><p>概念介绍</p></blockquote><ul><li>Role（角色）只能对命名空间内的资源进行授权。</li><li>一个 Role（角色）就是一组权限的集合，这里的权限都是许可形式的，不存在拒绝的规则。</li><li>在一个命名空间中，可以用 Role（角色）来定义一个角色。如果是集群级别的，就需要使用 ClusterRole（集群角色）了。</li></ul><blockquote><p>使用案例</p></blockquote><ul><li>在下面的例子中，定义的 Role（角色）具备读取 Pod 的权限：</li></ul><figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pod-reader</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]    <span class="comment"># 空字符串表示核心 API 组（/api/v1）</span></span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"pods"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"watch"</span>, <span class="string">"list"</span>]</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>参数名称</th><th>说明</th><th>配置示例</th></tr></thead><tbody><tr><td><code>apiGroups</code></td><td>支持的 API 组列表，用于指定资源所属的 API 组。核心组使用空字符串 <code>""</code> 表示。</td><td><code>"", "apps", "batch", "extensions"</code>（对应 <code>apiVersion: v1</code>、<code>apps/v1</code>、<code>batch/v1</code> 等）</td></tr><tr><td><code>resources</code></td><td>支持的资源对象列表，指定该角色可操作的 Kubernetes 资源类型。</td><td><code>pods</code>、<code>deployments</code>、<code>jobs</code>、<code>services</code> 等</td></tr><tr><td><code>verbs</code></td><td>对资源对象的操作方法列表，定义允许的具体操作。</td><td><code>get</code>、<code>watch</code>、<code>list</code>、<code>create</code>、<code>update</code>、<code>delete</code>、<code>patch</code>、<code>replace</code> 等</td></tr></tbody></table><h5 id="ClusterRole（集群角色）"><a href="#ClusterRole（集群角色）" class="headerlink" title="ClusterRole（集群角色）"></a>ClusterRole（集群角色）</h5><blockquote><p>概念介绍</p></blockquote><p>ClusterRole（集群角色）除了具备与 Role（角色）在单一命名空间内相同的资源管理能力外，由于其作用域为集群级别，还可用于授权以下几类对象：</p><ul><li>集群范围的资源，例如 <code>Node</code>（节点）；</li><li>非资源型路径，例如 <code>/healthz</code>；</li><li>跨全部命名空间的资源，例如在所有命名空间中的 <code>pods</code>。</li></ul><blockquote><p>使用案例</p></blockquote><ul><li>在下面的例子中，定义的 ClusterRole（集群角色）可以让用户有权访问任意一个或所有命名空间的 Secrets：</li></ul><figure class="highlight yaml"><table><tbody><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">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">secret-reader</span></span><br><span class="line">  <span class="comment"># ClusterRole 不受限于命名空间，因此不需要定义 namespace</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]     <span class="comment"># 空字符串表示核心 API 组（/api/v1）</span></span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"secrets"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"watch"</span>, <span class="string">"list"</span>]</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>参数名称</th><th>说明</th><th>配置示例</th></tr></thead><tbody><tr><td><code>apiGroups</code></td><td>支持的 API 组列表，用于指定资源所属的 API 组。核心组使用空字符串 <code>""</code> 表示。</td><td><code>"", "apps", "batch", "extensions"</code>（对应 <code>apiVersion: v1</code>、<code>apps/v1</code>、<code>batch/v1</code> 等）</td></tr><tr><td><code>resources</code></td><td>支持的资源对象列表，指定该角色可操作的 Kubernetes 资源类型。</td><td><code>pods</code>、<code>deployments</code>、<code>jobs</code>、<code>services</code> 等</td></tr><tr><td><code>verbs</code></td><td>对资源对象的操作方法列表，定义允许的具体操作。</td><td><code>get</code>、<code>watch</code>、<code>list</code>、<code>create</code>、<code>update</code>、<code>delete</code>、<code>patch</code>、<code>replace</code> 等</td></tr></tbody></table><h5 id="RoleBinding（角色绑定）"><a href="#RoleBinding（角色绑定）" class="headerlink" title="RoleBinding（角色绑定）"></a>RoleBinding（角色绑定）</h5><blockquote><p>核心概念</p></blockquote><ul><li>RoleBinding（角色绑定）用于将一个 Role（角色）绑定到一个指定的主体上，绑定的主体可以是 User（用户）、Group（用户组）或 ServiceAccount（服务账户）。</li><li>RoleBinding（角色绑定）的授权范围限定在某个命名空间内，它可以引用同一命名空间中的 Role，为该命名空间内的资源授予相应的访问权限。</li></ul><blockquote><p>使用案例</p></blockquote><ul><li>在下面的例子中，RoleBinding（角色绑定）将在 <code>default</code> 命名空间中将 <code>pod-reader</code> 角色授予用户 <code>jane</code>，可以让 <code>jane</code> 用户读取 <code>default</code> 命名空间里的 Pod：</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">RoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">read-pods</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">User</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">jane</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pod-reader</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>参数名称</th><th>说明</th><th>配置示例</th></tr></thead><tbody><tr><td><code>subjects</code></td><td>指定要绑定的访问主体，可以是用户（User）、用户组（Group）或服务账户（ServiceAccount）。此处绑定的主体是名为 <code>jane</code> 的用户。</td><td><code>jane</code></td></tr><tr><td><code>roleRef</code></td><td>指定要绑定的角色，用于定义该主体可执行的权限操作。此处引用的是命名空间内的 <code>pod-reader</code> 角色。</td><td><code>pod-reader</code></td></tr><tr><td><code>namespace</code></td><td>指定 RoleBinding 所属的命名空间，授权范围仅限于该命名空间内。此处为 <code>default</code> 命名空间。</td><td><code>default</code></td></tr></tbody></table><div class="admonition warning"><p class="admonition-title">特别注意</p><p>RoleBinding 除了可以引用 Role，还可以引用 ClusterRole，对属于同一命名空间内 ClusterRole 定义的资源主体进行授权。一种常见的做法是 Kubernetes 集群管理员为集群范围预先定义好一组角色（ClusterRole），然后在多个命名空间中重复使用这些 ClusterRole。</p></div><ul><li>在下面的例子中，使用 RoleBinding 绑定 ClusterRole（集群角色）<code>secret-reader</code>，使用户 <code>dave</code> 只能读取 <code>development</code> 命名空间中的 Secret：</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">RoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">read-secrets</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">development</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">User</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">dave</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">secret-reader</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><ul><li>配置说明：<ul><li>RoleBinding 将集群角色 <code>secret-reader</code> 授权给命名空间 <code>development</code> 中的用户 <code>dave</code>。</li><li>虽然绑定的是 集群角色（ClusterRole），但作用仍限定在 <code>development</code> 命名空间。</li><li><code>subjects</code> 指定了被授权的主体，<code>roleRef</code> 指定了要绑定的集群角色（ClusterRole）。</li></ul></li></ul><h5 id="ClusterRoleBinding（集群角色绑定）"><a href="#ClusterRoleBinding（集群角色绑定）" class="headerlink" title="ClusterRoleBinding（集群角色绑定）"></a>ClusterRoleBinding（集群角色绑定）</h5><blockquote><p>核心概念</p></blockquote><ul><li>ClusterRoleBinding（集群角色绑定）用于将一个 ClusterRole（集群角色）绑定到一个指定的主体上，绑定的主体可以是 User（用户）、Group（用户组）或 ServiceAccount（服务账户）。</li><li>ClusterRoleBinding（集群角色绑定）的授权范围为整个集群，它可以引用任意命名空间中的 ServiceAccount，并为所有命名空间或集群级资源授予相应的访问权限。</li><li>ClusterRoleBinding（集群角色绑定）中的角色只能是 ClusterRole（集群角色），不能是 Role（角色），用于执行集群级别或者对所有命名空间都生效的授权操作。</li></ul><blockquote><p>使用案例</p></blockquote><ul><li>在下面的例子中，使用 ClusterRoleBinding 绑定 ClusterRole（集群角色）<code>secret-reader</code>，允许 <code>manager</code> 组的用户读取所有命名空间中的 Secret：</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">read-secrets-global</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">Group</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">manager</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">secret-reader</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><ul><li>配置说明：<ul><li>ClusterRoleBinding 将集群角色 <code>secret-reader</code> 授权给整个集群中的用户组 <code>manager</code>。</li><li>授权作用范围为整个集群，不仅限于某个命名空间。</li><li><code>subjects</code> 指定了被授权的主体，<code>roleRef</code> 指定了要绑定的集群角色（ClusterRole）。</li></ul></li></ul><h4 id="RBAC-对资源的引用方式"><a href="#RBAC-对资源的引用方式" class="headerlink" title="RBAC 对资源的引用方式"></a>RBAC 对资源的引用方式</h4><ul><li>多数 Kubernetes 资源可以通过其名称字符串表示，即在 Endpoint 的 URL 相对路径中体现，例如 <code>pods</code>。</li><li>某些 Kubernetes API 包含下级资源，例如 Pod 的日志 (<code>logs</code>)。<ul><li>例如，Pod 日志的访问路径为： <code>GET /api/v1/namespaces/{namespaces}/pods/{name}/log</code>。</li><li>在这个例子中，Pod 是命名空间内的主资源，而 <code>log</code> 是 Pod 的下级资源。</li></ul></li><li>如果要在 RBAC 角色（Role 或 ClusterRole）中体现这种层级关系，需要用斜杠 <code>/</code> 来分隔主资源和下级资源。<ul><li>例如，如果希望某个主体同时拥有读取 Pod 以及读取 Pod 日志的权限，则可以配置 <code>resources</code> 为一个数组：<figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pod-and-pod-logs-reader</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]                  <span class="comment"># 空字符串表示核心 API 组（/api/v1）</span></span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"pods"</span>, <span class="string">"pods/log"</span>]  <span class="comment"># 同时包含了主资源 pods 和下级资源 pods/log</span></span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>]           <span class="comment"># 指定该角色允许执行的操作</span></span><br></pre></td></tr></tbody></table></figure></li></ul></li><li>Kubernetes 资源还可以通过名称（ResourceName）进行引用。<ul><li>在指定 ResourceName 后，使用 <code>get</code>、<code>delete</code>、<code>update</code>、<code>patch</code> 动词的请求，就会被限制在这个资源实例范围内。</li><li>例如，下面的声明可以授权一个主体只能对一个叫 <code>my-configmap</code> 的 ConfigMap 执行 <code>get</code> 和 <code>update</code> 操作：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">configmap-updater</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]                   <span class="comment"># 空字符串表示核心 API 组（/api/v1）</span></span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"configmaps"</span>]         <span class="comment"># resources 应为复数形式，比如 configmaps</span></span><br><span class="line">    <span class="attr">resourceNames:</span> [<span class="string">"my-configmap"</span>]   <span class="comment"># 指定作用对象</span></span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"update"</span>, <span class="string">"get"</span>]          <span class="comment"># 指定该角色允许执行的操作</span></span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul><h4 id="RBAC-中常见的角色定义示例"><a href="#RBAC-中常见的角色定义示例" class="headerlink" title="RBAC 中常见的角色定义示例"></a>RBAC 中常见的角色定义示例</h4><ul><li>(1) 允许读取核心 API 组中 Pod 的资源 </li></ul><figure class="highlight yml"><table><tbody><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="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]   <span class="comment"># 空字符串表示核心 API 组（/api/v1）</span></span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"pods"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>]</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 允许读写 <code>extensions</code> 和 <code>apps</code> 两个 API 组中的 <code>deployment</code> 资源 </li></ul><figure class="highlight yml"><table><tbody><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="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">"extensions"</span>, <span class="string">"apps"</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"deployments"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>, <span class="string">"create"</span>, <span class="string">"update"</span>, <span class="string">"patch"</span>, <span class="string">"delete"</span>]</span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 允许读写 <code>pods</code> 及读写 <code>jobs</code></li></ul><figure class="highlight yml"><table><tbody><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">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"pods"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>]</span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">"batch"</span>, <span class="string">"extensions"</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"jobs"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>, <span class="string">"create"</span>, <span class="string">"update"</span>, <span class="string">"patch"</span>, <span class="string">"delete"</span>]</span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 允许读取一个名为 <code>my-config</code> 的 ConfigMap（必须绑定到一个 RoleBinding 来限制到一个命名空间下的 ConfigMap）：</li></ul><figure class="highlight yml"><table><tbody><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="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"configmaps"</span>]</span><br><span class="line">    <span class="attr">resourceNames:</span> [<span class="string">"my-config"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>]</span><br></pre></td></tr></tbody></table></figure><ul><li>(5) 允许读取核心 API 组的 Node 资源（Node 属于集群级别的资源，必须放在 ClusterRole 中，并使用 ClusterRoleBinding 进行绑定）：</li></ul><figure class="highlight yml"><table><tbody><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="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"nodes"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>]</span><br></pre></td></tr></tbody></table></figure><ul><li>(6) 允许对非资源端点 <code>/healthz</code> 及其所有子路径进行 GET / POST 操作（必须使用 ClusterRole 和 ClusterRoleBinding）</li></ul><figure class="highlight yml"><table><tbody><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="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">nonResourceURLs:</span> [<span class="string">"/healthz"</span>, <span class="string">"/healthz/*"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"post"</span>]</span><br></pre></td></tr></tbody></table></figure><h4 id="RBAC-中常用的角色绑定示例"><a href="#RBAC-中常用的角色绑定示例" class="headerlink" title="RBAC 中常用的角色绑定示例"></a>RBAC 中常用的角色绑定示例</h4><ul><li>绑定用户名 <code>Alice@example.com</code></li></ul><figure class="highlight yml"><table><tbody><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="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">User</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">"Alice@example.com"</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><ul><li>绑定组名 <code>frontend-admins</code></li></ul><figure class="highlight yml"><table><tbody><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="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">Group</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">"frontend-admins"</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><ul><li>绑定 <code>kube-system</code> 命名空间中的默认 Service Account</li></ul><figure class="highlight yml"><table><tbody><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">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">Group</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">system:authentication</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">Group</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">system:unauthentication</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><h4 id="RBAC-中默认的角色和角色绑定"><a href="#RBAC-中默认的角色和角色绑定" class="headerlink" title="RBAC 中默认的角色和角色绑定"></a>RBAC 中默认的角色和角色绑定</h4><p>API Server 会创建一套默认的 ClusterRole 和 ClusterRoleBinding 资源对象，其中很多是以 <code>system:</code> 为前缀的，以表明这些资源属于基础架构，对这些对象的改动可能会造成集群故障。所有默认的 ClusterRole 和 RoleBinding 资源对象都会用标签 <code>kubernetes.io/bootstrapping=rbac-defaults</code> 进行标记。</p><blockquote><p>常见的系统角色</p></blockquote><table><thead><tr><th>默认的 ClusterRole</th><th> 默认的 ClusterRoleBinding</th><th> 描述</th></tr></thead><tbody><tr><td><code>system:basic-user</code></td><td><code>system:authenticated</code> 和 <code>system:unauthorized</code> 组</td><td>让用户能够读取自身的信息</td></tr><tr><td><code>system:discovery</code></td><td><code>system:authenticated</code> 和 <code>system:unauthorized</code> 组</td><td>对 API 发现 Endpoint 的只读访问，用于 API 级别的发现和协商</td></tr></tbody></table><blockquote><p>常见的用户角色</p></blockquote><p>有些默认角色不是以 <code>system:</code> 为前缀的，这部分角色是针对用户的，其中包含超级用户角色 <code>cluster-admin</code>，有的用于集群一级的角色 <code>cluster-status</code>，还有针对命名空间的角色 <code>admin</code>、<code>edit</code>、<code>view</code>。</p><table><thead><tr><th>默认的 ClusterRole</th><th> 默认的 ClusterRoleBinding</th><th> 描述</th></tr></thead><tbody><tr><td><code>cluster-admin</code></td><td><code>system:masters</code> 组</td><td>让超级用户可以对任何资源执行任何操作。如果在 ClusterRoleBinding 中使用，则影响的是整个集群的所有 NameSpace 中的任何资源；如果使用的是 RoleBinding，则能控制这一绑定的 NameSpace 中的资源，还包括 NameSpace 本身</td></tr><tr><td><code>cluster-status</code></td><td>None</td><td> 可以对基础集群状态信息进行只读访问</td></tr><tr><td><code>admin</code></td><td>None</td><td> 允许 <code>admin</code> 访问，可以限制在一个 NameSpace 中使用 RoleBinding。如果在 RoleBinding 中使用，则允许对 NameSpace 中的大多数资源进行读写访问，其中包含创建角色和角色绑定的能力。这一角色不允许操作 NameSpace 本身，也不能写入资源限制</td></tr><tr><td><code>edit</code></td><td>None</td><td> 允许对 NameSpace 内的大多数资源进行读写操作，不允许查看或更改角色，以及角色绑定</td></tr><tr><td><code>view</code></td><td>None</td><td> 允许对多数资源对象进行只读操作，但是对角色、角色绑定及 Secret 是不可访问的</td></tr></tbody></table><blockquote><p>核心 Master 组件角色</p></blockquote><table><thead><tr><th>默认的 ClusterRole</th><th> 默认的 ClusterRoleBinding</th><th> 描述</th></tr></thead><tbody><tr><td><code>system:kube-scheduler</code></td><td><code>system:kube-scheduler</code> 用户</td><td>能够访问 kube-scheduler 组件所需的资源</td></tr><tr><td><code>system:kube-controller-manager</code></td><td><code>system:kube-controller-manager</code> 用户</td><td>能够访问 kube-controller-manager 组件所需的资源</td></tr><tr><td><code>system:node</code></td><td><code>system:nodes</code> 组</td><td><br>- 允许访问 kubelet 所需的资源，包括对 Secret 的读取，以及对 Pod 的写入<br>- 未来会把上面的两个权限限制在分配到本 Node 的对象上<br>- 今后的鉴权过程，kubelet 必须以 <code>system:node</code> 及一个 <code>system:node</code> 形式的用户名进行。参看 <code>https://pr.k8s.io/40476</code></td></tr><tr><td><code>system:node-proxier</code></td><td><code>system:kube-proxy</code> 用户</td><td>允许访问 kube-proxy 所需的资源</td></tr><tr><td><code>system:kube-scheduler</code></td><td><code>system:kube-scheduler</code> 用户</td><td>能够访问 kube-scheduler 组件所需的资源</td></tr></tbody></table><h4 id="RBAC-中预防提权和授权初始化"><a href="#RBAC-中预防提权和授权初始化" class="headerlink" title="RBAC 中预防提权和授权初始化"></a>RBAC 中预防提权和授权初始化</h4><ul><li>RBAC API 会拒绝用户通过编辑角色或角色绑定的方式进行提权。这一限制是在 API 层面实现的，因此即使 RBAC 未启用，该限制仍然有效。</li><li>用户只能在拥有某个角色的所有权限，且与该角色的生效范围一致的前提下，才能对角色进行创建和更改。例如，用户 <code>user-1</code> 没有列出集群中所有 Secret 的权限，就无法创建具有该权限的 <code>ClusterRole</code>。</li><li>要让一个用户能够创建或者更改角色，需要满足以下条件：<ul><li>授予用户一个允许创建 / 更改 <code>Role</code> 或 <code>ClusterRole</code> 资源对象的角色；</li><li>为用户授予角色时，要覆盖该用户所能控制的所有权限范围。</li></ul></li><li>如果用户尝试创建超出自身权限的 Role 或 ClusterRole，该 API 调用会被拒绝。</li><li>如果一个用户的权限包含了某个角色的所有权限，则可以为其创建和更改该角色的绑定；或者，如果用户被授予了针对某个角色的绑定授权，也可以完成此操作。<ul><li>例如，用户 <code>user-1</code> 没有列出集群中所有 Secret 的权限，因此无法为一个具有此权限的角色创建 ClusterRoleBinding。要使用户能够创建或更改该角色绑定，需要：<ul><li>授予用户一个允许创建和更改 <code>RoleBinding</code> 或 <code>ClusterRoleBinding</code> 的角色；</li><li>授予用户绑定某一角色的权限，可以通过两种方式：<ul><li>隐式：让用户拥有该角色的所有权限；</li><li>显式：授予用户针对该角色或 ClusterRoleBinding 的操作权限。</li></ul></li></ul></li></ul></li><li>在进行第一个角色和角色绑定时，必须让初始用户具备其尚未被授予的权限，要进行初始的角色和角色绑定设置，有以下两种方法:<ul><li> 使用属于 <code>system:masters</code> 组的身份，这一群组默认具有 <code>cluster-admin</code> 这一超级用户角色的绑定；</li><li>如果 API Server 以 <code>--insecure-port</code> 参数启动，则客户端通过这个非安全端口进行接口调用，这一端口没有认证鉴权的限制。</li></ul></li></ul><blockquote><p>举个例子，允许用户 <code>user-1</code> 在 <code>user-1-namespace</code> 命名空间中，可以对其他用户授予 <code>admin</code>、<code>edit</code> 及 <code>view</code> 角色</p></blockquote><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">role-grantor</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">"rbac.authorization.k8s.io"</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"rolebindings"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"create"</span>]</span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">"rbac.authorization.k8s.io"</span>]</span><br><span class="line">    <span class="attr">resources:</span> [<span class="string">"clusterroles"</span>]</span><br><span class="line">    <span class="attr">verbs:</span> [<span class="string">"bind"</span>]</span><br><span class="line">    <span class="attr">resourceNames:</span> [<span class="string">"admin"</span>, <span class="string">"edit"</span>, <span class="string">"view"</span>]</span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">RoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">role-grantor-binding</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">user-1-namespace</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">User</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">user-1</span></span><br><span class="line">    <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">role-grantor</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><h4 id="RBAC-的完整使用演示案例"><a href="#RBAC-的完整使用演示案例" class="headerlink" title="RBAC 的完整使用演示案例"></a>RBAC 的完整使用演示案例</h4><div class="admonition note"><p class="admonition-title">提示</p><ul><li>本节将演示如何在 Kubernetes 集群中使用 RBAC，并通过 CA 证书进行认证（识别身份）。</li><li><strong>Kubernetes 集群要启用 RBAC 授权模式，需要在 API Server 的启动参数中添加 <code>--authorization-mode=RBAC</code>。</strong></li></ul></div><blockquote><p><strong>(1) 创建命名空间</strong></p></blockquote><ul><li>创建命名空间 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create ns roledemo</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有命名空间 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get ns</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME              STATUS   AGE</span><br><span class="line">default           Active   83d</span><br><span class="line">kube-flannel      Active   81d</span><br><span class="line">kube-node-lease   Active   83d</span><br><span class="line">kube-public       Active   83d</span><br><span class="line">kube-system       Active   83d</span><br><span class="line">roledemo          Active   111m</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(2) 创建 Deployment</strong></p></blockquote><ul><li>通过 YAML 文件（比如 <code>nginx-deploy.yaml</code>）创建 Deployment（用于创建和管理 Pod）</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-deploy</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">roledemo</span>     <span class="comment"># 指定命名空间</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-pod</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">nginx-pod</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Deployment 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> nginx-deploy.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看特定命名空间内的所有 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> roledemo</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                            READY   STATUS    RESTARTS   AGE</span><br><span class="line">nginx-deploy-85b7dd6b6d-29gs6   1/1     Running   0          16s</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(3) 创建 Role（角色）</strong></p></blockquote><ul><li>通过 YAML 文件（比如 <code>role-demo.yaml</code>）创建 Role（角色）</li></ul><figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">roledemo</span>     <span class="comment"># 指定命名空间</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pod-reader</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]                     <span class="comment"># 空字符串表示核心 API 组（/api/v1）</span></span><br><span class="line">  <span class="attr">resources:</span> [<span class="string">"pods"</span>]                 <span class="comment"># resources 应为复数形式，比如 pods</span></span><br><span class="line">  <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"watch"</span>, <span class="string">"list"</span>]     <span class="comment"># 指定该角色允许执行的操作</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Role 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> role-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看特定命名空间内的所有 Role（角色）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get roles<span class="params"> -n</span> roledemo</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         CREATED AT</span><br><span class="line">pod-reader   2025-10-08T11:47:10Z</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(4) 创建 RoleBinding（角色绑定）</strong></p></blockquote><ul><li>通过 YAML 文件（比如 <code>rolebinding-demo.yaml</code>）创建 RoleBinding（角色绑定）</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">RoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">read-pods</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">roledemo</span>     <span class="comment"># 指定命名空间</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">User</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mary</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pod-reader</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 RoleBinding（角色绑定）</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> rolebinding-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看特定命名空间内的所有 RoleBinding（角色绑定）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get rolebindings<span class="params"> -n</span> roledemo</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME        ROLE              AGE</span><br><span class="line">read-pods   Role/pod-reader   2m43s</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(5) 基于 CFSSL 生成 CA 证书</strong></p></blockquote><ul><li>创建 <code>ca-config.json</code>  文件 </li></ul><figure class="highlight json"><table><tbody><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><br><span class="line">  <span class="attr">"signing"</span>: {</span><br><span class="line">      <span class="attr">"default"</span>: {</span><br><span class="line">          <span class="attr">"expiry"</span>: <span class="string">"87600h"</span></span><br><span class="line">      },</span><br><span class="line">      <span class="attr">"profiles"</span>: {</span><br><span class="line">          <span class="attr">"kubernetes"</span>: {</span><br><span class="line">              <span class="attr">"expiry"</span>: <span class="string">"87600h"</span>,</span><br><span class="line">              <span class="attr">"usages"</span>: [</span><br><span class="line">                  <span class="string">"signing"</span>,</span><br><span class="line">                  <span class="string">"key encipherment"</span>,</span><br><span class="line">                  <span class="string">"server auth"</span>,</span><br><span class="line">                  <span class="string">"client auth"</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">}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建 <code>ca-csr.json</code> 文件 </li></ul><figure class="highlight yml"><table><tbody><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><br><span class="line">    <span class="attr">"CN":</span> <span class="string">"kubernetes"</span>,</span><br><span class="line">    <span class="attr">"key":</span> {</span><br><span class="line">        <span class="attr">"algo":</span> <span class="string">"rsa"</span>,</span><br><span class="line">        <span class="attr">"size":</span> <span class="number">2048</span></span><br><span class="line">    },</span><br><span class="line">    <span class="attr">"names":</span> [</span><br><span class="line">        {</span><br><span class="line">            <span class="attr">"C":</span> <span class="string">"CN"</span>,</span><br><span class="line">            <span class="attr">"L":</span> <span class="string">"Beijing"</span>,</span><br><span class="line">            <span class="attr">"ST":</span> <span class="string">"Beijing"</span>,</span><br><span class="line">            <span class="attr">"O":</span> <span class="string">"k8s"</span>,</span><br><span class="line">            <span class="attr">"OU":</span> <span class="string">"System"</span></span><br><span class="line">        }</span><br><span class="line">    ]</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建 <code>mary-csr.json</code> 文件 </li></ul><figure class="highlight json"><table><tbody><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><br><span class="line">  <span class="attr">"CN"</span>: <span class="string">"mary"</span>,</span><br><span class="line">  <span class="attr">"hosts"</span>: [],</span><br><span class="line">  <span class="attr">"key"</span>: {</span><br><span class="line">    <span class="attr">"algo"</span>: <span class="string">"rsa"</span>,</span><br><span class="line">    <span class="attr">"size"</span>: <span class="number">2048</span></span><br><span class="line">  },</span><br><span class="line">  <span class="attr">"names"</span>: [</span><br><span class="line">    {</span><br><span class="line">      <span class="attr">"C"</span>: <span class="string">"CN"</span>,</span><br><span class="line">      <span class="attr">"L"</span>: <span class="string">"BeiJing"</span>,</span><br><span class="line">      <span class="attr">"ST"</span>: <span class="string">"BeiJing"</span></span><br><span class="line">    }</span><br><span class="line">  ]</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>拷贝 Kubernetes 集群搭建时所创建的 CA 证书（包括 <code>ca.pem</code> 和 <code>ca-key.pem</code>），<strong>请自行更改 CA 证书的路径 </strong></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp /opt/kubernetes/ssl/ca* .</span><br></pre></td></tr></tbody></table></figure><ul><li>查看当前目录下的文件列表（最重要的是 <code>ca-key.pem</code>、<code>ca.pem</code> 文件）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls .</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ca-config.json  ca-csr.json  ca-key.pem  ca.pem  mary-csr.json  nginx-deploy.yaml  rolebinding-demo.yaml  role-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>通过 CFSSL 工具生成 CA 证书 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cfssl gencert<span class="params"> -ca</span>=ca.pem<span class="params"> -ca</span>-key=ca-key.pem<span class="params"> -config</span>=ca-config.json<span class="params"> -profile</span>=kubernetes mary-csr.json | cfssljson<span class="params"> -bare</span> mary</span><br></pre></td></tr></tbody></table></figure><ul><li>查看当前目录下的文件列表（最重要的是新生成的 <code>mary.csr</code>、<code>mary-key.pem</code>、<code>mary.pem</code> 文件）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls .</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ca-config.json  ca-csr.json  ca-key.pem  ca.pem  mary.csr  mary-csr.json  mary-key.pem  mary.pem  nginx-deploy.yaml  rolebinding-demo.yaml  role-demo.yaml</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(6) 为用户创建一个独立的 kubeconfig 配置文件</strong></p></blockquote><ul><li>定义集群连接信息（API Server 地址 + CA 证书），<strong>请自行将 <code>192.168.2.191</code> 更改为 API Server 的 IP 地址 </strong></li></ul><figure class="highlight shell"><table><tbody><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">kubectl config <span class="built_in">set</span>-cluster kubernetes \<span class="params"></span></span><br><span class="line"><span class="params">  --certificate</span>-authority=ca.pem \<span class="params"></span></span><br><span class="line"><span class="params">  --embed</span>-certs=<span class="literal">true</span> \<span class="params"></span></span><br><span class="line"><span class="params">  --server</span>=https://192.168.2.191:6443 \<span class="params"></span></span><br><span class="line"><span class="params">  --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><ul><li>配置用户身份（客户端证书和私钥）</li></ul><figure class="highlight shell"><table><tbody><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">kubectl config <span class="built_in">set</span>-credentials mary \<span class="params"></span></span><br><span class="line"><span class="params">  --client</span>-key=mary-key.pem \<span class="params"></span></span><br><span class="line"><span class="params">  --client</span>-certificate=mary.pem \<span class="params"></span></span><br><span class="line"><span class="params">  --embed</span>-certs=<span class="literal">true</span> \<span class="params"></span></span><br><span class="line"><span class="params">  --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><ul><li>将集群与用户 <code>mary</code> 绑定成一个上下文 </li></ul><figure class="highlight shell"><table><tbody><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">kubectl config <span class="built_in">set</span>-context default \<span class="params"></span></span><br><span class="line"><span class="params">  --cluster</span>=kubernetes \<span class="params"></span></span><br><span class="line"><span class="params">  --user</span>=mary \<span class="params"></span></span><br><span class="line"><span class="params">  --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><ul><li>切换到 <code>default</code> 上下文，使后续 <code>kubectl</code> 命令能够以用户 <code>mary</code> 的身份访问集群 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl config use-context default<span class="params"> --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><ul><li>查看当前目录下的文件列表（最重要的是新生成的 <code>mary-kubeconfig</code> 文件）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls .</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ca-config.json  ca-csr.json  ca-key.pem  ca.pem  mary.csr  mary-csr.json  mary-key.pem  mary-kubeconfig  mary.pem  nginx-deploy.yaml  rolebinding-demo.yaml  role-demo.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看凭证（用户）信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl<span class="params"> --kubeconfig</span>=mary-kubeconfig config view<span class="params"> --minify</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">apiVersion: v1</span><br><span class="line">clusters:</span><br><span class="line">- cluster:</span><br><span class="line">    certificate-authority-data: DATA+OMITTED</span><br><span class="line">    server: https://192.168.2.191:6443</span><br><span class="line">  name: kubernetes</span><br><span class="line">contexts:</span><br><span class="line">- context:</span><br><span class="line">    cluster: kubernetes</span><br><span class="line">    user: mary</span><br><span class="line">  name: default</span><br><span class="line">current-context: default</span><br><span class="line">kind: Config</span><br><span class="line">preferences: {}</span><br><span class="line">users:</span><br><span class="line">- name: mary</span><br><span class="line">  user:</span><br><span class="line">    client-certificate-data: REDACTED</span><br><span class="line">    client-key-data: REDACTED</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(7) 验证 RBAC 控制是否生效</strong></p></blockquote><ul><li>验证用户是否可以访问已被允许的资源，正常情况下用户 <code>mary</code> 可以在命名空间 <code>roledemo</code> 下列出或者查看 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> roledemo<span class="params"> --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                            READY   STATUS    RESTARTS   AGE</span><br><span class="line">nginx-deploy-85b7dd6b6d-29gs6   1/1     Running   1          18h</span><br></pre></td></tr></tbody></table></figure><ul><li>验证用户是否可以越权访问其他命名空间的资源，正常情况下用户 <code>mary</code> 不可以在其他命名空间（比如 <code>default</code>）下列出或者查看 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> default<span class="params"> --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error from server (Forbidden): pods is forbidden: User "mary" cannot list resource "pods" in API group "" in the namespace "default"</span><br></pre></td></tr></tbody></table></figure><ul><li>验证用户是否可以执行未被允许的操作（例如，删除 Pod），正常情况下用户 <code>mary</code> 不能执行删除 Pod 的操作 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl delete pod nginx-deploy-85b7dd6b6d-29gs6<span class="params"> -n</span> roledemo<span class="params"> --kubeconfig</span>=mary-kubeconfig</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error from server (Forbidden): pods "nginx-deploy-85b7dd6b6d-29gs6" is forbidden: User "mary" cannot delete resource "pods" in API group "" in the namespace "roledemo"</span><br></pre></td></tr></tbody></table></figure><ul><li>通过 API Server + CA 证书直接验证用户身份，如果身份验证失败，可以添加 <code>-v</code> 参数让 <code>curl</code> 命令输出详细的日志信息来排查问题 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl<span class="params"> --cert</span> ./mary.pem<span class="params"> --key</span> ./mary-key.pem<span class="params"> --cacert</span> ./ca.pem https://192.168.2.191:6443/api/v1/namespaces/roledemo/pods</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 或者，curl 命令加上 -v 参数，输出详细的日志信息</span></span><br><span class="line">curl<span class="params"> -v</span><span class="params"> --cert</span> ./mary.pem<span class="params"> --key</span> ./mary-key.pem<span class="params"> --cacert</span> ./ca.pem https://192.168.2.191:6443/api/v1/namespaces/roledemo/pods</span><br></pre></td></tr></tbody></table></figure>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 中实现 Nginx 配置信息自动热加载</title>
    <link href="https://www.techgrow.cn/posts/8639f08c.html"/>
    <id>https://www.techgrow.cn/posts/8639f08c.html</id>
    <published>2025-10-05T13:12:19.000Z</published>
    <updated>2025-10-05T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="Nginx-配置信息自动热加载"><a href="#Nginx-配置信息自动热加载" class="headerlink" title="Nginx 配置信息自动热加载"></a>Nginx 配置信息自动热加载</h2><p>在生产环境中，Nginx 的配置信息通常是通过 Kubernetes 的 ConfigMap 进行存储和管理。为了在 ConfigMap 更新后，让 Nginx 自动加载最新的配置信息（即热加载，不会重启 Pod，不会中断现有请求），可以采用以下几种方案：</p><table><thead><tr><th>方案序号</th><th>方案名称</th><th> Nginx 是否可以直接 Reload</th><th> 优点</th><th>缺点</th></tr></thead><tbody><tr><td>方案一</td><td>容器之间共享进程命名空间</td><td>可以</td><td>简单有效</td><td>依赖 <code>shareProcessNamespace</code>（共享进程命名空间），容器间进程可见，安全性较低</td></tr><tr><td>方案二</td><td>部署 Reload Agent</td><td> 可以</td><td>安全隔离</td><td>实现复杂一点</td></tr></tbody></table><div class="admonition note"><p class="admonition-title">方案选择建议</p><ul><li>如果是在开发或测试环境中简单实现 Nginx 配置信息自动热加载，推荐使用方案一（容器之间共享进程命名空间）。</li><li>如果是在生产环境中实现 Nginx 配置信息自动热加载，推荐使用方案二（部署 Reload Agent），避免跨容器进程控制，隔离性更好。</li></ul></div><span id="more"></span><div class="admonition warning"><p class="admonition-title">Secret 热更新</p><ul><li>在 Kubernetes 中，如果使用 Secret 来管理密码、证书、Token 等敏感信息，同样可以使用文中介绍的两种方案来实现 Secret 自动热更新，只需要简单更改对应的 YAML 配置文件（Volume 挂载的配置内容）即可。</li><li>这是因为 Secret 与 ConfigMap 的使用方式基本是一致的，都可以使用 Volume（卷）的方式将其挂载到 Pod 容器中。</li></ul></div><h3 id="实现方案一"><a href="#实现方案一" class="headerlink" title="实现方案一"></a>实现方案一</h3><h4 id="方案介绍"><a href="#方案介绍" class="headerlink" title="方案介绍"></a>方案介绍</h4><p>使用 ConfigMap + 共享进程命名空间实现 Nginx 配置信息自动热加载，其工作机制和特点如下：</p><ul><li><p>方案原理：</p><ul><li>主容器（Nginx）<ul><li>负责运行 Nginx；</li><li>将 ConfigMap 挂载到 <code>/etc/nginx/conf.d</code> 目录；</li><li>不负责监测配置变更，也不额外运行 Reload Agent 服务。</li></ul></li><li>Sidecar 容器（Reloader）<ul><li>与主容器（Nginx）共享同一个进程命名空间（通过 <code>shareProcessNamespace: true</code> 实现）；</li><li>将 ConfigMap 挂载到 <code>/etc/nginx/conf.d</code> 目录，并监测该目录的文件变更；</li><li>一旦监测到文件发生变更，立刻向 Nginx 的 Master 进程发送 <code>kill -HUP &lt;master_pid&gt;</code> 信号，触发配置热加载。</li></ul></li></ul></li><li><p>方案特点：</p><ul><li>支持通过 ConfigMap 实现 Nginx 配置信息自动热加载，无需重启 Pod；</li><li>依赖 <code>shareProcessNamespace</code> 特性，使 Sidecar 容器能直接访问主容器（Nginx）的进程；</li><li>实现简单，无需在主容器中暴露 HTTP 接口或额外的 Reload Agent；</li><li>进程空间共享带来一定的安全隐患（Sidecar 容器可直接操作主容器的进程）；</li><li>不易与外部系统（如 CI/CD、Webhook）直接集成，触发方式较固定；</li><li>适合轻量级场景或内部环境下使用，不建议在高安全要求的生产环境中采用。</li></ul></li></ul><h4 id="实现步骤"><a href="#实现步骤" class="headerlink" title="实现步骤"></a>实现步骤</h4><ul><li>创建 YAML 配置文件（比如 <code>nginx-reload.yaml</code>），由于 Pod 支持多个容器共享同一个进程命名空间（依赖 <code>shareProcessNamespace</code> 特性），因此在这种模式下，Sidecar 容器就能看到并操作主容器（Nginx）的进程 </li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义 ConfigMap</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-config</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">default.conf:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    server {</span></span><br><span class="line"><span class="string">      listen 80;</span></span><br><span class="line"><span class="string">      location / {</span></span><br><span class="line"><span class="string">        return 200 "Hello from ConfigMap.\n";</span></span><br><span class="line"><span class="string">      }</span></span><br><span class="line"><span class="string">    }</span></span><br><span class="line"><span class="string"></span><span class="meta">---</span></span><br><span class="line"><span class="comment"># 定义 Service</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hotreload</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>         <span class="comment"># Service 类型为 NodePort，可通过节点 IP 访问</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-hotreload</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>           <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span>     <span class="comment"># Pod 内容器实际监听的端口</span></span><br><span class="line">      <span class="attr">nodePort:</span> <span class="number">30080</span>    <span class="comment"># 映射到物理机的端口号，默认范围 30000 - 32767</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># 定义 Deployment </span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-hotreload</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-hotreload</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">nginx-hotreload</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="comment"># 共享进程命名空间</span></span><br><span class="line">      <span class="attr">shareProcessNamespace:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="comment"># 主容器：用于运行 Nginx</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-config-cm</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/etc/nginx/conf.d</span></span><br><span class="line">      <span class="comment"># Sidecar 容器：用于监控文件变更，并 Reload Nginx</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">reloader</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">alpine:3.19</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"/bin/sh"</span>, <span class="string">"-c"</span>]</span><br><span class="line">        <span class="attr">args:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">|</span></span><br><span class="line"><span class="string">            echo "$(date '+%F %T') [INFO] Starting config reloader ..."</span></span><br><span class="line"><span class="string">            # 计算初始配置的 md5sum</span></span><br><span class="line"><span class="string">            last_sum=$(find /etc/nginx/conf.d -type f -exec md5sum {} + | sort | md5sum)</span></span><br><span class="line"><span class="string">            while true; do</span></span><br><span class="line"><span class="string">              new_sum=$(find /etc/nginx/conf.d -type f -exec md5sum {} + | sort | md5sum)</span></span><br><span class="line"><span class="string">              # 判断配置是否已更新</span></span><br><span class="line"><span class="string">              if [ "$new_sum" != "$last_sum" ]; then</span></span><br><span class="line"><span class="string">                echo "$(date '+%F %T') [INFO] Config change detected, reloading nginx ..."</span></span><br><span class="line"><span class="string">                # 获取 Nginx Master 进程的 PID</span></span><br><span class="line"><span class="string">                nginx_pid=$(ps | grep "nginx: master process" | grep -v grep | awk '{print $1}')</span></span><br><span class="line"><span class="string">                if [ -n "$nginx_pid" ]; then</span></span><br><span class="line"><span class="string">                  echo "$(date '+%F %T') [INFO] Reload nginx (master pid: $nginx_pid)"</span></span><br><span class="line"><span class="string">                  kill -HUP $nginx_pid || echo "$(date '+%F %T') [WARN] Failed to send HUP"</span></span><br><span class="line"><span class="string">                else</span></span><br><span class="line"><span class="string">                  echo "$(date '+%F %T') [WARN] Nginx master pid not found"</span></span><br><span class="line"><span class="string">                fi</span></span><br><span class="line"><span class="string">                last_sum="$new_sum"</span></span><br><span class="line"><span class="string">              fi</span></span><br><span class="line"><span class="string">              sleep 5</span></span><br><span class="line"><span class="string">            done</span></span><br><span class="line"><span class="string"></span>        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-config-cm</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/etc/nginx/conf.d</span></span><br><span class="line">      <span class="comment"># 定义卷（Volume）</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-config-cm</span>     <span class="comment"># 指定卷的名称</span></span><br><span class="line">        <span class="attr">configMap:</span>                <span class="comment"># 指定卷的类型为 ConfigMap</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">nginx-config</span>      <span class="comment"># 指定引用的 ConfigMap 名称（需事先创建）</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的资源对象 </li></ul><figure class="highlight yml"><table><tbody><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"># 创建 ConfigMap 和 Deployment</span></span><br><span class="line"><span class="string">kubectl</span> <span class="string">apply</span> <span class="string">-f</span> <span class="string">nginx-reload.yaml</span></span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">关键点</p><ul><li><code>shareProcessNamespace: true</code>：表示整个 Pod 里面的所有容器共享同一个进程命名空间。</li><li>因此，在 Sidecar 容器中，可以直接看到并控制主容器（Nginx）的进程，比如 PID 为 11 的 Nginx Master 进程。</li><li>命令 <code>kill -HUP &lt;master_pid&gt;</code> 与 <code>nginx -s reload</code> 的效果在 Nginx 中是等价的，两者都会触发 Nginx 热加载（Reload）配置文件。</li><li>对于 <code>kill -HUP &lt;master_pid&gt;</code> 命令，这里必须使用 Nginx Master 进程的 ID，而不是 Worker 进程的 ID，否则无法实现 Nginx 平滑更新配置文件（不中断请求）。</li></ul></div><h4 id="验证步骤"><a href="#验证步骤" class="headerlink" title="验证步骤"></a>验证步骤</h4><ul><li>验证 Nginx 配置信息自动热加载的步骤 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看 Service 列表</span></span><br><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes        ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        66d</span><br><span class="line">nginx-hotreload   NodePort    10.0.0.96    &lt;none&gt;        80:30080/TCP   25s</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看 Pod 的运行状态</span></span><br><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-hotreload-6646755fcf-bq26b   2/2     Running   0          38s   10.244.1.32   k8s-node1   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看 Sidecar 容器的日志</span></span><br><span class="line">kubectl logs nginx-hotreload-6646755fcf-bq26b<span class="params"> -c</span> reloader</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2025-10-05 10:03:24 [INFO] Starting config reloader ...</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 进入 Sidecar 容器内部</span></span><br><span class="line">kubectl <span class="built_in">exec</span><span class="params"> -it</span> nginx-hotreload-6646755fcf-bq26b<span class="params"> -c</span> reloader sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 Nginx 的进程列表</span></span><br><span class="line"><span class="keyword">for</span> pid <span class="keyword">in</span> /proc/[0-9]*; <span class="keyword">do</span> name=$(cat <span class="variable">$pid</span>/comm 2&gt;/dev/null) || <span class="built_in">continue</span>; [ <span class="string">"<span class="variable">$name</span>"</span> = <span class="string">"nginx"</span> ] || <span class="built_in">continue</span>; <span class="built_in">type</span>=$(tr <span class="string">'\0'</span> <span class="string">' '</span> &lt; <span class="variable">$pid</span>/cmdline 2&gt;/dev/null | grep<span class="params"> -q</span> <span class="string">"master process"</span> &amp;&amp; <span class="built_in">echo</span> master || <span class="built_in">echo</span> worker); <span class="built_in">echo</span> <span class="string">"<span class="variable">$name</span> <span class="variable">$type</span> <span class="variable">$pid</span>"</span>; <span class="keyword">done</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">nginx master /proc/11</span><br><span class="line">nginx worker /proc/16</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 在集群外部通过节点 IP 访问 Nginx</span></span><br><span class="line">wget<span class="params"> -qO</span>- http://192.168.2.112:30080</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello from ConfigMap.</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 更改 ConfigMap</span></span><br><span class="line">kubectl create configmap nginx-config<span class="params"> --from</span>-literal=default.conf=<span class="string">"server { listen 80; return 200 'Updated ConfigMap.\n'; }"</span><span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml | kubectl apply<span class="params"> -f</span> -</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 等待约 60 秒后，再次查看 Sidecar 容器的日志</span></span><br><span class="line">kubectl logs nginx-hotreload-6646755fcf-bq26b<span class="params"> -c</span> reloader</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">2025-10-05 10:16:32 [INFO] Starting config reloader ...</span><br><span class="line">2025-10-05 10:19:02 [INFO] Config change detected, reloading nginx ...</span><br><span class="line">2025-10-05 10:19:02 [INFO] Reload nginx (master pid: 11)</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 在集群外部再次通过节点 IP 访问 Nginx</span></span><br><span class="line">wget<span class="params"> -qO</span>- http://192.168.2.112:30080</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Updated ConfigMap.</span><br></pre></td></tr></tbody></table></figure><h3 id="实现方案二"><a href="#实现方案二" class="headerlink" title="实现方案二"></a>实现方案二</h3><h4 id="方案介绍-1"><a href="#方案介绍-1" class="headerlink" title="方案介绍"></a>方案介绍</h4><p>使用 ConfigMap + 部署 Reload Agent 实现 Nginx 配置信息自动热加载，其工作机制和特点如下：</p><ul><li><p>方案概述：</p><ul><li>有时为了更安全，会在 Nginx 容器中部署一个小的 Reload Agent，例如：<ul><li>在 Nginx 容器里部署一个轻量级 API 服务（比如，基于 Python 开发一个简易的 Web 服务）；</li><li>当 Sidecar 容器监测到 ConfigMap 挂载的文件发生变更时，通过 <code>curl http://localhost:8080/reload</code> 调用 Reload Agent 的 API 服务；</li><li>Reload Agent 接收到 Reload 请求后，内部会执行 <code>nginx -s reload</code> 或者 <code>kill -HUP &lt;master_pid&gt;</code> 触发 Nginx 热加载。</li></ul></li></ul></li><li><p>方案原理：</p><ul><li>主容器（Nginx）<ul><li>负责运行 Nginx；</li><li>将 ConfigMap 挂载到 <code>/etc/nginx/conf.d</code> 目录；</li><li>额外部署一个轻量级 API 服务（Reload Agent），监听 <code>8080</code> 端口；</li><li>当接收到 <code>/reload</code> 请求时，执行 <code>nginx -s reload</code> 或者 <code>kill -HUP &lt;master_pid&gt;</code> 让 Nginx 重新加载配置文件。</li></ul></li><li>Sidecar 容器（Reloader）<ul><li>将 ConfigMap 挂载到 <code>/etc/nginx/conf.d</code> 目录；</li><li>监测 <code>/etc/nginx/conf.d</code> 目录中的文件更改；</li><li>当监测到有配置更改后，通过 HTTP 协议调用 Reload Agent 的 API 接口 <code>http://127.0.0.1:8080/reload</code>，触发 Nginx 热加载。</li></ul></li></ul></li><li><p>方案特点：</p><ul><li>支持通过 ConfigMap 实现 Nginx 配置信息自动热加载，无需重启 Pod；</li><li>不依赖 <code>shareProcessNamespace</code>，容器进程空间隔离（安全性更高）；</li><li>结构清晰，便于与 CI/CD 或外部触发器集成；</li><li>可扩展为 Webhook 式控制（比如在 GitOps 更新配置后自动触发 Reload）；</li><li>适合在高安全要求的生产环境下使用。</li></ul></li></ul><div class="admonition warning"><p class="admonition-title">特别注意</p><p>为了方便演示，在下面的 Reloader Agent 实现中，使用 Linux Socket 通信来替代 HTTP 接口。由于需要在容器之间通过 Volume（卷）共享 Socket 文件，因此生产环境推荐使用 HTTP 接口来实现 Reloader Agent，而不是 Linux Socket 通信。</p></div><h4 id="实现步骤-1"><a href="#实现步骤-1" class="headerlink" title="实现步骤"></a>实现步骤</h4><ul><li>创建 YAML 配置文件（比如 <code>nginx-hotreload.yaml</code>）</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义 ConfigMap</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-config</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">default.conf:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    server {</span></span><br><span class="line"><span class="string">      listen 80;</span></span><br><span class="line"><span class="string">      location / {</span></span><br><span class="line"><span class="string">        return 200 "Hello from ConfigMap.\n";</span></span><br><span class="line"><span class="string">      }</span></span><br><span class="line"><span class="string">    }</span></span><br><span class="line"><span class="string"></span><span class="meta">---</span></span><br><span class="line"><span class="comment"># 定义 Service</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hotreload</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>         <span class="comment"># Service 类型为 NodePort，可通过节点 IP 访问</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-hotreload</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>           <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span>     <span class="comment"># Pod 内容器实际监听的端口</span></span><br><span class="line">      <span class="attr">nodePort:</span> <span class="number">30080</span>    <span class="comment"># 映射到物理机的端口号，默认范围 30000 - 32767</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># 定义 Deployment</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-hotreload</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-hotreload</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">nginx-hotreload</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="comment"># 主容器：用于运行 Nginx + Reload Agent</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"/bin/sh"</span>, <span class="string">"-c"</span>]</span><br><span class="line">        <span class="attr">args:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">|</span></span><br><span class="line"><span class="string">            # 创建共享运行目录</span></span><br><span class="line"><span class="string">            mkdir -p /var/run/nginx</span></span><br><span class="line"><span class="string">            # 创建 Socket 文件</span></span><br><span class="line"><span class="string">            SOCKET=/var/run/nginx/nginx-reload.sock</span></span><br><span class="line"><span class="string">            rm -f $SOCKET</span></span><br><span class="line"><span class="string">            mkfifo $SOCKET</span></span><br><span class="line"><span class="string">            # 启动 Nginx</span></span><br><span class="line"><span class="string">            echo "$(date '+%F %T') [INFO] Starting Nginx ..."</span></span><br><span class="line"><span class="string">            nginx -g 'daemon off;' &amp;</span></span><br><span class="line"><span class="string">            echo "$(date '+%F %T') [INFO] Reload agent started, listening on $SOCKET"</span></span><br><span class="line"><span class="string">            # 监听 Sidecar 发来的 Reload 请求</span></span><br><span class="line"><span class="string">            while true; do</span></span><br><span class="line"><span class="string">              if read line &lt; $SOCKET; then</span></span><br><span class="line"><span class="string">                echo "$(date '+%F %T') [INFO] Reload request received from Sidecar"</span></span><br><span class="line"><span class="string">                # 先校验配置文件</span></span><br><span class="line"><span class="string">                if nginx -t -q; then</span></span><br><span class="line"><span class="string">                  echo "$(date '+%F %T') [INFO] Nginx config test passed"</span></span><br><span class="line"><span class="string">                  # 检查 PID 文件</span></span><br><span class="line"><span class="string">                  if [ -f /var/run/nginx.pid ]; then</span></span><br><span class="line"><span class="string">                    kill -HUP $(cat /var/run/nginx.pid)</span></span><br><span class="line"><span class="string">                    echo "$(date '+%F %T') [INFO] Nginx reloaded successfully"</span></span><br><span class="line"><span class="string">                  else</span></span><br><span class="line"><span class="string">                    echo "$(date '+%F %T') [WARN] file nginx.pid not found"</span></span><br><span class="line"><span class="string">                  fi</span></span><br><span class="line"><span class="string">                else</span></span><br><span class="line"><span class="string">                  echo "$(date '+%F %T') [ERROR] Invalid nginx config, skipping reload"</span></span><br><span class="line"><span class="string">                fi</span></span><br><span class="line"><span class="string">              fi</span></span><br><span class="line"><span class="string">            done</span></span><br><span class="line"><span class="string"></span>        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-config-cm</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/etc/nginx/conf.d</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-run</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/var/run/nginx</span></span><br><span class="line">      <span class="comment"># Sidecar 容器：用于监控文件变更，并发送 Reload 请求</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">reloader</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">alpine:3.19</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"/bin/sh"</span>, <span class="string">"-c"</span>]</span><br><span class="line">        <span class="attr">args:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">|</span></span><br><span class="line"><span class="string">            echo "$(date '+%F %T') [INFO] Starting config reloader ..."</span></span><br><span class="line"><span class="string">            last_sum=""</span></span><br><span class="line"><span class="string">            while true; do</span></span><br><span class="line"><span class="string">              new_sum=$(find /etc/nginx/conf.d -type f -exec md5sum {} + | sort | md5sum)</span></span><br><span class="line"><span class="string">              if [ -z "$last_sum" ]; then</span></span><br><span class="line"><span class="string">                last_sum="$new_sum"</span></span><br><span class="line"><span class="string">              elif [ "$new_sum" != "$last_sum" ]; then</span></span><br><span class="line"><span class="string">                echo "$(date '+%F %T') [INFO] Config change detected, reloading nginx ..."</span></span><br><span class="line"><span class="string">                if [ -p /var/run/nginx/nginx-reload.sock ]; then</span></span><br><span class="line"><span class="string">                  echo "reload" &gt; /var/run/nginx/nginx-reload.sock</span></span><br><span class="line"><span class="string">                else</span></span><br><span class="line"><span class="string">                  echo "$(date '+%F %T') [WARN] file nginx-reload.sock not found"</span></span><br><span class="line"><span class="string">                fi</span></span><br><span class="line"><span class="string">                last_sum="$new_sum"</span></span><br><span class="line"><span class="string">              fi</span></span><br><span class="line"><span class="string">              sleep 5</span></span><br><span class="line"><span class="string">            done</span></span><br><span class="line"><span class="string"></span>        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-config-cm</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/etc/nginx/conf.d</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-run</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/var/run/nginx</span></span><br><span class="line">      <span class="comment"># 定义卷（Volume）</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-config-cm</span>     <span class="comment"># 指定卷的名称</span></span><br><span class="line">        <span class="attr">configMap:</span>                <span class="comment"># 指定卷的类型为 ConfigMap</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">nginx-config</span>      <span class="comment"># 指定引用的 ConfigMap 名称（需事先创建）</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-run</span>           <span class="comment"># 指定卷的名称</span></span><br><span class="line">        <span class="attr">emptyDir:</span> {}              <span class="comment"># 共享运行目录（用于存放 nginx-reload.sock 文件）</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的资源对象 </li></ul><figure class="highlight shell"><table><tbody><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"># 创建 ConfigMap 和 Deployment</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> nginx-hotreload.yaml</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">关键点</p><ul><li>Nginx 默认使用的是 <code>/var/run/nginx.pid</code> 文件，该文件的内容就是 Nginx Master 进程的 ID。</li><li>命令 <code>kill -HUP &lt;master_pid&gt;</code> 与 <code>nginx -s reload</code> 的效果在 Nginx 中是等价的，两者都会触发 Nginx 热加载（Reload）配置文件。</li><li>对于 <code>kill -HUP &lt;master_pid&gt;</code> 命令，这里必须使用 Nginx Master 进程的 ID，而不是 Worker 进程的 ID，否则无法实现 Nginx 平滑更新配置文件（不中断请求）。</li></ul></div><h4 id="验证步骤-1"><a href="#验证步骤-1" class="headerlink" title="验证步骤"></a>验证步骤</h4><ul><li>验证 Nginx 配置信息自动热加载的步骤 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看 Service 列表</span></span><br><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes        ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        66d</span><br><span class="line">nginx-hotreload   NodePort    10.0.0.126   &lt;none&gt;        80:30080/TCP   59s</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看 Pod 的运行状态</span></span><br><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                              READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-hotreload-9d56b494b-vbkhv   2/2     Running   0          74s   10.244.0.39   k8s-node2    &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看 Nginx 容器的日志</span></span><br><span class="line">kubectl logs nginx-hotreload-9d56b494b-vbkhv<span class="params"> -c</span> nginx</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">2025-10-05 10:26:53 [INFO] Starting Nginx ...</span><br><span class="line">2025-10-05 10:26:53 [INFO] Reload agent started, listening on /var/run/nginx/nginx-reload.sock</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看 Sidecar 容器的日志</span></span><br><span class="line">kubectl logs nginx-hotreload-9d56b494b-vbkhv<span class="params"> -c</span> reloader</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2025-10-05 09:42:50 [INFO] Starting config reloader ...</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 在集群外部通过节点 IP 访问 Nginx</span></span><br><span class="line">wget<span class="params"> -qO</span>- http://192.168.2.131:30080</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello from ConfigMap.</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 更改 ConfigMap</span></span><br><span class="line">kubectl create configmap nginx-config<span class="params"> --from</span>-literal=default.conf=<span class="string">"server { listen 80; return 200 'Updated ConfigMap.\n'; }"</span><span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml | kubectl apply<span class="params"> -f</span> -</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 等待约 60 秒后，再次查看 Sidecar 容器的日志</span></span><br><span class="line">kubectl logs nginx-hotreload-9d56b494b-vbkhv<span class="params"> -c</span> reloader</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">2025-10-05 10:26:53 [INFO] Starting config reloader ...</span><br><span class="line">2025-10-05 10:32:09 [INFO] Config change detected, reloading nginx ...</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 等待约 60 秒后，再次查看 Nginx 容器的日志</span></span><br><span class="line">kubectl logs nginx-hotreload-9d56b494b-vbkhv<span class="params"> -c</span> nginx</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">2025-10-05 10:26:53 [INFO] Reload agent started, listening on /var/run/nginx/nginx-reload.sock</span><br><span class="line">2025-10-05 10:32:09 [INFO] Reload request received from Sidecar</span><br><span class="line">2025-10-05 10:32:09 [INFO] Nginx config test passed</span><br><span class="line">2025-10-05 10:32:09 [INFO] Nginx reloaded successfully</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 在集群外部再次通过节点 IP 访问 Nginx</span></span><br><span class="line">wget<span class="params"> -qO</span>- http://192.168.2.131:30080</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Updated ConfigMap.</span><br></pre></td></tr></tbody></table></figure><h3 id="方案补充说明"><a href="#方案补充说明" class="headerlink" title="方案补充说明"></a>方案补充说明</h3><ul><li>如果更新 ConfigMap 后不要求 Nginx 进行热加载，且可以接受 Pod 重启或者 Nginx 中断现有请求，那么可以手动触发 Deployment 的滚动重启，例如执行命令 <code>kubectl rollout restart deployment &lt;name&gt;</code>。Pod 重启后，Nginx 会自动加载最新的配置。</li><li>更推荐使用自动检测 ConfigMap 变更的方案（更高级），例如借助 Stakater Reloader 等第三方工具监控 ConfigMap 的变化。一旦检测到更新，就会自动触发相关 Pod 的滚动更新，从而确保配置自动生效。</li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/posts/20eeb044.html#kill-HUP-%E5%91%BD%E4%BB%A4%E7%9A%84%E4%BD%9C%E7%94%A8">Nginx 中 kill -HUP 命令的作用</a></li><li><a href="https://blog.csdn.net/cghcgyhbc/article/details/139752940">Nginx 平滑升级版本和回滚版本</a></li><li><a href="/posts/cfb1715d.html#ConfigMap">Kubernetes 中 ConfigMap 的使用</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 中如何实现 Nginx 配置信息自动热加载。</summary>
    
    
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
    <category term="Web服务器" scheme="https://www.techgrow.cn/tags/Web%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之六</title>
    <link href="https://www.techgrow.cn/posts/76121b26.html"/>
    <id>https://www.techgrow.cn/posts/76121b26.html</id>
    <published>2025-09-17T13:12:19.000Z</published>
    <updated>2025-09-17T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><span id="more"></span><h3 id="Probe"><a href="#Probe" class="headerlink" title="Probe"></a>Probe</h3><p>为了监控容器的运行状态，Kubernetes 提供了探针（Probe）。</p><h4 id="Probe-的类型"><a href="#Probe-的类型" class="headerlink" title="Probe 的类型"></a>Probe 的类型</h4><p>Kubernetes 提供了三种类型的探针：Liveness Probe（存活探针）、Readiness Probe（就绪探针）、Startup Probe（启动探针）。</p><h5 id="Liveness-Probe"><a href="#Liveness-Probe" class="headerlink" title="Liveness Probe"></a>Liveness Probe</h5><p><strong>Liveness Probe（存活探针）用于判断容器是否处于健康状态，即 Pod 是否真正处于 <code>Running</code> 状态。</strong>如果 Liveness Probe 探测到容器不健康，kubelet 会将该容器 Kill 掉，并根据 Pod 的重启策略决定是否重启它。<strong>Liveness Probe 可用于修复死锁、无响应的应用（如数据库卡死）。</strong>如果容器未配置 Liveness Probe，kubelet 会默认认为其探测结果始终为成功。在实际场景中，应用程序可能由于某些原因（例如后端服务故障）暂时无法对外提供服务，但进程本身仍在运行。这种情况下，Kubernetes 无法识别并隔离这个有故障的 Pod，调用方仍可能访问到该 Pod，导致业务不稳定。为解决这一问题，Kubernetes 提供了 Liveness Probe 来检测应用程序的运行健康状况，并在检测到异常时执行相应的补救措施，例如重启容器，以保证系统的整体稳定性。Liveness Probe 的配置示例如下：</p><figure class="highlight yml"><table><tbody><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">livenessProbe:</span></span><br><span class="line">  <span class="attr">exec:</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">"cat"</span>, <span class="string">"/tmp/healthy"</span>]  <span class="comment"># 执行命令检查文件是否存在</span></span><br><span class="line">  <span class="attr">initialDelaySeconds:</span> <span class="number">5</span>              <span class="comment"># 容器启动后等待5秒开始检查</span></span><br><span class="line">  <span class="attr">periodSeconds:</span> <span class="number">5</span>                    <span class="comment"># 每5秒检查一次</span></span><br><span class="line">  <span class="attr">failureThreshold:</span> <span class="number">3</span>                 <span class="comment"># 连续失败3次后判定为不健康</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Readiness-Probe"><a href="#Readiness-Probe" class="headerlink" title="Readiness Probe"></a>Readiness Probe</h5><p><strong>Readiness Probe（就绪探针）用于判断容器是否已经启动完成并能够对外提供服务，即容器的 <code>Ready</code> 状态是否为 <code>true</code>。</strong>如果 Readiness Probe 探测失败，容器的 <code>Ready</code> 状态会被置为 <code>False</code>，Kubernetes 控制器会将该 Pod 的 Endpoint 从对应 Service 的 Endpoint 列表中移除，从而停止将任何请求调度到该 Pod，直到下一次探测成功为止。<strong>Readiness Probe 可用于控制流量进入（如应用启动时需要加载大量数据，导致容器启动后无法立刻对外提供服务）。</strong>通过 Readiness Probe，Kubernetes 可以在应用完全就绪之前，阻止流量被路由到尚未准备好的 Pod 副本，确保服务稳定性。例如，对于基于 Tomcat 的应用来说，Tomcat 进程启动成功并不代表应用可以立即对外提供服务，可能还需要等待 Spring 容器初始化、数据库连接建立等操作完成。在 Spring Boot 应用中，可以使用 Actuator 提供的 <code>/health</code> 接口作为 Readiness Probe 的检测目标，用于判断应用是否已经准备好对外提供服务。Readiness Probe 的配置示例如下：</p><figure class="highlight yml"><table><tbody><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">readinessProbe:</span></span><br><span class="line">  <span class="attr">httpGet:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/healthz</span>                    <span class="comment"># 发送 HTTP 请求进行检测</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">  <span class="attr">initialDelaySeconds:</span> <span class="number">10</span>             <span class="comment"># 容器启动后等待10秒开始检查</span></span><br><span class="line">  <span class="attr">periodSeconds:</span> <span class="number">3</span>                    <span class="comment"># 每3秒检查一次</span></span><br><span class="line">  <span class="attr">successThreshold:</span> <span class="number">1</span>                 <span class="comment"># 成功1次即标记为就绪</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Startup-Probe"><a href="#Startup-Probe" class="headerlink" title="Startup Probe"></a>Startup Probe</h5><p><strong>Startup Probe（启动探针）是在 Kubernetes <code>1.16+</code> 版本中引入的，主要用于检测慢启动应用是否完成初始化。在 Startup Probe 检测成功之前，Liveness Probe 和 Readiness Probe 都不会生效</strong>；从而避免因应用启动过慢，被存活探针或就绪探针误判为异常并提前终止容器。<strong>Startup Probe 非常适合启动时间较长的应用，例如 Java / SpringBoot 应用、需要加载大量数据或复杂初始化逻辑的服务。</strong>通过配置合适的 Startup Probe，可以为应用提供足够的启动缓冲时间，确保在应用真正完成初始化之前，Kubernetes 不会对其进行健康检查或流量调度，从而保证系统的稳定性。Startup Probe 的配置示例如下：</p><figure class="highlight yml"><table><tbody><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">startupProbe:</span></span><br><span class="line">  <span class="attr">httpGet:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/actuator/health</span>             <span class="comment"># Spring Boot 健康检查端点</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">  <span class="attr">failureThreshold:</span> <span class="number">30</span>                 <span class="comment"># 允许的最大失败次数</span></span><br><span class="line">  <span class="attr">periodSeconds:</span> <span class="number">10</span>                    <span class="comment"># 每10秒检查一次</span></span><br></pre></td></tr></tbody></table></figure><h4 id="Probe-的核心参数"><a href="#Probe-的核心参数" class="headerlink" title="Probe 的核心参数"></a>Probe 的核心参数</h4><ul><li>探针（Probe）可配置的核心参数（用于精确控制探针的行为）</li></ul><table><thead><tr><th>参数</th><th>作用</th><th>默认值</th><th>最小值</th><th>适用探针类型</th></tr></thead><tbody><tr><td><code>initialDelaySeconds</code></td><td>容器启动后，等待多少秒才开始第一次执行探测，避免容器未完成启动就被误判为失败。</td><td>0 秒</td><td> 0 秒</td><td> liveness、readiness、startup</td></tr><tr><td><code>periodSeconds</code></td><td>探测的执行频率，即两次探测之间的间隔时间。</td><td>10 秒</td><td> 1 秒</td><td> liveness、readiness</td></tr><tr><td><code>timeoutSeconds</code></td><td>单次探测的超时时间，超过该时间未响应则判定为探测失败。</td><td>1 秒</td><td> 1 秒</td><td> liveness、readiness</td></tr><tr><td><code>failureThreshold</code></td><td>探测成功后，连续失败多少次才会被认定为容器不健康。</td><td>3</td><td>1</td><td>liveness、readiness、startup</td></tr><tr><td><code>successThreshold</code></td><td>对于已标记为不健康的容器，需要连续成功多少次才会重新标记为健康。对于 Liveness Probe，该值必须为 <code>1</code>。</td><td>1</td><td>1</td><td>liveness、readiness</td></tr></tbody></table><h4 id="Probe-的检测方法"><a href="#Probe-的检测方法" class="headerlink" title="Probe 的检测方法"></a>Probe 的检测方法</h4><ul><li>Kubernetes 的三类探针都支持以下三种检测方法</li></ul><table><thead><tr><th>检测方法</th><th>说明</th><th>配置示例</th></tr></thead><tbody><tr><td><code>exec</code></td><td>- 在容器内执行命令，返回状态码为 <code>0</code> 表示检测成功。<br> - 适用于复杂检测逻辑或没有 HTTP 接口的服务。</td><td><code>command: ["cat", "/tmp/healthy"]</code></td></tr><tr><td><code>httpGet</code></td><td>- 通过发送 HTTP 请求检查服务是否正常。<br> - 返回状态码 <code>200 ~ 399</code> 表示检测成功。</td><td><code>httpGet: { path: /health, port: 80 }</code></td></tr><tr><td><code>tcpSocket</code></td><td>- 通过容器 IP + 端口建立 TCP 连接，<br> - 连接成功，即表示容器健康。</td><td><code>tcpSocket: { port: 3306 }</code></td></tr></tbody></table><h4 id="Probe-的检测结果"><a href="#Probe-的检测结果" class="headerlink" title="Probe 的检测结果"></a>Probe 的检测结果</h4><ul><li>探针（Probe）的三种检测结果状态</li></ul><table><thead><tr><th>检测结果状态</th><th>说明</th></tr></thead><tbody><tr><td><code>Success</code></td><td>容器通过检查，状态正常。</td></tr><tr><td><code>Failure</code></td><td>容器未通过检查，状态异常。</td></tr><tr><td><code>Unknown</code></td><td>无法执行检查，因此不采取任何措施。</td></tr></tbody></table><blockquote><p>Pod 的重启策略有以下三种（如果 Liveness Probe 探测到容器不健康，kubelet 会将该容器 Kill 掉，并根据 Pod 的重启策略决定是否重启它）</p></blockquote><table><thead><tr><th>重启策略</th><th>说明</th></tr></thead><tbody><tr><td><code>Always</code></td><td>默认值，当容器退出时，总是由 kubelet 自动重启该容器（适用于长期运行的 Pod，如 Web 服务）。</td></tr><tr><td><code>OnFailure</code></td><td>仅在容器异常退出（非 0 状态码）时，由 kubelet 自动重启该容器（适合批处理任务）。</td></tr><tr><td><code>Never</code></td><td>无论容器如何退出，kubelet 都不会重启该容器（适合一次性任务）。</td></tr></tbody></table><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mynginx</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:1.14</span></span><br><span class="line">    <span class="attr">imagePullPolicy:</span> <span class="string">Always</span></span><br><span class="line">  <span class="attr">restartPolicy:</span> <span class="string">OnFailure</span>  <span class="comment"># Pod 的重启策略</span></span><br></pre></td></tr></tbody></table></figure><h4 id="Probe-的使用示例"><a href="#Probe-的使用示例" class="headerlink" title="Probe 的使用示例"></a>Probe 的使用示例</h4><ul><li>基于 Liveness Probe（存活探针）检测容器健康 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</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">test:</span> <span class="string">liveness</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">liveness-exec</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="bullet">-</span> <span class="attr">name:</span> <span class="string">liveness</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">    <span class="attr">args:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/bin/sh</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">-c</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">touch</span> <span class="string">/tmp/healthy;</span> <span class="string">sleep</span> <span class="number">30</span><span class="string">;</span> <span class="string">rm</span> <span class="string">-rf</span> <span class="string">/tmp/healthy</span></span><br><span class="line">    <span class="comment"># 存活检查</span></span><br><span class="line">    <span class="attr">livenessProbe:</span></span><br><span class="line">      <span class="attr">exec:</span></span><br><span class="line">        <span class="attr">command:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">cat</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">/tmp/healthy</span></span><br><span class="line">      <span class="attr">initialDelaySeconds:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">periodSeconds:</span> <span class="number">5</span></span><br></pre></td></tr></tbody></table></figure><ul><li>存活探针、就绪探针二者配合使用 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">goproxy</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">goproxy</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="bullet">-</span> <span class="attr">name:</span> <span class="string">goproxy</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">k8s.gcr.io/goproxy:0.1</span></span><br><span class="line">      <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span>      <span class="comment"># 容器内应用（比如 Goproxy）监听的端口</span></span><br><span class="line">      <span class="comment"># 存活检查</span></span><br><span class="line">      <span class="attr">livenessProbe:</span></span><br><span class="line">        <span class="attr">tcpSocket:</span></span><br><span class="line">          <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">        <span class="attr">initialDelaySeconds:</span> <span class="number">15</span></span><br><span class="line">        <span class="attr">periodSeconds:</span> <span class="number">20</span></span><br><span class="line">      <span class="comment"># 就绪检查</span></span><br><span class="line">      <span class="attr">readinessProbe:</span></span><br><span class="line">        <span class="attr">tcpSocket:</span></span><br><span class="line">          <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">        <span class="attr">initialDelaySeconds:</span> <span class="number">5</span></span><br><span class="line">        <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br></pre></td></tr></tbody></table></figure><ul><li>启动探针、存活探针和就绪探针三者配合使用 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">springboot-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">app</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">my-springboot-app:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span>      <span class="comment"># 容器内应用（比如 Tomcat）监听的端口</span></span><br><span class="line">    <span class="comment"># 启动探针（给予充足启动时间）</span></span><br><span class="line">    <span class="attr">startupProbe:</span></span><br><span class="line">      <span class="attr">httpGet:</span></span><br><span class="line">        <span class="attr">path:</span> <span class="string">/actuator/health</span></span><br><span class="line">        <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">      <span class="attr">failureThreshold:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br><span class="line">    <span class="comment"># 存活检查（启动探针成功后生效）</span></span><br><span class="line">    <span class="attr">livenessProbe:</span></span><br><span class="line">      <span class="attr">httpGet:</span></span><br><span class="line">        <span class="attr">path:</span> <span class="string">/actuator/health</span></span><br><span class="line">        <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">      <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">timeoutSeconds:</span> <span class="number">3</span></span><br><span class="line">    <span class="comment"># 就绪检查（启动探针成功后生效）</span></span><br><span class="line">    <span class="attr">readinessProbe:</span></span><br><span class="line">      <span class="attr">httpGet:</span></span><br><span class="line">        <span class="attr">path:</span> <span class="string">/actuator/health/readiness</span></span><br><span class="line">        <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">      <span class="attr">initialDelaySeconds:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">periodSeconds:</span> <span class="number">5</span></span><br></pre></td></tr></tbody></table></figure><h3 id="调度器"><a href="#调度器" class="headerlink" title="调度器"></a>调度器</h3><h4 id="调度器的概述"><a href="#调度器的概述" class="headerlink" title="调度器的概述"></a>调度器的概述</h4><p>一个容器平台的核心功能是为容器分配运行所需的计算、存储和网络资源。其中，容器调度系统负责在最合适的主机上启动容器，并将相关容器进行关联。它必须能够自动处理容器故障，并在应用访问量增加时，自动在更多主机上启动容器以应对扩展需求。目前，主流的三大容器平台 Swarm、Mesos 和 Kubernetes 各自拥有不同的调度系统：</p><ul><li>Swarm：直接调度 Docker 容器，并提供与标准 Docker API 一致的接口，使用起来较为简单。</li><li>Mesos：采用多框架并行的调度模型，不同运行框架拥有相对独立的调度系统。其中，Marathon 框架对 Docker 容器提供了原生支持。</li><li>Kubernetes：引入 Pod 和 Label 的概念，将一组有依赖关系的容器组合成一个逻辑单元 Pod，并以 Pod 为基本单位进行部署和调度。同时，多个 Pod 可以通过 Service 形成一个完整的服务。</li></ul><p>Kubernetes 通过这种抽象，简化了集群范围内相关容器的共同调度和管理复杂度。从另一个角度看，这种设计使得 Kubernetes 更容易实现功能更强大、逻辑更复杂的容器调度算法，这也是其与 Swarm 和 Mesos 的主要区别所在。</p><h4 id="K8s-的资源分类"><a href="#K8s-的资源分类" class="headerlink" title="K8s 的资源分类"></a>K8s 的资源分类</h4><p>Kubernetes 调度器作为整个集群的 “大脑”，在提升集群资源利用率和保障服务稳定运行方面起着至关重要的作用，其重要性也会随着集群规模和复杂度的提升而不断增加。在 Kubernetes 中，资源可分为两类：</p><ul><li><p>可压缩资源（Compressible Resources）</p><ul><li>这类资源可以被限制或回收，例如 CPU 周期、磁盘 I/O 带宽等。</li><li>当 Pod 资源不足时，Kubernetes 可以通过降低资源分配的方式来限制 Pod 对这些资源的使用，而无需直接杀掉 Pod。</li></ul></li><li><p>不可压缩资源（Incompressible Resources）</p><ul><li>这类资源一旦被 Pod 占用，除非终止 Pod，否则无法回收，例如内存、硬盘空间等。</li><li>当 Pod 占用不可压缩资源过多且无法满足需求时，Kubernetes 只能通过驱逐（Eviction）或杀掉 Pod 来释放资源。</li></ul></li></ul><p>未来，Kubernetes 还将支持更多类型的资源，如网络带宽、存储 IOPS 等，使得调度和资源管理更加精细化和智能化。</p><h4 id="K8s-调度器的概述"><a href="#K8s-调度器的概述" class="headerlink" title="K8s 调度器的概述"></a>K8s 调度器的概述</h4><p><code>kube-scheduler</code> 是 Kubernetes 系统的核心组件之一，主要负责整个集群的资源调度工作。它通过特定的调度算法和调度策略，将 Pod 调度到最合适的 Node（工作节点） 上，从而更高效、更合理地利用集群资源。这也是企业选择 Kubernetes 的一个重要原因：如果一项新技术不能帮助企业节约成本、提升效率，那么它将很难被真正落地和推广。</p><h4 id="K8s-调度器的工作流程"><a href="#K8s-调度器的工作流程" class="headerlink" title="K8s 调度器的工作流程"></a>K8s 调度器的工作流程</h4><blockquote><p>K8s 调度器的简介</p></blockquote><p><code>kube-scheduler</code> 是一个独立的二进制程序，启动后会持续监听 API Server，获取所有 <code>PodSpec.NodeName</code> 为空的 Pod，并为其执行调度。每个成功调度的 Pod，调度器都会生成一个 Binding 对象 并存入 Etcd，随后目标节点上的 Kubelet 会根据调度结果创建 Pod。在默认情况下，<code>kube-scheduler</code> 内置的默认调度器已经能够满足大多数场景的需求。例如，默认策略可以保证 Pod 被分配到资源充足的节点上运行。但在实际生产环境中，企业往往对业务需求和应用特性有更深入的了解，因此需要更灵活、可控的调度策略，例如：</p><ul><li>限制某些 Pod 只能运行在特定节点上；</li><li>某些节点只允许运行特定类型的应用；</li><li>针对资源隔离、安全性或性能优化进行特殊调度。</li></ul><div class="admonition note"><p class="admonition-title">Pod 的创建流程</p><p>更多关于 Pod 的创建流程的介绍，可以看 <a href="/posts/c57e8370.html#Pod-%E7%9A%84%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B">这里</a>。</p></div><blockquote><p>K8s 调度器的工作流程</p></blockquote><p>K8s 调度器的工作流程主要分为以下四个阶段（<a href="../../../asset/2025/09/k8s-scheduler-process.png">点击查看流程图</a>）：</p><ul><li><p>(1) 预选阶段（Predicates）— 节点过滤</p><ul><li>在该阶段，K8s 调度器会根据一系列规则过滤掉不符合要求的节点，形成候选节点列表。</li><li>例如，当 Pod 设置了资源 <code>requests</code>，如果某节点的可用资源不足，则该节点会被过滤掉。</li><li>常见的 Predicates 过滤算法：<table><thead><tr><th>算法名称</th><th>说明</th></tr></thead><tbody><tr><td><code>PodFitsResources</code></td><td>节点剩余资源是否满足 Pod 的资源请求（CPU / 内存等）。</td></tr><tr><td><code>PodFitsHost</code></td><td>如果 Pod 指定了 <code>NodeName</code>，检查节点名称是否匹配。</td></tr><tr><td><code>PodFitsHostPorts</code></td><td>节点上已使用的端口是否与 Pod 申请的端口冲突。</td></tr><tr><td><code>PodSelectorMatches</code></td><td>节点的标签是否与 Pod 指定的 <code>labelSelector</code> 匹配。</td></tr><tr><td><code>NoDiskConflict</code></td><td>检查 Pod 所需的 Volume 是否与节点上已挂载的 Volume 冲突（只读 Volume 除外）。</td></tr><tr><td><code>CheckNodeDiskPressure</code></td><td>节点磁盘压力是否过大，是否满足调度要求。</td></tr><tr><td><code>CheckNodeMemoryPressure</code></td><td>节点内存压力是否过大，是否满足调度要求。</td></tr></tbody></table></li></ul></li><li><p>(2) 优选阶段（Priorities）— 节点打分</p><ul><li>在该阶段，K8s 调度器会对通过预选阶段的节点进行打分，分数越高，表示该节点越适合部署该 Pod。</li><li>打分规则是由一组键值对组成的：<ul><li>键：优先级策略的名称</li><li>值：该策略的权重</li></ul></li><li>常见的 Priorities 优先级策略：<table><thead><tr><th>优先级策略</th><th>说明</th></tr></thead><tbody><tr><td><code>LeastRequestedPriority</code></td><td>根据 CPU 和内存使用率计算权重，使用率越低，权重越高，从而优先选择负载较低的节点。</td></tr><tr><td><code>SelectorSpreadPriority</code></td><td>为了实现高可用，将同一个 Deployment / Replica Set 下的多个 Pod 尽量分散到不同节点上。运行该类型 Pod 数量较少的节点权重更高。</td></tr><tr><td><code>ImageLocalityPriority</code></td><td>如果某节点已经存在 Pod 需要的镜像，且镜像总大小越大，则该节点权重越高，从而减少镜像拉取时间。</td></tr><tr><td><code>NodeAffinityPriority</code></td><td>根据 Node Affinity（节点亲和性）规则计算权重，优先调度到符合亲和性条件的节点上。</td></tr></tbody></table></li></ul></li><li><p>(3) 绑定阶段（Binding）</p><ul><li>K8s 调度器会从打分结果中选择分数最高的节点，将该 Pod 与该 Node（工作节点）进行绑定（Binding）。</li><li>绑定结果会被写入 Etcd，供集群其他组件使用。</li></ul></li><li><p>(4) Kubelet 执行</p><ul><li>最终被选定的 Node（工作节点）对应的 Kubelet 会接收到绑定信息，随后拉取容器镜像并创建 Pod。</li></ul></li></ul><blockquote><p>K8s 调度器的工作流程图</p></blockquote><p><img data-src="../../../asset/2025/09/k8s-scheduler-1.png"></p><div class="admonition note"><p class="admonition-title">总结</p><p>K8s 的调度过程分为两个阶段：首先是预选阶段（Predicates），用于过滤掉不满足条件的节点；然后是优选阶段（Priorities），对通过预选的节点按优先级进行排序。最后，从中选择优先级最高的节点进行调度。如果在任意阶段出现错误，调度器会直接返回错误。在预选阶段（Predicates），调度器会遍历所有节点，过滤掉不满足条件的节点。该阶段属于强制性规则，输出的所有符合要求的节点将作为第二阶段（优选阶段）的输入。如果所有节点都不满足条件，Pod 将一直处于 Pending 状态，直到出现满足条件的节点。期间，调度器会不断重试。因此，在部署应用时，如果发现 Pod 长时间处于 Pending 状态，说明没有符合调度条件的节点，可以检查节点资源是否可用。在优选阶段（Priorities），如果有多个节点都通过了预选条件，系统会根据节点的优先级对这些节点进行排序，最终选择优先级最高的节点来部署 Pod。K8s 调度器除了有上面介绍的 Predicates 过滤算法之外，还有一些其他的算法，更多更详细的过滤算法可以查看源码文件：<code>k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go</code>。</p></div><h4 id="K8s-调度器的核心特性"><a href="#K8s-调度器的核心特性" class="headerlink" title="K8s 调度器的核心特性"></a>K8s 调度器的核心特性</h4><div class="admonition note"><p class="admonition-title">提示</p><p>更多关于影响 Pod 调度的因素可以看<a href="/posts/c57e8370.html#%E5%BD%B1%E5%93%8D-Pod-%E8%B0%83%E5%BA%A6%E7%9A%84%E5%9B%A0%E7%B4%A0">这里</a>。</p></div><h5 id="节点亲和性调度"><a href="#节点亲和性调度" class="headerlink" title="节点亲和性调度"></a>节点亲和性调度</h5><ul><li><p>概述：</p><ul><li>K8s 节点亲和性（Node Affinity）调度规则有两种：硬亲和性（<code>required</code>）、软亲和性（<code>preferred</code>）。</li></ul></li><li><p>作用：</p><ul><li>节点亲和性调度使得 Pod 对象被吸引运行到一类特定的节点上。</li></ul></li><li><p>特性：</p><ul><li>比 <code>nodeSelector</code> 字段更灵活的规则。</li><li>支持硬亲和性（约束条件必须满足）和软亲和性（尝试满足约束条件，但不保证满足）。</li><li>支持常用操作：<code>In</code>、<code>NotIn</code>、<code>Exists</code>、<code>Gt</code>、<code>Lt</code>、<code>DoesNotExist</code>。</li></ul></li><li><p>定义字段：</p><ul><li>节点硬亲和性：<code>requiredDuringSchedulingIgnoredDuringExecution</code></li><li>节点软亲和性：<code>preferredDuringSchedulingIgnoredDuringExecution</code></li><li>权重 <code>weight</code>：用于定义优先级，范围是 1 ~ 100，值越大优先级越高</li></ul></li><li><p>定义方式：</p><ul><li>定义方式一：Pod 使用 <code>spec.nodeSelector</code>（基于等值关系）</li><li>定义方式二：Pod 使用 <code>spec.affinity.nodeAffinity</code> 支持 <code>matchExpressions</code> 属性（基于复杂标签选择机制）</li></ul></li></ul><hr><ul><li>节点硬亲和性的配置示例：</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">myapp-deployment</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">myapp</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">myapp</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">affinity:</span></span><br><span class="line">        <span class="attr">nodeAffinity:</span></span><br><span class="line">          <span class="comment"># 节点硬亲和性：Pod 只能调度到满足条件的节点</span></span><br><span class="line">          <span class="attr">requiredDuringSchedulingIgnoredDuringExecution:</span></span><br><span class="line">            <span class="attr">nodeSelectorTerms:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">matchExpressions:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">env_role</span></span><br><span class="line">                    <span class="attr">operator:</span> <span class="string">In</span></span><br><span class="line">                    <span class="attr">values:</span></span><br><span class="line">                      <span class="bullet">-</span> <span class="string">dev</span></span><br><span class="line">                      <span class="bullet">-</span> <span class="string">test</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">myapp</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">ikubernetes/myapp:v1</span></span><br></pre></td></tr></tbody></table></figure><ul><li>节点软亲和性的配置示例：</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">myapp-deployment</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">myapp</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">name:</span> <span class="string">myapp-pod</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">myapp</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">affinity:</span></span><br><span class="line">        <span class="attr">nodeAffinity:</span></span><br><span class="line">          <span class="comment"># 节点软亲和性：Pod 优先调度到符合条件的节点，但不是强制要求</span></span><br><span class="line">          <span class="attr">preferredDuringSchedulingIgnoredDuringExecution:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">weight:</span> <span class="number">60</span></span><br><span class="line">              <span class="attr">preference:</span></span><br><span class="line">                <span class="attr">matchExpressions:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">zone</span></span><br><span class="line">                    <span class="attr">operator:</span> <span class="string">In</span></span><br><span class="line">                    <span class="attr">values:</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="attr">weight:</span> <span class="number">30</span></span><br><span class="line">              <span class="attr">preference:</span></span><br><span class="line">                <span class="attr">matchExpressions:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">ssd</span></span><br><span class="line">                    <span class="attr">operator:</span> <span class="string">Exists</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">myapp</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">ikubernetes/myapp:v1</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Pod-亲和性调度"><a href="#Pod-亲和性调度" class="headerlink" title="Pod 亲和性调度"></a>Pod 亲和性调度</h5><p>K8s 的 Pod 亲和性（Pod Affinity）调度用于控制 Pod 倾向于与指定 Pod 调度到同一拓扑域，常用于需要紧密协作或低延迟通信的场景。</p><ul><li><p>Pod 亲和性（Pod Affinity）调度规则有两种：</p><ul><li>硬亲和性（约束条件必须满足）：<code>requiredDuringSchedulingIgnoredDuringExecution</code></li><li>软亲和性（尝试满足约束条件，但不保证满足）：<code>preferredDuringSchedulingIgnoredDuringExecution</code></li></ul></li><li><p>可用于将相互依赖的 Pod 部署在同一机架（Rack）或同一可用区（Zone），以降低网络延迟、提升性能，比如：</p><ul><li><code>topologyKey: kubernetes.io/hostname</code>：倾向于将 Pod 调度到同一 Node（工作节点）。</li><li><code>topologyKey: zone</code>：倾向于将 Pod 调度到同一可用区。</li></ul></li></ul><hr><ul><li>Pod 硬亲和性调度的配置示例：</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">busybox-deployment</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">busybox</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">busybox</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">affinity:</span></span><br><span class="line">        <span class="comment"># Pod 硬亲和性调度</span></span><br><span class="line">        <span class="attr">podAffinity:</span></span><br><span class="line">          <span class="attr">requiredDuringSchedulingIgnoredDuringExecution:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">labelSelector:</span></span><br><span class="line">                <span class="attr">matchExpressions:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">app</span></span><br><span class="line">                    <span class="attr">operator:</span> <span class="string">In</span></span><br><span class="line">                    <span class="attr">values:</span></span><br><span class="line">                      <span class="bullet">-</span> <span class="string">myapp</span></span><br><span class="line">              <span class="attr">topologyKey:</span> <span class="string">zone</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">busybox</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">busybox:latest</span></span><br><span class="line">          <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line">          <span class="attr">command:</span> [<span class="string">"/bin/sh"</span>, <span class="string">"-c"</span>, <span class="string">"sleep 3600"</span>]</span><br></pre></td></tr></tbody></table></figure><ul><li>配置说明：<ul><li>Kubernetes 调度器会强制保证，Pod 调度到与匹配 <code>labelSelector</code> 条件的 Pod 所在的同一 <code>zone</code> 中。</li><li>如果集群中所有 <code>zone</code> 都没有匹配的 Pod，则新 Pod 会一直处于 <code>Pending</code> 状态。<table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>requiredDuringSchedulingIgnoredDuringExecution</code></td><td>硬亲和性约束：必须满足条件，否则 Pod 无法被调度。</td></tr><tr><td><code>labelSelector.matchExpressions</code></td><td>指定匹配条件，比如匹配拥有 <code>app=myapp</code> 标签的 Pod。</td></tr><tr><td><code>topologyKey</code></td><td>定义拓扑域的维度，如 <code>zone</code>、<code>kubernetes.io/hostname</code> 等。</td></tr></tbody></table></li></ul></li></ul><h5 id="Pod-反亲和性调度"><a href="#Pod-反亲和性调度" class="headerlink" title="Pod 反亲和性调度"></a>Pod 反亲和性调度</h5><p>K8s 的 Pod 反亲和性（Pod AntiAffinity）调度用于控制 Pod 不与指定 Pod 调度到同一拓扑域，常用于高可用场景。</p><ul><li>可用于将 Pod 副本分布到不同机架（Rack）或可用区（Zone） 中，避免单点故障，比如：<ul><li><code>topologyKey: kubernetes.io/hostname</code>：避免 Pod 调度到同一 Node（工作节点）。</li><li><code>topologyKey: zone</code>：避免 Pod 调度到同一可用区。</li></ul></li></ul><p>Pod 反亲和性调度的配置示例：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">busybox-deployment</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">busybox</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">busybox</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="bullet">-</span> <span class="attr">name:</span> <span class="string">busybox</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">busybox:latest</span></span><br><span class="line">          <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line">          <span class="attr">command:</span> [<span class="string">"/bin/sh"</span>, <span class="string">"-c"</span>, <span class="string">"sleep 3600"</span>]</span><br><span class="line">      <span class="attr">affinity:</span></span><br><span class="line">        <span class="comment"># Pod 反亲和性调度</span></span><br><span class="line">        <span class="attr">podAntiAffinity:</span></span><br><span class="line">          <span class="attr">requiredDuringSchedulingIgnoredDuringExecution:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">labelSelector:</span></span><br><span class="line">                <span class="attr">matchExpressions:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">app</span></span><br><span class="line">                    <span class="attr">operator:</span> <span class="string">In</span></span><br><span class="line">                    <span class="attr">values:</span></span><br><span class="line">                      <span class="bullet">-</span> <span class="string">myapp</span></span><br><span class="line">              <span class="attr">topologyKey:</span> <span class="string">zone</span></span><br></pre></td></tr></tbody></table></figure><ul><li>配置说明：<ul><li>Kubernetes 调度器会强制保证，同一 <code>zone</code> 下，不会与匹配 <code>labelSelector</code> 条件的 Pod 同时调度运行。</li><li>如果所有 <code>zone</code> 都有匹配的 Pod，则新 Pod 会一直处于 <code>Pending</code> 状态。<table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>requiredDuringSchedulingIgnoredDuringExecution</code></td><td>硬亲和性约束：必须满足条件，否则 Pod 无法被调度。</td></tr><tr><td><code>labelSelector.matchExpressions</code></td><td>指定匹配条件，比如匹配拥有 <code>app=myapp</code> 标签的 Pod。</td></tr><tr><td><code>topologyKey</code></td><td>定义拓扑域的维度，如 <code>zone</code>、<code>kubernetes.io/hostname</code> 等。</td></tr></tbody></table></li></ul></li></ul><h5 id="污点和容忍度"><a href="#污点和容忍度" class="headerlink" title="污点和容忍度"></a>污点和容忍度</h5><blockquote><p>污点（Taints）</p></blockquote><ul><li><p>污点的作用：</p><ul><li>让节点拒绝调度 Pod 到其上运行，除非 Pod 显式声明可以容忍该污点。</li></ul></li><li><p>污点的定义：</p><ul><li>定义在节点（Node）上的键值型属性数据。</li><li>字段 <code>spec.taints</code>，语法是 <code>key=value:effect</code>。</li></ul></li><li><p>污点的适用场景：</p><ul><li>专用节点隔离：保留节点给特定 Pod（如 GPU 节点只运行 AI 任务）</li><li>节点维护：标记节点为不可调度（如 <code>NoSchedule）</code>，避免新 Pod 被分配到正在维护的节点</li><li>特殊硬件限制：防止普通 Pod 调度到带特殊硬件（如 FPGA）的节点</li></ul></li><li><p>污点的类型：</p><table><thead><tr><th>污点类型（Effect）</th><th>作用</th><th>对已运行 Pod 的影响</th><th>典型场景</th></tr></thead><tbody><tr><td> NoSchedule</td><td> 新的 Pod 无法调度到该节点，除非 Pod 明确声明容忍该污点</td><td>不容忍此污点的 Pod 不会被驱逐</td><td>保留节点给特定用途（如 GPU 节点、生产环境专用节点）</td></tr><tr><td>PreferNoSchedule</td><td> 调度器尽量避免将 Pod 调度到该节点，但若无其他节点可选，仍可被调度</td><td>不容忍此污点的 Pod 不会被驱逐</td><td>软性隔离，如临时维护节点但不强制拒绝调度</td></tr><tr><td> NoExecute</td><td> 新的 Pod 无法调度到该节点（与 NoSchedule 相同）</td><td>不容忍此污点的 Pod 会被驱逐（Evict）</td><td>节点故障或紧急隔离，如磁盘损坏需立即迁移所有 Pod</td></tr></tbody></table></li><li><p> 节点自动添加的污点</p><ul><li>当节点出现特定状态或资源异常时，Kubernetes 会自动为节点添加带有 <code>NoExecute</code> 效果的污点，从而驱逐不具备相应容忍度的 Pod。</li><li>K8s 核心组件通常会自动容忍下面这些系统级别的污点，以确保系统服务的持续运行。<table><thead><tr><th>污点键（Taint Key）</th><th>触发条件 / 含义</th><th>说明</th></tr></thead><tbody><tr><td><code>node.kubernetes.io/not-ready</code></td><td>节点进入 NotReady 状态</td><td>表示节点不可调度且无法响应心跳</td></tr><tr><td><code>node.alpha.kubernetes.io/unreachable</code></td><td>节点进入 NotReachable 状态</td><td>旧版本中使用，表示节点网络不可达（已被废弃，改为下一个键）</td></tr><tr><td><code>node.kubernetes.io/unreachable</code></td><td>节点网络不可达</td><td>替代 <code>alpha</code> 版本的键，节点与控制平面失联时自动添加</td></tr><tr><td><code>node.kubernetes.io/out-of-disk</code></td><td>节点磁盘空间不足</td><td>节点磁盘空间耗尽时自动添加</td></tr><tr><td><code>node.kubernetes.io/memory-pressure</code></td><td>节点内存资源紧张</td><td>表示节点内存使用率过高</td></tr><tr><td><code>node.kubernetes.io/disk-pressure</code></td><td>节点磁盘面临压力</td><td>表示节点磁盘可用空间或 I/O 受限</td></tr><tr><td><code>node.kubernetes.io/network-unavailable</code></td><td>节点网络不可用</td><td>节点网络尚未就绪或中断</td></tr><tr><td><code>node.cloudprovider.kubernetes.io/uninitialized</code></td><td>节点由云提供商组件初始化中</td><td>当 kubelet 由云环境程序启动时自动添加，待控制器初始化节点后自动移除</td></tr></tbody></table></li></ul></li><li><p>节点的污点操作示例：</p></li></ul><figure class="highlight shell"><table><tbody><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"># 给节点添加污点</span></span><br><span class="line">kubectl taint nodes &lt;node-name&gt; &lt;key&gt;=&lt;value&gt;:NoSchedule</span><br><span class="line"></span><br><span class="line"><span class="comment"># 举个例子</span></span><br><span class="line">kubectl taint node kube-node1 node-<span class="built_in">type</span>=production:NoShedule</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看节点的污点</span></span><br><span class="line">kubectl describe node &lt;node-name&gt; | grep Taint</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">kubectl get nodes &lt;node-name&gt;<span class="params"> -o</span> go-template={{.spec.taints}}</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 删除节点的单个污点（末尾的 "-" 符号表示删除）</span></span><br><span class="line">kubectl taint nodes &lt;node-name&gt; &lt;key&gt;:NoSchedule-</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除节点的指定键的所有污点（末尾的 "-" 符号表示删除）</span></span><br><span class="line">kubectl taint nodes &lt;node-name&gt; &lt;key&gt;-</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除节点的所有污点</span></span><br><span class="line">kubectl patch nodes &lt;node-name&gt;<span class="params"> -p</span> <span class="string">'{"spec":{"taints":[]}}'</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p>容忍度（Tolerations）</p></blockquote><ul><li><p>容忍度的概述：</p><ul><li>节点设置污点后，Pod 必须声明可以容忍哪些污点，才允许其被调度到具有这些污点的节点上。</li></ul></li><li><p>容忍度的定义：</p><ul><li>定义在 Pod 上的键值型属性数据。</li><li>字段 <code>spec.tolerations</code>，语法是 <code>key=value:effect</code>。</li><li>字段 <code>tolerationSeconds</code> 用于定义延迟驱逐 Pod 的时间<ul><li><code>tolerationSeconds</code> 仅在 <code>effect: NoExecute</code> 的容忍规则中生效；</li><li>超过设定时间后，若节点上的污点仍存在，则 Pod 会被驱逐；</li><li>如果未设置 <code>tolerationSeconds</code>，则表示 Pod 将无限期地容忍该污点，即 Pod 不会因为该污点被驱逐。</li></ul></li></ul></li><li><p>容忍度的调度规则：</p><ul><li>Pod 优先调度到没有污点的节点。</li><li>如果目标节点有污点，则 Pod 必须显式声明容忍该污点，否则无法被调度过去。</li></ul></li><li><p>容忍度的适用场景：</p><ul><li>特权 Pod 调度：允许关键 Pod（如日志收集组件）无视污点，调度到任意节点</li><li>故障恢复：容忍 <code>NoExecute</code> 污点，使 Pod 在节点故障时不被驱逐（如数据库 Pod）</li><li>共享特殊节点：让普通 Pod 通过容忍临时使用专用节点（如容忍 GPU 节点污点）</li></ul></li><li><p>容忍度的类型：</p></li></ul><table><thead><tr><th>容忍度类型（Effect）</th><th>作用</th><th>典型场景</th></tr></thead><tbody><tr><td> NoSchedule</td><td> 允许 Pod 调度到带有 <code>NoSchedule</code> 污点的节点，无视节点的硬性隔离规则</td><td>关键 Pod，如存储服务、核心系统组件</td></tr><tr><td> PreferNoSchedule</td><td> 允许 Pod 调度到带有 <code>PreferNoSchedule</code> 污点的节点，但调度器仍会优先选择其他节点</td><td>非关键 Pod 在资源不足时，仍可使用软隔离节点</td></tr><tr><td> NoExecute</td><td>1. 允许 Pod 调度到带有 <code>NoExecute</code> 污点的节点 <br>2. 豁免驱逐：即使节点新增 NoExecute 污点，Pod 也不会被驱逐</td><td>守护进程（如日志收集器、监控代理）需长期运行，无视节点维护状态</td></tr><tr><td>空值（未指定 Effect）</td><td>容忍所有类型的污点，包括未来新增的类型</td><td>超级特权 Pod，如集群管理组件、CNI/CSI 插件等需在所有节点运行</td></tr></tbody></table><ul><li> Pod 的容忍度配置示例：</li></ul><figure class="highlight yml"><table><tbody><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">spec:</span></span><br><span class="line">  <span class="attr">tolerations:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">"key1"</span></span><br><span class="line">      <span class="attr">operator:</span> <span class="string">"Equal"</span>           <span class="comment"># 等值判断，判断条件为 Equal</span></span><br><span class="line">      <span class="attr">value:</span> <span class="string">"value1"</span></span><br><span class="line">      <span class="attr">effect:</span> <span class="string">"NoExecute"</span></span><br><span class="line">      <span class="attr">tolerationSeconds:</span> <span class="number">600</span>      <span class="comment"># 延迟 600 秒后驱逐 Pod（可选）</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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">spec:</span></span><br><span class="line">  <span class="attr">tolerations:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">"key1"</span></span><br><span class="line">      <span class="attr">operator:</span> <span class="string">"Exists"</span>          <span class="comment"># 存在性判断，只要污点键（Key）存在即可匹配</span></span><br><span class="line">      <span class="attr">effect:</span> <span class="string">"NoExecute"</span></span><br><span class="line">      <span class="attr">tolerationSeconds:</span> <span class="number">600</span>      <span class="comment"># 延迟 600 秒后驱逐 Pod（可选）</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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">spec:</span></span><br><span class="line">  <span class="attr">tolerations:</span></span><br><span class="line">    <span class="comment"># 容忍 NoSchedule 污点</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">"dedicated"</span></span><br><span class="line">      <span class="attr">operator:</span> <span class="string">"Equal"</span></span><br><span class="line">      <span class="attr">value:</span> <span class="string">"gpu"</span></span><br><span class="line">      <span class="attr">effect:</span> <span class="string">"NoSchedule"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 容忍 NoExecute 污点</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">"unreachable"</span></span><br><span class="line">      <span class="attr">operator:</span> <span class="string">"Exists"</span></span><br><span class="line">      <span class="attr">effect:</span> <span class="string">"NoExecute"</span></span><br><span class="line">      <span class="attr">tolerationSeconds:</span> <span class="number">600</span>  <span class="comment"># 延迟 600 秒后驱逐 Pod（可选）</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 容忍所有污点（危险！慎用！）</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">operator:</span> <span class="string">"Exists"</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">myapp-deploy</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">myapp</span></span><br><span class="line">      <span class="attr">release:</span> <span class="string">canary</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">myapp</span></span><br><span class="line">        <span class="attr">release:</span> <span class="string">canary</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="bullet">-</span> <span class="attr">name:</span> <span class="string">myapp</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">ikubernetes/myapp:v1</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line">              <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line">      <span class="comment"># 容忍 NoExecute 污点</span></span><br><span class="line">      <span class="attr">tolerations:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">key:</span> <span class="string">"node-type"</span></span><br><span class="line">          <span class="attr">operator:</span> <span class="string">"Equal"</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">"production"</span></span><br><span class="line">          <span class="attr">effect:</span> <span class="string">"NoExecute"</span></span><br><span class="line">          <span class="attr">tolerationSeconds:</span> <span class="number">600</span>  <span class="comment"># 延迟 600 秒后驱逐 Pod（可选）</span></span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>一个节点可以配置多个污点，一个 Pod 也可以有多个容忍度。</li><li>污点提供了让节点（Node）排斥运行特定 Pod 对象的能力。</li><li>节点亲和性（Node Affinity）调度使得 Pod 对象被吸引运行到一类特定的节点上。</li></ul></div><h5 id="Pod-优先级与抢占式调度"><a href="#Pod-优先级与抢占式调度" class="headerlink" title="Pod 优先级与抢占式调度"></a>Pod 优先级与抢占式调度</h5><p>在 Pod 上定义容忍度时，Pod 的优先级与抢占式调度机制如下：</p><ul><li><p>优先级（Pod Priority）</p><ul><li>表示 Pod 对象的重要程度。</li><li>作用：<ul><li>影响调度顺序：高优先级 Pod 会优先被调度。</li><li>影响驱逐次序：节点资源不足时，低优先级 Pod 会先被驱逐。</li></ul></li></ul></li><li><p>抢占机制（Preemption）</p><ul><li>当一个 Pod 无法被调度时，调度器会尝试驱逐节点上优先级更低的 Pod，为当前高优先级 Pod 腾出资源。</li><li>适合关键业务 Pod 需要资源保障的场景。</li></ul></li><li><p>启用方法</p><ul><li>Pod 优先级与抢占式调度机制默认处于禁用状态，需要手动启用。</li><li>启用方式：在以下组件的启动参数中增加 <code>--feature-gates=PodPriority=true</code>：<ul><li><code>kube-apiserver</code></li><li><code>kube-scheduler</code></li><li><code>kubelet</code></li></ul></li></ul></li><li><p>使用步骤</p><ul><li><p>(1) 创建优先级类别（<code>PriorityClass</code>）</p><ul><li>定义不同的优先级，如关键业务、高优先级、低优先级等。</li><li>配置示例：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">scheduling.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PriorityClass</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">high-priority</span>                               <span class="comment"># 优先级类别名称</span></span><br><span class="line"><span class="attr">value:</span> <span class="number">1000</span>                                         <span class="comment"># 优先级值，数值越大优先级越高</span></span><br><span class="line"><span class="attr">globalDefault:</span> <span class="literal">false</span>                                <span class="comment"># 是否为默认优先级类别</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">"用于关键业务 Pod，例如存储、网络组件"</span>      <span class="comment"># 优先级的描述信息</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">scheduling.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">PriorityClass</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">low-priority</span>                                <span class="comment"># 优先级类别名称</span></span><br><span class="line"><span class="attr">value:</span> <span class="number">100</span>                                          <span class="comment"># 优先级值，数值越大优先级越高</span></span><br><span class="line"><span class="attr">globalDefault:</span> <span class="literal">false</span>                                <span class="comment"># 是否为默认优先级类别</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">"用于低优先级 Pod，例如测试或批处理任务"</span>     <span class="comment"># 优先级的描述信息</span></span><br></pre></td></tr></tbody></table></figure></li></ul></li><li><p>(2) 在 Pod 中指定优先级</p><ul><li>创建 Pod 时，通过 <code>priorityClassName</code> 属性绑定到对应的优先级类别。</li><li>配置示例：<figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">critical-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">priorityClassName:</span> <span class="string">high-priority</span>    <span class="comment"># Pod 绑定高优先级类别</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">critical-container</span></span><br><span class="line">      <span class="attr">image:</span> <span class="string">nginx</span></span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul></li></ul><div class="admonition note"><p class="admonition-title">Pod 的优先级与抢占式调度总结</p><p>高优先级 Pod 无法被调度 → 调度器检查目标节点 → 驱逐低优先级 Pod → 为高优先级 Pod 腾出资源 → 高优先级 Pod 调度成功。</p></div>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之五</title>
    <link href="https://www.techgrow.cn/posts/6bf07963.html"/>
    <id>https://www.techgrow.cn/posts/6bf07963.html</id>
    <published>2025-09-13T13:12:19.000Z</published>
    <updated>2025-09-13T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><h3 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h3><h4 id="Service-的概念"><a href="#Service-的概念" class="headerlink" title="Service 的概念"></a>Service 的概念</h4><p>Service 是 Kubernetes 的核心概念之一。<strong>通过创建 Service，可以为一组具备相同功能的 Pod 提供统一的访问入口（即暴露服务），并将请求流量负载均衡地分发到后端各个 Pod 上。</strong>Pod 与 Service 之间是通过 Label（标签）和 Label Selector（标签选择器）建立关联关系的。值得一提的是，在 Kubernetes 中，Service + EndpointController 的配合实现了类似注册中心的功能：当 Pod 创建后，Kubernetes 控制器会自动将其加入对应 Service 的 Endpoints 列表，并通过 Readiness Probe（就绪探针）动态更新，确保只有可用的 Pod 接收流量。Pod 可以通过 DNS 直接访问 Service 名称（如 <code>&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code>），而流量则由 kube-proxy 负责负载均衡。整体上，Service 提供了 DNS 名称 + 虚拟 IP（ClusterIP）的抽象，真正存储服务实例信息的是 EndpointSlice，整个过程完全自动化，Pod 无需显式注册，从而实现了透明的服务注册与发现。</p><span id="more"></span><div class="admonition note"><p class="admonition-title">提示</p><p>Kubernetes 中的 Service + EndpointController 机制，提供了与注册中心类似的自动注册和服务发现能力，但 Pod 无需显式注册，且数据由 Kubernetes 控制平面自动维护，主要面向集群内部的服务（Pod）注册与发现。</p></div><h4 id="Service-的作用"><a href="#Service-的作用" class="headerlink" title="Service 的作用"></a>Service 的作用</h4><ul><li><p>服务发现（Service Discovery）</p><ul><li>Pod 间互相通信的唯一入口：<ul><li>为一组 Pod 提供一个固定的访问入口，解决 Pod IP 动态变化的问题。</li></ul></li><li>DNS 服务发现：<ul><li>集群内部 Pod 可通过 <code>&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code> 访问目标服务，无需感知 Pod 的 IP。</li></ul></li><li>自动注册与维护：<ul><li>Pod 创建或销毁时，Kubernetes 会自动更新 Service 对应的 <code>EndpointSlice</code>，应用无需显式注册。</li></ul></li></ul></li><li><p>负载均衡（Load Balancing）</p><ul><li>集群内负载均衡：<ul><li>kube-proxy 会自动将访问 Service 的流量分发到后端多个 Pod。</li></ul></li><li>负载策略：<ul><li>默认采用轮询（Round Robin）。</li><li>结合 <code>SessionAffinity</code> 可以实现会话保持。</li></ul></li><li>Service 类型扩展：<ul><li><code>ClusterIP</code>：仅集群内访问，内部负载均衡。</li><li><code>NodePort</code>：通过每个节点固定端口暴露服务。</li><li><code>LoadBalancer</code>：集成云厂商的外部负载均衡器。</li></ul></li></ul></li><li><p>对外暴露服务</p><ul><li>集群外访问能力：<ul><li><code>NodePort</code>：通过节点 IP + 端口访问。</li><li><code>LoadBalancer</code>：借助云厂商负载均衡对外暴露。</li><li><code>ExternalName</code>：将集群内部访问映射到外部域名。</li></ul></li></ul></li><li><p>解耦应用与底层 Pod</p><ul><li>稳定访问地址：<ul><li>应用通过 Service 名称访问后端服务，不依赖具体 Pod IP。</li></ul></li><li>支持滚动升级：<ul><li>Pod 替换过程中，Service 始终提供不变的入口，保障请求不中断。</li></ul></li><li>简化业务逻辑：<ul><li>业务代码不需要实现服务注册、心跳检测、路由等逻辑。</li></ul></li></ul></li><li><p>健康检查与流量控制</p><ul><li>与 Readiness Probe（就绪探针）结合：<ul><li>只将健康的 Pod 添加到 <code>EndpointSlice</code>，自动摘除异常 Pod。</li></ul></li><li>支持蓝绿发布 / 灰度发布：<ul><li>结合标签选择器（<code>selector</code>），灵活管理流量转发目标。</li></ul></li></ul></li><li><p>服务注册中心的替代方案</p><ul><li>自动化注册与发现：<ul><li>无需像 Zookeeper / Eureka 那样主动注册，Pod 生命周期事件由 Kubernetes 控制器接管。</li></ul></li><li>真实的 “注册表”：<ul><li>Pod 实例信息存储在 <code>EndpointSlice</code> 中，Service 只是抽象层，负责提供 DNS 和虚拟 IP（ClusterIP）。</li></ul></li></ul></li></ul><h4 id="Service-的类型"><a href="#Service-的类型" class="headerlink" title="Service 的类型"></a>Service 的类型</h4><h5 id="五大类型"><a href="#五大类型" class="headerlink" title="五大类型"></a>五大类型</h5><p>在 Kubernetes 中，Service 有以下几种类型：</p><ul><li><strong>ClusterIP</strong><ul><li> 概述<ul><li> ClusterIP 是 Service 的默认类型。</li></ul></li><li>作用：<ul><li>为一组 Pod 提供一个集群内部虚拟 IP，只能通过集群内部的 Pod 或 Service 访问。</li></ul></li><li>使用场景：<ul><li>内部微服务之间通信。</li><li>数据库、内部 API 等只在集群内部访问的服务。</li></ul></li><li>配置示例：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&lt;service-name&gt;</span>          <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">ClusterIP</span>           <span class="comment"># Service 类型为 ClusterIP，集群内部可访问</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">my-app</span>             <span class="comment"># 选择标签为 app=my-app 的 Pod 作为后端</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>              <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>      <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br></pre></td></tr></tbody></table></figure></li><li>访问方式：<ul><li>外部访问：<ul><li><code>ClusterIP</code> 类型的 Service 默认无法被外部访问，它只在集群内部有效。</li><li>如果需要外部访问，则必须通过 Ingress、LoadBalancer 或 NodePort 将流量引入集群，再访问集群内的 Pod 或 Service。</li></ul></li><li>集群内部访问：<ul><li>通过虚拟 IP（ClusterIP）访问：<ul><li>集群内 Pod 可以直接访问 Service 的 ClusterIP：<code>http://&lt;clusterIp&gt;:&lt;port&gt;</code></li></ul></li><li>通过 DNS 名称（域名）访问：<ul><li>Kubernetes 会自动为 Service 创建 DNS 名称（域名）。</li><li>Pod 内部可以通过这个 DNS 名称（域名）访问 Service：<code>http://&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local:&lt;port&gt;</code>。</li><li>Service 的默认命名空间（<code>namespace</code>）是 <code>default</code>，可以通过 <code>kubectl get svc -A</code> 命令查看。</li><li>kube-proxy 会将流量转发到对应 Pod 的 <code>targetPort</code>。</li></ul></li></ul></li></ul></li></ul></li></ul><hr><ul><li><strong>NodePort</strong><ul><li> 概述：<ul><li>将 Service 暴露在每个集群节点的固定端口上。</li></ul></li><li>作用：<ul><li>外部流量可以通过集群节点的 IP 和 NodePort 访问到集群内的 Pod。</li></ul></li><li>端口范围：<ul><li>默认 <code>30000 ~ 32767</code>。</li></ul></li><li>使用场景：<ul><li>测试环境或临时访问集群服务。</li><li>没有 Ingress 或云负载均衡器时，简单暴露服务。</li></ul></li><li>配置示例：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&lt;service-name&gt;</span>        <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>          <span class="comment"># Service 类型为 NodePort，可通过节点 IP 访问</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">my-app</span>           <span class="comment"># 选择标签为 app=my-app 的 Pod 作为后端</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>            <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>    <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br><span class="line">      <span class="attr">nodePort:</span> <span class="number">30080</span>     <span class="comment"># 映射到物理机的端口号，默认范围 30000 - 32767</span></span><br></pre></td></tr></tbody></table></figure></li><li>访问方式：<ul><li>外部访问：<ul><li>通过任意一个集群节点的 IP 和 <code>nodePort</code> 访问集群内的 Pod：<code>http://&lt;nodeIP&gt;:&lt;nodePort&gt;</code>。</li></ul></li><li>集群内部访问：<ul><li>Kubernetes 会自动为 Service 创建 DNS 名称（域名）。</li><li>集群内部 Pod 可以通过这个 DNS 名称（域名）访问 Service：<code>http://&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local:&lt;port&gt;</code>。</li><li>Service 的默认命名空间（<code>namespace</code>）是 <code>default</code>，可以通过 <code>kubectl get svc -A</code> 命令查看。</li><li>kube-proxy 会将流量路由到对应 Pod 的 <code>targetPort</code>。</li></ul></li></ul></li></ul></li></ul><hr><ul><li><strong>LoadBalancer</strong><ul><li> 概述：<ul><li>依赖云厂商的负载均衡器，将外部流量分发到集群。</li></ul></li><li>作用：<ul><li>自动向云平台申请一个外部负载均衡器（如 AWS ELB、阿里云 SLB）。</li></ul></li><li>特点：<ul><li>会自动分配到一个公网 IP。</li><li>负载均衡器将流量转发到后端 NodePort。</li></ul></li><li>使用场景：<ul><li>云环境生产集群中，外部流量访问的标准方式。</li><li>对外提供 API 网关、Web 服务、支付网关等服务。</li></ul></li><li>配置示例：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&lt;service-name&gt;</span>          <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">LoadBalancer</span>        <span class="comment"># Service 类型为 LoadBalancer，自动申请外部负载均衡器（LB）</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">my-app</span>             <span class="comment"># 选择标签为 app=my-app 的 Pod 作为后端</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>              <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>      <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br></pre></td></tr></tbody></table></figure></li><li>访问方式：<ul><li>外部访问：<ul><li>外部通过云负载均衡器的公网 IP 或域名访问：<figure class="highlight shell"><table><tbody><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"># 基于公网 IP 访问</span></span><br><span class="line">curl http://&lt;loadbalancer-ip&gt;:80</span><br><span class="line"></span><br><span class="line"><span class="comment"># 基于域名访问</span></span><br><span class="line">curl http://&lt;service-name&gt;.example.com</span><br></pre></td></tr></tbody></table></figure></li><li><code>&lt;LoadBalancer-IP&gt;</code> 由云平台自动分配。</li><li>可绑定自定义域名，通过 DNS 解析访问。</li></ul></li><li>集群内部访问：<ul><li>Kubernetes 会自动为 Service 创建 DNS 名称（域名）。</li><li>集群内部 Pod 可以通过这个 DNS 名称（域名）访问 Service：<code>http://&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local:&lt;port&gt;</code>。</li><li>Service 的默认命名空间（<code>namespace</code>）是 <code>default</code>，可以通过 <code>kubectl get svc -A</code> 命令查看。</li><li>kube-proxy 会将流量路由到对应 Pod 的 <code>targetPort</code>。</li></ul></li></ul></li></ul></li></ul><hr><ul><li><strong>ExternalName</strong><ul><li> 概述：<ul><li>通过 DNS 将 Service 名称映射到集群外部服务域名，不做流量代理。</li></ul></li><li>作用：<ul><li>通过 Kubernetes 内部的 DNS 把 Service 映射为外部域名，Pod 通过访问 Service 名称即可访问外部服务。</li></ul></li><li>特点：<ul><li>不会创建虚拟 IP（ClusterIP）。</li><li>只是一个 DNS CNAME 解析，流量不经过 Kubernetes 负载均衡或代理（kube-proxy）。</li></ul></li><li>使用场景：<ul><li>访问外部数据库、外部 API 服务等。</li></ul></li><li>配置示例：<figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">external-service</span>            <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">ExternalName</span>                <span class="comment"># Service 类型为 ExternalName，将 Service 名称映射到外部域名</span></span><br><span class="line">  <span class="attr">externalName:</span> <span class="string">db.example.com</span>      <span class="comment"># 集群内部访问 Service 时，DNS 解析到的外部域名</span></span><br></pre></td></tr></tbody></table></figure></li><li>访问方式：<ul><li>外部访问：<ul><li><code>ExternalName</code> 类型的 Service 本身不提供外部访问入口，它只是 Kubernetes 内部的 DNS 映射，外部无法通过 Service 名称访问集群内的 Pod 或 Service。</li><li>如果需要外部访问，则必须通过 Ingress、LoadBalancer 或 NodePort 将流量引入集群，再访问集群内的 Pod 或 Service。</li></ul></li><li>集群内部访问：<ul><li>Kubernetes 会自动为 Service 创建 DNS 名称（域名）。</li><li>集群内部 Pod 可以通过这个 DNS 名称（域名）访问 Service：<code>http://&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code>，实际上会解析到 <code>db.example.com</code>。</li><li>Service 的默认命名空间（<code>namespace</code>）是 <code>default</code>，可以通过 <code>kubectl get svc -A</code> 命令查看。</li></ul></li></ul></li></ul></li></ul><hr><ul><li><strong>None</strong><ul><li> 概述：<ul><li>无虚拟 IP（ClusterIP）的 Service（Headless Service - 无头服务），是一种没有 ClusterIP 的特殊 Service 类型。</li><li>用于暴露 K8s 集群内 Pod 的真实 IP 和 DNS 名称（域名），而不是通过一个统一的虚拟 IP（ClusterIP）进行负载均衡，即 K8s 不会做流量负载均衡。</li></ul></li><li>作用：<ul><li>不需要虚拟 IP（ClusterIP）和负载均衡时使用。</li><li>客户端可以直接感知 Pod 的 IP，实现自定义的负载均衡或服务发现。</li></ul></li><li>定义方式：<ul><li>在 Service 配置中设置：<code>clusterIP: None</code>。</li></ul></li><li>使用场景：<ul><li>部署有状态服务（StatefulSet），如 MySQL、ZooKeeper、Kafka 等。</li><li>客户端需要自己实现负载均衡或服务发现的场景。</li></ul></li><li>配置示例：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">headless-service</span>      <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">clusterIP:</span> <span class="string">None</span>             <span class="comment"># 设置为 None 表示无虚拟 IP（ClusterIP），直接返回 Pod 的 IP</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">my-app</span>               <span class="comment"># 选择标签为 app=my-app 的 Pod 作为后端</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>                <span class="comment"># Service 对外暴露的端口，客户端访问时使用</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>        <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br></pre></td></tr></tbody></table></figure></li><li>访问方式：<ul><li>外部访问：<ul><li>由于 <code>None</code> 类型的 Service 不提供虚拟 IP，Kubernetes 不做负载均衡，因此外部不能直接访问 Service。</li><li>如果外部需要访问 <code>None</code> 类型的 Service，可以使用以下方式实现：<ul><li>通过 Pod 的 Node IP + 容器端口（Pod 暴露端口需要通过 NodePort 或其他方式实现）。</li><li>或者借助 Ingress 或 LoadBalancer 将流量引入集群，再由客户端自行选择 Pod。</li></ul></li></ul></li><li>集群内部访问：<ul><li>通过 DNS 名称（域名）访问：<ul><li>Kubernetes 会自动为 Service 创建 DNS 名称（域名）。</li><li>Service 的默认命名空间（<code>namespace</code>）是 <code>default</code>，可以通过 <code>kubectl get svc -A</code> 命令查看。</li><li>第一种 DNS 访问方式：<ul><li>集群内部 Pod，可以通过 Service 的 DNS 名称（域名）查询所有匹配 Pod 的 IP，域名格式：<code>&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code><figure class="highlight shell"><table><tbody><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">nslookup mysql.default.svc.cluster.<span class="built_in">local</span></span><br><span class="line">10.244.1.5</span><br><span class="line">10.244.1.6</span><br><span class="line">10.244.1.7</span><br></pre></td></tr></tbody></table></figure></li></ul></li><li>第二种 DNS 访问方式（StatefulSet 专用访问）<ul><li>ClusterIP 为 <code>None</code> 的 Service，每个 Pod 都有固定的 DNS 名称（域名），适用于数据库或分布式系统访问（比如 MySQL、ZooKeeper），保证客户端可以稳定访问指定 Pod。</li><li>集群内部 Pod，可以通过 Pod 的 DNS 名称（域名）直接访问指定的 Pod，域名格式：<code>&lt;pod-name&gt;.&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code><figure class="highlight shell"><table><tbody><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">nslookup mysql-statefulset-0.mysql.default.svc.cluster.<span class="built_in">local</span></span><br><span class="line"></span><br><span class="line">telnet mysql-statefulset-0.mysql.default.svc.cluster.<span class="built_in">local</span> 3306</span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul></li><li>直接访问 Pod 的 IP：<ul><li>客户端可根据自定义的负载均衡策略（轮询、随机、哈希等）直接访问 Pod 的 <code>targetPort</code>：<figure class="highlight shell"><table><tbody><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">curl http://10.244.1.5:8080</span><br><span class="line">curl http://10.244.1.6:8080</span><br></pre></td></tr></tbody></table></figure></li></ul></li></ul></li></ul></li></ul></li></ul><hr><table><thead><tr><th>类型</th><th>访问范围</th><th>是否需要 kube-proxy</th><th> 是否有虚拟 IP（ClusterIP）</th><th>典型场景</th></tr></thead><tbody><tr><td> ClusterIP</td><td> 仅集群内部</td><td>✅ 是</td><td>✅ 有</td><td>微服务内部通信</td></tr><tr><td> NodePort</td><td> 外部可访问，通过集群节点的 IP</td><td>✅ 是</td><td>✅ 有</td><td>简单对外暴露服务</td></tr><tr><td> LoadBalancer</td><td> 外部可访问，通过 LB 公网 IP</td><td>✅ 是</td><td>✅ 有</td><td>生产外部访问</td></tr><tr><td> ExternalName</td><td> 集群内部访问外部域名</td><td>❌ 否</td><td>❌ 无</td><td>外部服务映射</td></tr><tr><td> None</td><td> 集群内部直接返回 Pod 的 IP</td><td>✅ 是</td><td>❌ 无</td><td>有状态服务访问（如 MySQL、ZooKeeper）</td></tr></tbody></table><h5 id="网络测试"><a href="#网络测试" class="headerlink" title="网络测试"></a>网络测试</h5><div class="admonition warning"><p class="admonition-title">特别注意</p><p><code>ClusterIP</code> 类型的 Service 只能在 Kubernetes 集群内部访问，如果在集群外部机器（比如直接在集群的 Master 节点）上，通过 Service 的 DNS 名称直接访问 Pod（比如 <code>http://nginx.default.svc.cluster.local:80</code>），肯定是无法访问成功的。</p></div><blockquote><p>在 Kubernetes 中，创建一个 Nginx 的 Pod，使用 ClusterIP 类型的 Service 来暴露服务</p></blockquote><ul><li>创建一个 Nginx 的 Deployment 和 Service</li></ul><figure class="highlight shell"><table><tbody><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"># 创建 Nginx</span></span><br><span class="line">kubectl create deployment nginx<span class="params"> --image</span>=nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 暴露 Nginx 的端口（Service 的默认类型是 ClusterIP，可以通过 --type 参数指定类型）</span></span><br><span class="line">kubectl expose deployment nginx<span class="params"> --port</span>=80<span class="params"> --target</span>-port=80</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-6799fc88d8-dkltf   1/1     Running   0          12m   10.244.2.2   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Service 列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> -A</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAMESPACE     NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE</span><br><span class="line">default       kubernetes   ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP                  42d</span><br><span class="line">default       nginx        ClusterIP   10.0.0.231   &lt;none&gt;        80/TCP                   12s</span><br><span class="line">kube-system   kube-dns     ClusterIP   10.0.0.2     &lt;none&gt;        53/UDP,53/TCP,9153/TCP   25m</span><br></pre></td></tr></tbody></table></figure><blockquote><p>在 Kubernetes 集群内部，通过 Service 的 DNS 名称（域名）访问 Nginx 的 Pod</p></blockquote><ul><li>创建一个临时 Pod，并进入 Pod 内部的交互式 Shell</li></ul><figure class="highlight shell"><table><tbody><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"># 添加 --rm 参数，为了在 Shell 中执行 exit 命令退出后自动销毁 Pod</span></span><br><span class="line">kubectl run <span class="built_in">test</span>-pod<span class="params"> --image</span>=busybox:1.35<span class="params"> --restart</span>=Never<span class="params"> -it</span><span class="params"> --rm</span> -- sh</span><br></pre></td></tr></tbody></table></figure><ul><li>在临时 Pod 的内部，通过 Service 的 DNS 名称（域名）访问 Nginx 的 Pod（必须保证临时 Pod 与 Service 处于同一个命名空间），域名格式：<code>&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nslookup nginx.default.svc.cluster.<span class="built_in">local</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Server:10.0.0.2</span><br><span class="line">Address:10.0.0.2:53</span><br><span class="line"></span><br><span class="line">Name:nginx.default.svc.cluster.local</span><br><span class="line">Address: 10.0.0.231</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 或者通过 Service 的 DNS 名称（域名）访问 Nginx 的首页面</span></span><br><span class="line">wget<span class="params"> -qO</span>- http://nginx.default.svc.cluster.<span class="built_in">local</span>:80</span><br></pre></td></tr></tbody></table></figure><ul><li>如果 <code>nslookup</code> 或者 <code>wget</code> 工具无法通过 Service 的 DNS 名称（域名）来访问 Nginx 的 Pod，建议重点检查 CoreDNS（Kubernetes 官方提供的 DNS 服务）是否可以正常运行（也可能是没有<a href="/posts/ccd6f2d4.html#%E9%83%A8%E7%BD%B2-CoreDNS%EF%BC%88%E5%8F%AF%E9%80%89%EF%BC%89">安装 CoreDNS</a>）</li></ul><figure class="highlight shell"><table><tbody><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"># 查看 CoreDNS 的运行状态</span></span><br><span class="line">kubectl get pod<span class="params"> -n</span> kube-system<span class="params"> -l</span> k8s-app=kube-dns</span><br></pre></td></tr></tbody></table></figure><ul><li>预期输出以下内容，如果 CoreDNS 的 <code>STATUS</code> 不是 <code>Running</code> 或 Pod 不存在，则说明 CoreDNS 服务没有正常运行 </li></ul><figure class="highlight plaintext"><table><tbody><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">NAME                       READY   STATUS    RESTARTS   AGE</span><br><span class="line">coredns-6b9bb479b9-g6t6f   1/1     Running   0          34m</span><br><span class="line">coredns-6b9bb479b9-lfd7j   1/1     Running   0          34m</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 删除 Deployment 和 Service</p></blockquote><ul><li>如果需要删除上面创建的 Deployment 和 Service，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除Service</span></span><br><span class="line">kubectl delete service nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除Deployment</span></span><br><span class="line">kubectl delete deployment nginx</span><br></pre></td></tr></tbody></table></figure><h4 id="Service-的定义"><a href="#Service-的定义" class="headerlink" title="Service 的定义"></a>Service 的定义</h4><figure class="highlight yaml"><table><tbody><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"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-service</span>                     <span class="comment"># Service 的名称</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">production</span>                   <span class="comment"># 所属命名空间，例如生产环境</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span>                            <span class="comment"># 标签，标识该服务属于哪个应用</span></span><br><span class="line">    <span class="attr">tier:</span> <span class="string">frontend</span>                        <span class="comment"># 层级标签，例如前端、后端</span></span><br><span class="line">  <span class="attr">annotations:</span></span><br><span class="line">    <span class="attr">description:</span> <span class="string">"Nginx Web Service for production"</span>  <span class="comment"># 业务描述信息</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span>                               <span class="comment"># 匹配后端 Pod</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span>                            <span class="comment"># 必须与 Pod 的 labels 匹配</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>                          <span class="comment"># Service 类型：ClusterIP / NodePort / LoadBalancer / ExternalName</span></span><br><span class="line">  <span class="attr">clusterIP:</span> <span class="number">10.0</span><span class="number">.0</span><span class="number">.15</span>                    <span class="comment"># 集群内部 IP（可选，默认自动分配）</span></span><br><span class="line">  <span class="attr">sessionAffinity:</span> <span class="string">None</span>                   <span class="comment"># 会话亲和性，可选值：None / ClientIP</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span>                          <span class="comment"># 端口名称（可选）</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span>                       <span class="comment"># 协议类型：TCP 或 UDP</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">80</span>                            <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>                    <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br><span class="line">      <span class="attr">nodePort:</span> <span class="number">30080</span>                     <span class="comment"># 映射到物理机的端口号，默认范围 30000 - 32767</span></span><br><span class="line"><span class="attr">status:</span></span><br><span class="line">  <span class="attr">loadBalancer:</span></span><br><span class="line">    <span class="attr">ingress:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">ip:</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span>                 <span class="comment"># 外部负载均衡器分配的 IP</span></span><br><span class="line">        <span class="attr">hostname:</span> <span class="string">lb-prod.example.com</span>     <span class="comment"># 外部负载均衡器的域名</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>属性名称</th><th>取值类型</th><th>是否必选</th><th>取值说明</th></tr></thead><tbody><tr><td><code>spec.ports[].targetPort</code></td><td><code>int</code></td><td></td><td>需要转发到后端 Pod 的端口号</td></tr><tr><td><code>spec.ports[].nodePort</code></td><td><code>int</code></td><td></td><td>当 <code>spec.type=NodePort</code> 时，指定映射到物理机的端口号</td></tr><tr><td><code>status</code></td><td><code>object</code></td><td></td><td>当 <code>spec.type=LoadBalance</code> 时，设置外部负载均衡器的地址，用于公有云环境</td></tr><tr><td><code>status.loadBalancer</code></td><td><code>object</code></td><td></td><td>外部负载均衡器</td></tr><tr><td><code>status.loadBalancer.ingress</code></td><td><code>object</code></td><td></td><td>外部负载均衡器</td></tr><tr><td><code>status.loadBalancer.ingress.ip</code></td><td><code>string</code></td><td></td><td>外部负载均衡器的 IP 地址</td></tr><tr><td><code>status.loadBalancer.ingress.hostname</code></td><td><code>string</code></td><td></td><td>外部负载均衡器的主机名</td></tr></tbody></table><h4 id="Service-的使用"><a href="#Service-的使用" class="headerlink" title="Service 的使用"></a>Service 的使用</h4><h5 id="Service-基础使用"><a href="#Service-基础使用" class="headerlink" title="Service 基础使用"></a>Service 基础使用</h5><p>一般来说，对外提供服务的应用程序需要通过一定的机制来实现暴露，而对于容器化应用，最简便的方式就是通过 TCP/IP 协议，并结合监听 IP 和端口号来对外提供服务。比如，创建一个带基本功能的 Controller：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ReplicationController</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mywebapp</span>                       <span class="comment"># RC 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span>                          <span class="comment"># 副本数量</span></span><br><span class="line">  <span class="attr">template:</span>                            <span class="comment"># Pod 的模板</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">mywebapp</span>                   <span class="comment"># Pod 的名称</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">mywebapp</span>                  <span class="comment"># Pod 的标签，用于 Service 或 RC 选择器</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="bullet">-</span> <span class="attr">name:</span> <span class="string">mywebapp</span>               <span class="comment"># 容器的名称</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">tomcat</span>                <span class="comment"># 容器使用的镜像</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span>      <span class="comment"># 容器内应用（比如 Tomcat）监听的端口</span></span><br></pre></td></tr></tbody></table></figure><p>可以通过 <code>kubectl get pods -l app=mywebapp -o yaml | grep podIP</code> 命令获取 Pod 的 IP 地址，然后使用 Pod 的 IP 地址和端口号来访问 Tomcat 服务。但是，直接通过 Pod 的 IP 来访问服务是不可靠的，因为当 Pod 所在的 Node（工作节点）发生故障时，Kubernetes 会将该 Pod 重新调度到其他 Node（工作节点），此时 Pod 的 IP 地址会发生变化，导致原有访问地址失效。为了解决这一问题，可以通过 YAML 配置文件再定义一个 Service，并使用以下命令来创建：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mywebAppService</span>          <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span>                      <span class="comment"># 选择器，匹配后端 Pod 的标签（labels）</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">mywebapp</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">8081</span>                 <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>           <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 根据指定的 YAML 配置文件创建 Service</span></span><br><span class="line">kubectl create<span class="params"> -f</span> service.yaml</span><br></pre></td></tr></tbody></table></figure><h5 id="多端口-Service"><a href="#多端口-Service" class="headerlink" title="多端口 Service"></a>多端口 Service</h5><p>有时，一个容器应用可能需要对外提供多个端口的服务，这时可以在 Service 的定义中配置多个端口，将每个端口映射到对应的应用服务，如下所示：</p><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">mywebAppService</span>            <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span>                        <span class="comment"># 选择器，匹配后端 Pod 的标签（labels）</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">mywebapp</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span>                    <span class="comment"># 第一个端口的名称</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">8080</span>                   <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>             <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">management</span>             <span class="comment"># 第二个端口的名称</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">8005</span>                   <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8005</span>             <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br></pre></td></tr></tbody></table></figure><h5 id="外部服务-Service"><a href="#外部服务-Service" class="headerlink" title="外部服务 Service"></a>外部服务 Service</h5><p>在某些特殊场景下，应用系统可能需要将外部数据库作为后端服务，或者将其他集群或命名空间中的服务作为后端服务。这时候，可以通过创建一个不带 Label Selector（标签选择器）的 Service 来实现对这些外部服务的访问，如下所示：</p><figure class="highlight yml"><table><tbody><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="comment"># Service 定义</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&lt;service-name&gt;</span>                  <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">protocol:</span> <span class="string">TCP</span>                 <span class="comment"># 协议类型</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">80</span>                      <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8080</span>              <span class="comment"># Pod 容器或 Endpoints 实际监听的端口</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment"># Endpoints 定义</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Endpoints</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&lt;service-name&gt;</span>                  <span class="comment"># 对应的 Service 的名称</span></span><br><span class="line"><span class="attr">subsets:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">addresses:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">ip:</span> <span class="number">10.254</span><span class="number">.74</span><span class="number">.3</span>             <span class="comment"># 外部服务的 IP</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">8080</span>                  <span class="comment"># 外部服务实际监听的端口</span></span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>当 Service 有 <code>selector</code> 时，Service 会自动匹配 Pod，并自动生成 Endpoints。</li><li>当 Service 没有 <code>selector</code> 时，它会通过 Endpoints 的 <code>metadata.name</code> 与 Service 的 <code>metadata.name</code> 相同（一致）来建立关联。</li></ul></div><h3 id="Ingress"><a href="#Ingress" class="headerlink" title="Ingress"></a>Ingress</h3><h4 id="K8s-整体网络架构"><a href="#K8s-整体网络架构" class="headerlink" title="K8s 整体网络架构"></a>K8s 整体网络架构</h4><ul><li><p>Kubernetes 网络主要解决四方面的问题:</p><ul><li>一个 Pod 中的多个容器之间可以通过本地回路（Loopback）互通。</li><li>集群网络在不同 Pod 之间提供通信，Pod 和 Pod 之间互通。</li><li>Service 资源允许用户对外暴露 Pods 中运行的应用程序，以支持来自于集群外部的访问。Service 和 Pod 之间要互通。</li><li>可以使用 Service 来发布仅供集群内部使用的服务。</li></ul></li><li><p>Kubernetes 的整体网络架构</p></li></ul><p><img data-src="../../../asset/2025/09/k8s-ingress-3.png"></p><ul><li>Kubernetes 的网络访问流程</li></ul><p><img data-src="../../../asset/2025/09/k8s-ingress-4.png"></p><h4 id="Ingress-的基本概念"><a href="#Ingress-的基本概念" class="headerlink" title="Ingress 的基本概念"></a>Ingress 的基本概念</h4><ul><li><strong>为什么需要 Ingress？</strong><ul><li>Service 可以使用 NodePort 暴露集群外访问端口，但是性能差不安全。</li><li>缺少 Layer7 的统一访问入口，可以负载均衡、限流等。</li><li>Ingress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由，且流量路由是由 Ingress 资源上定义的规则控制。</li><li>使用 Ingress 作为整个集群统一的入口，配置 Ingress 规则将流量转发到对应的 Service（如下图所示）。</li></ul></li></ul><p><img data-src="../../../asset/2025/09/k8s-ingress-1.png"></p><ul><li><p><strong>Service 中 NodePort 的缺点</strong></p><ul><li>端口资源有限且容易冲突<ul><li> NodePort 在每个节点（Node）上都会占用相同的端口号。</li><li>每个端口只能对应一个 Service，一个节点上的端口号不能重复使用，端口资源有限。</li></ul></li><li>访问方式不够灵活<ul><li>必须通过节点 IP + 端口号访问，不符合实际生产中通常使用域名访问的方式。</li><li>无法直接根据域名自动路由到不同服务，需要额外配置反向代理或者 Ingress 实现域名分流。</li></ul></li><li>对外暴露复杂，安全性较低<ul><li>在所有节点上都暴露了特定的端口，增加了攻击面和安全风险。</li></ul></li><li>难以与外部负载均衡器集成<ul><li> NodePort 只提供基础的端口转发，不具备智能流量分配、健康检查等高级功能。</li></ul></li></ul></li><li><p><strong>Ingress 与 Pod 的关系</strong></p><ul><li>Ingress 并不直接与 Pod 通信，而是通过 Service 与 Pod 进行关联，访问链路是：<code>外部请求 → Ingress → Service → Pod</code>。</li><li>具体来说，Ingress 作为集群对外的统一访问入口，负责根据访问的域名或路径规则，将外部请求转发到对应的 Service；而 Service 再根据其标签选择器（Label Selector）将请求负载均衡地分发给一组符合条件的 Pod。</li></ul></li></ul><h4 id="Ingress-的两种实现"><a href="#Ingress-的两种实现" class="headerlink" title="Ingress 的两种实现"></a>Ingress 的两种实现</h4><p>Ingress 本质上是一个控制器（Controller），需要单独安装，它有两种实现，包括：</p><ul><li><p>Nginx Ingress</p><ul><li>这是 Nginx 官方开发的，适配 Kubernetes 的，分为开源版和 Nginx Plus 版（收费）。</li><li>官方文档：<code>https://docs.nginx.com/nginx-ingress-controller/overview/</code></li><li>官方网站：<code>https://www.nginx.com/products/nginx-ingress-controller</code><br><img data-src="../../../asset/2025/09/k8s-ingress-2.png"></li></ul></li><li><p>Ingress-Nginx</p><ul><li>这是 Kubernetes 官方开发的，适配 Nginx 的，开源免费的；它会及时更新一些特性，而且性能很高，被各大互联网公司广泛采用。</li><li>官方文档：<code>https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/</code></li><li>官方网站：<code>https://kubernetes.github.io/ingress-nginx/examples/auth/basic/</code></li><li>推荐使用这个镜像来部署 Ingress-Nginx：<code>registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/ingress-nginx-controller:v0.46.0</code><br><img data-src="../../../asset/2025/09/k8s-ingress-5.png"></li></ul></li></ul><h4 id="Ingress-的安装步骤"><a href="#Ingress-的安装步骤" class="headerlink" title="Ingress 的安装步骤"></a>Ingress 的安装步骤</h4><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>由于 Ingress 本质上是一个 Kubernetes 控制器（Controller），因此可以通过 YAML 文件进行安装（部署），这里使用的是 Nginx Ingress（由 Nginx 官方开发）。</li><li><strong>在下述的 YAML 配置内容中，<code>hostNetwork</code> 参数必须设置为 <code>true</code>，否则在 Kubernetes 集群外部无法直接通过域名访问 Ingress。</strong></li><li><strong>Ingress 的所有安装（部署）步骤都是在 Kubernetes 集群的 Master 节点上执行。</strong></li></ul></div><ul><li>通过 YAML 文件（比如 <code>nginx-ingress-deploy.yaml）</code> 部署 Nginx Ingress（由 Nginx 官方开发）</li></ul><figure class="highlight yml"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Namespace</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-configuration</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">tcp-services</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">udp-services</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-serviceaccount</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-clusterrole</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">configmaps</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">endpoints</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">nodes</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">pods</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">secrets</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">list</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">watch</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">nodes</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">get</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">services</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">get</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">list</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">watch</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">events</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">create</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">patch</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">"extensions"</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">"networking.k8s.io"</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ingresses</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">get</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">list</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">watch</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">"extensions"</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">"networking.k8s.io"</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ingresses/status</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">update</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-role</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">configmaps</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">pods</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">secrets</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">namespaces</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">get</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">configmaps</span></span><br><span class="line">    <span class="attr">resourceNames:</span></span><br><span class="line">      <span class="comment"># Defaults to "&lt;election-id&gt;-&lt;ingress-class&gt;"</span></span><br><span class="line">      <span class="comment"># Here: "&lt;ingress-controller-leader&gt;-&lt;nginx&gt;"</span></span><br><span class="line">      <span class="comment"># This has to be adapted if you change either parameter</span></span><br><span class="line">      <span class="comment"># when launching the nginx-ingress-controller.</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">"ingress-controller-leader-nginx"</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">get</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">update</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">configmaps</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">create</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">endpoints</span></span><br><span class="line">    <span class="attr">verbs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">get</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">RoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-role-nisa-binding</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">Role</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-role</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-ingress-serviceaccount</span></span><br><span class="line">    <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-clusterrole-nisa-binding</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-ingress-clusterrole</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-ingress-serviceaccount</span></span><br><span class="line">    <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-ingress-controller</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">      <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</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.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">        <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line">      <span class="attr">annotations:</span></span><br><span class="line">        <span class="attr">prometheus.io/port:</span> <span class="string">"10254"</span></span><br><span class="line">        <span class="attr">prometheus.io/scrape:</span> <span class="string">"true"</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">hostNetwork:</span> <span class="literal">true</span></span><br><span class="line">      <span class="comment"># wait up to five minutes for the drain of connections</span></span><br><span class="line">      <span class="attr">terminationGracePeriodSeconds:</span> <span class="number">300</span></span><br><span class="line">      <span class="attr">serviceAccountName:</span> <span class="string">nginx-ingress-serviceaccount</span></span><br><span class="line">      <span class="attr">nodeSelector:</span></span><br><span class="line">        <span class="attr">kubernetes.io/os:</span> <span class="string">linux</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-ingress-controller</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">lizhenliang/nginx-ingress-controller:0.30.0</span></span><br><span class="line">          <span class="attr">args:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">/nginx-ingress-controller</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">--configmap=$(POD_NAMESPACE)/nginx-configuration</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">--tcp-services-configmap=$(POD_NAMESPACE)/tcp-services</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">--udp-services-configmap=$(POD_NAMESPACE)/udp-services</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">--publish-service=$(POD_NAMESPACE)/ingress-nginx</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">--annotations-prefix=nginx.ingress.kubernetes.io</span></span><br><span class="line">          <span class="attr">securityContext:</span></span><br><span class="line">            <span class="attr">allowPrivilegeEscalation:</span> <span class="literal">true</span></span><br><span class="line">            <span class="attr">capabilities:</span></span><br><span class="line">              <span class="attr">drop:</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">ALL</span></span><br><span class="line">              <span class="attr">add:</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">NET_BIND_SERVICE</span></span><br><span class="line">            <span class="comment"># www-data -&gt; 101</span></span><br><span class="line">            <span class="attr">runAsUser:</span> <span class="number">101</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <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="bullet">-</span> <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">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line">              <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line">              <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">https</span></span><br><span class="line">              <span class="attr">containerPort:</span> <span class="number">443</span></span><br><span class="line">              <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">          <span class="attr">livenessProbe:</span></span><br><span class="line">            <span class="attr">failureThreshold:</span> <span class="number">3</span></span><br><span class="line">            <span class="attr">httpGet:</span></span><br><span class="line">              <span class="attr">path:</span> <span class="string">/healthz</span></span><br><span class="line">              <span class="attr">port:</span> <span class="number">10254</span></span><br><span class="line">              <span class="attr">scheme:</span> <span class="string">HTTP</span></span><br><span class="line">            <span class="attr">initialDelaySeconds:</span> <span class="number">10</span></span><br><span class="line">            <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br><span class="line">            <span class="attr">successThreshold:</span> <span class="number">1</span></span><br><span class="line">            <span class="attr">timeoutSeconds:</span> <span class="number">10</span></span><br><span class="line">          <span class="attr">readinessProbe:</span></span><br><span class="line">            <span class="attr">failureThreshold:</span> <span class="number">3</span></span><br><span class="line">            <span class="attr">httpGet:</span></span><br><span class="line">              <span class="attr">path:</span> <span class="string">/healthz</span></span><br><span class="line">              <span class="attr">port:</span> <span class="number">10254</span></span><br><span class="line">              <span class="attr">scheme:</span> <span class="string">HTTP</span></span><br><span class="line">            <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br><span class="line">            <span class="attr">successThreshold:</span> <span class="number">1</span></span><br><span class="line">            <span class="attr">timeoutSeconds:</span> <span class="number">10</span></span><br><span class="line">          <span class="attr">lifecycle:</span></span><br><span class="line">            <span class="attr">preStop:</span></span><br><span class="line">              <span class="attr">exec:</span></span><br><span class="line">                <span class="attr">command:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="string">/wait-shutdown</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">LimitRange</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/part-of:</span> <span class="string">ingress-nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">limits:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">min:</span></span><br><span class="line">      <span class="attr">memory:</span> <span class="string">90Mi</span></span><br><span class="line">      <span class="attr">cpu:</span> <span class="string">100m</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">Container</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 K8s 资源对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> nginx-ingress-deploy.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看特定命名空间下所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> ingress-nginx<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                                       READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-ingress-controller-5dc64b58f-x7stf   1/1     Running   0          17m   192.168.2.236   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>若需要取消 Ingress 的安装，可以执行以下命令删除 Ingress 相关的所有资源 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl delete<span class="params"> -f</span> nginx-ingress-deploy.yaml</span><br></pre></td></tr></tbody></table></figure><h4 id="Ingress-的使用案例"><a href="#Ingress-的使用案例" class="headerlink" title="Ingress 的使用案例"></a>Ingress 的使用案例</h4><div class="admonition note"><p class="admonition-title">提示</p><p>本节将使用 Ingress 对外暴露 Pod，让 Kubernetes 集群外部可以直接通过域名访问 Pod。</p></div><blockquote><p><strong>(1) 创建 Deployment，用于部署 Nginx 的 Pod</strong></p></blockquote><ul><li>通过 YAML 文件（比如 <code>nginx-deploy.yaml</code>）创建 Deployment（用于创建和管理 Pod）</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-deploy</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-pod</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">nginx-pod</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Deployment 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> nginx-deploy.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                            READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-deploy-85b7dd6b6d-grk6n   1/1     Running   0          65m   10.244.3.47   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(2) 创建 Service，用于在集群内部暴露 Nginx 的 Pod</strong></p></blockquote><ul><li>通过 YAML 文件（比如 <code>nginx-service.yaml</code>）创建 Service（用于对外暴露服务）</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-service</span>       <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">ClusterIP</span>           <span class="comment"># Service 类型为 ClusterIP，集群内部可访问</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-pod</span>          <span class="comment"># 选择标签为 app=nginx-pod 的 Pod 作为后端</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>              <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span>        <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Service 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> nginx-service.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get services</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE</span><br><span class="line">kubernetes      ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP   84d</span><br><span class="line">nginx-service   ClusterIP   10.0.0.193   &lt;none&gt;        80/TCP    27s</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(3) 创建 Ingress 的路由规则，用于将外部请求转发给 Service</strong></p></blockquote><ul><li>通过 YAML 文件（比如 <code>ingress-http.yaml</code>）创建 Ingress 的路由规则 </li></ul><figure class="highlight yml"><table><tbody><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.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Ingress</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">example-ingress</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">host:</span> <span class="string">example.ingress.com</span></span><br><span class="line">    <span class="attr">http:</span></span><br><span class="line">      <span class="attr">paths:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">path:</span> <span class="string">/</span></span><br><span class="line">        <span class="attr">pathType:</span> <span class="string">Prefix</span></span><br><span class="line">        <span class="attr">backend:</span></span><br><span class="line">          <span class="attr">service:</span></span><br><span class="line">            <span class="attr">name:</span> <span class="string">nginx-service</span>         <span class="comment"># Service 的名称</span></span><br><span class="line">            <span class="attr">port:</span></span><br><span class="line">              <span class="attr">number:</span> <span class="number">80</span>                <span class="comment"># Service 对外暴露的端口</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 创建或更新 YAML 文件中定义的 Ingress 对象</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> ingress-http.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Ingress 路由规则 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get ingress</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME              CLASS    HOSTS                 ADDRESS   PORTS   AGE</span><br><span class="line">example-ingress   &lt;none&gt;   example.ingress.com             80      13s</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(4) 在 K8s 集群外部的操作系统中，添加 Hosts 映射记录</strong></p></blockquote><ul><li>在 K8s 集群外部的操作系统中，编辑系统配置文件 <code>/etc/hosts</code>，添加域名映射记录，其中 <code>192.168.2.236</code> 是 Ingress Controller 所在节点的 IP 地址（<strong>请自行更改 IP 地址</strong>）</li></ul><figure class="highlight shell"><table><tbody><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"># 编辑系统配置文件，添加以下内容</span></span><br><span class="line">vim /etc/hosts</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">192.168.2.236     example.ingress.com</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>(5) 在 K8s 集群外部的操作系统中，通过域名访问 Ingress，验证 Pod 是否可以访问</strong></p></blockquote><ul><li>在 K8s 集群外部的操作系统中，通过域名访问 Ingress；如果可以成功访问 Nginx 的首页，则说明 Ingress + Service + Pod 都正常运行 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://example.ingress.com</span><br></pre></td></tr></tbody></table></figure>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之七</title>
    <link href="https://www.techgrow.cn/posts/2ca57d7f.html"/>
    <id>https://www.techgrow.cn/posts/2ca57d7f.html</id>
    <published>2025-09-13T13:12:19.000Z</published>
    <updated>2025-09-13T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><h3 id="Helm"><a href="#Helm" class="headerlink" title="Helm"></a>Helm</h3><h4 id="Helm-的引入"><a href="#Helm-的引入" class="headerlink" title="Helm 的引入"></a>Helm 的引入</h4><p>在 Kubernetes 中，应用由特定的资源对象组成，如 Deployment、Service、Ingress 等。通常，这些资源的配置会分别保存在多个独立的 YAML 资源文件中，或集中写入一个 YAML 资源文件中，然后通过 <code>kubectl apply -f</code> 命令进行部署。对于只包含一个或少数几个服务的简单应用，这种方式已经足够。但对于复杂应用（例如微服务架构的系统），往往由十几个甚至数十个服务组成。如果需要更新或回滚应用，就必须修改和维护大量的 YAML 资源文件，这种分散式的管理方式显得十分低效。此外，由于缺乏对应用整体的版本管理与控制，Kubernetes 在应用的维护与更新方面面临以下主要问题：</p><ul><li>(1) YAML 资源文件难以实现高效复用；</li><li>(2) YAML 资源文件不支持应用级别的版本管理与回滚；</li><li>(3) 难以将多个服务作为一个整体进行统一管理。</li></ul><p>为此，Kubernetes 引入了 Helm 来解决上述问题。</p><h4 id="Helm-的概念"><a href="#Helm-的概念" class="headerlink" title="Helm 的概念"></a>Helm 的概念</h4><p>Helm 是 Kubernetes 的包管理工具，类似于 Linux 下的包管理器（如 <code>yum</code> 或 <code>apt</code>），可以方便地将预先打包好的 YAML 资源文件部署到 Kubernetes 集群中。Helm 主要包含以下三个核心概念：</p><ul><li>(1) <code>Helm</code>：命令行客户端工具，用于创建、打包、发布和管理 Kubernetes 应用的 Chart。</li><li>(2) <code>Chart</code>：应用的描述包，由一组用于定义 Kubernetes 资源的 YAML 资源文件组成。</li><li>(3) <code>Release</code>：基于 Chart 的部署实体。每当通过 Helm 部署一个 Chart 时，都会在 Kubernetes 集群中自动生成一个对应的 Release，用于表示实际运行的资源对象。</li></ul><p>值得注意的是，在 Chart 安装后，Helm 会自动创建一个对应的 Release 对象，并根据 Chart 模板文件创建相应的 Kubernetes 资源对象（如 Deployment、Service、Ingress 等），随后由控制器（Controller）自动拉起并运行相应的 Pod。</p><h4 id="Helm-的版本变化"><a href="#Helm-的版本变化" class="headerlink" title="Helm 的版本变化"></a>Helm 的版本变化</h4><p>2019 年 11 月 13 日，Helm 团队发布 Helm <code>v3</code> 的第一个稳定版本。该版本的主要变化如下：</p><ul><li>(1) 最明显的变化是 Tiller 的删除</li><li> (2) Release 名称可以在不同命名空间中重用</li><li> (3) 支持将 Chart 推送至 Docker 镜像仓库中</li><li> (4) 使用 JSONSchema 验证 Chart Values</li><li>(5) 其他变化</li></ul><p>Helm <code>v2</code> 与 <code>v3</code> 版本的整体架构对比如下图所示：</p><p><img data-src="../../../asset/2025/09/k8s-helm-1.png"></p><h4 id="Helm-客户端安装"><a href="#Helm-客户端安装" class="headerlink" title="Helm 客户端安装"></a>Helm 客户端安装</h4><div class="admonition warning"><p class="admonition-title">特别注意</p><ul><li>Helm 不同版本的客户端可以从 <a href="https://github.com/helm/helm/releases">GitHub Releases</a> 下载得到。</li><li><strong>通常只需要在 Kubernetes 集群的 Master 节点上安装 Helm，其他 Worker 节点不需要安装 Helm。</strong></li></ul></div><blockquote><p>Helm 客户端的安装</p></blockquote><figure class="highlight shell"><table><tbody><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"># 下载压缩包</span></span><br><span class="line">wget https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解压压缩包</span></span><br><span class="line">tar<span class="params"> -zxvf</span> helm-v3.2.1-linux-amd64.tar.gz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 移动文件</span></span><br><span class="line">sudo mv linux-amd64/helm /usr/bin/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看版本</span></span><br><span class="line">helm version</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Helm 客户端配置国内 Chart 仓库（存储库）</p></blockquote><ul><li><p>微软仓库：</p><ul><li>仓库地址：<code>http://mirror.azure.cn/kubernetes/charts</code></li><li>这个 Chart 仓库推荐使用，基本上 Kubernetes 官方仓库有的 Chart 它都有。</li></ul></li><li><p>阿里云仓库：</p><ul><li>仓库地址：<code>https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts</code></li><li>这个 Chart 仓库国内可以正常访问。</li></ul></li><li><p>官方仓库：</p><ul><li>仓库地址：<code>https://hub.kubeapps.com/charts/incubator</code></li><li>Kubernetes 官方的 Chart 仓库，国内可能无法正常访问。</li></ul></li></ul><hr><ul><li>添加新的仓库 </li></ul><figure class="highlight shell"><table><tbody><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"># 添加微软仓库</span></span><br><span class="line">helm repo add stable http://mirror.azure.cn/kubernetes/charts</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加阿里云仓库</span></span><br><span class="line">helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts</span><br><span class="line"></span><br><span class="line"><span class="comment"># 更新本地仓库索引</span></span><br><span class="line">helm repo update</span><br></pre></td></tr></tbody></table></figure><ul><li>查看已有的存储库 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看已添加的仓库列表</span></span><br><span class="line">helm repo list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在本地仓库中搜索名称包含 stable 的 Chart</span></span><br><span class="line">helm search repo stable</span><br></pre></td></tr></tbody></table></figure><ul><li>删除已有的存储库 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除名称为 aliyun 的仓库  </span></span><br><span class="line">helm repo remove aliyun</span><br><span class="line"></span><br><span class="line"><span class="comment"># 更新本地仓库索引</span></span><br><span class="line">helm repo update</span><br></pre></td></tr></tbody></table></figure><h4 id="Helm-的常用命令"><a href="#Helm-的常用命令" class="headerlink" title="Helm 的常用命令"></a>Helm 的常用命令</h4><blockquote><p>Chart 的三个核心命令</p></blockquote><ul><li><code>chart install</code>：安装一个新的 Chart，并在集群中创建对应的应用实例。</li><li><code>chart upgrade</code>：升级已安装的 Chart 到新版本或更新其配置。</li><li><code>chart rollback</code>：将已部署的 Chart 回滚到指定的历史版本。</li></ul><blockquote><p>Chart 的常用命令列表</p></blockquote><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td><code>create</code></td><td>创建一个 Chart 并指定名称</td></tr><tr><td><code>dependency</code></td><td>管理 Chart 依赖</td></tr><tr><td><code>get</code></td><td>下载一个 Release，可用于命令：<code>all</code>、<code>hooks</code>、<code>manifest</code>、<code>notes</code>、<code>values</code></td></tr><tr><td><code>history</code></td><td>获取 Release 历史</td></tr><tr><td><code>install</code></td><td>安装一个 Chart</td></tr><tr><td><code>list</code></td><td>列出 Release</td></tr><tr><td><code>package</code></td><td>将 Chart 目录打包到 Chart 存档文件中</td></tr><tr><td><code>pull</code></td><td>从远程仓库中下载 Chart 并解压到本地</td></tr><tr><td><code>repo</code></td><td>添加、列出、移除、更新和索引 Chart 仓库，可用于命令：<code>add</code>、<code>index</code>、<code>list</code>、<code>remove</code>、<code>update</code></td></tr><tr><td><code>rollback</code></td><td>从之前版本回滚</td></tr><tr><td><code>search</code></td><td>根据关键字搜索 Chart，可用于命令：<code>hub</code>、<code>repo</code></td></tr><tr><td><code>show</code></td><td>查看 Chart 的详细信息，可用于命令：<code>all</code>、<code>chart</code>、<code>readme</code>、<code>values</code></td></tr><tr><td><code>status</code></td><td>显示已命名版本的状态</td></tr><tr><td><code>template</code></td><td>本地呈现模板</td></tr><tr><td><code>uninstall</code></td><td>卸载一个 Release</td></tr><tr><td><code>upgrade</code></td><td>更新一个 Release</td></tr><tr><td><code>version</code></td><td>查看 Helm 客户端的版本</td></tr></tbody></table><h4 id="Helm-的使用案例"><a href="#Helm-的使用案例" class="headerlink" title="Helm 的使用案例"></a>Helm 的使用案例</h4><h5 id="创建管理员用户"><a href="#创建管理员用户" class="headerlink" title="创建管理员用户"></a>创建管理员用户</h5><div class="admonition note"><p class="admonition-title">提示</p><p>本节将演示如何在 Kubernetes 集群中创建管理员用户，否则在后续使用 Helm 时，可能无法正常访问 Kubernetes 资源，比如执行 <code>helm install</code> 命令会出现错误：<code>Error: Kubernetes cluster unreachable</code>。</p></div><ul><li>通过 YAML 文件（比如 <code>admin-user-sa.yaml</code>）创建管理员用户 <code>admin-user</code></li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-user</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> admin-user-sa.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>通过 YAML 文件（比如 <code>admin-user-rolebinding.yaml</code>）给管理员用户 <code>admin-user</code> 授权 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-user-binding</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-user</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">cluster-admin</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> admin-user-rolebinding.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>获取管理员用户 <code>admin-user</code> 的 Token</li></ul><figure class="highlight shell"><table><tbody><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">SECRET_NAME=$(kubectl get sa admin-user<span class="params"> -n</span> kube-system<span class="params"> -o</span> jsonpath=<span class="string">"{.secrets[0].name}"</span>)</span><br><span class="line"></span><br><span class="line">kubectl get secret <span class="variable">$SECRET_NAME</span><span class="params"> -n</span> kube-system<span class="params"> -o</span> jsonpath=<span class="string">"{.data.token}"</span> | base64<span class="params"> --decode</span></span><br></pre></td></tr></tbody></table></figure><ul><li>获取 Kubernetes 集群 CA 证书的 Base64 编码（<strong>请自行更改 CA 证书的路径</strong>）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /opt/kubernetes/ssl/ca.pem | base64<span class="params"> -w</span> 0</span><br></pre></td></tr></tbody></table></figure><ul><li>创建一个 kubeconfig 文件（比如 <code>admin-user.kubeconfig</code>）</li></ul><figure class="highlight shell"><table><tbody><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"># 创建配置目录（可自定义）</span></span><br><span class="line">mkdir<span class="params"> -p</span> /opt/kubernetes/cfg</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建并编辑文件，写入以下 YAML 配置内容</span></span><br><span class="line">vim /opt/kubernetes/cfg/admin-user.kubeconfig</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Config</span></span><br><span class="line"><span class="attr">clusters:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">cluster:</span></span><br><span class="line">    <span class="attr">server:</span> <span class="string">https://&lt;IP&gt;:6443</span>                       <span class="comment">#  填写 API Server 的 IP 地址和端口</span></span><br><span class="line">    <span class="attr">certificate-authority-data:</span> <span class="string">&lt;CA_CERT_BASE64&gt;</span>    <span class="comment">#  填写 K8s 集群 CA 证书的 Base64 编码</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">kubernetes</span></span><br><span class="line"><span class="attr">contexts:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">context:</span></span><br><span class="line">    <span class="attr">cluster:</span> <span class="string">kubernetes</span></span><br><span class="line">    <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">admin-user</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-context</span></span><br><span class="line"><span class="attr">current-context:</span> <span class="string">admin-context</span></span><br><span class="line"><span class="attr">users:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">admin-user</span></span><br><span class="line">  <span class="attr">user:</span></span><br><span class="line">    <span class="attr">token:</span> <span class="string">&lt;TOKEN&gt;</span>      <span class="comment"># 填写管理员用户的 Token</span></span><br></pre></td></tr></tbody></table></figure><ul><li>验证 kubeconfig 文件（比如 <code>admin-user.kubeconfig</code>）是否可以正常使用 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> --kubeconfig</span>=/opt/kubernetes/cfg/admin-user.kubeconfig</span><br></pre></td></tr></tbody></table></figure><ul><li>添加系统环境变量 <code>KUBECONFIG</code>（可选步骤）</li></ul><figure class="highlight shell"><table><tbody><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"># 添加系统环境变量</span></span><br><span class="line"><span class="built_in">export</span> KUBECONFIG=/opt/kubernetes/cfg/admin-user.kubeconfig</span><br><span class="line"></span><br><span class="line"><span class="comment"># 之后就可以直接访问 K8s 资源了，不再需要通过 "--kubeconfig" 参数指定 kubeconfig 文件</span></span><br><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><h5 id="通过-Chart-部署应用"><a href="#通过-Chart-部署应用" class="headerlink" title="通过 Chart 部署应用"></a>通过 Chart 部署应用</h5><div class="admonition note"><p class="admonition-title">提示</p><ul><li>本节将演示如何使用 Chart 快速部署应用（比如 Weave，这是 Kubernetes 集群可视化与监控工具）。</li></ul></div><div class="admonition warning"><p class="admonition-title">注意</p><ul><li>如果 <code>helm</code> 命令执行失败，并提示错误信息 <code>Error: Kubernetes cluster unreachable</code>，可以参考以下任意一种方案来解决：</li><li>(1) 尝试往 <code>helm</code> 命令的末尾添加参数 <code>--kubeconfig=/opt/kubernetes/cfg/admin-user.kubeconfig</code>。</li><li>(2) 通过 <code>export KUBECONFIG=/opt/kubernetes/cfg/admin-user.kubeconfig</code> 命令添加对应的环境变量。</li></ul></div><ul><li>搜索指定的 Chart</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm search repo weave</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME              CHART VERSIONAPP VERSIONDESCRIPTION                                       </span><br><span class="line">aliyun/weave-cloud0.1.2                   Weave Cloud is a add-on to Kubernetes which pro...</span><br><span class="line">aliyun/weave-scope0.9.2        1.6.5      A Helm chart for the Weave Scope cluster visual...</span><br><span class="line">stable/weave-cloud0.3.9        1.4.0      DEPRECATED - Weave Cloud is a add-on to Kuberne...</span><br><span class="line">stable/weave-scope1.1.12       1.12.0     DEPRECATED - A Helm chart for the Weave Scope c...</span><br></pre></td></tr></tbody></table></figure><ul><li>查看指定 Chart 的详细信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm show chart stable/weave-scope</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">apiVersion: v1</span><br><span class="line">appVersion: 1.12.0</span><br><span class="line">deprecated: true</span><br><span class="line">description: DEPRECATED - A Helm chart for the Weave Scope cluster visualizer.</span><br><span class="line">home: https://www.weave.works/oss/scope/</span><br><span class="line">icon: https://avatars1.githubusercontent.com/u/9976052?s=64</span><br><span class="line">keywords:</span><br><span class="line">- containers</span><br><span class="line">- dashboard</span><br><span class="line">- monitoring</span><br><span class="line">name: weave-scope</span><br><span class="line">sources:</span><br><span class="line">- https://github.com/weaveworks/scope</span><br><span class="line">version: 1.1.12</span><br></pre></td></tr></tbody></table></figure><ul><li>安装指定的 Chart</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm install ui stable/weave-scope</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME: ui</span><br><span class="line">LAST DEPLOYED: Tue Oct 28 20:17:33 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 1</span><br><span class="line">NOTES:</span><br><span class="line">You should now be able to access the Scope frontend in your web browser, by</span><br><span class="line">using kubectl port-forward:</span><br><span class="line"></span><br><span class="line">kubectl -n default port-forward $(kubectl -n default get endpoints \</span><br><span class="line">ui-weave-scope -o jsonpath='{.subsets[0].addresses[0].targetRef.name}') 8080:4040</span><br><span class="line"></span><br><span class="line">then browsing to http://localhost:8080/.</span><br><span class="line">For more details on using Weave Scope, see the Weave Scope documentation:</span><br><span class="line"></span><br><span class="line">https://www.weave.works/docs/scope/latest/introducing/</span><br></pre></td></tr></tbody></table></figure><ul><li>查看发布（Release）的列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm list</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAMENAMESPACEREVISIONUPDATED                                STATUS  CHART             APP VERSION</span><br><span class="line">ui  default  1       2025-10-28 20:17:33.899483553 +0800 CSTdeployedweave-scope-1.1.121.12.0  </span><br></pre></td></tr></tbody></table></figure><ul><li>查看指定发布（Release）的详细信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm status ui</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME: ui</span><br><span class="line">LAST DEPLOYED: Tue Oct 28 20:17:33 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 1</span><br><span class="line">NOTES:</span><br><span class="line">You should now be able to access the Scope frontend in your web browser, by</span><br><span class="line">using kubectl port-forward:</span><br><span class="line"></span><br><span class="line">kubectl -n default port-forward $(kubectl -n default get endpoints \</span><br><span class="line">ui-weave-scope -o jsonpath='{.subsets[0].addresses[0].targetRef.name}') 8080:4040</span><br><span class="line"></span><br><span class="line">then browsing to http://localhost:8080/.</span><br><span class="line">For more details on using Weave Scope, see the Weave Scope documentation:</span><br><span class="line"></span><br><span class="line">https://www.weave.works/docs/scope/latest/introducing/</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                                            READY   STATUS    RESTARTS   AGE</span><br><span class="line">weave-scope-agent-ui-lw2lf                      1/1     Running   0          35s</span><br><span class="line">weave-scope-agent-ui-nn4vs                      1/1     Running   0          35s</span><br><span class="line">weave-scope-agent-ui-pzqgk                      1/1     Running   0          35s</span><br><span class="line">weave-scope-agent-ui-qbcvm                      1/1     Running   0          35s</span><br><span class="line">weave-scope-cluster-agent-ui-5cbc84db49-4wvvt   1/1     Running   0          35s</span><br><span class="line">weave-scope-frontend-ui-6698fd5545-4lpl9        1/1     Running   0          35s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE</span><br><span class="line">kubernetes       ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP   86d</span><br><span class="line">ui-weave-scope   ClusterIP   10.0.0.64    &lt;none&gt;        80/TCP    78s</span><br></pre></td></tr></tbody></table></figure><ul><li>若希望在 Kubernetes 集群外部访问 Weave，可以更改 Service 的类型 </li></ul><figure class="highlight shell"><table><tbody><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"># 编辑 Service 的配置，更改 type 参数，保存退出后自动生效</span></span><br><span class="line">kubectl edit svc ui-weave-scope</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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">spec:</span><br><span class="line">  clusterIP: 10.0.0.64</span><br><span class="line">  ports:</span><br><span class="line">  - name: http</span><br><span class="line">    port: 80</span><br><span class="line">    protocol: TCP</span><br><span class="line">    targetPort: http</span><br><span class="line">  selector:</span><br><span class="line">    app: weave-scope</span><br><span class="line">    component: frontend</span><br><span class="line">    release: ui</span><br><span class="line">  sessionAffinity: None</span><br><span class="line">  <span class="built_in">type</span>: NodePort      <span class="comment"># 将 Service 的类型更改为 NodePort</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看所有 Service，获取节点暴露的端口</span></span><br><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes       ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        86d</span><br><span class="line">ui-weave-scope   NodePort    10.0.0.64    &lt;none&gt;        80:31764/TCP   6m4s</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">最后通过任意一个集群节点的 IP 与 Service 对外暴露的端口（比如 `http://192.168.2.191:31764`），就可以在 Kubernetes 集群外部通过浏览器访问 Weave 的 Web 控制台页面</span><br></pre></td></tr></tbody></table></figure><ul><li>若希望卸载前面所安装的 Chart，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 直接删除 Chart，默认不会删除关联的 PVC（数据卷）</span></span><br><span class="line">helm uninstall ui</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果需要删除 Chart 关联的 PVC（数据卷），可以执行以下命令</span></span><br><span class="line">kubectl delete pvc<span class="params"> -l</span> release=ui</span><br></pre></td></tr></tbody></table></figure><h5 id="自定义-Chart-配置信息"><a href="#自定义-Chart-配置信息" class="headerlink" title="自定义 Chart 配置信息"></a>自定义 Chart 配置信息</h5><div class="admonition note"><p class="admonition-title">提示</p><p>本节将演示如何在安装 Chart 之前，自定义 Chart 的配置信息，目的是覆盖 Chart 的默认配置信息。</p></div><div class="admonition warning"><p class="admonition-title">注意</p><ul><li>如果 <code>helm</code> 命令执行失败，并提示错误信息 <code>Error: Kubernetes cluster unreachable</code>，可以参考以下任意一种方案来解决：</li><li>(1) 尝试往 <code>helm</code> 命令的末尾添加参数 <code>--kubeconfig=/opt/kubernetes/cfg/admin-user.kubeconfig</code>。</li><li>(2) 通过 <code>export KUBECONFIG=/opt/kubernetes/cfg/admin-user.kubeconfig</code> 命令添加对应的环境变量。</li></ul></div><p>自定义 Chart 配置信息的原因在于，Chart 仓库中并非所有 Chart 都能在默认配置下成功运行，有时需要根据环境提供额外依赖，例如 <code>PVC</code>（数据卷）。因此，需要在安装 Chart 前自定义配置信息，主要有以下两种配置信息传递方式：</p><ul><li><p><code>--values</code> 或 <code>-f</code></p><ul><li>指定一个包含覆盖配置信息的 YAML 文件。</li><li>可以指定多个 YAML 文件，右边的 YAML 文件优先级更高。</li></ul></li><li><p><code>--set</code></p><ul><li>直接在命令行中指定覆盖配置信息。</li><li>当同时使用 <code>--values</code> 与 <code>--set</code> 时，<code>--set</code> 的优先级更高。</li><li>YAML 与 <code>--set</code> 配置格式的对比请看 <a href="../../../asset/2025/09/k8s-helm-2.png">这里</a>。</li></ul></li></ul><hr><ul><li>查看指定 Chart 的默认配置信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm show values stable/mysql</span><br></pre></td></tr></tbody></table></figure><ul><li>创建 YAML 配置文件，用于覆盖 Chart 的默认配置信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim mysql-server-config.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">persistence:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">false</span>                          <span class="comment"># 关闭持久化存储</span></span><br><span class="line"><span class="attr">mysqlUser:</span> <span class="string">"k8s"</span>                          <span class="comment"># 自定义 MySQL 用户名</span></span><br><span class="line"><span class="attr">mysqlPassword:</span> <span class="string">"123456"</span>                   <span class="comment"># 自定义 MySQL 密码</span></span><br><span class="line"><span class="attr">mysqlDatabase:</span> <span class="string">"k8s"</span>                      <span class="comment"># 自定义创建的数据库名称</span></span><br></pre></td></tr></tbody></table></figure><ul><li>安装指定的 Chart，并覆盖默认配置 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm install mysql-server<span class="params"> -f</span> mysql-server-config.yaml stable/mysql</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE</span><br><span class="line">mysql-server-9c7558dc8-rb89z   0/1     Running   0          15s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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">NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE</span><br><span class="line">kubernetes     ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP    86d</span><br><span class="line">mysql-server   ClusterIP   10.0.0.4     &lt;none&gt;        3306/TCP   88s</span><br></pre></td></tr></tbody></table></figure><ul><li>验证 Chart 的安装 </li></ul><figure class="highlight shell"><table><tbody><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"># 启动一个临时容器并连接进去，--rm 参数表示退出后会自动销毁容器</span></span><br><span class="line">kubectl run<span class="params"> -it</span> mysql-client<span class="params"> --rm</span><span class="params"> --restart</span>=Never<span class="params"> --image</span>=mysql:5.7 -- bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在临时容器的内部登录 MySQL（可以通过 Service 的名称直接访问 MySQL Server，前提是在同一个命名空间，且安装了 CoreDNS）</span></span><br><span class="line">mysql<span class="params"> -hmysql</span>-server<span class="params"> -uk8s</span><span class="params"> -p123456</span></span><br></pre></td></tr></tbody></table></figure><h4 id="Helm-构建自定义的-Chart"><a href="#Helm-构建自定义的-Chart" class="headerlink" title="Helm 构建自定义的 Chart"></a>Helm 构建自定义的 Chart</h4><div class="admonition note"><p class="admonition-title">提示</p><p>本节将演示如何构建一个自己的 Chart（比如，用于部署 Nginx 的 Chart），并将其安装和打包。</p></div><h5 id="三大核心构建步骤"><a href="#三大核心构建步骤" class="headerlink" title="三大核心构建步骤"></a>三大核心构建步骤</h5><p>Kubernetes 支持用户构建（开发）自己的 Chart，核心的构建步骤如下：</p><ul><li><p>(1) 创建 Chart 基础模板</p><ul><li>使用 <code>helm create &lt;chart-name&gt;</code> 命令创建基础模板。</li></ul></li><li><p>(2) 更改 Chart 配置文件</p><ul><li>编辑 <code>Chart.yaml</code> 文件，填写应用名称、版本、描述等信息。</li><li>编辑 <code>values.yaml</code> 文件，添加常用变量和默认值，用于自定义配置。</li></ul></li><li><p>(3) 创建 Chart 模板文件</p><ul><li>在自动生成的 <code>templates</code> 目录下，创建或编辑部署应用所需的 YAML 文件（如 Deployment、Service、Ingress 等）。</li><li>在 YAML 文件中，可以使用变量引用经常变化的字段，支持从 <code>values.yaml</code> 文件中获取动态值，比如 <code>replicas: {{ .Values.replicas }}</code>。</li></ul></li></ul><h5 id="Chart-的构建安装"><a href="#Chart-的构建安装" class="headerlink" title="Chart 的构建安装"></a>Chart 的构建安装</h5><blockquote><p>(1) 构建自定义的 Chart</p></blockquote><ul><li>创建一个 Chart，自动生成 Chart 的模板文件（YAML 配置文件）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm create nginx</span><br></pre></td></tr></tbody></table></figure><ul><li>查看自动生成的目录结构 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tree nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">nginx/</span><br><span class="line">├── charts</span><br><span class="line">├── Chart.yaml</span><br><span class="line">├── templates</span><br><span class="line">│&nbsp;&nbsp; ├── deployment.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── _helpers.tpl</span><br><span class="line">│&nbsp;&nbsp; ├── hpa.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── ingress.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── NOTES.txt</span><br><span class="line">│&nbsp;&nbsp; ├── serviceaccount.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── service.yaml</span><br><span class="line">│&nbsp;&nbsp; └── tests</span><br><span class="line">│&nbsp;&nbsp;     └── test-connection.yaml</span><br><span class="line">└── values.yaml</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>文件 / 目录名</th><th>类型</th><th>描述</th><th>主要作用</th></tr></thead><tbody><tr><td><code>charts/</code></td><td>目录</td><td>存放 Chart 依赖的所有子 Chart</td><td> 管理当前 Chart 所依赖的其他 Chart，支持复杂的应用依赖关系</td></tr><tr><td><code>Chart.yaml</code></td><td>文件</td><td>描述 Chart 的基本信息</td><td>定义 Chart 的名称、描述、版本、依赖关系等元数据</td></tr><tr><td><code>values.yaml</code></td><td>文件</td><td>存储模板文件中使用的变量值</td><td>提供用户可配置的参数默认值，支持部署时的自定义配置</td></tr><tr><td><code>templates/</code></td><td>目录</td><td>存放所有 YAML 模板文件</td><td>包含 Kubernetes 资源清单模板，如 Deployment、Service、ConfigMap 等</td></tr><tr><td><code>NOTES.txt</code></td><td>文件</td><td>介绍 Chart 的帮助信息</td><td>在 <code>helm install</code> 部署后展示给用户，包含使用指南和默认设置说明</td></tr><tr><td><code>_helpers.tpl</code></td><td>文件</td><td>放置模板助手的地方</td><td>定义可以在整个 Chart 中重复使用的模板片段或函数，提高模板代码的复用性和可维护性</td></tr></tbody></table><ul><li>删除自动生成的 Chart 模板文件（可选步骤）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm<span class="params"> -rf</span> nginx/templates/*</span><br></pre></td></tr></tbody></table></figure><ul><li>编辑 Chart 的 <code>Chart.yaml</code> 文件，定义 Chart 的名称、描述、版本等信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/Chart.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">v2</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">A</span> <span class="string">custom</span> <span class="string">Helm</span> <span class="string">chart</span> <span class="string">for</span> <span class="string">nginx</span> <span class="string">application</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">application</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">1.0</span><span class="number">.0</span></span><br><span class="line"><span class="attr">appVersion:</span> <span class="number">1.15</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或编辑 Chart 的模板文件 <code>deployment.yaml</code>，定义要创建的 Deployment</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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">apiVersion: apps/v1</span><br><span class="line">kind: Deployment</span><br><span class="line">metadata:</span><br><span class="line">  name: nginx-deploy</span><br><span class="line">spec:</span><br><span class="line">  replicas: 1</span><br><span class="line">  selector:</span><br><span class="line">    matchLabels:</span><br><span class="line">      app: nginx-pod</span><br><span class="line">  template:</span><br><span class="line">    metadata:</span><br><span class="line">      labels:</span><br><span class="line">        app: nginx-pod</span><br><span class="line">    spec:</span><br><span class="line">      containers:</span><br><span class="line">        - name: nginx</span><br><span class="line">          image: nginx:1.15</span><br><span class="line">          ports:</span><br><span class="line">            - containerPort: 80</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或编辑 Chart 的模板文件 <code>service.yaml</code>，定义要创建的 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/templates/service.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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">apiVersion: v1</span><br><span class="line">kind: Service</span><br><span class="line">metadata:</span><br><span class="line">  labels:</span><br><span class="line">    app: nginx-pod</span><br><span class="line">  name: nginx-svc</span><br><span class="line">spec:</span><br><span class="line">  ports:</span><br><span class="line">  - port: 80</span><br><span class="line">    protocol: TCP</span><br><span class="line">    targetPort: 80</span><br><span class="line">  selector:</span><br><span class="line">    app: nginx-pod</span><br><span class="line">  <span class="built_in">type</span>: NodePort</span><br></pre></td></tr></tbody></table></figure><blockquote><p>(2) 安装自定义的 Chart</p></blockquote><ul><li>安装自定义的 Chart</li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm install web nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME: web</span><br><span class="line">LAST DEPLOYED: Wed Oct 13 21:10:20 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 1</span><br><span class="line">TEST SUITE: None</span><br></pre></td></tr></tbody></table></figure><ul><li>还可以将自定义的 Chart 打包成 <code>.tgz</code> 压缩包，共享给别人使用 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm package nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nginx-1.0.0.tgz</span><br></pre></td></tr></tbody></table></figure><ul><li>查看实际的 Chart 模板被渲染过后的 YAML 资源文件 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm get manifest web</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># Source: nginx/templates/service.yaml</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</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">nginx-pod</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-svc</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">    <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">    <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-pod</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># Source: nginx/templates/deployment.yaml</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-deploy</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-pod</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">nginx-pod</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p>(3) 验证自定义的 Chart</p></blockquote><ul><li>查看发布（Release）的列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm list</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAMENAMESPACEREVISIONUPDATED                                STATUS  CHART      APP VERSION</span><br><span class="line">web default  1       2025-10-13 21:10:20.593922287 +0800 CSTdeployednginx-1.0.01.15      </span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                            READY   STATUS    RESTARTS   AGE</span><br><span class="line">nginx-deploy-85b7dd6b6d-2dswj   1/1     Running   0          104s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes   ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        86d</span><br><span class="line">nginx-svc    NodePort    10.0.0.150   &lt;none&gt;        80:32377/TCP   2m20s</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">最后通过任意一个集群节点的 IP 与 Service 对外暴露的端口（比如 `http://192.168.2.191:32377`），就可以在 Kubernetes 集群外部通过浏览器访问 Nginx 的首页面</span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-的版本升级"><a href="#Chart-的版本升级" class="headerlink" title="Chart 的版本升级"></a>Chart 的版本升级</h5><ul><li>在 Chart 安装（发布）后，如果 Chart 的模板文件发生了变更，可以执行以下命令对已部署的应用进行升级 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里 web 是 Release 的名称</span></span><br><span class="line">helm upgrade web nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Release "web" has been upgraded. Happy Helming!</span><br><span class="line">NAME: web</span><br><span class="line">LAST DEPLOYED: Wed Oct 13 21:37:23 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 2</span><br><span class="line">TEST SUITE: None</span><br></pre></td></tr></tbody></table></figure><ul><li>或者使用 <code>-</code> 参数进行升级（指定包含覆盖配置信息的 YAML 文件）</li></ul><figure class="highlight shell"><table><tbody><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"># 这里 web 是 Release 的名称</span></span><br><span class="line">helm upgrade<span class="params"> -f</span> values.yaml web nginx/</span><br></pre></td></tr></tbody></table></figure><ul><li>或者使用 <code>--set</code> 参数进行升级（直接传入参数值）</li></ul><figure class="highlight shell"><table><tbody><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"># 这里 web 是 Release 的名称</span></span><br><span class="line">helm upgrade<span class="params"> --set</span> imageTag=1.17 web nginx/</span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-的版本回滚"><a href="#Chart-的版本回滚" class="headerlink" title="Chart 的版本回滚"></a>Chart 的版本回滚</h5><div class="admonition note"><p class="admonition-title">提示</p><p>在 Chart 安装（发布）后，如果没有达到预期的效果，则可以使用 <code>helm rollback</code> 将 Release 回滚到之前的版本。</p></div><ul><li>查看 Release 的列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm list</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAMENAMESPACEREVISIONUPDATED                                STATUS  CHART      APP VERSION</span><br><span class="line">web default  1       2025-10-13 21:10:20.593922287 +0800 CSTdeployednginx-1.0.01.15      </span><br></pre></td></tr></tbody></table></figure><ul><li>查看指定 Release 的历史版本 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm <span class="built_in">history</span> web</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">REVISIONUPDATED                 STATUS    CHART      APP VERSIONDESCRIPTION     </span><br><span class="line">1       Wed Oct 13 21:10:20 2025supersedednginx-1.0.01.15       Install complete</span><br><span class="line">2       Wed Oct 13 21:23:26 2025deployed  nginx-1.0.01.15       Upgrade complete</span><br></pre></td></tr></tbody></table></figure><ul><li>将 Release 回滚到指定的历史版本 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称，1 是要 Release 回滚到的历史版本号</span></span><br><span class="line">helm rollback web 1</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Release 的版本是否成功回滚 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm status web</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME: web</span><br><span class="line">LAST DEPLOYED: Wed Oct 13 21:31:56 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 3</span><br><span class="line">TEST SUITE: None</span><br></pre></td></tr></tbody></table></figure><div class="admonition note"><p class="admonition-title">推送 Chart 到仓库</p><p>对于自定义的 Chart，除了可以打包成 <code>.tgz</code> 压缩包，还可以将其推送到 Chart 仓库（如 Harbor、ChartMuseum 等），详细的推送步骤可以参考网上的资料，这里不再累述。</p></div><h4 id="Helm-中-Chart-模板的使用"><a href="#Helm-中-Chart-模板的使用" class="headerlink" title="Helm 中 Chart 模板的使用"></a>Helm 中 Chart 模板的使用</h4><h5 id="Chart-模板的简单介绍"><a href="#Chart-模板的简单介绍" class="headerlink" title="Chart 模板的简单介绍"></a>Chart 模板的简单介绍</h5><p>Helm 的核心在于模板化的 Kubernetes Manifests 文件，这些模板本质上是基于 Go Template 的模板文件。在原生 Go 模板语法的基础上，Helm 还扩展了许多功能，例如：</p><ul><li>自定义的元数据定义（如 <code>Chart.yaml</code>）</li><li>内置函数库和模板函数</li><li>类似编程语言的控制语句（如条件判断、循环、管道等）</li></ul><p>这些扩展使模板具备了强大的灵活性和复用性。为了将用户的具体配置与模板结合，Helm 使用 <code>values.yaml</code> 文件来提供参数化的配置数据。</p><div class="admonition note"><p class="admonition-title">提示</p><p>模板文件与 <code>values</code> 文件的结合，这就是 Helm Chart 的核心机制 —— 通过模板化 + 参数化的方式，实现 Kubernetes 应用的灵活部署与管理。</p></div><h5 id="Chart-模板的使用案例"><a href="#Chart-模板的使用案例" class="headerlink" title="Chart 模板的使用案例"></a>Chart 模板的使用案例</h5><blockquote><p>(1) 构建自定义的 Chart</p></blockquote><ul><li>创建一个 Chart，自动生成 Chart 的模板文件（YAML 配置文件）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm create nginx</span><br></pre></td></tr></tbody></table></figure><ul><li>查看自动生成的目录结构 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tree nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">nginx/</span><br><span class="line">├── charts</span><br><span class="line">├── Chart.yaml</span><br><span class="line">├── templates</span><br><span class="line">│&nbsp;&nbsp; ├── deployment.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── _helpers.tpl</span><br><span class="line">│&nbsp;&nbsp; ├── hpa.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── ingress.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── NOTES.txt</span><br><span class="line">│&nbsp;&nbsp; ├── serviceaccount.yaml</span><br><span class="line">│&nbsp;&nbsp; ├── service.yaml</span><br><span class="line">│&nbsp;&nbsp; └── tests</span><br><span class="line">│&nbsp;&nbsp;     └── test-connection.yaml</span><br><span class="line">└── values.yaml</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>文件 / 目录名</th><th>类型</th><th>描述</th><th>主要作用</th></tr></thead><tbody><tr><td><code>charts/</code></td><td>目录</td><td>存放 Chart 依赖的所有子 Chart</td><td> 管理当前 Chart 所依赖的其他 Chart，支持复杂的应用依赖关系</td></tr><tr><td><code>Chart.yaml</code></td><td>文件</td><td>描述 Chart 的基本信息</td><td>定义 Chart 的名称、描述、版本、依赖关系等元数据</td></tr><tr><td><code>values.yaml</code></td><td>文件</td><td>存储模板文件中使用的变量值</td><td>提供用户可配置的参数默认值，支持部署时的自定义配置</td></tr><tr><td><code>templates/</code></td><td>目录</td><td>存放所有 YAML 模板文件</td><td>包含 Kubernetes 资源清单模板，如 Deployment、Service、ConfigMap 等</td></tr><tr><td><code>NOTES.txt</code></td><td>文件</td><td>介绍 Chart 的帮助信息</td><td>在 <code>helm install</code> 部署后展示给用户，包含使用指南和默认设置说明</td></tr><tr><td><code>_helpers.tpl</code></td><td>文件</td><td>放置模板助手的地方</td><td>定义可以在整个 Chart 中重复使用的模板片段或函数，提高模板代码的复用性和可维护性</td></tr></tbody></table><ul><li>删除自动生成的 Chart 模板文件（可选步骤）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm<span class="params"> -rf</span> nginx/templates/*</span><br></pre></td></tr></tbody></table></figure><ul><li>编辑 Chart 的 <code>Chart.yaml</code> 文件，定义 Chart 的名称、描述、版本等信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/Chart.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">apiVersion: v2</span><br><span class="line">name: nginx</span><br><span class="line">description: A Helm chart for Nginx</span><br><span class="line">type: application</span><br><span class="line">version: 0.1.0</span><br><span class="line">appVersion: 1.15</span><br></pre></td></tr></tbody></table></figure><ul><li>编辑 Chart 的 <code>values.yaml</code> 文件，定义全局的变量值 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">image: nginx</span><br><span class="line">tag: 1.15</span><br><span class="line">replicas: 3</span><br><span class="line">serviceport: 80</span><br><span class="line">targetport: 80</span><br><span class="line">containerPort: 80</span><br><span class="line">label: nginx-app</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或编辑 Chart 的模板文件 <code>NOTES.txt</code>，定义 Chart 的帮助信息（使用指南）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/templates/NOTES.txt</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Get service expose port: `kubectl get svc`</span><br><span class="line">Access nginx by: `http://&lt;node_ip&gt;:&lt;service_port&gt;`</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或编辑 Chart 的模板文件 <code>deployment.yaml</code>，定义要创建的 Deployment</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">.Release.Name</span> }}</span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> {{ <span class="string">.Values.label</span> }}</span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> {{ <span class="string">.Values.replicas</span> }}</span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> {{ <span class="string">.Values.label</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">.Values.label</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> {{ <span class="string">.Values.image</span> }}<span class="string">:{{</span> <span class="string">.Values.tag</span> <span class="string">}}</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> {{ <span class="string">.Values.containerPort</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或编辑 Chart 的模板文件 <code>service.yaml</code>，定义要创建的 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/templates/service.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> {{ <span class="string">.Release.Name</span> }}</span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> {{ <span class="string">.Values.label</span> }}</span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> {{ <span class="string">.Values.label</span> }}</span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> {{ <span class="string">.Values.serviceport</span> }}</span><br><span class="line">      <span class="attr">targetPort:</span> {{ <span class="string">.Values.targetport</span> }}</span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p>(2) 安装自定义的 Chart</p></blockquote><ul><li>安装自定义的 Chart</li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm install web nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME: web</span><br><span class="line">LAST DEPLOYED: Wed Oct 13 17:39:32 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 1</span><br><span class="line">TEST SUITE: None</span><br><span class="line">NOTES:</span><br><span class="line">Get service expose port: `kubectl get svc`</span><br><span class="line">Access nginx by: `http://&lt;node_ip&gt;:&lt;service_port&gt;`</span><br></pre></td></tr></tbody></table></figure><ul><li>查看实际的 Chart 模板被渲染过后的 YAML 资源文件 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm get manifest web</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="meta">---</span></span><br><span class="line"><span class="comment"># Source: nginx/templates/service.yaml</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># Source: nginx/templates/deployment.yaml</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p>(3) 验证自定义的 Chart</p></blockquote><ul><li>查看发布（Release）的列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm list</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAMENAMESPACEREVISIONUPDATED                                STATUS  CHART      APP VERSION</span><br><span class="line">web default  1       2025-10-29 17:39:32.267654796 +0800 CSTdeployednginx-0.1.01.15  </span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                   READY   STATUS    RESTARTS   AGE</span><br><span class="line">web-766795cc8b-6knwm   1/1     Running   0          100s</span><br><span class="line">web-766795cc8b-7nlr4   1/1     Running   0          100s</span><br><span class="line">web-766795cc8b-q4h8q   1/1     Running   0          100s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes   ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        86d</span><br><span class="line">web          NodePort    10.0.0.197   &lt;none&gt;        80:30654/TCP   2m13s</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">最后通过任意一个集群节点的 IP 与 Service 对外暴露的端口（比如 `http://192.168.2.191:30654`），就可以在 Kubernetes 集群外部通过浏览器访问 Nginx 的首页面</span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-模板的调试技巧"><a href="#Chart-模板的调试技巧" class="headerlink" title="Chart 模板的调试技巧"></a>Chart 模板的调试技巧</h5><ul><li>Helm 提供了 <code>--dry-run</code> 和 <code>--debug</code> 调试参数，可用于在执行 <code>helm install</code> 命令之前，验证 Chart 模板文件的正确性。</li><li>当 <code>helm install</code> 命令加上这两个参数后，Helm 会将模板文件与对应的 <code>values.yaml</code> 文件进行渲染，并打印出生成的 YAML 资源清单，而不会实际部署任何 Release。</li><li>比如，调试 <a href="/posts/2ca57d7f.html#Chart-%E6%A8%A1%E6%9D%BF%E7%9A%84%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B">上面案例</a> 中自定义的 Chart 包，可以使用以下命令：</li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm install web<span class="params"> --dry</span>-run nginx/</span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-模板的内置对象"><a href="#Chart-模板的内置对象" class="headerlink" title="Chart 模板的内置对象"></a>Chart 模板的内置对象</h5><p>上面使用 <code>{{.Release.Name}}</code> 将 Release 的名称插入到 Chart 模板中。这里的 <code>Release</code> 就是 Helm 的内置对象，下面是一些常用的内置对象：</p><table><thead><tr><th>Helm 内置对象</th><th>描述</th></tr></thead><tbody><tr><td><code>Release.Name</code></td><td>Release 名称</td></tr><tr><td><code>Release.Namespace</code></td><td>Release 命名空间</td></tr><tr><td><code>Release.Service</code></td><td>Release 服务的名称</td></tr><tr><td><code>Release.Revision</code></td><td>Release 修订版本号，从 1 开始累加</td></tr></tbody></table><h5 id="Chart-模板的-Values-对象"><a href="#Chart-模板的-Values-对象" class="headerlink" title="Chart 模板的 Values 对象"></a>Chart 模板的 Values 对象</h5><p>Values 对象用于为 Chart 模板文件提供参数值，其来源主要有以下四个：</p><ul><li>(1) Chart 包中的 <code>values.yaml</code> 文件</li><li> (2) 父 Chart 包中的 <code>values.yaml</code> 文件</li><li> (3) 通过 <code>helm install</code> 或 <code>helm upgrade</code> 命令使用 <code>-f</code> 或 <code>--values</code> 参数传入的自定义 YAML 配置文件</li><li> (4) 通过 <code>helm install</code> 或 <code>helm upgrade</code> 命令使用 <code>--set</code> 参数直接传入的值</li></ul><div class="admonition warning"><p class="admonition-title">参数值不同来源的优先级关系</p><p>Chart 参数值的优先级遵循一定的覆盖顺序（优先级）：<code>Chart 包中的 values.yaml 文件</code> &lt; <code>用户通过 -f 参数自定义的 YAML 配置文件</code> &lt; <code>用户通过 --set 参数传入的值</code>。</p></div><ul><li>举个例子，通过 <code>--set</code> 参数更新 Chart（比如，Pod 的副本数量）</li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm upgrade web<span class="params"> --set</span> replicas=5 nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Release "web" has been upgraded. Happy Helming!</span><br><span class="line">NAME: web</span><br><span class="line">LAST DEPLOYED: Wed Oct 13 18:04:34 2025</span><br><span class="line">NAMESPACE: default</span><br><span class="line">STATUS: deployed</span><br><span class="line">REVISION: 2</span><br><span class="line">TEST SUITE: None</span><br><span class="line">NOTES:</span><br><span class="line">Get service expose port: `kubectl get svc`</span><br><span class="line">Access nginx by: `http://&lt;node_ip&gt;:&lt;service_port&gt;`</span><br></pre></td></tr></tbody></table></figure><ul><li>查看指定 Release 的历史版本 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm <span class="built_in">history</span> web</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">REVISIONUPDATED                 STATUS    CHART      APP VERSIONDESCRIPTION     </span><br><span class="line">1       Wed Oct 13 17:39:32 2025supersedednginx-0.1.01.15       Install complete</span><br><span class="line">2       Wed Oct 13 18:04:34 2025deployed  nginx-0.1.01.15       Upgrade complete</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                   READY   STATUS    RESTARTS   AGE</span><br><span class="line">web-766795cc8b-6knwm   1/1     Running   0          28m</span><br><span class="line">web-766795cc8b-7nlr4   1/1     Running   0          28m</span><br><span class="line">web-766795cc8b-lpr27   1/1     Running   0          3m57s</span><br><span class="line">web-766795cc8b-q4h8q   1/1     Running   0          28m</span><br><span class="line">web-766795cc8b-vpdh4   1/1     Running   0          3m57s</span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-模板的函数与管道"><a href="#Chart-模板的函数与管道" class="headerlink" title="Chart 模板的函数与管道"></a>Chart 模板的函数与管道</h5><blockquote><p>模板函数与管道的简单介绍</p></blockquote><p>前面讲的内容，其实就是将参数值传给 Go 模板引擎进行渲染，模板引擎还支持通过模板函数和管道对拿到数据（参数值）进行二次处理。Helm 支持的模板函数和管道如下所示：</p><ul><li><p>默认值处理</p><ul><li><code>default</code><ul><li>为不存在或空的值提供默认值</li><li>调用语法：<code>{{ .Values.name | default "nginx" }}</code></li></ul></li></ul></li><li><p>大小写转换</p><ul><li><code>upper</code><ul><li>将字符串转换为大写</li><li>调用语法：<code>{{ upper .Values.resources }}</code></li></ul></li><li><code>lower</code><ul><li>将字符串转换为小写</li><li>调用语法：<code>{{ lower .Values.resources }}</code></li></ul></li><li><code>title</code><ul><li>将首字母大写</li><li>调用语法：<code>{{ title .Values.resources }}</code></li></ul></li></ul></li><li><p>布尔、数字、长度</p><ul><li><code>bool</code><ul><li>将值转换为布尔</li><li>调用语法：<code>{{ bool .Values.featureFlag }}</code></li></ul></li><li><code>int</code><ul><li>将值转换为整数</li><li>调用语法：<code>{{ int .Values.replicaCount }}</code></li></ul></li><li><code>float</code><ul><li>将值转换为浮点数</li><li>调用语法：<code>{{ float .Values.cpus }}</code></li></ul></li><li><code>len</code><ul><li>获取列表（数组）、字典或字符串的长度</li><li>调用语法：<code>{{ len .Values.env }}</code></li></ul></li></ul></li><li><p>字符串转换、拼接与替换</p><ul><li><code>quote</code><ul><li>将参数值转换为带双引号的字符串</li><li>调用语法：<code>{{ quote .Values.label }}</code></li></ul></li><li><code>cat</code><ul><li>拼接多个字符串</li><li>调用语法：<code>{{ cat .Release.Name "-" .Chart.Name }}</code></li></ul></li><li><code>replace</code><ul><li>替换字符串</li><li>调用语法：<code>{{ .Values.name | replace "_" "-" }}</code></li></ul></li><li><code>trim</code><ul><li>去除字符串首尾空格</li><li>调用语法：<code>{{ .Values.name | trim " " }}</code></li></ul></li><li><code>trimAll</code><ul><li>去除指定字符</li><li>调用语法：<code>{{ .Values.name | trimAll "-" }}</code></li></ul></li></ul></li><li><p>加空格与缩进</p><ul><li><code>indent</code><ul><li>每行前加指定空格数</li><li>调用语法：<code>{{ .Values.resources | indent 10 }}</code></li></ul></li><li><code>nindent</code><ul><li>每行缩进并加换行</li><li>调用语法：<code>{{ .Values.resources | nindent 10 }}</code></li></ul></li></ul></li><li><p>对象转换</p><ul><li><code>toYaml</code><ul><li>将对象转换为 YAML</li><li> 调用语法：<code>{{ .Values.resources | toYaml }}</code></li></ul></li><li><code>toJson</code><ul><li>将对象转换为 JSON</li><li> 调用语法：<code>{{ .Values.config | toJson | quote }}</code></li></ul></li></ul></li><li><p>条件 / 验证</p><ul><li><code>hasKey</code><ul><li>判断字典中是否存在某个键</li><li>调用语法：<code>{{- if hasKey .Values.env "JAVA_HOME" }}</code></li></ul></li><li><code>required</code><ul><li>必填参数验证，参数不存在时报错</li><li>调用语法：<code>{{ required "image.repository is required" .Values.image.repository }}</code></li></ul></li></ul></li></ul><blockquote><p>模板函数 quote 的使用案例</p></blockquote><ul><li>在 <a href="/posts/2ca57d7f.html#Chart-%E6%A8%A1%E6%9D%BF%E7%9A%84%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B">上面案例</a> 的基础上，通过模板函数 <code>quote</code> 将从 <code>.Values</code> 中读取到的参数值转换成字符串 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim nginx/templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">.Release.Name</span> }}</span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> {{ <span class="string">quote</span> <span class="string">.Values.label</span> }}</span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> {{ <span class="string">.Values.replicas</span> }}</span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> {{ <span class="string">quote</span> <span class="string">.Values.label</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">quote</span> <span class="string">.Values.label</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> {{ <span class="string">.Values.image</span> }}<span class="string">:{{</span> <span class="string">.Values.tag</span> <span class="string">}}</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> {{ <span class="string">.Values.containerPort</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>将模板文件与对应的 <code>values.yaml</code> 文件进行渲染，并打印出生成的 YAML 资源清单，不会实际部署任何 Release</li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm install web<span class="params"> --dry</span>-run nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="string">...(省略)</span></span><br><span class="line"></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">"nginx-app"</span>    <span class="comment"># 参数值已经被 quote 模板函数转换为字符串</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">"nginx-app"</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">"nginx-app"</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-模板的流程控制语句"><a href="#Chart-模板的流程控制语句" class="headerlink" title="Chart 模板的流程控制语句"></a>Chart 模板的流程控制语句</h5><p>流程控制是为 Chart 模板提供了一种能力，满足更复杂的数据逻辑处理。Helm 模板语言提供以下流程控制语句：</p><ul><li><code>if/else</code>：条件块</li><li><code>with</code>：指定范围</li><li><code>range</code>：循环块</li></ul><h6 id="if-else"><a href="#if-else" class="headerlink" title="if/else"></a>if/else</h6><blockquote><p><strong>if/else 的简单介绍</strong></p></blockquote><ul><li><code>if/else</code> 块是用于在模板中有条件地包含文本块的方法，条件块的基本结构如下：</li></ul><figure class="highlight yaml"><table><tbody><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="string">if</span> <span class="string">PIPELINE</span> }}</span><br><span class="line"><span class="comment"># Do something</span></span><br><span class="line">{{ <span class="string">elseif</span> <span class="string">OTHER</span> <span class="string">PIPELINE</span> }}</span><br><span class="line"><span class="comment"># Do something else</span></span><br><span class="line">{{ <span class="string">else</span> }}</span><br><span class="line"><span class="comment"># Default case</span></span><br><span class="line">{{ <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>条件判断支持使用 <code>eq</code> 运算符来判断是否相等，除此之外，还支持 <code>ne</code>、<code>lt</code>、<code>gt</code>、<code>and</code>、<code>or</code> 等运算符，请注意数据类型。</li><li>条件判断就是判断条件是否为 <code>true</code>，如果值为以下几种情况之一则为 <code>false</code>：<ul><li>一个布尔类型的 <code>false</code></li><li>一个数字零</li><li>一个空的字符串</li><li>一个空的集合（<code>map</code>、<code>slice</code>、<code>tuple</code>、<code>dict</code>、<code>array</code>）</li></ul></li><li>除了上面的这些情况外，其他所有条件都为 <code>true</code>。</li></ul><blockquote><p><strong>if/else 的使用案例一，演示如何使用基本的条件判断</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">devops: k8s</span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx</span></span><br><span class="line">        {{ <span class="string">if</span> <span class="string">eq</span> <span class="string">.Values.devops</span> <span class="string">"k8s"</span> }}</span><br><span class="line">        <span class="attr">devops:</span> <span class="literal">true</span></span><br><span class="line">        {{ <span class="string">else</span> }}</span><br><span class="line">        <span class="attr">devops:</span> <span class="literal">false</span></span><br><span class="line">        {{ <span class="string">end</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight shell"><table><tbody><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"># 这里的 web 是 Release 的名称</span></span><br><span class="line">helm install web<span class="params"> --dry</span>-run nginx/</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx</span></span><br><span class="line">        </span><br><span class="line">        <span class="attr">devops:</span> <span class="literal">true</span></span><br><span class="line">        </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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>可以看到渲染出来结果会有多余的空行，这是因为当模板引擎渲染时，会将控制指令删除掉，所以之前占的位置也就空白了，需要使用 <code>{{- if ... }}</code> 的方式来消除空行 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx</span></span><br><span class="line">        {{<span class="bullet">-</span> <span class="string">if</span> <span class="string">eq</span> <span class="string">.Values.devops</span> <span class="string">"k8s"</span> }}</span><br><span class="line">        <span class="attr">devops:</span> <span class="literal">true</span></span><br><span class="line">        {{<span class="bullet">-</span> <span class="string">else</span> }}</span><br><span class="line">        <span class="attr">devops:</span> <span class="literal">false</span></span><br><span class="line">        {{<span class="bullet">-</span> <span class="string">end</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>如果使用 <code>-}}</code> 需谨慎，比如在上面的模板文件中：</li></ul><figure class="highlight yaml"><table><tbody><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="string">...(省略)</span></span><br><span class="line"></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line">  {{<span class="bullet">-</span> <span class="string">if</span> <span class="string">eq</span> <span class="string">.Values.devops</span> <span class="string">"k8s"</span> <span class="string">-</span>}}</span><br><span class="line">  <span class="bullet">-</span> <span class="attr">devops:</span> <span class="literal">true</span></span><br><span class="line">  {{<span class="bullet">-</span> <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>最终会渲染成下面这样子，因为 <code>-}}</code> 它会删除双方的换行符，导致模板文件渲染失败 </li></ul><figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">env:- devops:</span> <span class="literal">true</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>if/else 的使用案例二，演示如何判断一个数组是否为空</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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">resources:</span> {}</span><br><span class="line">  <span class="comment"># limits:</span></span><br><span class="line">  <span class="comment">#   cpu: "100m"</span></span><br><span class="line">  <span class="comment">#   memory: "128Mi"</span></span><br><span class="line">  <span class="comment"># requests:</span></span><br><span class="line">  <span class="comment">#   cpu: "100m"</span></span><br><span class="line">  <span class="comment">#   memory: "128Mi"</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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">apiVersion: apps/v1</span><br><span class="line">kind: Deployment</span><br><span class="line">metadata:</span><br><span class="line">  name: web</span><br><span class="line">  labels:</span><br><span class="line">    app: nginx-app</span><br><span class="line">spec:</span><br><span class="line">  replicas: 3</span><br><span class="line">  selector:</span><br><span class="line">    matchLabels:</span><br><span class="line">      app: nginx-app</span><br><span class="line">  template:</span><br><span class="line">    metadata:</span><br><span class="line">      labels:</span><br><span class="line">        app: nginx</span><br><span class="line">    spec:</span><br><span class="line">      containers:</span><br><span class="line">        - name: nginx</span><br><span class="line">          image: nginx:1.16</span><br><span class="line">          {{- <span class="keyword">if</span> .Values.resources }}</span><br><span class="line">          resources:</span><br><span class="line">{{ toYaml .Values.resources | indent 12 }}</span><br><span class="line">          {{- end }}</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>if/else 的使用案例三，演示如何判断一个布尔值是否为 true</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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">service:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">ClusterIP</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line"><span class="attr">ingress:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">host:</span> <span class="string">example.ingress.com</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>ingress.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/ingress.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="bullet">-</span> <span class="string">if</span> <span class="string">.Values.ingress.enabled</span> }}</span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">networking.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Ingress</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> {{ <span class="string">.Release.Name</span> }}<span class="string">-ingress</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">rules:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">host:</span> {{ <span class="string">.Values.ingress.host</span> }}</span><br><span class="line">      <span class="attr">http:</span></span><br><span class="line">        <span class="attr">paths:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">path:</span> <span class="string">/</span></span><br><span class="line">            <span class="attr">pathType:</span> <span class="string">Prefix</span></span><br><span class="line">            <span class="attr">backend:</span></span><br><span class="line">              <span class="attr">service:</span></span><br><span class="line">                <span class="attr">name:</span> {{ <span class="string">.Release.Name</span> }}</span><br><span class="line">                <span class="attr">port:</span></span><br><span class="line">                  <span class="attr">number:</span> {{ <span class="string">.Values.service.port</span> }}</span><br><span class="line">{{<span class="bullet">-</span> <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><h6 id="range"><a href="#range" class="headerlink" title="range"></a>range</h6><p>在 Helm 模板语言中，可以使用 <code>range</code> 语句来进行循环操作。</p><blockquote><p><strong>range 的使用案例，演示如何通过 <code>range</code> 读取一个数组的所有元素值</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">list:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="number">1</span></span><br><span class="line">  <span class="bullet">-</span> <span class="number">2</span></span><br><span class="line">  <span class="bullet">-</span> <span class="number">3</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下（在循环内部使用的是一个 <code>.</code> 符号，这是因为当前的作用域就在当前循环内，这个 <code>.</code> 符号用于表示当前读取到的元素）</li></ul><figure class="highlight shell"><table><tbody><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">apiVersion: v1</span><br><span class="line">kind: ConfigMap</span><br><span class="line">metadata:</span><br><span class="line">  name: {{ .Release.Name }}</span><br><span class="line">data:</span><br><span class="line">  list: |</span><br><span class="line">  {{- range .Values.list }}</span><br><span class="line">    {{ . }}</span><br><span class="line">  {{- end }}</span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight yaml"><table><tbody><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">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">list:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    1</span></span><br><span class="line"><span class="string">    2</span></span><br><span class="line"><span class="string">    3</span></span><br></pre></td></tr></tbody></table></figure><h6 id="with"><a href="#with" class="headerlink" title="with"></a>with</h6><blockquote><p><strong>with 的简单介绍</strong></p></blockquote><ul><li><code>with</code> 语句就可以用来控制变量的作用域范围。</li><li><code>with</code> 的使用语法和一个简单的 <code>if</code> 语句比较类似：</li></ul><figure class="highlight yaml"><table><tbody><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="string">with</span> <span class="string">PIPELINE</span> }}</span><br><span class="line">  <span class="comment"># restricted scope</span></span><br><span class="line">{{ <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li><strong>值得注意的是，在 <code>with</code> 语句块内不能使用内置对象（比如 <code>.Release.Name</code>），否则模板渲染会失败，可以将内置对象赋值给一个变量来解决该问题。</strong></li><li><code>with</code> 语句可以允许将当前范围 <code>.</code> 设置为特定的对象，比如前面一直使用的 <code>.Values.label</code>，就是使用 <code>with</code> 语句来将当前范围 <code>.</code> 指向 <code>.Values.label</code>。</li><li>还记得之前的 <code>{{ .Release.xxx }}</code> 或者 <code>{{ .Values.xxx }}</code> 吗？其中的 <code>.</code> 符号就是表示对当前范围的引用，<code>.Values</code> 就是告诉模板引擎在当前范围中查找 <code>Values</code> 对象的值。</li></ul><blockquote><p><strong>with 的使用案例，演示如何使用 with 来控制变量作用域</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">nodeSelector:</span></span><br><span class="line">  <span class="attr">team:</span> <span class="string">python</span></span><br><span class="line">  <span class="attr">gpu:</span> <span class="literal">yes</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">.Release.Name</span> }}<span class="string">-deployment</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</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">nginx</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      {{<span class="bullet">-</span> <span class="string">with</span> <span class="string">.Values.nodeSelector</span> }}</span><br><span class="line">      <span class="attr">nodeSelector:</span></span><br><span class="line">        <span class="attr">team:</span> {{ <span class="string">.team</span> }}</span><br><span class="line">        <span class="attr">gpu:</span> {{ <span class="string">.gpu</span> }}</span><br><span class="line">      {{<span class="bullet">-</span> <span class="string">end</span> }}</span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.16</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web-deployment</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</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">nginx</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">nodeSelector:</span></span><br><span class="line">        <span class="attr">team:</span> <span class="string">python</span></span><br><span class="line">        <span class="attr">gpu:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.16</span></span><br></pre></td></tr></tbody></table></figure><ul><li>上面的配置内容还可以继续优化一下 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">.Release.Name</span> }}<span class="string">-deployment</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</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">nginx</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      {{<span class="bullet">-</span> <span class="string">with</span> <span class="string">.Values.nodeSelector</span> }}</span><br><span class="line">      <span class="attr">nodeSelector:</span></span><br><span class="line">        {{ <span class="string">toYaml</span> <span class="string">.</span> <span class="string">|</span> <span class="string">nindent</span> <span class="number">8</span> }}</span><br><span class="line">      {{<span class="bullet">-</span> <span class="string">end</span> }}</span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.16</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-模板的变量"><a href="#Chart-模板的变量" class="headerlink" title="Chart 模板的变量"></a>Chart 模板的变量</h5><p>在 Chart 模板中，使用变量的场景不多，但下面将看到如何使用变量来简化模板代码，并更好地使用 <code>with</code> 和 <code>range</code> 语句。</p><blockquote><p><strong>变量的使用案例一，演示如何通过变量获取数组的键值</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">env:</span></span><br><span class="line">  <span class="attr">NAME:</span> <span class="string">"gateway"</span></span><br><span class="line">  <span class="attr">JAVA_OPTS:</span> <span class="string">"-Xmx2G"</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下（在 <code>range</code> 循环中，使用 <code>$k</code> 和 <code>$v</code> 这两个变量来接收后面数组循环的键和值）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">gateway-deploy</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">gateway-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">gateway-app</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">gateway-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">gateway</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">gateway:2.13</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            {{<span class="bullet">-</span> <span class="string">range</span> <span class="string">$k</span>, <span class="string">$v</span> <span class="string">:=</span> <span class="string">.Values.env</span> }}</span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> {{ <span class="string">$k</span> }}</span><br><span class="line">              <span class="attr">value:</span> {{ <span class="string">$v</span> <span class="string">|</span> <span class="string">quote</span> }}</span><br><span class="line">            {{<span class="bullet">-</span> <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">gateway-deploy</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">gateway-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">gateway-app</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">gateway-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">gateway</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">gateway:2.13</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">NAME</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">"gateway"</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">JAVA_OPTS</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">"-Xmx2G"</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>变量的使用案例二，演示如何解决在 <code>with</code> 语句块中不能使用内置对象的问题</strong></p></blockquote><ul><li>在 Chart 包中，<code>values.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat values.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">replicas:</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="attr">label:</span></span><br><span class="line">  <span class="attr">project:</span> <span class="string">my-project</span></span><br><span class="line">  <span class="attr">app:</span> <span class="string">nginx</span></span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下（模板文件渲染会报错，因为在 <code>with</code> 语句块内不能再使用内置对象，比如 <code>.Release.Name</code>）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">.Release.Name</span> }}<span class="string">-deploy</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> {{ <span class="string">.Values.replicas</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="bullet">-</span> <span class="string">with</span> <span class="string">.Values.label</span> }}</span><br><span class="line">        <span class="attr">project:</span> {{ <span class="string">.project</span> }}</span><br><span class="line">        <span class="attr">app:</span> {{ <span class="string">.app</span> }}</span><br><span class="line">        <span class="attr">release:</span> {{ <span class="string">.Release.Name</span> }}</span><br><span class="line">        {{<span class="bullet">-</span> <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>上面的模板内容会渲染失败，但可以将内置对象赋值给一个变量来解决该问题 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">.Release.Name</span> }}<span class="string">-deploy</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> {{ <span class="string">.Values.replicas</span> }}</span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> {{ <span class="string">.Values.label.app</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="bullet">-</span> <span class="string">$releaseName</span> <span class="string">:=</span> <span class="string">.Release.Name</span> <span class="string">-</span>}}</span><br><span class="line">        {{<span class="bullet">-</span> <span class="string">with</span> <span class="string">.Values.label</span> }}</span><br><span class="line">        <span class="attr">project:</span> {{ <span class="string">.project</span> }}</span><br><span class="line">        <span class="attr">app:</span> {{ <span class="string">.app</span> }}</span><br><span class="line">        <span class="attr">release:</span> {{ <span class="string">$releaseName</span> }}</span><br><span class="line">        {{<span class="bullet">-</span> <span class="string">end</span> }}</span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">web-deploy</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</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">project:</span> <span class="string">my-project</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">release:</span> <span class="string">web</span></span><br></pre></td></tr></tbody></table></figure><h5 id="Chart-模板的命名模板"><a href="#Chart-模板的命名模板" class="headerlink" title="Chart 模板的命名模板"></a>Chart 模板的命名模板</h5><blockquote><p><strong>命名模板的简单介绍</strong></p></blockquote><ul><li><p>命名模板</p><ul><li>用于在 Chart 中复用模板代码片段。</li><li>使用 <code>{{- define "templateName" }} ... {{- end }}</code> 定义模板。</li><li>使用 <code>{{ template "templateName" . }}</code> 引用模板，并传递上下文 <code>.</code>。</li></ul></li><li><p>公共模板文件</p><ul><li>Helm 中，<code>templates</code> 目录下以下划线 <code>_</code> 开头的文件（如 <code>_helpers.tpl</code>）都会被视作公共模板文件。</li><li>这些公共模板文件不会直接渲染成 Kubernetes 对象，而是用于存放命名模板或函数，供其他模板使用。</li></ul></li><li><p>使用注意事项</p><ul><li><code>template</code> 函数不能用于 Go 模板的管道，可以使用 <code>include</code> 语句来解决该问题。</li></ul></li></ul><blockquote><p><strong>命名模板的使用案例一，演示命名模板的基础使用</strong></p></blockquote><ul><li>在 Chart 包中，<code>_helpers.tpl</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/_helpers.tpl</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="bullet">-</span> <span class="string">define</span> <span class="string">"demo.fullname"</span> <span class="string">-</span>}}</span><br><span class="line">{{<span class="bullet">-</span> <span class="string">.Chart.Name</span> <span class="string">-</span>}}<span class="string">-{{</span> <span class="string">.Release.Name</span> <span class="string">}}</span></span><br><span class="line">{{<span class="bullet">-</span> <span class="string">end</span> <span class="string">-</span>}}</span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">template</span> <span class="string">"demo.fullname"</span> <span class="string">.</span> }}</span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>命名模板的使用案例二，演示如何解决 template 函数不能用于 Go 模板的管道的问题</strong></p></blockquote><ul><li>在 Chart 包中，<code>_helpers.tpl</code> 文件的内容如下 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/_helpers.tpl</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="bullet">-</span> <span class="string">define</span> <span class="string">"demo.fullname"</span> <span class="string">-</span>}}</span><br><span class="line">{{<span class="bullet">-</span> <span class="string">.Chart.Name</span> <span class="string">-</span>}}<span class="string">-{{</span> <span class="string">.Release.Name</span> <span class="string">}}</span></span><br><span class="line">{{<span class="bullet">-</span> <span class="string">end</span> <span class="string">-</span>}}</span><br><span class="line"></span><br><span class="line">{{<span class="bullet">-</span> <span class="string">define</span> <span class="string">"demo.labels"</span> <span class="string">-</span>}}</span><br><span class="line"><span class="attr">app:</span> {{ <span class="string">template</span> <span class="string">"demo.fullname"</span> <span class="string">.</span> }}</span><br><span class="line"><span class="attr">chart:</span> <span class="string">"<span class="template-variable">{{ .Chart.Name }}</span>-<span class="template-variable">{{ .Chart.Version }}</span>"</span></span><br><span class="line"><span class="attr">release:</span> <span class="string">"<span class="template-variable">{{ .Release.Name }}</span>"</span></span><br><span class="line">{{<span class="bullet">-</span> <span class="string">end</span> <span class="string">-</span>}}</span><br></pre></td></tr></tbody></table></figure><ul><li>在 Chart 包中，<code>deployment.yaml</code> 文件的内容如下（模板文件渲染会报错，因为 <code>template</code> 函数不能用于 Go 模板的管道）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat templates/deployment.yaml</span><br></pre></td></tr></tbody></table></figure><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">template</span> <span class="string">"demo.fullname"</span> <span class="string">.</span> }}</span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    {{<span class="bullet">-</span> <span class="string">template</span> <span class="string">"demo.labels"</span> <span class="string">.</span> <span class="string">|</span> <span class="string">nindent</span> <span class="number">4</span> }}</span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>上面的模板内容会渲染失败，但可以使用 <code>include</code> 语句来解决该问题 </li></ul><figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> {{ <span class="string">include</span> <span class="string">"demo.fullname"</span> <span class="string">.</span> }}</span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    {{<span class="bullet">-</span> <span class="string">include</span> <span class="string">"demo.labels"</span> <span class="string">.</span> <span class="string">|</span> <span class="string">nindent</span> <span class="number">4</span> }}</span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过模板引擎渲染后，会得到如下结果 </li></ul><figure class="highlight yaml"><table><tbody><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">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-web</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-web</span></span><br><span class="line">    <span class="attr">chart:</span> <span class="string">"nginx-0.1.0"</span></span><br><span class="line">    <span class="attr">release:</span> <span class="string">"web"</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-app</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">nginx-app</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="bullet">-</span> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之十</title>
    <link href="https://www.techgrow.cn/posts/6158b4d2.html"/>
    <id>https://www.techgrow.cn/posts/6158b4d2.html</id>
    <published>2025-09-13T13:12:19.000Z</published>
    <updated>2025-09-13T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><h3 id="部署集群性能监控平台"><a href="#部署集群性能监控平台" class="headerlink" title="部署集群性能监控平台"></a>部署集群性能监控平台</h3><p>开源软件 cAdvisor（Container Advisor）可用于监控所在节点的容器运行状态，当前已经被默认集成到 Kubernetes 的 Kubelet 组件内，默认使用 TCP <code>4194</code> 端口。在中小型规模容器集群中，通常使用 Prometheus + Grafana 来实现容器集群性能数据的采集、存储与展示。</p><h4 id="集群可监控的指标"><a href="#集群可监控的指标" class="headerlink" title="集群可监控的指标"></a>集群可监控的指标</h4><p>在 Kubernetes 集群中，可以监控的指标有以下这些：</p><ul><li><p>节点级（Node Metrics）：监控每个工作节点（Worker Node）的系统资源使用情况</p><ul><li>CPU 使用率 / 空闲率 / 负载（Load Average）</li><li>内存使用量 / 可用内存</li><li>磁盘使用量 / I/O 吞吐</li><li>网络流量（带宽、收发包量、错误包）</li><li>节点状态（Ready / NotReady）</li><li>节点文件系统使用率（根分区、容器存储路径）</li></ul></li><li><p>Pod / 容器级（Pod &amp; Container Metrics）：监控集群中每个 Pod 或容器的资源使用情况</p><ul><li>Pod 的 CPU 使用率 / 限额 / 请求</li><li> Pod 的内存使用量 / 限额 / 请求</li><li>容器重启次数</li><li>容器启动时间、运行时间</li><li>网络收发流量（in/out bytes）</li><li>文件系统使用（容器内存储卷）</li><li>容器状态（Running / Waiting / Terminated）</li></ul></li><li><p>Kubernetes 组件指标（Control Plane Metrics）：监控 K8s 控制面（Control Plane）自身的健康状态</p><ul><li>API Server 请求速率 / 延迟 / 错误率</li><li> Scheduler 调度延迟 / 排队任务数</li><li> Controller Manager 队列长度 / 事件处理速率</li><li> Etcd 存储延迟 / Leader 选举状态 / 写入吞吐</li></ul></li><li><p>服务与网络（Service &amp; Network Metrics）：监控 Service、Ingress、DNS、网络插件等组件</p><ul><li>Service 请求速率 / 成功率 / 延迟</li><li> Ingress 访问量 / 响应时间 / 4xx、5xx 错误率</li><li> DNS 查询速率 / 失败率（CoreDNS）</li><li>CNI 插件流量、丢包、延迟</li></ul></li><li><p>工作负载与资源对象状态（Workload Metrics）：反映 K8s 资源对象（如 Deployment、DaemonSet、Job 等）的运行健康度</p><ul><li>Deployment 可用副本数 / 期望副本数</li><li> ReplicaSet、StatefulSet 状态</li><li> Job 成功 / 失败次数</li><li> CronJob 最近执行时间</li><li> Namespace 级资源使用量</li><li> HPA（HorizontalPodAutoScaler）触发状态</li></ul></li><li><p>存储指标（Storage Metrics）：监控 PV、PVC、StorageClass 的使用与性能</p><ul><li>PV 容量使用率</li><li> PVC 绑定状态</li><li> I/O 延迟、读写吞吐</li><li>挂载错误 / 超时</li></ul></li><li><p>应用与业务指标（Application Metrics）: 通过 Prometheus Exporter 或 SDK 自定义的应用性能指标</p><ul><li>请求 QPS（请求数每秒）</li><li>错误率（Error Rate）</li><li>响应时间（Latency）</li><li>业务统计（订单数、任务完成数等）</li><li>自定义计数器 / 计时器 / 直方图（Histogram）</li></ul></li><li><p>告警与事件（Events &amp; Alerts）：用于监控异常行为与故障</p><ul><li>Pod CrashLoopBackOff</li><li> 节点不可用</li><li>资源超限（CPU / 内存）</li><li>Deployment 副本不足</li><li> PV 绑定失败 / 存储空间不足</li><li> API 请求超时 / 错误率过高</li></ul></li></ul><h4 id="集群性能监控方案"><a href="#集群性能监控方案" class="headerlink" title="集群性能监控方案"></a>集群性能监控方案</h4><blockquote><p>常见的 Kubernetes 集群性能监控方案</p></blockquote><ul><li><p>Heapster + InfluxDB + Grafana</p><ul><li>架构：Heapster + InfluxDB + Grafana</li><li> 功能：Heapster 汇聚各 Node 上 cAdvisor 的监控数据，存入 InfluxDB 后通过 Grafana 展示。</li><li>状态：已被 Kubernetes 官方弃用，自 Kubernetes <code>v1.13</code> 起不再维护。</li><li>适用场景：早期集群监控，仅作学习了解。</li></ul></li><li><p>Metrics Server</p><ul><li>角色：Heapster 的官方替代品。</li><li>功能：提供实时的资源用量指标（CPU、内存）给 <code>kubectl top</code>、Horizontal Pod Autoscaler（HPA）等使用。</li><li>限制：不存储历史数据，不提供可视化界面或持久化存储。</li><li>状态：Kubernetes 官方的核心组件。</li><li>适用场景：HPA 自动扩缩容、轻量实时监控。</li></ul></li><li><p>Prometheus + Grafana</p><ul><li>架构：Prometheus + Grafana</li><li> 功能：<ul><li>Prometheus = 数据收集 + 存储 + 告警。</li><li>Grafana = 数据展示 + 可视化分析界面。</li></ul></li><li>优点：<ul><li>Prometheus 会周期性自动拉取 kubelet、cAdvisor、kube-state-metrics、node-exporter 等监控指标</li><li>监控维度丰富（节点、容器、Pod、Service、集群状态）</li><li>数据查询灵活（PromQL）</li><li>支持历史数据存储</li></ul></li><li>状态：当前主流的开源监控方案。</li><li>适用场景：中小型或自建环境中的主力方案。</li></ul></li><li><p>Prometheus Operator + kube-prometheus-stack</p><ul><li>架构组件：<ul><li>Prometheus（采集与存储指标）</li><li>Alertmanager（告警）</li><li>Grafana（可视化）</li><li>kube-state-metrics（集群资源状态）</li><li>node-exporter（节点系统指标）</li></ul></li><li>特点：<ul><li>Operator 自动化管理 Prometheus、Alertmanager、Grafana 等部署与配置。</li><li>社区维护的 “kube-prometheus-stack” Helm Chart 是生产级推荐方案。</li></ul></li><li>状态：功能最全、生态最活跃。</li><li>适用场景：生产级集群的监控、告警、可视化一体化方案。</li></ul></li><li><p>Weave Scope / Weave Cloud</p><ul><li>功能：<ul><li>直观展示 Pod、容器、Service 之间的拓扑关系与状态。</li><li>支持查看部分性能指标（CPU、内存、网络流量）。</li></ul></li><li>定位：<ul><li>更偏向于可视化与运维调试工具。</li><li>不属于严格意义上的 “性能监控方案”。</li></ul></li><li>适用场景：<ul><li>集群拓扑观测、实时诊断、开发或测试环境。</li><li>可作为 Prometheus 或 Metrics Server 的补充。</li></ul></li></ul></li></ul><blockquote><p>不同 Kubernetes 集群性能监控方案的对比</p></blockquote><table><thead><tr><th>性能监控方案</th><th>类型</th><th>是否官方推荐</th><th>是否可替代 Heapster</th><th> 可视化展示</th><th>是否持久化存储</th></tr></thead><tbody><tr><td> Heapster + InfluxDB + Grafana</td><td> 旧版指标监控</td><td>❌ 已弃用</td><td>✔</td><td>✔</td><td>✔</td></tr><tr><td>Metrics Server</td><td> 资源指标采集</td><td>✔ 官方推荐</td><td>✔</td><td>❌</td><td>❌</td></tr><tr><td>Prometheus + Grafana</td><td> 指标监控</td><td>✔ 官方推荐</td><td>✔</td><td>✔</td><td>✔</td></tr><tr><td>Prometheus Operator Stack</td><td> 生产级集群监控告警平台</td><td>✔ 官方推荐</td><td>✔</td><td>✔</td><td>✔</td></tr><tr><td>Weave Scope / Weave Cloud</td><td> 拓扑与可视化</td><td>⚙️ 可选</td><td>❌</td><td>✔</td><td>部分</td></tr></tbody></table><h4 id="性能监控平台部署"><a href="#性能监控平台部署" class="headerlink" title="性能监控平台部署"></a>性能监控平台部署</h4><p>本节将基于 Prometheus + Grafana 搭建 Kubernetes 集群的性能监控平台。</p><h5 id="版本说明"><a href="#版本说明" class="headerlink" title="版本说明"></a>版本说明</h5><table><thead><tr><th>组件</th><th>版本</th></tr></thead><tbody><tr><td> Kubernetes</td><td><code>v1.19.10</code></td></tr><tr><td>Prometheus</td><td><code>v2.0.0</code></td></tr><tr><td>Grafana</td><td><code>v4.4.3</code></td></tr><tr><td>NodeExporter</td><td><code>v1.10.2</code></td></tr></tbody></table><h5 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h5><h6 id="拉取镜像"><a href="#拉取镜像" class="headerlink" title="拉取镜像"></a>拉取镜像</h6><p>在 Kubernetes 部署服务时，为了避免部署过程中出现镜像拉取超时（Image Pull Timeout）的问题，建议：</p><ul><li>提前将相关镜像预拉取到所有节点里面，确保部署时无需从远程仓库重新下载镜像；</li><li>或者搭建本地镜像仓库（比如 Harbor），提高镜像拉取的速度与可靠性。</li></ul><p>在 Kubernetes 集群的所有节点上（包括 Master 和 Worker），分别执行以下命令，提前将镜像拉取到本地</p><figure class="highlight shell"><table><tbody><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"># 拉取 Node Exporter 镜像</span></span><br><span class="line">docker pull prom/node-exporter:v1.10.2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 拉取 Prometheus 镜像</span></span><br><span class="line">docker pull prom/prometheus:v2.0.0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 拉取 Grafana 镜像</span></span><br><span class="line">docker pull grafana/grafana:4.2.0</span><br></pre></td></tr></tbody></table></figure><h6 id="安装-CoreDNS"><a href="#安装-CoreDNS" class="headerlink" title="安装 CoreDNS"></a>安装 CoreDNS</h6><ul><li><a href="/posts/ccd6f2d4.html#%E9%83%A8%E7%BD%B2-CoreDNS%EF%BC%88%E5%8F%AF%E9%80%89%EF%BC%89">Kubernetes 安装 CoreDNS 组件</a></li></ul><h6 id="安装-Ingress"><a href="#安装-Ingress" class="headerlink" title="安装 Ingress"></a>安装 Ingress</h6><ul><li><a href="/posts/6bf07963.html#Ingress-%E7%9A%84%E5%AE%89%E8%A3%85%E6%AD%A5%E9%AA%A4">Kubernetes 安装 Ingress 组件</a></li></ul><div class="admonition warning"><p class="admonition-title">特别注意</p><p>建议先安装 CoreDNS，然后再安装 Ingress，因为 Ingress 在启动时需要解析域名，有时候会依赖集群 DNS 组件（比如 CoreDNS）。</p></div><h5 id="部署步骤"><a href="#部署步骤" class="headerlink" title="部署步骤"></a>部署步骤</h5><h6 id="Prometheus-部署"><a href="#Prometheus-部署" class="headerlink" title="Prometheus 部署"></a>Prometheus 部署</h6><ul><li>创建 YAML 配置文件 <code>prometheus-config.yml</code>，用于部署 Prometheus 的 ConfigMap</li></ul><figure class="highlight yml"><table><tbody><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><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus-config</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line">  <span class="attr">prometheus.yml:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    global:</span></span><br><span class="line"><span class="string">      scrape_interval:     15s</span></span><br><span class="line"><span class="string">      evaluation_interval: 15s</span></span><br><span class="line"><span class="string">    scrape_configs:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-apiservers'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">endpoints</span></span><br><span class="line">      <span class="attr">scheme:</span> <span class="string">https</span></span><br><span class="line">      <span class="attr">tls_config:</span></span><br><span class="line">        <span class="attr">ca_file:</span> <span class="string">/var/run/secrets/kubernetes.io/serviceaccount/ca.crt</span></span><br><span class="line">      <span class="attr">bearer_token_file:</span> <span class="string">/var/run/secrets/kubernetes.io/serviceaccount/token</span></span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_namespace</span>, <span class="string">__meta_kubernetes_service_name</span>, <span class="string">__meta_kubernetes_endpoint_port_name</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">keep</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">default;kubernetes;https</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-nodes'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">node</span></span><br><span class="line">      <span class="attr">scheme:</span> <span class="string">https</span></span><br><span class="line">      <span class="attr">tls_config:</span></span><br><span class="line">        <span class="attr">ca_file:</span> <span class="string">/var/run/secrets/kubernetes.io/serviceaccount/ca.crt</span></span><br><span class="line">      <span class="attr">bearer_token_file:</span> <span class="string">/var/run/secrets/kubernetes.io/serviceaccount/token</span></span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">labelmap</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">__meta_kubernetes_node_label_(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">target_label:</span> <span class="string">__address__</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">kubernetes.default.svc:443</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_node_name</span>]</span><br><span class="line">        <span class="attr">regex:</span> <span class="string">(.+)</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__metrics_path__</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">/api/v1/nodes/${1}/proxy/metrics</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-cadvisor'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">node</span></span><br><span class="line">      <span class="attr">scheme:</span> <span class="string">https</span></span><br><span class="line">      <span class="attr">tls_config:</span></span><br><span class="line">        <span class="attr">ca_file:</span> <span class="string">/var/run/secrets/kubernetes.io/serviceaccount/ca.crt</span></span><br><span class="line">      <span class="attr">bearer_token_file:</span> <span class="string">/var/run/secrets/kubernetes.io/serviceaccount/token</span></span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">labelmap</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">__meta_kubernetes_node_label_(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">target_label:</span> <span class="string">__address__</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">kubernetes.default.svc:443</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_node_name</span>]</span><br><span class="line">        <span class="attr">regex:</span> <span class="string">(.+)</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__metrics_path__</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">/api/v1/nodes/${1}/proxy/metrics/cadvisor</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-service-endpoints'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">endpoints</span></span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_service_annotation_prometheus_io_scrape</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">keep</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_service_annotation_prometheus_io_scheme</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__scheme__</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">(https?)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_service_annotation_prometheus_io_path</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__metrics_path__</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__address__</span>, <span class="string">__meta_kubernetes_service_annotation_prometheus_io_port</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__address__</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">([^:]+)(?::\d+)?;(\d+)</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">$1:$2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">labelmap</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">__meta_kubernetes_service_label_(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_namespace</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_namespace</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_service_name</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_name</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-services'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">service</span></span><br><span class="line">      <span class="attr">metrics_path:</span> <span class="string">/probe</span></span><br><span class="line">      <span class="attr">params:</span></span><br><span class="line">        <span class="attr">module:</span> [<span class="string">http_2xx</span>]</span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_service_annotation_prometheus_io_probe</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">keep</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__address__</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__param_target</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">target_label:</span> <span class="string">__address__</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">blackbox-exporter.example.com:9115</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__param_target</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">instance</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">labelmap</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">__meta_kubernetes_service_label_(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_namespace</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_namespace</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_service_name</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_name</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-ingresses'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">ingress</span></span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_ingress_annotation_prometheus_io_probe</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">keep</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_ingress_scheme</span>,<span class="string">__address__</span>,<span class="string">__meta_kubernetes_ingress_path</span>]</span><br><span class="line">        <span class="attr">regex:</span> <span class="string">(.+);(.+);(.+)</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">${1}://${2}${3}</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__param_target</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">target_label:</span> <span class="string">__address__</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">blackbox-exporter.example.com:9115</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__param_target</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">instance</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">labelmap</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">__meta_kubernetes_ingress_label_(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_namespace</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_namespace</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_ingress_name</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_name</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'kubernetes-pods'</span></span><br><span class="line">      <span class="attr">kubernetes_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">pod</span></span><br><span class="line">      <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_pod_annotation_prometheus_io_scrape</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">keep</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_pod_annotation_prometheus_io_path</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__metrics_path__</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__address__</span>, <span class="string">__meta_kubernetes_pod_annotation_prometheus_io_port</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">([^:]+)(?::\d+)?;(\d+)</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">$1:$2</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">__address__</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">labelmap</span></span><br><span class="line">        <span class="attr">regex:</span> <span class="string">__meta_kubernetes_pod_label_(.+)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_namespace</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_namespace</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__meta_kubernetes_pod_name</span>]</span><br><span class="line">        <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">kubernetes_pod_name</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建 YAML 配置文件 <code>prometheus-deploy.yml</code>，用于部署 Prometheus 的 Deployment</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">labels:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">prometheus-deployment</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">prometheus</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">prometheus</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="bullet">-</span> <span class="attr">image:</span> <span class="string">prom/prometheus:v2.0.0</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line">        <span class="attr">command:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">"/bin/prometheus"</span></span><br><span class="line">        <span class="attr">args:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">"--config.file=/etc/prometheus/prometheus.yml"</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">"--storage.tsdb.path=/prometheus"</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">"--storage.tsdb.retention=24h"</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">9090</span></span><br><span class="line">          <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">mountPath:</span> <span class="string">"/prometheus"</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">data</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">mountPath:</span> <span class="string">"/etc/prometheus"</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">config-volume</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="string">100m</span></span><br><span class="line">            <span class="attr">memory:</span> <span class="string">100Mi</span></span><br><span class="line">          <span class="attr">limits:</span></span><br><span class="line">            <span class="attr">cpu:</span> <span class="string">500m</span></span><br><span class="line">            <span class="attr">memory:</span> <span class="string">2500Mi</span></span><br><span class="line">      <span class="attr">serviceAccountName:</span> <span class="string">prometheus</span>    </span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">data</span></span><br><span class="line">        <span class="attr">emptyDir:</span> {}</span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span></span><br><span class="line">        <span class="attr">configMap:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">prometheus-config</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建 YAML 配置文件 <code>prometheus-svc.yml</code>，用于部署 Prometheus 的 Service</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</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">prometheus</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">9090</span></span><br><span class="line">    <span class="attr">targetPort:</span> <span class="number">9090</span></span><br><span class="line">    <span class="attr">nodePort:</span> <span class="number">30003</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">prometheus</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建 YAML 配置文件 <code>prometheus-rbac.yml</code>，用于对 Prometheus 进行 RBAC 授权 </li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">apiGroups:</span> [<span class="string">""</span>]</span><br><span class="line">  <span class="attr">resources:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">nodes</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">nodes/proxy</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">endpoints</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">pods</span></span><br><span class="line">  <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>]</span><br><span class="line"><span class="bullet">-</span> <span class="attr">apiGroups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">extensions</span></span><br><span class="line">  <span class="attr">resources:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">ingresses</span></span><br><span class="line">  <span class="attr">verbs:</span> [<span class="string">"get"</span>, <span class="string">"list"</span>, <span class="string">"watch"</span>]</span><br><span class="line"><span class="bullet">-</span> <span class="attr">nonResourceURLs:</span> [<span class="string">"/metrics"</span>]</span><br><span class="line">  <span class="attr">verbs:</span> [<span class="string">"get"</span>]</span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过上述的 YAML 配置文件，快速部署 Prometheus（注意 K8s 资源对象的部署顺序）</li></ul><figure class="highlight shell"><table><tbody><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"># 部署 ConfigMap</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> prometheus-config.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 部署 Deployment</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> prometheus-deploy.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 部署 Service</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> prometheus-svc.yml</span><br><span class="line"><span class="keyword"></span></span><br><span class="line"><span class="keyword"># RBAC</span> 授权</span><br><span class="line">kubectl apply<span class="params"> -f</span> prometheus-rbac.yml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> kube-system<span class="params"> -l</span> app=prometheus</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                         READY   STATUS    RESTARTS   AGE</span><br><span class="line">prometheus-68546b8d9-bg69k   1/1     Running   0          96s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> -n</span> kube-system<span class="params"> -l</span> app=prometheus</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE</span><br><span class="line">prometheus   NodePort   10.0.0.21    &lt;none&gt;        9090:30003/TCP   2m12s</span><br></pre></td></tr></tbody></table></figure><h6 id="Grafana-部署"><a href="#Grafana-部署" class="headerlink" title="Grafana 部署"></a>Grafana 部署</h6><ul><li>创建 YAML 配置文件 <code>grafana-deploy.yml</code>，用于部署 Grafana 的 Deployment</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">grafana-core</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">grafana</span></span><br><span class="line">    <span class="attr">component:</span> <span class="string">core</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">grafana</span></span><br><span class="line">      <span class="attr">component:</span> <span class="string">core</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">grafana</span></span><br><span class="line">        <span class="attr">component:</span> <span class="string">core</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="bullet">-</span> <span class="attr">image:</span> <span class="string">grafana/grafana:4.2.0</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">grafana-core</span></span><br><span class="line">        <span class="attr">imagePullPolicy:</span> <span class="string">IfNotPresent</span></span><br><span class="line">        <span class="comment"># env:</span></span><br><span class="line">        <span class="attr">resources:</span></span><br><span class="line">          <span class="comment"># keep request = limit to keep this container in guaranteed class</span></span><br><span class="line">          <span class="attr">limits:</span></span><br><span class="line">            <span class="attr">cpu:</span> <span class="string">100m</span></span><br><span class="line">            <span class="attr">memory:</span> <span class="string">100Mi</span></span><br><span class="line">          <span class="attr">requests:</span></span><br><span class="line">            <span class="attr">cpu:</span> <span class="string">100m</span></span><br><span class="line">            <span class="attr">memory:</span> <span class="string">100Mi</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="comment"># The following env variables set up basic auth twith the default admin user and admin password.</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">GF_AUTH_BASIC_ENABLED</span></span><br><span class="line">            <span class="attr">value:</span> <span class="string">"true"</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">GF_AUTH_ANONYMOUS_ENABLED</span></span><br><span class="line">            <span class="attr">value:</span> <span class="string">"false"</span></span><br><span class="line">          <span class="comment"># - name: GF_AUTH_ANONYMOUS_ORG_ROLE</span></span><br><span class="line">          <span class="comment">#   value: Admin</span></span><br><span class="line">          <span class="comment"># does not really work, because of template variables in exported dashboards:</span></span><br><span class="line">          <span class="comment"># - name: GF_DASHBOARDS_JSON_ENABLED</span></span><br><span class="line">          <span class="comment">#   value: "true"</span></span><br><span class="line">        <span class="attr">readinessProbe:</span></span><br><span class="line">          <span class="attr">httpGet:</span></span><br><span class="line">            <span class="attr">path:</span> <span class="string">/login</span></span><br><span class="line">            <span class="attr">port:</span> <span class="number">3000</span></span><br><span class="line">          <span class="comment"># initialDelaySeconds: 30</span></span><br><span class="line">          <span class="comment"># timeoutSeconds: 1</span></span><br><span class="line">        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">grafana-persistent-storage</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/var</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">grafana-persistent-storage</span></span><br><span class="line">        <span class="attr">emptyDir:</span> {}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建 YAML 配置文件 <code>grafana-svc.yml</code>，用于部署 Grafana 的 Service</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">grafana</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">grafana</span></span><br><span class="line">    <span class="attr">component:</span> <span class="string">core</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">3000</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">grafana</span></span><br><span class="line">    <span class="attr">component:</span> <span class="string">core</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建 YAML 配置文件 <code>grafana-ingress.yml</code>，用于部署 Ingress 的路由规则 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">networking.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Ingress</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">grafana</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">host:</span> <span class="string">k8s.grafana.com</span></span><br><span class="line">    <span class="attr">http:</span></span><br><span class="line">      <span class="attr">paths:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">path:</span> <span class="string">/</span></span><br><span class="line">        <span class="attr">pathType:</span> <span class="string">Prefix</span></span><br><span class="line">        <span class="attr">backend:</span></span><br><span class="line">          <span class="attr">service:</span></span><br><span class="line">            <span class="attr">name:</span> <span class="string">grafana</span></span><br><span class="line">            <span class="attr">port:</span></span><br><span class="line">              <span class="attr">number:</span> <span class="number">3000</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过上述的 YAML 配置文件，快速部署 Grafana（注意 K8s 资源对象的部署顺序）</li></ul><figure class="highlight shell"><table><tbody><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"># 部署 Deployment</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> grafana-deploy.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 部署 Service</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> grafana-svc.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 部署 Ingress 的路由规则</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> grafana-ingress.yml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> kube-system<span class="params"> -l</span> app=grafana<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">grafana-core-6d6fb7566-5tv6t   1/1     Running   0          13m   10.244.0.16   k8s-node1    &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> -n</span> kube-system<span class="params"> -l</span> app=grafana</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME      TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE</span><br><span class="line">grafana   NodePort   10.0.0.242   &lt;none&gt;        3000:31671/TCP   3m41s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Ingress 路由规则 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get ingress<span class="params"> -n</span> kube-system</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME      CLASS    HOSTS             ADDRESS   PORTS   AGE</span><br><span class="line">grafana   &lt;none&gt;   k8s.grafana.com             80      86m</span><br></pre></td></tr></tbody></table></figure><h6 id="对外暴露节点"><a href="#对外暴露节点" class="headerlink" title="对外暴露节点"></a>对外暴露节点</h6><ul><li>创建 YAML 配置文件 <code>node-exporter.yml</code>，用于对外暴露节点（Node）</li></ul><figure class="highlight yml"><table><tbody><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></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">DaemonSet</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">node-exporter</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">k8s-app:</span> <span class="string">node-exporter</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">k8s-app:</span> <span class="string">node-exporter</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">k8s-app:</span> <span class="string">node-exporter</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="bullet">-</span> <span class="attr">image:</span> <span class="string">prom/node-exporter:v1.10.2</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">node-exporter</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">9100</span></span><br><span class="line">          <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</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">k8s-app:</span> <span class="string">node-exporter</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">node-exporter</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kube-system</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">9100</span></span><br><span class="line">    <span class="attr">nodePort:</span> <span class="number">31672</span></span><br><span class="line">    <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">k8s-app:</span> <span class="string">node-exporter</span></span><br></pre></td></tr></tbody></table></figure><ul><li>通过上述的 YAML 配置文件，对外暴露节点（Node）</li></ul><figure class="highlight shell"><table><tbody><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"># 部署 DaemonSet 和 Service</span></span><br><span class="line">kubectl apply<span class="params"> -f</span> node-exporter.yml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> kube-system<span class="params"> -l</span> k8s-app=node-exporter</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                  READY   STATUS    RESTARTS   AGE</span><br><span class="line">node-exporter-2xgh6   1/1     Running   0          37s</span><br><span class="line">node-exporter-6r2jz   1/1     Running   0          37s</span><br><span class="line">node-exporter-l5hjf   1/1     Running   0          37s</span><br><span class="line">node-exporter-q5zc2   1/1     Running   0          37s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> -n</span> kube-system<span class="params"> -l</span> k8s-app=node-exporter</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE</span><br><span class="line">node-exporter   NodePort   10.0.0.118   &lt;none&gt;        9100:31672/TCP   5m16s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看相关的 Ingress 规则 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get ingress<span class="params"> -n</span> kube-system</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME      CLASS    HOSTS             ADDRESS   PORTS   AGE</span><br><span class="line">grafana   &lt;none&gt;   k8s.grafana.com             80      86m</span><br></pre></td></tr></tbody></table></figure><h5 id="验证步骤"><a href="#验证步骤" class="headerlink" title="验证步骤"></a>验证步骤</h5><h6 id="通过-Service-访问-Grafana"><a href="#通过-Service-访问-Grafana" class="headerlink" title="通过 Service 访问 Grafana"></a>通过 Service 访问 Grafana</h6><ul><li>首先，查看 Grafana 相关的 Service</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> -n</span> kube-system</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE</span><br><span class="line">grafana         NodePort    10.0.0.242   &lt;none&gt;        3000:31671/TCP           20m</span><br><span class="line">kube-dns        ClusterIP   10.0.0.2     &lt;none&gt;        53/UDP,53/TCP,9153/TCP   8d</span><br><span class="line">node-exporter   NodePort    10.0.0.153   &lt;none&gt;        9100:31672/TCP           38m</span><br><span class="line">prometheus      NodePort    10.0.0.21    &lt;none&gt;        9090:30003/TCP           31m</span><br></pre></td></tr></tbody></table></figure><ul><li>通过任意一个集群节点的 IP 与 Grafana 的 Service 对外暴露的端口（比如 <code>http://192.168.2.191:31671</code>），就可以在 Kubernetes 集群外部通过浏览器访问 Grafana 的控制台页面（如下图所示）</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-0.png"></p><div class="admonition warning"><p class="admonition-title">特别注意</p><p>在 Kubernetes 集群外部，浏览器通过任意集群节点的 IP + Service 对外暴露的端口（<code>NodePort</code>）来访问 Grafana 的控制台页面（比如 <code>http://192.168.2.191:31671</code>），这并没有使用到 Ingress。</p></div><h6 id="通过-Ingress-访问-Grafana"><a href="#通过-Ingress-访问-Grafana" class="headerlink" title="通过 Ingress 访问 Grafana"></a>通过 Ingress 访问 Grafana</h6><ul><li>查看 Grafana 相关的 Ingress 路由规则 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get ingress<span class="params"> -n</span> kube-system</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME      CLASS    HOSTS             ADDRESS   PORTS   AGE</span><br><span class="line">grafana   &lt;none&gt;   k8s.grafana.com             80      86m</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Nginx Ingress Controller 所在的节点（Node）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -n</span> ingress-nginx<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                                       READY   STATUS    RESTARTS   AGE     IP              NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-ingress-controller-5dc64b58f-s82x6   1/1     Running   0          6m51s   192.168.2.131   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>在 K8s 集群外部的操作系统中，编辑系统配置文件 <code>/etc/hosts</code>，添加域名映射记录，其中 <code>192.168.2.131</code> 是 Nginx Ingress Controller 所在节点的 IP 地址（<strong>请自行更改 IP 地址</strong>）</li></ul><figure class="highlight shell"><table><tbody><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"># 编辑系统配置文件，添加以下内容</span></span><br><span class="line">vim /etc/hosts</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">192.168.2.131      k8s.grafana.com </span><br></pre></td></tr></tbody></table></figure><ul><li>在 K8s 集群外部的操作系统中，浏览器通过域名 <code>k8s.grafana.com</code> 访问 Ingress。如果可以成功访问 Grafana 的控制台页面（如下图所示），则说明 Ingress + Service + Grafana 可以正常运行</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-0.png"></p><h6 id="Grafana-展示监控数据图表"><a href="#Grafana-展示监控数据图表" class="headerlink" title="Grafana 展示监控数据图表"></a>Grafana 展示监控数据图表</h6><ul><li>首先，查看所有 Service，记住 Prometheus 的 ClusterIP（比如 <code>10.0.0.21</code>）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc<span class="params"> -n</span> kube-system</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE</span><br><span class="line">grafana         NodePort    10.0.0.242   &lt;none&gt;        3000:31671/TCP           20m</span><br><span class="line">kube-dns        ClusterIP   10.0.0.2     &lt;none&gt;        53/UDP,53/TCP,9153/TCP   8d</span><br><span class="line">node-exporter   NodePort    10.0.0.153   &lt;none&gt;        9100:31672/TCP           38m</span><br><span class="line">prometheus      NodePort    10.0.0.21    &lt;none&gt;        9090:30003/TCP           31m</span><br></pre></td></tr></tbody></table></figure><ul><li>登录 Grafana 的控制台页面，默认的登录用户名和密码分别是 <code>admin / admin</code>，成功登录后的控制台页面如下：</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-1.png"></p><ul><li>在 Grafana 的控制台页面中，添加相应的数据源，这里数据源地址里的 IP 是 Prometheus 的 Pod 在 K8s 集群内部的 IP 地址（ClusterIP），比如 <code>10.0.0.21</code>，端口号是 <code>9090</code></li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-2.png"></p><ul><li>在 Grafana 的控制台页面中，导入 DashBoard 模板</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-3.png"></p><ul><li>在模板输入框中填写数字 <code>315</code>（Grafana 模板的编号），然后让模板输入框失去焦点（或者点击 <code>Load</code> 按钮），等待一会模板信息就会加载出来</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-4.png"></p><ul><li>模板信息加载出来后，手动选择之前添加的数据源，然后点击 <code>Import</code> 按钮就可以导入模板</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-5.png"></p><ul><li>模板导入成功后，Grafana 会自动跳转到 DashBoard 页面</li></ul><p><img data-src="../../../asset/2025/11/k8s-grafana-6.png"></p><ul><li>在 DashBoard 页面中，如果可以看到 K8s 集群的监控图表，则说明基于 Prometheus + Grafana 的 K8s 集群性能监控平台搭建成功</li></ul><h3 id="实战部署-Java-应用程序"><a href="#实战部署-Java-应用程序" class="headerlink" title="实战部署 Java 应用程序"></a>实战部署 Java 应用程序</h3><p>本节将介绍如何在 Kubernetes 环境中实现 Java 项目的 CI/CD（持续集成与持续交付），其中 Docker 镜像仓库采用阿里云容器镜像服务（ACR）。</p><h4 id="容器交付的完整流程"><a href="#容器交付的完整流程" class="headerlink" title="容器交付的完整流程"></a>容器交付的完整流程</h4><blockquote><p>Java 项目开发 / 部署的流程</p></blockquote><p><img data-src="../../../asset/2025/11/k8s-cicd-1.png"></p><blockquote><p>Kubernetes 部署项目的流程</p></blockquote><p><img data-src="../../../asset/2025/11/k8s-cicd-2.png"></p><blockquote><p>Kubernetes 实现 CI / CD 的流程</p></blockquote><p><img data-src="../../../asset/2025/11/k8s-cicd-3.png"></p><h4 id="创建-Java-应用的镜像"><a href="#创建-Java-应用的镜像" class="headerlink" title="创建 Java 应用的镜像"></a>创建 Java 应用的镜像</h4><h5 id="制作-Java-应用的镜像"><a href="#制作-Java-应用的镜像" class="headerlink" title="制作 Java 应用的镜像"></a>制作 Java 应用的镜像</h5><div class="admonition note"><p class="admonition-title">运行环境说明</p><p>制作镜像的所有操作，都可以在任意一台安装了 JDK + Maven + Docker 的机器上执行。</p></div><ul><li>(1) 准备一个 Java 项目（基于 SpringBoot），项目结构如下（<a href="../../../downloads/2025/11/demojenkins.tar.gz">点击下载完整的项目源码</a>）：</li></ul><figure class="highlight plaintext"><table><tbody><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">demojenkins</span><br><span class="line">├── demojenkins.iml</span><br><span class="line">├── Dockerfile</span><br><span class="line">├── pom.xml</span><br><span class="line">└── src</span><br><span class="line">    ├── main</span><br><span class="line">    │&nbsp;&nbsp; ├── java</span><br><span class="line">    │&nbsp;&nbsp; │&nbsp;&nbsp; └── com</span><br><span class="line">    │&nbsp;&nbsp; │&nbsp;&nbsp;     └── clay</span><br><span class="line">    │&nbsp;&nbsp; │&nbsp;&nbsp;         └── demojenkins</span><br><span class="line">    │&nbsp;&nbsp; │&nbsp;&nbsp;             ├── controller</span><br><span class="line">    │&nbsp;&nbsp; │&nbsp;&nbsp;             │&nbsp;&nbsp; └── UserController.java</span><br><span class="line">    │&nbsp;&nbsp; │&nbsp;&nbsp;             └── DemoJenkinsApplication.java</span><br><span class="line">    │&nbsp;&nbsp; └── resources</span><br><span class="line">    │&nbsp;&nbsp;     └── application.yml</span><br><span class="line">    └── test</span><br><span class="line">        └── java</span><br><span class="line">            └── com</span><br><span class="line">                └── clay</span><br><span class="line">                    └── demojenkins</span><br><span class="line">                        └── DemoJenkinsApplicationTests.java</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 使用 Maven 命令将 Java 项目打包成可执行的 Jar 包或者 War 包（运行依赖 Tomcat 等外部 Web 容器），比如 <code>demojenkins.jar</code></li></ul><figure class="highlight shell"><table><tbody><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"># 进入项目根目录</span></span><br><span class="line"><span class="keyword"># cd</span> demojenkins</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编译打包项目</span></span><br><span class="line"><span class="keyword"># mvn</span> clean package</span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 使用 Docker 命令构建镜像 </li></ul><figure class="highlight shell"><table><tbody><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"># 进入项目根目录（该目录下有 Dockerfile 文件）</span></span><br><span class="line"><span class="keyword"># cd</span> demojenkins</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建镜像（注意，命令的末尾有一个点号）</span></span><br><span class="line"><span class="keyword"># docker</span> build<span class="params"> -t</span> demojenkins:0.0.1 .</span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 查看构建生成的 Docker 镜像 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看镜像列表</span></span><br><span class="line"><span class="keyword"># docker</span> images</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">demojenkins                             0.0.1               51ecb07ed574        48 seconds ago      122MB</span><br><span class="line">openjdk                                 8-jdk-alpine        a3562aa0b991        6 years ago         105MB</span><br><span class="line">...</span><br></pre></td></tr></tbody></table></figure><ul><li>(5) 测试 Docker 镜像是否可用 </li></ul><figure class="highlight shell"><table><tbody><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"># 启动容器（后台启动）</span></span><br><span class="line"><span class="keyword"># docker</span> run<span class="params"> -d</span><span class="params"> --name</span> demojenkins<span class="params"> -p</span> 8111:8111 demojenkins:0.0.1</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看容器的运行状态，输出示例如下所示</span></span><br><span class="line"><span class="keyword"># docker</span> ps<span class="params"> -a</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS                     PORTS                    NAMES</span><br><span class="line">85d0ce0e5edd        demojenkins:0.0.1             "java -jar /demojenk…"   7 seconds ago       Up 6 seconds               0.0.0.0:8111-&gt;8111/tcp   demojenkins</span><br><span class="line">...</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 通过 Curl 工具测试 Java 项目的接口，输出示例如下所示</span></span><br><span class="line"><span class="keyword"># curl</span> http://127.0.0.1:8111/user/findAll</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hello 1763004675787</span><br></pre></td></tr></tbody></table></figure><ul><li>(6) 最后删除前面启动的 Docker 容器 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword"># docker</span> rm<span class="params"> -f</span> demojenkins</span><br></pre></td></tr></tbody></table></figure><h5 id="推送-Java-应用的镜像"><a href="#推送-Java-应用的镜像" class="headerlink" title="推送 Java 应用的镜像"></a>推送 Java 应用的镜像</h5><div class="admonition note"><p class="admonition-title">运行环境说明</p><p>推送镜像的所有操作，都可以在任意一台安装了 JDK + Maven + Docker 的机器上执行。</p></div><ul><li>(1) 浏览器打开阿里云的 <a href="https://cr.console.aliyun.com/">容器镜像服务（ACR）</a> 页面（选择个人版实例）</li></ul><p><img data-src="../../../asset/2025/11/k8s-cicd-6.png"></p><ul><li>(2) 创建命名空间（比如 <code>java-dev</code>）</li></ul><p><img data-src="../../../asset/2025/11/k8s-cicd-5.png"></p><ul><li>(3) 创建镜像仓库（比如 <code>demojenkins</code>），<strong>需要选择上面创建的命名空间，且代码源必须选择本地仓库</strong></li></ul><p><img data-src="../../../asset/2025/11/k8s-cicd-4.png"></p><ul><li> (4) 在终端中使用命令登录阿里云镜像仓库（<strong>用于登录的用户名为阿里云账号全名，密码为开通容器镜像服务时设置的密码，镜像仓库域名可以从阿里云特定镜像仓库的详情页面获取</strong>）</li></ul><figure class="highlight shell"><table><tbody><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"># 登录阿里云镜像仓库</span></span><br><span class="line"><span class="keyword"># docker</span> login<span class="params"> --username</span>=xxxxx [镜像仓库域名]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># docker</span> login<span class="params"> --username</span>=xxxxx registry.cn-beijing.aliyuncs.com</span><br></pre></td></tr></tbody></table></figure><ul><li>(5) 推送镜像到阿里云镜像仓库 </li></ul><figure class="highlight shell"><table><tbody><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"># 镜像打上标签</span></span><br><span class="line"><span class="keyword"># docker</span> tag [镜像ID] [镜像仓库域名]/[命名空间]/[镜像名称]:[镜像版本号]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># docker</span> tag 51ecb07ed574 registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.1</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 推送镜像</span></span><br><span class="line"><span class="keyword"># docker</span> push [镜像仓库域名]/[命名空间]/[镜像名称]:[镜像版本号]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># docker</span> push registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.1</span><br></pre></td></tr></tbody></table></figure><h5 id="部署-Java-应用的镜像"><a href="#部署-Java-应用的镜像" class="headerlink" title="部署 Java 应用的镜像"></a>部署 Java 应用的镜像</h5><div class="admonition note"><p class="admonition-title">运行环境说明</p><p>部署镜像所有操作，必须在 Kubernetes 集群的任意一个 Master 节点上执行。</p></div><ul><li>(1) 在 Kubernetes 集群中，创建镜像拉取凭据 Secret（<strong>用于登录的用户名为阿里云账号全名，密码为开通容器镜像服务时设置的密码，邮箱地址可以自定义，镜像仓库域名可以从阿里云特定镜像仓库的详情页面获取</strong>）</li></ul><figure class="highlight shell"><table><tbody><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"># 创建Secret</span></span><br><span class="line"><span class="keyword"># kubectl</span> create secret docker-registry aliyun-regcred \<span class="params"></span></span><br><span class="line"><span class="params">  --docker</span>-server=&lt;镜像仓库域名&gt; \<span class="params"></span></span><br><span class="line"><span class="params">  --docker</span>-username=&lt;登录用户名&gt; \<span class="params"></span></span><br><span class="line"><span class="params">  --docker</span>-password=&lt;登录密码&gt; \<span class="params"></span></span><br><span class="line"><span class="params">  --docker</span>-email=&lt;邮箱地址&gt;</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 查看创建的Secret，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get secret aliyun-regcred</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME             TYPE                             DATA   AGE</span><br><span class="line">aliyun-regcred   kubernetes.io/dockerconfigjson   1      14s</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 创建一个 YAML 配置文件（比如 <code>demojenkins-deploy.yaml</code>），用于部署 Deployment 和 Service，使用了阿里云镜像仓库中的 Docker 镜像 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">demojenkins</span>         <span class="comment"># Service 的名称</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>            <span class="comment"># ervice 类型为 NodePort，可通过节点 IP 访问</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">demojenkins</span>        <span class="comment"># 选择标签为 app=demojenkins 的 Pod 作为后端</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span>              <span class="comment"># Service 对外暴露的端口，集群内部访问时也可以使用该端口</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8111</span>      <span class="comment"># Pod 内容器实际监听的端口（即将请求转发到容器的 xxxx 端口）</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">demojenkins</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span>       <span class="comment"># Pod 的副本数量</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">demojenkins</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">demojenkins</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="bullet">-</span> <span class="attr">name:</span> <span class="string">demojenkins</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">&lt;镜像仓库域名&gt;/&lt;命名空间&gt;/&lt;镜像名称&gt;:&lt;镜像版本号&gt;</span>    <span class="comment"># 阿里云镜像地址，比如：registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.1</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8111</span>     <span class="comment"># 容器内应用（比如 Tomcat）监听的端口</span></span><br><span class="line">      <span class="attr">imagePullSecrets:</span>           <span class="comment"># 引用镜像拉取凭据 Secret</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">aliyun-regcred</span></span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 应用 YAML 配置文件，创建 Deployment 和 Service 资源对象 </li></ul><figure class="highlight shell"><table><tbody><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"># 应用YAML配置文件</span></span><br><span class="line"><span class="keyword"># kubectl</span> apply<span class="params"> -f</span> demojenkins-deploy.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 查看 Service 的列表 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看Service列表，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME          TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">demojenkins   NodePort    10.0.0.112   &lt;none&gt;        80:30577/TCP   2m25s</span><br><span class="line">kubernetes    ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        30h</span><br></pre></td></tr></tbody></table></figure><ul><li>(5) 查看 Deployment 的列表 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看Deployment列表，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get deployments</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME          READY   UP-TO-DATE   AVAILABLE   AGE</span><br><span class="line">demojenkins   3/3     3            3           171m</span><br></pre></td></tr></tbody></table></figure><ul><li>(6) 查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-5486657b45-9ck68   1/1     Running   0          169m   10.244.4.10   k8s-node1     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-n6t88   1/1     Running   0          169m   10.244.2.6    k8s-node3     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-v6zsl   1/1     Running   0          169m   10.244.3.8    k8s-node2     &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 如果Pod启动失败，可以查看Pod的详细运行情况来定位问题</span></span><br><span class="line"><span class="keyword"># kubectl</span> describe pod &lt;pod-name&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除启动失败的Pod，触发Deployment对Pod的重建</span></span><br><span class="line"><span class="keyword"># kubectl</span> delete pod &lt;pod-name&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>(7) 在 Kubernetes 集群外部，通过 <code>curl</code> 网络工具访问容器内的 Java 应用（<strong>IP 可以是 Kubernetes 集群任意一个节点的 IP 地址，端口号可以通过 <code>kubectl get svc</code> 命令获取得到</strong>）</li></ul><figure class="highlight shell"><table><tbody><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"># 访问Java应用的接口，输出示例如下</span></span><br><span class="line"><span class="keyword"># curl</span> http://&lt;node-ip&gt;:30577/user/findAll</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hello 1763023637288</span><br></pre></td></tr></tbody></table></figure><ul><li>(8) 若希望删除上面创建的 Secret、Deployment、Service、Pod 所有资源对象，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除Deployment、Service、Pod</span></span><br><span class="line"><span class="keyword"># kubectl</span> delete<span class="params"> -f</span> demojenkins-deploy.yaml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除Secret</span></span><br><span class="line"><span class="keyword"># kubectl</span> delete secret aliyun-regcred </span><br></pre></td></tr></tbody></table></figure><h4 id="扩容-缩容-Java-应用程序"><a href="#扩容-缩容-Java-应用程序" class="headerlink" title="扩容 / 缩容 Java 应用程序"></a>扩容 / 缩容 Java 应用程序</h4><div class="admonition note"><p class="admonition-title">运行环境说明</p><p>扩容和缩容操作，必须在 Kubernetes 集群的任意一个 Master 节点上执行。</p></div><h5 id="扩容-Java-应用程序"><a href="#扩容-Java-应用程序" class="headerlink" title="扩容 Java 应用程序"></a>扩容 Java 应用程序</h5><ul><li>(1) 当完成上述步骤并将 Java 应用部署到 Kubernetes 集群后，可以通过以下命令调整该应用对应 Pod 的副本数量，实现扩容操作 </li></ul><figure class="highlight shell"><table><tbody><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"># 对Pod的副本进行扩容（比如，扩容至 5 个副本）</span></span><br><span class="line"><span class="keyword"># kubectl</span> scale deployment &lt;Deployment名称&gt;<span class="params"> --replicas</span>=5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># kubectl</span> scale deployment demojenkins<span class="params"> --replicas</span>=5</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-5486657b45-9ck68   1/1     Running   0          177m   10.244.4.10   k8s-node2     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-bkc6s   1/1     Running   0          52s    10.244.1.8    k8s-node1     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-n6t88   1/1     Running   0          177m   10.244.2.6    k8s-node3     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-v6zsl   1/1     Running   0          177m   10.244.3.8    k8s-node2     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-vjpxq   1/1     Running   0          58s    10.244.0.8    k8s-node1     &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h5 id="缩容-Java-应用程序"><a href="#缩容-Java-应用程序" class="headerlink" title="缩容 Java 应用程序"></a>缩容 Java 应用程序</h5><ul><li>(1) 当完成上述步骤并将 Java 应用部署到 Kubernetes 集群后，可以通过以下命令调整该应用对应 Pod 的副本数量，实现缩容操作 </li></ul><figure class="highlight shell"><table><tbody><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"># 对Pod的副本进行缩容（比如，缩容至 2 个副本）</span></span><br><span class="line"><span class="keyword"># kubectl</span> scale deployment &lt;Deployment名称&gt;<span class="params"> --replicas</span>=2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># kubectl</span> scale deployment demojenkins<span class="params"> --replicas</span>=2</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-5486657b45-9ck68   1/1     Running   0          3h4m   10.244.4.10   k8s-node2     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-n6t88   1/1     Running   0          3h4m   10.244.2.6    k8s-node3     &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h4 id="升级-回滚-Java-应用程序"><a href="#升级-回滚-Java-应用程序" class="headerlink" title="升级 / 回滚 Java 应用程序"></a>升级 / 回滚 Java 应用程序</h4><div class="admonition note"><p class="admonition-title">运行环境说明</p><p>升级和回滚操作，必须在 Kubernetes 集群的任意一个 Master 节点上执行。<strong>值得注意的是，这里的升级和回滚操作针对的是 Pod 所使用的 Docker 镜像版本，也就意味着在实际执行时，相当于对 Java 应用的版本进行升级或回退。</strong></p></div><h5 id="升级-Java-应用程序"><a href="#升级-Java-应用程序" class="headerlink" title="升级 Java 应用程序"></a>升级 Java 应用程序</h5><ul><li><p>(1) 项目代码迭代更新后，重新 <a href="/posts/6158b4d2.html#%E5%88%B6%E4%BD%9C-Java-%E5%BA%94%E7%94%A8%E7%9A%84%E9%95%9C%E5%83%8F">制作 Java 应用的镜像</a>，且镜像使用新的版本号（比如 <code>0.0.2</code>），并推送新版本镜像到阿里云仓库</p></li><li><p>(2) 为了更好观察 Java 应用的升级过程，先查看所有 Pod 的运行状态</p></li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-5486657b45-9ck68   1/1     Running   0          3h4m   10.244.4.10   k8s-node2     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-n6t88   1/1     Running   0          3h4m   10.244.2.6    k8s-node3     &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 当新版本镜像推送到阿里云仓库后，可以通过以下命令来升级 Java 应用对应 Pod 的版本（比如，升级到 <code>0.0.2</code> 版本）</li></ul><figure class="highlight shell"><table><tbody><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"># 升级Pod的版本</span></span><br><span class="line"><span class="keyword"># kubectl</span> <span class="built_in">set</span> image deployment &lt;Deployment名称&gt; &lt;容器名称&gt;=&lt;镜像仓库域名&gt;/&lt;命名空间&gt;/&lt;镜像名&gt;:&lt;版本号&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># kubectl</span> <span class="built_in">set</span> image deployment demojenkins demojenkins=registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.2</span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 查看 Java 应用升级版本的状态（过程）</li></ul><figure class="highlight shell"><table><tbody><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"># 查看升级版本的状态</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout status deployment &lt;Deployment名称&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout status deployment demojenkins</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Waiting for deployment "demojenkins" rollout to finish: 1 out of 2 new replicas have been updated...</span><br><span class="line">Waiting for deployment "demojenkins" rollout to finish: 1 out of 2 new replicas have been updated...</span><br><span class="line">Waiting for deployment "demojenkins" rollout to finish: 1 out of 2 new replicas have been updated...</span><br><span class="line">Waiting for deployment "demojenkins" rollout to finish: 1 old replicas are pending termination...</span><br><span class="line">Waiting for deployment "demojenkins" rollout to finish: 1 old replicas are pending termination...</span><br><span class="line">deployment "demojenkins" successfully rolled out</span><br></pre></td></tr></tbody></table></figure><ul><li>(5) 再次查看所有 Pod 的运行状态，发现 Pod 名称已经发生变化 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                          READY   STATUS    RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-6c58b8c7b-mt8hp   1/1     Running   0          6m50s   10.244.1.9   k8s-node1   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-6c58b8c7b-tng8r   1/1     Running   0          6m20s   10.244.3.9   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h5 id="回滚-Java-应用程序"><a href="#回滚-Java-应用程序" class="headerlink" title="回滚 Java 应用程序"></a>回滚 Java 应用程序</h5><ul><li>(1) 为了更好观察 Java 应用的回滚过程，先查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-6c58b8c7b-mt8hp   1/1     Running   0          10m   10.244.1.9   k8s-node1   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-6c58b8c7b-tng8r   1/1     Running   0          10m   10.244.3.9   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) 查看指定 Deployment 的所有历史版本 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看指定Deployment的所有历史版本</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout <span class="built_in">history</span> deployment &lt;Deployment名称&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout <span class="built_in">history</span> deployment demojenkins</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">deployment.apps/demojenkins </span><br><span class="line">REVISION  CHANGE-CAUSE</span><br><span class="line">1         &lt;none&gt;</span><br><span class="line">2         &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>(3) 当执行完上述步骤将 Java 应用 的版本从 <code>0.0.1</code> 升级到 <code>0.0.2</code> 后，若希望回滚到旧的版本（即用旧版本的 Pod 替换掉所有新版本的 Pod），可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 回滚指定Deployment到上一个版本</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout undo deployment &lt;Deployment名称&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout undo deployment demojenkins</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 或者，回滚指定Deployment到指定的版本</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout undo deployment &lt;Deployment名称&gt;<span class="params"> --to</span>-revision=&lt;版本&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout undo deployment demojenkins<span class="params"> --to</span>-revision=1</span><br></pre></td></tr></tbody></table></figure><ul><li>(4) 再次查看所有 Pod 的运行状态，发现 Pod 名称已经发生变化 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看所有Pod的运行状态，输出示例如下</span></span><br><span class="line"><span class="keyword"># kubectl</span> get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES</span><br><span class="line">demojenkins-5486657b45-knbnp   1/1     Running   0          66s   10.244.4.11   k8s-node2     &lt;none&gt;           &lt;none&gt;</span><br><span class="line">demojenkins-5486657b45-md6lh   1/1     Running   0          64s   10.244.2.7    k8s-node3     &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h4 id="平滑重启-Java-应用程序"><a href="#平滑重启-Java-应用程序" class="headerlink" title="平滑重启 Java 应用程序"></a>平滑重启 Java 应用程序</h4><div class="admonition note"><p class="admonition-title">运行环境说明</p><p>平滑重启操作，必须在 Kubernetes 集群的任意一个 Master 节点上执行。</p></div><h5 id="什么是平滑重启"><a href="#什么是平滑重启" class="headerlink" title="什么是平滑重启"></a>什么是平滑重启</h5><ul><li>在 Kubernetes 中实现 Java 应用程序的平滑重启，本质上是通过 Deployment 滚动更新（Rolling Update）的方式实现，也就是逐个重建 Pod，而不是将全部 Pod 停掉再重建，这样可以保证服务在重启过程中尽量不丢失请求。</li><li>所谓 Java 应用平滑重启（Graceful Restart）有两个关键点：<ul><li>(1) 不中断现有请求：Java 应用在收到 <code>SIGTERM</code> 信号时，应该先停止接收新请求，但允许正在处理的请求完成，再退出。</li><li>(2) 保证服务可用性：Kubernetes 通过 Deployment 的滚动更新或 Pod 生命周期钩子来保证至少有一部分实例在运行，避免服务全部不可用。</li></ul></li></ul><div class="admonition warning"><p class="admonition-title">扩展阅读</p><p>更多关于 Kubernetes 如何实现 Java 应用程序平滑重启的详细介绍，可以看 <a href="/posts/5f66357f.html#%E5%B9%B3%E6%BB%91%E9%87%8D%E5%90%AF-Java-%E5%BA%94%E7%94%A8">这里</a>。</p></div><h5 id="平滑重启的实现"><a href="#平滑重启的实现" class="headerlink" title="平滑重启的实现"></a>平滑重启的实现</h5><ul><li>(1) 手动触发 Deployment 滚动更新（Rolling Update），也就是说，Kubernetes 会逐个重建 Pod，而不是将全部 Pod 停掉再重建 </li></ul><figure class="highlight shell"><table><tbody><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"># 触发Deployment滚动更新</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout restart deployment &lt;Deployment名称&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 比如</span></span><br><span class="line"><span class="keyword"># kubectl</span> rollout restart deployment demojenkins</span><br></pre></td></tr></tbody></table></figure><ul><li>(2) <strong>特别注意，光是依靠 <code>kubectl rollout restart deployment &lt;Deployment名称&gt;</code> 命令，不一定就可以真正实现 Java 应用的平滑重启，它是有几个前提条件的，详细说明请看 <a href="/posts/5f66357f.html#%E5%B9%B3%E6%BB%91%E9%87%8D%E5%90%AF-Java-%E5%BA%94%E7%94%A8">这里</a>。</strong></li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/posts/54626c2e.html">DevOps 的技术选型介绍</a></li><li><a href="/posts/37a21b7b.html#%E5%BA%94%E7%94%A8%E5%8D%87%E7%BA%A7%E5%9B%9E%E6%BB%9A">Kubernetes 应用升级回滚</a></li><li><a href="/posts/37a21b7b.html#%E5%BA%94%E7%94%A8%E5%BC%B9%E6%80%A7%E4%BC%B8%E7%BC%A9">Kubernetes 应用弹性伸缩</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Spring Cloud Gateway 实现动态路由和灰度发布</title>
    <link href="https://www.techgrow.cn/posts/d806b69a.html"/>
    <id>https://www.techgrow.cn/posts/d806b69a.html</id>
    <published>2025-09-12T14:33:05.000Z</published>
    <updated>2025-09-12T14:33:05.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="Gateway-动态路由"><a href="#Gateway-动态路由" class="headerlink" title="Gateway 动态路由"></a>Gateway 动态路由</h2><span id="more"></span><ul><li>创建数据库表 </li></ul><figure class="highlight sql"><table><tbody><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">CREATE</span> <span class="keyword">TABLE</span> `gateway_api_route` (</span><br><span class="line">   `id` <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `path` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `service_id` <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `url` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `retryable` tinyint(<span class="number">1</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `enabled` tinyint(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `strip_prefix` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `api_name` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line"> ) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br></pre></td></tr></tbody></table></figure><ul><li>插入表数据 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INSERT INTO gateway_api_route (id, path, service_id, retryable, strip_prefix, url, enabled) VALUES (<span class="string">'order-service'</span>, <span class="string">'/order/**'</span>, <span class="string">'order-service'</span>,0,1, NULL, 1);</span><br></pre></td></tr></tbody></table></figure><div class="admonition note"><p class="admonition-title">提示</p><p>企业项目中可以使用 Spring MVC 结合前端页面，开发一个可视化网关管理工作台。当新服务开发完成后，可以通过这个工作台对网关配置进行管理，比如为某个服务绑定对应的 URL 路径，并支持增删改查操作，实现服务路由配置的可视化管理。</p></div><h2 id="Gateway-灰度发布"><a href="#Gateway-灰度发布" class="headerlink" title="Gateway 灰度发布"></a>Gateway 灰度发布</h2><ul><li>创建数据库表 </li></ul><figure class="highlight sql"><table><tbody><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">CREATE</span> <span class="keyword">TABLE</span> `gray_release_config` (</span><br><span class="line">   `id` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line">   `service_id` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `path` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   `enable_gray_release` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">   <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line"> ) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br></pre></td></tr></tbody></table></figure><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://github.com/shishan100/Java-Interview-Advanced/blob/master/docs/distributed-system/dynamic-route.md">Zuul 网关实现动态路由</a></li><li><a href="https://github.com/shishan100/Java-Interview-Advanced/blob/master/docs/distributed-system/gray-environment.md">Zuul 网关实现灰度发布</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Spring Cloud Gateway 实现动态路由和灰度发布。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="微服务" scheme="https://www.techgrow.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之三</title>
    <link href="https://www.techgrow.cn/posts/2722157d.html"/>
    <id>https://www.techgrow.cn/posts/2722157d.html</id>
    <published>2025-09-10T13:12:19.000Z</published>
    <updated>2025-09-10T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><span id="more"></span><h3 id="Controller-的介绍"><a href="#Controller-的介绍" class="headerlink" title="Controller 的介绍"></a>Controller 的介绍</h3><div class="admonition note"><p class="admonition-title">Pod 与 Controller（控制器）的关系</p><ul><li>Pod 通过 Controller 进行运维管理，包括创建、扩缩容、滚动更新等操作。</li><li>Pod 与 Controller 之间是通过 Label（标签）和 Label Selector（标签选择器）机制建立关联关系。</li><li>Controller 通过识别 Pod 的 Label（标签）来实现对一组 Pod 的集中管理。</li></ul></div><h4 id="Replication-Controller（RC）"><a href="#Replication-Controller（RC）" class="headerlink" title="Replication Controller（RC）"></a>Replication Controller（RC）</h4><h5 id="RC-的概念"><a href="#RC-的概念" class="headerlink" title="RC 的概念"></a>RC 的概念</h5><p>Replication Controller（RC）是 Kubernetes 系统中的核心概念之一。当定义一个 RC 并将其提交到 Kubernetes 集群后，Master 节点上的 Controller Manager 组件会接收到通知，并持续监控集群中 Pod 的运行状态。</p><h5 id="RC-的作用"><a href="#RC-的作用" class="headerlink" title="RC 的作用"></a>RC 的作用</h5><ul><li><p>Pod 的副本数量管理</p><ul><li>确保集群中实际运行的 Pod 副本数量与 RC 定义的期望值（<code>spec.replicas</code>）保持一致：<ul><li>如果运行的 Pod 副本数量 过多，RC 会自动停止并删除多余的 Pod；</li><li>如果运行的 Pod 副本数量 不足，RC 会自动创建新的 Pod 来补足数量。</li></ul></li></ul></li><li><p>Pod 的自动修复能力</p><ul><li>当 Pod 因故障或异常退出时，RC 会自动创建新的 Pod 来替代，确保服务始终可用。</li></ul></li><li><p>Pod 的弹性伸缩能力</p><ul><li>用户可以通过调整 RC 定义中的副本数，实现 Pod 的动态扩缩容（Scaling），从而根据业务需求灵活提升或降低服务处理能力。</li><li>比如：<code>kubectl scale rc nginx --replicas=5</code></li></ul></li></ul><h5 id="RS-替代-RC"><a href="#RS-替代-RC" class="headerlink" title="RS 替代 RC"></a>RS 替代 RC</h5><p>从 Kubernetes <code>1.2</code> 版本开始，Replica Set（RS）已经逐渐取代 Replication Controller（RC），成为更常用的 Pod 副本管理控制器。二者的演进说明如下：</p><ul><li><p>命名冲突</p><ul><li>由于 Replication Controller 与 Kubernetes 代码模块中同名，在 Kubernetes <code>1.2</code> 版本中，RC 升级为新的概念 Replica Set ，官方将其定义为 RC 的下一代版本。  </li></ul></li><li><p>主要区别  </p><ul><li>Replication Controller：只支持基于等式的 Label Selector，如 <code>app=nginx</code>。</li><li>Replica Set ：除了支持等式的 Label Selector 外，还支持基于集合式的 Label Selector，如 <code>in</code>、<code>notin</code>、<code>exists</code> 等更复杂的匹配规则。</li></ul></li><li><p>使用场景</p><ul><li>在实际工作中，很少单独使用 Replica Set，它通常由 Deployment 管理。Deployment 提供了更高层次的功能，包括 Pod 创建、删除、更新 的完整编排与滚动升级机制。</li><li>在生产环境中，通常通过 Deployment → Replica Set → Pod 这一管理链路进行编排和管理。</li></ul></li></ul><blockquote><p>使用 RC / RS 管理 Pod 的原因</p></blockquote><ul><li><p>避免直接创建 Pod</p><ul><li>不建议越过 RC / RS 直接创建 Pod，因为直接创建的 Pod 无法自动修复或扩缩容。</li><li>RC 或 RS 通过副本管理机制，可以实现 Pod 的自动创建、补足、替换和删除。</li></ul></li><li><p>提升容灾能力</p><ul><li>当节点故障或 Pod 异常退出时，RC / RS 会自动创建新的 Pod，确保服务稳定可用，减少因节点崩溃等意外带来的损失。</li></ul></li><li><p>适用于单副本场景</p><ul><li>即使应用只有一个 Pod 副本，也强烈建议使用 RC / RS 来管理 Pod，以获得自动恢复和高可用能力。</li></ul></li></ul><h4 id="Replica-Set（RS）"><a href="#Replica-Set（RS）" class="headerlink" title="Replica Set（RS）"></a>Replica Set（RS）</h4><h5 id="RS-的概念"><a href="#RS-的概念" class="headerlink" title="RS 的概念"></a>RS 的概念</h5><p>从 Kubernetes <code>1.2</code> 版本开始，Replica Set（RS）已经逐渐取代 Replication Controller（RC），成为更常用的 Pod 副本管理控制器。Replica Set 是 Replication Controller 的升级版本，支持更强大的 Label Selector，二者的关系如下：</p><ul><li><p>功能一致</p><ul><li>Replica Set 与 Replication Controller 在功能上没有本质的区别，二者的核心作用都是确保 Pod 副本数量与预期值保持一致。</li></ul></li><li><p>用法差异</p><ul><li>Replication Controller 只支持基于等式的 Label Selector，例如 <code>app=nginx</code>。</li><li>Replica Set 除了支持等式的 Label Selector 外，还支持基于集合式的 Label Selector，如 <code>in</code>、<code>notin</code>、<code>exists</code> 等更复杂的匹配规则。</li></ul></li><li><p>官方建议</p><ul><li>Kubernetes 官方强烈建议避免直接使用 Replica Set，而是通过 Deployment 来创建和管理 Replica Set 及其 Pod，以此获得滚动更新、回滚等高级功能。</li><li>在生产环境中，通常通过 Deployment → Replica Set → Pod 这一管理链路进行编排和管理。</li></ul></li></ul><h4 id="Deployment"><a href="#Deployment" class="headerlink" title="Deployment"></a>Deployment</h4><h5 id="Deployment-的概念"><a href="#Deployment-的概念" class="headerlink" title="Deployment 的概念"></a>Deployment 的概念</h5><p>Deployment 控制器是 Kubernetes 在 <code>1.2</code> 版本中引入的新概念，主要目的是为了更好地解决 Pod 的编排问题 。</p><ul><li><p>主要功能</p><ul><li>部署和管理无状态应用；</li><li>维护期望数量的 Pod 实例，支持自动修复异常 Pod；</li><li>提供滚动升级、灰度发布、快速回滚 等版本管理功能；</li><li>支持自动扩缩容（结合 HPA 使用）；</li><li>与 Service 配合，实现应用的高可用与负载均衡。</li></ul></li><li><p>实现机制</p><ul><li>Deployment 内部通过 Replica Set 管理 Pod 副本：<ul><li>Replica Set 负责 Pod 的实际创建、扩容和缩容；</li><li>Deployment 作为更高一层控制器，可以管理多个 Replica Set，从而支持版本管理和回滚。</li></ul></li><li>当更新镜像或配置时，Deployment 会创建新的 Replica Set 并逐步替换旧 Pod，实现平滑升级。</li></ul></li><li><p>定义特点</p><ul><li>定义结构与 Replica Set 类似，但提供了更高层的编排能力：<ul><li>Replica Set 的 Kind 类型：<code>ReplicaSet</code>；</li><li>Deployment 的 Kind 类型：<code>Deployment</code>，支持滚动更新、回滚、暂停、恢复、历史版本管理等高级功能。</li></ul></li><li>可通过 YAML 文件、命令行等多种方式定义，易于集成到 CI/CD 流水线中。</li></ul></li><li><p>适用场景</p><ul><li>部署无状态 Web 应用，如 Nginx、前端服务、API 网关；结合 Service 实现高可用和负载均衡，支持滚动更新不中断访问；</li><li>部署微服务中的业务服务实例，支持自动扩缩容，满足不同流量需求；通过滚动更新和灰度发布，保证服务持续迭代；</li><li>部署无状态的计算或处理任务，如日志收集、ETL、数据清洗等；</li><li>部署一些无状态的基础设施，如 Prometheus、Grafana、Fluentd；</li><li>通过 Deployment 的版本控制能力，实现持续交付流程；支持分批滚动更新、A/B 测试和金丝雀发布。</li></ul></li></ul><h4 id="StatefulSet"><a href="#StatefulSet" class="headerlink" title="StatefulSet"></a>StatefulSet</h4><h5 id="StatefulSet-的概念"><a href="#StatefulSet-的概念" class="headerlink" title="StatefulSet 的概念"></a>StatefulSet 的概念</h5><p>StatefulSet 控制器是 Kubernetes 在 <code>1.5</code> 版本中引入的控制器，主要用于管理有状态应用，为每个 Pod 提供固定身份标识、稳定的网络标识符和持久化存储，确保 Pod 的部署和管理有序进行。</p><ul><li><p>主要功能</p><ul><li>部署和管理有状态应用；</li><li>保证每个 Pod 具有固定的标识符（名称、网络标识）；</li><li>按照顺序有序创建、有序扩容、有序删除 Pod；</li><li>结合 PersistentVolume 为每个 Pod 提供独立的持久化存储；</li><li>确保 Pod 在重启或迁移后仍能保持原有的存储和网络标识。</li></ul></li><li><p>实现机制</p><ul><li>StatefulSet 内部通过 Headless Service 实现固定 DNS 解析，为 Pod 提供稳定的网络标识；</li><li>每个 Pod 会被分配一个有序的编号，例如 <code>mysql-0</code>、<code>mysql-1</code>；</li><li>Pod 与 PersistentVolumeClaim（PVC）绑定，确保数据不会因 Pod 重建而丢失；</li><li>Pod 创建、扩容、更新、删除等操作严格按照顺序进行，保证集群一致性。</li></ul></li><li><p>定义特点</p><ul><li>StatefulSet 的定义与 Deployment 类似，但支持更多有状态特性：<ul><li>Pod 命名固定：Pod 名称由 StatefulSet 名称 + 编号组成，如 <code>mysql-0</code>；</li><li>网络标识稳定：通过 Headless Service 绑定，Pod 拥有固定 DNS，如 <code>mysql-0.mysql</code>；</li><li>持久化存储：每个 Pod 自动绑定独立 PVC，与 Pod 生命周期解耦；</li><li>严格的顺序控制：Pod 启动、扩容和删除过程严格有序。</li></ul></li></ul></li><li><p>适用场景</p><ul><li>部署数据库服务，如 MySQL 主从、PostgreSQL；</li><li>部署分布式存储，如 HDFS、Ceph；</li><li>部署分布式协调服务，如 ZooKeeper、Etcd；</li><li>部署需要稳定网络标识的集群，如 Kafka、RabbitMQ。</li></ul></li></ul><h4 id="DaemonSet"><a href="#DaemonSet" class="headerlink" title="DaemonSet"></a>DaemonSet</h4><h5 id="DaemonSet-的概念"><a href="#DaemonSet-的概念" class="headerlink" title="DaemonSet 的概念"></a>DaemonSet 的概念</h5><p>DaemonSet 控制器是 Kubernetes 在 <code>1.2</code> 版本中引入的重要控制器，主要用于确保集群中每个（或指定）Node（工作节点）上都运行一个 Pod，非常适合运行节点级的后台服务或守护进程。</p><ul><li><p>主要功能</p><ul><li>在每个节点上运行一个指定的 Pod<ul><li> 自动在集群中每个符合条件的节点上部署且只运行一个指定的 Pod 实例。</li></ul></li><li>节点加入自动部署<ul><li>当新节点加入集群时，DaemonSet 会自动在该节点上调度并启动 Pod。</li></ul></li><li>节点移除自动回收<ul><li>节点被移除或不可用时，对应 Pod 会自动删除，保持一致性。</li></ul></li><li>不支持手动扩容 / 缩容<ul><li> Pod 的副本数量与节点数量直接关联，不支持手动管理 <code>replicas</code>。</li></ul></li><li>支持滚动更新与回滚<ul><li>可平滑升级版本，并在出现问题时快速回滚。</li></ul></li><li>可结合节点选择器、节点亲和性、污点 / 容忍等使用<ul><li>支持精确控制 DaemonSet Pod 部署在哪些节点上。</li></ul></li><li>与 Deployment 区别<ul><li> Deployment：通常用于无状态服务，副本数固定，由用户定义。</li><li>DaemonSet：与节点数量绑定，强调 “每个节点一个 Pod”。</li></ul></li><li>删除行为可控<ul><li>使用 <code>kubectl delete daemonset</code> 删除 DaemonSet 时，可通过 <code>--cascade=orphan</code> 参数控制是否保留关联的 Pod。</li></ul></li></ul></li><li><p>实现机制</p><ul><li>DaemonSet 控制器实时监听集群节点变化：<ul><li>当新节点加入时，DaemonSet 会根据调度规则自动为该节点创建一个 Pod；</li><li>当节点下线或被删除时，DaemonSet 会清理对应的 Pod；</li><li>通过 <code>updateStrategy</code> 配置支持滚动更新，保证节点上的 Pod 平滑升级；</li></ul></li><li>一个 DaemonSet 只能管理一组相同功能的 Pod，不会像 Deployment 那样创建多个 Replica Set（RS）；</li><li>与 Deployment 不同，DaemonSet 的 Pod 不通过调度器进行普通调度，而是直接绑定到目标节点。</li></ul></li><li><p>定义特点</p><ul><li>Kind 类型是 <code>DaemonSet</code>；</li><li>Pod 数量等于匹配规则的节点数量；</li><li>支持的更新策略：<ul><li><code>RollingUpdate</code>：逐个节点更新 Pod，保证服务连续性；</li><li><code>OnDelete</code>：需要手动删除旧 Pod 时，DaemonSet 才会创建新 Pod；</li></ul></li><li>可通过节点标签、节点亲和性、污点 / 容忍等配置精确控制 Pod 的分布；</li><li>YAML 文件定义结构与 Deployment 类似，但 <code>spec.strategy</code> 配置略有不同。</li><li>使用 <code>kubectl delete daemonset</code> 删除 DaemonSet 时，可通过 <code>--cascade=orphan</code> 参数控制是否保留关联的 Pod。</li></ul></li><li><p>适用场景</p><ul><li>日志收集：如 Fluentd、Logstash、Filebeat，保证每个节点日志都能被采集。</li><li>监控代理：如 Prometheus Node Exporter、Datadog Agent、cAdvisor 等，采集节点和 Pod 的监控指标。</li><li>网络插件：如 Flannel、Calico、Cilium 等 CNI 插件，需要在所有节点上运行网络代理；</li><li>存储插件：如 Ceph、GlusterFS、CSI Driver 等，部署存储卷管理进程；</li><li>安全与合规审计：如 Falco、Sysdig Secure 等安全审计、防护 Agent；</li><li>节点运维任务：自动在每个节点运行健康检查、系统运维脚本或运维工具；</li><li>边缘计算场景：在特定节点部署边缘服务或代理。</li></ul></li><li><p>总结对比</p><table><thead><tr><th>特性</th><th> Deployment</th><th>DaemonSet</th></tr></thead><tbody><tr><td>Pod 数量控制</td><td>支持自定义 Pod 的副本数量</td><td>每个节点部署 1 个 Pod，自动匹配节点</td></tr><tr><td>调度机制</td><td>由调度器调度</td><td>直接绑定到目标节点</td></tr><tr><td>适用场景</td><td>无状态应用、微服务、Web 服务</td><td>节点级服务、日志、监控、网络插件等</td></tr><tr><td>支持扩缩容</td><td>支持 HPA 自动扩缩容</td><td>不支持扩缩容，Pod 数量由节点数决定</td></tr><tr><td>更新策略</td><td>滚动更新、回滚、暂停、恢复</td><td>滚动更新，或者手动删除更新</td></tr></tbody></table></li></ul><h4 id="Job"><a href="#Job" class="headerlink" title="Job"></a>Job</h4><h5 id="Job-的概念"><a href="#Job-的概念" class="headerlink" title="Job 的概念"></a>Job 的概念</h5><p>Job 控制器是 Kubernetes 中用于<strong>一次性任务</strong>管理的重要控制器，适合运行批处理作业或有限执行次数的任务。Job 会确保指定数量的 Pod 成功执行完成（即运行到 Completed 状态），通常用于离线计算、数据处理或临时任务。</p><ul><li><p>主要功能</p><ul><li>执行一次性任务，保证任务至少成功执行一次<ul><li> Job 会确保定义的 Pod 按照预期执行，直到成功完成（运行状态为 <code>Completed</code>）。</li><li>Pod 运行失败时，Job 会根据重试策略自动重新创建新的 Pod 继续执行任务。</li></ul></li><li>支持并行或串行执行<ul><li>可以通过 <code>spec.parallelism</code> 控制同时运行的 Pod 数量；</li><li>可以通过 <code>spec.completions</code> 控制任务总共需要成功完成的 Pod 数量。</li></ul></li><li>适合一次性任务，执行完成后不会再次运行<ul><li> Job 完成后，Pod 不会被自动删除，但状态保持为 <code>Completed</code>；</li><li>可以通过配置 TTL 控制器自动清理已完成的 Job 及 Pod。</li></ul></li></ul></li><li><p>实现机制</p><ul><li>Job 控制器实时监听任务的执行状态：<ul><li>创建 Pod：Job 根据并发配置启动一个或多个 Pod；</li><li>失败重试：如果 Pod 执行失败（运行状态为 <code>Failed</code>），Job 会根据 <code>backoffLimit</code> 限制重试次数；</li><li>完成判断：当成功完成的 Pod 数量达到 <code>spec.completions</code> 时，Job 进入 <code>Completed</code> 状态；</li><li>清理策略：可通过 <code>ttlSecondsAfterFinished</code> 设置任务完成后延迟删除资源。</li></ul></li><li>Pod 调度：Job 创建的 Pod 由调度器进行普通调度，可结合节点选择策略运行在特定节点。</li><li>Job 控制器本身不做并发同步逻辑，任务并发控制需通过应用自身或外部工具实现。</li></ul></li><li><p>定义特点</p><ul><li>Kind 类型：<code>Job</code></li><li>核心参数：<ul><li><code>spec.completions</code>：任务需要成功完成的 Pod 总数量；</li><li><code>spec.parallelism</code>：允许同时运行的 Pod 数量；</li><li><code>spec.backoffLimit</code>：Pod 失败时的最大重试次数，超过该次数后 Job 会被标记为失败；</li><li><code>spec.ttlSecondsAfterFinished</code>：Job 完成后延迟清理的时间（秒）；</li><li><code>spec.template.spec.restartPolicy</code>：Pod 的重启策略，Job 必须设置为：<ul><li><code>Never</code>：Pod 失败时，不会在同一个 Pod 内重启，而是由 Job 创建新的 Pod；</li><li><code>OnFailure</code>：Pod 失败时，会在同一个 Pod 内重启一次，仍未成功则由 Job 重新创建 Pod。</li></ul></li></ul></li><li>YAML 定义结构与 Deployment 类似，但 <code>spec.strategy</code> 等部署策略不适用。</li></ul></li><li><p>更新策略</p><ul><li>Job 通常不支持直接滚动更新：<ul><li>如果需要修改 Job 逻辑，通常是删除旧 Job 后重新创建新 Job；</li><li>可以通过 <code>kubectl replace</code> 或者 <code>kubectl apply</code> 覆盖更新。</li></ul></li></ul></li><li><p>适用场景</p><ul><li>一次性批处理作业<ul><li>数据清理、日志分析、批量数据转换等。</li></ul></li><li>离线计算任务<ul><li>机器学习模型训练、视频转码、大数据计算等。</li></ul></li><li>自动化任务<ul><li>备份数据库、生成报表、执行临时脚本等。</li></ul></li><li>测试任务<ul><li>压力测试、集成测试或单次验证任务。</li></ul></li></ul></li></ul><h4 id="CronJob"><a href="#CronJob" class="headerlink" title="CronJob"></a>CronJob</h4><h5 id="CronJob-的概念"><a href="#CronJob-的概念" class="headerlink" title="CronJob 的概念"></a>CronJob 的概念</h5><p>CronJob 控制器是 Kubernetes 中用于<strong>定时任务调度</strong>的重要控制器，适合周期性执行任务或在特定时间点自动运行一次性任务。CronJob 基于 Linux 的 <code>cron</code> 语法定义任务调度规则，本质上是按时间计划自动创建 Job 资源，由 Job 再去管理 Pod 的执行与重试。</p><ul><li><p>主要功能</p><ul><li>周期性任务调度<ul><li>使用 <code>cron</code> 表达式定义执行计划，精确到分钟；</li><li>可在每天、每周、每月或特定时间点自动运行任务。</li></ul></li><li>自动创建 Job<ul><li>CronJob 在到达调度时间后，会自动创建对应的 Job 资源；</li><li>CronJob 不直接创建和运行 Pod，所有 Pod 都由其生成的 Job 进行管理；</li><li>Job 负责任务的执行、失败重试和状态维护。</li></ul></li><li>控制并发执行<ul><li>可通过 <code>concurrencyPolicy</code> 控制多次调度的 Job 是否允许并发执行：<ul><li><code>Allow</code>：允许多个任务并发运行；</li><li><code>Forbid</code>：禁止并发，若上一个任务未完成，跳过新的调度；</li><li><code>Replace</code>：如果上一个任务未完成，先删除旧任务，再启动新任务。</li></ul></li></ul></li><li>支持任务历史管理<ul><li>可以配置保留的成功任务和失败任务历史数量，避免资源无限增长。</li></ul></li><li>支持一次性定时任务<ul><li>通过指定一次性运行的时间点，实现一次性定时触发的 Job。</li></ul></li></ul></li><li><p>实现机制</p><ul><li>CronJob 控制器周期性检查当前时间是否符合 <code>schedule</code> 定义的规则：<ul><li>(1) 到达调度时间点 → 创建新的 Job；</li><li>(2) Job 执行任务 → 根据 Pod 模板启动 Pod；</li><li>(3) 任务执行失败 → 根据 Job 的重试策略进行管理；</li><li>(4) 任务历史清理 → 按配置保留一定数量的成功和失败记录。</li></ul></li><li>CronJob 仅负责调度和 Job 创建，实际的 Pod 管理由 Job 负责；</li><li>当控制器或 API Server 不可用时，会在恢复后补偿执行任务（可通过 <code>startingDeadlineSeconds</code> 控制补偿的时间窗口）。</li></ul></li><li><p>定义特点</p><ul><li>Kind 类型：<code>CronJob</code></li><li>核心参数：<ul><li><code>spec.schedule</code>：调度时间，使用标准 <code>cron</code> 表达式；</li><li><code>spec.concurrencyPolicy</code>：任务并发执行策略；</li><li><code>spec.startingDeadlineSeconds</code>：任务延迟启动的容忍时间（秒）；</li><li><code>spec.successfulJobsHistoryLimit</code>：保留的成功 Job 数量；</li><li><code>spec.failedJobsHistoryLimit</code>：保留的失败 Job 数量；</li><li><code>spec.jobTemplate</code>：定义要运行的 Job 模板<ul><li><code>spec.jobTemplate.spec.template.spec.restartPolicy</code>：Pod 的重启策略，CronJob 必须设置为：<ul><li><code>Never</code>：Pod 失败后不重启（常用于一次性任务）；</li><li><code>OnFailure</code>：Pod 失败后自动重启，直到成功或超过 <code>backoffLimit</code> 限制。</li></ul></li></ul></li></ul></li></ul></li><li><p>更新策略</p><ul><li>CronJob 更新时：<ul><li>新的调度规则在应用后立即生效；</li><li>已经创建的 Job 不会被中断或自动更新；</li><li>修改任务逻辑需更新 <code>jobTemplate</code> 并等待下一个调度周期生效。</li></ul></li><li>如果需要中断已生成的 Job，需要手动删除 Job 或 Pod。</li></ul></li><li><p>适用场景</p><ul><li>定时数据处理<ul><li>每天凌晨自动跑批处理作业，生成统计报表；</li><li>定时清理临时文件或过期数据。</li></ul></li><li>数据库备份<ul><li>每天或每小时自动执行数据库备份任务。</li></ul></li><li>日志归档<ul><li>定期收集、压缩和上传日志文件到集中存储。</li></ul></li><li>周期性健康检查<ul><li>定时执行诊断脚本或检查任务，输出报告。</li></ul></li><li>定时通知或消息推送<ul><li>定时触发消息发送、告警提醒或业务事件。</li></ul></li><li>一次性延时执行任务<ul><li>通过设置特定时间点，完成一次性延时任务的执行。</li></ul></li></ul></li><li><p>Linux 的 Cron 表达式规则</p><ul><li><p>常用示例（仅支持精确到分钟）：</p><ul><li><code>"*/5 * * * *"</code> → 每 5 分钟执行一次；</li><li><code>"0 0 * * *"</code> → 每天 0 点执行；</li><li><code>"0 2 * * 1"</code> → 每周一凌晨 2 点执行。</li></ul><table><thead><tr><th>字段位置</th><th>含义</th><th>取值范围</th></tr></thead><tbody><tr><td>第 1 位</td><td>分钟</td><td> 0–59</td></tr><tr><td> 第 2 位</td><td>小时</td><td> 0–23</td></tr><tr><td> 第 3 位</td><td>日期（日）</td><td>1–31</td></tr><tr><td> 第 4 位</td><td>月份</td><td> 1–12</td></tr><tr><td> 第 5 位</td><td>星期</td><td> 0–7（0 和 7 都表示星期日）</td></tr></tbody></table></li></ul></li></ul><h4 id="Horizontal-Pod-Autoscaler"><a href="#Horizontal-Pod-Autoscaler" class="headerlink" title="Horizontal Pod Autoscaler"></a>Horizontal Pod Autoscaler</h4><h5 id="HPA-的概念"><a href="#HPA-的概念" class="headerlink" title="HPA 的概念"></a>HPA 的概念</h5><p>Horizontal Pod Autoscaler（Pod 横向自动扩容，简称 HPA）与 Replication Controller（RC）、Deployment 一样，都是 Kubernetes 的资源对象。HPA 的实现原理是：通过持续追踪和分析 Replication Controller（RC）或 Deployment 控制的目标 Pod 的负载变化，判断是否需要针对性地调整 Pod 的副本数量（自动扩容和缩容）。</p><h5 id="HPA-的两种模式"><a href="#HPA-的两种模式" class="headerlink" title="HPA 的两种模式"></a>HPA 的两种模式</h5><p>Kubernetes 对 Pod 的扩容与缩容提供了手动和自动两种模式。</p><ul><li><p>手动扩容和缩容：</p><ul><li>通过 <code>kubectl scale</code> 命令对 Deployment 或 Replication Controller 的 Pod 副本数量进行设置；</li><li>比如：<code>kubectl scale deployment frontend --replicas 1</code>。</li></ul></li><li><p>自动扩容和缩容（HPA）：</p><ul><li>用户需要根据某个性能指标或自定义业务指标，指定 Pod 副本数量的最小值和最大值范围，Kubernetes 会根据实时指标变化，在这个范围内自动调整 Pod 的副本数量。</li><li>HPA 控制器通过 Master 节点的 <code>kube-controller-manager</code> 服务的启动参数 <code>--horizontal-pod-autoscaler-sync-period</code>（默认值为 30 秒）来周期性运行，包括：<ul><li>HPA 控制器会定期监测目标 Pod 的 CPU 使用率；</li><li>当 Pod 的 CPU 平均使用率达到用户设定的阈值条件时，HPA 控制器会自动调整 Replication Controller 或 Deployment 中的 Pod 副本数量，以使实际 Pod 副本数满足用户定义的 CPU 平均使用率要求。</li></ul></li></ul></li></ul><h5 id="HPA-的扩容配置示例"><a href="#HPA-的扩容配置示例" class="headerlink" title="HPA 的扩容配置示例"></a>HPA 的扩容配置示例</h5><ul><li>配置示例 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">name:</span> <span class="string">nginx-deployment</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</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">nginx</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx</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="string">50m</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-svc</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">autoscaling/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">targetCPUUtilizationPercentage:</span> <span class="number">50</span>    <span class="comment"># 当 Pod 的 CPU 平均使用率超过 50% 时扩容</span></span><br></pre></td></tr></tbody></table></figure><ul><li>配置说明：<ul><li>Deployment（<code>nginx-deployment</code>）<ul><li>核心作用：<ul><li>创建并管理 Nginx Pod，实现副本管理、自动恢复。</li></ul></li><li>核心配置：<ul><li><code>replicas: 1</code>：初始创建 1 个 Pod 副本。</li><li><code>selector.matchLabels</code>：用于匹配 Pod 的标签，这里为 <code>app: nginx</code>，确保 Deployment 管理正确的 Pod。</li><li><code>template.metadata.labels</code>：Pod 模板的标签，与 <code>selector</code> 对应。</li><li><code>containers</code>：定义容器信息<ul><li><code>name</code> / <code>image</code>：容器名称及镜像（nginx）</li><li><code>resources.requests.cpu: 50m</code>：CPU 请求资源，保证 Pod 调度时的资源预留（<code>50m</code> 表示 <code>0.05</code> 个 CPU 核心）</li><li><code>ports.containerPort: 80</code>：容器端口</li></ul></li></ul></li></ul></li><li> Service（<code>nginx-svc</code>）<ul><li>核心作用：<ul><li>为 Pod 提供统一访问入口，实现负载均衡。</li></ul></li><li>核心配置：<ul><li><code>ports.port: 80</code>：Service 暴露的端口</li><li><code>ports.targetPort: 80</code>：转发到 Pod 的容器端口</li><li><code>selector.app: nginx</code>：Service 选择带有 <code>app=nginx</code> 标签的 Pod 作为后端</li></ul></li></ul></li><li> HorizontalPodAutoscaler（<code>nginx-hpa</code>）<ul><li>核心作用：<ul><li>基于 CPU 使用率自动调整 Pod 副本数量，实现弹性伸缩（自动扩容和缩容）。</li></ul></li><li>核心配置：<ul><li><code>scaleTargetRef</code>：指定 HPA 控制器管理的对象，这里为 <code>Deployment/nginx-deployment</code></li><li><code>minReplicas: 1</code>：Pod 副本数量的最小值</li><li><code>maxReplicas: 5</code>：Pod 副本数量的最大值</li><li><code>targetCPUUtilizationPercentage: 50</code>：HPA 控制器会根据 Pod 的 CPU 平均使用率是否达到 <code>50%</code> 来自动扩缩容</li></ul></li></ul></li></ul></li></ul><h5 id="HPA-的扩容高级配置"><a href="#HPA-的扩容高级配置" class="headerlink" title="HPA 的扩容高级配置"></a>HPA 的扩容高级配置</h5><p>在 HPA 中，除了 <code>targetCPUUtilizationPercentage</code> 这种基于 CPU 使用率的扩缩容条件外，还可以配置更多维度的指标，例如内存、Pod 自定义指标、外部 Kubernetes 对象指标、外部监控系统指标等。</p><hr><ul><li><p>API 版本 <code>autoscaling/v1</code>（最基础版本）</p><ul><li>核心作用：<ul><li>只支持 CPU 使用率指标；</li><li>无法基于内存或自定义指标来扩容或缩容，只适用于简单场景。</li></ul></li><li>配置字段：<figure class="highlight yml"><table><tbody><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="attr">spec:</span></span><br><span class="line">  <span class="attr">targetCPUUtilizationPercentage:</span> <span class="number">50</span>    <span class="comment"># 当 Pod 的 CPU 平均使用率超过 50% 时扩容</span></span><br></pre></td></tr></tbody></table></figure></li></ul></li><li><p>API 版本 <code>autoscaling/v2beta2</code> 或者 <code>autoscaling/v2</code>（推荐版本）</p><ul><li>核心作用：<ul><li>支持基于多种指标类型来扩容或缩容，通过 <code>metrics</code> 字段配置。</li></ul></li><li>四种核心指标类型：</li></ul><table><thead><tr><th>指标类型</th><th>用途</th><th>示例场景</th><th>是否需要结合 Metrics Adapter（比如 Prometheus Adapter）使用</th></tr></thead><tbody><tr><td> Resource</td><td> 基于 Pod 资源指标（CPU、内存等）</td><td>当 Pod 的 CPU 平均使用率超过 <code>50%</code> 时扩容</td><td>不需要</td></tr><tr><td> Pods</td><td> 基于每个 Pod 计算出的指标</td><td>当每个 Pod 处理的业务请求数超过 1000 时扩容</td><td>需要</td></tr><tr><td> Object</td><td> 基于外部 Kubernetes 对象指标</td><td>根据某个 Service 的 QPS 来扩容</td><td>需要</td></tr><tr><td> External</td><td> 基于外部监控系统指标</td><td>根据 Prometheus 或外部监控系统的 QPS 扩容</td><td>需要</td></tr></tbody></table></li></ul><hr><ul><li>基于资源指标（CPU / 内存）自动扩容和缩容，不需要结合 Metrics Adapter（比如 Prometheus Adapter）使用 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">autoscaling/v2</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Resource</span></span><br><span class="line">      <span class="attr">resource:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">cpu</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">Utilization</span></span><br><span class="line">          <span class="attr">averageUtilization:</span> <span class="number">50</span>   <span class="comment"># 当 Pod 的 CPU 平均使用率超过 50% 时扩容</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Resource</span></span><br><span class="line">      <span class="attr">resource:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">memory</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">AverageValue</span></span><br><span class="line">          <span class="attr">averageValue:</span> <span class="string">200Mi</span>      <span class="comment"># 当 Pod 的内存平均使用量超过 200Mi 时扩容</span></span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置字段</th><th>配置作用</th></tr></thead><tbody><tr><td><code>averageUtilization</code></td><td>百分比，基于 Pod CPU 或内存的使用率</td></tr><tr><td><code>averageValue</code></td><td>绝对值，基于 Pod CPU 或内存的使用量</td></tr></tbody></table><ul><li>基于每个 Pod 的自定义指标来自动扩容和缩容，需要结合 Metrics Adapter（比如 Prometheus Adapter）使用，适用于：根据每个 Pod 处理的业务请求数、活跃连接数等指标来扩容 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">autoscaling/v2</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Pods</span></span><br><span class="line">      <span class="attr">pods:</span></span><br><span class="line">        <span class="attr">metric:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">requests_per_second</span>    <span class="comment"># Prometheus Adapter 暴露的指标名称</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">AverageValue</span></span><br><span class="line">          <span class="attr">averageValue:</span> <span class="string">"1000"</span>   <span class="comment"># 当每个 Pod 每秒所处理的业务请求数大于 1000 时扩容</span></span><br></pre></td></tr></tbody></table></figure><ul><li>基于外部 Kubernetes 对象指标来自动扩容和缩容，需要结合 Metrics Adapter（比如 Prometheus Adapter）使用，适用于：根据 Service、Ingress 等对象的指标来扩容 </li></ul><figure class="highlight yml"><table><tbody><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">apiVersion:</span> <span class="string">autoscaling/v2</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Object</span></span><br><span class="line">      <span class="attr">object:</span></span><br><span class="line">        <span class="attr">describedObject:</span></span><br><span class="line">          <span class="attr">apiVersion:</span> <span class="string">networking.k8s.io/v1</span></span><br><span class="line">          <span class="attr">kind:</span> <span class="string">Ingress</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">my-ingress</span></span><br><span class="line">        <span class="attr">metric:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">requests_per_second</span>    <span class="comment"># Prometheus Adapter 暴露的指标名称</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">Value</span></span><br><span class="line">          <span class="attr">value:</span> <span class="string">"1000"</span>   <span class="comment"># 当该 Ingress 的每秒请求数超过 1000 时扩容</span></span><br></pre></td></tr></tbody></table></figure><ul><li>基于外部监控系统指标来自动扩容和缩容，需要结合 Metrics Adapter（比如 Prometheus Adapter）使用，适用于：根据 Prometheus、CloudWatch、阿里云 ARMS 等外部监控系统指标来扩容 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">autoscaling/v2</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">External</span></span><br><span class="line">      <span class="attr">external:</span></span><br><span class="line">        <span class="attr">metric:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">nginx_ingress_qps</span>   <span class="comment"># Prometheus Adapter 暴露的指标名称</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">AverageValue</span></span><br><span class="line">          <span class="attr">averageValue:</span> <span class="string">"2000"</span>  <span class="comment"># 当外部系统的 QPS 大于 2000 时扩容</span></span><br></pre></td></tr></tbody></table></figure><div class="admonition note"><p class="admonition-title">总结说明</p><ul><li>Kubernetes 的 Metrics Server：仅提供 CPU、内存指标，适用于简单扩缩容场景。</li><li>推荐使用 Prometheus + Prometheus Adapter：采集并适配 QPS、连接数、延迟等复杂业务指标供 HPA 使用。</li><li>多个指标组合使用：HPA 支持同时配置 CPU、内存、QPS 等多个指标，最终副本数取最大值，确保系统稳定性。</li><li>生产最佳实践：统一部署 Metrics Server + Prometheus + Prometheus Adapter，构建完整的自动扩缩容体系。</li></ul></div><h5 id="HPA-的缩容高级配置"><a href="#HPA-的缩容高级配置" class="headerlink" title="HPA 的缩容高级配置"></a>HPA 的缩容高级配置</h5><p>在 Kubernetes 的 Horizontal Pod Autoscaler（HPA） 中，自动缩容实际上是自动扩容机制的一部分，一般不需要单独写一个专门的 “缩容配置”。HPA 会根据用户设定的 Metrics 指标，自动计算目标 Pod 副本数量，既包括扩容，也包括缩容。</p><ul><li><p><strong>缩容的核心逻辑</strong></p><ul><li>HPA 会根据当前负载和目标值计算期望 Pod 副本数量：<ul><li>计算公式：<code>desiredReplicas = ceil(currentReplicas × currentMetricValue ÷ targetMetricValue)</code></li><li>如果 <code>desiredReplicas &gt; currentReplicas</code>，HPA 会进行扩容</li><li>如果 <code>desiredReplicas &lt; currentReplicas</code>，HPA 会进行缩容</li></ul></li><li>假设配置里有以下内容，这意味着 Pod 缩容最小会缩到 1 个副本，不会被缩容到 0 个副本 <figure class="highlight yml"><table><tbody><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="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line"><span class="attr">maxReplicas:</span> <span class="number">10</span></span><br></pre></td></tr></tbody></table></figure></li></ul></li><li><p><strong>如何让缩容生效</strong></p><ul><li><p>(1) HPA 默认就支持缩容，一般不需要额外配置</p><ul><li>比如，当 CPU 或内存使用率持续低于目标值时，Pod 的数量会被逐步缩减，直到达到 <code>minReplicas</code> 限制</li></ul></li><li><p> (2) 如果想控制 Pod 缩容的速度和行为，可以在 <code>spec.behavior</code> 中配置策略</p><ul><li>必须使用 API 版本 <code>autoscaling/v2</code> 或者 <code>autoscaling/v2beta2</code></li><li>配置示例：<figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">autoscaling/v2</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Resource</span></span><br><span class="line">      <span class="attr">resource:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">cpu</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">Utilization</span></span><br><span class="line">          <span class="attr">averageUtilization:</span> <span class="number">50</span>             <span class="comment"># 当 Pod 的 CPU 平均使用率超过 50% 时扩容</span></span><br><span class="line">  <span class="attr">behavior:</span></span><br><span class="line">    <span class="attr">scaleDown:</span></span><br><span class="line">      <span class="attr">stabilizationWindowSeconds:</span> <span class="number">300</span>        <span class="comment"># 缩容前观察 5 分钟，避免波动</span></span><br><span class="line">      <span class="attr">policies:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Percent</span></span><br><span class="line">          <span class="attr">value:</span> <span class="number">50</span>                          <span class="comment"># 每次最多缩减 50%</span></span><br><span class="line">          <span class="attr">periodSeconds:</span> <span class="number">60</span>                  <span class="comment"># 每 60 秒评估一次缩容</span></span><br></pre></td></tr></tbody></table></figure></li><li>配置字段：<ul><li><code>stabilizationWindowSeconds</code>：稳定窗口，只有指标持续低于目标值这段时间后才缩容，避免频繁抖动</li><li><code>policies</code>：定义缩容速率，可以按百分比或固定数量缩减 Pod 的数量</li><li><code>periodSeconds</code>：缩容策略评估的时间间隔</li></ul></li></ul></li></ul></li><li><p><strong> Pod 缩容为 0 个副本</strong></p><ul><li>在 Kubernetes 中，当 Pod 缩容到 0 个副本时，指的是 Pod 数量为 0，即一个 Pod 都不会存在（相当于删除所有正在运行的 Pod）。</li><li>如果希望 Pod 完全缩到 0 个副本，HPA 本身做不到，需要配合 KEDA 或 VPA 或 Deployment 的 <code>scale-to-zero</code> 机制。</li><li>但是，如果只是普通业务场景，直接设置 <code>minReplicas: 0</code> 即可让 HPA 在负载极低时将 Pod 缩到 0 个副本：<figure class="highlight yaml"><table><tbody><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="attr">spec:</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">0</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br></pre></td></tr></tbody></table></figure></li><li>注意：当 Pod 缩容到 0 个副本后<ul><li>如果没有请求进来，这个服务将处于完全停机状态</li><li>如果之后有请求到达，Kubernetes 不会自动重建 Pod</li><li> 必须由 HPA 再次检测到指标上升，将 Pod 的副本数量从 0 调整为 1 或更多，Pod 才会被重新启动</li><li>这期间会有冷启动延迟（Pod 拉取镜像、启动应用、健康检查等）</li></ul></li></ul></li><li><p><strong>完整的扩缩容配置示例</strong></p><ul><li>配置示例：<figure class="highlight yaml"><table><tbody><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="attr">apiVersion:</span> <span class="string">autoscaling/v2</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">HorizontalPodAutoscaler</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-hpa</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">scaleTargetRef:</span></span><br><span class="line">    <span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line">    <span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">minReplicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">maxReplicas:</span> <span class="number">10</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Resource</span></span><br><span class="line">      <span class="attr">resource:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">cpu</span></span><br><span class="line">        <span class="attr">target:</span></span><br><span class="line">          <span class="attr">type:</span> <span class="string">Utilization</span></span><br><span class="line">          <span class="attr">averageUtilization:</span> <span class="number">50</span>             <span class="comment"># 当 Pod 的 CPU 平均使用率超过 50% 时扩容</span></span><br><span class="line">  <span class="attr">behavior:</span></span><br><span class="line">    <span class="attr">scaleDown:</span></span><br><span class="line">      <span class="attr">stabilizationWindowSeconds:</span> <span class="number">300</span>        <span class="comment"># 缩容前观察 5 分钟，避免波动</span></span><br><span class="line">      <span class="attr">policies:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">Percent</span></span><br><span class="line">          <span class="attr">value:</span> <span class="number">50</span>                          <span class="comment"># 每次最多缩减 50%</span></span><br><span class="line">          <span class="attr">periodSeconds:</span> <span class="number">60</span>                  <span class="comment"># 每 60 秒评估一次缩容</span></span><br></pre></td></tr></tbody></table></figure></li><li>配置效果<ul><li>当 Pod 的 CPU 平均使用率超过 50% 时扩容 </li><li>当 Pod 的 CPU 平均使用率低于 50% 且持续 5 分钟时，每 60 秒最多缩容 50%，直到达到 <code>minReplicas</code> 限制</li></ul></li><li>验证缩容<ul><li>实时查看 HPA 的决策过程：<code>kubectl get hpa nginx-hpa -w</code>，命令的输出结果如下：<figure class="highlight plaintext"><table><tbody><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">NAME        REFERENCE          TARGETS         MINPODS   MAXPODS   REPLICAS   AGE</span><br><span class="line">nginx-hpa   Deployment/nginx   10%/50%, 80Mi   1         10        3          10m</span><br></pre></td></tr></tbody></table></figure></li><li><code>TARGETS</code>：当前值 VS 目标值</li><li>当 <code>10%/50%</code> 持续低于目标值，<code>REPLICAS</code>（Pod 的副本数量）会逐步减少。</li></ul></li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 入门教程之四</title>
    <link href="https://www.techgrow.cn/posts/37a21b7b.html"/>
    <id>https://www.techgrow.cn/posts/37a21b7b.html</id>
    <published>2025-09-10T13:12:19.000Z</published>
    <updated>2025-09-10T13:12:19.000Z</updated>
    
    <content type="html"><![CDATA[<!-- toc --><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li><a href="/posts/99bf51b3.html">Kubernetes 入门教程之一</a>、<a href="/posts/c57e8370.html">Kubernetes 入门教程之二</a>、<a href="/posts/2722157d.html">Kubernetes 入门教程之三</a></li><li><a href="/posts/37a21b7b.html">Kubernetes 入门教程之四</a>、<a href="/posts/6bf07963.html">Kubernetes 入门教程之五</a>、<a href="/posts/76121b26.html">Kubernetes 入门教程之六</a></li><li><a href="/posts/2ca57d7f.html">Kubernetes 入门教程之七</a>、<a href="/posts/723af70c.html">Kubernetes 入门教程之八</a>、<a href="/posts/cfb1715d.html">Kubernetes 入门教程之九</a></li><li><a href="/posts/6158b4d2.html">Kubernetes 入门教程之十</a></li></ul><h2 id="Kubernetes-核心技术"><a href="#Kubernetes-核心技术" class="headerlink" title="Kubernetes 核心技术"></a>Kubernetes 核心技术</h2><h3 id="Controller-的使用"><a href="#Controller-的使用" class="headerlink" title="Controller 的使用"></a>Controller 的使用</h3><h4 id="部署应用"><a href="#部署应用" class="headerlink" title="部署应用"></a>部署应用</h4><h5 id="部署无状态应用"><a href="#部署无状态应用" class="headerlink" title="部署无状态应用"></a>部署无状态应用</h5><div class="admonition note"><p class="admonition-title">学习目标</p><p>本节将演示在 Kubernetes 集群中，如何通过 Deployment 部署一个 Nginx 的 Pod，并通过 Service 暴露端口，以便从 Kubernetes 集群外部访问 Nginx。</p></div><blockquote><p>Kubernetes 无状态应用（Stateless）的特点</p></blockquote><ul><li>主要特点：<ul><li>Pod 之间完全一致<ul><li>所有 Pod 完全等价，没有区别，任意一个 Pod 都可以处理请求。</li></ul></li><li>没有启动顺序要求<ul><li> Pod 启动、停止的先后顺序不影响整体业务。</li></ul></li><li>不依赖固定 Node 节点<ul><li> Pod 可以在任意 Node 节点上调度和运行，不需要绑定特定节点。</li></ul></li><li>可随意扩缩容<ul><li> Pod 数量可以随时增加或减少，自动水平扩容（HPA）非常适合。</li></ul></li><li>无持久化存储依赖<ul><li>不保存本地状态，数据通常存储在外部系统中，例如：数据库、对象存储、缓存服务等。</li></ul></li><li>Kubernetes 控制器<ul><li>通常使用 Deployment 管理，支持滚动升级和回滚。</li></ul></li></ul></li><li>典型代表：<ul><li>Nginx、Web 服务、后端接口、无状态微服务。</li></ul></li></ul><blockquote><p>Kubernetes 通过 Deployment 部署无状态应用</p></blockquote><ul><li>生成用于部署一个 Nginx 的 Deployment 的 YAML 配置文件，其中 Deployment 的名称为 <code>nginx</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create deployment nginx<span class="params"> --image</span>=nginx<span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml &gt; nginx_deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>自动生成的 YAML 配置文件的内容如下所示 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">labels:</span>             <span class="comment"># 标签：标识 Deployment 自身</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span>      <span class="comment"># 标签：在 Deployment 与 Pod 之间建立管理关系</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">strategy:</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">creationTimestamp:</span> <span class="literal">null</span></span><br><span class="line">      <span class="attr">labels:</span>         <span class="comment"># 标签：定义 Pod 的身份（标签）</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</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="bullet">-</span> <span class="attr">image:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">resources:</span> {}</span><br><span class="line"><span class="attr">status:</span> {}</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>配置信息</th><th>作用对象</th><th>核心作用</th><th>简单解释</th></tr></thead><tbody><tr><td><code>metadata.labels</code></td><td>Deployment 自身</td><td>标识 Deployment 自身</td><td>给 Deployment 资源对象自己打上标签，用于被其他资源（如 Service、HPA）查询和选择，或者方便用户管理。它说：” 我是谁。”</td></tr><tr><td><code>spec.selector.matchLabels</code></td><td>Deployment 的选择器</td><td>在 Deployment 与 Pod 之间建立管理关系</td><td> Deployment 用来寻找和管理拥有指定标签的 Pod 的规则。它说：” 哪些 Pod 归我管。”</td></tr><tr><td><code>spec.template.metadata.labels</code></td><td>Pod 模板</td><td>定义 Pod 的身份（标签）</td><td>规定了 Deployment 在创建新 Pod 时会为其打上的标签。它确保了新 Pod 都带有能被选择器识别的标签。它说：” 我创建的 Pod 长这样。”</td></tr></tbody></table><ul><li> 创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 Deployment）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-6799fc88d8-jwp6g   1/1     Running   0          29m   10.244.2.18   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 通过 Service 暴露无状态应用</p></blockquote><ul><li>生成 YAML 配置文件，用于为名称为 <code>nginx</code> 的 Deployment 创建一个 NodePort 类型的 Service，将该 Deployment 的 <code>80</code> 端口暴露到 Kubernetes 集群每个节点的静态端口上，以便从集群外部访问 Nginx</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl expose deployment nginx<span class="params"> --port</span>=80<span class="params"> --type</span>=NodePort<span class="params"> --target</span>-port=80<span class="params"> --name</span>=nginx<span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml &gt; nginx_service.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>自动生成的 YAML 配置文件的内容如下所示 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">creationTimestamp:</span> <span class="string">"2025-09-11T10:15:16Z"</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">managedFields:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line">    <span class="attr">fieldsType:</span> <span class="string">FieldsV1</span></span><br><span class="line">    <span class="attr">fieldsV1:</span></span><br><span class="line">      <span class="attr">f:metadata:</span></span><br><span class="line">        <span class="attr">f:labels:</span></span><br><span class="line">          <span class="string">.:</span> {}</span><br><span class="line">          <span class="attr">f:app:</span> {}</span><br><span class="line">      <span class="attr">f:spec:</span></span><br><span class="line">        <span class="attr">f:externalTrafficPolicy:</span> {}</span><br><span class="line">        <span class="attr">f:ports:</span></span><br><span class="line">          <span class="string">.:</span> {}</span><br><span class="line">          <span class="string">k:{"port":80,"protocol":"TCP"}:</span></span><br><span class="line">            <span class="string">.:</span> {}</span><br><span class="line">            <span class="attr">f:port:</span> {}</span><br><span class="line">            <span class="attr">f:protocol:</span> {}</span><br><span class="line">            <span class="attr">f:targetPort:</span> {}</span><br><span class="line">        <span class="attr">f:selector:</span></span><br><span class="line">          <span class="string">.:</span> {}</span><br><span class="line">          <span class="attr">f:app:</span> {}</span><br><span class="line">        <span class="attr">f:sessionAffinity:</span> {}</span><br><span class="line">        <span class="attr">f:type:</span> {}</span><br><span class="line">    <span class="attr">manager:</span> <span class="string">kubectl-expose</span></span><br><span class="line">    <span class="attr">operation:</span> <span class="string">Update</span></span><br><span class="line">    <span class="attr">time:</span> <span class="string">"2025-09-11T10:15:16Z"</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line">  <span class="attr">resourceVersion:</span> <span class="string">"491002"</span></span><br><span class="line">  <span class="attr">selfLink:</span> <span class="string">/api/v1/namespaces/default/services/nginx</span></span><br><span class="line">  <span class="attr">uid:</span> <span class="string">e3729f4c-17f9-4100-86dd-2178fe1aa65f</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">clusterIP:</span> <span class="number">10.0</span><span class="number">.0</span><span class="number">.106</span></span><br><span class="line">  <span class="attr">externalTrafficPolicy:</span> <span class="string">Cluster</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">nodePort:</span> <span class="number">30754</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">    <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">    <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">sessionAffinity:</span> <span class="string">None</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line"><span class="attr">status:</span></span><br><span class="line">  <span class="attr">loadBalancer:</span> {}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 Service）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_service.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Servcie 列表 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes   ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP        38d</span><br><span class="line">nginx        NodePort    10.0.0.106   &lt;none&gt;        80:30754/TCP   30s</span><br></pre></td></tr></tbody></table></figure><ul><li>通过浏览器访问 <code>http://192.168.2.112:30754</code>，请自行更改 IP 和端口；其中 IP 可以是 Kubernetes 集群任意节点的 IP 地址，端口由 <code>kubectl get svc</code> 命令可得知。若 Ngninx 容器在 Kubernetes 集群中创建并启动成功，则浏览器可以正常访问 Nginx 的首页（如下图所示）。</li></ul><blockquote><p>Kubernetes 删除 Deployment 和 Service</p></blockquote><ul><li>如果需要删除上面创建的 Deployment 和 Service，可以执行以下命令：</li></ul><figure class="highlight shell"><table><tbody><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"># 删除Service</span></span><br><span class="line">kubectl delete service nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除Deployment</span></span><br><span class="line">kubectl delete deployment nginx</span><br></pre></td></tr></tbody></table></figure><h5 id="部署有状态应用"><a href="#部署有状态应用" class="headerlink" title="部署有状态应用"></a>部署有状态应用</h5><div class="admonition note"><p class="admonition-title">学习目标</p><p>本节将演示在 Kubernetes 集群中，如何通过 StatefulSet 部署一个 Nginx 的 Pod，并通过 Headless Service（无头服务）暴露应用，以便从 Kubernetes 集群内部访问。</p></div><blockquote><p>Kubernetes 有状态应用（Stateful）的特点</p></blockquote><ul><li><p>主要特点：</p><ul><li>每个 Pod 独立且不可互换<ul><li>每个 Pod 都有自己的身份、配置、存储，不能随意替换或重建。</li></ul></li><li>需要固定的网络标识<ul><li> Pod 需要固定的名称（DNS）或网络标识符，便于集群内通信，比如 <code>mysql-0.mysql</code>。</li></ul></li><li>有启动和停止顺序<ul><li> Pod 必须按照特定顺序启动或停止。</li><li>例如，先启动主节点，再启动从节点。</li></ul></li><li>需要持久化存储<ul><li>必须使用 PersistentVolume（PV）保证数据不随 Pod 删除而丢失。</li></ul></li><li>Pod 调度位置需要考虑<ul><li> Pod 可能需要绑定特定的 Node 节点，确保存储卷挂载一致性或性能需求。</li></ul></li><li>Kubernetes 控制器<ul><li>使用 StatefulSet 管理，支持：<ul><li>固定的 Pod 命名（如 <code>mysql-0</code>, <code>mysql-1</code>）；</li><li>有序部署、有序扩容和缩容；</li><li>持久化存储与 Pod 一一对应。</li></ul></li></ul></li></ul></li><li><p>典型代表：</p><ul><li>MySQL、Kafka、ZooKeeper、ElasticSearch、Redis Cluster。</li></ul></li></ul><blockquote><p>Kubernetes 通过 StatefulSet 部署有状态应用</p></blockquote><ul><li>创建 YAML 配置文件（比如 <code>nginx_statefulset.yaml</code>），用于通过 StatefulSet 部署一个 Nginx 的 Pod</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">StatefulSet</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-statefulset</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">default</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">serviceName:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</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">nginx</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:latest</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 StatefulSet）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_statefulset.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态，可以看到 Pod 的名称是根据一定规则生成的（全局唯一）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                  READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-statefulset-0   1/1     Running   0          77s   10.244.2.29   k8s-node3    &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-statefulset-1   1/1     Running   0          65s   10.244.3.11   k8s-node2    &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-statefulset-2   1/1     Running   0          37s   10.244.0.15   k8s-node1    &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 通过 Service 暴露有状态应用</p></blockquote><ul><li>创建 YAML 配置文件（如 <code>nginx_service.yaml</code>），用于通过 Service 暴露有状态应用 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line">  <span class="attr">clusterIP:</span> <span class="string">None</span>   <span class="comment"># None 表示无头服务（Headless Service）</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br></pre></td></tr></tbody></table></figure><div class="admonition note"><p class="admonition-title">无头服务（Headless Service）是什么</p><ul><li>无头服务是没有虚拟 IP（ClusterIP）的 Service（Headless Service），是一种没有 ClusterIP 的特殊 Service 类型，详细介绍请看 <a href="/posts/6bf07963.html#%E4%BA%94%E5%A4%A7%E7%B1%BB%E5%9E%8B">这里</a>。</li><li>无头服务可以用于暴露 Kubernetes 集群内 Pod 的真实 IP 和 DNS 名称（域名），而不是通过一个统一的虚拟 IP（ClusterIP）进行负载均衡，即 Kubernetes 不会做流量负载均衡。</li></ul></div><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 Service）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_service.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Servcie 列表，可以看到有状态应用的 ClusterIP 为 <code>None</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get svc</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE</span><br><span class="line">kubernetes   ClusterIP   10.0.0.1     &lt;none&gt;        443/TCP   53d</span><br><span class="line">nginx        ClusterIP   None         &lt;none&gt;        60/TCP    2m12s</span><br></pre></td></tr></tbody></table></figure><div class="admonition note"><p class="admonition-title">提示</p><ul><li>通过 Headless Service 暴露 Pod 后，每个 Pod 都有一个唯一的名称和 DNS 名称（域名），域名格式：<code>&lt;pod-name&gt;.&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code>，默认的命名空间（<code>namespace</code>）是 <code>default</code>。</li><li>在 Kubernetes 集群内，可以通过 Pod 的 DNS 域名直接访问 Pod（前提是已经<a href="/posts/ccd6f2d4.html#%E9%83%A8%E7%BD%B2-CoreDNS%EF%BC%88%E5%8F%AF%E9%80%89%EF%BC%89">部署 CoreDNS</a>），比如：<code>http:://nginx-statefulset-0.nginx.default.svc.cluster.local:80</code>。</li></ul></div><blockquote><p>在 Kubernetes 集群内部，通过 Service 的 DNS 名称（域名）访问有状态应用</p></blockquote><div class="admonition warning"><p class="admonition-title">特别注意</p><p>ClusterIP 为 <code>None</code> 的 Service（Headless Service）只能在 Kubernetes 集群内部访问，如果在集群外部机器（比如直接在集群的 Master 节点）上，通过 Pod 的 DNS 名称直接访问 Pod（比如 <code>http://nginx-statefulset-0.nginx.default.svc.cluster.local:80</code>），肯定是无法访问成功的。</p></div><ul><li>创建一个临时 Pod，并进入 Pod 内部的交互式 Shell</li></ul><figure class="highlight shell"><table><tbody><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"># 添加 --rm 参数，为了在 Shell 中执行 exit 命令退出后自动销毁 Pod</span></span><br><span class="line">kubectl run <span class="built_in">test</span>-pod<span class="params"> --image</span>=busybox:1.35<span class="params"> --restart</span>=Never<span class="params"> -it</span><span class="params"> --rm</span> -- sh</span><br></pre></td></tr></tbody></table></figure><ul><li>在临时 Pod 的内部，可以通过 Service 的 DNS 名称（域名）查询所有匹配 Pod 的 IP（必须保证临时 Pod 与 Service 处于同一个命名空间），域名格式：<code>&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nslookup nginx.default.svc.cluster.<span class="built_in">local</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Server:10.0.0.2</span><br><span class="line">Address:10.0.0.2:53</span><br><span class="line"></span><br><span class="line">Name:nginx.default.svc.cluster.local</span><br><span class="line">Address: 10.244.3.11</span><br><span class="line">Name:nginx.default.svc.cluster.local</span><br><span class="line">Address: 10.244.2.29</span><br><span class="line">Name:nginx.default.svc.cluster.local</span><br><span class="line">Address: 10.244.0.15</span><br></pre></td></tr></tbody></table></figure><ul><li>在临时 Pod 的内部，还可以通过 Pod 的 DNS 名称（域名）直接访问指定的 Pod，域名格式：<code>&lt;pod-name&gt;.&lt;service-name&gt;.&lt;namespace&gt;.svc.cluster.local</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nslookup nginx-statefulset-0.nginx.default.svc.cluster.<span class="built_in">local</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Server:10.0.0.2</span><br><span class="line">Address:10.0.0.2:53</span><br><span class="line"></span><br><span class="line">Name:nginx-statefulset-0.nginx.default.svc.cluster.local</span><br><span class="line">Address: 10.244.2.29</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 或者通过 Pod 的 DNS 名称（域名）访问 Nginx 的首页面</span></span><br><span class="line">wget<span class="params"> -qO</span>- http://nginx-statefulset-0.nginx.default.svc.cluster.<span class="built_in">local</span>:80</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 删除 StatefulSet 和 Service</p></blockquote><ul><li>如果需要删除上面创建的 StatefulSet 和 Service，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除 Service</span></span><br><span class="line">kubectl delete service nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除 StatefulSet，--cascade=true 参数表示连同 Pod 一起删除，但 PVC 默认不会被删除（如果存在）</span></span><br><span class="line">kubectl delete statefulset nginx-statefulset<span class="params"> --cascade</span>=<span class="literal">true</span></span><br></pre></td></tr></tbody></table></figure><h5 id="部署守护进程"><a href="#部署守护进程" class="headerlink" title="部署守护进程"></a>部署守护进程</h5><div class="admonition note"><p class="admonition-title">note</p><ul><li>本节将演示在 Kubernetes 集群中，如何通过 DaemonSet 在每个 Node（工作节点）上分别部署一个守护进程。</li><li>这里的守护进程是指节点级的后台服务，比如日志收集（Filebeat）、网络插件（Flannel）、监控代理（Prometheus Node Exporter）等服务，为了方便演示，下面直接使用 Nginx 来代替。</li></ul></div><blockquote><p>Kubernetes 中 DaemonSet 的主要功能和适用场景</p></blockquote><ul><li><p>主要功能：</p><ul><li>在每个节点上运行一个指定的 Pod<ul><li> 自动在集群中每个符合条件的节点上部署且只运行一个指定的 Pod 实例。</li></ul></li><li>节点加入自动部署<ul><li>当新节点加入集群时，DaemonSet 会自动在该节点上调度并启动 Pod。</li></ul></li><li>节点移除自动回收<ul><li>节点被移除或不可用时，对应 Pod 会自动删除，保持一致性。</li></ul></li><li>不支持手动扩容 / 缩容<ul><li> Pod 的副本数量与节点数量直接关联，不支持手动管理 <code>replicas</code>。</li></ul></li><li>支持滚动更新与回滚<ul><li>可平滑升级版本，并在出现问题时快速回滚。</li></ul></li><li>可结合节点选择器、节点亲和性、污点 / 容忍等使用<ul><li>支持精确控制 DaemonSet Pod 部署在哪些节点上。</li></ul></li><li>与 Deployment 区别<ul><li> Deployment：通常用于无状态服务，副本数固定，由用户定义。</li><li>DaemonSet：与节点数量绑定，强调 “每个节点一个 Pod”。</li></ul></li><li>删除行为可控<ul><li>使用 <code>kubectl delete daemonset</code> 删除 DaemonSet 时，可通过 <code>--cascade=orphan</code> 参数控制是否保留关联的 Pod。</li></ul></li></ul></li><li><p>适用场景：</p><ul><li>日志收集<ul><li>部署日志收集 Agent（如 Fluentd、Logstash、Filebeat），保证每个节点日志都能被采集。</li></ul></li><li>监控与指标采集<ul><li>例如 Prometheus Node Exporter、Datadog Agent、cAdvisor 等，采集节点和 Pod 的监控指标。</li></ul></li><li>网络插件或 CNI 管理<ul><li> Kubernetes CNI 插件通常以 DaemonSet 运行，例如 Calico、Flannel、Cilium 等。</li></ul></li><li>存储插件或 CSI 驱动<ul><li>如 Ceph、Rook 等分布式存储系统的节点守护进程。</li></ul></li><li>安全与合规审计<ul><li>如 Falco、Sysdig Secure 等安全审计或防护 Agent。</li></ul></li><li>节点运维任务<ul><li>自动在每个节点运行健康检查、系统维护脚本或运维工具。</li></ul></li></ul></li></ul><blockquote><p>Kubernetes 通过 DaemonSet 部署守护进程</p></blockquote><ul><li>创建 YAML 配置文件（比如 <code>nginx_daemonset.yaml</code>），用于通过 DaemonSet 在每个节点上分别部署一个 Nginx 的 Pod，，并将宿主机内的 <code>/var/log</code> 目录挂载到容器内部的 <code>/tmp/log</code> 目录（可选操作）</li></ul><figure class="highlight yml"><table><tbody><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"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">DaemonSet</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-daemonset</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span>   <span class="comment"># DaemonSet 资源自身的标签，用于标识和选择这个 DaemonSet 资源</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span>   <span class="comment"># DaemonSet 选择器标签，用于匹配和管理具有此标签的 Pod</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">nginx</span>   <span class="comment"># Pod 模板标签，DaemonSet 创建的每个 Pod 都会被打上这个标签</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="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.15</span></span><br><span class="line">        <span class="attr">ports:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">volumeMounts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">varlog</span></span><br><span class="line">          <span class="attr">mountPath:</span> <span class="string">/tmp/log</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">varlog</span></span><br><span class="line">        <span class="attr">hostPath:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">/var/log</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 DaemonSet）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_daemonset.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 DaemonSet 的状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get daemonset nginx-daemonset</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE</span><br><span class="line">nginx-daemonset   4         4         4       4            4           &lt;none&gt;          6m17s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态，可以发现每个 Node（工作节点）都部署了一个 Nginx 的 Pod</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                    READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-daemonset-mhxpg   1/1     Running   0          4s    10.244.2.35   k8s-node3    &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-daemonset-nsb7r   1/1     Running   0          4s    10.244.1.16   k8s-node1    &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-daemonset-p9k2d   1/1     Running   0          4s    10.244.3.17   k8s-node2    &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>进入某个 Pod 内部查看挂载的日志文件 </li></ul><figure class="highlight shell"><table><tbody><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"># 进入某个 Pod 的内部</span></span><br><span class="line">kubectl <span class="built_in">exec</span><span class="params"> -it</span> nginx-daemonset-p9k2d bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 Pod 内部的日志文件</span></span><br><span class="line">ls /tmp/<span class="built_in">log</span>/</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 删除 DaemonSet</p></blockquote><ul><li>如果需要删除上面创建的 DaemonSet，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除 DaemonSet，会级联删除该 DaemonSet 以及由它创建的所有 Pod</span></span><br><span class="line">kubectl delete daemonset nginx-daemonset</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者使用 --cascade=orphan 参数，只删除 DaemonSet，但保留它创建的所有 Pod</span></span><br><span class="line">kubectl delete daemonset nginx-daemonset<span class="params"> --cascade</span>=orphan</span><br></pre></td></tr></tbody></table></figure><h5 id="部署一次性任务"><a href="#部署一次性任务" class="headerlink" title="部署一次性任务"></a>部署一次性任务</h5><div class="admonition note"><p class="admonition-title">提示</p><p>本节将演示在 Kubernetes 集群中，如何通过 Job 执行一次性任务。</p></div><blockquote><p>Kubernetes 中 Job 的主要功能和适用场景</p></blockquote><ul><li><p>主要功能</p><ul><li>执行一次性任务，保证任务至少成功执行一次<ul><li> Job 会确保定义的 Pod 按照预期执行，直到成功完成（运行状态为 <code>Completed</code>）。</li><li>Pod 运行失败时，Job 会根据重试策略自动重新创建新的 Pod 继续执行任务。</li></ul></li><li>支持并行或串行执行<ul><li>可以通过 <code>spec.parallelism</code> 控制同时运行的 Pod 数量；</li><li>可以通过 <code>spec.completions</code> 控制任务总共需要成功完成的 Pod 数量。</li></ul></li><li>适合一次性任务，执行完成后不会再次运行<ul><li> Job 完成后，Pod 不会被自动删除，但状态保持为 <code>Completed</code>；</li><li>可以通过配置 TTL 控制器自动清理已完成的 Job 及 Pod。</li></ul></li></ul></li><li><p>适用场景</p><ul><li>一次性批处理作业<ul><li>数据清理、日志分析、批量数据转换等。</li></ul></li><li>离线计算任务<ul><li>机器学习模型训练、视频转码、大数据计算等。</li></ul></li><li>自动化任务<ul><li>备份数据库、生成报表、执行临时脚本等。</li></ul></li><li>测试任务<ul><li>压力测试、集成测试或单次验证任务。</li></ul></li></ul></li></ul><blockquote><p>Kubernetes 通过 Job 执行一次性任务</p></blockquote><ul><li>创建 YAML 配置文件（比如 <code>pi_job.yaml</code>），用于通过 Job 执行一次 Perl 脚本（计算圆周率）</li></ul><figure class="highlight yml"><table><tbody><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">batch/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Job</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pi-job</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="comment"># completions: 3                # 总共需要 3 个 Pod 完成任务</span></span><br><span class="line">  <span class="comment"># parallelism: 2                # 最多允许 2 个 Pod 并行运行</span></span><br><span class="line">  <span class="comment"># ttlSecondsAfterFinished: 60   # 任务执行完成后 60 秒自动清理</span></span><br><span class="line">  <span class="attr">backoffLimit:</span> <span class="number">4</span>                 <span class="comment"># 任务执行失败后，最大重试次数为 4</span></span><br><span class="line">  <span class="attr">template:</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="bullet">-</span> <span class="attr">name:</span> <span class="string">pi</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">perl</span></span><br><span class="line">        <span class="attr">command:</span> [<span class="string">"perl"</span>, <span class="string">"-Mbignum=bpi"</span>, <span class="string">"-wle"</span>, <span class="string">"print bpi(2000)"</span>]    <span class="comment"># 计算圆周率，并打印日志信息</span></span><br><span class="line">      <span class="attr">restartPolicy:</span> <span class="string">Never</span>        <span class="comment"># Pod 失败（任务执行失败）时的重启策略</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 Job）</li></ul><figure class="highlight yml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">kubectl</span> <span class="string">apply</span> <span class="string">-f</span> <span class="string">pi_job.yaml</span></span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Job 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get <span class="built_in">jobs</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME     COMPLETIONS   DURATION   AGE</span><br><span class="line">pi-job   1/1           25s        25s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态，可以发现当 Job（一次性任务）执行完成后，Pod 的状态会切换为 <code>Completed</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME       READY   STATUS      RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">pi-ks6pn   0/1     Completed   0          41s   10.244.2.36   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 的日志信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl logs pi-ks6pn</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 删除 Job</p></blockquote><ul><li>如果需要删除上面创建的 Job，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除 Job，会级联删除该 Job 以及由它创建的所有 Pod</span></span><br><span class="line">kubectl delete job pi-job</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者使用 --cascade=orphan 参数，只删除 Job，但保留它创建的所有 Pod</span></span><br><span class="line">kubectl delete job pi-job<span class="params"> --cascade</span>=orphan</span><br></pre></td></tr></tbody></table></figure><h5 id="部署定时任务"><a href="#部署定时任务" class="headerlink" title="部署定时任务"></a>部署定时任务</h5><div class="admonition note"><p class="admonition-title">提示</p><p>本节将演示在 Kubernetes 集群中，如何通过 CronJob 周期性（定时）执行任务。</p></div><blockquote><p>Kubernetes 中 CronJob 的主要功能和适用场景</p></blockquote><ul><li><p>主要功能</p><ul><li>周期性任务调度<ul><li>使用 <code>cron</code> 表达式定义执行计划，精确到分钟；</li><li>可在每天、每周、每月或特定时间点自动运行任务。</li></ul></li><li>自动创建 Job<ul><li>CronJob 在到达调度时间后，会自动创建对应的 Job 资源；</li><li>Job 负责任务的执行、失败重试和状态维护。</li></ul></li><li>控制并发执行<ul><li>可通过 <code>concurrencyPolicy</code> 控制多次调度的 Job 是否允许并发执行：<ul><li><code>Allow</code>：允许多个任务并发运行；</li><li><code>Forbid</code>：禁止并发，若上一个任务未完成，跳过新的调度；</li><li><code>Replace</code>：如果上一个任务未完成，先删除旧任务，再启动新任务。</li></ul></li></ul></li><li>支持任务历史管理<ul><li>可以配置保留的成功任务和失败任务历史数量，避免资源无限增长。</li></ul></li><li>支持一次性定时任务<ul><li>通过指定一次性运行的时间点，实现一次性定时触发的 Job。</li></ul></li></ul></li><li><p>适用场景</p><ul><li>定时数据处理<ul><li>每天凌晨自动跑批处理作业，生成统计报表；</li><li>定时清理临时文件或过期数据。</li></ul></li><li>数据库备份<ul><li>每天或每小时自动执行数据库备份任务。</li></ul></li><li>日志归档<ul><li>定期收集、压缩和上传日志文件到集中存储。</li></ul></li><li>周期性健康检查<ul><li>定时执行诊断脚本或检查任务，输出报告。</li></ul></li><li>定时通知或消息推送<ul><li>定时触发消息发送、告警提醒或业务事件。</li></ul></li><li>一次性延时执行任务<ul><li>通过设置特定时间点，完成一次性延时任务的执行。</li></ul></li></ul></li></ul><blockquote><p>Kubernetes 通过 CronJob 运行定时任务</p></blockquote><ul><li>创建 YAML 配置文件（比如 <code>hello_cronjob.yaml</code>），用于通过 CronJob 周期性地执行 Shell 脚本（打印日志信息）</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">batch/v1beta1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">CronJob</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">hello-cronjob</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">schedule:</span> <span class="string">"*/1 * * * *"</span>            <span class="comment"># Cron 表达式</span></span><br><span class="line">  <span class="comment"># concurrencyPolicy: Forbid        # 禁止并发执行任务</span></span><br><span class="line">  <span class="comment"># startingDeadlineSeconds: 300     # 最长允许任务延迟启动 5 分钟</span></span><br><span class="line">  <span class="comment"># successfulJobsHistoryLimit: 3    # 保留最近 3 次执行成功的任务</span></span><br><span class="line">  <span class="comment"># failedJobsHistoryLimit: 1        # 保留最近 1 次执行失败的任务</span></span><br><span class="line">  <span class="attr">jobTemplate:</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">template:</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="bullet">-</span> <span class="attr">name:</span> <span class="string">hello</span></span><br><span class="line">            <span class="attr">image:</span> <span class="string">busybox</span></span><br><span class="line">            <span class="attr">args:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">/bin/sh</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">-c</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">date;</span> <span class="string">echo</span> <span class="string">Hello</span> <span class="string">from</span> <span class="string">the</span> <span class="string">Kubernetes</span> <span class="string">cluster</span></span><br><span class="line">          <span class="attr">restartPolicy:</span> <span class="string">OnFailure</span>    <span class="comment"># Pod 失败（任务执行失败）时的重启策略</span></span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 CronJob）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> hello_cronjob.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 CronJob 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get cronjobs</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME            SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE</span><br><span class="line">hello-cronjob   */1 * * * *   False     0        49s             52s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Job 的运行状态，CronJob 不直接创建和运行 Pod，所有 Pod 都由其生成的 Job 进行管理 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get <span class="built_in">jobs</span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                       COMPLETIONS   DURATION   AGE</span><br><span class="line">hello-cronjob-1759064520   1/1           17s        3m11s</span><br><span class="line">hello-cronjob-1759064580   1/1           13s        2m20s</span><br><span class="line">hello-cronjob-1759064640   1/1           17s        78s</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态，可以发现当 CronJob 每次执行完成后，Pod 的状态会切换为 Completed</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                             READY   STATUS      RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">hello-cronjob-1759064340-mfppj   0/1     Completed   0          2m28s   10.244.2.38   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">hello-cronjob-1759064400-djk6l   0/1     Completed   0          86s     10.244.2.39   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">hello-cronjob-1759064460-5lbxj   0/1     Completed   0          25s     10.244.3.18   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>查看 Pod 的日志信息 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl logs hello-cronjob-1759064460-5lbxj</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">Sun Sep 28 13:01:26 UTC 2025</span><br><span class="line">Hello from the Kubernetes cluster</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Kubernetes 删除 CronJob</p></blockquote><ul><li>如果需要删除上面创建的 CronJob，可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 删除 CronJob，会级联删除该 CronJob 以及由它创建的所有 Job 和 Pod</span></span><br><span class="line">kubectl delete cronjob hello-cronjob</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者使用 --cascade=orphan 参数，只删除 CronJob，但保留它创建的所有 Job 和 Pod</span></span><br><span class="line">kubectl delete cronjob hello-cronjob<span class="params"> --cascade</span>=orphan</span><br></pre></td></tr></tbody></table></figure><h4 id="应用升级回滚"><a href="#应用升级回滚" class="headerlink" title="应用升级回滚"></a>应用升级回滚</h4><div class="admonition note"><p class="admonition-title">学习目标</p><p>本节将演示在 Kubernetes 集群中部署 Nginx 后，如何升级 Nginx 的版本，还有如何回滚 Nginx 的版本，更详细的应用升级回滚实战可以看 <a href="/posts/6158b4d2.html#%E5%8D%87%E7%BA%A7-%E5%9B%9E%E6%BB%9A-Java-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F">这里</a>。</p></div><h5 id="部署应用-1"><a href="#部署应用-1" class="headerlink" title="部署应用"></a>部署应用</h5><ul><li>生成用于部署一个 Nginx 的 Deployment 的 YAML 配置文件，其中 Deployment 的名称为 <code>nginx</code>，Nginx 的版本号为 <code>1.14</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create deployment nginx<span class="params"> --image</span>=nginx:1.14<span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml &gt; nginx_deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>更改自动生成的 YAML 配置文件内容（如下所示），将 <code>replicas</code> 改为 <code>2</code>（表示 Pod 有两个副本）</li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">labels:</span>             <span class="comment"># 标签：标识 Deployment 自身</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span>      <span class="comment"># 标签：在 Deployment 与 Pod 之间建立管理关系</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">strategy:</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">creationTimestamp:</span> <span class="literal">null</span></span><br><span class="line">      <span class="attr">labels:</span>         <span class="comment"># 标签：定义 Pod 的身份（标签）</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</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="bullet">-</span> <span class="attr">image:</span> <span class="string">nginx:1.14</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">resources:</span> {}</span><br><span class="line"><span class="attr">status:</span> {}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 Deployment）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-5658bdf5d4-6sbmg   1/1     Running   0          31m   10.244.1.2   k8s-node1   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-5658bdf5d4-ksjgm   1/1     Running   0          31m   10.244.3.2   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h5 id="升级应用版本"><a href="#升级应用版本" class="headerlink" title="升级应用版本"></a>升级应用版本</h5><ul><li>当执行完上述步骤将 Nginx 部署到 Kubernetes 集群后，可以执行以下命令来升级 Nginx 的版本（比如，将 Nginx 升级到 <code>1.15</code> 版本）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl <span class="built_in">set</span> image deployment nginx nginx=nginx:1.15</span><br></pre></td></tr></tbody></table></figure><ul><li>查看应用升级版本的状态（过程）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl rollout status deployment nginx</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-764b95f4c5-f744r   1/1     Running   0          31m   10.244.1.2   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-764b95f4c5-w96gl   1/1     Running   0          31m   10.244.3.2   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><hr><ul><li>若执行完应用升级版本的命令后，在查看所有 Pod 的运行状态时，新 Pod 的 <code>STATUS</code> 一直显示 <code>ContainerCreating</code>，则说明应用升级版本存在问题 </li></ul><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS              RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-5658bdf5d4-6sbmg   1/1     Running             0          77m     10.244.1.2    k8s-node1   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-764b95f4c5-f744r   1/1     Running             0          27m     10.244.2.19   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-764b95f4c5-w96gl   0/1     ContainerCreating   0          3m36s   &lt;none&gt;        k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><ul><li>可以执行以下命令来排查问题（比如，原因是一直卡在拉取镜像的环节上）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl describe pod nginx-764b95f4c5-w96gl</span><br></pre></td></tr></tbody></table></figure><ul><li>若无法排查应用升级版本的问题，则可以删除卡住的 Pod 以触发重建 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl delete pod nginx-764b95f4c5-w96gl</span><br></pre></td></tr></tbody></table></figure><ul><li>若无法排查应用升级版本的问题，又急需恢复服务，最快捷的方法是回滚（回退）到上一个版本（即用旧版本的 Pod 替换掉所有新版本的 Pod），之后服务会逐渐恢复正常 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl rollout undo deployment nginx</span><br></pre></td></tr></tbody></table></figure><div class="admonition warning"><p class="admonition-title">特别注意</p><p>Kubernetes 在升级应用的版本时，是不会中断服务的（比如，升级期间 Nginx 依然可以对外提供服务），这主要归功于其精细的流量控制和渐进的替换过程。具体是通过 Deployment 控制器，先启动新版本的 Pod 并确认其就绪，然后逐步终止旧版本的 Pod，同时 Service 的负载均衡器会确保流量只路由到健康的 Pod 上，从而实现了服务的平滑升级。</p></div><h5 id="回滚应用版本"><a href="#回滚应用版本" class="headerlink" title="回滚应用版本"></a>回滚应用版本</h5><ul><li>当执行完上述步骤将 Nginx 的版本从 <code>1.14</code> 升级到 <code>1.15</code> 后，若希望回滚到旧的版本（即用旧版本的 Pod 替换掉所有新版本的 Pod），可以执行以下命令 </li></ul><figure class="highlight shell"><table><tbody><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"># 查看指定 Deployment 的所有历史版本</span></span><br><span class="line">kubectl rollout <span class="built_in">history</span> deployment nginx</span><br></pre></td></tr></tbody></table></figure><figure class="highlight shell"><table><tbody><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"># 回滚指定 Deployment 到上一个版本</span></span><br><span class="line">kubectl rollout undo deployment nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者，回滚指定 Deployment 到指定的版本</span></span><br><span class="line">kubectl rollout undo deployment nginx<span class="params"> --to</span>-revision=2</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-5658bdf5d4-6sbmg   1/1     Running   0          31m   10.244.1.2   k8s-node1   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-5658bdf5d4-ksjgm   1/1     Running   0          31m   10.244.3.2   k8s-node2   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h4 id="应用弹性伸缩"><a href="#应用弹性伸缩" class="headerlink" title="应用弹性伸缩"></a>应用弹性伸缩</h4><div class="admonition note"><p class="admonition-title">学习目标</p><p>本节将演示在 Kubernetes 集群中部署 Nginx 后，如何对 Nginx 进行扩容（即增加 Pod 的副本数量），更详细的应用扩容缩容实战可以看 <a href="/posts/6158b4d2.html/posts/6158b4d2.html#%E6%89%A9%E5%AE%B9-%E7%BC%A9%E5%AE%B9-Java-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F">这里</a>。</p></div><h5 id="部署应用-2"><a href="#部署应用-2" class="headerlink" title="部署应用"></a>部署应用</h5><ul><li>生成用于部署一个 Nginx 的 Deployment 的 YAML 配置文件，其中 Deployment 的名称为 <code>nginx</code></li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create deployment nginx<span class="params"> --image</span>=nginx<span class="params"> --dry</span>-run=client<span class="params"> -o</span> yaml &gt; nginx_deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>自动生成的 YAML 配置文件的内容如下所示 </li></ul><figure class="highlight yml"><table><tbody><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="attr">apiVersion:</span> <span class="string">apps/v1</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">labels:</span>             <span class="comment"># 标签：标识 Deployment 自身</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx</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">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span>      <span class="comment"># 标签：在 Deployment 与 Pod 之间建立管理关系</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">strategy:</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">creationTimestamp:</span> <span class="literal">null</span></span><br><span class="line">      <span class="attr">labels:</span>         <span class="comment"># 标签：定义 Pod 的身份（标签）</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</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="bullet">-</span> <span class="attr">image:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">resources:</span> {}</span><br><span class="line"><span class="attr">status:</span> {}</span><br></pre></td></tr></tbody></table></figure><ul><li>创建或更新 YAML 文件中定义的 Kubernetes 资源对象（比如 Deployment）</li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply<span class="params"> -f</span> nginx_deployment.yaml</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-5658bdf5d4-kt6fv   1/1     Running   0          29m   10.244.2.18   k8s-node3   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h5 id="应用扩缩容"><a href="#应用扩缩容" class="headerlink" title="应用扩缩容"></a>应用扩缩容</h5><ul><li>当执行完上述步骤将 Nginx 部署到 Kubernetes 集群后，可以执行以下命令来对 Pod 的副本数进行扩缩容 </li></ul><figure class="highlight shell"><table><tbody><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"># 对 Pod 的副本进行扩容（比如，扩容至 3 个副本）</span></span><br><span class="line">kubectl scale deployment nginx<span class="params"> --replicas</span>=3</span><br></pre></td></tr></tbody></table></figure><ul><li>查看所有 Pod 的运行状态 </li></ul><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods<span class="params"> -o</span> wide</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><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">NAME                     READY   STATUS    RESTARTS   AGE     IP           NODE         NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-5658bdf5d4-kt6fv   1/1     Running   0          29m     10.244.0.7   k8s-node3    &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-5658bdf5d4-tbvzz   1/1     Running   0          2m55s   10.244.1.3   k8s-node1    &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-5658bdf5d4-z48mx   1/1     Running   0          2m57s   10.244.3.6   k8s-node2    &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></tbody></table></figure><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/posts/6158b4d2.html#%E5%AE%9E%E6%88%98%E9%83%A8%E7%BD%B2-Java-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F">Kubernetes 实战部署 Java 应用程序</a></li></ul>]]></content>
    
    
    <summary type="html">本文主要介绍 Kubernetes 的入门使用教程。</summary>
    
    
    
    <category term="hide" scheme="https://www.techgrow.cn/categories/hide/"/>
    
    
    <category term="容器化" scheme="https://www.techgrow.cn/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/"/>
    
  </entry>
  
</feed>
