<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>docker | 伪架构师</title>
    <link>/tags/docker/</link>
      <atom:link href="/tags/docker/index.xml" rel="self" type="application/rss+xml" />
    <description>docker</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Wed, 06 Jan 2021 14:50:56 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>docker</title>
      <link>/tags/docker/</link>
    </image>
    
    <item>
      <title>送容器下乡</title>
      <link>/post/down2countryside/</link>
      <pubDate>Wed, 06 Jan 2021 14:50:56 +0800</pubDate>
      <guid>/post/down2countryside/</guid>
      <description>

&lt;h2 id=&#34;kubernetes-上天了&#34;&gt;Kubernetes 上天了&lt;/h2&gt;

&lt;p&gt;2020 年里，Kubernetes 的疆界有了一个有趣的扩展——美国人把 Kubernetes 和 Istio 装到了 F16 战斗机上。战斗机应该算是&lt;code&gt;真&lt;/code&gt;边缘了吧？读了几篇相关材料，发现整个过程远不止一个极限部署这么简单，DoD 在军方的大背景下，实现了一整套基于 DevSecOps 理念的云原生生态，那么一个问题就是，为什么单独要说 Kubernetes 和 Istio 呢？只是因为热门吗？&lt;/p&gt;

&lt;p&gt;我的看法是，容器化和容器编排，是云原生的“阵眼”。云原生是个覆盖方方面面的体系，除了我们熟知的容器链条等技术要素之外，还以方法论的方式渗透到整个 IT 环境的市场、商务、架构、开发、运维、安全等各个方面。而其中的容器技术，其底蕴来自于几十年来整个业界不断的虚拟化和隔离技术的积累，是云原生的众多概念中，最能被“看得见，摸得着”的形象。同时作为制品和运行时的一等公民，容器和（Kubernetes 的）声明式 API 结合起来，已经能够满足绝大多数业务应用的运行需要。一个常见的 Kubernetes 环境，有足够条件能够符合 12 要素中至少一半的要求。这个组合是最常见也是最应该的云原生入门选择。很大程度上，Kubernetes 能走到哪里，云原生才能走到哪里。&lt;/p&gt;

&lt;h2 id=&#34;部署是个大问题&#34;&gt;部署是个大问题&lt;/h2&gt;

&lt;p&gt;回到前面的新闻，把 Kubernetes 装到哪里，当然不代表成功，但是它代表了一个重要的方向，YAML 架构师们都知道——只要这东西起来了，给我一个 Helm，就能搞他个天翻地覆。所以从诞生之初直到现在，Kubernetes 的部署都是个大问题。&lt;/p&gt;

&lt;p&gt;然而一谈到 F16 之流的边缘部署，不可避免的会想到奇奇怪怪的设备们，长期以来都有一个固定的句式——我们给 XX 减肥，把它塞到资源有限的 YY 设备里。不过这对 Kubernetes 可能不太合适。&lt;/p&gt;

&lt;p&gt;我一直对“魔改”这个事情有点抵触——感觉像是在车子上跳下来，虽然会有一个更高的速度，但是很难保障你真的就是火箭鱼雷航天飞机，下车才是刚起步，更多的情况是，跳车之后快了一瞬间，才发现跟不上了。&lt;/p&gt;

&lt;p&gt;资源不足的设备，和上不了容器的用是一样的，如果存在真正的需求，它们自然会适应实际需要，无法适应只能说是需求不强。强扭的瓜不甜，只想要瓜不管甜不甜的可以忽略。&lt;/p&gt;

&lt;p&gt;所以在我一个 YAML 架构师的眼里，Kubernetes 下乡，应该是基于原装的 Kubernetes，在一定程度内，满足大部分容器化业务的支撑需要，其它的东西，应该是设备归设备、虚机归虚机。Kubernetes 目前的下乡重点，应该在边缘机房，而非末梢节点。&lt;/p&gt;

&lt;h2 id=&#34;什么样的-kubernetes-能下乡&#34;&gt;什么样的 Kubernetes 能下乡&lt;/h2&gt;

&lt;p&gt;那么要让 Kubernetes 下乡，除了要求“原装货”，乡下有点什么不一样呢？&lt;/p&gt;

&lt;h3 id=&#34;非标准环境&#34;&gt;非标准环境&lt;/h3&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;h3 id=&#34;反锁定&#34;&gt;反锁定&lt;/h3&gt;

&lt;p&gt;我们历尽千辛万苦将 Kubernetes 送到乡下之后，可能会有很多嗷嗷待哺的容器化应用要运行，以及各方厂商的种种设备尝试接入进行就近处理，因此对通用性的需要是显而易见的，简单说就是远端的计算节点应该有足够的软硬件兼容性，能够以一定的标准运行在通用硬件、虚拟化和操作系统上，支撑多种厂商的、或通用或边缘的软件系统的运行。&lt;/p&gt;

&lt;h3 id=&#34;低运维&#34;&gt;低运维&lt;/h3&gt;

&lt;p&gt;通常来说，运维人员还是围绕数据中心工作的，被“下放”的 Kubernetes 必须能够在一个少运维甚至零运维的情况下运行，原本在数据中心如臂使指的虚拟化、Ansible 之流可能都会因为前方条件的不足而受到种种限制，此时就要求我们的远端节点有强大的自愈、自治和被远程运维的能力。&lt;/p&gt;

&lt;h2 id=&#34;没结论&#34;&gt;没结论&lt;/h2&gt;

&lt;p&gt;这几天偶尔看了一些边缘集群的一些东西，看到减肥蔚然成风，想起多年以前我对 Java 太吃内存的嘲讽，有感而发。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>（闲聊）听说 K8s 要甩了 Docker？</title>
      <link>/post/gossip-k8s-docker/</link>
      <pubDate>Wed, 02 Dec 2020 21:01:13 +0800</pubDate>
      <guid>/post/gossip-k8s-docker/</guid>
      <description>&lt;p&gt;今天偶然看到 Kubernetes 1.20 的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md&#34; target=&#34;_blank&#34;&gt;ChangeLog&lt;/a&gt;，其中有一行大动作：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Deprecation
