<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>龙天的博客</title>
  <icon>https://blog.longtian.io/icon.png</icon>
  <subtitle>大前端，DevOps，敏捷开发</subtitle>
  <link href="https://blog.longtian.io/atom.xml" rel="self"/>
  
  <link href="https://blog.longtian.io/"/>
  <updated>2024-11-06T15:55:39.000Z</updated>
  <id>https://blog.longtian.io/</id>
  
  <author>
    <name>longtian</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>[译] 等了 20 年，实时 Linux 终于进入了主线</title>
    <link href="https://blog.longtian.io/2024/11/06/rtos.html"/>
    <id>https://blog.longtian.io/2024/11/06/rtos.html</id>
    <published>2024-11-06T15:55:39.000Z</published>
    <updated>2024-11-06T15:55:39.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.zdnet.com/article/20-years-later-real-time-linux-makes-it-to-the-kernel-really/">原文</a></p><p>实时 Linux 相关的工作已经让开源操作系统受益了好多年，但直到这周 Linus Torvalds 才终于接受它的最后一部分进入主线内核。为什么会花这么久呢？</p><p>作者： Steven Vaughan-Nichols 资深特约编辑</p><p>2024 年 9 月 18 号</p><p>维也纳 – 实时 Linux （<code>PREEMPT_RT</code>） 终于在 20 年后进入了主线内核。Linus Torvalds 在欧洲开源峰会上为这份代码祈福。为什么这是个大事件？让我们首先介绍一下实时操作系统（RTOS）和它适用场景。</p><h1 id="什么是-RTOS-？"><a href="#什么是-RTOS-？" class="headerlink" title="什么是 RTOS ？"></a>什么是 RTOS ？</h1><p>RTOS 是一种专门的操作系统，旨在精准和可靠地处理时间关键任务。与 Windows 或者 MacOS 这类通用操作系统不同，RTOS 建立之初是为了在严格的时间限制下响应事件和处理数据，通常是以毫秒或微秒为单位。正如著名的实时 Linux 开发者、谷歌员工 Steven Rostedet 所说：“实时是最快的最坏场景。” </p><p>他的意思是 RTOS 最基本的特征是确定性行为。RTOS 能够保证关键任务在指定期限内完成。很多人认为 RTOS 就是为了快速处理。其实不然，速度不是 RTOS 的关键，可靠性才是。这种可预测性在时间关键应用中尤为重要，例如工业控制系统，医疗设备和航空航天器材。</p><p>当今一个正被使用的 RTOS 例子是 VxWorks，它在 NASA 火星探测器上被用来导航，并在波音 787 梦想号客机上用作飞行控制系统，确保飞行控制的实时响应。另一个例子是 QNX Neutrino，它被广泛应用与车载信息娱乐系统和高级辅助驾驶系统中，比如防抱死刹车系统。</p><h1 id="实时-Linux-的历史"><a href="#实时-Linux-的历史" class="headerlink" title="实时 Linux 的历史"></a>实时 Linux 的历史</h1><p>实时 Linux 的代码现在已经通过即将发布的 Linux 6.12 内核被纳入到了所有的 Linux 发行版中。这意味着 Linux 很快会出现在更多的关键任务设备和工业硬件上。 这着实花了不少时间。</p><p>实时 Linux 起源于 1990 年代对实时应用的不断增长的需求。早期工作主要致力于在 Linux 内核之外创建一个单独的内核。它包括若干个学术项目，比如堪萨斯大学的 KURT 项目，米兰大学的 RTAI 项目和新墨西哥矿业理工学院的 RTLinux。</p><p>到 2004 年，Ingo Molnar，一个资深的 Linux 内核开发者开始收集和重塑这些技术碎片，并奠定了实时抢占补丁集 <code>PREEMPT_RT</code> 的基础。</p><p>这个方法和早期的实时 Linux 的方案不同，因为它修改了已有的 Linux 内核而不是创建另外一个单独的实时内核。到 2006 年，它已经获得了足够的关注，以至于 Linus  Torvalds 说 “用 Linux 控制一束激光很疯狂，但这个屋子每个人都或多或少有一些疯狂。所以如果你想用 Linux 来控制工业焊接激光器，我对你使用 PREEPT_RT 没意见”</p><p>到 2009 年，一个包含了 Thomas Gleixner, Peter Ziljstra 和 Rostedt 的内核开发者小队，已经把之前的若干原型开发整合成了一个单独的树外补丁集。也就是从这个时候开始，很多公司开始使用这个补丁集来创建需要精确到毫秒的硬实时工业系统。</p><p>随着项目推进，它的很多组件已经进入了内核。 Rostedt 告诉我，说实时现在才进入 Linux 在某种程度上是错误的。它的很多功能已经进入了主线 Linux 很多年。有些对于你日常使用的 Linux 已经是必不可少。</p><p>比如，你可能从来没听说过 “NO_HZ” ，它能够减少系统在空闲状态下的功耗。“NO_HZ” 使得 Linux 能够在有几千个 CPU 的机器上高效运行。“你感受不到实时补丁给 Linux 带来了多大的改进” Rostedt 强调说 “我们的工作是 Linux 能够在数据中心里运行的唯一原因”</p><p>因此，如果没有 “NO_HZ”，Linux 就基本上不能在所有数据中运行。这也反过来解释了为什么 Linux 能在云上运行。我不知道没有这个实时补丁的贡献世界将会怎么样，但是它一定不像今天这样。</p><p>起初，谁做梦都不会想到实时 Linux 被证明有用的方式。 Rostedt 回忆道 “早在 2005 年的时候，我收到一个关于实时的错误报告，我回复：‘嘿这是修复的代码，你能应用它吗？’那个家伙说‘我不知道我在做什么’，我回复：‘等等你不是一个内核开发者吗？’他答道：‘我是一个吉他手’” </p><p>原来，他之所以会用到早期的实时性补丁是因为他在用一个叫做 JACK 的系统，这是一个低延迟音频链接的声音服务器。他和大多数音乐人一样，穷到没有钱买高级的设备，Rostedt 继续说道 “他搞到一台便宜的笔记本，上面装着 Linux 和 JACK，正因为实时性补丁，它能够录到很好的音频，没有由于硬盘写导致的声音跳跃”</p><p>结果就是很多音乐家都是实时 Linux 的早期用户，因为这让他们能够在便宜的设备上录制高质量的唱片。这谁会知道？近几年，其它悄然进入内核的实时特性还包括</p><ul><li>互斥锁的引入</li><li>Ftrace，可以说是最重要的 Linux 调试工具</li><li>用户空间应用程序的优先级继承</li></ul><h1 id="实时-Linux-怎么用了这么长时间？"><a href="#实时-Linux-怎么用了这么长时间？" class="headerlink" title="实时 Linux 怎么用了这么长时间？"></a>实时 Linux 怎么用了这么长时间？</h1><p>那么为什么实时 Linux 直到现在才进入内核呢？“实际上我们不会推上去任何东西，除非我们觉得准备好了” Rostedt 解释到 “几乎所有的代码都会被重写三次以上才会进入到主干，因为我们的准入门槛非常高”</p><p>此外，通往主线之路并不只是一个技术挑战。政治和观念也起了很重要的作用，“开始的时候我们甚至不能提实时” Rostedt 回忆道，每个人都会说 “哦，我们不在乎实时”</p><p>另外一个问题是资金。实时 Linux 开发经费在好几年里飘忽不定。在 2015 年， Linux 基金会成立了实时 Linux （RTL） 的合作项目，来协调围绕主线 <code>PREEMPT_RT</code> 的维护工作。</p><p>全面集成前最后一个关卡是内核 <code>print_k</code> 函数的重构，这是一个可以追溯到 1991 年的重要调试工具。Torvalds 对 <code>print_k</code> 有很强的保护欲，他写的原始的代码并且一直用到今天。然而，每当 <code>print_k</code> 在被调用的时候也导致了 Linux 程序产生严重的延迟。这种延迟在实时系统里是无法接受的。</p><p>Rostedt 解释说：“<code>print_k</code> 为了处理上千种不同的场景有上千个 Hack。每当我们想要修改 <code>print_k</code> 做些什么的时候，它就会破坏其中某个场景。<code>print_k</code> 实际上是非常棒的调试工具，他能让你准确知道一个进程在哪里挂掉了。当我非常努力想要锻造系统的时候，延迟总是在 30 毫秒左右，然后突然的延迟就降到了 5 微秒” 这个延迟就是 <code>print_k</code> 消息。</p><p>经过大量工作，多次激烈讨论和若干被毙掉的提案后，今年早些时候终于达成了折中方案。这下 Torvalds 高兴了，实时 Linux 开发者高兴了，<code>print_k</code> 用户也高兴了，到此实时 Linux 才终成真。</p><p>经历了 20 年的不懈努力，Linux 实时补丁终被纳入主线内核。这一里程碑标志着内核开发者将确定性、低延迟性能带进了 Linux 的多年的努力达到了顶峰。</p><p>有了它，Linux 内核才可以完全抢占，从而能在微秒之间相应事件。这个能力对于要求精确计时的如工业控制系统、机器人和音频制作的应用来所是非常关键的。</p><p>随着实时性补丁的合并， Linux 现在已经成为 RTOS 世界一个重要玩家。这不只是实时开发者的胜利更是所有 Linux 用户的胜利。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.zdnet.com/article/20-years-later-real-time-linux-makes-it-to-the-kernel-really/&quot;&gt;原文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;实时 Linux</summary>
        
      
    
    
    
    <category term="翻译" scheme="https://blog.longtian.io/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
    <category term="linux" scheme="https://blog.longtian.io/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>我的第一个 Alpine OS Commit</title>
    <link href="https://blog.longtian.io/2024/05/01/my-first-commit-to-alpine.html"/>
    <id>https://blog.longtian.io/2024/05/01/my-first-commit-to-alpine.html</id>
    <published>2024-04-30T16:00:00.000Z</published>
    <updated>2024-04-30T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近特别爱用 Alpine OS， 试着在上面使用 K3S 搭建一个 Kubernetes 集群。</p><p>当时使用的版本是 <code>Alpine v3.19</code> ，包管理器里自带的 Kubernetes 版本是 <code>k3s-1.28.8.1-r1</code>，安装过程没有报错，但是启动的时候有一个 <code>Fatal Error</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">time=<span class="string">&quot;2024-04-10T00:58:17+08:00&quot;</span> level=fatal msg=<span class="string">&quot;Failed to validate golang version: kubernetes golang build version not set - see &#x27;golang: upstream version&#x27; in https://github.com/kubernetes/kubernetes/blob/v1.28.8/build/dependencies.yaml&quot;</span></span><br></pre></td></tr></table></figure><p>之后定位到是 Alpine OS 包管理器没有及时同步上游的改动的问题，看到有人已经在 <code>Edge</code> 频道里修复了 <a href="https://gitlab.alpinelinux.org/alpine/aports/-/issues/15748">#15748</a>，我就基于这个改动，在 <code>Community</code> 频道里也修复了 <a href="https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/63824">#63824</a></p><span id="more"></span><p>基本就是照葫芦画瓢，不过还是被 Alpine Maintainer 的响应速度和的 CICD 的流程惊艳到了，提交了 Mege Request 后就会自动出发一个构建 <a href="https://gitlab.alpinelinux.org/longtian/aports/-/pipelines/225327">#225327</a>，验证不同的架构下能否正常编译 </p><table><thead><tr><th>stage</th><th>status</th></tr></thead><tbody><tr><td>build-riscv64-emulated</td><td>warning</td></tr><tr><td>lint</td><td>warning</td></tr><tr><td>build-aarch64</td><td>pass</td></tr><tr><td>build-armhf</td><td>pass</td></tr><tr><td>build-armv7</td><td>pass</td></tr><tr><td>build-ppc64le</td><td>pass</td></tr><tr><td>build-s390x</td><td>pass</td></tr><tr><td>build-x86</td><td>pass</td></tr><tr><td>build-x86_64</td><td>pass</td></tr></tbody></table><p>一天之内这个 Mege Request 就被合并了，修复后的 k3s 版本号为 <code>k3s-1.28.8.1-r3</code>，国内的 Alpine 镜像站会有几天的延迟。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近特别爱用 Alpine OS， 试着在上面使用 K3S 搭建一个 Kubernetes 集群。&lt;/p&gt;
&lt;p&gt;当时使用的版本是 &lt;code&gt;Alpine v3.19&lt;/code&gt; ，包管理器里自带的 Kubernetes 版本是 &lt;code&gt;k3s-1.28.8.1-r1&lt;/code&gt;，安装过程没有报错，但是启动的时候有一个 &lt;code&gt;Fatal Error&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;time=&lt;span class=&quot;string&quot;&gt;&amp;quot;2024-04-10T00:58:17+08:00&amp;quot;&lt;/span&gt; level=fatal msg=&lt;span class=&quot;string&quot;&gt;&amp;quot;Failed to validate golang version: kubernetes golang build version not set - see &amp;#x27;golang: upstream version&amp;#x27; in https://github.com/kubernetes/kubernetes/blob/v1.28.8/build/dependencies.yaml&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;之后定位到是 Alpine OS 包管理器没有及时同步上游的改动的问题，看到有人已经在 &lt;code&gt;Edge&lt;/code&gt; 频道里修复了 &lt;a href=&quot;https://gitlab.alpinelinux.org/alpine/aports/-/issues/15748&quot;&gt;#15748&lt;/a&gt;，我就基于这个改动，在 &lt;code&gt;Community&lt;/code&gt; 频道里也修复了 &lt;a href=&quot;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/63824&quot;&gt;#63824&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="devops" scheme="https://blog.longtian.io/tags/devops/"/>
    
    <category term="alpine" scheme="https://blog.longtian.io/tags/alpine/"/>
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://blog.longtian.io/2023/12/27/new-baby.html"/>
    <id>https://blog.longtian.io/2023/12/27/new-baby.html</id>
    <published>2023-12-27T03:00:00.000Z</published>
    <updated>2023-12-27T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>几天绝对是一个值得纪念的日子 :)</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Baby</span>()</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;几天绝对是一个值得纪念的日子 :)&lt;/p&gt;
