<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Zh-Cns on 翟志军 Jack Zhai</title>
    <link>https://showme.codes/zh-cn/</link>
    <description>Recent content in Zh-Cns on 翟志军 Jack Zhai</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <copyright>showme.codes</copyright>
    <lastBuildDate>Sun, 14 Jun 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://showme.codes/zh-cn/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>技术决策的隐藏陷阱:用持续负债消除一次性成本</title>
      <link>https://showme.codes/zh-cn/2026-06-14-one-time-cost-vs-continuous-debt/</link>
      <pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2026-06-14-one-time-cost-vs-continuous-debt/</guid>
      <description>以自建 AI Gateway 为例，聊聊技术决策中用持续性负债换一次性成本的陷阱</description>
    </item>
    <item>
      <title>从法庭到代码：谁主张谁举证的工程实践</title>
      <link>https://showme.codes/zh-cn/2025-09-13-applying-the-burden-of-proof-principle/</link>
      <pubDate>Sat, 13 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2025-09-13-applying-the-burden-of-proof-principle/</guid>
      <description>另类软件工程</description>
    </item>
    <item>
      <title>Web前端构建之依赖版本管理最佳实践</title>
      <link>https://showme.codes/zh-cn/2024-03-12-frontend-engineering/</link>
      <pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-03-12-frontend-engineering/</guid>
      <description>工程化的构建在于把握细节</description>
    </item>
    <item>
      <title>优秀的DevOps工程师应该具有什么特质？</title>
      <link>https://showme.codes/zh-cn/2024-03-12-devops-characters/</link>
      <pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-03-12-devops-characters/</guid>
      <description>&lt;p&gt;这是我最近面试时遇到的一个非常好的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在你的心目中，优秀的DevOps工程师应该是什么样的？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很长一段时间里，我没有想到这个问题。所以，当HR问起时，我边思考边回答。&lt;/p&gt;
&lt;p&gt;我已经不记得原话，本文就当作从性格角度思考“优秀的DevOps工程师应该具有什么特质”。&lt;/p&gt;
&lt;p&gt;首先，优秀的DevOps工程师，TA应该是&lt;strong&gt;严谨的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一个严谨的特质的人才会主动考虑软件工程化过程中的各种可能。&lt;/p&gt;
&lt;p&gt;其次，TA应该对手工操作产生“生理性上的不适”，即&lt;strong&gt;追求自动化极致&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一个再怎么严谨的人，如果是手工操作，面对复杂的线上环境的运维，TA的能力也是有限的。TA必须自动化所有能自动化的东西，并争取自动化所有的内容，TA才能有可能“驯服野兽”。&lt;/p&gt;
&lt;p&gt;然后，TA应该是一个节约的人，即&lt;strong&gt;节俭&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因为浪费导致企业不必要损失，是否要避免这个损失，很多时间里是一个DevOps工程师的选择问题。&lt;/p&gt;
&lt;p&gt;最后，TA应该是一个&lt;strong&gt;热爱&lt;/strong&gt;这个领域的人。我常常忘记自己对这个领域的热爱。以致于我忘记回答。&lt;/p&gt;
&lt;p&gt;只有热爱，才能产生自驱力，驱动TA去成长，去不断思考。即使你再严谨，你也有考虑不到的地方，只有热爱，TA才会探索到TA不知道他不知道的。&lt;/p&gt;
&lt;p&gt;以上只是我个人的看法，欢迎一起讨论。&lt;/p&gt;</description>
    </item>
    <item>
      <title>回滚的两种模式</title>
      <link>https://showme.codes/zh-cn/2024-03-05-rollback-pattern/</link>
      <pubDate>Tue, 05 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-03-05-rollback-pattern/</guid>
      <description>很多人只知道其中一种</description>
    </item>
    <item>
      <title>Jenkins kubernetes插件的原理</title>
      <link>https://showme.codes/zh-cn/2024-02-26-jenkins-kubernetes-plugin/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-02-26-jenkins-kubernetes-plugin/</guid>
      <description>&lt;h2 id=&#34;如何使用&#34;&gt;如何使用&lt;/h2&gt;
&lt;p&gt;使用Kubernetes插件时，我们需要做三件事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据官方文档，在Jenkins上加入kubernetes配置。&lt;/li&gt;
&lt;li&gt;在Jenkinsfile中加入kubernetes agent的申明。&lt;/li&gt;
&lt;li&gt;指定容器执行你的业务脚本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于第2点，kubernetes agent的申明又有两种方式。一种是脚本式的，代码样例如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;podTemplate&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;containers:&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;…&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;POD_LABEL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Run shell&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;container&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;mycontainer&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;echo hello world&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一种是申明式，代码样例如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pipeline&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;stages&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Run maven&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;kubernetes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;yaml&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  labels:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    app: jenkins-agent
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  - name: maven
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    image: maven:alpine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    command:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    - cat
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    tty: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  - name: busybox
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    image: busybox
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    command:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    - cat
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    tty: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;container&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;maven&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;mvn -version&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}}}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;笔者推荐使用申明式。yaml配置部分看起来并不优雅，这是另一个话题。咱们今后再讲。&lt;/p&gt;</description>
    </item>
    <item>
      <title>K8s工程化：K8s中的Java应用出现OOM后怎么办？</title>
      <link>https://showme.codes/zh-cn/2024-2-26-jvm-oom-kubernetes/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-2-26-jvm-oom-kubernetes/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;完整代码在文末&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;背景&#34;&gt;背景&lt;/h3&gt;