Docker support in the kubelet is now deprecated and will be removed in a future release. The kubelet uses a module called &amp;quot;dockershim&amp;quot; which implements CRI support for Docker and it has seen maintenance issues in the Kubernetes community. We encourage you to evaluate moving to a container runtime that is a full-fledged implementation of CRI (v1alpha1 or v1 compliant) as they become available. (#94624, @dims) [SIG Node]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;大意是，Kubelet 中的 Docker 支持已经进入淘汰阶段，将在未来移除。原因是 Kubelet 中使用 &lt;code&gt;dockershim&lt;/code&gt; 组件为 Docker 提供了 CRI 支持，Kubernetes 认为维护这个组件是有问题的。建议用户评估并迁移到 CRI 支持更完善的运行时上。&lt;/p&gt;

&lt;p&gt;其中引用了 9 月提出的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/94624&#34; target=&#34;_blank&#34;&gt;PR #94624&lt;/a&gt;。其中提出，为了使用 Docker，从 moby 进行了大量移植开发了 dockershim 嵌入到 Kubelet 之中。Kubelet 和 CRI 的正确沟通方式是像 containerd、cri-o 这样。各自使用独自的进程，互相以 gRPC 进行对接。Docker 目前仍然是主流，进行迁移需要广而告之并逐步推进。&lt;/p&gt;

&lt;p&gt;实际上早在 2018 年 5 月，Kubernetes 的 Containerd 集成就已经宣告了 GA。其中有两张图很能说明问题：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/containerd.png&#34; alt=&#34;images/containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 1.0 中，Kubelet 使用 Docker Shim 和 Docker 进行通信，Docker 再和下面的 containerd 进行通信。&lt;/p&gt;

&lt;p&gt;此时如果采用 containerd 作为运行时，Kubelet 要使用 CRI Containerd 和 Containerd 打交道，不过相对于 Docker，还是少了一跳。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cri-containerd.png&#34; alt=&#34;images/cri-containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 1.1 中这个结构得到了优化——Containerd 直接内置 CRI 接口，Kubelet 甩掉包袱可以直接用 CRI 方式对 Containerd 进行控制，这样就又省了一跳。&lt;/p&gt;

&lt;p&gt;此时 Docker 在这个调用链上的位置已经有点尴尬。随着其它 CRI 运行时的发展，这种尴尬越发明显。&lt;code&gt;#94624&lt;/code&gt; 中提到过，Docker 有个优势就是提供了 Build 等“Kubelet 不需要但是很有用”的功能；然而换个角度来看，这些功能是有悖于单一职责的原则的。&lt;/p&gt;

&lt;p&gt;个人认为，Docker 这样的全能选手，在计算节点上的长期存在证明了这个阶段里，计算节点还没有进入理想的 &lt;code&gt;cattle&lt;/code&gt; 状态，用户一方面还没有心思对“多余”的功能进行剪裁，另一方面还有可能人工进入节点上进行运行时范围以外的操作。在 GA 一年多之后，砍刀开始落下，说明了什么呢？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;容器和 Docker 这两个经常被混用的词，其间的边界可能会变得越来越清晰，构建、运行、管理越来越倾向于使用各自领域的专业工具各司其职；&lt;/li&gt;
&lt;li&gt;计算节点会变得更加“没性格”，换句话说，仅为了“运行容器”为目的的基础设施软件，例如操作系统、CRI 这样的工具会逐步代替大而全的通用 Linux Server 操作系统和 Docker 出现在容器节点上；&lt;/li&gt;
&lt;li&gt;“没性格”的计算节点将会更加容易地被创建、运行、调整和销毁，也就是说会提高容器集群规模的伸缩能力，甚至逐渐形成普遍的动态扩缩容能力。&lt;/li&gt;
&lt;li&gt;集群级别的批量化、自动化运维能力的要求会越来越高——或者以后的节点上没有 ssh、vim 也未可知。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;带点个人感情的说，前两天刚刚遭遇 DockerHub 限流的我还是生出了一点卑鄙的快意，Google 的铁拳再一次敲在了 Docker 的头上，Docker EE 怎么办？但是 Docker Desktop for Mac 还是真香的。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>镜像是怎样炼成的</title>
      <link>/post/how-are-docker-images-built/</link>
      <pubDate>Fri, 24 Apr 2020 08:45:50 +0800</pubDate>
      <guid>/post/how-are-docker-images-built/</guid>
      <description>

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/napicellatwit&#34; target=&#34;_blank&#34;&gt;Nicola Apicella&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文：&lt;a href=&#34;https://dev.to/napicella/how-are-docker-images-built-a-look-into-the-linux-overlay-file-systems-and-the-oci-specification-175n&#34; target=&#34;_blank&#34;&gt;How are docker images built? A look into the Linux overlay file-systems and the OCI specification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;要使用 Docker，就不可避免地要和 Docker 镜像打交道。本文将会讲述 Docker 镜像的基石： Overlay 文件系统。首先我会简单介绍一下这个文件系统，接下来会看看如何把这个技术用在 Docker 镜像上，以及 Docker 是怎样从 Dockerfile 构建出 Docker 镜像的。最后还会介绍分层缓存以及 OCI 格式的容器镜像。&lt;/p&gt;

&lt;p&gt;遵循我的一贯风格，我会尽可能的让本文具备更好的操作性。&lt;/p&gt;

&lt;h2 id=&#34;overlay-文件系统是什么&#34;&gt;Overlay 文件系统是什么&lt;/h2&gt;

&lt;p&gt;Overlay 文件系统（也被称为联合文件系统），能够使用两个或更多的目录创建一个联合：它由低层和高层的目录组成。文件系统中低层的目录是只读的，而高层的文件系统则是可读可写的。我们可以试试加载一个，看看操作效果。&lt;/p&gt;

&lt;h3 id=&#34;创建-overlay-文件系统&#34;&gt;创建 Overlay 文件系统&lt;/h3&gt;

&lt;p&gt;我们可以创建几个目录然后把它们联合起来。首先会创建一个叫做 “mount” 的目录，我们将它作为这个联合的父目录。接下来会创建 “layer-1”、“layer-2”、“layer-3”、“layer-4” 着几个目录。最后还要创建一个叫做 “workdir” 的目录， Overlay 文件系统必须有这个目录才能正常工作。&lt;/p&gt;

&lt;p&gt;这些目录可以随意命名，不过 “layer-1”、“layer-2” 这样的命名方式，和 Docker 镜像对比起来会比较容易理解。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-comand&#34;&gt;
$ cd /tmp &amp;amp;&amp;amp; mkdir overlay-example &amp;amp;&amp;amp; cd overlay-example

[2020-04-19 16:02:35] [ubuntu] [/tmp/overlay-example]  
&amp;gt; mkdir mount layer-1 layer-2 layer-3 layer-4 workdir

[2020-04-19 16:02:38] [ubuntu] [/tmp/overlay-example]  
$ ls
layer-1  layer-2  layer-3  layer-4 mount  workdir
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后要在除 &amp;ldquo;layer-4&amp;rdquo; 之外的每个目录下创建文件，这个步骤也不是必要的，只是为了更像镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;[2020-04-19 16:02:40] [ubuntu] [/tmp/overlay-example]  
$ echo &amp;quot;Layer-1 file&amp;quot; &amp;gt; ./layer-1/some-file-in-layer-1

[2020-04-19 16:03:36] [ubuntu] [/tmp/overlay-example]  
$ echo &amp;quot;Layer-2 file&amp;quot; &amp;gt; ./layer-2/some-file-in-layer-2

[2020-04-19 16:03:53] [ubuntu] [/tmp/overlay-example]  
$ echo &amp;quot;Layer-3 file&amp;quot; &amp;gt; ./layer-3/some-file-in-layer-3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们来挂载这个文件系统：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo mount -t overlay overlay-example \
-o lowerdir=/tmp/overlay-example/layer-1:/tmp/overlay-example/layer-2:/tmp/overlay-example/layer-3,upperdir=/tmp/overlay-example/layer-4,workdir=/tmp/overlay-example/workdir \
/tmp/overlay-example/mount
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看看挂载目录的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;[2020-04-19 16:13:28] [ubuntu] [/tmp/overlay-example]  
&amp;gt; cd mount/

[2020-04-19 16:13:31] [ubuntu] [/tmp/overlay-example/mount]  
&amp;gt; ls -la
total 20
drwxr-xr-x 1 napicell domain^users 4096 Apr 19 16:07 .
drwxr-xr-x 8 napicell domain^users 4096 Apr 19 16:07 ..
-rw-r--r-- 1 napicell domain^users   13 Apr 19 16:03 some-file-in-layer-1
-rw-r--r-- 1 napicell domain^users   13 Apr 19 16:03 some-file-in-layer-2
-rw-r--r-- 1 napicell domain^users   13 Apr 19 16:03 some-file-in-layer-3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不出所料，前三层的文件都被加载到了挂载根目录。可以看到我们之前写入文件的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ cat some-file-in-layer-3
Layer-3 file
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;试试创建文件&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ echo &amp;quot;new content&amp;quot; &amp;gt; new-file

$ ls
new-file  some-file-in-layer-1  some-file-in-layer-2  some-file-in-layer-3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;新文件在哪里呢？自然是在上层，我们的例子里就是 &amp;ldquo;layer-4&amp;rdquo;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt; [2020-04-19 16:23:49] [ubuntu] [/tmp/overlay-example]  
 pactvm &amp;gt; tree
.
├── layer-1
│   └── some-file-in-layer-1
├── layer-2
│   └── some-file-in-layer-2
├── layer-3
│   └── some-file-in-layer-3
├── layer-4
│   └── new-file
├── mount
│   ├── new-file
│   ├── some-file-in-layer-1
│   ├── some-file-in-layer-2
│   └── some-file-in-layer-3
└── workdir
    └── work [error opening dir]

7 directories, 8 files
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;试试看删除文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;[2020-04-19 16:27:33] [ubuntu] [/tmp/overlay-example/mount]  
&amp;gt; rm some-file-in-layer-2

[2020-04-19 16:28:58] [ubuntu] [/tmp/overlay-example/mount]  
&amp;gt; ls
new-file  some-file-in-layer-1  some-file-in-layer-3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;你猜猜，原始文件系统中的 &amp;ldquo;layer-2&amp;rdquo; 目录会怎么样：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt; [2020-04-19 16:29:57] [ubuntu] [/tmp/overlay-example]  
 pactvm &amp;gt; tree
.
├── layer-1
│   └── some-file-in-layer-1
├── layer-2
│   └── some-file-in-layer-2
├── layer-3
│   └── some-file-in-layer-3
├── layer-4
│   ├── new-file
│   └── some-file-in-layer-2
├── mount
│   ├── new-file
│   ├── some-file-in-layer-1
│   └── some-file-in-layer-3
└── workdir
    └── work [error opening dir]

7 directories, 8 files
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;ldquo;layer-4&amp;rdquo; 中出现了个新文件 &amp;ldquo;some-file-in-layer-2&amp;rdquo;。奇怪的是这个文件的属性（”Character file“），这种文件在 Overlay 文件系统中被称为 ”Whitout“，用于表达被删除的文件。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt; [2020-04-19 16:31:09] [ubuntu] [/tmp/overlay-example/layer-4]  
 pactvm &amp;gt; ls -la
total 12
drwxr-xr-x 2 napicell domain^users 4096 Apr 19 16:28 .
drwxr-xr-x 8 napicell domain^users 4096 Apr 19 16:07 ..
-rw-r--r-- 1 napicell domain^users   12 Apr 19 16:23 new-file
c--------- 1 root     root         0, 0 Apr 19 16:28 some-file-in-layer-2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成之后，卸载这个文件系统，然后删除目录：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;[2020-04-19 16:37:11] [ubuntu] [/tmp/overlay-example]  
$ sudo umount /tmp/overlay-example/mount &amp;amp;&amp;amp; rm -rf *
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;理顺概念&#34;&gt;理顺概念&lt;/h3&gt;

&lt;p&gt;正如开篇所说， Overlay 文件系统上可以把多个目录联合在一起。在前边的例子里，这个联合过程由 “layer-{1,2,3,4}” 在  “mount” 目录里组成。对文件的修改、创建和删除都在上层发生——也就是这里的 “layer-4”，因此这一层也被称为差异层。上层的文件会对下层文件造成遮盖。假设 “layer-2” 和 “layer-1” 中，在相同的相对目录下有同名的文件，那么在 “mount” 目录中就会以 “layer-2” 为准。下一节将会看看这一技术在 Docker 镜像中的应用。&lt;/p&gt;

&lt;h2 id=&#34;什么是-docker-镜像&#34;&gt;什么是 Docker 镜像&lt;/h2&gt;

&lt;p&gt;简单总结，Docker 镜像就是一个 Tar 文件，其中包含一个根文件系统和一些愿数据。你可能听说过，Dockerfile 中的每一行都会生成一个层。例如下面的代码就会生成一个三层的镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Dockerfile&#34;&gt;FROM scratch
ADD my-files /doc
ADD hello /
CMD [&amp;quot;/hello&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;“docker run” 的过程很复杂，但是本文中只会关注和镜像有关的一点点内容。概括的说，Docker 会下载这个文件包，把每个层解压到单独的目录中，然后用 Overlay 文件系统将这些目录以及用于进行写入的一个上层空目录联合起来。当你在容器中进行修改、创建或者删除操作时，这些变更都会保存到这个空目录中。容器退出时，Docker 会清理这个目录——这就是在容器中的变更无法保持的原因。&lt;/p&gt;

&lt;h3 id=&#34;层缓存&#34;&gt;层缓存&lt;/h3&gt;

&lt;p&gt;要运行容器，就要构建镜像，Docker 将这两个步骤分离开来独立运作，是它得以流行的重要原因。OCI 就是业界公认的规范。&lt;/p&gt;

&lt;p&gt;OCI 当前包括两个规范：运行规范和镜像规范。运行规范描述了如何运行一个解压到磁盘上的 “复合文件系统” 。简单说来，OCI 实现会把 OCI 镜像下载回来，然后解压到一个 OCI 运行时复合文件系统之中。这一操作完成后就可以让 OCI 运行时运行了。&lt;/p&gt;

&lt;p&gt;标准化的意义就是让其他人可以自己开发容器的构建工具和运行时。例如 &lt;code&gt;jess/img&lt;/code&gt;、&lt;code&gt;Buildah&lt;/code&gt; 以及 &lt;code&gt;Skopeo&lt;/code&gt; 都是可以脱离 Docker 构建镜像的工具。类似地还有很多容器运行时，例如 runc（Docker 使用） 和 rkt。&lt;/p&gt;

&lt;h2 id=&#34;其他的-overlay-文件系统&#34;&gt;其他的 Overlay 文件系统&lt;/h2&gt;

&lt;p&gt;Docker 能够使用的联合文件系统不止这一种。任何有差异层和联合特性的文件系统都是可能的候选者。例如 Docker 还能运行在 aufs、btrfs、zfs 和 devicemapper 系统上。&lt;/p&gt;

&lt;h2 id=&#34;构建镜像时发生了什么&#34;&gt;构建镜像时发生了什么&lt;/h2&gt;

&lt;p&gt;假设我们要使用下面的 Dockerfile 来构建镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Dockerfile&#34;&gt;FROM ubuntu
RUN apt-get update
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单描述一下这个过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Docker 下载 FROM 语句中指定的 tar 文件，这是目标镜像的第一层。&lt;/li&gt;
&lt;li&gt;加载一个联合文件系统，其底层就是刚下载的部分，在上面创建一个空目录。&lt;/li&gt;
&lt;li&gt;在 chroot 中启动一个 bash，运行 RUN 语句中的命令：&lt;code&gt;RUN: chroot . /bin/bash -c &amp;quot;apt get update&amp;quot;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;命令结束后，会把上层目录压缩，形成新镜像中的新的一层。&lt;/li&gt;
&lt;li&gt;如果 Dockerfile 中包含其它命令，就以之前构建的层次为基础，从第二步开始重复创建新层，直到完成所有语句后退出。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上述过程是个极度简化的过程，其中缺乏一些常见指令，例如 &lt;code&gt;ENTRYPOINT&lt;/code&gt;、&lt;code&gt;ENV&lt;/code&gt; 等。这些内容会被写入元数据，和文件层封装在一起。&lt;/p&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;这种将根文件系统和每个差异层都进行打包的思路非常强大。它不仅是 Docker 的基础，我想还能用在其它一些领域里，以后可能会诞生更多这类工具。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>一个小工具：Hadolint</title>
      <link>/post/intro-hadolint/</link>
      <pubDate>Sun, 12 Jan 2020 22:12:50 +0800</pubDate>
      <guid>/post/intro-hadolint/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/hadolint/hadolint&#34; target=&#34;_blank&#34;&gt;Hadolint&lt;/a&gt; 是使用不明觉厉的 Haskell 实现的 Dockerfile linter，其实现依据来自于 Docker 官网推荐的 &lt;a href=&#34;https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices&#34; target=&#34;_blank&#34;&gt;Dockerfile 最佳实践&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在 Mac 下的安装只要使用简单的 &lt;code&gt;brew install hadolint&lt;/code&gt; 就能够完成安装，其它平台也有各自的支持方式。&lt;/p&gt;

&lt;p&gt;用法非常简单：&lt;code&gt;hadolint &amp;lt;dockerfile&amp;gt;&lt;/code&gt; 即可，例如我们编写一个简单的 Dockerfile：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat &amp;lt;&amp;lt; EOF &amp;gt; /tmp/Dockerfile                                                                             master  ✱
heredoc&amp;gt; FROM alpine
heredoc&amp;gt; CMD [&amp;quot;sleep&amp;quot;, &amp;quot;3600&amp;quot;]
heredoc&amp;gt; EOF

$ hadolint /tmp/Dockerfile
/tmp/Dockerfile:1 DL3006 Always tag the version of an image explicitly
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以使用 &lt;code&gt;--ignore&lt;/code&gt; 参数忽略指定的问题，如果是固定配置，可以使用 &lt;code&gt;-c&lt;/code&gt; 参数指定配置文件。例如下面的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;ignored:
  - DL3000
  - SC1010

trustedRegistries:
  - docker.io
  - my-company.com:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;其检测范围包括在 &lt;a href=&#34;https://github.com/hadolint/hadolint/blob/master/README.md&#34; target=&#34;_blank&#34;&gt;README&lt;/a&gt; 有非常详细的描述，并在连接中给出了建议。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;hadolint 提供了很多&lt;a href=&#34;https://github.com/hadolint/hadolint/blob/master/docs/INTEGRATION.md&#34; target=&#34;_blank&#34;&gt;集成选项&lt;/a&gt;，可以集成到 Jenkins、Gitlab 等自动化流程中使用，在 vim、VSCode、Atom 编辑器中也可以直接生效。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;README：&lt;code&gt;https://github.com/hadolint/hadolint/blob/master/README.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;集成选项：&lt;code&gt;https://github.com/hadolint/hadolint/blob/master/docs/INTEGRATION.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>使用 Harbor 提供可信镜像</title>
      <link>/post/content-trust-with-harbor/</link>
      <pubDate>Fri, 13 Dec 2019 10:39:08 +0800</pubDate>
      <guid>/post/content-trust-with-harbor/</guid>
      <description>

&lt;p&gt;应用上云的过程中，过了部署关和应用改造关之后，安全就是下一个大问题了。对于容器化应用来说，镜像的安全是个非常根本的问题，例如 Harbor 中集成了 Clair 组件，用于对镜像进行漏洞扫描；&lt;a href=&#34;https://blog.yamler.io/post/introducing-trivy/&#34; target=&#34;_blank&#34;&gt;之前介绍的 Trivy&lt;/a&gt; 也能够提供对镜像各层进行扫描的能力，类似的工具还有很多。在完成镜像本身的安全保障之后，一方面要把安全构建出来的镜像能够”原汁原味“的提供给运行时进行使用，同时还要对运行时环境进行约束，只允许获取和运行可靠镜像，如此才能够保证镜像供应链的完整。&lt;/p&gt;

&lt;h2 id=&#34;快速上手&#34;&gt;快速上手&lt;/h2&gt;

&lt;p&gt;Harbor 中提供了 &lt;a href=&#34;https://github.com/theupdateframework/notary&#34; target=&#34;_blank&#34;&gt;Notary&lt;/a&gt; 服务来提供了这方面的保障，Docker 17.12 之后也提供了对应的运行时支持。&lt;/p&gt;

&lt;p&gt;这里用 1.10.0 版本的 Harbor 为例，在安装命令中加入参数 &lt;code&gt;--with-notary&lt;/code&gt; 就可以启用这个服务了。启动 Harbor 之后，使用 Docker 客户端的终端设置环境变量：&lt;code&gt;export DOCKER_CONTENT_TRUST=1&lt;/code&gt;。启用 Docker 的内容信任模式。&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;docker login&lt;/code&gt; 命令登录仓库，然后进行镜像推送，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker push 10.211.55.27/sign/clare:s1
The push refers to repository [10.211.55.27/sign/clare]
bbef02a499c4: Layer already exists
...
47a4bb1cfbc7: Layer already exists
s1: digest: sha256:bafc293fd765dbbad5ed3d57d771f0566e5d63a668213f1f61c469cbb199fca6 size: 1162
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
...
Enter passphrase for new root key with ID b52c1ba:
Repeat passphrase for new root key with ID b52c1ba:
Enter passphrase for new repository key with ID c37e6d2:
Repeat passphrase for new repository key with ID c37e6d2:
Error: trust data missing for remote repository 10.211.55.27/sign/clare or remote repository not found: timestamp key trust data unavailable.  Has a notary repository been initialized?
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里多出了一个初始化过程，在我们照章输入密码之后，发现出了错，这是因为我们没有设置 Notary 服务地址，加入环境变量来解决：&lt;code&gt;export DOCKER_CONTENT_TRUST_SERVER=https://10.211.55.27:4443&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;再次推送：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker push 10.211.55.27/sign/clare:s1
The push refers to repository [10.211.55.27/sign/clare]
...
Repeat passphrase for new repository key with ID d6068a9:
Finished initializing &amp;quot;10.211.55.27/sign/clare&amp;quot;
Successfully signed 10.211.55.27/sign/clare:s1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，推送已经成功了，并且还有签署成功的反馈。查看一下他的签名信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker trust inspect 10.211.55.27/sign/clare:s1
[
    {
        &amp;quot;Name&amp;quot;: &amp;quot;10.211.55.27/sign/clare:s1&amp;quot;,
        &amp;quot;SignedTags&amp;quot;: [
            {
                &amp;quot;SignedTag&amp;quot;: &amp;quot;s1&amp;quot;,
                &amp;quot;Digest&amp;quot;: &amp;quot;bafc293fd765dbbad5ed3d57d771f0566e5d63a668213f1f61c469cbb199fca6&amp;quot;,
                &amp;quot;Signers&amp;quot;: [
                    &amp;quot;Repo Admin&amp;quot;
                ]
            }

...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果我们取消 Docker 内容信任：&lt;code&gt;unset DOCKER_CONTENT_TRUST&lt;/code&gt;。接下来推送一个新镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker push 10.211.55.27/sign/alpine:latest
The push refers to repository [10.211.55.27/sign/alpine]
77cae8ab23bf: Pushed
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次开启 Docker 内容信任开关：&lt;code&gt;export DOCKER_CONTENT_TRUST=1&lt;/code&gt;，尝试拉取：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker pull 10.211.55.27/sign/alpine:latest
Error: remote trust data does not exist for 10.211.55.27/sign/alpine: 10.211.55.27:4443 does not have trust data for 10.211.55.27/sign/alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 Docker 拒绝了未经签署的镜像。&lt;/p&gt;

&lt;h2 id=&#34;幕后&#34;&gt;幕后&lt;/h2&gt;

&lt;p&gt;Docker 包含了简写为 DCT 的内容签名（Docker Content Trust）支持，能够借助 Notary 进行内容签署和校验。首次签署时会要求生成根密钥，每次创建一个新的 Repository 时候，会为其单独生成签署密钥。接下来，每个 Tag 的推送都会进行签署，从而保证内容的稳定性。&lt;/p&gt;

&lt;h2 id=&#34;问题&#34;&gt;问题&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kubernetes 能享受到这个么？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;托管 Kubernetes 怎么办？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多镜像仓库怎么办？&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>用 Python 脚本拉取 Docker 镜像</title>
      <link>/post/pull-image-without-docker/</link>
      <pubDate>Sun, 10 Nov 2019 00:19:51 +0800</pubDate>
      <guid>/post/pull-image-without-docker/</guid>
      <description>&lt;p&gt;好久没有介绍小工具了，今天碰到一个，简单粗糙但是有用的一个，这个工具有多简单粗糙呢？证据有二：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;连 Python shebang 都没有；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;简单到原创 300 字都很难凑够。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;言归正传：安装工经常会遇到一个问题：没 Docker 怎么拉镜像？这个小工具就是做这个事情的。&lt;/p&gt;

&lt;p&gt;该项目同样是个开源项目，地址为：&lt;a href=&#34;https://github.com/NotGlop/docker-drag&#34; target=&#34;_blank&#34;&gt;https://github.com/NotGlop/docker-drag&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个项目使用 Python 的 Request 库，借助 API 直接从仓库中拉取镜像，并保存为 TAR 文件。&lt;/p&gt;

&lt;p&gt;保存下来的 Tar 文件可以直接使用 &lt;code&gt;docker load -i&lt;/code&gt; 命令进行载入。由于去掉了对 Docker/Podman/xxxx 等的依赖，在实际工作中，例如对于文件传输或者 CICD 流程来说，这个脚本都有可能发挥很有意思的作用。&lt;/p&gt;

&lt;p&gt;用法也是相当的简单粗暴：&lt;code&gt;python3 docker_pull.py [image name]&lt;/code&gt;，就完成任务了。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ python3 docker_pull.py nginx:alpine
Creating image structure in: tmp_nginx_alpine
89d9c30c1d48: Pull complete [2787134]
110ad692b782: Pull complete [5953615]
Docker image pulled: library_nginx.tar

$ ls library_nginx.tar
library_nginx.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者放到镜像里：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Dockerfile&#34;&gt;FROM alpine:3.10.3
RUN apk add -u ca-certificates python3 \
    &amp;amp;&amp;amp; pip3 install requests \
    &amp;amp;&amp;amp; wget https://raw.githubusercontent.com/NotGlop/docker-drag/master/docker_pull.py
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/show.gif&#34; alt=&#34;show&#34; /&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 OS X 下构建 ARM 64 镜像</title>
      <link>/post/buildx-and-osx/</link>
      <pubDate>Thu, 17 Oct 2019 01:51:55 +0800</pubDate>
      <guid>/post/buildx-and-osx/</guid>
      <description>&lt;p&gt;Mac OS X 的 Docker 桌面版中加入了一个 buildx 的试验特性，启用之后，可以直接在 MAC 系统中构建 ARM64 和 ARM7 的镜像。启用方法很简单，打开 Docker 的配置窗口，进行如下配置：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/options.png&#34; alt=&#34;options&#34; /&gt;&lt;/p&gt;

&lt;p&gt;打开这个功能之后，Docker 会重新启动一次，命令行中的 Docker 就多出一个 &lt;code&gt;buildx&lt;/code&gt; 命令，可以用于构建异构镜像了。
进行构建之前，首先进行初始化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker buildx create --name builderx
$ docker buildx use mybuilder
$ docker buildx inspect --bootstrap
...
Endpoint:  unix:///var/run/docker.sock
Status:    running
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来我们随便写一个 Dockerfile：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM ubuntu:18.10
CMD [&amp;quot;tini&amp;quot;, &amp;quot;--&amp;quot;, &amp;quot;sleep&amp;quot;, &amp;quot;1d&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在镜像库或者 Docker hub 上创建一个新仓库，就可以尝试 Build-&amp;gt;Push 的操作了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t dustise/debug-container:v0.2 . --push
 =&amp;gt; [internal] load .dockerignore                                                                                    0.0s
 =&amp;gt; =&amp;gt; transferring context: 2B                                                                                      0.0s
 =&amp;gt; [internal] load build definition from Dockerfile                                                                 0.0s
 =&amp;gt; =&amp;gt; transferring dockerfile: 182B                                                                                 0.0s
 =&amp;gt; [linux/arm64 internal] load metadata for docker.io/library/ubuntu:18.10                                          3.5s
 =&amp;gt; [linux/arm/v7 internal] load metadata for docker.io/library/ubuntu:18.10                                         4.0s
 =&amp;gt; [linux/amd64 internal] load metadata for docker.io/library/ubuntu:18.10                                          3.5s
 =&amp;gt; [linux/arm64 1/2] FROM docker.io/library/ubuntu:18.10@sha256:7d657275047118bb77b052c4c0ae43e8a289ca2879ebfa78a7  0.0s

 ...
 =&amp;gt; =&amp;gt; pushing layers                                                                                                5.1s
 =&amp;gt; =&amp;gt; pushing manifest for docker.io/dustise/debug-container:v0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来可以打开你的 Harbor 或者 Docker Hub 查看一下，镜像库中是否已经加入了多平台的镜像内容：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/image.png&#34; alt=&#34;image&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;buildx 还有 save 语法，可以直接将构建结果输出为压缩包，但是目前还没有提供完整支持。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>多数 Dockerfile 示例可能都不够严谨</title>
      <link>/post/broken-by-default/</link>
      <pubDate>Wed, 29 May 2019 13:22:44 +0800</pubDate>
      <guid>/post/broken-by-default/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://pythonspeed.com/articles/dockerizing-python-is-hard/&#34; target=&#34;_blank&#34;&gt;Broken by default: why you should avoid most Dockerfile examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;mailto://itamar@pythonspeed.com&#34; target=&#34;_blank&#34;&gt;Itamar Turner-Trauring&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;想把 Python 应用打包成 Docker 镜像，很自然的行为就是上网搜个例子。简单的一搜，就能得出大量简单易懂的结果。&lt;/p&gt;

&lt;p&gt;不幸的是这些简单方便的例子经常是有一些这样那样的缺陷，有的显而易见，有的可能就不那么明显了。为了发掘这些问题，本文中将要：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;用一个在 Google 搜索结果中常见的 Dockerfile 开始。&lt;/li&gt;
&lt;li&gt;展示其中的问题。&lt;/li&gt;
&lt;li&gt;给出一些修复问题的建议。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;本文的 Docerfile 仅用于解决这里发现的问题，不能算作最佳实践。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;先天不足&#34;&gt;先天不足&lt;/h2&gt;

&lt;p&gt;看看下面的 Dockerfile，这是一个网上搜到的 Python 的容器化例子。做了一点点修改，来隐藏其出处，不过主干是一致的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;# DO NOT USE THIS DOCKERFILE AS AN EXAMPLE, IT IS BROKEN
FROM python:3

COPY yourscript.py /

RUN pip install flask

CMD [ &amp;quot;python&amp;quot;, &amp;quot;./yourscript.py&amp;quot; ]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;这个-dockerfile-的一些问题&#34;&gt;这个 Dockerfile 的一些问题&lt;/h2&gt;

&lt;p&gt;这个镜像中你能看到什么问题？&lt;/p&gt;

&lt;h3 id=&#34;问题-1-python-版本的不确定性&#34;&gt;问题 1：Python 版本的不确定性&lt;/h3&gt;

&lt;p&gt;这里第一个需要注意的问题是，基础镜像是：&lt;code&gt;python:3&lt;/code&gt;。在编写这个文件的时候，会安装 Python 3.7，但是可能未来某一天的重新构建，可能会变成 Python 3.8。这种版本切换，可能会让这一应用完全无法运行，从而打断了产品的交付过程。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：使用 &lt;code&gt;python:3.7&lt;/code&gt; 作为基镜像。&lt;/p&gt;

&lt;h3 id=&#34;问题-2-依赖库版本的不确定性&#34;&gt;问题 2：依赖库版本的不确定性&lt;/h3&gt;

&lt;p&gt;这里的 &lt;code&gt;pip install flask&lt;/code&gt;，没有包含版本信息，所以每次重新构建，都可能升级成最新的 flask（或者 flask 的依赖，又或者 flask 的依赖的依赖）。保持兼容自然没问题，否则的话麻烦就大了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：创建 &lt;code&gt;requirements.txt&lt;/code&gt;，其中记载所有依赖的版本号，可以用 &lt;a href=&#34;https://hynek.me/articles/python-app-deps-2018/&#34; target=&#34;_blank&#34;&gt;pip-tools&lt;/a&gt; 完成这一任务。&lt;/p&gt;

&lt;h3 id=&#34;问题-3-代码的变更会让构建缓存失效&#34;&gt;问题 3：代码的变更会让构建缓存失效&lt;/h3&gt;

&lt;p&gt;Docker 的层缓存对提高构件速度很有帮助。但是如果把 &lt;code&gt;COPY&lt;/code&gt; 操作放在 &lt;code&gt;pip install&lt;/code&gt; 前面，所有后续的层就都失效了，也就是说这一镜像会完全重新构建。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：在合适的时机进行文件复制。&lt;/p&gt;

&lt;h3 id=&#34;问题-4-用-root-身份运行&#34;&gt;问题 4：用 root 身份运行&lt;/h3&gt;

&lt;p&gt;缺省情况下，Docker 容器是用 root 身份运行的，这&lt;a href=&#34;http://canihaznonprivilegedcontainers.info/&#34; target=&#34;_blank&#34;&gt;并不安全&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：如果不是有特定需要，例如监听 1024 以下的端口或者完成一些必须 root 身份的操作，建议使用非 root 账号。&lt;/p&gt;

&lt;h2 id=&#34;改良版本&#34;&gt;改良版本&lt;/h2&gt;

&lt;p&gt;为了解决上面发现的几个问题，对 Dockerfile 做出如下修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM python:3.7

COPY requirements.txt /tmp/

RUN pip install -r /tmp/requirements.txt

RUN useradd --create-home appuser
WORKDIR /home/appuser
USER appuser

COPY yourscript.py .

CMD [ &amp;quot;python&amp;quot;, &amp;quot;./yourscript.py&amp;quot; ]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样改进了之后，也并不是就适合在生产环境中运行了，这个镜像还有一些不足。&lt;/p&gt;

&lt;p&gt;例如，用一种受控的方式来对 &lt;code&gt;requirements.txt&lt;/code&gt; 进行常规更新，以便进行安全更新和 Bug 修复，可能还要&lt;a href=&#34;https://pythonspeed.com/articles/docker-cache-insecure-images/&#34; target=&#34;_blank&#34;&gt;禁用缓存对镜像进行周期性重建&lt;/a&gt;，来获取安全加固。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pythonspeed.com/articles/docker-cache-insecure-images/&#34; target=&#34;_blank&#34;&gt;https://pythonspeed.com/articles/docker-cache-insecure-images/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/develop/develop-images/dockerfile_best-practices/&#34; target=&#34;_blank&#34;&gt;https://docs.docker.com/develop/develop-images/dockerfile_best-practices/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hynek.me/articles/python-app-deps-2018/&#34; target=&#34;_blank&#34;&gt;https://hynek.me/articles/python-app-deps-2018/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>全面易用的镜像漏洞检测工具：Trivy</title>
      <link>/post/introducing-trivy/</link>
      <pubDate>Fri, 17 May 2019 20:36:33 +0800</pubDate>
      <guid>/post/introducing-trivy/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;道路千万条，安全第一条；
镜像不规范，同事两行泪。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/aquasecurity/trivy&#34; target=&#34;_blank&#34;&gt;Trivy&lt;/a&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;CI 友好&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;相对于老前辈 &lt;a href=&#34;https://github.com/coreos/clair&#34; target=&#34;_blank&#34;&gt;Clair&lt;/a&gt;，Trivy 的使用非常直观方便，适用于更多的场景。&lt;/p&gt;

&lt;p&gt;下面是官方出具的对比表格：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;扫描器&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;操作系统&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;依赖检测&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;适用性&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;准确度&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;CI 友好&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trivy&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◎&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Clair&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Anchore Engine&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Quay&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;MicroScanner&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Docker Hub&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;GCR&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;另外还提供了精确度的对比表格，但是追究下来，无非是采用的参考数据的差异。至少这并不是我看重的东西，顺手是第一要务。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;h3 id=&#34;macos&#34;&gt;MacOS&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ brew tap knqyf263/trivy
$ brew install knqyf263/trivy/trivy
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;rhel-centos&#34;&gt;RHEL/CentOS&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ sudo vim /etc/yum.repos.d/trivy.repo
[trivy]
name=Trivy repository
baseurl=https://knqyf263.github.io/trivy-repo/rpm/releases/$releasever/$basearch/
gpgcheck=0
enabled=1
$ sudo yum -y update
$ sudo yum -y install trivy
&lt;/code&gt;&lt;/pre&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;pre&gt;&lt;code&gt;$ trivy centos
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;扫描镜像文件&#34;&gt;扫描镜像文件&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ docker save ruby:2.3.0-alpine3.9 -o ruby-2.3.0.tar
$ trivy --input ruby-2.3.0.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;根据严重程度进行过滤&#34;&gt;根据严重程度进行过滤&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy --severity HIGH,CRITICAL ruby:2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;忽略未修复问题&#34;&gt;忽略未修复问题&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy --ignore-unfixed ruby:2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;忽略特定问题&#34;&gt;忽略特定问题&lt;/h3&gt;

&lt;p&gt;使用 &lt;code&gt;.trivyignore&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cat .trivyignore
# Accept the risk
CVE-2018-14618

# No impact in our settings
CVE-2019-1543

$ trivy python:3.4-alpine3.9
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;使用-json-输出结果&#34;&gt;使用 JSON 输出结果&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy -f json dustise/translat-chatbot:20190428-5
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;定义返回值&#34;&gt;定义返回值&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy --exit-code 0 --severity MEDIUM,HIGH ruby:2.3.0
$ trivy --exit-code 1 --severity CRITICAL ruby:2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;相对于&lt;a href=&#34;https://github.com/knqyf263/trivy#comparison-with-other-scanners&#34; target=&#34;_blank&#34;&gt;其它同类工具&lt;/a&gt;，Trivy 非常适合自动化操作，从 CircleCI 之类的公有服务，到企业内部使用的 Jenkins、Gitlab 等私有工具，或者作为开发运维人员的自测环节，都有 Trivy 的用武之地。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;https://github.com/aquasecurity/trivy&lt;/code&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Hitler Uses Docker</title>
      <link>/post/hitler-uses-docker/</link>
      <pubDate>Wed, 01 May 2019 20:39:58 +0800</pubDate>
      <guid>/post/hitler-uses-docker/</guid>
      <description>


&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/PivpCKEiQOQ&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h2 id=&#34;克莱勃斯&#34;&gt;克莱勃斯&lt;/h2&gt;

&lt;p&gt;我们把镜像推送到 Dockerhub，然后使用 docker compose 部署到集群上。&lt;/p&gt;

&lt;p&gt;我们在这些节点上加载数据卷。&lt;/p&gt;

&lt;p&gt;然后在这里连接到应用容器。&lt;/p&gt;

&lt;p&gt;最后，我们更新了 DNS 记录。&lt;/p&gt;

&lt;h2 id=&#34;希特勒&#34;&gt;希特勒&lt;/h2&gt;

&lt;p&gt;所以我们在每个节点上运行了 20 个容器。我们什么时候可以去掉多余的服务器？&lt;/p&gt;

&lt;h2 id=&#34;克莱勃斯-1&#34;&gt;克莱勃斯&lt;/h2&gt;

&lt;p&gt;元首，内核。。。&lt;/p&gt;

&lt;h2 id=&#34;约德尔&#34;&gt;约德尔&lt;/h2&gt;

&lt;p&gt;有个第三方容器引发了内核恐慌。我们损失了 70% 的集群和数据卷。&lt;/p&gt;

&lt;h2 id=&#34;希特勒-1&#34;&gt;希特勒&lt;/h2&gt;

&lt;p&gt;没在生产环境上使用 Docker 的，出去。&lt;/p&gt;

&lt;p&gt;隔离个屁！&lt;/p&gt;

&lt;p&gt;你们想什么呢！&lt;/p&gt;

&lt;p&gt;谁特么会用 Docker hub 上的公共镜像？&lt;/p&gt;

&lt;p&gt;你们该知道那都是俄罗斯黑客做的！&lt;/p&gt;

&lt;p&gt;你可能还用 &lt;code&gt;curl | sudo bash&lt;/code&gt;！&lt;/p&gt;

&lt;p&gt;你觉得公共仓库上的所有东西都是安全的？就因为是开源软件？&lt;/p&gt;

&lt;p&gt;你们这群赶 Node.js 时髦的人，只会看着 Hacker news 啥都装上去！&lt;/p&gt;

&lt;h2 id=&#34;克莱勃斯-2&#34;&gt;克莱勃斯&lt;/h2&gt;

&lt;p&gt;但是 Docker 让我们能够在任何地方运行我们的应用！&lt;/p&gt;

&lt;h2 id=&#34;希特勒-2&#34;&gt;希特勒&lt;/h2&gt;

&lt;p&gt;为了在你的笔记本上运行 Docker，你还得用个虚拟机！&lt;/p&gt;

&lt;h2 id=&#34;克莱勃斯-3&#34;&gt;克莱勃斯&lt;/h2&gt;

&lt;p&gt;元首，docker-machine 用的是轻量级虚拟机！&lt;/p&gt;

&lt;h2 id=&#34;希特勒-3&#34;&gt;希特勒&lt;/h2&gt;

&lt;p&gt;你知道自己在说什么？我们都用了虚拟机了，为什么还需要 Docker？&lt;/p&gt;

&lt;p&gt;容器里的容器！&lt;/p&gt;

&lt;p&gt;就为了部署一个 10 MB 的 Go 程序，你们压缩了一整个 Linux 操作系统，然后因为太大了，又用上 CoW 存储。&lt;/p&gt;

&lt;p&gt;别跟我说什么资源限制。cgroups 的黑魔法连简单的 fork 炸弹都挡不住！&lt;/p&gt;

&lt;p&gt;如果数据库需要服务器上的所有资源，Docker 还让你在上面运行更多程序！&lt;/p&gt;

&lt;p&gt;Docker 之前，我只要选择个合适尺寸的虚拟机。&lt;/p&gt;

&lt;p&gt;突然人们告诉我什么数据中心效能什么超融合。&lt;/p&gt;

&lt;p&gt;所有人都觉得自己是 Google！&lt;/p&gt;

&lt;p&gt;你都不用运行自己的机器了！&lt;/p&gt;

&lt;p&gt;大家都在 GCE 上运行 Docker，在 Borg 虚拟机实例上运行 Linux 容器！&lt;/p&gt;

&lt;p&gt;还有人觉得 Docker 能做配置管理，他们觉得 Docker 解决了所有问题！&lt;/p&gt;

&lt;p&gt;连微软都有容器了！&lt;/p&gt;

&lt;p&gt;我正在把所有东西都转移到 Windows！&lt;/p&gt;

&lt;h2 id=&#34;容格夫人&#34;&gt;容格夫人&lt;/h2&gt;

&lt;p&gt;别哭了，现在可以在 Windows 10 里运行 Bash 了。&lt;/p&gt;

&lt;h2 id=&#34;希特勒-4&#34;&gt;希特勒&lt;/h2&gt;

&lt;p&gt;Docker 本该有更好的性能。&lt;/p&gt;

&lt;p&gt;但是 userland 代理比 28.8k 的猫还慢。&lt;/p&gt;

&lt;p&gt;现在连企业都想跑 Docker 了，但他们还在运行 Red Hat 5。&lt;/p&gt;

&lt;p&gt;你们这些白痴认为 Docker 能帮你们的应用进行伸缩。&lt;/p&gt;

&lt;p&gt;关键业务用 Openstack 吧。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Containerd 1.1.0 尝鲜记</title>
      <link>/post/containerd-kubeadm/</link>
      <pubDate>Wed, 30 May 2018 00:41:44 +0800</pubDate>
      <guid>/post/containerd-kubeadm/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://blog.fleeto.us/post/kubernetes-containerd-integration-goes-ga/&#34; target=&#34;_blank&#34;&gt;Containerd 1.1.0 的 Kubernetes 支持已经进入可用阶段&lt;/a&gt;，Kubernetes 1.10 和未来的的 Docker 版本都会以此为基础，作为一个熟练软件安装工，自然是要先睹为快了。&lt;/p&gt;

&lt;p&gt;这里使用 Kubeadm 进行测试。&lt;/p&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;p&gt;首先进行 Kubeadm 的环境准备：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;安装 libseccomp, conntrack&lt;/li&gt;
&lt;li&gt;关闭防火墙服务&lt;/li&gt;
&lt;li&gt;开启 sysctl：&lt;code&gt;ip_forward&lt;/code&gt;、&lt;code&gt;net.bridge.bridge-nf-call-iptables&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;参考&lt;a href=&#34;https://kubernetes.io/docs/tasks/tools/install-kubeadm/&#34; target=&#34;_blank&#34;&gt;官方指南&lt;/a&gt;，安装 kubeadm、kubelet 以及 kubectl，此处暂时不启动 kubelet 服务。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;安装-contaierd&#34;&gt;安装 contaierd&lt;/h2&gt;

&lt;p&gt;下载 &lt;a href=&#34;https://storage.googleapis.com/cri-containerd-release/cri-containerd-1.1.0.linux-amd64.tar.gz&#34; target=&#34;_blank&#34;&gt;cri-containerd 1.1.0&lt;/a&gt;，并解压，其中包含 &lt;code&gt;/usr&lt;/code&gt;、&lt;code&gt;/etc&lt;/code&gt; 以及 &lt;code&gt;opt&lt;/code&gt; 三个目录，这里我们只是用前两个目录的内容，目录结构如下，直接复制即可：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;├── etc
│   ├── crictl.yaml
│   └── systemd
│       └── system
│           └── containerd.service
└── usr
    └── local
        ├── bin
        │   ├── containerd
        │   ├── containerd-release
        │   ├── containerd-shim
        │   ├── containerd-stress
        │   ├── crictl
        │   ├── critest
        │   └── ctr
        └── sbin
            └── runc
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;crictl.yaml&lt;/code&gt;：crictl 的配置文件，缺省包含一行 &lt;code&gt;runtime-endpoint: unix:///run/containerd/containerd.sock&lt;/code&gt;，指定运行时的连接方式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;containerd.service&lt;/code&gt;：服务文件，设置自动启动即可。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctr&lt;/code&gt;：containerd 客户端&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crictl&lt;/code&gt;：cri 客户端&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runc&lt;/code&gt;：运行时，contaienrd 依赖项&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里可以发现，并没有包含 containerd 自己的配置文件，可以使用 &lt;code&gt;containerd config default &amp;gt; /etc/containerd/config.toml&lt;/code&gt; 命令，来生成缺省配置文件，然后自行变更。例如可以&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/docs/registry.md&#34; target=&#34;_blank&#34;&gt;修改仓库镜像地址&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;另外对国内用户比较重要的一点是，仍然是可以使用环境变量方式的配置来设置 &lt;code&gt;HTTP_PROXY&lt;/code&gt; 以及 &lt;code&gt;NO_PROXY&lt;/code&gt; 的内容。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;配置完成后，使用 &lt;code&gt;systemctl&lt;/code&gt; 启动服务。&lt;/p&gt;

&lt;h2 id=&#34;载入镜像&#34;&gt;载入镜像&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;docker.io/coredns/coredns:1.0.6&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-proxy-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/etcd-amd64&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-apiserver-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-controller-manager-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-proxy-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-scheduler-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/pause:3.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ctr 的镜像载入命令&lt;/strong&gt;：&lt;code&gt;ctr cri load image.tar&lt;/code&gt;，似乎不支持 gz。&lt;/p&gt;

&lt;h2 id=&#34;配置-kubelet-使用-containerd&#34;&gt;配置 Kubelet 使用 containerd&lt;/h2&gt;

&lt;p&gt;简单的在 Kubelet 的环境变量上加入如下内容，再启动 Kubelet 服务：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[Service]
Environment=&amp;quot;KUBELET_EXTRA_ARGS=--runtime-cgroups=/system.slice/containerd.service --container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;kubeadm-集群安装&#34;&gt;Kubeadm 集群安装&lt;/h2&gt;

&lt;p&gt;这里提供一个简单的初始化命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;kubeadm init \
--pod-network-cidr=192.168.0.0/16 \
--feature-gates CoreDNS=true \
--ignore-preflight-errors=Service-Docker \
--ignore-preflight-errors=SystemVerification \
--kubernetes-version=v1.10.3 # 防止 kubeadm 向服务器查询镜像列表。
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubeadm 缺省情况下依旧是需要检查 Docker 的运行情况的，因此这里我们使用 &lt;code&gt;--ignore-preflight-errors&lt;/code&gt; 开关关闭这项检查。&lt;/p&gt;

&lt;p&gt;Master 初始化结束之后，就可以跟随 kubeadm 指示，进入其他节点，运行 &lt;code&gt;kubeadm join&lt;/code&gt; 命令来加入集群了，加入命令同样需要设置 &lt;code&gt;--ignore-preflight-errors=all&lt;/code&gt; 来规避 Docker 检查。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;接下来可以按照自己喜好安装网络插件了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以使用 &lt;code&gt;kubectl describe nodes [node name]&lt;/code&gt; 来检查节点信息：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
Container Runtime Version:  containerd://1.1.0
Kubelet Version:            v1.10.3
Kube-Proxy Version:         v1.10.3
PodCIDR:                     192.168.0.0/24
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里可以看到，运行时已经更新为 &lt;code&gt;containerd://1.1.0&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;正如在前面文章提到的，containerd 并非 Docker 的替代品，只是一个子集，独立使用是很困难的，因此还是比较适合用于 Kubelet 控制之下的容器运行支持。&lt;/p&gt;

&lt;h2 id=&#34;下载链接以及参考链接&#34;&gt;下载链接以及参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cri-containerd 1.1.0&lt;/strong&gt;：&lt;code&gt;https://storage.googleapis.com/cri-containerd-release/cri-containerd-1.1.0.linux-amd64.tar.gz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kubeadm 安装指南&lt;/strong&gt;：&lt;code&gt;https://kubernetes.io/docs/tasks/tools/install-kubeadm/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;containerd 安装指南&lt;/strong&gt;：&lt;code&gt;https://github.com/containerd/containerd/releases&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Containerd 1.1.0 的 Kubernetes 支持已经进入可用阶段&lt;/strong&gt;： &lt;code&gt;https://blog.fleeto.us/post/kubernetes-containerd-integration-goes-ga/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes Containerd 集成进入 GA 阶段</title>
      <link>/post/kubernetes-containerd-integration-goes-ga/</link>
      <pubDate>Fri, 25 May 2018 02:09:22 +0800</pubDate>
      <guid>/post/kubernetes-containerd-integration-goes-ga/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga/&#34; target=&#34;_blank&#34;&gt;Kubernetes Containerd Integration Goes GA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/in/liu-lantao-96a97351&#34; target=&#34;_blank&#34;&gt;Lantao Liu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.twitter.com/mikebrow&#34; target=&#34;_blank&#34;&gt;Mike Brown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在之前的博客（&lt;a href=&#34;https://kubernetes.io/blog/2017/11/containerd-container-runtime-options-kubernetes&#34; target=&#34;_blank&#34;&gt;Containerd Brings More Container Runtime Options for Kubernetes&lt;/a&gt;）中，我们介绍了 Kubernetes Containerd 集成的 Alpha 版本。经过六个月的开发，Containerd 的集成现在进入了 GA 阶段，现在可以将 &lt;a href=&#34;https://github.com/containerd/containerd/releases/tag/v1.1.0&#34; target=&#34;_blank&#34;&gt;Containerd 1.1&lt;/a&gt; 作为容器运行时为生产环境的 Kubernetes 提供支撑了。&lt;/p&gt;

&lt;p&gt;Containerd 1.1 支持 Kubernetes 1.10 及以上版本，支持 Kubernetes 的所有特性。目前在 Kubernetes 的测试设施中，Containerd 在 &lt;a href=&#34;https://cloud.google.com/&#34; target=&#34;_blank&#34;&gt;Google 云平台&lt;/a&gt;上的测试覆盖已经和 Docker 集成持平了。（参见：&lt;a href=&#34;https://k8s-testgrid.appspot.com/sig-node-containerd&#34; target=&#34;_blank&#34;&gt;Test Dashboard&lt;/a&gt;）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;很高兴看到 Containerd 快速成长到今天的这一重要里程碑。阿里云从 Containerd 诞生之初就开始积极的采用 Containerd，开发团队对于简单和健壮的重视，使其完美的运行在我们的无服务器 Kubernetes 产品之中，提供了很好的性能和稳定性。Containerd 无疑将会成为容器世界的核心引擎，并持续创新前行。&lt;/p&gt;

&lt;p&gt;Xinwei，阿里云工程师。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;架构提升&#34;&gt;架构提升&lt;/h2&gt;

&lt;p&gt;Kubernetes 的 Containerd 集成架构有两次重大改进，每一次都让整个体系更加稳定和高效。&lt;/p&gt;

&lt;p&gt;Containerd 1.0 - CRI-Containerd（已终止）&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cri-containerd.png&#34; alt=&#34;cri-containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Containerd 1.0 中，需要一个叫做 cri-containerd 的守护进程，他的功能是提供 Kubelet 和 Containerd 之间的互操作支持。Cri-Containerd 处理来自 Kubelet 的 &lt;a href=&#34;https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/&#34; target=&#34;_blank&#34;&gt;容器运行时接口（CRI）&lt;/a&gt;服务请求，并使用 containerd 来管理容器和容器的镜像。对比之前的 Docker CRI 实现（&lt;a href=&#34;https://github.com/kubernetes/kubernetes/tree/v1.10.2/pkg/kubelet/Dockershim&#34; target=&#34;_blank&#34;&gt;Dockershim&lt;/a&gt;），他清理了整个体系中的一些多余部分。&lt;/p&gt;

&lt;p&gt;然而 Cri-containerd 和 Containerd 1.0 还是两个不同的守护进程，相互之间使用 gRPC 进行通信。额外进程给用户的理解和部署都造成了麻烦，并引入了不必要的通信开支。&lt;/p&gt;

&lt;p&gt;Containerd 1.1 - CRI 插件（目前）&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/containerd.png&#34; alt=&#34;containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 Containerd 1.1 中，Cri-containerd 守护进程进行了重构，成为了 Containerd 的 CRI 插件。CRI 插件处于 Containerd 1.1 内部，缺省启用。和 Cri-containerd 不同，CRI 插件和 Containerd 之间通过直接的程序调用来协同工作。新架构让这一产品更加稳定高效，去除了过程中的 gRPC 开销。用户现在可以直接使用 Containerd 1.1 来支撑 Kubernetes，不再需要 Cri-containerd 守护进程。&lt;/p&gt;

&lt;h2 id=&#34;性能&#34;&gt;性能&lt;/h2&gt;

&lt;p&gt;Containerd 1.1 的一个主要目标就是提高性能。这里的性能主要指的是 Pod 启动延迟以及守护进程的资源使用情况。&lt;/p&gt;

&lt;p&gt;下面的结果是 Containerd 1.1 和 Docker 18.03 CE 之间的对比。Containerd 1.1 集成使用了内置其中的 CRI 插件；Docker 18.03 CE 集成使用的是 Dockershim。&lt;/p&gt;

&lt;p&gt;下面的结果是使用 Kubernetes 节点性能 Benchmark 生成的，这个 Benchmark 工具是 &lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/e2e-node-tests.md&#34; target=&#34;_blank&#34;&gt;Kubernetes 节点端到端测试&lt;/a&gt;的一部分。绝大多数的 Containerd 测试结果都是可以在 &lt;a href=&#34;http://node-perf-dash.k8s.io/&#34; target=&#34;_blank&#34;&gt;节点性能 Dashboard&lt;/a&gt; 上进行公开访问的。&lt;/p&gt;

&lt;h3 id=&#34;pod-启动延迟&#34;&gt;Pod 启动延迟&lt;/h3&gt;

&lt;p&gt;“105 pod batch startup benchmark” 结果显示，相对 Docker 18.03 CE 的 dochershim 集成来说，Containerd 1.1 的集成的延迟时间更短（越低越好）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/latency.png&#34; alt=&#34;latency&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;cpu-和内存&#34;&gt;CPU 和内存&lt;/h2&gt;

&lt;p&gt;在 105 个 Pod 的稳定状态下，Containerd 1.1 集成消耗的 CPU 和内存都比 Docker 18.03 CE 的 Dockershim 集成要少。这个结果和节点上运行的 Pod 数量关系紧密，之所以选择 105 这个数字，是因为这是目前每节点上运行 Pod 的缺省数量上限。&lt;/p&gt;

&lt;p&gt;如下图所示，对比 Docker 18.03 CE 的 Dockershim 集成，Containerd 1.1 集成的 Kubelet CPU 占用降低了 30.89%，容器运行时 CPU 消耗降低了 68.13%，Kubelet 实际使用内存（RSS）降低了 11.30%，容器运行时 RSS 降低了 12.78%。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cpu.png&#34; alt=&#34;cpu&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/memory.png&#34; alt=&#34;memory&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;crictl&#34;&gt;crictl&lt;/h2&gt;

&lt;p&gt;容器运行时命令行接口（CLI）对系统和应用的排错来说是个有用的工具。如果用 Docker 作为 Kubernetes 的容器运行时，系统管理员有时候需要登录到 Kubernetes 节点上去运行 Docker 命令，以便收集系统和应用的信息。例如使用 &lt;code&gt;docker ps&lt;/code&gt; 和 &lt;code&gt;docker inspect&lt;/code&gt; 检查应用的进程情况，&lt;code&gt;docker images&lt;/code&gt; 列出节点上的镜像，或者 &lt;code&gt;docker info&lt;/code&gt; 来检查容器运行时的配置等。&lt;/p&gt;

&lt;p&gt;对 Containerd 和所有其他的 CRI 兼容的容器运行时，尤其是 Dockershim 来说，我们推荐使用 &lt;code&gt;crictl&lt;/code&gt; 作为 Docker CRI 的继任者，用于 Kubernetes 节点上 pod、容器以及镜像的除错工具。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;crictl&lt;/code&gt; 在 Kubernetes 节点除错方面，提供了类似 Docker CLI 的使用体验， 并且 &lt;code&gt;crictl&lt;/code&gt; 能够支持所有 CRI 兼容的容器运行时。这一项目存放于 &lt;a href=&#34;https://github.com/kubernetes-incubator/cri-tools&#34; target=&#34;_blank&#34;&gt;kubernetes-incubator/cri-tools&lt;/a&gt;，目前版本是 &lt;a href=&#34;https://github.com/kubernetes-incubator/cri-tools/releases/tag/v1.0.0-beta.1&#34; target=&#34;_blank&#34;&gt;v1.0.0-beta.1&lt;/a&gt;。&lt;code&gt;crictl&lt;/code&gt; 的设计目的是理顺 Docker CLI 的功能，为用户提供更好的过渡体验，但是和 Docker CLI 又不尽相同。下面讲讲两者之间的一些重要区别。&lt;/p&gt;

&lt;h3 id=&#34;适用范围-crictl-是一个排错工具&#34;&gt;适用范围：crictl 是一个排错工具&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;crictl&lt;/code&gt; 的设计目的是排错，并非 Docker 或者 kubectl 的替代品。Docker 的 CLI 提供了大量的命令，使之成为重要的开发工具，但是在 Kubernetes 节点排错方面，就不尽人意了。有些 Docker 命令在 Kubernetes 上没什么用，例如 &lt;code&gt;docker network&lt;/code&gt; 和 &lt;code&gt;docker build&lt;/code&gt;；有些甚至会损害系统，比如说 &lt;code&gt;docker rename&lt;/code&gt;，&lt;code&gt;crictl&lt;/code&gt; 提供了刚好够用的命令来进行节点方面的除错工作，对于生产节点来说，明显会有更好的安全性。&lt;/p&gt;

&lt;h3 id=&#34;kubernetes-特性&#34;&gt;Kubernetes 特性&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;crictl&lt;/code&gt; 提供了一个对 Kubernetes 来说更加友好的容器视角。Docker CLI 并不了解 Kubernetes 的概念，例如 &lt;code&gt;pod&lt;/code&gt; 和 &lt;a href=&#34;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;namespace&lt;/code&gt;&lt;/a&gt;，所以他无法提供容器和 Pod 的清晰视图。一个例子就是 &lt;code&gt;docker ps&lt;/code&gt; 的混乱输出：过长的 Docker 容器名称、Pause 容器和应用容器混杂在一起：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/docker-ps.png&#34; alt=&#34;docker ps&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.ianlewis.org/en/almighty-pause-container&#34; target=&#34;_blank&#34;&gt;Pause 容器&lt;/a&gt;是一个 Pod 的实现手段，每个 Pod 都会有一个 Pause 容器，所以列出 Pod 中包含的容器的时候，没必要把 Pause 容器显示出来。&lt;/p&gt;

&lt;p&gt;而 &lt;code&gt;crictl&lt;/code&gt; 是为 Kubernetes 设计的，他有不同的一组命令来和 Pod 以及容器进行交互。例如 &lt;code&gt;crictl pods&lt;/code&gt; 会列出 Pod 信息，而 &lt;code&gt;crictl ps&lt;/code&gt; 只会列出应用容器的信息。所有的信息都以表格形式进行展示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/crictl-pods.png&#34; alt=&#34;!crictl-pods.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;关于 &lt;code&gt;crictl&lt;/code&gt; 在 containerd 方面的细节，可以参看：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/master/docs/crictl.md&#34; target=&#34;_blank&#34;&gt;文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://asciinema.org/a/179047&#34; target=&#34;_blank&#34;&gt;演示视频&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;docker-怎么办&#34;&gt;Docker 怎么办？&lt;/h2&gt;

&lt;p&gt;“切换到 Containerd 是不是说我不能再用 Docker Engine 了？”我们经常听到这个问题，简单的答案就是：NO。&lt;/p&gt;

&lt;p&gt;Docker Engine 是在 Containerd 之上构建的。下个版本的 &lt;a href=&#34;https://www.docker.com/community-edition&#34; target=&#34;_blank&#34;&gt;Docker CE&lt;/a&gt; 就会使用 Containerd 1.1。当然，也就会有内置的缺省激活的 CRI 插件。这样一来，用户可以选择继续使用 Docker Engine 来做一些 Docker 的事情，也可以配置 Kubernetes 来使用其中的 Containerd，同时 Containerd 还会同时给同一节点上的 Docker Engine 提供支撑。下面的架构图就描述了 Docker Engine 和 Kubelet 共用 Containerd 的情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/docker-ce.png&#34; alt=&#34;docker ce&#34; /&gt;&lt;/p&gt;

&lt;p&gt;既然 Containerd 同时能够给 Kubelet 和 Docker Engine 提供支持，选择了使用 Containerd 集成的用户，得到的不仅仅是新的 Kubernetes 特性、性能和稳定性的增强，他们还会得到保留 Docker Engine 以便用于其他用例的选择。&lt;/p&gt;

&lt;p&gt;Containerd 的&lt;a href=&#34;https://github.com/containerd/containerd/blob/master/docs/namespaces.md&#34; target=&#34;_blank&#34;&gt;命名空间&lt;/a&gt;机制，让 Kubelet 和 Docker Engine 之间无法互相访问对方的容器和镜像。这样就保证了他们无法互相影响，这样的后果：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;docker ps&lt;/code&gt; 命令无法看到 Kubernetes 创建的容器；而应该使用 &lt;code&gt;crictl ps&lt;/code&gt;。反之亦然，用 &lt;code&gt;crictl ps&lt;/code&gt; 也是无法看到 Docker CLI 创建的容器。&lt;code&gt;crictl create&lt;/code&gt; 以及 &lt;code&gt;crictl runp&lt;/code&gt; 命令只用于出错。不推荐在生产节点上手动使用 &lt;code&gt;crictl&lt;/code&gt; 启动 Pod 或者容器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker images&lt;/code&gt; 不会看到 Kubernetes 拉回的镜像。同样需要使用 &lt;code&gt;crictl images&lt;/code&gt; 命令。反过来用 &lt;code&gt;docker pull&lt;/code&gt;、&lt;code&gt;docker load&lt;/code&gt; 或者 &lt;code&gt;docker build&lt;/code&gt; 生成的镜像，Kubernetes 也是无法看到的。可以使用 &lt;code&gt;crictl pull&lt;/code&gt; 命令来替代，可以使用 &lt;code&gt;[ctr](https://github.com/containerd/containerd/blob/master/docs/man/ctr.1.md) cri load&lt;/code&gt; 来载入镜像。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Containerd 1.1 天然支持 CRI，可以直接给 Kubernetes 使用。&lt;/li&gt;
&lt;li&gt;Containerd 1.1 满足生产要求。&lt;/li&gt;
&lt;li&gt;Containerd 1.1 在 Pod 启动延迟和系统资源占用方面具有良好的性能表现。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crictl&lt;/code&gt; 是用于和 Containerd 1.1 以及其他 cri 兼容的容器运行时进行操作和节点除错的 CLI 工具。&lt;/li&gt;
&lt;li&gt;下一个 Docker CE 版本会包含 Containerd 1.1。用户有选择继续使用 Docker 来满足 Kubernetes 之外的容器需求，同时让 Kubernetes 使用来自 Docker 的同样的底层容器运行时。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里要感谢来自 Google、IBM、Docker、ZTE、ZJU 以及很多其他的个人，让这一产品发展至今。&lt;/p&gt;

&lt;p&gt;可以阅读 &lt;a href=&#34;https://github.com/containerd/containerd/releases/tag/v1.1.0&#34; target=&#34;_blank&#34;&gt;Release Notes&lt;/a&gt;，来了解 Containerd 1.1 详细的变更情况。&lt;/p&gt;

&lt;h2 id=&#34;尝鲜&#34;&gt;尝鲜&lt;/h2&gt;

&lt;p&gt;要使用 Containerd 作为 Kubernetes 的容器运行时来搭建集群：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/docs/kube-up.md&#34; target=&#34;_blank&#34;&gt;在 GCE 上用 kube-up.sh 来启动一个生产级别的集群&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/contrib/ansible/README.md&#34; target=&#34;_blank&#34;&gt;使用 Ansible 和 Kubeadm 搭建多节点集群&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在 Google 云从头搭建集群，可以看看 &lt;a href=&#34;https://github.com/kelseyhightower/kubernetes-the-hard-way&#34; target=&#34;_blank&#34;&gt;Kubernetes the Hard Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/docs/installation.md&#34; target=&#34;_blank&#34;&gt;从发布压缩包开始自定义安装&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/linuxkit/linuxkit/tree/master/projects/kubernetes&#34; target=&#34;_blank&#34;&gt;使用 LinuxKit 在本地虚拟机上安装&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;贡献&#34;&gt;贡献&lt;/h2&gt;

&lt;p&gt;Container CRI 插件是一个开源的 Github 项目，在 Containerd 之内：&lt;a href=&#34;https://github.com/containerd/cri&#34; target=&#34;_blank&#34;&gt;https://github.com/containerd/cri&lt;/a&gt;。我们欢迎任何建议、问题、代码方面的贡献。&lt;a href=&#34;https://github.com/containerd/cri#getting-started-for-developers&#34; target=&#34;_blank&#34;&gt;开发者起步指南&lt;/a&gt;提供了如何成为贡献者方面的入门知识。&lt;/p&gt;

&lt;h2 id=&#34;社区&#34;&gt;社区&lt;/h2&gt;

&lt;p&gt;这个项目的开发和维护是由 Kubernetes SIG-Node 社区和 Containerd 社区联合负责的。我们希望能听到用户的反馈，要加入集群：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes/community/tree/master/sig-node&#34; target=&#34;_blank&#34;&gt;SIG-Node 社区网站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Slack：

&lt;ul&gt;
&lt;li&gt;#sig-node，&lt;a href=&#34;http://kubernetes.slack.com&#34; target=&#34;_blank&#34;&gt;kubernetes.slack.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;#containerd, &lt;a href=&#34;https://dockr.ly/community&#34; target=&#34;_blank&#34;&gt;https://dockr.ly/community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;邮件列表：&lt;a href=&#34;https://groups.google.com/forum/#!forum/kubernetes-sig-node&#34; target=&#34;_blank&#34;&gt;https://groups.google.com/forum/#!forum/kubernetes-sig-node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>在 K8S 集群中分布构建 Docker 镜像</title>
      <link>/post/jenkins-build-image-on-k8s-tricks/</link>
      <pubDate>Fri, 18 May 2018 18:48:12 +0800</pubDate>
      <guid>/post/jenkins-build-image-on-k8s-tricks/</guid>
      <description>

&lt;h2 id=&#34;镜像更新&#34;&gt;镜像更新&lt;/h2&gt;

&lt;p&gt;最近给我的 &lt;a href=&#34;https://github.com/fleeto/docker-jenkins&#34; target=&#34;_blank&#34;&gt;Jenkins 镜像&lt;/a&gt; 做了一些更新：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;加入了基于 RunC 的镜像操作工具：&lt;a href=&#34;https://github.com/genuinetools/img&#34; target=&#34;_blank&#34;&gt;IMG&lt;/a&gt;。在特权模式下，可以方便的使用命令行创建和操作 Docker 镜像。&lt;/li&gt;
&lt;li&gt;更新 Jenkins 以及 Remoting 版本。&lt;/li&gt;
&lt;li&gt;修复一些初始化问题。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面根据这一版本的镜像简单说说分布式构建过程中的一些要点。&lt;/p&gt;

&lt;h2 id=&#34;jenkins-镜像的一些值得注意的东西&#34;&gt;Jenkins 镜像的一些值得注意的东西&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jenkins 在启动 Slave 节点时，会设置下面三个环境变量，所以在 &lt;code&gt;run.sh&lt;/code&gt; 就可以引用这几个环境变量启动 Worker 节点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JENKINS_URL：MASTER 服务的地址。&lt;/li&gt;
&lt;li&gt;JENKINS_NAME：为 Worker 节点分配的名称&lt;/li&gt;

&lt;li&gt;&lt;p&gt;JENKINS_SECRET：Worker 节点和 Master 节点通信时所使用的认证密钥。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;java \
-cp /usr/share/jenkins/slave.jar hudson.remoting.jnlp.Main \
-headless -url $JENKINS_URL $JENKINS_SECRET $JENKINS_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;img&lt;/code&gt; 工具依赖于 &lt;a href=&#34;https://github.com/opencontainers/runc/&#34; target=&#34;_blank&#34;&gt;runc&lt;/a&gt;，因此也需要把 runc 加入到镜像之中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;config.xml&lt;/code&gt; 中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;直接设置了 8081 作为 jnlp 端口，因此在 Dockerfile 中也进行了 EXPOSE 声明。&lt;/li&gt;
&lt;li&gt;开启 CSRF 选项，并设置代理服务器兼容，防止在 Kube-Proxy 的情况下无法提供服务。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;jenkins-在-kubernetes-中的启动&#34;&gt;Jenkins 在 Kubernetes 中的启动：&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RBAC：因为要启动 Worker 节点，因此在开启了 RBAC 的集群上，要给 Jenkins 的 Service Account 进行授权，允许进行 Pod 的操作。&lt;/li&gt;
&lt;li&gt;Service：需要暴露 jnlp 端口到集群内部，无需为其提供 loadbalancer 以及 nodePort 等形式的外网接入能力。&lt;/li&gt;
&lt;li&gt;可以根据需要提供加载卷，用于保存配置等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;分布式构建的配置&#34;&gt;分布式构建的配置&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;首先需要安装 Kubernetes 插件。&lt;/li&gt;
&lt;li&gt;插件安装后，就可以在 Jenkins 的 credentials 中加入 &lt;code&gt;Kubernetes ServiceAccount&lt;/code&gt; 类型的凭据，用于连接和操作 Kubernetes 集群。&lt;/li&gt;
&lt;li&gt;容器模板设置中，系统缺省会提供一个名为 jnlp 的容器模板，要想使用自己的镜像，需要进行同名覆盖。

&lt;ul&gt;
&lt;li&gt;容器模板中可以按需设置加载卷等，例如要进行镜像的 PUSH 操作，就需要将 CA 证书通过一定方法加入到容器的信任列表之中。&lt;/li&gt;
&lt;li&gt;如果要进行 IMG 的操作，需要选中容器的 &lt;code&gt;Run in privileged mode&lt;/code&gt; 选项，开启特权模式。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;任务的设置&#34;&gt;任务的设置&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;要进行镜像的构建，就非常的容易，加入几行简单的 Shell 命令，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir tmp
# 生成一个 Dokerfile
echo &amp;quot;FROM registry.docker-cn.com/library/alpine&amp;quot; &amp;gt; tmp/Dockerfile
# 构建镜像
img build -t 10.211.55.19:5000/alpine:163 tmp
# 更新证书信任列表
update-ca-certificates
# 把镜像推送到私库
img push 10.211.55.19:5000/alpine:163
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>实用 Jenkins Docker 镜像</title>
      <link>/post/my-docker-image-for-jenkins/</link>
      <pubDate>Fri, 14 Apr 2017 23:09:13 +0800</pubDate>
      <guid>/post/my-docker-image-for-jenkins/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;Jenkins 跟 GKE 的 Load Balancer 不兼容怎么办？当然是选择原谅他啊。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;最近在玩 Google 的 Container Engine，发现 Jenkins 的安装过程的安全防护跟 GKE 的负载均衡器有点不和谐。要在启动初始化过程之前，完成对 CSRF 特性的调整。弄着弄着就收不住了，所以就有了对我那个 “要你命3000” Jenkins 镜像的一次大升级。&lt;/p&gt;

&lt;h2 id=&#34;主要功能&#34;&gt;主要功能&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;分为 Alpine 和 Ubuntu 两个基础版本。&lt;/li&gt;
&lt;li&gt;内嵌多种 CI/CD 相关工具，例如 git、robotframework with selinium、mvn、nodejs 等。&lt;/li&gt;
&lt;li&gt;可越过初始化过程，直接指定既有的 config.xml。&lt;/li&gt;
&lt;li&gt;可定制的初始化 Groovy 脚本。&lt;/li&gt;
&lt;li&gt;可直接设置一号用户的用户 ID 和密码。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;更重要的功能&#34;&gt;更重要的功能&lt;/h2&gt;

&lt;p&gt;这一版加入了一个神奇的脚本：&lt;code&gt;install-plugins.sh&lt;/code&gt;，这一脚本能够自动安装指定插件极其依赖，配合上面的自定义 Groovy 脚本和 config.xml 功能。就方便的打造符合个人口味的、开箱即用的 Jenkins 镜像了，例如加入 git 支持，只需 &lt;code&gt;install-plugins.sh git&lt;/code&gt; 即可。下面的 Dockerfile 会建立一个带有 docker-build-step 和 git 支持的，用户名密码分别为 &amp;lsquo;admin&amp;rsquo; 和 &amp;lsquo;password&amp;rsquo; 的镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM dustise/jenkins
RUN install-plugins.sh git
RUN install-plugins.sh docker-build-step
ENV ADMIN_USER=&amp;quot;admin&amp;quot;
ENV ADMIN_PASSWORD=&amp;quot;password&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/fleeto/docker-jenkins&#34; target=&#34;_blank&#34;&gt;Github 项目地址&lt;/a&gt;或者用下面的命令直接运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;docker run -it -p 3001:8080 --rm
-e ADMIN_PASSWORD=&amp;quot;MY_PaSS_W0rd&amp;quot;
-e ADMIN_USER=&amp;quot;administrator&amp;quot;
--name=jenkins
dustise/jenkins:latest
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>OpenVAS 打包记</title>
      <link>/post/openvas-to-docker-image/</link>
      <pubDate>Thu, 23 Mar 2017 21:05:33 +0800</pubDate>
      <guid>/post/openvas-to-docker-image/</guid>
      <description>

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker pull dustise/openvas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/fleeto/docker-openvas.git&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上回书说到的 OpenVAS，其中的安装过程用的居然不是 Docker，其主要原因有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;第一，安装使用都不熟悉，直接上手第三方比较容易跑偏，官网靠谱一点（现在我知道了，官网也有不靠谱的）。&lt;/li&gt;
&lt;li&gt;第二，因为上面的原因，Docker Hub 上的 &lt;code&gt;mikesplain/openvas-docker&lt;/code&gt; 不得要领，尤其是客户端连接这部分。（后来知道他的强制更新高估了天朝的网络条件）。&lt;/li&gt;
&lt;li&gt;第三，我喜新厌旧，不想用老旧的 8 版本。&lt;/li&gt;
&lt;li&gt;第四，多语言支持，在上述 Docker Hub 版本上删节了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是作为一个容器云鼓吹者，不弄个顺手的封装的确是不合适的，所以花了些时间，重新来了一次，中间波折蛮多，不过也多了些容器封装方面的经验，就此记录下来。&lt;/p&gt;

&lt;h2 id=&#34;openvas-组件&#34;&gt;OpenVAS 组件&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;openvassd：OpenVAS Scanner，扫描器，会根据本地漏洞库对任务目标进行扫描，是扫描任务的执行者。&lt;/li&gt;
&lt;li&gt;openvasmd：OpenVAS Manager，管理服务，任务和各种漏洞库的管理。&lt;/li&gt;
&lt;li&gt;gsad：Greenbone Security Assistent，他不是 Open VAS 的产品，在这里用作 Web 端，和 OpenVAS Manager 协同工作。&lt;/li&gt;
&lt;li&gt;omp: 命令行客户端。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;基础镜像的选择&#34;&gt;基础镜像的选择&lt;/h2&gt;

&lt;p&gt;基于尺寸考虑，个人一般是首选 Alpine Linux。对于普通的 Java、PHP 应用，以及相对成熟的被 APK 支持的软件都会使用这个。&lt;/p&gt;

&lt;p&gt;需要编译的东西一般就比较烦了，Alpine 的一些基础包跟主流世界不太一样，一般尝试失败的话，会选择 Ubuntu/Debian 或者 CentOS 之类。最先尝试的是 CentOS，但因为库版本的问题导致编译起来问题多多，最后无奈回到 Ubuntu。&lt;/p&gt;

&lt;h2 id=&#34;守护进程问题&#34;&gt;守护进程问题&lt;/h2&gt;

&lt;p&gt;一般来说，各种服务都以后台运行，然而在容器环境下，需要挂在前台保持容器的运行，所以单进程的服务我们一般都会强制其以前台运行，多进程的服务，通常会使用 SupervisorD 来完成各个服务的启动和运行、维持等操作。&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;code&gt;docker run -it --rm ubuntu bash&lt;/code&gt;，其中可以添加环境变量等设置。&lt;/li&gt;
&lt;li&gt;可以利用 &lt;code&gt;docker cp&lt;/code&gt; 命令传输文件到运行中的容器。&lt;/li&gt;
&lt;li&gt;之后的过程和平时的编译安装并无不同，configure/cmake 等查找缺失的库，尽可能用 apt/yum/apk 进行依赖库的安装。&lt;/li&gt;
&lt;li&gt;因为要使用 OMP，所以我们需要调整 OpenVAS Manager 的启动参数，要求其暴露端口。&lt;/li&gt;
&lt;li&gt;另外，OpenVAS 提供了配置检查功能，openvas-check-setup，其中对设置会有很多建议，逐条完善即可，窃以为，这是对这个不愉快的安装过程的最大安慰。&lt;/li&gt;
&lt;li&gt;每一步都记录到前面提到的 prepare.sh 脚本之中&lt;/li&gt;
&lt;li&gt;能正常运行之后，取出脚本，编写 Dockerfile。&lt;/li&gt;
&lt;li&gt;打包测试。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;坑&#34;&gt;坑&lt;/h2&gt;

&lt;h3 id=&#34;gnupg-的问题&#34;&gt;gnupg 的问题&lt;/h3&gt;

&lt;p&gt;OpenVAS Manager 首次运行会需要利用 gnupg 来生成 Key，但是在容器环境下，随机数设备受宿主系统保护，会导致启动过程挂起，因此这里用了个土办法，生成好之后直接植入进去。&lt;/p&gt;

&lt;h3 id=&#34;manager-的-scanner-socket-参数&#34;&gt;Manager 的 Scanner Socket 参数&lt;/h3&gt;

&lt;p&gt;在 Scanner 指定了不同的 Sock 位置之后，Manager 做出相应的变更进行启动，部分操作仍然会走向缺省配置，推测是其中有一些硬编码，只是推测，并没有深入调查。&lt;/p&gt;

&lt;h3 id=&#34;sqlite-3&#34;&gt;SQLite 3&lt;/h3&gt;

&lt;p&gt;这一软件是 gsad 的一个非常硬的依赖，但是在他的编译过程中并无需求，具体症状就是 Greenbone 有两个同步进程闪退，无法执行，需要用 &amp;ndash;selftest 运行才找到问题所在。&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;p&gt;接下来 Scanner 和 Manager 都需要进行相应的更新，才能正常工作。&lt;/p&gt;

&lt;p&gt;最后，写了一个 update.all.sh 来完成这一工作。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>清理 Docker 和私库镜像</title>
      <link>/post/another-new-project-on-github/</link>
      <pubDate>Fri, 11 Nov 2016 10:27:26 +0800</pubDate>
      <guid>/post/another-new-project-on-github/</guid>
      <description>&lt;p&gt;最近一直忙些不靠谱的玩意，意外的发现， Docker 和 DevOps 的苟合之后，没有计划生育的结果就是镜像的极度膨胀，兄弟团队每天上百次的构建，让他们可怜的存储条件无法镜像爆炸的后果，因此就有了这俩工具。&lt;/p&gt;

&lt;p&gt;两个工具分别用于清理 Docker 的本地镜像以及私库镜像，Github 地址：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://github.com/fleeto/clear-registry-image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/fleeto/clear-docker-image&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;之所以没有采用 request 之类而是用的原始的命令行方式，主要是考虑降低部署依赖。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Docker DevOps：数据容器和网络</title>
      <link>/post/data-container-network-in-docker-world/</link>
      <pubDate>Mon, 04 Jul 2016 08:57:38 +0800</pubDate>
      <guid>/post/data-container-network-in-docker-world/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;Matt Saunders 具有企业和创业的双重背景，&lt;a href=&#34;http://contino.io/&#34; target=&#34;_blank&#34;&gt;Contino&lt;/a&gt; 的高级工程师和首席 DevOps 顾问。Matt 还是 [伦敦 DevOps 会议]（超过 3000 成员的月度会议）的组织者之一。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;docker-眼中数据容器的未来是怎样的-针对数据容器不应使用在复制系统上的批评-docker-如何回应呢&#34;&gt;Docker 眼中数据容器的未来是怎样的？针对数据容器不应使用在复制系统上的批评，Docker 如何回应呢？&lt;/h2&gt;

&lt;p&gt;Docker 建议持久化数据应该存储到具名卷中，现在和未来的创新会提高这一技术的稳定性和容错性。我相信多数针对数据容器的批评的焦点在于，数据容器无法在主机间迅速转移，这就意味着随 Docker 建立的数据容器只能存在于一个主机上。然而随着 Docker 1.9 的出现， &lt;a href=&#34;https://clusterhq.com/2015/10/27/flocker-plugin-for-docker/&#34; target=&#34;_blank&#34;&gt;Flocker&lt;/a&gt;、&lt;a href=&#34;https://github.com/rancher/convoy&#34; target=&#34;_blank&#34;&gt;Convoy&lt;/a&gt; 以及 &lt;a href=&#34;http://glusterfs/&#34; target=&#34;_blank&#34;&gt;GlusterFS&lt;/a&gt; 这些的插件让 Docker 能够在这些存储集群上建立卷了。&lt;/p&gt;

&lt;h2 id=&#34;docker-的网络模型-针对目前的需要-做了哪些增强呢&#34;&gt;Docker 的网络模型，针对目前的需要，做了哪些增强呢？&lt;/h2&gt;

&lt;p&gt;Docker 1.12 带来了很多改进，包括无缝的利用独立网络来启动整个应用栈的巨大进步。同样受益于插件系统，Docker 能够使用第三方的网络插件。Docker 1.12 还增强了网络安全性，利用软件定义网络，为应用提供了隔离良好的能跨越多个 Docker 主机的网络堆栈。负载均衡得到了显著增强，这是这一版本的主要特性之一。&lt;/p&gt;

&lt;h2 id=&#34;你认为最近启动的-docker-安全扫描-对企业云用户有效果么-docker-对安全的态度是怎样的&#34;&gt;你认为最近启动的 Docker 安全扫描，对企业云用户有效果么？Docker 对安全的态度是怎样的？&lt;/h2&gt;

&lt;p&gt;Docker 最大的好处，也是企业用户最大的担心。把整个应用的依赖打包在一个镜像之中，就获得了让容器有了被迁移的能力，但是与此同时也带来了透明性的损失。&lt;/p&gt;

&lt;p&gt;Docker 的安全扫描，在 DockerHub 上根据已知的软件缺陷进行评估，降低了这一风险，在 Docker Hub 上建立了私有仓库的企业将从中获益。在私有云中运行 Docker 受信仓库的用户同样也具有这方面的担忧。，我们正准备更进一步的把这一特性带给 Docker 数据中心的用户。&lt;/p&gt;

&lt;p&gt;安全是 Docker 第一优先考虑的问题，过去发行的版本在这一方面进行了持续的增强，去年还发布了 Docker 内容信任（机制？）。这使得基于 Docker 的安装越来越好的同现有安全策略进行写作。&lt;/p&gt;

&lt;h2 id=&#34;it-团队是如何看待这一新兴技术的投资回报率的&#34;&gt;IT 团队是如何看待这一新兴技术的投资回报率的？&lt;/h2&gt;

&lt;p&gt;可以用多种途径来度量投资回报率 —— 最显而易见的就是缩减了虚拟化层造成的性能浪费。Docker 能够更快的进行代码的测试和发布，也大大的加速了软件的交付过程。&lt;/p&gt;

&lt;h2 id=&#34;你认为哪些行业更愿意拥抱容器技术&#34;&gt;你认为哪些行业更愿意拥抱容器技术？&lt;/h2&gt;

&lt;p&gt;容器技术对于 Web 应用有很好的支撑能力，因此在线商务最乐于进行这方面的投入。不过这并不是排他的，任何想要提高交付速度的行业都会跟进。&lt;/p&gt;

&lt;h2 id=&#34;在初步接触容器技术的过程中-最容易犯下什么错误&#34;&gt;在初步接触容器技术的过程中，最容易犯下什么错误？&lt;/h2&gt;

&lt;p&gt;很多公司会尝试用（过去的）虚拟化同样的思路来运行容器，这当然还是会有一定的好处，但是也有其局限性。这种做法会造出肥胖的容器，是一种买椟还珠的行为。&lt;/p&gt;

&lt;h2 id=&#34;是否存在不适合-docker-的场景&#34;&gt;是否存在不适合 Docker 的场景？&lt;/h2&gt;

&lt;p&gt;目前还局限于 Linux。&lt;/p&gt;

&lt;p&gt;Windows 支持目前还比较幼稚，但是今年会有显著的增强。另外不希望迎合技术变革的企业也不应该选择 Docker —— 这一新技术对软件的生命周期会造成巨大变化，对身处其中的企业和雇员来说，都会带来很大挑战。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>靠谱的适合 Drupal DevOps 实践的镜像</title>
      <link>/post/image-for-drupal-container/</link>
      <pubDate>Wed, 01 Jun 2016 06:37:43 +0800</pubDate>
      <guid>/post/image-for-drupal-container/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;有 Drush，有 Opcache，关键设置可通过环境变量进行调整。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&#34;docker-pull-dustise-drush&#34;&gt;docker pull dustise/drush&lt;/h1&gt;

&lt;p&gt;LAMP image with drush involved.&lt;/p&gt;

&lt;h2 id=&#34;features&#34;&gt;Features&lt;/h2&gt;

&lt;h3 id=&#34;lamp-environment-for-drupal&#34;&gt;LAMP environment for Drupal&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OS&lt;/strong&gt;: Ubuntu 14.04 LTS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt;: 5.5.9&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apache&lt;/strong&gt;: 2.4.7&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP Extensions&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;mcrypt&lt;/li&gt;
&lt;li&gt;gd&lt;/li&gt;
&lt;li&gt;memcached&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opcache enabled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drush&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;path&#34;&gt;Path&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/web/codebase&lt;/code&gt;: Web Root&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/web/logs&lt;/code&gt;: Log Root&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;more-environment-variables&#34;&gt;More environment variables&lt;/h3&gt;

&lt;p&gt;Can be replaced through parameters of &lt;code&gt;docker run&lt;/code&gt; command&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_DATE_TIMEZONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;Asia/Shanghai&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MAX_EXECUTION_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;30&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MEMORY_LIMIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;128M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_ERROR_REPORTING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;E_ALL &amp;amp; ~E_DEPRECATED &amp;amp; ~E_STRICT&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_UPLOAD_MAX_FILESIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;2M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_STARTSERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MINSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;10&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXREQUESTWORKERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXCONNECTIONSPERCHILD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;0&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;300&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXKEEPALIVEREQUESTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_KEEPALIVETIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;500M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;50M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&#34;log-file-will-be-truncated&#34;&gt;Log file will be truncated&lt;/h3&gt;

&lt;p&gt;We can set max file size through the variables of error log (&lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt;) and access log (&lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt;). &lt;code&gt;rotatelog&lt;/code&gt; will truncate the log files when it exceed the limitaion.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;为 Drupal 容器化准备的一个 Docker 镜像。&lt;/p&gt;

&lt;h2 id=&#34;特性&#34;&gt;特性&lt;/h2&gt;

&lt;h3 id=&#34;适用于-drupal-的-lamp-环境&#34;&gt;适用于 Drupal 的 LAMP 环境&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;操作系统&lt;/strong&gt;：Ubuntu 14.04 LTS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt;：5.5.9&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apache&lt;/strong&gt;：2.4.7&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP 扩展&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;mcrypt&lt;/li&gt;
&lt;li&gt;memcached&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;li&gt;gd&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opcache 已启用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drush&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;路径&#34;&gt;路径&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/web/codebase&lt;/code&gt;: Web 根目录&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/web/logs&lt;/code&gt;: 日志根目录&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;可配置的环境变量&#34;&gt;可配置的环境变量&lt;/h3&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;变量名称&lt;/th&gt;
&lt;th&gt;缺省值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_DATE_TIMEZONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;Asia/Shanghai&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MAX_EXECUTION_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;30&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_MEMORY_LIMIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;128M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_ERROR_REPORTING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;E_ALL &amp;amp; ~E_DEPRECATED &amp;amp; ~E_STRICT&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PHP_UPLOAD_MAX_FILESIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;2M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_STARTSERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MINSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXSPARESERVERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;10&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXREQUESTWORKERS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXCONNECTIONSPERCHILD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;0&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;300&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAXKEEPALIVEREQUESTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;100&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_KEEPALIVETIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;5&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;500M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;ldquo;&lt;code&gt;50M&lt;/code&gt;&amp;ldquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&#34;日志的自动截断&#34;&gt;日志的自动截断&lt;/h3&gt;

&lt;p&gt;保存在 &lt;code&gt;/web/logs&lt;/code&gt; 中的日志，可以利用 &lt;code&gt;APACHE_MAX_ACCESSLOG&lt;/code&gt; 和 &lt;code&gt;APACHE_MAX_ERRORLOG&lt;/code&gt; 两个变量来调整最大文件尺寸，超出限制的日志将会被截断。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Docker 镜像：Cent OS 7 的 Yum 仓库</title>
      <link>/post/centos-7-yum-repostion-image/</link>
      <pubDate>Thu, 17 Mar 2016 22:22:31 +0800</pubDate>
      <guid>/post/centos-7-yum-repostion-image/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://github.com/fleeto/yum.centos7&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt;
&lt;a href=&#34;https://hub.docker.com/r/dustise/yum.centos7/&#34; target=&#34;_blank&#34;&gt;Dockerhub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker file, Yum repository for CentOS 7.&lt;/p&gt;

&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;

&lt;h3 id=&#34;build&#34;&gt;Build&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Build&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;docker build \
-t image.registry/yumrepo:0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Build with proxy&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;docker build \
--build-arg http_proxy=web.proxy:8080 \
--build-arg https_proxy=web.proxy:8080 \
-t image.registry/yumrepo:0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;run&#34;&gt;Run&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
docker run -d \
    -e http_proxy=&#39;web.proxy:8080&#39; \
    -e https_proxy=&#39;web.proxy:8080&#39; \
    -v /u01/repo.data/:/repo \
    -v /u01/repo.total:/repo.def \
    -p 8084:80 \
    --name=centos7yum \
    &amp;quot;image.registry/yumrepo:0.1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;/repo&lt;/code&gt;: RPMs path, we can map it to an external volume to make it persistent&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/repo.def&lt;/code&gt;: you can store &lt;code&gt;*.repo&lt;/code&gt; files into it, and it will replace the origin &lt;code&gt;total.repo&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&#34;update&#34;&gt;Update&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docker exec [container id] refresh.sh&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&#34;yum-repository-defination&#34;&gt;Yum Repository defination&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;[localrepo]
name=LAN Repository
baseurl=http://repo.server.url:8084
enabled=1
gpgcheck=0
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>优化 Dockerfile，缩减镜像尺寸</title>
      <link>/post/reduce-image-size-by-dockerfile-optimization/</link>
      <pubDate>Wed, 02 Mar 2016 05:01:53 +0800</pubDate>
      <guid>/post/reduce-image-size-by-dockerfile-optimization/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://blog.replicated.com/refactoring-a-dockerfile-for-image-size/&#34; target=&#34;_blank&#34;&gt;Refactoring a Dockerfile for image size&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker 社区最近有一篇关于镜像文件尺寸的&lt;a href=&#34;http://www.iron.io/blog/2016/01/microcontainers-tiny-portable-containers.html&#34; target=&#34;_blank&#34;&gt;文章&lt;/a&gt;。&lt;a href=&#34;https://news.ycombinator.com/item?id=11000827&#34; target=&#34;_blank&#34;&gt;Docker&lt;/a&gt; 和&lt;a href=&#34;https://github.com/docker-library/golang/issues/77#issuecomment-172597368&#34; target=&#34;_blank&#34;&gt;社区&lt;/a&gt;都提倡更小的镜像。下面列出今天 Docker Hub 上的 Top 10 镜像的尺寸（ &lt;a href=&#34;https://medium.com/@mccode/the-misunderstood-docker-tag-latest-af3babfd6375#.nzttiqglh&#34; target=&#34;_blank&#34;&gt;Latest&lt;/a&gt; ）：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;镜像名称&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;尺寸（MB）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;busybox&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;1&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;ubuntu&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;188&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;swarm&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;17&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;nginx&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;134&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;registry&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;423&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;redis&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;151&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;mysql&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;360&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;mongo&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;317&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;node&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;643&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;debian&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;125&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;使用较小的基础镜像（例如 Alphine Linux, BusyBox 等）有很多好处。这方面有不少相关文档：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.xebia.com/create-the-smallest-possible-docker-container/&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;Create the smallest possible docker container&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.librato.com/posts/docker-images&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;Docker Images&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://yasermartinez.com/blog/posts/creating-super-small-docker-images.html&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;Create super small docker images&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.brianchristner.io/docker-image-base-os-size-comparison/&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;Docker images base os size comparison&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以我假设你已经试用了其中的一个为基础镜像。接下来的事情就取决于操作者控制镜像尺寸的能力了。特别的，我们会验证一些缩减镜像大小的手段的效果，例如把多个 &lt;code&gt;RUN&lt;/code&gt; 指令中的命令集中到一行，一些关于 &lt;code&gt;apt-get&lt;/code&gt; 的技巧（例如移除 apt-get 缓存以及 &lt;code&gt;--no-install-recommends&lt;/code&gt;）&lt;/p&gt;

&lt;h2 id=&#34;在同一行中进行清理工作&#34;&gt;在同一行中进行清理工作&lt;/h2&gt;

&lt;p&gt;Docker 镜像的基础是一种&lt;a href=&#34;https://en.wikipedia.org/wiki/Aufs&#34; target=&#34;_blank&#34;&gt;层次化文件系统&lt;/a&gt;。每一层都只是包含同下面一层不同的部分。在最上方只能看到一个统一视图，而无法获知他的构建历史。Dockerfile 的每一行都会在顶部建立一个新的层。&lt;/p&gt;

&lt;p&gt;例如我们以这样一个 Dockerfile 片段开始：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;ADD https://storage.googleapis.com/golang/go1.5.3.src.tar.gz /tmp

# do some things with that file

RUN rm /tmp/go1.5.3.src.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;你可以能会觉得删除 &lt;code&gt;.tar.gz&lt;/code&gt; 文件是个负责任的好办法。但是包含这一文件的层还遗留在镜像之中。&lt;code&gt;rm&lt;/code&gt; 操作只是对最终镜像隐藏了这一文件，使用 &lt;code&gt;docker pull&lt;/code&gt; 获取这一镜像时还是会下载这些内容的。&lt;/p&gt;

&lt;p&gt;更好的办法就是把这些操作集中到一行之中，这样就不会提交多个层了。例如，对上面的指令进行简单的改写：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;RUN curl -o \
        /tmp/go.1.5.3.src.tar.gz \
        https://storage.googleapis.com/golang/go1.5.3.src.tar.gz &amp;amp;&amp;amp; \
      &amp;lt;do some things with the file&amp;gt; &amp;amp;&amp;amp; \
      rm /tmp/go1.5.3.src.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样看起来有点丑，不过能够优化镜像尺寸。如果你讨厌这种写法，可以建立一个脚本，然后用 &lt;code&gt;ADD&lt;/code&gt;、&lt;code&gt;RUN&lt;/code&gt; 来在 Dokerfile 中运行。&lt;/p&gt;

&lt;h2 id=&#34;用正确的方式移除-apt-yum-缓存&#34;&gt;用正确的方式移除 apt/yum 缓存&lt;/h2&gt;

&lt;p&gt;多数 Dockerfile 作者都知道应该 &lt;code&gt;apt-get remove&lt;/code&gt; 所有不必要的包。一个例子就是，用 curl/wget 来进行下载安装其他软件。可以事后使用 &lt;code&gt;apt-get remove curl&lt;/code&gt;，但是同上面的例子一样，curl 所在的层已经被封装到镜像之中，因此也应该把删除（包括相关的自动安装的依赖包）工作放到同一行中。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&#34;一个实际的例子&#34;&gt;一个实际的例子。&lt;/h2&gt;

&lt;p&gt;这是一个简化版本的用于运行 Python 服务的 Dokerfile，我们将对他进行优化。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl python-pip

RUN pip install requests

ADD ./my_service.py /my_service.py
ENTRYPOINT [&amp;quot;python&amp;quot;, &amp;quot;/my_service.py&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;myservice.py&lt;/code&gt; 是一个 Python 脚本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;#!/usr/bin/python
print &#39;Hello, world!&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来 build 并检查尺寸：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo docker build -t size .
$ sudo docker images
REPOSITORY      TAG           IMAGE ID            CREATED           VIRTUAL SIZE
size            latest        da8a9be731ac        4 seconds ago     360.5 MB
ubuntu          14.04         6cc0fc2a5ee3        2 weeks ago       187.9 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;188 MB 的基础镜像已经很吓人了，不过更值得注意的是，只是一个 hello-world 的 Python 脚本为什么会导致镜像尺寸倍增。这 360.5 MB 中到底有什么？其中包含了最上一层（本例中是 da8a9be731ac），以及用于建立顶层的所有的层的内容。&lt;/p&gt;

&lt;h3 id=&#34;添加一个清理层&#34;&gt;添加一个清理层&lt;/h3&gt;

&lt;p&gt;我们也许应该做一些清理工作，我们试试这样的 Dockerfile ：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl python-pip

RUN pip install requests

## Clean up
RUN apt-get remove -y python-pip curl
RUN rm -rf /var/lib/apt/lists/*

ADD ./my_service.py /my_service.py
ENTRYPOINT [&amp;quot;python&amp;quot;, &amp;quot;/my_service.py&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Build，然后再看尺寸：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo docker build -t size .
$ sudo docker images
REPOSITORY      TAG           IMAGE ID            CREATED           VIRTUAL SIZE
size            latest        c6dacdd00660        2 seconds ago     361.3 MB
ubuntu          14.04         6cc0fc2a5ee3        2 weeks ago       187.9 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;事与愿违，镜像更大了。&lt;/p&gt;

&lt;h3 id=&#34;在同一层进行清理&#34;&gt;在同一层进行清理&lt;/h3&gt;

&lt;p&gt;接下来试试把 APT 操作进行合并：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM ubuntu:14.04
RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y curl python-pip &amp;amp;&amp;amp; \
    pip install requests &amp;amp;&amp;amp; \
    apt-get remove -y python-pip curl &amp;amp;&amp;amp; \
    rm -rf /var/lib/apt/lists/*

ADD ./my_service.py /my_service.py
ENTRYPOINT [&amp;quot;python&amp;quot;, &amp;quot;/my_service.py&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Build 然后查看镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo docker build -t size .
$ sudo docker images
REPOSITORY      TAG           IMAGE ID            CREATED           VIRTUAL SIZE
size            latest        e531f8674f33        9 seconds ago     338 MB
ubuntu          14.04         6cc0fc2a5ee3        2 weeks ago       187.9 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;果然小了一点点 —— 只是一点点。&lt;/p&gt;

&lt;h3 id=&#34;进一步优化-apt&#34;&gt;进一步优化 APT&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;apt-get install&lt;/code&gt; 过程中加入了很多 &lt;code&gt;推荐&lt;/code&gt; 内容。&lt;a href=&#34;http://unix.stackexchange.com/questions/77053/apt-installing-more-packages-than-specified-as-dependencies&#34; target=&#34;_blank&#34;&gt;推荐包&lt;/a&gt;只是简单的加入了一些可有可无的依赖。有些用户会因为特殊的应用方式，或者环境要求才需要这些东西，换句话说，这些推荐内容并非必要。&lt;/p&gt;

&lt;p&gt;在 Ubuntu 14.04 中运行 PIP，很容易就可以得出结论：删除推荐包并不会有什么副作用。这一点在产品发布之前是完全可以确认的。可以在 Docker Hub 上看看 &lt;a href=&#34;https://github.com/docker-library/redis/blob/b375650fb69b7db819e90c0033433c705b28656e/3.0/Dockerfile#L6&#34; target=&#34;_blank&#34;&gt;redis&lt;/a&gt;，&lt;a href=&#34;https://github.com/docker-library/mysql/blob/ed198ce2e8aa78613c615f20c5c4dd09fa450f66/5.7/Dockerfile#L13&#34; target=&#34;_blank&#34;&gt;mysql&lt;/a&gt;，&lt;a href=&#34;https://github.com/docker-library/mongo/blob/7da0e6d6520607c99d40cf71a2e4b0a2da0beca9/3.2/Dockerfile#L7&#34; target=&#34;_blank&#34;&gt;mongo&lt;/a&gt;，&lt;a href=&#34;https://github.com/docker-library/postgres/blob/916a840510b481e7d3f0f74fa04fde3edfdfbd04/9.5/Dockerfile#L9&#34; target=&#34;_blank&#34;&gt;postgres&lt;/a&gt;，&lt;a href=&#34;https://github.com/docker-library/elasticsearch/blob/5b2bf54e2c17a8e2e1b062ea0d071eae600bfec2/2.2/Dockerfile#L23&#34; target=&#34;_blank&#34;&gt;elasticsearch&lt;/a&gt; 等的镜像，使用这一技巧来缩减镜像尺寸。&lt;/p&gt;

&lt;p&gt;实际操作一下带有 &lt;code&gt;--no-install-recommends&lt;/code&gt; 选项的 APT-GET：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM ubuntu:14.04
RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y --no-install-recommends curl python-pip &amp;amp;&amp;amp; \
    pip install requests &amp;amp;&amp;amp; \
    apt-get remove -y python-pip curl &amp;amp;&amp;amp; \
    rm -rf /var/lib/apt/lists/*

ADD ./my_service.py /my_service.py
ENTRYPOINT [&amp;quot;python&amp;quot;, &amp;quot;/my_service.py&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Build，再次检查尺寸：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;REPOSITORY      TAG           IMAGE ID            CREATED           VIRTUAL SIZE
size            latest        fddc30aee4dc        6 seconds ago     229.2 MB
ubuntu          14.04         6cc0fc2a5ee3        2 weeks ago       187.9 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这次我们把镜像缩小了 120 MB。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Dockerfile 的语法非常简单，也同样有优化的需求，因此在组织中应用 Docker 时，需要为 Dockerfile 建立健全相应的策略来优化这一过程。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>CentOS 7 &#43; Kubernetes 1.1.x  &#43; Docker 1.9.x  安装指南</title>
      <link>/post/install-kubernetes-and-docker-on-centos/</link>
      <pubDate>Thu, 24 Dec 2015 00:17:17 +0800</pubDate>
      <guid>/post/install-kubernetes-and-docker-on-centos/</guid>
      <description>

&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;http://docker.io/&#34; target=&#34;_blank&#34;&gt;Docker&lt;/a&gt; 和 &lt;a href=&#34;http://kubernetes.io/&#34; target=&#34;_blank&#34;&gt;Kubernates&lt;/a&gt;最近可以说红的发紫，各种大部头和高深研究也层出不穷。&lt;/p&gt;

&lt;p&gt;学习过程中看了官网和社区提供的不少起步文档，但是手工半手工这一块，总感觉语焉不详，操作性不很好，因此就边学边记，整理成这么一篇东西，本文只涉及操作，理论、架构、前景等内容，还请读者自行翻阅相关材料。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意这里按照我的个人习惯，会把所有可执行文件复制到 &lt;code&gt;/usr/local/share&lt;/code&gt;，并链接到 &lt;code&gt;/usr/local/bin&lt;/code&gt;；环境配置文件统一放置到 &lt;code&gt;/etc/sysconfig/kubernetes&lt;/code&gt; 中，这个做法跟手工安装一样，纯属个人恶趣味，没什么具体理由。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;p&gt;这里我们使用的是 CentOS 7 为例子。&lt;/p&gt;

&lt;p&gt;接下来内容里面会使用 A, B 两台服务器：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;服务器 A

&lt;ul&gt;
&lt;li&gt;IP: 10.211.55.12&lt;/li&gt;
&lt;li&gt;职责&lt;/li&gt;
&lt;li&gt;Kubernate Master&lt;/li&gt;
&lt;li&gt;Docker Private Registry&lt;/li&gt;
&lt;li&gt;Kubernate Master UI&lt;/li&gt;
&lt;li&gt;Kubernate Node&lt;/li&gt;
&lt;li&gt;ETCD&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;服务器 B

&lt;ul&gt;
&lt;li&gt;IP: 10.211.55.13&lt;/li&gt;
&lt;li&gt;职责&lt;/li&gt;
&lt;li&gt;Kubernate Node&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;etcd-仅-master-需要&#34;&gt;ETCD （仅 Master 需要）&lt;/h3&gt;

&lt;p&gt;可以简单理解为，用来管理容器 IP 的数据库。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/coreos/etcd&#34; target=&#34;_blank&#34;&gt;Github 地址&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;使用 Yum 直接安装：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yum install -y etcd;&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：安装后需要修改 &lt;code&gt;/etc/etcd/etcd.conf&lt;/code&gt;，将其中的监听地址由 &lt;code&gt;127.0.0.1&lt;/code&gt; 改为 &lt;code&gt;0.0.0.0&lt;/code&gt; 或者其他 Node 可以访问的地址。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 启动服务
systemctl daemon-reload
systemctl enable etcd
systemctl start etcd

# 为后续服务提供初始值
etcdctl mk /docker.intranet/network/config &#39;{&amp;quot;Network&amp;quot;:&amp;quot;192.168.0.0/16&amp;quot;}&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;flannel-所有服务器都需要&#34;&gt;Flannel (所有服务器都需要)&lt;/h3&gt;

&lt;p&gt;对前面的 ETCD 有依赖，这里利用这一服务来为 Docker 提供网络分配和部分网络参数生成的任务。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yum install -y flannel&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;接下来编辑配置文件 &lt;code&gt;/etc/sysconfig/flanneld&lt;/code&gt;，将其中的 &lt;code&gt;FLANNEL_ETCD&lt;/code&gt; 地址修改为之前我们配置的地址，并修改 &lt;code&gt;FLANNEL_ETCD_KEY&lt;/code&gt; 为我们使用 etcdctl 设置的值，上文中是 &lt;strong&gt;&amp;ldquo;docker.intranet&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 启动服务
systemctl daemon-reload
systemctl enable flanneld
systemctl start flanneld
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;h2 id=&#34;kubernates-master&#34;&gt;Kubernates Master&lt;/h2&gt;

&lt;h3 id=&#34;下载和安装&#34;&gt;下载和安装&lt;/h3&gt;

&lt;p&gt;首先下载压缩包，并复制其中需要的文件，然后做符号链接到习惯位置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;wget https://github.com/kubernetes/kubernetes/releases/download/v1.1.3/kubernetes.tar.gz
cd kubernetes/server
tar xf kubernetes-server-linux-amd64.tar.gz
cp -Rf kubernetes/ /usr/local/share
cp kubernetes/cluster/centos/node/scripts/remove-docker0.sh /usr/local/share/kubernetes/server/bin
cd /usr/local/bin
ln -s /usr/local/share/kubernetes/server/bin/hyperkube
ln -s /usr/local/share/kubernetes/server/bin/kube-apiserver
ln -s /usr/local/share/kubernetes/server/bin/kube-controller-manager
ln -s /usr/local/share/kubernetes/server/bin/kubectl
ln -s /usr/local/share/kubernetes/server/bin/kubelet
ln -s /usr/local/share/kubernetes/server/bin/kube-proxy
ln -s /usr/local/share/kubernetes/server/bin/kube-scheduler
ln -s /usr/local/share/kubernetes/server/bin/linkcheck
ln -s /usr/local/share/kubernetes/server/bin/remove-docker0.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;主控服务器 A 除去前面提到的 ETCD 和 FlannelD 两个服务之外，需要三个服务&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Server&lt;/li&gt;
&lt;li&gt;Controller Manager&lt;/li&gt;
&lt;li&gt;Scheduler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;前面下载的包解压之后，&lt;code&gt;kubernetes/cluster/centos/master/scripts&lt;/code&gt; 中有以下脚本分别对应上面三个必要的服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apiserver.sh&lt;/li&gt;
&lt;li&gt;controller-manager.sh&lt;/li&gt;
&lt;li&gt;scheduler.sh&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;这几个脚本大概看了一下，是自动安装过程的一部分，这里拿来进行修改，以适应我们自己的环境，方便应用，也有利于学习，并最终生成一个符合自己洁癖的环境:D&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;apiserver-sh&#34;&gt;apiserver.sh&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MASTER_ADDRESS&lt;/code&gt; 取值为服务器 A 的地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ETCD_SERVERS&lt;/code&gt; 取值为ETCD的完整网址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SERVICE_CLUSTER_IP_RANGE&lt;/code&gt; 中的内容，按照之前我们给 ETCD 初始化的 IP 范围来设置。&lt;/li&gt;
&lt;li&gt;修改过时用法：&lt;code&gt;KUBE_API_ADDRESS=&amp;quot;--insecure-bind-address=${MASTER_ADDRESS}&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修改过时用法：&lt;code&gt;KUBE_API_PORT=&amp;quot;--insecure-port=8080&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF&lt;/code&gt; 所在行涉及的文件名，按照个人习惯更改&lt;/li&gt;
&lt;li&gt;用于服务定义的 service 文件，其中的 &lt;code&gt;ExecStart&lt;/code&gt; 要注意修改为我们之前生成连接的位置。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后把涉及证书的一块删掉，这部分内容比较繁杂，先砍掉为好&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;KUBE_APISERVER_OPTS=&amp;quot;   \${KUBE_LOGTOSTDERR}         \\
                        \${KUBE_LOG_LEVEL}           \\
                        \${KUBE_ETCD_SERVERS}        \\
                        \${KUBE_API_ADDRESS}         \\
                        \${KUBE_API_PORT}            \\
                        \${MINION_PORT}              \\
                        \${KUBE_ALLOW_PRIV}          \\
                        \${KUBE_SERVICE_ADDRESSES}   \\&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;编辑结束之后，运行该脚本，也就完成了 API SERVER 的配置。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;语法错误等情况是在所难免的，可以使用 &lt;code&gt;systemctl status -l [服务名称]&lt;/code&gt; 来检查出错信息，如果信息不够详尽，可以把所生成的环境文件中的 &lt;code&gt;loglevel&lt;/code&gt; 设置为 0。&lt;/p&gt;

&lt;p&gt;另外还可以直接把 ExecStart 中的最终内容直接在命令行执行进行除错。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;controller-manager-sh-和-scheduler-sh&#34;&gt;controller-manager.sh 和 scheduler.sh&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;修改 &lt;code&gt;MASTER_ADDRESS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修改文件位置&lt;/li&gt;
&lt;li&gt;执行&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;走到这里，就可以使用 &lt;code&gt;kubectl get nodes&lt;/code&gt; 之类的命令来跟 Master 互动了，浏览器也可以在服务器的 8080 端口获得一点没用的基本信息了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;docker&#34;&gt;Docker&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;本不想在 Master 机安装 Docker，但是后面的 Kubernates UI，如果不涉及网络操作的话，似乎必须在 Master 上运行，所以只能如此处理了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Docker 这里我们使用 Docker.io 提供的官方源进行安装：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# !bin/sh
tee /etc/yum.repos.d/docker.repo &amp;lt;&amp;lt;-&#39;EOF&#39;
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF

yum update -y
yum install -y docker-engine
systemctl daemon-reload
systemctl enable docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个配置就不准备使用脚本生成了，会稍微麻烦一些，简单粗暴上代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;首先创建文件 &lt;code&gt;/etc/systemd/system/docker.service.d/custom.conf&lt;/code&gt; 这个文件会覆盖 docker.service 中的部分内容&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-conf&#34;&gt;[Service]
Environment=&amp;quot;HTTP_PROXY=10.211.55.2:8016&amp;quot; &amp;quot;NO_PROXY=localhost,center.docker.local,16.158.51.247&amp;quot;
EnvironmentFile=-/run/flannel/docker
EnvironmentFile=-/etc/sysconfig/kubernetes/docker
ExecStartPre=/usr/local/bin/remove-docker0.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上面文件中包含了代理的设置，用于在公司内网环境下，使用代理服务器获取外部仓库的内容。
两行&lt;code&gt;EnvironmentFile&lt;/code&gt;，其中一个是我们自行编写的 Docker 环境文件，另一个则是 flannel 运行生成的配置文件，而 &lt;code&gt;ExecStartPre&lt;/code&gt; 则是从 Kubernetes 压缩包中的 Docker 配置内容中抄来的，修改了文件位置而已。&lt;/p&gt;

&lt;p&gt;上面看出，Docker 服务对 Flanneld 服务是有依赖的，经过对 &lt;code&gt;systemctl status docker&lt;/code&gt; 的观察，可以看到这一服务在 &lt;code&gt;/usr/lib/systemd/system/docker.service.d&lt;/code&gt; 生成了一份文件，用于声明这一依赖关系。&lt;/p&gt;

&lt;p&gt;编辑这个文件：&lt;code&gt;/etc/systemd/system/multi-user.target.wants/docker.service&lt;/code&gt;
如果位置不同，可以使用 &lt;code&gt;find /etc -name &#39;docker.service&#39;&lt;/code&gt;，ExecStart 一行改为 &lt;code&gt;ExecStart=/usr/bin/docker daemon $DOCKER_OPT_BIP $DOCKER_OPT_MTU $DOCKER_OPTS&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;systemctl daemon-reload
systemctl enable docker
systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;node&#34;&gt;Node&lt;/h2&gt;

&lt;p&gt;同样的，可以在 &lt;code&gt;kubernetes/cluster/centos/node/scripts&lt;/code&gt; 找到两个 Node 服务的配置脚本，这里先后修改运行 &lt;code&gt;kubelet.sh&lt;/code&gt; 以及 &lt;code&gt;proxy.sh&lt;/code&gt; 即可。&lt;/p&gt;

&lt;h2 id=&#34;kubeletes-ui&#34;&gt;Kubeletes UI&lt;/h2&gt;

&lt;p&gt;Kubeletes UI 的安装很简单：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl create -f cluster/addons/kube-ui/kube-ui-rc.yaml --namespace=kube-system
kubectl create -f cluster/addons/kube-ui/kube-ui-svc.yaml --namespace=kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行之后，可以使用 &lt;code&gt;kubectl get pods --all-namespace&lt;/code&gt; 查看进展状况，第一次创建，会到 Google 仓库下载基础镜像，前面介绍的代理使用技巧可能就派上用场了。等到状态从 Pending 变为 Running 之后，就可以用浏览器访问 &lt;code&gt;http://master:8080/ui&lt;/code&gt; 来查看控制台了。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