&lt;figure class=&quot;highlight js&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td</summary>
        
      
    
    
    
    
  </entry>
  
  <entry>
    <title>我的 2022 书单</title>
    <link href="https://blog.longtian.io/2022/06/21/reading-2022.html"/>
    <id>https://blog.longtian.io/2022/06/21/reading-2022.html</id>
    <published>2022-06-21T03:23:16.000Z</published>
    <updated>2022-06-21T03:23:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>今年上半年突然多了很多时间看书，先把以前囤的纸质书翻了翻，后来又买了一个华为出品的墨水屏，立刻荣升阅读主力。<br>总的来说搭配微信阅读 App 的付费会员还是比较省钱的，预计今年底就能回本。</p><span id="more"></span><p><strong>读完：</strong></p><h2 id="《原则2》"><a href="#《原则2》" class="headerlink" title="《原则2》"></a>《原则2》</h2><p>居家隔离 7 天的时候读完，大循环的概念可以说是非常实用的长期投资参考</p><h2 id="《一本书读懂从来没懂过的经济学》"><a href="#《一本书读懂从来没懂过的经济学》" class="headerlink" title="《一本书读懂从来没懂过的经济学》"></a>《一本书读懂从来没懂过的经济学》</h2><p>以前确实忽视了理财，导致薪资占收入的几乎 100%，现在渐渐能看懂财报了，也学着投资黄金和基金</p><h2 id="《隐秘战争》"><a href="#《隐秘战争》" class="headerlink" title="《隐秘战争》"></a>《隐秘战争》</h2><h2 id="《美国陷阱》"><a href="#《美国陷阱》" class="headerlink" title="《美国陷阱》"></a>《美国陷阱》</h2><p>长臂管辖太强大了，美国政府的压倒性制裁普通公司根本扛不住，国家才是坚强的后盾</p><p>看本书我还整理了一个 <a href="https://github.com/longtian/justice-system">美国刑事司法制度流程图</a> 需要自取，233333</p><h2 id="《金融战争》"><a href="#《金融战争》" class="headerlink" title="《金融战争》"></a>《金融战争》</h2><p>书架上翻出来的一本已经泛黄的盗版书，里面的内容还是有点阴谋论的味道的</p><h2 id="《创新公司：皮克斯的启示》"><a href="#《创新公司：皮克斯的启示》" class="headerlink" title="《创新公司：皮克斯的启示》"></a>《创新公司：皮克斯的启示》</h2><p>给自媒体公司如何保持作品质量、避免创意枯竭带来很多启示</p><h2 id="《斯蒂夫·乔布斯传》"><a href="#《斯蒂夫·乔布斯传》" class="headerlink" title="《斯蒂夫·乔布斯传》"></a>《斯蒂夫·乔布斯传》</h2><p>RIP Jobs, 再次读已经是 10 年后了，很多东西都发生了变化，苹果也不再是原来的苹果<br>连 Apple Music 都可以在备受鄙夷的 Android 系统上使用了</p><h2 id="《创新者的窘境》"><a href="#《创新者的窘境》" class="headerlink" title="《创新者的窘境》"></a>《创新者的窘境》</h2><p>据说是乔布斯读了好多遍的书（另外一本是关于禅修的，无感），怎么发展延续性和破坏性创新，这可能就是苹果至今保持市值的秘密</p><h2 id="《乌合之众》"><a href="#《乌合之众》" class="headerlink" title="《乌合之众》"></a>《乌合之众》</h2><p>失智的人群</p><h2 id="《无厂模式》"><a href="#《无厂模式》" class="headerlink" title="《无厂模式》"></a>《无厂模式》</h2><p>通过这本书可以大概了解芯片的产业链，尤其是最新的以 TSMC 为代表的的 Fabless 模式</p><h2 id="《芯片先进封装》"><a href="#《芯片先进封装》" class="headerlink" title="《芯片先进封装》"></a>《芯片先进封装》</h2><p>稀有气体在芯片制造业中的应用</p><h2 id="《奇妙的化学元素》"><a href="#《奇妙的化学元素》" class="headerlink" title="《奇妙的化学元素》"></a>《奇妙的化学元素》</h2><p>复习高中化学知识了属于是</p><h2 id="《没有工作的世界》"><a href="#《没有工作的世界》" class="headerlink" title="《没有工作的世界》"></a>《没有工作的世界》</h2><h2 id="《人体简史》"><a href="#《人体简史》" class="headerlink" title="《人体简史》"></a>《人体简史》</h2><h2 id="《看懂芯片原来这么简单》"><a href="#《看懂芯片原来这么简单》" class="headerlink" title="《看懂芯片原来这么简单》"></a>《看懂芯片原来这么简单》</h2><h2 id="《汽车是怎样跑起来的》"><a href="#《汽车是怎样跑起来的》" class="headerlink" title="《汽车是怎样跑起来的》"></a>《汽车是怎样跑起来的》</h2><p>好像从来没有研究过汽车是怎么跑起来的</p><h2 id="《寄生虫星球》"><a href="#《寄生虫星球》" class="headerlink" title="《寄生虫星球》"></a>《寄生虫星球》</h2><p>寄生虫太可怕了！ 寄生虫和情绪的关系令人称奇。</p><h2 id="《看一眼你就会笑》"><a href="#《看一眼你就会笑》" class="headerlink" title="《看一眼你就会笑》"></a>《看一眼你就会笑》</h2><p>好笑是好笑，就是有的梗有点重复了</p><h2 id="《生活蒙太奇》"><a href="#《生活蒙太奇》" class="headerlink" title="《生活蒙太奇》"></a>《生活蒙太奇》</h2><p>画风很减压</p><h2 id="《从0到无穷》"><a href="#《从0到无穷》" class="headerlink" title="《从0到无穷》"></a>《从0到无穷》</h2><p>数学啊数学</p><h2 id="《挽救计划》"><a href="#《挽救计划》" class="headerlink" title="《挽救计划》"></a>《挽救计划》</h2><p>期待电影</p><h2 id="《月背征途》"><a href="#《月背征途》" class="headerlink" title="《月背征途》"></a>《月背征途》</h2><p>嫦娥和月兔怎么做敏捷迭代的</p><h2 id="《识别急症陷阱》"><a href="#《识别急症陷阱》" class="headerlink" title="《识别急症陷阱》"></a>《识别急症陷阱》</h2><h2 id="《癌症·真相》"><a href="#《癌症·真相》" class="headerlink" title="《癌症·真相》"></a>《癌症·真相》</h2><p>跨国治疗癌症指南</p><h2 id="《牛津通识读本：免疫系统》"><a href="#《牛津通识读本：免疫系统》" class="headerlink" title="《牛津通识读本：免疫系统》"></a>《牛津通识读本：免疫系统》</h2><h2 id="《牛津通识读本：细胞》"><a href="#《牛津通识读本：细胞》" class="headerlink" title="《牛津通识读本：细胞》"></a>《牛津通识读本：细胞》</h2><p>很担心身边的家人、朋友会得癌症，提前做一些知识储备</p><p><strong>在读：</strong></p><p>《沃兹传：与苹果一起疯狂》<br>《癌症传》<br>《国富论》<br>《美联储》<br>《微积分的力量》<br>《财报就像一本故事书》<br>《枪炮、病菌和钢铁》<br>《算力时代：一场新的产业革命》</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今年上半年突然多了很多时间看书，先把以前囤的纸质书翻了翻，后来又买了一个华为出品的墨水屏，立刻荣升阅读主力。&lt;br&gt;总的来说搭配微信阅读 App 的付费会员还是比较省钱的，预计今年底就能回本。&lt;/p&gt;</summary>
    
    
    
    <category term="读书" scheme="https://blog.longtian.io/categories/%E8%AF%BB%E4%B9%A6/"/>
    
    
  </entry>
  
  <entry>
    <title>[译] GPS 工作原理的可视化</title>
    <link href="https://blog.longtian.io/2022/02/11/how-gps-work.html"/>
    <id>https://blog.longtian.io/2022/02/11/how-gps-work.html</id>
    <published>2022-02-11T10:09:40.000Z</published>
    <updated>2022-02-11T10:09:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前订阅了 ciechanowski 大神的博客，虽然更新不频繁（一年几篇）但是每一篇都属于精品。大神总是能结合前端可视化技术把一些天文、物理什么的知识点讲解的很透彻。</p><p><a href="https://ciechanow.ski/gps/">GPS</a> 这篇文章刚发布没几天我就注意到了，赶紧拜读了一下。从三角定位讲到相对论再到卫星信号的编解码，内容非常详实，交互动画的制作精美无比可以说是教案水准。</p><p>因为某些原因，2022 这个春节能够在家待上好长一阵子。所以我就利用这个机会前后花了大概 10 天时间把这篇文章翻译成了 <a href="https://pages.longtian.info/gps/">中文版</a>，希望能够对所有对 GPS 工作原理感兴趣的朋友带来帮助。</p><h3 id="文章地址"><a href="#文章地址" class="headerlink" title="文章地址"></a>文章地址</h3><p><a href="https://pages.longtian.info/gps/">pages.longtian.info&#x2F;gps</a></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;之前订阅了 ciechanowski 大神的博客，虽然更新不频繁（一年几篇）但是每一篇都属于精品。大神总是能结合前端可视化技术把一些天文、物理什么的知识点讲解的很透彻。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ciechanow.ski/gps/&quot;&gt;GPS&lt;/a&gt;</summary>
        
      
    
    
    
    <category term="翻译" scheme="https://blog.longtian.io/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
  </entry>
  
  <entry>
    <title>为什么 Docker Pull 显示的 Digest 和 Docker Hub 的不一样</title>
    <link href="https://blog.longtian.io/2021/11/25/different-image-digest-in-console-and-on-hub.html"/>
    <id>https://blog.longtian.io/2021/11/25/different-image-digest-in-console-and-on-hub.html</id>
    <published>2021-11-25T03:48:29.000Z</published>
    <updated>2021-11-25T03:48:29.000Z</updated>
    
    <content type="html"><![CDATA[<p>这个问题困扰自己很久了，今天终于搞明白了。</p><p>复现： 在 Docker Hub 查看 alpine 的镜像信息，并在主机上执行 <code>docker pull alpine:3.12</code><br>问题： 在网站上看到的 <code>Digest</code> 和控制台里显示的 <code>Digest</code> 不一样</p><p>控制台显示的 <code>Digest</code> 是 <code>d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">3.12: Pulling from library/alpine</span><br><span class="line">Digest: sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280</span><br><span class="line">Status: Image is up to <span class="built_in">date</span> <span class="keyword">for</span> alpine:3.12</span><br><span class="line">docker.io/library/alpine:3.12</span><br></pre></td></tr></table></figure><p>这个 <code>Digest</code> 并不在 alpine:3.12 官方仓库对应不同平台的<a href="https://hub.docker.com/_/alpine?tab=tags&page=1&name=3.12">镜像列表</a> 里</p><table><thead><tr><th>DIGEST</th><th>OS&#x2F;ARCH</th><th>COMPRESSED SIZE</th></tr></thead><tbody><tr><td>7893969ec350</td><td>linux&#x2F;386</td><td>2.67 MB</td></tr><tr><td><a href="https://hub.docker.com/layers/alpine/library/alpine/3.12/images/sha256-74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18">74e5ad84a67e</a></td><td>linux&#x2F;amd64</td><td>2.68 MB</td></tr><tr><td>8a6e4b2093ee</td><td>linux&#x2F;arm&#x2F;v6</td><td>2.49 MB</td></tr><tr><td>45a18fc6f681</td><td>linux&#x2F;arm&#x2F;v7</td><td>2.31 MB</td></tr><tr><td>29ba524fa7f5</td><td>linux&#x2F;arm64&#x2F;v8</td><td>2.59 MB</td></tr><tr><td>08340ab4a605</td><td>linux&#x2F;ppc64le</td><td>2.69 MB</td></tr><tr><td>9a7276e7579f</td><td>linux&#x2F;s390x</td><td>2.46 MB</td></tr></tbody></table><p>那么这两个 <code>Digest</code>，到底以那个为准呢</p><ul><li>d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280</li><li>74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18</li></ul><span id="more"></span><p>试着都拉取一下，发现都可以</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280</span><br><span class="line">docker pull alpine@sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18</span><br></pre></td></tr></table></figure><p>它们对应的镜像都是 <code>alpine:3.12</code> 可以说是一样但又不完全一样。这点可以通过 <code>docker image inspect alpine:3.12</code> 验证。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;Id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;RepoTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;alpine:3.12&quot;</span></span><br><span class="line">        <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;RepoDigests&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;alpine@sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280&quot;</span></span><br><span class="line">        <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Created&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2021-11-12T17:20:08.442217528Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Container&quot;</span><span class="punctuation">:</span> <span class="string">&quot;385e1cc96cc7482dfb6847e293bb24baecd3f48a49791b9b45e297204b160287&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;...&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span> </span><br><span class="line">   <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>也就是两个 <code>Digest</code> 都对应着 Docker 官方打包机在 <code>2021-11-12T17:20:08.442217528Z</code> 这个时间点打包出来的镜像的 ID</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">构建链接（会被定期删除）</span><br><span class="line">https://doi-janky.infosiftr.net/job/multiarch/job/amd64/job/alpine/lastSuccessfulBuild/artifact/build-info/image-ids/alpine_3.12.txt</span><br><span class="line">镜像 ID</span><br><span class="line">sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507</span><br></pre></td></tr></table></figure><p>那么不同的 <code>Digest</code> 是怎么对应同一个镜像的呢，这就要查看 Docker Registry 的 API 文档并了解 Docker 拉取镜像的原理了。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p><code>docker pull</code> 显示的是跨平台的 manifest list 的 <code>Digest</code>，而 Docker Hub 显示的是各个平台 manifest 的 <code>Digest</code>。</p><table><thead><tr><th>请求对象</th><th>MIME</th></tr></thead><tbody><tr><td>manifest list</td><td>application&#x2F;vnd.docker.distribution.manifest.list.v2+json</td></tr><tr><td>manifest</td><td>application&#x2F;vnd.docker.distribution.manifest.v2+json</td></tr></tbody></table><p>可以通过 curl 指定不同的 <code>Accpet</code> HTTP 请求头参数测试</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">curl https://registry.mirror.aliyuncs.com/v2/library/alpine/manifests/3.12 \</span><br><span class="line">  -H <span class="string">&quot;Accept: application/vnd.docker.distribution.manifest.list.v2+json&quot;</span> -v</span><br><span class="line">  </span><br><span class="line">curl https://registry.mirror.aliyuncs.com/v2/library/alpine/manifests/3.12 \</span><br><span class="line">  -H <span class="string">&quot;Accept: application/vnd.docker.distribution.manifest.v2+json&quot;</span> -v</span><br></pre></td></tr></table></figure><h3 id="Manifest-List"><a href="#Manifest-List" class="headerlink" title="Manifest List"></a>Manifest List</h3><p>Response Header</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">Content-Type:</span> <span class="string">application/vnd.docker.distribution.manifest.list.v2+json</span></span><br><span class="line"><span class="attr">Content-Length:</span> <span class="number">1638</span></span><br><span class="line"><span class="attr">Docker-Content-Digest:</span> <span class="string">sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280</span></span><br><span class="line"><span class="attr">Docker-Distribution-Api-Version:</span> <span class="string">registry/2.0</span></span><br><span class="line"><span class="attr">Etag:</span> <span class="string">&quot;sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280&quot;</span></span><br></pre></td></tr></table></figure><p>Response Body</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;manifests&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;digest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;mediaType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/vnd.docker.distribution.manifest.v2+json&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;platform&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;architecture&quot;</span><span class="punctuation">:</span> <span class="string">&quot;amd64&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;os&quot;</span><span class="punctuation">:</span> <span class="string">&quot;linux&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">528</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;digest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sha256:8a6e4b2093eee6246fdd5181cfcad4b587db1068c93315ccf5366f79d8117485&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;mediaType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/vnd.docker.distribution.manifest.v2+json&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;platform&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;architecture&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arm&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;os&quot;</span><span class="punctuation">:</span> <span class="string">&quot;linux&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;variant&quot;</span><span class="punctuation">:</span> <span class="string">&quot;v6&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">528</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;...&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;mediaType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/vnd.docker.distribution.manifest.list.v2+json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;schemaVersion&quot;</span><span class="punctuation">:</span> <span class="number">2</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="Manifest"><a href="#Manifest" class="headerlink" title="Manifest"></a>Manifest</h3><p>Response Header</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">Content-Type:</span> <span class="string">application/vnd.docker.distribution.manifest.v2+json</span></span><br><span class="line"><span class="attr">Content-Length:</span> <span class="number">528</span></span><br><span class="line"><span class="attr">Docker-Content-Digest:</span> <span class="string">sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18</span></span><br><span class="line"><span class="attr">Docker-Distribution-Api-Version:</span> <span class="string">registry/2.0</span></span><br><span class="line"><span class="attr">Etag:</span> <span class="string">&quot;sha256:74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18&quot;</span></span><br></pre></td></tr></table></figure><p>Response Body</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;schemaVersion&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">   <span class="attr">&quot;mediaType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/vnd.docker.distribution.manifest.v2+json&quot;</span><span class="punctuation">,</span></span><br><span class="line">   <span class="attr">&quot;config&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;mediaType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/vnd.docker.container.image.v1+json&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">1471</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;digest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507&quot;</span></span><br><span class="line">   <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">   <span class="attr">&quot;layers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">         <span class="attr">&quot;mediaType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/vnd.docker.image.rootfs.diff.tar.gzip&quot;</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">2809473</span><span class="punctuation">,</span></span><br><span class="line">         <span class="attr">&quot;digest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sha256:8572bc8fb8a32061648dd183b2c0451c82be1bd053a4ea8fae991436b92faebb&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">   <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;这个问题困扰自己很久了，今天终于搞明白了。&lt;/p&gt;
&lt;p&gt;复现： 在 Docker Hub 查看 alpine 的镜像信息，并在主机上执行 &lt;code&gt;docker pull alpine:3.12&lt;/code&gt;&lt;br&gt;问题： 在网站上看到的 &lt;code&gt;Digest&lt;/code&gt; 和控制台里显示的 &lt;code&gt;Digest&lt;/code&gt; 不一样&lt;/p&gt;
&lt;p&gt;控制台显示的 &lt;code&gt;Digest&lt;/code&gt; 是 &lt;code&gt;d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;3.12: Pulling from library/alpine&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Digest: sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Status: Image is up to &lt;span class=&quot;built_in&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; alpine:3.12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;docker.io/library/alpine:3.12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这个 &lt;code&gt;Digest&lt;/code&gt; 并不在 alpine:3.12 官方仓库对应不同平台的&lt;a href=&quot;https://hub.docker.com/_/alpine?tab=tags&amp;page=1&amp;name=3.12&quot;&gt;镜像列表&lt;/a&gt; 里&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DIGEST&lt;/th&gt;
&lt;th&gt;OS&amp;#x2F;ARCH&lt;/th&gt;
&lt;th&gt;COMPRESSED SIZE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;7893969ec350&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;386&lt;/td&gt;
&lt;td&gt;2.67 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://hub.docker.com/layers/alpine/library/alpine/3.12/images/sha256-74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18&quot;&gt;74e5ad84a67e&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;amd64&lt;/td&gt;
&lt;td&gt;2.68 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8a6e4b2093ee&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;arm&amp;#x2F;v6&lt;/td&gt;
&lt;td&gt;2.49 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45a18fc6f681&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;arm&amp;#x2F;v7&lt;/td&gt;
&lt;td&gt;2.31 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29ba524fa7f5&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;arm64&amp;#x2F;v8&lt;/td&gt;
&lt;td&gt;2.59 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08340ab4a605&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;ppc64le&lt;/td&gt;
&lt;td&gt;2.69 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9a7276e7579f&lt;/td&gt;
&lt;td&gt;linux&amp;#x2F;s390x&lt;/td&gt;
&lt;td&gt;2.46 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;那么这两个 &lt;code&gt;Digest&lt;/code&gt;，到底以那个为准呢&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280&lt;/li&gt;
&lt;li&gt;74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Kubernetes 资源分配</title>
    <link href="https://blog.longtian.io/2021/07/12/kubernetes-resources.html"/>
    <id>https://blog.longtian.io/2021/07/12/kubernetes-resources.html</id>
    <published>2021-07-12T03:27:21.000Z</published>
    <updated>2021-07-12T03:27:21.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近遇到了 Kubernetes 上的 Web 应用响应时间长尾的问题，上网收集资料，分析了一下原因。</p><ul><li>CGroup 节流导致 Web 请求相应时间增加</li><li>当前执行的 CPU 不断变动</li><li>核数越多 CGroup 补偿碎片越严重</li><li>跨越 NUMA 节点</li></ul><h2 id="请求和限制计算资源"><a href="#请求和限制计算资源" class="headerlink" title="请求和限制计算资源"></a>请求和限制计算资源</h2><p>Kubernetes 对资源的细粒度管理功能可以说是吸引传统应用上 Kubernetes 的重要原因之一</p><p>我们在定义 Pod 的时候通过以下两个字段可以控制计算资源的分配：</p><table><thead><tr><th>字段</th><th>备注</th></tr></thead><tbody><tr><td><code>requests</code></td><td>请求的最少资源</td></tr><tr><td><code>limits</code></td><td>资源限制</td></tr></tbody></table><ul><li>当 <code>requests</code> 无法满足时，<code>Pod</code> 会一直停在 <code>Pending</code> 状态，触发 <code>FailedScheduling</code> 事件</li><li>当使用内存资源超过 <code>limits</code> 时，如果有其他 POD 需要内存，则使用内存最多的容器会被 <code>sacrifice</code>，触发 <code>OOMKilled</code> 事件</li><li>如果不指定 <code>limits</code> , 在没有命名空间默认值的情况下可以无限制地使用资源</li><li>如果指定了 <code>limits</code> , 但是没有指定 <code>requests</code> , 则 <code>requests</code> 值默认为 <code>limits</code> 值</li><li>从分配角度讲 <code>CPU</code> 属于可以压缩的资源、内存属于不可以压缩的资源</li></ul><p>例如</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">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">memory-demo</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">mem-example</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">memory-demo-ctr</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">polinux/stress</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="attr">limits:</span></span><br><span class="line">        <span class="attr">memory:</span> <span class="string">&quot;200Mi&quot;</span></span><br><span class="line">      <span class="attr">requests:</span></span><br><span class="line">        <span class="attr">memory:</span> <span class="string">&quot;100Mi&quot;</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">&quot;stress&quot;</span>]</span><br><span class="line">    <span class="attr">args:</span> [<span class="string">&quot;--vm&quot;</span>, <span class="string">&quot;1&quot;</span>, <span class="string">&quot;--vm-bytes&quot;</span>, <span class="string">&quot;150M&quot;</span>, <span class="string">&quot;--vm-hang&quot;</span>, <span class="string">&quot;1&quot;</span>]</span><br></pre></td></tr></table></figure><h2 id="命名空间默认值、最小值和最大值"><a href="#命名空间默认值、最小值和最大值" class="headerlink" title="命名空间默认值、最小值和最大值"></a>命名空间默认值、最小值和最大值</h2><p>有些 Kubernetes 集群环境下会出现这样的问题：定义 Pod 的时候明明没有给计算资源的限制，但是在实际运行的 Pod 上出现了资源限制的定义。</p><p>这很有可能是命名空间的默认值造成的。 通过 <code>LimitRange</code> 对象可以设置命名空间资源的默认值、最小值和最大值。</p><table><thead><tr><th>关键字</th><th>备注</th></tr></thead><tbody><tr><td>default</td><td><code>limit</code></td></tr><tr><td>defaultRequest</td><td><code>request</code></td></tr><tr><td>min</td><td>最小值</td></tr><tr><td>max</td><td>最大值</td></tr><tr><td>maxLimitRequestRatio</td><td>比率</td></tr><tr><td>type</td><td>对象</td></tr></tbody></table><p>例如</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></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">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">mem-min-max-demo-lr</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">default:</span></span><br><span class="line">      <span class="attr">memory:</span> <span class="string">1Gi</span></span><br><span class="line">    <span class="attr">defaultRequest:</span></span><br><span class="line">      <span class="attr">memory:</span> <span class="string">1Gi</span></span><br><span class="line">    <span class="attr">max:</span></span><br><span class="line">      <span class="attr">memory:</span> <span class="string">1Gi</span></span><br><span class="line">    <span class="attr">min:</span></span><br><span class="line">      <span class="attr">memory:</span> <span class="string">500Mi</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">Container</span></span><br></pre></td></tr></table></figure><h2 id="Pod-服务质量"><a href="#Pod-服务质量" class="headerlink" title="Pod 服务质量"></a>Pod 服务质量</h2><p>当系统资源不足时就会有 Pod 遭殃，但是那么多 Pod 怎么知道要干掉哪一个呢。这就和 Pod 的服务质量有关。</p><p>查看运行中的 <code>Pod</code> 会有一个 <code>qosClass</code> 字段，它有以下三种取值：</p><table><thead><tr><th>类型</th><th>备注</th></tr></thead><tbody><tr><td>Guaranteed</td><td>被保证的，优先级最高，除非超过限制不然不会被干掉</td></tr><tr><td>Burstable</td><td>允许突发，位于中间, 当系统资源不足且没有 <code>Best-Effort</code> 级别时</td></tr><tr><td>BestEffort</td><td>尽力保证，优先级最低，当系统资源不足时最先被干掉</td></tr></tbody></table><p>需要注意 <code>qosClass</code> 的值无法直接设置，而是通过 <code>requests</code> 和 <code>limits</code> 组合隐式设置</p><h3 id="Guaranteed"><a href="#Guaranteed" class="headerlink" title="Guaranteed"></a>Guaranteed</h3><ul><li>Pod 中的每个容器都要指定 CPU 请求和限制，并且两者相等</li><li>Pod 中的每个容器都要指定内存请求和限制，并且两者相等</li></ul><h3 id="Burstable"><a href="#Burstable" class="headerlink" title="Burstable"></a>Burstable</h3><ul><li>Pod 不符合 Guaranteed QoS 类的标准</li><li>Pod 至少有一个容器具有 CPU 或内存请求</li></ul><h3 id="BestEffort"><a href="#BestEffort" class="headerlink" title="BestEffort"></a>BestEffort</h3><ul><li>没有 CPU、内存请求和限制</li></ul><h2 id="CPU-管理策略"><a href="#CPU-管理策略" class="headerlink" title="CPU 管理策略"></a>CPU 管理策略</h2><p>在现代操作系统的设计下，负载运行可能会不断地迁移到不同的 CPU 核心，Kubernetes 上也是一样。</p><p>Kubernetes 提供了 CPU 管理策略来实现负载和 CPU 核的绑定。</p><p>通过 kubelet 参数 <code>--cpu-manager-policy</code> 来指定 CPU 管理策略。</p><table><thead><tr><th>取值</th><th>备注</th></tr></thead><tbody><tr><td>none</td><td>默认策略，按照 <a href="https://zh.wikipedia.org/wiki/%E5%AE%8C%E5%85%A8%E5%85%AC%E5%B9%B3%E6%8E%92%E7%A8%8B%E5%99%A8">CFS</a> 来执行调度</td></tr><tr><td>static</td><td>允许为节点上具有某些资源特征的 Pod 赋予增强的 CPU 亲和性和独占性</td></tr></tbody></table><p>为了将 Pod 绑定到 CPU 需要</p><ul><li>Kubelet 开启 <code>--cpu-manager-policy static</code></li><li>Pod 的服务优先级为 <code>Guaranteed</code></li><li>Pod 的 CPU 资源为整数</li></ul><p>例如，下面的 Nginx Pod</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">spec:</span><br><span class="line">  containers:</span><br><span class="line">  - name: nginx</span><br><span class="line">    image: nginx</span><br><span class="line">    resources:</span><br><span class="line">      limits:</span><br><span class="line">        memory: &quot;200Mi&quot;</span><br><span class="line">        cpu: &quot;2&quot;</span><br><span class="line">      requests:</span><br><span class="line">        memory: &quot;200Mi&quot;</span><br><span class="line">        cpu: &quot;2&quot;</span><br></pre></td></tr></table></figure><p>在运行时可独占两颗 CPU</p><h2 id="NUMA-和拓扑管理策略"><a href="#NUMA-和拓扑管理策略" class="headerlink" title="NUMA 和拓扑管理策略"></a>NUMA 和拓扑管理策略</h2><p>NUMA 是系统优化的一个常用思路，它是伴随着多处理器出现的一个问题。</p><blockquote><p>非统一内存访问架构（英语：Non-uniform memory access，简称NUMA）是一种为多处理器的电脑设计的内存架构，内存访问时间取决于内存相对于处理器的位置。</p></blockquote><p>常见的硬件有</p><ul><li>CPU</li><li>Memory</li><li>GPU</li><li>NIC</li></ul><p>安装 <code>numactl</code> 后可以通过下面的命令查看主机的 NUMA 相关信息</p><p><code>numactl --harware</code></p><p>通过下面的命令查看 NUMA 的统计信息</p><p><code>numastat</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">numa_hit       | Number of pages allocated from the node the process wanted.</span><br><span class="line">numa_miss      | Number of pages allocated from this node, but the process preferred another node.</span><br><span class="line">numa_foreign   | Number of pages allocated another node, but the process preferred this node.</span><br><span class="line">local_node     | Number of pages allocated from this node while the process was running locally.</span><br><span class="line">other_node     | Number of pages allocated from this node while the process was running remotely (on another node).</span><br><span class="line">interleave_hit | Number of pages allocated successfully with the interleave strategy.</span><br></pre></td></tr></table></figure><p>以上指标均可以通过 Telegraf 监控</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[[inputs.kernel_vmstat]]</span><br></pre></td></tr></table></figure><p><code>--cpu-manager-policy static --topology-manager-scope pod --topology-manager-policy single-numa-node</code></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-memory-resource/">https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-memory-resource/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-cpu-resource/">https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-cpu-resource/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/">https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/cpu-default-namespace/">https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/cpu-default-namespace/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/memory-constraint-namespace/">https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/memory-constraint-namespace/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/cpu-constraint-namespace/">https://kubernetes.io/zh/docs/tasks/administer-cluster/manage-resources/cpu-constraint-namespace/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/configure-pod-container/quality-service-pod/">https://kubernetes.io/zh/docs/tasks/configure-pod-container/quality-service-pod/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/administer-cluster/cpu-management-policies/">https://kubernetes.io/zh/docs/tasks/administer-cluster/cpu-management-policies/</a></li><li><a href="https://kubernetes.io/zh/docs/tasks/administer-cluster/topology-manager/">https://kubernetes.io/zh/docs/tasks/administer-cluster/topology-manager/</a></li><li><a href="https://kubernetes.io/docs/tasks/administer-cluster/memory-manager/">https://kubernetes.io/docs/tasks/administer-cluster/memory-manager/</a></li><li><a href="https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/resource-qos.md">https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/resource-qos.md</a></li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;最近遇到了 Kubernetes 上的 Web 应用响应时间长尾的问题，上网收集资料，分析了一下原因。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CGroup 节流导致 Web 请求相应时间增加&lt;/li&gt;
&lt;li&gt;当前执行的 CPU 不断变动&lt;/li&gt;
&lt;li&gt;核数越多 CGroup</summary>
        
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Istio 请求响应标志</title>
    <link href="https://blog.longtian.io/2021/04/21/istio-response-flags.html"/>
    <id>https://blog.longtian.io/2021/04/21/istio-response-flags.html</id>
    <published>2021-04-21T07:40:35.000Z</published>
    <updated>2021-04-21T07:40:35.000Z</updated>
    
    <content type="html"><![CDATA[<p>整理了一下 Istio 的 Response Flags</p><p><a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format-response-flags">介绍</a></p><p><a href="https://github.com/envoyproxy/envoy/blob/v1.18.2/source/common/stream_info/utility.h#L21">源码</a></p><table><thead><tr><th>协议</th><th>缩写</th><th>备注</th><th>备注</th></tr></thead><tbody><tr><td>HTTP</td><td>DC</td><td>DOWNSTREAM_CONNECTION_TERMINATION</td><td>Downstream connection termination.</td></tr><tr><td>HTTP</td><td>DI</td><td>DELAY_INJECTED</td><td>The request processing was delayed for a period specified via fault injection.</td></tr><tr><td>HTTP</td><td>DPE</td><td>DOWNSTREAM_PROTOCOL_ERROR</td><td>The downstream request had an HTTP protocol error.</td></tr><tr><td>HTTP</td><td>FI</td><td>FAULT_INJECTED</td><td>The request was aborted with a response code specified via fault injection.</td></tr><tr><td>HTTP</td><td>IH</td><td>INVALID_ENVOY_REQUEST_HEADERS</td><td>The request was rejected because it set an invalid value for a strictly-checked header in addition to 400 response code.</td></tr><tr><td>HTTP</td><td>LH</td><td>FAILED_LOCAL_HEALTH_CHECK</td><td>Local service failed health check request in addition to 503 response code.</td></tr><tr><td>HTTP</td><td>LR</td><td>LOCAL_RESET</td><td>Connection local reset in addition to 503 response code.</td></tr><tr><td>HTTP&#x2F;TCP</td><td>NC</td><td>NO_CLUSTER_FOUND</td><td>Upstream cluster not found.</td></tr><tr><td>HTTP&#x2F;TCP</td><td>NR</td><td>NO_ROUTE_FOUND</td><td>No route configured for a given request in addition to 404 response code, or no matching filter chain for a downstream connection.</td></tr><tr><td>HTTP</td><td>RL</td><td>RATE_LIMITED</td><td>The request was ratelimited locally by the HTTP rate limit filter in addition to 429 response code.</td></tr><tr><td>HTTP</td><td>RLSE</td><td>RATELIMIT_SERVICE_ERROR</td><td>The request was rejected because there was an error in rate limit service.</td></tr><tr><td>HTTP</td><td>SI</td><td>STREAM_IDLE_TIMEOUT</td><td>Stream idle timeout in addition to 408 response code.</td></tr><tr><td>HTTP</td><td>UAEX</td><td>UNAUTHORIZED_EXTERNAL_SERVICE</td><td>The request was denied by the external authorization service.</td></tr><tr><td>HTTP</td><td>UC</td><td>UPSTREAM_CONNECTION_TERMINATION</td><td>Upstream connection termination in addition to 503 response code.</td></tr><tr><td>HTTP&#x2F;TCP</td><td>UF</td><td>UPSTREAM_CONNECTION_FAILURE</td><td>Upstream connection failure in addition to 503 response code.</td></tr><tr><td>HTTP&#x2F;TCP</td><td>UH</td><td>NO_HEALTHY_UPSTREAM</td><td>No healthy upstream hosts in upstream cluster in addition to 503 response code.</td></tr><tr><td>HTTP</td><td>UMSDR</td><td>UPSTREAM_MAX_STREAM_DURATION_REACHED</td><td>The upstream request reached to max stream duration.</td></tr><tr><td>HTTP&#x2F;TCP</td><td>UO</td><td>UPSTREAM_OVERFLOW</td><td>Upstream overflow (circuit breaking) in addition to 503 response code.</td></tr><tr><td>HTTP</td><td>UPE</td><td>UPSTREAM_PROTOCOL_ERROR</td><td>The upstream response had an HTTP protocol error.</td></tr><tr><td>HTTP</td><td>UR</td><td>UPSTREAM_REMOTE_RESET</td><td>Upstream remote reset in addition to 503 response code.</td></tr><tr><td>HTTP&#x2F;TCP</td><td>URX</td><td>UPSTREAM_RETRY_LIMIT_EXCEEDED</td><td>The request was rejected because the upstream retry limit (HTTP) or maximum connect attempts (TCP) was reached.</td></tr><tr><td>HTTP</td><td>UT</td><td>UPSTREAM_REQUEST_TIMEOUT</td><td>Upstream request timeout in addition to 504 response code.</td></tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;整理了一下 Istio 的 Response Flags&lt;/p&gt;
&lt;p&gt;&lt;a</summary>
        
      
    
    
    
    
    <category term="istio" scheme="https://blog.longtian.io/tags/istio/"/>
    
  </entry>
  
  <entry>
    <title>JVM 对容器化支持的参数</title>
    <link href="https://blog.longtian.io/2021/04/13/containerized-jvm-parameters.html"/>
    <id>https://blog.longtian.io/2021/04/13/containerized-jvm-parameters.html</id>
    <published>2021-04-13T09:23:31.000Z</published>
    <updated>2021-04-13T09:23:31.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景知识"><a href="#背景知识" class="headerlink" title="背景知识"></a>背景知识</h2><p>阅读本文你需要了解</p><ul><li>JVM 的启动参数</li><li>容器的启动参数</li></ul><h3 id="JVM-启动参数"><a href="#JVM-启动参数" class="headerlink" title="JVM 启动参数"></a>JVM 启动参数</h3><table><thead><tr><th>参数</th><th>备注</th></tr></thead><tbody><tr><td><code>-Xms</code></td><td>MiniumHeapSize</td></tr><tr><td><code>-Xmx</code></td><td>MaxHeapSize</td></tr><tr><td><code>-Xss</code></td><td>StackSize</td></tr></tbody></table><p>在容器化之前一般由运维工程师根据部署环境机器的内存大小来调整，并写在启动脚本里</p><h3 id="容器启动参数"><a href="#容器启动参数" class="headerlink" title="容器启动参数"></a>容器启动参数</h3><p>在使用 k8s 编排容器的时候通常通过定义 resources 字段来限制容器使用的资源</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">resources:</span></span><br><span class="line">  <span class="attr">limit:</span></span><br><span class="line">     <span class="attr">cpu:</span> <span class="string">1000m</span></span><br><span class="line">     <span class="attr">memory:</span> <span class="string">1G</span></span><br></pre></td></tr></table></figure><p>为了方便验证，我们直接用本地 docker 来实验，docker run 命令的一些参数</p><table><thead><tr><th>参数</th><th>备注</th></tr></thead><tbody><tr><td>–cpus</td><td>Number of CPUs</td></tr><tr><td>–cpu-period int</td><td>Limit CPU CFS (Completely Fair Scheduler) period</td></tr><tr><td>–cpu-quota int</td><td>Limit CPU CFS (Completely Fair Scheduler) quota</td></tr><tr><td>–cpu-rt-period int</td><td>Limit CPU real-time period in microseconds</td></tr><tr><td>–cpu-rt-runtime int</td><td>Limit CPU real-time runtime in microseconds</td></tr><tr><td>–cpu-shares int</td><td>CPU shares (relative weight)</td></tr><tr><td>–memory</td><td>Memory limit</td></tr></tbody></table><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>容器化的 JVM 程序会遇到这几个问题</p><ul><li>问题 1： 超过了容器的资源限制被 OOM 机制杀掉</li><li>问题 2： 在核数比较高的机器上 JVM 频繁 GC</li></ul><span id="more"></span><h3 id="问题-1"><a href="#问题-1" class="headerlink" title="问题 1"></a>问题 1</h3><p>JDK 8u191 之前这个问题很常见，主要是由于 JVM 获得的最大内存是主机的最大内存而不是容器的最大可用内存，解决方法是修改启动逻辑。<br>让 JVM 能够获取到正确的最大可用内存</p><h3 id="问题-2"><a href="#问题-2" class="headerlink" title="问题 2"></a>问题 2</h3><p>ParallelGC 线程数如果过高会导致 GC 过于频繁，和问题1 类似，这是由于 JVM 获得的可用核数不正确导致的。</p><p>为了解决这两个需要同时修改容器的资源限制和 JVM 的启动参数，并使之相匹配。<br>当然也有一种不那么笨的做法是引入一个动态的启动脚本，先根据容器的实际资源限制算出对应的 JVM 参数再去启动 JVM。<br>JDK 社区已经注意到以上这几个容器化常遇到的问题并在新的 JDK 版本里解决了，由于 JDK8 的超长待机时间，以下便以 JDK8 为例</p><table><thead><tr><th>版本</th><th>GA 时间</th><th>验证镜像</th><th>容器化支持</th></tr></thead><tbody><tr><td><code>8u191</code></td><td><a href="https://www.oracle.com/java/technologies/javase/8u191-relnotes.html">2018-10-16</a></td><td>openjdk:8u191-alpine</td><td>支持</td></tr><tr><td><code>8u131</code></td><td><a href="https://www.oracle.com/java/technologies/javase/8u131-relnotes.html">2017-04-18</a></td><td>openjdk:8u131-alpine</td><td>实验性支持</td></tr><tr><td><code>8u121</code></td><td><a href="https://www.oracle.com/java/technologies/javase/8u121-relnotes.html">2017-01-17</a></td><td>openjdk:8u121-alpine</td><td>不支持</td></tr></tbody></table><h2 id="识别-CPU-资源"><a href="#识别-CPU-资源" class="headerlink" title="识别 CPU 资源"></a>识别 CPU 资源</h2><p>实验： 通过 <code>ParallelGCThreads</code> 是否符合预期来验证 JDK 对 CPU 资源的识别是否正确</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -XX:+PrintFlagsFinal -XX:+UseParallelGC -version | grep -i ParallelGCThreads</span><br></pre></td></tr></table></figure><h3 id="8u191-之前不支持"><a href="#8u191-之前不支持" class="headerlink" title="8u191 之前不支持"></a>8u191 之前不支持</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --cpus 1 --rm openjdk:8u121-alpine java -XX:+PrintFlagsFinal -XX:+UseParallelGC -version | grep -i ParallelGCThreads</span><br></pre></td></tr></table></figure><p>结果不符合预期，结果为主机核数</p><h3 id="8u191-支持识别可用-CPU-核数"><a href="#8u191-支持识别可用-CPU-核数" class="headerlink" title="8u191 支持识别可用 CPU 核数"></a>8u191 支持识别可用 CPU 核数</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --cpus 1 --rm openjdk:8u191-alpine java -XX:+PrintFlagsFinal -XX:+UseParallelGC -version | grep -i ParallelGCThreads</span><br></pre></td></tr></table></figure><p>结果符合预期，结果为 1</p><h3 id="8u191-关闭自动识别可用-CPU-核数"><a href="#8u191-关闭自动识别可用-CPU-核数" class="headerlink" title="8u191 关闭自动识别可用 CPU 核数"></a>8u191 关闭自动识别可用 CPU 核数</h3><p>在分到 4 个 CPU 核数的情况下，指定可用 CPU 核数为 1</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --cpus 4 --rm openjdk:8u191-alpine java -XX:+PrintFlagsFinal -XX:+UseParallelGC -XX:ActiveProcessorCount=1 -version | grep -i ParallelGCThreads</span><br></pre></td></tr></table></figure><p>结果符合预期，结果为 1</p><h2 id="识别-Memory-资源"><a href="#识别-Memory-资源" class="headerlink" title="识别 Memory 资源"></a>识别 Memory 资源</h2><p>实验： 通过查看 JVM 运行时的参数来验证 JVM 对 Memory 资源的识别是否正确</p><p><code>java -XshowSettings:vm -version</code></p><h3 id="8u121-不支持"><a href="#8u121-不支持" class="headerlink" title="8u121 不支持"></a>8u121 不支持</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100MB --rm openjdk:8u121-alpine java -XshowSettings:vm -version</span><br></pre></td></tr></table></figure><p>结果不符合预期，结果为主机内存的 25%，这是因为这个版本 JDK 的 MaxHeapSize 计算公式为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MaxHeapSize = MaxRAM / MaxRAMFraction</span><br></pre></td></tr></table></figure><p>其中的 <code>MaxRAM</code> <a href="">来自于主机</a></p><h3 id="8u131-实验性支持"><a href="#8u131-实验性支持" class="headerlink" title="8u131 实验性支持"></a>8u131 实验性支持</h3><p>直接使用 8u131 版本的 JDK 运行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100MB --rm openjdk:8u131-alpine java -XshowSettings:vm -version</span><br></pre></td></tr></table></figure><p>结果不符合预期，说明 8u131 默认没有开启对容器化的支持，要开启需要加上实验性参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100MB --rm openjdk:8u131-alpine java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -version</span><br></pre></td></tr></table></figure><p>结果符合预期</p><h3 id="8u191-默认支持"><a href="#8u191-默认支持" class="headerlink" title="8u191 默认支持"></a>8u191 默认支持</h3><p>使用 8u191 版本运行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100MB --rm openjdk:8u191-alpine java -XshowSettings:vm -version</span><br></pre></td></tr></table></figure><p>结果符合预期</p><h3 id="8u191-关闭自动识别最大可用内存"><a href="#8u191-关闭自动识别最大可用内存" class="headerlink" title="8u191 关闭自动识别最大可用内存"></a>8u191 关闭自动识别最大可用内存</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100MB --rm openjdk:8u191-alpine java -XX:-UseContainerSupport -XshowSettings:vm -version</span><br></pre></td></tr></table></figure><p>结果符合预期，结果为主机内存的 25%</p><h2 id="JVM-容器化相关参数"><a href="#JVM-容器化相关参数" class="headerlink" title="JVM 容器化相关参数"></a>JVM 容器化相关参数</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100M --rm openjdk:8u191-alpine java \</span><br><span class="line">  -XX:+UnlockExperimentalVMOptions \</span><br><span class="line">  -XX:+UnlockDiagnosticVMOptions \</span><br><span class="line">  -XX:+PrintFlagsFinal \</span><br><span class="line">  -XX:+UseParallelGC -version | grep -i &#x27;cgroup\|container\|parallelgcthreads&#x27;</span><br></pre></td></tr></table></figure><h3 id="ActiveProcessorCount"><a href="#ActiveProcessorCount" class="headerlink" title="ActiveProcessorCount"></a>ActiveProcessorCount</h3><blockquote><p>Specify the CPU count the VM should use and report as active</p></blockquote><p>8U191 引入</p><h3 id="PrintContainerInfo"><a href="#PrintContainerInfo" class="headerlink" title="PrintContainerInfo"></a>PrintContainerInfo</h3><p>必须使用 UnlockDiagnosticVMOptions 开启</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 100MB --rm openjdk:8u191-alpine java -XshowSettings:vm -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version</span><br></pre></td></tr></table></figure><h3 id="UseContainerSupport"><a href="#UseContainerSupport" class="headerlink" title="UseContainerSupport"></a>UseContainerSupport</h3><blockquote><p>Enable detection and runtime container configuration support</p></blockquote><p><code>8U191</code> 引入</p><h3 id="PreferContainerQuotaForCPUCount"><a href="#PreferContainerQuotaForCPUCount" class="headerlink" title="PreferContainerQuotaForCPUCount"></a>PreferContainerQuotaForCPUCount</h3><blockquote><p>Calculate the container CPU availability based on the value of quotas (if set), when true. Otherwise, use the CPU shares value, provided it is less than quota.</p></blockquote><h3 id="UseCGroupMemoryLimitForHeap"><a href="#UseCGroupMemoryLimitForHeap" class="headerlink" title="UseCGroupMemoryLimitForHeap"></a>UseCGroupMemoryLimitForHeap</h3><blockquote><p>Use CGroup memory limit as physical memory limit for heap sizing</p></blockquote><p><code>8U131</code> 引入</p><p>在 JDK11 中已移除，被默认开启的 <code>UseContainerSupport</code> 代替</p><h3 id="MinRAMFraction"><a href="#MinRAMFraction" class="headerlink" title="MinRAMFraction"></a>MinRAMFraction</h3><blockquote><p>Minimum fraction (1&#x2F;n) of real memory used for maximum heap  size on systems with small physical memory size.<br>Deprecated, use MinRAMPercentage instead</p></blockquote><p><code>8U131</code> 引入 <code>8U191</code> 移除</p><h3 id="MaxRAMFraction"><a href="#MaxRAMFraction" class="headerlink" title="MaxRAMFraction"></a>MaxRAMFraction</h3><blockquote><p>Maximum fraction (1&#x2F;n) of real memory used for maximum heap size.                                                          <br>Deprecated, use MaxRAMPercentage instead</p></blockquote><p><code>8U131</code> 引入 <code>8U191</code> 移除</p><h3 id="InitialRAMPercentage"><a href="#InitialRAMPercentage" class="headerlink" title="InitialRAMPercentage"></a>InitialRAMPercentage</h3><blockquote><p>Percentage of real memory used for initial heap size</p></blockquote><p><code>8U191</code> 引入</p><h3 id="MaxRAMPercentage"><a href="#MaxRAMPercentage" class="headerlink" title="MaxRAMPercentage"></a>MaxRAMPercentage</h3><blockquote><p>Maximum percentage of real memory used for maximum heap size</p></blockquote><p>MaxRAMPercentage 在可用内存大于 250MB 时生效</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 1000MB --rm openjdk:8u212-alpine java -XshowSettings:vm -XX:MaxRAMPercentage=50.0 -XX:MinRAMPercentage=50.0 -version</span><br></pre></td></tr></table></figure><h3 id="MinRAMPercentage"><a href="#MinRAMPercentage" class="headerlink" title="MinRAMPercentage"></a>MinRAMPercentage</h3><blockquote><p>Minimum percentage of real memory used for maximum heap size on systems with small physical memory size</p></blockquote><p>MaxRAMPercentage 和 MaxRAMPercentage 都是用来计算 MaxHeapSize 的</p><p>MinRAMPercentage 在可用内存小于 250MB 时生效 （实际很少除非 IoT 设备）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -m 200MB --rm openjdk:8u212-alpine java -XshowSettings:vm -XX:MaxRAMPercentage=50.0 -XX:MinRAMPercentage=50.0 -version</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://dzone.com/articles/difference-between-initialrampercentage-minramperc">https://dzone.com/articles/difference-between-initialrampercentage-minramperc</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景知识&quot;&gt;&lt;a href=&quot;#背景知识&quot; class=&quot;headerlink&quot; title=&quot;背景知识&quot;&gt;&lt;/a&gt;背景知识&lt;/h2&gt;&lt;p&gt;阅读本文你需要了解&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JVM 的启动参数&lt;/li&gt;
&lt;li&gt;容器的启动参数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JVM-启动参数&quot;&gt;&lt;a href=&quot;#JVM-启动参数&quot; class=&quot;headerlink&quot; title=&quot;JVM 启动参数&quot;&gt;&lt;/a&gt;JVM 启动参数&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Xms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MiniumHeapSize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Xmx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MaxHeapSize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Xss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;StackSize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在容器化之前一般由运维工程师根据部署环境机器的内存大小来调整，并写在启动脚本里&lt;/p&gt;
&lt;h3 id=&quot;容器启动参数&quot;&gt;&lt;a href=&quot;#容器启动参数&quot; class=&quot;headerlink&quot; title=&quot;容器启动参数&quot;&gt;&lt;/a&gt;容器启动参数&lt;/h3&gt;&lt;p&gt;在使用 k8s 编排容器的时候通常通过定义 resources 字段来限制容器使用的资源&lt;/p&gt;
&lt;figure class=&quot;highlight yaml&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;attr&quot;&gt;resources:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;limit:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     &lt;span class=&quot;attr&quot;&gt;cpu:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;1000m&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     &lt;span class=&quot;attr&quot;&gt;memory:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;1G&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;为了方便验证，我们直接用本地 docker 来实验，docker run 命令的一些参数&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;–cpus&lt;/td&gt;
&lt;td&gt;Number of CPUs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;–cpu-period int&lt;/td&gt;
&lt;td&gt;Limit CPU CFS (Completely Fair Scheduler) period&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;–cpu-quota int&lt;/td&gt;
&lt;td&gt;Limit CPU CFS (Completely Fair Scheduler) quota&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;–cpu-rt-period int&lt;/td&gt;
&lt;td&gt;Limit CPU real-time period in microseconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;–cpu-rt-runtime int&lt;/td&gt;
&lt;td&gt;Limit CPU real-time runtime in microseconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;–cpu-shares int&lt;/td&gt;
&lt;td&gt;CPU shares (relative weight)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;–memory&lt;/td&gt;
&lt;td&gt;Memory limit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h2&gt;&lt;p&gt;容器化的 JVM 程序会遇到这几个问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;问题 1： 超过了容器的资源限制被 OOM 机制杀掉&lt;/li&gt;
&lt;li&gt;问题 2： 在核数比较高的机器上 JVM 频繁 GC&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://blog.longtian.io/2021/03/18/tencentyun.html"/>
    <id>https://blog.longtian.io/2021/03/18/tencentyun.html</id>
    <published>2021-03-18T05:28:28.000Z</published>
    <updated>2021-03-18T05:28:28.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="腾讯-Centos-软件源的使用"><a href="#腾讯-Centos-软件源的使用" class="headerlink" title="腾讯 Centos 软件源的使用"></a>腾讯 Centos 软件源的使用</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base.repo</span><br><span class="line">wget -O /etc/yum.repos.d/epel.repo http://mirrors.cloud.tencent.com/repo/epel-7.repo</span><br><span class="line">sed -i &#x27;s/cloud\.tencent\.com/tencentyun\.com/1&#x27;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;腾讯-Centos-软件源的使用&quot;&gt;&lt;a href=&quot;#腾讯-Centos-软件源的使用&quot; class=&quot;headerlink&quot; title=&quot;腾讯 Centos 软件源的使用&quot;&gt;&lt;/a&gt;腾讯 Centos 软件源的使用&lt;/h1&gt;&lt;figure</summary>
        
      
    
    
    
    
  </entry>
  
  <entry>
    <title>[译] Let&#39;s Encrypt 的新根证书和中间证书</title>
    <link href="https://blog.longtian.io/2020/09/18/letsencrypt-root-ca.html"/>
    <id>https://blog.longtian.io/2020/09/18/letsencrypt-root-ca.html</id>
    <published>2020-09-18T05:22:00.000Z</published>
    <updated>2020-09-18T05:22:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://letsencrypt.org/2020/09/17/new-root-and-intermediates.html">原文</a></p><p>在 2020 年 9 月 30 日这天，Let’s Encrypt 颁发了六个新证书：一个根证书，四个中间证书和一个交叉证书。这些证书属于改善网络隐私的更大计划的一部分，让 ECDSA 终端证书被更广泛的采纳，和更小的证书体积。</p><p>鉴于我们每天要颁发 150 万张证书，这些证书有什么特别？为什么颁发它们？怎么颁发它们？让我们通过解释 CA 是如何思考和工作的来回答这些问题。</p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>每一个被公众信任的 CA (例如 Let’s Encrypt) 都至少有一个根证书被众多的浏览器和操作系统 (例如 Mozilla 和 Google) 的信任跟存储所包含。通过这种方式，用户可以确认他们从网站收到的证书是一个被浏览器信任的机构所颁发的。但是根证书由于它们的广泛传播以及较长的信赖周期，它的秘钥必须被妥善的保护并离线保管，以防止被用来不停地签发。因此 CA 会有若干个中间证书来替代根证书以确保证日常安全。</p><p>最近 5 年里， Let’s Encrypt 只有一个根证书： ISRG Rott X1，它拥有一个 4096 位的 RSA 秘钥并且有效期直到 2035 年。</p><p>也是在这段时间里，我们有了四个中间证书，分别是 Let’s Encrypt Authorities X1, X2, X3 和 X4。 前两个是 Let’s Encrypt 刚开始运营的 2015 年颁发的，有效期为 5 年；后两个是一年后，也就是 2016 年颁发的，有效期 5 年，并将在明年的这个时候过期。所有的这些中间证书都使用 2048 位的秘钥。此外，这些中间证书都由 IdenTrust 公司的 DST Root CA X3 根证书交叉签署，这个证书由另一家广泛被信任的 CA 所管理。</p><p>Let’s Encrypt 到 2020 年 8 月的结构图</p><p><img src="/images/2020/letsencrypt-aug-2020.png" alt="8月"></p><h2 id="新证书"><a href="#新证书" class="headerlink" title="新证书"></a>新证书</h2><p>首先，我们颁发了两个 2048 位秘钥的 RSA 中间证书：R3 和 R4. 这两个证书都由 ISRG Root X1 签署，拥有 5 年的有效期。同样交由 IdentTrust 交叉签署。它们实际上是 X3 和 X4 的直接替代，考虑到它们一年后即将到期。我们预计会在今年底把主要的证书颁发流水线切换到使用 R3 证书，不会对证书的颁发和续签的造成实际的影响。</p><p>另一个新证书更有意思一点。首先，我们有了一个新的 ISRG Root X2，将会使用 ECDSA P-384 替代 RSA，有效期到 2040 年。由它颁发了两个新的中间证书：E1 和 E2，签名算法也是 ECDSA 并且有效期为 5 年。</p><p>值得注意的是，这些新中间证书并没交由 IdentTrust 的 DST Root CA X3 交叉签署, ISRG Root X2 本身由 ISRG Root X1 交叉签署。敏锐的观察者可能也会注意到我们没有通过  ISRG Root X2 颁发有 OCSP 签名的证书。</p><p>Let’s Encrypt 到 2020 年 9 月的结构图</p><p><img src="/images/2020/letsencrypt-sept-2020.png" alt="9月"></p><p>既然已经讨论到了技术细节，不妨再深入了解下这个结构的由来。</p><h2 id="为什么颁发-ECDSA-的根证书和中间证书"><a href="#为什么颁发-ECDSA-的根证书和中间证书" class="headerlink" title="为什么颁发 ECDSA 的根证书和中间证书"></a>为什么颁发 ECDSA 的根证书和中间证书</h2><p>已经有好多文章讨论过 ECDSA 的好处 (相同加密程度下更小的秘钥，更快的加密，解密，签名，验证等操作)。不过对我们来说，更大的好处来自于证书体积的缩小。</p><p>每一个通过 <code>https://</code> 到远程主机的链接都需要一次 TLS 握手。每一个 TLS 握手都需要服务器提供它的证书。校验证书的过程还包括检查证书链（包括直到可信根证书的所有中间证书），这通常也是由有服务器提供的。这就意味这每个链接，一个覆盖各种广告和跟踪像素的页面会包含几十甚至上百个链接，这会需要传输大量的证书数据。并且每个证书都包含自己的公钥和签发者的签名。</p><p>一个 2048 位的 RSA 公钥大概 256 字节，而一个 ECDSA P-284 的公钥只有 48 字节。类似的， RSA 的签名会需要额外的 256 字节，而 ECDSA 只需要 96 字节。再考虑到其他一些开销，每个证书能节约大约 400 字节。用这个数乘以你的证书链长度以及每天的链接数，带宽的节省就很可观了。</p><p>这些省下的流量不仅会使我们的证书使用者每月节省大量的带宽费用，也惠及那些有限的、受限的终端用户。提高整个互联网的隐私性不仅是采用证书技术，也包括这让这些技术更经济。</p><p>此外由于我们很关心证书的大小，我们还采取了一些其它手段来让证书变得更小。我们把证书的主体名字由 “Let’s Encrypt Authority X3” 缩减到 “R3”，这是给予机构名称里已经提供了冗余的 “Let’s Encrypt”。同时我们还缩短了 Authority Information Access Issuer 和 CRL Distribution Point 的 URL 长度，我们还整个砍掉了 CPS 和 OCSP 的 URL。通过这些手段我们能够在不丢失实质性信息的情况下让证书再缩小 120 字节。</p><h2 id="为什么交叉签署-ECDSA-根证书"><a href="#为什么交叉签署-ECDSA-根证书" class="headerlink" title="为什么交叉签署 ECDSA 根证书"></a>为什么交叉签署 ECDSA 根证书</h2><p>交叉签署是新根证书从被签发到被主流信任的过渡阶段很重要的步骤。我们知道 ISRG Root X2 可能需要 5 年甚至更长时间才能被广泛接受，所以 E1 中间证书签发的证书如果希望被信任，一定需要证书链中某处的交叉签署。</p><p>我们基本上有两种方法：用现有的 ISRG Root X1 交叉签署 ISRG Root X2，或者用 ISRG Root X1 直接交叉签署 E1 和 E2。接下来我们就来分析下这两种做法分别有什么优缺点。</p><p>交叉签署 ISRG Root X2 意味着如果一个用户在信任库里有 ISRG Root X2，那么该证书链就是 100% ECDSA ，可以利用前文所述的快速校验。并且在接下来的几年里随着 ISRG Root X2 被加到越来越多的信任库里，ECDSA 终端证书的验证会越来越快而不需要用户或者网站做什么。这么做的代价是，只要 X2 还不在信任库里，用户的客户端就需要验证两个中间证书包括 E1 和 X2 直到 X1 根证书。这显然增加了证书验证的时间。</p><p>直接交叉新签署中间证书也有问题。一方面所有证书链的长度是相同的，在证书使用者和被广泛信任的 ISRG Root X1 之间只有一个中间证书。但是另一方面，随着 ISRG Root X2 获得越来越广泛的的信任，我们需要通过切换到另外一个链来<br>保证所有都能享受全链 ECDSA 的好处。</p><p>最终我们认为全链 ECDSA 更重要，所以我们选择了第一个方案，交叉签署 ISRG Root X2 证书。</p><h2 id="为什么我们不提供-OCSP-Responder-了"><a href="#为什么我们不提供-OCSP-Responder-了" class="headerlink" title="为什么我们不提供 OCSP Responder 了"></a>为什么我们不提供 OCSP Responder 了</h2><p>OCSP 协议是用户客户端用来发现并实时检查证书是否被吊销的一种方式。无论何时，一个浏览器如果想要知道证书是否有效，它可以通过访问证书里的一个 URL 就能得到是或否的答案，这个结果是由另一个可以被用相同方式检查的证书签名。这对终端用户的证书来说是非常棒的，应为请求响应体积很小并且速度很快。根据访问的站点不同，任何一个用户可能会关心（因此必须下载）海量证书集合的有效性。</p><p>但是中间证书只是海量证书的一个小小的子集，并且通常是广为人知的，也很少被吊销。因此，提供一个所有常用中间证书的吊销列表可能会更有效。我们的中间证书都包括一个 URL，通过这个 URL 浏览器可以下载证书的 CRl。实际上有些浏览器甚至会在例行更新里带上 CRL 列表集合，这使得在检查中间证书有效性的时候不需要再进行一次额外的访问开销，从而为大家创造更好的体验。</p><p>实际上用来指导 CA 的 Baseline Requirements 最近一次更新表明，中间证书已经不再强制要求包含一个 OCSP URL，而是可以只通过 CRL 来发布自己的吊销信息。鉴于此，我们从中间证书里移除了 OCSP URL，即我们不再需要为所有 ISRG Root X2 颁发的中间证书提供 OCSP Responder。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>至此我们已经介绍了新证书，最后再提一点：我们是怎么颁发这些证书的。</p><p>创建新根证书和中间证书是一个大事件，因为它们是被监管的的并且需要极其小心地看管好秘钥。这个事件是如此重要以至于颁发新证书被称作是“仪式”。在 Let’s Encrypt 我们非常推崇机器自动化，所以我们想要这个仪式的人为干预越少越好。</p><p>在过去的几个月里我们为这个仪式建立了一个工具，如果输入正确的配置，就能够生成所需的秘钥、证书和交叉签名请求等。我们还建立了一个仪式的 Demo 来说明这个配置文件可以并且允许所有人来运行和检查结果。我们的 SRE 搭建了一个相同并配有硬件安全模块的网络，自己执行了若干次仪式以确保整个流程完美无暇。我们与技术委员会、社区和若干邮件列表分享了这个 Demo，在这个过程中获得了很多有价值的反馈，其中一些甚至影响了上面我们提到的一些决定。最终、在 2020 年 9 月 3 号，我们的执行董事和 SRE 在一个安全的数据中心碰面并执行了整个仪式，并且有录像以供审计用。</p><p>现在仪式已经完成。我们已经更新了证书页面上关于新证书的细节，并且开始着手于申请将我们的新根证书加入到若干个信任库中。我们会在未来几周里开始用新中间证书来签发证书，并在社区论坛里发布进一步的公告。</p><p>希望我们关于新证书结构的导览是有趣的和干货的。我们期待继续通过一张张的证书来改进互联网隐私。我们由衷感谢 IdenTrust 在早期和后来不间断的支持我们让互联网更安全的愿景。</p><p>我们依靠社区和支持者来提供我们的服务。如果你的公司或者机构希望可以赞助 Let’s Encrypt 可以发邮件到 sponsor#letsencrypt.org 。我们需要您力所能及的帮助、</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://letsencrypt.org/2020/09/17/new-root-and-intermediates.html&quot;&gt;原文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在 2020 年 9 月 30 日这天，Let’s Encrypt</summary>
        
      
    
    
    
    <category term="翻译" scheme="https://blog.longtian.io/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
    <category term="pki" scheme="https://blog.longtian.io/tags/pki/"/>
    
  </entry>
  
  <entry>
    <title>JIRA 内置用户目录与 LDAP 的关系</title>
    <link href="https://blog.longtian.io/2020/08/12/jira-internal-and-ldap.html"/>
    <id>https://blog.longtian.io/2020/08/12/jira-internal-and-ldap.html</id>
    <published>2020-08-12T02:56:34.000Z</published>
    <updated>2020-08-12T02:56:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>作为运维开发怎么能不折腾一下 JIRA 呢。JIRA 支持从多个来源获取用户信息，默认的是内置用户目录，一个比较常见需求是改用 LDAP 作为用户目录，特别适合公司里有多个账号体系的情况。</p><p>如果想了解其中的细节最好方式还是搭个独立的测试环境，主要是 Jira, OpenLDAP 和 MySQL，可以用一个 docker-compose 搞定。安装和配置部分略去，以下直接给出分析过程：</p><h2 id="相关的表"><a href="#相关的表" class="headerlink" title="相关的表"></a>相关的表</h2><table><thead><tr><th>表名</th><th>功能</th></tr></thead><tbody><tr><td>cwd_directory</td><td>用户目录</td></tr><tr><td>cwd_group</td><td>用户组</td></tr><tr><td>cwd_user</td><td>用户</td></tr><tr><td>cwd_membership</td><td>用户组 -&gt;　用户</td></tr></tbody></table><p><strong>cwd_directory</strong></p><table><thead><tr><th>ID</th><th>directory_name</th><th>impl_class</th><th>directory_type</th></tr></thead><tbody><tr><td>1</td><td>JIRA Internal Directory</td><td>com.atlassian.crowd.directory.InternalDirectory</td><td>INTERNAL</td></tr><tr><td>10000</td><td>My OpenLDAP</td><td>com.atlassian.crowd.directory.OpenLDAP</td><td>CONNECTOR</td></tr></tbody></table><p>之前看到网上有一条语句改库完成 LDAP 迁移的神操作，这个操作会假设 OpenLDAP 的库 ID 是 10000，果然是艺高人胆大。</p><p><strong>cwd_group</strong></p><table><thead><tr><th>ID</th><th>directory_id</th><th>group_name</th><th>active</th><th>local</th><th>group_type</th></tr></thead><tbody><tr><td>10000</td><td>1</td><td>jira-administrators</td><td>1</td><td>0</td><td>GROUP</td></tr><tr><td>10010</td><td>1</td><td>jira-software-users</td><td>1</td><td>0</td><td>GROUP</td></tr><tr><td>10110</td><td>1</td><td>jira-software-users</td><td>1</td><td>1</td><td>GROUP</td></tr></tbody></table><p><strong>cwd_user</strong></p><table><thead><tr><th>ID</th><th>directory_id</th><th>username</th><th>active</th><th>email_address</th><th>CREDENTIAL</th></tr></thead><tbody><tr><td>10000</td><td>1</td><td>root</td><td>1</td><td><a href="mailto:&#114;&#x6f;&#x6f;&#x74;&#64;&#x65;&#120;&#97;&#109;&#x70;&#x6c;&#101;&#x2e;&#99;&#111;&#109;">&#114;&#x6f;&#x6f;&#x74;&#64;&#x65;&#120;&#97;&#109;&#x70;&#x6c;&#101;&#x2e;&#99;&#111;&#109;</a></td><td>{PKCS5S2}********</td></tr><tr><td>10102</td><td>10000</td><td>foo</td><td>1</td><td><a href="mailto:&#x66;&#111;&#111;&#64;&#x65;&#120;&#x61;&#x6d;&#112;&#108;&#x65;&#46;&#x63;&#x6f;&#x6d;">&#x66;&#111;&#111;&#64;&#x65;&#120;&#x61;&#x6d;&#112;&#108;&#x65;&#46;&#x63;&#x6f;&#x6d;</a></td><td>nopass</td></tr><tr><td>10100</td><td>1</td><td>foo</td><td>1</td><td><a href="mailto:&#x66;&#111;&#x6f;&#x40;&#x65;&#120;&#97;&#109;&#x70;&#108;&#101;&#x2e;&#x63;&#111;&#109;">&#x66;&#111;&#x6f;&#x40;&#x65;&#120;&#97;&#109;&#x70;&#108;&#101;&#x2e;&#x63;&#111;&#109;</a></td><td>{PKCS5S2}********</td></tr></tbody></table><p>迁移之前最大的顾虑就是迁移前的数据能够保存了，这块主要分两部分</p><ul><li>各种任务，评论等</li><li>在项目里的角色</li><li>用户组信息</li></ul><p>通过实验发现只要保证 internal 和 ldap 里的 username 一致 1 和 2 就可以得到保留，同样，只要保证 group_name 一致 3 就可以得到保留。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>结合表里的数据可得到以下结论：</p><ul><li>username 和 group_name 在各自的库里都是唯一的</li><li>从全局看 username 对应的用户只能有一个，取决于目录的优先级顺序</li><li>从全局看 group_name 是不同目录下的用户的集合</li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;作为运维开发怎么能不折腾一下 JIRA 呢。JIRA 支持从多个来源获取用户信息，默认的是内置用户目录，一个比较常见需求是改用 LDAP 作为用户目录，特别适合公司里有多个账号体系的情况。&lt;/p&gt;
&lt;p&gt;如果想了解其中的细节最好方式还是搭个独立的测试环境，主要是</summary>
        
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Pycharm 里为 Vagrantfile 如何设置语法高亮</title>
    <link href="https://blog.longtian.io/2020/07/09/vagrantfile-highliting.html"/>
    <id>https://blog.longtian.io/2020/07/09/vagrantfile-highliting.html</id>
    <published>2020-07-09T02:00:25.000Z</published>
    <updated>2020-07-09T02:00:25.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近又开始捣鼓 <code>Vagrant</code> 了，但是在 Pycharm (2020.1.2) 里一直没有语法高亮，明明装了 <code>Vagrant</code> 插件的。</p><p>还好网上搜到了这个<a href="https://gist.github.com/boneskull/efcf2dcf265096f014d7#file-vagrantfile-xml">方法</a>，新建 <code>Vagrantfile.xml</code> 如下</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">filetype</span> <span class="attr">binary</span>=<span class="string">&quot;false&quot;</span> <span class="attr">description</span>=<span class="string">&quot;Vagrant Configuration File&quot;</span> <span class="attr">name</span>=<span class="string">&quot;Vagrantfile&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">highlighting</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">options</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;LINE_COMMENT&quot;</span> <span class="attr">value</span>=<span class="string">&quot;#&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;COMMENT_START&quot;</span> <span class="attr">value</span>=<span class="string">&quot;=begin&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;COMMENT_END&quot;</span> <span class="attr">value</span>=<span class="string">&quot;=end&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;HEX_PREFIX&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;NUM_POSTFIXES&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;HAS_BRACES&quot;</span> <span class="attr">value</span>=<span class="string">&quot;true&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;HAS_BRACKETS&quot;</span> <span class="attr">value</span>=<span class="string">&quot;true&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;HAS_PARENS&quot;</span> <span class="attr">value</span>=<span class="string">&quot;true&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">option</span> <span class="attr">name</span>=<span class="string">&quot;HAS_STRING_ESCAPES&quot;</span> <span class="attr">value</span>=<span class="string">&quot;true&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">options</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">keywords</span> <span class="attr">keywords</span>=<span class="string">&quot;BEGIN;END;begin;break;case;do;else;elsif;end;ensure;for;if;in;next;rescue;retry;then;until;when;while&quot;</span> <span class="attr">ignore_case</span>=<span class="string">&quot;false&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">keywords2</span> <span class="attr">keywords</span>=<span class="string">&quot;__ENCODING__;__END__;__FILE__;__LINE__&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">keywords3</span> <span class="attr">keywords</span>=<span class="string">&quot;and;false;nil;not;or;true&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">keywords4</span> <span class="attr">keywords</span>=<span class="string">&quot;class;def;module;return;self;super;undef;yield&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">highlighting</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">extensionMap</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mapping</span> <span class="attr">pattern</span>=<span class="string">&quot;Vagrantfile&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">extensionMap</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">filetype</span>&gt;</span></span><br></pre></td></tr></table></figure><p>貌似得放到某个文件夹下，懒得找了，直接在 IDE 里设置吧：</p><p>进入 <code>File &gt; Settings &gt; Editor &gt; File Types</code> 对话框，在 <code>Recognized file types:</code> 里点击 <code>+</code> 号，<br>添加 <code>Vagrantfile</code> 类型即可，其它的配置参照上面 <code>Vagrantfile.xml</code> 里，</p><p>唯一要注意的是 <code>Keywords</code> 部分不是以 <code>;</code> 分割而是需要一行一个 <code>Keyword</code></p><p><strong>错误</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__ENCODING__;__END__;__FILE__;__LINE__</span><br></pre></td></tr></table></figure><p><strong>正确</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">__ENCODING__</span><br><span class="line">__END__</span><br><span class="line">__FILE__</span><br><span class="line">__LINE__</span><br></pre></td></tr></table></figure><p>用这种方法还可为任何类型的文件增加语法高亮，是不是很赞！</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;最近又开始捣鼓 &lt;code&gt;Vagrant&lt;/code&gt; 了，但是在 Pycharm (2020.1.2) 里一直没有语法高亮，明明装了 &lt;code&gt;Vagrant&lt;/code&gt; 插件的。&lt;/p&gt;
&lt;p&gt;还好网上搜到了这个&lt;a</summary>
        
      
    
    
    
    
    <category term="vagrant" scheme="https://blog.longtian.io/tags/vagrant/"/>
    
    <category term="pycharm" scheme="https://blog.longtian.io/tags/pycharm/"/>
    
  </entry>
  
  <entry>
    <title>在 Docker 容器里使用 Crontab</title>
    <link href="https://blog.longtian.io/2020/06/22/crontab-in-docker.html"/>
    <id>https://blog.longtian.io/2020/06/22/crontab-in-docker.html</id>
    <published>2020-06-22T14:11:55.000Z</published>
    <updated>2020-06-22T14:11:55.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近公司有一个需求是在容器里运行 Cron 服务</p><h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><p>CentOS 上安装 Cron 很简单，一条命令就可以搞定</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install cronie</span><br></pre></td></tr></table></figure><p>目前的 <code>cronie</code> 版本是 <code>1.4.11</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Name        : cronie</span><br><span class="line">Arch        : x86_64</span><br><span class="line">Version     : 1.4.11</span><br><span class="line">Release     : 23.el7</span><br><span class="line">Size        : 215 k</span><br><span class="line">Repo        : installed</span><br><span class="line">From repo   : base</span><br><span class="line">Summary     : Cron daemon for executing programs at set times</span><br><span class="line">URL         : https://github.com/cronie-crond/cronie</span><br><span class="line">License     : MIT and BSD and ISC and GPLv2+</span><br><span class="line">Description : Cronie contains the standard UNIX daemon crond that runs specified programs at</span><br><span class="line">            : scheduled times and related tools. It is a fork of the original vixie-cron and</span><br><span class="line">            : has security and configuration enhancements like the ability to use pam and</span><br><span class="line">            : SELinux.</span><br></pre></td></tr></table></figure><p>直接基于 CentOS7 编写 Dockerfile</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> centos:<span class="number">7</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> groupadd --gid 1000 foo &amp;&amp;\</span></span><br><span class="line"><span class="language-bash">    useradd  --uid 1000 --gid foo foo</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> yum install cronie -y</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;crond&quot;</span>,<span class="string">&quot;-n&quot;</span>,<span class="string">&quot;-x&quot;</span>,<span class="string">&quot;misc,load&quot;</span>,<span class="string">&quot;-i&quot;</span>]</span></span><br></pre></td></tr></table></figure><h2 id="启动命令"><a href="#启动命令" class="headerlink" title="启动命令"></a>启动命令</h2><p>Cron 的入口当然是 crond 啦，不过 crond 需要前台运行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">crond -n -i -x misc,load</span><br></pre></td></tr></table></figure><p>其中：</p><p><code>-n</code> 进程挂前台</p><p><code>-i</code> 关闭 inotify，因为容器里的 inotify 不生效因此检测不到挂载文件的变更</p><p><code>-x</code> 开启调试信息： misc 可以打印命令的输出，load 可以显示读取了哪些配置，其它配置项见文末参考</p><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><p>cron 的配置文件可以在构建镜像的时候打进去，也可以按照需要挂在</p><h3 id="挂载为用户服务"><a href="#挂载为用户服务" class="headerlink" title="挂载为用户服务"></a>挂载为用户服务</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -it --<span class="built_in">rm</span> -v ./foo:/var/spool/cron/foo cron crond -n -x misc,load -i</span><br></pre></td></tr></table></figure><p><code>foo</code> 文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SHELL=/bin/bash</span><br><span class="line">PATH=/sbin:/bin:/usr/sbin:/usr/bin</span><br><span class="line">MAILTO=root</span><br><span class="line"> </span><br><span class="line">* * * * * id</span><br></pre></td></tr></table></figure><h3 id="挂载为系统服务"><a href="#挂载为系统服务" class="headerlink" title="挂载为系统服务"></a>挂载为系统服务</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -it --<span class="built_in">rm</span> -v ./root:/etc/crontab cron crond -n -x misc,load -i</span><br></pre></td></tr></table></figure><p><code>crontab</code> 文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SHELL=/bin/bash</span><br><span class="line">PATH=/sbin:/bin:/usr/sbin:/usr/bin</span><br><span class="line">MAILTO=root</span><br><span class="line"> </span><br><span class="line">* * * * * root id</span><br></pre></td></tr></table></figure><p>注意 &#x2F;etc&#x2F;crontab 和 &#x2F;var&#x2F;spool&#x2F;cron&#x2F;foo 不一样还有第 6 个参数，即指定运行的用户</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><table><thead><tr><th>参数</th><th>备注</th></tr></thead><tbody><tr><td>ext</td><td>通常和其它几个变量组合使用</td></tr><tr><td>sch</td><td>scheduler</td></tr><tr><td>proc</td><td>进程控制</td></tr><tr><td>pars</td><td>语法解析</td></tr><tr><td>load</td><td>读取</td></tr><tr><td>misc</td><td>杂项</td></tr><tr><td>test</td><td>测试模式，命令实际不会执行</td></tr><tr><td>bit</td><td>？？</td></tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;最近公司有一个需求是在容器里运行 Cron 服务&lt;/p&gt;
&lt;h2 id=&quot;Dockerfile&quot;&gt;&lt;a href=&quot;#Dockerfile&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    
    <category term="docker" scheme="https://blog.longtian.io/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>也谈 HashiCorp 禁止中国公司使用</title>
    <link href="https://blog.longtian.io/2020/05/30/hashicorp-export-control.html"/>
    <id>https://blog.longtian.io/2020/05/30/hashicorp-export-control.html</id>
    <published>2020-05-30T02:52:05.000Z</published>
    <updated>2020-05-30T02:52:05.000Z</updated>
    
    <content type="html"><![CDATA[<p>昨天最大的瓜当属 <code>HashCorp</code> 禁止中国公司使用。</p><p>作为一名 Devops 工程师，<code>HashCorp</code> 的产品或多或少都接触过。例如之前我们就用他家的 <code>Vagrant</code> 搭建 <code>Ansible</code> 脚本的 CI 环境， 用 <code>Consul</code> 做服务发现，整体给人的感觉还是不错的。</p><p>今天这个瓜的震惊之处在于 <code>Hashicorp</code> 赤裸裸的在协议里写了禁止在中国使用，虽然实际上 <code>出口管制法</code> 并不是第一天存在了. 中国的很多外企分公司实际上都在遵守这一法律。比如微软、英特尔这样的公司很多核心技术都不会出口到中国来，甚至中国国籍的员工参与这些项目的研发也会被法律所限制。 </p><p><code>HashiCorp</code> 错就错在中美斗争的这个关键节点上，把这个一直存在的问题又强调了一遍，<code>HashiCorp</code> 本可以只写 <code>出口限制国家</code> 的（这个列表里还有我们的朝鲜兄弟、伊朗兄弟等），但是非得写 <code>PEOPLE&#39;S REPUBLIC OF CHINA</code>，瞬间在技术圈引爆了一个大瓜。</p><p>另一家公司 <code>Github</code> 其实也不让人省心，因为之前就爆出过屏蔽伊朗开发者的新闻。类似的事件或多或少都会影响中国的开发者参与国际开源软件的热情。还好疫情期间我自己在阿里云上搭了一个 <code>Git</code> 服务器，CI 环境使用的也是自己的，不再担心 <code>Travis</code> 总是很慢的问题。</p><p>总之这次事件再次给我们提了个醒，自主知识产权是多么重要，国产软件加油，国产基础设施加油！</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;昨天最大的瓜当属 &lt;code&gt;HashCorp&lt;/code&gt; 禁止中国公司使用。&lt;/p&gt;
&lt;p&gt;作为一名 Devops 工程师，&lt;code&gt;HashCorp&lt;/code&gt; 的产品或多或少都接触过。例如之前我们就用他家的 &lt;code&gt;Vagrant&lt;/code&gt; 搭建</summary>
        
      
    
    
    
    <category term="随想" scheme="https://blog.longtian.io/categories/%E9%9A%8F%E6%83%B3/"/>
    
    
    <category term="opensource" scheme="https://blog.longtian.io/tags/opensource/"/>
    
    <category term="devops" scheme="https://blog.longtian.io/tags/devops/"/>
    
  </entry>
  
  <entry>
    <title>Snap 设置代理</title>
    <link href="https://blog.longtian.io/2020/05/30/snap-proxy.html"/>
    <id>https://blog.longtian.io/2020/05/30/snap-proxy.html</id>
    <published>2020-05-30T02:34:25.000Z</published>
    <updated>2020-05-30T02:34:25.000Z</updated>
    
    <content type="html"><![CDATA[<p>Snap 从 <code>2.28</code> 版本开始支持代理设置了，安装开发用的工具就方便很多。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> snap <span class="built_in">set</span> system proxy.http=<span class="string">&quot;http://&lt;proxy_addr&gt;:&lt;proxy_port&gt;&quot;</span></span><br><span class="line"><span class="built_in">sudo</span> snap <span class="built_in">set</span> system proxy.https=<span class="string">&quot;http://&lt;proxy_addr&gt;:&lt;proxy_port&gt;&quot;</span></span><br></pre></td></tr></table></figure><p>分享几个我常用的</p><p><strong>Redis Desktop Manager</strong></p><p>Redis 的一个图形界面客户端</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> snap install redis-desktop-manager</span><br></pre></td></tr></table></figure><p><strong>Slack</strong></p><p>Slack 客户端</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> snap install slack</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;Snap 从 &lt;code&gt;2.28&lt;/code&gt; 版本开始支持代理设置了，安装开发用的工具就方便很多。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span</summary>
        
      
    
    
    
    
    <category term="ubuntu" scheme="https://blog.longtian.io/tags/ubuntu/"/>
    
  </entry>
  
  <entry>
    <title>更换博客域名</title>
    <link href="https://blog.longtian.io/2020/05/14/migrate-to-aliyun-oss.html"/>
    <id>https://blog.longtian.io/2020/05/14/migrate-to-aliyun-oss.html</id>
    <published>2020-05-14T04:27:44.000Z</published>
    <updated>2020-05-14T04:27:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>如你所见，博客域名迁移到了 <code>blog.longtian.info</code> 并且开启了 CDN，国内访问的速度更快。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;hello world&quot;</span>)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;如你所见，博客域名迁移到了 &lt;code&gt;blog.longtian.info&lt;/code&gt; 并且开启了 CDN，国内访问的速度更快。&lt;/p&gt;
&lt;figure class=&quot;highlight js&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td</summary>
        
      
    
    
    
    
  </entry>
  
  <entry>
    <title>[译] 导致 SourceMap 无效常见的 4 个原因</title>
    <link href="https://blog.longtian.io/2018/10/21/5-reasons-sourcemaps-are-broken.html"/>
    <id>https://blog.longtian.io/2018/10/21/5-reasons-sourcemaps-are-broken.html</id>
    <published>2018-10-21T02:59:16.000Z</published>
    <updated>2018-10-21T02:59:16.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://blog.sentry.io/2018/10/18/4-reasons-why-your-source-maps-are-broken">原文</a></p><p>Souce map 非常好用。换句话说，它们被用来在调试阶段显示源代码，这比线上压缩后的代码好懂多了。从某种意义上讲，source map 可以说是秘密代码（压缩后的代码）的解码器。</p><p>但是要让 source map 正常工作可能很棘手。如果你遇到了麻烦，接下来的一些提示或许能帮助你更好的工作。</p><p>如果你第一次接触 source map，请在继续阅读前看看这篇早期的博客 <a href="https://blog.sentry.io/2015/10/29/debuggable-javascript-with-source-maps">Debugging Minified JavaScript with Source Maps</a>.</p><h2 id="丢失或错误的-source-map-注释"><a href="#丢失或错误的-source-map-注释" class="headerlink" title="丢失或错误的 source map 注释"></a>丢失或错误的 source map 注释</h2><p>我们假设你已经通过 <a href="https://www.npmjs.com/package/uglify-js">UglifyJS</a> 或者 <a href="https://webpack.js.org/configuration/devtool/">Webpack</a> 生成了一个 source map。但如果只是生成，而浏览器实际上找不到它，那就很划不来了。要做到这一点，浏览器会假设打包好的 JavaScript 文件里有一行含 <code>sourceMappingURL</code> 的注释或者返回一个叫 <code>SourceMap</code> 的 HTTP 响应头，这个响应头指向 source map 文件的位置。</p><p>为了验证 source map 注释能够正常工作，你需要：</p><p><strong>找到文件最后，自成一行的 <code>sourceMappingURL</code> 注释</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//# sourceMappingURK=script.min.js.map</span></span><br></pre></td></tr></table></figure><p>这个值必须是一个有效的 URI。如果是相对路径，那么它是相对于打包出来的 JavaScript 文件（例如 <code>script.min.js</code>）的路径。大多数 source map 生成工具会自动生成这个值，而且提供了选项用于覆盖它。</p><p>如果用的是 UglifyJS，可以通过指定 source map 参数 <code>url=script.min.js.map</code> 来生成这个注释：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># Using UglifyJS 3.3</span><br><span class="line">$ uglifyjs --source-map url=script.min.js.map,includeSources --output script.min.js script.js</span><br></pre></td></tr></table></figure><p>如果用的是 Webpack ，通过指定 <code>devtool: &quot;source-map&quot;</code> 能够开启 source map，Webpack 会在最终生成的文件最后输出 <code>sourceMappingURL</code>。你可以通过 <code>sourceMapFilename</code> 自定义该文件的名称。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">app</span>: <span class="string">&quot;src/app.js&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;dist&#x27;</span>),</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;[name].js&quot;</span>,</span><br><span class="line">    <span class="attr">sourceMapFilename</span>: <span class="string">&quot;[name].js.map&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">devtool</span>: <span class="string">&quot;source-map&quot;</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要注意的是即使你正确生成了 <code>sourceMappingURL</code>，也有可能它没有在最终上线的版本里出现。例如，前端构建工具链里其它的工具可能会移除所有的注释，结果就是把 <code>//# sourceMappingURL</code> 也一并删掉。</p><p>还有一种情况是你的 CDN 可能会相当智能地把不认识的注释统统删掉；Cloudflare 的自动压缩功能以前就会这么干。所以记住上线后一定要再次确认！</p><p><strong>另外一种做法是：确保服务器返回有效的 SourceMap HTTP 响应头</strong></p><p>除了这个神奇的 <code>sourceMappingURL</code> 注释，你还可以通过返回一个 <code>SourceMap</code> HTTP 响应头来指定 source map 的地址。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SourceMap: /path/to/script.min.js.map</span><br></pre></td></tr></table></figure><p>和 <code>sourceMappingURL</code> 一样，如果这个值是相对路径则相对于打包出来的 JavaScript 文件。浏览器解析 <code>SourceMap</code> HTTP 响应头和 <code>sourceMappingURL</code> 的规则是一样的。</p><p>注意你需要配置你的 web 服务器或者 CDN 来返回这个响应头。但是很多 JavaScript 开发者并不能够随意的修改线上资源的头，所以对大多数人来说，生成 <code>sourceMappingURL</code> 要更简单一些。</p><h2 id="缺少源代码文件"><a href="#缺少源代码文件" class="headerlink" title="缺少源代码文件"></a>缺少源代码文件</h2><p>我们假设你已经正确配置好 source map，你的 <code>sourceMappingURL</code>（或者 <code>SourMap</code> 响应头）存在且生效。到源代码的转换前面部分已经能够正常工作；例如，错误堆栈现在指向源文件的文件名，并且行号和列号也有意义了。尽管这已经算有所提升，但还是缺少一部分，你还是不能通过浏览器的调试工具查看到源代码。</p><p>这很有可能是由于你的 source map 文件没有包含或是指向你的源文件导致的。如果没有源文件，你在调试压缩后的代码时还是会卡住。哦天哪。</p><p>有几种解决方案可以让源代码文件能够正常工作：</p><p><strong>通过 <code>sourcesContent</code> 把源代码嵌到 source map 文件里</strong></p><p>实际上把源代码放到 source map 里是有可能的。在 source map 里，这个字段是 <code>sourcesContent</code>。虽然这会导致 source map 的体积增迅速增长(数以兆计)，但是能够非常简单地让浏览器定位并关联你的源文件。如果你为了让浏览器显示源文件而焦头烂额，我们推荐你这么做。</p><p>如果你用 UglifyJS，你可以用过 <code>includeSources</code> 命令行参数把源代码包含到 source map 的 <code>sourcesContent</code> 属性里：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uglifyjs --source-map url=script.min.js.map,includeSources --output script.min.js script.js</span><br></pre></td></tr></table></figure><p>如果你用 Webpack，不需要做什么 - Webpack 会默认把源代码包含进 source map （前提是已经打开了 <code>devtool:&quot;source-map&quot;</code> 配置）。</p><p><strong>把源代码放到开放服务器上</strong></p><p>除了在 <code>source map</code> 里包含源代码，你也可以把它们放到服务器上供浏览器下载。如果你对安全性有担忧，毕竟是你的原始代码，你可以放到 <code>localhost</code> 服务器或者确保它们通过 VPN 才能访问（即这些文件只能通过公司内部网络访问）</p><p><strong>Sentry 用户可以上传源文件</strong></p><p>如果你是一个 Sentry 用户并且你的首要目的是确保 source map 文件能够被用来还原堆栈信息以及前后的源代码，你可以试一下第三种方法：使用 sentry-cli 或者直接调用 API <a href="https://docs.sentry.io/platforms/javascript/sourcemaps/#uploading-source-maps-to-sentry">上传源文件</a>。</p><p>当然，如果你用的是前两种方法 - 不管是在 source map 了包含源代码还是放到对外开放的服务器上 - Sentry 都能够找到。这完全取决于你。</p><h2 id="多次转换导致-source-map-失效"><a href="#多次转换导致-source-map-失效" class="headerlink" title="多次转换导致 source map 失效"></a>多次转换导致 source map 失效</h2><p>如果你用到了两个或以上的 JavaScript 编译器（例如 Babel 和 UglifyJS）独立调用，有可能生成的 source map 文件指向的是一个处于中间转换状态的代码，而不是源代码。这意味着你在浏览器里调试的时候，步进的是未压缩的代码（这已经有所改善）而不是和你的源代码一一对应。</p><p>举个例子，你用 Babel 把 ES2018 的代码转换成 ES2015，然后用 UglifyJS 进行压缩</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Using Babel7.1 and UglifyJS 3.3</span></span><br><span class="line">$ babel-cli script.js --presets=@babel/env | uglifyjs -o script script.min.js --<span class="built_in">source</span> -map <span class="string">&quot;filename=app.min.js.map&quot;</span></span><br><span class="line">$ <span class="built_in">ls</span> script*</span><br><span class="line">script.js script.min.js script.min.js.map</span><br></pre></td></tr></table></figure><p>如果你直接用这个命令生成的 source map 文件，你就会发现它并不准确。这是因为这个 source map 只能把压缩后的代码转换成 Bebel 生成的代码。它并不会指向你的源代码。</p><p><strong>注意这个问题在用 Gulp 或者 Grunt 这类任务管理器的时候也很常见。</strong></p><p>要解决这个问题，有两种方案：</p><p><strong>用类似 Webpack 的打包工具管理所有的转换</strong></p><p>不再把 Babel 和 UglifyJS 分开调用，而是用它们的 Webpack 插件形式（例如 <a href="https://github.com/babel/babel-loader">babel-loader</a> 和 <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin">uglifyjs-webpack-plugin</a>）。Webpack 能够生成单一的 source map 文件来把最终结果转换回源代码，虽然实际上背后依然有多个转换步骤。</p><p><strong>用一个库把不同转换步骤的 source map 串联起来</strong></p><p>如果你决意要分开使用编译器，你可以用 <a href="https://www.npmjs.com/package/source-map-merger">source-map-merger</a>，或者 Webpack 的 <a href="https://webpack.js.org/loaders/source-map-loader/">source-map-loader</a> 插件，来把上一步的 source map 吐给下一步的转换。</p><p>如果你有的选，还是推荐你用第一步，直接用 Webpack 省得后来哀怨。</p><h2 id="文件版本不对或缺少版本管理"><a href="#文件版本不对或缺少版本管理" class="headerlink" title="文件版本不对或缺少版本管理"></a>文件版本不对或缺少版本管理</h2><p>我们假设你遵循了上面所有的步骤。你的 <code>sourceMappingURL</code>（或 <code>SourceMap</code> HTTP 响应头）存在并且被正确的声明。你的 source map 包括了你的源代码（或放到公网上）。并且你用了 Webpack 做转换端到端的管理。你的 source map 还是会时不时地映射错误。</p><p>还剩下这样的可能：source map 和生成的代码不匹配。</p><p>这个问题会在这种情况下会发生：首先、浏览器或者工具下载了一个生成的代码（例如 <code>script.min.js</code>），然后试着去下载对应的 source map 文件（<code>script.min.js.map</code>），但是下载到的是 “更新” 后的 source map 文件，和之前的生成代码已经不匹配了。</p><p>这种情况并不会很常见，但是当你在调试的同时进行部署的时候会发生，或者你调试的是即将过期的、被浏览器缓存的资源时会发生。</p><p><strong>要解决这个问题，你需要管理好文件和 source map 的版本</strong>，有下面几种方式：</p><ul><li>给每个文件添加版本号，例如：<code>script.abc123.min.js</code></li><li>在 URL 里添加版本号字符，例如 <code>script.min.js?abc123</code></li><li>为父级目录添加版本号，例如 <code>abc123/script.min.js</code></li></ul><p>选择哪种策略并不要紧，关键是对所有的 JavaScript 资源要使用一致的策略。最好每一个生成的文件和 source map 都有相同的版本号和命名规则，就像下面这样：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// script.abc123.min.js</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> a=[i=<span class="number">0</span>];++i&lt;<span class="number">20</span>;a[i]=i);</span><br><span class="line"><span class="comment">//# sourceMappingURL=script.abc123.min.js.map</span></span><br></pre></td></tr></table></figure><p>用这种方法管理版本能够保证浏览器下载到生成代码和 source map 文件对应上，避免不必要的版本不一致问题。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://blog.sentry.io/2018/10/18/4-reasons-why-your-source-maps-are-broken&quot;&gt;原文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Souce map</summary>
        
      
    
    
    
    <category term="翻译" scheme="https://blog.longtian.io/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
  </entry>
  
  <entry>
    <title>[译] 书写灵活、可维护，可扩展的 Ansible Playbook</title>
    <link href="https://blog.longtian.io/2018/10/05/make-your-ansible-playbooks-flexible-maintainable-and-scalable.html"/>
    <id>https://blog.longtian.io/2018/10/05/make-your-ansible-playbooks-flexible-maintainable-and-scalable.html</id>
    <published>2018-10-05T03:20:35.000Z</published>
    <updated>2018-10-05T03:20:35.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.ansible.com/blog/make-your-ansible-playbooks-flexible-maintainable-and-scalable">原文</a></p><p>自从 2013 年开始使用 Ansible，我已经用 Ansible 自动化完成很多事情：SaaS 服务，树莓派集群，家庭自动化系统，甚至我自己的电脑。</p><p>从那以后，我学会了很多能够降低维护负担的技巧。对我来说项目的可维护性异常重要，因为我的很多项目，例如一个 Apache Solr 项目已经存在超过 10 年了！如果项目难维护或者架构上难以做出大的改变，我会把项目输给其它更敏捷（nimble）的对手，进而丢掉金钱，更重要的是我可能会疯掉。</p><p>今年我会在奥斯汀举办的 AnsibleFest 上做一个<a href="https://agenda.fest.ansible.com/SessionDetail.aspx?id=482018">同名分享</a>，本文即总结这次分享的主题。</p><h2 id="保持井井有条"><a href="#保持井井有条" class="headerlink" title="保持井井有条"></a>保持井井有条</h2><p>我喜欢摄影和自动化，所以我花了很多时间在涉及树莓派和相机的电子项目上。如果没有图中的组织系统，想要把部件放到正确的位置会是一件让人沮丧的事情。</p><p><img src="https://www.ansible.com/hs-fs/hubfs/45982928-455cdb80-c020-11e8-96e4-833efbac87f4.jpg" alt="组织系统"></p><p>同样的，在 Ansible 中，我喜欢把我常用的 task 组织起来，这样才能更轻松编写和测试它们，并且不需要太多的精力就可以管理好它们。</p><p>开始的时候我会把所有的 task 写到一个 playbook 文件里。当文件到达 100 行左右，我会把相关的任务拆分到不同的文件里，并在 playbook 中使用 include_tasks 引入它们。</p><p>随着 playbook 越来越复杂，我经常注意到有一些相关性很高的的 task 可以被独立开，例如安装一个软件、拷贝配置文件、启动（重启）一个守护进程。这种情况下我会用 <code>ansible-galaxy init ROLE_NAME</code> 命令新建一个 role，并且把那些 tasks 放进这个 role 里。</p><p>如果这个 role 够通用，我会把 role 放到 Github 并且提交到 Ansible Galaxy 里，又或者放到一个单独的私有 Git 仓库里。现在我可以通过 <a href="https://github.com/metacloud/molecule/">Molecule</a> 或者其它测试工具为 role 添加一系列的通用测试，哪怕这些 role 被隶属不同的团队的不同项目所使用。</p><p>之后我会通过 <code>requirements.txt</code> 文件把外部的 role 引入到项目里。对于某些稳定性至关重要的项目，我会通过 git ref 或者 tag 指定 role 的版本。对于其它项目我则会牺牲一点稳定性以换取更好的可维护性（例如测试 playbook 或者一次性的服务器配置），我直接使用 role 的名字（如果不在 Ansible Galaxy 上就指定仓库的详情）。</p><p>对于大部分项目我都不会把外部 role 提交到代码仓库里，因为在 CI 系统里每次从头运行的时候都会去安装。但是在有一些情况下，最好把所有的 role 都提交到仓库里。比如有一些开发者日常会用到我写的 <a href="https://www.drupalvm.com/">Drupal 虚拟机</a>的 playbook，这些开发者通常住在离 Ansible Galaxy 服务器很远的地方，所以他们在安装大量必要依赖的时候会遇到麻烦。因此我把所有 role 都提交到仓库里了，这样他们在构建一个新的 Drupal 虚拟机实例的时候就不用等着所有 role 安装完成了。</p><p>如果你真的把 role 都提交到仓库里了，你需要在每次更新 role 的时候有一个彻底（thorough）的流程，确保你的 <code>requirements.yml</code> 文件和已经安装的 role 同步！我通常通过 <code>ansible-galaxy install -r requirements.yml --force</code> 命令来强制替换仓库里的 role，并且保持诚实（踏实？）！</p><h2 id="简化和优化"><a href="#简化和优化" class="headerlink" title="简化和优化"></a>简化和优化</h2><blockquote><p>YAML 不是一门编程语言<br>- Jeff Geerling</p></blockquote><p>大家喜欢用 Ansible 的一个原因是它基于 YAML 并且拥有一套声明式的语法。如果你要安装一个模块就在 task 里这样写：<code>package: name=httpd state=present</code>。如果你要确保一个服务运行就这样写 <code>service: name=httpd state=started</code></p><p>然而在很多情况下，你会需要让一切更智能化。举个例子，如果你用相同的 role 构建虚拟机和容器，但是你并不想在容器里启动服务，你需要增加一个只在某些条件下执行（when condition）的限制：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Ensure</span> <span class="string">Apache</span> <span class="string">is</span> <span class="string">started</span></span><br><span class="line">  <span class="attr">service:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">httpd</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">started</span></span><br><span class="line">  <span class="attr">when:</span> <span class="string">&#x27;server_type != &quot;container&quot; &#x27;</span></span><br></pre></td></tr></table></figure><p>此类逻辑是简单的，并且在别人阅读 task 以搞清楚它的目的时候很有用。但是有的人会在 when condition 里写上一大堆花里胡哨的判断，甚至是 Ansible 暴露出的 Jinja2 和 Python 的接口，这种情况下容易失控（get off rails）。</p><p>根据经验（as a rule of thumb），如果你在 playbook 的 when condition 里为了正确的转义引号上花费了 10 分钟以上，你这时候就应该考虑写一个单独的模块来完成 task 用到的逻辑。Python 脚本通常应该位于独立的模块里，而不是和其它的 YAML 写到行内。当然也有例外（比如比较复杂的字典和字符串时），但我会努力避免在 Ansible playbook 里写任何复杂的代码。</p><p>除了避免复杂逻辑，还有一个很有效的方法是让 playbook 运行更快。我经常 profile 一个 playbook （通过设置 callback_whitelist &#x3D; profile_roles, profile_tasks, timer 默认参数），发现一两个 task 或者 role 和 playbook 其它的相比花了很长的时间。</p><p>举个例子，有一个 playbook 里用了 copy 模块来复制一个有几十个文件的大目录。由于 Ansible 拷贝文件模块的内部实现，复制每个文件都意味着一直在 SSH 链接上等待着传输完成。</p><p>把这个 task 改成基于 synchronize 的可以在每次运行的时候节省好长时间。针对单次运行这看起来没什么，但是当 playbook 需要定期运行的时候（例如确保一台服务器的配置），或者作为 CI 流程的一部分的时候保持它的高效就很重要了。否则它会让 CPU 周期耗费在一些无用代码上，开发者通常会很讨厌等待 CI 测试通过，他们只想知道代码会不会导致问题。</p><p>请关注我在 AnsibleFest Austin 2018 的演讲 <a href="https://agenda.fest.ansible.com/SessionDetail.aspx?id=482018">Make your Ansible Playbooks flexible, maintainable, and scalable</a>。如果这还不够，我在我的书 《Ansible for DevOsp》 里有很多关于编写和维护 Ansible Playbook 的文章（<a href="https://www.ansible.com/resources/ebooks/ansible-for-devops">你可以从 Red Hat 官网下载到摘录</a>）。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.ansible.com/blog/make-your-ansible-playbooks-flexible-maintainable-and-scalable&quot;&gt;原文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;自从 2013 年开始使用</summary>
        
      
    
    
    
    <category term="翻译" scheme="https://blog.longtian.io/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
  </entry>
  
  <entry>
    <title>基于 Webpack 的跨平台开发</title>
    <link href="https://blog.longtian.io/2018/09/16/webpack-cross-platform-development.html"/>
    <id>https://blog.longtian.io/2018/09/16/webpack-cross-platform-development.html</id>
    <published>2018-09-16T01:07:50.000Z</published>
    <updated>2018-09-16T01:07:50.000Z</updated>
    
    <content type="html"><![CDATA[<p>小程序、快应用的开发最近相当热门，公司也在开发对应的 SDK 。既然小程序、快应用都是选用的 JavaScript 做为开发语言，那么有没有<br>可能，让小程序和快应用都能公用基于 H5 的 SDK 核心。</p><p>答案是肯定可以！如果用一个词来概括软件工程最想解决的问题，那么 <code>问题拆分</code> 也许是最合适的，我们把问题拆解：</p><ul><li>JavaScript 核心</li><li>对不同平台接口的抽象</li></ul><p><code>JavaScript 核心</code> 不用说，和具体的业务逻辑有关，设计之初就要清晰的知道系统的边界在哪里，核心和接口之间如何通信。</p><p>主要的问题在如何降低接口抽象的复杂度。</p><h2 id="环境变量-代码如何知道当前运行的环境"><a href="#环境变量-代码如何知道当前运行的环境" class="headerlink" title="环境变量 - 代码如何知道当前运行的环境"></a>环境变量 - 代码如何知道当前运行的环境</h2><p>现代软件开发越来越重视过程，例如很多软件都很明显的区分为 <code>构建</code> 和 <code>运行</code> 两个部分。通过参数的不同取值指定运行环境<br>是 <code>构建</code> 过程一个比较常用的步骤，这样做的好处是能够为不同平台构建出不同的工件，有利于减小工件的体积。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 为 H5 打包</span></span><br><span class="line">PLATFORM=H5 build</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 为快应用打包</span></span><br><span class="line">PLATFORM=quickapp build</span><br></pre></td></tr></table></figure><p>这样就通过环境变量把运行时的环境传给了负责构件的命令 <code>build</code> (这里假设构件的命令是 build)。</p><p>下面的代码就能够打出不同的包，并在不同平台上会打印出不同的内容：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`我在 <span class="subst">$&#123;process.env.PLATFORM&#125;</span> 平台下`</span>);</span><br></pre></td></tr></table></figure><p><strong>不同平台如何访问环境变量</strong></p><p><code>process</code> 其实是 Node.js 全局定义的一个属性，那么为什么在非 Node.js 平台下也能访问到呢？这就要借助 <strong><del>前端</del> JavaScript 打包</strong><br>工具 <code>webpack</code> 了, 关于 process 的处理有两种情况：</p><ul><li>在全局会有一个 mock 的 process 对象，确保相关代码能够访问到 process 对象而不会报错</li><li>通过 <code>DefinePlugin</code> 替换代码里的 <code>process.env.PLATFORM</code></li></ul><h2 id="模块隔离-根据平台执行不同的逻辑"><a href="#模块隔离-根据平台执行不同的逻辑" class="headerlink" title="模块隔离 - 根据平台执行不同的逻辑"></a>模块隔离 - 根据平台执行不同的逻辑</h2><p>知道了当前的运行环境，就能够根据平台执行不同的逻辑了</p><p><strong>一般做法</strong></p><p>其实就是 <code>if else</code> ，代码里一定多多少少有一些判断当前平台的代码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">if</span>(process.<span class="property">env</span>.<span class="property">PLATFORM</span> === <span class="string">&#x27;H5&#x27;</span>)&#123;</span><br><span class="line"> <span class="comment">// 如果是 H5 平台就执行这里的代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 <code>if else</code> 虽然能够解决问题，但是当代码比较复杂的时候还是会导致难以维护，比如下面这种情况：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">if</span>(process.<span class="property">env</span>.<span class="property">PLATFORM</span> === <span class="string">&#x27;H5&#x27;</span>)&#123;</span><br><span class="line">  <span class="comment">// H5 相关逻辑</span></span><br><span class="line">&#125;<span class="keyword">else</span> <span class="keyword">if</span>(process.<span class="property">env</span>.<span class="property">PLATFORM</span> === <span class="string">&#x27;quickapp&#x27;</span>)&#123;</span><br><span class="line">  <span class="comment">// 快应用下要加载一个模块</span></span><br><span class="line">  <span class="keyword">const</span> dep = <span class="built_in">require</span>(<span class="string">&#x27;some-dependency&#x27;</span>);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">  <span class="comment">// 其它</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因此这种做法只适合代码比较简单的情况，例如传递一些 <code>flag</code>，否则可以用下面这种方法。</p><p><strong>更优方案</strong></p><p>假设 H5、快应用、小程序都需要用到一个模块叫做 <code>foo</code>，引入的源代码如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;./foo&#x27;</span>)</span><br></pre></td></tr></table></figure><p>目录结构如下，<code>foo</code> 模块对应不同平台有不同实现，但是暴露的方法签名以及返回类型是一致的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">foo.js</span><br><span class="line">foo.quickapp.js</span><br><span class="line">foo.miniprogram.js</span><br></pre></td></tr></table></figure><p>通过 <code>webpack</code> 的 <code>config</code> 参数可以指定不同平台的配置文件。</p><table><thead><tr><th>平台</th><th>webpack 配置文件</th></tr></thead><tbody><tr><td>h5</td><td>webpack.config.js</td></tr><tr><td>miniprogram</td><td>webpack.config.miniprogram.js</td></tr><tr><td>quickapp</td><td>webpack.config.quickapp.js</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">extensions</span>: [<span class="string">&#x27;.js&#x27;</span>, <span class="string">&#x27;.json&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果是快应用的话就修改成这样</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.quickapp.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">extensions</span>: [<span class="string">&#x27;.quickapp.js&#x27;</span>, <span class="string">&#x27;.js&#x27;</span>, <span class="string">&#x27;.json&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以看到 <code>resolve.extensions</code> 其实是一个数组，当 <code>webpack</code> 遇到 <code>require</code> 一个文件依赖的时候会按照这个顺序进行匹配。</p><h2 id="命名空间-解决第三方模块依赖于浏览器的问题"><a href="#命名空间-解决第三方模块依赖于浏览器的问题" class="headerlink" title="命名空间 - 解决第三方模块依赖于浏览器的问题"></a>命名空间 - 解决第三方模块依赖于浏览器的问题</h2><p>这其实是开发过程过程中一个非常具体的问题，这个模块是 <a href="https://www.npmjs.com/package/jsencrypt">jsencrypt</a>。</p><p>在 <code>H5</code> 平台下引入了这个模块，打包后运行没有问题，但是当尝试在快应用上运行的时候一直报这个错：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;window is undefined&quot;</span></span><br></pre></td></tr></table></figure><p>显然这个插件是为了 H5 开发的，没有考虑其它平台的兼容性。</p><p><strong>一般做法</strong></p><p>直接把源码下下来丢到 <code>vendors</code> 文件夹下，然后把 <code>window</code>、<code>navigator</code> 相关的兼容性一个一个问题修复。这种做法其实放弃了使用 <code>npm</code> 管理依赖的优势，<br>并且在项目中留下了一个技术债。以后如果 <code>jsencrypt</code> 发布了一个新的不得不使用的版本（例如修复某个安全漏洞），需要手动更新依赖并且打补丁。</p><p><strong>更优方案</strong></p><p>后来突然想起来网上有人遇到过类似的情况：社区有很多的 jQuery 插件，这些插件在编写的时候会假设全局有一个 $ 对象，但是使用了 <code>webpack</code> 以后，<br>由于用到了闭包，全局环境下其实是没有 <code>$</code> 这个对象的的，而解决这个问题一个比较通用的做法是使用 <code>webpack</code> 的 <code>ProvidePlugin</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="comment">//...,</span></span><br><span class="line">    <span class="keyword">new</span> webpack.<span class="title class_">ProvidePlugin</span>(&#123;</span><br><span class="line">      <span class="attr">$</span>: <span class="string">&#x27;jquery&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>顺着这个思路，修改了一下 <code>webpack</code> 配置文件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.quickapp.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="comment">//...,</span></span><br><span class="line">    <span class="keyword">new</span> webpack.<span class="title class_">ProvidePlugin</span>(&#123;</span><br><span class="line">      <span class="attr">window</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;noop.js&#x27;</span>),</span><br><span class="line">      <span class="attr">navigator</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;noop.js&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>而 <code>noop</code> 的代码也非常简单，就是返回一个空对象</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// noop.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;&#125;;</span><br></pre></td></tr></table></figure><p>这样至少可以保证所有第三方模块都不会报 <code>window</code> 或者 <code>navigator</code> 找不到的错误。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过上面几个由浅入深的问题，我们都够了解到 <code>webpack</code> 不仅仅是前端的打包工具，也可以用于跨平台的开发。</p><table><thead><tr><th>解决问题</th><th>Webpack 配置</th><th>试用场景</th></tr></thead><tbody><tr><td>环境变量</td><td>DefinePlugin</td><td>判断平台，传递 Flag 等较为简单的逻辑</td></tr><tr><td>模块隔离</td><td>resolve.extensions</td><td>引入同一个模块在不同平台下的实现</td></tr><tr><td>命名空间</td><td>ProvidePlugin</td><td>修复兼容性问题，提供跨平台的全局空间</td></tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;小程序、快应用的开发最近相当热门，公司也在开发对应的 SDK 。既然小程序、快应用都是选用的 JavaScript 做为开发语言，那么有没有&lt;br&gt;可能，让小程序和快应用都能公用基于 H5 的 SDK</summary>
        
      
    
    
    
    
  </entry>
  
</feed>