&lt;p&gt;前段时间，线上系统出现了两次持续时间比较长的事故。这两次事故暴露我在某些方面的不足。同时，也意识到在SRE这个领域，经验的重要性。&lt;/p&gt;
&lt;p&gt;事故过程中，我们发现大量的FullGC。当时，我们想到了要dump内存出来分析，可惜发现没有加&lt;code&gt;-XX:HeapDumpPath&lt;/code&gt;参数。同时，我们也发现，如果dump出来了，我们也没法拿到dump出来的文件。因为我们的应用是跑在K8s中的。&lt;/p&gt;
&lt;h3 id=&#34;方案调研&#34;&gt;方案调研&lt;/h3&gt;
&lt;p&gt;经复盘，我们得到一个action：在Java应用出现OOM时，将内存dump出来，并持久化，并且方便分析。&lt;/p&gt;
&lt;p&gt;这个action可以细分为两个任务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OOM时，dump内存出来；&lt;/li&gt;
&lt;li&gt;提供一种途径方便分析。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经过权衡，任务2的优先级是可以降低的。puvad只要把任务1做好就可以。所以，这两个任务最终变成：在Java应用出现OOM时，将内存dump到NAS中。&lt;/p&gt;
&lt;p&gt;笔者在网上搜索一通，看到的方案基本就是启动一个sidecar容器，与应用共享一个目录。然后监控这个目录，发现内容就上传到s3这类对象存储中。&lt;/p&gt;
&lt;p&gt;这种方案的问题在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;sidecar在传输过程，有出现问题的风险；&lt;/li&gt;
&lt;li&gt;为了OOM这个小概率事件启动一个sidecar，资源有点浪费。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;个人觉得，Java应用的Pod应该只负责将OOM时的内存dump到NAS即可，其它事情应该由其它Pod完成。&lt;/p&gt;
&lt;h3 id=&#34;具体实现&#34;&gt;具体实现&lt;/h3&gt;
&lt;p&gt;以下方案是基于Helm自动化部署。如果你使用的是其它自动化部署工具，思路大体相同。&lt;/p&gt;
&lt;h4 id=&#34;准备nfs服务&#34;&gt;准备NFS服务&lt;/h4&gt;
&lt;p&gt;这部分不是本文范畴。&lt;/p&gt;
&lt;h4 id=&#34;java应用启动时参数配置&#34;&gt;Java应用启动时参数配置&lt;/h4&gt;
&lt;p&gt;在Dockerfile中必须将变量$JAVA_OPTS加入到启动参数中。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;FROM openjdk:11.0.12-jre-buster
COPY target/app.jar /app.jar
CMD java -jar $JAVA_OPTS /app.jar
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;加入initcontainers&#34;&gt;加入InitContainers&lt;/h4&gt;
&lt;p&gt;作用：创建符合指定规则的Dump目录（注意DUMP_FOLDER变量的定义）。如下代码，在init容器启动后，它会创建目录：/nfs/dump/default/jvm-oom-example/10.233.66.38 。在应用出现OOM，内存文件会被dump在此目录下。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;initContainers:
- name: init
  image: registry.cn-shenzhen.aliyuncs.com/aliacs-app-catalog/busybox:1.30.1
  command: [&amp;#39;sh&amp;#39;, &amp;#39;-c&amp;#39;, &amp;#39;echo $DUMP_FOLDER;mkdir -p $DUMP_FOLDER&amp;#39;]
  {{- with .Values.volumeMounts }}
  volumeMounts:
  {{- toYaml . | nindent 12 }}
  {{- end }}
  env:
  - name: MY_NODE_NAME
      valueFrom:
      fieldRef:
          fieldPath: spec.nodeName
  - name: MY_POD_NAME
      valueFrom:
      fieldRef:
          fieldPath: metadata.name
  - name: MY_POD_NAMESPACE
      valueFrom:
      fieldRef:
          fieldPath: metadata.namespace
  - name: MY_POD_IP
      valueFrom:
      fieldRef:
          fieldPath: status.podIP
  - name: DUMP_FOLDER
      value: &amp;#34;/nfs/dump/$(MY_POD_NAMESPACE)/{{ include &amp;#34;app.fullname&amp;#34; . }}/$(MY_POD_IP)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;配置应用容器&#34;&gt;配置应用容器&lt;/h4&gt;
&lt;p&gt;我们要做的，其实就是设置JAVA_OPTS环境变量。这里要注意的是JAVA_OPTS可以由三部分组成的：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Kubernetes包管理器Helm的本质</title>
      <link>https://showme.codes/zh-cn/2024-2-26-theory-of-helm/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-2-26-theory-of-helm/</guid>
      <description>了解Helm的本质后，学习其它工具就很容易了</description>
    </item>
    <item>
      <title>SRE-DevOps不得不懂的：Prometheus的配置工程化</title>
      <link>https://showme.codes/zh-cn/2024-2-26-prometheus-engineering/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-2-26-prometheus-engineering/</guid>
      <description>优雅地管理Prometheus的配置</description>
    </item>
    <item>
      <title>云原生部署之Helm最佳实践</title>
      <link>https://showme.codes/zh-cn/2024-2-26-helm-best-practice/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-2-26-helm-best-practice/</guid>
      <description>Helm部署实践总结</description>
    </item>
    <item>
      <title>可落地的云原生应用规范</title>
      <link>https://showme.codes/zh-cn/2024-2-26-cloud-native-specification/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-2-26-cloud-native-specification/</guid>
      <description>这个规范可以让你的云原生实践更顺畅</description>
    </item>
    <item>
      <title>我是如何将同事的代码改成DDD风格的</title>
      <link>https://showme.codes/zh-cn/2024-02-26-ddd-coworker-code/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-02-26-ddd-coworker-code/</guid>
      <description>一次亲自代码教学</description>
    </item>
    <item>
      <title>这10年，我所经历的领域驱动设计（DDD）</title>
      <link>https://showme.codes/zh-cn/2024-02-26-ddd-ten-years/</link>
      <pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-02-26-ddd-ten-years/</guid>
      <description>10年回顾一下下</description>
    </item>
    <item>
      <title>Effortless English英文学习小结</title>
      <link>https://showme.codes/zh-cn/2024-2-19-effortless-english/</link>
      <pubDate>Mon, 19 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-2-19-effortless-english/</guid>
      <description>第一次英文学习小结，主要讲如何坚持、Effortless English课程的方法</description>
    </item>
    <item>
      <title>如何将DDD应用到基础设施设计？</title>
      <link>https://showme.codes/zh-cn/2024-01-27-ddd-infra-integrated/</link>
      <pubDate>Sat, 27 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-01-27-ddd-infra-integrated/</guid>
      <description>&lt;p&gt;前段时间在面试的时候，面试官问到：你是如何将DDD（领域驱动设计）应用到基础设施的？&lt;/p&gt;
&lt;p&gt;我很惊讶，终于有人问我这个问题了。&lt;/p&gt;
&lt;p&gt;在过去从事基础设施（DevOps、SRE、运维）的这5年里，我经常说起DDD是一种思维模式，可以应用到任何的领域，包括基础设施的设计。&lt;/p&gt;
&lt;p&gt;但是，从来没有人像这位面试官问起我具体的做法。&lt;/p&gt;
&lt;p&gt;为什么没有人问？原因大概是这两个概念通常是不会放在一起的。大多数开发不会深入理解基础设施的设计，而大多数从事基础设施设计的人是不会接触到DDD。而且，开发人员对于DDD的理解，也仅局限于用它开发业务系统。&lt;/p&gt;
&lt;p&gt;我就是那少部分人，即做基础设施的设计，又觉得自己懂DDD的人。&lt;/p&gt;
&lt;p&gt;说回问题本身。&lt;/p&gt;
&lt;h2 id=&#34;我所理解的ddd&#34;&gt;我所理解的DDD&lt;/h2&gt;
&lt;p&gt;我首先会向提问的人澄清我所理解的DDD。&lt;/p&gt;
&lt;p&gt;为什么要这样呢？很久以前有一次面试，因为我说我擅长DevOps，面试官就认为我不懂GitOps。然后在这个点上他就认为我不适合，不再问我DevOps方面的问题。我只能说没有缘份。&lt;/p&gt;
&lt;p&gt;我是这样解释DDD的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;就像开发一个象棋游戏，不论你要开发手机端，还是web端，象棋规则本身都是不变的。这个规则本身就可以理解为“领域”。
其它所有的技术（包括架构）都是具体实现，它们应该由“领域”来驱动设计。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当时的解释与以上的解释大差不差。&lt;/p&gt;
&lt;h2 id=&#34;将ddd应用到基础设施设计的具体做法&#34;&gt;将DDD应用到基础设施设计的具体做法&lt;/h2&gt;
&lt;p&gt;那么该如何将DDD应用到基础设施的设计呢？&lt;/p&gt;
&lt;p&gt;DDD的思维方式要求我们首先问：我们要设计的软件的领域（核心）问题是什么？&lt;/p&gt;
&lt;p&gt;基础设施的领域问题是什么？我的回答是&lt;strong&gt;配置&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我认为基础设施的搭建、维护，本质就是配置的设计、部署、维护。&lt;/p&gt;
&lt;p&gt;寻找领域（本质）问题的能力是DDD的核心能力。&lt;/p&gt;
&lt;p&gt;为了让读者更好理解，我们以一个一个基于云上的虚拟机的分布式系统为例。它的基础设施就包括：vpc、LB、MQ、DB等。&lt;/p&gt;
&lt;p&gt;要搭建、维护这一套基础设施。&lt;/p&gt;
&lt;p&gt;根据“配置管理是基础设施设计的核心问题”，我首先将基础设施的所有的配置放在清单代码（并不一定是一个文件）中，如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;vpc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# ....&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;LB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;DB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;MQ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;APP1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;mq_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{ MQ.addr }}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;db_host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{ DB.addr }}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;APP2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;mq_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{ MQ.addr }}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;db_host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{ DB.addr }}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app1_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{ APP1.addr }}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从清单中，你看不出它使用何种部署方式、部署顺序。你只知道APP1引用了MQ和DB，APP2会调用APP2这样纯粹的领域知识。&lt;/p&gt;
&lt;p&gt;现实通常是多个环境，所以，我一开始就会将不同环境的值从清单上抽离到独立的文件夹中。&lt;/p&gt;
&lt;p&gt;第二步，我才会考虑如何部署它们。这时，我通过两个工具实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Terraform负责云基础设施；&lt;/li&gt;
&lt;li&gt;Ansible负责业务基础设施。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ansible是可以直接读取我们的YAML格式配置文件。而Terraform代码引用YAML代码，就没有那么方便了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>程序员想告赢开发商一户一表违约</title>
      <link>https://showme.codes/zh-cn/2024-01-22-owners-and-developers-fight-lawsuit/</link>
      <pubDate>Mon, 22 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-01-22-owners-and-developers-fight-lawsuit/</guid>
      <description>一名程序员与开发商打官司的故事</description>
    </item>
    <item>
      <title>Bazel使用案例：构建Springboot工程</title>
      <link>https://showme.codes/zh-cn/2024-01-19-bazel-springboot/</link>
      <pubDate>Fri, 19 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-01-19-bazel-springboot/</guid>
      <description>&lt;p&gt;本文是关于如何使用Bazel搭建Springboot 3.1.0工程（基于JDK17）。为什么使用Bazel，而不是使用Maven或者Gradle？可以看我之前关于Bazel的介绍文章。&lt;/p&gt;
&lt;h2 id=&#34;前期准备&#34;&gt;前期准备&lt;/h2&gt;
&lt;p&gt;在根目录加入&lt;code&gt;.bazelversion&lt;/code&gt;文件，并加入&lt;code&gt;6.2.0&lt;/code&gt;，指定当前工程使用的Bazel的版本。这样，Bazel命令自动使用该版本的Bazel进行构建。&lt;/p&gt;
&lt;p&gt;在根目录加入&lt;code&gt;.bazelrc&lt;/code&gt;文件，并指定构建和测试时使用JDK17，内容如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;build --java_language_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; --java_runtime_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; --tool_java_language_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; --tool_java_runtime_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;test&lt;/span&gt;  --java_language_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; --java_runtime_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; --tool_java_language_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; --tool_java_runtime_version&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;17&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;外部依赖准备&#34;&gt;外部依赖准备&lt;/h2&gt;
&lt;p&gt;在根目录中创建以下两个文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WORKSPACE：在Bazel中，所有的外部依赖统一定义WORKSPACE文件中；&lt;/li&gt;
&lt;li&gt;BUILD.bazel：内容留空即可，用于告诉Bazel当前目录也是一个Package。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bazel本身是支持多语言的。所以，我们需要特定语言的rule来帮助我们在WORKSPACE中定义外部依赖。&lt;/p&gt;
&lt;p&gt;对于Java工程，我们使用&lt;a href=&#34;https://github.com/bazelbuild/rules_jvm_external&#34;&gt;rules_jvm_external&lt;/a&gt;进行外部依赖的管理。它的使用步骤如下：&lt;/p&gt;
&lt;h3 id=&#34;步骤1在workspace中增加rules_jvm_external配置&#34;&gt;步骤1：在WORKSPACE中增加rules_jvm_external配置&lt;/h3&gt;
&lt;p&gt;以下配置指定了rules_jvm_external的下载位置，并进行rule的初始化：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@bazel_tools//tools/build_defs/repo:http.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;http_archive&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RULES_JVM_EXTERNAL_TAG&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;4.5&amp;#34;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RULES_JVM_EXTERNAL_SHA&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;sha hash value&amp;gt;&amp;#34;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;http_archive&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rules_jvm_external&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;n&#34;&gt;strip_prefix&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rules_jvm_external-&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%s&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RULES_JVM_EXTERNAL_TAG&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;n&#34;&gt;sha256&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RULES_JVM_EXTERNAL_SHA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;n&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://github.com/bazelbuild/rules_jvm_external/archive/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%s&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.zip&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RULES_JVM_EXTERNAL_TAG&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@rules_jvm_external//:repositories.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rules_jvm_external_deps&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rules_jvm_external_deps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@rules_jvm_external//:setup.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rules_jvm_external_setup&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rules_jvm_external_setup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@rules_jvm_external//:defs.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;maven_install&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;maven_install&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;n&#34;&gt;artifacts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;c1&#34;&gt;# The project&amp;#39;s dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;s2&#34;&gt;&amp;#34;junit:junit:4.12&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;s2&#34;&gt;&amp;#34;org.hamcrest:hamcrest-library:1.3&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;n&#34;&gt;repositories&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;c1&#34;&gt;# Private repositories are supported through HTTP Basic auth  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;# &amp;#34;http://username:password@localhost:8081/artifactory/my-repository&amp;#34;,    &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;s2&#34;&gt;&amp;#34;https://maven.aliyun.com/repository/public&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;以上采用了非Bzlmod的管理rule。&lt;/p&gt;</description>
    </item>
    <item>
      <title>DevOps架构师是如何看待Github Actions的共享制品解决方案的？</title>
      <link>https://showme.codes/zh-cn/2024-01-12-github-actions-share-things/</link>
      <pubDate>Tue, 16 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-01-12-github-actions-share-things/</guid>
      <description>只要对这一问题深入理解，所有的平台一通百通。</description>
    </item>
    <item>
      <title>Ebean：一款被低估的ORM框架</title>
      <link>https://showme.codes/zh-cn/2024-01-02-ebean/</link>
      <pubDate>Tue, 02 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-01-02-ebean/</guid>
      <description>&lt;h1 id=&#34;orm框架为什么不香&#34;&gt;ORM框架为什么不香？&lt;/h1&gt;
&lt;h2 id=&#34;对orm框架的偏见&#34;&gt;对ORM框架的偏见&lt;/h2&gt;
&lt;p&gt;看了一些MyBaties与Hibernate进行对比的文章。可能是因为一些Hibernate历史原因，国内对于Hibernate普遍存在偏见，我摘抄了几点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;hibernate是全自动，而mybatis是半自动&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;hibernate完全可以通过对象关系模型实现对数据库的操作，拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射，对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;sql直接优化上，mybatis要比hibernate方便很多&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;由于mybatis的sql都是写在xml里，因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的，无法直接维护sql&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;应用场景&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;MyBatis 适合需求多变的互联网项目，例如电商项目、金融类型、旅游类、售票类项目等。
Hibernate 适合需求明确、业务固定的项目，例如 OA 项目、ERP 项目和 CRM 项目等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;也不知道是不是因为这些对Hibernate的偏见，导致大家对ORM框架也普遍存在偏见。&lt;/p&gt;
&lt;p&gt;现状是不论大小公司，国内清一色地使用MyBaties。有时，我都不敢说，我喜欢使用ORM框架。&lt;/p&gt;
&lt;p&gt;本文并不是一篇为Hibernate洗地的文章，而是介绍另一款比较小众的ORM框架：Ebean。&lt;/p&gt;
&lt;h2 id=&#34;领域问题分析&#34;&gt;领域问题分析&lt;/h2&gt;
&lt;p&gt;介绍Ebean之前，我们需要弄清楚一个问题：为什么会有MyBaties和ORM这些框架？对于这个问题，我们无从下手，那么，我们将问题倒置：如果没有这些框架，会怎么样？&lt;/p&gt;
&lt;p&gt;问题倒置的好处是我们立马就有了可下手的方向。我们找到了不使用框架的情况下，Java代码与数据库进行交互的代码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;viewTable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Connection&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;con&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;throws&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SQLException&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Statement&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stmt&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;con&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;createStatement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ResultSet&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;executeQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;coffeeName&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;COF_NAME&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;supplierID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getInt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;SUP_ID&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;price&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getFloat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;PRICE&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sales&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getInt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;SALES&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;total&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getInt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;TOTAL&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SQLException&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JDBCTutorialUtilities&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;printSQLException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样的代码存在什么问题呢？&lt;/p&gt;</description>
    </item>
    <item>
      <title>PostgreSQL15 Public Schema没有权限问题解决</title>
      <link>https://showme.codes/zh-cn/2024-01-01-postgresql15-public-schema-permission/</link>
      <pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2024-01-01-postgresql15-public-schema-permission/</guid>
      <description>&lt;p&gt;PostgreSQL15后，Public Schema的权限发生了变化：普通用户默认在Public schema中不再有CREATE的权限。当他们执行CREATE TABLE命令时，就会报以下错误：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ERROR: permission denied for schema public
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，我们需要为该用户再分配权限。命令如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;GRANT USAGE, CREATE on SCHEMA PUBLIC to &amp;lt;username&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因为某些应用程序的sql的migration是自动的，你可能还需要为用户分配更多权限，命令如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;grant all on database &amp;lt;db_name&amp;gt; to &amp;lt;username&amp;gt;;
ALTER DATABASE  &amp;lt;db_name OWNER to  &amp;lt;username&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    <item>
      <title>使用Mxroute和Sendgrid实现邮箱服务和邮件发送服务</title>
      <link>https://showme.codes/zh-cn/2023-12-31-email-service-setup/</link>
      <pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-12-31-email-service-setup/</guid>
      <description>&lt;p&gt;最近在做一个产品，需要用邮箱服务和邮件发送服务。本文以&lt;a href=&#34;https://mxroute.com/&#34;&gt;Mxroute&lt;/a&gt;和&lt;a href=&#34;https://sendgrid.com/&#34;&gt;Sendgrid&lt;/a&gt;为例介绍邮箱服务和邮件发送服务的配置。但是所有的这类产品，思路都应该是一致的。&lt;/p&gt;
&lt;p&gt;Mxroute是邮箱服务，类似Web服务，只不过，它是专门为邮箱协议而设计的。Sendgrid就是邮件发送服务，也就是你需要批量向一堆邮箱发送邮件时，就需要用邮件发送服务。本质上Sendgrid与Mxroute是两回事。但是，通常我们先配置邮箱服务，再配置邮件发送服务。&lt;/p&gt;
&lt;p&gt;本文只为记录一下，将来忘记了可以重新拾起。&lt;/p&gt;
&lt;h2 id=&#34;配置邮箱服务&#34;&gt;配置邮箱服务&lt;/h2&gt;
&lt;p&gt;首先，邮箱服务需要MX类型的域名解析记录。这能让邮箱服务能在整个互联网被解析到。&lt;/p&gt;
&lt;p&gt;每一个在Mxroute付费的用户，都会被分配到一个独立的MX域名，如first.mxrouting.net 。他们应该会发邮件给你，你需要留意。&lt;/p&gt;
&lt;p&gt;在Mxroute上配置的步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建一个域名。比如example.com。如果你使用的是子域名，也可以是mail.example.com。
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/mail-service-setup-1.png&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;拿到DKIM Keys等信息。在Mxroute的左边菜单中可以找到链接
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/mail-service-setup-2.png&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在域名提供商中，再配置以下这些DNS记录&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;strong&gt;记录类型&lt;/strong&gt;&lt;/th&gt;
					&lt;th&gt;&lt;strong&gt;name&lt;/strong&gt;&lt;/th&gt;
					&lt;th&gt;&lt;strong&gt;content&lt;/strong&gt;&lt;/th&gt;
					&lt;th&gt;&lt;strong&gt;优先级&lt;/strong&gt;&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;MX&lt;/td&gt;
					&lt;td&gt;_dmac&lt;/td&gt;
					&lt;td&gt;v=DMARC1; p=none&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CNAME&lt;/td&gt;
					&lt;td&gt;mail&lt;/td&gt;
					&lt;td&gt;&amp;lt;mxroute分配的独立MX域名&amp;gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MX&lt;/td&gt;
					&lt;td&gt;mail&lt;/td&gt;
					&lt;td&gt;&amp;lt;mxroute分配的独立MX域名&amp;gt;&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MX&lt;/td&gt;
					&lt;td&gt;mail&lt;/td&gt;
					&lt;td&gt;&amp;lt;mxroute分配的独立MX域名&amp;gt;&lt;/td&gt;
					&lt;td&gt;20&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;mail&lt;/td&gt;
					&lt;td&gt;&amp;lt;从mxroute上获取&amp;gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;x._domainkey&lt;/td&gt;
					&lt;td&gt;&amp;lt;从mxroute上获取&amp;gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你使用的是子域名，那么，还需要在 _dmas和x.domainkey 后加上 .&lt;subdomain&gt; 。例如mail子域名，就是 x.domainkey.mail。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过以上配置，只证明我们的“邮箱服务器”已经配置好了。现在在上面创建账号，并进行测试了。如果你可以向这个账号收发邮件，就证明，你的邮箱服务已经配置完成。&lt;/p&gt;
&lt;h2 id=&#34;配置邮件发送服务&#34;&gt;配置邮件发送服务&lt;/h2&gt;
&lt;p&gt;当你有了一个邮箱账号后，你就可以Sendgrid上配置了。登录后，从左边菜单“Senders”进入列表页。
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/mail-service-setup-3.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;然后再点击按钮“Create new Sender”，即可创建。这部分就不细说了。因为太简单了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用Google OSV工具扫描依赖安全漏洞</title>
      <link>https://showme.codes/zh-cn/2023-12-25-google-osv/</link>
      <pubDate>Mon, 25 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-12-25-google-osv/</guid>
      <description>&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/xkcd-dependency.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;安全漏洞是软件工程化能力的试金石&#34;&gt;安全漏洞是软件工程化能力的试金石&lt;/h2&gt;
&lt;p&gt;2021年年底，Log4j的漏洞陆续被公开。因为该框架被大量的开源软件依赖，所以，漏洞影响面非常大。&lt;/p&gt;
&lt;p&gt;面对这个漏洞，我们遇到的第一个问题是：如何知道我们哪些工程使用了Log4j？&lt;/p&gt;
&lt;p&gt;在我看来，这个漏洞是企业软件工程化的一颗非常好的试金石。因为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如何第一时间了解到这个漏洞，反应这家企业的安全能力；&lt;/li&gt;
&lt;li&gt;如何第一时间能找到所有使用了Log4j的位置，体现了这家企业第三方软件依赖管理能力；&lt;/li&gt;
&lt;li&gt;替换Log4j的速度，体现企业的持续集成、持续部署的能力。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;google的开源软件安全漏洞扫描工具&#34;&gt;Google的开源软件安全漏洞扫描工具&lt;/h2&gt;
&lt;p&gt;今天介绍的OSC-Scanner，能加强我们第1项和第2项能力。&lt;/p&gt;
&lt;p&gt;OSV-Scanner是Google在2022年12月13日推出的一款免费的安全扫描工具。它具有以下特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持多生态系统，包括：Go、PyPI、RubyGens、Linux、Maven等16个生态系统；&lt;/li&gt;
&lt;li&gt;同时支持直接依赖的扫描和间接依赖的扫描；&lt;/li&gt;
&lt;li&gt;采用标准的漏洞记录格式；&lt;/li&gt;
&lt;li&gt;从当前最大的开源软件漏洞数据库（https://osv.dev/）获取信息。这也是DenpencyTrack和Flutter安全工具的漏洞数据库。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;OSV-Scanner是一款命令行工具，我们可以将它集成到我们的构建工具或者CICD Pipeline中。目前它已经被集成到Scorecard中。Scorecard是一款为开发源软件的安全健康度打分的开源软件。我们可以在Github Actions中使用它：https://github.com/ossf/scorecard/tree/main?tab=readme-ov-file#scorecard-github-action&lt;/p&gt;
&lt;h2 id=&#34;osv-scanner的安装&#34;&gt;OSV-Scanner的安装&lt;/h2&gt;
&lt;p&gt;Windows：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;scoop install osv-scanner
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mac Homebrew:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install osv-scanner
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;也可以直接下载二进制包：https://github.com/google/osv-scanner/releases&lt;/p&gt;
&lt;p&gt;具体安装文档：https://google.github.io/osv-scanner/installation/&lt;/p&gt;
&lt;h2 id=&#34;osv-scanner的使用&#34;&gt;OSV-Scanner的使用&lt;/h2&gt;
&lt;p&gt;Keras是一个使用Python编写的开源人工神经网络库。我们以它为例。命令行里运行以下命令：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./osv-scanner_1.3.6_linux_amd64 --format json keras/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/osv-scanner-keras.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;输出内容说明：keras存在一个“潜在内存泄漏”的漏洞。&lt;/p&gt;
&lt;p&gt;当拿到json结果后，我们的DevOps平台就可以进行一些告警监控的操作。&lt;/p&gt;
&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;
&lt;p&gt;osv-scanner目前需要连osv.dev，才能使用。但是，已经开放实验功能，允许用户离线使用osv-scanner。这是自建DevOps平台的福音！&lt;/p&gt;</description>
    </item>
    <item>
      <title>不出事故，没有人知道你重要</title>
      <link>https://showme.codes/zh-cn/2023-11-21-incident-important-person/</link>
      <pubDate>Tue, 21 Nov 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-11-21-incident-important-person/</guid>
      <description>&lt;p&gt;有同学在知乎上提问：“线上无事故，运维还重要吗？”，描述如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本人运维行业，本部门在近几年一直保持效率增长且极少出现重大saas生产事故，并且为其他部门输出提升方法以及友好协同提升，但是最近从各层面接到反馈说对运维的投入减少，着实想不通，线上出了事故要运维背锅，产品出了bug要运维陪着到最晚，为什么把线上环境搞得稳定了，却不重视运维岗了？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是原贴：https://www.zhihu.com/question/497361582&lt;/p&gt;
&lt;p&gt;以上提问的是一个运维的同学。言下之义是不出事故，没有人知道运维重要。&lt;/p&gt;
&lt;p&gt;这位同学的的感受，过去几年，我感同深受。我相信因为这个标题而点进这篇博客的同学，也有同样的感受。&lt;/p&gt;
&lt;p&gt;但是，为什么出事故后，是运维重要呢？而不是测试、开发或者手机端开发呢？&lt;/p&gt;
&lt;p&gt;通常是因为运维这个角色：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;线上环境，他们最清楚，通常也只有他们有权限操作线上环境，可以紧急加一个数据库索引；&lt;/li&gt;
&lt;li&gt;他们掌握了部署能力，可以发起回滚操作；&lt;/li&gt;
&lt;li&gt;有权限查看各个组件的情况，并诊断根因；&lt;/li&gt;
&lt;li&gt;为团队准备基础设施能力，如金丝雀发布能力；&lt;/li&gt;
&lt;li&gt;搭建告警监控系统、CMDB、DevOps平台等。&lt;/li&gt;
&lt;li&gt;等等&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是，这些与是否出事故，有多大的关联性呢？我们应该统计各种事故的根因的类型的比例，才有答案。&lt;/p&gt;
&lt;p&gt;就目前而言，我们并不能说因为我们看重运维，就不出事故。&lt;/p&gt;
&lt;p&gt;以上的问题是从个人感受出发的提问。只是更深层次问题的表象。&lt;/p&gt;
&lt;p&gt;从企业层面上，我的疑问是：为什么在企业里，稳定性建设通常都是一阵阵的。即出一次事故，就立个项，就加班加点去完成“稳定性”项目。&lt;/p&gt;
&lt;p&gt;比起讨论个人感受，从企业层面讨论这个问题，似乎更有趣。&lt;/p&gt;
&lt;p&gt;其实，除了稳定性，软件的质量建设也是一阵阵的。想想，不是吗？不出Bug，没有人知道测试重要。&lt;/p&gt;
&lt;p&gt;也许这是所有企业的正常表现。就像人的身体，痛风（一种慢性病）不发作时，你是不会感受它的存在，也自然就不会想到要去治疗或者预防它。然而，如果平时不注意饮食和锻炼，痛风经常复发。&lt;/p&gt;
&lt;p&gt;线上事故就如同企业的痛风。企业应对“痛风”，容易好了伤疤忘了痛。&lt;/p&gt;
&lt;p&gt;虽说可能是所有企业的正常表现，但不是一种健康的表现。&lt;/p&gt;
&lt;p&gt;预防痛风，只能通过健康的生活方式如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;限制或避免饮酒，尤其是啤酒。&lt;/li&gt;
&lt;li&gt;限制或者避免饮用含糖饮料，尤其是含高果糖玉米糖浆的饮料。&lt;/li&gt;
&lt;li&gt;限制肉类摄入量，尤其是红肉、内脏和海鲜。&lt;/li&gt;
&lt;li&gt;保持健康的体重。如果您需要减肥，请避免断食或过快地减肥，因为这可能会暂时增加尿酸水平。&lt;/li&gt;
&lt;li&gt;增加水和低脂乳制品的摄入量。这些有预防痛风的作用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个人应对痛风的健康表现应该是采用健康的生活方式。&lt;/p&gt;
&lt;p&gt;说回企业的稳定性建设，也是一样的道理。&lt;/p&gt;
&lt;p&gt;稳定性不是通过“一阵阵的运动”或者“一阵阵的表演”来建设的，而是通过平时健康的企业活动来实现（我无意指导别人的企业，这只是我个人的思考）。&lt;/p&gt;
&lt;p&gt;当然，现实中，对于有些人，要维持健康的生活方式是一件很难的事情（想想有身边有多少人做到早睡早起），而另一些人是一件很自然的事。为什么呢？&lt;/p&gt;
&lt;p&gt;相同的，一家企业为什么无法自然地做到健康的企业活动？一定要出事故，才知道X的重要性呢？（X代表任何东西）&lt;/p&gt;
&lt;p&gt;这个问题就很大了。希望对各位读者有启发。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Bazel作为构建工具之王，将会颠覆你对CI的认知</title>
      <link>https://showme.codes/zh-cn/2023-11-20-bazel-king-of-build-tool/</link>
      <pubDate>Mon, 20 Nov 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-11-20-bazel-king-of-build-tool/</guid>
      <description>&lt;p&gt;说到构建工具，不同语言技术栈的人，想起的构建工具不同。&lt;/p&gt;
&lt;p&gt;Java程序员想到的是Maven，前端程序员想的是NPM或者Webpack、Android程序员想到的是Gradle、Rust程序想到的是Cargo、C++程序员想到的是Make等等。&lt;/p&gt;
&lt;p&gt;然而这些工具在Bazel面前，层次有些低。所以，我愿称Bazel是构建工具之王。&lt;/p&gt;
&lt;p&gt;P.S. Android平台的构建，2020年已经开始了迁移到Bazel的工作。 具体地址：https://blog.bazel.build/2020/11/12/aosp_migrating_to_bazel.html&lt;/p&gt;
&lt;h2 id=&#34;bazel介绍&#34;&gt;Bazel介绍&lt;/h2&gt;
&lt;p&gt;Bazel是Google在2015年开源的一款构建工具。&lt;/p&gt;
&lt;p&gt;目前使用Bazel的知名公司有：Esty、Canva、Databricks、Dropbox、Huawei、Line、LinkedIn、Stripe、Twitter、Tinder、Uber、VMware、Wix等。具体可以看：https://bazel.build/community/users 。&lt;/p&gt;
&lt;p&gt;其中Twitter是从自家的Pants迁移到的Bazel的，具体迁移过程介绍：https://opensourcelive.withgoogle.com/events/bazelcon2020/watch?talk=day1-talk2&lt;/p&gt;
&lt;p&gt;Facebook使用的是其自研的&lt;a href=&#34;https://engineering.fb.com/2023/04/06/open-source/buck2-open-source-large-scale-build-system/&#34;&gt;Buck2&lt;/a&gt;，但是，其与Bazel使用的是相同的远程执行的API。&lt;/p&gt;
&lt;p&gt;除了公司，某些著名的开源软件也使用Bazel构建，包括自动化测试领域的Selenium，AI领域的TensorFlow，容器编排领域的Kubernetes等。具体还有：https://bazel.build/community/users#open-source-projects-using-Bazel&lt;/p&gt;
&lt;p&gt;相对于其它构建工具，它的显著的特点有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持多语言；&lt;/li&gt;
&lt;li&gt;支持远程分布式构建；&lt;/li&gt;
&lt;li&gt;支持增量构建；&lt;/li&gt;
&lt;li&gt;支持强大的密闭性；&lt;/li&gt;
&lt;li&gt;支持构建缓存；&lt;/li&gt;
&lt;li&gt;支持并行构建。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;假设存在一个复杂的软件工程&#34;&gt;假设存在一个复杂的软件工程&lt;/h2&gt;
&lt;p&gt;假设存在一个软件工程中，它包含5部分：Web前端、Android端、Java后端、Go后端、嵌入式端。&lt;/p&gt;
&lt;p&gt;作为Java后端的程序员，他们修改了一个API。但是他作为个人，他无法预知到底发生了哪些影响。&lt;/p&gt;
&lt;p&gt;所以，他把这个问题交给了持续集成（CI），让它去发现集成问题。&lt;/p&gt;
&lt;p&gt;在过去很长一段时间里，行业里只有一种CI模式，我称之为传统的CI模式。&lt;/p&gt;
&lt;p&gt;殊不知，还有另一种模式。&lt;/p&gt;
&lt;h2 id=&#34;传统的ci模式&#34;&gt;传统的CI模式&lt;/h2&gt;
&lt;p&gt;目前行业里比较传统的CI架构，通常如下：
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/ci-traditional-architechure.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;在这样的架构下，实现CI的步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开发人员提交代码；&lt;/li&gt;
&lt;li&gt;Gitlab检测到开发人员提交代码，然后触发Jenkins controller执行；&lt;/li&gt;
&lt;li&gt;Jenkins controller根据该代码仓库预先设计的pipeline执行；&lt;/li&gt;
&lt;li&gt;Jenkins controller根据pipeline中的任务所需要的构建环境，将任务分配给不同的Jenkins agent；&lt;/li&gt;
&lt;li&gt;在agent构建完成后，将制品release到制品仓库中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果开发者希望验证自己写的代码，就必须将代码commit到Gitlab中。因为整个验证环境被定义在CI环境的Pipeline中。而且这个过程，越大的工程，集成速度越慢。开发者也无法在本地进行全量验证。&lt;/p&gt;
&lt;p&gt;作为Pipeline的维护者，他需要清楚知道哪些任务是可以并行执行的，并手工配置并行，这样才能加快构建速度。比如前端构建和后端构建可以并行进行。&lt;/p&gt;
&lt;p&gt;也就是说在传统的CI模式下，开发者的效率会随着软件的规模越大而降低。换句话，这样的模式，开发效率无法scale。&lt;/p&gt;
&lt;h2 id=&#34;案例&#34;&gt;案例&lt;/h2&gt;
&lt;p&gt;希望以下案例可以给你一个感性的认知。下图是Google在2010年到2015年的周commit数量。绿线代表commit总数，黄线是人数。我们取离我们最近的2015年的数据来讨论。2015年的代码量如下：
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/linesofcodeofgoogle.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;在这个代码量下，每周能达到300左右的commit。如下图：
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/google-commit-per-week.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;根据持续集成的原则，每一个commit都必须构建通过。20亿行代码一次全量构建需要多久？&lt;/p&gt;
&lt;p&gt;我们以一个开源项目作参考。apitable是一个开源的数据表格项目，它有200万左右的代码，全量构建一次需要20分钟左右。那么，根据不准确的类推，20亿行代码，全量构建一次需要：&lt;code&gt;20/2,000,000 * 2000,000,000=200,000,000&lt;/code&gt;分钟，也就是13天左右。&lt;/p&gt;
&lt;p&gt;在传统的CI模式下，是尽量避免执行全量构建这样庞大的代码量的。所以，传统CI模式下，通常是多仓库模式管理代码。&lt;/p&gt;
&lt;p&gt;那么Bazel呢？Bazel如果真要构建这样庞大的代码量，估计也够呛。但是由于Bazel天然支持并行构建、构建缓存和增量构建，所以，Bazel通常不会遇到真正意义的全量构建的情况。&lt;/p&gt;
&lt;h2 id=&#34;为什么其它公司不使用bazel&#34;&gt;为什么其它公司不使用Bazel&lt;/h2&gt;
&lt;p&gt;也许有人会问：为什么阿里2018年新增的代码行(&lt;a href=&#34;https://zhuanlan.zhihu.com/p/54435171&#34;&gt;https://zhuanlan.zhihu.com/p/54435171&lt;/a&gt;)就有12亿，不也没有使用Bazel吗？&lt;/p&gt;
&lt;p&gt;这个是一个好问题。&lt;/p&gt;
&lt;p&gt;但是，无法简单的回答这个问题，而是需要深入到各自组织内部才能分析清楚。个人觉得可以从以下维度分析：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在代码仓库上工作的人员的规模：同样的代码量，不同的组织需要不同数量的人维护；&lt;/li&gt;
&lt;li&gt;代码管理方式：阿里使用多仓库的管理办法，不需要统一的版本号；&lt;/li&gt;
&lt;li&gt;持续集成的程度不同：阿里可能不需要对每一个commit跑一次全量。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;为什么bazel会颠覆你对ci的认知&#34;&gt;为什么Bazel会颠覆你对CI的认知&lt;/h2&gt;
&lt;p&gt;Bazel是如何解决传统CI模式下开发效率无法scale的问题呢？其主要通过它的六个特性来解决。&lt;/p&gt;
&lt;p&gt;首先，Bazel支持远程分布式构建。&lt;/p&gt;
&lt;p&gt;在一个使用Bazel构建的仓库中，开发者写好代码后，不用commit代码到Git仓库，只要在本地命令行执行&lt;code&gt;bazel run --remote_executor=grpc://localhost:8980 //...&lt;/code&gt; ，代码仓库中所有构建和测试任务都将运行在远程执行服务器。远程执行服务器越多，构建速度越快。&lt;/p&gt;
&lt;p&gt;这一特性可以明显地提高开发者本地的开发效率。因为开发者在本地就可以执行全量构建和全量测试。&lt;/p&gt;
&lt;p&gt;传统CI模式下，无法提升开发者本地的开发效率。&lt;/p&gt;
&lt;p&gt;第二，Bazel支持增量构建和增量测试（精准测试）。&lt;/p&gt;
&lt;p&gt;开发者在本地执行build命令时，Bazel检测出修改了a.java文件，所以，Bazel只将构建a.java的任务及其相关的构建任务给远程执行服务器执行。这就是增量构建。&lt;/p&gt;
&lt;p&gt;如果开发者执行test命令，Bazel则能检测出被影响的测试，然后只运行这些测试。其实这就是精准测试了。在Bazel中，精准测试实现起来并不难。&lt;/p&gt;
&lt;p&gt;传统CI模式下，它是不关心增量构建和增量测试的。所以，每次运行都是全量。这是一种极大的浪费。&lt;/p&gt;</description>
    </item>
    <item>
      <title>听说最近的事故都是循环依赖导致的？</title>
      <link>https://showme.codes/zh-cn/2023-11-11-sre-dimond-incident/</link>
      <pubDate>Sat, 11 Nov 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-11-11-sre-dimond-incident/</guid>
      <description>&lt;p&gt;​年底了，事故频发。但是都听说是因为循环依赖导致。所以，我决定来写写依赖管理领域中，通常不被重视的循环依赖问题。&lt;/p&gt;
&lt;h2 id=&#34;循环依赖circular-dependencies的定义&#34;&gt;循环依赖(circular dependencies)的定义&lt;/h2&gt;
&lt;p&gt;来自维基百科的定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在软件工程中，循环依赖是两个或多个模块之间的关系，这些模块直接或间接地相互依赖才能正常运行。此类模块也称为相互递归。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;循环依赖是依赖管理领域中经常出现的一种现象，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%962.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;循环依赖的不同层次以及后果&#34;&gt;循环依赖的不同层次以及后果&lt;/h2&gt;
&lt;p&gt;循环依赖可以发生在两个层次：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;源码之间相互引用依赖；&lt;/li&gt;
&lt;li&gt;服务之间相互调用依赖。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;源码之间产生循环依赖带来的问题是：构建工具不知道应该从何处开始构建。因为构建工具无从下手。&lt;/p&gt;
&lt;p&gt;构建工具底线这时就体现出来了，如果它可以忽略其中一个节点，“勉强”构建出一个制品，那么这个制品估计你也不敢用，因为该制品存在不确定性。这是源码层次中，循环依赖的后果。&lt;/p&gt;
&lt;p&gt;而服务之间调用的循环依赖就更麻烦了。平时非常难发现，是不会出事故的，但一出事故就会雪崩。&lt;/p&gt;
&lt;p&gt;因为你无法单独启动循环依赖中的任何一个服务，而循环依赖中的任何一个服务挂了，其它所有的节点都会同时挂。&lt;/p&gt;
&lt;p&gt;循环依赖的环越大，影响面越大。&lt;/p&gt;
&lt;h2 id=&#34;为什么会出现循环依赖&#34;&gt;为什么会出现循环依赖&lt;/h2&gt;
&lt;p&gt;既然循环依赖从任何一个节点都法进行构建或者启动，那么它又为什么会产生？难道定义依赖的人不知道吗？Code Review的人不知道吗？&lt;/p&gt;
&lt;p&gt;因为循环依赖是软件系统在长时间发展中，不加以合理的依赖管理所导致的。如下图。一开始只是B的V1版本依赖A的V1版本，最后变成B的V2版本与A的V3版本相互依赖。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%963.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;软件系统发展的时间足够长，交接的人数足够多，软件依赖关系的规模早就超出了人类能处理的限度。&lt;/p&gt;
&lt;h2 id=&#34;如何从根上就避免循环依赖&#34;&gt;如何从根上就避免循环依赖&lt;/h2&gt;
&lt;p&gt;用人力的办法解决循环依赖，是不现实的。依赖管理这种吃力又不讨好，还拿不上台面的术语，没有人会去做。更不会得到KPI的“赏识”。&lt;/p&gt;
&lt;p&gt;可以预料得到，如果对依赖管理治理不当，那么每几年，就可能出现一次循环依赖的事故。不发生事故的原因可能只有一个：软件的规模还不够大。&lt;/p&gt;
&lt;p&gt;所以，我们必须想办法从根上避免循环依赖，即从依赖的定义的地方开始治理。&lt;/p&gt;
&lt;p&gt;源代码层次，构建工具就可以帮我们依赖。只要出现循环依赖，构建就不通过。例如Make工具：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;make: Circular main.asm.o &amp;lt;- main.asm dependency dropped.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，如果是多仓库管理源代码的模式下，你可能还是很难避免循环依赖的情况。如果是单仓库，就不会出现这样的情况。&lt;/p&gt;
&lt;p&gt;而服务之间的调用关系，在不同的公司定义的位置不一样。这也决定了查找循环依赖的成本。&lt;/p&gt;
&lt;p&gt;服务之间的调用关系的定义，本质上属于配置管理的范畴。因为你总要在某个地方定义这些依赖。&lt;/p&gt;
&lt;p&gt;服务之间的循环依赖查找，本质上就是配置管理领域中，查找配置之间的相互引用关系。&lt;/p&gt;
&lt;p&gt;这说明配置管理的方式决定了查找服务之间循环依赖的成本。&lt;/p&gt;
&lt;p&gt;什么样的配置管理方式才能低成本的实现查找循环依赖呢？&lt;/p&gt;
&lt;p&gt;在这里，我提出我的方案：使用Jsonnet定义所有的配置（某些case无法覆盖），然后通过Bazel进行构建。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jsonnet是一门专为配置定义而设计语言，语言只有一页A4纸；&lt;/li&gt;
&lt;li&gt;Bazel是一款支持增量构建、分布构建、构建缓存、支持多语言的构建工具。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过这个方案，Bazel会自动构建一个软件依赖关系图，同时检测其中是否存在循环关系。只要循环关系存在，构建就不通过，当然就无法上线了。这样就从根上就避免了循环依赖。&lt;/p&gt;
&lt;p&gt;如果想了解更多具体的落地方式，请关注我。并转发本文，让更多人看到这种神奇的配置管理方式。&lt;/p&gt;</description>
    </item>
    <item>
      <title>反思一次效能提升</title>
      <link>https://showme.codes/zh-cn/2023-10-23-a-case-of-productivity/</link>
      <pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-10-23-a-case-of-productivity/</guid>
      <description>一个案例</description>
    </item>
    <item>
      <title>Shell Bash能力对于运维很重要吗？</title>
      <link>https://showme.codes/zh-cn/2023-10-22-shell-bash-non-important/</link>
      <pubDate>Sun, 22 Oct 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-10-22-shell-bash-non-important/</guid>
      <description>不重要是假的，重要也是假的</description>
    </item>
    <item>
      <title>微服务架构下的配置治理模式</title>
      <link>https://showme.codes/zh-cn/2023-06-12-patterns-of-configuration-under-microservice/</link>
      <pubDate>Mon, 12 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-06-12-patterns-of-configuration-under-microservice/</guid>
      <description>&lt;p&gt;微服务被滥用是不争的事实。被滥用的同时，很少人留意到它所带来的配置治理的问题。本文我们介绍两种常见的治理模式。&lt;/p&gt;
&lt;h2 id=&#34;基于common的配置治理模式&#34;&gt;基于common的配置治理模式&lt;/h2&gt;
&lt;p&gt;当微服务数量多时，开发人员倾向于创建这样的配置文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;common-redis.json&lt;/li&gt;
&lt;li&gt;common-mysql.json&lt;/li&gt;
&lt;li&gt;common-mq.json&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;甚至还有会有common.json这种从名字上就不知道它的作用的配置。但是，几乎所有的微服务都会引用common.json这个配置。原因如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在common.json可以无脑增加配置项，不需要改业务代码；&lt;/li&gt;
&lt;li&gt;配置项可能是被n个微服务引用，为了这一个配置项，又新增一个配置文件，不值得。common.json看起来是最合适的。反正每个微服务都已经引用了common.json。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;![](/assets/images/Pasted image 20221220161521.png)&lt;/p&gt;
&lt;p&gt;基于common的配置，在写入配置项的时候是爽了，但是，也带来了问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;改了common.json文件中的配置后，很难确认这个变更会影响到哪里，因为每个微服务都引用了common.json；&lt;/li&gt;
&lt;li&gt;common.json会变得越来越大；&lt;/li&gt;
&lt;li&gt;并不是每次发布，都发布所有的微服务。所以，微服务A可能采用的是common.json的v1版本，而微服务B可能采用的是common.json的v2版本。&lt;/li&gt;
&lt;li&gt;随着时间迁移，谁也不敢动common.json中的配置，即使有些配置项已经很久没有被使用了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;基于服务级别粒度的配置治理模式&#34;&gt;基于服务级别粒度的配置治理模式&lt;/h2&gt;
&lt;p&gt;基于服务级别粒度的配置方式，很容易理解，如下图：
![](/assets/images/Pasted image 20221220161557.png)
每个服务只引用一个配置文件。此模式完全避免了基于common的治理模式所带来的问题。但是，又带来了新的问题，即不同的微服务配置之间出现大量的重复配置。修改大量重复配置容易出错，且痛苦。&lt;/p&gt;
&lt;p&gt;大量配置重复的问题，可以通过类似Jsonnet或者CUE这样的配置编程语言解决。如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/microservice-common-config.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;当修改metrics.libsonnet时，我们很容易就知道这个变更将直接影响：microservice-c.jsonnet和microservice-a.jsonnet。进而，我们也就可以知道了它将间接影响microservice-c和microservice-b两个服务。&lt;/p&gt;
&lt;p&gt;不存在没有缺点的解决方案。使用Jsonnet和CUE这样的语言，意味着一定的学习成本和现在有的工程的改造成本（引入新的构建工具和对现在有的配置的转换）。&lt;/p&gt;
&lt;h2 id=&#34;不论哪种模式你都必须要做到&#34;&gt;不论哪种模式，你都必须要做到&lt;/h2&gt;
&lt;p&gt;不论哪种配置治理模式，都必须要做到：配置应该尽量小。&lt;/p&gt;
&lt;p&gt;至于小到什么程度？这个问题回答了，作用也不大。就像菜谱上写的是10克的香精，也没有几个人在放香精时进行称重，而是凭感觉。&lt;/p&gt;
&lt;p&gt;如果真要我回答，我的回答是：作为一个逻辑单元，多一行代码是多余，少一行代码则不行。&lt;/p&gt;
&lt;h2 id=&#34;成本&#34;&gt;成本&lt;/h2&gt;
&lt;p&gt;当我提出采用一种新的配置编程语言来统一所有的配置时，团队里的人都反对。行业里其他的人，也啧啧着这样做所带来的成本。&lt;/p&gt;
&lt;p&gt;所以，以下是我们团队经验，供大家参考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Jsonnet的学习成本：像Jsonnet这样专为配置而生的配置编程语言，语法也只有一张A4纸，非常值得投入。我们团队里两个不懂编程的刚毕业没有多久的运维小年轻，很快就上手了。多快？半天左右吧。&lt;/li&gt;
&lt;li&gt;构建工具Bazel的学习成本：Jsonnet本身的构建命令就和Java的javac一样低级，所以，需要借助其它构建工具。我们选择Bazel。它支持Jsonnet的单元测试。我们顺便实现了配置的自动化测试。Bazel的学习只需要团队里的几个人会就可以了。这个工具本身其实不难，但是因为中文学习教程太少了，导致了学习成本高。&lt;/li&gt;
&lt;li&gt;其它配置格式转换成Jsonnet格式的成本：这个应该是我们成本最高的，也是风险最高的。因为一个配置错，可能带来线上事故。这个过程也是一个还债的过程。以前不合理的配置，在这个过程中会被发现。我们转换的过程是
&lt;ol&gt;
&lt;li&gt;通过自动转换工具将旧配置转成json格式，json格式与jsonnet格式是兼容的，所以就相当于自动得到了jsonnet格式的配置；&lt;/li&gt;
&lt;li&gt;将公共配置抽离出来，比如redis的配置。并对敏感配置进行加密处理。这个过程是重建配置的过程；&lt;/li&gt;
&lt;li&gt;将所有的配置转换完成后，再与原来的配置的json格式进行内容级别的对比。如果没有区别，就代表转换成功。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因为我们很早之前就对配置进行了标准化，所以对我们来说这个转换成本和配置的量对比起来，也不算太高。而这个成本绝大多数都是由基础设施团队完成，而不是业务开发团队。&lt;/p&gt;</description>
    </item>
    <item>
      <title>我是如何进行日志降本的</title>
      <link>https://showme.codes/zh-cn/2023-09-22-logs-finops/</link>
      <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-09-22-logs-finops/</guid>
      <description>&lt;p&gt;最近行业里流行降本增效。本文就一个现实中经常发生的日志成本的案例进行讨论，讨论该如何降本。&lt;/p&gt;
&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;假如存在一家IoT公司，它拥有1亿的在线设备（长连接着云端）。这些设备每21秒会向云端发送心跳，以进行连接的保活。假如1次连接的保活，在云端产生1条日志，那么1台设备1天产生的日志量是：&lt;code&gt;24 * 60 * 60 / 21 = 4114条&lt;/code&gt;，假设1条日志100字节，所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1台设备1天保活日志数：&lt;code&gt;24 * 60 * 60 / 21 = 4114&lt;/code&gt;条&lt;/li&gt;
&lt;li&gt;1条日志占用100字节，1台设备1天产生保活日志：&lt;code&gt;4114 * 100 = 411400&lt;/code&gt;byte&lt;/li&gt;
&lt;li&gt;1天内1亿的设备产生日志：&lt;code&gt;411400 * 100000000&lt;/code&gt; ≈ 37.4T&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们来看下该保活日志内容：
&lt;code&gt;2021-02-12:12:32:12 application-abc-prod INFO com.platform.cloud.impl keep alive, deviceId:1a2b3b3p8&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;从日志内容来看，没有什么“营养”，纯粹是为方便程序员debug。&lt;/p&gt;
&lt;h2 id=&#34;这是问题吗&#34;&gt;这是问题吗？&lt;/h2&gt;
&lt;p&gt;一行没有营养的日志每天产生37.4T的日志，是个问题吗？对谁是个问题？&lt;/p&gt;
&lt;p&gt;对于写那行代码的人来说，并不是问题，因为日志量大小本身并不是他的KPI。&lt;/p&gt;
&lt;p&gt;对于运维可能是个问题，因为他们可能因此需要增加存储服务扩容的工作。但是从另一个方面来看不是个问题，因为他们有借口招更多的人。&lt;/p&gt;
&lt;p&gt;对于大数据团队可能是个问题，因为他们发现数据清洗pipeline比之前更慢了，进而导致每日报表都被延迟了。但是从另一个角度来看不是个问题，因为这是一个表现的机会——在人员不变的情况下优化清洗速度的机会。&lt;/p&gt;
&lt;p&gt;这对于关心成本的人来说是个问题。谁关心成本呢？这不是本文讨论的话题。&lt;/p&gt;
&lt;h2 id=&#34;如何面对问题&#34;&gt;如何面对问题？&lt;/h2&gt;
&lt;h3 id=&#34;事后面对&#34;&gt;事后面对&lt;/h3&gt;
&lt;p&gt;事后面对指的是事情已经发生了，即大量无用配置已经产生了。只能采取补救止血措施。&lt;/p&gt;
&lt;p&gt;作为运维，需要对于日志量进行多维度监控与告警。可以包括以下维度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用维度&lt;/li&gt;
&lt;li&gt;实例维度&lt;/li&gt;
&lt;li&gt;设备维度：部分设备可能有Bug，会死循环会不停进行连接请求&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;告警内容可以包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进行环比对比，出现突增&lt;/li&gt;
&lt;li&gt;进行环比对比，突降&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在收到告警后，找到相应的应用的owner，让其检查日志是否合理，不合理则需要制定改进措施。&lt;/p&gt;
&lt;p&gt;同时，每周都向相应的owner发送日志量报告，让其了解当前生产环境的日志量情况。&lt;/p&gt;
&lt;h3 id=&#34;code-review时面对&#34;&gt;Code Review时面对&lt;/h3&gt;
&lt;p&gt;Code Review时，有经验的人会看出日志问题，但是这种方式并不可靠。&lt;/p&gt;
&lt;h3 id=&#34;写代码时就避免&#34;&gt;写代码时，就避免&lt;/h3&gt;
&lt;p&gt;指的是在程序员写在&lt;code&gt;log.info(&amp;quot;无营养日志&amp;quot;)&lt;/code&gt;的那一时刻，IDE就提醒程序员：这行日志每日将产生4114条日志，将占所有日志总量的80%，请注意！&lt;/p&gt;
&lt;p&gt;当然，除了在IDE时提醒，我们可以在Code Review时，review机器人自动对该行代码进行评论。&lt;/p&gt;
&lt;p&gt;这个实现起来难度会比较大。&lt;/p&gt;
&lt;p&gt;但是并不是没有思路。笔者的思路如下，仅供参考，欢迎交流：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们要知道，只有被请求到API，API执行时，被执行到的代码才会打日志。所以，只要能知道打日志的代码在过去一段时间内，在生产环境的相应的API被请求了多少次，我们就可以预测到增加1行日志会增加多少日志了。进而在IDE里提示，或者Code Review进行评论。&lt;/p&gt;
&lt;p&gt;而对于那些完全新增的API，就没有办法预测了，因为没有历史纪录。&lt;/p&gt;</description>
    </item>
    <item>
      <title>精准测试不过是增量构建的副产品</title>
      <link>https://showme.codes/zh-cn/2023-04-22-accuration-testing-is-wrong-2/</link>
      <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-04-22-accuration-testing-is-wrong-2/</guid>
      <description>&lt;p&gt;前文中，我们给了“精准测试”定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;它是一种能力，能只针对变更进行测试，而不是每次变更都进行全量测试。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;同时，介绍了当前行业里的主流实现方法。个人并不看好该实现方法。&lt;/p&gt;
&lt;p&gt;本文介绍的另一种实现精准测试的方法。在真正介绍前，我们就必须先说增量构建和Bazel。&lt;/p&gt;
&lt;h2 id=&#34;全量构建与增量构建&#34;&gt;全量构建与增量构建&lt;/h2&gt;
&lt;p&gt;在软件构建领域，存在两种构建类型：全量构建和增量构建。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全量构建指的是针对代码仓库中所有的代码进行构建；&lt;/li&gt;
&lt;li&gt;增量构建是指只针对有变动的代码及受变动影响的相关代码进行重新构建。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从定义看，增量构建要做的事情和精准测试要做的事情几乎是一样的。只不过，把build命令，换成test命令罢了。&lt;/p&gt;
&lt;p&gt;这也就是为什么我觉得应该把“精准测试”叫做“增量测试”才对。&lt;/p&gt;
&lt;p&gt;目前行业里，在增量构建领域，Bazel可谓是佼佼者。&lt;/p&gt;
&lt;h2 id=&#34;bazel介绍&#34;&gt;Bazel介绍&lt;/h2&gt;
&lt;p&gt;Bazel是Google 2015年开源的一款构建工具。采用声明式的方式定义所有的构建任务。Bazel叫target。&lt;/p&gt;
&lt;p&gt;每个target声明包含了：构建类型、输入、构建方式、输出、依赖等。以下代码展示了两个构建任务：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 声明打成jar包，作为library被其他任务使用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;java_library&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;greeter&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;srcs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;src/main/java/com/example/Greeting.java&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 声明打包成一个可执行Jar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;java_binary&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ProjectRunner&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;srcs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;src/main/java/com/example/ProjectRunner.java&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;main_class&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;com.example.ProjectRunner&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 依赖之前打好的library，这是实现增量构建的关键&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;deps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;:greeter&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bazel在运行时，就会根据target声明，在内部维护一个有向依赖图，如下：
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/dag-bazel2.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;有了这个有向依赖图，Bazel就可以实现增量构建。&lt;/p&gt;
&lt;p&gt;当用户修改了 Greeting.java 文件时，Bazel知道 //:greeter target 依赖它，所以Bazel知道要执行 //:greeter 时。同时Bazel又知道 //:ProjectRunner 依赖 //:greeter ，所以Bazel知道还要执行 //:ProjectRunner target。&lt;/p&gt;
&lt;p&gt;以上是在同一个语言下的增量构建的案例，让我们看下多语言场景下，Bazel是如何实现增量构建的。&lt;/p&gt;
&lt;h2 id=&#34;多语言场景下bazel是如何实现增量构建的&#34;&gt;多语言场景下Bazel是如何实现增量构建的&lt;/h2&gt;
&lt;p&gt;如下图，在一个软件工程下，同时使用到了：Docker、Python、YAML、C++等技术。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/bazel-test2.png&#34;&gt;
这个依赖关系是在开发和运维在写代码的时候就定义好了的。所以，Bazel从一开始就有了这个依赖图。&lt;/p&gt;
&lt;p&gt;Bazel允许声明不同语言的target之间的依赖，所以，很自然的，一个软件工程的完成的依赖图就有了。你不需要花额外的精力去收集。&lt;/p&gt;
&lt;p&gt;当执行Bazel进行构建build时，Bazel发现配置文件config.yaml是被修改了，这时它就计算出接下来要执行的构建，如下图标为橙色的路径。即所有的依赖于//:config.yaml 的直接依赖和间接依赖。&lt;/p&gt;
&lt;p&gt;但是，因为执行的是build，所以，Bazel只会build路径上的所有源代码，并不会去执行 &lt;code&gt;*_test&lt;/code&gt; 测试任务。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/bazel-test.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;这就是精准构建，不，叫增量构建：只构建需要构建的。&lt;/p&gt;
&lt;p&gt;也许你会好奇Bazel是怎么做到的？请关注我的公众号，将来我还会分享更多Bazel的内容。&lt;/p&gt;
&lt;h2 id=&#34;bazel是如何精准测试&#34;&gt;Bazel是如何精准测试&lt;/h2&gt;
&lt;p&gt;Bazel有很多子命令，有两个常用的子命令，一个是&lt;code&gt;build&lt;/code&gt;，一个是&lt;code&gt;test&lt;/code&gt;。这两个子命令是用于区分构建的类型的。因为有时，你可能只是想build，不想test。&lt;/p&gt;
&lt;p&gt;接着上面的例子，同样修改了config.yaml文件，当我们执行的是test子命令，Bazel会计算出要执行的路径——和上文一样的路径，因为//:config.yaml所影响的依赖范围是一样的。&lt;/p&gt;
&lt;p&gt;区别是，这次它除了执行build，还会运行&lt;code&gt;*_test&lt;/code&gt;类的任务。Bazel并不会关心它是单元测试，还是集成测试，只关心该测试的大小。如下图中的标为黑色的部分：
![](/assets/images/Screen Shot 2023-11-02 at 9.30.45 PM.png)&lt;/p&gt;</description>
    </item>
    <item>
      <title>精准测试是个错误</title>
      <link>https://showme.codes/zh-cn/2023-04-21-accuration-testing-is-wrong/</link>
      <pubDate>Fri, 21 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-04-21-accuration-testing-is-wrong/</guid>
      <description>&lt;p&gt;如果你已经了解了精准测试在行业的主流做法，你可以跳过相关内容。&lt;/p&gt;
&lt;h2 id=&#34;行业里对于精准测试的定义&#34;&gt;行业里对于精准测试的定义&lt;/h2&gt;
&lt;p&gt;在网上流传着一些精准测试的定义（如果你对这些定义不感冒，可直接跳到我个人的定义）：&lt;/p&gt;
&lt;p&gt;自网易陈逸青（2020）的定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;借助一定的技术手段、通过辅助算法对传统软件测试过程进行可视化、分析及优化的过程，使得测试过程更加可视化、智能、可信和精准。 原文：https://www.infoq.cn/article/xuu91crqa4hcjz8uomjs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;来自HSBC的测试咨询专家齐磊（2021年）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;通俗点讲：核心基于源代码变更分析，结合分析算法，确定影响范围，提升测试效率。 原文：https://www.infoq.cn/article/2feiv8a5kogaqlbzwosh&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;来自星云测试（2022年）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;精准测试一句话概括就是：测试用例和代码之间的追溯，这是它最本质的东西。精准测试的本质决定了它抓住了测试的一个核心要点。 原文： &lt;a href=&#34;https://testerhome.com/topics/34557&#34;&gt;https://testerhome.com/topics/34557&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;来自得物技术（2023年）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;精准测试是基于源代码变更分析，结合一些分析算法，从而确定改动代码影响的范围，设计测试用例进行针对性测试，一方面可以提升测试效率，另一方面精准测试还可以将测试用例与程序代码之间的逻辑映射关系建立起来， 而这个过程则是通过工具去采集测试过程执行的代码逻辑及测试数据。这两个点也正是精准测试的核心：正向追溯和逆向追溯。原文： &lt;a href=&#34;https://tech.dewu.com/article?id=43&#34;&gt;https://tech.dewu.com/article?id=43&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下是来自网易严选的架构图：&lt;/p&gt;
&lt;p&gt;![](/assets/images/网易严选的架构图1.png]]&lt;/p&gt;
&lt;h2 id=&#34;我个人的定义&#34;&gt;我个人的定义&lt;/h2&gt;
&lt;p&gt;在笔者看来，精准测试的定义应该是这样的：它是一种能力，能只针对变更进行测试，而不是每次变更都进行全量测试。注意，我指的是“变更”，而不只是“代码变更”，也就是说所有类型的变更，包括手动变更。&lt;/p&gt;
&lt;p&gt;精准测试的思路并不复杂，分成三个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找到变更；&lt;/li&gt;
&lt;li&gt;根据变更找到相关联的测试用例；&lt;/li&gt;
&lt;li&gt;只执行相关联的测试用例。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实，把这种方法叫&lt;strong&gt;增量测试（Incremental Testing）&lt;/strong&gt; 更准确，更合适。毕竟你是针对增量的代码变更进行测试。&lt;/p&gt;
&lt;p&gt;如果不是针对增量变更进行测试，你也能只执行一个你想测试的测试。难道这样不算精准测试吗？&lt;/p&gt;
&lt;h2 id=&#34;国内行业主流的实现精准测试的方法&#34;&gt;国内行业主流的实现精准测试的方法&lt;/h2&gt;
&lt;h3 id=&#34;步骤一找到代码变更&#34;&gt;步骤一：找到代码变更&lt;/h3&gt;
&lt;p&gt;通过commit之间进行差异对比​。​&lt;/p&gt;
&lt;h3 id=&#34;步骤二根据代码变更找到相关联的测试用例&#34;&gt;步骤二：根据代码变更找到相关联的测试用例&lt;/h3&gt;
&lt;p&gt;要做到“根据代码变更找到相关联的测试用例”，我们就必须知道代码与测试用例之间的关系。&lt;/p&gt;
&lt;p&gt;获取这个关系的做法是在执行测试的同时，做以下事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将流量记录下来；&lt;/li&gt;
&lt;li&gt;将因流量而执行地代码的调用链记录下来；&lt;/li&gt;
&lt;li&gt;将测试用例的元数据与代码调用链的关系记录下来；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个过程就完成了对被调用代码与测试用例之间的映射关系的建立。&lt;/p&gt;
&lt;p&gt;另，现实往往存在很多未被测试用例覆盖到的代码，这时，通过静态代码分析和测试覆盖率计算技术结合，生成未被测试到的代码的报告。&lt;/p&gt;
&lt;p&gt;可以看出，通过以上方式“找出代码与测试用例之间的关系”的成本是极高的。所以，在这个领域会有：引流平台、测试用例管理平台、精准测试平台等等平台。这也给大家一个感觉，我们要先有一个平台才能做到精准测试。&lt;/p&gt;
&lt;p&gt;说到底就是通过插桩技术，构建代码的执行路径，并找到​对应的测试用例之间关系。&lt;/p&gt;
&lt;p&gt;目前在网上目前看到大多还只是针对Java语言或者C++来实现精准测试，其它的语言目前没有见到。&lt;/p&gt;
&lt;h3 id=&#34;步骤三只执行相关联的测试用例&#34;&gt;步骤三：只执行相关联的测试用例&lt;/h3&gt;
&lt;p&gt;当有了代码与测试用例之间的关系，只执行相关联的测试用例就简单很多了。&lt;/p&gt;
&lt;h2 id=&#34;主流方法的坑&#34;&gt;主流方法的坑&lt;/h2&gt;
&lt;p&gt;以下是齐磊总结的精准测试存在的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;基于手工测试的精准测试建立映射关系繁杂，如果需求改变频繁，用例维护以及之间的关系维护需要耗费大量时间精力。&lt;/li&gt;
&lt;li&gt;精准测试需要一定的自动化测试的覆盖，这样做起来更有意义，例如 api 自动化测试，如果本身用例过少，与代码之间关联关系不多时，变更代码后可能不会得出什么结果。&lt;/li&gt;
&lt;li&gt;最好有对应的用例管理系统，能够方便的帮助我们建立与代码之间的关系。&lt;/li&gt;
&lt;li&gt;需要投入开发能力强的 QA 或者测试开发建立整套系统环境，但长远考虑，将精准测试嵌入整个公司的质量平台中，不管对于新项目还说维护项目来说都是一种提升。&lt;/li&gt;
&lt;li&gt;项目生命周期需要较长，短期项目花费巨大精力开发和维护整套精准测试系统得不偿失。短期项目可以利用精准测试以 api 测试覆盖率作为衡量标准。不去建立繁杂的关系，只监控 UI API 测试覆盖率迭代时的变更来达到目的。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是，个人认为齐磊总结的内容没有问题，的确都是坑。但是那些不是精准测试的坑，而是国内行业主流的实现方式的坑。直白地说就是喝水时，喝水的角度错了。&lt;/p&gt;
&lt;h2 id=&#34;为什么主流实现方法从方向上就是错的&#34;&gt;为什么主流实现方法从方向上就是错的&lt;/h2&gt;
&lt;p&gt;为什么我认为以上地坑是由实现方法导致的？以下是我的论点，欢迎讨论指正：&lt;/p&gt;
&lt;h3 id=&#34;该方法只局限于单一语言&#34;&gt;该方法只局限于单一语言&lt;/h3&gt;
&lt;p&gt;准确来说，精准测试不应该只针对代码的变更，而是所有的变更。更不应该只针对单一语言的变更，而是可以针对所有的语言。&lt;/p&gt;
&lt;p&gt;因为精准测试的定义本身不局限于某种语言的代码变更，而是对一个软件工程中所有的变更而言。一次SQL的变更，你是否需要精准的知道要执行哪些测试？一个前端的CSS代码的变更，你是否需要精准的知道要执行哪些测试？&lt;/p&gt;
&lt;p&gt;目前行业里主流的方法，只是针对单一语言下的场景而设计的。按同样的思路是无法做到多语言的。我说的多语言指提同一工程下的多语言，不是指相互独立的单语言工程。&lt;/p&gt;
&lt;h3 id=&#34;只能在平台上做精准测试&#34;&gt;只能在平台上做精准测试&lt;/h3&gt;
&lt;p&gt;即，我们首先需要一个平台，才能做到精准测试。&lt;/p&gt;
&lt;p&gt;但我们希望在开发者本地开发环境就可以做到精准测试。&lt;/p&gt;
&lt;h2 id=&#34;最后&#34;&gt;最后&lt;/h2&gt;
&lt;p&gt;文章标题并不是说“精准测试”本身是一个错误，是想说上述的实现方法是一个错误方法。&lt;/p&gt;</description>
    </item>
    <item>
      <title>当同事实践Everything as Code之后</title>
      <link>https://showme.codes/zh-cn/2023-04-20-when-coworker-use-everything-as-code/</link>
      <pubDate>Thu, 20 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-04-20-when-coworker-use-everything-as-code/</guid>
      <description>&lt;h3 id=&#34;背景&#34;&gt;背景&lt;/h3&gt;
&lt;p&gt;团队基于Jsonnet（配置语言）和Bazel（构建工具）实现Everything as Code。但是，将近一年的时间里，团队也只有少数人深入参与Everything as Code的代码的编写（其实，大多数人是不需要深入参与）。&lt;/p&gt;
&lt;p&gt;最近，组内的一个开发同学——他之前对Everything as Code完全零基础——在运维同事的辅助下，为网关配置增加了金丝雀的能力。&lt;/p&gt;
&lt;p&gt;在这种能力下，开发人员可以只需要改几行配置，并部署配置，就可以实现金丝雀发布。这种能力的根本就是：通过Jsonnet动态生成网关“根据Header进行流量路由”的配置的能力。&lt;/p&gt;
&lt;p&gt;如果没有这种能力，需要手写大量的重复的YAML配置，即麻烦，又容易出错。&lt;/p&gt;
&lt;h3 id=&#34;同事使用everything-as-code的工具后有感&#34;&gt;同事使用Everything as Code的工具后有感&lt;/h3&gt;
&lt;p&gt;在他与Everything as Code“亲密”接触一个多月后，他说出了他的感受：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;直接写yaml的话，所见即所得，非常直观。但是有个致命缺陷，没法动态化生成配置，所以必须要寻找一个脚本语言。&lt;/li&gt;
&lt;li&gt;jsonnet其实就是一门简单化的脚本语言，通过自定义方法，通过for循环，通过读取自定义的变量，更灵活生成我们想要的json。但是写代码都有一个共性，代码可以写的很复杂，现在用jsonnet了，你想看一个key的value是什么，需要看懂别人的代码才知道value是什么甚至可能理解错别人的代码，违背了配置所见即所得，这对新手来说非常痛苦。很多时候，我们不关心jsonnet的过程，我们只想看到生成最终的配置文件是什么。&lt;/li&gt;
&lt;li&gt;如果能把云端编译好的jsonnet展示出来，就可以看到生成的最终json是啥了，但是我们没有。所以需要本地搭建bazel，但是bazel对win非常不友好，搞起来非常困难，但是本地如果不能把monorepo buil通过，那么可以宣告你无法彻底搞清楚monorepo，更不可能自定义更多自己想要的东西了。&lt;/li&gt;
&lt;li&gt;既然是通过代码来生成json，用jsonnet和python和js甚至是用java都可以，现在我觉得jsonnet的设计者对灵活性做了一个取舍，有一些我想要的操作jsonnet没法实现。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后他又补充到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只要自己本地能build monorepo，那么就既能享受jsonnet的灵活性，又能通过编译好的文件做到所见即所得。可惜bazel对win不友好，新手能在win 编译通过mainrepo太难了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总结下来，这位同事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对Jsonnet的理解还不够：Jsonnet并不是脚本语言，而是一门可编程的配置语言，一种为配置而生的DSL（领域特定语言）；&lt;/li&gt;
&lt;li&gt;在Windows系统上使用Bazel遇到了非常大的阻力。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上是同事针对Jsonnet和构建工具Bazel的感受。&lt;/p&gt;
&lt;p&gt;我认为不是Bazel对Windows不友好，而是Bazel在Windows下的生态太弱了。谁叫后端服务是Linux的天下呢？&lt;/p&gt;
&lt;h3 id=&#34;同事对everything-as-code本身的看法&#34;&gt;同事对Everything as Code本身的看法&lt;/h3&gt;
&lt;p&gt;接着，他说出对Everything as Code的看法：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我觉得everything as code太理想，只能面向开发人员。你不能保证运维的能力，绝对不开放给第三方使用。指不定某天某个需求就需要你的运维的某个能力，让不懂代码的人也要操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;假如某个运维能力，要开放给产品或者测试来用，要让别人给你学个jsonnet，那要我是产品或者测试只能口吐芬芳了。所以我认为，面向开发者的部分，使用every thing as code是趋势，比较好溯源跟踪，迭代。但是架构设计上，还需要有支持面向第三方的部分，可以做到让不懂代码的人也能用到一些运维的能力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这位同事实现的是金丝雀配置生成，是需要修改Jsonnet代码的。当产品经理需要将某用户的流量路由到后端服务的某个版本上时，这位同事就认为产品经理就需要去改Jsonnet代码了。&lt;/p&gt;
&lt;p&gt;同事认为这就是Everything as Code的问题：不方便非开发人使用。&lt;/p&gt;
&lt;p&gt;同事的另一层意思没有说出来：并不是所有的事情都适合as Code。&lt;/p&gt;
&lt;p&gt;其实，有这样的误解很正常，“Everything as Code”中的“Everything”字面含义上就是所有的，一切的意思。而Code是代码的意思。&lt;/p&gt;
&lt;p&gt;所以，“Everything as Code”字面上完整的意思是：一切皆代码。&lt;/p&gt;
&lt;p&gt;从字面上理解“Everything as Code”，只看到了它的表面&lt;/p&gt;
&lt;h3 id=&#34;everything-as-code的本质&#34;&gt;Everything as Code的本质&lt;/h3&gt;
&lt;p&gt;“Everything as Code”中的”Code“，其实指的是配置，即：一切皆配置。&lt;/p&gt;
&lt;p&gt;这句话本身是没有错的，如同“道生一，一生二，二生三，三生万物“这句话。&lt;/p&gt;
&lt;p&gt;“Everything as Code”这句话并没说什么是配置，以及哪些配置该放哪。这才是我们实践时真正要考虑的问题。&lt;/p&gt;
&lt;p&gt;配置的本质是软件的灵活（soft）部分。通过这个”灵活部分“，我们可以根据自己的需求，有限地改变软件的行为。&lt;/p&gt;</description>
    </item>
    <item>
      <title>基于Bazel &#43; SQLFluff实现SQL lint</title>
      <link>https://showme.codes/zh-cn/2023-04-17-sql-lint-by-bazel-sqlfluff/</link>
      <pubDate>Mon, 17 Apr 2023 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2023-04-17-sql-lint-by-bazel-sqlfluff/</guid>
      <description>&lt;h3 id=&#34;背景&#34;&gt;背景&lt;/h3&gt;
&lt;p&gt;SQL进行版本化控制后，我们希望为SQL加入lint步骤。这样做的好处是我们可以在真正执行SQL前发现问题。&lt;/p&gt;
&lt;p&gt;本文中，我们通过Bazel执行&lt;a href=&#34;https://github.com/sqlfluff/sqlfluff&#34;&gt;SQLFluff&lt;/a&gt;以实现SQL的lint。&lt;/p&gt;
&lt;p&gt;SQLFluff是一款使用Python语言使用的，支持SQL多方言的SQL lint工具。&lt;/p&gt;
&lt;p&gt;它的特点是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持多方言。如：Snowflake、PostgreSQL、ClickHouse。所有支持的方言列表：https://docs.sqlfluff.com/en/stable/dialects.html；&lt;/li&gt;
&lt;li&gt;可以输出正确的SQL，减少了我们手工修正SQL的工作；&lt;/li&gt;
&lt;li&gt;同时支持命令行方式使用和API调用方式。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;集成到cicd流水线中&#34;&gt;集成到CI/CD流水线中&lt;/h3&gt;
&lt;p&gt;在我看来，在CICD流水线中实现SQL lint有两种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方式一：在流水线中增加一个SQL lint步骤；&lt;/li&gt;
&lt;li&gt;方式二：将SQL lint的逻辑写在测试代码，执行测试步骤，就自动执行了SQL lint。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;方式二是我最爱，我会在本文最后讲原因。&lt;/p&gt;
&lt;h3 id=&#34;工程结构&#34;&gt;工程结构&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── BUILD.bazel
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── WORKSPACE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── repository-hibernate-impl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── BUILD.bazel
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── src
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       ├── main
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       │   └── sql
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       │       └── V1__runbook_table.sql
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       └── &lt;span class=&#34;nb&#34;&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│           └── python
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│               ├── BUILD.bazel
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│               ├── requirements_lock.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│               └── sql_test.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;步骤1-在workspace中增加python外部依赖&#34;&gt;步骤1: 在WORKSPACE中增加Python外部依赖&lt;/h3&gt;
&lt;p&gt;本文中我们使用的是Bazel 5.4.0，所以还在使用WORKSPACE定义外部依赖&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;http_archive&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rules_python&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;sha256&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;a644da969b6824cc87f8fe7b18101a8a6c57da5db39caa6566ec6109f37d2141&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;strip_prefix&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rules_python-0.20.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://github.com/bazelbuild/rules_python/releases/download/0.20.0/rules_python-0.20.0.tar.gz&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@rules_python//python:repositories.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;py_repositories&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;py_repositories&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@rules_python//python:repositories.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;python_register_toolchains&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;python_register_toolchains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;python3_11&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;python_version&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;3.11&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@python3_11//:defs.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;interpreter_3_11&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;interpreter&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@rules_python//python:pip.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;pip_parse&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Create a central repo that knows about the dependencies needed from  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# requirements_lock.txt.  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pip_parse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;pip_deps&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;n&#34;&gt;python_interpreter_target&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;interpreter_3_11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;n&#34;&gt;requirements_lock&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;//repository-hibernate-impl/src/test/python:requirements_lock.txt&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Load the starlark macro which will define your dependencies.  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;@pip_deps//:requirements.bzl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;install_deps&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Call it to define repos for your requirements.  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;install_deps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;步骤2-定义sqlfluff依赖&#34;&gt;步骤2: 定义SQLFluff依赖&lt;/h3&gt;
&lt;p&gt;requirements_lock.txt的内容如下：&lt;/p&gt;</description>
    </item>
    <item>
      <title>和行业里多家DevOps平台的同学交流后，我发现……</title>
      <link>https://showme.codes/zh-cn/2020-11-02-devops-platform-interview/</link>
      <pubDate>Mon, 02 Nov 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-11-02-devops-platform-interview/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;sunset-815270_640.jpg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-dc1ef40bb713c6f5.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;前段时间和不同的人交流了DevOps平台后，大概了解了行业里DevOps平台是怎么回事。&lt;/p&gt;
&lt;h3 id=&#34;devops平台最大的问题&#34;&gt;DevOps平台最大的问题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;DevOps&lt;/strong&gt;的定义是什么？面对这样的问题，我想说行业里，10个人可能有11个答案。既然&lt;strong&gt;DevOps&lt;/strong&gt;的定义在行业都没有一个权威的定义，那么，对于&lt;strong&gt;DevOps平台&lt;/strong&gt;是什么这样一一个问题，自然也就没有权威的定义了。这就是行业里DevOps平台最大的问题。&lt;/p&gt;
&lt;p&gt;P.S. 对于DevOps Master证书又应该如何定义呢？&lt;/p&gt;
&lt;p&gt;如果DevOps平台没有定义，那么，我们就无法以下类似的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;它解决了谁的什么问题？&lt;/li&gt;
&lt;li&gt;行业里，它的趋势是什么？&lt;/li&gt;
&lt;li&gt;DevOps平台发展到后期，是不是AIOps？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因为，你再怎么回答这些问题，只要你与提问人对于DevOps的认知不一样，你都会与提问人内心的答案不一样。&lt;/p&gt;
&lt;p&gt;这让我想起马斯克的名言：我现在不和人争吵了，因为我开始意识到，每个人只能在他的认知水准基础上去思考，以后有人告诉我2+2等于10，我会说，你真厉害！&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-8006bbebe81e5cc9.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;谈devops平台的设计&#34;&gt;谈DevOps平台的设计&lt;/h3&gt;
&lt;p&gt;我看到的是它们有很多功能：项目管理、需求管理、测试管理、流水线、制品管理、自动化部署……所有人都类似地美其名为：一站式研发云平台。&lt;/p&gt;
&lt;p&gt;可是，当你在使用时，你会发现，它们不过是在以前的项目管理平台、测试管理平台、流水线平台、制品管理平台、部署平台等多个平台的功能的重新堆砌。而这些功能之间有有联系吗？很少。&lt;/p&gt;
&lt;p&gt;行业里不少客户会要求DevOps平台的Dashboard，不同的角色进入要显示不一样的东西。其实就是开发者进入Dashboard应该只显示开发者关心的功能，测试人员进入Dashboard应该只显示测试用例相关的功能……这是不是可以成为康威推论的一个佐证？&lt;/p&gt;
&lt;p&gt;总的一句，DevOps平台不过是过去多个不相关的烟囱系统，通过一个Dashboard，让它们显得像是一个平台。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-b6ecbc50818d9413.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;研发效率有最优解吗&#34;&gt;研发效率有最优解吗？&lt;/h3&gt;
&lt;p&gt;和这么多人交流后，发现大家还是有一些共识的。那就是：DevOps平台应该是可以提高研发（or 运维）效率的（如果我只说研发效率，估计有人会以为我认为DevOps平台只是为研发侧服务的）。&lt;/p&gt;
&lt;p&gt;行业里，To C产品的设计，最优解通常是未知的，是需要不断寻找的。所以，发展出各种方法论寻找方法论。这对于To C产品的设计是有效的。因为它的前提是最优解真的在于用户。&lt;/p&gt;
&lt;p&gt;抛开产品设计方法论，对于研发效率的提升这个领域， DevOps平台的设计者应该比用户知道最优解是什么？&lt;/p&gt;
&lt;p&gt;那我们使用To B的产品设计方法论去设计如何？个人觉得，即对，也不对。对的地方是DevOps产品毕竟是卖给企业的，所以，在设计DevOps时不得不考虑谁给它买单这个问题。不对的还是我上文提的最优解问题。&lt;/p&gt;
&lt;h3 id=&#34;devops平台与srcum敏捷迭代开发之间的关系&#34;&gt;DevOps平台与Srcum、敏捷、迭代开发之间的关系&lt;/h3&gt;
&lt;p&gt;如果你的DevOps平台不与这些听起来牛x、高大上的名词关系起来，在这个行业里，你的产品估计有点难打开市场。&lt;/p&gt;
&lt;p&gt;这就要求，我们在设计DevOps平台时，不得不考虑行业里的人对于Scrum、敏捷、迭代开发的理解。&lt;/p&gt;
&lt;p&gt;个人觉得难点在于：在DevOps平台的设计上，无论用户希望使用何种方法论，都能在平台内实现DevOps平台本身的目标，同时平台还能保持自身的简洁。&lt;/p&gt;
&lt;p&gt;用户使用那些方法论背后的目的才是关键。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;感谢这些同学花时间与我交流。本文作为一篇交流文章，目的不是为了贬低DevOps平台。&lt;/p&gt;</description>
    </item>
    <item>
      <title>持续构建、持续测试、持续集成、持续部署、持续交付、持续.....“持续”到底是什么意思？</title>
      <link>https://showme.codes/zh-cn/2020-12-01-ci-cd-ct/</link>
      <pubDate>Wed, 16 Sep 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-12-01-ci-cd-ct/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;dawn-190055_640.jpg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-3086e60b5982cac6.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;虽然，读者朋友可能觉得自己已经理解这些概念了，但是，还是希望读者读完。笔者从&lt;strong&gt;权威&lt;/strong&gt;的书上将这些概念的定义摘抄下来，最后给出笔者对于“持续”的理解。&lt;/p&gt;
&lt;p&gt;构建（Build）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一次构建不止是一次编译（或者动态语言中的某种称谓）。一次构建可能包含编译、测试、审查和部署以及其他一些事情。一次构建是将源代码放在一起，并验证软件可以作为一个一致的单元运行的过程。摘自《持续集成》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其实构建过程中还可以包括测试、部署。这点可能和很多人的理解有出入。这里就会有疑问了，既然构建中包括了部署，那么持续构建与持续部署又有什么关系？笔者是这样理解的，因为软件系统是需要部署了，才能测试的，所以，为了在构建过程加入测试，就必须引入部署。&lt;/p&gt;
&lt;p&gt;部署（Deployment）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;部署是一种技术领域的操作，也就是说从某处获取软件包，并按照预先设计的方案将其安装在计算节点上，并确保系统可以正常启动，但它并不定意味着“必须包含业务功能的发布或交付”。摘自《持续交付2.0》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;交付（Delivery，也被称为发布）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;是一个业务决策活动，通常也被称为“发布”，也就是说，如果将新的构建的特性交到客户（用户）手中，用户就可以看到并使用它们。摘自《持续交付2.0》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们可以将代码部署上生产环境，但是我们可以通过某种技术手段，让用户看不到，也不能使用它。这就是只部署，但不交付。部署与交付的差异在于部署是技术端操作、交付是业务端决策。&lt;/p&gt;
&lt;p&gt;这里，读者可能又有疑问了：未完成的功能，可以部署上生产环境吗？笔者的回答：是的。前提是你能控制该功能是否对用户可见。这称为功能开关。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;IMG_20200916_054551__01.jpg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-73c0527482838a56.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;持续集成（CI）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;它是一种软件开发实践，即团队的成员经常集成他们的工作。通常每个成员每天至少集成一次——这导致每天发生多次集成。每次集成都通过自动化的构建（包括测试）来验证，从而尽快地检测出集成错误。摘自《持续集成——软件质量改进和风险降低之道》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意，持续集成中包括了构建与测试。所以，我们在行业里经常听到的“持续测试（CT）”又是什么呢？这是笔者的疑问。&lt;/p&gt;
&lt;p&gt;持续交付1.0（CD1.0）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;持续交付是一种能力，也就是说，能够以持续方式，安全快速地把代码变更（包括特性、配置、缺陷和试验）部署到生产环境上，让用户使用。摘自《持续交付2.0——业务引领的DevOps精要》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;持续交付2.0（CD2.0）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“持续交付2.0”建立在“持续交付1.0”的“可持续地快速发布软件服务”及精益创业的“最小化可行产品”两种理念基础之上，强调要以业务为导向，从一开始就业务问题进行分解，并通过不断的科学探索与快速验证，减少浪费的同时，快速找到正确的业务前进方向，简称为“双环模型”。摘自《持续交付2.0》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;持续集成、持续部署、持续交付之间是什么关系呢？笔者认为是：持续交付的过程会包含持续部署，持续部署的前提是持续集成。把集成与部署组合到一起，并完全自动化，这个自动化的过程，称为部署流水线。持续交付1.0强调的是交付的效率，持续交付2.0则除了强调交付的效率，还强调交付的效果。&lt;/p&gt;
&lt;p&gt;最后，我们来谈谈“持续”，笔者是这样理解的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所谓的“持续”，就是指经常地做。而“经常”是一个相对的概念。对于每年交付一次的软件系统，优化成每个月一次，也算是“持续”了。另，“持续”代表的是一种能力。有能力持续交付，但是业务不一定允许。要实现“持续”的能力，自动化就成为了必然的选择。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说到了“持续”就不得不说“持续改进”。上文说过，持续指的是经常做。持续改进的意思是经常做改进。持续改进的极限是無時不刻地在改进。那么，如何让一个团队无时不刻地进行改进呢？这是一个非常大的话题。关注笔者的公众号，将来会讲到。&lt;/p&gt;</description>
    </item>
    <item>
      <title>一些持续交付的实践经验</title>
      <link>https://showme.codes/zh-cn/2020-09-09-experience-devops-cd/</link>
      <pubDate>Wed, 09 Sep 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-09-09-experience-devops-cd/</guid>
      <description>&lt;h3 id=&#34;分析团队的问题&#34;&gt;分析团队的问题&lt;/h3&gt;
&lt;p&gt;我是2020年3月份加入该部门。刚加入时发现问题还挺多。而这些问题在行业里都很典型，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分支管理不统一：虽说大部分人还是在master上开发，但是还有部分人自己拉feature分支开发。&lt;/li&gt;
&lt;li&gt;没有统一的制品打包：Docker镜像的打包基本都是在开发人员的电脑上进行的。&lt;/li&gt;
&lt;li&gt;对制品仓库的push权限没有管理：每个人都有push权限，而且使用的是同一个账号。&lt;/li&gt;
&lt;li&gt;没有版本管理：在交给测试人员测试（俗称提测）时，开发在本地打包后，push上制品库的包的版本号为&lt;code&gt;uat20200302&lt;/code&gt;（UAT是测试环境的简称）。测试通过后，再使用此版本号，部署到生产环境。结果就是你会看到生产环境运行的包的版本：&lt;code&gt;uat20200302&lt;/code&gt;，是不是很奇怪？&lt;/li&gt;
&lt;li&gt;没有监控：这也是很多团队的通病了。&lt;/li&gt;
&lt;li&gt;没有单元测试：开发人员有在main方法写单元测试的，有写出来的测试是无法自动化的。&lt;/li&gt;
&lt;li&gt;开发团队没有自己的自动化测试。&lt;/li&gt;
&lt;li&gt;多个应用部署在同一台机器。&lt;/li&gt;
&lt;li&gt;手工部署：每次部署都是人工登录到服务器执行部署。&lt;/li&gt;
&lt;li&gt;数据库没有版本化。&lt;/li&gt;
&lt;li&gt;没有代码审查。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;问题还有很多，就不一一列举。在笔者看来以上问题的最终表现都是软件系统的质量低下。笔者希望通过实践持续交付以提升软件系统的质量。&lt;/p&gt;
&lt;p&gt;但是问题是该如何实践呢？笔者认为只要掌握了它的基本原则，剩下的就是根据实际情况结合基本原则来解决问题了。&lt;/p&gt;
&lt;h3 id=&#34;持续交付的原则&#34;&gt;持续交付的原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;幸福的家庭都是相似的，不幸的家庭各有各的不幸——《安娜卡列尼娜》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从《持续交付》书中，基本原则有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为软件的发布创建一个可重复且可靠的过程&lt;/li&gt;
&lt;li&gt;将几乎所有的事情自动化&lt;/li&gt;
&lt;li&gt;把所有的东西都纳入版本控制&lt;/li&gt;
&lt;li&gt;提前并频繁地做让你感到痛苦的事&lt;/li&gt;
&lt;li&gt;内建质量&lt;/li&gt;
&lt;li&gt;“DONE”意味着“已发布”&lt;/li&gt;
&lt;li&gt;交付过程是每个成员的责任&lt;/li&gt;
&lt;li&gt;持续改进&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们可以这么理解这些原则：基于所有东西都要进行版本化的原则，所有的东西都要代码化。&lt;/p&gt;
&lt;p&gt;因为代码化以后就可以放到类似Git这类版本化工具中。而代码化以后的东西就可以很容易地实现自动化。在实现自动化以后就可以为软件的发布创建一个可重复且可靠的过程。&lt;/p&gt;
&lt;p&gt;实现自动化的过程是需要每一位成员持续参与的，因为交付过程是每个人责任。&lt;/p&gt;
&lt;p&gt;“DONE意味着已发布”是团队每个成员都要达成的共识。达成共识后，才能更好的参与持续改进。在持续改进过程中，我们的软件系统就获得了内建质量。&lt;/p&gt;
&lt;p&gt;笔者认为，在持续交付中，代码化与版本化是基础。&lt;/p&gt;
&lt;h3 id=&#34;实践持续交付&#34;&gt;实践持续交付&lt;/h3&gt;
&lt;p&gt;在理解原则后，我们就可以开始实践了。可是该如何下手呢？笔者通常遵循以下指导思想：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先CI，后CD。&lt;/li&gt;
&lt;li&gt;无监控，无安心觉。&lt;/li&gt;
&lt;li&gt;先配置项版本化，后标准化，最后才有自动化。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据指导思想，再结合团队的实际情况，笔者做出以下计划：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打包自动化。&lt;/li&gt;
&lt;li&gt;实现基础监控（机器级别监控、中间件监控）。&lt;/li&gt;
&lt;li&gt;实现所有的配置版本化。&lt;/li&gt;
&lt;li&gt;实现自动化部署应用。&lt;/li&gt;
&lt;li&gt;实现应用监控。&lt;/li&gt;
&lt;li&gt;实现数据库版本化。&lt;/li&gt;
&lt;li&gt;实现业务监控。&lt;/li&gt;
&lt;li&gt;研发数据收集&lt;/li&gt;
&lt;li&gt;&amp;hellip;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于团队原来已经有日志收集机制，所以，暂且不需要实现。以上步骤只代表一个优先级。如果团队人力充足，可以同时一起做。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;虽说有了计划，但是团队不具备相应的能力，什么计划都白搭。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;1-打包自动化&#34;&gt;1. 打包自动化&lt;/h3&gt;
&lt;p&gt;之所以使用“打包”这个听起来不怎么“高端”的词，而不是使用“构建”。是因为“构建”这个词，太容易引起歧义。而且打包这个词很形象，就是把源代码编译后，链接，最后打包成一个可执行包。当然不同的编程语言，打包过程可能不同。&lt;/p&gt;
&lt;p&gt;因为大多数团队都没有写自动化测试的习惯（我们团队也不例外），让他们写自动化测试，他们只会觉得自己的工作量增加了。所以，我在团队中导入持续交付实践时，一开始就不要求自动化测试。团队意识的转变需要很长的过程。这是使用“打包”的第二个原因：它不包括自动化测试。&lt;/p&gt;
&lt;p&gt;要实现自动化打包，其实并不难。基本步骤就是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;搭建制品库：Nexus。&lt;/li&gt;
&lt;li&gt;搭建自动化服务：Jenkins。&lt;/li&gt;
&lt;li&gt;在Jenkins中创建pipeline任务。&lt;/li&gt;
&lt;li&gt;在业务代码仓库中加入Jenkinsfile，将打包逻辑写到Jenkinsfile中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所谓打包逻辑就是你在本地开发时，利用IDE或命令将源代码编译成可执行文件的过程。打包自动化的过程，就是将你在本地执行的打包过程“搬”到自动化系统上执行，再加上一些优化。&lt;/p&gt;
&lt;p&gt;在这个阶段中，我们需要实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;统一制品库：收回所有人上传制品的账号。只能由Jenkins打包上传。&lt;/li&gt;
&lt;li&gt;统一版本号：比如使用格式&lt;code&gt;年-月-日-commitId-构建号&lt;/code&gt;来定义所有的后端应用。注意，不管使用哪种方式，你必须很容易的根据版本号找回相应的源码。&lt;/li&gt;
&lt;li&gt;统一分支管理：使用主干开发，分支发布的模式。我们根据团队情况有稍微做了一些改变。发布并没有切分支出来，而在发布后发现某版本有Bug，我们就会从该版本的代码切一个分支出来改，打包，部署。最后再将该分支的commit cherry pick回master分支。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;2-实现基础监控&#34;&gt;2. 实现基础监控&lt;/h3&gt;
&lt;p&gt;没有监控，在我们这个行业太常见了。所以，在我加入团队后，发现几乎没有任何监控，也就没有什么好惊讶的了。所以，在解决打包问题之后 ，紧接着就是给所有的机器加上监控。至少机器的CPU、硬盘、内存等要监控起来。&lt;/p&gt;
&lt;p&gt;上一阶段，我们已经把Jenkins搭建起来，所以，Prometheus就开始自动化部署了。&lt;/p&gt;
&lt;p&gt;使用Prometheus的原因很多，但是关键是它的配置是代码化的，非常容易版本化。持续交付的原则：将几乎所有的事情自动化、把所有的东西都纳入版本控制。像Zabbix，使用需要使用界面进行操作的，就被我排除了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>工程化实践：使用flyway进行数据库版本化</title>
      <link>https://showme.codes/zh-cn/2020-09-06-db-versioning/</link>
      <pubDate>Sun, 06 Sep 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-09-06-db-versioning/</guid>
      <description>版本化一切之一：数据库版本化</description>
    </item>
    <item>
      <title>远程办公十分钟，干一个月的活，剩下的时间……</title>
      <link>https://showme.codes/zh-cn/2020-08-02-about-result-orientation2/</link>
      <pubDate>Sun, 02 Aug 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-08-02-about-result-orientation2/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-dd447744b3fa2ed2.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;三年多前，一位国外的老哥在 stackexchange.com（国外的技术问答社区）上发表了一个问题。&lt;/p&gt;
&lt;p&gt;问题大概内容就是远程工作的他是一名程序员，每个月他只需要花大约10分钟就完成整个月的工作了。这样的状态，他维持了6个月。公司也从来没有表示过对我的表现不满意，事实上，公司聘用他，并从他那得到了想要的。他的疑问是这样继续下去道德吗。&lt;/p&gt;
&lt;p&gt;本文不想谈道德，而是从公司经营和团队管理角度开始谈。&lt;/p&gt;
&lt;h3 id=&#34;公司经营角度&#34;&gt;公司经营角度&lt;/h3&gt;
&lt;p&gt;如果放他在公司里坐班，他能不能用10分钟完成一个月的工作呢？我们不知道。但是，如果他能做到，他会告诉上级领导吗？&lt;/p&gt;
&lt;p&gt;我们也不知道。这里，我想问问坐班的读者，你会告诉你的上级吗？&lt;/p&gt;
&lt;p&gt;站在公司经营的角度，公司当然期望10分钟做完以前1个月的工作，节约下来的人力成本可以做其他的事情。&lt;/p&gt;
&lt;p&gt;这时，如果你是公司的经营者，你如何达到自己的期望呢？&lt;/p&gt;
&lt;p&gt;国内某些公司的做法，似乎能避免国外老哥这种情况的发生。做法简单粗暴：设立KPI等级，同时每半年淘汰KPI倒数10%的人。&lt;/p&gt;
&lt;p&gt;国外老哥想得到高的KPI等级，会主动告诉领导他的功劳。但是，现实会是这样吗？&lt;/p&gt;
&lt;p&gt;这位老哥，并不一定要一次把1个月缩短到10分钟。他可以在评KPI前的一个星期，主动告诉领导他缩短几天的工作量就可以了。&lt;/p&gt;
&lt;p&gt;职场里的老油条，应该能懂我说的话。在评KPI前告诉领导是为了让领导在评KPI时，能快速想起你的成绩（KPI真的很主观）。本来可以缩短1个月的，而你故意只缩短几天，是为了给自己下次评KPI留有余地。&lt;/p&gt;
&lt;p&gt;现实可能更复杂。如果你是公司经营者，你会如何做呢？&lt;/p&gt;
&lt;h3 id=&#34;团队管理角度&#34;&gt;团队管理角度&lt;/h3&gt;
&lt;p&gt;如果你是这位老哥的直接领导，你觉得是什么原因，一项工作本来只需要10分钟完成，你的团队却需要1个月？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;你可能会怪这位老哥不“老实”。可是老哥一辈子不告诉你事实，你连“怪”的机会都没有。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你可能觉得这不是问题。因为手上人越多，你在公司里的份量就越大。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你可能根本就不知道自己的团队效率能提高那么多。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有很多可能性。留给读者朋友自己体会。&lt;/p&gt;
&lt;h3 id=&#34;问题到底是什么&#34;&gt;问题到底是什么&lt;/h3&gt;
&lt;p&gt;说了这么多，关于这位老哥的案例，问题到底是什么？&lt;/p&gt;
&lt;p&gt;不知道各位读者心里有没有问：他的工作内容是什么，为什么他的工作需要每个月重复。笔者认为这才是本案例的关键问题。&lt;/p&gt;
&lt;p&gt;如果你深入问了，团队效率提升是自然而然发生的事情。让程序员每天做重复的事情，TA会很难受。否则你招的可能是一个假的程序员。&lt;/p&gt;
&lt;p&gt;笔者认为：作为团队管理，通过发现“重复工作”来提高效率是一种常识。所以，如果有了这种常识，根本就不会发生本案例了。&lt;/p&gt;
&lt;p&gt;如何让每位团队成员拥有这种常识，这是另一个议题。而如何让整家企业的人都有这样的常识，这又是另一个议题。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;过去到现在，笔者经常听到的一句话：“过程我不管，我只看结果”（也被人称为：结果导向）。这句话本身是正确的，但是，我们如果把团队管理者看作篮球队的教练，比赛中，你和你的队员一直盯着计分牌（结果），对于比赛最后得分是没有任何益处的。&lt;/p&gt;
&lt;p&gt;是时候重新审视“以结果导向”在企业中带来的负作用了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>突发！！！Terraform、Consul、Vagrant等可以继续在中国使用！</title>
      <link>https://showme.codes/zh-cn/2020-5-30-hashicorp/</link>
      <pubDate>Sat, 30 May 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-5-30-hashicorp/</guid>
      <description>&lt;p&gt;昨天各种朋友、群，广泛传播以下信息：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;重磅消息!!!Terraform、Consul、Vagrant等禁止中国使用！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&#34;WechatIMG169.jpeg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-d392a13ead288926.jpeg&#34;&gt;&lt;/p&gt;
&lt;p&gt;我不清楚上面“Terms of Evaluation for HashiCorp Software”这个页面截图是什么时候的。HashiCorp旗下这么多软件，如上图。为什么他只圈Terraform、Consul、Vagrant？其它几款软件怎么不提？难道当时“Terms of Evaluation for HashiCorp Software”页面下文只提了Terraform、Consul、Vagrant？&lt;/p&gt;
&lt;p&gt;以下是我的最新截图（2020-5-30 06:34 中国时间）：
&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-80beb80388997476.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;使用机器翻译如下：
&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-0def52638593cbce.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;请注意，“Terms of Evaluation for HashiCorp Software”最新版说的是&lt;strong&gt;Vault企业版&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;整件事情，我们其实更应该问HashiCorp的人，他们为什么做这样的决定。
以下是2020-5-3 6:43 北京时间截图：
&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-fe46f33a640ffd8f.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;原文链接：&lt;a href=&#34;https://news.ycombinator.com/item?id=23349635&#34;&gt;https://news.ycombinator.com/item?id=23349635&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;笔者使用机器翻译如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;您好，我是HashiCorp的创始人，我想解释一下。
首先，本文档仅适用于企业评估软件。这不适用于我们的OSS软件，除非在注册企业评估的上下文中，否则不应将其链接到我们的OSS附近。
最重要的是：这为什么在这里？这不是政治声明。这是法律要求。我们在保险柜中使用的加密受中国出口管制法律的约束，并且（根据中国法律）我们在中国销售是非法的。
为了能够在中国销售保险柜，我们必须将可以在保险柜中使用的加密限制为政府可接受的版本。
我们不这样做，因此在中国销售是非法的。我们必须在企业术语中包括这一行。
编辑：我们的法律团队已更详尽地更新了我们的条款。您可以在此处的第二段中阅读更新的副本：https://www.hashicorp.com/terms-of-evaluation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;最后结论，Terraform、Consul、Vagrant等可以继续在中国使用！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文不是为了给HashiCorp洗白，其实别人也没故意黑。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最后，如果您觉得此文章说的是事实，请转发给更多的朋友，让他们看到事实。&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Jenkins kubernates原理</title>
      <link>https://showme.codes/zh-cn/2020-5-4-jenkins-kubernates-plugin/</link>
      <pubDate>Mon, 04 May 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-5-4-jenkins-kubernates-plugin/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-8163b7fa93e5d1fb.jpg&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;如何使用&#34;&gt;如何使用&lt;/h2&gt;
&lt;p&gt;使用Kubernetes插件时，我们需要做三件事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据官方文档，在Jenkins上加入kubernetes配置。&lt;/li&gt;
&lt;li&gt;在Jenkinsfile中加入kubernetes agent的申明。&lt;/li&gt;
&lt;li&gt;指定容器执行你的业务脚本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于第2点，kubernetes agent的申明又有两种方式。一种是脚本式的，代码样例如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;podTemplate&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;containers:&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;…&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;POD_LABEL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Run shell&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;container&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;mycontainer&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;echo hello world&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一种是申明式，代码样例如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pipeline&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;stages&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Run maven&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;kubernetes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;yaml&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  labels:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    app: jenkins-agent
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  - name: maven
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    image: maven:alpine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    command:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    - cat
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    tty: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                  - name: busybox
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    image: busybox
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    command:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    - cat
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                    tty: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;container&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;maven&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;mvn -version&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}}}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;笔者推荐使用申明式。yaml配置部分看起来并不优雅，这是另一个话题。咱们今后再讲。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 Jenkins &#43; Ansible 实现跨应用配置管理</title>
      <link>https://showme.codes/zh-cn/2020-3-12-jenkins-ansible-cross-conf/</link>
      <pubDate>Thu, 12 Mar 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-3-12-jenkins-ansible-cross-conf/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-29bb768786bc0467.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;本文继续前两篇 Jenkins + Ansible 的文章（见附录）的例子。代码仓库结构与 《使用 Jenkins + Ansible 实现 Spring Boot 自动化部署101》 介绍的相似。&lt;/p&gt;
