<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>I&#39;m Yunlong</title>
  
  <subtitle>DevOps, Agile, Learner</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://yunlzheng.github.io/"/>
  <updated>2020-03-08T09:37:14.682Z</updated>
  <id>http://yunlzheng.github.io/</id>
  
  <author>
    <name>云龙</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>KT Connect项目在迭代了10个小版本后变化</title>
    <link href="http://yunlzheng.github.io/2020/03/08/ktconnect-update-2020/"/>
    <id>http://yunlzheng.github.io/2020/03/08/ktconnect-update-2020/</id>
    <published>2020-03-08T02:23:47.000Z</published>
    <updated>2020-03-08T09:37:14.682Z</updated>
    
    <content type="html"><![CDATA[<p>在Dockone在线分享二三一期中，我分享了如何使用<a href="https://github.com/alibaba/kt-connect" target="_blank" rel="noopener">KT Connect</a>实现本地与Kubernetes集群内服务的双向联调，以实现面向Kubernetes的高效的本地开发测试体验。之后我们继续迭代了10+小版本后为KT Connect带来了一些新的变化。</p><a id="more"></a><h3><span id="特性介绍">特性介绍</span></h3><p><a href="https://github.com/alibaba/kt-connect" target="_blank" rel="noopener">KT Connect</a>是面向Kubernetes开发者提供的一款轻量级的开发测试辅助工具。基于SSH协议实现本地与Kubernetes集群内服务之间的双向网络互通。从而可以直接在本地访问Kubernetes集群中的服务，以及从集群访问本地服务的能力。并提供了以下命令：</p><ul><li>connect: 建立本地-&gt;Kubernetes的网络通道，从而在本地可以直接访问PodIP, CluserIP以及Service DNS</li><li>exchange: 将集群中所有对特定应用的网络请求，全部转发到本地服务</li><li>mesh: 将部分对特定应用的网络请求，转发到本地服务。配合Istio可以实现精确的流量控制</li></ul><h3><span id="windows的原生支持">Windows的原生支持</span></h3><p>在新版本中全新引入socks5代理模式，解决了由于依赖sshuttle导致Windows用户无法使用connect功能的问题。 在全新的版本中，Windows用户可以直接使用如下命令：</p><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ktctl -d connect --<span class="function"><span class="keyword">method</span> <span class="title">socks5</span></span></span><br></pre></td></tr></table></figure><p>在命令执行成功后，通过设置http_proxy和https_proxy环境变量就可以直接在本地访问集群的PodIP以及ClusterIP。 </p><p>同时为了解决socks5模式下无法使用DNS域名解析的问题新增加参数<code>--dump2hosts</code>可以自动同步service dns解析规则到系统的hosts文件，并且在退出后自动清理。</p><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ktctl -d connect --<span class="function"><span class="keyword">method</span> <span class="title">socks5</span> --<span class="title">dump2hosts</span></span></span><br></pre></td></tr></table></figure><p>配合新增的IDEA插件<a href="https://plugins.jetbrains.com/plugin/13482-jvm-inject/versions" target="_blank" rel="noopener">JVM Inject</a>可以自动加载http_proxy以及https_proxy参数到Java启动命令中，从而可以在IDEA中无缝与集群内服务进行联调，<a href="https://alibaba.github.io/kt-connect/#/en-us/guide/how-to-use-in-idea" target="_blank" rel="noopener">点击这里</a>查看详细介绍</p><h3><span id="dns域名解析增强">DNS域名解析增强</span></h3><p>在旧版的KT Connect中本地如果要使用域名访问Kubernetes集群中的服务，只能使用类似于<code>&lt;svc&gt;.&lt;namespace&gt;.svc.cluster.local</code>这样的完整域名，在新版本中为了保持本地与集群的一致性。 新增了对<code>&lt;svc&gt;</code>以及<code>&lt;svc&gt;.&lt;namespace&gt;</code>DNS解析的完整支持。</p><h3><span id="新增run命令暴露本地服务到集群">新增<em>run</em>命令：暴露本地服务到集群</span></h3><p>在kubectl中我们可以使用<code>kubectl run</code>命令在集群中快速使用镜像创建deployment并暴露svc.</p><p>在KT Connect中我们也可以采用类似的方式直接在集群中暴露本地服务:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ktctl <span class="builtin-name">run</span> localservice <span class="attribute">--port</span>=5701 --expose</span><br></pre></td></tr></table></figure><p>其中localservice是注册到Kubernetes集群中的服务名，通过在集群中访问<code>localservice:5701</code>可以直接访问到本地运行的服务。</p><h3><span id="新增dashboard以及check命令">新增<em>dashboard</em>以及<em>check</em>命令</span></h3><p>为了简化用户对当前本地环境无法正常使用ktctl相关命令的问题，新增<code>check</code>命令用于检查本地环境依赖。通过<code>ktctl dashboard init</code>以及<code>ktctl dashboard open</code>可以帮助用户快速安装以及使用KT Connect的Dashboard支持。</p><h3><span id="新的项目kt-virtualenvironment">新的项目：Kt VirtualEnvironment</span></h3><p><a href="https://github.com/alibaba/kt-connect" target="_blank" rel="noopener">KT Connect</a>在本地通过双向网络互通让用户可以在本地和集群之间双向连通。而在集群侧我们引入[KT VirtualEnvironment]，(<a href="https://github.com/alibaba/virtual-environment)基于Service" target="_blank" rel="noopener">https://github.com/alibaba/virtual-environment)基于Service</a> Mesh实现隔离的测试环境，本地服务可以通过ktctl命令直接加入到特定的隔离测试环境中。更多详情请查看<a href="https://alibaba.github.io/virtual-environment/#/zh-cn/ve/quickstart" target="_blank" rel="noopener">KT VirtualEnvironment Quick Start</a></p><h3><span id="特别感谢">特别感谢</span></h3><p>在这10多个版本迭代过程中除了云效团队以外，还有以下用户一起参与了KT Connect的建设中排名不分先后：</p><ul><li><a href="https://github.com/linfan" target="_blank" rel="noopener">linfan</a></li><li><a href="https://github.com/fudali113" target="_blank" rel="noopener">fudali113</a></li><li><a href="https://github.com/zeusro" target="_blank" rel="noopener">zeusro</a></li><li><a href="https://github.com/sunmiOS" target="_blank" rel="noopener">sunmiOS</a></li><li><a href="https://github.com/mojo-zd" target="_blank" rel="noopener">mojo-zd</a></li><li><a href="https://github.com/csdnshyang" target="_blank" rel="noopener">csdnshyang</a></li><li><a href="https://github.com/sunny0826" target="_blank" rel="noopener">sunny0826</a></li><li>root2wf(dingtalk)</li></ul><p>也希望有更多的用户和开发者能够参与到KT Connect的项目中，一起从Cloud Native到Cloud To Native，打造面向Kubernetes的高效的本地开发测试体验。</p><p>想了解更多关于KT Connect相关的问题，欢迎加入钉钉群：</p><p><img src="/images/dingtalk-group-v2.png" alt="kt-dev"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在Dockone在线分享二三一期中，我分享了如何使用&lt;a href=&quot;https://github.com/alibaba/kt-connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KT Connect&lt;/a&gt;实现本地与Kubernetes集群内服务的双向联调，以实现面向Kubernetes的高效的本地开发测试体验。之后我们继续迭代了10+小版本后为KT Connect带来了一些新的变化。&lt;/p&gt;
    
    </summary>
    
    
      <category term="kubernetes" scheme="http://yunlzheng.github.io/tags/kubernetes/"/>
    
      <category term="kt connect" scheme="http://yunlzheng.github.io/tags/kt-connect/"/>
    
  </entry>
  
  <entry>
    <title>Windows环境下如何在本地IDEA中联调Kubernetes集群中的服务</title>
    <link href="http://yunlzheng.github.io/2019/12/14/how-to-integration-with-kubernetes-from-idea/"/>
    <id>http://yunlzheng.github.io/2019/12/14/how-to-integration-with-kubernetes-from-idea/</id>
    <published>2019-12-14T01:18:35.000Z</published>
    <updated>2020-03-08T02:41:26.441Z</updated>
    
    <content type="html"><![CDATA[<p>本地将介绍如何在Idea中使用KT Connect: <a href="https://github.com/alibaba/kt-connect" target="_blank" rel="noopener">https://github.com/alibaba/kt-connect</a> 项目实现本地与集群中服务的联调开发</p><a id="more"></a><h2><span id="本文适用于">本文适用于</span></h2><ul><li>使用Windows的Java开发者</li><li>IDEA作为主要开发IDE</li><li>Kubernetes作为开发测试环境</li><li>开发的应用包含多个服务，且存在相互调用关系</li></ul><h2><span id="通过本文希望解决的问题">通过本文希望解决的问题</span></h2><ul><li>本地联调环境搭建复杂</li></ul><p>既然已经有现成的在Kubernetes中的测试环境，为什么还要费劲在本地搭建一套副本？</p><ul><li>本地开发，本地联调</li></ul><p>想使用Kubernetes中的测试环境，那就得至少做一次代码编译，镜像构建然后再部署到集群中</p><h2><span id="快速开始">快速开始</span></h2><blockquote><p>前提条件，本地已安装kubectl并且能够正常与Kubernetes集群交互</p></blockquote><h3><span id="使用kt-connect启动本地到kubernetes集群的socks5代理服务">使用KT Connect启动本地到Kubernetes集群的SOCKS5代理服务</span></h3><p>从<a href="https://alibaba.github.io/kt-connect/#/zh-cn/downloads" target="_blank" rel="noopener">下载</a>最新版本的KT Connect Cli工具到本地，解压并拷贝ktctl命令行工具到系统PATH路径下。</p><p>验证安装结果</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ktctl <span class="comment">--version</span></span><br><span class="line">KT Connect <span class="built_in">version</span> <span class="number">0.0</span><span class="number">.8</span></span><br></pre></td></tr></table></figure><p>进入到Java项目根路径，并启动ktctl</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">cd $PROJECT_ROOT</span><br><span class="line">$ sudo ktctl -d connect <span class="comment">--method socks5</span></span><br><span class="line">...省略其它输出...</span><br><span class="line">4:31PM INF ==============================================================</span><br><span class="line">4:31PM INF <span class="keyword">Start</span> SOCKS5 Proxy: <span class="keyword">export</span> http_proxy=socks5://<span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">2223</span></span><br><span class="line"><span class="number">4</span>:<span class="number">31</span>PM INF ==============================================================</span><br><span class="line"><span class="number">4</span>:<span class="number">31</span>PM DBG <span class="keyword">Child</span>, os.Args = [ktctl -d <span class="keyword">connect</span> <span class="comment">--method socks5]</span></span><br><span class="line"><span class="number">4</span>:<span class="number">31</span>PM DBG <span class="keyword">Child</span>, cmd.Args = [ssh -oStrictHostKeyChecking=<span class="keyword">no</span> -oUserKnownHostsFile=/dev/<span class="literal">null</span> -i /<span class="keyword">Users</span>/yunlong/.kt_id_rsa -D <span class="number">2223</span> root@<span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span> -p2222 sh loop.sh]</span><br><span class="line">Handling <span class="keyword">connection</span> <span class="keyword">for</span> <span class="number">2222</span></span><br><span class="line"><span class="keyword">Warning</span>: Permanently added <span class="string">'[127.0.0.1]:2222'</span> (ECDSA) <span class="keyword">to</span> the <span class="keyword">list</span> <span class="keyword">of</span> known hosts.</span><br><span class="line"><span class="number">4</span>:<span class="number">31</span>PM DBG vpn(ssh) <span class="keyword">start</span> <span class="keyword">at</span> pid: <span class="number">56190</span></span><br><span class="line"><span class="number">4</span>:<span class="number">31</span>PM DBG KT proxy <span class="keyword">start</span> successful</span><br></pre></td></tr></table></figure><p>使用ktctl可以快速在本地启动一个基于<code>SOCKS5</code>协议的代理服务，通过该代理服务可以直接访问集群内资源。<br>在CMD中验证网络连通性，新建一个CMD窗口，并<code>根据ktctl中的日志输</code>出设置http_proxy，对于Windows用户需要使用<code>set</code>命令</p><figure class="highlight gams"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> http_proxy=socks5:<span class="comment">//127.0.0.1:2223</span></span><br><span class="line"># 尝试访问CLUSTER <span class="comment">IP</span>和<span class="comment">POD IP</span></span><br><span class="line">curl <span class="comment">http:</span>//<span class="comment">&lt;ClusterIP&gt;:&lt;Port&gt;</span></span><br></pre></td></tr></table></figure><h3><span id="在idea中与kubernetes集群中的程序联调">在IDEA中与Kubernetes集群中的程序联调</span></h3><p>对于Java程序来说如果希望所有网络请求能够通过SOCKS5代理完成，需要在启动程序时设置JVM参数<code>-DsocksProxyHost</code>和<code>-DsocksProxyPort</code>指定代理的IP和端口。当然手动配置是肯定不能接受的，首先ktctl启动的代理端口是动态的，其次并不是每次在IDEA中启动程序都需要使用代理。</p><p>如何解决？ktctl在socks5模式下，会自动在当前路径下生成.jvmrc文件。 该文件中会包含SOCKS5代理的相关JVM参数。在$PROJECT_ROOT下查看.jvmrc文件内容如下：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">-DsocksProxyHost</span>=<span class="number">127.0</span>.<span class="number">0.1</span></span><br><span class="line"><span class="attr">-DsocksProxyPort</span>=<span class="number">2223</span></span><br></pre></td></tr></table></figure><p>为了能够让IDEA在启动时自动使用该文件作为Java启动参数，我们需要在IDEA中安装插件<a href="https://plugins.jetbrains.com/plugin/13482-jvm-inject" target="_blank" rel="noopener">JVM Inject</a>, 用户可以在IDEA的Plugin管理中搜索并安装该插件。</p><p><img src="/images/install_jvm_inject_idea_plugin.png" alt="/images/install_jvm_inject_idea_plugin.png"></p><p>在IDEA中启动Java程序时，该插件会自动加载当前项目根路径下的.jvmrc并追加到Java的启动参数中。</p><p><img src="/images/idea_run_application.png" alt="/images/idea_run_application.png"></p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java <span class="built_in">..</span>.省略的其他输出<span class="built_in">..</span>. -Djava.rmi.server.<span class="attribute">hostname</span>=127.0.0.1 -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.<span class="attribute">enabled</span>=<span class="literal">true</span> -Dhttp.<span class="attribute">proxyHost</span>=127.0.0.1 -Dhttp.<span class="attribute">proxyPort</span>=2223 <span class="built_in">..</span>.其它输出<span class="built_in">..</span>.</span><br></pre></td></tr></table></figure><p>从而可以在Java程序中直接访问集群资源（ClusterIP和PodIP)。当ktctl退出时，会自动删除.jvmrc。 通过JVM Inject配合KT Connect开发者可以在IDEA按需决定是否直接访问Kubernetes集群。</p><h2><span id="参考资料">参考资料</span></h2><ul><li>KT Connect: <a href="https://alibaba.github.io/kt-connect/#/zh-cn/guide/how-to-use-in-idea" target="_blank" rel="noopener">https://alibaba.github.io/kt-connect</a></li><li>JVM Inject: <a href="https://plugins.jetbrains.com/plugin/13482-jvm-inject" target="_blank" rel="noopener">https://plugins.jetbrains.com/plugin/13482-jvm-inject</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本地将介绍如何在Idea中使用KT Connect: &lt;a href=&quot;https://github.com/alibaba/kt-connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/alibaba/kt-connect&lt;/a&gt; 项目实现本地与集群中服务的联调开发&lt;/p&gt;
    
    </summary>
    
    
      <category term="Kubernetes" scheme="http://yunlzheng.github.io/tags/Kubernetes/"/>
    
      <category term="IDEA" scheme="http://yunlzheng.github.io/tags/IDEA/"/>
    
      <category term="Windows" scheme="http://yunlzheng.github.io/tags/Windows/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes服务发现之ClusterIP</title>
    <link href="http://yunlzheng.github.io/2018/09/16/cluster-ip-in-kubernetes/"/>
    <id>http://yunlzheng.github.io/2018/09/16/cluster-ip-in-kubernetes/</id>
    <published>2018-09-16T07:38:16.000Z</published>
    <updated>2018-09-16T07:47:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>在<a href="http://ylzheng.com/2018/09/07/k8s-flannel-in-alicloud/" target="_blank" rel="noopener">《Flannel网络以及在阿里云下的实现解析》</a>中说过在Kubernetes中网络中，主要包含两种IP，分别是Pod IP和Cluster IP。 Pod IP是实际存在于网卡之上（如VETH的虚拟网卡），而Cluster IP则是一个虚拟的IP地址，该虚拟机IP由kube-proxy进行维护，kube-proxy目前提供了两种实现方式，包括默认的ip tables实现以及在K8S 1.8之后开始支持的ipvs实现。文章中以阿里云Kubernetes集群为例，从Pod IP的角度介绍了Pod和Pod之间是如何通讯的。这篇文章，笔者将解释基于ClusterIP的服务发现是个什么鬼。</p><a id="more"></a><h2><span id="基于clusterip的服务发现">基于ClusterIP的服务发现</span></h2><p>Kubernetes中服务发现主要通过每个主机上的kube-proxy组件实现，其作用是通过控制iptables将对Service ClusterIP的请求，转发到后端Endpoints中，剩下就交给容器网络。</p><p>以default命名空间下的nginx svc为例：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl <span class="builtin-name">get</span> svc --selector <span class="attribute">app</span>=nginx</span><br><span class="line">NAME     <span class="built_in"> TYPE </span>       CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE</span><br><span class="line">nginx     ClusterIP   172.19.0.166   &lt;none&gt;        80/TCP    1m</span><br></pre></td></tr></table></figure><p>查看Service详情:</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl describe svc nginx</span><br><span class="line"><span class="symbol">Name:</span>              nginx</span><br><span class="line"><span class="symbol">Namespace:</span>         default</span><br><span class="line"><span class="symbol">Labels:</span>            app=nginx</span><br><span class="line"><span class="symbol">Annotations:</span>       <span class="params">&lt;none&gt;</span></span><br><span class="line"><span class="symbol">Selector:</span>          app=nginx</span><br><span class="line"><span class="symbol">Type:</span>              ClusterIP</span><br><span class="line"><span class="symbol">IP:</span>                <span class="number">172.19</span><span class="number">.0</span><span class="number">.166</span></span><br><span class="line"><span class="symbol">Port:</span>              <span class="params">&lt;unset&gt;</span>  <span class="number">80</span>/TCP</span><br><span class="line"><span class="symbol">TargetPort:</span>        <span class="number">80</span>/TCP</span><br><span class="line"><span class="symbol">Endpoints:</span>         <span class="number">172.16</span><span class="number">.2</span><span class="number">.125</span>:<span class="number">80</span>,<span class="number">172.16</span><span class="number">.2</span><span class="number">.229</span>:<span class="number">80</span></span><br><span class="line">Session Affinity:  None</span><br><span class="line"><span class="symbol">Events:</span>            <span class="params">&lt;none&gt;</span></span><br></pre></td></tr></table></figure><p>上述信息中可以看出该svc的ClusterIP为172.19.0.166，后端代理了2个Pod实例:172.16.2.125:80,172.16.2.229:80</p><p>在任意Node节点中找到flannel实例，查看iptables信息：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ k -n kube-system exec -it kube-flannel-ds-hjlb4 -c kube-flannel -- iptables -S -t nat</span><br><span class="line"># 省略输出</span><br><span class="line">-A KUBE-SERVICES -d <span class="number">39.96</span><span class="number">.133</span><span class="number">.156</span>/<span class="number">32</span> -p tcp -m comment --comment <span class="string">"default/wrinkled-crocodile-selenium-hub:hub loadbalancer IP"</span> -m tcp --dport <span class="number">4444</span> -j KUBE-FW-SI6MMWWVN6LUBWIY</span><br><span class="line">-A KUBE-SERVICES ! -s <span class="number">172.16</span><span class="number">.0</span><span class="number">.0</span>/<span class="number">16</span> -d <span class="number">172.19</span><span class="number">.15</span><span class="number">.240</span>/<span class="number">32</span> -p tcp -m comment --comment <span class="string">"default/nginx: cluster IP"</span> -m tcp --dport <span class="number">80</span> -j KUBE-MARK-MASQ</span><br><span class="line">-A KUBE-SERVICES -d <span class="number">172.19</span><span class="number">.15</span><span class="number">.240</span>/<span class="number">32</span> -p tcp -m comment --comment <span class="string">"default/nginx: cluster IP"</span> -m tcp --dport <span class="number">80</span> -j KUBE-SVC<span class="number">-4</span>N57TFCL4MD7ZTDA</span><br><span class="line"># 省略出书</span><br></pre></td></tr></table></figure><p>根据路由转发规则，从Pod访问ClusterIP 172.19.0.166的80端口的请求，匹配到转发规则：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-A KUBE-SERVICES ! -s <span class="number">172.16</span><span class="number">.0</span><span class="number">.0</span>/<span class="number">16</span> -d <span class="number">172.19</span><span class="number">.0</span><span class="number">.166</span>/<span class="number">32</span> -p tcp -m comment --comment <span class="string">"default/nginx: cluster IP"</span> -m tcp --dport <span class="number">80</span> -j KUBE-MARK-MASQ</span><br><span class="line">-A KUBE-SERVICES -d <span class="number">172.19</span><span class="number">.0</span><span class="number">.166</span>/<span class="number">32</span> -p tcp -m comment --comment <span class="string">"default/nginx: cluster IP"</span> -m tcp --dport <span class="number">80</span> -j KUBE-SVC<span class="number">-4</span>N57TFCL4MD7ZTDA</span><br></pre></td></tr></table></figure><p>直接跳转到KUBE-SVC-4N57TFCL4MD7ZTDA:</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-A KUBE-SVC-<span class="number">4</span>N57TFCL4MD7ZTDA -m <span class="built_in">comment</span> --<span class="built_in">comment</span> <span class="string">"default/nginx:"</span> -m statistic --mode <span class="built_in">random</span> --probability <span class="number">0.50000000000</span> -j KUBE-SEP-ZWDBLNQ3XRBMUP33</span><br><span class="line">-A KUBE-SVC-<span class="number">4</span>N57TFCL4MD7ZTDA -m <span class="built_in">comment</span> --<span class="built_in">comment</span> <span class="string">"default/nginx:"</span> -j KUBE-SEP-H2XFNPZ6MLIHFOVM</span><br></pre></td></tr></table></figure><p>通过iptables的–probability的特性，使连接有50%的概率进入到KUBE-SEP-ZWDBLNQ3XRBMUP33，KUBE-SEP-H2XFNPZ6MLIHFOVM的作用是把请求转发到172.16.2.125:80：</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-A KUBE-SEP-ZWDBLNQ3XRBMUP33 -s <span class="number">172.16</span>.<span class="number">2.125</span>/<span class="number">32</span> -m <span class="built_in">comment</span> --<span class="built_in">comment</span> <span class="string">"default/nginx:"</span> -j KUBE-MARK-MASQ</span><br><span class="line">-A KUBE-SEP-ZWDBLNQ3XRBMUP33 -p tcp -m <span class="built_in">comment</span> --<span class="built_in">comment</span> <span class="string">"default/nginx:"</span> -m tcp -j DNAT --<span class="keyword">to</span>-destination <span class="number">172.16</span>.<span class="number">2.125</span>:<span class="number">80</span></span><br></pre></td></tr></table></figure><p>另外50%的请求，则可能进入到KUBE-SEP-QKRDMLY5MWSFYSJG，同理，该规则的作用是把请求转发到172.16.2.229:80:</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-A KUBE-SEP-H2XFNPZ6MLIHFOVM -s <span class="number">172.16</span>.<span class="number">2.229</span>/<span class="number">32</span> -m <span class="built_in">comment</span> --<span class="built_in">comment</span> <span class="string">"default/nginx:"</span> -j KUBE-MARK-MASQ</span><br><span class="line">-A KUBE-SEP-H2XFNPZ6MLIHFOVM -p tcp -m <span class="built_in">comment</span> --<span class="built_in">comment</span> <span class="string">"default/nginx:"</span> -m tcp -j DNAT --<span class="keyword">to</span>-destination <span class="number">172.16</span>.<span class="number">2.229</span>:<span class="number">80</span></span><br></pre></td></tr></table></figure><p>Kubernetes通过iptables规则，将对ClusterIP的访问，负载到后端的PodID，剩下的事情，就是在文章<a href="http://ylzheng.com/2018/09/07/k8s-flannel-in-alicloud/" target="_blank" rel="noopener">《Flannel网络以及在阿里云下的实现解析》</a>介绍的部分了</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在&lt;a href=&quot;http://ylzheng.com/2018/09/07/k8s-flannel-in-alicloud/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;《Flannel网络以及在阿里云下的实现解析》&lt;/a&gt;中说过在Kubernetes中网络中，主要包含两种IP，分别是Pod IP和Cluster IP。 Pod IP是实际存在于网卡之上（如VETH的虚拟网卡），而Cluster IP则是一个虚拟的IP地址，该虚拟机IP由kube-proxy进行维护，kube-proxy目前提供了两种实现方式，包括默认的ip tables实现以及在K8S 1.8之后开始支持的ipvs实现。文章中以阿里云Kubernetes集群为例，从Pod IP的角度介绍了Pod和Pod之间是如何通讯的。这篇文章，笔者将解释基于ClusterIP的服务发现是个什么鬼。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Kubernetes" scheme="http://yunlzheng.github.io/tags/Kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>使用Helm优化Kubernetes下的研发体验：实现持续交付流水线</title>
    <link href="http://yunlzheng.github.io/2018/09/15/use-helm-improve-k8s-dev-experience2/"/>
    <id>http://yunlzheng.github.io/2018/09/15/use-helm-improve-k8s-dev-experience2/</id>
    <published>2018-09-15T13:33:19.000Z</published>
    <updated>2018-12-30T09:01:37.903Z</updated>
    
    <content type="html"><![CDATA[<p>接着上一篇《使用Helm优化Kubernetes下的研发体验：基础设施即代码》中笔者介绍了如何在项目中使用Helm，在项目源码中，我们通过Dockerfile定义了项目是如何构建的，使用Helm定义了项目是如何部署的。 团队中的任何人员(角色）在获取源码的同时就已经具备了一键构建，一键部署的能力。</p><a id="more"></a><h2><span id="整体目标">整体目标</span></h2><p>在这一篇中，我们将使用Jenkins在此基础上构建一条完整的持续交付流水线，并且让团队不同成员能够基于该流水线展开基本的协作。</p><ul><li>开发： 持续提交代码并能够通过持续集成(CI)过程快速获取反馈，在通过CI验证后，能够自动化部署到开发环境，以便后续的进一步功能测试（手动/自动自动化测试）等；</li><li>测试： 在需要对项目功能进行验证时，可以一键部署测试环境，并且在此环境基础上可以完成功能验收(手动)，以及全量的自动化验收测试等；</li><li>运维：一键部署生产环境，同时发布创建版本，以便在发布异常时能够快速回归</li></ul><blockquote><p>资料来源： <a href="https://dzone.com/articles/easily-automate-your-cicd-pipeline-with-jenkins-he" target="_blank" rel="noopener">https://dzone.com/articles/easily-automate-your-cicd-pipeline-with-jenkins-he</a></p></blockquote><p>示例项目的代码可以从<a href="https://github.com/yunlzheng/project-samples" target="_blank" rel="noopener">Github</a>下载，示例项目为<code>containerization-spring-with-helm</code>。接下来，我们将分阶段介绍如何通过<a href="https://github.com/yunlzheng/project-samples/blob/master/Jenkinsfile" target="_blank" rel="noopener">Jenkinsfile</a>定义整个过程。</p><p><img src="/images/ci-cd-jenkins-helm-k8s.png" alt="在端到端中使用Helm"></p><h2><span id="项目构建阶段">项目构建阶段</span></h2><p>当前阶段Jenkinsfile定义如下：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">stage</span><span class="params">(<span class="string">'Build And Test'</span>)</span></span> &#123;</span><br><span class="line">    steps &#123;</span><br><span class="line"></span><br><span class="line">        dir(<span class="string">'containerization-spring-with-helm'</span>) &#123;</span><br><span class="line">            sh <span class="string">'docker build -t yunlzheng/spring-sample:$GIT_COMMIT .'</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>Build And Test</code>阶段，我们直接通过源码中的Dockerfile定义了整个持续集成阶段的任务，通过docker的<code>Multi-Stage Builds</code>特性，持续集成的所有任务全部通过Dockerfile进行定义，这样无论是在本地还是持续集成服务器中，我们都可以非常方便的进行运行CI任务。</p><p><img src="/images/build-and-test.png" alt="Build And Test"></p><h2><span id="发布镜像和helm阶段">发布镜像和Helm阶段</span></h2><p>当前阶段Jenkinsfile定义如下：</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">stage(<span class="string">'Publish Docker And Helm'</span>) &#123;</span><br><span class="line">  steps &#123;</span><br><span class="line"></span><br><span class="line">    withDockerRegistry([credentialsId: <span class="string">'dockerhub'</span>, ur<span class="variable">l:</span> <span class="string">''</span>]) &#123;</span><br><span class="line">      <span class="keyword">sh</span> <span class="string">'docker push yunlzheng/spring-sample:$GIT_COMMIT'</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    script &#123;</span><br><span class="line">      def filename = <span class="string">'containerization-spring-with-helm/chart/values.yaml'</span></span><br><span class="line">      def data = readYaml <span class="keyword">file</span>: filename</span><br><span class="line">      data.image.<span class="keyword">tag</span> = env.GIT_COMMIT</span><br><span class="line">      <span class="keyword">sh</span> <span class="string">"rm $filename"</span></span><br><span class="line">      writeYaml <span class="keyword">file</span>: filename, dat<span class="variable">a:</span> data</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    script &#123;</span><br><span class="line">      def filename = <span class="string">'containerization-spring-with-helm/chart/Chart.yaml'</span></span><br><span class="line">      def data = readYaml <span class="keyword">file</span>: filename</span><br><span class="line">      data.<span class="keyword">version</span> = env.GIT_COMMIT</span><br><span class="line">      <span class="keyword">sh</span> <span class="string">"rm $filename"</span></span><br><span class="line">      writeYaml <span class="keyword">file</span>: filename, dat<span class="variable">a:</span> data</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    dir(<span class="string">'containerization-spring-with-helm'</span>) &#123;</span><br><span class="line">      <span class="keyword">sh</span> <span class="string">'helm push chart https://repomanage.rdc.aliyun.com/helm_repositories/26125-play-helm --username=$HELM_USERNAME --password=$HELM_PASSWORD  --version=$GIT_COMMIT'</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3><span id="push镜像">Push镜像</span></h3><p>通过<code>withDockerRegistry</code>的上下文中，Jenkins会确保docker client首先通过<code>credentials dockerhub</code>中定义的用户名和密码完成登录后，在运行<code>docker push</code>任务。并且我们确保使用当前代码版本的COMMIT_ID作为镜像的Tag，从而将Docker镜像版本与源码版本进行一一对应；</p><h3><span id="重写chart镜像版本">重写Chart镜像版本</span></h3><p>通过<code>readYaml</code>读取chart的values.yaml内容到变量data后，通过<code>writeYaml</code>重写values.yaml中的镜像tag版本与当前构建镜像版本一致；</p><h3><span id="重写chart版本">重写Chart版本</span></h3><p>与镜像一样，我们希望Chart的版本与源码版本能够一一对应；</p><h3><span id="上传chart">上传Chart</span></h3><p>这里我们直接使用<a href="https://rdc.aliyun.com/" target="_blank" rel="noopener">阿里云效</a>提供的Helm仓库服务，<a href="https://repomanage.rdc.aliyun.com/my/repo" target="_blank" rel="noopener">点击开通私有仓库服务</a>。通过Helm Push插件发布Chart到Helm仓库。</p><p>其中环境变量<code>$HELM_USERNAME</code>和<code>$HELM_PASSWORD</code>是通过jenkins的Credentials加载到环境变量中：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">environment</span> &#123;</span><br><span class="line">      <span class="attribute">HELM_USERNAME</span> = credentials(<span class="string">'HELM_USERNAME'</span>)</span><br><span class="line">      HELM_PASSWORD = credentials(<span class="string">'HELM_PASSWORD'</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="/images/publish-docker-and-helm.png" alt="Publish Docker And Helm"></p><h2><span id="部署到开发测试环境阶段">部署到开发/测试环境阶段</span></h2><p>当前阶段Jenkinsfile定义如下：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">stage</span><span class="params">(<span class="string">'Deploy To Dev'</span>)</span></span> &#123;</span><br><span class="line">  steps &#123;</span><br><span class="line">    dir(<span class="string">'containerization-spring-with-helm'</span>) &#123;</span><br><span class="line">      dir(<span class="string">'chart'</span>) &#123;</span><br><span class="line">        sh <span class="string">'helm upgrade spring-app-dev --install --namespace=dev --set ingress.host=dev.spring-example.local .'</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">stage</span><span class="params">(<span class="string">'Deploy To Stageing'</span>)</span></span> &#123;</span><br><span class="line">  steps &#123;</span><br><span class="line">    <span class="selector-tag">input</span> <span class="string">'Do you approve staging?'</span></span><br><span class="line">    dir(<span class="string">'containerization-spring-with-helm'</span>) &#123;</span><br><span class="line">      dir(<span class="string">'chart'</span>) &#123;</span><br><span class="line">        sh <span class="string">'helm upgrade spring-app-staging --install --namespace=staging --set ingress.host=staging.spring-example.local .'</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在Jenkinsfile中我们分别定义了两个阶段<code>Deploy To Dev</code>和<code>Deploy To Stageing</code>。我们通过Kubernetes的命名空间划分单独的开发环境和测试环境。并且通过覆盖ingress.host确保能够通过ingress域名<code>dev.spring-example.local</code>和<code>staging.spring-example.local</code>访问到不同环境。 对于Staging环境而言，通过<code>input</code>确保该流程一定是通过人工确认的。</p><p>通过<code>helm upgrade</code>命令可以确保在特定命名空间下部署或者升级已有的Chart:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm<span class="built_in"> upgrade </span>spring-app-staging --install <span class="attribute">--namespace</span>=staging --<span class="builtin-name">set</span> ingress.<span class="attribute">host</span>=staging.spring-example.local .</span><br></pre></td></tr></table></figure><p><img src="/images/deploy-to-dev.png" alt="Deploy To Dev"></p><p><img src="/images/deploy-to-staging.png" alt="Deploy To Stageing"></p><h2><span id="部署到生产环境阶段">部署到生产环境阶段</span></h2><p>当前阶段Jenkinsfile定义如下：</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">stage(<span class="string">'Deploy To Production'</span>) &#123;</span><br><span class="line">  steps &#123;</span><br><span class="line">    <span class="built_in">input</span> <span class="string">'Do you approve production?'</span></span><br><span class="line"></span><br><span class="line">    script &#123;                </span><br><span class="line">        env.RELEASE = <span class="built_in">input</span> message: <span class="string">'Please input the release version'</span>,</span><br><span class="line">        ok: <span class="string">'Deploy'</span>,</span><br><span class="line">        parameter<span class="variable">s:</span> [</span><br><span class="line">          [$clas<span class="variable">s:</span> <span class="string">'TextParameterDefinition'</span>, defaultValue: <span class="string">'0.0.1'</span>, description: <span class="string">'Cureent release version'</span>, name: <span class="string">'release'</span>]</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">echo</span> <span class="string">'Deploy and release: $RELEASE'</span></span><br><span class="line"></span><br><span class="line">    script &#123;</span><br><span class="line">      def filename = <span class="string">'containerization-spring-with-helm/chart/Chart.yaml'</span></span><br><span class="line">      def data = readYaml <span class="keyword">file</span>: filename</span><br><span class="line">      data.<span class="keyword">version</span> = env.RELEASE</span><br><span class="line">      <span class="keyword">sh</span> <span class="string">"rm $filename"</span></span><br><span class="line">      writeYaml <span class="keyword">file</span>: filename, dat<span class="variable">a:</span> data</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    dir(<span class="string">'containerization-spring-with-helm'</span>) &#123;</span><br><span class="line">      dir(<span class="string">'chart'</span>) &#123;</span><br><span class="line">        <span class="keyword">sh</span> <span class="string">'helm lint'</span></span><br><span class="line">        <span class="keyword">sh</span> <span class="string">'helm upgrade spring-app-prod --install --namespace=production --set ingress.host=production.spring-example.local .'</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">sh</span> <span class="string">'helm push chart https://repomanage.rdc.aliyun.com/helm_repositories/26125-play-helm --username=$HELM_USERNAME --password=$HELM_PASSWORD  --version=$RELEASE'</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在最后一个<code>Deploy To Production</code>阶段中，与Dev和Stageing的部署不同在于当人工确认部署测试环境之后，我们需要用户手动输入当前发布的版本，以确保对当前发布的Chart版本能完成一个基线的定义：</p><p><img src="/images/release-version.png" alt="Release Version"></p><p>这里，我们需要确保当前定义的版本是符合Sem规范的，因此这里使用了<code>helm lint</code>对Chart定义进行校验。</p><h2><span id="小结">小结</span></h2><p>通过代码提交版本(COMMIT_ID)关联了源码版本，镜像版本以及Chart版本。同时对于正式发布的软件版本而言，单独定义了正式发布的版本号。对于实践持续交付的研发团队而言，我们可以通过上述一条流水线基本实现软件交付的整个生命周期。而对于传统交付模式的团队，则可以通过将上述过程分拆到多条流水线（开发流水线，测试流水线，发布流水线）来适应自己的发布模式。</p><p>回到我们的总体目标而言，通过基础设施及代码的方式，我们定义了一个相对完备且自描述的应用。通过流水线即代码的方式，定义了应用的端到端交付过程。通过Docker定义项目的构建过程，通过Helm实现Kubernetes下应用的发布管理，通过Jenkinsfile定义了软件的整个交付过程，并且不同职能的团队成员，可以方便的在此基础上实现协作。最后借用《持续交付》的话“提前并频繁地做让你感到痛苦的事!“ ，希望大家都能够Happy Coding。</p><h2><span id="参考资料">参考资料</span></h2><ul><li><a href="https://www.aliyun.com/product/yunxiao?spm=5176.224200.developerService.27.1b776ed6tdwLm5" target="_blank" rel="noopener">云效一站式企业协同研发云</a></li><li><a href="https://repomanage.rdc.aliyun.com/my/repo" target="_blank" rel="noopener">云效私有仓库服务</a></li><li><a href="https://dzone.com/articles/easily-automate-your-cicd-pipeline-with-jenkins-he" target="_blank" rel="noopener">Easily Automate Your CI/CD Pipeline With Jenkins, Helm, and Kubernetes</a></li><li><a href="https://jenkins.io/" target="_blank" rel="noopener">Jenkins</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;接着上一篇《使用Helm优化Kubernetes下的研发体验：基础设施即代码》中笔者介绍了如何在项目中使用Helm，在项目源码中，我们通过Dockerfile定义了项目是如何构建的，使用Helm定义了项目是如何部署的。 团队中的任何人员(角色）在获取源码的同时就已经具备了一键构建，一键部署的能力。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Kubernetes" scheme="http://yunlzheng.github.io/tags/Kubernetes/"/>
    
      <category term="Helm" scheme="http://yunlzheng.github.io/tags/Helm/"/>
    
  </entry>
  
  <entry>
    <title>使用Helm优化Kubernetes下的研发体验：基础设施即代码</title>
    <link href="http://yunlzheng.github.io/2018/09/13/use-helm-improve-k8s-dev-experience/"/>
    <id>http://yunlzheng.github.io/2018/09/13/use-helm-improve-k8s-dev-experience/</id>
    <published>2018-09-13T04:00:00.000Z</published>
    <updated>2018-09-15T15:03:38.000Z</updated>
    
    <content type="html"><![CDATA[<p>容器即进程，Kubernetes则解决了如何部署和运行应用的问题。对于任何一个部署在Kubernetes得应用而言，通常都可以由几个固定的部分组成：Ingress,Service,Deployment等。直接使用Kubernetes原生的YAML定义服务，虽然能一定程度上简化应用的部署，但是对于大部分研发人员来说编写和使用YAML依然是一件相对痛苦的事情。HELM应允而生，Helm作为Kubernetes下的包管理工具，对原生服务定义过程进行了增强，通过模板化，参数化的形式大大简化用户部署Kubernetes应用的复杂度。</p><p>在本文中笔者，将以一个Spring Boot程序为例，介绍如何在软件研发端到端过程中是使用Helm。本文中所使用的示例代码可以通过<a href="https://github.com/yunlzheng/project-samples/tree/master/containerization-spring-with-helm" target="_blank" rel="noopener">Github</a>下载。</p><a id="more"></a><h2><span id="创建应用程序">创建应用程序</span></h2><p>项目采用Maven作为项目的编译和构建工具，项目目录结构如下：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">├── <span class="selector-tag">README</span><span class="selector-class">.md</span></span><br><span class="line">├── <span class="selector-tag">chart</span></span><br><span class="line">│   ├── <span class="selector-tag">Chart</span><span class="selector-class">.yaml</span> # <span class="selector-tag">Chart</span>基本信息</span><br><span class="line">│   ├── <span class="selector-tag">charts</span> # 依赖</span><br><span class="line">│   ├── <span class="selector-tag">templates</span> # <span class="selector-tag">Kubernetes</span>模板</span><br><span class="line">│   │   ├── <span class="selector-tag">NOTES</span><span class="selector-class">.txt</span></span><br><span class="line">│   │   ├── _<span class="selector-tag">helpers</span><span class="selector-class">.tpl</span></span><br><span class="line">│   │   ├── <span class="selector-tag">deployment</span><span class="selector-class">.yaml</span></span><br><span class="line">│   │   ├── <span class="selector-tag">ingress</span><span class="selector-class">.yaml</span></span><br><span class="line">│   │   └── <span class="selector-tag">service</span><span class="selector-class">.yaml</span></span><br><span class="line">│   └── <span class="selector-tag">values</span><span class="selector-class">.yaml</span> # 变量</span><br><span class="line">├── <span class="selector-tag">Dockerfile</span> # <span class="selector-tag">Dockerfile</span>定义</span><br><span class="line">├── <span class="selector-tag">entrypoint</span><span class="selector-class">.sh</span> # 容器的<span class="selector-tag">entrypoint</span><span class="selector-class">.sh</span>文件</span><br><span class="line">├── <span class="selector-tag">mvnw</span></span><br><span class="line">├── <span class="selector-tag">mvnw</span><span class="selector-class">.cmd</span></span><br><span class="line">├── <span class="selector-tag">pom</span><span class="selector-class">.xml</span> </span><br><span class="line">├── <span class="selector-tag">src</span> # 应用源码</span><br><span class="line">│   └── <span class="selector-tag">main</span></span><br><span class="line">│       └── <span class="selector-tag">java</span></span><br><span class="line">│           └── <span class="selector-tag">hello</span></span><br><span class="line">│               ├── <span class="selector-tag">Application</span><span class="selector-class">.java</span></span><br><span class="line">│               └── <span class="selector-tag">HelloController</span><span class="selector-class">.java</span></span><br></pre></td></tr></table></figure><p>该项目SCM中通过基础实施即代码的方式，我们定义了应用的3大要素：应用源码，应用是如何构建的（Dockerfile）以及应用是如何部署的(Chart)。</p><h2><span id="构建容器镜像">构建容器镜像</span></h2><p>容器相关内容</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">├── Dockerfile <span class="comment"># Dockerfile定义</span></span><br><span class="line">├── <span class="keyword">entrypoint</span>.<span class="bash">sh <span class="comment"># 容器的entrypoint.sh文件</span></span></span><br></pre></td></tr></table></figure><p>为了简化容器镜像构建过程，在Dockerfile中我们采用了Multi-Stage Builds的方式构建镜像，Dockerfile的具体内容如下：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Build</span></span><br><span class="line"><span class="keyword">FROM</span> maven:<span class="number">3.5</span>.<span class="number">0</span>-jdk-<span class="number">8</span>-alpine AS builder</span><br><span class="line"></span><br><span class="line"><span class="keyword">ADD</span><span class="bash"> ./pom.xml pom.xml</span></span><br><span class="line"><span class="bash">ADD ./src src/</span></span><br><span class="line"><span class="bash">RUN mvn clean package</span></span><br><span class="line"><span class="bash"></span></span><br><span class="line"><span class="bash"></span></span><br><span class="line"><span class="bash"><span class="comment"># Package</span></span></span><br><span class="line"><span class="bash">FROM java:8</span></span><br><span class="line"><span class="bash"></span></span><br><span class="line"><span class="bash">COPY --from=builder target/gs-spring-boot-0.1.0.jar gs-spring-boot.jar</span></span><br><span class="line"><span class="bash">RUN bash -c <span class="string">'touch /gs-spring-boot.jar'</span></span></span><br><span class="line"><span class="bash"></span></span><br><span class="line"><span class="bash">ADD entrypoint.sh entrypoint.sh</span></span><br><span class="line"><span class="bash">RUN chmod +x entrypoint.sh</span></span><br><span class="line"><span class="bash">ENTRYPOINT [<span class="string">"./entrypoint.sh"</span>]</span></span><br></pre></td></tr></table></figure><p>在第一个阶段中，我们将pom.xml以及源码加载到一个maven基础镜像中，并命名为builder，通过<code>mvn clean package</code>命令实现Java源码的编译打包，产生的jar包会保存到容器的targets目录下。</p><p>在第二个阶段中，我们在java:8基础镜像的基础上直接从builder容器中拷贝jar文件，到当前容器中。为了能够在容器中运行该jar文件，这里我们定义了一个entrypoint.sh作为容器的启动命令，其内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line">ACTIVE_PROFILE=<span class="variable">$&#123;PROFILE:=default&#125;</span></span><br><span class="line">java -Xmx1024m -Djava.security.egd=file:/dev/./urandom -jar gs-spring-boot.jar --spring.profiles.active=<span class="variable">$&#123;ACTIVE_PROFILE&#125;</span> <span class="variable">$@</span></span><br></pre></td></tr></table></figure><p>这里需要注意的是在命令的最后我们添加了一个$@，该语法可以获取命令命令行中的所有参数，这样在后期运行容器时，可以在命令行中使用参数，覆盖应用的默认配置，例如<code>--spring.profiles.active=prod</code></p><p>运行以下命令，编译并打包应用:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ docker build -t yunlzheng/spring-app . # 修改为自己的镜像仓库</span><br><span class="line">Sending build context <span class="keyword">to</span> Docker daemon  16.38MB</span><br><span class="line"><span class="keyword">Step</span> 1/10 : <span class="keyword">FROM</span> maven:3.5.0-jdk-8-alpine AS builder</span><br><span class="line"> ---&gt; 67d11473f554</span><br><span class="line"><span class="built_in">..</span><span class="built_in">..</span><span class="built_in">..</span></span><br><span class="line">Successfully built e332622092ce</span><br><span class="line">Successfully tagged yunlzheng/spring-app:latest</span><br></pre></td></tr></table></figure><p>上传镜像到镜像仓库中(需要实现注册容器镜像服务)</p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="keyword">push</span> yunlzheng/spring-app <span class="meta"># 修改为自己的镜像仓库</span></span><br></pre></td></tr></table></figure><h2><span id="构建chart">构建Chart</span></h2><p>通过容器镜像我们为服务定义了一个隔离的运行时环境，而为了能够让我们的应用程序能够运行到Kubernetes集群当中，我们还需要定义Helm相关的内容，来标准化容器的编排和部署信息：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">├── <span class="selector-tag">chart</span></span><br><span class="line">│   ├── <span class="selector-tag">Chart</span><span class="selector-class">.yaml</span> # <span class="selector-tag">Chart</span>基本信息</span><br><span class="line">│   ├── <span class="selector-tag">charts</span> # 依赖</span><br><span class="line">│   ├── <span class="selector-tag">templates</span> # <span class="selector-tag">Kubernetes</span>模板</span><br><span class="line">│   │   ├── <span class="selector-tag">NOTES</span><span class="selector-class">.txt</span></span><br><span class="line">│   │   ├── _<span class="selector-tag">helpers</span><span class="selector-class">.tpl</span></span><br><span class="line">│   │   ├── <span class="selector-tag">deployment</span><span class="selector-class">.yaml</span></span><br><span class="line">│   │   ├── <span class="selector-tag">ingress</span><span class="selector-class">.yaml</span></span><br><span class="line">│   │   └── <span class="selector-tag">service</span><span class="selector-class">.yaml</span></span><br><span class="line">│   └── <span class="selector-tag">values</span><span class="selector-class">.yaml</span> # 变量</span><br></pre></td></tr></table></figure><p>在以上结构中我们定义了该应用是如何在Kubernetes集群中运行的。在初始化应用时，用户可以通过使用helm命令生成以上内容：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> helm create chart</span></span><br><span class="line">Creating chart</span><br></pre></td></tr></table></figure><p>Chart我们可以理解为一组K8S manifest文件的模板，Chart.yaml中包含了该chart的基本信息,如名称，版本等:</p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">apiVersion:</span> v1</span><br><span class="line"><span class="symbol">appVersion:</span> <span class="string">"1.0"</span></span><br><span class="line"><span class="symbol">description:</span> A Spring Boot Application</span><br><span class="line"><span class="symbol">name:</span> chart</span><br><span class="line"><span class="symbol">version:</span> <span class="number">0.1</span><span class="number">.0</span></span><br></pre></td></tr></table></figure><p>在values.yaml中，我们定义了当前模板中所有的变量，如下所示：</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">replicaCount</span>: 1</span><br><span class="line"></span><br><span class="line"><span class="attribute">image:</span></span><br><span class="line">  repository: yunlzheng/spring-app #修改为自己的镜像</span><br><span class="line">  <span class="attribute">tag</span>: latest</span><br><span class="line">  <span class="attribute">pullPolicy</span>: IfNotPresent</span><br><span class="line"></span><br><span class="line"><span class="attribute">service:</span></span><br><span class="line">  type: ClusterIP</span><br><span class="line">  <span class="attribute">port</span>: 8080 #容器映射的端口</span><br><span class="line">  </span><br><span class="line"><span class="attribute">ingress:</span></span><br><span class="line">  enabled: true # 打开集群ingress</span><br><span class="line">  <span class="attribute">annotations</span>: &#123;&#125;</span><br><span class="line">    # kubernetes.io/ingress.class: nginx</span><br><span class="line">    # kubernetes.io/tls-acme: "true"</span><br><span class="line">  <span class="attribute">path</span>: /</span><br><span class="line">  <span class="attribute">hosts:</span></span><br><span class="line">    - spring-example.local</span><br><span class="line">  <span class="attribute">tls</span>: []</span><br></pre></td></tr></table></figure><p>templates目录下，则是K8S用户熟悉的如deployment.yaml, service.yaml。当然你也可以根据自己的需求添加更多的模板文件。</p><p>以deployment.yaml为例，文件内容如下所示：</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"># deployment.yaml</span></span><br><span class="line"><span class="symbol">apiVersion:</span> apps/v1beta2</span><br><span class="line"><span class="symbol">kind:</span> Deployment</span><br><span class="line"><span class="symbol">metadata:</span></span><br><span class="line"><span class="symbol">  name:</span> &#123;&#123; template <span class="string">"chart.fullname"</span> . &#125;&#125;</span><br><span class="line"><span class="symbol">  labels:</span></span><br><span class="line"><span class="symbol">    app:</span> &#123;&#123; template <span class="string">"chart.name"</span> . &#125;&#125;</span><br><span class="line"><span class="symbol">    chart:</span> &#123;&#123; template <span class="string">"chart.chart"</span> . &#125;&#125;</span><br><span class="line"><span class="symbol">    release:</span> &#123;&#123; .Release.Name &#125;&#125;</span><br><span class="line"><span class="symbol">    heritage:</span> &#123;&#123; .Release.Service &#125;&#125;</span><br><span class="line"><span class="symbol">spec:</span></span><br><span class="line"><span class="symbol">  replicas:</span> &#123;&#123; .Values.replicaCount &#125;&#125;</span><br><span class="line"><span class="symbol">  selector:</span></span><br><span class="line"><span class="symbol">    matchLabels:</span></span><br><span class="line"><span class="symbol">      app:</span> &#123;&#123; template <span class="string">"chart.name"</span> . &#125;&#125;</span><br><span class="line"><span class="symbol">      release:</span> &#123;&#123; .Release.Name &#125;&#125;</span><br><span class="line"><span class="symbol">  template:</span></span><br><span class="line"><span class="symbol">    metadata:</span></span><br><span class="line"><span class="symbol">      labels:</span></span><br><span class="line"><span class="symbol">        app:</span> &#123;&#123; template <span class="string">"chart.name"</span> . &#125;&#125;</span><br><span class="line"><span class="symbol">        release:</span> &#123;&#123; .Release.Name &#125;&#125;</span><br><span class="line"><span class="symbol">    spec:</span></span><br><span class="line"><span class="symbol">      containers:</span></span><br><span class="line">        - name: &#123;&#123; .Chart.Name &#125;&#125;</span><br><span class="line"><span class="symbol">          image:</span> <span class="string">"&#123;&#123; .Values.image.repository &#125;&#125;:&#123;&#123; .Values.image.tag &#125;&#125;"</span></span><br><span class="line"><span class="symbol">          imagePullPolicy:</span> &#123;&#123; .Values.image.pullPolicy &#125;&#125;</span><br><span class="line"><span class="symbol">          ports:</span></span><br><span class="line">            - name: http</span><br><span class="line"><span class="symbol">              containerPort:</span> <span class="number">8080</span></span><br><span class="line"><span class="symbol">              protocol:</span> TCP</span><br></pre></td></tr></table></figure><p>在文件中使用了了values.yaml中定义的相关变量，如Values.replicaCount，Values.image.repository，Values.image.tag等，使用这些变量的好处是，在部署Chart的时候，我们可以在命令行中动态修改这些变量的值，例如，修改镜像部署的版本等， service.yaml中的内容也是类似的，这里就不做描述。</p><p>完成以上内容后，我们就可以将当前应用打包成一个chart文件，首先我们需要验证一下chart文件的内容：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> chart</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> helm lint</span></span><br><span class="line">==&gt; Linting .</span><br><span class="line">[INFO] Chart.yaml: icon is recommended</span><br><span class="line"></span><br><span class="line">1 chart(s) linted, no failures</span><br></pre></td></tr></table></figure><p>在确认chart格式没有问题之后，开发人员就可以直接通过helm部署实例到Kubernetes集群：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ cd chart</span><br><span class="line">$ helm <span class="keyword">install</span> .</span><br><span class="line"># 省略其它输出</span><br><span class="line">==&gt; v1/Service</span><br><span class="line"><span class="keyword">NAME</span>                        <span class="keyword">TYPE</span>       CLUSTER-IP    <span class="keyword">EXTERNAL</span>-IP  PORT(S)   AGE</span><br><span class="line">womping-sparrow-spring-app  ClusterIP  <span class="number">172.19</span><span class="number">.11</span><span class="number">.41</span>  &lt;<span class="keyword">none</span>&gt;       <span class="number">8080</span>/TCP  <span class="number">1</span>s</span><br><span class="line"></span><br><span class="line">NOTES:</span><br><span class="line"><span class="number">1.</span> <span class="keyword">Get</span> the application <span class="keyword">URL</span> <span class="keyword">by</span> running these commands:</span><br><span class="line">  <span class="keyword">http</span>://spring-example.local/</span><br></pre></td></tr></table></figure><p>开发人员访问，并验证应用是否按照预期运行：</p><p>[attach]15417[/attach]</p><h2><span id="发布chart">发布Chart</span></h2><p>在确认应用能够正常运行之后，我们就可以对Chart进行打包和发布了。对于运维和测试人员，而言，他们只需要直接使用特定版本的应用chart，并对其进行测试或者是部署：</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ cd ..</span><br><span class="line">$ helm <span class="keyword">package</span> chart</span><br><span class="line">Successfully packaged chart and saved it to: <span class="regexp">/Users/yu</span>nlong<span class="regexp">/workspace/</span><span class="keyword">project</span>-samples<span class="regexp">/containerization-spring-with-helm/</span>chart-<span class="number">0.1</span>.<span class="number">0</span>.tgz</span><br></pre></td></tr></table></figure><p>在默认情况下，<code>helm package</code>命令会使用Charts.yaml中文件定义的版本。 而如果在持续集成工具中，如果我们希望每次都能动态生成一个新版本的Chart，那在打包时，可以通过–version，动态修改，从而确保每次持续集成过程都能产生一个新的版本，并且能够对该版本进行独立验证。</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ helm package chart <span class="comment">--version 0.0.2</span></span><br><span class="line">Successfully packaged chart <span class="keyword">and</span> saved <span class="keyword">it</span> <span class="keyword">to</span>: /workspace/tmp/spring-sample/chart<span class="number">-0.0</span><span class="number">.2</span>.tgz</span><br></pre></td></tr></table></figure><p>万事具备，当然现在还没有任何人能够使用你构建的chart，为了能够让其他人（测试，运维，or anyone）能够使用Chart我们需要将Chart发布到一个公共的仓库(Repository)当中。</p><p>Helm官方提供了一个名叫<a href="https://github.com/helm/chartmuseum" target="_blank" rel="noopener">Chartmusem</a>的开源项目，支持对接AWS S3，Google Storage，Alibaba OSS等存储服务，用户可通过其API上传Chart,并且自动生成仓库索引文件，有精力的同学可以自行研究。</p><p>这里我们直接使用<a href="https://rdc.aliyun.com/" target="_blank" rel="noopener">阿里云效</a>提供的Helm仓库服务，用户只需要注册账号，并<a href="https://repomanage.rdc.aliyun.com/my/repo" target="_blank" rel="noopener">开通私有仓库服务</a>，即可免费创建自己私有的，无容量限制的Helm仓库。</p><p>由于通过<a href="https://rdc.aliyun.com/" target="_blank" rel="noopener">阿里云效</a>创建的Helm仓库是私有的，因此在添加仓库时需要通过参数<code>--username=kHKvnX</code>和<code>--password=WsCH7zuHH2</code>指定用户名和密码：</p><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm repo add play-helm https:<span class="string">//repomanage.rdc.aliyun.com/helm_repositories/26125-play-helm</span> <span class="params">--username=kHKvnX</span> <span class="params">--password=WsCH7zuHH2</span></span><br></pre></td></tr></table></figure><p>为了更好的Chart发布体验，Helm官方为Chartmusem提供了一个<a href="https://github.com/chartmuseum/helm-push" target="_blank" rel="noopener">Helm Push</a>的插件，<a href="https://rdc.aliyun.com/" target="_blank" rel="noopener">云效</a>Helm仓库服务对该插件进行了完整兼容，因此用户可以直接使用该插件完成chart的发布：</p><p>安装Helm Push插件：</p><figure class="highlight x86asm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ helm plugin install https://github.com/chartmuseum/helm-<span class="keyword">push</span></span><br><span class="line">Downloading <span class="keyword">and</span> installing helm-<span class="keyword">push</span> v0<span class="meta">.7</span><span class="meta">.1</span> ...</span><br><span class="line"><span class="symbol">https:</span>//github.com/chartmuseum/helm-<span class="keyword">push</span>/releases/download/v0<span class="meta">.7</span><span class="meta">.1</span>/helm-push_0<span class="meta">.7</span>.1_darwin_amd64.tar.gz</span><br><span class="line">Installed plugin: <span class="keyword">push</span></span><br></pre></td></tr></table></figure><p>由于已经将Helm仓库添加到了本地，我们可以直接使用以下命令将chart发布到仓库中：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ helm push chart<span class="number">-0.1</span><span class="number">.0</span>.tgz play-helm </span><br><span class="line">Pushing chart<span class="number">-0.1</span><span class="number">.0</span>.tgz to play-helm...</span><br><span class="line">Done.</span><br></pre></td></tr></table></figure><p>发布完成后重新更新本地仓库索引：</p><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ helm update</span><br><span class="line">...Successfully got <span class="keyword">an</span> update <span class="built_in">from</span> <span class="keyword">the</span> <span class="string">"play-helm"</span> chart repository</span><br><span class="line">...Successfully got <span class="keyword">an</span> update <span class="built_in">from</span> <span class="keyword">the</span> <span class="string">"stable"</span> chart repository</span><br><span class="line">Update Complete. ⎈ Happy Helming!⎈</span><br></pre></td></tr></table></figure><p>搜索play-helm仓库并部署的chart：</p><figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ helm <span class="keyword">search</span> play-helm</span><br><span class="line">NAME            CHART <span class="keyword">VERSION</span>   <span class="keyword">APP</span> <span class="keyword">VERSION</span>     DESCRIPTION              </span><br><span class="line">play-helm/chart 0.1.0           1.0             A Spring <span class="keyword">Boot</span> Application</span><br><span class="line"></span><br><span class="line">$ helm install play-helm/chart</span><br></pre></td></tr></table></figure><h2><span id="其它的小技巧">其它的小技巧</span></h2><p>在发布镜像的时候指定版本：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ helm push chart<span class="number">-0.1</span><span class="number">.0</span>.tgz play-helm --version=<span class="number">0.2</span><span class="number">.0</span></span><br><span class="line">Pushing chart<span class="number">-0.2</span><span class="number">.0</span>.tgz to play-helm...</span><br><span class="line">Done.</span><br></pre></td></tr></table></figure><p>直接发布chart目录:</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ helm push chart play-helm --version=<span class="number">0.3</span><span class="number">.0</span></span><br><span class="line">Pushing chart<span class="number">-0.1</span><span class="number">.0</span>.tgz to play-helm...</span><br><span class="line">Done.</span><br></pre></td></tr></table></figure><p>在不添加helm仓库的情况下直接发布chart:</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ helm push chart http<span class="variable">s:</span>//repomanage.rdc.aliyun.<span class="keyword">com</span>/helm_repositories/<span class="number">26125</span>-play-helm --username=kHKvnX --password=WsCH7zuHH2</span><br><span class="line">Pushing chart-<span class="number">0.1</span>.<span class="number">0</span>.tgz <span class="keyword">to</span> http<span class="variable">s:</span>//repomanage.rdc.aliyun.<span class="keyword">com</span>/helm_repositories/<span class="number">26125</span>-play-helm...</span><br><span class="line">Done.</span><br></pre></td></tr></table></figure><h2><span id="小结">小结</span></h2><p>到目前为止，我们展示了如何在软件研发的端到端过程中使用Helm，通过基础设施即代码的模式，开发人员可以直接在源码中通过Chart定义管理应用的部署架构，在完成开发工作后开发人员只需要将Chart发布到Helm仓库中，接下来无论是测试，还是运维都可以直接使用Chart快速在Kubernetes集群中对应用进行测试与发布。</p><p>下一篇文章中，我们将会介绍如何使用Jenkins构建一条基于容器和Helm的持续交付流水线，同时介绍研发团队中的不同角色如何围绕Helm，围绕持续交付流水线实现一个高效，协作的研发流程。</p><h2><span id="参考资料">参考资料</span></h2><ul><li><a href="https://github.com/helm/helm" target="_blank" rel="noopener">Github Helm项目</a></li><li><a href="https://github.com/helm/chartmuseum" target="_blank" rel="noopener">Github Chartmuseum项目</a></li><li><a href="https://github.com/helm/chartmuseum" target="_blank" rel="noopener">Github Chartmuseum项目</a></li><li><a href="https://github.com/chartmuseum/helm-push" target="_blank" rel="noopener">Helm Push插件</a></li><li><a href="https://www.aliyun.com/product/yunxiao?spm=5176.224200.developerService.27.1b776ed6tdwLm5" target="_blank" rel="noopener">云效一站式企业协同研发云</a></li><li><a href="https://repomanage.rdc.aliyun.com/my/repo" target="_blank" rel="noopener">云效私有仓库服务</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;容器即进程，Kubernetes则解决了如何部署和运行应用的问题。对于任何一个部署在Kubernetes得应用而言，通常都可以由几个固定的部分组成：Ingress,Service,Deployment等。直接使用Kubernetes原生的YAML定义服务，虽然能一定程度上简化应用的部署，但是对于大部分研发人员来说编写和使用YAML依然是一件相对痛苦的事情。HELM应允而生，Helm作为Kubernetes下的包管理工具，对原生服务定义过程进行了增强，通过模板化，参数化的形式大大简化用户部署Kubernetes应用的复杂度。&lt;/p&gt;
&lt;p&gt;在本文中笔者，将以一个Spring Boot程序为例，介绍如何在软件研发端到端过程中是使用Helm。本文中所使用的示例代码可以通过&lt;a href=&quot;https://github.com/yunlzheng/project-samples/tree/master/containerization-spring-with-helm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github&lt;/a&gt;下载。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Kubernetes" scheme="http://yunlzheng.github.io/tags/Kubernetes/"/>
    
      <category term="Helm" scheme="http://yunlzheng.github.io/tags/Helm/"/>
    
  </entry>
  
  <entry>
    <title>Flannel网络以及在阿里云下的实现解析</title>
    <link href="http://yunlzheng.github.io/2018/09/07/k8s-flannel-in-alicloud/"/>
    <id>http://yunlzheng.github.io/2018/09/07/k8s-flannel-in-alicloud/</id>
    <published>2018-09-07T01:00:00.000Z</published>
    <updated>2018-12-30T08:52:30.471Z</updated>
    
    <content type="html"><![CDATA[<p>在Kubernetes中网络中，主要包含两种IP，分别是Pod IP和Cluster IP。 Pod IP是实际存在于网卡之上（如VETH的虚拟网卡），而Cluster IP则是一个虚拟的IP地址，该虚拟机IP由kube-proxy进行维护，kube-proxy目前提供了两种实现方式，包括默认的ip tables实现以及在K8S 1.8之后开始支持的ipvs实现。</p><a id="more"></a><p>对于Kubernetes其网络而言，其实现需要确保集群中每个Pod都有一个唯一的IP地址，并且Pod之间可以直接进行跨主机通讯。在符合这一原则的前提下，Kubernetes允许通过插件的方式，集成不同的容器集群网络实现。其中最常用的应该是Flannel。Flannel是由CoreOS团队针对KUbernetes设计的一个Overlay Network实现，通过隧道协议（udp，vxlan）封装容器之间的通讯报文，实现集群间网络通讯。</p><p><img src="/images/flannel.png" alt="/images/flannel.png"></p><p>Flannel默认使用UDP作为集群间通讯实现，如上图所示，Flannel通过ETCD管理整个集群中所有节点与子网的映射关系，如上图所示，Flannel分别为节点A和B划分了两个子网：10.1.15.0/16和10.1.20.0/16。同时通过修改docker启动参数，确保Docker启动的容器能够特定的网段中如10.1.15.1/24。</p><ul><li><p>同一Pod实例容器间通信：对于Pod而言，其可以包含1~n个容器实例，这些容器实例共享Pod的存储以及网络资源，Pod直接可以直接通过127.0.0.1进行通讯。其通过Linux的Network Namespace为这组容器实现了一个隔离网络。</p></li><li><p>相同主机上Pod间通信：对于Pod而言，每一个Pod实例都有一个独立的Pod IP，该IP是挂载到虚拟网卡（VETH）上，并且bridge到docker0的网卡上。以节点A为例，其节点上运行的Pod均在10.1.15.1/24的网段中，其属于相同网络，因此直接通过docker0进行通信。</p></li><li><p>对于跨节点间的Pod通信：以节点A和节点B通讯而言，由于不同节点docker0网卡的网段并不相同，因此flannel通过主机路由表的方式，将对节点B POD IP网段地址的访问路由到flannel0的网卡上。 而flannel0网卡的背后运行的则是flannel在每个节点上运行的进程flanneld。由于flannel通过ETCD维护了节点间所有网络的路由关系，原本容器将的数据报文，被flanneld封装成UDP协议，发送到了目标节点的flanneld进程，再对udp报文进行解包，后将数据发送到docker0，从而实现跨主机的Pod通讯。</p></li></ul><h2><span id="解析阿里云flannel实现">解析阿里云Flannel实现</span></h2><p>上述简单解释了Flannel默认的UDP实现过程，可以看出，由于存在大量的数据报文封装和解析的过程，其必然会导致Pod间网络性能的下降。除了默认的UDP实现以外，Flannel还支持基于vxlan的方式，vxlan是一个在已有3层物理网络上构建的2层逻辑网络的协议。</p><p>这里我们以通过阿里云Kubernetes服务创建的集群为例，解释跨主机将Pod是如何通讯的。</p><p>这里创建了两个Pod实例，其分别运行在节点cn-beijing.i-2ze52j61t5p9z4n60c9m和cn-beijing.i-2ze52j61t5p9z4n60c9l上：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl get pods -o wide --selector app=nginx</span><br><span class="line">NAME                     READY     STATUS    RESTARTS   AGE       IP             NODE</span><br><span class="line">nginx<span class="number">-56</span>f766d96f<span class="number">-2</span>dl9t   <span class="number">1</span>/<span class="number">1</span>       Running   <span class="number">0</span>          <span class="number">2</span>m        <span class="number">172.16</span><span class="number">.2</span><span class="number">.229</span>   cn-beijing.i<span class="number">-2</span>ze52j61t5p9z4n60c9m</span><br><span class="line">nginx2<span class="number">-6</span>f4bb4799-t84rh   <span class="number">1</span>/<span class="number">1</span>       Running   <span class="number">0</span>          <span class="number">3</span>m        <span class="number">172.16</span><span class="number">.2</span><span class="number">.125</span>   cn-beijing.i<span class="number">-2</span>ze52j61t5p9z4n60c9l</span><br></pre></td></tr></table></figure><p>以172.16.2.229访问172.16.2.125为例，我们进入到172.16.2.229所在节点的flannel容器:</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ k -n kube-system get pods -o wide --selector app=flannel</span><br><span class="line">NAME                    READY     STATUS    RESTARTS   AGE       IP             NODE</span><br><span class="line">kube-flannel-ds<span class="number">-7</span>zdnw   <span class="number">2</span>/<span class="number">2</span>       Running   <span class="number">4</span>          <span class="number">31</span>d       <span class="number">192.168</span><span class="number">.3</span><span class="number">.91</span>   cn-beijing.i<span class="number">-2</span>ze52j61t5p9z4n60c9k</span><br><span class="line">kube-flannel-ds<span class="number">-86</span>d5j   <span class="number">2</span>/<span class="number">2</span>       Running   <span class="number">0</span>          <span class="number">31</span>d       <span class="number">192.168</span><span class="number">.3</span><span class="number">.90</span>   cn-beijing.i<span class="number">-2</span>ze52j61t5p9z4n60c9m</span><br><span class="line">kube-flannel-ds<span class="number">-9</span>xn6p   <span class="number">2</span>/<span class="number">2</span>       Running   <span class="number">1</span>          <span class="number">31</span>d       <span class="number">192.168</span><span class="number">.3</span><span class="number">.87</span>   cn-beijing.i<span class="number">-2</span>ze44hu8106jqyw43i8d</span><br><span class="line">kube-flannel-ds-hjlb4   <span class="number">2</span>/<span class="number">2</span>       Running   <span class="number">1</span>          <span class="number">31</span>d       <span class="number">192.168</span><span class="number">.3</span><span class="number">.92</span>   cn-beijing.i<span class="number">-2</span>ze52j61t5p9z4n60c9l</span><br><span class="line">kube-flannel-ds-nb28r   <span class="number">2</span>/<span class="number">2</span>       Running   <span class="number">1</span>          <span class="number">31</span>d       <span class="number">192.168</span><span class="number">.3</span><span class="number">.88</span>   cn-beijing.i<span class="number">-2</span>ze8rkx46zywd36w8noo</span><br><span class="line">kube-flannel-ds-vmsxn   <span class="number">2</span>/<span class="number">2</span>       Running   <span class="number">1</span>          <span class="number">31</span>d       <span class="number">192.168</span><span class="number">.3</span><span class="number">.89</span>   cn-beijing.i<span class="number">-2</span>ze3pggklybyryt9475e</span><br></pre></td></tr></table></figure><p>首先，在上文中说了，Flannel通过ETCD会统一为每个节点分配相应的网段：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ k -n kube-system exec -it kube-flannel-ds-hjlb4 -c kube-flannel cat /run/flannel/subnet.env</span><br><span class="line">FLANNEL_NETWORK=<span class="number">172.16</span><span class="number">.0</span><span class="number">.0</span>/<span class="number">16</span></span><br><span class="line">FLANNEL_SUBNET=<span class="number">172.16</span><span class="number">.2</span><span class="number">.1</span>/<span class="number">25</span></span><br><span class="line">FLANNEL_MTU=<span class="number">1500</span></span><br><span class="line">FLANNEL_IPMASQ=true</span><br></pre></td></tr></table></figure><p>如上所示，Flannel建立了一个172.16.0.0/16的大网，而节点cn-beijing.i-2ze52j61t5p9z4n60c9m则分配了一个172.16.2.1/25的小网。所以该节点上所有Pod的IP地址一定是在该网段中（172.16.2.1 ~ 172.16.2.126）。</p><h3><span id="出口方向">出口方向</span></h3><p>从nginx-56f766d96f-2dl9t（172.16.2.229）所在节点的flannel实例kube-flannel-ds-86d5j查看网卡信息：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">$</span> <span class="string">k</span> <span class="bullet">-n</span> <span class="string">kube-system</span> <span class="string">exec</span> <span class="bullet">-it</span> <span class="string">kube-flannel-ds-86d5j</span> <span class="bullet">-c</span> <span class="string">kube-flannel</span> <span class="string">ifconfig</span></span><br><span class="line"><span class="string">cni0</span>      <span class="string">Link</span> <span class="attr">encap:Ethernet</span>  <span class="string">HWaddr</span> <span class="number">0</span><span class="attr">A:58:AC:10:02:81</span></span><br><span class="line">          <span class="string">inet</span> <span class="attr">addr:172.16.2.129</span>  <span class="attr">Bcast:0.0.0.0</span>  <span class="attr">Mask:255.255.255.128</span></span><br><span class="line">          <span class="string">UP</span> <span class="string">BROADCAST</span> <span class="string">RUNNING</span> <span class="string">MULTICAST</span>  <span class="attr">MTU:1500</span>  <span class="attr">Metric:1</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">packets:613034223</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">frame:0</span></span><br><span class="line">          <span class="string">TX</span> <span class="attr">packets:410106254</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">carrier:0</span></span><br><span class="line"><span class="attr">          collisions:</span><span class="number">0</span> <span class="attr">txqueuelen:1000</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">bytes:97180782429</span> <span class="string">(90.5</span> <span class="string">GiB)</span>  <span class="string">TX</span> <span class="attr">bytes:855792296086</span> <span class="string">(797.0</span> <span class="string">GiB)</span></span><br><span class="line"></span><br><span class="line"><span class="string">docker0</span>   <span class="string">Link</span> <span class="attr">encap:Ethernet</span>  <span class="string">HWaddr</span> <span class="number">02</span><span class="string">:42:31:24:3A:76</span></span><br><span class="line">          <span class="string">inet</span> <span class="attr">addr:172.17.0.1</span>  <span class="attr">Bcast:0.0.0.0</span>  <span class="attr">Mask:255.255.0.0</span></span><br><span class="line">          <span class="string">UP</span> <span class="string">BROADCAST</span> <span class="string">MULTICAST</span>  <span class="attr">MTU:1500</span>  <span class="attr">Metric:1</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">packets:0</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">frame:0</span></span><br><span class="line">          <span class="string">TX</span> <span class="attr">packets:0</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">carrier:0</span></span><br><span class="line"><span class="attr">          collisions:</span><span class="number">0</span> <span class="attr">txqueuelen:0</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">bytes:0</span> <span class="string">(0.0</span> <span class="string">B)</span>  <span class="string">TX</span> <span class="attr">bytes:0</span> <span class="string">(0.0</span> <span class="string">B)</span></span><br><span class="line"></span><br><span class="line"><span class="string">eth0</span>      <span class="string">Link</span> <span class="attr">encap:Ethernet</span>  <span class="string">HWaddr</span> <span class="number">00</span><span class="string">:16:3E:12:3F:B9</span></span><br><span class="line">          <span class="string">inet</span> <span class="attr">addr:192.168.3.90</span>  <span class="attr">Bcast:192.168.3.255</span>  <span class="attr">Mask:255.255.252.0</span></span><br><span class="line">          <span class="string">UP</span> <span class="string">BROADCAST</span> <span class="string">RUNNING</span> <span class="string">MULTICAST</span>  <span class="attr">MTU:1500</span>  <span class="attr">Metric:1</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">packets:1026400899</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">frame:0</span></span><br><span class="line">          <span class="string">TX</span> <span class="attr">packets:731775772</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">carrier:0</span></span><br><span class="line"><span class="attr">          collisions:</span><span class="number">0</span> <span class="attr">txqueuelen:1000</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">bytes:933504290702</span> <span class="string">(869.3</span> <span class="string">GiB)</span>  <span class="string">TX</span> <span class="attr">bytes:151441072517</span> <span class="string">(141.0</span> <span class="string">GiB)</span></span><br><span class="line"></span><br><span class="line"><span class="string">lo</span>        <span class="string">Link</span> <span class="attr">encap:Local</span> <span class="string">Loopback</span></span><br><span class="line">          <span class="string">inet</span> <span class="attr">addr:127.0.0.1</span>  <span class="attr">Mask:255.0.0.0</span></span><br><span class="line">          <span class="string">UP</span> <span class="string">LOOPBACK</span> <span class="string">RUNNING</span>  <span class="attr">MTU:65536</span>  <span class="attr">Metric:1</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">packets:2848730</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">frame:0</span></span><br><span class="line">          <span class="string">TX</span> <span class="attr">packets:2848730</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">carrier:0</span></span><br><span class="line"><span class="attr">          collisions:</span><span class="number">0</span> <span class="attr">txqueuelen:1</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">bytes:1107009588</span> <span class="string">(1.0</span> <span class="string">GiB)</span>  <span class="string">TX</span> <span class="attr">bytes:1107009588</span> <span class="string">(1.0</span> <span class="string">GiB)</span></span><br><span class="line"></span><br><span class="line"><span class="string">veth00c70308</span> <span class="string">Link</span> <span class="attr">encap:Ethernet</span>  <span class="string">HWaddr</span> <span class="attr">D2:D9:ED:7B:3F:A7</span></span><br><span class="line">          <span class="string">UP</span> <span class="string">BROADCAST</span> <span class="string">RUNNING</span> <span class="string">MULTICAST</span>  <span class="attr">MTU:1500</span>  <span class="attr">Metric:1</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">packets:10680903</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">frame:0</span></span><br><span class="line">          <span class="string">TX</span> <span class="attr">packets:12038380</span> <span class="attr">errors:0</span> <span class="attr">dropped:0</span> <span class="attr">overruns:0</span> <span class="attr">carrier:0</span></span><br><span class="line"><span class="attr">          collisions:</span><span class="number">0</span> <span class="attr">txqueuelen:0</span></span><br><span class="line">          <span class="string">RX</span> <span class="attr">bytes:717656154</span> <span class="string">(684.4</span> <span class="string">MiB)</span>  <span class="string">TX</span> <span class="attr">bytes:108607531374</span> <span class="string">(101.1</span> <span class="string">GiB)</span></span><br><span class="line"><span class="comment"># 省略其它输出</span></span><br></pre></td></tr></table></figure><p>其中veth00c70308是每个Pod实例虚拟网卡，并且通过网桥的方式链接到cni0网卡：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">$ k -n kube-system exec -it kube-flannel-ds-86d5j -c kube-flannel brctl show</span><br><span class="line">bridge name<span class="built_in">bridge </span>idSTP enabledinterfaces</span><br><span class="line">docker08000.024231243a76<span class="literal">no</span></span><br><span class="line">cni08000.0a58ac100281<span class="literal">no</span>veth00c70308</span><br><span class="line">veth244016e7</span><br><span class="line">veth41b59852</span><br><span class="line">veth1bde8f9e</span><br><span class="line">veth5758e57f</span><br><span class="line">vethfb90332d</span><br><span class="line">veth6fd79bb3</span><br><span class="line">veth80ab3625</span><br><span class="line">veth2f19245f</span><br><span class="line">vethb593c87a</span><br><span class="line">vethbc655860</span><br><span class="line">vethde851a00</span><br><span class="line">veth0c794757</span><br><span class="line">veth46c15d7c</span><br><span class="line">vethdddc772a</span><br><span class="line">veth9e77c7d5</span><br><span class="line">veth17b62b88</span><br><span class="line">veth3810c1b0</span><br></pre></td></tr></table></figure><p>从172.16.2.229向172.16.2.125，从源容器发出后通过网桥全部发送到cni0的网卡上。查看系统路由表，遗憾的是在系统中找不到任何从cni0网卡向后转发的规则：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ k -n kube-system exec -it kube-flannel-ds<span class="number">-86</span>d5j -c kube-flannel route</span><br><span class="line">Kernel IP routing table</span><br><span class="line">Destination     Gateway         Genmask         Flags Metric Ref    Use Iface</span><br><span class="line"><span class="section">default</span>         <span class="number">192.168</span><span class="number">.3</span><span class="number">.253</span>   <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         UG    <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> eth0</span><br><span class="line"><span class="number">169.254</span><span class="number">.0</span><span class="number">.0</span>     *               <span class="number">255.255</span><span class="number">.0</span><span class="number">.0</span>     U     <span class="number">1002</span>   <span class="number">0</span>        <span class="number">0</span> eth0</span><br><span class="line"><span class="number">172.16</span><span class="number">.2</span><span class="number">.128</span>    *               <span class="number">255.255</span><span class="number">.255</span><span class="number">.128</span> U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> cni0</span><br><span class="line"><span class="number">172.17</span><span class="number">.0</span><span class="number">.0</span>      *               <span class="number">255.255</span><span class="number">.0</span><span class="number">.0</span>     U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> docker0</span><br><span class="line"><span class="number">192.168</span><span class="number">.0</span><span class="number">.0</span>     *               <span class="number">255.255</span><span class="number">.252</span><span class="number">.0</span>   U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> eth0</span><br></pre></td></tr></table></figure><p>这部分的路由转发在阿里云环境中是通过VPC路由表实现，如下所示：</p><p><img src="https://github.com/yunlzheng/kubernetes-hands-on-workshop/raw/master/02-core-concept/images/aliyun-vpc-route.png" alt=""></p><p>从172.16.2.225发送到172.16.2.125的请求，匹配的路由记录为172.16.2.0/25。流量会被转发到 主机i-2ze52j61t5p9z4n60c9l，即Pod实例nginx2-6f65c584d-nglvf（172.16.2.125）所在的主机。</p><h3><span id="入口方向">入口方向</span></h3><p>出口方向，从源容器nginx-56f766d96f-2dl9t（172.16.2.229）发送到nginx2-6f4bb4799-t84rh（172.16.2.125)的流量已经正确的发送到目标节点i-2ze52j61t5p9z4n60c9l。</p><p>查看接收流量主机的路由规则：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ k -n kube-system exec -it kube-flannel-ds-hjlb4 -c kube-flannel -- route -n</span><br><span class="line">Kernel IP routing table</span><br><span class="line">Destination     Gateway         Genmask         Flags Metric Ref    Use Iface</span><br><span class="line"><span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         <span class="number">192.168</span><span class="number">.3</span><span class="number">.253</span>   <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         UG    <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> eth0</span><br><span class="line"><span class="number">169.254</span><span class="number">.0</span><span class="number">.0</span>     <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         <span class="number">255.255</span><span class="number">.0</span><span class="number">.0</span>     U     <span class="number">1002</span>   <span class="number">0</span>        <span class="number">0</span> eth0</span><br><span class="line"><span class="number">172.16</span><span class="number">.2</span><span class="number">.0</span>      <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         <span class="number">255.255</span><span class="number">.255</span><span class="number">.128</span> U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> cni0</span><br><span class="line"><span class="number">172.17</span><span class="number">.0</span><span class="number">.0</span>      <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         <span class="number">255.255</span><span class="number">.0</span><span class="number">.0</span>     U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> docker0</span><br><span class="line"><span class="number">192.168</span><span class="number">.0</span><span class="number">.0</span>     <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         <span class="number">255.255</span><span class="number">.252</span><span class="number">.0</span>   U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> eth0</span><br></pre></td></tr></table></figure><p>根据主机路由表规则，发送到172.16.2.125的请求会落到路由表：</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">172.16</span><span class="number">.2</span><span class="number">.0</span>      <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>         <span class="number">255.255</span><span class="number">.255</span><span class="number">.128</span> U     <span class="number">0</span>      <span class="number">0</span>        <span class="number">0</span> cni0</span><br></pre></td></tr></table></figure><p>从而请求进入到cni0网卡，并发送到相应的容器。</p><p>相比于UDP的实现方式，在vxlan中，不需要依赖于额外的flannel接口，通过(<a href="https://github.com/coreos/flannel/blob/master/Documentation/alicloud-vpc-backend-cn.md" target="_blank" rel="noopener">ali-vpc backend</a>)来代替封装IP规则以获得最佳的性能表现。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在Kubernetes中网络中，主要包含两种IP，分别是Pod IP和Cluster IP。 Pod IP是实际存在于网卡之上（如VETH的虚拟网卡），而Cluster IP则是一个虚拟的IP地址，该虚拟机IP由kube-proxy进行维护，kube-proxy目前提供了两种实现方式，包括默认的ip tables实现以及在K8S 1.8之后开始支持的ipvs实现。&lt;/p&gt;
    
    </summary>
    
    
      <category term="kubernetes" scheme="http://yunlzheng.github.io/tags/kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>图解Pod</title>
    <link href="http://yunlzheng.github.io/2018/08/01/kubernetes-pod/"/>
    <id>http://yunlzheng.github.io/2018/08/01/kubernetes-pod/</id>
    <published>2018-08-01T04:59:45.000Z</published>
    <updated>2018-12-30T09:03:13.393Z</updated>
    
    <content type="html"><![CDATA[<p>最近做Kubernetes培训时画的几张关于Pod的图</p><a id="more"></a><p>Pod模型：</p><p><img src="/images/pod-model.png" alt="Pod模型"></p><p>Pod生命周期：</p><p><img src="/images/pod-lifecycle.png" alt="Pod生命周期"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近做Kubernetes培训时画的几张关于Pod的图&lt;/p&gt;
    
    </summary>
    
    
      <category term="Kubernetes" scheme="http://yunlzheng.github.io/tags/Kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>Tips:解决Consul中服务实例重复注册的问题</title>
    <link href="http://yunlzheng.github.io/2018/06/04/service-repeated-registration-with-consul-in-kubernetes/"/>
    <id>http://yunlzheng.github.io/2018/06/04/service-repeated-registration-with-consul-in-kubernetes/</id>
    <published>2018-06-03T16:59:45.000Z</published>
    <updated>2018-12-30T09:14:24.435Z</updated>
    
    <content type="html"><![CDATA[<p>为了提升系统核心服务的问题性，避免由于K8S网络导致服务间调用的稳定性。目前将系统核心服务的部署方式从Pod Network切换到了Host Network。 Host Network限制了Pod的部署数量，最大情况下只能和主机数量保持一致。因此需要在Deployment中设置一些反亲和性的调度策略。 确保Pod不会被反复注册到相同主机上。</p><a id="more"></a><p><img src="/images/to-many-instance.png" alt=""></p><p>Spring Cloud中每个实例启动时都会产生一个唯一的InstanceID,并且通过InstanceID向Consul中进行注册。不同的InstanceID对于Consul而言就代表着不同的服务实例。但是由于目前将Pod网络方式设置成为了HostNetwork。因此只要是相同主机上启动的服务，其访问地址一定是相同的。 但是反复启动时，Spring Cloud注册会生成不同的InstanceID。 这些对于COnsul而言不同的Instance。 实际指向了一个相同的Pod网络。 在应用反复升级/部署之后，会发现Consul中存在大量的服务实例，而这些服务实例指向的地址都是相同的。对于这些服务Consul会定期调用Health Check去检查服务可用性。 大量的检查项导致Consul性能下降。为此需要一个简单的解决方案，确保在相同主机上运行的Pod实例，在部署/重启/升级后的InstanceID保持一致。</p><p>一个简单的方案就是在InstanceID中取出随机值，并且使用当前Pod的IP地址作为标识，例如： service-192-168-1-2。 这种方式可以确保Pod在默认容器网络或者Host网络下可以保持一致的行为。</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">apiVersion:</span> v1</span><br><span class="line"><span class="symbol">kind:</span> Pod</span><br><span class="line"><span class="symbol">metadata:</span></span><br><span class="line"><span class="symbol">  name:</span> your-spring-cloud-service</span><br><span class="line"><span class="symbol">spec:</span></span><br><span class="line"><span class="symbol">  containers:</span></span><br><span class="line">    - name: your-spring-cloud-service</span><br><span class="line"><span class="symbol">      image:</span> your-spring-cloud-service-image</span><br><span class="line"><span class="symbol">      command:</span> [ <span class="string">"sh"</span>, <span class="string">"-c"</span>]</span><br><span class="line"><span class="symbol">      args:</span></span><br><span class="line">      - --spring.cloud.consul.discovery.instanceId=$&#123;spring.application.name&#125;$&#123;MY_POD_IP&#125;</span><br><span class="line"><span class="symbol">      env:</span></span><br><span class="line">        - name: MY_POD_IP</span><br><span class="line"><span class="symbol">          valueFrom:</span></span><br><span class="line"><span class="symbol">            fieldRef:</span></span><br><span class="line"><span class="symbol">              fieldPath:</span> status.podIP</span><br><span class="line"><span class="symbol">  restartPolicy:</span> Never</span><br></pre></td></tr></table></figure><p>这里通过将当前Pod的IP地址注入到容器实例的环境变量变量中，并且覆盖默认的–spring.cloud.consul.discover.instanceId来确保注册到Consul中的ServieID唯一。</p><p>除了podIP以外，K8S还支持从spec，metadata中获取Pod相关的信息：</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">env</span>:</span><br><span class="line">    - <span class="attribute">name</span>: MY_NODE_NAME</span><br><span class="line">      <span class="attribute">valueFrom</span>:</span><br><span class="line">        <span class="attribute">fieldRef</span>:</span><br><span class="line">          <span class="attribute">fieldPath</span>: spec.nodeName</span><br><span class="line">    - <span class="attribute">name</span>: MY_POD_NAME</span><br><span class="line">      <span class="attribute">valueFrom</span>:</span><br><span class="line">        <span class="attribute">fieldRef</span>:</span><br><span class="line">          <span class="attribute">fieldPath</span>: metadata.name</span><br><span class="line">    - <span class="attribute">name</span>: MY_POD_NAMESPACE</span><br><span class="line">      <span class="attribute">valueFrom</span>:</span><br><span class="line">        <span class="attribute">fieldRef</span>:</span><br><span class="line">          <span class="attribute">fieldPath</span>: metadata.namespace</span><br><span class="line">    - <span class="attribute">name</span>: MY_POD_SERVICE_ACCOUNT</span><br><span class="line">      <span class="attribute">valueFrom</span>:</span><br><span class="line">        <span class="attribute">fieldRef</span>:</span><br><span class="line">          <span class="attribute">fieldPath</span>: spec.serviceAccountName</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;为了提升系统核心服务的问题性，避免由于K8S网络导致服务间调用的稳定性。目前将系统核心服务的部署方式从Pod Network切换到了Host Network。 Host Network限制了Pod的部署数量，最大情况下只能和主机数量保持一致。因此需要在Deployment中设置一些反亲和性的调度策略。 确保Pod不会被反复注册到相同主机上。&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Promtheus Remote Storage使用案例：多Kubernetes集群监控方案</title>
    <link href="http://yunlzheng.github.io/2018/04/25/prometheus-with-mutil-k8s-environment/"/>
    <id>http://yunlzheng.github.io/2018/04/25/prometheus-with-mutil-k8s-environment/</id>
    <published>2018-04-25T02:35:20.000Z</published>
    <updated>2018-12-30T09:05:49.862Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/mutil-k8s-cluster.png" alt="/images/mutil-k8s-cluster.png"></p><a id="more"></a><p>Agent侧组件：</p><ul><li>Prometheus Server: 负责采集当前K8s集群下所有样本数据，并通过Remote Write写入数据到Remote Storage Adaptor；</li><li>Exporters: 暴露当前集群各监控指标的Exporter实例。</li></ul><p>Server侧组件：</p><ul><li>Prometheus Server: 负责监控数据查询以及告警规则计算;</li><li>Prometheus Rest: 自定义API组件，负责创建和管理告警规则文件;</li><li>Remote Storage Adaptor: 提供Remote Storage相关API。 持久化数据到Influxdb以及从Influxdb中获取样本数据；</li><li>Alertmanager：负责对Prometheus产生的告警进行后续处理。</li></ul><p>优点:</p><ul><li>Prometheus读写分离</li><li>支持垮网络环境(类Push模式)</li></ul><p>缺点：</p><ul><li>Remote Storage Adaptor的单点故障和性能问题</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/images/mutil-k8s-cluster.png&quot; alt=&quot;/images/mutil-k8s-cluster.png&quot;&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="Kubernetes" scheme="http://yunlzheng.github.io/tags/Kubernetes/"/>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
  </entry>
  
  <entry>
    <title>监控MySQL运行状态</title>
    <link href="http://yunlzheng.github.io/2018/04/02/use-prometheus-monitor-mysql/"/>
    <id>http://yunlzheng.github.io/2018/04/02/use-prometheus-monitor-mysql/</id>
    <published>2018-04-02T01:02:20.000Z</published>
    <updated>2018-12-30T09:06:57.576Z</updated>
    
    <content type="html"><![CDATA[<p>MySQL是一个关系型数据库管理系统，由瑞典MySQL AB公司开发，目前属于Oracle旗下的产品。 MySQL是最流行的关系型数据库管理系统之一。数据库的稳定运行时保证业务可用性的关键因素之一。这一小节当中将介绍如何使用Prometheus提供的MySQLD Exporter实现对MySQL数据库性能以及资源利用率的监控和度量。</p><a id="more"></a><h2><span id="部署mysqld-exporter">部署MySQLD Exporter</span></h2><p>为了简化测试环境复杂度，这里使用Docker Compose定义并启动MySQL以及MySQLD Exporter：</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">version:</span> <span class="string">'3'</span></span><br><span class="line"><span class="symbol">services:</span></span><br><span class="line"><span class="symbol">  mysql:</span></span><br><span class="line"><span class="symbol">    image:</span> mysql:<span class="number">5.7</span></span><br><span class="line"><span class="symbol">    ports:</span></span><br><span class="line">      - <span class="string">"3306:3306"</span></span><br><span class="line"><span class="symbol">    environment:</span></span><br><span class="line">      - MYSQL_ROOT_PASSWORD=password</span><br><span class="line">      - MYSQL_DATABASE=database</span><br><span class="line"><span class="symbol">  mysqlexporter:</span></span><br><span class="line"><span class="symbol">    image:</span> prom/mysqld-exporter</span><br><span class="line"><span class="symbol">    ports:</span></span><br><span class="line">      - <span class="string">"9104:9104"</span></span><br><span class="line"><span class="symbol">    environment:</span></span><br><span class="line">      - DATA_SOURCE_NAME=root:password@(mysql:<span class="number">3306</span>)/database</span><br></pre></td></tr></table></figure><p>这里通过环境变量DATA_SOURCE_NAME方式定义监控目标。使用Docker Compose启动测试用的MySQL实例以及MySQLD Exporter:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker-compose up -d</span></span><br></pre></td></tr></table></figure><p>启动完成后，可以通过以下命令登录到MySQL容器当中，并执行MySQL相关的指令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it &lt;mysql_container_id&gt; mysql -uroot -ppassword</span></span><br><span class="line"><span class="meta">mysql&gt;</span><span class="bash"></span></span><br></pre></td></tr></table></figure><p>可以通过<a href="http://localhost:9104" target="_blank" rel="noopener">http://localhost:9104</a>访问MySQLD Exporter暴露的服务：</p><p>可以通过/metrics查看mysql_up指标判断当前MySQLD Exporter是否正常连接到了MySQL实例，当指标值为1时表示能够正常获取监控数据：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># HELP mysql_up Whether the MySQL server is up.</span></span><br><span class="line"><span class="comment"># TYPE mysql_up gauge</span></span><br><span class="line"><span class="attribute">mysql_up</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>修改Prometheus配置文件/etc/prometheus/prometheus.yml，增加对MySQLD Exporter实例的采集任务配置:</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">-<span class="ruby"> <span class="symbol">job_name:</span> mysqld</span></span><br><span class="line"><span class="ruby">  <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">  - <span class="symbol">targets:</span></span></span><br><span class="line"><span class="ruby">    - <span class="symbol">localhost:</span><span class="number">9104</span></span></span><br></pre></td></tr></table></figure><p>启动Prometheus:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">prometheus --config.file=/etc/prometheus/prometheus<span class="selector-class">.yml</span> --storage<span class="selector-class">.tsdb</span><span class="selector-class">.path</span>=/data/prometheus</span><br></pre></td></tr></table></figure><p>通过Prometheus的状态页，可以查看当前Target的状态：</p><p><img src="/images/mysqld_exporter_target_stats.png" alt="MySQLD Exporter实例状态"></p><p>为了确保数据库的稳定运行，通常会关注一下四个与性能和资源利用率相关的指标：查询吞吐量、连接情况、缓冲池使用情况以及查询执行性能等。</p><h2><span id="监控数据库吞吐量">监控数据库吞吐量</span></h2><p>对于数据库而言，最重要的工作就是实现对数据的增、删、改、查。为了衡量数据库服务器当前的吞吐量变化情况。在MySQL内部通过一个名为Questions的计数器，当客户端发送一个查询语句后，其值就会+1。可以通过以下MySQL指令查询Questions等服务器状态变量的值：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql&gt;</span><span class="bash"> SHOW GLOBAL STATUS LIKE <span class="string">"Questions"</span>;</span></span><br><span class="line">+---------------+-------+</span><br><span class="line">| Variable_name | Value |</span><br><span class="line">+---------------+-------+</span><br><span class="line">| Questions     | 1326  |</span><br><span class="line">+---------------+-------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>MySQLD Exporter中返回的样本数据中通过mysql_global_status_questions反映当前Questions计数器的大小：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># HELP mysql_global_status_questions Generic metric from SHOW GLOBAL STATUS.</span><br><span class="line"># TYPE mysql_global_status_questions untyped</span><br><span class="line">mysql_global_status_questions 1016</span><br></pre></td></tr></table></figure><p>通过以下PromQL可以查看当前MySQL实例查询速率的变化情况，查询数量的突变往往暗示着可能发生了某些严重的问题，因此用于用户应该关注并且设置响应的告警规则，以及时获取该指标的变化情况：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rate(mysql_global_status_questions[2m])</span><br></pre></td></tr></table></figure><p>一般还可以从监控读操作和写操作的执行情况进行判断。通过MySQL全局状态中的Com_select可以查询到当前服务器执行查询语句的总次数：相应的，也可以通过Com_insert、Com_update以及Com_delete的总量衡量当前服务器写操作的总次数，例如，可以通过以下指令查询当前MySQL实例insert语句的执行次数总量：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql&gt;</span><span class="bash"> SHOW GLOBAL STATUS LIKE <span class="string">"Com_insert"</span>;</span></span><br><span class="line">+---------------+-------+</span><br><span class="line">| Variable_name | Value |</span><br><span class="line">+---------------+-------+</span><br><span class="line">| Com_insert    | 0     |</span><br><span class="line">+---------------+-------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>从MySQLD Exporter的/metrics返回的监控样本中，可以通过global_status_commands_total获取当前实例各类指令执行的次数：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># HELP mysql_global_status_commands_total Total number of executed MySQL commands.</span><br><span class="line"># TYPE mysql_global_status_commands_total counter</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;admin_commands&quot;&#125; 0</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;alter_db&quot;&#125; 0</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;alter_db_upgrade&quot;&#125; 0</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;select&quot;&#125; 10</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;insert&quot;&#125; 2</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;update&quot;&#125; 2</span><br><span class="line">mysql_global_status_commands_total&#123;command=&quot;delete&quot;&#125; 1</span><br></pre></td></tr></table></figure><p>用户可以通过以下PromQL查看当前MySQL实例写操作速率的变化情况：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sum(rate(mysql_global_status_commands_total&#123;command=~&quot;insert|update|delete&quot;&#125;[2m])) without (command)</span><br></pre></td></tr></table></figure><h2><span id="连接情况">连接情况</span></h2><p>在MySQL中通过全局设置max_connections限制了当前服务器允许的最大客户端连接数量。一旦可用连接数被用尽，新的客户端连接都会被直接拒绝。 因此当监控MySQL运行状态时，需要时刻关注MySQL服务器的连接情况。用户可以通过以下指令查看当前MySQL服务的max_connections配置：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql&gt;</span><span class="bash"> SHOW VARIABLES LIKE <span class="string">'max_connections'</span>;</span></span><br><span class="line">+-----------------+-------+</span><br><span class="line">| Variable_name   | Value |</span><br><span class="line">+-----------------+-------+</span><br><span class="line">| max_connections | 151   |</span><br><span class="line">+-----------------+-------+</span><br><span class="line">1 row in set (0.01 sec)</span><br></pre></td></tr></table></figure><p>MySQL默认的最大链接数为151。临时调整最大连接数，可以通过以下指令进行设置：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SET GLOBAL max_connections = 200;</span><br></pre></td></tr></table></figure><p>如果想永久化设置，则需要通过修改MySQL配置文件my.cnf，添加以下内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">max_connections = 200</span><br></pre></td></tr></table></figure><p>通过Global Status中的Threads_connected、Aborted_connects、Connection_errors_max_connections以及Threads_running可以查看当前MySQL实例的连接情况。</p><p>例如，通过以下指令可以直接当前MySQL实例的连接数：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SHOW GLOBAL STATUS LIKE &quot;Threads_connected&quot;;</span><br><span class="line">+-------------------+-------+</span><br><span class="line">| Variable_name     | Value |</span><br><span class="line">+-------------------+-------+</span><br><span class="line">| Threads_connected | 1     |</span><br><span class="line">+-------------------+-------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>当所有可用连接都被占用时，如果一个客户端尝试连接至MySQL，会出现“Too many connections(连接数过多)”错误，同时Connection_errors_max_connections的值也会增加。为了防止出现此类情况，你应该监控可用连接的数量，并确保其值保持在max_connections限制以内。同时如果Aborted_connects的数量不断增加时，说明客户端尝试连接到MySQL都失败了。这是应该通过Connection_errors_max_connections以及Connection_errors_internal分析连接失败的问题原因。</p><p>下面列举了与MySQL连接相关的监控指标：</p><ul><li>mysql_global_variables_max_connections： 允许的最大连接数；</li><li>mysql_global_status_threads_connected： 当前开放的连接；</li><li>mysql_global_status_threads_running：当前开放的连接；</li><li>mysql_global_status_aborted_connects：当前开放的连接；</li><li>mysql_global_status_connection_errors_total{error=”max_connections”}：由于超出最大连接数导致的错误；</li><li>mysql_global_status_connection_errors_total{error=”internal”}：由于系统内部导致的错误；</li></ul><p>通过PromQL查询当前剩余的可用连接数：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql_global_variables_max_connections - mysql_global_status_threads_connected</span><br></pre></td></tr></table></figure><p>使用PromQL查询当前MySQL实例连接拒绝数：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql_global_status_aborted_connects</span><br></pre></td></tr></table></figure><h2><span id="缓冲池使用情况">缓冲池使用情况</span></h2><p>MySQL默认的存储引擎InnoDB使用了一篇称为缓冲池的内存区域，用于缓存数据表以及索引的数据。 当缓冲池的资源使用超出限制后，可能会导致数据库性能的下降，同时很多查询命令回直接在磁盘中执行，导致磁盘I/O不断攀升。 因此关注MySQL缓冲池的资源使用情况，并且在合理的时间扩大缓冲池的大小可以优化数据库的性能。</p><p>Innodb_buffer_pool_pages_total反应了当前缓冲池中的内存页的总页数。可以通过以下指令查看：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SHOW GLOBAL STATUS LIKE &quot;Innodb_buffer_pool_pages_total&quot;;</span><br><span class="line">+--------------------------------+-------+</span><br><span class="line">| Variable_name                  | Value |</span><br><span class="line">+--------------------------------+-------+</span><br><span class="line">| Innodb_buffer_pool_pages_total | 8191  |</span><br><span class="line">+--------------------------------+-------+</span><br><span class="line">1 row in set (0.02 sec)</span><br></pre></td></tr></table></figure><p>MySQLD Exporter通过以下指标返回缓冲池中各类内存页的数量：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># HELP mysql_global_status_buffer_pool_pages Innodb buffer pool pages by state.</span><br><span class="line"># TYPE mysql_global_status_buffer_pool_pages gauge</span><br><span class="line">mysql_global_status_buffer_pool_pages&#123;state=&quot;data&quot;&#125; 516</span><br><span class="line">mysql_global_status_buffer_pool_pages&#123;state=&quot;dirty&quot;&#125; 0</span><br><span class="line">mysql_global_status_buffer_pool_pages&#123;state=&quot;free&quot;&#125; 7675</span><br><span class="line">mysql_global_status_buffer_pool_pages&#123;state=&quot;misc&quot;&#125; 0</span><br></pre></td></tr></table></figure><p>Innodb_buffer_pool_read_requests记录了正常从缓冲池读取数据的请求数量。可以通过以下指令查看：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SHOW GLOBAL STATUS LIKE &quot;Innodb_buffer_pool_read_requests&quot;;</span><br><span class="line">+----------------------------------+--------+</span><br><span class="line">| Variable_name                    | Value  |</span><br><span class="line">+----------------------------------+--------+</span><br><span class="line">| Innodb_buffer_pool_read_requests | 797023 |</span><br><span class="line">+----------------------------------+--------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>MySQLD Exporter通过以下指标返回缓冲池中Innodb_buffer_pool_read_requests的值：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># HELP mysql_global_status_innodb_buffer_pool_read_requests Generic metric from SHOW GLOBAL STATUS.</span><br><span class="line"># TYPE mysql_global_status_innodb_buffer_pool_read_requests untyped</span><br><span class="line">mysql_global_status_innodb_buffer_pool_read_requests 736711</span><br></pre></td></tr></table></figure><p>当缓冲池无法满足时，MySQL只能从磁盘中读取数据。Innodb_buffer_pool_reads即记录了从磁盘读取数据的请求数量。通常来说从内存中读取数据的速度要比从磁盘中读取快很多，因此，如果Innodb_buffer_pool_reads的值开始增加，可能意味着数据库的性能有问题。 可以通过以下只能查看Innodb_buffer_pool_reads的数量</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SHOW GLOBAL STATUS LIKE &quot;Innodb_buffer_pool_reads&quot;;</span><br><span class="line">+--------------------------+-------+</span><br><span class="line">| Variable_name            | Value |</span><br><span class="line">+--------------------------+-------+</span><br><span class="line">| Innodb_buffer_pool_reads | 443   |</span><br><span class="line">+--------------------------+-------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>在MySQLD Exporter中可以通过以下指标查看Innodb_buffer_pool_reads的数量。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># HELP mysql_global_status_innodb_buffer_pool_reads Generic metric from SHOW GLOBAL STATUS.</span><br><span class="line"># TYPE mysql_global_status_innodb_buffer_pool_reads untyped</span><br><span class="line">mysql_global_status_innodb_buffer_pool_reads 443</span><br></pre></td></tr></table></figure><p>通过以上监控指标，以及实际监控的场景，我们可以利用PromQL快速建立多个监控项。</p><p>通过以下PromQL可以得到各个MySQL实例的缓冲池利用率。一般来说还需要结合Innodb_buffer_pool_reads的增长率情况来结合判断缓冲池大小是否合理：</p><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(<span class="name">sum</span>(<span class="name">mysql_global_status_buffer_pool_pages</span>) by (<span class="name">instance</span>) - sum(<span class="name">mysql_global_status_buffer_pool_pages</span>&#123;state=<span class="string">"free"</span>&#125;) by (<span class="name">instance</span>)) / sum(<span class="name">mysql_global_status_buffer_pool_pages</span>) by (<span class="name">instance</span>)</span><br></pre></td></tr></table></figure><p>也可以通过以下PromQL计算2分钟内磁盘读取请求次数的增长率的变化情况：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">rate</span><span class="params">(mysql_global_status_innodb_buffer_pool_reads[<span class="number">2</span>m])</span></span></span><br></pre></td></tr></table></figure><h2><span id="查询性能">查询性能</span></h2><p>MySQL还提供了一个Slow_queries的计数器，当查询的执行时间超过long_query_time的值后，计数器就会+1，其默认值为10秒，可以通过以下指令在MySQL中查询当前long_query_time的设置：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SHOW VARIABLES LIKE <span class="emphasis">'long_query_time'</span>;</span><br><span class="line"><span class="code">+-----------------+</span>-----------+</span><br><span class="line">| Variable<span class="emphasis">_name   | Value     |</span></span><br><span class="line"><span class="emphasis">+-----------------+-----------+</span></span><br><span class="line"><span class="emphasis">| long_</span>query<span class="emphasis">_time | 10.000000 |</span></span><br><span class="line"><span class="emphasis">+-----------------+-----------+</span></span><br><span class="line"><span class="emphasis">1 row in set (0.00 sec)</span></span><br></pre></td></tr></table></figure><p>通过以下指令可以查看当前MySQL实例中Slow_queries的数量：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SHOW GLOBAL STATUS LIKE "Slow<span class="emphasis">_queries";</span></span><br><span class="line"><span class="emphasis">+---------------+-------+</span></span><br><span class="line"><span class="emphasis">| Variable_</span>name | Value |</span><br><span class="line"><span class="code">+---------------+</span>-------+</span><br><span class="line">| Slow<span class="emphasis">_queries  | 0     |</span></span><br><span class="line"><span class="emphasis">+---------------+-------+</span></span><br><span class="line"><span class="emphasis">1 row in set (0.00 sec)</span></span><br></pre></td></tr></table></figure><p>MySQLD Exporter返回的样本数据中，通过以下指标展示当前的Slow_queries的值：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> HELP mysql_global_status_slow_queries Generic metric from SHOW GLOBAL STATUS.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> TYPE mysql_global_status_slow_queries untyped</span></span><br><span class="line">mysql_global_status_slow_queries 0</span><br></pre></td></tr></table></figure><p>通过监控Slow_queries的增长率，可以反映出当前MySQL服务器的性能状态，可以通过以下PromQL查询Slow_queries的增长情况：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">rate</span><span class="params">(mysql_global_status_slow_queries[<span class="number">2</span>m])</span></span></span><br></pre></td></tr></table></figure><p>在MySQL中还可以通过安装response time插件，从而支持记录查询时间区间的统计信息。启动该功能后MySQLD Exporter也会自动获取到相关数据，从而可以细化MySQL查询响应时间的分布情况。 感兴趣的读者可以自行尝试。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;MySQL是一个关系型数据库管理系统，由瑞典MySQL AB公司开发，目前属于Oracle旗下的产品。 MySQL是最流行的关系型数据库管理系统之一。数据库的稳定运行时保证业务可用性的关键因素之一。这一小节当中将介绍如何使用Prometheus提供的MySQLD Exporter实现对MySQL数据库性能以及资源利用率的监控和度量。&lt;/p&gt;
    
    </summary>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/categories/Prometheus/"/>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="MySQL" scheme="http://yunlzheng.github.io/tags/MySQL/"/>
    
      <category term="MySQL Exporter" scheme="http://yunlzheng.github.io/tags/MySQL-Exporter/"/>
    
  </entry>
  
  <entry>
    <title>Java回炉重造之 - 并发与异步编程[图文]</title>
    <link href="http://yunlzheng.github.io/2018/03/21/java-concurrency-and-asynchronous/"/>
    <id>http://yunlzheng.github.io/2018/03/21/java-concurrency-and-asynchronous/</id>
    <published>2018-03-21T14:46:39.000Z</published>
    <updated>2018-12-30T09:08:35.076Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/concurrency-parallel.png" alt="并发与并行"></p><a id="more"></a><h2><span id="并发与并行">并发与并行</span></h2><p>对于操作系统而言，一个单核的CPU就像写字楼中一层只有一个马桶一样。 即使有再多的人想要用，也不可能让两个人同时使用。因此只能将时间片段分配个每一个人单独利用。 当CPU被占用时，其他人只能等待。这种方式就叫做并发(concurrency)。</p><p>而多余多核CPU而言，就好多了。可以有不同的人同时使用，这种就叫做并行(Paralle)。 并行强调时间上的同时发生。</p><p>而一个程序(即一个进程)运行过程中，不会说这个程序(进程)占用了多少CPU时间，而是说这个进程中派生出的线程(Thread)占用了多少CPU时间。 在操作系统中进程是操作系统资源调度和分配的基本单位，而线程才是CPUdi调度和分配的单位。线程才是上图中的那些小人儿。</p><p>Java创建线程有两种方式。一种直接继承Thread类，复写run()方法。在使用时，直接新建一个线程对象，然后使用<strong>new MyThread().start()</strong>启动线程;</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">MyThread</span> <span class="keyword">extends</span> <span class="title">Thread</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    public void run() &#123;</span><br><span class="line">        <span class="type">System</span>.out.println(<span class="string">"Hello thread"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外一种方式是实现Runable接口。使用时使用<strong>new Thread(new MyRunable()).start()</strong>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyRunable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"Hello runable"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一般来说我们更推荐使用第二种方式，直接继承Thread，会让实现关注了线程自身的行为。 而实际上我们只需要让这个线程运行点什么东西就行了。下文中的线程池也很好的佐证了这样的思路。</p><p>当然从理论上讲线程越多，抢占到CPU使用时间的可能性就越大，越多的CPU使用时间就等于越快的处理速度。(注意适可而止，线程的创建和销毁都会消耗资源，线程太多系统也会崩溃掉的)。</p><h2><span id="线程池">线程池</span></h2><p>上一部分说了，虽然理论上讲，线程越多程序可能更快，但是在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗，以及保护操作系统本身的目的。我们通常需要将线程限制在一定的范围之类，线程池就起到了这样的作用。<br>和所有的池一样，都需要喂养一些东西。线程池中喂养的就是我们的线程，通常来说线程池中会有一定数量的核心线程配比，以及一定的Buffer配比。一定的Buffer比例可以让系统适应并发压力的波动，而固定数量的核心线程，可以确保减少线程创建以及销毁的资源损耗。 这样当有任务需要执行的时候，那就直接把任务丢给线程池就好了。</p><p><img src="/images/thread_pool.png" alt="线程池"></p><p>Java提供了以下方式来创建线程池：</p><figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ThreadPoolExecutor threadPoolExecutor = <span class="keyword">new</span> <span class="type">ThreadPoolExecutor</span>(<span class="number">0</span>, Integer.MAX_VALUE, <span class="number">60</span>L, TimeUnit.SECONDS, <span class="keyword">new</span> <span class="type">SynchronousQueue</span>&lt;&gt;());</span><br><span class="line"><span class="comment">// 等价于 Executors.newCachedThreadPool();</span></span><br><span class="line">threadPoolExecutor.execute(runable);</span><br></pre></td></tr></table></figure><p>TheadPoolExecutor的构造函数定义如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ThreadPoolExecutor</span><span class="params">(<span class="keyword">int</span> corePoolSize,</span></span></span><br><span class="line"><span class="function"><span class="params">                              <span class="keyword">int</span> maximumPoolSize,</span></span></span><br><span class="line"><span class="function"><span class="params">                              <span class="keyword">long</span> keepAliveTime,</span></span></span><br><span class="line"><span class="function"><span class="params">                              TimeUnit unit,</span></span></span><br><span class="line"><span class="function"><span class="params">                              BlockingQueue&lt;Runnable&gt; workQueue)</span></span></span><br></pre></td></tr></table></figure><p>其中corePoolSize用于指定核心线程数量，maximumPoolSize指定最大线程数，keepAliveTime和TimeUnit指定线程空闲后的最大存活时间，workQueue则是线程池的缓冲队列。还未执行的线程会在队列中等待。java中提供的Executors提供了多种默认的线程池实现。</p><h2><span id="异步与并行">异步与并行</span></h2><p>在经常接触Java Web开发的同学都知道应用服务器如(Tomcat),会为每一个请求分配一个独立的线程。因此在大部分情况下，都不需要考虑多线程的问题。</p><p>例如，可以通过修改Tomcat的配置，修改其线程池的相关配置。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">Connector</span> <span class="attr">port</span>=<span class="string">"8080"</span>     </span></span><br><span class="line"><span class="tag">               <span class="attr">maxThreads</span>=<span class="string">"150"</span> <span class="attr">minSpareThreads</span>=<span class="string">"25"</span> <span class="attr">maxSpareThreads</span>=<span class="string">"75"</span>     </span></span><br><span class="line"><span class="tag">               <span class="attr">enableLookups</span>=<span class="string">"false"</span> <span class="attr">redirectPort</span>=<span class="string">"8443"</span> <span class="attr">acceptCount</span>=<span class="string">"100"</span>     </span></span><br><span class="line"><span class="tag">               <span class="attr">debug</span>=<span class="string">"0"</span> <span class="attr">connectionTimeout</span>=<span class="string">"20000"</span>      </span></span><br><span class="line"><span class="tag">               <span class="attr">disableUploadTimeout</span>=<span class="string">"true"</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>但是对于每一个Request而言，假如对应了多个业务操作，特别是在微服务的方式中可能还会远程调用多个其它远程服务，会使得该Request的响应时间过长，降低系统的吞吐量。</p><p><img src="/images/java-asynchronous-1.png" alt="HttpServer的多线程与请求的单线程"></p><p>因此为了提升系统的吞吐量(注意，不是并发量，并发量由Webserver的线程池大小决定)。可以使用异步的方式对请求进行处理。将这些远程调用，变成异步的方式，而在外部等待这些异步操作执行完成后，对结果进行汇集后再返回给客户端。 响应时间减少到最长远程调用的时间。</p><p><img src="/images/java-asynchronous-3.png" alt="异步处理"></p><p>除了以上的方式以外，如果业务操作时间就非常长，可能就需要结合消息队列的方式对请求进行处理。当客户端发起请求后，理解响应Accepted，告诉客户端，“朕知道了，我会慢慢处理，你先下去吧”。然后由后台任务对业务进行处理即可。</p><p><img src="/images/java-asynchronous-queue.png" alt="使用队列解决异步问题"></p><h3><span id="使用future实现并发">使用Future实现并发</span></h3><p>在前面的部分，介绍过在Java中使用Thread和Runnable实现多线程程序，这种模式下调用者并不关心线程的返回状态，直接把线程丢给线程池执行即可。而有些情况下，调用者是需要了解线程中任务的执行结果，然后对结果进行汇集。在还没有Future之前，可以使用CountDownLatch的方式。</p><p>例如，这里有一个远程调用的服务RemoteService:</p><figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.github.concurrent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> class RemoteService &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Random <span class="built_in">random</span> = <span class="keyword">new</span> Random(System.nanoTime());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> call() <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="built_in">int</span> target = <span class="built_in">random</span>.nextInt(<span class="number">10</span>) + <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">int</span> <span class="built_in">millis</span> = target * <span class="number">10</span>;</span><br><span class="line">        Thread.sleep(<span class="built_in">millis</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">millis</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了模拟远程调用的等待时间，这里随机让线程sleep一段时间。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.github.concurrent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.CountDownLatch;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ExecutorService;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Executors;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CountDownSample</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">static</span> ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        RemoteService remoteService = <span class="keyword">new</span> RemoteService();</span><br><span class="line">        <span class="comment">// 定义CountDownLatch的count为2</span></span><br><span class="line">        CountDownLatch _latch = <span class="keyword">new</span> CountDownLatch(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">        RemoteCallRunnable calback1 = <span class="keyword">new</span> RemoteCallRunnable(remoteService, _latch);</span><br><span class="line">        RemoteCallRunnable callback2 = <span class="keyword">new</span> RemoteCallRunnable(remoteService, _latch);</span><br><span class="line"></span><br><span class="line">        executorService.execute(calback1);</span><br><span class="line">        executorService.execute(callback2);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 等待count为0，即等待两次remote call调用成功</span></span><br><span class="line">        _latch.await();</span><br><span class="line"></span><br><span class="line">        System.out.println(calback1.getResult() + callback2.getResult());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RemoteCallRunnable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> RemoteService remoteService;</span><br><span class="line">    <span class="keyword">private</span> CountDownLatch _latch;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 存储远程调用的返回结果</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> result;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">RemoteCallRunnable</span><span class="params">(RemoteService remoteService, CountDownLatch _latch)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.remoteService = remoteService;</span><br><span class="line">        <span class="keyword">this</span>._latch = _latch;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            result = remoteService.call();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// count计数减1</span></span><br><span class="line">            _latch.countDown();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getResult</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述的代码中，为了能够以多线程的方式实现对远程服务的调用，并且需要获取结果时，专门将RemoteService包装到了一个Runnable当中。 那有没有更简单的方式？答案是：Future。</p><p>下面的代码演示了如何通过Future简化上述代码：</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.github.concurrent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ExecutorService;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Executors;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Future;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> FutureSample &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">static</span> ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> main(String[] args) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        RemoteService remoteService = <span class="keyword">new</span> RemoteService();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用submit提交异步任务，并且获取返回值为future</span></span><br><span class="line">        Future&lt;Integer&gt; future1 = executorService.submit(remoteService::<span class="keyword">call</span>);</span><br><span class="line">        Future&lt;Integer&gt; future2 = executorService.submit(remoteService::<span class="keyword">call</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 调用future.get() 阻塞主线程，获取异步任务的返回结果</span></span><br><span class="line">            Integer result1 = future1.get(<span class="number">200</span>, TimeUnit.MILLISECONDS);</span><br><span class="line">            Integer result2 = future2.get(<span class="number">200</span>, TimeUnit.MILLISECONDS);</span><br><span class="line">            System.out.<span class="keyword">println</span>(<span class="string">"sum="</span> + (result1 + result2));</span><br><span class="line">            System.out.<span class="keyword">println</span>(<span class="string">"Spend Time Millis:"</span> + (System.currentTimeMillis() - start));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">if</span> (future1 != <span class="keyword">null</span>) &#123;</span><br><span class="line">                future1.cancel(<span class="keyword">true</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (future2 != <span class="keyword">null</span>) &#123;</span><br><span class="line">                future2.cancel(<span class="keyword">true</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        executorService.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Future可以在不阻塞主线程的情况下，进行异步调用，并且监视远程调用的返回结果。 当需要得到异步任务的结果时，再通过get方法获取。</p><blockquote><p>未完待续： 使用CompletableFuture对Future进行编排</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;/images/concurrency-parallel.png&quot; alt=&quot;并发与并行&quot;&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="Java" scheme="http://yunlzheng.github.io/tags/Java/"/>
    
      <category term="Concurrency" scheme="http://yunlzheng.github.io/tags/Concurrency/"/>
    
      <category term="Asynchronous" scheme="http://yunlzheng.github.io/tags/Asynchronous/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus高可用(5)：Prometheus高可用方案策略</title>
    <link href="http://yunlzheng.github.io/2018/03/17/promethues-ha-deploy/"/>
    <id>http://yunlzheng.github.io/2018/03/17/promethues-ha-deploy/</id>
    <published>2018-03-17T03:35:37.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>前面部分介绍了Prometheus的本地数据存储模型模型，本地存储给Prometheus带来了简单高效的使用体验，可以让Promthues在单节点的情况下满足大部分用户的监控需求。但是本地存储也同时限制了Prometheus的可扩展性，带来了数据持久化等一系列的问题。通过Prometheus的Remote Storage特性可以解决这一系列问题，包括Promthues的动态扩展，以及历史数据的存储。</p><p>而除了数据持久化问题以外，影响Promthues性能表现的另外一个重要因素就是数据采集任务量，以及单台Promthues能够处理的时间序列数。因此当监控规模大到Promthues单台无法有效处理的情况下，可以选择利用Promthues的联邦集群的特性，将Promthues的监控任务划分到不同的实例当中。</p><p>这一部分将重点讨论Prometheus的高可用架构，并且根据不同的使用场景介绍了一种常见的高可用方案。</p><a id="more"></a><h2><span id="基本ha服务可用性">基本HA：服务可用性</span></h2><p>由于Promthues的Pull机制的设计，为了确保Promthues服务的可用性，用户只需要部署多套Prometheus Server实例，并且采集相同的Exporter目标即可。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/promethues-ha-01.png" alt="基本HA"></p><p>基本的HA模式只能确保Promthues服务的可用性问题，但是不解决Prometheus Server之间的数据一致性问题以及持久化问题(数据丢失后无法恢复)，也无法进行动态的扩展。因此这种部署方式适合监控规模不大，Promthues Server也不会频繁发生迁移的情况，并且只需要保存短周期监控数据的场景。</p><h2><span id="基本ha-远程存储">基本HA + 远程存储</span></h2><p>在基本HA模式的基础上通过添加Remote Storage存储支持，将监控数据保存在第三方存储服务上。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prometheus-ha-remote-storage.png" alt="HA + Remote Storage"></p><p>在解决了Promthues服务可用性的基础上，同时确保了数据的持久化，当Promthues Server发生宕机或者数据丢失的情况下，可以快速的恢复。 同时Promthues Server可能很好的进行迁移。因此，该方案适用于用户监控规模不大，但是希望能够将监控数据持久化，同时能够确保Promthues Server的可迁移性的场景。</p><h2><span id="基本ha-远程存储-联邦集群">基本HA + 远程存储 + 联邦集群</span></h2><p>当单台Promthues Server无法处理大量的采集任务时，用户可以考虑基于Prometheus联邦集群的方式将监控采集任务划分到不同的Promthues实例当中即在任务级别功能分区。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prometheus-ha-rs-fedreation.png" alt="基本HA + 远程存储 + 联邦集群"></p><p>这种部署方式一般适用于两种场景：</p><p>场景一： 单数据中心 + 大量的采集任务</p><p>这种场景下Promthues的性能瓶颈主要在于大量的采集任务，因此用户需要利用Prometheus联邦集群的特性，将不同类型的采集任务划分到不同的Promthues子服务中，从而实现功能分区。例如一个Promthues Server负责采集基础设施相关的监控指标，另外一个Prometheus Server负责采集应用监控指标。再有上层Prometheus Server实现对数据的汇聚。</p><p>场景二: 多数据中心</p><p>这种模式也适合与多数据中心的情况，当Promthues Server无法直接与数据中心中的Exporter进行通讯时，在每一个数据中部署一个单独的Promthues Server负责当前数据中心的采集任务是一个不错的方式。这样可以避免用户进行大量的网络配置，只需要确保主Promthues Server实例能够与当前数据中心的Prometheus Server通讯即可。 中心Promthues Server负责实现对多数据中心数据的聚合。</p><h2><span id="按照实例进行功能分区">按照实例进行功能分区</span></h2><p>这时在考虑另外一种极端情况，即单个采集任务的Target数也变得非常巨大。这是简单通过联邦集群进行功能分区，Prometheus Server也无法有效处理时。这种情况只能考虑继续在实例级别进行功能划分。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/promethues-sharding-targets.png" alt="实例级别功能分区"></p><p>如上图所示，将统一任务的不同实例的监控数据采集任务划分到不同的Prometheus实例。通过relabel设置，我们可以确保当前Prometheus Server只收集当前采集任务的一部分实例的监控指标。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">global:</span></span><br><span class="line"><span class="attr">  external_labels:</span></span><br><span class="line"><span class="attr">    slave:</span> <span class="number">1</span>  <span class="comment"># This is the 2nd slave. This prevents clashes between slaves.</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line"><span class="attr">  - job_name:</span> <span class="string">some_job</span></span><br><span class="line">    <span class="comment"># Add usual service discovery here, such as static_configs</span></span><br><span class="line"><span class="attr">    relabel_configs:</span></span><br><span class="line"><span class="attr">    - source_labels:</span> <span class="string">[__address__]</span></span><br><span class="line"><span class="attr">      modulus:</span>       <span class="number">4</span>    <span class="comment"># 4 slaves</span></span><br><span class="line"><span class="attr">      target_label:</span>  <span class="string">__tmp_hash</span></span><br><span class="line"><span class="attr">      action:</span>        <span class="string">hashmod</span></span><br><span class="line"><span class="attr">    - source_labels:</span> <span class="string">[__tmp_hash]</span></span><br><span class="line"><span class="attr">      regex:</span>         <span class="string">^1$</span>  <span class="comment"># This is the 2nd slave</span></span><br><span class="line"><span class="attr">      action:</span>        <span class="string">keep</span></span><br></pre></td></tr></table></figure><p>并且通过当前数据中心的一个中心Prometheus Server将监控数据进行聚合到任务级别。</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">-<span class="ruby"> <span class="symbol">scrape_config:</span></span></span><br><span class="line"><span class="ruby">  - <span class="symbol">job_name:</span> slaves</span></span><br><span class="line"><span class="ruby">    <span class="symbol">honor_labels:</span> <span class="literal">true</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">metrics_path:</span> /federate</span></span><br><span class="line"><span class="ruby">    <span class="symbol">params:</span></span></span><br><span class="line"><span class="ruby">      match[]<span class="symbol">:</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'&#123;__name__=~"^slave:.*"&#125;'</span>   <span class="comment"># Request all slave-level time series</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">      - <span class="symbol">targets:</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave0:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave1:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave3:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave4:</span><span class="number">9090</span></span></span><br></pre></td></tr></table></figure><h2><span id="高可用方案选择">高可用方案选择</span></h2><p>上面的部分，根据不同的场景演示了3中不同的高可用部署方案。当然对于Promthues部署方案需要用户根据监控规模以及自身的需求进行动态调整，下表展示了Promthues和高可用有关3个选项各自解决的问题，用户可以根据自己的需求灵活选择。</p><table><thead><tr><th>选项\需求</th><th>服务可用性</th><th>数据持久化</th><th>水平扩展</th></tr></thead><tbody><tr><td>主备HA</td><td>v</td><td>x</td><td>x</td></tr><tr><td>远程存储</td><td>x</td><td>v</td><td>x</td></tr><tr><td>联邦集群</td><td>x</td><td>x</td><td>v</td></tr></tbody></table>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;前面部分介绍了Prometheus的本地数据存储模型模型，本地存储给Prometheus带来了简单高效的使用体验，可以让Promthues在单节点的情况下满足大部分用户的监控需求。但是本地存储也同时限制了Prometheus的可扩展性，带来了数据持久化等一系列的问题。通过Prometheus的Remote Storage特性可以解决这一系列问题，包括Promthues的动态扩展，以及历史数据的存储。&lt;/p&gt;
&lt;p&gt;而除了数据持久化问题以外，影响Promthues性能表现的另外一个重要因素就是数据采集任务量，以及单台Promthues能够处理的时间序列数。因此当监控规模大到Promthues单台无法有效处理的情况下，可以选择利用Promthues的联邦集群的特性，将Promthues的监控任务划分到不同的实例当中。&lt;/p&gt;
&lt;p&gt;这一部分将重点讨论Prometheus的高可用架构，并且根据不同的使用场景介绍了一种常见的高可用方案。&lt;/p&gt;
    
    </summary>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/categories/Prometheus/"/>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="高可用" scheme="http://yunlzheng.github.io/tags/%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    
  </entry>
  
  <entry>
    <title>面向微服务的持续交付架构</title>
    <link href="http://yunlzheng.github.io/2018/03/15/opensource-microservice-end-to-end-architecture/"/>
    <id>http://yunlzheng.github.io/2018/03/15/opensource-microservice-end-to-end-architecture/</id>
    <published>2018-03-15T15:05:56.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="http://oskkqdn5j.bkt.clouddn.com/mircroservices-and-devops.png" alt=""></p><p>先来一个图，剩下的后面慢慢说。</p><a id="more"></a>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;http://oskkqdn5j.bkt.clouddn.com/mircroservices-and-devops.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;先来一个图，剩下的后面慢慢说。&lt;/p&gt;
    
    </summary>
    
    
      <category term="DevOps" scheme="http://yunlzheng.github.io/tags/DevOps/"/>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="Microservice" scheme="http://yunlzheng.github.io/tags/Microservice/"/>
    
      <category term="Istio" scheme="http://yunlzheng.github.io/tags/Istio/"/>
    
      <category term="Jenkins" scheme="http://yunlzheng.github.io/tags/Jenkins/"/>
    
      <category term="Container" scheme="http://yunlzheng.github.io/tags/Container/"/>
    
      <category term="Harbor" scheme="http://yunlzheng.github.io/tags/Harbor/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus高可用(4)：Alertmanager高可用</title>
    <link href="http://yunlzheng.github.io/2018/03/12/prometheus-alertmanager-ha/"/>
    <id>http://yunlzheng.github.io/2018/03/12/prometheus-alertmanager-ha/</id>
    <published>2018-03-12T03:26:52.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>在前面的部分我们主要讨论了Prometheus Server自身的高可用问题。而接下来，重点将放在告警处理也就是Alertmanager部分。如下所示。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prom-ha-with-single-am.png" alt="Alertmanager成为单点"></p><a id="more"></a><p>为了提升Promthues的服务可用性，通常用户会部署两个或者两个以上的Promthus Server，它们具有完全相同的配置包括Job配置，以及告警配置等。当某一个Prometheus Server发生故障后可以去报Promthues持续可用。</p><p>同时基于Alertmanager的告警分组机制即使不同的Promtheus Sever分别发送相同的告警给Alertmanager，Alertmanager也可以自动将这些告警合并为一个通知向receiver发送。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/alertmanager-features.png" alt="Alertmanager特性"></p><p>但不幸的是，虽然Alertmanager能够同时处理多个相同的Promthues Server所产生的告警。但是由于单个Alertmanager的存在，当前的部署结构存在明显的单点故障风险，当Alertmanager单点失效后，告警的后续所有业务全部失效。</p><p>如下所示，最直接的方式，就是尝试部署多套Alertmanager。但是由于ALertmanager之间不存在并不了解彼此的存在，因此则会出现告警通知被不同的Alertmanager重复发送多次的问题。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prom-ha-with-double-am.png" alt=""></p><p>为了解决这一问题，如下所示。Alertmanager引入了Gossip机制。Gossip机制为多个Alertmanager之间提供了信息传递的机制。确保及时在多个Alertmanager分别接收到相同告警信息的情况下，也只有一个告警通知被发送给Receiver。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prom-ha-with-am-gossip.png" alt="Alertmanager Gossip"></p><h2><span id="gossip机制">Gossip机制</span></h2><p>要理解Gossip机制，首先需要了解Alertmanager中的一次告警通知是如何产生的，如下所示，Alertmanager通过流水线的形式处理告警通知：</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/am-notifi-pipeline.png" alt="通知流水线"></p><ol><li>在流水线的第一个阶段Silence中，Alertmanager会判断当前通知是否匹配到任何的静默规则，如果没有则进入下一个阶段，否则则中断流水线不发送通知。</li><li>在第二个阶段Wait中，Alertmanager会根据当前Alertmanager在集群中所在的顺序(index)等待index * 5s的时间。</li><li>当前Alertmanager等待阶段结束后，Dedup阶段则会判断当前Alertmanager数据库中该改进是否已经发送，如果已经发送则中断流水线，不发送告警，否则则进入下一阶段Send对外发送告警通知。</li><li>告警发送完成后该Alertmanager进入最后一个阶段Gossip，Gossip会通知其他Alertmanager实例当前告警已经发送。其他实例接收到Gossip消息后，则会在自己的数据库中保存该通知已发送的记录。</li></ol><p>因此如下所示，Gossip机制的关键在于两点：</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/am-gossip.png" alt="Gossip机制"></p><ul><li>Alertmanager实例之间需要保证Silence设置完全相同。这样可以确保被设置为静默的告警都不会对外通知。</li><li>Alertmanager通过Gossip机制同步告警通知状态，同时流水线中定义Wait阶段确保告警依次被集群中的Alertmanager处理。</li></ul><p>Alertmanager基于Gossip实现的集群机制虽然不能保证所有实例上的数据时刻保持一致，但是实现了CAP理论中的AP系统，即可用性和分区容错性。同时对于Prometheus Server而言保持了配置了简单性，Promthues Server之间不需要任何的状态同步。</p><h2><span id="搭建本地集群环境">搭建本地集群环境</span></h2><p>为了能够让Alertmanager节点之间进行通讯，需要在Alertmanager启动时设置相应的参数。其中主要的参数包括：</p><ul><li>–cluster.listen-address string: 当前实例集群服务监听地址</li><li>–cluster.peer value: 初始化时关联的其它实例的集群服务地址</li></ul><p>例如：</p><p>定义Alertmanager实例a1，其中Alertmanager的服务运行在9093端口，集群服务地址运行在8001端口。</p><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alertmanager  <span class="params">--web</span>.listen-address=<span class="string">":9093"</span> <span class="params">--cluster</span>.listen-address=<span class="string">"127.0.0.1:8001"</span> <span class="params">--config</span>.file=<span class="string">/etc/prometheus/alertmanager.yml</span>  <span class="params">--storage</span>.path=<span class="string">/data/alertmanager/</span></span><br></pre></td></tr></table></figure><p>定义Alertmanager实例a2，其中主服务运行在9094端口，集群服务运行在8002端口。为了将a1，a2组成集群。 a2启动时需要定义–cluster.peer参数并且指向a1实例的集群服务地址:8001。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alertmanager  --web.<span class="attribute">listen-address</span>=<span class="string">":9094"</span> --cluster.<span class="attribute">listen-address</span>=<span class="string">"127.0.0.1:8002"</span> --cluster.<span class="attribute">peer</span>=127.0.0.1:8001 --config.<span class="attribute">file</span>=/etc/prometheus/alertmanager.yml  --storage.<span class="attribute">path</span>=/data/alertmanager2/</span><br></pre></td></tr></table></figure><p>为了能够在本地模拟集群环境，这里使用了一个轻量级的多线程管理工具goreman。使用以下命令可以在本地安装goreman命令行工具。</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> <span class="built_in">get</span> github.<span class="keyword">com</span>/mattn/goreman</span><br></pre></td></tr></table></figure><h3><span id="创建alertmanager集群">创建Alertmanager集群</span></h3><p>创建Alertmanager配置文件/etc/prometheus/alertmanager-ha.yml, 为了验证Alertmanager的集群行为，这里在本地启动一个webhook服务用于打印Alertmanager发送的告警通知信息。</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">route</span>:</span><br><span class="line">  <span class="attribute">receiver</span>: <span class="string">'default-receiver'</span></span><br><span class="line"><span class="attribute">receivers</span>:</span><br><span class="line">  - <span class="attribute">name</span>: default-receiver</span><br><span class="line">    <span class="attribute">webhook_configs</span>:</span><br><span class="line">    - <span class="attribute">url</span>: <span class="string">'http://127.0.0.1:5001/'</span></span><br></pre></td></tr></table></figure><p>本地webhook服务可以直接从Github获取。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 获取alertmanager提供的webhook示例，如果该目录下定义了main函数，go get会自动将其编译成可执行文件</span></span><br><span class="line">go <span class="builtin-name">get</span> github.com/prometheus/alertmanager/examples/webhook</span><br><span class="line"><span class="comment"># 设置环境变量指向GOPATH的bin目录</span></span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">PATH</span>=<span class="variable">$GOPATH</span>/bin:$PATH</span><br><span class="line"><span class="comment"># 启动服务</span></span><br><span class="line">webhook</span><br></pre></td></tr></table></figure><p>创建alertmanager.procfile文件，并且定义了三个Alertmanager节点（a1，a2，a3）以及用于接收告警通知的webhook服务:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">a1: alertmanager  --web.<span class="attribute">listen-address</span>=<span class="string">":9093"</span> --cluster.<span class="attribute">listen-address</span>=<span class="string">"127.0.0.1:8001"</span> --config.<span class="attribute">file</span>=/etc/prometheus/alertmanager-ha.yml  --storage.<span class="attribute">path</span>=/data/alertmanager/ --log.<span class="attribute">level</span>=debug</span><br><span class="line">a2: alertmanager  --web.<span class="attribute">listen-address</span>=<span class="string">":9094"</span> --cluster.<span class="attribute">listen-address</span>=<span class="string">"127.0.0.1:8002"</span> --cluster.<span class="attribute">peer</span>=127.0.0.1:8001 --config.<span class="attribute">file</span>=/etc/prometheus/alertmanager-ha.yml  --storage.<span class="attribute">path</span>=/data/alertmanager2/ --log.<span class="attribute">level</span>=debug</span><br><span class="line">a3: alertmanager  --web.<span class="attribute">listen-address</span>=<span class="string">":9095"</span> --cluster.<span class="attribute">listen-address</span>=<span class="string">"127.0.0.1:8003"</span> --cluster.<span class="attribute">peer</span>=127.0.0.1:8001 --config.<span class="attribute">file</span>=/etc/prometheus/alertmanager-ha.yml  --storage.<span class="attribute">path</span>=/data/alertmanager2/ --log.<span class="attribute">level</span>=debug</span><br><span class="line"></span><br><span class="line">webhook: webhook</span><br></pre></td></tr></table></figure><blockquote><p>TODO: 补充拓扑结构</p></blockquote><p>在Procfile文件所在目录，执行goreman start命令，启动所有进程:</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ goreman -f alertmanager.procfile start</span><br><span class="line">10:27:57      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:27:57.399166371Z <span class="attribute">caller</span>=cluster.go:125 <span class="attribute">component</span>=cluster <span class="attribute">msg</span>=<span class="string">"joined cluster"</span> <span class="attribute">peers</span>=0</span><br><span class="line">10:27:57      a3 | <span class="attribute">level</span>=info <span class="attribute">ts</span>=2018-03-12T02:27:57.40004678Z <span class="attribute">caller</span>=main.go:346 <span class="attribute">msg</span>=Listening <span class="attribute">address</span>=:9095</span><br><span class="line">10:27:57      a1 | <span class="attribute">level</span>=info <span class="attribute">ts</span>=2018-03-12T02:27:57.400212246Z <span class="attribute">caller</span>=main.go:271 <span class="attribute">msg</span>=<span class="string">"Loading configuration file"</span> <span class="attribute">file</span>=/etc/prometheus/alertmanager.yml</span><br><span class="line">10:27:57      a1 | <span class="attribute">level</span>=info <span class="attribute">ts</span>=2018-03-12T02:27:57.405638714Z <span class="attribute">caller</span>=main.go:346 <span class="attribute">msg</span>=Listening <span class="attribute">address</span>=:9093</span><br></pre></td></tr></table></figure><p>启动完成后访问任意Alertmanager节点<a href="http://localhost:9093/#/status" target="_blank" rel="noopener">http://localhost:9093/#/status</a>,可以查看当前Alertmanager集群的状态。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/am-ha-status.png" alt="Alertmanager集群状态"></p><p>当集群中的Alertmanager节点不在一台主机时，通常需要使用–cluster.advertise-address参数指定当前节点所在网络地址。</p><blockquote><p>注意：由于goreman不保证进程之间的启动顺序，如果集群状态未达到预期，可以使用goreman -f alertmanager.procfile run restart <strong>service</strong>重启a2，a3服务。</p></blockquote><p>当Alertmanager集群启动完成后，可以使用send-alerts.sh脚本对集群进行简单测试，这里利用curl分别向3个Alertmanager实例发送告警信息。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">alerts1='[</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"labels"</span>: &#123;</span><br><span class="line">       <span class="string">"alertname"</span>: <span class="string">"DiskRunningFull"</span>,</span><br><span class="line">       <span class="string">"dev"</span>: <span class="string">"sda1"</span>,</span><br><span class="line">       <span class="string">"instance"</span>: <span class="string">"example1"</span></span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="string">"annotations"</span>: &#123;</span><br><span class="line">        <span class="string">"info"</span>: <span class="string">"The disk sda1 is running full"</span>,</span><br><span class="line">        <span class="string">"summary"</span>: <span class="string">"please check the instance example1"</span></span><br><span class="line">      &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"labels"</span>: &#123;</span><br><span class="line">       <span class="string">"alertname"</span>: <span class="string">"DiskRunningFull"</span>,</span><br><span class="line">       <span class="string">"dev"</span>: <span class="string">"sdb2"</span>,</span><br><span class="line">       <span class="string">"instance"</span>: <span class="string">"example2"</span></span><br><span class="line">     &#125;,</span><br><span class="line">     <span class="string">"annotations"</span>: &#123;</span><br><span class="line">        <span class="string">"info"</span>: <span class="string">"The disk sdb2 is running full"</span>,</span><br><span class="line">        <span class="string">"summary"</span>: <span class="string">"please check the instance example2"</span></span><br><span class="line">      &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"labels"</span>: &#123;</span><br><span class="line">       <span class="string">"alertname"</span>: <span class="string">"DiskRunningFull"</span>,</span><br><span class="line">       <span class="string">"dev"</span>: <span class="string">"sda1"</span>,</span><br><span class="line">       <span class="string">"instance"</span>: <span class="string">"example3"</span>,</span><br><span class="line">       <span class="string">"severity"</span>: <span class="string">"critical"</span></span><br><span class="line">     &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"labels"</span>: &#123;</span><br><span class="line">       <span class="string">"alertname"</span>: <span class="string">"DiskRunningFull"</span>,</span><br><span class="line">       <span class="string">"dev"</span>: <span class="string">"sda1"</span>,</span><br><span class="line">       <span class="string">"instance"</span>: <span class="string">"example3"</span>,</span><br><span class="line">       <span class="string">"severity"</span>: <span class="string">"warning"</span></span><br><span class="line">     &#125;</span><br><span class="line">  &#125;</span><br><span class="line">]'</span><br><span class="line"></span><br><span class="line">curl -<span class="type">XPOST</span> -<span class="string">d"<span class="subst">$alerts1</span>"</span> http:<span class="comment">//localhost:9093/api/v1/alerts</span></span><br><span class="line">curl -<span class="type">XPOST</span> -<span class="string">d"<span class="subst">$alerts1</span>"</span> http:<span class="comment">//localhost:9094/api/v1/alerts</span></span><br><span class="line">curl -<span class="type">XPOST</span> -<span class="string">d"<span class="subst">$alerts1</span>"</span> http:<span class="comment">//localhost:9095/api/v1/alerts</span></span><br></pre></td></tr></table></figure><p>运行send-alerts.sh后，查看alertmanager日志，可以看到以下输出，3个Alertmanager实例分别接收到模拟的告警信息：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">10:43:36      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.853173705Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[8320f0a][active]</span><br><span class="line">10:43:36      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.853281927Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[e1d3beb][active]</span><br><span class="line">10:43:36      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.853315837Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[831ef0a][active]</span><br><span class="line">10:43:36      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.853333024Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[74eed93][active]</span><br><span class="line">10:43:36      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.853350057Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[7666d39][active]</span><br><span class="line">10:43:36      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.853370185Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[6543bc1][active]</span><br><span class="line">10:43:36      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.871180749Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[8320f0a][active]</span><br><span class="line">10:43:36      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.871488042Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[e1d3beb][active]</span><br><span class="line">10:43:36      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.871782588Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[831ef0a][active]</span><br><span class="line">10:43:36      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.871835329Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[74eed93][active]</span><br><span class="line">10:43:36      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.871911952Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[7666d39][active]</span><br><span class="line">10:43:36      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.871955021Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[6543bc1][active]</span><br><span class="line">10:43:36      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.894923811Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[8320f0a][active]</span><br><span class="line">10:43:36      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.894999803Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[e1d3beb][active]</span><br><span class="line">10:43:36      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.895023822Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[831ef0a][active]</span><br><span class="line">10:43:36      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.895043184Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[74eed93][active]</span><br><span class="line">10:43:36      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.895063654Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[7666d39][active]</span><br><span class="line">10:43:36      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T02:43:36.895082512Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=DiskRunningFull[6543bc1][active]</span><br></pre></td></tr></table></figure><p>查看webhook日志只接收到一个告警通知：</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">| 2018/03/12 10:44:06 &#123;</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;  "</span>receiver<span class="string">": "</span>default-receiver<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;  "</span>status<span class="string">": "</span>firing<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;  "</span>alerts<span class="string">": [</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;    &#123;</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      "</span>status<span class="string">": "</span>firing<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      "</span>labels<span class="string">": &#123;</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;        "</span>alertname<span class="string">": "</span>DiskRunningFull<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;        "</span>dev<span class="string">": "</span>sda1<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;        "</span>instance<span class="string">": "</span>example3<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;        "</span>severity<span class="string">": "</span>critical<span class="string">"</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      &#125;,</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      "</span>annotations<span class="string">": &#123;&#125;,</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      "</span>startsAt<span class="string">": "</span><span class="number">2018</span>-<span class="number">03</span>-<span class="number">12</span>T10:<span class="number">43</span>:<span class="number">36.853079566</span>+<span class="number">08</span>:<span class="number">00</span><span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      "</span>endsAt<span class="string">": "</span><span class="number">0001</span>-<span class="number">01</span>-<span class="number">01</span>T00:<span class="number">00</span>:<span class="number">00</span>Z<span class="string">",</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;      "</span>generatorURL<span class="string">": ""</span></span><br><span class="line"><span class="number">10</span>:<span class="number">44</span>:<span class="number">06</span> webhook <span class="string">|  &gt;    &#125;,</span></span><br></pre></td></tr></table></figure><h3><span id="多实例prometheus与alertmanager集群">多实例Prometheus与Alertmanager集群</span></h3><p>由于Gossip机制的实现，在Promthues和Alertmanager实例之间不要使用任何的负载均衡，需要确保Promthues将告警发送到所有的Alertmanager实例中：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">alerting</span>:</span><br><span class="line">  <span class="selector-tag">alertmanagers</span>:</span><br><span class="line">  <span class="selector-tag">-</span> <span class="selector-tag">static_configs</span>:</span><br><span class="line">    <span class="selector-tag">-</span> <span class="selector-tag">targets</span>:</span><br><span class="line">      <span class="selector-tag">-</span> 127<span class="selector-class">.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span><span class="selector-pseudo">:9093</span></span><br><span class="line">      <span class="selector-tag">-</span> 127<span class="selector-class">.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span><span class="selector-pseudo">:9094</span></span><br><span class="line">      <span class="selector-tag">-</span> 127<span class="selector-class">.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span><span class="selector-pseudo">:9095</span></span><br></pre></td></tr></table></figure><p>创建Promthues集群配置文件/etc/prometheus/prometheus-ha.yml，完整内容如下：</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">global:</span><br><span class="line">  scrape_interval: 15s</span><br><span class="line">  scrape_timeout: 10s</span><br><span class="line">  evaluation_interval: 15s</span><br><span class="line">rule_files:</span><br><span class="line">  -<span class="ruby"> /etc/prometheus/rules/*.rules</span></span><br><span class="line"><span class="ruby"><span class="symbol">alerting:</span></span></span><br><span class="line"><span class="ruby">  <span class="symbol">alertmanagers:</span></span></span><br><span class="line"><span class="ruby">  - <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">    - <span class="symbol">targets:</span></span></span><br><span class="line"><span class="ruby">      - <span class="number">127.0</span>.<span class="number">0</span>.<span class="number">1</span><span class="symbol">:</span><span class="number">9093</span></span></span><br><span class="line"><span class="ruby">      - <span class="number">127.0</span>.<span class="number">0</span>.<span class="number">1</span><span class="symbol">:</span><span class="number">9094</span></span></span><br><span class="line"><span class="ruby">      - <span class="number">127.0</span>.<span class="number">0</span>.<span class="number">1</span><span class="symbol">:</span><span class="number">9095</span></span></span><br><span class="line"><span class="ruby"><span class="symbol">scrape_configs:</span></span></span><br><span class="line"><span class="ruby">- <span class="symbol">job_name:</span> prometheus</span></span><br><span class="line"><span class="ruby">  <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">  - <span class="symbol">targets:</span></span></span><br><span class="line"><span class="ruby">    - <span class="symbol">localhost:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">- <span class="symbol">job_name:</span> <span class="string">'node'</span></span></span><br><span class="line"><span class="ruby">  <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">  - <span class="symbol">targets:</span> [<span class="string">'localhost:9100'</span>]</span></span><br></pre></td></tr></table></figure><p>同时定义告警规则文件/etc/prometheus/rules/hoststats-alert.rules，如下所示：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">groups:</span></span><br><span class="line"><span class="attr">- name:</span> <span class="string">hostStatsAlert</span></span><br><span class="line"><span class="attr">  rules:</span></span><br><span class="line"><span class="attr">  - alert:</span> <span class="string">hostCpuUsageAlert</span></span><br><span class="line"><span class="attr">    expr:</span> <span class="string">sum(avg</span> <span class="string">without</span> <span class="string">(cpu)(irate(node_cpu&#123;mode!='idle'&#125;[5m])))</span> <span class="string">by</span> <span class="string">(instance)</span> <span class="string">*</span> <span class="number">100</span> <span class="string">&gt; 50</span></span><br><span class="line"><span class="string"></span><span class="attr">    for:</span> <span class="number">1</span><span class="string">m</span></span><br><span class="line"><span class="attr">    labels:</span></span><br><span class="line"><span class="attr">      severity:</span> <span class="string">page</span></span><br><span class="line"><span class="attr">    annotations:</span></span><br><span class="line"><span class="attr">      summary:</span> <span class="string">"Instance <span class="template-variable">&#123;&#123; $labels.instance &#125;&#125;</span> CPU usgae high"</span></span><br><span class="line"><span class="attr">      description:</span> <span class="string">"<span class="template-variable">&#123;&#123; $labels.instance &#125;&#125;</span> CPU usage above 50% (current value: <span class="template-variable">&#123;&#123; $value &#125;&#125;</span>)"</span></span><br><span class="line"><span class="attr">  - alert:</span> <span class="string">hostMemUsageAlert</span></span><br><span class="line"><span class="attr">    expr:</span> <span class="string">(node_memory_MemTotal</span> <span class="bullet">-</span> <span class="string">node_memory_MemAvailable)/node_memory_MemTotal</span> <span class="string">*</span> <span class="number">100</span> <span class="string">&gt; 85</span></span><br><span class="line"><span class="string"></span><span class="attr">    for:</span> <span class="number">1</span><span class="string">m</span></span><br><span class="line"><span class="attr">    labels:</span></span><br><span class="line"><span class="attr">      severity:</span> <span class="string">page</span></span><br><span class="line"><span class="attr">    annotations:</span></span><br><span class="line"><span class="attr">      summary:</span> <span class="string">"Instance <span class="template-variable">&#123;&#123; $labels.instance &#125;&#125;</span> MEM usgae high"</span></span><br><span class="line"><span class="attr">      description:</span> <span class="string">"<span class="template-variable">&#123;&#123; $labels.instance &#125;&#125;</span> MEM usage above 85% (current value: <span class="template-variable">&#123;&#123; $value &#125;&#125;</span>)"</span></span><br></pre></td></tr></table></figure><p>创建Procfile_promethues文件，创建两个Promthues节点。分别监听9090和9091端口</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">p1: prometheus --config.<span class="attribute">file</span>=/etc/prometheus/prometheus-ha.yml --storage.tsdb.<span class="attribute">path</span>=/data/prometheus/ --web.<span class="attribute">listen-address</span>=<span class="string">"127.0.0.1:9090"</span></span><br><span class="line">p2: prometheus --config.<span class="attribute">file</span>=/etc/prometheus/prometheus-ha.yml --storage.tsdb.<span class="attribute">path</span>=/data/prometheus2/ --web.<span class="attribute">listen-address</span>=<span class="string">"127.0.0.1:9091"</span></span><br><span class="line"></span><br><span class="line">node_exporter: node_exporter -web.<span class="attribute">listen-address</span>=<span class="string">"0.0.0.0:9100"</span></span><br></pre></td></tr></table></figure><blockquote><p>TODO: 补充拓扑结构</p></blockquote><p>使用goreman启动多节点Promthues。</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">goreman -f prometheus<span class="selector-class">.procfile</span> start</span><br></pre></td></tr></table></figure><p>Promthues启动完成后，手动拉高系统CPU使用率：</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat <span class="regexp">/dev/</span>zero&gt;<span class="regexp">/dev/</span><span class="keyword">null</span></span><br></pre></td></tr></table></figure><blockquote><p>注意，对于多核主机，如果CPU达不到预期，运行多个命令</p></blockquote><p>当CPU利用率达到告警规则触发条件，两个Promtheus实例告警分别被触发。查看Alertmanager输出日志：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">11:14:41      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:41.945493505Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:41      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:41.945759947Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:41      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:41.945534548Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:41      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:41.945590881Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:41      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:41.945687812Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:41      a2 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:41.945778452Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:56      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:56.944938301Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:56      a3 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:56.944997091Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:56      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:56.945079005Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br><span class="line">11:14:56      a1 | <span class="attribute">level</span>=debug <span class="attribute">ts</span>=2018-03-12T03:14:56.94513205Z <span class="attribute">caller</span>=dispatch.go:188 <span class="attribute">component</span>=dispatcher <span class="attribute">msg</span>=<span class="string">"Received alert"</span> <span class="attribute">alert</span>=hostCpuUsageAlert[7d698ac][active]</span><br></pre></td></tr></table></figure><p>3个Alertmanager实例分别接收到来自不同Promtheus实例的告警信息。而Webhook服务只接收到来自Alertmanager集群的一条告警通知：</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">| 2018/03/12 11:15:11 &#123;</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;  "</span>receiver<span class="string">": "</span>default-receiver<span class="string">",</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;  "</span>status<span class="string">": "</span>firing<span class="string">",</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;  "</span>alerts<span class="string">": [</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;    &#123;</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;      "</span>status<span class="string">": "</span>firing<span class="string">",</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;      "</span>labels<span class="string">": &#123;</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;        "</span>alertname<span class="string">": "</span>hostCpuUsageAlert<span class="string">",</span></span><br><span class="line"><span class="number">11</span>:<span class="number">15</span>:<span class="number">11</span> webhook <span class="string">|  &gt;        "</span>instance<span class="string">": "</span>localhost:<span class="number">9100</span><span class="string">",</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在前面的部分我们主要讨论了Prometheus Server自身的高可用问题。而接下来，重点将放在告警处理也就是Alertmanager部分。如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://p2n2em8ut.bkt.clouddn.com/prom-ha-with-single-am.png&quot; alt=&quot;Alertmanager成为单点&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/categories/Prometheus/"/>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="Alertmanager" scheme="http://yunlzheng.github.io/tags/Alertmanager/"/>
    
      <category term="高可用" scheme="http://yunlzheng.github.io/tags/%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus高可用(3)：联邦集群</title>
    <link href="http://yunlzheng.github.io/2018/03/08/prometheus-fedreation/"/>
    <id>http://yunlzheng.github.io/2018/03/08/prometheus-fedreation/</id>
    <published>2018-03-08T04:55:41.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>单个Prometheus Server可以轻松的处理数以百万的时间序列。当然根据规模的不同的变化，Prometheus同样可以轻松的进行扩展。这部分将会介绍利用Prometheus的联邦集群特性，对Prometheus进行扩展。</p><a id="more"></a><h2><span id="使用联邦集群">使用联邦集群</span></h2><p>Prometheus支持使用联邦集群的方式，对Prometheus进行扩展。对于大部分监控规模而言，我们只需要在每一个数据中心(例如：EC2可用区，Kubernetes集群)安装一个Prometheus Server实例，就可以在各个数据中心处理上千规模的集群。同时将Prometheus Server部署到不同的数据中心可以避免网络配置的复杂性。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prometheus-federation.png" alt="联邦集群"></p><p>如上图所示，在每个数据中心部署单独的Prometheus Server用于采集当前数据中心监控数据。并由一个中心的Prometheus Server负责聚合多个数据中心的监控数据。</p><p>每一个Prometheus Server实例包含一个/federate接口，用于获取一组指定的时间序列的监控数据。因此在中心Prometheus Server中只需要配置一个采集任务用于从其他Prometheus Server中获取监控数据。</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">scrape_configs:</span><br><span class="line">  -<span class="ruby"> <span class="symbol">job_name:</span> <span class="string">'federate'</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">scrape_interval:</span> <span class="number">15</span>s</span></span><br><span class="line"><span class="ruby">    <span class="symbol">honor_labels:</span> <span class="literal">true</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">metrics_path:</span> <span class="string">'/federate'</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">params:</span></span></span><br><span class="line"><span class="ruby">      <span class="string">'match[]'</span><span class="symbol">:</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'&#123;job="prometheus"&#125;'</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'&#123;__name__=~"job:.*"&#125;'</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'&#123;__name__=~"node.*"&#125;'</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">      - <span class="symbol">targets:</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'192.168.77.11:9090'</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'192.168.77.12:9090'</span></span></span><br></pre></td></tr></table></figure><p>通过params可以用于控制Prometheus Server向Target实例请求监控数据的URL当中添加请求参数。例如：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">"<span class="link">http://192.168.77.11:9090/federate?match</span>[<span class="string"></span>]=&#123;job%3D"prometheus"&#125;&amp;match[]=&#123;_<span class="emphasis">_name_</span><span class="emphasis">_%3D~"job%3A.*"&#125;&amp;match[]=&#123;_</span><span class="emphasis">_name_</span><span class="emphasis">_%3D~"node.*"&#125;"</span></span><br></pre></td></tr></table></figure><p>通过URL中的match[]参数指定我们可以指定需要获取的时间序列。match[]参数必须是一个瞬时向量选择器，例如up或者{job=”api-server”}。配置多个match[]参数，用于获取多组时间序列的监控数据。</p><p>horbor_labels配置true可以确保当采集到的监控指标冲突时，能够自动忽略冲突的监控数据。如果为false时，prometheus会自动将冲突的标签替换为”exported_<original-label>“的形式。</original-label></p><h2><span id="功能分区">功能分区</span></h2><p>而当你的监控大道单个Prometheus Server无法处理的情况下，我们可以在各个数据中心中部署多个Prometheus Server实例。每一个Prometheus Server实例只负责采集当前数据中心中的一部分任务(Job)，例如可以将应用监控和主机监控分离到不同的Prometheus实例当中。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prometheus-sharding.png" alt="功能分区"></p><p>假如监控采集任务的规模继续增大，通过功能分区的方式可以进一步细化采集任务。对于中心Prometheus Server只需要从这些实例中聚合数据即可。</p><p>功能分区，即通过联邦集群的特性在任务级别对Prometheus采集任务进行划分，以支持规模的扩展。</p><h2><span id="水平扩展">水平扩展</span></h2><p>另外一种极端的情况，假如当单个采集任务的量也变得非常的大，这时候单纯通过功能分区Prometheus Server也无法有效处理。在这种情况下，我们只能考虑在任务(Job)的实例级别进行水平扩展。将采集任务的目标实例划分到不同的Prometheus Server当中。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/prometheus-horizontal.png" alt="水平扩展"></p><p>如上图所示，将统一任务的不同实例的监控数据采集任务划分到不同的Prometheus实例。通过relabel设置，我们可以确保当前Prometheus Server只收集当前采集任务的一部分实例的监控指标。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">global:</span></span><br><span class="line"><span class="attr">  external_labels:</span></span><br><span class="line"><span class="attr">    slave:</span> <span class="number">1</span>  <span class="comment"># This is the 2nd slave. This prevents clashes between slaves.</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line"><span class="attr">  - job_name:</span> <span class="string">some_job</span></span><br><span class="line">    <span class="comment"># Add usual service discovery here, such as static_configs</span></span><br><span class="line"><span class="attr">    relabel_configs:</span></span><br><span class="line"><span class="attr">    - source_labels:</span> <span class="string">[__address__]</span></span><br><span class="line"><span class="attr">      modulus:</span>       <span class="number">4</span>    <span class="comment"># 4 slaves</span></span><br><span class="line"><span class="attr">      target_label:</span>  <span class="string">__tmp_hash</span></span><br><span class="line"><span class="attr">      action:</span>        <span class="string">hashmod</span></span><br><span class="line"><span class="attr">    - source_labels:</span> <span class="string">[__tmp_hash]</span></span><br><span class="line"><span class="attr">      regex:</span>         <span class="string">^1$</span>  <span class="comment"># This is the 2nd slave</span></span><br><span class="line"><span class="attr">      action:</span>        <span class="string">keep</span></span><br></pre></td></tr></table></figure><p>并且通过当前数据中心的一个中心Prometheus Server将监控数据进行聚合到任务级别。</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">-<span class="ruby"> <span class="symbol">scrape_config:</span></span></span><br><span class="line"><span class="ruby">  - <span class="symbol">job_name:</span> slaves</span></span><br><span class="line"><span class="ruby">    <span class="symbol">honor_labels:</span> <span class="literal">true</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">metrics_path:</span> /federate</span></span><br><span class="line"><span class="ruby">    <span class="symbol">params:</span></span></span><br><span class="line"><span class="ruby">      match[]<span class="symbol">:</span></span></span><br><span class="line"><span class="ruby">        - <span class="string">'&#123;__name__=~"^slave:.*"&#125;'</span>   <span class="comment"># Request all slave-level time series</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">static_configs:</span></span></span><br><span class="line"><span class="ruby">      - <span class="symbol">targets:</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave0:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave1:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave3:</span><span class="number">9090</span></span></span><br><span class="line"><span class="ruby">        - <span class="symbol">slave4:</span><span class="number">9090</span></span></span><br></pre></td></tr></table></figure><p>水平扩展，即通过联邦集群的特性在任务的实例级别对Prometheus采集任务进行划分，以支持规模的扩展。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;单个Prometheus Server可以轻松的处理数以百万的时间序列。当然根据规模的不同的变化，Prometheus同样可以轻松的进行扩展。这部分将会介绍利用Prometheus的联邦集群特性，对Prometheus进行扩展。&lt;/p&gt;
    
    </summary>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/categories/Prometheus/"/>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="高可用" scheme="http://yunlzheng.github.io/tags/%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus高可用(2)：理解远端存储</title>
    <link href="http://yunlzheng.github.io/2018/03/07/promethues-remote-storage/"/>
    <id>http://yunlzheng.github.io/2018/03/07/promethues-remote-storage/</id>
    <published>2018-03-07T05:49:31.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>Prometheus的本地存储设计可以减少其自身运维和管理的复杂度，同时能够满足大部分用户监控规模的需求。但是本地存储也意味着Prometheus无法持久化数据，无法存储大量历史数据，同时也无法灵活扩展。</p><p>为了保持Prometheus的简单性，Prometheus并没有尝试在自身中解决以上问题，而是通过定义两个标准接口(remote_write/remote_read)，让用户可以基于这两个接口对接任意第三方的存储服务，这种方式在Promthues中成为Remote Storage。</p><a id="more"></a><h2><span id="remote-write">Remote Write</span></h2><p>用户可以在Promtheus配置文件中指定Remote Write(远程写)的URL地址，一旦设置了该配置项，Prometheus将样本数据通过HTTP的形式发送给适配器(Adaptor)。而用户则可以在适配器中对接外部任意的服务。外部服务可以是真正的存储系统，公有云的存储服务，也可以是消息队列等任意形式。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/remote-write-path-2.png" alt="Remote Write"></p><h2><span id="remote-read">Remote Read</span></h2><p>如下图所示，Promthues的Remote Read(远程读)也通过了一个适配器实现。在远程读的流程当中，当用户发起查询请求后，Promthues将向remote_read中配置的URL发起查询请求(matchers,ranges)，Adaptor根据请求条件从第三方存储服务中获取响应的数据。同时将数据转换为Promthues的原始样本数据返回给Prometheus Server。</p><p>当获取到样本数据后，Promthues在本地使用PromQL对样本数据进行二次处理。</p><blockquote><p>注意：即使使用了远程读，Prometheus中对于规则文件的处理，以及Metadata API的处理都只在本地完成。</p></blockquote><p><img src="http://p2n2em8ut.bkt.clouddn.com/remote_read_path-2.png" alt="Remote Read"></p><h3><span id="配置文件">配置文件</span></h3><p>用户需要使用远程读写功能时，主要通过在Prometheus配置文件中添加remote_write和remote_read配置，其中url用于指定远程读/写的HTTP服务地址。如果该URL启动了认证则可以通过basic_auth进行安全认证配置。对于https的支持需要设定tls_concig。proxy_url主要用于Prometheus无法直接访问适配器服务的情况下。</p><p>remote_write和remote_write具体配置如下所示：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">remote_write:</span><br><span class="line">    url: &lt;string&gt;</span><br><span class="line">    [ remote_timeout: &lt;duration&gt; |<span class="built_in"> default </span>= 30s ]</span><br><span class="line">    write_relabel_configs:</span><br><span class="line">    [ - &lt;relabel_config&gt; <span class="built_in">..</span>. ]</span><br><span class="line">    basic_auth:</span><br><span class="line">    [ username: &lt;string&gt; ]</span><br><span class="line">    [ password: &lt;string&gt; ]</span><br><span class="line">    [ bearer_token: &lt;string&gt; ]</span><br><span class="line">    [ bearer_token_file: /path/<span class="keyword">to</span>/bearer/token/file ]</span><br><span class="line">    tls_config:</span><br><span class="line">    [ &lt;tls_config&gt; ]</span><br><span class="line">    [ proxy_url: &lt;string&gt; ]</span><br><span class="line"></span><br><span class="line">remote_read:</span><br><span class="line">    url: &lt;string&gt;</span><br><span class="line">    required_matchers:</span><br><span class="line">    [ &lt;labelname&gt;: &lt;labelvalue&gt; <span class="built_in">..</span>. ]</span><br><span class="line">    [ remote_timeout: &lt;duration&gt; |<span class="built_in"> default </span>= 30s ]</span><br><span class="line">    [ read_recent: &lt;boolean&gt; |<span class="built_in"> default </span>= <span class="literal">false</span> ]</span><br><span class="line">    basic_auth:</span><br><span class="line">    [ username: &lt;string&gt; ]</span><br><span class="line">    [ password: &lt;string&gt; ]</span><br><span class="line">    [ bearer_token: &lt;string&gt; ]</span><br><span class="line">    [ bearer_token_file: /path/<span class="keyword">to</span>/bearer/token/file ]</span><br><span class="line">    [ &lt;tls_config&gt; ]</span><br><span class="line">    [ proxy_url: &lt;string&gt; ]</span><br></pre></td></tr></table></figure><h2><span id="自定义remote-stoarge-adaptor">自定义Remote Stoarge Adaptor</span></h2><p>实现自定义Remote Storage需要用户分别创建用于支持remote_read和remote_write的HTTP服务。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/remote-storage-paths.png" alt="Remote Storage"></p><p>当前Prometheus中Remote Storage相关的协议主要通过以下proto文件进行定义：</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">syntax = <span class="string">"proto3"</span>;</span><br><span class="line"><span class="keyword">package</span> prometheus;</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span> go_package = <span class="string">"prompb"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"types.proto"</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">WriteRequest</span> </span>&#123;</span><br><span class="line">  <span class="keyword">repeated</span> prometheus.TimeSeries timeseries = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">ReadRequest</span> </span>&#123;</span><br><span class="line">  <span class="keyword">repeated</span> Query queries = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">ReadResponse</span> </span>&#123;</span><br><span class="line">  <span class="comment">// In same order as the request's queries.</span></span><br><span class="line">  <span class="keyword">repeated</span> QueryResult results = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">Query</span> </span>&#123;</span><br><span class="line">  <span class="built_in">int64</span> start_timestamp_ms = <span class="number">1</span>;</span><br><span class="line">  <span class="built_in">int64</span> end_timestamp_ms = <span class="number">2</span>;</span><br><span class="line">  <span class="keyword">repeated</span> prometheus.LabelMatcher matchers = <span class="number">3</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">QueryResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// Samples within a time series must be ordered by time.</span></span><br><span class="line">  <span class="keyword">repeated</span> prometheus.TimeSeries timeseries = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下代码展示了一个简单的remote_write服务，创建用于接收remote_write的HTTP服务，将请求内容转换成WriteRequest后，用户就可以按照自己的需求进行后续的逻辑处理。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"io/ioutil"</span></span><br><span class="line"><span class="string">"net/http"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"github.com/gogo/protobuf/proto"</span></span><br><span class="line"><span class="string">"github.com/golang/snappy"</span></span><br><span class="line"><span class="string">"github.com/prometheus/common/model"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"github.com/prometheus/prometheus/prompb"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">http.HandleFunc(<span class="string">"/receive"</span>, <span class="function"><span class="keyword">func</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">compressed, err := ioutil.ReadAll(r.Body)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, err.Error(), http.StatusInternalServerError)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">reqBuf, err := snappy.Decode(<span class="literal">nil</span>, compressed)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, err.Error(), http.StatusBadRequest)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> req prompb.WriteRequest</span><br><span class="line"><span class="keyword">if</span> err := proto.Unmarshal(reqBuf, &amp;req); err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, err.Error(), http.StatusBadRequest)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, ts := <span class="keyword">range</span> req.Timeseries &#123;</span><br><span class="line">m := <span class="built_in">make</span>(model.Metric, <span class="built_in">len</span>(ts.Labels))</span><br><span class="line"><span class="keyword">for</span> _, l := <span class="keyword">range</span> ts.Labels &#123;</span><br><span class="line">m[model.LabelName(l.Name)] = model.LabelValue(l.Value)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(m)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, s := <span class="keyword">range</span> ts.Samples &#123;</span><br><span class="line">fmt.Printf(<span class="string">"  %f %d\n"</span>, s.Value, s.Timestamp)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">http.ListenAndServe(<span class="string">":1234"</span>, <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2><span id="使用influxdb作为remote-stoarge">使用Influxdb作为Remote Stoarge</span></h2><p>目前Prometheus社区也提供了部分对于第三方数据库的Remote Storage支持：</p><table><thead><tr><th>存储服务</th><th>支持模式</th></tr></thead><tbody><tr><td>AppOptics</td><td>write</td></tr><tr><td>Chronix</td><td>write</td></tr><tr><td>Cortex:</td><td>read/write</td></tr><tr><td>CrateDB</td><td>read/write</td></tr><tr><td>Gnocchi</td><td>write</td></tr><tr><td>Graphite</td><td>write</td></tr><tr><td>InfluxDB</td><td>read/write</td></tr><tr><td>OpenTSDB</td><td>write</td></tr><tr><td>PostgreSQL/TimescaleDB:</td><td>read/write</td></tr><tr><td>SignalFx</td><td>write</td></tr></tbody></table><p>这里我们演示将如何使用Influxdb作为Prometheus的Remote Storage，从而确保当Prometheus发生宕机或者重启之后能够从Influxdb中恢复和获取历史数据。</p><p>这里使用docker-compose定义并启动Influxdb数据库服务，docker-compose.yml定义如下：</p><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">version: '2'</span><br><span class="line">services:</span><br><span class="line">  influxdb:</span><br><span class="line">    image: influxdb:1.3.5</span><br><span class="line">    command: -config /etc/influxdb/influxdb.conf</span><br><span class="line">    ports:</span><br><span class="line">      -<span class="ruby"> <span class="string">"8086:8086"</span></span></span><br><span class="line"><span class="ruby">    <span class="symbol">environment:</span></span></span><br><span class="line"><span class="ruby">      - INFLUXDB_DB=prometheus</span></span><br><span class="line"><span class="ruby">      - INFLUXDB_ADMIN_ENABLED=<span class="literal">true</span></span></span><br><span class="line"><span class="ruby">      - INFLUXDB_ADMIN_USER=admin</span></span><br><span class="line"><span class="ruby">      - INFLUXDB_ADMIN_PASSWORD=admin</span></span><br><span class="line"><span class="ruby">      - INFLUXDB_USER=prom</span></span><br><span class="line"><span class="ruby">      - INFLUXDB_USER_PASSWORD=prom</span></span><br></pre></td></tr></table></figure><p>启动influxdb服务</p><figure class="highlight x86asm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose <span class="meta">up</span> -d</span><br><span class="line">$ docker ps</span><br><span class="line">CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES</span><br><span class="line">795d0ead87a1        influxdb:<span class="number">1.3</span><span class="meta">.5</span>      <span class="string">"/entrypoint.sh -c..."</span>   <span class="number">3</span> hours ago         <span class="meta">Up</span> <span class="number">3</span> hours          <span class="number">0.0</span><span class="meta">.0</span><span class="meta">.0</span>:<span class="number">8086</span>-&gt;<span class="number">8086</span>/tcp   localhost_influxdb_1</span><br></pre></td></tr></table></figure><p>获取并启动Prometheus提供的Remote Storage Adapter：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go get github.com<span class="regexp">/prometheus/</span>prometheus<span class="regexp">/documentation/</span>examples<span class="regexp">/remote_storage/</span>remote_storage_adapter</span><br></pre></td></tr></table></figure><p>获取remote_storage_adapter源码后，go会自动把相关的源码编译成可执行文件，并且保存在$GOPATH/bin/目录下。</p><p>启动remote_storage_adapter并且设置Influxdb相关的认证信息：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">INFLUXDB_PW</span>=prom <span class="variable">$GOPATH</span>/bin/remote_storage_adapter <span class="attribute">-influxdb-url</span>=http://localhost:8086 -influxdb.<span class="attribute">username</span>=prom -influxdb.<span class="attribute">database</span>=prometheus -influxdb.<span class="attribute">retention-policy</span>=autogen</span><br></pre></td></tr></table></figure><p>修改prometheus.yml添加Remote Storage相关的配置内容：</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">remote_write</span>:</span><br><span class="line">  - <span class="attribute">url</span>: <span class="string">"http://localhost:9201/write"</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">remote_read</span>:</span><br><span class="line">  - <span class="attribute">url</span>: <span class="string">"http://localhost:9201/read"</span></span><br></pre></td></tr></table></figure><p>重新启动Prometheus能够获取数据后，登录到influxdb容器，并验证数据写入。如下所示，当数据能够正常写入Influxdb后可以看到Promtheus相关的指标。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">docker</span> <span class="string">exec</span> <span class="bullet">-it</span> <span class="number">795</span><span class="string">d0ead87a1</span> <span class="string">influx</span></span><br><span class="line"><span class="string">Connected</span> <span class="string">to</span> <span class="attr">http://localhost:8086</span> <span class="string">version</span> <span class="number">1.3</span><span class="number">.5</span></span><br><span class="line"><span class="string">InfluxDB</span> <span class="string">shell</span> <span class="attr">version:</span> <span class="number">1.3</span><span class="number">.5</span></span><br><span class="line"><span class="string">&gt; auth</span></span><br><span class="line"><span class="string"></span><span class="attr">username:</span> <span class="string">prom</span></span><br><span class="line"><span class="attr">password:</span></span><br><span class="line"></span><br><span class="line"><span class="string">&gt; use prometheus</span></span><br><span class="line"><span class="string">&gt; SHOW MEASUREMENTS</span></span><br><span class="line"><span class="string"></span><span class="attr">name:</span> <span class="string">measurements</span></span><br><span class="line"><span class="string">name</span></span><br><span class="line"><span class="bullet">-</span><span class="meta">---</span></span><br><span class="line"><span class="string">go_gc_duration_seconds</span></span><br><span class="line"><span class="string">go_gc_duration_seconds_count</span></span><br><span class="line"><span class="string">go_gc_duration_seconds_sum</span></span><br><span class="line"><span class="string">go_goroutines</span></span><br><span class="line"><span class="string">go_info</span></span><br><span class="line"><span class="string">go_memstats_alloc_bytes</span></span><br><span class="line"><span class="string">go_memstats_alloc_bytes_total</span></span><br><span class="line"><span class="string">go_memstats_buck_hash_sys_bytes</span></span><br><span class="line"><span class="string">go_memstats_frees_total</span></span><br><span class="line"><span class="string">go_memstats_gc_cpu_fraction</span></span><br><span class="line"><span class="string">go_memstats_gc_sys_bytes</span></span><br><span class="line"><span class="string">go_memstats_heap_alloc_bytes</span></span><br><span class="line"><span class="string">go_memstats_heap_idle_bytes</span></span><br></pre></td></tr></table></figure><p>当数据写入成功后，停止Prometheus服务。同时删除Prometheus的data目录，模拟Promthues数据丢失的情况后重启Prometheus。打开Prometheus UI如果配置正常，Prometheus可以正常查询到本地存储以删除的历史数据记录。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/promethues-remote-storage.png" alt="从Remote Storage获取历史数据"></p><h2><span id="接下来">接下来</span></h2><p>通过Remote Storage特性可以将Promthues中的监控样本数据存储在第三方的存储服务中，从而解决了Promthues的数据持久化问题。同时由于解除了本地存储的限制，Promthues自身也可以进行弹性的扩展，在诸如Kubernetes这样的环境下可以进行动态的调度。</p><p>在解决了数据持久化和弹性的问题之后，接下来笔者将介绍Promthues的另外一个特性联邦集群，使用联邦集群可以进行步实现Promthues的横向扩展以及功能分区。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Prometheus的本地存储设计可以减少其自身运维和管理的复杂度，同时能够满足大部分用户监控规模的需求。但是本地存储也意味着Prometheus无法持久化数据，无法存储大量历史数据，同时也无法灵活扩展。&lt;/p&gt;
&lt;p&gt;为了保持Prometheus的简单性，Prometheus并没有尝试在自身中解决以上问题，而是通过定义两个标准接口(remote_write/remote_read)，让用户可以基于这两个接口对接任意第三方的存储服务，这种方式在Promthues中成为Remote Storage。&lt;/p&gt;
    
    </summary>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/categories/Prometheus/"/>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="高可用" scheme="http://yunlzheng.github.io/tags/%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    
      <category term="InfluxDB" scheme="http://yunlzheng.github.io/tags/InfluxDB/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus高可用(1)：理解本地存储</title>
    <link href="http://yunlzheng.github.io/2018/03/06/promethus-local-storage/"/>
    <id>http://yunlzheng.github.io/2018/03/06/promethus-local-storage/</id>
    <published>2018-03-06T00:52:03.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>在构建Prometheus高可用方案之前，我们先来了解一下Prometheus的本地存储相关的内容。</p><a id="more"></a><h2><span id="本地存储">本地存储</span></h2><p>Prometheus 2.x 采用自定义的存储格式将样本数据保存在本地磁盘当中。如下所示，按照两个小时为一个时间窗口，将两小时内产生的数据存储在一个块(Block)中，每一个块中包含该时间窗口内的所有样本数据(chunks)，元数据文件(meta.json)以及索引文件(index)。</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">t0</span>            <span class="built_in">t1</span>             <span class="built_in">t2</span>             <span class="built_in">t3</span>             now</span><br><span class="line"> ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐</span><br><span class="line"> │           │  │           │  │           │  │           │                 ┌────────────┐</span><br><span class="line"> │           │  │           │  │           │  │  mutable  │ &lt;─── write ──── ┤ Prometheus │</span><br><span class="line"> │           │  │           │  │           │  │           │                 └────────────┘</span><br><span class="line"> └───────────┘  └───────────┘  └───────────┘  └───────────┘                        ^</span><br><span class="line">       └──────────────┴───────┬──────┴──────────────┘                              │</span><br><span class="line">                              │                                                  query</span><br><span class="line">                              │                                                    │</span><br><span class="line">                            merge ─────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>而在当前时间窗口内正在收集的样本数据，Prometheus则会直接将数据保存在内容当中。为了确保此期间如果Prometheus发生崩溃或者重启时能够恢复数据，Prometheus启动时会以写入日志(WAL)的方式来实现重播，从而恢复数据。此期间如果通过API删除时间序列，删除记录也会保存在单独的逻辑文件当中(tombstone)。</p><p>在文件系统中这些块保存在单独的目录当中，Prometheus保存块数据的目录结构如下所示：</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">./data </span><br><span class="line">   <span class="string">|- 01BKGV7JBM69T2G1BGBGM6KB12</span></span><br><span class="line">      <span class="string">|- meta.json</span></span><br><span class="line">      <span class="string">|- wal</span></span><br><span class="line">        <span class="string">|- 000002</span></span><br><span class="line">        <span class="string">|- 000001</span></span><br><span class="line">   <span class="string">|- 01BKGTZQ1SYQJTR4PB43C8PD98</span></span><br><span class="line">      <span class="string">|- meta.json</span></span><br><span class="line">      <span class="string">|- index</span></span><br><span class="line">      <span class="string">|- chunks</span></span><br><span class="line">        <span class="string">|- 000001</span></span><br><span class="line">      <span class="string">|- tombstones</span></span><br><span class="line">   <span class="string">|- 01BKGTZQ1HHWHV8FBJXW1Y3W0K</span></span><br><span class="line">      <span class="string">|- meta.json</span></span><br><span class="line">      <span class="string">|- wal</span></span><br><span class="line">        <span class="string">|-000001</span></span><br></pre></td></tr></table></figure><p>通过时间窗口的形式保存所有的样本数据，可以明显提高Prometheus的查询效率，当查询一段时间范围内的所有样本数据时，只需要简单的从落在该范围内的块中查询数据即可。</p><p>同时该存储方式可以简化历史数据的删除逻辑。只要一个块的时间范围落在了配置的保留范围之外，直接丢弃该块即可。</p><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">                     |</span><br><span class="line">┌────────────┐  ┌────┼─────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐</span><br><span class="line">│ <span class="number">1</span>          │  │ <span class="number">2</span>  |     │  │ <span class="number">3</span>         │  │ <span class="number">4</span>         │  │ <span class="number">5</span>         │   . . .</span><br><span class="line">└────────────┘  └────┼─────┘  └───────────┘  └───────────┘  └───────────┘</span><br><span class="line">                     |</span><br><span class="line">                     |</span><br><span class="line">            retention boundary</span><br></pre></td></tr></table></figure><h2><span id="本地存储配置">本地存储配置</span></h2><p>用户可以通过命令行启动参数的方式修改本地存储的配置。</p><table><thead><tr><th>启动参数</th><th>默认值</th><th>含义</th></tr></thead><tbody><tr><td>–storage.tsdb.path</td><td>data/</td><td>Base path for metrics storage</td></tr><tr><td>–storage.tsdb.retention</td><td>15d</td><td>How long to retain samples in the storage</td></tr><tr><td>–storage.tsdb.min-block-duration</td><td>2h</td><td>The timestamp range of head blocks after which they get persisted</td></tr><tr><td>–storage.tsdb.max-block-duration</td><td>36h</td><td>The maximum timestamp range of compacted blocks,It’s the minimum duration of any persisted block.</td></tr><tr><td>–storage.tsdb.no-lockfile</td><td>false</td><td>Do not create lockfile in data directory</td></tr></tbody></table><p>在一般情况下，Prometheus中存储的每一个样本大概占用1-2字节大小。如果需要对Prometheus Server的本地磁盘空间做容量规划时，可以通过以下公式计算：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">needed_disk_space</span> = retention_time_seconds * ingested_samples_per_second * bytes_per_sample</span><br></pre></td></tr></table></figure><p>保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下，如果想减少本地磁盘的容量需求，只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。因此有两种手段，一是减少时间序列的数量，二是增加采集样本的时间间隔。考虑到Prometheus会对时间序列进行压缩，因此减少时间序列的数量效果更明显。</p><h3><span id="从失败中恢复">从失败中恢复</span></h3><p>如果本地存储由于某些原因出现了错误，最直接的方式就是停止Prometheus并且删除data目录中的所有记录。当然也可以尝试删除那些发生错误的块目录，不过这就意味着用户会丢失该块中保存的大概两个小时的监控记录。</p><h2><span id="接下来">接下来</span></h2><p>Promthues高效的本地存储模型，可以让单台Prometheus能够高效的处理大量的数据。 但是也导致Promthues数据持久化的问题，无法保存长时间的数据。同时也导致Promthues自身无法进行弹性的扩展，下一部分介绍Promthues的持久化存储方案Remote Storae。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在构建Prometheus高可用方案之前，我们先来了解一下Prometheus的本地存储相关的内容。&lt;/p&gt;
    
    </summary>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/categories/Prometheus/"/>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="高可用" scheme="http://yunlzheng.github.io/tags/%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    
  </entry>
  
  <entry>
    <title>使用Webhook扩展Alertmanager(钉钉版)</title>
    <link href="http://yunlzheng.github.io/2018/03/01/alertmanager-webhook-dingtalk/"/>
    <id>http://yunlzheng.github.io/2018/03/01/alertmanager-webhook-dingtalk/</id>
    <published>2018-03-01T13:58:33.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>在某些情况下除了Alertmanager已经内置的集中告警通知方式以外，对于不同的用户和组织而言还需要一些自定义的告知方式支持。通过Alertmanager提供的webhook支持可以轻松实现这一类的扩展。除了用于支持额外的通知方式，webhook还可以与其他第三方系统集成实现运维自动化，或者弹性伸缩等。</p><a id="more"></a><p>在Alertmanager中可以使用如下配置定义基于webhook的告警接收器receiver。一个receiver可以对应一组webhook配置。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">&lt;string&gt;</span></span><br><span class="line"><span class="attr">webhook_configs:</span></span><br><span class="line">  <span class="string">[</span> <span class="bullet">-</span> <span class="string">&lt;webhook_config&gt;,</span> <span class="string">...</span> <span class="string">]</span></span><br></pre></td></tr></table></figure><p>每一项webhook_config的具体配置格式如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Whether or not to notify about resolved alerts.</span></span><br><span class="line"><span class="string">[</span> <span class="attr">send_resolved:</span> <span class="string">&lt;boolean&gt;</span> <span class="string">| default = true ]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"># The endpoint to send HTTP POST requests to.</span></span><br><span class="line"><span class="string"></span><span class="attr">url:</span> <span class="string">&lt;string&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># The HTTP client's configuration.</span></span><br><span class="line"><span class="string">[</span> <span class="attr">http_config:</span> <span class="string">&lt;http_config&gt;</span> <span class="string">| default = global.http_config ]</span></span><br></pre></td></tr></table></figure><p>send_resolved用于指定是否在告警消除时发送回执消息。url则是用于接收webhook请求的地址。http_configs则是在需要对请求进行SSL配置时使用。</p><p>当用户定义webhook用于接收告警信息后，当告警被触发时，Alertmanager会按照以下格式向这些url地址发送HTTP Post请求，请求内容如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"version"</span>: <span class="string">"4"</span>,</span><br><span class="line">  "groupKey": &lt;string&gt;,    // key identifying the group of alerts (e.g. to deduplicate)</span><br><span class="line">  "status": "&lt;resolved|firing&gt;",</span><br><span class="line">  "receiver": &lt;string&gt;,</span><br><span class="line">  "groupLabels": &lt;object&gt;,</span><br><span class="line">  "commonLabels": &lt;object&gt;,</span><br><span class="line">  "commonAnnotations": &lt;object&gt;,</span><br><span class="line">  "externalURL": &lt;string&gt;,  // backlink to the Alertmanager.</span><br><span class="line">  "alerts": [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">"labels"</span>: &lt;object&gt;,</span><br><span class="line">      <span class="attr">"annotations"</span>: &lt;object&gt;,</span><br><span class="line">      <span class="attr">"startsAt"</span>: <span class="string">"&lt;rfc3339&gt;"</span>,</span><br><span class="line">      <span class="attr">"endsAt"</span>: <span class="string">"&lt;rfc3339&gt;"</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3><span id="使用golang创建webhook服务">使用Golang创建webhook服务</span></h3><p>首先我们尝试使用Golang创建用于接收webhook告警通知的服务。首先创建model包，用于映射ALertmanager发送的告警信息，Alertmanager的一个通知中根据配置的group_by规则可能会包含多条告警信息Alert。创建告警通知对应的结构体Notification。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> model</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"time"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Alert <span class="keyword">struct</span> &#123;</span><br><span class="line">Labels      <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span> <span class="string">`json:"labels"`</span></span><br><span class="line">Annotations <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span> <span class="string">`json:annotations`</span></span><br><span class="line">StartsAt    time.Time         <span class="string">`json:"startsAt"`</span></span><br><span class="line">EndsAt      time.Time         <span class="string">`json:"endsAt"`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Notification <span class="keyword">struct</span> &#123;</span><br><span class="line">Version           <span class="keyword">string</span>            <span class="string">`json:"version"`</span></span><br><span class="line">GroupKey          <span class="keyword">string</span>            <span class="string">`json:"groupKey"`</span></span><br><span class="line">Status            <span class="keyword">string</span>            <span class="string">`json:"status"`</span></span><br><span class="line">Receiver          <span class="keyword">string</span>            <span class="string">`json:receiver`</span></span><br><span class="line">GroupLabels       <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span> <span class="string">`json:groupLabels`</span></span><br><span class="line">CommonLabels      <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span> <span class="string">`json:commonLabels`</span></span><br><span class="line">CommonAnnotations <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span> <span class="string">`json:commonAnnotations`</span></span><br><span class="line">ExternalURL       <span class="keyword">string</span>            <span class="string">`json:externalURL`</span></span><br><span class="line">Alerts            []Alert           <span class="string">`json:alerts`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里使用gin-gonic框架创建用于接收Webhook通知的Web服务。定义路由/webhook接收来自Alertmanager的POST请求。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"net/http"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"github.com/gin-gonic/gin"</span></span><br><span class="line">model <span class="string">"github.com/yunlzheng/alertmanaer-dingtalk-webhook/model"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">router := gin.Default()</span><br><span class="line">router.POST(<span class="string">"/webhook"</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> notification model.Notification</span><br><span class="line"></span><br><span class="line">err := c.BindJSON(&amp;notification)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">c.JSON(http.StatusBadRequest, gin.H&#123;<span class="string">"error"</span>: err.Error()&#125;)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;<span class="string">"message"</span>: <span class="string">" successful receive alert notification message!"</span>&#125;)</span><br><span class="line"></span><br><span class="line">&#125;)</span><br><span class="line">router.Run()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3><span id="与钉钉集成">与钉钉集成</span></h3><p>钉钉，阿里巴巴出品，专为中国企业打造的免费智能移动办公平台，提供了即时通讯以及移动办公等丰富的功能。</p><p><a href="https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.8M9OKD&amp;treeId=257&amp;articleId=105733&amp;docType=1" target="_blank" rel="noopener">钉钉群机器人</a>是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中，实现自动化的信息同步。例如：通过聚合GitHub，GitLab等源码管理服务，实现源码更新同步；通过聚合Trello，JIRA等项目协调服务，实现项目信息同步。不仅如此，群机器人支持Webhook协议的自定义接入，支持更多可能性。这里我们将演示如果将Alertmanager运维报警提醒通过自定义机器人聚合到钉钉群。</p><p>这里将继续扩展webhook服务，以支持将Alertmanager的告警通知转发到钉钉平台。完整的示例代码可以从github仓库<a href="https://github.com/yunlzheng/alertmanaer-dingtalk-webhook" target="_blank" rel="noopener">https://github.com/yunlzheng/alertmanaer-dingtalk-webhook</a>中获取。</p><h5><span id="自定义webhook群机器人">自定义webhook群机器人</span></h5><p>通过钉钉客户端（如：桌面或者手机）进入到群设置后选择“群机器人”。将显示如下界面：</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/dingding-group-robot.png" alt="群机器人"></p><p>选择“自定义机器人”，并且按照提示填写机器人名称，获取机器人webhook地址，如下所示：</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/dingtalk-robot-create-webhook.png" alt="获取webhook地址"></p><p>webhook机器人创建成功后，用户就可以使用任何方式向该地址发起HTTP POST请求，即可实现向该群主发送消息。目前自定义机器人支持文本(text)，连接(link)，markdown三种消息类型。</p><p>例如，可以向webhook地址以POST形式发送以下</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">     <span class="attr">"msgtype"</span>: <span class="string">"markdown"</span>,</span><br><span class="line">     <span class="attr">"markdown"</span>: &#123;</span><br><span class="line">         <span class="attr">"title"</span>:<span class="string">"Prometheus告警信息"</span>,</span><br><span class="line">         <span class="attr">"text"</span>: <span class="string">"#### 监控指标\n"</span> +</span><br><span class="line">                 <span class="string">"&gt; 监控描述信息\n\n"</span> +</span><br><span class="line">                 <span class="string">"&gt; ###### 告警时间 \n"</span></span><br><span class="line">     &#125;,</span><br><span class="line">    <span class="attr">"at"</span>: &#123;</span><br><span class="line">        <span class="attr">"atMobiles"</span>: [</span><br><span class="line">            <span class="string">"156xxxx8827"</span>,</span><br><span class="line">            <span class="string">"189xxxx8325"</span></span><br><span class="line">        ], </span><br><span class="line">        <span class="attr">"isAtAll"</span>: <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>可以使用curl验证钉钉webhook是否能够成功调用：</p><figure class="highlight flix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ curl -l -H <span class="string">"Content-type: application/json"</span> -X POST -d '&#123;<span class="string">"msgtype"</span>: <span class="string">"markdown"</span>,<span class="string">"markdown"</span>: &#123;<span class="string">"title"</span>:<span class="string">"Prometheus告警信息"</span>,<span class="string">"text"</span>: <span class="string">"#### 监控指标\n&gt; 监控描述信息\n\n&gt; ###### 告警时间 \n"</span>&#125;,<span class="string">"at"</span>: &#123;<span class="string">"isAtAll"</span>: <span class="literal">false</span>&#125;&#125;' https:<span class="comment">//oapi.dingtalk.com/robot/send?access_token=xxxx</span></span><br><span class="line">&#123;<span class="string">"errcode"</span>:<span class="number">0</span>,<span class="string">"errmsg"</span>:<span class="string">"ok"</span>&#125;</span><br></pre></td></tr></table></figure><p>调用成功后，可以在钉钉应用群消息中接收到类似于如下通知消息:</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/dingtalk-message-test.png" alt="测试消息"></p><h5><span id="定义转换器将告警通知转化为dingtalk消息对象">定义转换器将告警通知转化为Dingtalk消息对象</span></h5><p>这里定义结构体DingTalkMarkdown用于映射Dingtalk的消息体。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> model</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> At <span class="keyword">struct</span> &#123;</span><br><span class="line">AtMobiles []<span class="keyword">string</span> <span class="string">`json:"atMobiles"`</span></span><br><span class="line">IsAtAll   <span class="keyword">bool</span>     <span class="string">`json:"isAtAll"`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> DingTalkMarkdown <span class="keyword">struct</span> &#123;</span><br><span class="line">MsgType  <span class="keyword">string</span>    <span class="string">`json:"msgtype"`</span></span><br><span class="line">At       *At       <span class="string">`json:at`</span></span><br><span class="line">Markdown *Markdown <span class="string">`json:"markdown"`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Markdown <span class="keyword">struct</span> &#123;</span><br><span class="line">Title <span class="keyword">string</span> <span class="string">`json:"title"`</span></span><br><span class="line">Text  <span class="keyword">string</span> <span class="string">`json:"text"`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>定义转换器将Alertmanager发送的告警通知转换为Dingtalk的消息体。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> transformer</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"bytes"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"github.com/yunlzheng/alertmanaer-dingtalk-webhook/model"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// TransformToMarkdown transform alertmanager notification to dingtalk markdow message</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TransformToMarkdown</span><span class="params">(notification model.Notification)</span> <span class="params">(markdown *model.DingTalkMarkdown, err error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">groupKey := notification.GroupKey</span><br><span class="line">status := notification.Status</span><br><span class="line"></span><br><span class="line">annotations := notification.CommonAnnotations</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> buffer bytes.Buffer</span><br><span class="line"></span><br><span class="line">buffer.WriteString(fmt.Sprintf(<span class="string">"### 通知组%s(当前状态:%s) \n"</span>, groupKey, status))</span><br><span class="line"></span><br><span class="line">buffer.WriteString(fmt.Sprintf(<span class="string">"#### 告警项:\n"</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, alert := <span class="keyword">range</span> notification.Alerts &#123;</span><br><span class="line">annotations := alert.Annotations</span><br><span class="line">buffer.WriteString(fmt.Sprintf(<span class="string">"##### %s\n &gt; %s\n"</span>, annotations[<span class="string">"summary"</span>], annotations[<span class="string">"description"</span>]))</span><br><span class="line">buffer.WriteString(fmt.Sprintf(<span class="string">"\n&gt; 开始时间：%s\n"</span>, alert.StartsAt.Format(<span class="string">"15:04:05"</span>)))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">markdown = &amp;model.DingTalkMarkdown&#123;</span><br><span class="line">MsgType: <span class="string">"markdown"</span>,</span><br><span class="line">Markdown: &amp;model.Markdown&#123;</span><br><span class="line">Title: fmt.Sprintf(<span class="string">"通知组：%s(当前状态:%s)"</span>, groupKey, status),</span><br><span class="line">Text:  buffer.String(),</span><br><span class="line">&#125;,</span><br><span class="line">At: &amp;model.At&#123;</span><br><span class="line">IsAtAll: <span class="literal">false</span>,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5><span id="创建dingtalk通知发送包">创建Dingtalk通知发送包</span></h5><p>notifier包中使用golang的net/http包实现与Dingtalk群机器人的交互。Send方法包含两个参数：接收到的告警通知结构体指针，以及Dingtalk群机器人的Webhook地址。</p><p>通过包transformer.TransformToMarkdown将Alertmanager告警通知与Dingtalk消息进行映射。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> notifier</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"bytes"</span></span><br><span class="line"><span class="string">"encoding/json"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"net/http"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"github.com/yunlzheng/alertmanaer-dingtalk-webhook/model"</span></span><br><span class="line"><span class="string">"github.com/yunlzheng/alertmanaer-dingtalk-webhook/transformer"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Send</span><span class="params">(notification model.Notification, dingtalkRobot <span class="keyword">string</span>)</span> <span class="params">(err error)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">markdown, err := transformer.TransformToMarkdown(notification)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data, err := json.Marshal(markdown)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">req, err := http.NewRequest(</span><br><span class="line"><span class="string">"POST"</span>,</span><br><span class="line">dingtalkRobot,</span><br><span class="line">bytes.NewBuffer(data))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">req.Header.Set(<span class="string">"Content-Type"</span>, <span class="string">"application/json"</span>)</span><br><span class="line">client := &amp;http.Client&#123;&#125;</span><br><span class="line">resp, err := client.Do(req)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> resp.Body.Close()</span><br><span class="line">fmt.Println(<span class="string">"response Status:"</span>, resp.Status)</span><br><span class="line">fmt.Println(<span class="string">"response Headers:"</span>, resp.Header)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5><span id="扩展启动函数">扩展启动函数</span></h5><p>首先为程序添加命令行参数支持，用于在启动时添加全局的Dingtalk群聊机器人地址。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">"flag"</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="string">"github.com/yunlzheng/alertmanaer-dingtalk-webhook/notifier"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">h            <span class="keyword">bool</span></span><br><span class="line">defaultRobot <span class="keyword">string</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">flag.BoolVar(&amp;h, <span class="string">"h"</span>, <span class="literal">false</span>, <span class="string">"help"</span>)</span><br><span class="line">flag.StringVar(&amp;defaultRobot, <span class="string">"defaultRobot"</span>, <span class="string">""</span>, <span class="string">"global dingtalk robot webhook"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">flag.Parse()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> h &#123;</span><br><span class="line">flag.Usage()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时通过notifier包的Send方法将告警通知发送给Dingtalk群聊机器人</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line">  err = notifier.<span class="type">Send</span>(notification, defaultRobot)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="built_in">c</span>.<span class="type">JSON</span>(http.<span class="type">StatusBadRequest</span>, gin.<span class="type">H</span>&#123;<span class="string">"error"</span>: err.<span class="type">Error</span>()&#125;)</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">c</span>.<span class="type">JSON</span>(http.<span class="type">StatusOK</span>, gin.<span class="type">H</span>&#123;<span class="string">"message"</span>: <span class="string">"send to dingtalk successful!"</span>&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5><span id="使用dingtalk扩展">使用Dingtalk扩展</span></h5><p>运行并启动dingtalk webhook服务之后，修改Alertmanager配置文件, 为default-receiver添加webhook配置，如下所示：</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">receivers</span>:</span><br><span class="line">  - <span class="attribute">name</span>: default-receiver</span><br><span class="line">    <span class="attribute">email_configs</span>:</span><br><span class="line">      - <span class="attribute">to</span>: yunl.zheng<span class="variable">@wise2c</span>.com</span><br><span class="line"><span class="attribute">webhook_configs</span>:</span><br><span class="line">  - <span class="attribute">url</span>: <span class="attribute">http</span>:<span class="comment">//localhost:8080/webhook</span></span><br></pre></td></tr></table></figure><p>重启Alertmanager服务后，手动拉高虚拟机CPU使用率触发告警条件，此时Dingtalk即可接收到相应的告警通知信息:</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/alertmanager-dingtalk-test-result.png" alt="钉钉群机器人告警信息"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在某些情况下除了Alertmanager已经内置的集中告警通知方式以外，对于不同的用户和组织而言还需要一些自定义的告知方式支持。通过Alertmanager提供的webhook支持可以轻松实现这一类的扩展。除了用于支持额外的通知方式，webhook还可以与其他第三方系统集成实现运维自动化，或者弹性伸缩等。&lt;/p&gt;
    
    </summary>
    
    
      <category term="DevOps" scheme="http://yunlzheng.github.io/tags/DevOps/"/>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="Alertmanager" scheme="http://yunlzheng.github.io/tags/Alertmanager/"/>
    
  </entry>
  
  <entry>
    <title>监控什么？4个黄金指标/RED方法/USE方法</title>
    <link href="http://yunlzheng.github.io/2018/02/02/monitor-best-praticase4-golden-signals/"/>
    <id>http://yunlzheng.github.io/2018/02/02/monitor-best-praticase4-golden-signals/</id>
    <published>2018-02-02T13:18:02.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>这里先思考一个基本的问题，在实现监控时，我们到底应该监控哪些对象以及哪些指标。本文会介绍会介绍一些通用的套路，包括Goole的”4个黄金指标”和此基础上演进出的”RED方法“，以及注重分析系统性能问题”USE方法”。</p><a id="more"></a><h2><span id="监控所有">监控所有</span></h2><p>在之前<strong>Prometheus简介</strong>部分介绍监控的基本目标，首先是及时发现问题其次是要能够快速对问题进行定位。对于传统监控解决方案而言，用户看到的依然是一个黑盒，用户无法真正了解系统的真正的运行状态。因此Prometheus鼓励用户监控所有的东西。下面列举一些常用的监控维度。</p><table><thead><tr><th>级别</th><th>监控什么</th><th>Exporter</th></tr></thead><tbody><tr><td>网络</td><td>网络协议：http、dns、tcp、icmp；网络硬件：路由器，交换机等</td><td>BlockBox Exporter;SNMP Exporter</td></tr><tr><td>主机</td><td>资源用量</td><td>node exporter</td></tr><tr><td>容器</td><td>资源用量</td><td>cAdvisor</td></tr><tr><td>应用(包括Library)</td><td>延迟，错误，QPS，内部状态等</td><td>代码中集成Prmometheus Client</td></tr><tr><td>中间件状态</td><td>资源用量，以及服务状态</td><td>代码中集成Prmometheus Client</td></tr><tr><td>编排工具</td><td>集群资源用量，调度等</td><td>Kubernetes Components</td></tr></tbody></table><h2><span id="监控模式">监控模式</span></h2><p>除了上述介绍的不同监控级别以外。实际上根据不同的系统类型和目标，这里还有一些通用的套路和模式可以使用。</p><h3><span id="4个黄金指标">4个黄金指标</span></h3><p>Four Golden Signals是Google针对大量分布式监控的经验总结，4个黄金指标可以在服务级别帮助衡量终端用户体验、服务中断、业务影响等层面的问题。主要关注与以下四种类型的指标：延迟，通讯量，错误以及饱和度:</p><ul><li>延迟：服务请求所需时间。</li></ul><p>记录用户所有请求所需的时间，重点是要区分成功请求的延迟时间和失败请求的延迟时间。 例如在数据库或者其他关键祸端服务异常触发HTTP 500的情况下，用户也可能会很快得到请求失败的响应内容，如果不加区分计算这些请求的延迟，可能导致计算结果与实际结果产生巨大的差异。除此以外，在微服务中通常提倡“快速失败”，开发人员需要特别注意这些延迟较大的错误，因为这些缓慢的错误会明显影响系统的性能，因此追踪这些错误的延迟也是非常重要的。</p><ul><li>通讯量：监控当前系统的流量，用于衡量服务的容量需求。</li></ul><p>流量对于不同类型的系统而言可能代表不同的含义。例如，在HTTP REST API中, 流量通常是每秒HTTP请求数；</p><ul><li>错误：监控当前系统所有发生的错误请求，衡量当前系统错误发生的速率。</li></ul><p>对于失败而言有些是显式的(比如, HTTP 500错误)，而有些是隐式(比如，HTTP响应200，单实际业务流程依然是失败的)。</p><p>对于一些显式的错误如HTTP 500可以通过在负载均衡器(如Nginx)上进行捕获，而对于一些系统内部的异常，则可能需要直接从服务中添加钩子统计并进行获取。</p><ul><li>饱和度：衡量当前服务的饱和度。</li></ul><p>主要强调最能影响服务状态的受限制的资源。 例如，如果系统主要受内存影响，那就主要关注系统的内存状态，如果系统主要受限与磁盘I/O，那就主要观测磁盘I/O的状态。因为通常情况下，当这些资源达到饱和后，服务的性能会明显下降。同时还可以利用饱和度对系统做出预测，比如，“磁盘是否可能在4个小时候就满了”。</p><h3><span id="red方法">RED方法</span></h3><p>RED方法是Weave Cloud在基于Google的“4个黄金指标”的原则下结合Prometheus以及Kubernetes容器实践，细化和总结的方法论，特别适合于云原生应用以及微服务架构应用的监控和度量。主要关注以下三种关键指标：</p><ul><li>(请求)速率：服务每秒接收的请求数。</li><li>(请求)错误：每秒失败的请求数。</li><li>(请求)耗时：每个请求的耗时。</li></ul><p>在“4大黄金信号”的原则下，RED方法可以有效的帮助用户衡量云原生以及微服务应用下的用户体验问题。</p><h3><span id="use方法">USE方法</span></h3><p>USE方法全称”Utilization Saturation and Errors Method”，主要用于分析系统性能问题，可以指导用户快速识别资源瓶颈以及错误的方法。正如USE方法的名字所表示的含义，USE方法主要关注与资源的：使用率(Utilization)、饱和度(Saturation)以及错误(Errors)。</p><ul><li>使用率：关注系统资源的使用情况。 这里的资源主要包括但不限于：CPU，内存，网络，磁盘等等。100%的使用率通常是系统性能瓶颈的标志。</li><li>饱和度：例如CPU的平均运行排队长度，这里主要是针对资源的饱和度(注意，不同于4大黄金信号)。任何资源在某种程度上的饱和都可能导致系统性能的下降。</li><li>错误：错误计数。例如：“网卡在数据包传输过程中检测到的以太网网络冲突了14次”。</li></ul><p>通过对资源以上指标持续观察，通过以下流程可以知道用户识别资源瓶颈：</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/USEMethod.png" alt="识别资源瓶颈">rm</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;这里先思考一个基本的问题，在实现监控时，我们到底应该监控哪些对象以及哪些指标。本文会介绍会介绍一些通用的套路，包括Goole的”4个黄金指标”和此基础上演进出的”RED方法“，以及注重分析系统性能问题”USE方法”。&lt;/p&gt;
    
    </summary>
    
    
      <category term="DevOps" scheme="http://yunlzheng.github.io/tags/DevOps/"/>
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="Monitor" scheme="http://yunlzheng.github.io/tags/Monitor/"/>
    
  </entry>
  
  <entry>
    <title>自定义Metrics：让Prometheus监控你的应用程序（Spring版）</title>
    <link href="http://yunlzheng.github.io/2018/01/24/use-prometheus-monitor-your-spring-boot-application/"/>
    <id>http://yunlzheng.github.io/2018/01/24/use-prometheus-monitor-your-spring-boot-application/</id>
    <published>2018-01-24T02:07:45.000Z</published>
    <updated>2018-08-09T08:01:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文将以Spring Boot/Spring Cloud为例，介绍如果使用Prometheus SDK实现自定义监控指标的定义以及暴露，并且会介绍Prometheus中四种不同指标类型(Counter, Gauge, Histogram, Summary)的实际使用场景；</p><a id="more"></a><h2><span id="前言">前言</span></h2><p>Prometheus社区提供了大量的<a href="https://prometheus.io/docs/instrumenting/exporters/" target="_blank" rel="noopener">官方以及第三方Exporters</a>，可以满足Prometheus的采纳者快速实现对关键业务，以及基础设施的监控需求。</p><p><img src="http://p2n2em8ut.bkt.clouddn.com/app-with-exporters.png" alt=""></p><p>如上所示，一个简单的应用以及环境架构。一般而言，我们通常会从几个层面进行监控指标的采集：</p><ul><li>入口网关：这里可以是Nginx/HaProxy这一类的负载均衡器，也可以是注入Spring Cloud Zuul这一类框架提供的微服务入口。一般来说我们需要对所有Http Request相关的指标数据进行采集。如请求地址，Http Method,返回状态码，响应时长等。从而可以通过这些指标历史数据去分析业务压力，服务状态等信息。</li><li>应用服务：对于应用服务而言，基本的如应用本身的资源使用率,比如如果是Java类程序可以直接通过JVM信息来进行统计，如果是部署到容器中，则可以通过Container的资源使用情况来统计。除了资源用量外，某些特殊情况下，我们可能还会对应用中的某些业务指标进行采集。</li><li>基础设施：虚拟机或者物理机的资源使用情况等。</li><li>其它：集群环境中所使用到的数据库，缓存，消息队列等中间件状态等。</li></ul><p>对于以上的集中场景中，除了直接使用Prometheus社区提供的Exporter外，不同的项目可能还需要实现一些自定义的Exporter用于实现对于特定目的的指标的采集和监控需求。</p><h2><span id="扩展spring应用程序支持prometheus采集">扩展Spring应用程序，支持Prometheus采集</span></h2><h3><span id="添加prometheus-java-client依赖">添加Prometheus Java Client依赖</span></h3><blockquote><p>这里使用0.0.24的版本，在之前的版本中Spring Boot暴露的监控地址，无法正确的处理Prometheus Server的请求，详情：<a href="https://github.com/prometheus/client_java/issues/265" target="_blank" rel="noopener">https://github.com/prometheus/client_java/issues/265</a></p></blockquote><figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># build.gradle</span><br><span class="line">...</span><br><span class="line">dependencies &#123;</span><br><span class="line">    ...</span><br><span class="line">    compile <span class="string">'io.prometheus:simpleclient:0.0.24'</span></span><br><span class="line">    compile <span class="string">"io.prometheus:simpleclient_spring_boot:0.0.24"</span></span><br><span class="line">    compile <span class="string">"io.prometheus:simpleclient_hotspot:0.0.24"</span></span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3><span id="启用prometheus-metrics-endpoint">启用Prometheus Metrics Endpoint</span></h3><p>添加注解@EnablePrometheusEndpoint启用Prometheus Endpoint,这里同时使用了simpleclient_hotspot中提供的DefaultExporter该Exporter会在metrics endpoint中放回当前应用JVM的相关信息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnablePrometheusEndpoint</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringApplication</span> <span class="keyword">implements</span> <span class="title">CommandLineRunner</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(GatewayApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">(String... strings)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        DefaultExports.initialize();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>默认情况下Prometheus暴露的metrics endpoint为 /prometheus，可以通过endpoint配置进行修改</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">endpoints:</span></span><br><span class="line"><span class="attr">  prometheus:</span></span><br><span class="line"><span class="attr">    id:</span> <span class="string">metrics</span></span><br><span class="line"><span class="attr">  metrics:</span></span><br><span class="line"><span class="attr">    id:</span> <span class="string">springmetrics</span></span><br><span class="line"><span class="attr">    sensitive:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">    enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>启动应用程序访问 <a href="http://localhost:8080/metrics" target="_blank" rel="noopener">http://localhost:8080/metrics</a> 可以看到以下输出：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># HELP jvm_gc_collection_seconds Time spent in a given JVM garbage collector in seconds.</span></span><br><span class="line"><span class="comment"># TYPE jvm_gc_collection_seconds summary</span></span><br><span class="line">jvm_gc_collection_seconds_count&#123;<span class="attribute">gc</span>=<span class="string">"PS Scavenge"</span>,&#125; 11.0</span><br><span class="line">jvm_gc_collection_seconds_sum&#123;<span class="attribute">gc</span>=<span class="string">"PS Scavenge"</span>,&#125; 0.18</span><br><span class="line">jvm_gc_collection_seconds_count&#123;<span class="attribute">gc</span>=<span class="string">"PS MarkSweep"</span>,&#125; 2.0</span><br><span class="line">jvm_gc_collection_seconds_sum&#123;<span class="attribute">gc</span>=<span class="string">"PS MarkSweep"</span>,&#125; 0.121</span><br><span class="line"><span class="comment"># HELP jvm_classes_loaded The number of classes that are currently loaded in the JVM</span></span><br><span class="line"><span class="comment"># TYPE jvm_classes_loaded gauge</span></span><br><span class="line">jvm_classes_loaded 8376.0</span><br><span class="line"><span class="comment"># HELP jvm_classes_loaded_total The total number of classes that have been loaded since the JVM has started execution</span></span><br><span class="line"><span class="comment"># TYPE jvm_classes_loaded_total counter</span></span><br><span class="line"><span class="built_in">..</span>.</span><br></pre></td></tr></table></figure><h3><span id="添加拦截器为监控埋点做准备">添加拦截器，为监控埋点做准备</span></h3><p>除了获取应用JVM相关的状态以外，我们还可能需要添加一些自定义的监控Metrics实现对系统性能，以及业务状态进行采集，以提供日后优化的相关支撑数据。首先我们使用拦截器处理对应用的所有请求。</p><p>继承WebMvcConfigurerAdapter类，复写addInterceptors方法，对所有请求/**添加拦截器</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnablePrometheusEndpoint</span></span><br><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">SpringApplication</span> <span class="keyword">extends</span> <span class="title">WebMvcConfigurerAdapter</span> <span class="title">implements</span> <span class="title">CommandLineRunner</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    public void addInterceptors(<span class="type">InterceptorRegistry</span> registry) &#123;</span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="type">PrometheusMetricsInterceptor</span>()).addPathPatterns(<span class="string">"/**"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>PrometheusMetricsInterceptor集成HandlerInterceptorAdapter，通过复写父方法，实现对请求处理前/处理完成的处理。</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PrometheusMetricsInterceptor</span> <span class="keyword">extends</span> <span class="title">HandlerInterceptorAdapter</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">boolean</span> <span class="title">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="function"><span class="keyword">return</span> <span class="keyword">super</span>.<span class="title">preHandle</span><span class="params">(request, response, <span class="keyword">handler</span>)</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>, Exception ex)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.afterCompletion(request, response, <span class="keyword">handler</span>, ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3><span id="自定义metrics指标">自定义Metrics指标</span></h3><p>Prometheus提供了4中不同的Metrics类型:Counter,Gauge,Histogram,Summary</p><h4><span id="counter只增不减的计数器">Counter:只增不减的计数器</span></h4><p>计数器可以用于记录只会增加不会减少的指标类型,比如记录应用请求的总量(http_requests_total)，cpu使用时间(process_cpu_seconds_total)等。</p><p>对于Counter类型的指标，只包含一个inc()方法，用于计数器+1</p><p>一般而言，Counter类型的metrics指标在命名中我们使用_total结束。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">PrometheusMetricsInterceptor</span> <span class="keyword">extends</span> <span class="title">HandlerInterceptorAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    static <span class="keyword">final</span> <span class="type">Counter</span> requestCounter = <span class="type">Counter</span>.build()</span><br><span class="line">            .name(<span class="string">"io_namespace_http_requests_total"</span>).labelNames(<span class="string">"path"</span>, <span class="string">"method"</span>, <span class="string">"code"</span>)</span><br><span class="line">            .help(<span class="string">"Total requests."</span>).register();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    public void afterCompletion(<span class="type">HttpServletRequest</span> request, <span class="type">HttpServletResponse</span> response, <span class="type">Object</span> handler, <span class="type">Exception</span> ex) <span class="keyword">throws</span> <span class="type">Exception</span> &#123;</span><br><span class="line">        <span class="type">String</span> requestURI = request.getRequestURI();</span><br><span class="line">        <span class="type">String</span> method = request.getMethod();</span><br><span class="line">        int status = response.getStatus();</span><br><span class="line"></span><br><span class="line">        requestCounter.labels(requestURI, method, <span class="type">String</span>.valueOf(status)).inc();</span><br><span class="line">        <span class="keyword">super</span>.afterCompletion(request, response, handler, ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用Counter.build()创建Counter metrics，name()方法，用于指定该指标的名称 labelNames()方法，用于声明该metrics拥有的维度label。在preHandle方法中，我们获取当前请求的，RequesPath，Method以及状态码。并且调用inc()方法，在每次请求发生时计数+1。</p><p>Counter.build()…register(),会像Collector中注册该指标，并且当访问/metrics地址时，返回该指标的状态。</p><p>通过指标io_namespace_http_requests_total我们可以：</p><ul><li>查询应用的请求总量</li></ul><figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"># PromQL</span></span><br><span class="line"><span class="keyword">sum</span>(io_namespace_http_requests_total)</span><br></pre></td></tr></table></figure><p><img src="http://p2n2em8ut.bkt.clouddn.com/httP_request_total.png" alt=""></p><ul><li>查询每秒Http请求量</li></ul><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># PromQL</span><br><span class="line">sum(<span class="name">rate</span>(<span class="name">io_wise2c_gateway_requests_total</span>[<span class="number">5</span>m]))</span><br></pre></td></tr></table></figure><p><img src="http://p2n2em8ut.bkt.clouddn.com/http_request_rate.png" alt=""></p><ul><li>查询当前应用请求量Top N的URI</li></ul><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># PromQL</span><br><span class="line">topk(<span class="number">10</span>, sum(<span class="name">io_namespace_http_requests_total</span>) by (<span class="name">path</span>))</span><br></pre></td></tr></table></figure><h4><span id="gauge-可增可减的仪表盘">Gauge: 可增可减的仪表盘</span></h4><p>对于这类可增可减的指标，可以用于反应应用的<strong>当前状态</strong>,例如在监控主机时，主机当前空闲的内容大小(node_memory_MemFree)，可用内存大小(node_memory_MemAvailable)。或者容器当前的cpu使用率,内存使用率。</p><p>对于Gauge指标的对象则包含两个主要的方法inc()以及dec(),用户添加或者减少计数。在这里我们使用Gauge记录当前正在处理的Http请求数量。</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PrometheusMetricsInterceptor</span> <span class="keyword">extends</span> <span class="title">HandlerInterceptorAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    ...省略的代码</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> Gauge inprogressRequests = Gauge.build()</span><br><span class="line">            .name(<span class="string">"io_namespace_http_inprogress_requests"</span>).labelNames(<span class="string">"path"</span>, <span class="string">"method"</span>, <span class="string">"code"</span>)</span><br><span class="line">            .help(<span class="string">"Inprogress requests."</span>).register();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">boolean</span> <span class="title">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        <span class="comment">// 计数器+1</span></span><br><span class="line">        inprogressRequests.labels(requestURI, method, String.valueOf(status)).inc();</span><br><span class="line">        <span class="function"><span class="keyword">return</span> <span class="keyword">super</span>.<span class="title">preHandle</span><span class="params">(request, response, <span class="keyword">handler</span>)</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>, Exception ex)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        <span class="comment">// 计数器-1</span></span><br><span class="line">        inprogressRequests.labels(requestURI, method, String.valueOf(status)).dec();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">super</span>.afterCompletion(request, response, <span class="keyword">handler</span>, ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过指标io_namespace_http_inprogress_requests我们可以直接查询应用当前正在处理中的Http请求数量:</p><figure class="highlight roboconf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># PromQL</span></span><br><span class="line">io_namespace_http_inprogress_requests&#123;&#125;</span><br></pre></td></tr></table></figure><h4><span id="histogram自带buckets区间用于统计分布统计图">Histogram：自带buckets区间用于统计分布统计图</span></h4><p>主要用于在指定分布范围内(Buckets)记录大小(如http request bytes)或者事件发生的次数。</p><p>以请求响应时间requests_latency_seconds为例，假如我们需要记录http请求响应时间符合在分布范围{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}中的次数时。</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PrometheusMetricsInterceptor</span> <span class="keyword">extends</span> <span class="title">HandlerInterceptorAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> Histogram requestLatencyHistogram = Histogram.build().labelNames(<span class="string">"path"</span>, <span class="string">"method"</span>, <span class="string">"code"</span>)</span><br><span class="line">            .name(<span class="string">"io_namespace_http_requests_latency_seconds_histogram"</span>).help(<span class="string">"Request latency in seconds."</span>)</span><br><span class="line">            .register();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Histogram.Timer histogramRequestTimer;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">boolean</span> <span class="title">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        histogramRequestTimer = requestLatencyHistogram.labels(requestURI, method, String.valueOf(status)).startTimer();</span><br><span class="line">        ...省略的代码</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>, Exception ex)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        histogramRequestTimer.observeDuration();</span><br><span class="line">        ...省略的代码</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用Histogram构造器可以创建Histogram监控指标。默认的buckets范围为{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}。如何需要覆盖默认的buckets，可以使用.buckets(double… buckets)覆盖。</p><p>Histogram会自动创建3个指标，分别为：</p><ul><li>事件发生总次数： basename_count</li></ul><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 实际含义： 当前一共发生了<span class="number">2</span>次http请求</span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_count&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,&#125; <span class="number">2</span>.<span class="number">0</span></span><br></pre></td></tr></table></figure><ul><li>所有事件产生值的大小的总和: basename_sum</li></ul><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 实际含义： 发生的<span class="number">2</span>次http请求总的响应时间为<span class="number">13</span>.<span class="number">107670803000001</span> 秒</span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_sum&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,&#125; <span class="number">13</span>.<span class="number">107670803000001</span></span><br></pre></td></tr></table></figure><ul><li>事件产生的值分布在bucket中的次数： basename_bucket{le=”上包含”}</li></ul><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"># 在总共<span class="number">2</span>次请求当中。http请求响应时间 &lt;=<span class="number">0</span>.<span class="number">005</span> 秒 的请求次数为<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.005"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line"># 在总共<span class="number">2</span>次请求当中。http请求响应时间 &lt;=<span class="number">0</span>.<span class="number">01</span> 秒 的请求次数为<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.01"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line"># 在总共<span class="number">2</span>次请求当中。http请求响应时间 &lt;=<span class="number">0</span>.<span class="number">025</span> 秒 的请求次数为<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.025"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.05"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.075"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.1"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.25"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.5"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"0.75"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"1.0"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"2.5"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"5.0"</span>,&#125; <span class="number">0</span>.<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"7.5"</span>,&#125; <span class="number">2</span>.<span class="number">0</span></span><br><span class="line"># 在总共<span class="number">2</span>次请求当中。http请求响应时间 &lt;=<span class="number">10</span> 秒 的请求次数为<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"10.0"</span>,&#125; <span class="number">2</span>.<span class="number">0</span></span><br><span class="line"># 在总共<span class="number">2</span>次请求当中。http请求响应时间 <span class="number">10</span> 秒 的请求次数为<span class="number">0</span></span><br><span class="line">io_namespace_http_requests_latency_seconds_histogram_bucket&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,le=<span class="string">"+Inf"</span>,&#125; <span class="number">2</span>.<span class="number">0</span></span><br></pre></td></tr></table></figure><h4><span id="summary-客户端定义的数据分布统计图">Summary: 客户端定义的数据分布统计图</span></h4><p>Summary和Histogram非常类型相似，都可以统计事件发生的次数或者发小，以及其分布情况。</p><p>Summary和Histogram都提供了对于事件的计数_count以及值的汇总_sum。 因此使用_count,和_sum时间序列可以计算出相同的内容，例如http每秒的平均响应时间：rate(basename_sum[5m]) / rate(basename_count[5m])。</p><p>同时Summary和Histogram都可以计算和统计样本的分布情况，比如中位数，9分位数等等。其中 0.0&lt;= 分位数Quantiles &lt;= 1.0。</p><p>不同在于Histogram可以通过histogram_quantile函数在服务器端计算分位数。 而Sumamry的分位数则是直接在客户端进行定义。因此对于分位数的计算。 Summary在通过PromQL进行查询时有更好的性能表现，而Histogram则会消耗更多的资源。相对的对于客户端而言Histogram消耗的资源更少。</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PrometheusMetricsInterceptor</span> <span class="keyword">extends</span> <span class="title">HandlerInterceptorAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> Summary requestLatency = Summary.build()</span><br><span class="line">            .name(<span class="string">"io_namespace_http_requests_latency_seconds_summary"</span>)</span><br><span class="line">            .quantile(<span class="number">0.5</span>, <span class="number">0.05</span>)</span><br><span class="line">            .quantile(<span class="number">0.9</span>, <span class="number">0.01</span>)</span><br><span class="line">            .labelNames(<span class="string">"path"</span>, <span class="string">"method"</span>, <span class="string">"code"</span>)</span><br><span class="line">            .help(<span class="string">"Request latency in seconds."</span>).register();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">boolean</span> <span class="title">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        requestTimer = requestLatency.labels(requestURI, method, String.valueOf(status)).startTimer();</span><br><span class="line">        ...省略的代码</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object <span class="keyword">handler</span>, Exception ex)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        requestTimer.observeDuration();</span><br><span class="line">        ...省略的代码</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用Summary指标，会自动创建多个时间序列：</p><ul><li>事件发生总的次数</li></ul><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 含义：当前http请求发生总次数为<span class="number">12</span>次</span><br><span class="line">io_namespace_http_requests_latency_seconds_summary_count&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,&#125; <span class="number">12</span>.<span class="number">0</span></span><br></pre></td></tr></table></figure><ul><li>事件产生的值的总和</li></ul><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 含义：这<span class="number">12</span>次http请求的总响应时间为 <span class="number">51</span>.029495508s</span><br><span class="line">io_namespace_http_requests_latency_seconds_summary_sum&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,&#125; <span class="number">51</span>.<span class="number">029495508</span></span><br></pre></td></tr></table></figure><ul><li>事件产生的值的分布情况</li></ul><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># 含义：这<span class="number">12</span>次http请求响应时间的中位数是<span class="number">3</span>.052404983s</span><br><span class="line">io_namespace_http_requests_latency_seconds_summary&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,quantile=<span class="string">"0.5"</span>,&#125; <span class="number">3</span>.<span class="number">052404983</span></span><br><span class="line"># 含义：这<span class="number">12</span>次http请求响应时间的<span class="number">9</span>分位数是<span class="number">8</span>.003261666s</span><br><span class="line">io_namespace_http_requests_latency_seconds_summary&#123;path=<span class="string">"/"</span>,method=<span class="string">"<span class="keyword">GET</span>"</span>,code=<span class="string">"200"</span>,quantile=<span class="string">"0.9"</span>,&#125; <span class="number">8</span>.<span class="number">003261666</span></span><br></pre></td></tr></table></figure><h3><span id="使用collector暴露业务指标">使用Collector暴露业务指标</span></h3><p>除了在拦截器中使用Prometheus提供的Counter,Summary,Gauage等构造监控指标以外，我们还可以通过自定义的Collector实现对相关业务指标的暴露</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnablePrometheusEndpoint</span></span><br><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">SpringApplication</span> <span class="keyword">extends</span> <span class="title">WebMvcConfigurerAdapter</span> <span class="title">implements</span> <span class="title">CommandLineRunner</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">CustomExporter</span> customExporter;</span><br><span class="line"></span><br><span class="line">    ...省略的代码</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    public void run(<span class="type">String</span>... args) <span class="keyword">throws</span> <span class="type">Exception</span> &#123;</span><br><span class="line">        ...省略的代码</span><br><span class="line">        customExporter.register();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CustomExporter集成自io.prometheus.client.Collector，在调用Collector的register()方法后，当访问/metrics时，则会自动从Collector的collection()方法中获取采集到的监控指标。</p><p>由于这里CustomExporter存在于Spring的IOC容器当中，这里可以直接访问业务代码，返回需要的业务相关的指标。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.prometheus.client.<span class="type">Collector</span>;</span><br><span class="line"><span class="keyword">import</span> io.prometheus.client.<span class="type">GaugeMetricFamily</span>;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.<span class="type">Component</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.<span class="type">ArrayList</span>;</span><br><span class="line"><span class="keyword">import</span> java.util.<span class="type">Collections</span>;</span><br><span class="line"><span class="keyword">import</span> java.util.<span class="type">List</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">CustomExporter</span> <span class="keyword">extends</span> <span class="title">Collector</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    public <span class="type">List</span>&lt;<span class="type">MetricFamilySamples</span>&gt; collect() &#123;</span><br><span class="line">        <span class="type">List</span>&lt;<span class="type">MetricFamilySamples</span>&gt; mfs = <span class="keyword">new</span> <span class="type">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        # 创建metrics指标</span><br><span class="line">        <span class="type">GaugeMetricFamily</span> labeledGauge =</span><br><span class="line">                <span class="keyword">new</span> <span class="type">GaugeMetricFamily</span>(<span class="string">"io_namespace_custom_metrics"</span>, <span class="string">"custom metrics"</span>, <span class="type">Collections</span>.singletonList(<span class="string">"labelname"</span>));</span><br><span class="line"></span><br><span class="line">        # 设置指标的label以及value</span><br><span class="line">        labeledGauge.addMetric(<span class="type">Collections</span>.singletonList(<span class="string">"labelvalue"</span>), <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        mfs.add(labeledGauge);</span><br><span class="line">        <span class="keyword">return</span> mfs;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然这里也可以使用CounterMetricFamily，SummaryMetricFamily声明其它的指标类型。</p><h2><span id="小结">小结</span></h2><p>好了。 目前为止，启动应用程序，并且访问 <a href="http://localhost:8080/metrics。我们可以看到如下结果。" target="_blank" rel="noopener">http://localhost:8080/metrics。我们可以看到如下结果。</a><br><img src="http://p2n2em8ut.bkt.clouddn.com/spring_boot_etrics.png" alt=""></p><p>这部分分别介绍了两种方式，在Spring应用中实现对于自定义Metrics指标的定义：</p><ul><li>拦截器/过滤器：用于统计所有应用请求的情况</li><li>自定义Collector: 可以用于统计应用业务能力相关的监控情况</li></ul><p>同时介绍了4中Metrics指标类型以及使用场景：</p><ul><li>Counter，只增不减的计数器</li><li>Gauge，可增可减的仪表盘</li><li>Histogram，自带buckets区间用于统计分布统计图</li><li>Summary， 客户端定义的数据分布统计图</li></ul><p>恭喜，接下来在后面的文章中我们会尝试将应用程序部署到Kubernetes当中，并且通过Prometheus采集其数据,通过PromQL聚合数据，并且在Grafana中进行监控可视化。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文将以Spring Boot/Spring Cloud为例，介绍如果使用Prometheus SDK实现自定义监控指标的定义以及暴露，并且会介绍Prometheus中四种不同指标类型(Counter, Gauge, Histogram, Summary)的实际使用场景；&lt;/p&gt;
    
    </summary>
    
    
      <category term="Prometheus" scheme="http://yunlzheng.github.io/tags/Prometheus/"/>
    
      <category term="Java" scheme="http://yunlzheng.github.io/tags/Java/"/>
    
      <category term="Spring" scheme="http://yunlzheng.github.io/tags/Spring/"/>
    
  </entry>
  
</feed>
