<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>青云卷</title><link>https://mydream.ink/</link><description>Recent content on 青云卷</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Copyright © 2018–2023</copyright><lastBuildDate>Sun, 09 Jul 2023 15:02:04 +0800</lastBuildDate><atom:link href="https://mydream.ink/index.xml" rel="self" type="application/rss+xml"/><item><title>为啥不给扩容？已经影响到业务了</title><link>https://mydream.ink/posts/cloud-native/kubernetes/why-not-scale-up/</link><pubDate>Sun, 09 Jul 2023 15:02:04 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/why-not-scale-up/</guid><description>&lt;p>正常云厂商 kubernetes 集群的节点池（也叫节点组或 &lt;code>NodeGroup&lt;/code>）都是由另一款较老的产品 &lt;em>弹性伸缩&lt;/em> 来实现的，当然啦，不同云厂商有不同叫法，这儿就不细说了。也正是由于这种不那么紧密的关系使得集群弹性伸缩对于异常情况的处理显得有些欠火候，用户也是叫苦连天。&lt;/p>
&lt;blockquote>
&lt;p>cluster-autoscaler 中有一个超时参数在很多地方都有用到 &lt;code>--max-node-provision-time&lt;/code>，默认为 &lt;code>15min&lt;/code>，下面会大量用到。&lt;/p>
&lt;/blockquote>
&lt;p>扩容逻辑在另一篇文章中有讲，所以本文会略过，主要讲一下当 pending 的 pod 已经触发扩容后失败的情况。&lt;/p>
&lt;h2 id="创建节点失败">创建节点失败&lt;/h2>
&lt;p>正常来说，经过一系列的逻辑处理，有了扩容方案后，CA 会调用底层弹性伸缩的接口调整节点的的期望实例数触发扩容，同时通过 ScaleUpRequest 缓存该事件，在以后的每次的循环中查看该事件的完成情况，如果扩容完成了就会清理掉该事情。&lt;/p>
&lt;blockquote>
&lt;p>CA 判断底层有没有将节点扩出来的标准是看通过 &lt;code>(cloudprovider.NodeGroup).Nodes()&lt;/code> 查到的节点数量是不是小于 &lt;code>(cloudprovider.NodeGroup).TargetSize()&lt;/code>，如果小于，那就说明还在扩。&lt;/p>
&lt;/blockquote>
&lt;p>但事情往往总会发生点意外：&lt;/p>
&lt;ol>
&lt;li>如果 CA 调用底层弹性伸缩的接口失败了该怎么办？&lt;/li>
&lt;li>如果底层一直建不出机器怎么办？&lt;/li>
&lt;/ol>
&lt;p>针对情况 1，CA 会立即将对应的节点池 backoff 掉，并报告事件 &lt;code>FailedToScaleUpGroup&lt;/code>；情况 2，CA 的等待会有一个超时时间（&lt;code>--max-node-provision-time&lt;/code>指定），时间一到也会将其 backoff 掉并报告事件 &lt;code>ScaleUpTimedOut&lt;/code>。&lt;/p>
&lt;p>然而，针对情况 2，仅仅是 backoff 掉没有扩出来的节点池还是不够的，因为弹性伸缩的期望实例数并不等于当前实例数，这在 CA 看来就是有扩容中的节点从而不会触发新扩容，而且后面确实有可能被弹性伸缩再扩出来（比如在弹性伸缩在超过&lt;code>--max-node-provision-time&lt;/code>指定的时间的重试中又给扩出来了）。此时就引出了 CA 的修正逻辑，当 CA 观察到伸缩组已经没有在扩容了，并且长时间（&lt;code>--max-node-provision-time&lt;/code>指定）期望实例数小于当前实例数且数量一直不曾改变时，就会将期望实例数调整与当前实例数保持一致。&lt;/p>
&lt;p>情况 1 多见于 IaaS 层故障，并不多见，且我们也无能为力。但是针对于情况 2，扩容要 &lt;code>15min&lt;/code>（&lt;code>--max-node-provision-time&lt;/code>默认）超时才算作失败，且在超时 &lt;code>15min&lt;/code> 后还得观察 &lt;code>15min&lt;/code> 修正后才会触发新的扩容，里外里足足 &lt;strong>30 分钟&lt;/strong>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>，这哪个客户受得了，一般扩容都是十万火急的场景。&lt;/p>
&lt;p>也因为这个原因，CA 社区又给提供了第三种失败，即在每一次循环中查询失败的节点，如果看到有失败的节点就 backoff 节点池，并报告事件 &lt;code>ScaleUpFailed&lt;/code>，紧接着立即删除失败的节点。这就使得 CA 能在最短的时间内响应扩容失败并立即触发其它节点池扩容。&lt;/p>
&lt;p>既然这个特性这么好，是不是各厂商都用起来了呢？其实并没有，大多厂商的弹性伸缩并不会保存创建失败节点。这里列一下已经支持的厂商：&lt;/p>
&lt;ul>
&lt;li>&lt;code>gce&lt;/code>，原本就在 MIG 中保留了失败的节点，所以先天支持，也是目前支持的最好的&lt;/li>
&lt;li>&lt;code>aws&lt;/code>，大概一年前支持的，通过 mock 创建中的节点来实现的&lt;/li>
&lt;li>&lt;code>tke&lt;/code>原生节点，原生节点失败的 &lt;code>machine&lt;/code> 会被保留下来，所以也是先天支持&lt;/li>
&lt;/ul>
&lt;p>不过，总结来说，创建失败一定跟 &lt;em>弹性伸缩&lt;/em> 有关。&lt;/p>
&lt;h3 id="节点池的-backoff-机制">节点池的 backoff 机制&lt;/h3>
&lt;ul>
&lt;li>&lt;code>--initial-node-group-backoff-duration&lt;/code>，初始 backoff 时间&lt;/li>
&lt;li>&lt;code>--max-node-group-backoff-duration&lt;/code>，最大 backoff 时间&lt;/li>
&lt;li>&lt;code>--node-group-backoff-reset-timeout&lt;/code>，backoff 重置时间&lt;/li>
&lt;/ul>
&lt;p>假设节点池一直扩不出来 5m 10m 20m 30m 30m &amp;hellip; 每次 backoff 时间不超过 3h，3h 后又会按 5m 开始&lt;/p>
&lt;h3 id="异常分析">异常分析&lt;/h3>
&lt;p>这里假设只能通过超时来发现扩容失败，同时也从侧面讲一下循环中查询扩容失败的必要性。&lt;/p>
&lt;ul>
&lt;li>场景 1：集群 pod 触发节点池 A 扩容 2 个节点，15min 过后节点没扩出来，节点池 A 被 backoff, 5min 后节点池 A 退出 backoff 状态，后续一直没有再触发节点池 A 产生新的扩容，15min 后触发修正逻辑，将节点池 A 的期望实例数减 2，由于在之前节点池 A 就已经退出了 backoff 状态，所以这次扩容还有可能扩到节点池 A，如果再次扩节点池 A，过 15min 后，再次被 backoff，此次 10min 退出 backoff 状态，15min 后第二次修正节点池 A，同理，第三次扩容还有可能扩到节点池 A，又过 15min 后，继续 backoff，这次 20min 才能退出，由于还处理 backoff 状态中，所以再过 15min 第三次修正后，可以确保不会触发节点池 A 扩容。至此，当节点池 A 持续不可用时，可能需要 15x6=&lt;code>90min&lt;/code>（实际不止这么多，因为还有&lt;code>--scan-interval&lt;/code>指定的循环间隔需要被算入） 才能切换到其它节点池扩容。
&lt;img src="../images/2023-07-08-19-20.png" alt="Alt text">&lt;/li>
&lt;li>场景 2：在场景 1 中，如果在扩容的 15min 内又有新的 pod pending，并且还是触发节点池 A 的扩容，那么超时时间会被重置，那么无法扩出的时间可能比 90min 还会更长。&lt;/li>
&lt;li>场景 3：当前节点池是一个异常状态，没有节点在集群中，并且当前实例数小于期望数，CA 由于重启或者其它原因并没有该节点池的 ScaleUpRequest 缓存，这种情况下，CA 将无法获取到异常节点池的修正信息，从而不会触发修正，而又由于节点池的期望实例没有修正会被 CA 认为是有节点池节点正常加入中，而不会触发新的扩容。这种情况下要人工介入修正节点数才能解决。&lt;/li>
&lt;/ul>
&lt;p>如果云厂商提供了在查询扩容失败的解决路径，那么以上三种情况都可解决。&lt;/p>
&lt;h2 id="注册节点失败">注册节点失败&lt;/h2>
&lt;p>如果有节点被创建出来了，长时间（&lt;code>--max-node-provision-time&lt;/code>指定）没有注册，也会被删除并上报事件 &lt;code>DeleteUnregistered&lt;/code>。这种一般是由于集群的节点添加流程出现了问题。&lt;/p>
&lt;h2 id="不满足前置条件">不满足前置条件&lt;/h2>
&lt;h3 id="集群健康">集群健康&lt;/h3>
&lt;p>如果 CA 的日志中出现 &lt;code>Cluster is not ready for autoscaling&lt;/code>，那说明集群当前状态异常，这样 CA 唯一能进行的写操作是移除长时间未注册的节点（这有可能使集群状态恢复正常），而不会再触发扩缩容，满足下述条件时，集群会被认为异常：&lt;/p>
&lt;ol>
&lt;li>集群中未就绪节点数&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>达到&lt;code>--ok-total-unready-count&lt;/code>指定值（默认是&lt;code>3&lt;/code>），其占比达到&lt;code>max-total-unready-percentage&lt;/code>指定值，默认是&lt;code>45&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="节点池健康">节点池健康&lt;/h3>
&lt;p>当节点池被认为不健康或已经 backoff 时，不会触发该节点池扩缩，即当出现下述情况之一时：&lt;/p>
&lt;ol>
&lt;li>当集群中没有该节点池节点，但节点池的期望实例数又不为 0 时，此时 CA 日志中会出现&lt;code>Failed to find readiness information&lt;/code>&lt;/li>
&lt;li>当前节点池中已就绪实例数比该节点池期可接受的最小实例数&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>还小时，且其差值大于&lt;code>--ok-total-unready-count&lt;/code>，并且在节点池中的占比超过&lt;code>max-total-unready-percentage&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="参数限制">参数限制&lt;/h3>
&lt;p>达到下面这些参数限制时，不会触发扩缩容：&lt;/p>
&lt;ol>
&lt;li>&lt;code>--max-nodes-total&lt;/code>，限制集群最大节点数&lt;/li>
&lt;li>&lt;code>--cores-total&lt;/code>，限制集群最大核心数&lt;/li>
&lt;li>&lt;code>--memory-total&lt;/code>，限制集群最大内存数&lt;/li>
&lt;li>&lt;code>--gpu-total&lt;/code>，限制集群最大 gpu 数&lt;/li>
&lt;/ol>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>实际应该是超过 90 分钟，具体待测试，在&lt;a href="#%E5%BC%82%E5%B8%B8%E5%88%86%E6%9E%90">异常分析&lt;/a>场景 2 中有介绍&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>未就绪节点的定义方式在系列文章中有提到，可以自行查阅&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>当节点池正在扩容时，这个值为期望实例数-正在扩容节点数；当其正在缩容时，这个值为期望实例数&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>通过 kubeadm 安装 kubernetes 1.26</title><link>https://mydream.ink/posts/cloud-native/kubernetes/install-kubenetes-1.26-by-kubeadm/</link><pubDate>Sat, 18 Mar 2023 18:04:55 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/install-kubenetes-1.26-by-kubeadm/</guid><description>&lt;h1 id="环境准备">环境准备&lt;/h1>
&lt;p>master 节点使用 cri-o、worker 节点使用 containerd 作为运行时&lt;/p>
&lt;h2 id="master-节点">master 节点&lt;/h2>
&lt;ul>
&lt;li>操作系统：Ubuntu Server 22.04 LTS&lt;/li>
&lt;li>规格：2c4g&lt;/li>
&lt;li>cloud-master1 10.67.3.9&lt;/li>
&lt;li>cloud-master2 10.67.3.13&lt;/li>
&lt;li>cloud-master3 10.67.3.15&lt;/li>
&lt;/ul>
&lt;h2 id="worker-节点">worker 节点&lt;/h2>
&lt;ul>
&lt;li>操作系统：Ubuntu Server 22.04 LTS&lt;/li>
&lt;li>规格：2c2g&lt;/li>
&lt;li>VM-2-42-ubuntu 10.67.2.42&lt;/li>
&lt;/ul>
&lt;h2 id="负载均衡">负载均衡&lt;/h2>
&lt;ul>
&lt;li>43.135.64.117&lt;/li>
&lt;/ul>
&lt;h1 id="节点配置">节点配置&lt;/h1>
&lt;p>所有节点都需要的初始化配置，含 master 和 worker 节点&lt;/p>
&lt;h2 id="安装-kubeadmkubeletkubectl">安装 kubeadm、kubelet、kubectl&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>更新 &lt;code>apt&lt;/code> 包索引并安装使用 Kubernetes &lt;code>apt&lt;/code> 仓库所需要的包：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install -y apt-transport-https ca-certificates curl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>下载 Google Cloud 公开签名秘钥：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>添加 Kubernetes &lt;code>apt&lt;/code> 仓库：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#34;deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main&amp;#34;&lt;/span> &lt;span style="color:#111">|&lt;/span> sudo tee /etc/apt/sources.list.d/kubernetes.list
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>更新 &lt;code>apt&lt;/code> 包索引，安装 kubelet、kubeadm 和 kubectl，并锁定其版本：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install -y kubelet kubeadm kubectl
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-mark hold kubelet kubeadm kubectl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h2 id="配置操作系统">配置操作系统&lt;/h2>
&lt;h3 id="转发-ipv4-并让-iptables-看到桥接流量">&lt;strong>&lt;strong>转发 IPv4 并让 iptables 看到桥接流量&lt;/strong>&lt;/strong>&lt;/h3>
&lt;p>执行下述指令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">overlay
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">br_netfilter
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo modprobe overlay
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo modprobe br_netfilter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*# 设置所需的 sysctl 参数，参数在重新启动后保持不变*
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">net.bridge.bridge-nf-call-iptables = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">net.bridge.bridge-nf-call-ip6tables = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">net.ipv4.ip_forward = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*# 应用 sysctl 参数而不重新启动*
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo sysctl --system
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>通过运行以下指令确认 &lt;code>br_netfilter&lt;/code> 和 &lt;code>overlay&lt;/code> 模块被加载：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>lsmod &lt;span style="color:#111">|&lt;/span> grep br_netfilter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lsmod &lt;span style="color:#111">|&lt;/span> grep overlay
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>通过运行以下指令确认 &lt;code>net.bridge.bridge-nf-call-iptables&lt;/code>、&lt;code>net.bridge.bridge-nf-call-ip6tables&lt;/code> 和 &lt;code>net.ipv4.ip_forward&lt;/code> 系统变量在你的 &lt;code>sysctl&lt;/code> 配置中被设置为 1：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="创建高可用控制面">创建高可用控制面&lt;/h1>
&lt;h2 id="配置负载均衡器">配置负载均衡器&lt;/h2>
&lt;p>43.135.64.117:443 → 10.67.3.9:6443、10.67.3.13:6443、10.67.3.15:6443&lt;/p>
&lt;h2 id="配置-master-运行时">配置 master 运行时&lt;/h2>
&lt;h3 id="查看默认-cgroup-驱动">查看默认 cgroup 驱动&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>stat -fc %T /sys/fs/cgroup/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="安装-cri-o">安装 CRI-O&lt;/h3>
&lt;p>root 执行&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">export&lt;/span> &lt;span style="color:#111">OS&lt;/span>&lt;span style="color:#f92672">=&lt;/span>xUbuntu_22.04
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">export&lt;/span> &lt;span style="color:#111">VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>1.26
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#34;deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/&lt;/span>&lt;span style="color:#111">$OS&lt;/span>&lt;span style="color:#d88200">/ /&amp;#34;&lt;/span> &amp;gt; /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#34;deb [signed-by=/usr/share/keyrings/libcontainers-crio-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/&lt;/span>&lt;span style="color:#111">$VERSION&lt;/span>&lt;span style="color:#d88200">/&lt;/span>&lt;span style="color:#111">$OS&lt;/span>&lt;span style="color:#d88200">/ /&amp;#34;&lt;/span> &amp;gt; /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:&lt;span style="color:#111">$VERSION&lt;/span>.list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /usr/share/keyrings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/&lt;span style="color:#111">$OS&lt;/span>/Release.key &lt;span style="color:#111">|&lt;/span> gpg --dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/&lt;span style="color:#111">$VERSION&lt;/span>/&lt;span style="color:#111">$OS&lt;/span>/Release.key &lt;span style="color:#111">|&lt;/span> gpg --dearmor -o /usr/share/keyrings/libcontainers-crio-archive-keyring.gpg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get install cri-o cri-o-runc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>CRI-O 默认也使用 systemd cgroup 驱动&lt;/p>
&lt;h3 id="启动-cri-o">启动 CRI-O&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> crio
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl start crio
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="配置控制面">配置控制面&lt;/h2>
&lt;h3 id="初始化-master1">初始化 master1&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo kubeadm init --control-plane-endpoint &lt;span style="color:#d88200">&amp;#34;43.135.64.117:443&amp;#34;&lt;/span> --upload-certs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>
&lt;p>成功后得到如下输出：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>Your Kubernetes control-plane has initialized successfully!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>To start using your cluster, you need to run the following as a regular user:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mkdir -p &lt;span style="color:#111">$HOME&lt;/span>/.kube
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sudo cp -i /etc/kubernetes/admin.conf &lt;span style="color:#111">$HOME&lt;/span>/.kube/config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sudo chown &lt;span style="color:#00a8c8">$(&lt;/span>id -u&lt;span style="color:#00a8c8">)&lt;/span>:&lt;span style="color:#00a8c8">$(&lt;/span>id -g&lt;span style="color:#00a8c8">)&lt;/span> &lt;span style="color:#111">$HOME&lt;/span>/.kube/config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Alternatively, &lt;span style="color:#00a8c8">if&lt;/span> you are the root user, you can run:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">export&lt;/span> &lt;span style="color:#111">KUBECONFIG&lt;/span>&lt;span style="color:#f92672">=&lt;/span>/etc/kubernetes/admin.conf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>You should now deploy a pod network to the cluster.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Run &lt;span style="color:#d88200">&amp;#34;kubectl apply -f [podnetwork].yaml&amp;#34;&lt;/span> with one of the options listed at:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> https://kubernetes.io/docs/concepts/cluster-administration/addons/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>You can now join any number of the control-plane node running the following &lt;span style="color:#111">command&lt;/span> on each as root:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kubeadm join 43.135.64.117:443 --token 3639bw.3f5i3dooplnd982i &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --discovery-token-ca-cert-hash sha256:e66819320b79ccab1ca6f570db82fd67d1d17c2f59e8091779a4c99a3c0f37c6 &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --control-plane --certificate-key 30c003d1c1d480f0c4c6df6bdba9d097b2e771013e11430bec4a4151afd730fc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>As a safeguard, uploaded-certs will be deleted in two hours&lt;span style="color:#111">;&lt;/span> If necessary, you can use
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">&amp;#34;kubeadm init phase upload-certs --upload-certs&amp;#34;&lt;/span> to reload certs afterward.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Then you can join any number of worker nodes by running the following on each as root:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubeadm join 43.135.64.117:443 --token 3639bw.3f5i3dooplnd982i &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --discovery-token-ca-cert-hash sha256:e66819320b79ccab1ca6f570db82fd67d1d17c2f59e8091779a4c99a3c0f37c6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>要重新上传证书并生成新的解密密钥，请在已加入集群节点的控制平面上使用以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubeadm token create --print-join-command
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h3 id="初始化其它-master">初始化其它 master&lt;/h3>
&lt;p>命令来自于前面初始化得到的输出：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubeadm join 43.135.64.117:443 --token 3639bw.3f5i3dooplnd982i &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --discovery-token-ca-cert-hash sha256:e66819320b79ccab1ca6f570db82fd67d1d17c2f59e8091779a4c99a3c0f37c6 &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --control-plane --certificate-key 30c003d1c1d480f0c4c6df6bdba9d097b2e771013e11430bec4a4151afd730fc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="创建计算节点">创建计算节点&lt;/h1>
&lt;h2 id="配置-worker-运行时">配置 worker 运行时&lt;/h2>
&lt;h3 id="查看默认-cgroup-驱动-1">查看默认 cgroup 驱动&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>stat -fc %T /sys/fs/cgroup/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="安装-containerd">安装 containerd&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>安装 containerd&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> ca-certificates &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> curl &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> gnupg &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> lsb-release
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo mkdir -m &lt;span style="color:#ae81ff">0755&lt;/span> -p /etc/apt/keyrings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg &lt;span style="color:#111">|&lt;/span> sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> &lt;span style="color:#d88200">&amp;#34;deb [arch=&lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>dpkg --print-architecture&lt;span style="color:#00a8c8">)&lt;/span>&lt;span style="color:#d88200"> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>lsb_release -cs&lt;span style="color:#00a8c8">)&lt;/span>&lt;span style="color:#d88200"> stable&amp;#34;&lt;/span> &lt;span style="color:#111">|&lt;/span> sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo chmod a+r /etc/apt/keyrings/docker.gpg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install containerd.io
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装 cni&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>wget https://github.com/containernetworking/plugins/releases/download/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo mkdir -p /opt/cni/bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.2.0.tgz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装 containerd&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>wget https://github.com/containerd/containerd/releases/download/v1.6.16/containerd-1.6.16-linux-amd64.tar.gz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo tar Cxzvf /usr/local/ containerd-1.6.16-linux-amd64.tar.gz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>配置 containerd&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>containerd config default &amp;gt; /etc/containerd/config.toml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>结合 &lt;code>runc&lt;/code> 使用 &lt;code>systemd&lt;/code> cgroup 驱动，在 &lt;code>/etc/containerd/config.toml&lt;/code> 中设置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-toml" data-lang="toml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">[&lt;/span>&lt;span style="color:#75af00">plugins&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#d88200">&amp;#34;io.containerd.grpc.v1.cri&amp;#34;&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">containerd&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">runtimes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">runc&lt;/span>&lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[&lt;/span>&lt;span style="color:#75af00">plugins&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#d88200">&amp;#34;io.containerd.grpc.v1.cri&amp;#34;&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">containerd&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">runtimes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">runc&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">options&lt;/span>&lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">SystemdCgroup&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装 CNI&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>wget https://github.com/containernetworking/plugins/releases/download/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo mkdir -p /opt/cni/bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.2.0.tgz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>配置 CNI&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo mkdir /etc/cni/net.d/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo cat &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF &amp;gt; /etc/cni/net.d/100-containerd-bridge.conflist
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">{
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;cniVersion&amp;#34;: &amp;#34;1.0.0&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;name&amp;#34;: &amp;#34;containerd&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;plugins&amp;#34;: [
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> {
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;type&amp;#34;: &amp;#34;bridge&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;bridge&amp;#34;: &amp;#34;cni0&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;isGateway&amp;#34;: true,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;ipMasq&amp;#34;: true,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;hairpinMode&amp;#34;: true,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;ipam&amp;#34;: {
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;type&amp;#34;: &amp;#34;host-local&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;routes&amp;#34;: [
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> { &amp;#34;dst&amp;#34;: &amp;#34;0.0.0.0/0&amp;#34; },
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> { &amp;#34;dst&amp;#34;: &amp;#34;::/0&amp;#34; }
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> ],
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &amp;#34;ranges&amp;#34;: [
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> [{ &amp;#34;subnet&amp;#34;: &amp;#34;10.85.0.0/16&amp;#34; }],
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> [{ &amp;#34;subnet&amp;#34;: &amp;#34;1100:200::/24&amp;#34; }]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> ]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> }
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> }
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> ]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>systemd 启动&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> containerd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl start containerd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h3 id="加入集群">加入集群&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo kubeadm join 43.135.64.117:443 --token 3639bw.3f5i3dooplnd982i &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --discovery-token-ca-cert-hash sha256:e66819320b79ccab1ca6f570db82fd67d1d17c2f59e8091779a4c99a3c0f37c6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>一文让你搞明白集群弹性伸缩是如何运作的</title><link>https://mydream.ink/posts/cloud-native/kubernetes/introduce-cluster-autoscaler/</link><pubDate>Mon, 06 Mar 2023 21:52:50 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/introduce-cluster-autoscaler/</guid><description>&lt;h1 id="功能简介">功能简介&lt;/h1>
&lt;p>&lt;strong>弹性伸缩&lt;/strong> 可以根据用户实际需求调整计算资源（算力）的多寡，可以说这是每个厂商必备的能力，比如&lt;a href="https://cloud.tencent.com/document/product/377">腾讯云 AS&lt;/a>、Amazon EC2 Auto Scaling、Azure Autoscale、Google MIG、阿里云 ESS 等等。&lt;strong>集群弹性伸缩&lt;/strong> 作用于 kubernetes 集群中，功能上与弹性伸缩保持一致，两者最大的差异是衡量资源需求量的标准。弹性伸缩常常以实际负载作为扩缩依据，而集群弹性伸缩是以资源分配占比作为依据，为 workload 分配资源往往需要我们对自身的业务具有相对合理的认识。&lt;/p>
&lt;p>CA 可以根据集群实际使用情况，为集群增加或者减少节点：&lt;/p>
&lt;ol>
&lt;li>当有 pod 由于资源不足而不可调度时，考虑扩容&lt;/li>
&lt;li>当有节点资源利用率不足时，考虑缩容&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>repo&lt;/strong>: &lt;a href="https://github.com/alphajc/autoscaler/tree/master/cluster-autoscaler">https://github.com/alphajc/autoscaler/tree/master/cluster-autoscaler&lt;/a>&lt;/li>
&lt;li>&lt;strong>FAQ&lt;/strong>: &lt;a href="https://github.com/alphajc/autoscaler/blob/master/cluster-autoscaler/FAQ.md">https://github.com/alphajc/autoscaler/blob/master/cluster-autoscaler/FAQ.md&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="与-hpahpc-的关系">与 HPA、HPC 的关系&lt;/h2>
&lt;p>在 kubernetes 中有关 HPA 的介绍可以移步&lt;a href="https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/">官方文档&lt;/a>，HPA 可以根据实际负载调整 workload 的副本数，即 Pod 数量，如果调升后计算资源不足就会导致 Pod Pending，调降后资源过剩就会导致节点闲置。尽量保证 Pending 的节点得到调度，闲置的节点得到释放，就是 CA(cluster-autoscaler 简称) 的作用。CA + HPA 与云厂商的弹性伸缩功能大致相当。&lt;/p>
&lt;p>&lt;img src="../images/hpa-ca.webp" alt="hpa、ca 协同">&lt;/p>
&lt;p>根据负载得到的资源需求往往具有一定的滞后性，触发扩容时或多或少都已经影响到了业务，所以云厂商提供的弹性伸缩一般还具有定时伸缩的能力。TKE 团队提供的 &lt;a href="https://cloud.tencent.com/document/product/457/56753">HPC&lt;/a> 可以定时调整 workload 的副本数，其与 CA 配合就可以达到集群节点定时伸缩的效果了。&lt;/p>
&lt;h2 id="使用须知">使用须知&lt;/h2>
&lt;ol>
&lt;li>CA 一般基于云厂商提供的弹性伸缩功能实现 NodeGroup，但其伸缩机制有冲突，故不可在集群中的启用原弹性机制&lt;/li>
&lt;li>开启集群弹性伸缩的集群中的 workload 务必指定 request&lt;/li>
&lt;li>NodeGroup 中配置的机型资源充足&lt;/li>
&lt;li>当单节点池配置了多可用区的子网时，含可用区亲和性调度的扩容会有异常&lt;/li>
&lt;li>不支持单节点池多规格机型&lt;/li>
&lt;/ol>
&lt;h1 id="实现逻辑">实现逻辑&lt;/h1>
&lt;p>CA 的代码主体是一个无限循环，通过一次次的扫描集群，根据集群当前状态决策和执行扩缩容。具体的循环间隔通过 &lt;code>--scan-interval&lt;/code> 参数控制，默认为 &lt;code>10s&lt;/code>，一张图带你理解扩缩容全流程：&lt;/p>
&lt;p>&lt;img src="../images/2023-03-13-22-00-52.png" alt="">&lt;/p>
&lt;ol>
&lt;li>集群快照&lt;code>ClusterSnapshot&lt;/code>中，会存储所有节点和非低优的 Pod&lt;/li>
&lt;li>生成加工出来的模板用于扩容，将在后续扩容专题中细讲&lt;/li>
&lt;li>更新集群状态的主要目的是：
&lt;ul>
&lt;li>找出已经触发扩容但还没有注册的节点&lt;/li>
&lt;li>为扩容超时节点对应的节点池设置 backoff&lt;/li>
&lt;li>查询创建失败的节点，设置对应节点池为 backoff&lt;/li>
&lt;li>找出当前已注册节点数不在可接受区间内的节点池&lt;/li>
&lt;li>清理 backoff&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>长时间没有注册上的节点会被删除，时间由 &lt;code>--max-node-provision-time&lt;/code> 指定，默认为 &lt;code>15min&lt;/code>&lt;/li>
&lt;li>只有当集群是健康时，CA 才会实际扩缩容，否则循环会直接返回&lt;/li>
&lt;li>删除在&lt;strong>更新集群状态&lt;/strong>时找到的创建失败的节点&lt;/li>
&lt;li>当已经注册的实例数长时间小于期望实例数时，会将节点池的期望实例数调整为已注册实例数，时间由 &lt;code>--max-node-provision-time&lt;/code> 指定，默认为 &lt;code>15min&lt;/code>&lt;/li>
&lt;li>过滤掉一些不需要触发扩容的 pod
&lt;ul>
&lt;li>不考虑优先级太低的 Pod，&lt;code>--expendable-pods-priority-cutoff&lt;/code> 指定，默认为 &lt;code>-10&lt;/code>&lt;/li>
&lt;li>过滤掉有 NominatedNodeName 属性的 Pod&lt;/li>
&lt;li>过滤掉可调度到已有节点和 upcoming 节点上的 Pod&lt;/li>
&lt;li>过滤掉创建时间过短的节点，时间由 &lt;code>--new-pod-scale-up-delay&lt;/code> 指定，默认为 &lt;code>0&lt;/code>，即不过滤&lt;/li>
&lt;li>过滤掉创建时间短于 2s 的 pod，或者短于 30s 的 gpu pod&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>扩容将在后续扩容专题中细讲&lt;/li>
&lt;li>缩容将在后续缩容专题中细讲&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>集群健康认定&lt;/strong>&lt;/p>
&lt;p>当集群同时满足下述两个条件时，集群会被认为是异常的，反之健康：&lt;/p>
&lt;ol>
&lt;li>未就绪的 Node 数大于启动参数中 &lt;code>—-ok-total-unready-count&lt;/code> 指定值（默认 3）&lt;/li>
&lt;li>未就绪的 Node 数占总 Node 数百分比大于 &lt;code>—-max-total-unready-percentage&lt;/code> 指定值（默认 45）&lt;/li>
&lt;/ol>
&lt;p>当集群不健康时，在会在 kube-system 下记录一个 ClusterUnhealthy 的事件。&lt;/p>
&lt;p>&lt;strong>扩容中节点（Upcoming Nodes）&lt;/strong>&lt;/p>
&lt;p>扩容中实例数 = 期望实例数 - (就绪实例数 + 未就绪实例数 + 长时间未注册的实例数)&lt;/p>
&lt;ul>
&lt;li>期望实例数：节点池当前期望的实例数&lt;/li>
&lt;li>就绪实例数：节点池在集群中已经就绪可用的实例数&lt;/li>
&lt;li>未就绪实例数：已经注册到集群但还不可用的实例数&lt;/li>
&lt;li>未注册实例数：通过 cloudprovider 可以查到，但是在集群中还查不到的实例数&lt;/li>
&lt;li>已删除的实例数：被打上 &lt;code>ToBeDeletedByClusterAutoscaler&lt;/code> 污点的实例数&lt;/li>
&lt;li>长时间未注册的实例数：存在时间超过 &lt;code>—-max-node-provision-time&lt;/code> 的未注册实例数&lt;/li>
&lt;/ul></description></item><item><title>Pyflink in Kubernetes</title><link>https://mydream.ink/posts/cloud-native/kubernetes/pyflink-in-kubernetes/</link><pubDate>Wed, 06 Jan 2021 15:00:43 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/pyflink-in-kubernetes/</guid><description>&lt;h2 id="pyflink-on-kubernetes">pyFlink on kubernetes&lt;/h2>
&lt;blockquote>
&lt;p>在 TKE 环境下&lt;/p>
&lt;/blockquote>
&lt;h3 id="准备工作">准备工作&lt;/h3>
&lt;p>为 flink 创建专用的命名空间、ServiceAccount，并设置好资源配额&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubectl create ns flink
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl -n flink create sa flink
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl -n flink create role flink --verb&lt;span style="color:#f92672">=&lt;/span>* --resource&lt;span style="color:#f92672">=&lt;/span>*.*
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl -n flink create rolebinding flink --role&lt;span style="color:#f92672">=&lt;/span>flink --serviceaccount&lt;span style="color:#f92672">=&lt;/span>flink:flink
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF &amp;gt; apiserver.crt # base 集群 apiserver 证书
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">-----BEGIN CERTIFICATE-----
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">cm5ldGVzMB4XDTIwMDMwNTA4MjYzOFoXDTMwMDMwMzA4MjYzOFowFTETMBEGA1UE
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMq/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">6ae6qIZbchBK7b0AaP2714K+tuxtGnl7p3yNwlCgVEnZjfytAr8cvkIzxCBH8xBc
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">vqV2vtDOy0r6HecbIPTkd0bGn6BXjqPp6XZY0ffFgyBhGv6/DD7x7aJIG+A8uSVk
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">/yTGcahGQ9WOQ6CDWDjZvC+K9zkeNqhtj1wm9P/hQcECAQkQbkIqLUF/P8SF4b8N
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">QlodYsrZIg9MDqgQDq744AoFPm/F6G2GfHs1DfesYMoSYQs6ECec0+N/Nf5Kx7XX
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">KW+ARAGEZxC3X7bJmxWr5JSRmmS46rmrN/MUDpMBaWUwtxubVxtIwt1FIFlFqCYz
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">MR9UtvZkTaL6oSd6Q18CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKUMA8GA1UdEwEB
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABXd9WbwOlhlqyFsr/2s9wgf86+/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">oo05gZhybHtuKHYvSwzoxAeyaMFGn4b7S2okcLoo3EwjmAN2Pup/qhTtiqM9xNfa
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">+GuN+NX4zjkra+T2NEywIykwhodaFiYB+r6xM0LIHANlKKT7kRtgzkq/s8ui6Wkt
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">b94T8BpE5U3f6YGz/4NtbqpYS6XVjIIjfP5MxoHRCh8H2LXmWs9A2RlmuZAwo0M2
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">FJjxcfIgvHrgCerPogVcXYT23BavOhMFlx7Jck2GAbDg0HYTabZuLNkj9T1//raL
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">aknXJxuLYHbZe6I1cbsIdXJjav5c83YgqGDKetcJdkQqMgk8lVWW7RIdLb0=
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">-----END CERTIFICATE-----
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl config --kubeconfig&lt;span style="color:#f92672">=&lt;/span>flink-kubeconfig set-cluster base --server&lt;span style="color:#f92672">=&lt;/span>https://cls-xxxxxxx.ccs.tencent-cloud.com --certificate-authority&lt;span style="color:#f92672">=&lt;/span>apiserver.crt --embed-certs&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl config --kubeconfig&lt;span style="color:#f92672">=&lt;/span>flink-kubeconfig set-credentials flink --token&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>kubectl -n flink get sa flink -o&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">jsonpath&lt;/span>&lt;span style="color:#f92672">={&lt;/span>.secrets&lt;span style="color:#f92672">[&lt;/span>0&lt;span style="color:#f92672">]&lt;/span>.name&lt;span style="color:#f92672">}&lt;/span> &lt;span style="color:#111">|&lt;/span> xargs kubectl -n flink get secrets -o&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">jsonpath&lt;/span>&lt;span style="color:#f92672">={&lt;/span>.data.token&lt;span style="color:#f92672">}&lt;/span> &lt;span style="color:#111">|&lt;/span> base64 -d&lt;span style="color:#00a8c8">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl config --kubeconfig&lt;span style="color:#f92672">=&lt;/span>flink-kubeconfig set-context default --user&lt;span style="color:#f92672">=&lt;/span>flink --cluster&lt;span style="color:#f92672">=&lt;/span>base --namespace&lt;span style="color:#f92672">=&lt;/span>flink
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF | kubectl create -f -
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">apiVersion: v1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">kind: ResourceQuota
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">metadata:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> name: flink-rq
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> namespace: flink
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">spec:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> hard:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> limits.cpu: &amp;#34;20&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> limits.memory: 40Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> requests.cpu: &amp;#34;10&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> requests.memory: 20Gi
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="配置-flink-环境">配置 flink 环境&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>登录工作服务器，将上述生成的文件拷贝到&lt;code>~/.kube/&lt;/code>目录下，并更名为&lt;code>config&lt;/code>，此外还需要满足以下依赖条件：&lt;/p>
&lt;p>－ 该服务器需与目标集群网络内网互通
－ 已安装&lt;code>kubectl&lt;/code>工具
－ 已安装 Java 版本 8 或 11（官方推荐）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>修改&lt;code>/etc/hosts&lt;/code>文件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo sed -i &lt;span style="color:#d88200">&amp;#39;$a 10.1.192.48 cls-xxxxxxx.ccs.tencent-cloud.com&amp;#39;&lt;/span> /etc/hosts
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装 Flink 工具包&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>wget https://www.apache.org/dyn/closer.lua/flink/flink-1.12.0/flink-1.12.0-bin-scala_2.12.tgz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tar zxf flink-1.12.0-bin-scala_2.12.tgz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">export&lt;/span> &lt;span style="color:#111">PATH&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">$PATH&lt;/span>:&lt;span style="color:#d88200">`&lt;/span>&lt;span style="color:#111">pwd&lt;/span>&lt;span style="color:#d88200">`&lt;/span>/flink-1.12.0/bin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h3 id="部署应用">部署应用&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>构建 docker base 镜像&lt;/p>
&lt;blockquote>
&lt;p>不依赖 flink 环境&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>git clone https://github.com/apache/flink-docker.git
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> flink-docker/1.12/scala_2.12-java11-debian
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker build -t alphajc/flink:1.12-scala_2.12-java11 .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker tag alphajc/flink:1.12-scala_2.12-java11 alphajc/flink:1.12-scala_2.12
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker tag alphajc/flink:1.12-scala_2.12-java11 alphajc/flink:1.12-java11
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker tag alphajc/flink:1.12-scala_2.12-java11 alphajc/flink:1.12
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker push -a alphajc/flink &lt;span style="color:#75715e"># 需要先使用 docker login 登录 docker hub，alphajc 为我的账号&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上述镜像现在可直接使用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>制作 pyFlink 1.12 的 docker 镜像&lt;/p>
&lt;blockquote>
&lt;p>不依赖 flink 环境&lt;/p>
&lt;/blockquote>
&lt;p>创建下述 Dockerfile 文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">FROM&lt;/span>&lt;span style="color:#d88200"> alphajc/flink:1.12&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># install python3 and pip3&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">RUN&lt;/span> apt-get update -y &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>apt-get install -y python3.7 python3-pip python3.7-dev &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> rm -rf /var/lib/apt/lists/*&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">RUN&lt;/span> ln -s /usr/bin/python3 /usr/bin/python&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># install Python Flink&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">RUN&lt;/span> pip3 config &lt;span style="color:#111">set&lt;/span> global.index-url https://mirrors.aliyun.com/pypi/simple/ &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> pip3 install apache-flink&lt;span style="color:#f92672">==&lt;/span>1.12.0&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>构建并提交：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>docker build -t alphajc/pyflink:1.12 .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker push alphajc/pyflink:1.12
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>运行一个 python 的批处理程序&lt;code>word_count&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>依赖 flink 环境&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>flink run-application &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --target kubernetes-application &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --parallelism &lt;span style="color:#ae81ff">8&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dkubernetes.cluster-id&lt;span style="color:#f92672">=&lt;/span>word-count &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dtaskmanager.memory.process.size&lt;span style="color:#f92672">=&lt;/span>4096m &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dkubernetes.namespace&lt;span style="color:#f92672">=&lt;/span>flink &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dkubernetes.jobmanager.service-account&lt;span style="color:#f92672">=&lt;/span>flink &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dkubernetes.taskmanager.cpu&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dtaskmanager.numberOfTaskSlots&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> -Dkubernetes.container.image&lt;span style="color:#f92672">=&lt;/span>alphajc/pyflink:1.12 &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --pyModule word_count &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --pyFiles /opt/flink/examples/python/table/batch/word_count.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这是一个批处理程序，运行完就没了，可以使用&lt;code>kubectl get po -w&lt;/code>持续观察 Pod 的变化：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/word_count_pods_watch.png" alt="">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>自定义镜像创建一个需要连接 mysql 和 kafka 的流处理应用(test)&lt;/p>
&lt;blockquote>
&lt;p>不依赖 flink 环境&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>git clone ssh://git@github.com:xxxx/flink_test.git &lt;span style="color:#75715e"># 克隆 flink 测试库&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> flink_test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>创建下述的 Dockerfile 文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">FROM&lt;/span>&lt;span style="color:#d88200"> alphajc/pyflink:1.12&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">ADD&lt;/span> --chown&lt;span style="color:#f92672">=&lt;/span>9999:9999 https://repo.maven.apache.org/maven2/org/apache/flink/flink-sql-connector-kafka_2.11/1.12.0/flink-sql-connector-kafka_2.11-1.12.0.jar /opt/flink/lib/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">ADD&lt;/span> --chown&lt;span style="color:#f92672">=&lt;/span>9999:9999 https://repo.maven.apache.org/maven2/org/apache/flink/flink-connector-jdbc_2.11/1.12.0/flink-connector-jdbc_2.11-1.12.0.jar /opt/flink/lib/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">ADD&lt;/span> --chown&lt;span style="color:#f92672">=&lt;/span>9999:9999 https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/8.0.22/mysql-connector-java-8.0.22.jar /opt/flink/lib/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#00a8c8">ADD&lt;/span> --chown&lt;span style="color:#f92672">=&lt;/span>9999:9999 examples_112 /app/flink_test&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>构建发布镜像：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>docker build -t alphajc/flink_test .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker push alphajc/flink_test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>运行 test 应用&lt;/p>
&lt;blockquote>
&lt;p>依赖 flink 环境&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>flink run-application &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--target kubernetes-application &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--parallelism &lt;span style="color:#ae81ff">8&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.cluster-id&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">test&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.container.image&lt;span style="color:#f92672">=&lt;/span>alphajc/flink_test &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.namespace&lt;span style="color:#f92672">=&lt;/span>flink &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.jobmanager.service-account&lt;span style="color:#f92672">=&lt;/span>flink &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dtaskmanager.memory.process.size&lt;span style="color:#f92672">=&lt;/span>4096m &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.taskmanager.cpu&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dtaskmanager.numberOfTaskSlots&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.container.image.pull-secrets&lt;span style="color:#f92672">=&lt;/span>qcloudregistrykey &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dexecution.attached&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">true&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>-Dkubernetes.container.image.pull-policy&lt;span style="color:#f92672">=&lt;/span>Always &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--pyModule &lt;span style="color:#111">test&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--pyFiles /app/flink_test/test.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h4 id="一些提示一些坑">一些提示＆一些坑&lt;/h4>
&lt;ol>
&lt;li>jobmanager 的日志中可以看到 jar 包有没使用成功&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>2021-01-05 02:00:00,510 INFO org.apache.flink.runtime.entrypoint.ClusterEntrypoint [] - Classpath: /opt/flink/lib/flink-csv-1.12.0.jar:/opt/flink/lib/flink-json-1.12.0.jar:/opt/flink/lib/flink-shaded-zookeeper-3.4.14.jar:/opt/flink/lib/flink-table-blink_2.12-1.12.0.jar:/opt/flink/lib/flink-table_2.12-1.12.0.jar:/opt/flink/lib/log4j-1.2-api-2.12.1.jar:/opt/flink/lib/log4j-api-2.12.1.jar:/opt/flink/lib/log4j-core-2.12.1.jar:/opt/flink/lib/log4j-slf4j-impl-2.12.1.jar:/opt/flink/lib/flink-dist_2.12-1.12.0.jar:::
&lt;/code>&lt;/pre>&lt;ol start="2">
&lt;li>缺少 kafka connector 插件&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>Caused by: org.apache.flink.table.api.ValidationException: Could not find any factory for identifier &amp;#39;kafka&amp;#39; that implements &amp;#39;org.apache.flink.table.factories.DynamicTableFactory&amp;#39; in the classpath.
&lt;/code>&lt;/pre>&lt;ol start="3">
&lt;li>缺少 jdbc 插件&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>Caused by: org.apache.flink.table.api.ValidationException: Could not find any factory for identifier &amp;#39;jdbc&amp;#39; that implements &amp;#39;org.apache.flink.table.factories.DynamicTableFactory&amp;#39; in the classpath.
&lt;/code>&lt;/pre>&lt;ol start="4">
&lt;li>缺少 mysql 插件&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>java.io.IOException: unable to open JDBC writer
at org.apache.flink.connector.jdbc.internal.AbstractJdbcOutputFormat.open(AbstractJdbcOutputFormat.java:61) ~[flink-connector-jdbc_2.11-1.12.0.jar:1.12.0]
at org.apache.flink.connector.jdbc.internal.JdbcBatchingOutputFormat.open(JdbcBatchingOutputFormat.java:114) ~[flink-connector-jdbc_2.11-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.api.functions.sink.OutputFormatSinkFunction.open(OutputFormatSinkFunction.java:65) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.api.common.functions.util.FunctionUtils.openFunction(FunctionUtils.java:36) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.api.operators.AbstractUdfStreamOperator.open(AbstractUdfStreamOperator.java:102) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.table.runtime.operators.sink.SinkOperator.open(SinkOperator.java:63) ~[flink-table-blink_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.runtime.tasks.OperatorChain.initializeStateAndOpenOperators(OperatorChain.java:401) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.runtime.tasks.StreamTask.lambda$beforeInvoke$2(StreamTask.java:507) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.runtime.tasks.StreamTaskActionExecutor$1.runThrowing(StreamTaskActionExecutor.java:47) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.runtime.tasks.StreamTask.beforeInvoke(StreamTask.java:501) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:531) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:722) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at org.apache.flink.runtime.taskmanager.Task.run(Task.java:547) ~[flink-dist_2.12-1.12.0.jar:1.12.0]
at java.lang.Thread.run(Unknown Source) ~[?:?]
Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
at jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) ~[?:?]
at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source) ~[?:?]
at java.lang.ClassLoader.loadClass(Unknown Source) ~[?:?]
at java.lang.Class.forName0(Native Method) ~[?:?]
at java.lang.Class.forName(Unknown Source) ~[?:?]
at org.apache.flink.connector.jdbc.internal.connection.SimpleJdbcConnectionProvider.getConnection(SimpleJdbcConnectionProvider.java:52) ~[flink-connector-jdbc_2.11-1.12.0.jar:1.12.0]
at org.apache.flink.connector.jdbc.internal.AbstractJdbcOutputFormat.establishConnection(AbstractJdbcOutputFormat.java:66) ~[flink-connector-jdbc_2.11-1.12.0.jar:1.12.0]
at org.apache.flink.connector.jdbc.internal.AbstractJdbcOutputFormat.open(AbstractJdbcOutputFormat.java:59) ~[flink-connector-jdbc_2.11-1.12.0.jar:1.12.0]
... 13 more
&lt;/code>&lt;/pre></description></item><item><title>记一次 IPVS 引起的 kubernetes 服务异常</title><link>https://mydream.ink/posts/cloud-native/kubernetes/record-a-kubernetes-service-exception-caused-by-ipvs/</link><pubDate>Sun, 06 Dec 2020 17:14:02 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/record-a-kubernetes-service-exception-caused-by-ipvs/</guid><description>&lt;h2 id="问题描述">问题描述&lt;/h2>
&lt;p>在我们的环境中，业务 A 会访问业务 B（也即 B 是 A 的上游服务），当 B 发版时，A 的请求偶尔会出现 timeout。现象看起来是流量路由到了已经不能提供服务的老的 pod 中。&lt;/p>
&lt;h2 id="问题处理">问题处理&lt;/h2>
&lt;h3 id="排查点一">排查点一&lt;/h3>
&lt;p>当 pod 下线时，endpoint controller 未及时将 pod 从 endpoint 中取出，导致流量进入了旧的 pod。&lt;/p>
&lt;p>由于集群 master 是托管部署的，所以我们没有办法查看相关日志和负载，求助于腾讯云的同时，自己这边也在排查。排查分两步进行，监控服务 B endpoint 的变化，同时滚动重启该服务。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;em>监控 ep 变化&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>watch -n &lt;span style="color:#ae81ff">1&lt;/span> kubectl -n production describe ep wk-dispatcher
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;em>重启服务&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubectl -n production rollout restart deployment wk-dispatcher
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>通过排查发现，跟 endpoint controller 并没有关系，因为 ep 变化很及时，加上至少 30 秒的优雅终止时间，基本可以确定在服务结束前，pod 是从 ep 中移除了的。&lt;/p>
&lt;p>那不是这个原因，又会是什么原因呢？既然 ep 已经及时卸载了 pod，那是不是 kube-proxy 没有及时将相关规则改掉呢？&lt;/p>
&lt;h3 id="排查点二">排查点二&lt;/h3>
&lt;p>kube-proxy 没有及时将相关规则改掉&lt;/p>
&lt;p>虽然按照我的理解，这么长的优雅终止时间，怎么着规则都应该同步了才对，那可能同步时间加上请求处理时间超过了优雅终止时间，如果这样也还是有可能的，无论如何观察一下总是好的。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;em>查看 ipvs 规则变化&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">CLUSTERIP&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#d88200">`&lt;/span>kubectl -n production get svc wk-dispatcher -o&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">jsonpath&lt;/span>&lt;span style="color:#f92672">={&lt;/span>.spec.clusterIP&lt;span style="color:#f92672">}&lt;/span>&lt;span style="color:#d88200">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">PORT&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#d88200">`&lt;/span>kubectl -n production get svc wk-dispatcher -o&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">jsonpath&lt;/span>&lt;span style="color:#f92672">={&lt;/span>.spec.ports&lt;span style="color:#f92672">[&lt;/span>0&lt;span style="color:#f92672">]&lt;/span>.port&lt;span style="color:#f92672">}&lt;/span>&lt;span style="color:#d88200">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>watch -n &lt;span style="color:#ae81ff">1&lt;/span> ipvsadm -Ln -t &lt;span style="color:#111">$CLUSTERIP&lt;/span>:&lt;span style="color:#111">$PORT&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>同时也需要重启服务&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubectl -n production rollout restart deployment wk-dispatcher
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>这时候发现 ipvs 规则变化也很快（随着一些新的 rs 加入，老的 rs 的权重会变为0），虽然权重很快变为 0，但是老的 rs 并不会立即消失，直到 &lt;code>ActiveConn &lt;/code>＋ &lt;code>InActConn&lt;/code> = 0，老的 rs 才会真正移除。&lt;/p>
&lt;p>既然 endpoint 的变化和 ipvs 规则同步都很快，那问题极有可能出现在权重变为 0 到真正消失的之段时间。&lt;/p>
&lt;h3 id="排查点三">排查点三&lt;/h3>
&lt;p>流量路由到了权重为 0 的 rs&lt;/p>
&lt;p>带着这样的疑惑，我们找到了 TKE 团队。在 TKE 的指导下，我们查询了一下&lt;code>net.ipv4.vs.conn_reuse_mode&lt;/code>，发现其值为 0。随后他们给了我们个&lt;a href="https://github.com/kubernetes/kubernetes/issues/81775">关键 issue&lt;/a>。&lt;/p>
&lt;p>ipvs 相关内核参数的说明如下：&lt;/p>
&lt;blockquote>
&lt;p>conn_reuse_mode - INTEGER&lt;/p>
&lt;p>1 - default&lt;/p>
&lt;p>Controls how ipvs will deal with connections that are detected port reuse. It is a bitmap, with the values being:&lt;/p>
&lt;p>0: disable any special handling on port reuse. The new connection will be delivered to the same real server that was servicing the previous connection. This will effectively disable expire_nodest_conn.&lt;br>
禁用对端口重用的任何特殊处理。 新连接将被传递到为先前连接提供服务的同一台真实服务器。 这将有效地禁用expire_nodest_conn。&lt;/p>
&lt;p>bit 1: enable rescheduling of new connections when it is safe. That is, whenever expire_nodest_conn and for TCP sockets, when the connection is in TIME_WAIT state (which is only possible if you use NAT mode).&lt;br>
NAT 模式下，连接处于 TIME_WAIT 状态时，新的连接请求将会重新调度而不再复用。&lt;/p>
&lt;p>bit 2: it is bit 1 plus, for TCP connections, when connections are in FIN_WAIT state, as this is the last state seen by load balancer in Direct Routing mode. This bit helps on adding new real servers to a very busy cluster.&lt;/p>
&lt;p>expire_nodest_conn - BOOLEAN&lt;/p>
&lt;p>0 - disabled (default)&lt;br>
not 0 - enabled&lt;/p>
&lt;p>The default value is 0, the load balancer will silently drop packets when its destination server is not available. It may be useful, when user-space monitoring program deletes the destination server (because of server overload or wrong detection) and add back the server later, and the connections to the server can continue.&lt;/p>
&lt;p>If this feature is enabled, the load balancer will expire the connection immediately when a packet arrives and its destination server is not available, then the client program will be notified that the connection is closed. This is equivalent to the feature some people requires to flush connections when its destination is not available.&lt;/p>
&lt;/blockquote>
&lt;p>有关 ipvs 超时时间的说明：&lt;/p>
&lt;blockquote>
&lt;p>ipvs default timeouts (900s for TCP, 120s for TCPFIN and 300s for UDP) are very high, especially in conjonction with conn_reuse_mode=0 (increased chances of bad port reuse). We can definitely decrease them but it&amp;rsquo;s not perfect.&lt;br>
The better option would be to set weight to 0 on backend removal from endpoint and delete the realserver when the pod is completely deleted, but this would require modifying the endpoint controller and the associated API.&lt;/p>
&lt;/blockquote>
&lt;h2 id="原因分析">原因分析&lt;/h2>
&lt;p>我们 kubernetes 节点机&lt;code>sys.net.ipv4.vs.conn_reuse_mode&lt;/code>值为 0，ipvs 中，系统使用&lt;code>conntrack&lt;/code>跟踪连接，&lt;code>conntrack&lt;/code>使用三元组（即源IP、源端口、协议）定位具体连接，当客户端产生大量短连接时，其本身可能与之前的端口复用，而 TCP 在&lt;code>conntrack&lt;/code>中的超时时间达 900s，也就是说&lt;code>conntrack&lt;/code>中记录的连接可能存在很长时间，客户端过来的新连接极有可能复用老连接，此时已经 TIME_WAIT
的老连接无法响应请求，从而导致超时。可通过设置&lt;code>sys.net.ipv4.vs.conn_reuse_mode&lt;/code>的值为 1 来解决，但这有可能引入一个&lt;a href="https://marc.info/?t=151683118100004&amp;amp;r=1&amp;amp;w=2">一秒延时的问题&lt;/a>。&lt;/p>
&lt;h2 id="最终解决">最终解决&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>一秒延时问题&lt;/strong>:&lt;/p>
&lt;p>当&lt;code>sys.net.ipv4.vs.conn_reuse_mode&lt;/code>的值为 1 时，SYN 包到达&lt;code>conntrack&lt;/code>时发现该连接处于 TIME_WAIT 状态，将会直接丢弃包，直至超时重发，这个时间大概一秒，打了补丁后包不会被丢弃而是在新进程中新建连接。&lt;/p>
&lt;/blockquote>
&lt;p>将节点的操作系统升级到打了&lt;a href="https://lkml.org/lkml/2020/6/16/254">补丁&lt;/a>（解决一秒延时问题）的版本，同时将&lt;code>sys.net.ipv4.vs.conn_reuse_mode&lt;/code>的值调整为 1。&lt;/p></description></item><item><title>使用LDAP的各种客户端进行统一身份认证</title><link>https://mydream.ink/posts/tools/ldap-clients-in-everywhere/</link><pubDate>Sat, 30 Nov 2019 14:20:59 +0800</pubDate><guid>https://mydream.ink/posts/tools/ldap-clients-in-everywhere/</guid><description>&lt;p>本文主要讲客户端的配置及部署，不含服务器，这里的一切都是以服务器端正确配置为前提的，有关内容后续会一步步完善，将包括 Linux 登录、gitlab、sonarqube、habor、nginx、pypiserver、verdaccio 等等。&lt;/p>
&lt;h2 id="linux-篇">Linux 篇&lt;/h2>
&lt;h3 id="前提">前提&lt;/h3>
&lt;ul>
&lt;li>一个或一组完备的 LDAP 服务端&lt;/li>
&lt;/ul>
&lt;h3 id="步骤">步骤&lt;/h3>
&lt;p>在 linux 上配置 LDAP 认证其实有很多种方式，这里我仅挑一种我认为最简单的方式。以 CentOS 为例:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>安装依赖&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>yum install libselinux-python nss-pam-ldapd -y
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>客户端配置&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>authconfig --enableshadow &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--disablesssd &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--disablesssdauth &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--enableldap &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--enableldapauth &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--ldapserver&lt;span style="color:#f92672">=&lt;/span>LDAP_SERVER &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--ldapbasedn&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#d88200">&amp;#34;BASE_DN&amp;#34;&lt;/span> &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--enableldaptls &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--enablecache &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--enablelocauthorize &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--enablemkhomedir &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--updateall
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注：将 LDAP_SERVER 替换为真实的地址，如果需要配置多个，则以半角逗号(,)隔开，确保 BASE_DN 为祖先结点，如我们的用户信息存在于&lt;code>ou=People,dc=example,dc=com&lt;/code>下，用户分组信息位于&lt;code>ou=Group,dc=example,dc=com&lt;/code>下，仔细观察会发现我们需要的认证信息都位于&lt;code>dc=example,dc=com&lt;/code>下，所以&lt;code>dc=example,dc=com&lt;/code>作为 BASE_DN 是个不错的选择。&lt;code>--enableldaptls&lt;/code>视自己的服务端是否配了证书而定。&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>单独配置 sudo&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF &amp;gt;&amp;gt; /etc/sudo-ldap.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">uri &amp;lt;LDAP_SERVER&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">sudoers_base &amp;lt;SUDOER_BASE&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>默认安装的 LDAP Server 中，没有 sudoer 的 schema，需要手动导入，真实配置了&lt;code>ou=SUDOer,dc=example,dc=com&lt;/code>才会有效，并将&amp;lt;SUDOER_BASE&amp;gt;替换为&lt;code>ou=SUDOer,dc=example,dc=com&lt;/code>。&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>配置命名交换文件&lt;/p>
&lt;p>在文件中加入或者修改这么一行：&lt;/p>
&lt;pre tabindex="0">&lt;code>sudoers: files ldap
&lt;/code>&lt;/pre>&lt;p>以确保在使用 sudo 权限的时候，ldap 会成为其验证来源&lt;/p>
&lt;/li>
&lt;li>
&lt;p>启动 nslcd&lt;/p>
&lt;ul>
&lt;li>
&lt;p>CentOS6&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>service nslcd start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chkconfig nslcd --add
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chkconfig nslcd on --level 2,3,4,5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>CentOS7&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> nslcd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl start nslcd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol></description></item><item><title>使用 cert-manager 自动管理 tls 证书</title><link>https://mydream.ink/posts/cloud-native/kubernetes/use-cert-manager-in-kubernetes/</link><pubDate>Sat, 09 Nov 2019 08:49:39 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/use-cert-manager-in-kubernetes/</guid><description>&lt;p>http 协议是一个全明文传输的协议，不加任何处理的 http 报文将在因特网中裸奔，只要有人对监听你的报文，你所有隐私将被人一览无余。Let’s Encrypt 是一家免费、开放、自动化的证书颁发机构（CA），为公众的利益而运行。它是一项由 &lt;a href="https://www.abetterinternet.org/">Internet Security Research Group（ISRG）&lt;/a>提供的服务。本着想要创建一个更安全，更尊重隐私的 Web 环境的初衷，Let’s Encrypt 以尽可能对用户友好的方式免费提供为网站启用 HTTPS（SSL/TLS）所需的数字证书。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/cloud-native/http_to_https-1.jpg" alt="http_to_https">&lt;/p>
&lt;p>但是 Let’s Encrypt 颁发的证书有个特点，有效期只有三个月。这意味着我们需要频繁续期证书，倘若手动续期，无疑会增加运维成本。有什么方式可以为我们自动颁发证书吗？当然有！&lt;a href="https://tools.ietf.org/html/rfc8555">ACME&lt;/a> 无疑就是我们的最佳选择。&lt;/p>
&lt;p>有一款很好用的 ACME 客户端叫 &lt;a href="https://certbot.eff.org/about/">Certbot&lt;/a>，可以帮助自动颁发、续期证书。在 kubernetes 的生态中有没有什么好的工具能帮助我们非常方便地使用证书么？当然有，那就是本期主题——&lt;a href="https://docs.cert-manager.io/en/latest/">&lt;strong>cert-manager&lt;/strong>&lt;/a>。&lt;/p>
&lt;p>cert-manager 是本地 Kubernetes 证书管理控制器。它可以帮助从各种来源颁发证书，例如 Let&amp;rsquo;s Encrypt，HashiCorp Vault，Venafi，简单的签名密钥对或自签名。它将确保证书有效并且是最新的，并在到期前尝试在配置的时间续订证书。&lt;/p>
&lt;p>原理比较简单，本文就不讲了，主要谈谈来安装和使用。&lt;/p>
&lt;h2 id="安装">安装&lt;/h2>
&lt;h3 id="yaml-安装">YAML 安装&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>创建 namespace&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl create namespace cert-manager
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装 crd 和 cert-manager&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>值得说明的是：在 kubernetes 1.15 以前的版本需要在执行&lt;code>kubectl apply&lt;/code>时添加一个参数&lt;code>--validate=false&lt;/code>，否则在创建 CustomResourceDefinition 资源时会收到与 x-kubernetes-preserve-unknown-fields 字段有关的验证错误。&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;/ol>
&lt;h3 id="helm-安装">helm 安装&lt;/h3>
&lt;p>依次执行下述命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install the CustomResourceDefinition resources separately&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl apply --validate&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#111">false&lt;/span> -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create the namespace for cert-manager&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl create namespace cert-manager
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Add the Jetstack Helm repository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo add jetstack https://charts.jetstack.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Update your local Helm chart repository cache&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install the cert-manager Helm chart&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm install &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --name cert-manager &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --namespace cert-manager &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> --version v0.11.0 &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> jetstack/cert-manager
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="确认安装">确认安装&lt;/h3>
&lt;pre tabindex="0">&lt;code>kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-5c6866597-zw7kh 1/1 Running 0 2m
cert-manager-cainjector-577f6d9fd7-tr77l 1/1 Running 0 2m
cert-manager-webhook-787858fcdb-nlzsq 1/1 Running 0 2m
&lt;/code>&lt;/pre>&lt;h2 id="使用">使用&lt;/h2>
&lt;h3 id="创建-issuer">创建 issuer&lt;/h3>
&lt;p>在建好 cert-manager 后，我们需要继续创建 issuer 才能颁发证书。有两种 issuer 可供我们选择：ClusterIssuer 和 Issuer，两都配置方式都是一样的，所不同的是作用域。ClusterIssuer，顾名思义是在集群中使用，作用域为所有命名空间，相对应的 Issuer 只作用于所在的命名空间。一般用 ClusterIssuer 比较方便。这里也通过 ClusterIssuer 举例。&lt;/p>
&lt;p>证书的验证方式也有两种，一个是 http-01，另一个是 dns-01，由于 dns-01 需要域名解析服务商的支持，所以，在这里就没有使用了。有关这两种方式的详细说明参照 &lt;a href="https://letsencrypt.org/zh-cn/docs/challenge-types/">https://letsencrypt.org/zh-cn/docs/challenge-types/&lt;/a>。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>创建一个名叫&lt;code>letsencrypt-staging.yaml&lt;/code>的文件，内容是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">cert-manager.io/v1alpha2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">ClusterIssuer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">letsencrypt-staging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">acme&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># You must replace this email address with your own.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Let&amp;#39;s Encrypt will use this to contact you about expiring&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># certificates, and issues related to your account.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">email&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">user@example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">server&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">https://acme-staging-v02.api.letsencrypt.org/directory&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">privateKeySecretRef&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Secret resource used to store the account&amp;#39;s private key.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">example-issuer-account-key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Add a single challenge solver, HTTP01 using nginx&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">solvers&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">http01&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ingress&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">class&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">nginx&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>通过&lt;code>kubectl&lt;/code>安装：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl apply -f letsencrypt-staging.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>确保创建成功&lt;/p>
&lt;pre tabindex="0">&lt;code>kubectl describe clusterissuer letsencrypt-staging
...
Status:
Acme:
Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/7571319
Conditions:
Last Transition Time: 2019-01-30T14:52:03Z
Message: The ACME account was registered with the ACME server
Reason: ACMEAccountRegistered
Status: True
Type: Ready
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>Let&amp;rsquo;s Encrypt 不支持通过 HTTP-01 的方式颁发通配符证书。要颁发通配符证书，必须使用DNS-01。&lt;/p>
&lt;/blockquote>
&lt;h3 id="单独颁发">单独颁发&lt;/h3>
&lt;p>单独颁发证书，其实是一种手动的方式，一般不这么使用，但这却能加深我们对这整套流程的理解。颁发证书即创建并处理 Certificate 资源的过程。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>有如下&lt;code>certificate-example.yaml&lt;/code>文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">cert-manager.io/v1alpha2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">Certificate&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">acme-crt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">secretName&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">acme-crt-secret&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">dnsNames&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">foo.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">issuerRef&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">letsencrypt-stagging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">ClusterIssuer&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>使用&lt;code>kubectl&lt;/code>创建 Certificate：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl apply -f certificate-example.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注：这前提是域名得配置好了&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>静待 1-2 分钟：&lt;/p>
&lt;p>执行下述命令，静待&lt;code>READY&lt;/code>值变成&lt;code>True&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl get certificate acme-crt -w
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>错误排查：&lt;/p>
&lt;p>如果整个颁发过程出现问题导致证书颁发失败，可以利用&lt;code>kubectl describe&lt;/code>查看对应的 order 进行排查。&lt;code>order&lt;/code>是一个订单，记录了这笔交易（虽然我们并未花钱），这可以告诉我们失败的原因。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubcectl describe order acme-crt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h3 id="指定-issuer-颁发">指定 issuer 颁发&lt;/h3>
&lt;p>更多的时候我们不需要手动创建 Certificate，只需要在创建 Ingress 时作好对应的注释，cert-manager 就将自动为我们创建。对应的 yaml 文件大致是长这样的：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">extensions/v1beta1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">Ingress&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">annotations&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">acme.cert-manager.io/http01-edit-in-place&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">cert-manager.io/cluster-issuer&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">letsencrypt-stagging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kubernetes.io/ingress.class&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">nginx&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">example&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">rules&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">host&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">foo.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">http&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">paths&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">backend&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">serviceName&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">example&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">servicePort&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">9000&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">path&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tls&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">hosts&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">foo.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">secretName&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">acme-crt-secret&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这将自动创建一个与上一步骤中相同的 Certificate。&lt;/p>
&lt;blockquote>
&lt;p>注：&lt;code>acme.cert-manager.io/http01-edit-in-place: &amp;quot;true&amp;quot;&lt;/code>这明 http-01 验证将就在这个 Ingress 中进行而不会另行创建。&lt;/p>
&lt;/blockquote>
&lt;h3 id="配置默认-issuer">配置默认 issuer&lt;/h3>
&lt;p>在 cert-manager 的启动命令中加上这么两句：&lt;/p>
&lt;pre tabindex="0">&lt;code>--set ingressShim.defaultIssuerName=letsencrypt-stagging \
--set ingressShim.defaultIssuerKind=ClusterIssuer
&lt;/code>&lt;/pre>&lt;p>这就为 cert-manager 设置了一个默认的 Issuer 了，设置成功后，我们就可以把 Ingress 资源中的&lt;code>cert-manager.io/cluster-issuer: letsencrypt-stagging&lt;/code>改成&lt;code>kubernetes.io/tls-acme: &amp;quot;true&amp;quot;&lt;/code>效果是完全一样的。&lt;/p></description></item><item><title>自动化运维之自动构建</title><link>https://mydream.ink/posts/devops/sonarqube-and-gitlab/</link><pubDate>Sat, 09 Nov 2019 08:32:31 +0800</pubDate><guid>https://mydream.ink/posts/devops/sonarqube-and-gitlab/</guid><description>&lt;h2 id="版本说明">版本说明&lt;/h2>
&lt;ul>
&lt;li>gitlab: 11.4.5&lt;/li>
&lt;li>sonarqube: 8.0-community-beta&lt;/li>
&lt;li>helm: v3.0.0-rc.2&lt;/li>
&lt;li>&lt;a href="https://github.com/gabrie-allaigre/sonar-gitlab-plugin">sonar-gitlab-plugin&lt;/a>: &lt;a href="https://github.com/gabrie-allaigre/sonar-gitlab-plugin/releases/download/4.1.0-SNAPSHOT/sonar-gitlab-plugin-4.1.0-SNAPSHOT.jar">4.1.0-SNAPSHOT&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/helm/charts/tree/master/stable/sonarqube">chart&lt;/a>: 3.2.2&lt;/li>
&lt;/ul>
&lt;h2 id="预处理">预处理&lt;/h2>
&lt;ul>
&lt;li>可用的 ingress controller&lt;/li>
&lt;li>域名：确保对应的域名&lt;code>sonar.example.com&lt;/code> 能够访问到 ingress&lt;/li>
&lt;li>已经有 gitlab，并且配置好了一个全局可用的 Kubernetes 环境的 runner&lt;/li>
&lt;/ul>
&lt;h2 id="安装-sonarqube">安装 sonarqube&lt;/h2>
&lt;p>没有已经存在的数据库用于相关数据存取，所以在安装 sonarqube 的同时，还将根据依赖安装 postgresql。&lt;/p>
&lt;h3 id="自定义变量">自定义变量&lt;/h3>
&lt;p>进入&lt;code>stable/sonarqube&lt;/code>，创建 custom.yaml 文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">image&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tag&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">8.0&lt;/span>-&lt;span style="color:#ae81ff">community-beta&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">ingress&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">hosts&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">sonar.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">annotations&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kubernetes.io/ingress.class&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">nginx&lt;/span> &lt;span style="color:#75715e"># 选择指定的 ingress controller&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">cert-manager.io/cluster-issuer&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">letsencrypt-stagging&lt;/span> &lt;span style="color:#75715e"># 使用提前创建好的可用的 Issue/ClusterIssue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">acme.cert-manager.io/http01-edit-in-place&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;true&amp;#34;&lt;/span> &lt;span style="color:#75715e"># 颁发证书时依托原原有的 ingress 而不新建&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tls&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">hosts&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">sonar.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">secretName&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">sonar-tls&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">persistence&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">accessMode&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">ReadWriteOnce&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">size&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">10Gi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">plugins&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">install&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#75715e"># 预装 sonar-gitlab-plugin 插件&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#d88200">&amp;#34;https://github.com/gabrie-allaigre/sonar-gitlab-plugin/releases/download/4.1.0-SNAPSHOT/sonar-gitlab-plugin-4.1.0-SNAPSHOT.jar&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">postgresql&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">postgresUser&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;sonar&amp;#34;&lt;/span> &lt;span style="color:#75715e"># 供 sonarqube 的 JDBC 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">postgresqlUsername&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;sonar&amp;#34;&lt;/span> &lt;span style="color:#75715e"># postgresql chart&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">postgresPassword&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;pg-pass&amp;#34;&lt;/span> &lt;span style="color:#75715e"># 供 sonarqube 的 JDBC 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">postgresqlPassword&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;pg-pass&amp;#34;&lt;/span> &lt;span style="color:#75715e"># postgresql chart&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">postgresDatabase&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;sonar&amp;#34;&lt;/span> &lt;span style="color:#75715e"># 供 sonarqube 的 JDBC 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">postgresqlDatabase&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;sonar&amp;#34;&lt;/span> &lt;span style="color:#75715e"># postgresql chart&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注：如上配置，由于国内访问 &lt;a href="https://kubernetes-charts.storage.googleapis.com/">https://kubernetes-charts.storage.googleapis.com/&lt;/a> 时会存在困难，此处我直接将 postgresql 的 chart 软链到了 sonarqube 的 charts 目录下，省去了依赖安装的步骤，但由此带来的问题是版本配置的不兼容。为了兼容，同一配置出现了两个配置项，为了能够正常使用，不得不确保相同配置项的一致性。&lt;/p>
&lt;/blockquote>
&lt;h3 id="依赖软链">依赖软链&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mkdir charts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> charts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ln -s ../../postgresql ../../mysql . &lt;span style="color:#75715e"># 虽然 mysql 没用上，但由于在 requirements.yaml 中存在也一并处理了&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="helm-安装">helm 安装&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>helm install -g -n kube-public -f custom.yaml . &lt;span style="color:#75715e"># 此处使用的是 helm3&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="检测安装">检测安装&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>证书是否颁发成功&lt;/p>
&lt;pre tabindex="0">&lt;code>kubectl -n kube-public get certificate sonar-tls
&lt;/code>&lt;/pre>&lt;p>READY 如果为 true 的话说明证书颁发成功&lt;/p>
&lt;/li>
&lt;li>
&lt;p>检测服务是否可用&lt;/p>
&lt;p>这有两种方式，一个是看 pod 是否为 ready 状态；另一个是看 svc 是否有对应的 endpoint。具体要根据 helm 部署的 release 的名字来看。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>检测没有问题后，可以在浏览器中输入域名试一下。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="配置">配置&lt;/h2>
&lt;h3 id="gitlab-配置">gitlab 配置&lt;/h3>
&lt;p>登入 sonarqube 系统，依次点击 &lt;code>Administration&lt;/code> -&amp;gt; &lt;code>Configuration&lt;/code> -&amp;gt; &lt;code>General Settings&lt;/code> -&amp;gt; &lt;code>Gitlab&lt;/code>&lt;/p>
&lt;h4 id="配置项">配置项&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>GitLab url&lt;/p>
&lt;p>例如：gitlab.example.com&lt;/p>
&lt;/li>
&lt;li>
&lt;p>GitLab User Token&lt;/p>
&lt;ol>
&lt;li>
&lt;p>登入 gitlab 系统，依次点击&lt;code>右上角头像&lt;/code> -&amp;gt; &lt;code>Settings&lt;/code> -&amp;gt; &lt;code>Access Tokens&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Scopes&lt;/code> 勾选 &lt;code>api&lt;/code>，&lt;code>Name&lt;/code>随便填，点击&lt;code>Create personal access tokens&lt;/code>即可创建。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/get-access-token.png" alt="Get Access Token">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将复制好的 Token 填入。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>Global template&lt;/p>
&lt;p>对于 commit 的评论分为全局评论和行内评论，该配置项为全局评论设定模版，模板有多种方式，可参照&lt;a href="https://github.com/gabrie-allaigre/sonar-gitlab-plugin/tree/master/templates/global">全局配置&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Inline template&lt;/p>
&lt;p>这个配置项为行内评论设置模板，具体配置可参照&lt;a href="https://github.com/gabrie-allaigre/sonar-gitlab-plugin/tree/master/templates/inline">行内配置&lt;/a>。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="中文配置可选">中文配置（可选）&lt;/h3>
&lt;p>登入 sonarqube 系统，依次点击 &lt;code>Administration&lt;/code> -&amp;gt; &lt;code>Marketplace&lt;/code>，搜索&lt;code>chinese&lt;/code>，可以找到一款名为&lt;code>Chinese Pack&lt;/code>的插件，点击&lt;code>install&lt;/code>安装。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/reboot-after-install-plugins.png" alt="Reboot after intsll plugins">&lt;/p>
&lt;p>插件下载完成后会进入 pending 状态，这时需要如图所示点击重启。&lt;/p>
&lt;blockquote>
&lt;p>注：安装所有插件都需要重启生效，重启时，正在进行的分析会停止。&lt;/p>
&lt;/blockquote>
&lt;h3 id="编程语言插件">编程语言插件&lt;/h3>
&lt;p>这个必须正确安装才能进行代码分析，同样是在&lt;code>Marketplace&lt;/code>中进行安装，但凡语言组件会有一个&lt;code>LANGUAGES&lt;/code>的标签，这需要根据自己代码的实际情况酌情安装。常见的有：&lt;/p>
&lt;ul>
&lt;li>SonarJS&lt;/li>
&lt;li>SonarJava&lt;/li>
&lt;li>SonarPython&lt;/li>
&lt;li>SonarGo&lt;/li>
&lt;li>SonarHTML&lt;/li>
&lt;li>SonarCSS&lt;/li>
&lt;li>&amp;hellip;&lt;/li>
&lt;/ul>
&lt;p>部分语言社区版是不支持的，如：SQL、C 等等&lt;/p>
&lt;h2 id="使用">使用&lt;/h2>
&lt;p>由于我们是非 Java 项目，所以使用 &lt;a href="http://sonar.test.weike.fm/documentation/analysis/scan/sonarscanner/">SonarScanner&lt;/a>。&lt;/p>
&lt;p>&lt;code>sonar-scanner&lt;/code>命令有三种配置方式：环境变量、命令行参数、配置文件。&lt;/p>
&lt;ul>
&lt;li>环境变量——我所知道的环境变量有两个：&lt;code>SONAR_TOKEN&lt;/code>的&lt;code>SONAR_HOST_URL&lt;/code>，但在使用插件的时候，环境变量有坑，所以不建议使用，后面会提到；&lt;/li>
&lt;li>命令行参数——可以定义和配置文件一样的配置，一些敏感或可变的配置项可以采用这种方式注入；&lt;/li>
&lt;li>配置文件——分为全局配置文件和项目配置文件，容器环境中，一般不使用全局配置。推荐配置一些常规又繁琐基本不会改变的项。&lt;/li>
&lt;/ul>
&lt;h3 id="示例httpsgithubcomsonarsourcesonar-scanning-examples">&lt;a href="https://github.com/SonarSource/sonar-scanning-examples">示例&lt;/a>：&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>下载示例代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git clone https://github.com/SonarSource/sonar-scanning-examples
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>在 gitlab 中创建 sonar-scanning-examples 项目:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>确保 runner 可用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>为 runner 配置好 values，也就是环境变量&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>PRIVATE_TOKEN&lt;/code>，也就是&lt;code>Gitlab User Token&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>SONAR_TOKEN&lt;/code>，需要在 sonarqube 控制台创建&lt;/p>
&lt;p>点击&lt;code>Administration&lt;/code> -&amp;gt; &lt;code>Security&lt;/code> -&amp;gt; &lt;code>Users&lt;/code>，在此处为指定用户生成 Token&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/generate-token.png" alt="Generate Token">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>SONAR_HOST_URL&lt;/code>，sonarqube 的地址&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>添加 .gitlab-ci.yml 文件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> sonarqube-scanner
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &amp;gt; .gitlab-ci.yml &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">stages:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - quality
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">sast:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> image:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> name: sonarsource/sonar-scanner-cli:latest
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> entrypoint: [&amp;#34;&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> stage: quality
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> script:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - sonar-scanner -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME -Dsonar.gitlab.project_id=$CI_PROJECT_ID -Dsonar.login=$SONAR_TOKEN -Dsonar.gitlab.user_token=$PRIVATE_TOKEN
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> only:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - merge_requests
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - branches
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>提交代码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git remote add origin git@gitlab.example.com/examples/sonar-scanning-examples
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git add .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git commit -m &lt;span style="color:#d88200">&amp;#34;Initial commit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git push -u origin master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>查看效果&lt;/p>
&lt;p>&lt;strong>行内注释&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/inline-comment.png" alt="行内注释">&lt;/p>
&lt;p>&lt;strong>全局注释&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/global-comment.png" alt="全局注释">&lt;/p>
&lt;p>&lt;strong>sonarqube 控制台列表&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/sonarqube-items.png" alt="sonarqube 控制台列表">&lt;/p>
&lt;p>&lt;strong>sonarqube 控制台详情&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/sonarqube-detail.png" alt="sonarqube 控制台详情">&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="那些坑">那些坑&lt;/h2>
&lt;ol>
&lt;li>&lt;code>sonar-scanner&lt;/code>在使用环境变量&lt;code>SONAR_TOKEN&lt;/code>的时候似乎存在问题，所以后来我是在命令行中使用的该变量&lt;/li>
&lt;li>gitlab 的 token 在 sonarqube 控制台中的配置似乎并没有效果，我在实际使用&lt;code>sonar-scanner&lt;/code>的过程中还需要再次配置&lt;/li>
&lt;li>&lt;code>sonar-gitlab-plugin&lt;/code>插件版本不对，起初用的&lt;code>4.0.0&lt;/code>的版本，会报一个奇怪的错误，换了&lt;code>4.1.0-SNAPSHOT&lt;/code>后，问题得到了解决&lt;/li>
&lt;/ol></description></item><item><title>跨 VPC 部署 zookeeper observer</title><link>https://mydream.ink/posts/middleware/zookeeper/deploy-zookeeper-observer-across-vpc/</link><pubDate>Sat, 02 Nov 2019 09:15:21 +0800</pubDate><guid>https://mydream.ink/posts/middleware/zookeeper/deploy-zookeeper-observer-across-vpc/</guid><description>&lt;h2 id="场景说明">场景说明&lt;/h2>
&lt;p>某云提供商网络技术更新迭代，遗留下了基础网络和 VPC 网络两种网络架构，我们公司目前的服务器还大多都在基础网络中。我在新环境中搭建了 kubernetes 集群，并在上面部署了高可用的 zookeeper 集群。配置中心方案是一个全局方案，公司大大小小的项目陆陆续续都会上，为了便于维护与迁移，我们采用一套环境统一管理。此时，集群内的 zookeeper 已经由 helm 部署完成。&lt;/p>
&lt;h2 id="需求">需求&lt;/h2>
&lt;ul>
&lt;li>一台已经安装了 docker 和 docker-compose 的位于基础网络服务器（作为 zookeeper observer，为避免单点故障最好备两台及以上)&lt;/li>
&lt;li>已经安装好三个结点的 zookeeper 的 kubernetes 的集群&lt;/li>
&lt;/ul>
&lt;h2 id="操作流程">操作流程&lt;/h2>
&lt;h3 id="1-基础网络互通">1. 基础网络互通&lt;/h3>
&lt;p>这个步骤具体怎么做我就不提了，每个云服务商可能不同，但并不是什么难点。达到的效果就是将 kubernetes 集群所在的 VPC 和 observer 服务器可以互联互通&lt;/p>
&lt;h3 id="2-向内网暴露-zk-集群相关服务">2. 向内网暴露 zk 集群相关服务&lt;/h3>
&lt;p>利用 statefulSet 会为每个 pod 生成的一个特殊的 label，为每一个 zookeeper 的 pod 建立内网 LB 类型的 Service。&lt;br>
这里有一个创建其中一个 svc 的例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">Service&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">annotations&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#75715e"># 这里边是与云服务商强相关的一些配置，不具备通用性但仍然具有参考价值，这里需要配置不然默认是公网 LB&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">service.kubernetes.io/qcloud-loadbalancer-clusterid&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">cls-xxxxxx&lt;/span> &lt;span style="color:#75715e"># 确定 tke 集群&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">service.kubernetes.io/qcloud-loadbalancer-internal-subnetid&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">subnet-xxxxxx&lt;/span> &lt;span style="color:#75715e"># 确定子网&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zk0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">namespace&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">kube-public&lt;/span> &lt;span style="color:#75715e"># zk 所在的 ns&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ports&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">tcp-2888-2888&lt;/span> &lt;span style="color:#75715e"># 数据同步端口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">port&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">2888&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">protocol&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">TCP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">targetPort&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">2888&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">tcp-3888-3888&lt;/span> &lt;span style="color:#75715e"># 选举端口&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">port&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">3888&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">protocol&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">TCP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">targetPort&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">3888&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">selector&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">app&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zookeeper&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">component&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">release&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">xxx&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">statefulset.kubernetes.io/pod-name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">xxx-zookeeper-0&lt;/span> &lt;span style="color:#75715e"># 有了这个其实其它 label 可以省略&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">type&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">LoadBalancer&lt;/span> &lt;span style="color:#75715e"># service 类型&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>创建完成后，可以通过命令查看 LB 的 ip 地址：&lt;/p>
&lt;pre tabindex="0">&lt;code>[root@VM_64_11_centos zookeeper]# kubectl -n kube-public get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
zk0 LoadBalancer 172.16.255.8 10.1.0.12 2888:30734/TCP,3888:30513/TCP 2d15h
zk1 LoadBalancer 172.16.255.66 10.1.0.13 2888:32569/TCP,3888:32718/TCP 2d14h
zk2 LoadBalancer 172.16.255.105 10.1.0.6 2888:32189/TCP,3888:30692/TCP 2d14h
&lt;/code>&lt;/pre>&lt;p>&lt;code>EXTERNAL-IP&lt;/code> 即为 LB 的 ip 地址，我们可以通过这个地址访问到对应的 pod 在 2888 和 3888 两个端口上提供的服务。记录下这三个 IP 地址，在配置 observer 的时候会用到。&lt;/p>
&lt;h3 id="3-创建-observer-所需环境">3. 创建 observer 所需环境&lt;/h3>
&lt;p>根据上一步得到的 ip，创建配置文件。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mkdir -p /data/zookeeper/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> /data/zookeeper/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &amp;gt; zoo.cfg &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">clientPort=2181
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">dataDir=/var/lib/zookeeper/data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">dataLogDir=/var/lib/zookeeper/log
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">tickTime=2000
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">initLimit=5
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">syncLimit=10
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">maxClientCnxns=60
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">minSessionTimeout=4000
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">maxSessionTimeout=40000
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">autopurge.snapRetainCount=3
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">autopurge.purgeInteval=0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">peerType=observer
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">4lw.commands.whitelist=*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">server.1=10.1.0.12:2888:3888
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">server.2=10.1.0.13:2888:3888
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">server.3=10.1.0.6:2888:3888
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">server.4=localhost:2888:3888:observer
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>创建存储&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>docker volume create zookeeper_data
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /var/lib/docker/volumes/zookeeper_data/_data/data/version-2/ /var/lib/docker/volumes/zookeeper_data/_data/log/version-2/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &amp;gt; /var/lib/docker/volumes/zookeeper_data/_data/data/myid
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chown 1000:1000 /var/lib/docker/volumes/zookeeper_data/_data/data/version-2/ /var/lib/docker/volumes/zookeeper_data/_data/log/version-2/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>创建 docker-compose.yaml&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cat &amp;gt; docker-compose.yaml &lt;span style="color:#d88200">&amp;lt;&amp;lt; EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">version: &amp;#34;3&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">services:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> zookeeper:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> image: zookeeper
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> ports:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - &amp;#34;2181:2181&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> volumes:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - &amp;#34;./zoo.cfg:/conf/zoo.cfg&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - &amp;#34;data:/var/lib/zookeeper&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> exporter:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> depends_on:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - zookeeper
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> image: josdotso/zookeeper-exporter:v1.1.2
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> ports:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - &amp;#34;9141:9141&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> command:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - -bind-addr=:9141
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - -metrics-path=/metrics
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - -zookeeper=zookeeper:2181
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - -log-level=info
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> - -reset-on-scrape=true
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">volumes:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> data:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-启动-observer">4. 启动 observer&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>docker-compose up -d
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>至此 zookeeper observer 已经启动&lt;/p>
&lt;h3 id="5-为-observer-配置监控">5. 为 observer 配置监控&lt;/h3>
&lt;blockquote>
&lt;p>思路：将集群外的 observer 以无 selector 的 service 的形式挂到 kubernetes 集群中，打好 label 供 serviceMonitor 匹配即可&lt;/p>
&lt;/blockquote>
&lt;p>创建文件 zk-ob-external.yaml&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">Service&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">labels&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">app&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zookeeper&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">release&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">xxx&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">position&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">external&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">xxx-zookeeper-external&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ports&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zookeeperxp&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">port&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">9141&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">protocol&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">TCP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">targetPort&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zookeeperxp&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">Endpoints&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">labels&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">app&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zookeeper&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">release&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">xxx&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">position&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">external&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">xxx-zookeeper-external&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">subsets&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#f92672">addresses&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">ip&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">10.104.170.174&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">ip&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">10.104.42.205&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ports&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">zookeeperxp&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">port&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">9141&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">protocol&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">TCP&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注意 label 要与对应 serviceMonitor 中 selector.matchLabels 匹配，端口名要一致&lt;/p>
&lt;/blockquote>
&lt;h2 id="其它">其它&lt;/h2>
&lt;h3 id="超级管理员">超级管理员&lt;/h3>
&lt;p>在给 zookeeper 配置权限时，由于需要加密的缘故，很多新手都容易配错，配错后对应的结点就无法删除了。在 zookeeper 中并没有什么超管可以无视 ACL 删除结点，所以我遍寻 zookeeper 的超级管理权限不得法。想要无视 ACL 对 Zookeeper 数据进行操作，只需找一个 zk 服务在配置文件中加入&lt;code>skipACL=yes&lt;/code>并重启服务即可，操作完成后记得注释掉这个配置并重启服务，因为没有 ACL 的 zk 很不安全。为了降低重启服务所带来的风险，建议在 observer 上进行该操作。&lt;/p></description></item><item><title>Kubernetes 中部署高可用的 Zookeeper</title><link>https://mydream.ink/posts/middleware/zookeeper/deploy-zookeeper-ha-in-kubernetes/</link><pubDate>Sat, 02 Nov 2019 08:01:06 +0800</pubDate><guid>https://mydream.ink/posts/middleware/zookeeper/deploy-zookeeper-ha-in-kubernetes/</guid><description>&lt;p>本文主要使用 &lt;a href="https://github.com/helm/charts/tree/master/incubator/zookeeper">helm chart&lt;/a> 进行 zookeeper 的高可用部署。&lt;/p>
&lt;h2 id="环境说明">环境说明&lt;/h2>
&lt;p>本文使用的是 TKE 托管环境，在该环境中安中了 cbs-csi 的 operator，为了方便持久卷备份，后文中的持久化存储会统一采用。CSI的解决方案在各种环境中都会有，我仅描述 cbs。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>kubernetes 版本：&lt;code>v1.14.3-tke.4&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>helm 版本：&lt;code>v2.10.0&lt;/code>（TKE控制台支持的版本）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>按步骤安装好的 &lt;a href="https://github.com/TencentCloud/kubernetes-csi-tencentcloud">cbs-csi&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>值得说明的是 cbs 存储不可跨可用区挂载，默认情况下先建存储再建 pod 的方式无法满足生产需要，所以才引入的 cbs-csi。但在建立 storageClass 时要注意加入 &lt;code>volumeBindingMode: WaitForFirstConsumer&lt;/code> 以保证 pod 完成调度之后再创建卷，从而使得存储不用影响 pod 的调度。sc 参考如下：&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">storage.k8s.io/v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">StorageClass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">annotations&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">storageclass.beta.kubernetes.io/is-default-class&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#d88200">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">cbs-csi-topo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">parameters&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">diskType&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">CLOUD_PREMIUM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">provisioner&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">com.tencent.cloud.csi.cbs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">reclaimPolicy&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">Delete&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">volumeBindingMode&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">WaitForFirstConsumer&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>已经部署好了的 prometheus 的监控方案，包括 &lt;code>serviceMonitor&lt;/code> 这样的 CRD&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="部署">部署&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>下载 chart：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git clone git@github.com:helm/charts.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>确定部署参数：&lt;/p>
&lt;p>部署时传参有命令行和文件两种方式，这里创建一个 &lt;code>custom.yaml&lt;/code> 的文件，填入想要修改的变量&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">exporters&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">jmx&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span> &lt;span style="color:#75715e"># jmx 监控&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">zookeeper&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span> &lt;span style="color:#75715e"># zookeeper 应用级监控&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">prometheus&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">serviceMonitor&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span> &lt;span style="color:#75715e"># 使用 serviceMonitor 作为 prometheus 的配置方案&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">selector&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">release&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">monitor&lt;/span> &lt;span style="color:#75715e"># 这需要与 promethus 的 serviceMonitorSelector.matchLabels 匹配，否则 promethus 无法获取该配置&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">persistence&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">storageClass&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">cbs-csi-topo&lt;/span> &lt;span style="color:#75715e"># 符合需求的 sc&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">size&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">10Gi&lt;/span> &lt;span style="color:#75715e"># zookeeper 持久卷的大小，视数据大小而定&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">image&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">tag&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">3.5.6&lt;/span> &lt;span style="color:#75715e"># zookeepr 的镜像版本，从 https://hub.docker.com/_/zookeeper 选择自己需要的&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 如果以上这些参数还满足不也自己的需求，就在 values.yaml 中找&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>helm install --namespace &amp;lt;NS&amp;gt; -f custom.yaml incubator/zookeeper
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这会生成一个名字随机的 Release，解决了取名字的烦恼，也可以通过 &lt;code>--name&lt;/code> 指定名字，如果是二次安装，环境对原来的名字形成了依赖，则需要指定原来的名字。&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>自动化运维之自动构建</title><link>https://mydream.ink/posts/devops/auto-devops-auto-build/</link><pubDate>Sun, 08 Sep 2019 18:37:36 +0800</pubDate><guid>https://mydream.ink/posts/devops/auto-devops-auto-build/</guid><description>&lt;p>本文供自己记录学习，我会尽量保证文章准确无误，但因本人水平有限，如果不幸产生任何错误，望读者不吝赐教。&lt;/p>
&lt;h2 id="浅析-gitlab-runner-相关配置">浅析 gitlab-runner 相关配置&lt;/h2>
&lt;p>在 gitlab-runner 中每个 runner 都需要指定 &lt;a href="https://docs.gitlab.com/runner/executors/">executor&lt;/a>，executor 有以下几种类型：&lt;/p>
&lt;ul>
&lt;li>SSH&lt;/li>
&lt;li>Shell&lt;/li>
&lt;li>Parallels&lt;/li>
&lt;li>VirtualBox&lt;/li>
&lt;li>Docker&lt;/li>
&lt;li>Docker Machine (auto-scaling)&lt;/li>
&lt;li>Kubernetes&lt;/li>
&lt;li>Custom&lt;/li>
&lt;/ul>
&lt;p>我们会将 gitlab-runner 部署在 kubernetes 集群中，所以此处选用 kubernetes 作为 executor。有关该 executor 的配置方式，在&lt;a href="https://docs.gitlab.com/runner/executors/kubernetes.html">官方文档&lt;/a>中有详细说明，此处就此次配置所需要的进行简单说明，其它的再视情况进行补充。&lt;/p>
&lt;p>与其它类型的 executor 一样，kubernetes 也需要根据 gitlab 仓库的 url 和 token 对 runner 进行注册方可使用。注册后会有一个常驻的 pod （我姑且叫它 runner 服务端）对来自于 gitlab 的 pipeline 任务进行监听，当监听到有新的任务需要执行时，就会创建新的 pipeline 跑 &lt;code>.gitlab-ci.yml&lt;/code> 上对应的job。所以 runner 服务端相关联的 ServieAccount 一定需要具备对应的 namespace 的 pod 的创建的权限。&lt;/p>
&lt;p>配置一个 runner 有多种方式，包括启动参数、环境变量、配置文件等。这其中配置文件的功能最强，参数可配置项较环境变量稍多（或许它们具有相同的能力，只是我没有发现，如果你知道欢迎帮忙补充）。通过&lt;code>gitlab-runner help register&lt;/code>命令我们可以找到所有的启动参数和环境变量，参数与环境变量之间的关系请参照&lt;a href="https://gist.github.com/alphajc/e2c19fa624ccc29fb3af0803e9afcb22">参数与环境变量对照表&lt;/a>。&lt;/p>
&lt;h3 id="runner-的集群中权限">runner 的集群中权限&lt;/h3>
&lt;p>前面已经谈到 runner 服务端在注册成功监听到新的任务后，需要以 Pod 的形式创建 Worker 对新对任务进行处理。这里我们需要考虑几种权限：Pod 的操作权限、Worker 镜像的拉取权限以及在 Worker 中构建镜像时对私有镜像的拉取权限。&lt;/p>
&lt;p>Pod 的操作权限通过 kubernetes 中的 ServiceAccount 来实现，我们需要赋予对应的 ServiceAccount 对指定命名空间的 Pod 的操作权限，相关的参数有&lt;code>--kubernetes-namespace&lt;/code>和&lt;code>--kubernetes-service-account&lt;/code>以及他们环境变量的开关参数&lt;code>--kubernetes-namespace_overwrite_allowed&lt;/code>和&lt;code>--kubernetes-service_account_overwrite_allowed&lt;/code>。这里指定的 ServiceAccount 一定要在指定的 Namespace 中有 Pod 的操作权限，否则 gitlab 在跑 pipeline 的时候会报权限错误。有关 ServiceAccount 的权限配置是通过 RBAC 实现的，详情参照&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/">kubernetes 中 RBAC 认证的文档&lt;/a>。&lt;/p>
&lt;p>对于 Worker 镜像的拉取权限，一般都是在集群创建时预先配置好的，此处无需另行配置。而 Worker 中构建镜像时对私有镜像的拉取和推送权限是在 &lt;code>.gitlab-ci.yml&lt;/code>中进行配置的，这里不要搞混。&lt;/p>
&lt;h3 id="runner-的持久化存储">runner 的持久化存储&lt;/h3>
&lt;p>runner 可以使用 hostPath 和 PVC 做持久化存储，跟 Kubernetes 中的概念完全一样。用法参照&lt;a href="https://docs.gitlab.com/runner/executors/kubernetes.html#using-volumes">使用卷&lt;/a>，参考配置如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-toml" data-lang="toml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">concurrent&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># usual configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">executor&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;kubernetes&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">host_path&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">name&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;hostpath-1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">mount_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/to/mount/point&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">read_only&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">host_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/on/host&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">host_path&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">name&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;hostpath-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">mount_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/to/mount/point_2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">read_only&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">pvc&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">name&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;pvc-1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">mount_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/to/mount/point1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">config_map&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">name&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;config-map-1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">mount_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/to/directory&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">config_map&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">items&lt;/span>&lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#d88200">&amp;#34;key_1&amp;#34;&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;relative/path/to/key_1_file&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#d88200">&amp;#34;key_2&amp;#34;&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;key_2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">secret&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">name&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;secrets&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">mount_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/to/directory1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">read_only&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">secret&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">items&lt;/span>&lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#d88200">&amp;#34;secret_1&amp;#34;&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;relative/path/to/secret_1_file&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">[[&lt;/span>&lt;span style="color:#75af00">runners&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">kubernetes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">volumes&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">empty_dir&lt;/span>&lt;span style="color:#111">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">name&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;empty_dir&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">mount_path&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;/path/to/empty_dir&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">medium&lt;/span> &lt;span style="color:#111">=&lt;/span> &lt;span style="color:#d88200">&amp;#34;Memory&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="在-runner-中构建镜像">在 runner 中构建镜像&lt;/h3>
&lt;p>在 runner 中构建 Docker 镜像有两种方式，最常见的是使用&lt;code>docker build&lt;/code>命令，另一种是使用谷歌推出的&lt;a href="https://github.com/GoogleContainerTools/kaniko">&lt;code>kaniko&lt;/code>&lt;/a>。相较于&lt;code>docker build&lt;/code>，&lt;code>kaniko&lt;/code>无需 Docker Daemon 即可工作，因此 Worker 无需在特权模式下工作，这种方式不会挑战 Kubernetes 集群的安全性。下面将具体谈谈两种方式：&lt;/p>
&lt;h4 id="docker-build">docker build&lt;/h4>
&lt;p>使用这种方式构建 Docker 镜像我们不得不为执行该 Job 的 Worker 开启特权模式，这会带来安全性问题。如果你对此不以为然的话，就可以进行接下来的步骤了。&lt;/p>
&lt;p>尽管这种方式本身就是不安全的，但是如果做好以下几点，依然可以有效降低安全风险：&lt;/p>
&lt;ol>
&lt;li>将特权模式的 runner 打上专用标签，仅供使用对应标签的 job 调用；&lt;/li>
&lt;li>创建这种类型的 runner 服务端时，使用 Taint 标记 Node，告诉其它应用这个结点的污点，在部署 runner 或其它可以容忍这个安全问题的应用时设置对应的 Toleration。&lt;/li>
&lt;/ol>
&lt;p>这可以确保特权模式不被滥用，亦可对应用的安全性负责。&lt;/p>
&lt;p>以特权模式运行的&lt;code>docker: dind&lt;/code>容器会在&lt;code>2375/tcp&lt;/code>端口上进行监听，我们可以通过 TCP 协议对 Docker 进行操作。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">image&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">docker:latest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">variables&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">DOCKER_HOST&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">tcp://localhost:2375&lt;/span> &lt;span style="color:#75715e"># 在 Kubernetes 中 service 会以 sidecar 的形式插到 Pod 中，同一 Pod 中的网络使用 localhost 访问即可&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">DOCKER_DRIVER&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">overlay2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">services&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker:18-dind&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">before_script&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker login -u &amp;#34;$CI_REGISTRY_USER&amp;#34; -p &amp;#34;$CI_REGISTRY_PASSWORD&amp;#34; $CI_REGISTRY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build-master&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker build --pull -t &amp;#34;$CI_REGISTRY_IMAGE&amp;#34; .&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker push &amp;#34;$CI_REGISTRY_IMAGE&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注：docker 宿主机的版本与&lt;code>dind&lt;/code>的版本最好一致。比如我宿主机使用的&lt;code>18.03&lt;/code>的版本，那么镜像就应该使用&lt;code>docker:18-dind&lt;/code>，如果直接使用&lt;code>docker:dind&lt;/code>会使用最新版本的 docker。我当时遇到的一个问题是在 dind 容器中只监听了&lt;code>2376&lt;/code>端口，没有&lt;code>2375&lt;/code>。&lt;code>2376&lt;/code>端口是做了加密的，至少需要配置证书都能使用，&lt;code>2375&lt;/code>端口不能正常使用，不知道是因为版本不一致，还是本来就只会监听&lt;code>2376&lt;/code>，我看 Dockerfile 中，两个端口都有暴露，时间关系我就没有进一步细研了。当我换作&lt;code>docker:18-dind&lt;/code>后，问题都解决了。&lt;/p>
&lt;/blockquote>
&lt;h4 id="kanikohttpsdocsgitlabcomeecidockerusing_kanikohtml">&lt;a href="https://docs.gitlab.com/ee/ci/docker/using_kaniko.html">kaniko&lt;/a>&lt;/h4>
&lt;p>用法如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">image&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">gcr.io/kaniko-project/executor:debug&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">entrypoint&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#111">[&lt;/span>&lt;span style="color:#d88200">&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">echo &amp;#34;{\&amp;#34;auths\&amp;#34;:{\&amp;#34;$CI_REGISTRY\&amp;#34;:{\&amp;#34;username\&amp;#34;:\&amp;#34;$CI_REGISTRY_USER\&amp;#34;,\&amp;#34;password\&amp;#34;:\&amp;#34;$CI_REGISTRY_PASSWORD\&amp;#34;}}}&amp;#34; &amp;gt; /kaniko/.docker/config.json&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">/kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="使用-helm-安装-gitlab-runner">使用 helm 安装 gitlab-runner&lt;/h2>
&lt;p>使用 gitlab 官方 chart 库进行 gitlab-runner 的安装。因为官方的 chart 使用参数和环境变量注册 gitlab-runner，在使用上会有一定的局限性，可能需要一些修改才能进行高级配置。以下是使用官方 chart 部署 gitlab-runner 的步骤：&lt;/p>
&lt;h3 id="前提">前提&lt;/h3>
&lt;ol>
&lt;li>安装了 tiller 的 kubernetes 的集群&lt;/li>
&lt;li>初始化完成了的 helm 命令行工具&lt;/li>
&lt;/ol>
&lt;h3 id="1-添加-gitlab-官方-chart-仓库">1. 添加 gitlab 官方 chart 仓库&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>helm repo add gitlab https://charts.gitlab.io/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-在-gitlab-代码仓库中找到-url-和-runner-的-token">2. 在 gitlab 代码仓库中找到 url 和 runner 的 token&lt;/h3>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/find-url-and-token.png" alt="获取 Gitlab 的 URL 和 runner 的 token">&lt;/p>
&lt;h3 id="3-安装-gitlab-runner">3. 安装 gitlab-runner&lt;/h3>
&lt;p>我们可以通过参数以及配置文件的形式为 helm 添加自定义配置。该 Chart 的可配置项可以查阅官方库&lt;a href="https://gitlab.com/gitlab-org/charts/gitlab-runner/blob/master/values.yaml">对于 &lt;code>values.yaml&lt;/code> 文件的注释&lt;/a>，这里把关键配置提出来说明一下：&lt;/p>
&lt;ul>
&lt;li>&lt;code>gitlabUrl&lt;/code>：gitlab 服务的地址，形如：https://gitlab.xxx.com；&lt;/li>
&lt;li>&lt;code>runnerRegistrationToken&lt;/code>：上一步中得到的 token；&lt;/li>
&lt;li>&lt;code>rbac.create&lt;/code>：我是没有遇到过没有 &lt;code>rbac&lt;/code> 的 kubernetes 集群，所以记得一定填 &lt;code>true&lt;/code>，否则创建出的 gitlab-runner 将无权对集群进行操作；&lt;/li>
&lt;li>&lt;code>rbac.clusterWideAccess&lt;/code>：如果涉及到在不同的 namespace 跑 job，设置为 &lt;code>true&lt;/code>，否则置为 &lt;code>false&lt;/code>（这只能操作同一 namespace）；&lt;/li>
&lt;li>&lt;code>rbac.serviceAccountName&lt;/code>：如果不设置默认使用 default，如果 sa 专用则建议另起一个名字，以避免 default 权限过大，其它用 default 的 pod 滥用；&lt;/li>
&lt;li>&lt;code>runners.image&lt;/code>：默认镜像，在 gitlab-ci.yml 中的 job 未配置 &lt;code>image&lt;/code> 时将自动选用；&lt;/li>
&lt;li>&lt;code>runners.privileged&lt;/code>：一般在该 runner 需要跑 &lt;code>docker:dind&lt;/code> 镜像时使用；&lt;/li>
&lt;li>&lt;code>runners.namespace&lt;/code>: Job 运行的 namespace；&lt;/li>
&lt;li>&lt;code>runners.imagePullSecrets&lt;/code>：在创建 job 时使用的镜像如果来自于私有仓库则需要配置该字段。注意使用前需要提前创建好对应的 secret；&lt;/li>
&lt;li>&lt;code>runners.cache&lt;/code>：如果要使用 runner 的 cache 功能，需要对此选项进行配置，不同的持久化方式配置不同，有些方式甚至需要对 chart 进行改造。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>注：该 Chart 的多 runner 注册功能受限，对 kubernetes 的高级功能配置支持也有限，后期我将对这个 Chart 进行改造，使其支持。&lt;/p>
&lt;/blockquote>
&lt;h2 id="浅析-gitlab-ciyml-相关配置">浅析 &lt;code>.gitlab-ci.yml&lt;/code> 相关配置&lt;/h2>
&lt;p>有关 &lt;code>.gitlab-ci.yml&lt;/code> 更详细的配置参照&lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html">官方文档&lt;/a>，在此仅挑部分有特点的东西记录一下。&lt;/p>
&lt;ol>
&lt;li>image 将确定 job 运行的环境，script 将在该环境中运行；&lt;/li>
&lt;li>service 是环境的依赖，如&lt;code>docker:dind&lt;/code> 一般以这种方式注入，具体在 kubernetes 集群中会以 sidecar 的形式与主环境放在一个 pod 中，所以如果 service 提供的是 socket 服务，在主环境中使用 localhost 即可访问；&lt;/li>
&lt;li>注意 only/except 的&lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#onlyexcept-advanced">高级用法&lt;/a>，可以服务于多种场景；&lt;/li>
&lt;li>使用 &lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#rules">rules&lt;/a> 对 job 进行合理控制；&lt;/li>
&lt;li>&lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#environment">environment&lt;/a> 跟持续部署相关，主要用于前端项目；&lt;/li>
&lt;li>&lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#cache">cache&lt;/a> 用于在多个 job 间缓存数据；&lt;/li>
&lt;li>&lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#artifacts">artifacts&lt;/a> 将对指定文件（一般是编译产物）进行打包，并上传至 gitlab，供下载使用，这也会在界面进行展示，配合 &lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#dependencies">dependencies&lt;/a> 使用，可以在不同 stage 的 job 间进行传递；&lt;/li>
&lt;li>使用 &lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html#coverage">coverage&lt;/a> 抓取测试覆盖率&lt;/li>
&lt;/ol>
&lt;h2 id="配置-gitlab-ciyml">配置 &lt;code>.gitlab-ci.yml&lt;/code>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">stages&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">.build-common&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#75715e">&amp;amp;build-common&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">image&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">docker:latest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">variables&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">DOCKER_HOST&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">tcp://localhost:2375&lt;/span> &lt;span style="color:#75715e"># kubernetes 集群中使用 localhost 访问&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">DOCKER_DRIVER&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">overlay2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">services&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">docker:18-dind&lt;/span> &lt;span style="color:#75715e"># 匹配环境中 docker 的版本&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">command&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#111">[&lt;/span>&lt;span style="color:#d88200">&amp;#34;--registry-mirror=https://mirror.ccs.tencentyun.com&amp;#34;&lt;/span>&lt;span style="color:#111">]&lt;/span> &lt;span style="color:#75715e"># 这是我找到的在编译镜像时，配置镜像加速器的最好办法&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">before_script&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker login -u &amp;#34;$CI_REGISTRY_USER&amp;#34; -p &amp;#34;$CI_REGISTRY_PASSWORD&amp;#34; $CI_REGISTRY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build-master&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#75715e">*build-common&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker build -t &amp;#34;$CI_REGISTRY_IMAGE&amp;#34; .&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker tag &amp;#34;$CI_REGISTRY_IMAGE&amp;#34; &amp;#34;$CI_REGISTRY/$CI_REGISTRY_IMAGE&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker tag &amp;#34;$CI_REGISTRY_IMAGE&amp;#34; &amp;#34;$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker push &amp;#34;$CI_REGISTRY/$CI_REGISTRY_IMAGE&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker push &amp;#34;$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">only&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">master&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#75715e">*build-common&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker build -t &amp;#34;$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}&amp;#34; .&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker tag &amp;#34;$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}&amp;#34; &amp;#34;$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">docker push &amp;#34;$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">except&lt;/span>&lt;span style="color:#111">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">master&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>修改 Git 的历史提交</title><link>https://mydream.ink/posts/tools/modify-the-history-commit-of-git/</link><pubDate>Sun, 11 Aug 2019 16:29:00 +0800</pubDate><guid>https://mydream.ink/posts/tools/modify-the-history-commit-of-git/</guid><description>&lt;p>现在工作生活中，基本都使用 Git 用作代码的版本控制。我们应该为自己的每次 commit 提供一个合理的 log，以让自己或其他开发者明白对应的 commit 都干了些什么，但有时我们不可避免地会出错，然后就需要修改某些该死的提交。我们大可以新建一个 commit 来干这个事情，但这对于有洁癖的我来讲实在太 low 了，也不利于代码维护。Git 作为一个强大的工具，当然有解决办法啦，下面就让我们来一起探讨该如何修改过去的提交。&lt;/p>
&lt;h2 id="环境说明">环境说明&lt;/h2>
&lt;p>我这里有一个 git 项目，进行过 3 次代码提交，日志如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>commit 5ae91d0efae757087386c0c4b80e660e97627d22 (HEAD -&amp;gt; master)
Author: Jerry Chan &amp;lt;jerry@mydream.ink&amp;gt;
Date: Sun Aug 11 20:04:43 2019 +0800
third commit
commit 6df839cab26ce48a561192933dc28c0d9fe302db
Author: Jerry Chan &amp;lt;jerry@mydream.ink&amp;gt;
Date: Sun Aug 11 19:57:46 2019 +0800
second commit
commit 61aa39cfbcbab89d8e0d44c03f01f65402bd20e6
Author: Jerry Chan &amp;lt;jerry@mydream.ink&amp;gt;
Date: Sun Aug 11 19:56:23 2019 +0800
fist commit
&lt;/code>&lt;/pre>&lt;ol>
&lt;li>
&lt;p>第一次，提交的时候我建了个文件&lt;code>test.txt&lt;/code>，并写了一行内容：&lt;/p>
&lt;pre tabindex="0">&lt;code>这是我第一次不应该提交的内容，但是却提交了的内容。
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>第二次，我新建了一个文件&lt;code>test2.text&lt;/code>，也写下了一行内容：&lt;/p>
&lt;pre tabindex="0">&lt;code>这是第二次提交的内容
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>第三次，我对前面两次创建的文件分别进行了重命名：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mv test.txt test1.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mv test2.text test2.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>现在的问题在于我第一次就提交错了，正确的内容应该是：&lt;/p>
&lt;pre tabindex="0">&lt;code>这是我第一次原本应该提交的内容
&lt;/code>&lt;/pre>&lt;h2 id="操作步骤">操作步骤&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>我们要回到第一次提交去进行修改。因为第一次提交前没有提交，所以没有办法指定 commit 作为基来对第一次提交进行操作。此时，我们需要使用以下命令对第一次提交进行操作：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git rebase -i --root
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此时，我们将看到如下信息：&lt;/p>
&lt;pre tabindex="0">&lt;code>pick 61aa39c fist commit
pick 6df839c second commit
pick 5ae91d0 third commit
# 变基 5ae91d0 到 64b66ae（3 个提交）
#
# 命令:
# p, pick &amp;lt;提交&amp;gt; = 使用提交
# r, reword &amp;lt;提交&amp;gt; = 使用提交，但修改提交说明
# e, edit &amp;lt;提交&amp;gt; = 使用提交，进入 shell 以便进行提交修补
# s, squash &amp;lt;提交&amp;gt; = 使用提交，但融合到前一个提交
# f, fixup &amp;lt;提交&amp;gt; = 类似于 &amp;#34;squash&amp;#34;，但丢弃提交说明日志
# x, exec &amp;lt;命令&amp;gt; = 使用 shell 运行命令（此行剩余部分）
# b, break = 在此处停止（使用 &amp;#39;git rebase --continue&amp;#39; 继续变基）
# d, drop &amp;lt;提交&amp;gt; = 删除提交
# l, label &amp;lt;label&amp;gt; = 为当前 HEAD 打上标记
# t, reset &amp;lt;label&amp;gt; = 重置 HEAD 到该标记
# m, merge [-C &amp;lt;commit&amp;gt; | -c &amp;lt;commit&amp;gt;] &amp;lt;label&amp;gt; [# &amp;lt;oneline&amp;gt;]
# . 创建一个合并提交，并使用原始的合并提交说明（如果没有指定
# . 原始提交，使用注释部分的 oneline 作为提交说明）。使用
# . -c &amp;lt;提交&amp;gt; 可以编辑提交说明。
#
# 可以对这些行重新排序，将从上至下执行。
#
# 如果您在这里删除一行，对应的提交将会丢失。
#
# 然而，如果您删除全部内容，变基操作将会终止。
#
# 注意空提交已被注释掉
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>注释里面写得很清楚，我们需要修改第一次提交，所以我们得将&lt;code>pick 61aa39c fist commit&lt;/code>改成&lt;code>edit 61aa39c fist commit&lt;/code>，然后保存并退出。&lt;/p>
&lt;p>此时使用&lt;code>git status&lt;/code>，我们可以看到如下内容：&lt;/p>
&lt;pre tabindex="0">&lt;code>交互式变基操作正在进行中；至 64b66ae
最后一条命令已完成（1 条命令被执行）：
edit 61aa39c fist commit
接下来要执行的命令（剩余 2 条命令）：
pick 6df839c second commit
pick 5ae91d0 third commit
（使用 &amp;#34;git rebase --edit-todo&amp;#34; 来查看和编辑）
您在执行将分支 &amp;#39;master&amp;#39; 变基到 &amp;#39;64b66ae&amp;#39; 的操作时编辑提交。
（使用 &amp;#34;git commit --amend&amp;#34; 修补当前提交）
（当您对您的修改满意后执行 &amp;#34;git rebase --continue&amp;#34;）
无文件要提交，干净的工作区
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>在这一步，我们需要把内容修改成我们想要的样子。将 test.txt 的内容改为：&lt;/p>
&lt;pre tabindex="0">&lt;code>这是我第一次原本应该提交的内容
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>暂存变更&lt;/p>
&lt;pre tabindex="0">&lt;code>git add test.txt
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>把暂存区中的内容变成自己想要的样子后，对本次提交进行修补&lt;/p>
&lt;p>执行命令&lt;code>git commit --amend&lt;/code>，我们将得到以下内容&lt;/p>
&lt;pre tabindex="0">&lt;code>fist commit
# 请为您的变更输入提交说明。以 &amp;#39;#&amp;#39; 开始的行将被忽略，而一个空的提交
# 说明将会终止提交。
#
# 日期： Sun Aug 11 19:56:23 2019 +0800
#
# 交互式变基操作正在进行中；至 64b66ae
# 最后一条命令已完成（1 条命令被执行）：
# edit 61aa39c fist commit
# 接下来要执行的命令（剩余 2 条命令）：
# pick 6df839c second commit
# pick 5ae91d0 third commit
# 您在执行将分支 &amp;#39;master&amp;#39; 变基到 &amp;#39;64b66ae&amp;#39; 的操作时拆分提交。
#
#
# 初始提交
#
# 要提交的变更：
# 新文件： test.txt
#
&lt;/code>&lt;/pre>&lt;p>你可能需要修改日志，但如果不需要的话，保存并退出即可。&lt;/p>
&lt;pre tabindex="0">&lt;code>[分离头指针 3baa727] fist commit
Date: Sun Aug 11 19:56:23 2019 +0800
1 file changed, 1 insertion(+)
create mode 100644 test.txt
&lt;/code>&lt;/pre>&lt;p>看到上面的信息后表示，我们对本次提交的更改就已经完成了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>我们得到满意的提交后，执行&lt;code>git rebase --continue&lt;/code>，回到当前 HEAD 下。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="注意">注意&lt;/h2>
&lt;p>当我们做这种操作时，修改了的提交之后的所有的提交都会发生改变，我们必须确保这些提交没有正在被别人使用。如果你还有什么问题欢迎留言。&lt;/p></description></item><item><title>在 Mac 上使用 Docker 自带的 Kubernetes</title><link>https://mydream.ink/posts/cloud-native/kubernetes/use-kubernetes-from-docker-on-mac/</link><pubDate>Fri, 12 Jul 2019 20:23:13 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/use-kubernetes-from-docker-on-mac/</guid><description>&lt;p>Mac 安装 Docker 的方法不再赘述，很简单，&lt;a href="https://download.docker.com/mac/stable/Docker.dmg">官方下载&lt;/a>安装即可。&lt;/p>
&lt;p>安装完成后，&lt;code>kubectl&lt;/code>的二进制文件就已经存在了。我们仅需要下载镜像，开启 kubernetes。&lt;/p>
&lt;h2 id="下载镜像">下载镜像&lt;/h2>
&lt;p>查看一下你的 Docker 和 Kubernetes 版本：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/cloud-native/k8s_from_docker.png" alt="About Version">&lt;/p>
&lt;p>使用&lt;a href="https://gist.github.com/alphajc/ad56aa01da685d665c856551c4d18baf%22">以下脚本&lt;/a>，记得将镜像版本改成你所需要的🍁&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">set&lt;/span> -e
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">KUBE_VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>v1.10.11
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">KUBE_PAUSE_VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>3.1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">ETCD_VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>3.1.12
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">DNS_VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>1.14.8
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">DOCKER_TOOL_VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>v0.4.12
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">GCR_URL&lt;/span>&lt;span style="color:#f92672">=&lt;/span>k8s.gcr.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">ALIYUN_URL&lt;/span>&lt;span style="color:#f92672">=&lt;/span>registry.cn-hangzhou.aliyuncs.com/google_containers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">images&lt;/span>&lt;span style="color:#f92672">=(&lt;/span>kube-proxy-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">KUBE_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-scheduler-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">KUBE_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-controller-manager-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">KUBE_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-apiserver-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">KUBE_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pause-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">KUBE_PAUSE_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>etcd-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">ETCD_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>k8s-dns-sidecar-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">DNS_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>k8s-dns-kube-dns-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">DNS_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>k8s-dns-dnsmasq-nanny-amd64:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">DNS_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">for&lt;/span> image in &lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">images&lt;/span>&lt;span style="color:#111">[@]&lt;/span>&lt;span style="color:#d88200">}&lt;/span> &lt;span style="color:#111">;&lt;/span> &lt;span style="color:#00a8c8">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docker pull &lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">ALIYUN_URL&lt;/span>&lt;span style="color:#d88200">}&lt;/span>/&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">image&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docker tag &lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">ALIYUN_URL&lt;/span>&lt;span style="color:#d88200">}&lt;/span>/&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">image&lt;/span>&lt;span style="color:#d88200">}&lt;/span> &lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">GCR_URL&lt;/span>&lt;span style="color:#d88200">}&lt;/span>/&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">image&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> docker rmi &lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">ALIYUN_URL&lt;/span>&lt;span style="color:#d88200">}&lt;/span>/&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">image&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 拉取 Docker 相关镜像&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker pull docker/kube-compose-api-server:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">DOCKER_TOOL_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker pull docker/kube-compose-controller:&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">DOCKER_TOOL_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>执行该脚本，将从阿里镜像库获得所需镜像。&lt;/p>
&lt;h2 id="启动-kubernetes-功能">启动 Kubernetes 功能&lt;/h2>
&lt;p>打开 Docker 配置面板，请至少勾选 Enable Kubernetes 以开启 Kubernetes 服务。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/cloud-native/start_k8s.png" alt="Start Kubenetes">&lt;/p>
&lt;p>开启片刻后，你将看到 Docker Engine 和 Kubernetes 同时处于&lt;code>running&lt;/code>状态。&lt;/p>
&lt;h2 id="查看-kubernetes-服务">查看 Kubernetes 服务&lt;/h2>
&lt;p>输入&lt;code>kubectl get pods --all-namespaces&lt;/code>，你将看到：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>➜ ~ kubectl get pods --all-namespaces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>NAMESPACE NAME READY STATUS RESTARTS AGE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker compose-74649b4db6-bjw57 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 1m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>docker compose-api-597cc7786b-vpnnk 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 1m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-system etcd-docker-for-desktop 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 2m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-system kube-apiserver-docker-for-desktop 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 2m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-system kube-controller-manager-docker-for-desktop 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 2m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-system kube-dns-86f4d74b45-n55kv 3/3 Running &lt;span style="color:#ae81ff">0&lt;/span> 2m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-system kube-proxy-dgwfm 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 2m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kube-system kube-scheduler-docker-for-desktop 1/1 Running &lt;span style="color:#ae81ff">0&lt;/span> 1m
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>至此，你就可以尽情享受 Kubernetes 带给你的乐趣了🎉🌴🎉&lt;/p></description></item><item><title>在 CentOS7 上安装新版 Docker CE（使用国内源）</title><link>https://mydream.ink/posts/cloud-native/container/install-docker-on-centos7/</link><pubDate>Fri, 12 Jul 2019 10:46:25 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/container/install-docker-on-centos7/</guid><description>&lt;h2 id="卸载老版本">卸载老版本&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo yum remove docker &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-client &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-client-latest &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-common &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-latest &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-latest-logrotate &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-logrotate &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> docker-engine
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果曾经安装过，&lt;code>/var/lib/docker/&lt;/code>中会有原来的镜像、容器、卷以及网络残留，如果不需要可将之一并删除。&lt;/p>
&lt;h2 id="安装-docker-ce">安装 Docker CE&lt;/h2>
&lt;h3 id="安装-docker-国内源">安装 Docker 国内源&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>安装 yum 配置管理工具&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo yum install -y yum-utils &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>device-mapper-persistent-data &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>lvm2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装清华大学的 Docker 安装源（本网站有效，该命令就有效）&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo yum-config-manager &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>--add-repo &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span>https://mydream.ink/utils/container/docker-ce.repo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你可能会遇到如下问题：&lt;/p>
&lt;pre>&lt;code> adding repo from: https://mydream.ink/utils/container/docker-ce.repo
grabbing file https://mydream.ink/utils/container/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
Could not fetch/save url https://mydream.ink/utils/container/docker-ce.repo to file /etc/yum.repos.d/docker-ce.repo: [Errno 14] curl#60 - &amp;quot;Peer's Certificate has expired.&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>出现该问题一般是由于本地时间不正确（经常挂起的虚拟机很容易出现），使用&lt;code>date&lt;/code>命令核对一下时间即可，若确认是这个问题，则：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo ntpdate pool.ntp.org &lt;span style="color:#75715e"># ntpdate 可使用 yum install ntpdate 进行安装&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h2 id="安装-docker-ce-1">安装 Docker CE&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>安装最新版的 Docker CE&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo yum install docker-ce docker-ce-cli containerd.io
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果弹出导入 Key 的问题，请接受&lt;/p>
&lt;/li>
&lt;li>
&lt;p>镜像加速&lt;/p>
&lt;p>新建或修改&lt;code>/etc/docker/daemon.json&lt;/code>，加入：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;registry-mirrors&amp;#34;&lt;/span>&lt;span style="color:#111">:&lt;/span> &lt;span style="color:#111">[&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#d88200">&amp;#34;https://dockerhub.azk8s.cn&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#d88200">&amp;#34;https://reg-mirror.qiniu.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一定要确保格式没有问题，否则 docker 无法启动，修改完成后执行以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo systemctl daemon-reload
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>启动 Docker&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo systemctl start docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h2 id="卸载-docker-ce">卸载 Docker CE&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>卸载安装包&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo yum remove docker-ce
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>如果你觉得曾经的数据没用了，就一并删除吧&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo rm -rf /var/lib/docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol></description></item><item><title>初尝创业</title><link>https://mydream.ink/posts/experiences/first-startup/</link><pubDate>Mon, 01 Jul 2019 17:58:19 +0800</pubDate><guid>https://mydream.ink/posts/experiences/first-startup/</guid><description>&lt;p>&lt;strong>本文记录了自己的个人生活经历及其感悟，以回忆为主，文中所有姓名均为化名，无心对任何人或者是公司造成影响，如果可能发生，请联系我进行修改&lt;/strong>&lt;/p>
&lt;hr>
&lt;p>正如我告诉别人的那样，我也这样告诉自己：&lt;strong>我从未怀疑过这辈子可以取得成功，不过同时我也非常清楚的是成功之路将充满坎坷与艰辛！&lt;/strong>&lt;/p>
&lt;h1 id="尝试">尝试&lt;/h1>
&lt;p>&lt;a href="../job2">上一份工作&lt;/a>于我而言确实是一个比较大的打击，我会因此感到郁闷，但很快也就过去了『👊沉浸于过去的悲痛是无能的表现』。对于实现自己的梦想，我最初的想法是，先认真工作几年，工作过程中多少会认识些志同道合的伙伴，然后就可以一起去做一些了不起的事情了。然而，两份工作都始终没有发现一个这样的伙伴，我觉得我快失去前进方向了，我特别好奇到底是哪里没对。因此，我特想看看自己离梦想有多远，现在好了，都不用选择就可以离开舒适区了。而这一次的尝试，无论结果如何，都是一种收获：要么创造奇迹，要么认清自己。&lt;/p>
&lt;p>我是一个山里长大的孩子，泥巴就是我从小到大的玩具。深耕互联网，心系乡土情，我觉得利用互联网促进我国小农经济发展像是我的一种使命。我认为，农者，国之根本也。有关该项目详情就不在此赘述了，相关资料有点多。&lt;/p>
&lt;p>创业，一个感觉上多么超凡脱俗的名词啊。它因大多数人不愿企及而神圣，然只有亲历者才知其艰辛，我曾想象过前路坎坷，但未曾想到还未出发便遭遇阻力。追逐自己的梦想本是一件无可厚非的事，但创业似乎就成了一件千夫所指之事。亲人、朋友、爱人，不是所有人都能理解你的雄心壮志，更不是所有人都支持你的所作所为。在这条道路上，我最先感受到的是孤独，所以，拥有战友在我看来是件多么可贵的事。&lt;/p>
&lt;blockquote>
&lt;p>我妈妈是残疾人，老爸能力有限，而我是祖上五代往下数第一个大学生。可以想象他们是经历了怎样的艰辛才把我送进大学校园，读的几乎最贵的软件工程专业。当辛苦供养出来的大学生决定创业时，妈妈便再也坐不住了。她不求我大富大贵，但求我一生顺遂，所以每次电话都无法抑制住内心的恐惧😢。&lt;/p>
&lt;/blockquote>
&lt;p>然而，为了自己的梦想，背负着父母的期待和女友的等待，我还是自私地出发了。我知道我除了自己什么都没有，所以目标也比较明确，边寻找创业伙伴，边打磨自己的 idea，只要找到战友，我就有理由相信这件事情能成。一方面，我开始不断地参加各种线上线下的交流活动，想尽可能地去认识一些以梦想为轴的人，希望能够找到合适的创业伙伴；另一方面，与各行各业的朋友交流我的想法，在此过程中，我们经常发现项目中还有很多缺陷，所以，找到问题并提出相应的解决方案，便成为了那时候我最为享受的事了。为了得到政策支持，我曾多次拜访省农业厅，给他们出示并讲解我的计划。为了更了解这个行业，我曾跟种植大户深入交流。&lt;/p>
&lt;p>每日醒来沐浴着清晨的第一缕阳光时，我会觉得一切都充满了希望，因为梦就在前方，不过很快又会回到现实中打滚。当事情毫无进展时，我会非常怀疑自己，偶尔还能和上妈妈“不怀好意”的问候，让自己切实体会一把什么是在夹缝中求生存。为了检验一下我想做的东西是否是伪需求，也为了证明自己还活着😱，我学着做了款&lt;a href="https://github.com/alphajc/pleasure-mall-producer">微信小程序&lt;/a>并给种子用户进行试用。为了给自己找个伙伴，我几乎去了活动行和 Meetup 上所有免费的活动。不放过任何机会的我还在国内外各大专业论坛和职场交友网站发帖，希望能求得一志趣相投的伙伴，还因为这个天真的行动被诸多网友讽刺得不要不要的，不过还好我有心里准备。很快，为梦想而努力的那种快感生生地被淹没在了现实的残酷之中。因为寻找创业伙伴的事情，一直没有进展。&lt;/p>
&lt;p>三月中旬，我偶然发现有人曾引用了我关于梦想的描述，莫非是有『共鸣』之人？此人35岁，传统行业人士，不懂技术，却是一个足以让我感到热血沸腾的人。我把自己的项目放在一边，加入了他已经有了一定投入的项目；我把他的项目看做自己的，帮他重写之前的商业计划书，并作为技术负责人验收、整理他曾外包出去的项目，我在 Github 上建了个组织，现在还管理着他的项目呢。自始自终，我从未提过利益分配的问题，这源于我对于一个热血团队的渴望。然而，跟他共事了一个月之久，我居然摸不透他哪句话是真哪句话是假，所以我最后还是选择了离开。&lt;/p>
&lt;p>重新回到了一个人，此时，我已经深刻地明白，想要通过这样的方式在短时间内找到创业伙伴根本就不可能了。『路遥知马力，日久见人心。』很真实的一堂课，且不说，满足自己期望的人少有，就算是有也不会是通过三两次的吃饭、聊天或者喝茶结交的。所以，后来当同样的机会摆在我面前的时候，我拒绝了。没有人脉、没有伙伴也没有金钱，我缺乏了太多的资源了。最终，我认清了我自己，&lt;strong>积累资源成为了我所能够倚仗的唯一途径。&lt;/strong>&lt;/p>
&lt;h1 id="抉择">抉择&lt;/h1>
&lt;p>之前想要不计一切后果去创业的时候，认为自己本来也都一无所有，所以也不怕失去什么。可真正走到那一步时，我才发现我有亲人、有爱人，发现我除了梦想，还有一份责任。我在做任何决定的时候也必须为他们考虑些什么。我有责任让我的父母不再为我操心，也有责任许我女友一个美好的未来，一个真正成功的男人应当学会责任与梦想一肩挑。&lt;strong>创业是一辈子的事情，我无需急于这一时&lt;/strong>，我本来起跑线就落后了别人一大截，更应该先积累一定的社会地位，也不至于让父母为我操心、让女友为我等待。既然已经认清了自己的方向，那就应当坚定不移地走下去。如何积累资源便成为了我首先需要考虑的问题。&lt;/p>
&lt;p>『&lt;strong>不积跬步无以至千里&lt;/strong>』&lt;/p>
&lt;p>我曾考虑过很多种未来的可能性，最终还是认为从技术做起才是最适合我的。如今，我也为此正准备着。💪💪💪&lt;/p></description></item><item><title>第二份工作</title><link>https://mydream.ink/posts/experiences/job2/</link><pubDate>Mon, 01 Jul 2019 17:56:50 +0800</pubDate><guid>https://mydream.ink/posts/experiences/job2/</guid><description>&lt;p>&lt;strong>本文记录了自己的个人生活经历及其感悟，以回忆为主，文中所有姓名均为化名，无心对任何人或者是公司造成影响，如果可能发生，请联系我进行修改&lt;/strong>&lt;/p>
&lt;hr>
&lt;h1 id="找工作">找工作&lt;/h1>
&lt;p>为什么这段经历要从找工作说起呢？我主要是回忆起了那时候的彷徨与迷茫。从上一家公司离职后，我有点不知所措。就像我&lt;a href="../job1">之前所诉&lt;/a>一样，我不知道我能够匹配什么样的工作，明明感觉自己充满能量，但又觉得前路黯淡。&lt;/p>
&lt;blockquote>
&lt;p>我明白我的特点是技术广度、快速上手能力以及发散性思维，其实这样的特点在什么岗位都是比较实用的，这在解决问题上是一把好手，不过这样的能力很难通过面试发现，我需要突出这一点。&lt;/p>
&lt;/blockquote>
&lt;p>我一共找了两周的工作，根据先前的工作经验，我是 Node.js 开发、项目经理、运维开发的综合体😕，这太难了😂。不知道是简历写得不好还是履历本来就不够吸引人，或许两者都有吧，我鲜有面试机会。正当我要放弃找工作打算先疯狂学习一阵做几个开源项目再找的时候，七陌电话邀我去面试。跟我约的时间是周五（也就是那两周的最后一个工作日），所以我就抱着最后试一试的心态去了。&lt;/p>
&lt;p>这原本是一个运维岗，去了以后我从面试官也就是运维总监那里得知他们的业务越来越复杂，正在向微服务转型，当时差不多已经拆成有大大小小 60 多个服务，在考虑上容器了，甚至考虑为此组建一个部门。听到这个后，我显得比较兴奋，因为这正是我想做的事。我也表明了我的态度：“如果让我来做这个的话，我愿意一试，并且我有信心做成。”。公司的 CTO 张总正在做容器化相关的调研，他对于这方面比较了解，所以面试官拨通了张总的电话，让他对我进行专业的面试。我们互相都比较满意，所以没过多久我就得到了 HR 的通知。&lt;/p>
&lt;h1 id="新的起点">新的起点&lt;/h1>
&lt;p>之前的公司10个人，新公司有200多人，从规模上讲，已经翻了好几倍了。现在还记得面试时，让我填的「职业生涯规划」，我有很认真的填写我要成为部门 Leader，结果我的部门就两个人，从编制上讲，还挂靠在别的部门😐。我心想我现在也刚进，以后肯定会招人的，先把自己事情做好就行，所以也就没想太多了。&lt;/p>
&lt;p>带我的人叫石冷，我去之前，架构部门就他一个人，因此他一向比较忙，平日里都戴着个耳机也不多话，我找他基本都是发 QQ 消息😓。为了快速融入到公司环境中，借着团建做主持的机会，我用了不到两周时间就把成都分公司这边近80号人都认识了。&lt;/p>
&lt;h1 id="容器化方案">容器化方案&lt;/h1>
&lt;p>这是我在这个公司最重要的事，至少在进公司前我都是这么认为的。刚进去那会儿，不了解公司业务、不了解公司组织架构、不知道测试环境、不知道开发环境、不知道生产环境，总之是啥都不了解，石冷不爱说话，也从不安排任务，所以我基本上是被晾在那儿了😁。不过没有状况本身也是一种状况，我能做的就是在不浪费时间的前提下尽可能独立解决问题。首先认清目标，我来公司的目的就是出具并实施容器化解决方案的。&lt;/p>
&lt;p>然后，我给自己设立了阶段性目标：&lt;/p>
&lt;ol>
&lt;li>理清公司微服务架构的业务逻辑「我的天，60多个服务，分别是干啥的，他们之间如何协同工作的，没有文档，也不清楚」&lt;/li>
&lt;li>学习 Docker、Kubernetes 相关技术「以前用的容器是 Zone，虽然接触过这俩，但并不多，想根据其能力出具解决方案还差远了」&lt;/li>
&lt;/ol>
&lt;p>针对第一点，我必须问，有时候会不好意思，但也不得不问，因为那是我开展工作的必要条件。至于第二点，翻阅官方文档进行学习，为此我还找石冷要了3台测试服务器。整个进展都比较顺利，我估计花了一个月的样子就出具了第一套过渡方案。主要策略是将原系统的中 Consul 和 Kubernetes 中的 etcd 的服务注册与发现的能力以及配置能力进行同步。我跟石冷讲了这个方案，他觉得还不错，我们就正式跟领导层进行了一场方案探讨的会议。可是，张总认为公司大部分研发人员连 Docker 都不了解，如果直接把 kubernetes 用起来有点太重了，难以落地，所以予以否决了『张总的考虑确实有道理，不过我觉得一个好的架构对于研发人员来讲应该是无觉察的，不知是否应当如此？😀』。尽管方案并没有落地实施，公司管理层对我的能力还是予以了充分肯定。&lt;/p>
&lt;p>此事过去后不久，公司把我和石冷分别临时下放到了运维部和云客服部，说是让我们能够更好地了解业务以便设计出更接地气的架构。&lt;/p>
&lt;p>去了庞然（运维部经理）那儿后，帮他协助私有云部门为私有云客户部署基础服务「MongoDB、Redis、Elasticsearch、Consul、Open office、Solr」，他们之前本来是脚本部署的，还要拷贝好多文件，特麻烦。为了简化流程，我把它们全部抬到了 Ansible 上来，感觉舒服多了「技能库里又多了件宝贝，开心😄」。本来待满一个月我就要回架构部了的，张总突然找到我说私有云部门的阿伟离职了，人手不够需要我，并且那边容器化方案更容易落地。于是乎，我就成了私有云老大湛成的手下。&lt;/p>
&lt;h1 id="去到私有云">去到私有云&lt;/h1>
&lt;p>私有云部门是一个业务部门，主要是为大客户提供自建云客服系统以及可能存在的定制开发服务。私有云项目从服务器批下来到最终完成交付大致会经历 PoC 测试和定制开发（如果需要）两个阶段。所以私有云下又有实施和开发两大模块，服务器给到公司后，会先由运维团队搭建基础服务（也就是我之前在庞然的团队所做的事情），然后由我部门实施人员搭建业务服务并做基本调通，没有问题后移交技术支持与售前。阿伟走后，湛成成了唯一一个具有实施能力的私有云成员，但私有云的工作量不降反升，所以这才把我调了过去。湛成来了成都后，组建实施团队便成了我的任务之一。最终我招募了两位小伙伴，并带他们参与实施工作。实施过程中最难的地方莫过于调通了，像 Consul、Nginx、MongoDB 都很容易出问题，有时调试时，需要用到各种前后端的编程知识，也只能自己去解决。&lt;/p>
&lt;p>我们部署项目需要两个自主开发的部署工具：&lt;/p>
&lt;ul>
&lt;li>vole —— node 编写的 Web 工具，用于录入配置并生成配置文件&lt;/li>
&lt;li>lever —— python 编写的命令行工具，依赖于 Ansible，用于部署业务服务&lt;/li>
&lt;/ul>
&lt;p>两个工具都还有这样那样的问题，功能也不全，可能也就支持20来个服务吧。将实施任务交给两个新伙伴后，我开始了两个部署工具的改造之旅，我新加了些项目支持，修复了一些 BUG，甚至还改了下界面，也正是因为在这里更加真切地感受到了这些痛点问题，使得我对于容器化的解决方案又有了一些新的想法。在湛成的支持下，我又开始了我的容器化之旅。我还是用的 Kubernetes，不过这次我落地实施了。我用双容器 Pod 捆绑 Nginx 解决了程序中访问服务写死 &lt;code>127.0.0.1&lt;/code>的问题，并用 Endpoint 直接挂载 Kubernetes 集群以外的 Consul 服务和由 consul-template 渲染出来的 Nginx 代理服务；利用 namespace 解决了环境区分问题；利用 Configmap 解决了每个服务的配置问题；利用 secrets 解决 tls 证书存储问题；利用 Ingress 解决了外部访问的问题，同时有一种非常方便的方式添加或删除 tls 证书；利用 「nfs provisioner」 + storageClass 解决共享存储的问题。最后，我将60多个服务编成镜像放到 Harbor 仓库中，利用 Helm 将所有服务加到一个 application 中，解决批量布署的问题（以前部署一套环境两天，我将其缩短到了两分钟）；利用 Telepresence 解决了远程调试问题。不用修改一点代码就将服务布署到了 Kubernetes 集群中。&lt;/p>
&lt;p>这60多个项目，大多都是老项目，代码用 SVN 管理的，我没有 SVN 的管理权限没有办法添加 hook，所以我将所有项目都迁移到了 gitlab 上，利用 gitlab-runner 进行 CI/CD，自动化编译和部署，好不方便！新版的 gitlab 对 DevOps 支持就更好了。&lt;/p>
&lt;p>公有云那边项目越来越多，再加上公司跟腾讯加强了合作，需要在除阿里云外的腾讯云上再建一套云客服系统。这时候石冷那边一个人忙不过来了，而我在私有云的事情基本告一段落了，于是湛成问我，要不要考虑回去，那里更能体现我的价值，于是乎，我又回去了。「😑老天仿佛跟我开了个玩笑，绕了一圈我又回到了这里」&lt;/p>
&lt;h1 id="回到架构部">回到架构部&lt;/h1>
&lt;p>回到架构部后，除了日常维护私有云团队的 Kubernetes 集群外，我还做了3件事：&lt;/p>
&lt;ol>
&lt;li>开发 MongoDB 迁移工具；&lt;/li>
&lt;li>为日志管理系统开发新的功能；&lt;/li>
&lt;li>设计开发平台管理系统。&lt;/li>
&lt;/ol>
&lt;h1 id="离开">离开&lt;/h1>
&lt;p>是的，我确实有想要离开的想法，在一个公司来回折腾了几个部门，最后又回到了原来的地方，直到我离开之前，石冷还告诉我，等架构部门的事情做完后，我有计划被调到运维部去和庞然一起调研负载均衡方案。能被组织需要本身是一件特别幸运的事情，但我失去了归属感，觉得这可能与我职业生涯规划的理念相悖。当然了，公司有公司的想法，这本就无可厚非，只是公司在面试时问我要了而我也给了我的职业生涯规划，我觉得这在人事调动的时候还是可以稍微考虑一下的。&lt;/p>
&lt;p>尽管我没有跟任何人讲过，并且很长一段时间我也不愿意提起，但我当时并非完全自愿离开的。&lt;/p>
&lt;p>公司的代码库做了防火墙，直接锁死只有公司的 ip 能够通过公网进行访问，开发人员如果想在自己家中提交代码，就必须连上 VPN，并且在 git 项目中修改 git 仓库的 url，使用内网地址进行访问。我想到了一个解决办法，就是如果连上 VPN 后，访问 git 的公网 ip 走 VPN 服务器那条线，那就可以合理避免开发人员频繁修改仓库 url 的操作了。VPN 服务器在有客户端连接时，会将路由策略分发至各客户端，客户端根据该策略设置路由，只需要在路由分发上添加一条记录即可实现。我将我的想法进行了测试，保证确实可行没有问题后，我便添加到了 VPN 服务器上。我正为又解决了开发人员一大痛点而感到满足时，公司群里在讨论生产环境服务连接中断的问题。时间有点太凑巧了，但我一直觉得不可能是我的操作导致的，因为我操作的是开发环境的 VPN 服务器啊😳。&lt;/p>
&lt;p>整个故障持续了一个多小时。由于我在操作前有向上头报备，运维总监很快找到了我，真是不敢相信😮。那时我才得知，原来生产环境的每一台服务器都连接着开发环境的 VPN，这还不算完，VPN 上 ip 地址分配策略居然跟阿里云的 ip 分配策略是冲突的，所以生产环境上的每一个 ip 地址都是根据阿里云的 ip 分配策略手动分配的，也就是说，ip 如果发生变动将导致网络紊乱😫。我知道了事情发生的详细情况，意识到自己踩了雷，便也不做辩解，当即表示愿意接受任何处罚。&lt;/p>
&lt;p>第二天，我发现我所有公司账户都被冻结了，我就大概知道结果了，老实说我并不甘心。在结果下来后，我告诉了湛成，挺对不起他的，因为我很多工作都跟私有云部门亲密合作，很多计划将因此破产，让大家的心血付诸流水，当时也还在帮他做一个运营管理平台的框架搭建，因为我的离开这个项目也搁浅了。&lt;/p>
&lt;p>下脚前，没有注意到那个位置有雷，我确有失察之过，假如我在决定操作或在重启 VPN 服务前，认真检查一下跟服务器连接的客户端分别是什么用途，也许就能避免了。为了走得体面一点，我甚至没有要一丁点的赔偿，而选择自愿辞职，若非今日忆起，我可能不会跟任何人讲。走的那天，张总给我来了个电话告诉我：“这件事情你本没有错，怪只能怪你运气不好，埋雷的人是公司创始人，他犯了天大的错，我们也只能削减他的股份，不可能让他走人，只是公司必须给客户和全体员工一个交待，牺牲你是一个让人心痛却又不得不做的决定。”听他说这些，我姑且就相信了吧，毕竟这能让自己心里好受些。后来，运维总监在微信里跟我也说了差不多类似的话，让我不要记恨公司。&lt;/p>
&lt;p>心高气傲的我，曾经想象过多种离开的方式，唯独没想过会这样。一个人骑着自行车走在成都的大街上，明明灯火通明，却感觉漆黑一片，眼看着就要过年了，这时候失去了工作，我不知道我该如何告知我的家人、朋友还有我的爱人，也不知找下一份工作时，如何告知面试官我曾被选择😲。&lt;/p>
&lt;p>或许，我真应该像我要离开时石冷告诉我的那样『决不做非自己职责范围内的事』吧？不过我真正学到的是，不管做什么样的事不可盲目自信，应当做好万全的考虑，以后无论做什么，我必将更加小心慎重。我还是那个我，初心不改，但挫折必将让我学会成长。如今，我不愿逃避，将此事记录下来，正视自己的错误，愿意面对并承担由此带来的一切后果。『👏我相信』&lt;/p></description></item><item><title>第一份工作</title><link>https://mydream.ink/posts/experiences/job1/</link><pubDate>Mon, 01 Jul 2019 17:56:47 +0800</pubDate><guid>https://mydream.ink/posts/experiences/job1/</guid><description>&lt;p>&lt;strong>本文记录了自己的个人生活经历及其感悟，以回忆为主，文中所有姓名均为化名，无心对任何人或者是公司造成影响，如果可能发生，请联系我进行修改&lt;/strong>&lt;/p>
&lt;hr>
&lt;h1 id="初入创业公司">初入创业公司&lt;/h1>
&lt;p>公司原本只有三个人，我本以为他们三个是联合创始人，但到后来我发现创始人只有朱总一个。朱总曾先后在多家大型外企工作（我记得的就有惠普和甲骨文），有很多年的技术经验，在「去 IOE」的大背景下，希望基于 PostgreSQL 研发自主可控的国产数据库。CTO ——范畴，在华为研究所里开发了多年的缓存，后来去过中东做市场，也是一个非常有想法的人，最初也是他找到了我。另一个叫何敏，2012年西安电子科技大学毕业，原本供职于深圳国家电网，在那里做 C#，因为公司数据库选型和朱总结识。除了他们，其他人都跟我一样，是应届大学生。除了前端的邵梦，我们都是 C 语言开发入职的，按照最初的计划，进公司后是要从事数据库开发的。&lt;/p>
&lt;p>范畴为我们进行了为期数日的 C 语言岗前培训，同时，朱总还为我们进行了数据库相关知识培训。老实说，那段时间学到了蛮多东西。公司有个项目叫 EcoX，是朱总以前用 Perl 脚本写的 PostgreSQL 高可用集群管理系统，因为要商用，所以范畴那段时间一直在用 C++ 重新实现。那时候公司还没有任何基础设施建设，他维护代码特别麻烦，所以让我给解决一下。svn、samba、ldap，这些我以前听都没听过的东西，也还包括一些其它基础服务，我都给搭了一遍。当时我本来想推荐 git 而不用 svn 的，但是我那时项目做得少，最多也就是用过 Github，没有其它解决方案，索性也就搭的 svn。直到后来在西科大做了很多项目的研究生建平过来带来了 gitlab 的解决方案，我们才正式使用 git 的。不得不说，那确实是个好东西，直至今日我做自动化运维还用它✨✨✨。&lt;/p>
&lt;h1 id="鸿鹄系列">鸿鹄系列&lt;/h1>
&lt;p>我们的数据库称作 &lt;strong>鸿鹄数据库&lt;/strong>，PG 是 OLTP 的一把好手，为了丰富应用场景，使其更好地支持 OLAP ，他第一步想要做的是加入列存。自主研发数据库门槛特别高，然而我们都是新人，大多数人连数据库原理都不懂，这可如何开始？我给范畴推荐了「中国大学 MOOC」上哈工大战德臣教授数据库系统的系列课程，这是我当时备战秋招的时候学过的，觉得还不错，后来他也在上面学习，还推荐给了大家。只是可惜的是，我后来一直没有在做数据库。&lt;/p>
&lt;p>刚进公司那会儿，公司有三台服务器（两台二手 hp，一台华为），我们自用的服务器都是从华为那台虚拟出来的。宿主机是 &lt;a href="https://www.joyent.com/smartos">SmartOS&lt;/a> 系统，它支持多种虚拟化技术：zone、基于 zone 改造的 lxc 以及从 linux 移植过来的 KVM ，前两种就是我们当前总在讲的容器化技术，使得运行在上面的应用有可以利用裸机的 IO 能力，在传统虚拟化技术面前有着非常大的优势。而支持 KVM 可以让用户将原本运行在虚拟机中的服务迁移过来；其次，SmartOS 原生支持 ZFS 文件系统。ZFS 作为最先进的文件系统之一，自带存储解决方案，服务器上的磁盘甚至不需要做阵列。这种技术特别适合数据库这种对 IOPS 要求很高的应用。朱总希望将其改造成自主可控的国产数据中心产品，所以做一个管理系统将这种能力管理起来成为了我们另一个项目，后来朱总给它取名叫 &lt;strong>鸿鹄彩云&lt;/strong>「😅老实说，我对这个名字存有保留意见」。&lt;/p>
&lt;h1 id="数据中心国产化之路">数据中心国产化之路&lt;/h1>
&lt;p>由于数据库产品的门槛实在过高，「鸿鹄彩云」便成为了我们的当务之急。范畴主要负责架构设计，我和常齐负责技术选型。因为采用 SOA 架构，所以选择一种较为合理的服务间通信方式是我们技术选型的头等任务。我们决定消息队列直接采用 OpenStack 在用的 RabbitMQ；Protobuf 以二进制流方式进行网络传输，作为序列化技术远比 XML 和 JSON 的效率高，只使用相对复杂，基于 Protobuf 的 gRPC 还是 Beta 版本，当时我们还在考虑要不要采用来着😯。因为大家都是新手，所以也没做什么详尽的测试，就都用了一下。不过，很快事情就变得不一样了。&lt;/p>
&lt;p>某天，朱总跟我演示了一样东西——&lt;a href="https://www.joyent.com/">Triton&lt;/a>「Joyent 旗下的数据中心产品」，它基于 &lt;a href="https://www.joyent.com/smartos">SmartOS&lt;/a>「SmartOS 也是 Joyent 公司的产品」，它拥有几乎所有我们想要的功能，并且还经市场检测过的。没经过太多犹豫，我们就放弃原来的工作，一门心思放到了 Triton 上来。这是一个开源产品，我们计划先进行界面改造，然后再深入底层开发，实际上我们也这么做了。&lt;a href="https://github.com/joyent/sdc-adminui">adminui&lt;/a> 这是一个纯前后端分离的项目，但是我们前端开发只有一个，还是一个新手，所以我们连学带用的也加入到了前端开发的阵营。为了简化开发测试流程，我利用 gitlab runner 建了一套自动化运维系统，用以实现持续集成和持续部署。最后完成了一个 1.0 版本，只是确实有点丑😰。&lt;/p>
&lt;p>朱总应该发现了效果不怎么好，所以又找了一个有经验的前端开始 2.0 版本的开发。由于一直以来我表现都非常突出，加之强烈的责任心，因此深受朱总信赖，他把整个项目全权交由我来负责，命我做项目经理，当时我们这个项目组无疑成为了撑起公司的希望「前端：尹月、前端+文案：邵梦、后端：常齐+我+齐政+范畴、测试：灿灿」。&lt;/p>
&lt;blockquote>
&lt;p>齐政当时还在成都的 Veritas 上班，在我们公司兼职，基本上有事就可以找他支援。后来 Veritas 散伙后，直接来了我们公司，不过来的时候我已经快离开了。&lt;/p>
&lt;/blockquote>
&lt;p>当时，我将前端所有部分交由尹月负责，毕竟，她在我们所有人中前端经验是最丰富的，她可以安排邵梦跟她分工协作。邵梦还有一个任务就是完成用户说明文档，优先级在前端任务之后，说明文档以 Markdown 的形式书写，用 Gitbook 整理，把项目框架搭好后给到她。灿灿主要就负责测试。我记得我们在底层加了两个功能：IaaS 层的虚拟机迁移和 PaaS 层的 RDS。虚机迁移完全是利用 ZFS 的能力完成的，我们将这套流程加到 Triton 的 Workflow 中，使其可以利用前端界面进行管理。在做 RDS 时，我们使用 Triton 带的系统组件 Zookeeper 实现服务的注册与发现，以自动实现主备切换。我们后端几人任务分配方式基本都是按模块，一个模块就是一个微服务，使用 RESTful 对接。&lt;/p>
&lt;h1 id="运维">运维&lt;/h1>
&lt;p>一进公司，我一心为公司发展的一片赤子之心便展露无遗，当然了，仅仅有心还不够，还得有能力「&lt;strong>我的特点是：超强的学习能力、快速的上手能力以及问题的分析解决能力&lt;/strong>」，这使得我很快得到了朱总和范畴的认可，几乎把公司所有的基建都交给了我……&lt;strong>搭建&lt;/strong>😂 是的，会的、不会的、见过的、没见过的，只要有需求，我就得满足😩。我前后为公司搭建了 SVN、LDAP、Samba、Bind9、Gitlab 等多种服务。&lt;/p>
&lt;p>为验证 triton 软件定义数据中心的能力，公司前前后后又买了两台满配的 Dell 服务器、一台 Dell 的万兆三层交换机、两台 Dell 的千兆三层交换机。另外，朱总让我非常佩服的地方在于他总能跟各种公司借到想要设备。这其中包括了从宝德借来的一台 x86 服务器，从 Cavium 借来了一台 ARM 服务器，还从 Mellanox 借来了两块 infiniband 的网卡想把 RDMA 技术放到自研数据库中。有幸管理和使用这些设备着实让我硬件知识提升了不少，以前仅仅在书上看到的东西在这里都尝试了一把😄。还记得当时照着手册给交换机配置 VLAN 可真是把我折腾惨了😨。&lt;/p>
&lt;h1 id="出差">出差&lt;/h1>
&lt;p>作为公司颜值担当和实力担当的我（不好意思，我膨胀了🙈），实习期就开始出差了。玩笑归玩笑，不过说真的，那时我确实是全公司最了解 Triton 的人。当时我们在川内有个项目要做 PoC，公司派我过去的，他们的服务器又老又旧，可难受死我了。他们那机器用的 LSI 的磁盘驱动器，但是 SmartOS 并没有内置该驱动，我们当时花了好长时间才发现这个问题，最后朱总亲自重新编译了内核，问题才得以解决；翌日安装完成后，我又发现计算节点的 PXE 启动有问题，系统初始化一直不能成功，而这些问题在自己机房是从未出现过的，我只能通过微服务日志跟踪的方式寻找原因，当时现场有10来个人看我直播排错来着，可把我惊出一身冷汗，庆幸的是，我找到了原因，客户现场的路由器时间有问题，引发服务器使用 NTP 服务出错，Triton 不允许在时间不同步的情况下进行初始化。&lt;/p>
&lt;p>此外，还去北京参加过一次数据库大会，毕竟我们也是中国少有的几家能够提供 PostgreSQL 专业服务的公司。当然，我们也趁着这个机会推销了一下公司的鸿鹄彩云。🐾🐾🐾另外去北京、上海各一次，都是为了去宣传我们的「鸿鹄彩云」。&lt;/p>
&lt;blockquote>
&lt;p>数据库大会的时候，我负责宣传我们的「鸿鹄彩云」，敏哥负责「EcoX」，在那之前，公司在招了一个销售叫 Sott，当时负责招揽意向客户。&lt;/p>
&lt;p>Scott 是一个有近20年工作经验的前辈，也是我见过少有的能够为了梦想豁出自己的人，也是一个特别热心的人，不过由于跟朱总理念不合，没过多久就离开了。我们一直都还有联系，后来还帮了我不少忙。&lt;/p>
&lt;/blockquote>
&lt;h1 id="纪律">纪律&lt;/h1>
&lt;p>公司管理特别放松，本来人也少，不打卡、不考核，秉承着革命靠自觉的管理方式，本意是觉得大家都是高素质人才，只要大家把工作完成好了就行。刚开始的时候，大家确实都还蛮自觉的，然而随着时间的推移，情况变得越来越糟。数据中心产品始终没有办法变现，办公室里终日人心惶惶。每天九点、十点、十一点来上班的人都有，更有甚者过来直接就到饭点了😟，但是下班基本都比较准时，过了 18:10 几乎很难看到公司还有其他人。平常在公司里，看球赛的看球赛、看电视的看电视，也没有人管，我几乎不敢相信这是一家创业公司，感觉国企也不能这样吧？朱总平时都不在办公室里，对公司的情况并不了解，我看在眼里，痛在心里，我知道这是主人翁意识在“作祟”。&lt;/p>
&lt;p>我们大多数人都是刚出校门的学生，没有形成什么职业素养，很容易受环境所累。公司产品销路一直有问题，所以在当时，公司正处于一种内忧外患的境地。为此，我跟朱总探讨了上班打卡的可能性，朱总最终决定使用钉钉打卡，尽管如此，还是不计入考评（准确来讲是没有考评😂）。从那以后，每天的上下班时间都有记录了，碍于情面，出勤效果改善了不少。但是后来有人来晚了就直接选择忘记打卡😥。为了给公司营造一个良好的工作氛围，我后来还建议朱总加入绩效考评，这在我离开公司那会儿得到了采用，可能也是我觉得当时我所还能够给公司带来的吧。&lt;/p>
&lt;h1 id="离开">离开&lt;/h1>
&lt;p>不论到哪儿，我都会问自己的一个问题是：&lt;strong>我可以为这里带来些什么？&lt;/strong>&lt;/p>
&lt;p>&lt;strong>价值体现&lt;/strong> 大概是我职业生涯最在乎的东西了。公司发展一直都比较缓慢，又内外交困，我隐隐觉得看不到未来。我希望凭借自己的努力可以让公司变得越来越好，因为我与公司荣辱与共。我每天都在不断地思考着，除了跟朱总一起探讨办公室氛围问题，关于公司发展，我也会出谋划策。我不见得说的都对，但我的立场和出发点完全都是在为公司考虑，朱总看到眼里也记在心里，因而一直都挺欣赏我的，也愿意跟我一起探讨。&lt;/p>
&lt;p>朱总每天或每周偶尔来一下办公室，因此，范畴就是实际意义上的办公室领袖，有什么问题我们都找他解决，特别是刚去公司那会儿，那时的他跟招我时一样干劲十足。在工作过程中，我的能力慢慢展现出来，范畴和朱总都有心提拔我，慢慢把「鸿鹄彩云」的控制权交到了我手里。自那以后，范畴主要和建平一起做数据库方面的一些东西，偶尔帮衬一下我们。可是过了一段时间，我发现范畴有点魂不守舍的，来得越来越晚了不说，还经常请假，当时我们已经在打卡了，他也经常不打。老实说，我觉得他有些无视纪律，恣意妄为。其实不单是我，大家都有发现，只是敢怒不敢言而已。可是作为办公室领袖如此行事，影响相当恶劣。范畴似乎早已看出了我们的不满，就在一次请假的假期结束之后单独找我聊天。从聊天我得知了他刚离婚的消息，看得出来他十分爱他的前妻，请假是因为悲伤过度，实在无心工作。他让我不要告诉大家，不想因为自己影响大家。他原计划当时就离去的，但是答应朱总的两年期限未满不能也不愿违背承诺，同时也不想动摇军心，过年前他会努力做好考勤的，事实上，他确实做到了。后来，我一直都跟大家讲的是范畴可能有什么苦衷，让大家给他点时间，直至最后范畴离开真相大白的时候，大家都说范畴平时都嘻嘻哈哈的，一点看不出来。或许这就是一个成熟男人吧👊👊👊。&lt;/p>
&lt;p>这件事情之后，我就在想如果解决不了公司的困境，我不得不为自己的未来早做打算。一方面，我仍旧试图帮助公司扭转局面，作为研发人员的我还跟朱总一起去外勤，但非常遗憾的是那时的我并不具备任何销售能力，眼见帮不上什么忙，我内心心急如焚；另一方面，我觉得我的未来特迷茫，研发我觉得自己不够深入，运维有点浪费能力，想做项目经理吧，我又没有走过标准化流程，真心纠结😖，但我心里很清楚，倘若我继续待下去，情况并不会有所改观，反而随着年龄增大压力会越来越大，我想要改变🏃‍♂️。&lt;/p>
&lt;p>朱总的挽留并没有让我留下，因为我觉得自己无能为力。我一直都特别感激朱总的赏识，觉得自己的离开有点辜负了他，所以我把所有工作做好后才离开的，并没有提前找好工作，这算是对这份工作的一种敬畏吧🙏。&lt;/p>
&lt;blockquote>
&lt;p>我走的时候尹月不知道，因为那时候她请假了。直到我去公司拿「离职证明」时她才知道，我现在还记得特清楚她当时微信里“质问”我：“你是不是觉得在这里学不到什么东西啦，所以你要离开？”“不是的”，我并没有想好要怎样回答。她随后说道：“工作找好了吗？我相信你到哪里都可以一样优秀的。”“还没呢，谢谢祝福！”我内心非常感激，我也一样相信 &lt;strong>我的未来不是梦！&lt;/strong>&lt;/p>
&lt;/blockquote></description></item><item><title>大学</title><link>https://mydream.ink/posts/experiences/university/</link><pubDate>Mon, 01 Jul 2019 17:29:58 +0800</pubDate><guid>https://mydream.ink/posts/experiences/university/</guid><description>&lt;p>&lt;strong>本文记录了自己的个人生活经历及其感悟，以回忆为主，文中所有姓名均为化名，无心对任何人或者是公司造成影响，如果可能发生，请联系我进行修改&lt;/strong>&lt;/p>
&lt;hr>
&lt;h1 id="前言">前言&lt;/h1>
&lt;p>早前基本不怎么接触过计算机的我，大学志愿毫不犹豫地全部填写了计算机相关专业。我很庆幸，当时的自己看到了未来，也很庆幸，我所学即我所爱。最后到了南昌航空大学攻读软件工程专业，只是学费真的好贵💔。&lt;/p>
&lt;h1 id="大一">大一&lt;/h1>
&lt;h2 id="入学">入学&lt;/h2>
&lt;p>上大学是我第一次出远门，也是第一次坐火车，我还记得那趟火车是T126，从成都开往东莞的，坐了大概19+小时。路上遇到一个从成都回襄樊过中秋节的老大哥，他说他是九几年毕业于哈工大一个汽车相关的专业，好像是内燃机啥的，我也记不太清楚了。从来没有讲过普通话的我，在车上跟他聊天，感觉特别扭，并不好意思讲。他跟我讲，我必须要克服，因为我到了学校还是得面对，逃避解决不了问题。所以，我在到学校前就把普通话讲顺畅了。临走时，他还给了我一本《读者》，为啥我还记得呢？也许是因为那是我唯一一本杂志的缘故吧😋。&lt;/p>
&lt;p>刚入学那会儿，住在一个临时宿舍，离学院大部队有点距离，我们有4个人，江西吉安的邹智彬，不会蒙语的内蒙人吴子贺，还有一个不跟我们一个班但跟我是四川老乡却不吃辣的莫天。我们都是外地人，所以虽然在一块儿没待多久，但是整个大学期间关系都非常不错。两周后一方向重新分班，我们仨都觉得微软不错，所以都选了它。我们分班后，便搬离了那里，我们三个还在一个班。邹智彬去了我对面宿舍；莫天回到了他原班级宿舍，与我们隔着几层楼。我和子贺呢，就多了俩新室友，江西吉安的胖子李忠，南昌本地人陈俊刚。莫天和邹智彬后面也经常来我们宿舍玩。&lt;/p>
&lt;blockquote>
&lt;p>注：我们软件学院共有6个方向，1方向（也就是我所在的方向）就叫软件工程，2方向是航空信息化，3方向是会计，4方向是嵌入式，5方向是测控（莫天所在），6方向是飞控。1方向在军训结束后，又分成了3个小方向：测试、Java、微软定向。&lt;/p>
&lt;/blockquote>
&lt;p>初入大学第一天就被骗😱。入学手续差不多办好后，都晚上了。我一个人走在学校食堂旁边，来了一个女孩，说她是一个大四学生，从内蒙过来找她同学的，钱包手机都被偷了，连吃饭的钱都没有了，让我借钱给她吃顿饭。我的天，一顿吃了我17块大洋，要知道我大一每顿自己就花1.1元（一个素菜0.8元，一份饭0.3元），所以当时吃得我特心痛。吃完饭后，她又管我要200块，说是要去南大后街住一晚，我表示没有，她还不死心，说要是不信的话，可以跟她一起过去。可那时候两百块是我一个月的生活费，所以任她说破天，我也不可能给的😝。我只能说，她看出了我是新生，却没看出我穷😆。最终，她放弃了，临走前还记了我电话号码，说是第二天要还我钱。不过，我心里也明白，基本没啥可能，而事实也确实如此。&lt;/p>
&lt;p>大一的我是一个特别自卑的存在，因为自己是从村里来的，见识短浅，怕稍有不慎闹了笑话，暴露了自己乡野匹夫的身份😅，一直不敢在公众场合讲话。别说大学了，初中高中都是如此，但也因此我特别在意自己的格局和视野。那时咱们班上竞选班干部，其实我特想选个班长啥的，奈何我始终没有勇气上台。当时，我们的军训教官（也是我们学长）似乎看出了我的心思，干脆让大家不论参选与否都上台讲几句。然而尽管如此，我还是憋到了最后一个上场，并且待了半天硬是说不出一个字。从那以后，我心里暗自发誓，我需要在公众场合正确地表达自己，甚至于成为一个极具魅力的演说家。&lt;/p>
&lt;p>一直以来，我都害怕考试，害怕在公众场合发言，但我非常清楚这是多么不利于自己发展，因此解决这个问题也成了我的一大目标。为此，我便加入了校团委，和我一道的还有子贺。此外，为了让自己保持良好的生活习惯，我还加入了学校的知行班，每日六点集合跑五公里。&lt;/p>
&lt;h2 id="组建团队">组建团队&lt;/h2>
&lt;p>到了大一下学期，我们开始有专业课了。有一天，莫天突然找到我，说想组建一个团队，一起成长、一起学习，说不定将来还能一起成就一番伟业。说到我心坎儿里了。他做队长，我做副队。我们印了传单，一起在住宿楼的楼道里分发，告诉学院的小伙伴，我们将组建一个了不起的团队，期待志同道合的小伙伴加入。会议当天，我们租了个大音响，然而来的人并不多。不过，我们也正式成立了我们9个人的小分队「莫天、我、文博、彭星、佳莹、郑朋、汤洋、王同、秦风」。我们每天早上6点整集合，跑步到图书馆门口做早操，带着非常夸张的动作高喊三遍：『左脚！右脚！啊！这是多么美好的一天啊，充满了爱、热情、效益、感恩、Power！』💪💪💪，然后进行英语晨读，结束后再一起去食堂吃早餐，为此我退出了知行班；平时有时间就组队去图书馆学习，周末我们就一起去机房练习编码。在早上练习英语时，我们吸引到了第一位女同学韩婷的加入；紧接着，我班上学委慧慧也通过我加入了我们。有个周末，我们如同往常一样，一起去机房进行编程练习。韩婷碰见了她班上的同学奚心，刚好那天奚心有个问题不能解决，韩婷就带她问了莫天，莫天圆满地搞定了。至此，我们又多了一个成员，我们的团队定格在九男三女共十一人。&lt;/p>
&lt;blockquote>
&lt;p>注：莫天成绩一直都是不错的，是他们52班的第一名；而奚心，后来我们了解到，她是11班的第一名；&lt;/p>
&lt;/blockquote>
&lt;h2 id="小美好">小美好&lt;/h2>
&lt;p>有一天，莫天让我去参加一个活动，说是可以提升自我。那是一个演讲训练营，我怀着一颗期待的心过去，果然被感染到了。平时都不舍得花钱的我，也没怎么犹豫就直接支付了300块用于报名参加了一个清明假期的演讲训练营。后来莫天跟我讲，本来只是让我体验下那种氛围的，没成想我会报班😂。&lt;/p>
&lt;p>2014年4月4日晚，我正式加入了训练营，我依稀记得我们好像是3队「与我同校的信工学院的大二妹子李真，南昌大学大三的谢文春，江西财经大学大二的汤洋洋，江西农业大学大一的妹子廖美琴，南昌工学院大一的妹子刘蕖」。那时候的我还特腼腆，知道自己位置后，一个人傻坐在位置上，也不跟人交流。突然有个妹子走过来坐我旁边跟我打招呼，搞得我的脸一下就红了，场面一度很尴尬，可能就这么一见钟情了吧😍。据她介绍，她叫刘蕖，跟我是一队的，至于我当时有没有反过来自我介绍，已经不记得了，应该有吧？😥 选队长的时候，我一如既往地在心里想了想，然后把机会留给了别人（😹感觉自己特无私的，有没有？），最后，汤洋洋成为了我们的队长。&lt;/p>
&lt;p>在这里的三个日夜，想哭就哭、想笑就笑、想说就说，我从未如此毫无顾忌地展示自己，可即便如此，我仍然决定将自己心中对刘蕖的那份爱意埋藏起来直至忘却。😳为何会如此？三天三夜的启发已经让我发生了蜕变，从那时起，我告诉自己：『&lt;strong>去做所有我认为是正确的事情，不论我是否做过、会不会做、敢不敢做、想不想做&lt;/strong>』。在我看来那不是一个正确的时间，那时候的日子过得实在寒酸，我认为自己还没有能力拥有一份爱情，所以基本没想要去争取。同时，我知道同队的谢文春也喜欢她，并且已经展开攻势了，我还想办法撮合他们来着😬。&lt;/p>
&lt;p>训练营结束后，我们团队组织了一次聚餐，我也去了，整个团队都知道谢文春在追她，都想撮合他俩，所以我基本上是计划去当观众的。饭局当天，她来得稍晚一点，大家都把谢文春旁边的位置给她留着（虽然自己并不计划去争取，但看着大家都想成全他们，心中难免还是有些失落，情绪不太高）。然而，等她到了后，她随便找了个理由，非常可爱地拒绝了谢文春的殷勤，还坐到了我旁边来。我是有点愣住了，不过喜欢的女孩坐自己身旁，心情肯定是不错的，那顿饭也是吃得有滋有味的😋。&lt;/p>
&lt;p>后来用 QQ 跟她网上聊天，具体聊的啥，我不太记得了。不过，通过交流我得知，原来她早就知道我对她有意思，刚好她对我也有意思。我问她是否介意我当时的情况，得到她的答复后，我们就在一起了。那是2014年4月24日，我们认识的第20天。所以，我的初恋就这么来了，像做梦一般，以前从不敢想。&lt;/p>
&lt;h2 id="三小项目">三小项目&lt;/h2>
&lt;p>所谓三小呢，就是：小发明、小制作、小创作。得益于莫天的班主任杨丰玉是学院带项目的老师，我们有一个做项目的机会。项目组有4个人「莫天、我、秦风、李磊」，我们用ege做了一套 C 指针动态演示系统帮助教学。项目经费下来后，杨老师给我们买了一些书，其中包括一些ACM的，只是我不知道后来为何不了了之了。只怪自己当初了解太少，不然说什么也要参加比赛才是。&lt;/p>
&lt;blockquote>
&lt;p>总有那么几个人在跨进大学校门前就接触过编程了，李磊就是其中一个，同时他是41班的No.1，一起做项目让我学到了很多，后来他被保送到了厦门大学深造。&lt;/p>
&lt;/blockquote>
&lt;h2 id="暑假工">暑假工&lt;/h2>
&lt;p>跟女朋友一起去东莞去做车辆模型制作的车间工人，每天都是重复的工作，38℃的高温，每天顶着一个巨型电风扇吹，还是觉得身上黏乎乎的。&lt;/p>
&lt;h1 id="大二">大二&lt;/h1>
&lt;h2 id="团队问题">团队问题&lt;/h2>
&lt;p>在经历了大半学期的坚持后，有人早上起不来了，为了团队纪律，我提出了迟到罚款的规定，纪律算是得到了有效控制。一周后郑朋和王同觉得自己不再适合待下去，索性请求离开了，我们剩下的九个人一如既往的在一块儿学习。值得一提的是，此前莫天和奚心走到了一起。&lt;/p>
&lt;p>莫天和奚心出现了问题，奚心说为了团队不受影响，她不得不退出。然而，莫天从此一蹶不振，不理朝事。一日，他把我们都召集到了一起，宣布了他要解散团队的决定。作为他的朋友也是团队发起人之一，我心里很不是滋味，不知道如何面对我们的队员，因为当初的承诺都还没能兑现，如今主心骨突然要退出，实在是有些手足无措。莫天走后，我问队员的想法，问他们是否还想继续，除了秦风要退出，其他人都愿意跟我一起继续下去。只可惜当时的自己眼界有限，并不能为他们带来什么。大三我去了沈阳后，这件事情落下了个不了了之，这是我整个大学阶段无论如何都挥之不去的遗憾，我一直期待着某天还能有机会弥补。&lt;/p>
&lt;h2 id="转专业">转专业&lt;/h2>
&lt;p>原计划大三就要去微软技术中心实训了，然而到了大二结束前的两周，我们被告知项目破产，没办法前去了。原本就觉得在学校学不到什么东西的我，就像是听到了一个噩耗。这时候我得知如果成绩足够优秀可以转专业到四方向，那可以去沈阳东软实训，我不由得动了这个念头，只是我需要考虑的是不得不跟女朋友异地恋了。莫天也想要转过去（我知道他是想逃避一些事情），所以他极力劝我跟他一起。思虑再三，为了自己的前途，我还是决定转过去。&lt;/p>
&lt;h2 id="暑假工-1">暑假工&lt;/h2>
&lt;p>这一年的暑假，我们去了重庆。本来还想进厂找工作的，但是厂里不要暑假工，我们在沙坪坝待了几天，一直也没有办法。没钱住宿，我们只能去网吧，希望省点钱。不幸的是，屋漏偏逢连夜雨，我们在网吧睡觉的时候，女朋友手机被偷了。手机有防盗功能，所以我们有嫌疑人照片，以及实名认证的电话号码，我们报了警，并将信息提供给了派出所，其实只要派出所肯查，肯定能找到的，但我们失败了。&lt;/p>
&lt;p>要是手机没被偷，我们找不到工作，或许就回去了吧？但当时的情况，如果就那样走了，我们不甘心。离开了沙坪坝，我们去了九龙坡，在谢家湾找到了一份烤肉店服务员的工作。之后，我们用挣来的钱给女朋友又重新买了一部手机。&lt;/p>
&lt;h1 id="大三">大三&lt;/h1>
&lt;h2 id="友情">友情&lt;/h2>
&lt;p>到了沈阳后，好长一段时间嘴唇都干巴巴的，没有一点知觉，跟女朋友异地，经常吵架，自己情绪变得特别不稳定。对莫天的不满也在这个时候彻底暴发了出来，于是我们决裂了，这种关系一直持续到毕业（毕业回南昌的火车上，我让我后来的室友金辉帮我将他约出，已是离别时，所有恩怨终是虚无，最终，我们冰释前嫌了，现在都在成都，仍是好友。）。&lt;/p>
&lt;p>我到了新的宿舍，认识了新的室友「姜伟、姜兴、金辉，他们都来自南昌」，这是一群非常有意思的小伙伴，我很快便平复心情，投入到新的征程中去了。在新的班级，我又认识了眭彬橙，他就坐我旁边。跟他交流是一件非常愉快的事，从技术到思想，总能碰撞出很多有意思的东西。&lt;/p>
&lt;blockquote>
&lt;p>眭彬橙是一个高中就自学编程的家伙，上大学之前就已经靠编程赚钱的主，毕业后去了B站，前些天刚得知现在已经到了阿里巴巴。&lt;/p>
&lt;/blockquote>
&lt;h2 id="选择">选择&lt;/h2>
&lt;p>大四那年秋天，一开始我就回到了成都准备秋招的事情。那时候，整日跟川大、电子科大的本科及硕士研究生同台竞技，我的笔试成绩比较好，基本都能获得面试机会。然而，我却被从Oracle出来，一心想做国产数据库的朱贤文吸引了。&lt;/p>
&lt;p>为了实现自己的梦想，放弃了给自己履历镀金的机会，加入一家创业公司实习并作为第一份工作。在现在看来，很难说这是一个明智的选择。但也没啥好后悔的，我做了自己想做的事，成就了今天的自己。整个大四我基本都在这里实习。我将在&lt;a href="../job1">第一份工作&lt;/a>中详细介绍。&lt;/p>
&lt;hr>
&lt;h1 id="总结">总结&lt;/h1>
&lt;p>大一、大二，学习一直跟着学校的节奏在走，尽管学习进度上有所超前，但是没有将时间很好地利用起来。暑假都用来打暑假工了，没有合理利用起来，太奢侈了，觉悟得太晚。团队的不了了之，是我对不起我的队员们，希望将来还有机会弥补。这些无一例外地都让我意识到了格局的重要性。&lt;/p>
&lt;p>一个人的视野和格局决定了他成就的高低，这是我从小到大最切身的感受。小学初中在乡下不知乡镇以外是何处，高中在市里发现城里人真的挺会玩，大学让我体会到了原来知识不仅是知识，出了社会后方知天外有天。学校的不同其实质就是平台所能给的视野和格局的差异。在格局不断变大的同时，人也会变得更加自信，我便是如此。&lt;/p></description></item><item><title>GNU sed用户手册</title><link>https://mydream.ink/posts/tools/gnu-sed/</link><pubDate>Sun, 11 Mar 2018 08:49:39 +0800</pubDate><guid>https://mydream.ink/posts/tools/gnu-sed/</guid><description>&lt;h1 id="1-简介">1 简介&lt;/h1>
&lt;p>&lt;code>sed&lt;/code>是一个流编辑器（Stream EDitor）。流编辑器对输入流（文件或者是来自于管道的输入）进行基本的文件转换。虽然在某些方面类似于脚本化编辑器（例如&lt;code>ed&lt;/code>），但是&lt;code>sed&lt;/code>通过对输入进行一次遍历来工作，因此更高效。比较大的区别是&lt;code>sed&lt;/code>能在管道中筛选文本。&lt;/p>
&lt;h1 id="2-运行">2 运行&lt;/h1>
&lt;h2 id="21-概述">2.1 概述&lt;/h2>
&lt;p>一般的&lt;code>sed&lt;/code>用法：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed SCRIPT INPUTFILE...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>例，将&lt;code>input.txt&lt;/code>中的‘hello’全部替换成‘world’：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果已没有指定INPUTFILE或者INPUTFILE是&lt;code>-&lt;/code>，&lt;code>sed&lt;/code>将处理标准输入的内容，下述命令是等价的：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> &amp;lt; input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat input.txt &lt;span style="color:#111">|&lt;/span> sed &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> - &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>sed&lt;/code>默认是将处理结果写到标准输出。使用&lt;code>-i&lt;/code>参数则是直接修改文件。另见&lt;code>W&lt;/code>和&lt;code>s///w&lt;/code>命令将输出写入到其他文件。下述命令将会修改file.txt文件而不会产生任何输出：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed -i &lt;span style="color:#d88200">&amp;#39;s/hello/world&amp;#39;&lt;/span> file.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>默认情况下，&lt;code>sed&lt;/code>打印所有的输入（修改和删除除外，比如命令&lt;code>d&lt;/code>）。用&lt;code>-n&lt;/code>限制输出，&lt;code>p&lt;/code>打印特定行。以下命令只打印输入文件的第45行：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed -n &lt;span style="color:#d88200">&amp;#39;45p&amp;#39;&lt;/span> file.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>sed&lt;/code>将多个输入文件作为一个长的输入流，在下述例子中，将打印第一个文件（one.txt）的第一行和第三个文件（three.txt）的最后一行。可用&lt;code>-s&lt;/code>反转。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed -n &lt;span style="color:#d88200">&amp;#39;1p ; $p&amp;#39;&lt;/span> one.txt two.txt three.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>若没有指定&lt;code>-e&lt;/code>或者&lt;code>-f&lt;/code>选项，&lt;code>sed&lt;/code>使用第一个非选项参数作为执行脚本，接下来的非选项参数作为输入文件。如果使用&lt;code>-e&lt;/code>或者&lt;code>-f&lt;/code>指定一个执行脚本，其它所有的非参数选项将作为输入文件。&lt;code>-e&lt;/code>和&lt;code>-f&lt;/code>可以结合使用，并且可以多次出现（最终的执行脚本是这些单独脚本连接后的结果）。 下述例子是等价的：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -e &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed --expression&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> &amp;gt; myscript.sed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -f myscript.sed input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed --file&lt;span style="color:#f92672">=&lt;/span>myscript.sed input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="22-命令行选项">2.2 命令行选项&lt;/h2>
&lt;p>调用&lt;code>sed&lt;/code>的完整格式是：&lt;/p>
&lt;pre>&lt;code>sed OPTIONS... [SCRIPT] [INPUTFILE...]
&lt;/code>&lt;/pre>
&lt;p>有如下的命令行选项：&lt;/p>
&lt;p>&lt;code>--version&lt;/code> 打印&lt;code>sed&lt;/code>的版本和版权信息，然后退出&lt;/p>
&lt;p>&lt;code>--help&lt;/code> 打印一个使用信息，简要总结这些命令行选项和错误报告地址，然后退出。&lt;/p>
&lt;p>&lt;code>-n&lt;/code> &lt;code>--quiet&lt;/code> &lt;code>--slient&lt;/code> 默认情况下，sed在每个循环结束时通过脚本打印出模式空间（请参阅参考资料）。这些选项禁用了这种自动打印功能，sed只有在通过p命令明确告知时才会产生输出。&lt;/p>
&lt;p>&lt;code>-e script&lt;/code> &lt;code>--expression=script&lt;/code> 将脚本中的命令添加到处理输入时要运行的一组命令中。&lt;/p>
&lt;p>&lt;code>-f script-file&lt;/code> &lt;code>--file=script-file&lt;/code> 将包含在文件脚本文件中的命令添加到处理输入时要运行的一组命令中。&lt;/p>
&lt;p>&lt;code>-i[SUFFIX]&lt;/code> &lt;code>--in-place[=SUFFIX]&lt;/code> 此选项指定编辑源文件。 &lt;code>sed&lt;/code>通过创建临时文件并将输出发送到此文件而不是标准输出来完成此操作。&lt;/p>
&lt;p>这个选项意味着&lt;code>-s&lt;/code>。&lt;/p>
&lt;p>到达文件末尾时，临时文件将被重命名为输出文件的原始名称。扩展名（如果提供）用于在重命名临时文件之前修改旧文件的名称，从而创建备份副本2）。&lt;br>
该规则如下：如果扩展名不包含_，则将其作为后缀追加到当前文件名的末尾;如果扩展名包含一个或多个_字符，则每个星号都将替换为当前的文件名。这使您可以将前缀添加到备份文件中，而不是后缀，甚至将原始文件的备份副本放到另一个目录（假定该目录已经存在）。&lt;/p>
&lt;p>如果没有提供扩展名，原始文件将被覆盖而不进行备份。&lt;/p>
&lt;p>&lt;code>-l N&lt;/code> &lt;code>--line-length=N&lt;/code> 指定每行默认换行长度，长度为0表示用不换行，默认值为70。&lt;/p>
&lt;p>&lt;code>--posix&lt;/code> GNU sed包括POSIX sed的几个扩展。为了简化书写便携式脚本，这个选项禁用了本手册文档中的所有扩展，包括附加的命令。大多数扩展接受不在POSIX规定的语法之外的sed程序，但其中的一些（如Reporting Bug中描述的N命令的行为）实际上违反了标准。如果只想禁用后一种扩展，则可以将POSIXLY_CORRECT变量设置为非空值。&lt;/p>
&lt;p>&lt;code>-b&lt;/code> &lt;code>--binary&lt;/code> 此选项在每个平台上都可用，但仅在操作系统区分文本文件和二进制文件的情况下才有效。当与MS-DOS，Windows的情况一样时，Cygwin文本文件由回车符和换行符分隔的行组成，&lt;code>sed&lt;/code>不会看到结尾的&lt;code>CR&lt;/code>。当指定这个选项时，&lt;code>sed&lt;/code>将以二进制模式打开输入文件，因此不要求这个特殊处理，并且考虑以换行结束的行。&lt;/p>
&lt;p>&lt;code>--follow-symlinks&lt;/code> 此选项仅在支持符号链接的平台上可用，并且仅在指定选项&lt;code>-i&lt;/code>时才有效。在这种情况下，如果命令行中指定的文件是符号链接，&lt;code>sed&lt;/code>将跟随链接并编辑链接的最终目标。默认行为是破坏符号链接，以便链接目标不会被修改。&lt;/p>
&lt;p>&lt;code>-E&lt;/code> &lt;code>-r&lt;/code> &lt;code>--regexp-extended&lt;/code> 使用扩展正则表达式而不是基本正则表达式。扩展的正则表达式是那些&lt;code>egrep&lt;/code>接受的;通常有较少的反斜杠，表达更清楚。从历史上看，这是一个GNU扩展，但是&lt;code>-E&lt;/code>扩展已经被添加到&lt;a href="http://austingroupbugs.net/view.php?id=528">POSIX标准&lt;/a>，因此使用&lt;code>-E&lt;/code>来实现可移植性。GNU的sed已经接受&lt;code>-E&lt;/code>作为未记录选项， BSD 的sed也已经接受&lt;code>-E&lt;/code>很长一段时间了，但使用&lt;code>-E&lt;/code>的脚本可能无法移植到其他较老的系统。请参阅扩展正则表达式。&lt;/p>
&lt;p>&lt;code>-s&lt;/code> &lt;code>--separate&lt;/code> 默认情况下，sed会将命令行中指定的文件视为一个连续的长流。这个GNU的sed的扩展允许用户将它们视为单独的文件：范围地址（如&amp;rsquo;/abc/,/def/&amp;rsquo;）不能跨越多个文件，行号是相对于每个文件的开始，$是指到每个文件的最后一行，并且从&lt;code>R&lt;/code>命令调用的文件在每个文件的开始处倒回。&lt;/p>
&lt;p>&lt;code>--sandbox&lt;/code> 在沙箱模式下，&lt;code>e/w/r&lt;/code>命令被拒绝——包含它们的程序将被中止而不运行。沙箱模式可确保&lt;code>sed&lt;/code>仅在命令行上指定的输入文件上运行，并且不能运行外部程序。&lt;/p>
&lt;p>&lt;code>-u&lt;/code> &lt;code>--unbuffered&lt;/code> 缓冲输入和输出尽可能最小。 （如果输入来自&lt;code>tail -f&lt;/code>之类的输入，那么这个特别有用，而且希望尽快看到转换后的输出。）&lt;/p>
&lt;p>&lt;code>-z&lt;/code> &lt;code>--null-data&lt;/code> &lt;code>--zero-terminated&lt;/code> 将输入视为一组行，每个行以零字节（ASCII&amp;rsquo;NUL&amp;rsquo;字符）而不是换行符结尾。这个选项可以用于像&lt;code>sort -z&lt;/code>和&lt;code>find -print0&lt;/code>这样的命令来处理任意文件名。 如果命令行中没有给出&lt;code>-e&lt;/code>，&lt;code>-f&lt;/code>，&lt;code>--expression&lt;/code>或&lt;code>--file&lt;/code>选项，则命令行上的第一个非选项参数将被视为要执行的脚本。 如果在处理完上面的命令行参数后仍然存在，则这些参数将被解释为要处理的输入文件的名称。文件名“-”是指标准输入流。如果没有指定文件名称，标准输入将被处理。&lt;/p>
&lt;h2 id="23-退出状态">2.3 退出状态&lt;/h2>
&lt;p>零退出状态指示成功，非零值表示失败。 GNU sed退出状态，但返回以下错误值： &lt;strong>0&lt;/strong> 成功完成 &lt;strong>1&lt;/strong> 命令无效，语法无效，正则表达式无效或与&lt;code>--posix&lt;/code>一起使用的GNU sed的扩展命令。 &lt;strong>2&lt;/strong> 命令行上指定的一个或多个输入文件无法打开（例如，如果找不到文件，或者读权限被拒绝）。处理继续与其他文件。 &lt;strong>4&lt;/strong> 一个I/O错误或运行时严重的处理错误，GNU sed立即中止。 另外，命令q和Q可以用来以一个自定义的退出代码值来终止sed（这是一个GNU sed扩展）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ &lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#111">|&lt;/span> sed &lt;span style="color:#d88200">&amp;#39;Q42&amp;#39;&lt;/span> &lt;span style="color:#111">;&lt;/span> &lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#111">$?&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">42&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="3-sed脚本">3 &lt;code>sed&lt;/code>脚本&lt;/h1>
&lt;h2 id="31-sed脚本概述">3.1 &lt;code>sed&lt;/code>脚本概述&lt;/h2>
&lt;p>一个&lt;code>sed&lt;/code>程序由一个或多个&lt;code>sed&lt;/code>命令组成，这些命令由一个或多个-e，-f，&amp;ndash;expression和&amp;ndash;file选项传入，或者如果没有这些选项，则由第一个非选项参数传入。本文档当中所有的 &lt;em>&lt;code>sed&lt;/code>脚本&lt;/em> 均表示所有传入脚本连接后的结果。 &lt;code>sed&lt;/code>命令语法如下：&lt;/p>
&lt;pre>&lt;code>[addr]X[options]
&lt;/code>&lt;/pre>
&lt;p>X是单字符的&lt;code>sed&lt;/code>命令。[addr]是一个可选的行地址。如果[addr]被指定，则X命令只在指定行执行。[addr]可以是一个行号，一个正则表达式也可是一个行范围（参见&lt;a href="#sed%E5%9C%B0%E5%9D%80">&lt;code>sed&lt;/code>地址&lt;/a>）。其它选项被用于某些&lt;code>sed&lt;/code>命令。 下述的例子将在输入文件中删除第30至35行。&lt;code>30,35&lt;/code>是地址范围。&lt;code>d&lt;/code>是删除命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;30,35d&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>以下示例将打印所有输入，直到找到以“foo”开头的行。如果找到这一行，&lt;code>sed&lt;/code>将以状态42退出。如果没有找到这样的行（并且没有发生其他错误），则sed将以状态0退出。&lt;code>/^foo/&lt;/code>是一个正则表达式地址，&lt;code>q&lt;/code>是退出命令，&lt;code>42&lt;/code>是命令选项。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;/^foo/q42&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>脚本或脚本文件中的命令可以用分号（;）或换行符（ASCII 10）分隔。可以使用&lt;code>-e&lt;/code>或&lt;code>-f&lt;/code>选项指定多个脚本。 以下例子都是等价的。它们执行两个&lt;code>sed&lt;/code>操作：删除与正则表达式&lt;code>/^foo/&lt;/code>匹配的所有行，并用‘&lt;code>world&lt;/code>’替换所有出现的字符串‘&lt;code>hello&lt;/code>’：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed &lt;span style="color:#d88200">&amp;#39;/^foo/d ; s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -e &lt;span style="color:#d88200">&amp;#39;/^foo/d&amp;#39;&lt;/span> -e &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#39;/^foo/d&amp;#39;&lt;/span> &amp;gt; script.sed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> &amp;gt;&amp;gt; script.sed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -f script.sed input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#39;s/hello/world/&amp;#39;&lt;/span> &amp;gt; script2.sed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -e &lt;span style="color:#d88200">&amp;#39;/^foo/d&amp;#39;&lt;/span> -f script2.sed input.txt &amp;gt; output.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>命令&lt;code>a&lt;/code>，&lt;code>c&lt;/code>，&lt;code>i&lt;/code>由于它们的语法，所以不能使用作为命令分隔符的分号，因此应该用换行符结尾或是放在脚本或脚本文件的末尾。命令也能以可选的不重要的空白字符开头。请参阅&lt;a href="#%E5%A4%9A%E5%91%BD%E4%BB%A4%E8%AF%AD%E6%B3%95">多命令语法&lt;/a>。&lt;/p>
&lt;h2 id="32-sed命令汇总">3.2 &lt;code>sed&lt;/code>命令汇总&lt;/h2>
&lt;p>GNU sed支持以下命令。有些是标准的POSIX命令，而另一些是GNU扩展。每个命令的详细信息和示例在以下部分。（助记符）显示在括号内。&lt;/p>
&lt;p>&lt;code>a\&lt;/code>&lt;br/>
&lt;code>text&lt;/code> 在一行之后追加文本。&lt;/p>
&lt;p>&lt;code>a text&lt;/code> 在一行之后追加文本（替代语法）。&lt;/p>
&lt;p>&lt;code>b label&lt;/code> 无条件地分支标签。标签可以省略，在这种情况下下一个周期开始。&lt;/p>
&lt;p>&lt;code>c\&lt;/code>&lt;br/>
&lt;code>text&lt;/code> 用文本替换（更改）行。&lt;/p>
&lt;p>&lt;code>c text&lt;/code> 用文本替换（更改）行（替代语法）。&lt;/p>
&lt;p>&lt;code>d&lt;/code> 删除模式空间;立即开始下一个周期。&lt;/p>
&lt;p>&lt;code>D&lt;/code> 如果模式空间包含换行符，则删除模式空间中的文本直到第一个换行符，然后用结果模式空间重新启动循环，而不读取新的输入行。如果模式空间不包含换行符，就像&lt;code>d&lt;/code>命令一样，开始一个正常的新周期。&lt;/p>
&lt;p>&lt;code>e&lt;/code> 执行在模式空间中找到的命令，并用输出替换模式空间；尾随的换行符被压制。&lt;/p>
&lt;p>&lt;code>F&lt;/code> （filename）打印当前输入文件的文件名（带有换行符）。&lt;/p>
&lt;p>&lt;code>g&lt;/code> 用占位空间的内容替换模式空间的内容。&lt;/p>
&lt;p>&lt;code>G&lt;/code> 向模式空间的内容追加换行符，然后将保留空间的内容追加到模式空间的内容。&lt;/p>
&lt;p>&lt;code>h&lt;/code> （hold）将保持空间的内容替换为模式空间的内容。&lt;/p>
&lt;p>&lt;code>H&lt;/code> 追加一个换行符到保存空间的内容，然后将模式空间的内容追加到保存空间的内容。&lt;/p>
&lt;p>&lt;code>i\&lt;/code>&lt;br/>
&lt;code>text&lt;/code> 在一行之前插入文本。&lt;/p>
&lt;p>&lt;code>i text&lt;/code> 在一行之前插入文本（替代语法）。&lt;/p>
&lt;p>&lt;code>l&lt;/code> 以确定的形式打印模式空间&lt;/p>
&lt;p>&lt;code>n&lt;/code> （next）如果未禁用自动打印，请打印模式空间，然后用下一行输入替换模式空间。如果没有更多的输入，那么&lt;code>sed&lt;/code>退出而不处理任何更多的命令。&lt;/p>
&lt;p>&lt;code>N&lt;/code> 向模式空间添加换行符，然后将下一行输入添加到模式空间。如果没有更多的输入，那么&lt;code>sed&lt;/code>退出而不处理任何更多的命令。&lt;/p>
&lt;p>&lt;code>p&lt;/code> 打印模式空间&lt;/p>
&lt;p>&lt;code>P&lt;/code> 打印模式空间，直到第一个&amp;lt;换行符&amp;gt;。&lt;/p>
&lt;p>&lt;code>q[exit-code]&lt;/code> （quit）退出sed而不处理任何更多的命令或输入。&lt;/p>
&lt;p>&lt;code>Q[exit-code]&lt;/code> （quit）该命令与q相同，但不会打印模式空间的内容。像q一样，它提供了将退出代码返回给调用者的功能。&lt;/p>
&lt;p>&lt;code>r filename&lt;/code> 读文本文件的一个文件。&lt;/p>
&lt;p>&lt;code>R filename&lt;/code> 在当前周期结束时排队读取一行文件名并将其插入到输出流中，或读取下一个输入行时。&lt;/p>
&lt;p>&lt;code>s/regexp/replacement/[flags]&lt;/code> （substitue）匹配正则表达式和模式空间的内容。如果找到，用替换替换匹配的字符串。&lt;/p>
&lt;p>&lt;code>t label&lt;/code> （test）分支只有在自上次输入行被读取或进行了条件分支以来已成功进行替换时才进行标记。标签可以省略，在这种情况下下一个周期开始。&lt;/p>
&lt;p>&lt;code>T label&lt;/code> （test）分支只有在自上一条输入行被读取或条件分支被采用以来没有成功的替换时才被标记。标签可以省略，在这种情况下下一个周期开始。&lt;/p>
&lt;p>&lt;code>v [version]&lt;/code> （version）这个命令什么都不做，但是如果不支持GNU sed扩展，或者如果请求的版本不可用，sed会失败&lt;/p>
&lt;p>&lt;code>w filename&lt;/code> 将模式空间写入文件名。&lt;/p>
&lt;p>&lt;code>W filename&lt;/code> 写入给定的文件名模式空间的部分直到第一个换行符&lt;/p>
&lt;p>&lt;code>x&lt;/code> 交换持有和模式空间的内容。&lt;/p>
&lt;p>&lt;code>y/src/dst/&lt;/code> 将模式空间中所有与任何源字符匹配的字符与目标字符中的对应字符进行替换。&lt;/p>
&lt;p>&lt;code>z&lt;/code> （zap）该命令清空模式空间的内容。&lt;/p>
&lt;p>&lt;code>#&lt;/code> 注释，直到下一个换行。&lt;/p>
&lt;p>&lt;code>{ cmd ; cmd ... }&lt;/code> 将几个命令一起分组。&lt;/p>
&lt;p>&lt;code>=&lt;/code> 打印当前输入行号（带有换行符）。&lt;/p>
&lt;p>&lt;code>: label&lt;/code> 指定分支命令（b，t，T）的标签位置。&lt;/p>
&lt;h2 id="33-s命令">3.3 &lt;code>s&lt;/code>命令&lt;/h2>
&lt;p>&lt;code>s&lt;/code>命令（替换）在sed中可能是最重要的，并且有很多不同的选项。&lt;/p>
&lt;p>&lt;code>s&lt;/code>命令的语法是&lt;code>s/regexp/replacement/flags&lt;/code>。&lt;/p>
&lt;p>它的基本概念很简单：&lt;code>s&lt;/code>命令尝试将模式空间与提供的正则表达式&lt;code>regexp&lt;/code>进行匹配；如果匹配成功，则匹配的那部分模式空间被&lt;code>replacement&lt;/code>替换。&lt;/p>
&lt;p>有关&lt;code>regexp&lt;/code>语法的详细信息，请参阅正则表达式地址。&lt;/p>
&lt;p>&lt;code>replacement&lt;/code>可以包含\n（n是从1到9的数字，包括1和9），它是指包含在第n个（及其匹配的\）之间的匹配部分。此外，&lt;code>replacement&lt;/code>可以包含引用模式空间的整个匹配部分的非转义&lt;code>＆&lt;/code>字符。 在任何给定的&lt;code>s&lt;/code>命令中，&lt;code>/&lt;/code>字符可以被任何其他单个字符统一替换。 &lt;code>/&lt;/code>字符（或任何其他替代字符）可以出现在&lt;code>regexp&lt;/code>或&lt;code>replacement&lt;/code>中，只要前面有一个&lt;code>\&lt;/code>字符。 最后，作为一个GNU &lt;code>sed&lt;/code>扩展，你可以包含一个由反斜杠和字母&lt;code>L&lt;/code>，&lt;code>l&lt;/code>，&lt;code>U&lt;/code>，&lt;code>u&lt;/code>或&lt;code>E&lt;/code>中的一个组成的特殊序列。含义如下： &lt;code>\L&lt;/code> 替换为小写，直到找到&lt;code>\U&lt;/code>或&lt;code>\E&lt;/code>， &lt;code>\l&lt;/code> 将下一个字母转为小写， &lt;code>\U&lt;/code> 替换为大写，直到找到&lt;code>\L&lt;/code>或&lt;code>\E&lt;/code>， &lt;code>\u&lt;/code> 将下一个字符转为大写， &lt;code>\E&lt;/code> 停止由&lt;code>\L&lt;/code>或者&lt;code>\U&lt;/code>开始的大小写转换 使用&lt;code>g&lt;/code>标志时，大小写转换不会从正则表达式的一次出现传播到另一次。例如，当在模式空间中使用&lt;code>a-b-&lt;/code>执行以下命令时：&lt;/p>
&lt;pre>&lt;code>s/\(b\?\)-/x\u\1/g
&lt;/code>&lt;/pre>
&lt;p>输出是&lt;code>axxB&lt;/code>。当替换第一个&amp;rsquo;&lt;code>-&lt;/code>时，&lt;code>\u&lt;/code>序列只影响&lt;code>\1&lt;/code>的空白替换。它不会影响用&lt;code>xB&lt;/code>替换&lt;code>b-&lt;/code>时添加到模式空间的&lt;code>x&lt;/code>字符。 另一方面，如果&lt;code>\l&lt;/code>和&lt;code>\u&lt;/code>之后是空替换，确实会影响替换文本的其余部分。在模式空间中使用&lt;code>a-b-&lt;/code>，使用以下命令：&lt;/p>
&lt;pre>&lt;code>s/\(b\?\)-/\u\1x/g
&lt;/code>&lt;/pre>
&lt;p>将用&lt;code>X&lt;/code>（大写）替换&lt;code>-&lt;/code>和用&lt;code>Bx&lt;/code>代替&lt;code>b-&lt;/code>。如果这种行为是不可取的，那么在这种情况下，可以通过在&lt;code>\1&lt;/code>之后添加一个&lt;code>\E&lt;/code>序列来阻止它。 要在最终替换中包含文字&lt;code>\&lt;/code>，&lt;code>＆&lt;/code>或换行符，请务必在所需的&lt;code>\&lt;/code>，&lt;code>＆&lt;/code>或换行符之前加上转义符&lt;code>\&lt;/code>。 &lt;code>s&lt;/code>命令后面可以跟零个或多个以下&lt;code>flags&lt;/code>：&lt;/p>
&lt;p>&lt;code>g&lt;/code> 将所有匹配的替换应用于正则表达式，而不仅仅是第一个。&lt;/p>
&lt;p>&lt;code>number&lt;/code> 只替换正则表达式的第&lt;code>number&lt;/code>个匹配。 &lt;code>s&lt;/code>命令中的交互注意：POSIX标准没有规定当你将&lt;code>g&lt;/code>和&lt;code>number&lt;/code>修饰符混合时会发生什么，而且目前在&lt;code>sed&lt;/code>实现中没有统一的意见。&lt;/p>
&lt;p>对于GNU &lt;code>sed&lt;/code>，交互被定义为：在第&lt;code>number&lt;/code>之前忽略匹配，然后匹配并替换第&lt;code>number&lt;/code>后的所有匹配。 &lt;code>p&lt;/code> 如果进行替换，则打印新的模式空间。 注意：当指定&lt;code>p&lt;/code>和&lt;code>e&lt;/code>选项时，两者的相对顺序会产生非常不同的结果。一般来说，&lt;code>ep&lt;/code>（评估然后打印）是你想要的，但是反过来操作对于调试是有用的。由于这个原因，当前版本的GNU &lt;code>sed&lt;/code>特别解释了在&lt;code>e&lt;/code>之前和之后的&lt;code>p&lt;/code>选项的存在，在评估之前和之后打印模式空间，而&lt;code>s&lt;/code>命令的一般标志只显示其效果一次。这种行为虽然有文件记录，但在将来的版本中可能会发生变化。&lt;/p>
&lt;p>&lt;code>w filename&lt;/code> 如果进行了替换，则将结果写入指定的文件。作为一个GNU sed扩展，支持两个特殊的filename值：&lt;code>/dev/stderr&lt;/code>，将结果写入标准错误，&lt;code>/dev/stdout&lt;/code>写入标准输出。 &lt;code>e&lt;/code> 这个命令允许将命令从shell命令输入到模式空间。如果已经进行替换，则会执行在模式空间中找到的命令，并将模式空间替换为其输出。尾随的换行符被压制；如果要执行的命令包含NUL字符，则结果是不确定的。这是一个GNU &lt;code>sed&lt;/code>扩展。&lt;/p>
&lt;p>&lt;code>I&lt;/code>&lt;br>
&lt;code>i&lt;/code> 用于正则表达式匹配的&lt;code>I&lt;/code>修饰符是一个GNU扩展，它以不区分大小写的方式使&lt;code>sed&lt;/code>匹配正则表达式。&lt;/p>
&lt;p>&lt;code>M&lt;/code>&lt;br>
&lt;code>m&lt;/code> 正则表达式匹配的&lt;code>M&lt;/code>修饰符是一个GNU &lt;code>sed&lt;/code>扩展，它指示GNU &lt;code>sed&lt;/code>在多行模式下匹配正则表达式。该修饰符使&lt;code>^&lt;/code>和&lt;code>$&lt;/code>分别匹配（除了正常行为）换行之后的空字符串和换行之前的空字符串。有特殊的字符序列（&lt;code>\` &lt;/code>和&lt;code>\'&lt;/code>）总是匹配缓冲区的开始或结束。此外，句点字符在多行模式下与换行字符不匹配。&lt;/p></description></item><item><title>使用 kubeadm 安装 Kubernetes 1.10.0</title><link>https://mydream.ink/posts/cloud-native/kubernetes/kubeadm/</link><pubDate>Fri, 09 Mar 2018 00:00:00 +0000</pubDate><guid>https://mydream.ink/posts/cloud-native/kubernetes/kubeadm/</guid><description>&lt;p>kubernetes可以使用多种容器&lt;code>runtime&lt;/code>，此处使用当前最常见的&lt;code>docker&lt;/code>。&lt;/p>
&lt;h2 id="安装docker">安装docker&lt;/h2>
&lt;p>在各种机器上安装docker的方式不同，我曾翻译过一篇ubuntu安装docker的文章，还有一些没来的及翻译的，可以参考官方文档。推荐的docker版本为v1.12，v1.11, v1.13 和 17.03 也行，其它的官方就没测试过了，不过我使用当前最新的v18.03也行。&lt;/p>
&lt;h1 id="docker官方">docker官方&lt;/h1>
&lt;ol>
&lt;li>&lt;a href="https://www.geekare.com/2018/01/07/ubuntu%E5%AE%89%E8%A3%85-docker%E7%A4%BE%E5%8C%BA%E7%89%88/">使用Ubuntu安装docker-ce&lt;/a>&lt;/li>
&lt;li>Mac和Windows桌面安装docker-ce
&lt;ul>
&lt;li>下载&lt;a href="https://download.docker.com/mac/stable/Docker.dmg">Mac版本&lt;/a>的docker-ce，执行即安装&lt;/li>
&lt;li>下载&lt;a href="https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe">Windows版本&lt;/a>的docker-ce，执行即可安装&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://docs.docker.com/install/linux/docker-ce/centos/">CentOS安装docker-ce&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.docker.com/install/linux/docker-ce/fedora/">Fedora安装docker-ce&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.docker.com/install/linux/docker-ce/binaries/">二进制安装方式&lt;/a>&lt;/li>
&lt;/ol>
&lt;h1 id="kubernetes推荐">&lt;strong>kubernetes推荐&lt;/strong>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>Ubuntu/Debian/HypriotOS 从Ubuntu的库安装&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get install -y docker.io
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用docker库安装v17.03&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get install -y &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> apt-transport-https &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> ca-certificates &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> curl &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> software-properties-common
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg &lt;span style="color:#111">|&lt;/span> apt-key add -
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>add-apt-repository &lt;span style="color:#8045ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8045ff">&lt;/span> &lt;span style="color:#d88200">&amp;#34;deb https://download.docker.com/linux/&lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>. /etc/os-release&lt;span style="color:#111">;&lt;/span> &lt;span style="color:#111">echo&lt;/span> &lt;span style="color:#d88200">&amp;#34;&lt;/span>&lt;span style="color:#111">$ID&lt;/span>&lt;span style="color:#d88200">&amp;#34;&lt;/span>&lt;span style="color:#00a8c8">)&lt;/span>&lt;span style="color:#d88200"> \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> &lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>lsb_release -cs&lt;span style="color:#00a8c8">)&lt;/span>&lt;span style="color:#d88200"> \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200"> stable&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get update &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> apt-get install -y docker-ce&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>apt-cache madison docker-ce &lt;span style="color:#111">|&lt;/span> grep 17.03 &lt;span style="color:#111">|&lt;/span> head -1 &lt;span style="color:#111">|&lt;/span> awk &lt;span style="color:#d88200">&amp;#39;{print $3}&amp;#39;&lt;/span>&lt;span style="color:#00a8c8">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>CentOS/RHEL/Fedora&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>yum install -y docker
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> docker &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> systemctl start docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>Container Linux&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> docker &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> systemctl start docker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>如果安装速度太慢建议翻墙先把安装包下载回来。 文末附了红帽离线包供下载。&lt;/p>
&lt;h2 id="安装kubeadmkubectl和kubelet">安装kubeadm、kubectl和kubelet&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>Ubuntu/Debian/HypriotOS&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>apt-get update &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> apt-get install -y apt-transport-https curl
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg &lt;span style="color:#111">|&lt;/span> apt-key add -
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt;EOF &amp;gt;/etc/apt/sources.list.d/kubernetes.list
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">deb http://apt.kubernetes.io/ kubernetes-xenial main
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt-get install -y kubelet kubeadm kubectl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>CentOS/RHEL/Fedora&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt;EOF &amp;gt; /etc/yum.repos.d/kubernetes.repo
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">[kubernetes]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">name=Kubernetes
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">enabled=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">gpgcheck=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">repo_gpgcheck=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>setenforce &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>yum install -y kubelet kubeadm kubectl
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> kubelet &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> systemctl start kubelet
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>确保net.bridge.bridge-nf-call-iptables值为1&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cat &lt;span style="color:#d88200">&amp;lt;&amp;lt;EOF &amp;gt; /etc/sysctl.d/k8s.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">net.bridge.bridge-nf-call-ip6tables = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">net.bridge.bridge-nf-call-iptables = 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#d88200">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sysctl --system
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>Container Linux 先安装CNI插件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">CNI_VERSION&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#d88200">&amp;#34;v0.6.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /opt/cni/bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -L &lt;span style="color:#d88200">&amp;#34;https://github.com/containernetworking/plugins/releases/download/&lt;/span>&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">CNI_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>&lt;span style="color:#d88200">/cni-plugins-amd64-&lt;/span>&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">CNI_VERSION&lt;/span>&lt;span style="color:#d88200">}&lt;/span>&lt;span style="color:#d88200">.tgz&amp;#34;&lt;/span> &lt;span style="color:#111">|&lt;/span> tar -C /opt/cni/bin -xz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>安装 kubeadm, kubelet, kubectl 并添加kubelet systemd服务：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">RELEASE&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#d88200">&amp;#34;&lt;/span>&lt;span style="color:#00a8c8">$(&lt;/span>curl -sSL https://dl.k8s.io/release/stable.txt&lt;span style="color:#00a8c8">)&lt;/span>&lt;span style="color:#d88200">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /opt/bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">cd&lt;/span> /opt/bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -L --remote-name-all https://storage.googleapis.com/kubernetes-release/release/&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">RELEASE&lt;/span>&lt;span style="color:#d88200">}&lt;/span>/bin/linux/amd64/&lt;span style="color:#f92672">{&lt;/span>kubeadm,kubelet,kubectl&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chmod +x &lt;span style="color:#f92672">{&lt;/span>kubeadm,kubelet,kubectl&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -sSL &lt;span style="color:#d88200">&amp;#34;https://raw.githubusercontent.com/kubernetes/kubernetes/&lt;/span>&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">RELEASE&lt;/span>&lt;span style="color:#d88200">}&lt;/span>&lt;span style="color:#d88200">/build/debs/kubelet.service&amp;#34;&lt;/span> &lt;span style="color:#111">|&lt;/span> sed &lt;span style="color:#d88200">&amp;#34;s:/usr/bin:/opt/bin:g&amp;#34;&lt;/span> &amp;gt; /etc/systemd/system/kubelet.service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p /etc/systemd/system/kubelet.service.d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -sSL &lt;span style="color:#d88200">&amp;#34;https://raw.githubusercontent.com/kubernetes/kubernetes/&lt;/span>&lt;span style="color:#d88200">${&lt;/span>&lt;span style="color:#111">RELEASE&lt;/span>&lt;span style="color:#d88200">}&lt;/span>&lt;span style="color:#d88200">/build/debs/10-kubeadm.conf&amp;#34;&lt;/span> &lt;span style="color:#111">|&lt;/span> sed &lt;span style="color:#d88200">&amp;#34;s:/usr/bin:/opt/bin:g&amp;#34;&lt;/span> &amp;gt; /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>enable并启动kubelet&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl &lt;span style="color:#111">enable&lt;/span> kubelet &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> systemctl start kubelet
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h2 id="在主节点上配置kubelet使用的cgroup驱动程序">在主节点上配置kubelet使用的cgroup驱动程序&lt;/h2>
&lt;p>确保docker使用的cgroup驱动和kubelet一样&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>docker info &lt;span style="color:#111">|&lt;/span> grep -i cgroup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果不同则，使kubelet与docker的cgroup驱动相同&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sed -i &lt;span style="color:#d88200">&amp;#34;s/cgroup-driver=systemd/cgroup-driver=cgroupfs/g&amp;#34;&lt;/span> /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>重启kubelet&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl daemon-reload
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl restart kubelet
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>文末附了红帽离线包供下载。&lt;/p>
&lt;h2 id="创建集群httpskubernetesiodocssetupindependentcreate-cluster-kubeadm">&lt;a href="https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/">创建集群&lt;/a>&lt;/h2>
&lt;h2 id="离线安装">离线安装&lt;/h2>
&lt;blockquote>
&lt;p>yum安装离线资源 docker load 装载离线的docker镜像&lt;/p>
&lt;/blockquote>
&lt;h1 id="资源">资源&lt;/h1>
&lt;ul>
&lt;li>CentOS安装kubeadm需要的相关资源：（链接: &lt;a href="https://pan.baidu.com/s/1r7-LqH1Ju-5YTAIvRTcIUg">https://pan.baidu.com/s/1r7-LqH1Ju-5YTAIvRTcIUg&lt;/a> 密码: c2nf）&lt;/li>
&lt;li>安装kubernetes需要的Docker镜像：（链接: &lt;a href="https://pan.baidu.com/s/1fGQtWnuvxJ7pXK5attP8Jw">https://pan.baidu.com/s/1fGQtWnuvxJ7pXK5attP8Jw&lt;/a> 密码: mty5）&lt;/li>
&lt;/ul></description></item><item><title>Node使用RabbitMQ：远程过程调用（RPC）</title><link>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-rpc-with-node/</link><pubDate>Fri, 16 Feb 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-rpc-with-node/</guid><description>&lt;h1 id="前提条件">前提条件&lt;/h1>
&lt;p>本教程假定RabbitMQ已安装并在标准端口（5672）上的本地主机上运行。如果您使用不同的主机，端口或凭据，连接设置将需要调整。&lt;/p>
&lt;h1 id="使用amqpnode客户端">（使用amqp.node客户端）&lt;/h1>
&lt;p>在第二个教程中，我们学习了如何使用工作队列在多个Worker之间分配耗时的任务。 但是如果我们需要在远程计算机上运行一个函数并等待结果呢？那么，这是件不同的事。这种模式通常被称为远程过程调用或RPC。 在本教程中，我们将使用RabbitMQ构建一个RPC系统：一个客户端和一个可扩展的RPC服务器。由于我们没有任何值得分发的耗时任务，所以我们将创建一个返回斐波那契数列的虚拟RPC服务。&lt;/p>
&lt;h2 id="回调队列">回调队列&lt;/h2>
&lt;p>一般来讲，通过RabbitMQ使用RPC是非常容易的。客户端发送请求消息，服务器回复响应消息。为了收到回应，请求时，我们需要发送一个&lt;code>callback&lt;/code>队列地址。我们可以使用默认队列。让我们试试看：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">exclusive&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;rpc_queue&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span>&lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;10&amp;#39;&lt;/span>&lt;span style="color:#111">),&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">replyTo&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#75af00">queue_name&lt;/span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">#&lt;/span> &lt;span style="color:#111">...&lt;/span> &lt;span style="color:#75af00">then&lt;/span> &lt;span style="color:#75af00">code&lt;/span> &lt;span style="color:#75af00">to&lt;/span> &lt;span style="color:#75af00">read&lt;/span> &lt;span style="color:#75af00">a&lt;/span> &lt;span style="color:#75af00">response&lt;/span> &lt;span style="color:#75af00">message&lt;/span> &lt;span style="color:#75af00">from&lt;/span> &lt;span style="color:#75af00">the&lt;/span> &lt;span style="color:#75af00">callback&lt;/span> &lt;span style="color:#75af00">queue&lt;/span> &lt;span style="color:#111">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>消息属性&lt;/strong> AMQP 0-9-1协议预定义了一组与消息一起的14个属性。除了以下属性，大多数属性很少使用： &lt;code>persistent&lt;/code>：将消息标记为persistent（值为true）或transient（false）。您可能会从第二个教程中了解到这个属性。 &lt;code>content_type&lt;/code>：用于描述编码的MIME类型。例如，对于经常使用的JSON编码，将此属性设置为&lt;code>application/json&lt;/code>是一个很好的习惯。 &lt;code>reply_to&lt;/code>：通常用于命名回调队列。 &lt;code>correlation_id&lt;/code>：用于将RPC响应与请求相关联。&lt;/p>
&lt;/blockquote>
&lt;h2 id="关联标识correlation-id">关联标识（correlation id）&lt;/h2>
&lt;p>在上面介绍的方法中，我们建议为每个RPC请求创建一个回调队列。这是非常低效的，但幸运的是有一个更好的方法——让我们创建一个单一的客户端回调队列。 这引发了一个新的问题，在该队列中收到回复，不清楚回复属于哪个请求。什么时候使用&lt;code>correlation_id&lt;/code>属性？我们将把它设置为每个请求的唯一值。之后，当我们在回调队列中收到一条消息时，我们将查看这个属性，并基于这个属性，我们可以将响应与请求进行匹配。如果我们看到一个未知的&lt;code>correlation_id&lt;/code>值，我们可以放心地丢弃这个消息——这不属于我们的请求。 您可能会问，为什么我们应该忽略回调队列中的未知消息，而不是失败并报一个错误？这是由于在服务器端的竞争条件的可能性。虽然不太可能，但在发送给我们答复之后，在发送请求的确认消息之前，RPC服务器可能会死亡。如果发生这种情况，重新启动的RPC服务器将再次处理该请求。这就是为什么在客户端，我们必须优雅地处理重复的响应，理想情况下RPC应该是幂等的。&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/python-six.png" alt="">&lt;/p>
&lt;p>我们的RPC调用像这样： 当客户端启动的时候，它创建一个匿名的独占式回调队列。 对于一个RPC请求，客户端会发送具有这两个属性的消息：&lt;code>reply_to&lt;/code>，用于设置回调队列；&lt;code>correlation_id&lt;/code>，作为请求标识。 请求被发送到一个&lt;code>rpc_queue&lt;/code>队列。 RPC Worker（又名：服务器）在队列上等待请求。当出现一个请求时，它执行这个工作，并使用reply_to字段中的队列将结果发送回客户端。 客户端在回调队列中等待数据。出现消息时，会检查&lt;code>correlation_id&lt;/code>属性。如果它匹配来自请求的值，则返回对应用程序的响应。&lt;/p>
&lt;h2 id="全部代码">全部代码&lt;/h2>
&lt;p>斐波那契函数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">function&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">return&lt;/span> &lt;span style="color:#75af00">n&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">return&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们声明斐波那契函数。它只假定有效的正整数输入（不要指望这个函数可以使用大数字，因为这可能是最慢的递归实现）。 我们的RPC服务器&lt;code>rpc_server.js&lt;/code>的代码如下所示：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;rpc_queue&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">prefetch&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; [x] Awaiting RPC requests&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span> &lt;span style="color:#75af00">reply&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#111">parseInt&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [.] fib(%d)&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">n&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">r&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">properties&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">replyTo&lt;/span>&lt;span style="color:#111">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">r&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">correlationId&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">properties&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">correlationId&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">ack&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">function&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">return&lt;/span> &lt;span style="color:#75af00">n&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">return&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#75af00">fibonacci&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">n&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>服务器代码非常简单:&lt;/p>
&lt;ol>
&lt;li>像往常一样，我们首先建立连接，通道和声明队列。&lt;/li>
&lt;li>我们可能想要运行多个服务器进程。为了在多个服务器上平均分配负载，我们需要在通道上设置&lt;code>prefetch&lt;/code>设置。&lt;/li>
&lt;li>我们使用&lt;code>Channel.consume&lt;/code>来消费队列中的消息。然后我们进入回调函数，在那里做工作，并发回响应。&lt;/li>
&lt;/ol>
&lt;p>我们的RPC客户端&lt;code>rpc_client.js&lt;/code>的代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">args&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34;Usage: rpc_client.js num&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">exclusive&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">corr&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">generateUuid&lt;/span>&lt;span style="color:#111">();&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">num&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#111">parseInt&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">[&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; [x] Requesting fib(%d)&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">num&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">properties&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">correlationId&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#75af00">corr&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; [.] Got %s&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;rpc_queue&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">num&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">correlationId&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#75af00">corr&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">replyTo&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">function&lt;/span> &lt;span style="color:#75af00">generateUuid&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">return&lt;/span> &lt;span style="color:#111">Math&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">random&lt;/span>&lt;span style="color:#111">().&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">Math&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">random&lt;/span>&lt;span style="color:#111">().&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">Math&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">random&lt;/span>&lt;span style="color:#111">().&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">();&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们的RPC服务已经准备就绪。我们可以启动服务器：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./rpc_server.js
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Awaiting RPC requests&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行客户端去请求一个斐波那契数字：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./rpc_client.js &lt;span style="color:#ae81ff">30&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Requesting fib(30)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里介绍的设计不是RPC服务的唯一可能的实现，但它有一些重要的优点：&lt;/p>
&lt;ul>
&lt;li>如果RPC服务器速度太慢，可以通过运行另一个来扩展。尝试在新的控制台中运行第二个&lt;code>rpc_server.js&lt;/code>。&lt;/li>
&lt;li>在客户端，RPC需要发送和接收一条消息。因此，RPC客户端只需要一次网络往返就可以获得一个RPC请求。&lt;/li>
&lt;/ul>
&lt;p>我们的代码仍然非常简单，并不试图解决更复杂（但重要）的问题，如：&lt;/p>
&lt;ul>
&lt;li>如果没有服务器运行，客户端应该如何应对？&lt;/li>
&lt;li>客户端是否应该对RPC有某种超时？&lt;/li>
&lt;li>如果服务器发生故障并引发异常，是否应将其转发给客户端？&lt;/li>
&lt;li>在处理之前防止无效的传入消息（例如检查边界，类型）。&lt;/li>
&lt;/ul></description></item><item><title>Node使用RabbitMQ：主题交换器</title><link>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-exchange-with-node/</link><pubDate>Thu, 15 Feb 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-exchange-with-node/</guid><description>&lt;h1 id="前提条件">前提条件&lt;/h1>
&lt;p>本教程假定RabbitMQ已安装并在标准端口（5672）上的本地主机上运行。如果您使用不同的主机，端口或凭据，连接设置将需要调整。&lt;/p>
&lt;h1 id="使用amqpnode客户端">（使用amqp.node客户端）&lt;/h1>
&lt;p>在之前的教程中，我们改良了我们的日志系统。我们没有使用只有虚拟广播的&lt;code>fanout&lt;/code>交换器，而是使用&lt;code>direct&lt;/code>交换器，提供了有选择性地接收日志的可能性。 虽然使用&lt;code>direct&lt;/code>交换器改良了我们的系统，但是他还是存在一些限制——它不能根据多种标准路由。 在我们的日志系统中，我们可能不仅想要订阅基于严重性级别的日志，可能还有基于日志出处的。你可能从syslog unix工具知道这个概念，它根据严重性（info/warn/crit &amp;hellip;）和设备（auth/cron/kern &amp;hellip;）来路由日志。 这会给我们很大的灵活性——我们可能想要获取来自&amp;rsquo;cron&amp;rsquo;的严重错误，而且还要听取来自&amp;rsquo;kern&amp;rsquo;的所有日志。 为了在我们的日志系统中实现这一点，我们需要了解更复杂的主题（&lt;code>topic&lt;/code>）交换器。&lt;/p>
&lt;h2 id="主题交换器">主题交换器&lt;/h2>
&lt;p>发送到主题交换的消息不能有任意的&lt;code>routing_key&lt;/code>——它必须是由点分隔的单词列表。单词可以是任何东西，但通常它们指定连接到消息的一些功能。一些有效的路由键例子： &lt;code>stock.usd.nyse&lt;/code>，&lt;code>nyse.vmw&lt;/code>，&lt;code>quick.orange.rabbit&lt;/code>。在路由选择键中可以有许多单词，最多255个字母。 绑定键也必须有相同的形式。主题交换器背后的逻辑类似于直接的——使用特定的路由键发送的消息将被传送到与匹配的绑定键绑定的所有队列。但是绑定键有两个重要的特殊情况：&lt;/p>
&lt;ul>
&lt;li>*（星号）可以代替一个单词。&lt;/li>
&lt;li>#（井号）可以代替另个或者多个单词。&lt;/li>
&lt;/ul>
&lt;p>举个简单的例子：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/python-five.png" alt="">&lt;/p>
&lt;p>在这个例子中，我们将发送所有描述动物的信息。消息将使用由三个单词（两个点）组成的路由键发送。路由键中的第一个单词将描述速度，第二个颜色和第三个种类：&lt;code>&amp;lt;speed&amp;gt;.&amp;lt;color&amp;gt;.&amp;lt;species&amp;gt;&lt;/code>。 我们将创建三个绑定：Q1的绑定键是&lt;code>*.orange.*&lt;/code>，Q2是&lt;code>*.*.rabbit&lt;/code>和&lt;code>lazy.#&lt;/code>。 什么含义呢？&lt;/p>
&lt;ul>
&lt;li>Q1对所有橘色动物感兴趣。&lt;/li>
&lt;li>Q2想要监听所有的兔子和和所有懒惰的动物。&lt;/li>
&lt;/ul>
&lt;p>一条路由键为&lt;code>quick.orange.rabbit&lt;/code>将会被分发到两个队列，同样的，&lt;code>lazy.orange.elephant&lt;/code>也将被分发到两个队列。而&lt;code>quick.orange.fox&lt;/code>只会被发送到第一个队列中，&lt;code>lazy.brown.fox&lt;/code>只会被发送到第二个。&lt;code>lazy.pink.rabbit&lt;/code>虽然匹配第二个队列中的两个绑定键，但是它只会被发送一次。&lt;code>auick.brown.fox&lt;/code>不匹配任何绑定键所以会被直接取消并且被丢掉。 如果违反我们的约定，发送一个或四个单词，如&lt;code>orange&lt;/code>或&lt;code>quick.orange.male.rabbit&lt;/code>的消息会发生什么？它们也将因为不匹配绑定键而被丢掉。 另一方面，&lt;code>lazy.orange.male.rabbit&lt;/code>即使有四个单词，也会匹配最后一个绑定键，并被传递到第二个队列。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>主题交换器&lt;/strong> 主题交换器功能强大能够拥有其它交换器的功能。 当一个队列使用“＃”（井号）绑定键绑定时，它将接收所有的消息，而不管路由键如何——类似于&lt;code>fanout&lt;/code>交换器。 当在绑定中不使用特殊字符“*”（星号）和“＃”（井号）时，主题交换器将像直接交换器一样。&lt;/p>
&lt;/blockquote>
&lt;h2 id="全部代码">全部代码&lt;/h2>
&lt;p>我们将在我们的日志系统中使用主题交换器。我们首先假定日志的路由键有两个单词：&lt;code>&amp;lt;facility&amp;gt;.&amp;lt;severity&amp;gt;&lt;/code>。 这个代码和前一个教程基本一样。 &lt;code>emit_log_topic.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;topic_logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">args&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">key&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">?&lt;/span> &lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">[&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">]&lt;/span> &lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#d88200">&amp;#39;anonymous.info&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">msg&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">join&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;topic&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">publish&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">key&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent %s:&amp;#39;%s&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">key&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>receive_logs_topic.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">args&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34;Usage: receive_logs_topic.js &amp;lt;facility&amp;gt;.&amp;lt;severity&amp;gt;&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;topic_logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;topic&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">exclusive&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; [*] Waiting for logs. To exit press CTRL+C&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">forEach&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">key&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">key&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] %s:&amp;#39;%s&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">fields&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">routingKey&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接受所有日志：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>./receive_logs_topic.js &lt;span style="color:#d88200">&amp;#34;#&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>从&lt;code>kern&lt;/code>中接受所有日志：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./receive_logs_topic.js &lt;span style="color:#d88200">&amp;#34;kern.*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或者，你只想监听&lt;code>critical&lt;/code>的日志：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./receive_logs_topic.js &lt;span style="color:#d88200">&amp;#34;*.critical&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你也可以创建多个绑定：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./receive_logs_topic.js &lt;span style="color:#d88200">&amp;#34;kern.*&amp;#34;&lt;/span> &lt;span style="color:#d88200">&amp;#34;*.critical&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>发送&lt;code>kern.critical&lt;/code>类型的日志：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./emit_log_topic.js &lt;span style="color:#d88200">&amp;#34;kern.critical&amp;#34;&lt;/span> &lt;span style="color:#d88200">&amp;#34;A critical kernel error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>请注意，代码不会对路由或绑定键作任何假设，您可能需要使用两个以上的路由键参数。 接下来，在教程6中找出如何做一个远程过程调用的往返消息。&lt;/p></description></item><item><title>Node使用RabbitMQ：路由</title><link>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-route-with-node/</link><pubDate>Wed, 14 Feb 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-route-with-node/</guid><description>&lt;h1 id="前提条件">前提条件&lt;/h1>
&lt;p>本教程假定RabbitMQ已安装并在标准端口（5672）上的本地主机上运行。如果您使用不同的主机，端口或凭据，连接设置将需要调整。&lt;/p>
&lt;h1 id="使用amqpnode客户端">（使用amqp.node客户端）&lt;/h1>
&lt;p>在之前的教程中，我们构建了一个简单的日志系统我们能够将日志消息广播给许多接收者。 在本教程中，我们将添加一个功能——只订阅一部分消息。比如说，我们只把最严重的错误输出到日志文件（写到盘），而在控制台上打印所有的日志信息。&lt;/p>
&lt;h2 id="绑定">绑定&lt;/h2>
&lt;p>在前面的教程中我们已经创建过绑定了，这里我们可以重新使用：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一个绑定将一个交换器和一个队列关联起来。我们可以这样简单理解：队列将会对该交换器里的消息感兴趣。 绑定可以采用额外的绑定键参数（上面代码中的空字符串）。这是我们如何创建一个带key的绑定：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">queue_name&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">exchange_name&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;black&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>绑定键的含义取决于交换类型。我们之前使用的&lt;code>fanout&lt;/code>交换机，简单地忽略了它的值。&lt;/p>
&lt;h2 id="直接交换器direct-exchange">直接交换器（Direct exchange）&lt;/h2>
&lt;p>我们在前面的教程中创建的日志系统将广播所有的信息给消费者。我们希望扩展这个功能，以便根据消息的严重性来过滤消息。例如我们可能希望将日志写入磁盘的脚本只接收到严重的错误，而不会浪费磁盘去存储&lt;code>info&lt;/code>和&lt;code>warning&lt;/code>的日志。 我们之前使用了一个&lt;code>fanout&lt;/code>交换器，这并没有给我们太大的灵活性——它只能够无意识地广播。 现在我们使用&lt;code>direct&lt;/code>交换器代替。&lt;code>direct&lt;/code>交换器背后的路由算法很简单——消息将进入&lt;code>binding key&lt;/code>与消息的&lt;code>routing key&lt;/code>完全匹配的队列。 为了阐释清楚，我们可以参考一下示意图：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/direct-exchange.png" alt="">&lt;/p>
&lt;p>如图，我们可以看到&lt;code>direct&lt;/code>交换器&lt;code>X&lt;/code>绑定了两个队列。第一个队列的绑定键是&lt;code>orange&lt;/code>，第二个队列有两个绑定，两个绑定键分别是&lt;code>black&lt;/code>和&lt;code>green&lt;/code>。 此图中，发布一个路由键为&lt;code>orange&lt;/code>的消息到交换器中，将被路由到&lt;code>Q1&lt;/code>队列中。路由键为&lt;code>black&lt;/code>或者是&lt;code>green&lt;/code>的消息将路由到&lt;code>Q2&lt;/code>队列中。所有其他消息将被取消。&lt;/p>
&lt;h2 id="多绑定">多绑定&lt;/h2>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/direct-exchange-multiple.png" alt="">&lt;/p>
&lt;p>相同的绑定键绑定多个队列是完全合法的。在上述例子中，我们完全可以使用绑定键&lt;code>black&lt;/code>绑定&lt;code>X&lt;/code>和&lt;code>Q1&lt;/code>。如此一来这个直接交换器就跟删除交换器一样将会把消息广播给匹配的队列。带有路由键&lt;code>black&lt;/code>的消息将被同时推送到&lt;code>Q1&lt;/code>和&lt;code>Q2&lt;/code>。&lt;/p>
&lt;h2 id="发送日志">发送日志&lt;/h2>
&lt;p>我们将使用我们的日志系统模型，以直接交换器代替删除交换器，以日志的严重性级别作为路由键。这样接收脚本可以选择自己想接收的级别的日志。我们先关注发送日志。 我们总需要首先去创建一个交换器：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct_logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后准备发送一个消息：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct_logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">publish&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为了简化事情，我们将假定“严重性”可以是&lt;code>info&lt;/code>，&lt;code>warning&lt;/code>，&lt;code>error&lt;/code>之一。&lt;/p>
&lt;h2 id="订阅">订阅&lt;/h2>
&lt;p>接收消息的方式与上一个教程中的一样，除了一个例外——我们将为每个我们感兴趣的严重级别创建一个新的绑定。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">forEach&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">exchange&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="全部代码">全部代码&lt;/h2>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/python-four.png" alt="">&lt;/p>
&lt;p>&lt;code>emit_log_direct.js&lt;/code>脚本的代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct_logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">args&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">msg&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">join&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">severity&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">?&lt;/span> &lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">[&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">]&lt;/span> &lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#d88200">&amp;#39;info&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">publish&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent %s: &amp;#39;%s&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>receive_logs_direct.js&lt;/code>的代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">args&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">if&lt;/span> &lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34;Usage: receive_logs_direct.js [info] [warning] [error]&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct_logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;direct&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">exclusive&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; [*] Waiting for logs. To exit press CTRL+C&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">args&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">forEach&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">severity&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] %s: &amp;#39;%s&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">fields&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">routingKey&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果只想保存“警告”和“错误”（而不是“信息”），则将消息记录到文件中，只需打开控制台并输入:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./receive_logs_direct.js warning error &amp;gt; logs_from_rabbit.log
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果您想要在屏幕上查看所有日志消息，请打开一个新终端并执行以下操作:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./receive_logs_direct.js info warning error
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [*] Waiting for logs. To exit press CTRL+C&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>而且，例如，要输出错误日志消息，只需键入：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./emit_log_direct.js error &lt;span style="color:#d88200">&amp;#34;Run. Run. Or it will explode.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Sent &amp;#39;error&amp;#39;:&amp;#39;Run. Run. Or it will explode.&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>转到教程5，了解如何根据模式来侦听消息。&lt;/p></description></item><item><title>Node使用RabbitMQ：发布与订阅</title><link>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-pubsub-with-node/</link><pubDate>Tue, 13 Feb 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-pubsub-with-node/</guid><description>&lt;h1 id="前提条件">前提条件&lt;/h1>
&lt;p>本教程假定RabbitMQ已安装并在标准端口（5672）上的本地主机上运行。如果您使用不同的主机，端口或凭据，连接设置将需要调整。&lt;/p>
&lt;h1 id="使用amqpnode客户端">（使用amqp.node客户端）&lt;/h1>
&lt;p>在前面的教程中，我们创建了一个工作队列，假设每个任务只被传递给一个Worker。在这一部分，我们将做一些完全不同的事情——我们会向多个消费者传递信息。这种模式被称为“发布与订阅”。 为了说明这个模式，我们将建立一个简单的日志系统。它将包含两个程序 - 第一个将发送日志消息，第二个将接收并打印它们。 在我们的日志系统中，接收程序的每个运行副本都会收到消息。这样我们就可以运行一个接收器并将日志定向到磁盘，同时将能够运行另一个接收器并输出到屏幕上以便查看日志。 本质上，发布的日志消息将被广播给所有的接收者。&lt;/p>
&lt;h2 id="交换器exchange">交换器（exchange）&lt;/h2>
&lt;p>在本教程的前面部分，我们发送和接收来自队列的消息。现在是时候在Rabbit中引入完整的消息模型了。 让我们快速回顾一下前面的教程中介绍的内容：&lt;/p>
&lt;ul>
&lt;li>生产者（&lt;em>producer&lt;/em>）是发送消息的用户应用程序。&lt;/li>
&lt;li>队列（&lt;em>queue&lt;/em>）是存储消息的缓冲区。&lt;/li>
&lt;li>消费者（&lt;em>consumer&lt;/em>）是接收消息的用户应用程序。&lt;/li>
&lt;/ul>
&lt;p>RabbitMQ中消息传递模型的核心思想是生产者永远不会将任何消息直接发送到队列中。实际上，生产者通常甚至不知道邮件是否会被传送到任何队列中。 生产者只能将消息发送给交换器。交换器非常简单，它一面接受来自生产者的消息，一面将接收到的消息推送给队列。交换器必须知道如何处理接收到的消息：是否应该附加到特定的队列？是否应该附加到许多队列？还是应该丢弃。这些规则是由交换类型定义的。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/exchanges.png" alt="">&lt;/p>
&lt;p>有几种可用的交换类型：&lt;code>direct&lt;/code>，&lt;code>topic&lt;/code>，&lt;code>headers&lt;/code>和&lt;code>fanout&lt;/code>。我们将关注最后一个 ——&lt;code>fanout&lt;/code>。让我们创建一个这种类型的交换器，并将其称为&lt;code>logs&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;logs&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;fanout&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>fanout&lt;/code>交换非常简单。正如你可能从名字中猜到的那样，它只是将所有收到的消息广播到它所知道的所有队列中。这正是我们日志系统所需要的。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>列出交换器&lt;/strong> 要列出服务器上的交换，您可以运行有用的&lt;code>rabbitmqctl&lt;/code>：&lt;/p>
&lt;pre>&lt;code>sudo rabbitmqctl list_exchanges
&lt;/code>&lt;/pre>
&lt;p>在这个列表中将会有一些&lt;code>amq.*&lt;/code>交换和默认（未命名）交换。这些是默认创建的，但目前不太可能需要使用它们。 &lt;strong>默认交换器&lt;/strong> 在本教程的以前部分，我们对交换器一无所知，但仍能够将消息发送到队列。这是可能的，因为我们正在使用一个默认的交换，这是由空字符串（&amp;quot;&amp;quot;）标识。 回顾一下我们之前如何发布消息：&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;hello&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>这里我们使用默认的或无名的交换：消息被路由到队列中，如果它存在的话，该消息被指定为第一个参数。&lt;/p>
&lt;/blockquote>
&lt;p>现在，我们可以发布到我们的命名交换：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">publish&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;logs&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>作为第二个参数的空字符串意味着我们不想将消息发送到任何特定的队列。我们只想将其发布到我们的&lt;code>logs&lt;/code>交换器。&lt;/p>
&lt;h2 id="临时队列">临时队列&lt;/h2>
&lt;p>正如你以前可能记得我们使用的是具有指定名称的队列（请记住&lt;code>hello&lt;/code>和&lt;code>task_queue&lt;/code>？）。给队列命名是很重要的——我们需要给Workers指定相同的队列。当你想在生产者和消费者之间共享队列时，一定要给队列命名。 但是我们的日志系统并不是这样。我们想要监听所有日志消息，而不仅仅是其中的一部分。我们也只对目前流动的消息感兴趣，而不是旧消息。要解决这个问题，我们需要两件事。 首先，每当我们连接到Rabbit，我们需要一个新的、空的队列。要做到这一点，我们可以创建一个随机名称的队列，或者最好是让服务器为我们选择一个随机队列名称。 其次，一旦我们断开消费者，队列应该被自动删除。 在&lt;a href="http://www.squaremobius.net/amqp.node/">amqp.node&lt;/a>客户端中，当我们将队列名称作为空字符串提供时，我们使用生成的名称创建一个非持久队列：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">exclusive&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当方法返回时，队列实例包含由RabbitMQ生成的随机队列名称。例如，它可能看起来像&lt;code>amq.gen-JzTY20BRgKO-HjmUJj0wLg&lt;/code>。 当声明它的连接关闭时，队列将被删除，因为它被声明为独占（exclusive）。&lt;/p>
&lt;h2 id="绑定">绑定&lt;/h2>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/bindings.png" alt="">&lt;/p>
&lt;p>我们已经创建了一个扇出交换器和一个队列。现在我们需要告诉交换器把消息发送到我们的队列。交换器和队列之间的关系被称为绑定。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">queue_name&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;logs&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此后，&lt;code>logs&lt;/code>交换器将把消息附加到我们的队列中。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>列出绑定&lt;/strong> 你可以通过以下方式列出所有的绑定：&lt;/p>
&lt;pre>&lt;code>rabbitmqctl list_bindings
&lt;/code>&lt;/pre>
&lt;/blockquote>
&lt;h2 id="全部代码">全部代码&lt;/h2>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/python-three-overall.png" alt="">&lt;/p>
&lt;p>发出日志消息的生产者程序与前面的教程没有什么不同。最重要的变化是，我们现在要发布消息到我们的&lt;code>logs&lt;/code>交换器，而不是无名的。发送时我们需要提供一个路由键，但是对于扇出交换器，它的值将被忽略。这里是&lt;code>emit_log.js&lt;/code>脚本的代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">msg&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">join&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;fanout&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">publish&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如你所见，建立连接后，我们声明交换器。这一步是必要的，因为不允许发布到一个不存在的交换器。 如果没有队列绑定到交换机上，消息将会丢失，但对我们来说没关系；如果没有消费者正准备接收，我们可以放心地丢弃消息。 &lt;code>receive_logs.js&lt;/code>的代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">ex&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;logs&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertExchange&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;fanout&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">exclusive&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [*] Waiting for messages in %s. To exit press CTRL+C&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">bindQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ex&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#d88200">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">queue&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果您想将日志保存到文件中，只需打开一个控制台并输入：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>./receive_logs.js &amp;gt; logs_from_rabbit.log
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你想看到屏幕上的日志，开启一个新的终端，并运行：&lt;/p>
&lt;pre>&lt;code>./receive_logs.js
&lt;/code>&lt;/pre>
&lt;p>发送日志，需要输入：&lt;/p>
&lt;pre>&lt;code>./emit_log.js
&lt;/code>&lt;/pre>
&lt;p>使用&lt;code>rabbitmqctl list_bindings&lt;/code>，你可以验证代码实际上是否创建了绑定和队列。若有两个&lt;code>receive_logs.js&lt;/code>程序运行，你应该看到如下结果：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>sudo rabbitmqctl list_bindings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; Listing bindings ...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; ...done.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>结果的解释很简单：&lt;code>logs&lt;/code>交换器中的数据发送到两个带有服务器分配名称的队列中。这正是我们想要的。 关于如何监听消息子集，我们可以继续阅读教程4。&lt;/p></description></item><item><title>Node使用RabbitMQ：工作队列</title><link>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-queue-with-node/</link><pubDate>Mon, 12 Feb 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-queue-with-node/</guid><description>&lt;h1 id="前提条件">前提条件&lt;/h1>
&lt;p>本教程假定RabbitMQ已安装并在标准端口（5672）上的本地主机上运行。如果您使用不同的主机，端口或凭据，连接设置将需要调整。&lt;/p>
&lt;h1 id="使用amqpnodehttpswwwsquaremobiusnetamqpnode客户端">（使用&lt;a href="https://www.squaremobius.net/amqp.node/">amqp.node&lt;/a>客户端）&lt;/h1>
&lt;p>在第一个教程中，我们编写了用于从命名队列发送和接收消息的程序。在这一个中，我们将创建一个工作队列，用于在多个Worker之间分配耗时的任务。 工作队列（又名：任务队列）主要目的是避免立即执行资源密集型任务，避免必须等待任务完成。取而代之的是，安排稍后完成任务。我们把一个任务封装成一个消息并发送给一个队列。在后台运行的工作进程将弹出任务并最终执行作业。当你运行许多Worker时，任务将在他们之间共享。 这个概念在web应用程序中特别有用，在短的HTTP请求窗口中不可能处理复杂的任务。&lt;/p>
&lt;h2 id="准备">准备&lt;/h2>
&lt;p>在本教程的前一部分，我们发送了一个包含“Hello World！”的消息。现在我们将发送代表复杂任务的字符串。我们没有真正的任务，比如图像被重新调整大小或者渲染PDF文件，所以让我们假装我们忙——通过使用&lt;code>setTimeout&lt;/code>方法。 我们稍微修改前面例子中的&lt;code>send.js&lt;/code>代码，以允许从命令行发送任意消息。这个程序将安排任务到我们的工作队列，所以让我们把它命名为&lt;code>new_task.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;task_queue&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">msg&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">join&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#d88200">&amp;#34;Hello World!&amp;#34;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">),&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">persistent&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent &amp;#39;%s&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们旧的&lt;code>receive.js&lt;/code>脚本也需要进行一些更改：它需要伪造邮件正文中每个点的第二个工作。它将从队列中弹出消息并执行任务，所以我们称之为&lt;code>worker.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">secs&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">().&lt;/span>&lt;span style="color:#75af00">split&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;.&amp;#39;&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Received %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Done&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#75af00">secs&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>请注意，我们的假任务模拟执行时间。 按照教程1运行它们：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./worker.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./new_task.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="循环调度">循环调度&lt;/h2>
&lt;p>使用任务队列的优点之一是能够轻松地平行工作。如果我们积压工作，我们可以增加更多的工人，这样可以轻松扩展。 首先，我们试着同时运行两个&lt;code>worker.js&lt;/code>脚本。他们都会从队列中得到消息，但究竟是如何？让我们来看看。 您需要打开三个控制台。两个将运行worker.js脚本。这些控制台将是我们的两个消费者——C1和C2。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./worker.js
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [*] Waiting for messages. To exit press CTRL+C&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./worker.js
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [*] Waiting for messages. To exit press CTRL+C&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在第三个，我们将发布新的任务。一旦你开始了消费者，你可以发布一些消息：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./new_task.js First message.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./new_task.js Second message..
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./new_task.js Third message...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./new_task.js Fourth message....
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./new_task.js Fifth message.....
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>让我们看看交给我们工人的东西：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./worker.js
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [*] Waiting for messages. To exit press CTRL+C&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Received &amp;#39;First message.&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Received &amp;#39;Third message...&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Received &amp;#39;Fifth message.....&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># shell 2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./worker.js
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [*] Waiting for messages. To exit press CTRL+C&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Received &amp;#39;Second message..&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># =&amp;gt; [x] Received &amp;#39;Fourth message....&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>默认情况下，RabbitMQ将按顺序将每条消息发送给下一个使用者。平均而言，每个消费者将获得相同数量的消息。这种分发消息的方式称为循环法（round-robin）。试试三个或更多的Worker。&lt;/p>
&lt;h2 id="消息确认">消息确认&lt;/h2>
&lt;p>做任务可能需要几秒钟的时间。你可能会想知道如果其中一个消费者开始一个长期的任务，并且只是部分完成就死掉了会发生什么。使用我们当前的代码，一旦RabbitMQ向客户发送消息，立即将其标记为删除。在这种情况下，如果你杀死了一个Worker，我们将失去刚刚处理的信息，也将失去所有派发给这个特定工作人员但尚未处理的消息。 但我们不想失去任何任务。如果一个Worker死掉，我们希望将任务交付给另一个Worker。 为了确保消息永不丢失，RabbitMQ支持&lt;a href="https://www.rabbitmq.com/confirms.html">消息确认&lt;/a>。消费者发回确认（告知）告诉RabbitMQ已经收到，处理了一个特定的消息，并且RabbitMQ可以自由删除它。 如果消费者死掉（其通道关闭，连接关闭或TCP连接丢失），RabbitMQ将认为消息未被完全处理，并将重新排队。如果同时有其他消费者在线，则会迅速重新发送给另一个消费者。这样，即使Worker死掉，也可以确保没有任何信息丢失。 没有任何消息超时；当消费者死掉时，RabbitMQ将重新传递消息。即使处理消息需要非常长的时间也没关系。 在前面的例子中，消息确认是被关闭的。现在使用&lt;code>{noAck：false}&lt;/code>来打开它们（您也可以完全删除选项），并在完成任务后从Worker发送正确的确认。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">secs&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">().&lt;/span>&lt;span style="color:#75af00">split&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;.&amp;#39;&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Received %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Done&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">ack&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#75af00">secs&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用这段代码，我们可以确定，即使在处理消息的时候使用&lt;code>CTRL+C&lt;/code>来杀死一个Worker，也不会丢失任何东西。Worker死后不久，所有未确认的消息将被重新发送。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>忘记确认&lt;/strong> 丢掉&lt;code>ack&lt;/code>是一个常见的错误。这个错误很容易发生，但是后果很严重。当你的客户退出时（这可能看起来像随机的重新传递），消息将被重新传递，但是RabbitMQ将会消耗越来越多的内存，因为它将不能释放任何未被确认的消息。&lt;/p>
&lt;/blockquote>
&lt;p>为了调试这种错误，你可以使用非&lt;code>rabbitmqctl&lt;/code>打印&lt;code>messages_unacknowledged&lt;/code>字段：&lt;/p>
&lt;pre>&lt;code>sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
&lt;/code>&lt;/pre>
&lt;p>在Windows上去掉&lt;code>sudo&lt;/code>：&lt;/p>
&lt;pre>&lt;code>rabbitmqctl.bat list_queues name messages_ready messages_unacknowledged
&lt;/code>&lt;/pre>
&lt;h2 id="消息持久性">消息持久性&lt;/h2>
&lt;p>我们已经学会了如何确保即使消费者死掉，任务也不会丢失。但是如果RabbitMQ服务器停止，我们的任务仍然会丢失。 除非我们设置好，否则当RabbitMQ退出或崩溃时，它会忘记队列和消息。需要做两件事来确保消息不会丢失：我们需要将队列和消息标记为持久。 首先，我们需要确保RabbitMQ永远不会失去队列。为了做到这一点，我们需要宣布它是持久的：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;hello&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>虽然这个命令本身是正确的，但是在我们目前的设置中不起作用。这是因为我们已经定义了一个名为&lt;code>hello&lt;/code>的队列，这个队列并不具有持久性。RabbitMQ不允许你使用不同的参数重新定义一个已经存在的队列，并且会向任何尝试这样做的程序返回一个错误。但有一个快速的解决方法——声明一个不同名称的队列，例如&lt;code>task_queue&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;task_queue&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个&lt;code>durable&lt;/code>的选项更改需要应用于生产者和消费者代码。 此时我们确信，即使RabbitMQ重新启动，&lt;code>task_queue&lt;/code>队列也不会丢失。现在我们需要将消息标记为持久的——通过使用&lt;code>Channel.sendToQueue&lt;/code>持久选项。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">),&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">persistent&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>注意消息持久性&lt;/strong> 将消息标记为永久并不能完全保证消息不会丢失。尽管它告诉RabbitMQ将消息保存到磁盘，但是当RabbitMQ接收到消息并且还没有保存消息时，仍然有一个很短的时间窗口。此外，RabbitMQ不会为每个消息执行&lt;code>fsync(2)&lt;/code>——它可能只是保存到缓存中，而不是写入磁盘。持久性保证不强，但对于我们简单的任务队列已经足够了。如果您需要更强大的保证，那么您可以使用发布者确认。&lt;/p>
&lt;/blockquote>
&lt;h2 id="公平调度">公平调度&lt;/h2>
&lt;p>您可能已经注意到调度仍然不能按照我们的要求工作。例如在有两个Worker的情况下，当所有第奇数个消息都很重且第偶数个消息都很轻的时候，一个Worker就会一直很忙，而另一个Worker几乎不会做任何工作。然而，RabbitMQ不知道任何有关的信息，并将仍然均匀地发送消息。 发生这种情况是因为RabbitMQ只在消息进入队列时调度消息。它没有考虑消费者未确认消息的数量。它只是盲目地把第n条消息分发给第n个消费者。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/prefetch-count.png" alt="">&lt;/p>
&lt;p>为了解决这个问题，我们可以使用值为1的&lt;code>prefetch&lt;/code>方法。这告诉RabbitMQ一次不能给一个Worker多个消息。或者换句话说，在处理完前一个消息之前，不要向Worker发送新消息，它会将新消息分派给下一个还不忙的Worker。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">prefetch&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>关于队列大小的说明&lt;/strong> 如果所有的Worker都很忙，你的队伍就可能填满了。你可能想要会增加更多的Worker，或者有其他的策略。&lt;/p>
&lt;/blockquote>
&lt;h2 id="全部代码">全部代码&lt;/h2>
&lt;p>&lt;code>new_task.js&lt;/code>的最终代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;task_queue&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">msg&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">argv&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">slice&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">join&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#d88200">&amp;#34;Hello World!&amp;#34;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">),&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">persistent&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent &amp;#39;%s&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>work.js&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;task_queue&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">prefetch&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [*] Waiting for messages in %s. To exit press CTRL+C&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">secs&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">().&lt;/span>&lt;span style="color:#75af00">split&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;.&amp;#39;&lt;/span>&lt;span style="color:#111">).&lt;/span>&lt;span style="color:#75af00">length&lt;/span> &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Received %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Done&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">ack&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#75af00">secs&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用消息确认和&lt;code>prefetch&lt;/code>可以设置一个工作队列。即使RabbitMQ重新启动，持久性选项也能让任务继续存在。 有关Channel方法和消息属性的更多信息，可以浏览&lt;a href="https://www.squaremobius.net/amqp.node/channel_api.html">amqplib文档&lt;/a>。 现在我们可以继续阅读教程3，并学习如何向许多消费者传递相同的消息。&lt;/p></description></item><item><title>Node使用RabbitMQ：入门篇</title><link>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-base-with-node/</link><pubDate>Sun, 11 Feb 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/middleware/rabbitmq/rabbitmq-base-with-node/</guid><description>&lt;h1 id="前提条件">前提条件&lt;/h1>
&lt;p>本教程假定RabbitMQ已安装并在本地主机的标准端口（5672）上运行。如果您使用不同的主机，端口或凭据，连接设置将需要调整。&lt;/p>
&lt;h1 id="简介">简介&lt;/h1>
&lt;p>RabbitMQ是一个消息broker：它接受和转发消息。你可以把它想象成一个邮局：当你把邮件放在邮箱里时，你可以确定邮差先生最终会把邮件发送给你的收件人。在这个比喻中，RabbitMQ是整个邮政系统即信箱、邮局和邮递员。 RabbitMQ与邮局的主要区别是它不处理纸张，而是接受，存储和转发数据的二进制数据块——消息。 RabbitMQ和一般的消息传递使用了一些术语。&lt;/p>
&lt;blockquote>
&lt;p>生产（&lt;em>producing&lt;/em>）只不过是发送而已。一个发送消息的程序是一个生产者（&lt;em>producer&lt;/em>）：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/producer.png" alt="">&lt;/p>
&lt;p>队列（&lt;em>queue&lt;/em>）是RabbitMQ内部的邮箱名称。尽管消息流经RabbitMQ和您的应用程序，但它们只能存储在队列（&lt;em>queue&lt;/em>）中。队列（&lt;em>queue&lt;/em>）只受主机的内存和磁盘限制，实质上是一个大的消息缓冲区。许多生产者（&lt;em>producer&lt;/em>）可以发送消息到一个队列（&lt;em>queue&lt;/em>），许多消费者（&lt;em>consumers&lt;/em>）可以尝试从一个队列（&lt;em>queue&lt;/em>）接收数据。这就是我们代表队列（&lt;em>queue&lt;/em>）的方式：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/queue.png" alt="">&lt;/p>
&lt;p>消费（&lt;em>consuming&lt;/em>）与接受有类似的意义。消费者（&lt;em>consumer&lt;/em>）是主要等待接收消息的程序：&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/consumer.png" alt="">&lt;/p>
&lt;/blockquote>
&lt;p>请注意，producer，consumer和broker不必在同一主机上；事实上在大多数应用程序中都不会。&lt;/p>
&lt;h1 id="使用amqpnode客户端">(使用amqp.node客户端)&lt;/h1>
&lt;p>在本教程的这一部分，我们将用Node编写两个小程序；发送单个消息的生产者，以及接收消息并将其打印出来的消费者。我们将详细介绍&lt;a href="https://www.squaremobius.net/amqp.node/">amqp.node&lt;/a> API中的一些细节，将注意力集中在这个非常简单的事情上，以便开始。这是一个消息传递的“Hello World”。 在下图中，“P”是我们的生产者，“C”是我们的消费者。中间的盒子是一个队列——RabbitMQ消费者的消息缓冲区。 &lt;img src="https://mydream.ink/assets/images/posts/middleware/python-one.png" alt="">&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>amqp.node客户端库&lt;/strong> RabbitMQ提供多种协议。本教程使用AMQP 0-9-1，这是一个开放，通用的消息传递协议。 RabbitMQ有许多不同的语言客户端。我们将在本教程中使用amqp.node客户端。 首先，使用&lt;a href="https://www.npmjs.com/">npm&lt;/a>安装amqp.node：&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>npm install amqplib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>现在我们安装了amqp.node，我们可以写一些代码。&lt;/p>
&lt;h2 id="发送端">发送端&lt;/h2>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/sending.png" alt="">&lt;/p>
&lt;p>我们将调用我们的消息发布者（发送者）&lt;code>send.js&lt;/code>和我们的消息使用者（接收者）&lt;code>receive.js&lt;/code>。发布者将连接到RabbitMQ，发送一条消息，然后退出。 在send.js中，我们需要首先需要库：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后连接到RabbitMQ服务器&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来我们创建一个频道（channel），这是大部分API所需要做的：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>发送，我们必须申报队列给我们发送；然后我们可以发布消息到队列中：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;hello&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Note: on Node 6 Buffer.from(msg) should be used
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent &amp;#39;Hello World!&amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>声明一个队列是幂等的——只有当它不存在时才会被创建。消息内容是一个字节数组，所以你可以使用任何编码。 最后，我们关闭连接并退出：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>所以，整个代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;hello&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">msg&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Note: on Node 6 Buffer.from(msg) should be used
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">sendToQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">new&lt;/span> &lt;span style="color:#75af00">Buffer&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Sent %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">setTimeout&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">()&lt;/span> &lt;span style="color:#111">{&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">close&lt;/span>&lt;span style="color:#111">();&lt;/span> &lt;span style="color:#75af00">process&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">exit&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>发送端故障&lt;/strong> 如果这是您第一次使用RabbitMQ，并且您没有看到“已发送”消息，那么您可能会不知所措。也许broker启动没有足够的可用磁盘空间（默认情况下，它至少需要200 MB空闲空间），因此拒绝接受消息。检查代理日志文件以确认并在必要时减少限制。&lt;a href="https://www.rabbitmq.com/configure.html#config-items">配置文件文档&lt;/a>将告诉你如何设置&lt;code>disk_free_limit&lt;/code>。&lt;/p>
&lt;/blockquote>
&lt;h2 id="接收端">接收端&lt;/h2>
&lt;p>这是我们的接收方。消费者获取从RabbitMQ推送的消息，因此与发布单个消息的发布者不同，我们将持续运行以监听消息并将其打印出来。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/middleware/receiving.webp" alt="">&lt;/p>
&lt;p>代码（在&lt;code>receive.js&lt;/code>中）和send有相同的要求：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>设置与发布者相同；我们打开一个连接和一个通道，并声明我们将要使用的队列。注意这与&lt;code>sendToQueue&lt;/code>发布到的队列相匹配。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;hello&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>请注意，我们在这里也声明队列。因为我们可能会在发布者之前启动消费者，所以我们希望确保队列存在，然后再试图使用消息。 我们即将告诉服务器将队列中的消息传递给我们。由于它会异步推送消息，因此我们提供了一个回调函数，当RabbitMQ向消费者推送消息时，将执行回调函数。这是&lt;code>Channel.consume&lt;/code>所做的。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [*] Waiting for messages in %s. To exit press CTRL+C&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Received %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>完整&lt;code>receive.js&lt;/code>如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">amqp&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#75af00">require&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqplib/callback_api&amp;#39;&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75af00">amqp&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">connect&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#39;amqp://localhost&amp;#39;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">conn&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">createChannel&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">err&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#00a8c8">var&lt;/span> &lt;span style="color:#75af00">q&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#d88200">&amp;#39;hello&amp;#39;&lt;/span>&lt;span style="color:#111">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">assertQueue&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">durable&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">false&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [*] Waiting for messages in %s. To exit press CTRL+C&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">ch&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">consume&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">q&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#00a8c8">function&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">)&lt;/span> &lt;span style="color:#111">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75af00">console&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">log&lt;/span>&lt;span style="color:#111">(&lt;/span>&lt;span style="color:#d88200">&amp;#34; [x] Received %s&amp;#34;&lt;/span>&lt;span style="color:#111">,&lt;/span> &lt;span style="color:#75af00">msg&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">content&lt;/span>&lt;span style="color:#111">.&lt;/span>&lt;span style="color:#75af00">toString&lt;/span>&lt;span style="color:#111">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">},&lt;/span> &lt;span style="color:#111">{&lt;/span>&lt;span style="color:#75af00">noAck&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#00a8c8">true&lt;/span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#111">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="执行">执行&lt;/h2>
&lt;p>现在我们可以运行这两个脚本。在终端中，运行发布者：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>./send.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后运行接收者：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#272822;background-color:#fafafa;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>./receive.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>消费者将通过RabbitMQ打印从发布者处获得的消息。消费者将继续运行，等待消息（使用Ctrl-C停止它），所以尝试从另一个终端运行发布者。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>列出队列&lt;/strong> 你可能想看看RabbitMQ有什么队列，有多少条消息。您可以作为特权用户使用&lt;code>rabbitmqctl&lt;/code>工具执行此操作：&lt;/p>
&lt;pre>&lt;code>sudo rabbitmqctl list_queues
&lt;/code>&lt;/pre>
&lt;p>在Windows上，省略sudo：&lt;/p>
&lt;pre>&lt;code>rabbitmqctl.bat list_queues
&lt;/code>&lt;/pre>
&lt;/blockquote></description></item><item><title>详解VMware vCenter Server</title><link>https://mydream.ink/posts/virtualization/vmware/vmware-vcenter/</link><pubDate>Thu, 25 Jan 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/vmware/vmware-vcenter/</guid><description>&lt;p>作为vSphere的核心主键之一的 vCenter Server 可以部署在Linux上或者是Windows上，在Linux上被称为 vCenter Server Appliance。
根据 Platform Services Controller 所部署的位置的不同，有以下几种部署类型：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th align="left">部署类型&lt;/th>
&lt;th align="left">描述&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td align="left">具有嵌入式 Platform Services Controller 部署的 vCenter Server&lt;/td>
&lt;td align="left">与 Platform Services Controller 捆绑在一起的所有服务与 vCenter Server 服务一起部署在同一虚拟机或物理服务器上。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">Platform Services Controller&lt;/td>
&lt;td align="left">只有与 Platform Services Controller 捆绑在一起的服务会部署在虚拟机或物理服务器上。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">具有外部 Platform Services Controller 的 vCenter Server （需要外部 Platform Services Controller）&lt;/td>
&lt;td align="left">只有 vCenter Server 服务会部署在虚拟机或物理服务器上。必须向之前部署或安装的 Platform Services Controller 实例注册此类 vCenter Server 实例。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title>vSphere 和 vCenter Server</title><link>https://mydream.ink/posts/virtualization/vmware/vmware-vcenter-vsphere/</link><pubDate>Tue, 23 Jan 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/vmware/vmware-vcenter-vsphere/</guid><description>&lt;p>VMware vSphere 是一套虚拟化应用程序，包括 ESXi 和 vCenter Server。 vSphere 使用虚拟化执行以下任务：&lt;/p>
&lt;ul>
&lt;li>在一台物理机上同时运行多个操作系统。&lt;/li>
&lt;li>回收闲置资源并在多台物理机之间平衡工作负载。&lt;/li>
&lt;li>处理硬件故障和预定维护。&lt;/li>
&lt;/ul>
&lt;p>熟悉构成 vSphere 环境的组件有助于理解设置过程，并最终理解使用 VMware vCenter Server 管理主机和运行虚拟机的过程。 除了 ESXi 主机和 vSphere Client 之外，vSphere 还包括以下组件：&lt;/p>
&lt;p>&lt;strong>vSphere Web Client&lt;/strong> vSphere Web Client 是连接到 vCenter Server 和多主机环境的接口。另外，它还提供对虚拟机的控制台访问。借助 vSphere Web Client，您可以使用浏览器内界面执行所有管理任务。&lt;/p>
&lt;p>&lt;strong>VMware vCenter Server&lt;/strong> vCenter Server 将各台主机中的资源统一在一起，使这些资源可以在整个数据中心的虚拟机之间共享。其实现原理是：根据系统管理员设定的策略，管理主机的虚拟机分配，以及给定主机内虚拟机的资源分配。 vCenter Server 允许使用 vSphere 的高级功能，如 vSphere Distributed Resource Scheduler (DRS)、vSphere High Availability (HA)、vSphere vMotion 和 vSphere Storage vMotion。&lt;/p>
&lt;p>&lt;strong>数据中心&lt;/strong> 数据中心是一种结构，在该结构中，可以向清单中添加主机及其相关虚拟机。&lt;/p>
&lt;p>&lt;strong>主机&lt;/strong> 主机是使用 ESXi 虚拟化软件来运行虚拟机的计算机。主机为驻留在其上的虚拟机提供 CPU 和内存资源、对存储器的访问以及网络连接。&lt;/p>
&lt;p>&lt;strong>虚拟机&lt;/strong> 与物理机一样，虚拟机是运行操作系统和应用程序的软件计算机。多个虚拟机可在同一主机上同时运行。由 vCenter Server 管理的虚拟机也可在主机群集上运行。 下图显示了 vSphere 基本组件之间的关系，以及如何使用 vCenter Server 管理主机和运行虚拟机。&lt;/p>
&lt;p>&lt;strong>vSphere 组件&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/blog/2018-01/GUID-38835A7F-4823-4D92-A689-0B827A5A3DD1-high.png" alt="">&lt;/p></description></item><item><title>vCenter Server 组件和服务</title><link>https://mydream.ink/posts/virtualization/vmware/vmware-vcenter-component/</link><pubDate>Sun, 21 Jan 2018 08:52:19 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/vmware/vmware-vcenter-component/</guid><description>&lt;p>摘自于VMware官方文档，移至自己博客方便阅读，如有侵权，请联系我删除&lt;/p>
&lt;p>vCenter Server 为虚拟机和主机的管理、操作、资源置备和性能评估提供了一个集中式平台。 安装具有嵌入式 Platform Services Controller 的 vCenter Server 或部署具有嵌入式 Platform Services Controller 的 vCenter Server Appliance 时，vCenter Server、vCenter Server 组件以及 Platform Services Controller 中包含的服务将部署在同一个系统上。 安装具有外部 Platform Services Controller 的 vCenter Server 或部署具有外部 Platform Services Controller 的 vCenter Server Appliance 时，vCenter Server 和 vCenter Server 组件将部署在一个系统上，而 Platform Services Controller 中包含的服务将部署在另一个系统上。 以下组件包含在 vCenter Server 和 vCenter Server Appliance 安装中：&lt;/p>
&lt;ol>
&lt;li>VMware Platform Services Controller 基础架构服务组包含 vCenter Single Sign-On、License Service、Lookup Service 和 VMware Certificate Authority。&lt;/li>
&lt;li>vCenter Server 服务组包含 vCenter Server、vSphere Web Client、vSphere Auto Deploy 和 vSphere ESXi Dump Collector。适用于 Windows 的 vCenter Server 还包含 VMware vSphere Syslog Collector。vCenter Server Appliance 还包含 VMware vSphere Update Manager 扩展服务。&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>注：从 vSphere 6.5 开始，所有 vCenter Server 服务和部分 Platform Services Controller 服务作为 VMware Service Lifecycle Manager 服务的子进程运行。&lt;/p>
&lt;/blockquote>
&lt;h1 id="随-vmware-platform-services-controller-一起安装的服务">随 VMware Platform Services Controller 一起安装的服务&lt;/h1>
&lt;p>&lt;strong>vCenter Single Sign-On&lt;/strong>&lt;/p>
&lt;p>vCenter Single Sign-On 身份验证服务为 vSphere 软件组件提供了安全身份验证服务。使用 vCenter Single Sign-On，vSphere 组件可通过安全的令牌交换机制相互通信，而无需每个组件使用目录服务（如 Active Directory）分别对用户进行身份验证。vCenter Single Sign-On 可构建内部安全域（如 vsphere.local），vSphere 解决方案和组件将在安装或升级期间在该域中进行注册，从而提供基础架构资源。vCenter Single Sign-On 可以通过其自己的内部用户和组对用户进行身份验证，或者可以连接到受信任的外部目录服务（如 Microsoft Active Directory）。然后，可以在 vSphere 环境中为经过身份验证的用户分配基于注册的解决方案的权限或角色。 vCenter Server 需要 vCenter Single Sign-On。&lt;/p>
&lt;p>&lt;strong>vSphere License Service&lt;/strong>&lt;/p>
&lt;p>vSphere License Service 为连接到单个 Platform Services Controller 或多个链接的 Platform Services Controller 的所有 vCenter Server 系统提供公共许可证清单和管理功能。&lt;/p>
&lt;p>&lt;strong>VMware Certificate Authority&lt;/strong>&lt;/p>
&lt;p>默认情况下，VMware Certificate Authority (VMCA) 将使用以 VMCA 作为 root 证书颁发机构的签名证书置备每个 ESXi 主机。以显式方式将 ESXi 主机添加到 vCenter Server 时进行置备，或在 ESXi 主机安装过程中进行置备。所有 ESXi 证书都存储在本地主机上。 有关所有 Platform Services Controller 服务和功能的信息，请参见Platform Services Controller 管理。&lt;/p>
&lt;h1 id="随-vcenter-server-一起安装的服务">随 vCenter Server 一起安装的服务&lt;/h1>
&lt;p>安装 vCenter Server 时，将以静默方式安装这些附加组件。这些组件不能单独安装，因为它们没有其自己的安装程序。 PostgreSQL VMware 分发的用于 vSphere 和 vCloud Hybrid Service 的 PostgreSQL 数据库捆绑版本。 vSphere Web Client 通过 vSphere Web Client，可以使用 Web 浏览器连接到 vCenter Server 实例，以便管理 vSphere 基础架构。 vSphere Client 通过新用户界面，可以使用 Web 浏览器连接到 vCenter Server 实例。术语、拓扑及工作流与 vSphere Web Client 用户界面的相同方面和元素保持高度一致。&lt;/p>
&lt;blockquote>
&lt;p>注：在 vSphere 6.5 版本中，并未针对 vSphere Client 实现 vSphere Web Client 中的所有功能。有关不受支持的功能的最新列表，请参见《vSphere Client 功能更新指南》，网址为 &lt;a href="https://www.vmware.com/info?id=1413">https://www.vmware.com/info?id=1413&lt;/a>。&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>vSphere ESXi Dump Collector&lt;/strong>&lt;/p>
&lt;p>vCenter Server 支持工具。可以将 ESXi 配置为在系统发生严重故障时将 VMkernel 内存保存到网络服务器而非磁盘。vSphere ESXi Dump Collector 将通过网络收集这些内存转储。&lt;/p>
&lt;p>&lt;strong>VMware vSphere Syslog Collector&lt;/strong>&lt;/p>
&lt;p>Windows 上的 vCenter Server 支持工具，支持网络日志记录，并可将多台主机的日志合并。您可以使用 vSphere Syslog Collector 将 ESXi 系统日志定向到网络服务器而非本地磁盘。对于支持的从中收集日志的主机，建议的最大数目为 30 个。有关配置 vSphere Syslog Collector 的信息，请参见 &lt;a href="https://kb.vmware.com/kb/2021652">https://kb.vmware.com/kb/2021652&lt;/a>。 vCenter Server Appliance 使用 Linux OS 内置的 Rsyslog 服务。有关如何通过设备管理界面将日志文件重定向到其他计算机的信息，请参见vCenter Server Appliance 配置。&lt;/p>
&lt;p>&lt;strong>vSphere Auto Deploy&lt;/strong>&lt;/p>
&lt;p>vCenter Server 支持工具，能够使用 ESXi 软件置备大量物理主机。可以指定要部署的映像以及要使用此映像置备的主机。也可以指定应用到主机的主机配置文件，并且为每个主机指定 vCenter Server 位置（文件夹或群集）。&lt;/p>
&lt;p>&lt;strong>VMware vSphere Update Manager 扩展&lt;/strong>&lt;/p>
&lt;p>Update Manager 可让 VMware vSphere 执行集中式自动修补程序和版本管理，并提供对 VMware ESXi 主机、虚拟机和虚拟设备的支持。VMware vSphere Update Manager 扩展是单独使用的 vCenter Server Appliance 6.5 的可选服务。&lt;/p></description></item><item><title>Ironic裸机服务——OpenStack</title><link>https://mydream.ink/posts/virtualization/openstack/openstack-ironic/</link><pubDate>Mon, 11 Dec 2017 08:54:08 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/openstack/openstack-ironic/</guid><description>&lt;p>OpenStack为用户提供裸机服务（区别于虚拟机），该服务在OpenStack项目中被称作ironic。它可以独立使用或者作为OpenStack Cloud的一部分使用，并与OpenStack认证（keystone），计算（nova），网络（neutron），图像（glance）和对象存储（swift）服务集成。 当裸机服务适当配置了计算和网络服务时，可以使用计算服务的API来配置裸机。不过，由于物理服务器和交换机硬件的特性不同，对于裸机实例的操作很有限。例如，实时迁移不能在裸机实例上执行。 为了涵盖广泛的硬件，OpenStack社区利用开源技术（例如，PXE和IPMI）的参考驱动程序。 Ironic的可插拔驱动架构还允许硬件供应商编写和提供驱动程序来提高性能或补充社区驱动程序未提供的功能。&lt;/p>
&lt;h1 id="为何提供裸机">为何提供裸机？&lt;/h1>
&lt;p>以下是在云上裸机（物理服务器）的一些案例，当然有许多其它更有趣的：&lt;/p>
&lt;ul>
&lt;li>高性能计算集群&lt;/li>
&lt;li>使用无法虚拟化的设备&lt;/li>
&lt;li>数据库托管（某些数据库在hypervisor中效果不好）&lt;/li>
&lt;li>单租户，专用硬件的性能，安全性，可靠性和其他监管要求&lt;/li>
&lt;li>快速部署云基础架构&lt;/li>
&lt;/ul>
&lt;h1 id="概念架构">概念架构&lt;/h1>
&lt;p>下图显示了配置物理服务器期间的关系以及所有服务如何发挥作用。 （注：Ceilometer和Swift也可以和Ironic一起使用，此处未标出）&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/virtualization/conceptual_architecture.png" alt="Conceptual Architecture">&lt;/p>
&lt;h1 id="逻辑架构">逻辑架构&lt;/h1>
&lt;p>下图显示了逻辑体系结构。它显示了构成Ironic服务的基本组件，Ironic服务与其他OpenStack服务的关系以及引导实例请求导致物理服务器供应的逻辑流程。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/logical_architecture.png" alt="">&lt;/p>
&lt;p>Ironic由以下组件构成：&lt;/p>
&lt;ol>
&lt;li>一个RESTful API服务，管理员或者其他服务可以通过这个服务与被管理的裸机进行交互。&lt;/li>
&lt;li>一个Conductor服务，主导大部分工作。通过API服务暴露功能，Conductor和API通过RPC通信。&lt;/li>
&lt;li>各种支持异构硬件的驱动。&lt;/li>
&lt;li>一个消息队列。&lt;/li>
&lt;li>用于存储包括Conductor状态，节点（物理服务器）和驱动程序等资源信息的数据库。&lt;/li>
&lt;/ol>
&lt;p>如Figure 1.2所示，用户请求通Nova API和Nova  Scheduler启动Nova 计算服务的一个实例。计算服务将此请求移交给Ironic服务，在Ironic服务中，请求从Ironic API传递到Conductor，再传递给驱动程序，为用户成功配置物理服务器。 就像Nova计算服务通知诸如Glance、Neutron、Swift等不同的OpenStack服务去创建一个虚拟机实例一样，此处Ironic服务也通知相同的网络、镜像以及其他的一些服务去创建一个裸机实例。&lt;/p>
&lt;h1 id="ironic的关键技术">Ironic的关键技术&lt;/h1>
&lt;h2 id="预启动执行环境pxe">预启动执行环境（PXE）&lt;/h2>
&lt;p>PXE是由英特尔和微软开发的Wired for Management（WfM）规范的一部分。PXE使系统的BIOS和网络接口卡（NIC）从网络而非磁盘引导计算机。Bootstrapping是系统将OS加载到本地内存以便处理器执行的过程。该功能简化了管理员的服务器部署和管理。&lt;/p>
&lt;h2 id="动态主机配置协议dhcp">动态主机配置协议（DHCP）&lt;/h2>
&lt;p>DHCP是在IP网络上使用的标准化网络协议，用于动态分配网络配置参数，例如接口或者服务的IP地址。使用PXE，BIOS使用DHCP获取网络接口的IP地址，并找到存储网络引导程序（NBP）的服务器。&lt;/p>
&lt;h2 id="网络引导程序nbp">网络引导程序（NBP）&lt;/h2>
&lt;p>NBP相当于GRUB（GRand Unified Bootloader）或LILO（LInux LOader）—— 用于本地启动的传统加载器。像硬盘环境中的引导程序一样，NBP负责将OS内核加载到内存中，以便可以通过网络引导操作系统。&lt;/p>
&lt;h2 id="简单文件传输协议tftp">简单文件传输协议（TFTP）&lt;/h2>
&lt;p>TFTP是一种简单的文件传输协议，通常用于在本地环境中的机器之间自动传输配置或启动文件。在PXE环境中，TFTP使用来自DHCP服务器的信息通过网络下载NBP。&lt;/p>
&lt;h2 id="智能平台管理接口ipmi">智能平台管理接口（IPMI）&lt;/h2>
&lt;p>IPMI是系统管理员用于计算机系统的带外管理和操作监控的标准化计算机系统接口。这是一种通过仅使用硬件的网络连接而不是操作系统来管理可能无响应或断电的系统的方法。&lt;/p>
&lt;h1 id="ironic部署架构">Ironic部署架构&lt;/h1>
&lt;p>Ironic RESTful API服务用于注册Ironic将管理的硬件。云管理员通常会注册硬件，指定其属性（如MAC地址和IPMI凭证）。可以有多个API服务实例。 在Ironic中Conductor负责大部分的工作。出于安全原因，建议将Conductor服务放置在隔离的主机上，因为这是唯一需要访问数据平面和IPMI控制平面的服务。 可以有多个Conductor服务实例来支持各种类型的驱动程序，也可以管理故障转移。Conductor服务的实例应该位于不同的节点上。每个Conductor本身可以运行多个驱动器来操作异构硬件。如下图所示。 该API公开了支持的驱动程序的列表以及为其服务的指挥主机的名称。&lt;/p>
&lt;p>&lt;img src="https://mydream.ink/assets/images/posts/devops/deployment_architecture_2.png" alt="">&lt;/p>
&lt;h1 id="了解裸机部署">了解裸机部署&lt;/h1>
&lt;p>启动实例请求进来时会发生什么？下图说明了配置裸机实例过程中涉及的步骤。 在部署过程之前必须满足这些条件：&lt;/p>
&lt;ul>
&lt;li>在裸机服务节点上配置从属软件包，其中的Ironic的Conductor正在运行，如tftp-server，ipmi，syslinux等，用于裸机配置。&lt;/li>
&lt;li>Nova必须配置为使用裸机服务端点，并且应将在Nova计算节点上为计算驱动程序配置使用Ironic驱动程序。&lt;/li>
&lt;li>为可用的硬件创建flavor，Nova必须知道从哪个flavor启动。&lt;/li>
&lt;li>要在Glance中提供的可用镜像。下面列出的是成功部署裸机所需的一些镜像类型：
&lt;ul>
&lt;li>bm-deploy-kernel&lt;/li>
&lt;li>bm-deploy-ramdisk&lt;/li>
&lt;li>user-image&lt;/li>
&lt;li>user-image-vmlinuz&lt;/li>
&lt;li>user-image-initrd&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>要通过Ironic RESTful API服务注册的硬件。&lt;/li>
&lt;/ul></description></item><item><title>LXC介绍</title><link>https://mydream.ink/posts/cloud-native/container/lxc1/</link><pubDate>Sat, 11 Nov 2017 08:56:06 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/container/lxc1/</guid><description>&lt;h2 id="什么是lxc">什么是LXC？&lt;/h2>
&lt;p>LXC是Linux内核中拥有容器功能的用户空间接口。 通过强大的API和简单的工具，它可以让Linux用户轻松创建并管理系统或应用程序容器。&lt;/p>
&lt;h2 id="特征">特征&lt;/h2>
&lt;p>当前的LXC使用以下的内核特性来封装进程&lt;/p>
&lt;ul>
&lt;li>Kernel namespaces (ipc, uts, mount, pid, network and user)&lt;/li>
&lt;li>Apparmor and SELinux profiles&lt;/li>
&lt;li>Seccomp policies&lt;/li>
&lt;li>Chroots (using pivot_root)&lt;/li>
&lt;li>Kernel capabilities&lt;/li>
&lt;li>CGroups (control groups)&lt;/li>
&lt;/ul>
&lt;p>LXC容器通常被认为是chroot和一个完整的虚拟机。 LXC的目标是尽可能创造一个环境 到一个标准的Linux安装，但不需要单独的内核。&lt;/p>
&lt;h2 id="组件">组件&lt;/h2>
&lt;p>LXC目前由几个单独的组件组成：&lt;/p>
&lt;ul>
&lt;li>liblxc库&lt;/li>
&lt;li>API的几种语言绑定
&lt;ul>
&lt;li>python3 (in-tree, long term support in 1.0.x and 2.0.x)&lt;/li>
&lt;li>lua (in tree, long term support in 1.0.x and 2.0.x)&lt;/li>
&lt;li>Go&lt;/li>
&lt;li>ruby&lt;/li>
&lt;li>python2&lt;/li>
&lt;li>Haskell&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>一套控制容器的标准工具&lt;/li>
&lt;li>分发容器模板&lt;/li>
&lt;/ul></description></item><item><title>LXC简介</title><link>https://mydream.ink/posts/cloud-native/container/lxc0/</link><pubDate>Sun, 05 Nov 2017 08:56:09 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/container/lxc0/</guid><description>&lt;h1 id="lxc">LXC&lt;/h1>
&lt;p>LXC是著名的经过严格测试的底层Linux容器运行库。从2008年开始发展，现已在世界各地的关键生产环境中得到证明。其中一些核心贡献者是帮助在Linux内核中实现各种知名集装箱功能的人员。&lt;/p>
&lt;h2 id="系统容器">系统容器&lt;/h2>
&lt;p>LXC的主要重点是系统容器。也就是说，提供尽可能接近VM但没有运行单独内核并模拟所有硬件的开销的容器。 这是通过内核安全功能（如命名空间，强制访问控制和控制组合）实现的。&lt;/p>
&lt;h2 id="非特权容器">非特权容器&lt;/h2>
&lt;p>非特权容器是没有任何权限运行的容器。这需要对内核中运行容器的用户名空间的支持。用户命名空间合并到主线内核之后，LXC是第一个支持非特权容器的运行库。 实质上，用户名空间隔离给定的UID和GID集合。这通过在主机上的一系列UID和GID之间建立到容器中UID和GID的不同（非特权）范围之间的映射来实现。内核将以这样一种方式转换此映射，即在容器内部，所有UID和GID按照您期望的从主机显示，而在主机上，这些UID和GID实际上是无特权的。例如，在容器内作为UID和GID 0运行的进程可能在主机上显示为UID和GID 100000。可以从相应的用户命名空间手册页中收集实现和工作细节。 由于非特权容器是一种安全增强功能，因此它们自然会受到内核强制执行的一些限制。为了提供一个功能齐全的非特权容器LXC与3个setuid代码交互：&lt;/p>
&lt;ul>
&lt;li>lxc-user-nic（setuid帮助器创建一个veth对，并在主机上桥接它）&lt;/li>
&lt;li>newuidmap（从shawdow库中，设置一个uid映射）&lt;/li>
&lt;li>newgidmap（从shawdow库中，设置一个gid映射）&lt;/li>
&lt;/ul>
&lt;p>一切都是以您自己的用户身份运行，也可以作为用户拥有的uid运行。 一般来说，LXC的目标是利用内核中可用的每个安全功能。这意味着LXC的配置管理将使经验丰富的用户根据自己的需要对LXC进行复杂的调整。 有关LXC安全性的更详细的介绍可以在以下链接中找到：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://linuxcontainers.org/lxc/security/">https://linuxcontainers.org/lxc/security/&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="移除特权">移除特权&lt;/h2>
&lt;p>LXC在没有正确的配置这些工具的情况下是可以运行的。只不过你这种容器的用途通常是非常有限的。这里突出两个最常见的问题：&lt;/p>
&lt;ol>
&lt;li>网络：没有依赖一个setuid帮助器为非特权用户设置适当的网络设备（参见LXC的&lt;code>lxc-user-nic&lt;/code>二进制文件），唯一的选择是与主机共享网络命名空间。虽然原则上应该是安全的，但共享主机的网络命名空间仍然是减少隔离的一个步骤，并增加攻击向量。此外，当主机和容器共享相同的网络命名空间时，内核将拒绝任何sysfs挂接。这通常意味着容器内的init二进制文件将无法正确启动。&lt;/li>
&lt;li>用户命名空间：如上所述，用户名空间是一个很大的安全性增强。但是，不依赖特权帮助者，主机上无权限的用户只能将自己的UID映射到容器中。然而，标准的POSIX系统需要65536个UID和GID来保证完整的功能。&lt;/li>
&lt;/ol>
&lt;h2 id="配置">配置&lt;/h2>
&lt;p>LXC通过一些简单的关键字配置，例如：&lt;/p>
&lt;ul>
&lt;li>lxc.rootfs.path&lt;/li>
&lt;li>lxc.mount.entry&lt;/li>
&lt;/ul>
&lt;p>LXC通过使用单点命名空间配置关键字。这意味着复杂的配置关键字（如&lt;code>lxc.net.0&lt;/code>）会展示各种子项，例如&lt;code>lxc.net.0.type&lt;/code>，&lt;code>lxc.net.0.link&lt;/code>，&lt;code>lxc.net.0.ipv6.address&lt;/code>等一些更精细的配置。 LXC被用作LXD的默认运行库，LXD是容器管理程序，它暴露出设计良好且稳定的REST-api。&lt;/p>
&lt;h2 id="内核需求">内核需求&lt;/h2>
&lt;p>LXC在2.6.32以上的任何内核上运行。所有它需要的是一个功能C编译器。 LXC适用于提供必要内核功能的所有架构。这包括（但不限于）：&lt;/p>
&lt;ul>
&lt;li>i686&lt;/li>
&lt;li>x86_64&lt;/li>
&lt;li>ppc,ppc64,ppc64le&lt;/li>
&lt;li>s390x&lt;/li>
&lt;li>armvl7,arm64&lt;/li>
&lt;/ul>
&lt;p>LXC至少还支持以下C标准库：&lt;/p>
&lt;ul>
&lt;li>glibc&lt;/li>
&lt;li>musl&lt;/li>
&lt;li>bionic(Android&amp;rsquo;s libc)&lt;/li>
&lt;/ul>
&lt;h2 id="向后兼容">向后兼容&lt;/h2>
&lt;p>LXC一直专注于强大的向后兼容性。事实上，API从1.0.0版起没有被破坏。主LXC目前处于版本2。&lt;/p></description></item><item><title>SDN发展必然</title><link>https://mydream.ink/posts/virtualization/sdn/sdn-development/</link><pubDate>Sun, 22 Oct 2017 08:54:08 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/sdn/sdn-development/</guid><description>&lt;p>现在很多人认为云可能是虚拟化的终极。但是，ICT（Information Communications Technology，信息通信技术）当然还有许多要素才开始感受到虚拟化的影响，更不用说实际上被虚拟化了。网络是突出的例子。现在的首选术语是“软件定义网络”或SDN而不是V首字母缩略词，尤其是因为虚拟网络和VPN仍将存在于新的软件定义的世界，这肯定是正确的跨越所有ICT。 当光纤到桌面/家庭是可行，4G速度将很快在千兆位时，为什么我们需要虚拟化网络？开放网络基金会（ONF）刚刚两年多，已经成为了像ICT这样独立基础的领先机构，直白的说，今天的“传统网络架构不能满足企业、运营商和终端用户的需求”。即使硬件工程不断发展和进步，可是标准和协议的不足限制我们充分利用基础架构的能力。视频、图像、移动数据以及即将到来的“物联网”当然也还包括大数据的数据量可能会让因特网无法承受，这也就使得虚拟化网络变得更加重要了。基因组学、天文学、天体物理学、气象学和许多其他科学领域可以注重网络与数据量而不用管传统的分层交换架构。&lt;/p>
&lt;h1 id="传输和控制层面">传输和控制层面&lt;/h1>
&lt;p>SDN的概念很简单：如果数据或业务承载层和控制层被去耦合，则以前的静态网络可以变得智能、响应、可编程和集中控制。所有这些都可以根据逻辑规则自动响应流量模式、流量类型或紧急情况。网络设备，特别是交换机和路由器，通常涉及一些来自多个制造商不同的内置的智能，通常是专有的内部代码。现在，网络工程师几乎都在抱怨虚拟机只需要点几下，几分钟就能让它运行起来，但是网络设备却要花费数十天的时间来正确配置和测试与网络的接口。 OpenFlow是ONF为SDN设置的主协议集，采用了斯坦福大学和伯克利分校开发的标准，几年前由谷歌和后来的Facebook接受。如今已经广泛采用，如果您今天订购网络交换机或路由器，交付的产品几乎肯定会具有OpenFlow兼容性内置或将提供升级。另一方面，谨慎的网络设备制造商可能没有勉强地采用SDN，因为这威胁到了其Ethernet/IP的环境。但是，他们确实认识到SDN的必然性，因为它将为从WAN到数据中心到运营商的所有功能带来明确的下一代功能。这并不是我们这些用户和单个消费者能决定的，都是市场的驱动。&lt;/p>
&lt;h1 id="架构依赖">架构依赖&lt;/h1>
&lt;p>曾在IBM，HP和Nortel专注网络技术的专家，现任ONF执行董事Dan Pitt 说：“我们依赖于同样的网络架构，已经超过45年，其中有些仍然显示出原始Arpanet的特征。最初，互联网的目标是生存性，所有的数据都必须存在于每一个节点上，但也意味着只有这么多的数据可以存在于每一个设备上。SDN原则上满足了现在和将来的需求，这是ICT工程文化的一个全面变化，也是民主化的一个可喜的选择，使得网络本身具有可编程性和适应性。” 来自ICT部门的受访者Pitt认为，SDN将需要两三年或更多的时间，成为所有网络和软硬件运行和管理的缺省设置。“今天的例子比Google更加明显和开放，SDN已经成为了规范，并展示了应对网络上各种需求的优势。” 他提到了SDN一个大的应用场景。加州大学从教职工到学生再到安全和维护人员超过12万用户，这其中95%的人携带着自己的设备，他们高度分散。它拥有从电子邮件和文本到全动态高清视频和大数据处理几乎所有已知的数据流量和容量。这意味着它甚至比全球大多数企业面临更多的ICT复杂性和网络性能问题。Pitt说，SDN是管理所有这一切并且在整个校园提供高服务质量和满意度水平的最成功的工具。&lt;/p>
&lt;h1 id="复杂易错">复杂易错&lt;/h1>
&lt;p>如今的网络管理起来复杂、耗时、易错而且还成本巨大。Dan Pitt 解释说,完全灵活和动态网络管理控制的简单的用户界面可以是我们的一个终极愿景。“它们将是基础设施的象征，但不需要特殊技能，我们可以预见用简单语言编写的策略和业务规则，甚至可以使用符号和图形独立的语言来控制网络中没有按照已有策略自动化的所有元素。” 思科在网络的任何发展中都很重要，作为技术领导者，同时也可能是安装系统数量最多的供应商，尤其是硬件。从概念上讲，它必然算是参与SDN运动的ONF的一个成员。思科开放网络环境被描述为一种“网络编程策略”，并在协议和行业标准中提供选择。这对于ONF来讲可能不是一个福音，但重要的一点是，在这个早期的采用阶段，行业领先的网络系统供应商是广泛共识的一部分。 思科英国和爱尔兰首席技术官Ian Foddering 说：“一年多以来，我们一直在跟客户谈论SDN。目前，我们看到ONF模式可能是控制层和数据层的经典且简单的分离，但我们相信，在真实的市场世界中，混合SDN解决方案是多数组织最适合和实际使用的方法。他们希望利用他们已经拥有的东西，但要有新的集中控制。”他坚持认为，思科致力于开放标准，并提到上个月刚刚成立的OpenDaylight联盟。该联盟处于Linux基金会之下，其中包括ONF的大部分成员，Citrix，IBM，HP，Juniper Networks，VMware 等也随着Cisco一起加入。尽管该联盟似乎将使用openFlow，但ONF本身并不是该联盟的一部分。&lt;/p>
&lt;h1 id="发展">发展&lt;/h1>
&lt;p>Foddering谈到，现实点讲，在SDN在市场上想要达到任何规模至少需要3年的时间。他相信，数据中心和云服务提供商以及一些大型全球企业将引领这一方向。“SDN将协助创建大规模可扩展的数据中心和更高水平的自动化，同时具有经济和效率方面的优势。我们最近在爱尔兰和英国进行的一项调查表明，人们对于SDN所提供的服务的了解已经到达了一种几乎让人吃惊的程度。人们知道它是什么，它将走向何处以及潜在的好处。我们都在探索如何走向SDN。” 他说，简单的使用和管理是关键，网络复杂性完全隐藏在后端。“在某些方面，SDN应该真正被称为‘用户定义的网络’，这将为网络管理带来更多的敏捷性和响应性，我们也将它视为将网络智能暴露给应用程序，再次增加了协调和自动化。更深入的网络智能被引入一类新的分析应用程序，可以促进更复杂的网络策略并支持驱动网络的业务逻辑，”Foddering说。“这最终使得网络更有价值，并且可以支持更多的创新和创收服务。”&lt;/p>
&lt;h1 id="过去的困境">过去的困境&lt;/h1>
&lt;p>VMware的首席技术专家Joe Baguley认为：“网络在过去一直停滞不前，我们在服务器虚拟化和云计算方面取得了长足的进步，今天为新的虚拟机或应用程序的配置网络和安全性仍然是涉及人，键盘和CLI的大量的手动工作。“与ICT中的其他形式的虚拟化一样，自动化的关键是将软件从硬件中抽象出来。软件定义的数据中心的愿景是下一代ICT的核心，在这一阶段，网络是阻碍我们基础设施中几乎所有其他方面发展的障碍。 “ICT发展的总体趋势是明确的，”Baguley说。“我们正在转向一种逻辑可行且最终灵活的软件定义和控制的环境，当然了其本质还是在硬件上运行。管理一个组织中的数千台服务器必须成为编程式，并转向策略驱动的自动化，使其无论是在规模上，还是在应对不断变化的流量需求方面实现高效性和灵活性。” 他补充说，所有这些都将推进内部计算与私有云和公共云之间的关系。随着时间的推移，组织将更加清晰地建立起核心，而不是数据、应用程序和其他所有东西。SDN以及所有其他形式的虚拟化，都将能够根据组织的策略和当前的需求，实现工作负载的自由而无缝移动。“非常严格的sla和安全肯定会适用，”Baguley说，“事实上，在一个虚拟的和软件定义的ICT世界中，一些服务提供商无疑会开始根据他们的物理资产来区分他们自己。关键是，我们对公共云进行全面分析，以根据用户的策略和业务规则来处理所有工作负载。”&lt;/p>
&lt;h1 id="炒作之后">炒作之后&lt;/h1>
&lt;p>“我们在最近几十年里清楚地看到，ICT实际上从未成熟，当然也从未停止，”Citrix的云平台组总监Damian Saunders说。 “我们可以回顾一下那些在他们的时代引起了大量炒作的新技术，并清楚地看到那些成功的技术都是建立在良好的科学基础上的。虚拟化当然是一个很好的例子，从它所运行的硬件中抽象出软件和控制。现在，作为一个行业，我们已经把这个概念带到了网络上，并且已经得到了很多相关的专业知识和技术。” Saunders是另一个相信“user defined networking” 的人，在很多方面都是一个更有意义的术语，因为SDN提供的不仅仅是虚拟化，从现在起它将把策略驱动的灵活性带到我们管理企业计算的方式中。例如，它将使更灵活的软件许可条款，并将整个基础设施变成一个可编程的平台，供用户和信息使用。我们需要为数据中心设计更多的复杂性，以及在下一代ICT中为用户呈现简单性的智能。”&lt;/p>
&lt;h1 id="弹性能力">弹性能力&lt;/h1>
&lt;p>新时代下，弹性是一个关键的概念。Saunders说，“传统上，我们必须提前对硬件能力进行预测和计划，通常是为资产的可能生命周期做准备。当然，资源是可以增加的，但总的来说，我们总是在购买更多，而我们实际上知道我们不太可能使用这么多。“爆裂”是一个几乎不可能的概念。那是旧模式。现在，我们根据需求提供资源，如有必要就将其收回，并遵循业务和使用的正常的衰落和流动——更不用提收入了。服务条款和软件许可也可以遵循用户定义的需求和更好的条款。这都是真正的变革。” 惠普仍然是世界上最大的多产品、多服务ICT巨头之一，也同时参与了ONF和Open Daylight联盟。Bruno Hareng是惠普分管SDN的首席技术经理，他解释说，SDN是一种不可避免的回应，因为各种各样的网络都面临着各种各样的挑战，比如大数据和移动消费者和商业设备的指数需求。“传统网络面临着应用程序范围，流量类型和数据量的问题，” 他说，“我们需要将服务器，存储和网络集成到一个通用的平台中，集中智能地控制，以识别和满足所有不同和不断变化的应用程序和工作负载的需求，刚性架构不能这样做。”&lt;/p>
&lt;h1 id="多种实现">多种实现&lt;/h1>
&lt;p>Hareng承认，在市场上有许多不同的方法，但他说，惠普正在采用OpenFlow的定义和逻辑，在网络架构中对控制和数据层进行耦合。“这是相当通用的，允许跨ICT和电信的互操作性。在组织内部，可以采用其他方法来实现相同的SDN特性，并在基础设施中得到结果，尤其是应用程序感知和用户定义的灵活性。就像其他许多ICT一样，现实生活很可能会产生混合或融合的解决方案，我们当然还需要管理现有的网络设备。” Hareng指出了通用通信迅速成长为协作的关键元素，并在网络语音和视频造成实时需求的例子。“即使普通用户现在也能理解管理网络流量的困难，以确保通用通信的优先级和QoS。”但他们也很欣赏这种价值。SDN为应用程序提供了相同的功能，使网络能够动态地响应每个应用程序的需求。例如，已经证明了SDN已经极大地改善了Hadoop的性能。”&lt;/p>
&lt;h1 id="家庭领域">家庭领域&lt;/h1>
&lt;p>在这里，爱尔兰的网络专家Data Edge首席技术官Paul Phelan表示,现在全球拥有基础架构和带宽，也就拥有真正利用SDN的潜力。“我们不得不花费几代技术学习我们的方式，但SDN使现在成为一个网络拥有大脑的时代。传统上，离散组件的智能有限，这样做：配置一个大路由器确实仍然是ICT中最危险的工作之一，错误和错误配置有巨大的可能性，一位数字的输入错误可能会导致整个网络丢失。” 相当正确的是，在Phelan的意见中，应用程序正在跨越ICT占据首席地位，这是一个自然而然的演进。“从某种意义上说，我们可以问为什么现在还没有发生这样的事情，但实际上近年来，已经有了一种ICT的达尔文进化，我们已经开发出了强大和成熟的技术，由于一些专有的因素输家已经走了。最近，虚拟化和相关技术已经足够强大，足以应对网络的复杂性，尤其是控制层。” 即使SDN技术尚未成为市场的主流，SDN技术已经到位并进行了良好的测试。 “自从2010年以来，Google一直在SDN上运行，OpenFlow的标准和性能和设备测试也是我们所参与的一个领域 - SDN然后提供几乎无限的灵活性和可扩展性，突发和激增？看到服务提供商和运营商按需提供带宽，竞争将同事推动软件控制能够提供性能和经济性。”&lt;/p></description></item><item><title>Ubuntu安装 Docker 社区版</title><link>https://mydream.ink/posts/cloud-native/container/install-docker-on-ubuntu/</link><pubDate>Sun, 22 Oct 2017 08:54:08 +0800</pubDate><guid>https://mydream.ink/posts/cloud-native/container/install-docker-on-ubuntu/</guid><description>&lt;h2 id="准备">准备&lt;/h2>
&lt;h1 id="系统要求">系统要求&lt;/h1>
&lt;p>要安装Docker CE，我们需要这些64位版本的Ubuntu：&lt;/p>
&lt;ul>
&lt;li>Artful 17.10 (Docker CE 17.11 Edge and higher only)&lt;/li>
&lt;li>Zesty 17.04&lt;/li>
&lt;li>Xenial 16.04 (LTS)&lt;/li>
&lt;li>Trusty 14.04 (LTS)&lt;/li>
&lt;/ul>
&lt;p>在Ubuntu &lt;code>x86_64&lt;/code>、&lt;code>armhf&lt;/code>、&lt;code>s390x&lt;/code>（IBM Z）和&lt;code>ppc64le&lt;/code>（IBM Power）架构上支持Docker CE。&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>ppc64le&lt;/code>和&lt;code>s390x&lt;/code>限制：IBM Z和Power体系结构的软件包仅在Ubuntu Xenial及更高版本上可用。&lt;/p>
&lt;/blockquote>
&lt;h1 id="卸载旧版本">卸载旧版本&lt;/h1>
&lt;p>老版本的Docker被称为docker或者docker-engine。如果安装了这些，请将其卸载：&lt;/p>
&lt;pre>&lt;code>sudo apt-get remove docker docker-engine docker.io
&lt;/code>&lt;/pre>
&lt;p>如果&lt;code>apt-get&lt;/code>报告没有安装这些软件包，则可以直接跳过这一步。&lt;/p>
&lt;p>&lt;code>/var/lib/docker/&lt;/code>的内容（包括映像，容器，卷和网络）将被保留。 Docker CE包现在称为&lt;code>docker-ce&lt;/code>。&lt;/p>
&lt;h2 id="安装docker-ce">安装Docker CE&lt;/h2>
&lt;p>您可以根据您的需要以不同的方式安装Docker CE：&lt;/p>
&lt;ul>
&lt;li>大多数用户设置Docker的存储库并从中进行安装，以方便安装和升级任务。这是推荐的方法。&lt;/li>
&lt;li>有些用户下载deb软件包手动安装，并完全手动管理升级。这很适合在诸如没有互联网访问的封闭系统上安装Docker。&lt;/li>
&lt;li>在测试和开发环境中，有些用户选择使用自动便捷脚本来安装Docker。&lt;/li>
&lt;/ul>
&lt;h1 id="使用软件仓库安装">使用软件仓库安装&lt;/h1>
&lt;p>首次在新的主机上安装Docker CE之前，需要设置Docker软件仓库。之后，您可以从该库安装和更新Docker。&lt;/p>
&lt;h2 id="设置软件仓库">设置软件仓库&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>更新apt包索引：&lt;/p>
&lt;pre>&lt;code>sudo apt-get update
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;p>安装软件包以允许apt通过HTTPS使用存储库：&lt;/p>
&lt;pre>&lt;code>sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;p>添加Docker的官方GPG密钥：&lt;/p>
&lt;pre>&lt;code>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
&lt;/code>&lt;/pre>
&lt;p>通过查看指纹的最后8个字符，确认您现在已经拥有指纹&lt;code>9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88&lt;/code>的密钥。&lt;/p>
&lt;pre>&lt;code>sudo apt-key fingerprint 0EBFCD88
&lt;/code>&lt;/pre>
&lt;blockquote>
&lt;p>pub 4096R/0EBFCD88 2017-02-22&lt;br>
Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88&lt;br>
uid Docker Release (CE deb) &lt;a href="mailto:docker@docker.com">docker@docker.com&lt;/a>&lt;br>
sub 4096R/F273FCD8 2017-02-22&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>使用以下命令来设置&lt;code>stable&lt;/code>存储库。即使您想从&lt;code>edge&lt;/code>或&lt;code>test&lt;/code>存储库安装构建，也总是需要&lt;code>stable&lt;/code>存储库。要添加&lt;code>edge&lt;/code>或&lt;code>test&lt;/code>存储库，请在下面的命令中在单词&lt;code>stable&lt;/code>之后添加&lt;code>edge&lt;/code>或&lt;code>test&lt;/code>（或两者）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>：下面的&lt;code>lsb_release -cs&lt;/code>子命令返回你的Ubuntu发行版的名字，比如&lt;code>xenial&lt;/code>。有时候，在像Linux Mint这样的发行版中，您可能必须将&lt;code>$(lsb_release -cs)&lt;/code>更改为您的父级Ubuntu发行版。例如，如果您使用Linux Mint Rafaela，则可以使用&lt;code>trusty&lt;/code>。&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>Duplicate Widget&lt;/li>
&lt;li>Remove Widget&lt;/li>
&lt;/ul>
&lt;p>x86_64 / amd64&lt;/p>
&lt;p>armhf&lt;/p>
&lt;p>IBM Power (ppc64le)&lt;/p>
&lt;p>IBM Z (s390x)&lt;/p>
&lt;p>x86_64 / amd64&lt;/p>
&lt;pre>&lt;code>sudo add-apt-repository \
&amp;quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>armhf&lt;/p>
&lt;pre>&lt;code>sudo add-apt-repository \
&amp;quot;deb [arch=armhf] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>IBM Power (ppc64le)&lt;/p>
&lt;pre>&lt;code>sudo add-apt-repository \
&amp;quot;deb [arch=ppc64le] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>IBM Z (s390x)&lt;/p>
&lt;pre>&lt;code>sudo add-apt-repository \
&amp;quot;deb [arch=s390x] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable&amp;quot;
&lt;/code>&lt;/pre>
&lt;/li>
&lt;/ol></description></item><item><title>SDN的定义</title><link>https://mydream.ink/posts/virtualization/sdn/sdn-definition/</link><pubDate>Sat, 21 Oct 2017 09:54:08 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/sdn/sdn-definition/</guid><description>&lt;h1 id="sdnsoftware-defined-networking软件定义网络的定义httpswwwopennetworkingorgsdn-resourcessdn-definition">&lt;a href="https://www.opennetworking.org/sdn-resources/sdn-definition">SDN（Software-Defined Networking，软件定义网络）的定义&lt;/a>&lt;/h1>
&lt;h2 id="什么是sdn">什么是SDN？&lt;/h2>
&lt;p>转发层与网络控制层的物理分离以及控制多设备的控制层&lt;/p>
&lt;p>软件定义网络（SDN）是一种新兴的体系结构，具有动态，可管理性，成本效益和适应性，使其成为当今应用的高带宽，动态性能的理想选择。该架构解耦了网络控制和转发功能，使网络控制成为可直接编程的，底层基础设施将被抽象出来，用于应用和网络服务。OpenFlow®协议是构建SDN解决方案的基础元素。 SDN架构是：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>可直接编程&lt;/strong>：网络控制是直接可编程的，因为它与转发功能分离开来。&lt;/li>
&lt;li>&lt;strong>敏捷&lt;/strong>：来自转发的抽象控制使管理员可以动态调整网络流量，以满足不断变化的需求。&lt;/li>
&lt;li>&lt;strong>集中管理&lt;/strong>：网络智能（在逻辑上）集中在基于软件的SDN控制器中，其维护网络的全局视图，其将应用和策略引擎看作是单个逻辑交换机。&lt;/li>
&lt;li>&lt;strong>编程配置&lt;/strong>：SDN使网络管理员通过动态，自动化的SDN程序非常快速地配置，管理，保护和优化网络资源，他们可以自己编写，因为程序不依赖于专有软件。&lt;/li>
&lt;li>&lt;strong>开放标准和厂商中立&lt;/strong>：通过开放标准实现，SDN简化了网络设计和操作，因为SDN控制器提供了指令，而不是多个供应商特定的设备和协议。
![image](&lt;a href="https://images.weserv.nl/?url=https://www.opennetworking.org/images/stories/sdn-resources/meet-sdn/sdn-3layers.gif">https://images.weserv.nl/?url=https://www.opennetworking.org/images/stories/sdn-resources/meet-sdn/sdn-3layers.gif&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="计算趋势正在推动网络变革">计算趋势正在推动网络变革&lt;/h2>
&lt;p>SDN解决了传统网络的静态架构不适合当今数据中心、校园和运营商环境的动态计算和存储需求的事实。推动新网络范式需求的关键计算趋势包括：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>改变传输模式&lt;/strong>：通过公共和私有云通常访问地理分布式数据库和服务器的应用程序需要极其灵活的流量管理和按需访问带宽。&lt;/li>
&lt;li>&lt;strong>“消费化信息技术”&lt;/strong>：BYOD（Bring Your Own Device，携带您自己的设备）趋势需要既灵活又安全的网络。 - &lt;strong>云服务的兴起&lt;/strong>：用户期望按需访问应用程序，基础架构和其他IT资源。&lt;/li>
&lt;li>&lt;strong>大数据意味着更大带宽&lt;/strong>：处理今天的大型数据集需要大量的并行处理，这将加剧对额外容量和任何到任何连接的不断需求。 为了满足不断变化的计算趋势所带来的网络需求，网络设计师发现自己受到当前网络的限制：&lt;/li>
&lt;li>&lt;strong>过于复杂&lt;/strong>：添加或移动设备和实施全网络策略是复杂，耗时的，主要是手动尝试风险的服务中断，阻止网络更改。&lt;/li>
&lt;li>&lt;strong>无法缩放&lt;/strong>：链接超额订购对提供可扩展性的历史悠久的方法对于虚拟化网络中的动态流量模式是无效的，这是在整个计算池中具有大规模并行处理算法和相关数据集的服务提供商网络中更为显着的问题。&lt;/li>
&lt;li>&lt;strong>厂商依赖&lt;/strong>：冗长的供应商设备产品周期和缺乏标准的开放接口限制了网络运营商将网络定制到各自环境的能力。&lt;/li>
&lt;/ul></description></item><item><title>SDN架构组件</title><link>https://mydream.ink/posts/virtualization/sdn/sdn-architecture/</link><pubDate>Sat, 21 Oct 2017 08:54:08 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/sdn/sdn-architecture/</guid><description>&lt;h2 id="架构组件">架构组件&lt;/h2>
&lt;p>以下定义并解释了各架构组件：&lt;/p>
&lt;p>&lt;img src="https://images.weserv.nl/?url=https://upload.wikimedia.org/wikipedia/commons/e/e6/SDN-architecture-overview-transparent.png" alt="架构图">&lt;/p>
&lt;h1 id="sdn应用程序">SDN应用程序&lt;/h1>
&lt;p>&lt;code>SDN应用程序&lt;/code>是通过&lt;code>北向接口&lt;/code>（&lt;code>NBI&lt;/code>）显式，直接和编程地将其网络要求和所需网络行为传达给&lt;code>SDN控制器&lt;/code>。此外，他们可能会为了内部决策而消耗网络的抽象视图。&lt;code>SDN应用程序&lt;/code>由一个_SDN应用程序逻辑_和一个或多个_NBI驱动程序_组成。&lt;code>SDN应用程序&lt;/code>本身可能会暴露另一层抽象网络控制，从而通过各自的_NBI代理_提供一个或多个更高级别的&lt;code>NBI&lt;/code>。&lt;/p>
&lt;h1 id="sdn控制器">SDN控制器&lt;/h1>
&lt;p>&lt;code>SDN控制器&lt;/code>是一个逻辑上集中的实体，负责将&lt;code>SDN应用程序&lt;/code>的要求转换为&lt;code>SDN数据路径&lt;/code>，以及向&lt;code>SDN应用&lt;/code>提供网络的抽象视图（可能包括统计信息和事件） 。&lt;code>SDN控制器&lt;/code>由一个或多个_NBI代理_，_SDN控制逻辑_和&lt;code>控制层到数据层接口&lt;/code>（&lt;code>CDPI&lt;/code>）驱动器组成。作为逻辑集中式实体的定义既不规定也不排除实施细节，例如多个控制器的联合，控制器的分层连接，控制器之间的通信接口，以及虚拟化或网络资源的切片。&lt;/p>
&lt;h1 id="sdn数据路径">SDN数据路径&lt;/h1>
&lt;p>&lt;code>SDN数据路径&lt;/code>是一种逻辑网络设备，可以对其广告转发和数据处理功能进行可见性和无争议的控制。逻辑表示可以包括物理底物资源的全部或子集。&lt;code>SDN数据路径&lt;/code>包括_CDPI代理_和一组一个或多个流量转发引擎和零个或多个流量处理功能。这些引擎和功能可能包括数据通路的外部接口或内部流量处理或终止功能之间的简单转发。一个或多个&lt;code>SDN数据路径&lt;/code>可以包含在单个（物理）网络元件中——作为单元管理的通信资源的集成物理组合。&lt;code>SDN数据路径&lt;/code>也可以跨多个物理网元进行定义。这种逻辑定义既不规定也不排除实现细节，例如物理映射的逻辑映射，共享物理资源的管理，&lt;code>SDN数据通路&lt;/code>的虚拟化或分片，与非SDN网络的互操作性，以及数据处理功能，可以包括OSI 4-7层功能。&lt;/p>
&lt;h1 id="控制层到数据层接口">控制层到数据层接口&lt;/h1>
&lt;p>SDN &lt;code>CDPI&lt;/code>是在&lt;code>SDN控制器&lt;/code>和&lt;code>SDN数据路径&lt;/code>之间定义的接口，其至少提供： 1. 对所有转发操作的编程控制， 2. 能力广告， 3. 统计报告， 4. 事件通知。 SDN的一个价值在于期望&lt;code>CDPI&lt;/code>以开放，供应商中立和可互操作的方式实施。&lt;/p>
&lt;h1 id="sdn北向接口">SDN北向接口&lt;/h1>
&lt;p>SDN &lt;code>NBI&lt;/code>是&lt;code>SDN应用程序&lt;/code>和&lt;code>SDN控制器&lt;/code>之间的接口，通常提供抽象网络视图，并能够直接表达网络行为和要求。这可能发生在任何抽象级别（纬度）和不同功能集（经度）之间。SDN的一个价值在于期望这些接口以开放，供应商中立和可互操作的方式实现。&lt;/p></description></item><item><title>OpenFlow简介</title><link>https://mydream.ink/posts/virtualization/sdn/openflow/</link><pubDate>Wed, 11 Oct 2017 08:54:08 +0800</pubDate><guid>https://mydream.ink/posts/virtualization/sdn/openflow/</guid><description>&lt;h1 id="openflow">OpenFlow&lt;/h1>
&lt;p>来源于维基百科——自由百科全书 &lt;strong>OpenFlow&lt;/strong> 是一种通信协议，可以通过网络访问网络交换机或路由器的转发平面。&lt;/p>
&lt;h2 id="描述">描述&lt;/h2>
&lt;p>OpenFlow允许网络控制器确定网络交换机网络的路径。控制器与交换机不同。 控制与转发的分离允许使用可行的比&lt;a href="https://en.wikipedia.org/wiki/Access_control_list">访问控制列表&lt;/a>（ACL）和路由协议更复杂的流量管理。 此外，OpenFlow允许使用单一的开放协议远程管理来自不同供应商的交换机（通常每个都有自己的专有接口和脚本语言）。 协议的发明人认为OpenFlow是&lt;a href="https://en.wikipedia.org/wiki/Software_defined_networking">软件定义网络&lt;/a>（SDN）的推动者。 OpenFlow允许通过添加，修改和删除数据包匹配规则和操作来远程管理&lt;a href="https://en.wikipedia.org/wiki/Layer_3">第3层&lt;/a>交换机的数据包转发表。 这样，路由决策可以由控制器定期或临时进行，并转换成具有可配置寿命的规则和动作， 然后将其部署到交换机的流表中，以匹配的速度将匹配的分组实际转发到交换机这些规则的持续时间。 由交换机无法匹配的数据包可以转发给控制器。 然后，控制器可以决定修改一个或多个交换机上的现有流表规则，或者部署新的规则，以防止交换机与控制器之间的流量结构性流动。 它甚至可以决定转发流量本身，只要它告诉交换机转发整个数据包，而不是只是他们的header。 OpenFlow协议分层在&lt;a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">传输控制协议&lt;/a>（TCP）之上，并规定了传输层安全性（TLS）的使用。 控制器应该在TCP端口6653上侦听想要建立连接的交换机。 早期版本的OpenFlow协议非官方使用端口6633.&lt;/p>
&lt;h2 id="历史">历史&lt;/h2>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Open_Networking_Foundation">开放网络基金会&lt;/a>（ONF） 是一个致力于促进和采用&lt;a href="https://en.wikipedia.org/wiki/Software-defined_networking">软件定义网络&lt;/a>（SDN） 的用户主导的组织， ONF将OpenFlow定义为在SDN架构的控制层和转发层之间定义的第一个标准通信接口。 OpenFlow允许直接访问和操纵诸如交换机和路由器的网络设备的转发平面，包括物理设备和虚拟设备（基于虚拟机管理程序）。 没有一个开放接口的转发平台，导致了如今的网络设备的现状，如单一，封闭和专用。 需要像OpenFlow这样的协议，将网络控制从专有网络交换机转移到开源和本地管理的控制软件。 许多网络交换机和路由器供应商已经宣布意图支持或正在运送支持OpenFlow的交换机， 包括&lt;a href="https://en.wikipedia.org/wiki/Alcatel-Lucent" title="Alcatel-Lucent">阿尔卡特朗讯&lt;/a>, &lt;a href="https://en.wikipedia.org/wiki/Big_Switch_Networks" title="Big Switch Networks">大交换机网络&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Brocade_Communications" title="Brocade Communications">博科通信&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Radisys">Radisys&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Arista_Networks">Arista Networks&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Pica8">Pica8&lt;/a>，&lt;a href="https://en.wikipedia.org/w/index.php?title=NoviFlow&amp;amp;action=edit&amp;amp;redlink=1">NoviFlow&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Huawei">华为&lt;/a>，&lt;a href="https://en.wikipedia.org/wiki/Cisco" title="Cisco">思科&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Dell_EMC">戴尔EMC&lt;/a>，&lt;a href="https://en.wikipedia.org/wiki/Extreme_Networks">Extreme Networks&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/IBM">IBM&lt;/a>，&lt;a href="https://en.wikipedia.org/wiki/Juniper_Networks" title="Juniper Networks">瞻博网络&lt;/a>， &lt;a href="https://en.wikipedia.org/w/index.php?title=Digisol&amp;amp;action=edit&amp;amp;redlink=1">Digisol&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/Larch_Networks">Larch Networks&lt;/a>，&lt;a href="https://en.wikipedia.org/wiki/Hewlett-Packard">Hewlett-Packard&lt;/a>， &lt;a href="https://en.wikipedia.org/wiki/NEC">NEC&lt;/a>和&lt;a href="https://en.wikipedia.org/wiki/MikroTik">MikroTik&lt;/a>。 一些网络控制平面实现使用协议来管理网络转发元素。OpenFlow主要用于安全通道上的交换机和控制器之间。 有关OpenFlow相关产品的相当全面的列表可以在ONF网站和SDNCentral网站上找到。&lt;/p></description></item></channel></rss>