&lt;p&gt;但是以下改进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增加了展示跨应用配置管理的样例（本文重点）&lt;/li&gt;
&lt;li&gt;实现了二进制包与配置分离&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;跨应用配置是什么&#34;&gt;跨应用配置是什么&lt;/h3&gt;
&lt;p&gt;《持续交付》的2.4.4节介绍了“跨应用配置管理”。但是书中没有明确给出它的定义。以下是笔者所理解的“跨应用配置”：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所谓跨应用配置指的是在同一个配置项同时被多个应用引用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如现实中同一个 Redis 的配置项（如地址、端口）就可能同时被多个业务系统引用。如下图所示。
&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-fc2abb960d0575b5.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;为什么要进行跨应用的配置管理&#34;&gt;为什么要进行跨应用的配置管理&lt;/h3&gt;
&lt;p&gt;如果没有跨应用配置的管理，我们就必须在应用1和应用2的配置文件中写死 redis 的配置项（在没有配置中心的情况下）。这样一看是没有问题的。但是笔者认为应用在到达10个以上的时候会（经常）遇到以下问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;无法实现快速重建一整套新的环境。新的环境意味着新的 redis 地址。也意味着所有引用了 redis 地址的应用的配置都要改。手工修改很容易出错。&lt;/li&gt;
&lt;li&gt;当你希望对现有的 redis 进行调整时，你无法评估影响面，因为你不知道哪些应用使用了这个 redis。进而，导致团队对架构优化的信心不足。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这两个问题会随着系统数量增加而加重。&lt;/p&gt;
&lt;p&gt;那么如何实现跨应用的配置管理以解决上述问题呢？&lt;/p&gt;
&lt;h3 id=&#34;如何实现跨应用的配置管理&#34;&gt;如何实现跨应用的配置管理&lt;/h3&gt;
&lt;p&gt;如果使用如 Ansible、Puppet、Chef 这类自动化工具，跨应用的配置管理就很容易实现。因为它们的变量系统，天生就支持一处定义配置项，其它地方到处引用。对 Ansible 变量不熟悉的同学可以在文末找到学习链接。&lt;/p&gt;
&lt;p&gt;在我们的 Nginx + Spring Boot 的例子中，对配置代码仓库（2-env-conf）进行了调整，结构如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;l&#34;&gt;├── Jenkinsfile&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;l&#34;&gt;├── README.MD&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;l&#34;&gt;└── dev&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;├── group_vars&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;│   ├── all &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Ansible 默认的 all 组变量目录&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;│   │   └── global.yaml  &lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;│   └── nginx.yaml&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# nginx 组变量&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;├── host_vars&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;│   ├── 192.168.52.10&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;│   └── 192.168.52.11&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;└── hosts&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因为 Spring Boot 应用的端口会被 Nginx 的配置引用，所以，我们将端口的配置项放到 global.yaml 中，代码如下所示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如果张小龙谈 DevOps 平台</title>
      <link>https://showme.codes/zh-cn/2020-1-3-devops-zhangxiaolong-wechat/</link>
      <pubDate>Fri, 03 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2020-1-3-devops-zhangxiaolong-wechat/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;zhangxiaolong.jpg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-2967a495fcbf69d0.jpg&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;设计原则&#34;&gt;设计原则&lt;/h3&gt;
&lt;p&gt;张小龙谈微信：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们没办法让10亿人来投票决定什么是好的，也投不出来。那怎么才能通过改变寻求设计的优化，让它变得更好呢？这个决策必须遵循好的设计原则。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;张小龙谈 DevOps 平台：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们没办法让所有研发团队来投票决定什么样的 DevOps 平台是好的，也投不出来。那怎么才能通过改变寻求设计的优化，让它变得更好呢？这个决策必须遵循好的设计原则。
我把这几个原则念给大家听下，大家可以对照 DevOps 平台来思考一下，会很有意思。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为软件的发布创建一个可重复且可靠的过程&lt;/li&gt;
&lt;li&gt;将几乎所有的事情自动化&lt;/li&gt;
&lt;li&gt;把所有的东西都纳入版本控制&lt;/li&gt;
&lt;li&gt;提前并频繁地做让你感到痛苦的事&lt;/li&gt;
&lt;li&gt;内建质量&lt;/li&gt;
&lt;li&gt;&amp;ldquo;DONE&amp;rdquo; 意味着“已发布”&lt;/li&gt;
&lt;li&gt;交付过程是每个成员的责任&lt;/li&gt;
&lt;li&gt;持续改进&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;老翟插话：以上设计原则，是《持续交付》中1.6章节中写的。&lt;/p&gt;
&lt;h3 id=&#34;做最好的工具与-996&#34;&gt;做最好的工具与 996&lt;/h3&gt;
&lt;p&gt;张小龙谈微信：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个用户每天的时间是有限的，这是次要的。最主要的是，技术的使命应该是帮助人类提高效率。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;张小龙谈 DevOps 平台：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个程序员每天的时间是有限的，这是次要的。最主要的是 DevOps 平台的使命应该是帮助研发团队提高软件发布的效率。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;老翟插话：我的真实经历，当我问一个 DevOps 平台的设计人员为什么要把部署阶段设计得这么难用（效率低下）。得到的答案是怕用户部署错。这是使用老的思路来设计 DevOps 平台：如果一件事情容易出错，那我们就尽量少做。而让用户难用，就可以自然实现目的。&lt;/p&gt;
&lt;h3 id=&#34;关于社交关于-devops-本源&#34;&gt;关于社交，关于 DevOps 本源&lt;/h3&gt;
&lt;p&gt;张小龙谈微信：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实我们人的社交是没有发生改变的，或者说社交的需求并没有发生改变。我们在线上的社交只是线下的社交的一个映射而已。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;张小谈 DevOps 平台：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实我们研发团队的软件发布是没有发生改变的，或者说软件发布的需求并没有发生改变。我们在 DevOps 平台上的软件发布只是线下的软件发布的一个映射而已。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;老翟插话：为什么开源类的 CI/CD 平台，Jenkins 占有率那么高？很大一部分原因是人们从原来的手工发布迁移到 Jenkins 上，非常的平滑，自然。纵观现在很多 DevOps 平台，把基本的构建编译的命令都隐藏起来，不允许用户轻松地看到或者修改。这是那些 DevOps 平台“难用”的原因之一。&lt;/p&gt;
&lt;h3 id=&#34;什么是好的-devops-平台&#34;&gt;什么是好的 DevOps 平台&lt;/h3&gt;
&lt;p&gt;张小龙谈微信：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我觉得一个好的产品不需要费口舌解释，我解释了这么多，说明我们做得不够好。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;张小龙谈 DevOps 平台：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我觉得一个好的 DevOps 平台不需要费口舌解释，我解释了这么多，说明我们做得不够好。&lt;/p&gt;</description>
    </item>
    <item>
      <title>业务老大问 DevOps 改进半年后，会得什么确切结果？</title>
      <link>https://showme.codes/zh-cn/2019-12-31-devops-kpi/</link>
      <pubDate>Tue, 31 Dec 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-12-31-devops-kpi/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-a70cbe852bb13759.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;要的是确切结果不要忽悠我&#34;&gt;要的是确切结果，不要忽悠我&lt;/h3&gt;
&lt;p&gt;前段时间，乔帮主（乔梁，《持续交付2.0》的作者）在持续交付2.0的群里发出这一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;业务老大问：“原来100工程师做的一个产品，用半年时间做 devops 改进。半年之后会得到什么确切的结果？”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以上是原话。&lt;/p&gt;
&lt;p&gt;乔帮主在群发这话的意思是：如果你的业务老大问这样的问题，你该如何回答？&lt;/p&gt;
&lt;p&gt;请注意，业务老大要的是确切结果，不要拿“虚”的东西来忽悠人。&lt;/p&gt;
&lt;p&gt;请读者朋友思考一会。
。&lt;/p&gt;
&lt;p&gt;。&lt;/p&gt;
&lt;p&gt;。
客官不要急，请再思考一会鸭。
。&lt;/p&gt;
&lt;p&gt;。&lt;/p&gt;
&lt;p&gt;面对老大这样的提问，技术人可能觉得好笑，接着可能装作一本正经回答老大：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果DevOps改进半年后，会使单元测试覆盖率提高到 80%。&lt;/li&gt;
&lt;li&gt;如果DevOps改进半年后，会使A系统的部署耗时缩短到 1 分钟。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这回答真的很“确切”，但是，还是没有办法说服业务老大。注意，业务老大是不懂技术的。老大听到你的回答，估计一头雾水：什么是单元测试覆盖率？提高到 80% 后，对我的业务 KPI 又有什么关系？&lt;/p&gt;
&lt;p&gt;别笑，这在 IT 行业里是常态。很多时候是不懂技术的老大，却又领导着一批技术人员。&lt;/p&gt;
&lt;p&gt;面对这样的“常态”，作为技术人员，我们有必要，也有责任让不懂技术的业务老大理解 IT 行业里必要的“常识”。&lt;/p&gt;
&lt;p&gt;回到业务老大的问题，如果你仔细思考，还真不好回答。比如，单元测试覆盖率提高到 80% 后，对我的业务 KPI 又有什么关系？能让我的业务 KPI 提高 80% 吗？&lt;/p&gt;
&lt;h3 id=&#34;老翟的做法&#34;&gt;老翟的做法&lt;/h3&gt;
&lt;p&gt;当笔者看到这个问题，第一感觉就是：我们必须找到 DevOps 改进措施和业务老大关心的 KPI 之间的关系。换句话说，就是如果在100名工程师做一个产品的团队的情况下，进行 DevOps 改进半年后，会给我的业务 KPI 带来什么&lt;strong&gt;确切&lt;/strong&gt;的结果？&lt;/p&gt;
&lt;p&gt;笔者认为业务老大的问题，可以拆分成两个小问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有效性问题：如何证明 DevOps 改进是对业务 KPI 提升是有效的？&lt;/li&gt;
&lt;li&gt;进度问题：怎么评估 DevOps 改进对业务  KPI 提升了多少？&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;如何证明-devops-改进是对业务-kpi-提升是有效的&#34;&gt;如何证明 DevOps 改进是对业务 KPI 提升是有效的？&lt;/h4&gt;
&lt;p&gt;这就是我说的：&lt;strong&gt;我们必须找到 DevOps 改进措施和业务 KPI 之间的关系&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;“DevOps 改进措施和业务 KPI 之间的关系”指的是什么？这需要针对不同的业务场景进行举例说明。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从王垠面试阿里的事件看程序员招聘</title>
      <link>https://showme.codes/zh-cn/2019-12-25-yinwang-alibaba/</link>
      <pubDate>Wed, 25 Dec 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-12-25-yinwang-alibaba/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-fa34b7044dfbb1bc.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;餐饮行业里，有些饭店的服务员应聘是需要试工的。就是让应聘者穿上酒店的工作服，然后在工作繁忙时间，工作一段时间。&lt;/p&gt;
&lt;p&gt;这段时间里，面试官可以观察到他在点菜时会不会与客人互动，上菜过程中的专业程度等。进而判断要不要录入他。&lt;/p&gt;
&lt;p&gt;试工后，基础能回答这人能否胜任当前工作。&lt;/p&gt;
&lt;p&gt;当然，“试工”对于招聘起到效果也取决于面试官。这是另一个问题了。&lt;/p&gt;
&lt;p&gt;而 IT 行业，如果面试官不问个偏门算法，问个万万亿级流量的处理解决方案，似乎显得自己比面试者差。所以，IT 行业有个笑话：面试造火箭，入职拧螺丝。&lt;/p&gt;
&lt;p&gt;我们是不是应该换个方式来招聘程序员？也用“试工”来遴选人才。&lt;/p&gt;
&lt;p&gt;据我所知，ThoughtWorks 很多年前就已经采用“试工”的方式招人了。以下是笔者当时的面试经历：&lt;/p&gt;
&lt;p&gt;一面是与 HR 简单聊聊。&lt;/p&gt;
&lt;p&gt;二面，你必须在规定时间内完成一个家庭作业（需要写代码）。当你把作业交上去，HR 会找到公司内部的程序员帮忙看题（这样有助于缓和HR与程序员的关系，因为HR有求于程序员啊。）。&lt;/p&gt;
&lt;p&gt;三面，他们会邀请你到办公室，然后HR找两个程序员和你结对编程（注意：这里是真实的上机写代码），内容就是在你交的作业的基础上加需求。过程中，他们会观察你，会提问你。&lt;/p&gt;
&lt;p&gt;其实，整个三面的过程，就是试工的过程。虽然不能拿真实代码来改，但是也尽量模拟真实的工作场景：结对编程、别人对你代码的质疑等。&lt;/p&gt;
&lt;p&gt;像 ThoughtWorks 这样试工的，在我们行业里，真的太少了。&lt;/p&gt;
&lt;p&gt;回到阿里面试王垠（暂不说是不是受邀面试）这件事。&lt;/p&gt;
&lt;p&gt;从赵海平的回复来看：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;整个面试最关键的过程恰好是对简历上具体工作的详细了解，这个王垠在博客里完全没有提到，实际上我问了将近二十到三十分钟，我希望王垠能够意识到这部分才是面试真正考核的部分，应该尽量把自己最拿手最出彩的工作分享给面试官，详细解释为什么难，为什么有意义，为什么对公司有着深远的影响，而不是直接问面试官是做什么的，到底懂不懂，很遗憾，我恰好是做编译器的，在Facebook做了PHP编译器，在阿里巴巴领导了团队在Java里加入了透明的协程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从这一段话来看，赵海平花了很长时间问王垠的过去。&lt;/p&gt;
&lt;p&gt;赵海平是不是可以让王垠试试解决一下自己所在团队当前遇到的技术问题，又或者让王垠试着重新实现一遍自己骄傲的“透明的协程”？这个过程，我相信是非常兴奋的。&lt;/p&gt;
&lt;p&gt;以上两种尝试其实也算是一种试工。能解决团队遇到的问题，能做面试官能做的，应该算是能胜任他所面试的岗位了吧？&lt;/p&gt;
&lt;p&gt;毕竟，是想招这个人来解决问题的。而不是抓住他的过去不放。&lt;/p&gt;
&lt;p&gt;再说，有些应聘者可能真的不知道要怎么回答面试中的——真正考核的部分。所以，回答不上来，个人也觉得很正常。因为我也是那样的人。&lt;/p&gt;
&lt;p&gt;最后，我疑问，在赵海平的这次面试里，“真正考核的部分”真的比“这个人能否真正解决问题”重要吗？HR 面，我可以理解。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;我们都是外人。所以，真正的背后的动机，上下文只有当事人知道。我不想评价他们个人和公司。只想讨论一下“试工”在IT行业的可能性。让更多人知道，招聘程序员，还有另一种姿势。&lt;/p&gt;
&lt;p&gt;笔者是从这文章了解到事件的：&lt;a href=&#34;https://www.ithome.com/0/464/417.htm&#34;&gt;https://www.ithome.com/0/464/417.htm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;笔者的阿里三面经历：&lt;a href=&#34;https://showme.codes/2018-06-24/alibaba-interview/&#34;&gt;https://showme.codes/2018-06-24/alibaba-interview/&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈 DevOps 平台设计：为什么用户喜欢在构建后加一句 ls -al .</title>
      <link>https://showme.codes/zh-cn/2019-11-28-devops-platform-workspace/</link>
      <pubDate>Thu, 28 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-28-devops-platform-workspace/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-fcd261b506ae3d41.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;最近发现用户都喜欢构建命令最后加上一行：&lt;code&gt;ls -a .&lt;/code&gt;。为什么呢？&lt;/p&gt;
&lt;p&gt;是因为他们想知道执行 &lt;code&gt;npm run build&lt;/code&gt; 后的目录的结果是什么样的。目录里到底有没有出现期望的文件。&lt;/p&gt;
&lt;p&gt;这个功能在 Jenkins 中叫做“工作空间”。其实就是源码在 Jenkins 上下载后的目录。Jenkins 中，用户是可以直接像在查看本地文件夹一样查看这个工作空间的内容。如下图如示。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-43a761d9a9f32200.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;为什么用户想要看工作空间中的目录结构呢？说到底就是用户本地打包环境和平台打包环境还是有区别的。当出现构建结果不符合预期时，用户需要根据工作空间的信息来找到失败原因。&lt;/p&gt;
&lt;p&gt;另，笔者看了行业内的几个平台，没有找到“工作空间”的功能。为什么没有这个功能呢？不知道。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;“工作空间”这个特性本身提高了用户在平台上的自检能力。因为平台上的信息对于用户更透明了。那么实现这个功能的成本呢？这是我们在实现前要考虑的问题。&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈 DevOps 平台设计：为什么部署后的包还是旧的包？</title>
      <link>https://showme.codes/zh-cn/2019-11-27-devops-platform-deploy-inventory/</link>
      <pubDate>Wed, 27 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-27-devops-platform-deploy-inventory/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-44c2901e907f8a74.png&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么部署后的包还是旧的包？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;有位同学说他在 DevOps 平台部署后，代码还是旧的。&lt;/p&gt;
&lt;p&gt;在我跟他确认了构建时拉取的代码是版本是对的，部署时，使用的制品包也是正确的情况下 ，我也是摸不着头脑了。&lt;/p&gt;
&lt;p&gt;最后，这位同学在 DevOps 平台的界面上看到了部署的目标机器的IP。&lt;/p&gt;
&lt;p&gt;这才找到了真正的原因：他们自己部署的目标机器搞错了。也就是本来想部署到 A 机器，但是部署任务填写的是 B 机器的 IP。然后自己一直在 A 机器上检查部署结果。&lt;/p&gt;
&lt;p&gt;接着，这位同学就感叹自己对 DevOps 不熟。&lt;/p&gt;
&lt;p&gt;然而，笔者认为，那根本不是用户对 DevOps 熟不熟的问题。而是 DevOps 平台的设计问题。&lt;/p&gt;
&lt;p&gt;为什么用户连自己部署错了主机都不知道？笔者认为那是因为目标机器的 IP 信息在界面上不够明显，其他所有的信息就只能通过分析 DevOps 平台提供的日志才能知道了。&lt;/p&gt;
&lt;p&gt;所以，我们的平台需要提供这两个功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署清单&lt;/strong&gt;：列出本次部署的具体内容，可以包括：制品版本，执行人，目标机器列表，部署策略，回滚策略等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署结果报告&lt;/strong&gt;：制品版本，目标机器的部署结果，与上一次部署的差异（这是关键）、多次部署报告的差异性比较功能等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;部署结果报告中必须要强调此次部署与上次部署之间的差异，毕竟大多数应用不会那么频繁的更换部署机器。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;“DevOps” 这个概念本身已经把很多人搞得云里雾里。在网上搜索一下 DevOps 的定义就知道了。所以，用户在使用 DevOps 平台时，达不到预期值，就习惯性地认为是自己的问题。我们作为平台设计方，用户达不到预期，那就是平台设计的问题。&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈 DevOps 平台落地：你们流水线从编译到部署需要多少分钟啊？</title>
      <link>https://showme.codes/zh-cn/2019-11-21-devops-pipeline-speed/</link>
      <pubDate>Thu, 21 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-21-devops-pipeline-speed/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-7716aa968a03ce80.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;一位同学的疑问&#34;&gt;一位同学的疑问&lt;/h3&gt;
&lt;p&gt;有一位同学问我：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你们一个流水线从编译到部署成功需要多少分钟啊。我们快的2分钟，Java 普遍10分钟，开发同学总是觉得慢，我不知道业界是什么水平。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我的回答是： 没有行业标准的，有些项目10万行代码，有些100万行代码，没法比。&lt;/p&gt;
&lt;p&gt;后来，我又问他：你们的流水线都包括了哪些阶段？&lt;/p&gt;
&lt;p&gt;“编译，带数据库的测序，sonar，docker build，ansible deploy，mvn release”，他回答。&lt;/p&gt;
&lt;p&gt;后来的沟通中，我得知，他们流水线时间长发生在两个阶段：带数据库的测试和下载依赖。&lt;/p&gt;
&lt;p&gt;下载依赖慢是因为他们每次构建都重新下载依赖，这样做又是因为总是遇到缓存问题，所以，干脆就每次重新下载了。&lt;/p&gt;
&lt;p&gt;带数据库的测试通常会慢，因为要启动应用，然后操作数据库，这个数据不确定他是类似 MySQL 这样的真实数据库，还是使用 H2 这样的内存数据库。&lt;/p&gt;
&lt;p&gt;出乎我意料的是，他们的 SonarQube 扫描倒是很快。&lt;/p&gt;
&lt;p&gt;所以，他们的优化点就是那两个慢的阶段。具体解决办法还要看具体情况，比如带数据库的测试是不是可以通过并行解决、构建时依赖缓存遇到的问题是不是可以通修改构建工具配置来解决。&lt;/p&gt;
&lt;p&gt;写到这里，我想表达的是，优化流水线的速度的思路，差不多就是这样：先找到最慢的阶段，然后根据具体情况来优化。&lt;/p&gt;
&lt;h3 id=&#34;从-devops-平台设计角度解决&#34;&gt;从 DevOps 平台设计角度解决&lt;/h3&gt;
&lt;p&gt;那么，作为 DevOps 平台，我们能通过什么办法帮助用户提高流水线的速度呢？&lt;/p&gt;
&lt;p&gt;笔者认为，只要将流水线中的每个阶段中的每个步骤的耗时都记录下来，然后显示给用户，用户自然会注意到每次流水线的执行速度差异。当然，管理层也可以对这部分内容进行考核。&lt;/p&gt;
&lt;p&gt;同时，要将耗时进行分类，一类是用户步骤的耗时，比如执行mvn package，执行单元测试等，一类是 DevOps 平台本身的耗时，比如初始化构建环境耗时，上传制品耗时等。&lt;/p&gt;
&lt;p&gt;为什么要进行这样的分类？是因为 DevOps 平台使用过程中，用户遇到问题，往往是区分不了，是平台的问题，还是自己的问题。这时，我们将平台的信息显示给用户，用户就可以自行判断，自行处理了。这会大大节约平台维护者的时间。而且，平台维护者也可以根据平台运行耗时统计来对平台进行有依有据的优化。这是一箭双雕。&lt;/p&gt;
&lt;p&gt;我把这个功能叫做：&lt;strong&gt;流水线耗时统计&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那么这个功能，到底应该如何实现？不同的平台有不同的实现，比如基于 Jenkins 的话，在每个步骤后加上一个回调请求就可以了；基于 GitLab 的话，就不了解了。
&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-4a7380dad7d90eee.png&#34;&gt;
图来自：&lt;a href=&#34;https://wiki.jenkins.io/display/JENKINS/Pipeline+Stage+View+Plugin&#34;&gt;https://wiki.jenkins.io/display/JENKINS/Pipeline+Stage+View+Plugin&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;流水线的速度是一个很重要的指标，它直接显示了一个软件开发团队在工程方面的效率（正确性问题是另一个问题）。而&lt;strong&gt;流水线耗时统计&lt;/strong&gt;功能可以有效地帮助用户提高自己的流水线速度。&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈 DevOps 平台落地：前端项目构建又失败了</title>
      <link>https://showme.codes/zh-cn/2019-11-20-devops-platform-front-dependency-manage-error/</link>
      <pubDate>Wed, 20 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-20-devops-platform-front-dependency-manage-error/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-29f9b65037c41ed7.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;同事发了一个前端构建失败的链接过来，接着就是那句：任务执行失败了，麻烦帮忙看看。&lt;/p&gt;
&lt;p&gt;DevOps 平台的“老手”了，所以，在找我们解决问题时，都知道附上平台任务的链接。&lt;/p&gt;
&lt;p&gt;我们打开链接，第一件事情就是看日志。是的，DevOps 平台的使用者很多都认为：在本地执行构建成功，那么在平台上构建失败就是平台的问题。所以部分人连构建日志都不看，直接把链接发给我们这些平台维护者看。&lt;/p&gt;
&lt;p&gt;不出意外，这次又是依赖管理问题。只不过，这次是发生在前端项目上。错误截图下如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-2a1aace1705f4f15.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;日志里（画红线部分）已经说得很清楚了。虽然我不清楚“Tristan”是什么，但是可以猜到是他的业务代码报这样的错。但是他本地执行没报错，那通常就是依赖的版本的问题了。&lt;/p&gt;
&lt;p&gt;他的前端的依赖定义(package.json)类似以下这样：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;.....&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;cookie-parser&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;^1.4.3&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;debug&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;~2.6.9&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;express&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;^4.16.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;http-errors&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;^1.6.2&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;morgan&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;~1.9.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;pug&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;2.0.0-beta11&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们看到依赖的版本号的前缀有 &lt;code&gt;~&lt;/code&gt;，有也 &lt;code&gt;^&lt;/code&gt;。这是什么意思呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~&lt;/code&gt;: 前缀表示，安装大于指定的这个版本，并且匹配到 x.y.z 中 z 最新的版本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt;: 前缀在 ^0.y.z 时的表现和 ~0.y.z 是一样的，然而 ^1.y.z 的时候，就会 匹配到 y 和 z 都是最新的版本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，每次执行 npm install ，该项目所依赖的内容，都是有可能变的。&lt;/p&gt;
&lt;p&gt;这对我来说是不可思议的。为什么？&lt;/p&gt;
&lt;p&gt;因为依赖的版本代表着一个软件的基础。依赖的版本在你不知道的情况下发生变更，就好比建房子，建第一层时，地基是100个平方，建第二层时，地基突然就变成了90个平方。而前端项目中大量这种情况。&lt;/p&gt;
&lt;p&gt;你可能会说开源前端node项目都会遵循语义化的版本号，小版本升级不会出问题的。我想说，那只是约定，还是要看那个人遵守不遵守。如果你真相信每个人都遵守，本质上是把软件开发风险的控制权交给了开源软件作者的个人习惯。开发出来的软件注定是不稳定的。&lt;/p&gt;
&lt;p&gt;但是，为什么前端项目的依赖的版本号前普遍会加上 &lt;code&gt;~&lt;/code&gt; 和 &lt;code&gt;^&lt;/code&gt; 。在我亲自执行 &lt;code&gt;npm install express&lt;/code&gt; 命令时，我知道了原因。因为在执行命令后， package.json 文件中就出现了：&lt;code&gt;&amp;quot;express&amp;quot;: &amp;quot;^4.16.0&amp;quot;&lt;/code&gt;。也是 npm 在安装依赖时，默认就给版本号加上 &lt;code&gt;^&lt;/code&gt; 前缀。而很多人可能改都不会去改。这就导致了文章开头所说的那位同事的问题。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;真心希望大家固定下 package.json 中的依赖的版本号。这样的前端项目构建起来才有稳定的基础。&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈DevOps平台落地：前端构建怎么这么变态</title>
      <link>https://showme.codes/zh-cn/2019-11-12-devops-platform-front-dependency-manage/</link>
      <pubDate>Tue, 12 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-12-devops-platform-front-dependency-manage/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;tibet-4538357_640.jpg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-cdd2f3d31e821aad.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;题记：DevOps 平台通常搭建于内网环境，不能直接外网，所以，如果你也要在内网环境构建前端，就一定会遇到本文所说的问题。&lt;/p&gt;
&lt;p&gt;我们发现在 DevOps 平台构建前端项目时，会报这以下这样的错误：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;node scripts/install.js
Downloading binary from &lt;a href=&#34;https://github.com/sass/node-sass/releases/download/v4.9.0/linux-x64-57_binding.node&#34;&gt;https://github.com/sass/node-sass/releases/download/v4.9.0/linux-x64-57_binding.node&lt;/a&gt;
Cannot download &amp;ldquo;&lt;a href=&#34;https://github.com/sass/node-sass/releases/download/v4.9.0/linux-x64-57_binding.node&#34;&gt;https://github.com/sass/node-sass/releases/download/v4.9.0/linux-x64-57_binding.node&lt;/a&gt;&amp;rdquo;:
tunneling socket could not be established, statusCode=500
Hint: If github.com is not accessible in your location
try setting a proxy via HTTP_PROXY, e.g.
export HTTP_PROXY=&lt;a href=&#34;http://example.com:1234/&#34;&gt;http://example.com:1234&lt;/a&gt;
or configure npm proxy via
npm config set proxy &lt;a href=&#34;http://example.com:8080/&#34;&gt;http://example.com:8080&lt;/a&gt;
&lt;a href=&#34;mailto:node-sass@4.9.0&#34;&gt;node-sass@4.9.0&lt;/a&gt; postinstall &lt;/pre&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以上的错误日志的意思是node在安装 node-sass 时，要去 github.com/sass/node-sass 下载一个名为 &lt;code&gt;linux-x64-57_binding.node&lt;/code&gt; 的二进制包。然后它无法下载（其实是因为DevOps平台搭建在企业的内网，是无法直接连接外网的），就建议你设置一下系统的HTTP代理，让它能连接到 github.com。&lt;/p&gt;
&lt;p&gt;除此之外，错误日志中，还发现了，node-sass 依赖本身的构建，还需要 Python2 环境：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gyp verb check python checking for Python executable &amp;#34;python2&amp;#34; in the PATH
gyp verb `which` failed Error: not found: python2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于一个 Java 后端开发人员，看到这样的错误就懵了。心里在想：&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈 DevOps 平台实施：我在本地跑明明成功的，为什么在你平台跑就报错？</title>
      <link>https://showme.codes/zh-cn/2019-11-11-devops-platform-dependency-manage/</link>
      <pubDate>Mon, 11 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-11-devops-platform-dependency-manage/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;我在本地跑明明成功的，为什么在你平台跑就报错？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;用户在 Jenkins 上跑构建时，失败了，把日志截图给我看，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-61d91a89ff7c3df4.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;在过去几个月，每个星期都会有一两个 Jenkins 用户就会给我发送类似的错误日志。&lt;/p&gt;
&lt;p&gt;这样的日志，我通常回：请检查你们的依赖，是不是有依赖没有上传到咱们的 Nexus 仓库。验证方法是先在本地删除你的 .m2  目录，然后再执行一次构建。&lt;/p&gt;
&lt;p&gt;当用户业务开发比较急的时候，他们还会说本文标题中的那句话。有些抱怨的意思。我都已经习惯了。&lt;/p&gt;
&lt;p&gt;出现这样的情况，我总结大概会有以下原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户对于 Maven 这类构建工具不熟悉。&lt;/li&gt;
&lt;li&gt;用户对于依赖管理不重视，或者没有依赖管理的意识。&lt;/li&gt;
&lt;li&gt;用户根本不看日志。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;面对这三个原因，我就在思考：我们 DevOps 平台能做些什么呢？&lt;/p&gt;
&lt;p&gt;我觉得 DevOps 平台是不是可以直截了当地告诉用户：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;xxx 依赖在 Nexus 仓库（maven.abc.com）中没有找到，请您先 deploy 该依赖到 Nexus 仓库后，再执行此任务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果能检测到缺少的依赖放在哪个代码仓库就更好了。因为这样，就可以提示用户直接到该代码仓库的 deploy 了。&lt;/p&gt;
&lt;p&gt;这样的技术，我称为&lt;strong&gt;依赖AI管理技术&lt;/strong&gt;（笑）。当然，这样的技术，应该可以应用于所有的语言。&lt;/p&gt;
&lt;p&gt;同时，我们将这些数据（依赖管理失误）统计起来，就可以看出一个团队在依赖管理方面的能力表现了，进而可以有效的对团队进行培训，以提高相应的能力。&lt;/p&gt;
&lt;p&gt;回到本文主题，当用户自行检查依赖后，大多数时候，用户就不会来找我了，因为问题已经解决了。可是有一次，用户还是说不行，他已经把 .m2 删除，并把依赖包上传到 Nexus 仓库了。&lt;/p&gt;
&lt;p&gt;我检查了他的 pom.xml 文件，发现版本号的定义也是正确的。可是，放在 Jenkins 上执行时，使用的还是旧版本的类的定义。&lt;/p&gt;
&lt;p&gt;这就奇怪了。这种情况还是头一回遇到。来来回回检查了好几次，查了好久才知道，是因为用户 deploy 依赖到 Nexus 时，deploy 的是相同的版本号，就是覆盖了原来的版本的包，但是版本没有升级。而 Maven 检测到本地就该版本的依赖，就不会重新下载了。最后，就是大家看到的，本地可以，但是 Jenkins 上就是不行。&lt;/p&gt;
&lt;p&gt;最后的解决方式是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户 deploy 一个新的版本到 Nexus 仓库，并在 pom.xml 中使用新的版本。&lt;/li&gt;
&lt;li&gt;我们将 Nexus 设置为不允许重复 deploy。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;小结&#34;&gt;小结&lt;/h3&gt;
&lt;p&gt;经过这次事件，我们可以看出，依赖管理对于工程质量的重要性。因为，依赖管理不当，很有可能在连开发人员都不知情的情况下引入Bug。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Ansible 开发环境的搭建</title>
      <link>https://showme.codes/zh-cn/2019-11-7-ansible-dev-setup/</link>
      <pubDate>Thu, 07 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-7-ansible-dev-setup/</guid>
      <description>&lt;p&gt;通常我不喜欢写开发环境搭建类文章的，但是见到不少同学在 Ansible 的开发环境花了很多时间。所以，就想写这么一篇文章。希望能帮助到有需要的同学。&lt;/p&gt;
&lt;p&gt;在介绍开发环境搭建之前，需要介绍 Ansible 脚本的开发流程。&lt;/p&gt;
&lt;p&gt;不像普通的业务系统的开发，只需要打开 IDE 就可以写代码，然后调试了。当然 Ansible 脚本也可以进行单元测试，但是 Ansible 脚本还是需要真实运行并部署，才能验证脚本的正确性。&lt;/p&gt;
&lt;p&gt;所以，Ansible 脚本的开发过程通常是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动一台虚拟机。&lt;/li&gt;
&lt;li&gt;在开发机上编辑 Ansible 脚本。&lt;/li&gt;
&lt;li&gt;在开发机上执行 &lt;code&gt;ansible-playbook -i hosts playbook.yml&lt;/code&gt; 命令。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-d7b06e6bd82792df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&#34;&gt;&lt;/p&gt;
&lt;p&gt;通过 Ansible 脚本的开发过程了解到，开发环境的搭建可以分成3部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;测试机器的准备。&lt;/li&gt;
&lt;li&gt;文本编辑器的准备。&lt;/li&gt;
&lt;li&gt;Ansible 的安装。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;1-测试机器的准备&#34;&gt;1. 测试机器的准备&lt;/h3&gt;
&lt;p&gt;见到不少同学使用 Vmware 或 VirtualBox 手工创建虚拟机。这种方式是可以达到搭建测试机器的目的。但是笔者认为这样不够好。因为验证 Ansible 脚本，我们需要频繁创建新的虚拟机。手工创建虚拟机的效率太低。而且不利于版本控制。&lt;/p&gt;
&lt;p&gt;所以，测试机器的准备，笔者使用的是 Vagrant。通过它，可以自动化创建和配置虚拟机。当然，整个过程还是版本控制的。&lt;/p&gt;
&lt;p&gt;同时需要注意，Vagrant 本身并不是一个虚拟机的实现，它是基于 VirtualBox 和 Vmware 的。换句说就是我们可以通过 Vagrant 去控制 Vmware 和 VirtualBox。所以，在安装 Vagrant 的同时，也需要安装 VirtualBox 或 Vmware。本文使用 VirtualBox。&lt;/p&gt;
&lt;p&gt;Vagrant 和 VirtualBox 的具体安装在本文末有官方教程。&lt;/p&gt;
&lt;h3 id=&#34;2-vagrant-介绍&#34;&gt;2. Vagrant 介绍&lt;/h3&gt;
&lt;p&gt;Vagrant 本身只是一个软件，提供了 &lt;code&gt;vagrant&lt;/code&gt; 命令。我们通过一个名为 Vagrantfile 的文件声明启动什么配置的虚拟机。&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈 DevOps 平台设计：版本号相关功能的设计</title>
      <link>https://showme.codes/zh-cn/2019-11-6-devops-version-feature/</link>
      <pubDate>Wed, 06 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-6-devops-version-feature/</guid>
      <description>&lt;p&gt;在设计 DevOps 平台时，笔者认为版本号的管理是一个绕不开的课题。可是，行业里似乎很少人提这个事，笔者觉得要谈一谈，所以就有了这篇文章。&lt;/p&gt;
&lt;h3 id=&#34;一万个人的眼里有一万个版本号&#34;&gt;一万个人的眼里有一万个“版本号”&lt;/h3&gt;
&lt;p&gt;笔者这三年在同一家公司里，换岗换了四个团队。团队的成员组成各异，有的团队都是在大型跨国企业跳槽过来的，有的团队大部人都是刚毕业的。&lt;/p&gt;
&lt;p&gt;每到一个团队，团队运行一段时间，都会做一件事情：讨论该怎么定义这个版本号。版本号的制定，有些只有开发人员参与，有时会有产品经理参与，有时还有 PMO 参与。&lt;/p&gt;
&lt;p&gt;经过这些讨论，我发现：一万人的眼里有一万个“版本号”。讨论的最后，基本上就是谁的嗓子大，听谁的。&lt;/p&gt;
&lt;p&gt;所以，在讨论“版本号”之前，一定要搞清楚讨论各方对于“版本号”的理解，再深入讨论，否则，大家谈的都会是牛头不对马嘴的东西。浪费时间。&lt;/p&gt;
&lt;p&gt;为什么对于“版本号”，各方的理解，差异会如此大。笔者认为，主要是因为他们关心的面不同。&lt;/p&gt;
&lt;p&gt;APP产品经理关心的是该APP在用户界面上显示的版本号，比如当前爱彼迎的APP的版本号是：1.9.44.china。&lt;/p&gt;
&lt;p&gt;对于后端开发工程师，关心的是网关服务的版本是1.2.1、客服服务的版本是4.11.1。&lt;/p&gt;
&lt;p&gt;对于前端开发工程师，关心的是通用组件的版本是2.1.1、首页组件的版本是3.1.1。&lt;/p&gt;
&lt;p&gt;而对于 PMO，他们可能只关心在 Staging 环境的最后一个版本是否为一个稳定的版本（这写在他们的管理规范里），保证不影响测试人员的工作，根本不关心具体的“版本号”是多少。&lt;/p&gt;
&lt;h3 id=&#34;重新认识版本号&#34;&gt;重新认识版本号&lt;/h3&gt;
&lt;p&gt;各方的关注点不同，不是问题，但是我们作为一个平台的设计必须对“版本号”有更深入的理解。&lt;/p&gt;
&lt;p&gt;笔者分析各方的关注点，他们所说的“版本号”分布在以下两个层面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;技术层面：程序员关心线上跑的是哪份代码（对应的是Git\SVN中的Commit ID）、运维关心线上跑是哪个版本（对应的就是具体哪个包）。&lt;/li&gt;
&lt;li&gt;业务层面：方便终端用户识别的版本号，产品经理也属于这一层面。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;认识到这点，我们设计DevOps平台，就会对两种版本号进行区别对待，进而设计出对团队非常有用的功能，最终帮助团队更好的实现交付。&lt;/p&gt;
&lt;p&gt;为方便沟通，技术层面的版本号，如 Commit ID 我们称为&lt;strong&gt;技术版本号&lt;/strong&gt;，业务层面的版本号，称为&lt;strong&gt;业务版本号&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;版本号相关功能设计&#34;&gt;版本号相关功能设计&lt;/h3&gt;
&lt;p&gt;但是版本号有什么用？仔细想想，除了产品经理发布时要定个版，后端服务的版本用于保证服务之间的相互引用或调用不出问题，就没有什么别的用处了。&lt;/p&gt;
&lt;p&gt;也许是因为大家都不了解版本号的用处，也或者是认为它根本就不值得讨论，所以，笔者在国内的几个大的平台都没有看到版本号的相关功能的设计。唯一使用到版本号的地方就是在制品库，部署时需要指定制品的版本号。而业务版本号与技术号之间的关系被隐藏得很深，用户很难查到。&lt;/p&gt;
&lt;p&gt;笔者不想一开始就谈它的好处。我直接上功能，下图是笔者臆想出来的。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-5ee21298d18bf722.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;笔者认为，DevOps 平台应该有的功能之一：能输出这么一幅图，暂定名为版本关系图。图中的方块下，同时标有业务版本号和技术版本号。而图中的系统之间的连接线是应用系统的调用链，读者可忽略。&lt;/p&gt;
&lt;p&gt;版本关系图应该能提供以下信息：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系统应用之间的版本依赖。&lt;/li&gt;
&lt;li&gt;系统内部所依赖的组件的版本。&lt;/li&gt;
&lt;li&gt;能根据某系统的版本查到目前直接依赖于或间接依赖于它的其他系统。&lt;/li&gt;
&lt;li&gt;各系统的版本变迁信息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些信息能给用户带来的价值如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;团队内信息更透明，沟通效率更高，可以有效避免某个员工成为单点。你不必等其他成员，自己也可以得到整个系统的版本信息。&lt;/li&gt;
&lt;li&gt;可以提高团队成员的排错能力，因为当A发布新版本后，APP 首页打开变慢，有了版本关系，我们可以首根据整个平台的“版本事件”来排查问题。同时，团队也很快可以找到相应的代码变更，然后进行 review 及修复。&lt;/li&gt;
&lt;li&gt;上图中，当 A 服务是一个集群时，我们还可以将部署的目标机器与版本号关联起来了。这样，团队就可以轻松的知道，哪台机器部署了哪个版本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;上图只是整个业务系统的某个时间点的“快照”。事实上，我们还可以在版本号上做更大的文章。比如让技术版本号与代码质量、构建速度等过程指标关联起来，这样我们可以在不同的版本之间进行对比。再比如计算两个业务版本号之间，代码质量的差异，长期积累下来这些数据后，我们就有能力计算出代码质量与业务指标之间的关系。&lt;/p&gt;
&lt;p&gt;总的来说，版本号就是整个研发流程中的各项指标数据的枢纽。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;版本号和其它数据的关系的价值，笔者认为被大大低估了。希望本文能给 DevOps 平台设计者带来不一样的想法。&lt;/p&gt;</description>
    </item>
    <item>
      <title>谈DevOps平台实施：实现从内网拉取外网依赖的一种方案</title>
      <link>https://showme.codes/zh-cn/2019-11-4-devops-network/</link>
      <pubDate>Mon, 04 Nov 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-11-4-devops-network/</guid>
      <description>&lt;h3 id=&#34;背景&#34;&gt;背景&lt;/h3&gt;
&lt;p&gt;在大型企业内部，网络通常会被划分成多个不能直接访问的区域。比如本例中，网络被分成了内网和DMZ两个区域。出于安全的考虑，内网的机器不能直接访问外网。内网访问 DMZ 的机器、DMZ的机器要访问外网都需要单独提流程。&lt;/p&gt;
&lt;p&gt;但是，我们的应用能部署到 DMZ 区域中吗？答案是技术上不是问题，但是管理上不允许这样做。&lt;/p&gt;
&lt;p&gt;所以，在这样的大型企业内部，应用都会部署到内网中（本例中的A、B、C、D）。&lt;/p&gt;
&lt;p&gt;可是，总会有一些应用需要发 HTTP 请求到外网。比如实施DevOps平时，我们的应用需要从外网拉取依赖。&lt;/p&gt;
&lt;p&gt;这时，怎么办呢？本文就是为解决此问题而写。&lt;/p&gt;
&lt;h3 id=&#34;解决方案&#34;&gt;解决方案&lt;/h3&gt;
&lt;p&gt;最后的解决方案如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-062b26be43710a4e.png&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Privoxy 是一个HTTP 协议过滤代理。&lt;/li&gt;
&lt;li&gt;Squid 是HTTP代理服务器软件。Squid用途广泛，可以作为缓存服务器，可以过滤流量帮助网络安全，也可以作为代理服务器链中的一环，向上级代理转发数据或直接连接互联网。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说实话，光看介绍，笔者一开始也一头雾水。不过，看完本文就应该知道它们的作用了。&lt;/p&gt;
&lt;p&gt;以下是方案具体实施步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在应用机器上设置全局环境变量：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;http_proxy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;http://192.168.1.100:3126
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;https_proxy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;http://192.168.1.100:3126
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这一步的作用是将本机的 http 流量都代理到 192.168.1.100 的 3126 端口&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;在 192.168.1.100 上安装 Privoxy。它的作用是根据配置，决定流量走哪个网络。本例中，它的作用是我们指定的http请求，走到 dmz。而其它的则和原来一样。它的配置如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat /etc/privoxy/config
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;listen-address  192.168.1.100:3126
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;forward  .abc.com/  192.168.42.12:3127
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;listen-address 指 Privoxy 监听的IP和端口&lt;/li&gt;
&lt;li&gt;forward 指接收到符合域名规则（.abc.com）的请求，将转发给 192.168.42.12 的 3127 端口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到此，得到的效果就是当在应用机器访问 abc.com,admin.abc.com 等时，这些流量都会被 Privoxy 转发到 192.168.42.12 的 3127 端口。其它 HTTP 请求则不会。&lt;/p&gt;
&lt;p&gt;而 192.168.42.12 则是安装了 Squid 实现 HTTP 代理的机器的 IP。&lt;/p&gt;</description>
    </item>
    <item>
      <title>这样理解Ansible更容易</title>
      <link>https://showme.codes/zh-cn/2019-09-19-understand-ansible/</link>
      <pubDate>Thu, 19 Sep 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-09-19-understand-ansible/</guid>
      <description>本文实际上写的是 Ansible 的概念模型，理解了一个东西的概念模型后，对它的使用会变得得心应手。同时对阅读它的源码也非常有帮助。</description>
    </item>
    <item>
      <title>你们的 save 方法是写在实体上，还是写 Dao 上？</title>
      <link>https://showme.codes/zh-cn/2019-07-30-entity-repository/</link>
      <pubDate>Wed, 31 Jul 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-07-30-entity-repository/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;注：Dao 在不同语言中的叫法可能不一样。Dao 可以理解为对数据进行持久化的具体实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;关于实体的保存，笔者知道行业内有两种方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dogDao.save(dog);&lt;/li&gt;
&lt;li&gt;dog.save();&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;相信不少同学，现实中，通常使用第一种，很少见到第二种写法。&lt;/p&gt;
&lt;p&gt;为了让大家站在同一个讨论上下文，笔者决定贴出更详细的代码。&lt;/p&gt;
&lt;p&gt;注：以下代码会省略很多本文不相关的代码，比如数据校验。读者朋友不必太纠结。&lt;/p&gt;
&lt;h4 id=&#34;第一种写法save-方法写在-dao-上&#34;&gt;第一种写法：save 方法写在 Dao 上&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Dog&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;setName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;didi&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;setColor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;white&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dogDao&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;第二种写法save-方法写在实体上&#34;&gt;第二种写法：save 方法写在实体上&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// DogService.java&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Dog&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;setName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;didi&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;setColor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;white&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这两种写法，有什么区别吗？事实上，从字面上看，没有什么区别。因为 Dog 类的 save() 方法是这样实现：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Dog.java&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(){&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dogDao&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但是从抽象的角度来看，就不一样了。&lt;/p&gt;
&lt;p&gt;假如你的项目中存在这样的抽象：
&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-a0fbd312da0a8264.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;如果采用第一种写法，意味着，每多出多一种 Animal，我们就必须多写一套 Service。Service 中会很多这样的方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// DogService.java&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Dog&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;){&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dogDao&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// FishService.java&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Fish&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fish&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;){&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fishDao&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fish&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// BirdService.java&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Bird&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bird&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;){&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;birdDao&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bird&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;相信大家对于以上代码并不陌生。&lt;/p&gt;</description>
    </item>
    <item>
      <title>那只住在我们楼上的大黄狗</title>
      <link>https://showme.codes/zh-cn/2019-07-26-human-and-dog/</link>
      <pubDate>Fri, 26 Jul 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-07-26-human-and-dog/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-7b45268f64e22511.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;当电梯到的时候，一只大土狗也跟着我进了电梯。我按了7楼。大狗依然张开嘴吐着舌头，站在原地。电梯直达7楼，我不知道大土狗要去几楼，所以没帮它按。走出电梯后，电梯理所当然的关门，然后向下了。它还在电梯里。&lt;/p&gt;
&lt;p&gt;一天，我们一家人挤进电梯下楼。电梯有一股恶臭。儿子叫了一声：大黄。这时，我才注意到拥挤的电梯里，大土狗在角落里。看样子，它也是要下楼。&lt;/p&gt;
&lt;p&gt;后来，从我老婆那里了解到。她经常看到这只大土狗，所以和儿子给它起了一个名字：大黄。因为它的毛是黄色的。&lt;/p&gt;
&lt;p&gt;而狗的主人就住在11楼。只不过，现在主人不要它了。它现在每天就睡在前主人的11楼的门外。饭点的时候，就会看到它端端正正的坐在餐桌不远处，目不转睛地看着吃饭的人。偶尔会有好心丢给吃的给它。&lt;/p&gt;
&lt;p&gt;当听到这些时，就酸了鼻子，两眼湿润。&lt;/p&gt;
&lt;p&gt;再后来，当我和大黄坐电梯时，都会帮它按下11楼。&lt;/p&gt;
&lt;p&gt;某一天，在楼下的操场边上，看到大黄兴奋地围在一年轻人的身边疯跑。看得出来，年轻人是它的前主人。只是当年的主人已经不是它的主人。年轻人直视前方冷漠地自走自的，仿佛大黄不存在一样。&lt;/p&gt;
&lt;p&gt;而我身为旁观者，本来想说点什么，最后，也只能路过。&lt;/p&gt;</description>
    </item>
    <item>
      <title>我们不是研发，不会天天去关注代码</title>
      <link>https://showme.codes/zh-cn/2019-07-22-code/</link>
      <pubDate>Mon, 22 Jul 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-07-22-code/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-7a048e5ce07282ad.jpeg&#34;&gt;&lt;/p&gt;
&lt;p&gt;前段时间，项目实施人员告诉我，我写的 Ansible 脚本中有一处写死了版本号。并把代码截图给我看。我一看，这代码是老版本了。他代码应该没有更新。我这么跟他说。&lt;/p&gt;
&lt;p&gt;然后他说出了一句出乎我意料的话：我们不是研发，不会天天去关注代码。&lt;/p&gt;
&lt;p&gt;最后，我回了一句：要想自动化，就必须关注代码。屏幕背后的我，露出无奈与惋惜的表情。&lt;/p&gt;
&lt;p&gt;惋惜的不是他有没有关注代码这件事情。而是他使用职位来限制住自己的能力。&lt;/p&gt;
&lt;p&gt;不要笑话上面的同事，工作中不少这样的事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我是测试，那是研发的事情。&lt;/li&gt;
&lt;li&gt;我是研发，那是运维人员的事情。&lt;/li&gt;
&lt;li&gt;我是设计人员，不是研发。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;前段时间，听另一个项目组的同事说：两周一迭代，前一周测试闲死，后一周开发闲死。&lt;/p&gt;
&lt;p&gt;当时，我问了两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在后一周的时候，开发在干嘛？&lt;/li&gt;
&lt;li&gt;在后一周的时候，产品经理在干嘛？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;开发与产品经理都不是很忙的情况，为什么他们不可以参与测试呢？如果问他们，得到的回答可能是：因为那是测试的事情。&lt;/p&gt;
&lt;p&gt;后来，我仔细想，那是XXX的事情，其实也不能完全怪他们。因为现实中，如果线上出现测试不到位的Bug，测试人员很可能会被 KPI。&lt;/p&gt;
&lt;p&gt;最后，我才恍然大悟：那是XXX的事情的思维方式并不是员工原本的思维方式，而是这个管理制度下的结果。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何设计 Ansible 的入门工作坊</title>
      <link>https://showme.codes/zh-cn/2019-07-19-ansible-workshop/</link>
      <pubDate>Fri, 19 Jul 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-07-19-ansible-workshop/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-28f3aed45a46631e.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;本月在公司内部做了一次 Ansible 的入门工作坊。本文即对这次工作坊的设计过程进行一次总结。其他技术类的工作坊也可以参考。&lt;/p&gt;
&lt;p&gt;设计过程大概过程如下文所述。&lt;/p&gt;
&lt;p&gt;首先，我们需要确定参加本次工作坊的受众。他们是否具有最基本的前提。本次工作坊的受众有开发、测试、运维，还有毕业生。但是他们都会使用 shell。这已经满足最基本的前提。同时，了解受众后了，也就可以因材施教。&lt;/p&gt;
&lt;p&gt;第二，分析工作坊的内容。Ansible 是一款上手非常容易的自动化运维工具。它的特点就是实操性非常强，不需要理解 Ansible 背后的概念就可以使用的工具。&lt;/p&gt;
&lt;p&gt;笔者根据受众和教学内容的特点，得出本次工作坊的目标（教学目标）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;知道 Ansible 是什么，并知道它的作用。&lt;/li&gt;
&lt;li&gt;了解如何查文档。&lt;/li&gt;
&lt;li&gt;能部署一个 Spring Boot 应用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;是不是很简单？其实不然。整个工作坊没有一个人能完成所有的任务。同时发现有运维和开发基础的同学会做得更快。&lt;/p&gt;
&lt;p&gt;那接下来怎么实现这个目标呢？笔者使用的是任务驱动的方法。也就是受众通过做一个个任务，在任务中完成学习。同时，教师可以任务过程穿插讲相关的知识点。&lt;/p&gt;
&lt;p&gt;以下为任务列表：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行 &lt;code&gt;ansible-playbook -i hosts playbook.yml&lt;/code&gt; 成功&lt;/li&gt;
&lt;li&gt;创建用户 apps 及用户组 apps：
&lt;ul&gt;
&lt;li&gt;user 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/user_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/user_module.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;group 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/group_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/group_module.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;创建以下文件夹，并设置文件夹的用户和组为 apps：
/apps，/apps/hello，/apps/hello/bin，/apps/hello/logs
&lt;ul&gt;
&lt;li&gt;file 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/file_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/file_module.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将 helloworld-0.0.2.jar copy 到 /apps/hello/bin 目录下，设置该 jar 文件的用户和用户组为 apps
&lt;ul&gt;
&lt;li&gt;copy 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/copy_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/copy_module.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 template 模块将 app.service copy 到目标服务器的 /etc/systemd/system 中，并重命名 hello.service :
&lt;ul&gt;
&lt;li&gt;template 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/template_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/template_module.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;启动 hello 服务
&lt;ul&gt;
&lt;li&gt;service 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/service_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/service_module.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;监听 hello 服务是否启动成功
&lt;ul&gt;
&lt;li&gt;wait_for 模块: &lt;a href=&#34;https://docs.ansible.com/ansible/latest/modules/wait_for_module.html&#34;&gt;https://docs.ansible.com/ansible/latest/modules/wait_for_module.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为目标机器安装 JDK 1.8:
&lt;ol&gt;
&lt;li&gt;在本地仓库中创建 roles 目录&lt;/li&gt;
&lt;li&gt;clone 代码：https://github.com/geerlingguy/ansible-role-java 到 roles 目录中&lt;/li&gt;
&lt;li&gt;在 playbook.yml 文件中加入 ansible-role-java 的role&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;创建自定义 role: hello role
&lt;ol&gt;
&lt;li&gt;进入 roles 目录：cd roles&lt;/li&gt;
&lt;li&gt;使用命令生成 role 模板：&lt;code&gt;ansible-galaxy init hello&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将 hello 的部署逻辑（在 playbook.yml 中）写入到 hello role 中&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;将 hello 部署到多台机器
&lt;ul&gt;
&lt;li&gt;需要修改 hosts 文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;多环境部署&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;任务的设计并不是随意的，而是有意的。比如：&lt;/p&gt;</description>
    </item>
    <item>
      <title>小明说：代码是测试通过了，但是修改了一行代码，等和其他人的功能一起上吧</title>
      <link>https://showme.codes/zh-cn/2019-07-07-no-release/</link>
      <pubDate>Sun, 07 Jul 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-07-07-no-release/</guid>
      <description>&lt;p&gt;小明修复了 Web 后端的一个不大不小的 Bug。只修改了一行代码。并在 UAT 环境测试通过。&lt;/p&gt;
&lt;p&gt;可是，当我问他为什么不发版时，他说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;代码是测试通过了，但是修改了一行代码，等和其他人的功能一起上吧&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在我不长不短的职业生涯中，经常遇到这样的小明。我已经见怪不怪了。&lt;/p&gt;
&lt;p&gt;笔者的观念是：如果变更的代码上线不会死人，能上就上。如果每次发版都很痛苦，那么先把发版难这个问题解决。至少也要朝着这个方向前进。&lt;/p&gt;
&lt;p&gt;为什么我会这样觉得呢？因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序员写出来的代码，只有真正运行在生产环境上了，才算完成工作。测试通过的代码不上线，就是库存。这在丰田称为“库存的浪费”。也就是不发布到生产环境，你为什么要写出来？还测试通过了。&lt;/li&gt;
&lt;li&gt;如果一个分支停留太长时间，分支之间发生冲突的可能性就越大，而解决冲突这类操作对于产品的最终用户来说，是毫无价值的。用丰田生产方式的话说来，就是“动作上的无效劳动”。再者合并冲突过程容易再次引入缺陷。所以，应该避免分支停留过长时间。这个“过长”怎么定义，需要具体问题具体分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;其实，我很理解小明说出这样的话。大多是因为对部署没有足够的信心。对于没有自动化部署的团队来说，属于正常现象，你不能将责任推到一个人身上。而解决部署难的问题，不仅在管理上下功夫，还要在技术上下功能。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从文具的购买看一家企业的效率</title>
      <link>https://showme.codes/zh-cn/2019-05-27-pens-team-productivity/</link>
      <pubDate>Mon, 27 May 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-05-27-pens-team-productivity/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-bfb542fc0c82f75b.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Photo by **&lt;a href=&#34;https://www.pexels.com/@leon-macapagal-1234433?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels&#34;&gt;Leon Macapagal &lt;/a&gt;**from &lt;strong&gt;&lt;a href=&#34;https://www.pexels.com/photo/aerial-photo-of-railway-lines-2346006/?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels&#34;&gt;Pexels&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;团队每天都会面对着看板开早会。过完卡后，会有一些技术问题需要进行深入讨论。大家你一句我一句，实在说不清了，我就想从桌子上拿一支笔将想法画出来。&lt;/p&gt;
&lt;p&gt;但是在会议室找不到可用的笔，然后跑到文印室也找不到。这样，几分钟过去了，5个人团队就干等着我找笔。实在找不到，我们只得打开投影仪，打开 Windows 系统自带的画图软件画起架构图。&lt;/p&gt;
&lt;p&gt;在经历过几次这样的痛苦后，我决定找办公室负责采购的小姐姐说这事。小姐姐心好，从其它地方拿笔到我们经常早会的会议室。&lt;/p&gt;
&lt;p&gt;最后小姐姐补充：本月的文具已购买，下月我再向经理审批再买哦。&lt;/p&gt;
&lt;p&gt;我当时没有反应过来，没有理解她这句话的意思。&lt;/p&gt;
&lt;p&gt;过了两个星期，我发现又没有笔可用了（后来发现是我在文印室没有找仔细）。我再次找到小姐姐。并跟她说：能不能多放一些笔？&lt;/p&gt;
&lt;p&gt;小姐姐说：笔是统一放在文印室的，因为不知道哪个地方需要用。所以，你们有需要去文印室拿哦。&lt;/p&gt;
&lt;p&gt;我说：文印室也没有了，没找到。&lt;/p&gt;
&lt;p&gt;然后我实在没有忍住，一连串说了几句文具的成本是多低，节约人的时间，提高工作效率，可以节约更多的成本。&lt;/p&gt;
&lt;p&gt;她似乎没有听进去：之前放了一支，你们又丢了。&lt;/p&gt;
&lt;p&gt;我说：就一支，是不是别人拿到别的会议室了？&lt;/p&gt;
&lt;p&gt;她说：不知道。我买文具是要经理审批的。&lt;/p&gt;
&lt;p&gt;我这才想意识到，她没有决定权。问题出在经理那里。为什么经理不允许买呢？&lt;/p&gt;
&lt;p&gt;从小姐姐那里了解到原来有一次小姐姐按往常一次提流程买笔和本子。谁知经理说：纸巾每月买可以理解，但是为什么笔和本子每个月都要买。&lt;/p&gt;
&lt;p&gt;这就是现在为什么小姐姐很少买笔和本子的原因了。&lt;/p&gt;
&lt;p&gt;最后，我也不提笔的事情了。因为我单独找经理谈这个事情，在当前的环境下，会被人说“跨级”。再者对小姐姐可能也不好。&lt;/p&gt;
&lt;p&gt;谁知，过了两个星期，事业部的副经理从会议室跑出来，问：有没有见到大头笔？&lt;/p&gt;
&lt;p&gt;我苦笑地说：没有。&lt;/p&gt;
&lt;p&gt;苦笑是因为我猜到发生在我身上的事，也会发生在别人身上。只不过没想到，发生在了经理级别上而已。&lt;/p&gt;
&lt;p&gt;后来，也不知道他有没有找到笔。但是那个会议室还是偶尔找不到笔。&lt;/p&gt;
&lt;p&gt;不知道读者朋友看出来问题没有？笔者认为关键问题出在：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;提效不一定非要花巨资买个 DevOps 平台，效率发生在每一个企业运营的细节。&lt;/li&gt;
&lt;li&gt;软件工程是知识密集型工作，人与人之间需要高效沟通。那么笔、白板（或纸）就是即高效，成本又低的工具。说实在点，工作少出现一个沟通失误，节约下来的钱就可能买下整个公司几年的笔了。小姐姐没有认识到，经理也没有认识到。&lt;/li&gt;
&lt;li&gt;企业文化让看到问题的人不敢提问题。&lt;/li&gt;
&lt;li&gt;你没有想到吧，一个负责采购小姐姐的决定，也可能影响一家公司的效率？这里没有贬低小姐姐的意思。每个岗位都有它的意义。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些问题怎么从根本上解决？&lt;/p&gt;
&lt;p&gt;笔者认为，关键点是没有人从效率的角度考虑公司内部的行为。小姐姐考虑的是下次提流程时，经理不会问那样的问题。经理疑问了文具为什么用得这么快，但是并没有深究（几支笔的事情，当然没有必要深究）。其他人（可能）不想惹事，也不会找相关的人提“笔”的事情。在很多企业奉行谁提问题谁解决。有时我怀疑这句话对企业是有害的。&lt;/p&gt;
&lt;p&gt;怎么解决呢？我只说一句：解铃还须系铃人。&lt;/p&gt;
&lt;p&gt;一定能解决吗？我不知道。因为我也只是觉得可以解决。所以希望和更多人交流解决之道。&lt;/p&gt;
&lt;p&gt;我把这些写出来，很有可能犯“政治错误”，得罪某些人。我还是要写出来。因为我相信还有不少企业发生类似的事情。不敢面对，怎么进步。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如果给你一支外墙清洗工程队，你会如何管？</title>
      <link>https://showme.codes/zh-cn/2019-05-26-how-to-manage-cleaning-engineering-team/</link>
      <pubDate>Sun, 26 May 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-05-26-how-to-manage-cleaning-engineering-team/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-2abe0a46591e1aae.png&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本篇文章只是笔者看到外墙清洗工后，在管理方面思考的总结。笔者是外墙清洗行业的外行，所以，本文可能存在一些错误的假设。期望这些可能错误的假设不影响管理方面的思考。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;引发思考的起因&#34;&gt;引发思考的起因&lt;/h3&gt;
&lt;p&gt;某天上班，仰头看到大厦清洗外墙工人悬挂在20层楼的半空中作业，除了觉得他们危险外，脑海浮现一个问题。如果作为外墙清洗工的管理者，我们如何保证大厦的外墙的每一处被清洗干净了。&lt;/p&gt;
&lt;p&gt;我们经常听管理者这么说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我只要一个结果，不管过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可是这个“结果”是什么呢？&lt;/p&gt;
&lt;h3 id=&#34;谁来定义结果&#34;&gt;谁来定义“结果”&lt;/h3&gt;
&lt;p&gt;回到外墙清洗的管理，作为工程队的管理者，我们要的结果是什么呢？&lt;/p&gt;
&lt;p&gt;面对这个问题，我们要马上回答的并不是“结果”的定义是什么。而是要问：这个问题本身应该由谁来回答。是由老板回答、管理者回答、还是由清洗工回答（我们假设管理者与老板这两种角色由不同的人担当）？&lt;/p&gt;
&lt;p&gt;也就是说，对于不同角色的人，对于“结果”的定义是不一样的。&lt;/p&gt;
&lt;p&gt;清洗外墙作为一门生意，结果当然是以最低成本赚最多钱。对于这一点似乎不用定义了。&lt;/p&gt;
&lt;p&gt;但是，这个结果只是“老板”希望自己得到的结果。它与管理者希望清洗工给的结果是两回事。如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-bad3da559ae9330b.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;至此，我们发现“谁来定义结果”这个问题，应该换个问法：谁来确定管理者想要的结果与老板想要的结果之间的关系？&lt;/p&gt;
&lt;p&gt;笔者认为应该由管理者与老板共同确定。&lt;/p&gt;
&lt;h3 id=&#34;如何确定不同层次结果之间的关系&#34;&gt;如何确定不同层次结果之间的关系&lt;/h3&gt;
&lt;p&gt;管理者想要的结果与老板想要的结果处于不同的层次。那么如何确定不同层次结果之间的关系，这个问题由老板与管理者共同回答。&lt;/p&gt;
&lt;p&gt;如果是把清洗外墙是一次性的生意，结果很准确，就是收到甲方的款。不论外墙是否真的被清洗干净了，因为贿赂验收人员我们一样能得到想要的结果。我们甚至可以设置一个部门专门搞定验收人员。这不是本文要讨论的范围。&lt;/p&gt;
&lt;p&gt;如果基于企业长期发展的考虑，我们希望能把&lt;strong&gt;洗墙&lt;/strong&gt;这项业务做好。最终会得到老板想要的结果。&lt;/p&gt;
&lt;p&gt;本文，我们假设保证大厦外墙的每一处都被清洗干净是长期正作用于老板想要的结果的。如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-5ace4b542ab9cdc4.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;接下来，下文所说的“结果”均指管理者的结果。&lt;/p&gt;
&lt;h3 id=&#34;如何验证结果&#34;&gt;如何验证结果&lt;/h3&gt;
&lt;p&gt;绕了一圈，终于把结果定义好了。现在咱们把“结果”定义为在文章开头就提了的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;保证大厦外墙的每一处都被清洗干净&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是我们如何验证这个结果呢？我们的管理者不可能也把自己挂在高高的外墙眼睁睁地检查清洗工洗过的每一处。就像产品经理不可能自己打 IDE 一行行的检查程序员的代码。&lt;/p&gt;
&lt;p&gt;如果管理者无法做到，加一个监工不就行了？但是谁又来监督这个监工的工作呢？也就是谁来保证监工的工作做到位了呢？&lt;/p&gt;
&lt;p&gt;还有一种办法：让工人相互监工。这样，花两个人的工资，顶三个人的活。可是，在企业里待过的人动下脑子都知道这个方案就不可靠。他们会串通的嘛。因为“偷懒”符合他们的共同利益。（在信息透明度高的情况下，让人相互监工是可行的。比如结对编程。）&lt;/p&gt;
&lt;p&gt;以上都是臆造出来的解决方案。都是基于“监工”的模型。看看我们的身边，是不是也是这样？&lt;/p&gt;
&lt;p&gt;回到我们的问题本身：如何确保大厦外墙的每一处都被清洗干净？我们换一种方式解决。&lt;/p&gt;
&lt;p&gt;如果采用“激励”的方式呢？假如请了10个清洗工人，等最后清洗完成后，对这10个清洗工人的工作进行ABC评级。得A的人可以得到原来薪水的1.2倍，得B的人保持不变，得C的人是原来薪水的0.8倍。是不是感觉这套路很熟悉？&lt;/p&gt;
&lt;p&gt;“激励”的模型虽好，解决的是清洗工人的积极性问题，还是没有解决我们的根本问题：如何验证结果。&lt;/p&gt;
&lt;h3 id=&#34;定期验收&#34;&gt;定期验收&lt;/h3&gt;
&lt;p&gt;也许我们可以把“每一处”的标准降低。在所有的清洗工中，选一个最合适的人作为清洗工组长，让其工资高于一般的清洗工人。组长的其中一个重要职责是定期对玻璃进行验收。至于定期的周期设为多长，涉及到工作的反馈周期了，属于另一个问题了。暂不讨论。&lt;/p&gt;
&lt;p&gt;虽然这样管理者不用自己被挂在外墙，但是“保证大厦外墙的每一处都被清洗干净”的结果强依赖于组长的责任心。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-89de8e47586b3e39.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;这个模型的缺点是管理者想要的结果必须强依赖于一个很不稳定的因素：组长的责任心。&lt;/p&gt;
&lt;h3 id=&#34;用对比法解决&#34;&gt;用对比法解决&lt;/h3&gt;
&lt;p&gt;突然有一天，我再看大厦的时候想到，用清洗前和清洗后的大厦照片对比不就可以了吗？&lt;/p&gt;
&lt;p&gt;也就是在保证环境一致的情况下，在清洗前拍一次，在清洗后再拍一次。只要照片足够高清，你想对比多细节都可以。更进一步，我们甚至可以使用软件进行自动对比。&lt;/p&gt;
&lt;p&gt;使用此种办法，除了解决以上方案的缺点，还使得“保证大厦外墙的每一处都被清洗干净”的结果从模糊变成准确被量化了。&lt;/p&gt;
&lt;p&gt;这意味着什么？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;更短的反馈周期：每天都可以当天的清洗情况。这对于能否按时完成工作非常重要。&lt;/li&gt;
&lt;li&gt;更准确的定义“干净”：怎么样才算干净，一对比就知道。同时，清洗工之间绩效对比也有了。&lt;/li&gt;
&lt;li&gt;成本更低的检查“每一处”：由于只需要坐在电脑前进行检查，成本会低很多。可能不需要多发一份组长的工资了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后，笔者想找同一建筑的两张图进行对比，但是实在找不到。读者朋友就脑补一下吧。&lt;/p&gt;
&lt;p&gt;使用对比法就是最终答案了吗？不是的。也许，将来成本更低的办法是使用外墙清洗&lt;strong&gt;机器人&lt;/strong&gt;会代替人工。也许这样更容易得到老板想要的结果。&lt;/p&gt;
&lt;p&gt;更甚至于搞个 AI 外墙清洗，然后融个资？笔者调皮了。&lt;/p&gt;
&lt;h3 id=&#34;后记&#34;&gt;后记&lt;/h3&gt;
&lt;p&gt;以上内容虽不能完全重现笔者的思考过程。但是大体思路就是这样的。这个思考过程，在软件工程方面，笔者认为是相通的。&lt;/p&gt;
&lt;p&gt;产品经理必须思考做出来的软件功能是否正作用于老板想要的结果；必须想办法加快反馈；必须想办法更低成本的检查程序员的工作结果；必须想办法让所有人准确理解需求。&lt;/p&gt;
&lt;p&gt;同时，本文还有很多方面没有讨论，比如我们是否需要以及如何关心每个人想要的结果；清洗工也是人，企业中的人文关怀问题等等。&lt;/p&gt;
&lt;p&gt;最后，说明一下，我并不想挑战别人的专业。以上只是讨论。欢迎大家交流&lt;/p&gt;
&lt;h3 id=&#34;参考&#34;&gt;参考&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;玻璃外墙清洗的准备工作与注意事项：http://www.fjyybj.com/news/517.html&lt;/li&gt;
&lt;li&gt;外墙清洗机器人现身多幢大楼，清洗前后泾渭分明！
&lt;a href=&#34;https://juejin.im/post/5c73c3a251882562e5445024&#34;&gt;https://juejin.im/post/5c73c3a251882562e5445024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;人类真的是趋利避害的吗？：&lt;a href=&#34;https://www.zhihu.com/question/60711385&#34;&gt;https://www.zhihu.com/question/60711385&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    <item>
      <title>如何对 Jenkins 共享库进行单元测试</title>
      <link>https://showme.codes/zh-cn/2019-05-25-jenkins-pipeline-shared-lib-unit-test/</link>
      <pubDate>Sat, 25 May 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-05-25-jenkins-pipeline-shared-lib-unit-test/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-1cd01d6e3fadedae.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Jenkins 共享库是除了 Jenkins 插件外，另一种扩展 Jenkins 流水线的技术。通过它，可以定义轻松的自定义步骤，还可以对现有的流水线逻辑进行一定程度的抽象与封装。至于如何写及如何使用它，读者朋友可以移步附录中的官方文档。&lt;/p&gt;
&lt;h2 id=&#34;对共享库进行单元测试的原因&#34;&gt;对共享库进行单元测试的原因&lt;/h2&gt;
&lt;p&gt;但是如何对它进行单元测试呢？共享库越来越大时，你不得不考虑的问题。因为如果你不在早期就开始单元测试，共享库后期可能就会发展成如下图所示的“艺术品”——能工作，但是脆弱到没有人敢动。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-a904fdf36d90c714.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;[图片来自网络，侵权必删]&lt;/p&gt;
&lt;p&gt;这就是代码越写越慢的原因之一。后人要不断地填前人有意无意挖的坑。&lt;/p&gt;
&lt;h2 id=&#34;共享库单元测试搭建&#34;&gt;共享库单元测试搭建&lt;/h2&gt;
&lt;h3 id=&#34;共享库官方文档介绍的代码仓库结构&#34;&gt;共享库官方文档介绍的代码仓库结构&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(root)
+- src                     # Groovy source files
|   +- org
|       +- foo
|           +- Bar.groovy  # for org.foo.Bar class
+- vars
|   +- foo.groovy          # for global &amp;#39;foo&amp;#39; variable
|   +- foo.txt             # help for &amp;#39;foo&amp;#39; variable
+- resources               # resource files (external libraries only)
|   +- org
|       +- foo
|           +- bar.json    # static helper data for org.foo.Bar
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以上是共享库官方文档介绍的代码仓库结构。整个代码库可以分成两部分：src 目录部分和 vars 目录部分。它们的测试脚手架的搭建是不一样的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 Jenkins &#43; Ansible 实现 Springboot 自动化部署101</title>
      <link>https://showme.codes/zh-cn/2019-05-15-jenkins-ansible-springboot/</link>
      <pubDate>Wed, 15 May 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-05-15-jenkins-ansible-springboot/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-3d64f9fe7b80ed98.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;本文要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设计一条 Springboot 最基本的流水线：包括构建、制品上传、部署。&lt;/li&gt;
&lt;li&gt;使用 Docker 容器运行构建逻辑。&lt;/li&gt;
&lt;li&gt;自动化整个实验环境：包括 Jenkins 的配置，Jenkins slave 的配置等。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;1-代码仓库安排&#34;&gt;1. 代码仓库安排&lt;/h3&gt;
&lt;p&gt;本次实验涉及以下多个代码仓库：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;% tree -L 1
├── 1-cd-platform # 实验环境相关代码
├── 1-env-conf # 环境配置代码-实现配置独立
└── 1-springboot # Springboot 应用的代码及其部署代码
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;1-springboot 的目录结构如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;% cd 1-springboot
% tree -L 1 
├── Jenkinsfile # 流水线代码
├── README.md
├── deploy # 部署代码
├── pom.xml 
└── src # 业务代码
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所有代码，均放在 GitHub: &lt;a href=&#34;https://github.com/cd-in-practice&#34;&gt;https://github.com/cd-in-practice&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;2-实验环境准备&#34;&gt;2. 实验环境准备&lt;/h3&gt;
&lt;p&gt;笔者使用 Docker Compose + Vagrant 进行实验。环境包括以下几个系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jenkins * 1
Jenkins master，全自动安装插件、默认用户名密码：admin/admin。&lt;/li&gt;
&lt;li&gt;Jenkins agent * 2
Jenkins agent 运行在 Docker 容器中，共启动两个。&lt;/li&gt;
&lt;li&gt;Artifactory * 1
一个商业版的制品库。笔者申请了一个 30 天的商业版。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 Vagrant  是为了启动虚拟机，用于部署 Springboot 应用。如果你的开发机器无法使用 Vagrant，使用 VirtualBox 也可以达到同样的效果。但是有一点需要注意，那就是网络。如果在虚拟机中要访问 Docker 容器内提供的服务，需要在 DNS 上或者 hosts 上做相应的调整。所有的虚拟机的镜像使用 Centos7。&lt;/p&gt;</description>
    </item>
    <item>
      <title>基于 Jenkins 的 DevOps 平台应该如何设计凭证管理</title>
      <link>https://showme.codes/zh-cn/2019-05-07-devops-jenkins-credential-manage/</link>
      <pubDate>Tue, 07 May 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-05-07-devops-jenkins-credential-manage/</guid>
      <description>一种基于 Jenkins 的 DevOps 平台建设思路</description>
    </item>
    <item>
      <title>Jenkins 自动安装插件</title>
      <link>https://showme.codes/zh-cn/2019-04-27-jenkins-install-plugins-shell/</link>
      <pubDate>Sat, 27 Apr 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-04-27-jenkins-install-plugins-shell/</guid>
      <description>&lt;h3 id=&#34;手工安装-jenkins-插件的方法&#34;&gt;手工安装 Jenkins 插件的方法&lt;/h3&gt;
&lt;p&gt;通常，我们有两种方法安装  Jenkins 插件。第一种方法是到 Jenkins 插件管理页面搜索插件，然后安装。第二种方法是上传 Jenkins 插件的 hpi 文件安装。这两种方法能满足大多数人的需求。&lt;/p&gt;
&lt;p&gt;第一种方法，如下图所示：
&lt;img alt=&#34;搜索安装&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-12412dbb4c58b810.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;第二种方法，如下图所示：
&lt;img alt=&#34;上传插件&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-957598396b256971.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;但是对于需要保证 Jenkins 稳定或在 Jenkins 上进行二次开发的同学来说，以上方法是无法满足需求的。&lt;/p&gt;
&lt;p&gt;第一种方法是无法指定插件的版本。第二种方式必须自己找到该插件的依赖树，一个个依赖的安装。是的，手工上传插件的这种方法，Jenkins 是不会自动下载依赖的。&lt;/p&gt;
&lt;h3 id=&#34;自动安装插件的方法&#34;&gt;自动安装插件的方法&lt;/h3&gt;
&lt;p&gt;那么，有什么方法能做到即指定插件的版本，又能自动下载它的依赖呢？&lt;/p&gt;
&lt;p&gt;幸运的是，Jenkins 的 Docker 镜像的代码仓库里的 install-plugins.sh 脚本已经实现。只不过需要我们拿过来小小修改才能使用。笔者修改后创建了相应的代码仓库：jenkins-install-plugins-shell 。链接在文章末尾。&lt;/p&gt;
&lt;p&gt;以下是 jenkins-install-plugins-shell 的使用方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将代码 clone 到 JENKINS_HOME 目录中。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd $JENKINS_HOME
git clone https://github.com/zacker330/jenkins-install-plugins-shell.git
cd jenkins-install-plugins-shell
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;在 plugins.txt 中加入希望安装的插件
在 &lt;code&gt;jenkins-install-plugins-shell&lt;/code&gt; 目录中，有一个 plugins.txt 文件，在文件中写入希望安装的插件及版本号。例如：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ansible:1.0
powershell:1.3
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;执行安装&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;c1&#34;&gt;# Jenkins War 的路径，用于分析&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;JENKINS_WAR_PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&amp;lt;Jenkins war文件的路径&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chmod +x install-plugins.sh jenkins-support
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./install-plugins.sh &amp;lt; plugins.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;重启 Jenkins
install-plugins 本质上做的事情就只是将插件从云端下载到 JENKINS_HOME 下的 plugins 目录中。要使安装的插件生效，还需要重启 Jenkins。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;关于-jenkins-插件的名称&#34;&gt;关于 Jenkins 插件的名称&lt;/h3&gt;
&lt;p&gt;Jenkins 插件有两个名称。一个叫 display name，一个叫 short name。比如 Ansible 插件的 disply name 为 Ansible plugin，short  name 为 ansible。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 Jenkins &#43; Ansible 实现自动化部署 Nginx</title>
      <link>https://showme.codes/zh-cn/2019-04-22-jenkins-ansible-nginx/</link>
      <pubDate>Mon, 22 Apr 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-04-22-jenkins-ansible-nginx/</guid>
      <description>&lt;p&gt;本文介绍如何使用 Jenkins + Ansible 实现对 Nginx 的自动化部署。最终达到的效果有如下几点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;只要你将 Nginx 的配置推送到 GitHub 中，Jenkins 就会自动执行部署，然后目标服务器的 Nginx 配置自动生效。这个过程是幂等（idempotent）的，只要代码不变，执行多少遍，最终效果不变。&lt;/li&gt;
&lt;li&gt;如果目标机器没有安装 Nginx，则会自动安装 Nginx。&lt;/li&gt;
&lt;li&gt;自动设置服务器防火墙规则。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;1-实验环境介绍&#34;&gt;1. 实验环境介绍&lt;/h2&gt;
&lt;p&gt;本次实验使用 Docker Compose 搭建 Jenkins 及 Jenkins agent。使用 Vagrant 启动一台虚拟机，用于部署 Nginx。使用 Vagrant 是可选的，读者可以使用 VirtualBox 启动一个虚拟机。使用 Vagrant 完全是为了自动化搭建实验环境。&lt;/p&gt;
&lt;p&gt;以下是整个实验环境的架构图：
&lt;img alt=&#34;Jenkins Ansible Nginx&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-ab578a7d0b27c4c6.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;注意，图中的 &lt;code&gt;5123 &amp;lt;-&amp;gt; 80&lt;/code&gt; 代表将宿主机的 5123 端口请求转发到虚拟机中的 80 端口。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vagrant：虚拟机管理工具，通过它，我们可以使用文本来定义、管理虚拟机。&lt;/li&gt;
&lt;li&gt;Ansible：自动化运维工具&lt;/li&gt;
&lt;li&gt;Docker Compose：它是一个用于定义和运行多容器 Docker 应用程序的工具。可以使用YAML文件来配置应用程序的服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-启动实验环境&#34;&gt;2. 启动实验环境&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;克隆代码并进入文件夹&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/zacker330/jenkins-ansible-nginx.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; jenkins-ansible-nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构建 Jenkins agent 的镜像
需要自定义 Jenkins agent 镜像有两个原因：&lt;/p&gt;</description>
    </item>
    <item>
      <title>你觉得能通过共享 JENKINS_HOME 目录实现 Jenkins master 的高可用吗？</title>
      <link>https://showme.codes/zh-cn/2019-04-15-jenkins-standby/</link>
      <pubDate>Mon, 15 Apr 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-04-15-jenkins-standby/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;审校：&lt;a href=&#34;https://github.com/donhui&#34;&gt;王冬辉&lt;/a&gt;，&lt;a href=&#34;https://github.com/LinuxSuRen&#34;&gt;linuxsuren&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jenkins master 的高可用是个老大难的问题。和很多人一样，笔者也想过两个 Jenkins master 共享同一个 JENKINS_HOME 的方案。了解 Jenkins 原理的人，都会觉得这个方案不可行。但是真的不可行吗？&lt;/p&gt;
&lt;p&gt;由于工作原因，笔者需要亲自验证以上猜想。&lt;/p&gt;
&lt;h3 id=&#34;jenkins_home-介绍&#34;&gt;JENKINS_HOME 介绍&lt;/h3&gt;
&lt;p&gt;Jenkins 所有状态数据都存放文件系统的目录中，这个目录被称为 JENKINS_HOME 目录。&lt;/p&gt;
&lt;h3 id=&#34;实验环境介绍&#34;&gt;实验环境介绍&lt;/h3&gt;
&lt;p&gt;笔者通过 Docker compose 启动两个独立的 Jenkins master，分别为 jenkins-a 和 jenkins-b。它们共用同一个 JENKINS_HOME 目录。相应的代码仓库的链接放在文章底部。&lt;/p&gt;
&lt;p&gt;将代码克隆到本地后，进入仓库，执行 &lt;code&gt;docker-compose up -d&lt;/code&gt; 即可启动实验环境。启动完成，在浏览器中输入 http://localhost:7088 可访问 jenkins-a，jenkins-b 的地址是 http://localhost:7089 。但是你会发现它们启动后的界面显示是不一样的。&lt;/p&gt;
&lt;p&gt;jenkins-b 的界面如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-fd4b85d5b9c8bdf6.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;而 jenkins-a 的界面如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-1bfd4e033b6c25c8.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;这时，将 jenkins-a 日志中的解锁密码（Unlock password）输入到 jenkins-b 的页面中，会得到报错信息：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ERROR: The password entered is incorrect, please check the file for the correct password
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这时，再次 jenkins-b 日志中的解锁密码（Unlock password）输入到表单中即可进入下一步。接下来就是按照提示一步步完成了。在 jenkins-b 安装步骤的最后一步，我们设置了管理员的用户名密码：admin/admin。然后就算完成任务了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 Zabbix 监控 Jenkins</title>
      <link>https://showme.codes/zh-cn/2019-04-10-jenkins-zabbix-monitor/</link>
      <pubDate>Wed, 10 Apr 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-04-10-jenkins-zabbix-monitor/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文假设读者已经了解 Jenkins 基本概念及插件安装，Zabbix 基础概念。基于 Zabbix 3.4，Jenkins 2.8 做实验&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;笔者最近的工作涉及到使用 Zabbix 监控 Jenkins。在谷歌上搜索到的文章非常少，能操作的就更少了。所以决定写一篇文章介绍如何使用 Zabbix 监控 Jenkins。&lt;/p&gt;
&lt;p&gt;下图为整体架构图：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-ecc7d290dd4d0f0f.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;整体并不复杂，大体步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 Jenkins 上安装 Metrics 插件，使 Jenkins 暴露 metrics api。&lt;/li&gt;
&lt;li&gt;配置 Zabbix server 及 agent 以实现监控及告警&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为方便读者实验，笔者将自己做实验的代码上传到了 GitHub，链接在文章末尾。使用的是 Docker Compose 技术（方便一次性启动所有的系统）。&lt;/p&gt;
&lt;p&gt;接下来，我们详细介绍 Metrics插件及如何实现 Zabbix 监控 Jenkins。&lt;/p&gt;
&lt;h2 id=&#34;1-使-jenkins-暴露-metrics-api&#34;&gt;1. 使 Jenkins 暴露 metrics api&lt;/h2&gt;
&lt;p&gt;安装 Metrics 插件，在系统配置中，会多出“Metrics”的配置，如下图：
&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-ba867bb2509c6fc4.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;配置项不复杂。我们需要点击“Generate&amp;hellip;”生成一个 Access Key（生成后，记得要保存）。这个 Key 用于身份校验，后面我们会用到。&lt;/p&gt;
&lt;p&gt;保存后，我们在浏览器中输入URL：&lt;code&gt;http://localhost:8080/metrics/&amp;lt;刚生成的 Access Key&amp;gt;&lt;/code&gt; 验证 Jenkins 是否已经暴露 metrics。如果看到如下图，就说明可以进行下一步了。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-011d7fc64d176d63.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;11-metrics-插件介绍&#34;&gt;1.1 Metrics 插件介绍&lt;/h3&gt;
&lt;p&gt;Metrics 插件是基于 dropwizard/metrics 实现。它通过4个接口暴露指标数据：/metrics，/ping，/threads，/healthcheck。&lt;/p&gt;</description>
    </item>
    <item>
      <title>理解 Gerrit 的 Change-Id</title>
      <link>https://showme.codes/zh-cn/2019-03-24-understand-gerrit-change-id/</link>
      <pubDate>Sun, 24 Mar 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-03-24-understand-gerrit-change-id/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;审校：LinuxSuRen（https://github.com/LinuxSuRen）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&#34;Gerrit workflow&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/gerrit_workflow.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Gerrit 是一个基于 Git 版本控制的基于 Web 的代码审查工具 。笔者在学习它的过程中发现，要使用好它，第一步就是要理解 Change-Id。&lt;/p&gt;
&lt;h3 id=&#34;理解-change-id&#34;&gt;理解 Change-Id&lt;/h3&gt;
&lt;p&gt;要理解 Gerrit 的 Change-Id，我们就必须对“一次代码审查任务”有一个定义。通常，我们认为对一次完整的功能实现或 Bug 修复（即一次完整的变更）进行代码审查是合理的。而对一个半成品进行代码审查，得到的结论是不可靠的。因此，一次代码审查任务意味着是对一次变更进行审查。&lt;/p&gt;
&lt;p&gt;Gerrit 使用 Change-Id 来标识一次变更。Change-Id 实际上就是一串字符串，类似这样：&lt;code&gt;Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;但是，一次变更通常会伴随多次 Git 提交（Commit），而且每次提交的提交是不同的 Commit Id（提交Id）。Gerrit 如何将多次提交关联到同一个 Change-Id 呢？&lt;/p&gt;
&lt;p&gt;我们需要在每次提交时，将 Change-Id 以规定的格式放在提交消息（Commit message）的Footer 部分中（最后一行）。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/gerrit-commit-message-with-change-id.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;Change-Id 为避免与提交 Id 冲突，通常以大写字母&lt;code&gt;I&lt;/code&gt;为前缀。但是，我们怎么才能方便生成 Change-Id 呢？&lt;/p&gt;
&lt;h3 id=&#34;使用-git-钩子生成-change-id&#34;&gt;使用 Git 钩子生成 Change-Id&lt;/h3&gt;
&lt;p&gt;Change-Id 最好是自动生成，并放到提交消息指定位置，这样才能节约开发者的时间。Gerrit 提供了标准的“commit-msg”钩子来实现。&lt;/p&gt;
&lt;p&gt;Git 提供了4个提交工作流钩子：pre-commit、prepare-commit-msg、commit-msg、post-commit。其中 commit-msg 钩子，会在我们执行 &lt;code&gt;git commit&lt;/code&gt; 时被执行。&lt;/p&gt;
&lt;p&gt;本质上，commit-msg 钩子是一段脚本程序，放在 .git/hooks 目录下。commit-msg 脚本可以使用 Shell、Ruby、Python 等语言实现。&lt;/p&gt;
&lt;p&gt;Gerrit 的 commit-msg 钩子直接从 Gerrit 下载：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Electron 应用的流水线设计</title>
      <link>https://showme.codes/zh-cn/2019-3-10-electronjs-pipeline-demo/</link>
      <pubDate>Sun, 10 Mar 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-3-10-electronjs-pipeline-demo/</guid>
      <description>&lt;p&gt;面向读者：需要了解 Jenkins 流水线的基本语法。&lt;/p&gt;
&lt;p&gt;Electron 是由 Github 开发，用 HTML，CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。&lt;/p&gt;
&lt;p&gt;本文将介绍 Electron 桌面应用的流水线的设计。&lt;/p&gt;
&lt;p&gt;但是如何介绍呢？倒是个大问题。笔者尝试直接贴代码，在代码注释中讲解。这是一次尝试，希望得到你的反馈。&lt;/p&gt;
&lt;h3 id=&#34;完整代码&#34;&gt;完整代码&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pipeline&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 我们决定每一个阶段指定 agent，所以，
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 流水线的 agent 设置为 none，这样不会占用 agent
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;none&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 指定整条流水线的环境变量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;APP_VERSION&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;APP_NAME&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;electron-webpack-quick-start&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;stages&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;生成版本号&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;label&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;linux&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;APP_VERSION&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;generateVersion&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;version is ${APP_VERSION}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;并行构建&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// 快速失败，只要其中一个平台构建失败，
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// 整次构建算失败
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;failFast&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// parallel 闭包内的阶段将并行执行
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;parallel&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Windows平台下构建&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;label&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;windows &amp;amp;&amp;amp; nodejs&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;${APP_VERSION}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Linux平台下构建&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;label&lt;/span&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;linux &amp;amp;&amp;amp; nodejs&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// 不同平台可能存在不同的环境变量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// environment 支持阶段级的环境变量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;SUFFIX&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;tar.xz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;APP_PLATFORM&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;linux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;ARTIFACT_PATH&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;dist/${APP_NAME}-${APP_PLATFORM}-${APP_VERSION}.${SUFFIX}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;// Jenkins nodejs 插件提供的 nodejs 包装器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;// 包装器内可以执行 npm 命令。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;// nodejs10.15.2 是在 Jenkins 的全局工具配置中添加的 NodeJS 安装器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;nodejs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;nodeJSInstallationName:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;nodejs10.15.2&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;c1&#34;&gt;// 执行具体的构建命令
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;npm install yarn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;yarn version --new-version ${APP_VERSION}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;yarn install&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;yarn dist --linux deb ${SUFFIX}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;c1&#34;&gt;// 上传制品
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;uploadArtifact&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;${APP_NAME}&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;${APP_VERSION}&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;${ARTIFACT_PATH}&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;}}}&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 将括号合并是为了让代码看起来紧凑，提升阅读体验。下同。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Mac平台下构建&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;label&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;mac &amp;amp;&amp;amp; nodejs&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;stages&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;mac 下阶段1&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging 1&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;mac 下阶段2&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging 2&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;其它阶段，读者可根据情况自行添加&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;agent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;label&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;linux&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;发布&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;post&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;always&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cleanWs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 清理工作空间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;generateVersion&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ver&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gitCommitId&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;GIT_COMMIT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;take&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;${ver}-${gitCommitId}.${env.BUILD_NUMBER}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;uploadArtifact&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;appName&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;appVersion&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;artifactPath&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;根据参数将制品上传到制品库中，待测试&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;代码补充说明&#34;&gt;代码补充说明&lt;/h3&gt;
&lt;p&gt;因为 Electron 是跨平台的，我们需要将构建过程分别放到 Windows、Linux、Mac 各平台下执行。所以，不同平台的构建任务需要执行在不同的 agent 上。我们通过在 &lt;code&gt;stage&lt;/code&gt; 内定义 &lt;code&gt;agent&lt;/code&gt; 实现。如在“Mac平台下构建”的阶段中，&lt;code&gt;agent {label &amp;quot;mac &amp;amp;&amp;amp; nodejs&amp;quot; }&lt;/code&gt; 指定了只有 label 同时包括了 mac 和 nodejs 的 agent 才能执行构建。&lt;/p&gt;</description>
    </item>
    <item>
      <title>活多人少，每个需求都紧急，多数项目延期，怎么破？</title>
      <link>https://showme.codes/zh-cn/2019-3-1-software-engineering-tricky-problem/</link>
      <pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-3-1-software-engineering-tricky-problem/</guid>
      <description>&lt;p&gt;注意，下文所说的“老板”通常指业务提出方。&lt;/p&gt;
&lt;h3 id=&#34;问题描述&#34;&gt;问题描述&lt;/h3&gt;
&lt;p&gt;上个星期，在持续交付2.0的群中，群主发出一个别人的提问。在我看来，这个问题在软件工程领域非常的典型，所以想单独写一篇博客来讨论。以下是问题原文：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我是一个开发部门的管理人员，团队规模还较小，开发需要兼任测试和部分应用运维的工作，但公司业务条线不算少（差不多有4个左右，目前部门中基本是按每个条线分配2-3个开发，出现某个组资源不足以满足需求时再进行调剂），之前这么做还算稳定，因为各业务对交付频率的要求一般很低，但近期各业务都提出了一个较紧急的项目需求，而且都还无法推迟或拒绝，资源一下子变得非常紧张（如果可以推迟某一个当然就会有富裕资源，但是这些项目由于各种原因公司都不能放弃，也很难推迟），如果依然每个组独立完成自己的项目，可能导致大部分项目因无法按时交付失败，而且每个组还必须保留少部分资源用于处理日常业务，如果临时招聘也来不及做培训，我们暂时是让资深一些的程序员和管理人员参与多个项目中的开发，但仍不能完全解决问题（他们抱怨任务太多都来不及测试），请问帮主遇到这类情况应该如何协调？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;提问者所说的情况，在现实中，太普遍了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;（无法拒绝的）紧急需求的插入，打乱原有的步骤&lt;/li&gt;
&lt;li&gt;开发除了需求的开发，还需要处理日常业务&lt;/li&gt;
&lt;li&gt;人员不足：临时招聘也解决不了&lt;/li&gt;
&lt;li&gt;开发人员报怨任务太多，来不及测试（就上线？）&lt;/li&gt;
&lt;li&gt;项目无法按时交付&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以看出，“人员不足”是“项目无法按时交付”这个“果”的其中一个“因”。而提问者希望通过业务线之间人员的调剂和临时招聘的方式增加人员，但仍不能完全解决问题。笔者每当听到增加人员的信息，都会想起《人月神话》所说的：向进度落后的项目中增加人手，只会使进度更加落后。&lt;/p&gt;
&lt;p&gt;而从提问者口中也了解到，原来人员还算足（感觉是刚刚好），只是因为出现了一个无法拒绝的紧急需求，问题就暴露出来了。&lt;/p&gt;
&lt;h3 id=&#34;问题解决&#34;&gt;问题解决&lt;/h3&gt;
&lt;p&gt;面对这样的问题？你是如何解决的呢？以下是我个人提出来的解法，不一定对，只做交流：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第1是把当前工作的优先级排出来&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;把所有的工作内容（包括日常维护和新功能实现）列出来，同时，也要找到这些工作的交集（避免重复开发）。工作内容列出来后，确定它们的业务价值及优先级，并预估其开发难度。有些新功能是老板直接下发的，但是实现难度过高且业务价值又不高（团队及产品经理觉得），能和老板谈就和老板谈。这部分工作是我觉得最难的。&lt;/p&gt;
&lt;p&gt;这个工作的优先级一定要让老板看到。主要是避免老板中间随意的插入需求。当然，有时需要向现实妥协，但不是每次。同时也要让所有人达成共识，遵守这个优先级。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第2是找出团队平时工作中最耗时的环节（瓶颈），想办法在这个环节上减少耗时（自动化或者别的办法）。一般来说，经常工作在这个耗时环节的人会知道如何优化它。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第3是慢慢让人可以流动&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;意思是人没办法调剂到其它项目，通常是因为他不了解其它项目（业务或者技术）。所以，在平时，就要注意将项目的“知识”尽可能准确地传递给更多人。当然，也可以定向的传递。具体操作方式要看团队平时的协作方式。&lt;/p&gt;
&lt;p&gt;最后，1，2，3步需要重复执行，同时1，2，3步也不是顺序的。&lt;/p&gt;
&lt;p&gt;笔者提出这样的解法并不是笔者猜的，而是有依据的。依据如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;人员不足只是表象，我们怎么知道是真的人员不足，还是没有真正发挥每个人的最大潜能呢？第2、3步是为了让每个人发挥最大的潜能。而工具方面，个人建议通过看板可视化人员的工作内容，来达到了解当前资源状态的目的。&lt;/li&gt;
&lt;li&gt;即使每个人的潜能都发挥到了极致，但是还是出现人员不足的情况啊。这就是第1步要解决的问题。这时，我们要学会舍弃。但是为什么老板就不会舍弃，老爱插入一些所谓的紧急需求呢？个人认为是因为老板不了解你当前的工作内容及其优先级。所以，这个优先级一定要和老板达成共识。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;小结&#34;&gt;小结&lt;/h3&gt;
&lt;p&gt;当我把解法提出来了，群里的同学就提出了质疑：如何确定老板提出新功能是业务价值不高的？毕竟老板从整个公司考虑问题的。&lt;/p&gt;
&lt;p&gt;这位同学提出了一个软件工程领域内经常发生的问题：执行者怀疑业务提出者提出的需求的价值。个人觉得质疑是好事。但是质疑之后，双方有没有讨论及讨论结果才是关键。讨论了就容易形成共识，有了共识，大家才好力往一处使。题外话，“我只要结果，不管过程”的管理理念的适用范围是拿出来讨论的。&lt;/p&gt;
&lt;p&gt;最后，以上解法，不一定适用所有的情况。比如在外包项目管理中，可能就不适合。&lt;/p&gt;</description>
    </item>
    <item>
      <title>批量修改Jenkins任务的技巧</title>
      <link>https://showme.codes/zh-cn/2019-2-23-jenkins-script-console-in-practice/</link>
      <pubDate>Sat, 23 Feb 2019 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2019-2-23-jenkins-script-console-in-practice/</guid>
      <description>&lt;h3 id=&#34;通过脚本命令行批量修改jenkins任务&#34;&gt;通过脚本命令行批量修改Jenkins任务&lt;/h3&gt;
&lt;p&gt;最近，笔者所在团队的 Jenkins 所在的服务器经常报硬盘空间不足。经查发现很多任务没有设置“丢弃旧的构建”。通知所有的团队检查自己的 Jenkins 任务有没有设置丢弃旧的构建，有些不现实。&lt;/p&gt;
&lt;p&gt;一开始想到的是使用 Jenkins 的 API 来实现批量修改所有的 Jenkins 任务。笔者对这个解决方案不满意，经 Google 发现有同学和我遇到了同样的问题。他使用的更“技巧”的方式：在 Jenkins 脚本命令行中，通过执行 Groovy 代码操作 Jenkins 任务。&lt;/p&gt;
&lt;p&gt;总的来说，就两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;进入菜单：&lt;em&gt;系统管理 &amp;ndash;&amp;gt; 脚本命令行&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在输入框中，粘贴如下代码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;jenkins.model.Jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;hudson.model.Job&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;jenkins.model.BuildDiscarderProperty&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;hudson.tasks.LogRotator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 遍历所有的任务
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Jenkins&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;instance&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;allItems&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Job&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;each&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;job&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;job&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;isBuildable&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;job&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;supportsLogRotator&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;job&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getProperty&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;BuildDiscarderProperty&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;println&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34; \&amp;#34;${job.fullDisplayName}\&amp;#34; 处理中&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;job&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;addProperty&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;BuildDiscarderProperty&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LogRotator&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;println&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;$job.name 已更新&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;LogRotator构造参数分别为：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;daysToKeep:  If not -1, history is only kept up to this days.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;numToKeep: If not -1, only this number of build logs are kept.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;artifactDaysToKeep: If not -1 nor null, artifacts are only kept up to this days.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;artifactNumToKeep: If not -1 nor null, only this number of builds have their artifacts kept.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;**/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;脚本命令行介绍&#34;&gt;脚本命令行介绍&lt;/h3&gt;
&lt;p&gt;脚本命令行（Jenkins Script Console），它是 Jenkins 的一个特性，允许你在 Jenkins master 和 Jenkins agent 的运行时环境执行任意的 Groovy 脚本。这意味着，我们可以在脚本命令行中做任何的事情，包括关闭 Jenkins，执行操作系统命令 &lt;code&gt;rm -rf /&lt;/code&gt;（所以不能使用 root 用户运行 Jenkins agent）等危险操作。&lt;/p&gt;</description>
    </item>
    <item>
      <title>最好的礼物是一个真诚的建议</title>
      <link>https://showme.codes/zh-cn/2018-12-11-to-be-broad-minded-toward-others/</link>
      <pubDate>Tue, 11 Dec 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-12-11-to-be-broad-minded-toward-others/</guid>
      <description>&lt;p&gt;李翔商业内参 9月15日（中秋）那期的主题是：最好的礼物是一个真诚的建议。我深表认同。&lt;/p&gt;
&lt;p&gt;两年前，我被另一个资深的同事说了一通（当时我们正结对编程）。原因是我说了另一个同事的代码写得烂，还随手看了下提交记录，发现是当时的leader。&lt;/p&gt;
&lt;p&gt;先不说这位同事教训得是不是。我心里很不爽：我说的就是真话嘛。&lt;/p&gt;
&lt;p&gt;后来，我发邮件给总经理，说了一通，诚恳地向他请教，到底是谁的错。后来总经理给我回了一封邮件，邮件其中一段是这样的：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;   有个有意思的故事：
   弟子问：”师父您有时候打人、骂人；有时候对人又彬彬有礼，这里面有什么玄机吗？”
   师父说：对待上等人直指人心，可打可骂，以真面目待他；对待中等人最多隐喻他，要讲分寸，他受不起打骂；对待下等人要面带微笑，双手合十，他很脆弱、装不下太多指责和训斥，他只配用世俗的礼节
   我想说的不是谁是上等人，谁不是，而是受众不一样，说话的方法就会不一样。
   中国有句老话，世事洞明皆学问，人情练达即文章。能够把意见说的让人接受是个技能，要不断练习，你自己也多揣摩。
   到底是谁的问题不重要， 严于律己，宽以待人对你的职业生涯会很有帮助。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每每对别人过于苛刻时，我都会想起“严于律己，宽以待人”。虽然，有时还会偶尔犯贱。但是它的确改变了我。&lt;/p&gt;
&lt;p&gt;&amp;hellip;不小心翻出自己2016年写的小短文。&lt;/p&gt;</description>
    </item>
    <item>
      <title>阿里三面后的思考</title>
      <link>https://showme.codes/zh-cn/2018-6-24-alibaba-interview/</link>
      <pubDate>Sun, 24 Jun 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-6-24-alibaba-interview/</guid>
      <description>&lt;h4 id=&#34;你对你6年跳5次有什么感想&#34;&gt;你对你6年跳5次有什么感想？&lt;/h4&gt;
&lt;p&gt;上周进行了阿里三面。奇怪的是他们居然迟到10来分钟。上来直接问的都是我职业生涯的问题（不清楚面试官的岗位）。&lt;/p&gt;
&lt;p&gt;从我第一份工作到最近的工作，一个个问入职时间，做了什么，离职时间，为什么离职。我也“老实”一一回复。这个过程，加上视频的网络质量不好，我感觉好像被压着说话。&lt;/p&gt;
&lt;p&gt;最后，面试官问我：你对你6年跳5次是什么感想？（后来算算我应该是毕业6年，工作7年。而5次是指包括这次跳成功）&lt;/p&gt;
&lt;p&gt;听到这个问题，我一下懵了。我从来没有仔细想过这个问题，想了一下，说出了自己的内心的声音：太年轻，太冲动。&lt;/p&gt;
&lt;p&gt;面试官没有说下去。后来草草的结束了20多分钟的面试。&lt;/p&gt;
&lt;p&gt;结束后，我问自己：你为什么在别人眼里就是不稳定？HR 眼里跳槽“多”就是不稳定？&lt;/p&gt;
&lt;p&gt;回想自己的回答，的确给别人不稳定的感觉。因为一次主要是因为对 leader 不爽，一次是因为办公室政治干不过别人。所以，各位提前想好自己跳槽的“借口”很重要。&lt;/p&gt;
&lt;p&gt;但是，另一个问题开始不断萦绕自己：你为什么跳这么多次槽？回想自己的每次跳槽，没有答案。于是，我反过来想：公司如何才能留住我？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一家创业公司7人，我的导师走了，我唯一留在这家公司的理由都没有了。&lt;/li&gt;
&lt;li&gt;第二家公司做开源软件，Leader 换成了我不喜欢的人。现在想想真幼稚。现在想想，真没必要。&lt;/li&gt;
&lt;li&gt;第三家公司是一家咨询+外包的公司，做了一年多的外包，发现自己想做自己的产品，我留在公司的理由是有产品给我做，询问当时的办公室负责人，并没有产品可以做。&lt;/li&gt;
&lt;li&gt;第四家公司是做产品了，组织构架的调整我不满意，产品不再是我，细节不方便说，我就想好好做产品，留下来的理由也没有了。&lt;/li&gt;
&lt;li&gt;目前这家公司，进来的初衷是从零建设一个敏捷的团队，后来希望破灭。然后我的希望变成能好好写代码。目前留下来的理由是好好磨炼自己的技术。但有机会会看。因为这里是 code is cheap。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;企业应该如何留住员工&#34;&gt;企业应该如何留住员工&lt;/h4&gt;
&lt;p&gt;我尝试把“我”的私人问题扩大到组织上思考：企业应该如何留住员工？&lt;/p&gt;
&lt;p&gt;最近看的&lt;a href=&#34;https://movie.douban.com/subject/25704492/&#34;&gt;《红雀》&lt;/a&gt;的台词跳了出来：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;给了别人想要的，你就会得到你想要的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;留住员工，靠的不是“留”，而是“给”。当然，前提是这个人值得留。&lt;/p&gt;
&lt;p&gt;这时，会有读者想到马斯洛需求层次理论：
&lt;img alt=&#34;马斯洛模型&#34; loading=&#34;lazy&#34; src=&#34;https://upload-images.jianshu.io/upload_images/292372-8daa25b0ecf990bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&#34;&gt;&lt;/p&gt;
&lt;p&gt;这个理论告诉我们如何满足一个人的不同层次的需求。但是，站在公司层面，如何操作呢？毕竟公司里会同时存在不同层次需求的人，而且同一个人不同时期的需求还可能千差万别。&lt;/p&gt;
&lt;p&gt;如何能满足所有人的需求（至少是大部分）？我想到了一句话：海纳百川，有容乃大。但是不可能路边随随便便就收纳，而是能帮助公司实现目标的人。这是海纳百川的&lt;strong&gt;前提&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以，公司应该像大海，能满足不同的人不同时期的需求。这是什么意思？&lt;/p&gt;
&lt;p&gt;生理需求上，提供上行业内比较有竞争力的薪资，像奈飞。安全需求上，比如提供团体险；社交需求上，让工程师之间有更多的交流的机会，比如谷歌为促进员工的非正式交流，在食堂排队，一般要4分钟。因为时间长了大家会掏出手机来看，时间短了，同事们又聊不起来……等等。&lt;/p&gt;
&lt;p&gt;P.S. 公司文化并不是没有理论的发展，而是要根据公司的发展需要进行调整。&lt;/p&gt;
&lt;p&gt;我再次强调，上述想法的前提：&lt;strong&gt;不可能路边随随便便就收纳，而是能帮助公司实现目标的人&lt;/strong&gt;。所以，要严进。&lt;/p&gt;
&lt;p&gt;同时，上述想法是理想情况，处于&lt;a href=&#34;https://book.douban.com/subject/1084649/&#34;&gt;企业生命周期&lt;/a&gt;不同阶段的公司需要根据自己的具体情况量力而行。&lt;/p&gt;
&lt;p&gt;同时，我也提醒读者啊：我没有办过企业，上述想法都个人臆想。&lt;/p&gt;
&lt;h4 id=&#34;教训&#34;&gt;教训&lt;/h4&gt;
&lt;p&gt;给那些还在路上的新人，不要冲动，不要冲动，不要冲动。不要学我。我是反例。&lt;/p&gt;
&lt;h4 id=&#34;成本&#34;&gt;成本&lt;/h4&gt;
&lt;p&gt;最后，突然想到，好奇阿里为什么不让HR 先把这些稳定性问题在一面给问了？就可以节约一二面的工程师的时间了。毕竟工程师的时间永远相对 HR 的时间更缺。招工程师比招 HR 更难，如果我没有理解错的话。&lt;/p&gt;
&lt;h4 id=&#34;小结&#34;&gt;小结&lt;/h4&gt;
&lt;p&gt;听说30到35岁是大多数人职场的转折点。我相信了。想清自己想要的，是做好职业规划的前提。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用Ansible实现自动化运维的一些技巧</title>
      <link>https://showme.codes/zh-cn/2018-6-22-ansible-in-action-1/</link>
      <pubDate>Fri, 22 Jun 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-6-22-ansible-in-action-1/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;提示：本文要求读者有一定的 Ansible 使用经验&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近一年才有机会在生产环境上使用 Ansible。用的过程中，想把一些小技巧记录下来，避免自己忘记。如果能帮助到其他同学就更好了。如果有同学指出有更好的方法，就更更好了。&lt;/p&gt;
&lt;h3 id=&#34;技巧1校验你的模板文件是否正确&#34;&gt;技巧1：校验你的模板文件是否正确&lt;/h3&gt;
&lt;p&gt;通常我们会使用&lt;code&gt;template&lt;/code&gt; module 来生成应用的配置，比如生成 Nginx 的配置或者 sudoers 配置。而像 sudoers 文件内的配置错误可能直接导致无法登录。所以，我们希望在生成这些配置文件后能校验一下它的正确性。如果校验失败，直接停止，不生成该配置文件。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;template&lt;/code&gt; module 有一个属性 &lt;code&gt;validate&lt;/code&gt; 就是为了实现这一需求的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;user-sudoers&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/etc/sudoers.d/abc&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;validate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;visudo -cf %s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;校验 Nginx 配置文件的文件：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Copy the nginx file&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx.conf.j2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;/etc/nginx/nginx.conf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;validate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/sbin/nginx -t -c %s&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;notify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;restart nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;校验 Prometheus 配置文件：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Copy Prometheus config&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;prometheus.yml.j2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/etc/prometheus.yml&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;validate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;promtool check config %s&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;notify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;reload prometheus config&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;校验 Logstash 配置文件：&lt;/p&gt;</description>
    </item>
    <item>
      <title>一些小团队的自动化运维实践经验</title>
      <link>https://showme.codes/zh-cn/2018-6-7-devops-in-action/</link>
      <pubDate>Thu, 07 Jun 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-6-7-devops-in-action/</guid>
      <description>带团队的日志</description>
    </item>
    <item>
      <title>通俗解释有了 IP 地址，为什么还要用 MAC 地址？</title>
      <link>https://showme.codes/zh-cn/2018-5-17-understand-mac-ip/</link>
      <pubDate>Thu, 17 May 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-5-17-understand-mac-ip/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;题记：既生亮何生瑜。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;摘要：标题虽然是为了解释有了 IP 地址，为什么还要用 MAC 地址，但是本文的重点在于理解为什么要有 IP 这样的东西。本文对读者的定位是知道 MAC 地址是什么，IP 地址是什么。&lt;/p&gt;
&lt;p&gt;一开始时，网络中的机器并不多。大家都连到同一个集线器就可以了，就可以实现互通。这时，机器 A 发消息到机器 B ，消息头里附上机器 B 的MAC，集线器收到消息后就广播给所有连到集线器的机器。&lt;/p&gt;
&lt;p&gt;机器 C 收到消息，发现消息里的 MAC 地址和自己的不一样，就丢弃。机器B发现消息里的 MAC 地址和自己一样，就收到下并解析。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-dc3520e3cf34d31d.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;这样机制带来问题很明显：首先每次广播，给所在网络带来不必要的浪费。所以，就出现了交换机。它能识别消息里的目标 MAC 地址后，直接就消息丢到机器 B 所连接的端口中。另一个角度，交换机必须记住所有的 MAC 地址和端口之间的关系。&lt;/p&gt;
&lt;p&gt;这样的机制在网络规规模小的时候是高效的。但是当网络规模扩大到全球的时候，不可能让一台交换机记录下全球这么多的网络设备，也不可能让全球的机器连接到一台交换机上。&lt;/p&gt;
&lt;p&gt;那如果是多台交换机呢？&lt;/p&gt;
&lt;p&gt;想像一下，你是斯坦福的学生，你的电脑 x 的网络直连的是学校的交换机，而学校的交换机又连美国国家网络交换机。而美国国家网络交换机又直接的是中国国家网络交换机，中国服务器 y 直连的是中国国家交换机。&lt;/p&gt;
&lt;p&gt;你想访问中国的服务器 y 中的资源。你了解到服务器 y 的 MAC 地址是00:0C:29:01:00:12，所以你在消息里附上这个 MAC 地址。&lt;/p&gt;
&lt;p&gt;学校交换机收到消息后，拿到 MAC 地址后就愣了，这是要发给谁啊？因为中国服务器 y 并不是直连学校交换机的。这时，学校交换机有一个选择，就是收到不明的 MAC 地址时，一律转发给默认端口。斯坦福交换机就将消息转给美国国家交换机。&lt;/p&gt;
&lt;p&gt;美国国家交换机同样发愣了，因为没有这条 MAC 地址对应的端口。它又直接向默认端口：中国国家网络交换机。&lt;/p&gt;
&lt;p&gt;中国国家网络交换机收到消息，发现自己记录了 MAC 地址 对应的是服务器 y。就直接将你这位斯坦福学生的消息转发到服务器 y 所连接的端口。&lt;/p&gt;
&lt;p&gt;最终，我们的服务器 y 终于收到来自美国斯坦福学生的资源访问请求。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-dee29f5c16c55875.png&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>阿里云经典网络下如何节约公网 IP 费用</title>
      <link>https://showme.codes/zh-cn/2018-5-14-save-ip/</link>
      <pubDate>Mon, 14 May 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-5-14-save-ip/</guid>
      <description>&lt;h3 id=&#34;想法&#34;&gt;想法&lt;/h3&gt;
&lt;p&gt;历史原因，我们一直使用的是阿里云经典网络的 ECS，疲于业务的开发及人力不足，一直没有特别大的动力迁移到 VPC 下。&lt;/p&gt;
&lt;p&gt;而经典网络下的 ECS 的公网 IP 是收费的，而且没有公网 IP 有时会不方便。&lt;/p&gt;
&lt;h4 id=&#34;购买公网-ip-时的费用&#34;&gt;购买公网 IP 时的费用&lt;/h4&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-c3e63384a16f1863.png&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;未购买公网-ip-时的费用&#34;&gt;未购买公网 IP 时的费用&lt;/h4&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-f59626b2a8d6252a.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;如果是按月购买，每个月将节约：296 - 273 = 23 元。&lt;/p&gt;
&lt;p&gt;而我们的大多服务是内网使用的，所以，公网 IP 的申请完全是浪费。&lt;/p&gt;
&lt;p&gt;可是，我们有时，还是需要公网下载一些东西的。这时怎么办呢？&lt;/p&gt;
&lt;p&gt;我的方案是：没有公网 IP 的机器，使用 HTTP 代理就可以上网了。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-b00de3f909fab15b.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;当然，哪些机器需要上网，基于安全上的考虑，需要读者自己决定了。&lt;/p&gt;
&lt;h3 id=&#34;怎么做&#34;&gt;怎么做？&lt;/h3&gt;
&lt;p&gt;我的具体实现方便使用 Squid 搭建 http proxy 服务。其他机器通过配置环境变量配置，笔者是通过 Ansible 初始化机器时配置的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;use httpproxy&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;lineinfile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/etc/profile&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;line&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;&amp;lt;item&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with_items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;export http_proxy=http://&amp;lt;&amp;lt; httpproxy.host &amp;gt;&amp;gt;:&amp;lt;&amp;lt; httpproxy.port &amp;gt;&amp;gt;/&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;export https_proxy=http://&amp;lt;&amp;lt; httpproxy.host &amp;gt;&amp;gt;:&amp;lt;&amp;lt; httpproxy.port &amp;gt;&amp;gt;/&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;when&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;is_use_httpproxy is defined and is_use_httpproxy == &amp;#39;True&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;httpproxy&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关于 Squid Http Proxy 服务，笔者同样是使用 Ansible 搭建，具体不细表，看官可以自行看代码：&lt;a href=&#34;https://github.com/zacker330/squid-ansible&#34;&gt;Squid-ansible&lt;/a&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>为什么“分层”给我们带来好处——论软件工程的分层概念</title>
      <link>https://showme.codes/zh-cn/2018-5-1-all-problems-in-computer-science-can-be-solved-by-another-level-of-indirection/</link>
      <pubDate>Tue, 01 May 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-5-1-all-problems-in-computer-science-can-be-solved-by-another-level-of-indirection/</guid>
      <description>计算机科学中的任何问题，都可以通过加上一层逻辑层来解决。-- David Wheeler</description>
    </item>
    <item>
      <title>出现运维事故后，你会怎么办？</title>
      <link>https://showme.codes/zh-cn/2018-3-30-run-while-system-down/</link>
      <pubDate>Fri, 30 Mar 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-3-30-run-while-system-down/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;stormtrooper-2296199_640.jpg&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-d079a1036ccc1e08.jpg&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;从聊天说起&#34;&gt;从聊天说起&lt;/h3&gt;
&lt;p&gt;有一次和朋友聊天，他说他们有一次部署出事了，影响还挺大，那次事故后，他们公司对于部署流程增加了更多的审批。&lt;/p&gt;
&lt;p&gt;当朋友说完前半句时，我已经猜到下半句，那是很多公司或个人会做出的反应。至于为什么会做出这样的反应，我也不知道。&lt;/p&gt;
&lt;p&gt;我问：为什么那次部署会“出事”？&lt;/p&gt;
&lt;p&gt;他说：当时部署的人忘记了那台机器上有一条 Iptable 规则，导致了事故。&lt;/p&gt;
&lt;p&gt;我就在想，如果有人审批，那次事故就不会发生吗？审批的人就知道那台机器上有一条规则导致事故的发生？然后驳回这次部署吗？连一线的开发和运维都忘记了的 Iptable 规则，“高高在上的审批领导”就更不知道了。&lt;/p&gt;
&lt;p&gt;题外话：增加审批流程并不能避免这次事故，只不过当出现事故时，可以更好的定责。然而我又好奇了，这种“审批”是为了解决问题，解决什么问题？，还是为了逃避责任？谁逃避了责任？谁又有责任？&lt;/p&gt;
&lt;p&gt;对于这类问题，我心里已经有数了，但想知道这位朋友的回答，就接着问：那么怎么杜绝这类问题呢？&lt;/p&gt;
&lt;p&gt;他说：因为那条 Iptable 规则的设置太久远了，是谁都记不起。如果能把每次部署的步骤记录下来，这样下次部署的时候，过一下以前的部署记录，就会知道那个 Iptable 规则了。（作者：大概原意，已经记不清原话）&lt;/p&gt;
&lt;p&gt;这位朋友说的做法，我之前待的一个团队的做法也差不多：会有一个页面专门记录下每次部署的步骤，步骤由开发人员写，然后由运维人员执行。只是我不知道他们会不会回顾之前所有针对这台机器的部署步骤。&lt;/p&gt;
&lt;p&gt;这个团队里有某某大型互联网公司来的架构师和某财务软件公司来的运维，所以，我不负责地推测，我们这个行业很多公司对于配置的管理还没有达到足够的重视，也没有正确的看待。&lt;/p&gt;
&lt;p&gt;我笑了，接着问朋友：那我要知道当前机器的“最终状态”，是不是要找出所有部署记录，还要过滤出对这次部署有影响的每一个细节？比如那条 Iptable 规则。&lt;/p&gt;
&lt;p&gt;接下来的对话细节已经记不清，也不重要了。重要的是找出针对这类运维事故根本原因及解决办法。&lt;/p&gt;
&lt;p&gt;我个人认为这类问题的根本原因在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置管理的失控：
已经没有人完整知道线上环境配置是什么了？要了解时，只能一个个查。&lt;/li&gt;
&lt;li&gt;测试环境与生产环境的配置不一致：
如果那位倒霉的同学在测试环境部署出现这样的问题，到生产环境部署时，自然就会注意相关配置项了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上只是我个人认为的，不一定正确，欢迎各位读者讨论。&lt;/p&gt;
&lt;p&gt;那如何杜绝这类问题呢？&lt;/p&gt;
&lt;p&gt;这两个原因可以看作一个，也可以看作两个。但方法都是一样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用声明式的配置管理方法，而不是脚本式&lt;/li&gt;
&lt;li&gt;版本化这些声明的配置&lt;/li&gt;
&lt;li&gt;所有环境使用同一套装配置管理方法&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;使用声明式的配置管理方法而不是脚本式的&#34;&gt;使用声明式的配置管理方法，而不是脚本式的&lt;/h3&gt;
&lt;p&gt;脚本式的配置管理是这样的：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apt-get install build-essential
apt-get install libtool
cd /usr/local/src
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.37.tar.gz
tar -zxvf pcre-8.37.tar.gz
cd pcre-8.34
./configure
make
make install
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而声明式的配置管理是这样的：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# ./ansible-nginx/tasks/install_nginx.yml
    # 使用这个7-0.el7版本的yum包
    - name: NGINX | Installing NGINX repo rpm
       yum:
       name: http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

    # 当前机器的nginx的状态应该是最新版本
    - name: NGINX | Installing NGINX
       yum:
       name: nginx  
       state: latest

    # 当前机器的 nginx service 的状态应该是已经启动的。至于如何确保 nginx 这个 service，当前是什么状态的，又是如何启动的，我们不需要关心。
    - name: NGINX | Starting NGINX
       service:
       name: nginx
       state: started
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;声明式的配置里写的是当前环境的“状态”，语意上，声明式的配置不论你执行多少次，你得到最终的“状态”就是你所声明的，这也就实现了《持续交付》里说的：&lt;/p&gt;</description>
    </item>
    <item>
      <title>《孩子把你的手给我》给管理带来的启示</title>
      <link>https://showme.codes/zh-cn/2018-3-27-between-parent-and-child/</link>
      <pubDate>Tue, 27 Mar 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-3-27-between-parent-and-child/</guid>
      <description>前两年看完这本书时，我的个人感受：带小孩和带团队有几分相似。要培养他们，要处理他们之间的矛盾，一样要处理他们的情感，还要给他们发言权……然而，带小孩和带团队都知易行难，大家相互勉励。</description>
    </item>
    <item>
      <title>巧用 Ansible 实现配置管理：多环境配置问题</title>
      <link>https://showme.codes/zh-cn/2018-3-11-ansible-inventory-configuration/</link>
      <pubDate>Sun, 11 Mar 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-3-11-ansible-inventory-configuration/</guid>
      <description>配置管理是本书其他内容的基础。没有配置管理，根本谈不上持续集成、发布管理以及部署流水线。它对交付团队内部的协作也会起到巨大的促进作用</description>
    </item>
    <item>
      <title>现实中的康威推论——IoT系统为例</title>
      <link>https://showme.codes/zh-cn/2018-3-9-conway/</link>
      <pubDate>Fri, 09 Mar 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-3-9-conway/</guid>
      <description>是推论，还是定律，不重要</description>
    </item>
    <item>
      <title>如何进行有效的指导，如何成功的分配任务</title>
      <link>https://showme.codes/zh-cn/2018-2-19-secrets-of-great-management-1/</link>
      <pubDate>Mon, 19 Feb 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-2-19-secrets-of-great-management-1/</guid>
      <description>卓越管理的秘密</description>
    </item>
    <item>
      <title>如何充分“使用”研发团队新人</title>
      <link>https://showme.codes/zh-cn/2018-1-15-new-fish/</link>
      <pubDate>Mon, 15 Jan 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-1-15-new-fish/</guid>
      <description>缩短出活时长的目的不是为了让他们有更多时间加班。目的是提出团队的生产力。这样我们才有更多时间陪家人</description>
    </item>
    <item>
      <title>一个软件工程师对职场、管理、企业的思考——上篇</title>
      <link>https://showme.codes/zh-cn/2018-1-6-work-for-work-1/</link>
      <pubDate>Sat, 06 Jan 2018 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2018-1-6-work-for-work-1/</guid>
      <description>一个软件工程师的思考</description>
    </item>
    <item>
      <title>公交卡带来的管理启示</title>
      <link>https://showme.codes/zh-cn/2017-12-16-management-in-bus/</link>
      <pubDate>Sat, 16 Dec 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-12-16-management-in-bus/</guid>
      <description>公交车没白坐</description>
    </item>
    <item>
      <title>这就是领域驱动设计(DDD)的作用？</title>
      <link>https://showme.codes/zh-cn/2017-11-22-ddd/</link>
      <pubDate>Wed, 22 Nov 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-11-22-ddd/</guid>
      <description>说有用，就是有用，说没有用，就是没用</description>
    </item>
    <item>
      <title>西安火车站旁一小面馆带给我的启示</title>
      <link>https://showme.codes/zh-cn/2017-10-14-lean-noodle/</link>
      <pubDate>Sat, 14 Oct 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-10-14-lean-noodle/</guid>
      <description>精益面馆</description>
    </item>
    <item>
      <title>《精益产品开发》老翟读后感</title>
      <link>https://showme.codes/zh-cn/2017-10-8-lean-product-development/</link>
      <pubDate>Sun, 08 Oct 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-10-8-lean-product-development/</guid>
      <description>这是一本好书</description>
    </item>
    <item>
      <title>ChatOps实战</title>
      <link>https://showme.codes/zh-cn/2017-10-08-chatops-in-action/</link>
      <pubDate>Sun, 08 Oct 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-10-08-chatops-in-action/</guid>
      <description>ChatOps没有那么神秘，也就是正则&#43;脚本</description>
    </item>
    <item>
      <title>简单易懂Ansible系列 —— 实现ssh key主机之间复制</title>
      <link>https://showme.codes/zh-cn/2017-8-19-ansible-manage-sshkey/</link>
      <pubDate>Sat, 19 Aug 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-8-19-ansible-manage-sshkey/</guid>
      <description>&lt;p&gt;我们在搭建Hadoop完全分布式环境时，Hadoop的name node节点（理解为master节点）需要无密码登录到所有的data node节点。&lt;/p&gt;
&lt;p&gt;当然，我们使用手工的方式很容易就实现了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在name node节点上生成ssh key：ssh-keygen&lt;/li&gt;
&lt;li&gt;将public key copy到所有的data node节点上：ssh-copy-id slave1&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同时，你还必须设置&lt;code&gt;~/.ssh/config&lt;/code&gt;，以防止登录时不停的问yes or no：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;```yml
Host *
    StrictHostKeyChecking no
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完了，还要设置这个文件的权限为&lt;strong&gt;400&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以上步骤当然可以手工一步步执行。但是，总有那么一些人：希望所有的操作都可以版本化，所有的操作都应该自动化。我属于这些人。&lt;/p&gt;
&lt;p&gt;再说了，我发现在搭建Jenkins环境时，也遇到了同样的问题：需要将Jenkins master的public key加入到Jenkins agent机器中。&lt;/p&gt;
&lt;p&gt;可以预见到将来我还会遇到类似的问题。于是，我找到一个方法来自动化以上操作。&lt;/p&gt;
&lt;h3 id=&#34;在name-node机器上执行task如下&#34;&gt;在name node机器上执行task如下&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建用户的时候生成ssh_key：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;create hadoop user&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{hadoop_user}}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;group&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;{{hadoop_group}}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;createhome&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;yes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;generate_ssh_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;yes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ssh_key_bits&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2048&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ssh_key_file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;.ssh/id_rsa&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tags&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;hadoop&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将id_rsa.pub拉取到ansible执行机器上&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;fetch public key&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;fetch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/home/{{hadoop_user}}/.ssh/id_rsa.pub&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;/tmp/&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;flat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;yes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tags&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;hadoop&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置&lt;code&gt;StrictHostKeyChecking no&lt;/code&gt;
因为我们只想修改这个用户的ssh行为，所以我们的ssh的配置只是针对当前这个用户的：&lt;/p&gt;</description>
    </item>
    <item>
      <title>简单易懂Ansible系列 —— 解决了什么</title>
      <link>https://showme.codes/zh-cn/2017-06-12-ansible-introduce/</link>
      <pubDate>Mon, 12 Jun 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-06-12-ansible-introduce/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;Ansible&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-a504ab242c05db6a.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;不知什么时候，Ansible的slogan从“IT Automation Software for System Administrators”变成了“AUTOMATION FOR EVERYONE”。&lt;/p&gt;
&lt;p&gt;从一个给系统管理员使用的工具变成了给所有人使用的工具。&lt;/p&gt;
&lt;p&gt;但是，现实中，发现了解Ansible的人，还是太少了。同时，自己断断续续学习Ansible也有一段时间，希望拿出来和大家交流。所以就决定不定期写写一个关于Ansible的系列。如果你觉得我写得还可以，到文末扫码请我喝杯茶。&lt;/p&gt;
&lt;p&gt;此文为“简单易懂Ansible”系列文章的开篇 —— Ansible解决了什么&lt;/p&gt;
&lt;h2 id=&#34;ansible解决了什么&#34;&gt;Ansible解决了什么&lt;/h2&gt;
&lt;p&gt;首先，它是一个运维工具。当然要解决运维过程中遇到的问题了。运维过程遇到了什么问题？&lt;/p&gt;
&lt;p&gt;想像一下，你要在一台新的机器上安装Tomcat，你会怎么样呢，条件反射的：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh user@111.111.111.111

wget -c http://apache.fayea.com/tomcat/tomcat-8.5.15.tar.gz

tar -zxf apache-tomcat-8.5.15.tar.gz
.....省略
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好，10分钟后你愉快地完成了老板给你的任务。但是现在你需要给100台机器安装Tomcat呢？手工的重复100次？&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;懵逼满屏&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/292372-b33ed81e9618ca8a.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;而Ansible能让我们只定义一次，理论上可以在无限台机器上执行。换句话：&lt;strong&gt;减少运维工作中的重复工作&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;同时，如果是人工执行100次，那么失误是难免的！自动化运维工具会严格根据我们所给指令来执行，而不会因为失恋而手抖执行了：&lt;code&gt;sudo rm -rf /&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;不少人反对自动化，认为那样太危险，因为一不小心就在上百台机器删错文件。显然，他们没有注意到：自动化实现的是&lt;strong&gt;准确地执行指令&lt;/strong&gt;，解决人类执行任务时存在的指令理解不正确、执行不严格的问题。而机器不会出现这些问题的概念几乎为零。&lt;/p&gt;
&lt;p&gt;没有达到预期效果，往往是我们人类下达的指令不正确。&lt;/p&gt;
&lt;p&gt;所以，Ansible还解决了&lt;strong&gt;人执行指令不准确&lt;/strong&gt;的问题。&lt;/p&gt;
&lt;p&gt;如果使用Ansible来实现上述的运维需求，怎么做呢？你需要做三件事情：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义目标机器的列表：一种被称为inventory的类ini文件&lt;/li&gt;
&lt;li&gt;定义这些机器的配置：使用&lt;a href=&#34;https://en.wikipedia.org/wiki/YAML&#34;&gt;YAML&lt;/a&gt;格式的文件来描述你机器的配置&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;ansible-playbook -i inventory playbook.yml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是inventory文件：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[tomcat-servers]
111.111.111.111
112.112.112.112
....
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而这些ip的配置写在一种被称为playbook的YAML文件中：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;---
- hosts: tomcat-servers
  tasks:
    - name: download tomcat
      get_url:
          url: http://apache.fayea.com/tomcat/tomcat-8.5.15.tar.gz
          dest: /tmp
          
    - name: unarchive tomcat to /usr/local
      unarchive:
          src: /tmp/apache-tomcat-8.5.15.tar.gz
          dest: /usr/local/
          remote_src: true
.....省略
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果你想再添加100台机器，你需要做的，也只是在inventory文件里添加100个ip，再执行一遍ansible-playbook命令。&lt;/p&gt;</description>
    </item>
    <item>
      <title>即将三十，我不敢说我精通任何一项技术</title>
      <link>https://showme.codes/zh-cn/2017-05-10-30-years-old/</link>
      <pubDate>Wed, 10 May 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-05-10-30-years-old/</guid>
      <description>到底要不要转管理呢？</description>
    </item>
    <item>
      <title>为什么站会会成为形式</title>
      <link>https://showme.codes/zh-cn/2017-05-07-no-standup-no-problem/</link>
      <pubDate>Sun, 07 May 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-05-07-no-standup-no-problem/</guid>
      <description>想实践站会，但是过程非常痛苦</description>
    </item>
    <item>
      <title>什么？项目延期有解药？</title>
      <link>https://showme.codes/zh-cn/2017-4-14-split-task-by-project-manager/</link>
      <pubDate>Fri, 14 Apr 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-4-14-split-task-by-project-manager/</guid>
      <description>当我们要考虑如何让项目不延期时，我们是否做到让每个员工都满负荷了？我们追求的是不延期，还是追求更卓越的产品？</description>
    </item>
    <item>
      <title>如何防止程序员上班迟到？</title>
      <link>https://showme.codes/zh-cn/2017-3-3-prevent-late-for-work/</link>
      <pubDate>Fri, 03 Mar 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-3-3-prevent-late-for-work/</guid>
      <description>你今天打卡了没？</description>
    </item>
    <item>
      <title>我从Airbnb联合创始人的Talk里学到的</title>
      <link>https://showme.codes/zh-cn/2017-2-24-learn-from-joe/</link>
      <pubDate>Fri, 24 Feb 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-2-24-learn-from-joe/</guid>
      <description>学习别人如何思考比接受别人的知识更重要</description>
    </item>
    <item>
      <title>也许，这样理解HTTPS更容易</title>
      <link>https://showme.codes/zh-cn/2017-2-20-understand-https/</link>
      <pubDate>Mon, 20 Feb 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-2-20-understand-https/</guid>
      <description>本文尝试一步步**还原**HTTPS的设计过程，以理解为什么HTTPS最终会是这副模样。但是这并不代表HTTPS的真实设计过程。在阅读本文时，你可以尝试放下已有的对HTTPS的理解，这样更利于“还原”过程。</description>
    </item>
    <item>
      <title>我故意写了个死循环</title>
      <link>https://showme.codes/zh-cn/2017-2-17-endless-loop-cpu100/</link>
      <pubDate>Fri, 17 Feb 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-2-17-endless-loop-cpu100/</guid>
      <description>没有踩过死循环的坑的程序员不是好程序员</description>
    </item>
    <item>
      <title>垃圾回收原来是这么回事</title>
      <link>https://showme.codes/zh-cn/2017-2-4-what-is-gc/</link>
      <pubDate>Sat, 04 Feb 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-2-4-what-is-gc/</guid>
      <description>ohn McCarthy身为Lisp之父和人工智能之父，同时，他也是GC之父。1960年，他在其论文中首次发布了GC算法（其实是委婉的提出😂）。而Java的前身Oak是在1990发布的，利用JVM实现了跨平台。GC因此一举成名。</description>
    </item>
    <item>
      <title>这样搭建Spark学习环境效率似乎更高</title>
      <link>https://showme.codes/zh-cn/2017-1-31-setup-spark-dev-env/</link>
      <pubDate>Tue, 31 Jan 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-1-31-setup-spark-dev-env/</guid>
      <description>让搭建学习环境不再成为门槛</description>
    </item>
    <item>
      <title>关于“以结果为导向”的管理方式的碎碎语</title>
      <link>https://showme.codes/zh-cn/2017-1-20-about-result-orientation/</link>
      <pubDate>Fri, 20 Jan 2017 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2017-1-20-about-result-orientation/</guid>
      <description>为什么要以结果为导向？</description>
    </item>
    <item>
      <title>一个热词推荐的简单实现</title>
      <link>https://showme.codes/zh-cn/2016-12-31-hot-word-recommend-demo/</link>
      <pubDate>Sat, 31 Dec 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-12-31-hot-word-recommend-demo/</guid>
      <description>&lt;h4 id=&#34;为什么想做这个东西&#34;&gt;为什么想做这个东西&lt;/h4&gt;
&lt;p&gt;一直好奇像亚马逊这类网站的搜索是如何做到推荐的，最近刚好看到一篇文章：&lt;a href=&#34;http://blog.jobbole.com/95780/&#34;&gt;Redis 与搜索热词推荐&lt;/a&gt;，然而只写了思路。所以，就是想自己实现一个。&lt;/p&gt;
&lt;p&gt;先上个效果图，再聊：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/index.lsp.gif&#34;&gt;&lt;/p&gt;
&lt;p&gt;P.S. 按四年前，要写这样的前端效果，对于我这个后台开发，还是挺困难的。而现在，简单的学了下Vue.js，再加上同事的小小指点，就搞定了。😂&lt;/p&gt;
&lt;h4 id=&#34;热词推荐的本质&#34;&gt;热词推荐的本质&lt;/h4&gt;
&lt;p&gt;假如你预先就知道了用户输入：s、sz、shen、深这些字时，就是想搜“深圳”，那是不是说，我们只要提前将这些字放到一个Map结构中，将用户的输入想像出一个key，value就是“深圳”。&lt;/p&gt;
&lt;p&gt;说到底，热词推荐的本质就是一个大大的Map。&lt;strong&gt;难点就在于如何更新这个Map&lt;/strong&gt;，以至于让用户觉得“智能”，或觉得我们在给他们做“推荐”。&lt;/p&gt;
&lt;p&gt;这个Map，常常被人称为“索引”。其实使用“索引” 这个名词也更准确一些。Map中的Key是不能重复的。但是我们数据结构是要求可重复的，为什么呢？因为，在系统中，s、sh、shen、深等等这些都是key，而它们对应的value，可能相同，又可能不同。举个例子：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hotword:0&amp;gt;zrevrange s 0 10
1) 鼠蛟
2) 鼠场乡
3) 鳝鱼
4) 鳝溪校区
5) 鳝溪农场
6) 鳝溪
7) 骚子营社区
8) 骚子营
9) 驷马镇
10) 驷马桥街道
11) 驷马桥
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hotword:0&amp;gt;zrevrange sh 0 10
1) 鼠蛟
2) 鼠场乡
3) 鳝鱼
4) 鳝溪校区
5) 鳝溪农场
6) 鳝溪
7) 首院胡同
8) 首阳镇
9) 首阳山镇
10) 首阳山
11) 首钢试验厂
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;仔细看到其中的不同了吗？同时，这里还有一个问题，那就是当用户输入s时，出现了10个value，我们如何给这些value如何排序呢？&lt;/p&gt;
&lt;p&gt;为了与排序模型解耦，我们为每个value都给出一个分数score。score越大，越排前面。最终索引结构就变成了这样子：&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/key-value-score.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;P.S. 这些score之所以都为0，是因为数据问题。&lt;/p&gt;
&lt;p&gt;总的来说，关于热词推荐，我们需要解决以下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如何存储索引的数据？&lt;/p&gt;</description>
    </item>
    <item>
      <title>似乎百分之七十的人都理解错了KPI</title>
      <link>https://showme.codes/zh-cn/2016-12-21-how-to-save-my-kpi/</link>
      <pubDate>Wed, 21 Dec 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-12-21-how-to-save-my-kpi/</guid>
      <description>KPI的自我救赎</description>
    </item>
    <item>
      <title>反馈机制在企业中的作用？</title>
      <link>https://showme.codes/zh-cn/2016-12-10-feedback-in-company/</link>
      <pubDate>Sat, 10 Dec 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-12-10-feedback-in-company/</guid>
      <description>&lt;p&gt;进入ThoughtWork后，每天都会有人向你提反馈。比如站会，你这样说会更好；你代码这样写也许会更好；你这段时间英语进步得非常快……这些反馈中，有对你优点反馈，也会对你的缺点进行反馈。&lt;/p&gt;
&lt;p&gt;这个过程，我才发现自己以前很多没有发现的不足，某些固化的思维方式，我的视野也一下开了好多。&lt;/p&gt;
&lt;p&gt;后来，离开了ThoughtWorks。我尝试将反馈机制带入到自己的团队。怎么尝试呢？&lt;/p&gt;
&lt;p&gt;首先，你需要让团队成员达成共识：只有成长，我们的生活才更美好，而反馈就是在帮助对方成长。当然人们对于“美好”的理解不同，有些人觉得有钱就是美好，有些人觉得得真正学到东西就是美好。这些都无所谓。因为成长了，这些美好都会有。&lt;/p&gt;
&lt;p&gt;接着，从自己做起。每天尝试给团队成员反馈。只要找到合适的时机&lt;strong&gt;及时&lt;/strong&gt;给予反馈。举例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个成员写代码时，不加思索的就上个大大的if-else，这种情况，最好马上指出，同时告诉他背后的原因。但是现实中，有时，我们需要考虑多一些，比如这个成员是不是超级要面子的，这个成员是不是团队受尊重的老员工。这时就要考虑沟通方式了。&lt;/li&gt;
&lt;li&gt;团队中两个成员因为某件事情吵了起来。而这件事，也不是特别大的事情，也没有吵多久。这时，你就要看他们是不是还气在头。合适的时机是等他们的气消了后，再分别给他们反馈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后，争取每隔一段时间就每一位成员进行一次一对一沟通。沟通的内容包括生活和工作，主要是了解他们对当前工作的看法，生活上有什么需要帮忙，学习上有没有遇到困难，工作上有什么不顺心……这个过程其实是成员向你反馈：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工作安排得合理不合理？&lt;/li&gt;
&lt;li&gt;与同事相处是否舒服？&lt;/li&gt;
&lt;li&gt;有没有学习的焦虑？&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;《创业维艰》这本书专门有一节谈“一对一的沟通”，有兴趣的朋友可以看看。&lt;/p&gt;
&lt;p&gt;最后，引导并指导团队成员相互之间进行反馈。&lt;/p&gt;
&lt;p&gt;话说这里了。那反馈机制在企业中到底起到了什么作用？我不知道专业的管理行家如何回答。但是，我自己的理解是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;马斯洛的需求理论，反馈能满足马斯洛的需求理论中的的安全需求（你可以和他一对一沟通）、归属需求（Leader是把他当一个团队成员来对待的）、尊重需求（尊重了他）。&lt;/li&gt;
&lt;li&gt;人天生就是通过反馈来成长，就像小孩通过你他的反馈来调整自己的行为。而如果团队成员得到了成长，就刚好可以满足了他的“自我实现”需求。&lt;/li&gt;
&lt;li&gt;需求得到了满足，在其中工作，当然就会快乐。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;有人就会说了，员工在工作中能否找到快乐，那是员工的事。这个观点即对，也不对。快乐的确是每一个人的。但是员工毕竟是&lt;strong&gt;人&lt;/strong&gt;，不是机器。&lt;/p&gt;
&lt;p&gt;不快乐的工作，同样可以完成机械的活，像珠三角大批的工厂工人。而创造性的工作，如软件开发，做产品，不是机械完成就好了的，也不是能通过机械地堆人就能完成的。&lt;/p&gt;
&lt;p&gt;同时，反馈带给员工的成长，会正反馈到工作中，员工工作效率高了（学习新的技术、知道更多的解决方案），我们软件开发速度及开发质量会不会提高呢？&lt;/p&gt;
&lt;h5 id=&#34;小结&#34;&gt;小结&lt;/h5&gt;
&lt;p&gt;我个人认为，以上只是“反馈机制”给企业带来表面利益，它给企业的是更深层次的东西：企业相互学习、相互帮助的文化。不是说企业有了“反馈机制”就有了文化，而是说企业文化就是由这些道不清说不明的东西，通过时间一点点积累起来的。&lt;/p&gt;
&lt;p&gt;以上是个人习得的，希望能和大家交流。&lt;/p&gt;</description>
    </item>
    <item>
      <title>模仿京东使用Openresty&#43;Redis做读服务</title>
      <link>https://showme.codes/zh-cn/2016-10-15-copy-jd-openresty-redis/</link>
      <pubDate>Sat, 15 Oct 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-10-15-copy-jd-openresty-redis/</guid>
      <description>直接在Nginx上开发web应用是怎么一回事？</description>
    </item>
    <item>
      <title>商业模式与产品设计有什么关系？以媒体公司为例</title>
      <link>https://showme.codes/zh-cn/2016-9-15-whats-the-relation-between-production-and-business/</link>
      <pubDate>Thu, 15 Sep 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-9-15-whats-the-relation-between-production-and-business/</guid>
      <description>都是钱的问题。</description>
    </item>
    <item>
      <title>如何在半小时搭建一个简单的日志分析平台？</title>
      <link>https://showme.codes/zh-cn/2016-9-10-how-to-setup-a-simple-log-analysis-platform-in-half-hour/</link>
      <pubDate>Sat, 10 Sep 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-9-10-how-to-setup-a-simple-log-analysis-platform-in-half-hour/</guid>
      <description>最好了解下Ansible和Vagrant</description>
    </item>
    <item>
      <title>关于自动化配置还有什么好说的呢？</title>
      <link>https://showme.codes/zh-cn/2016-8-12-automation-configuration/</link>
      <pubDate>Fri, 12 Aug 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-8-12-automation-configuration/</guid>
      <description>不想说重复的话</description>
    </item>
    <item>
      <title>老翟书摘：《50大管理难题解决方案》</title>
      <link>https://showme.codes/zh-cn/2016-7-20-solutions-for-managers-problem/</link>
      <pubDate>Wed, 20 Jul 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-7-20-solutions-for-managers-problem/</guid>
      <description>初做管理，想快速成长，而不想只依赖磨时间来带动“管理”的能力成长。所以，找到此书。</description>
    </item>
    <item>
      <title>性别字段存储时应该使用的字符串，还是数字？</title>
      <link>https://showme.codes/zh-cn/2016-4-15-how-to-save-gender-in-database/</link>
      <pubDate>Fri, 15 Apr 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-4-15-how-to-save-gender-in-database/</guid>
      <description>软件设计过程需要考虑的问题</description>
    </item>
    <item>
      <title>每日站会、代码审查、结对编程 之开源中国实践</title>
      <link>https://showme.codes/zh-cn/2016-4-1-standup-codereview-pair-in-oschina/</link>
      <pubDate>Fri, 01 Apr 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-4-1-standup-codereview-pair-in-oschina/</guid>
      <description>在我来到开源中国之后，尝试将每日站会、代码审查、结对编程这三种编程实践带入团队。而这个过程，我个人觉得是一项非常宝贵的体验。我觉得可以拿出来和大家分享。</description>
    </item>
    <item>
      <title>老翟书摘：从《大野耐一的现场管理》看软件工程管理</title>
      <link>https://showme.codes/zh-cn/2016-2-15-software-dev-in-site-management-view/</link>
      <pubDate>Mon, 15 Feb 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-2-15-software-dev-in-site-management-view/</guid>
      <description>听过丰田生产方式，没听过大野耐一？</description>
    </item>
    <item>
      <title>Puppet，Chef，Ansible的共性</title>
      <link>https://showme.codes/zh-cn/2016-1-2-the-nature-of-ansible-puppet-chef/</link>
      <pubDate>Sat, 02 Jan 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-1-2-the-nature-of-ansible-puppet-chef/</guid>
      <description>曾经有人评我这篇博客：如果能说说它们的不同就好。我现在回答你：只有知道共性才能看到不同，否则它们所有的地方都是不同的。 同时，很多人只看到这篇博客的表象：技术方面的文章。鲜少人知道这篇博客的价值：教你一种发现事物共性的方式。</description>
    </item>
    <item>
      <title>Spark本地开发环境搭建</title>
      <link>https://showme.codes/zh-cn/2016-1-1-setup-spark-env-for-dev/</link>
      <pubDate>Fri, 01 Jan 2016 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2016-1-1-setup-spark-env-for-dev/</guid>
      <description>&lt;p&gt;本文使用Scala2.10.6，sbt。请自行提前装好。&lt;/p&gt;
&lt;h4 id=&#34;设置ssh本地免密码登录&#34;&gt;设置SSH，本地免密码登录&lt;/h4&gt;
&lt;p&gt;因为Spark master需要ssh到Spark worker中执行命令，所以，需要免密码登录。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;cat ~/.ssh/id_rsa.pub &amp;gt; ~/.ssh/authorized_keys&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;执行&lt;code&gt;ssh localhost&lt;/code&gt;确认一下，如果不需要密码登录就说明OK了。&lt;/p&gt;
&lt;p&gt;Tips：
Mac下可能ssh不到本地，请检查你sharing设置：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;Spark本地开发环境搭建&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/2016-1-spark-network.png&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;下载spark&#34;&gt;下载Spark&lt;/h4&gt;
&lt;p&gt;&lt;a href=&#34;http://spark.apache.org/downloads.html&#34;&gt;http://spark.apache.org/downloads.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我选择的是spark-1.6.0-bin-cdh4.tgz 。看到cdh4(Hadoop的一个分发版本)，别以为它是要你装Hadoop。其实不然，要看你自己的开发需求。因为我不需要，所以，我只装Spark。&lt;/p&gt;
&lt;h4 id=&#34;配置你的spark-slave&#34;&gt;配置你的Spark slave&lt;/h4&gt;
&lt;p&gt;我很好奇，worker和slave这个名称有什么不同？还是因为历史原因，导致本质上一个东西但是两种叫法？&lt;/p&gt;
&lt;p&gt;在你的Spark HOME路径下&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;cp ./conf/slaves.template ./conf/slaves&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;slaves&lt;/code&gt;文件中有一行&lt;code&gt;localhost&lt;/code&gt;代表在本地启动一个Spark worker。&lt;/p&gt;
&lt;h4 id=&#34;启动spark伪分布式&#34;&gt;启动Spark伪分布式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;SPARK_HOME&amp;gt;/sbin/start-all.sh&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;执行JPS验证Spark启动成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜ jps
83141 Worker
83178 Jps
83020 Master
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;打开你的spark界面&#34;&gt;打开你的Spark界面&lt;/h4&gt;
&lt;p&gt;http://localhost:8080&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;Spark本地开发环境搭建&#34; loading=&#34;lazy&#34; src=&#34;https://showme.codes/assets/images/2016-1-spark-webview.png&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;下载spark项目骨架&#34;&gt;下载Spark项目骨架&lt;/h4&gt;
&lt;p&gt;为方便我自己开发，我自己创建了一个Spark应用开发的项目骨架。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;下载项目骨架： &lt;a href=&#34;http://git.oschina.net/zacker330/spark-skeleton&#34;&gt;http://git.oschina.net/zacker330/spark-skeleton&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;项目路径中执行：&lt;code&gt;sbt package&lt;/code&gt; 编译打包你的spark应用程序。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;将你的spark应用程序提交给spark-master执行&#34;&gt;将你的spark应用程序提交给spark master执行&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;SPARK_HOME&amp;gt;/bin/spark-submit \ 
          --class &amp;quot;SimpleApp&amp;quot; \
          --master spark://Jacks-MBP.workgroup:7077 \
              target/scala-2.10/spark-skeleton_2.10-1.0.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个“spark://Jacks-MBP.workgroup:7077”是你在 http://localhost:8080 中看到的&lt;code&gt;URL&lt;/code&gt;的值&lt;/p&gt;
&lt;p&gt;可以看到打印出: hello world&lt;/p&gt;</description>
    </item>
    <item>
      <title>老翟书摘：《MBA教不了的创富课》</title>
      <link>https://showme.codes/zh-cn/2015-12-30-mba-wealth/</link>
      <pubDate>Wed, 30 Dec 2015 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2015-12-30-mba-wealth/</guid>
      <description>我一直希望能了解那些富人一步步富起来的过程，更重要的是了解他们是如何思考的。但是很多传记写的就像李开复的《世界因你而不同》那样，除了鸡汤还是鸡汤。我不是说鸡汤没有价值，而是我更想看到真实世界到底是怎么样的。</description>
    </item>
    <item>
      <title>老翟书摘：《丰田生产方式》</title>
      <link>https://showme.codes/zh-cn/2015-12-29-tps/</link>
      <pubDate>Tue, 29 Dec 2015 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2015-12-29-tps/</guid>
      <description>这本书的作者是大野耐一，原丰田汽车工业公司副社长。里面写了大多是一些生产过程中的原则和看法。但是，这些原则和看问题的角度是通用的，即使我们是软件行业。同时，在看完这本书之后，你会对市面上的那些”精益”理论，会有不一样的看法，也不至于盲从。</description>
    </item>
    <item>
      <title>耦合的本质</title>
      <link>https://showme.codes/zh-cn/2015-10-10-the-nature-of-coupling/</link>
      <pubDate>Tue, 29 Dec 2015 00:00:00 +0000</pubDate>
      <guid>https://showme.codes/zh-cn/2015-10-10-the-nature-of-coupling/</guid>
      <description>你真理解什么是耦合了？</description>
    </item>
  </channel>
</rss>
