<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; flannel</title>
	<atom:link href="http://tonybai.com/tag/flannel/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Fri, 17 Apr 2026 23:55:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>如何在Ubuntu 18.04 Server上部署Kubernetes集群</title>
		<link>https://tonybai.com/2019/10/21/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04/</link>
		<comments>https://tonybai.com/2019/10/21/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04/#comments</comments>
		<pubDate>Mon, 21 Oct 2019 15:26:04 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[cni]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[fstab]]></category>
		<category><![CDATA[gpg]]></category>
		<category><![CDATA[hostname]]></category>
		<category><![CDATA[join]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[sudo]]></category>
		<category><![CDATA[swap]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2797</guid>
		<description><![CDATA[如今，你几乎不可避免地会听到来自Kubernetes的发声，你更没有充分的理由拒绝去听。 一旦一切就绪，这个强大的容器编排工具将以您难以想象的敏捷性来扩展您的操作。 为了实际使用Kubernetes进行部署和管理容器，您首先必须创建Kubernetes服务器集群。 一旦集群建立后，您就能够部署，扩展和管理您的容器化应用程序了。 在Ubuntu Server 18.04的帮助下，我将引导您完成此过程。 我们至少需要2个Ubuntu Server 18.04实例和一个具有sudo特权的用户帐户才能完成此工作。 您需要确保所有计算机都已做完更新（使用sudo apt-get update和sudo apt-get upgrade -y命令）。 还需要一些时间，大约30分钟左右。 我将在两台机器上进行示范操作：一个master节点和一个worker节点。 我们开始吧！ 安装Docker master节点和worker节点上都需要进行下面的操作。 我们要做的第一件事就是安装Docker。要安装docker，先要登录到Server上，输入并执行下面命令： sudo apt-get install docker.io 一旦docker安装成功后，你需要将你的账号添加到docker组中(否则，每次运行docker命令，都需要带上sudo，这可能导致安全问题）。执行下面命令将你的账号添加到docker组中： sudo usermod -aG docker $USER 登出并重新登录，这样上述配置变化才能生效。 启动并使能docker后台驻留程序： sudo systemctl start docker sudo systemctl enable docker 安装Kubernetes 现在我们需要在所有节点上安装Kubernetes了。首先，我们通过下面命令添加Kubernetes GPG key： curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg &#124; sudo apt-key add 如果没有安装curl，可以通过下面命令安装： sudo [...]]]></description>
			<content:encoded><![CDATA[<p>如今，你几乎不可避免地会听到来自<a href="https://tonybai.com/tag/kubernetes">Kubernetes</a>的发声，你更没有充分的理由拒绝去听。 一旦一切就绪，这个强大的容器编排工具将以您难以想象的敏捷性来扩展您的操作。</p>
<p>为了实际使用Kubernetes进行部署和管理容器，您首先必须<a href="https://tonybai.com/2018/06/13/setup-efk-on-kubernetes-1-10-3-in-the-hard-way/">创建Kubernetes服务器集群</a>。 一旦集群建立后，您就能够部署，扩展和管理您的容器化应用程序了。</p>
<p>在<a href="https://tonybai.com/tag/ubuntu">Ubuntu</a> Server 18.04的帮助下，我将引导您完成此过程。 我们至少需要2个<a href="http://releases.ubuntu.com/18.04/">Ubuntu Server 18.04</a>实例和一个具有sudo特权的用户帐户才能完成此工作。 您需要确保所有计算机都已做完更新（使用sudo apt-get update和sudo apt-get upgrade -y命令）。</p>
<p>还需要一些时间，大约30分钟左右。</p>
<p>我将在两台机器上进行示范操作：一个master节点和一个worker节点。</p>
<p>我们开始吧！</p>
<h2>安装<a href="https://tonybai.com/tag/docker">Docker</a></h2>
<p>master节点和worker节点上都需要进行下面的操作。</p>
<p>我们要做的第一件事就是安装Docker。要安装docker，先要登录到Server上，输入并执行下面命令：</p>
<pre><code>sudo apt-get install docker.io
</code></pre>
<p>一旦docker安装成功后，你需要将你的账号添加到docker组中(否则，每次运行docker命令，都需要带上sudo，这可能导致安全问题）。执行下面命令将你的账号添加到docker组中：</p>
<pre><code>sudo usermod -aG docker $USER
</code></pre>
<p>登出并重新登录，这样上述配置变化才能生效。</p>
<p>启动并使能docker后台驻留程序：</p>
<pre><code>sudo systemctl start docker
sudo systemctl enable docker
</code></pre>
<h2><a href="https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part1/">安装Kubernetes</a></h2>
<p>现在我们需要在所有节点上安装Kubernetes了。首先，我们通过下面命令添加Kubernetes GPG key：</p>
<pre><code>curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
</code></pre>
<p>如果没有安装curl，可以通过下面命令安装：</p>
<pre><code>sudo apt-get install curl -y
</code></pre>
<p>接下来，添加必要的仓库：</p>
<pre><code>sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
</code></pre>
<p>通过下面命令安装必要的软件：</p>
<pre><code>sudo apt-get install kubeadm kubelet kubectl -y
</code></pre>
<p>上面的命令将自动安装目标软件以及它们的依赖。</p>
<h2>主机名</h2>
<p>为了让后续操作更简单，我们通过下面命令为每个server赋予新的主机名。</p>
<pre><code>sudo hostnamectl set-hostname HOSTNAME
</code></pre>
<blockquote>
<p>HOSTNAME是主机的名字</p>
</blockquote>
<p>你可以使用下面这些主机名：</p>
<ul>
<li>kubemaster</li>
<li>node1</li>
<li>node2</li>
<li>node3</li>
<li>其他等等</li>
</ul>
<p>登出并重新登录。最后，将主机名映射为IP地址。通过下面命令打开host文件并编辑：</p>
<pre><code>sudo nano /etc/hosts
</code></pre>
<p>在文件末尾附加类似下面的内容（保证你的主机名与其正确的ip一一对应）：</p>
<pre><code>192.168.1.218 kubemaster
192.168.1.219 kubenode1
192.168.1.220 kubenode2
</code></pre>
<p>关闭并保存文件。</p>
<h2>关闭Swap</h2>
<p>要运行kubernetes，你必须首先关闭swap。我们可以通过下面命令来做到这一点：</p>
<pre><code>sudo swapoff -a
</code></pre>
<p>如果要使修改永久生效(否则，下次重启时，swap会重新启用)，执行下面命令：</p>
<pre><code>sudo nano /etc/fstab
</code></pre>
<p>在fstab文件中，将swap入口注释掉，如下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04-1.jpg" alt="img{512x368}" /><br />
图: 通过fstab关闭swap</p>
<h2>初始化Master</h2>
<p>接下来，我们通过下面命令来初始化master节点。</p>
<pre><code>sudo kubeadm init --pod-network-cidr=192.168.1.90/16
</code></pre>
<p>初始化结束后，你将看到将worker node加入集群的精确命令(如下图)，保证要拷贝下该命令：</p>
<p><img src="https://tonybai.com/wp-content/uploads/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04-2.jpg" alt="img{512x368}" /><br />
图: 将worker node加入master的命令</p>
<p>仅在master上创建下面目录：</p>
<pre><code>mkdir -p $HOME/.kube
</code></pre>
<p>将相应的配置你文件拷贝到该目录下：</p>
<pre><code>sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
</code></pre>
<p>赋予配置文件适当的权限：</p>
<pre><code>sudo chown $(id -u):$(id -g) $HOME/.kube/config
</code></pre>
<h2>部署Pod网络</h2>
<p>在将worker加入到master之前，你必须先部署pod网络(否则，所有事情都无法按照预期那样正常工作）。<a href="https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/">Flannel</a>是可选的Pod网络之一。我们通过下面命令安装它（仅在master运行下面命令)：</p>
<pre><code>sudo kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</code></pre>
<h2>将worker node加入到master</h2>
<p>现在我们具备将worker node加入master的条件了。登录到worker node上，执行类型下面的命令：</p>
<pre><code>sudo kubeadm join 192.168.1.190:6443 --token bzbwl4.ll5o9x3jjhqqwofa --discovery-token-ca-cert-hash sha256:ecb0223a05be3502c2d102f3e56104b10fcd105430eb723d3b3e816618323d73
</code></pre>
<p>在每个worker node上执行join命令。一旦join命令执行成功，返回master node执行下面命令：</p>
<pre><code>kubectl get nodes
</code></pre>
<p>你可以看到所有加入到集群的worker node列表：</p>
<p><img src="https://tonybai.com/wp-content/uploads/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04-3.jpg" alt="img{512x368}" /><br />
图: 我们的node已经加入并处于ready状态</p>
<p>到此，kubernetes集群已经就绪并可以部署你的第一个容器化的应用或服务了。不要忘了，如果你要加入更多worker node（提高伸缩能力），你需要join命令。如果你忘记保存之前那个join命令了，你可以在任何时候通过下面命令获取它：</p>
<pre><code>kubeadm token create --print-join-command
</code></pre>
<p>上面命令将输出join命令，在你的新worker node上执行它即可。</p>
<p>本文翻译自<a href="https://thenewstack.io/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04/">《How to Deploy a Kubernetes Cluster with Ubuntu Server 18.04》</a></p>
<p>文本首次发表在<a href="https://www.imooc.com/article/293956">慕课网</a>上。</p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2019, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2019/10/21/how-to-deploy-a-kubernetes-cluster-with-ubuntu-server-18-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Kubernetes网络插件（CNI）基准测试的最新结果</title>
		<link>https://tonybai.com/2019/04/18/benchmark-result-of-k8s-network-plugin-cni/</link>
		<comments>https://tonybai.com/2019/04/18/benchmark-result-of-k8s-network-plugin-cni/#comments</comments>
		<pubDate>Thu, 18 Apr 2019 13:32:29 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[calico]]></category>
		<category><![CDATA[canal]]></category>
		<category><![CDATA[cilium]]></category>
		<category><![CDATA[cni]]></category>
		<category><![CDATA[configmap]]></category>
		<category><![CDATA[egress]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[ingress]]></category>
		<category><![CDATA[iperf3]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kops]]></category>
		<category><![CDATA[kube-router]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubespray]]></category>
		<category><![CDATA[MTU]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[rancher]]></category>
		<category><![CDATA[romana]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[UDP]]></category>
		<category><![CDATA[weavenet]]></category>
		<category><![CDATA[加密]]></category>
		<category><![CDATA[基准测试]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[插件]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2702</guid>
		<description><![CDATA[本文翻译自Alexis Ducastel的文章《Benchmark results of Kubernetes network plugins (CNI) over 10Gbit/s network (Updated: April 2019)》。 本文是我之前的基准测试的最新更新，这次测试在最新版Kubernetes 1.14上运行，其中CNI版本在2019年4月更新。 首先，非常感谢Cilium团队对我的帮助，包括协助审查测试结果以及更正我的指标监控脚本。 自2018年11月以来都有哪些新变化 如果你只是想知道自上次以来发生的变化，这里有一个简短的总结： Flannel仍然是CNI竞赛中最快和最精简的那个选手，但它仍然不支持NetworkPolicies(网络策略)，也不支持加密。 Romana不再维护，因此我们决定将其从基准测试中剔除。 WeaveNet现在同时支持Ingress和Egress的NetworkPolicies！但性能要略低于之前的版本。 如果您想获得最佳性能，Calico仍需要手动定制MTU。Calico为安装CNI提供了两个新选项，无需专用ETCD存储： 将状态存储在Kubernetes API中作为数据存储区（集群&#60;50个节点） 使用Typha代理将状态存储在Kubernetes API中，以减轻K8S API（集群> 50个节点）的压力 Calico宣布在Istio之上支持应用层策略(Application Layer Policy)，为应用层带来安全性。 Cilium现在支持加密！Cilium使用IPSec隧道提供加密，并为WeaveNet提供了加密网络的替代方案。但是，在启用加密的情况下，WeaveNet比Cilium更快。这是由于Cilium 1.4.2仅支持CBC加密，若使用GCM将会更好，但它将是1.5版本的Cilium的一部分。 由于嵌入了ETCD operator，因此Cilium现在更容易部署。 Cilium团队还通过降低内存消耗和CPU成本，努力减少CNI占用空间。但他们仍然比其他选手更重。 基准测试的上下文 基准测试是在通过Supermicro 10Gbit交换机连接的三台Supermicro裸机服务器上进行的。服务器通过DAC SFP +无源电缆直接连接到交换机，并在激活巨型帧（MTU 9000）的同一VLAN中设置。 Kubernetes 1.14.0​在Ubuntu 18.04 LTS上运行，运行Docker 18.09.2（此linux版本中的默认docker版本）。 为了提高可重复性，我们选择始终在第一个节点上设置master，在第二个服务器上设置基准测试的服务器部分，在第三个服务器上设置客户端部分。这是通过Kubernetes deployments中的NodeSelector实现的。 以下是我们将用于描述基准测试结果和解释的表情图： 为基准测试选择CNI 这个基准测试仅仅关注那些入选kubernetes正式文档：“create a single [...]]]></description>
			<content:encoded><![CDATA[<p>本文翻译自Alexis Ducastel的文章<a href="https://itnext.io/benchmark-results-of-kubernetes-network-plugins-cni-over-10gbit-s-network-updated-april-2019-4a9886efe9c4">《Benchmark results of Kubernetes network plugins (CNI) over 10Gbit/s network (Updated: April 2019)》</a>。</p>
<p>本文是我<a href="https://itnext.io/benchmark-results-of-kubernetes-network-plugins-cni-over-10gbit-s-network-36475925a560">之前的基准测试</a>的最新更新，这次测试在最新版<a href="https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement/">Kubernetes 1.14</a>上运行，其中CNI版本在2019年4月更新。</p>
<p>首先，非常感谢<a href="https://cilium.io/">Cilium团队</a>对我的帮助，包括协助审查测试结果以及更正我的指标监控脚本。</p>
<h2>自2018年11月以来都有哪些新变化</h2>
<p>如果你只是想知道自上次以来发生的变化，这里有一个简短的总结：</p>
<p><a href="https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/">Flannel</a>仍然是<a href="https://github.com/containernetworking/cni">CNI</a>竞赛中最快和最精简的那个选手，但它仍然不支持NetworkPolicies(网络策略)，也不支持加密。</p>
<p><a href="https://github.com/romana/cni">Romana</a>不再维护，因此我们决定将其从基准测试中剔除。</p>
<p><a href="https://github.com/weaveworks/weave">WeaveNet</a>现在同时支持<a href="https://tonybai.com/2018/06/21/kubernetes-ingress-controller-practice-using-four-examples/">Ingress</a>和Egress的NetworkPolicies！但性能要略低于之前的版本。</p>
<p>如果您想获得最佳性能，<a href="https://www.projectcalico.org/">Calico</a>仍需要手动定制MTU。Calico为安装CNI提供了两个新选项，无需专用<a href="https://tonybai.com/tag/etcd">ETCD存储</a>：</p>
<ul>
<li>将状态存储在Kubernetes API中作为数据存储区（集群&lt;50个节点）</li>
<li>使用Typha代理将状态存储在Kubernetes API中，以减轻K8S API（集群> 50个节点）的压力</li>
</ul>
<p>Calico宣布在<a href="https://tonybai.com/2018/01/03/an-intro-of-microservices-governance-by-istio/">Istio</a>之上支持应用层策略(Application Layer Policy)，为应用层带来安全性。</p>
<p>Cilium现在支持加密！Cilium使用IPSec隧道提供加密，并为WeaveNet提供了加密网络的替代方案。但是，在启用加密的情况下，WeaveNet比Cilium更快。这是由于Cilium 1.4.2仅支持CBC加密，若使用GCM将会更好，但它将是1.5版本的Cilium的一部分。</p>
<p>由于嵌入了ETCD operator，因此Cilium现在更容易部署。</p>
<p>Cilium团队还通过降低内存消耗和CPU成本，努力减少CNI占用空间。但他们仍然比其他选手更重。</p>
<h2>基准测试的上下文</h2>
<p>基准测试是在通过Supermicro 10Gbit交换机连接的三台Supermicro裸机服务器上进行的。服务器通过DAC SFP +无源电缆直接连接到交换机，并在激活巨型帧（MTU 9000）的同一VLAN中设置。</p>
<p><a href="https://tonybai.com/2018/10/17/imooc-course-kubernetes-practice-go-online/">Kubernetes</a> 1.14.0​在<a href="https://tonybai.com/tag/ubuntu">Ubuntu</a> 18.04 LTS上运行，运行<a href="https://tonybai.com/tag/docker">Docker</a> 18.09.2（此linux版本中的默认docker版本）。</p>
<p>为了提高可重复性，我们选择始终在第一个节点上设置master，在第二个服务器上设置基准测试的服务器部分，在第三个服务器上设置客户端部分。这是通过Kubernetes deployments中的NodeSelector实现的。</p>
<p>以下是我们将用于描述基准测试结果和解释的表情图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_CejvF2mlwcV27sAy-sz1VQ.png" alt="img{512x368}" /></p>
<h2>为基准测试选择CNI</h2>
<p>这个基准测试仅仅关注那些入选kubernetes正式文档：<a href="https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/">“create a single master cluster with kubeadm”</a>中的CNI列表。在提到的9个CNI中，我们只测试其中的6个，不包括那些我们无法轻松安装和/或不通过以下文档开箱即用的工具（Romana，Contiv-VPP和JuniperContrail / TungstenFabric）</p>
<p>以下是我们将要比较的CNI列表：</p>
<ul>
<li>Calico v3.6</li>
<li>Canal v3.6（事实上，Flannel用于网络+ Calico用于防火墙）</li>
<li>Cilium 1.4.2</li>
<li>Flannel 0.11.0</li>
<li>Kube-router 0.2.5</li>
<li>WeaveNet 2.5.1</li>
</ul>
<h2>安装</h2>
<p>CNI越容易设置，我们对其第一印象就越好。所有参与基准测试的CNI都很容易设置（一个或两个命令行）。</p>
<p>如前所述，服务器和交换机都配置了Jumbo帧激活（通过将MTU设置为9000）。我们非常感谢CNI可以自动发现要使用的MTU，具体取决于适配器。事实上，Cilium和Flannel是唯一能够正确自动检测MTU的选手。大多数其他CNI在GitHub中引发了启用MTU自动检测的问题，但是现在，我们需要通过修改Calico，Canal和Kube-router的ConfigMap或WeaveNet的ENV var来手动修复它。</p>
<p>也许您想知道错误的MTU会产生什么影响？这里有一个图表，显示WeaveNet与默认MTU和WeaveNet与Jumbo帧之间的区别：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_kQcutZL8nDrncSUbFAdcag.png" alt="img{512x368}" /></p>
<p>那么，既然我们知道MTU对性能非常重要，那么这些CNI如何自动检测MTU：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_Nr--EbXRgNeHQzyLdFvinQ.png" alt="img{512x368}" /></p>
<p>正如我们在上图中看到的，我们必须对Calico，Canal，Kube-router和WeaveNet应用一些MTU调整以获得最佳性能。Cilium和Flannel能够自行正确地自动检测MTU，确保开箱即用的最佳性能。</p>
<h2>安全</h2>
<p>在比较这些CNI的安全性时，我们谈论两件事：它们加密通信的能力，以及它们对Kubernetes网络策略的实现（根据实际测试，而不是来自他们的文档）。</p>
<p>只有两个CNI可以实现加密通信：Cilium和WeaveNet。通过将加密密码设置为CNI的ENV变量可以来启用WeaveNet加密。WeaveNet文档有点令人困惑，但这很容易做到。Cilium加密是通过创建Kubernetes Secrets和daemonSet修改的命令设置的（比WeaveNet复杂一点，但是Cilium有很棒的文档记录了它）。</p>
<p>在网络策略实现方面，通过实施Ingress和Egress规则，Calico，Canal，Cilium和WeaveNet是最好的控制面板。Kube-router实际上只实现了Ingress规则。</p>
<p>Flannel没有实现网络策略。</p>
<p>以下是结果摘要：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_8IXpUtCARPmm1W91nzWAxw.png" alt="img{512x368}" /></p>
<h2>性能</h2>
<p>该基准测试显示每次测试的三次运行（至少）的平均带宽。我们正在测试TCP和UDP性能（使用iperf3），真实应用程序，如HTTP（使用Nginx和curl），或FTP（使用vsftpd和curl），最后是使用SCP协议进行应用程序加密的行为（使用OpenSSH服务器和客户端）。</p>
<p>对于所有测试，我们还在裸机节点（绿色条）上运行基准测试，以比较CNI与本机网络性能的有效性。为了与我们的基准比例保持一致，我们在图表上使用以下颜色：</p>
<ul>
<li>黄色=非常好</li>
<li>橙色=好</li>
<li>蓝色=一般</li>
<li>红色=差</li>
</ul>
<p>因为我们不关注错误配置的CNI的性能，所以我们只会显示MTU调整的CNI基准测试结果。（NOTA BENE：如果激活加密，Cilium无法正确计算MTU，因此您必须在v1.4中手动将MTU降低到8900.下一版1.5将自动适应。）</p>
<p>结果如下：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_kuH6qDyQqqG7Nnex9PT4dg.png" alt="img{512x368}" /></p>
<p>每个CNI都在TCP基准测试中表现良好。由于加密成本，启用加密的CNI远远落后于其他CNI。</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_68nsITzOCSYw829nH0doTQ.png" alt="img{512x368}" /></p>
<p>同样，在UDP基准测试中，所有CNI都表现良好。加密的CNI现在彼此非常接近。Cilium落后于其竞争对手，但事实上，它仅略高于裸机结果的2,3％，这是公平的。我们应该记住的是，Cilium和Flannel都是唯一能够正确自动检测MTU的CNI，从而提供了开箱即用的结果。</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_8i95V9158G7EzMdwGuWdrQ.png" alt="img{512x368}" /></p>
<p>真实世界的应用程序怎么样？使用HTTP基准测试，我们可以看到全局性能略低于TCP测试。即使HTTP支持TCP，在TCP基准测试中，iperf3配置为避免任何“TCP慢启动”副作用，这可以有效地影响HTTP基准测试。这里的每个选手的表现都相当不错，Kube-router有明显的优势，WeaveNet在这项测试中表现非常糟糕，比裸机少了约20％。Cilium加密和WeaveNet加密现在都远远落后于裸机性能。</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_Riy2SAc46FcYgUqyAXypPg.png" alt="img{512x368}" /></p>
<p>使用FTP，另一个TCP支持的协议，结果更加复杂。虽然Flannel和Kube-router的表现非常好，但是Calico，Canal和Cilium稍稍落后，在裸机速度下约为10％。WeaveNet与裸机性能相差甚远，差距为17>％。无论如何，WeaveNet的加密版本比Cilium加密的性能高出约40％。</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_kv4J0qVlG7uEMlJEhw3RnQ.png" alt="img{512x368}" /></p>
<p>通过SCP，我们可以清楚地看到SSH协议的加密成本。大多数CNI表现良好，但WeaveNet再次落后于其他人。当然，由于双重加密成本（SSH加密+ CNI加密）。</p>
<p>以下是性能摘要总结：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_EcLeju0yLgd6CwKMxBqcaw.png" alt="img{512x368}" /></p>
<h2>资源消耗</h2>
<p>现在让我们比较这些CNI在负载很重的情况下处理所带来的资源消耗如何（在TCP 10Gbit传输期间）。在性能测试中，我们将CNI与裸金属（绿色条）进行比较。对于资源消耗测试，我们还显示了没有任何CNI设置的新闲置Kubernetes（紫色条）的消耗。然后我们可以计算出CNI真正消耗的开销。</p>
<p>让我们从内存方面开始吧。以下是传输期间以MB为单位的平均节点RAM使用率（无缓冲区/缓存）。</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_GnPPDLyxIBbASVCXb_c1BA.png" alt="img{512x368}" /></p>
<p>Flannel和Kube-router表现非常好，只有大约50MB的内存占用，其次是Calico和Canal，70MB。WeaveNet的消费量明显高于其竞争对手，资源占用约为130MB。凭借400MB的内存占用，Cilium具有最高的基准内存消耗。</p>
<p>现在，让我们检查CPU消耗。警告：图形单位不是百分比，而是permil。因此裸金属的38 permil实际上是3.8％。结果如下：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_le6lgtbArrW9x5bVkerUNQ.png" alt="img{512x368}" /></p>
<p>Calico，Canal，Flannel和Kube-router都非常高效的CPU使用，与没有CNI的kubernetes相比，开销仅多出2％。远远落后于WeaveNet，开销约为5％，然后是Cilium，CPU开销超过7％。</p>
<p>以下是资源消耗的摘要：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_2Hj9aTvF-tJ6A90sfIlFTw.png" alt="img{512x368}" /></p>
<h2>摘要</h2>
<p>以下是所有结果的汇总概述：</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_vN2PBlNLOs--eHgNUuKHCg.png" alt="img{512x368}" /></p>
<h2>结论</h2>
<p>最后一部分是主观的，并传达了我自己对结果的解释。请记住，此基准测试仅在一个非常小的集群（3个节点）上测试单个连接中的吞吐速度。它不反映大型集群（> 50个节点）的网络行为，也没有多少连接并发。</p>
<p>如果你在相应的场景中，我建议使用以下CNI：</p>
<ul>
<li>您的群集中有低资源节点（只有几GB的RAM，几个核心）并且您不需要安全功能，请使用Flannel。它是我们测试过的最精简的CNI之一。此外，它与大量架构兼容（amd64，arm，arm64等）。它是唯一一个能够正确自动检测MTU的CNI，和Cilium一起，因此您无需配置任何内容即可使其正常工作。Kube-router也很好，但标准较低，需要您手动设置MTU。</li>
<li>出于安全原因，您需要加密网络，请使用WeaveNet。如果您使用巨型帧并通过在环境变量中提供密码来激活加密，请不要忘记设置MTU大小。但话说回过来，忘掉性能，这就是加密的代价。</li>
<li>对于其他常见用法，我会推荐Calico。这种CNI广泛用于许多kubernetes部署工具（Kops，Kubespray，Rancher等）。就像WeaveNet一样，如果您使用的是巨型帧，请不要忘记在ConfigMap中设置MTU。事实证明，它在资源消耗，性能和安全性方面具有多用途和高效性。</li>
</ul>
<p>最后但并非最不重要的，我建议你关注Cilium的工作。他们的团队非常活跃，他们正在努力提高他们的CNI（功能，资源节约，性能，安全性，多集群跨越&#8230;&#8230;），他们的路线图听起来非常有趣。</p>
<p><img src="https://tonybai.com/wp-content/uploads/1_WRQ8B9lcHL2LlImVbppoEQ.png" alt="img{512x368}" /></p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2019, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2019/04/18/benchmark-result-of-k8s-network-plugin-cni/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>一步步打造基于Kubeadm的高可用Kubernetes集群-第二部分</title>
		<link>https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part2/</link>
		<comments>https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part2/#comments</comments>
		<pubDate>Mon, 15 May 2017 14:41:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[bootkube]]></category>
		<category><![CDATA[CA]]></category>
		<category><![CDATA[configmap]]></category>
		<category><![CDATA[DaemonSet]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[etcd3]]></category>
		<category><![CDATA[etcdctl]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[HA]]></category>
		<category><![CDATA[haproxy]]></category>
		<category><![CDATA[High-Available]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[journalctl]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kops]]></category>
		<category><![CDATA[kube-apiserver]]></category>
		<category><![CDATA[kube-controller-manager]]></category>
		<category><![CDATA[kube-proxy]]></category>
		<category><![CDATA[kube-scheduler]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[loadbalance]]></category>
		<category><![CDATA[lvs]]></category>
		<category><![CDATA[make-mirror]]></category>
		<category><![CDATA[Master]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[openssl]]></category>
		<category><![CDATA[PEM]]></category>
		<category><![CDATA[PKI]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[weave]]></category>
		<category><![CDATA[x509]]></category>
		<category><![CDATA[负载均衡]]></category>
		<category><![CDATA[高可用]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2318</guid>
		<description><![CDATA[续接上文。 五、第三步：启动emei、wudang上的apiserver 跨三个node的etcd cluster已经建成并完成了数据同步，下面进行ha cluster改造的重要一步：启动wudang、emei上的apiserver 1、启动emei、wudang上的apiserver 以shaolin node上的/etc/kubernetes/manifests/kube-apiserver.yaml为副本，制作emei、wudang上的kube-apiserver.yaml： 唯一需要变动的就是- --advertise-address这个option的值： wudang: - --advertise-address=10.24.138.208 emei: - --advertise-address=10.27.52.72 在各自node上将kube-apiserver.yaml放入/etc/kubernetes/manifests中，各自node上的kubelet将会启动kube-apiserver并且各个apiserver默认连接本节点的etcd: root@emei:~# pods NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE ... ... kube-system kube-apiserver-emei 1/1 Running 0 1d 10.27.52.72 emei kube-system kube-apiserver-shaolin 1/1 Running 0 1d 10.27.53.32 shaolin kube-system kube-apiserver-wudang 1/1 Running 0 2d 10.24.138.208 wudang 2、将emei、wudang上的kubelet改为连接自己所在节点的apiserver 所有apiserver都启动了。wudang、emei上的kubelet也应该连接自己节点的apiserver了！修改各自的/etc/kubernetes/kubelet.conf，修改server配置项： [...]]]></description>
			<content:encoded><![CDATA[<p>续接<a href="http://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part1/">上文</a>。</p>
<h2>五、第三步：启动emei、wudang上的apiserver</h2>
<p>跨三个node的etcd cluster已经建成并完成了数据同步，下面进行ha cluster改造的重要一步：启动wudang、emei上的apiserver</p>
<h3>1、启动emei、wudang上的apiserver</h3>
<p>以shaolin node上的/etc/kubernetes/manifests/kube-apiserver.yaml为副本，制作emei、wudang上的kube-apiserver.yaml：</p>
<pre><code>唯一需要变动的就是- --advertise-address这个option的值：

wudang:

- --advertise-address=10.24.138.208

emei:

- --advertise-address=10.27.52.72

</code></pre>
<p>在各自node上将kube-apiserver.yaml放入/etc/kubernetes/manifests中，各自node上的kubelet将会启动kube-apiserver并且各个apiserver默认连接本节点的etcd:</p>
<pre><code>root@emei:~# pods
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
... ...
kube-system   kube-apiserver-emei               1/1       Running   0          1d        10.27.52.72     emei
kube-system   kube-apiserver-shaolin            1/1       Running   0          1d        10.27.53.32     shaolin
kube-system   kube-apiserver-wudang             1/1       Running   0          2d        10.24.138.208   wudang
</code></pre>
<h3>2、将emei、wudang上的kubelet改为连接自己所在节点的apiserver</h3>
<p>所有apiserver都启动了。wudang、emei上的kubelet也应该连接自己节点的apiserver了！修改各自的/etc/kubernetes/kubelet.conf，修改server配置项：</p>
<pre><code>wudang:

server: https://10.24.138.208:6443

emei:

server: https://10.27.52.72:6443
</code></pre>
<p>各自重启kubelet:</p>
<pre><code>以wudang为例：

root@wudang:~# systemctl daemon-reload
root@wudang:~# systemctl restart kubelet
</code></pre>
<p>不过，问题出现了！查看重启的kubelet日志：</p>
<pre><code>root@wudang:~# journalctl -u kubelet -f
-- Logs begin at Mon 2017-05-08 15:12:01 CST. --
May 11 14:33:27 wudang kubelet[8794]: I0511 14:33:27.919223    8794 kubelet_node_status.go:230] Setting node annotation to enable volume controller attach/detach
May 11 14:33:27 wudang kubelet[8794]: I0511 14:33:27.921166    8794 kubelet_node_status.go:77] Attempting to register node wudang
May 11 14:33:27 wudang kubelet[8794]: E0511 14:33:27.926865    8794 kubelet_node_status.go:101] Unable to register node "wudang" with API server: Post https://10.24.138.208:6443/api/v1/nodes: x509: certificate is valid for 10.96.0.1, 10.27.53.32, not 10.24.138.208
May 11 14:33:28 wudang kubelet[8794]: E0511 14:33:28.283258    8794 event.go:208] Unable to write event: 'Post https://10.24.138.208:6443/api/v1/namespaces/default/events: x509: certificate is valid for 10.96.0.1, 10.27.53.32, not 10.24.138.208' (may retry after sleeping)
May 11 14:33:28 wudang kubelet[8794]: E0511 14:33:28.499209    8794 reflector.go:190] k8s.io/kubernetes/pkg/kubelet/kubelet.go:390: Failed to list *v1.Node: Get https://10.24.138.208:6443/api/v1/nodes?fieldSelector=metadata.name%3Dwudang&amp;resourceVersion=0: x509: certificate is valid for 10.96.0.1, 10.27.53.32, not 10.24.138.208
May 11 14:33:28 wudang kubelet[8794]: E0511 14:33:28.504593    8794 reflector.go:190] k8s.io/kubernetes/pkg/kubelet/config/apiserver.go:46: Failed to list *v1.Pod: Get https://10.24.138.208:6443/api/v1/pods?fieldSelector=spec.nodeName%3Dwudang&amp;resourceVersion=0: x509: certificate is valid for 10.96.0.1, 10.27.53.32, not 10.24.138.208
</code></pre>
<p>从错误日志判断来看，似乎是wudang上的kubelet在与同一节点上的kube-apiserver通信过程中，发现这个apiserver返回的tls证书是属于10.27.53.32的，即shaolin node上的apiserver的，而不是wudang node上的apiserver的，于是报了错！问题的原因很明了，因为Wudang上的kube-apiserver用的apiserver.crt的确是从shaolin node上copy过来的。也就是说要解决这个问题，我们需要为wudang、emei两个node上的apiserver各自生成自己的数字证书。</p>
<p>我们先来查看一下shaolin上的apiserver.crt内容是什么样子的：</p>
<pre><code>root@shaolin:/etc/kubernetes/pki# openssl x509 -noout -text -in apiserver.crt

Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=kubernetes

Subject: CN=kube-apiserver

X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                DNS:shaolin, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:10.27.53.32
</code></pre>
<p>我们看到证书使用到了x509v3的扩展功能：subject alternative name，并且指定了多个value。我们为wudang、emei生成的apiserver.crt也应该如此。如何做呢？好在我们有整个集群的ca.key和ca.crt，可以用来签署证书请求。以wudang node为例，我们来为wudang node上的apiserver生成apiserver-wudang.key和apiserver-wudang.crt：</p>
<pre><code>//生成2048位的密钥对
root@wudang:~# openssl genrsa -out apiserver-wudang.key 2048

//生成证书签署请求文件
root@wudang:~# openssl req -new -key apiserver-wudang.key -subj "/CN=kube-apiserver," -out apiserver-wudang.csr

// 编辑apiserver-wudang.ext文件，内容如下：
subjectAltName = DNS:wudang,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP:10.96.0.1, IP:10.24.138.208

// 使用ca.key和ca.crt签署上述请求
root@wudang:~# openssl x509 -req -in apiserver-wudang.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out apiserver-wudang.key.crt -days 365 -extfile apiserver-wudang.ext
Signature ok
subject=/CN=10.24.138.208
Getting CA Private Key

//查看新生成的证书：
root@wudang:~# openssl x509 -noout -text -in apiserver-wudang.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 16019625340257831745 (0xde51245f10ea0b41)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=kubernetes
        Validity
            Not Before: May 12 08:40:40 2017 GMT
            Not After : May 12 08:40:40 2018 GMT
        Subject: CN=kube-apiserver,
        Subject Public Key Info:
            ... ...
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:wudang, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:10.24.138.208
</code></pre>
<p>将apiserver-wudang.key和apiserver-wudang.crt放入/etc/kubernetes/pki目录下，修改kube-apiserver.yaml文件：</p>
<pre><code>// /etc/kubernetes/pki
- --tls-cert-file=/etc/kubernetes/pki/apiserver-wudang.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver-wudang.key
</code></pre>
<p>kube-apiserver重启后，再来查看kubelet日志，你会发现kubelet运行一切ok了。emei节点也要进行同样的操作。</p>
<p>至此，整个集群的状态示意图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-3.png" alt="img{512x368}" /></p>
<h2>六、第四步：启动emei、wudang上的kube-controller-manager和kube-scheduler</h2>
<p>这一步我们只需要将shaolin node上的/etc/kubernetes/manifests中的kube-controller-manager.yaml和kube-scheduler.yaml拷贝到wudang、emei两个node的相应目录下即可：</p>
<pre><code>root@emei:~/kubernetes-conf-shaolin/manifests# pods
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
... ...
kube-system   kube-controller-manager-emei      1/1       Running   0          8s        10.27.52.72     emei
kube-system   kube-controller-manager-shaolin   1/1       Running   3          1d        10.27.53.32     shaolin
kube-system   kube-controller-manager-wudang    1/1       Running   0          1m        10.24.138.208   wudang
... ...
kube-system   kube-scheduler-emei               1/1       Running   0          15s       10.27.52.72     emei
kube-system   kube-scheduler-shaolin            1/1       Running   3          1d        10.27.53.32     shaolin
kube-system   kube-scheduler-wudang             1/1       Running   0          3m        10.24.138.208   wudang
... ...
</code></pre>
<p>查看一下各个node下kcm和scheduler的日志：</p>
<pre><code>root@wudang:~/demo# kubectl logs -f kube-controller-manager-emei -n kube-system
I0511 07:34:53.804831       1 leaderelection.go:179] attempting to acquire leader lease...

root@wudang:~/demo# kubectl logs -f kube-controller-manager-wudang -n kube-system
I0511 07:33:20.725669       1 leaderelection.go:179] attempting to acquire leader lease...

root@wudang:~/demo# kubectl logs -f kube-scheduler-emei -n kube-system
I0511 07:34:45.711032       1 leaderelection.go:179] attempting to acquire leader lease...

root@wudang:~/demo# kubectl logs -f kube-scheduler-wudang -n kube-system
I0511 07:31:35.077090       1 leaderelection.go:179] attempting to acquire leader lease...

root@wudang:~/demo# kubectl logs -f kube-scheduler-shaolin -n kube-system

I0512 08:55:30.838806       1 event.go:217] Event(v1.ObjectReference{Kind:"Pod", Namespace:"default", Name:"my-nginx-2267614806-v1dst", UID:"c075c6c7-36f0-11e7-9c66-00163e000c7f", APIVersion:"v1", ResourceVersion:"166279", FieldPath:""}): type: 'Normal' reason: 'Scheduled' Successfully assigned my-nginx-2267614806-v1dst to emei
I0512 08:55:30.843104       1 event.go:217] Event(v1.ObjectReference{Kind:"Pod", Namespace:"default", Name:"my-nginx-2267614806-drnzv", UID:"c075da9f-36f0-11e7-9c66-00163e000c7f", APIVersion:"v1", ResourceVersion:"166278", FieldPath:""}): type: 'Normal' reason: 'Scheduled' Successfully assigned my-nginx-2267614806-drnzv to wudang
I0512 09:13:21.121864       1 event.go:217] Event(v1.ObjectReference{Kind:"Pod", Namespace:"default", Name:"my-nginx-2267614806-ld1dr", UID:"3e73d350-36f3-11e7-9c66-00163e000c7f", APIVersion:"v1", ResourceVersion:"168070", FieldPath:""}): type: 'Normal' reason: 'Scheduled' Successfully assigned my-nginx-2267614806-ld1dr to wudang
I0512 09:13:21.124295       1 event.go:217] Event(v1.ObjectReference{Kind:"Pod", Namespace:"default", Name:"my-nginx-2267614806-cmmkh", UID:"3e73c8b2-36f3-11e7-9c66-00163e000c7f", APIVersion:"v1", ResourceVersion:"168071", FieldPath:""}): type: 'Normal' reason: 'Scheduled' Successfully assigned my-nginx-2267614806-cmmkh to emei
</code></pre>
<p>可以看出，当前shaolin node上的kcm和scheduler是leader。</p>
<p>至此，整个集群的状态示意图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-4.png" alt="img{512x368}" /></p>
<h2>六、第五步：将wudang、emei设置为master node</h2>
<p>我们试着在wudang节点上创建一个pod:</p>
<pre><code>// run-my-nginx.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.10.1
        ports:
        - containerPort: 80
</code></pre>
<p>发现pod居然被调度到了wudang、emei节点上了！</p>
<pre><code>NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
default       my-nginx-2267614806-drnzv         1/1       Running   0          5s        172.32.192.1    wudang
default       my-nginx-2267614806-v1dst         1/1       Running   0          5s        172.32.64.0     emei
</code></pre>
<p>emei、wudang并没有执行taint，为何能承载workload? 查看当前cluster的node状态：</p>
<pre><code>root@wudang:~# kubectl get node --show-labels
NAME      STATUS    AGE       VERSION   LABELS
emei      Ready     1d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=emei
shaolin   Ready     2d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=shaolin,node-role.kubernetes.io/master=
wudang    Ready     1d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=wudang
</code></pre>
<p>从label看到，status列并没有明确输出谁是master，这和1.5.1版本以前似乎不同。emei、wudang与shaolin唯一的不同就是shaolin有一个key: node-role.kubernetes.io/master。难道这个label是指示谁是master的？我们给wudang打上这个label：</p>
<pre><code>root@wudang:~/demo# kubectl label node wudang node-role.kubernetes.io/master=
node "wudang" labeled
root@wudang:~/demo# kubectl get node --show-labels
NAME      STATUS    AGE       VERSION   LABELS
emei      Ready     1d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=emei
shaolin   Ready     2d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=shaolin,node-role.kubernetes.io/master=
wudang    Ready     1d        v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=wudang,node-role.kubernetes.io/master=
</code></pre>
<p>再创建nginx pod，我们发现pod依旧分配在wudang、emei两个node上:</p>
<pre><code>NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
default       my-nginx-2267614806-cmmkh         1/1       Running   0          5s        172.32.64.0     emei
default       my-nginx-2267614806-ld1dr         1/1       Running   0          5s        172.32.192.1    wudang
</code></pre>
<p>我们进一步查看并对比相关信息：</p>
<p>查看clustre-info：</p>
<pre><code>wuddang node:
root@wudang:~/demo# kubectl cluster-info
Kubernetes master is running at https://10.24.138.208:6443 //wudang node:
KubeDNS is running at https://10.24.138.208:6443/api/v1/proxy/namespaces/kube-system/services/kube-dns

shaolin node:

root@shaolin:~/k8s-install/demo# kubectl cluster-info
Kubernetes master is running at https://10.27.53.32:6443
KubeDNS is running at https://10.27.53.32:6443/api/v1/proxy/namespaces/kube-system/services/kube-dns
</code></pre>
<p>查看详细node信息：</p>
<pre><code>root@wudang:~# kubectl describe node/shaolin

Name:            shaolin
Role:
Labels:            beta.kubernetes.io/arch=amd64
            beta.kubernetes.io/os=linux
            kubernetes.io/hostname=shaolin
            node-role.kubernetes.io/master=
Annotations:        node.alpha.kubernetes.io/ttl=0
            volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:            node-role.kubernetes.io/master:NoSchedule

root@wudang:~# kubectl describe node/wudang

Name:            wudang
Role:
Labels:            beta.kubernetes.io/arch=amd64
            beta.kubernetes.io/os=linux
            kubernetes.io/hostname=wudang
            node-role.kubernetes.io/master=
Annotations:        node.alpha.kubernetes.io/ttl=0
            volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:            &lt;none&gt;
</code></pre>
<p>我们看到，在Taints属性里，shaolin node的值为 node-role.kubernetes.io/master:NoSchedule，而wudang node的为空。初步猜测这就是wudang被分配pod的原因了。</p>
<p>我们设置wudang node的Taints属性：</p>
<pre><code>root@wudang:~# kubectl taint nodes wudang node-role.kubernetes.io/master=:NoSchedule
node "wudang" tainted

root@wudang:~# kubectl describe node/wudang|more
Name:            wudang
Role:
Labels:            beta.kubernetes.io/arch=amd64
            beta.kubernetes.io/os=linux
            kubernetes.io/hostname=wudang
            node-role.kubernetes.io/master=
Annotations:        node.alpha.kubernetes.io/ttl=0
            volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:            node-role.kubernetes.io/master:NoSchedule
</code></pre>
<p>再创建nginx deployment:</p>
<p>root@wudang:~/demo# pods<br />
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE<br />
default       my-nginx-2267614806-hmz5d         1/1       Running   0          14s       172.32.64.0     emei<br />
default       my-nginx-2267614806-kkt79         1/1       Running   0          14s       172.32.64.1     emei</p>
<p>发现pod全部分配到emei上了！</p>
<p>接下来按同样操作对emei的taints属性进行设置，这里就不赘述了。</p>
<p>到目前为止，整个k8s cluster的状态如下示意图：<br />
<img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-5.png" alt="img{512x368}" /></p>
<h2>七、第六步：Load Balance</h2>
<p>Kubernetes HA cluster的建立得益于kube-apiserver的无状态，按照最终目标，在三个kube-apiserver的前面是要假设一个负载均衡器的。考虑到apiserver对外通过https暴露服务，在七层做lb需要将证书配置在lb上，这改动较大；这里我们用四层lb。在这里，我们仅是搭建一个简易的demo性质的基于nginx的四层lb，在生产环境，如果你有硬件lb或者你所在的cloud provider提供类似lb服务，可以直接使用。</p>
<p>演示方便起见，我直接在emei上安装一个nginx（注意一定要安装支持&#8211;with-stream支持的nginx，可以通过-V查看）:</p>
<pre><code>root@emei:~# nginx -V
nginx version: nginx/1.10.3 (Ubuntu)
built with OpenSSL 1.0.2g  1 Mar 2016
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads
</code></pre>
<p>我这里直接修改nginx的默认配置文件：/etc/nginx/nginx.conf，添加如下配置：</p>
<pre><code>// /etc/nginx/nginx.conf
... ...
stream {
    upstream apiserver {
        server 10.27.53.32:6443 weight=5 max_fails=3 fail_timeout=30s;
        server 10.24.138.208:6443 weight=5 max_fails=3 fail_timeout=30s;
        server 10.27.52.72:6443 weight=5 max_fails=3 fail_timeout=30s;
    }

    server {
        listen 8443;
        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass apiserver;
    }
}
... ...

</code></pre>
<p>nginx -s reload后，配置生效！</p>
<p>我们用wudang上的kubectl来访问一下lb，我们先来做一下配置</p>
<pre><code>root@wudang:~# cp /etc/kubernetes/admin.conf ./
root@wudang:~# mv admin.conf admin-lb.conf
root@wudang:~# vi admin-lb.conf

修改admin-lb.conf中的：
server: https://10.27.52.72:8443

export KUBECONFIG=~/admin-lb.conf
</code></pre>
<p>执行下面命令：</p>
<pre><code>root@wudang:~# kubectl get pods -n kube-system
Unable to connect to the server: x509: certificate is valid for 10.96.0.1, 10.27.53.32, not 10.27.52.72
root@wudang:~# kubectl get pods -n kube-system
Unable to connect to the server: x509: certificate is valid for 10.24.138.208, not 10.27.52.72

</code></pre>
<p>可以看到上述两个请求被lb分别转到了shaolin和wudang两个node的apiserver上，客户端在校验server端发送的证书时认为server端”有诈“，于是报了错！怎么解决呢？在上面我们为每个apiserver生成apiserver.crt时，我们在subject alternative name值中填写了多个域名，我们用域名来作为client端访问的目的地址，再来看看：</p>
<pre><code>修改~/admin-lb.conf中的：

server: https://kubernetes.default.svc:8443
</code></pre>
<p>在wudang node的/etc/hosts中添加：</p>
<pre><code>10.27.52.72 kubernetes.default.svc
</code></pre>
<p>再访问集群：</p>
<pre><code>root@wudang:~# kubectl get pods -n kube-system
NAME                              READY     STATUS    RESTARTS   AGE
etcd-emei                         1/1       Running   0          1d
etcd-shaolin                      1/1       Running   0          1d
etcd-wudang                       1/1       Running   0          4d
kube-apiserver-emei               1/1       Running   0          1d
... ...
</code></pre>
<p>这里只是一个demo，在您自己的环境里如何将lb与apiserver配合在一起，方法有很多种，需要根据实际情况具体确定。</p>
<p>到目前为止，整个k8s cluster的状态如下示意图：<br />
<img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-6.png" alt="img{512x368}" /></p>
<h2>八、第七步：kube-proxy配置修改</h2>
<p>kube-proxy是一个由一个daemonset创建的：</p>
<pre><code>root@wudang:~# kubectl get ds -n kube-system
NAME         DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE-SELECTOR   AGE
kube-proxy   3         3         3         3            3           &lt;none&gt;          5d
</code></pre>
<p>并且kube-proxy的配置是由一个configmap提供的，并未在外部留有修改的口，比如类似kube-scheduler.yaml或.conf那样：</p>
<pre><code>root@shaolin:~# kubectl get configmap -n kube-system
NAME                                 DATA      AGE
kube-proxy                           1         5d

root@shaolin:~# kubectl get configmap/kube-proxy -n kube-system -o yaml
apiVersion: v1
data:
  kubeconfig.conf: |
    apiVersion: v1
    kind: Config
    clusters:
    - cluster:
        certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        server: https://10.27.53.32:6443
      name: default
    contexts:
    - context:
        cluster: default
        namespace: default
        user: default
      name: default
    current-context: default
    users:
    - name: default
      user:
        tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
kind: ConfigMap
metadata:
  creationTimestamp: 2017-05-10T01:48:28Z
  labels:
    app: kube-proxy
  name: kube-proxy
  namespace: kube-system
  resourceVersion: "81"
  selfLink: /api/v1/namespaces/kube-system/configmaps/kube-proxy
  uid: c34f7d5f-3522-11e7-8f77-00163e000c7f
</code></pre>
<p>在这个默认的configmap中，kube-proxy连接的cluster的server地址硬编码为 https://10.27.53.32:6443，即shaolin node上apiserver的公共接口地址。这样一旦shaolin node宕掉了，其他node上的kube-proxy将无法连接到apiserver进行正常操作。而kube-proxy pod自身又是使用的是host network，因此我们需要将server地址配置为lb的地址，这样保证各node上kube-proxy的高可用。</p>
<p>我们根据上述输出的configmap的内容进行修改，并更新kube-proxy-configmap的内容：</p>
<pre><code>root@shaolin:~# kubectl get configmap/kube-proxy -n kube-system -o yaml &gt; kube-proxy-configmap.yaml

修改kube-proxy-configmap.yaml中的server为：

server: https://kubernetes.default.svc:6443

保存并更新configmap: kube-proxy：

root@shaolin:~# kubectl apply -f kube-proxy-configmap.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
configmap "kube-proxy" configured

root@shaolin:~# kubectl get configmap/kube-proxy -n kube-system -o yaml
apiVersion: v1
data:
  kubeconfig.conf: |
    apiVersion: v1
    kind: Config
    clusters:
    - cluster:
        certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        server: https://kubernetes.default.svc:6443
      name: default
... ...
</code></pre>
<p>重启kube-proxy(kubectl delete pods/kube-proxy-xxx -n kube-system)后，查看kube-proxy的日志：</p>
<pre><code>root@shaolin:~# kubectl logs -f kube-proxy-h5sg8 -n kube-system
I0515 13:57:03.526032       1 server.go:225] Using iptables Proxier.
W0515 13:57:03.621532       1 proxier.go:298] clusterCIDR not specified, unable to distinguish between internal and external traffic
I0515 13:57:03.621578       1 server.go:249] Tearing down userspace rules.
I0515 13:57:03.738015       1 conntrack.go:81] Set sysctl 'net/netfilter/nf_conntrack_max' to 131072
I0515 13:57:03.741824       1 conntrack.go:66] Setting conntrack hashsize to 32768
I0515 13:57:03.742555       1 conntrack.go:81] Set sysctl 'net/netfilter/nf_conntrack_tcp_timeout_established' to 86400
I0515 13:57:03.742731       1 conntrack.go:81] Set sysctl 'net/netfilter/nf_conntrack_tcp_timeout_close_wait' to 3600
</code></pre>
<h2>九、小结</h2>
<p>到这里，我们在第一部分中的最终思路方案已经实现了。不过这两篇文章对kubernetes ha cluster的打造还仅限于探索阶段，可能还有一些深层次的问题没有暴露出来，因此不建议在生产环境中采用。kubeadm在后续的版本中必然加入对k8s ha cluster的支持，那个时候，搭建一套可用于生产环境的HA cluster将不再这么麻烦了！</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part2/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>一步步打造基于Kubeadm的高可用Kubernetes集群-第一部分</title>
		<link>https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part1/</link>
		<comments>https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part1/#comments</comments>
		<pubDate>Mon, 15 May 2017 14:38:51 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[bootkube]]></category>
		<category><![CDATA[CA]]></category>
		<category><![CDATA[configmap]]></category>
		<category><![CDATA[DaemonSet]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[etcd3]]></category>
		<category><![CDATA[etcdctl]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[HA]]></category>
		<category><![CDATA[High-Available]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[journalctl]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kops]]></category>
		<category><![CDATA[kube-apiserver]]></category>
		<category><![CDATA[kube-controller-manager]]></category>
		<category><![CDATA[kube-proxy]]></category>
		<category><![CDATA[kube-scheduler]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[loadbalance]]></category>
		<category><![CDATA[make-mirror]]></category>
		<category><![CDATA[Master]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[PKI]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[weave]]></category>
		<category><![CDATA[负载均衡]]></category>
		<category><![CDATA[高可用]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2315</guid>
		<description><![CDATA[Kubernetes集群的核心是其master node，但目前默认情况下master node只有一个，一旦master node出现问题，Kubernetes集群将陷入“瘫痪”，对集群的管理、Pod的调度等均将无法实施，即便此时某些用户的Pod依旧可以正常运行。这显然不能符合我们对于运行于生产环境下的Kubernetes集群的要求，我们需要一个高可用的Kubernetes集群。 不过，目前Kubernetes官方针对构建高可用(high-availability)的集群的支持还是非常有限的，只是针对少数cloud-provider提供了粗糙的部署方法，比如：使用kube-up.sh脚本在GCE上、使用kops在AWS上等等。 高可用Kubernetes集群是Kubernetes演进的必然方向，官方在“Building High-Availability Clusters”一文中给出了当前搭建HA cluster的粗略思路。Kubeadm也将HA列入了后续版本的里程碑计划，并且已经出了一版使用kubeadm部署高可用cluster的方法提议草案。 在kubeadm没有真正支持自动bootstrap的HA Kubernetes cluster之前，如果要搭建一个HA k8s cluster，我们应该如何做呢？本文将探索性地一步一步的给出打造一个HA K8s cluster的思路和具体步骤。不过需要注意的是：这里搭建的HA k8s cluser仅在实验室中测试ok，还并未在生产环境中run过，因此在某些未知的细节方面可能存在思路上的纰漏。 一、测试环境 高可用Kubernetes集群主要就是master node的高可用，因此，我们申请了三台美国西部区域的阿里云ECS作为三个master节点。通过hostnamectl将这三个节点的static hostname分别改为shaolin、wudang和emei： shaolin: 10.27.53.32 wudang: 10.24.138.208 emei: 10.27.52.72 三台主机运行的都是Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-63-generic x86_64)，使用root用户。 Docker版本如下： root@shaolin:~# docker version Client: Version: 17.03.1-ce API version: 1.27 Go version: go1.7.5 Git commit: c6d412e Built: Mon Mar 27 17:14:09 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu/">Kubernetes集群</a>的核心是其<a href="http://tonybai.com/2017/01/24/explore-kubernetes-cluster-installed-by-kubeadm/">master node</a>，但目前默认情况下master node只有一个，一旦master node出现问题，<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/">Kubernetes集群</a>将陷入“瘫痪”，对<a href="http://tonybai.com/2016/11/25/the-security-settings-for-kubernetes-cluster/">集群的管理</a>、Pod的调度等均将无法实施，即便此时某些用户的Pod依旧可以正常运行。这显然不能符合我们对于运行于生产环境下的Kubernetes集群的要求，我们需要一个高可用的Kubernetes集群。</p>
<p>不过，目前<a href="https://kubernetes.io">Kubernetes官方</a>针对构建高可用(high-availability)的集群的支持还是非常有限的，只是针对少数cloud-provider提供了粗糙的部署方法，比如：<a href="https://kubernetes.io/docs/tasks/administer-cluster/highly-available-master/">使用kube-up.sh脚本在GCE上</a>、<a href="https://kubernetes.io/docs/getting-started-guides/kops/">使用kops在AWS上</a>等等。</p>
<p>高可用Kubernetes集群是Kubernetes演进的必然方向，官方在“<a href="https://kubernetes.io/docs/admin/high-availability/">Building High-Availability Clusters</a>”一文中给出了当前搭建HA cluster的粗略思路。<a href="https://github.com/kubernetes/kubeadm">Kubeadm</a>也将HA列入了后续版本的<a href="https://github.com/kubernetes/kubeadm/issues/261">里程碑计划</a>，并且已经出了一版<a href="https://docs.google.com/document/d/1lH9OKkFZMSqXCApmSXemEDuy9qlINdm5MfWWGrK3JYc/edit#heading=h.8hdxw3quu67g">使用kubeadm部署高可用cluster的方法提议草案</a>。</p>
<p>在kubeadm没有真正支持自动bootstrap的HA Kubernetes cluster之前，如果要搭建一个HA k8s cluster，我们应该如何做呢？本文将探索性地一步一步的给出打造一个HA K8s cluster的思路和具体步骤。<strong>不过需要注意的是：这里搭建的HA k8s cluser仅在实验室中测试ok，还并未在生产环境中run过，因此在某些未知的细节方面可能存在思路上的纰漏</strong>。</p>
<h2>一、测试环境</h2>
<p>高可用Kubernetes集群主要就是master node的高可用，因此，我们申请了三台美国西部区域的阿里云ECS作为三个master节点。通过<a href="http://tonybai.com/2017/05/09/exception-caused-by-kubernetes-node-hostname-change/">hostnamectl</a>将这三个节点的static hostname分别改为shaolin、wudang和emei：</p>
<pre><code>shaolin: 10.27.53.32
wudang: 10.24.138.208
emei: 10.27.52.72
</code></pre>
<p>三台主机运行的都是<a href="http://tonybai.com/tag/ubuntu">Ubuntu</a> 16.04.2 LTS (GNU/Linux 4.4.0-63-generic x86_64)，使用root用户。</p>
<p>Docker版本如下：</p>
<pre><code>root@shaolin:~# docker version
Client:
 Version:      17.03.1-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:14:09 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.1-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:14:09 2017
 OS/Arch:      linux/amd64
 Experimental: false
</code></pre>
<p>Ubuntu上<a href="https://www.docker.com/community-edition">Docker CE版本</a>的安装步骤可以参看<a href="https://store.docker.com/editions/community/docker-ce-server-ubuntu?tab=description">这里</a>，由于我的服务器在美西，因此不存在”墙”的问题。对于主机在国内的朋友，你需要根据安装过程中是否输出错误日志自行决定是否需要配置一个加速器。另外，这里用的docker版本有些新，Kubernetes官网上提及最多的、兼容最好的还是<a href="https://github.com/moby/moby/releases/tag/v1.12.5">docker 1.12.x版本</a>，你也可以直接安装这个版本。</p>
<h2>二、Master节点高可用的思路</h2>
<p>通过<a href="http://tonybai.com/2017/01/24/explore-kubernetes-cluster-installed-by-kubeadm/">对single-master node的探索</a>，我们知道master节点上运行着如下几个Kubernetes组件：</p>
<ul>
<li>kube-apiserver：集群核心，集群API接口、集群各个组件通信的中枢；集群安全控制；</li>
<li>etcd：集群的数据中心；</li>
<li>kube-scheduler：集群Pod的调度中心；</li>
<li>kube-controller-manager：集群状态管理器，当集群状态与期望不同时，kcm会努力让集群恢复期望状态，比如：当一个pod死掉，kcm会努力新建一个pod来恢复对应replicas set期望的状态；</li>
<li>kubelet: kubernetes node agent，负责与node上的<a href="http://tonybai.com/tag/docke">docker engine</a>打交道；</li>
<li>kubeproxy: 每个node上一个，负责service vip到endpoint pod的流量转发，当前主要通过设置iptables规则实现。</li>
</ul>
<p>Kubernetes集群的高可用就是master节点的高可用，master节点的高可用归根结底就是上述这些运行于master node上的组件的高可用。因此，我们的思路就是考量如何让这些组件高可用起来！综合Kubernetes官方提供的资料以及一些proposal draft，我们知道完全从头搭建的<a href="https://github.com/kelseyhightower/kubernetes-the-hard-way">hard way形式</a>似乎不甚理智^0^，将一个由kubeadm创建的k8s cluster改造为一个ha的k8s cluster似乎更可行。下面是我的思路方案：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-full-view.png" alt="img{512x368}" /></p>
<p>前面提到过，我们的思路是基于kubeadm启动的kubernetes集群，通过逐步修改配置或替换，形成最终HA的k8s cluster。上图是k8s ha cluster的最终图景，我们可以看到：</p>
<ul>
<li>kube-apiserver：得益于apiserver的无状态，每个master节点的apiserver都是active的，并处理来自Load Balance分配过来的流量；</li>
<li>etcd：状态的集中存储区。通过将多个master节点上的etcd组成一个etcd集群，使得apiserver共享集群状态和数据；</li>
<li>kube-controller-manager：kcm自带leader-elected功能，多个master上的kcm构成一个集群，但只有被elected为leader的kcm在工作。每个master节点上的kcm都连接本node上的apiserver；</li>
<li>kube-scheduler：scheduler自带leader-elected功能，多个master上的scheduler构成一个集群，但只有被elected为leader的scheduler在工作。每个master节点上的scheduler都连接本node上的apiserver；</li>
<li>kubelet: 由于master上的各个组件均以container的形式呈现，因此不承担workload的master节点上的kubelet更多是用来管理这些master组件容器。每个master节点上的kubelet都连接本node上的apiserver；</li>
<li>kube-proxy: 由于master节点不承载workload，因此master节点上的kube-proxy同样仅服务于一些特殊的服务，比如: kube-dns等。由于kubeadm下kube-proxy没有暴露出可供外部调整的配置，因此kube-proxy需要连接Load Balance暴露的apiserver的端口。</li>
</ul>
<p>接下来，我们就来一步步按照我们的思路，对kubeadm启动的single-master node k8s cluster进行改造，逐步演进到我们期望的ha cluster状态。</p>
<h2>三、第一步：使用kubeadm安装single-master k8s cluster</h2>
<p>距离第一次<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/">使用kubeadm安装kubernetes 1.5.1集群</a>已经有一些日子了，kubernetes和kubeadm都有了一些变化。当前kubernetes和kubeadm的最新release版都是1.6.2版本：</p>
<pre><code>root@wudang:~# kubeadm version
kubeadm version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.2", GitCommit:"477efc3cbe6a7effca06bd1452fa356e2201e1ee", GitTreeState:"clean", BuildDate:"2017-04-19T20:22:08Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}

root@wudang:~# docker images
REPOSITORY                                               TAG                 IMAGE ID            CREATED             SIZE
gcr.io/google_containers/kube-proxy-amd64                v1.6.2              7a1b61b8f5d4        3 weeks ago         109 MB
gcr.io/google_containers/kube-controller-manager-amd64   v1.6.2              c7ad09fe3b82        3 weeks ago         133 MB
gcr.io/google_containers/kube-apiserver-amd64            v1.6.2              e14b1d5ee474        3 weeks ago         151 MB
gcr.io/google_containers/kube-scheduler-amd64            v1.6.2              b55f2a2481b9        3 weeks ago         76.8 MB
... ...

</code></pre>
<p>虽然kubeadm版本有更新，但安装过程没有太多变化，这里仅列出一些关键步骤，一些详细信息输出就在这里省略了。</p>
<p>我们先在shaolin node上安装相关程序文件：</p>
<pre><code>root@shaolin:~# apt-get update &amp;&amp; apt-get install -y apt-transport-https

root@shaolin:~# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
OK

root@shaolin:~# cat &lt;&lt;EOF &gt;/etc/apt/sources.list.d/kubernetes.list
&gt; deb http://apt.kubernetes.io/ kubernetes-xenial main
&gt; EOF

root@shaolin:~# apt-get update

root@shaolin:~# apt-get install -y kubelet kubeadm kubectl kubernetes-cni

</code></pre>
<p>接下来，使用kubeadm启动集群。注意：由于在aliyun上<a href="https://github.com/coreos/flannel">flannel</a> 网络插件一直不好用，这里还是使用<a href="https://www.weave.works">weave network</a>。</p>
<pre><code>root@shaolin:~/k8s-install# kubeadm init --apiserver-advertise-address 10.27.53.32
[kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters.
[init] Using Kubernetes version: v1.6.2
[init] Using Authorization mode: RBAC
[preflight] Running pre-flight checks
[preflight] WARNING: docker version is greater than the most recently validated version. Docker version: 17.03.1-ce. Max validated version: 1.12
[preflight] Starting the kubelet service
[certificates] Generated CA certificate and key.
[certificates] Generated API server certificate and key.
[certificates] API Server serving cert is signed for DNS names [shaolin kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.27.53.32]
[certificates] Generated API server kubelet client certificate and key.
[certificates] Generated service account token signing key and public key.
[certificates] Generated front-proxy CA certificate and key.
[certificates] Generated front-proxy client certificate and key.
[certificates] Valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[apiclient] Created API client, waiting for the control plane to become ready
[apiclient] All control plane components are healthy after 17.045449 seconds
[apiclient] Waiting for at least one node to register
[apiclient] First node has registered after 5.008588 seconds
[token] Using token: a8dd42.afdb86eda4a8c987
[apiconfig] Created RBAC rules
[addons] Created essential addon: kube-proxy
[addons] Created essential addon: kube-dns

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run (as a regular user):

  sudo cp /etc/kubernetes/admin.conf $HOME/
  sudo chown $(id -u):$(id -g) $HOME/admin.conf
  export KUBECONFIG=$HOME/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:

http://kubernetes.io/docs/admin/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join --token abcdefghijklmn 10.27.53.32:6443

root@shaolin:~/k8s-install# pods
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP            NODE
kube-system   etcd-shaolin                      1/1       Running   0          34s       10.27.53.32   shaolin
kube-system   kube-apiserver-shaolin            1/1       Running   0          35s       10.27.53.32   shaolin
kube-system   kube-controller-manager-shaolin   1/1       Running   0          23s       10.27.53.32   shaolin
kube-system   kube-dns-3913472980-tkr91         0/3       Pending   0          1m        &lt;none&gt;
kube-system   kube-proxy-bzvvk                  1/1       Running   0          1m        10.27.53.32   shaolin
kube-system   kube-scheduler-shaolin            1/1       Running   0          46s       10.27.53.32   shaolin

</code></pre>
<p>k8s 1.6.2版本的weave network的安装与之前稍有不同，因为k8s 1.6启用了更为安全的机制，默认采用<a href="http://tonybai.com/2016/11/25/the-security-settings-for-kubernetes-cluster/">RBAC</a>对运行于cluster上的workload进行有限授权。我们要使用的weave network plugin的yaml为<a href="https://github.com/weaveworks/weave/releases/download/latest_release/weave-daemonset-k8s-1.6.yaml">weave-daemonset-k8s-1.6.yaml</a>：</p>
<pre><code>root@shaolin:~/k8s-install# kubectl apply -f https://git.io/weave-kube-1.6
clusterrole "weave-net" created
serviceaccount "weave-net" created
clusterrolebinding "weave-net" created
daemonset "weave-net" created
</code></pre>
<p>如果你的weave pod启动失败且原因类似如下日志：</p>
<pre><code>Network 172.30.0.0/16 overlaps with existing route 172.16.0.0/12 on host.
</code></pre>
<p>你需要修改你的weave network的 IPALLOC_RANGE(这里我使用了172.32.0.0/16)：</p>
<pre><code>//weave-daemonset-k8s-1.6.yaml
... ...
spec:
  template:
    metadata:
      labels:
        name: weave-net
    spec:
      hostNetwork: true
      hostPID: true
      containers:
        - name: weave
          env:
            - name: IPALLOC_RANGE
              value: 172.32.0.0/16
... ...
</code></pre>
<p>master安装ok后，我们将wudang、emei两个node作为k8s minion node，来测试一下cluster的搭建是否是正确的，同时这一过程也在wudang、emei上安装上了kubelet和kube-proxy，这两个组件在后续的“改造”过程中是可以直接使用的：</p>
<pre><code>以emei node为例：

root@emei:~# kubeadm join --token abcdefghijklmn 10.27.53.32:6443
[kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters.
[preflight] Running pre-flight checks
[preflight] WARNING: docker version is greater than the most recently validated version. Docker version: 17.03.1-ce. Max validated version: 1.12
[preflight] Starting the kubelet service
[discovery] Trying to connect to API Server "10.27.53.32:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://10.27.53.32:6443"
[discovery] Cluster info signature and contents are valid, will use API Server "https://10.27.53.32:6443"
[discovery] Successfully established connection with API Server "10.27.53.32:6443"
[bootstrap] Detected server version: v1.6.2
[bootstrap] The server supports the Certificates API (certificates.k8s.io/v1beta1)
[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request
[csr] Received signed certificate from the API server, generating KubeConfig...
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"

Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.
</code></pre>
<p>建立一个多pod的nginx服务，测试一下集群网络是否通！这里就不赘述了。</p>
<p>安装后的single-master kubernetes cluster的状态就如下图所示：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-1.png" alt="img{512x368}" /></p>
<h2>四、第二步：搭建etcd cluster for ha k8s cluster</h2>
<p>k8s集群状态和数据都存储在etcd中，高可用的k8s集群离不开高可用的etcd cluster。我们需要为最终的ha k8s cluster提供一个ha的etcd cluster，如何做呢？</p>
<p>当前k8s cluster中，shaolin master node上的etcd存储着k8s集群的所有数据和状态。我们需要在wudang和emei两个节点上也建立起etcd实例，与现存在 etcd共同构建成为高可用的且存储有cluster数据和状态的集群。我们将这一过程再细化为几个小步骤：</p>
<h3>0、在emei、wudang两个节点上启动kubelet服务</h3>
<p>etcd cluster可以采用完全独立的、与k8s组件无关的建立方法。不过这里我采用的是和master一样的方式，即采用由wudang和emei两个node上kubelet启动的etcd作为etcd cluster的两个member。此时，wudang和emei两个node的角色是k8s minion node，我们需要首先清理一下这两个node的数据：</p>
<pre><code>root@shaolin:~/k8s-install # kubectl drain wudang --delete-local-data --force --ignore-daemonsets
node "wudang" cordoned
WARNING: Ignoring DaemonSet-managed pods: kube-proxy-mxwp3, weave-net-03jbh; Deleting pods with local storage: weave-net-03jbh
pod "my-nginx-2267614806-fqzph" evicted
node "wudang" drained

root@wudang:~# kubeadm reset
[preflight] Running pre-flight checks
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
[reset] No etcd manifest found in "/etc/kubernetes/manifests/etcd.yaml", assuming external etcd.
[reset] Deleting contents of stateful directories: [/var/lib/kubelet /etc/cni/net.d /var/lib/dockershim]
[reset] Deleting contents of config directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]

root@shaolin:~/k8s-install # kubectl drain emei --delete-local-data --force --ignore-daemonsets
root@emei:~# kubeadm reset

root@shaolin:~/k8s-install# kubectl delete node/wudang
root@shaolin:~/k8s-install# kubectl delete node/emei

</code></pre>
<p>我们的小目标中：etcd cluster将由各个node上的kubelet自动启动；而kubelet则是由systemd在sys init时启动，且其启动配置如下：</p>
<pre><code>root@wudang:~# cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true"
Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"
Environment="KUBELET_NETWORK_ARGS=--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
Environment="KUBELET_DNS_ARGS=--cluster-dns=10.96.0.10 --cluster-domain=cluster.local"
Environment="KUBELET_AUTHZ_ARGS=--authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt"
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS $KUBELET_EXTRA_ARGS
</code></pre>
<p>我们需要首先在wudang和emei node上将kubelet启动起来，我们以wudang node为例：</p>
<pre><code>root@wudang:~# systemctl enable kubelet
root@wudang:~# systemctl start kubelet
</code></pre>
<p>查看kubelet service日志：</p>
<pre><code>root@wudang:~# journalctl -u kubelet -f

May 10 10:58:41 wudang systemd[1]: Started kubelet: The Kubernetes Node Agent.
May 10 10:58:41 wudang kubelet[27179]: I0510 10:58:41.798507   27179 feature_gate.go:144] feature gates: map[]
May 10 10:58:41 wudang kubelet[27179]: error: failed to run Kubelet: invalid kubeconfig: stat /etc/kubernetes/kubelet.conf: no such file or directory
May 10 10:58:41 wudang systemd[1]: kubelet.service: Main process exited, code=exited, status=1/FAILURE
May 10 10:58:41 wudang systemd[1]: kubelet.service: Unit entered failed state.
May 10 10:58:41 wudang systemd[1]: kubelet.service: Failed with result 'exit-code'.
</code></pre>
<p>kubelet启动失败，因为缺少/etc/kubernetes/kubelet.conf这个配置文件。我们需要向shaolin node求援，我们需要将shaolin node上的同名配置文件copy到wudang和emei两个node下面，当然同时需要copy的还包括shaolin node上的/etc/kubernetes/pki目录：</p>
<pre><code>root@wudang:~# kubectl --kubeconfig=/etc/kubernetes/kubelet.conf config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://10.27.53.32:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:node:shaolin
  name: system:node:shaolin@kubernetes
current-context: system:node:shaolin@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:shaolin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

root@wudang:~# ls /etc/kubernetes/pki
apiserver.crt  apiserver-kubelet-client.crt  ca.crt  ca.srl              front-proxy-ca.key      front-proxy-client.key  sa.pub
apiserver.key  apiserver-kubelet-client.key ca.key  front-proxy-ca.crt  front-proxy-client.crt  sa.key
</code></pre>
<p>systemctl daemon-reload; systemctl restart kubelet后，再查看kubelet service日志，你会发现kubelet起来了！</p>
<pre><code>以wudang node为例：

root@wudang:~# journalctl -u kubelet -f
-- Logs begin at Mon 2017-05-08 15:12:01 CST. --
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.213529   26907 factory.go:54] Registering systemd factory
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.213674   26907 factory.go:86] Registering Raw factory
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.213813   26907 manager.go:1106] Started watching for new ooms in manager
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.216383   26907 oomparser.go:185] oomparser using systemd
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.217415   26907 manager.go:288] Starting recovery of all containers
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.285428   26907 manager.go:293] Recovery completed
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.344425   26907 kubelet_node_status.go:230] Setting node annotation to enable volume controller attach/detach
May 11 10:37:07 wudang kubelet[26907]: E0511 10:37:07.356188   26907 eviction_manager.go:214] eviction manager: unexpected err: failed GetNode: node 'wudang' not found
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.358402   26907 kubelet_node_status.go:77] Attempting to register node wudang
May 11 10:37:07 wudang kubelet[26907]: I0511 10:37:07.363083   26907 kubelet_node_status.go:80] Successfully registered node wudang
</code></pre>
<p>此时此刻，我们先让wudang、emei node上的kubelet先连着shaolin node上的apiserver。</p>
<h3>1、在emei、wudang两个节点上建立一个etcd cluster</h3>
<p>我们以shaolin node上的/etc/kubernetes/manifests/etcd.yaml为蓝本，修改出wudang和emei上的etcd.yaml，主要的变化在于containers:command部分：</p>
<pre><code>wudang上的/etc/kubernetes/manifests/etcd.yaml：

spec:
  containers:
  - command:
    - etcd
    - --name=etcd-wudang
    - --initial-advertise-peer-urls=http://10.24.138.208:2380
    - --listen-peer-urls=http://10.24.138.208:2380
    - --listen-client-urls=http://10.24.138.208:2379,http://127.0.0.1:2379
    - --advertise-client-urls=http://10.24.138.208:2379
    - --initial-cluster-token=etcd-cluster
    - --initial-cluster=etcd-wudang=http://10.24.138.208:2380,etcd-emei=http://10.27.52.72:2380
    - --initial-cluster-state=new
    - --data-dir=/var/lib/etcd
    image: gcr.io/google_containers/etcd-amd64:3.0.17

emei上的/etc/kubernetes/manifests/etcd.yaml：

spec:
  containers:
  - command:
    - etcd
    - --name=etcd-emei
    - --initial-advertise-peer-urls=http://10.27.52.72:2380
    - --listen-peer-urls=http://10.27.52.72:2380
    - --listen-client-urls=http://10.27.52.72:2379,http://127.0.0.1:2379
    - --advertise-client-urls=http://10.27.52.72:2379
    - --initial-cluster-token=etcd-cluster
    - --initial-cluster=etcd-emei=http://10.27.52.72:2380,etcd-wudang=http://10.24.138.208:2380
    - --initial-cluster-state=new
    - --data-dir=/var/lib/etcd
    image: gcr.io/google_containers/etcd-amd64:3.0.17

</code></pre>
<p>将这两个文件分别放入各自node的/etc/kubernetes/manifests目录后，各自node上的kubelet将会自动将对应的etcd pod启动起来！</p>
<pre><code>root@shaolin:~# pods
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
kube-system   etcd-emei                         1/1       Running   0          11s       10.27.52.72     emei
kube-system   etcd-shaolin                      1/1       Running   0          25m       10.27.53.32     shaolin
kube-system   etcd-wudang                       1/1       Running   0          24s       10.24.138.208   wudang
</code></pre>
<p>我们查看一下当前etcd cluster的状态：</p>
<pre><code># etcdctl endpoint status --endpoints=10.27.52.72:2379,10.24.138.208:2379
10.27.52.72:2379, 6e80adf8cd57f826, 3.0.17, 25 kB, false, 17, 660
10.24.138.208:2379, f3805d1ab19c110b, 3.0.17, 25 kB, true, 17, 660

注：输出的列从左到右分别表示：endpoint URL, ID, version, database size, leadership status, raft term, and raft status.
因此，我们可以看出wudang(10.24.138.208)上的etcd被选为cluster leader了

</code></pre>
<p>我们测试一下etcd cluster，put一些key：</p>
<pre><code>在wudang节点：(注意：export ETCDCTL_API=3)

root@wudang:~# etcdctl put foo bar
OK
root@wudang:~# etcdctl put foo1 bar1
OK
root@wudang:~# etcdctl get foo
foo
bar

在emei节点：

root@emei:~# etcdctl get foo
foo
bar
</code></pre>
<p>至此，当前kubernetes cluster的状态示意图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-2-1.png" alt="img{512x368}" /></p>
<h3>2、同步shaolin上etcd的数据到etcd cluster中</h3>
<p>kubernetes 1.6.2版本默认使用3.x版本etcd。etcdctl 3.x版本提供了一个make-mirror功能用于在etcd cluster间同步数据，这样我们就可以通过etcdctl make-mirror将shaolin上etcd的k8s cluster数据同步到上述刚刚创建的etcd cluster中。在emei node上执行下面命令：</p>
<pre><code>root@emei:~# etcdctl make-mirror --no-dest-prefix=true  127.0.0.1:2379  --endpoints=10.27.53.32:2379 --insecure-skip-tls-verify=true
... ...
261
302
341
380
420
459
498
537
577
616
655

... ...
</code></pre>
<p>etcdctl make-mirror每隔30s输出一次日志，不过通过这些日志无法看出来同步过程。并且etcdctl make-mirror似乎是流式同步：没有结束的边界。因此你需要手工判断一下数据是否都同步过去了！比如通过查看某个key，对比两边的差异的方式：</p>
<pre><code># etcdctl get --from-key /api/v2/registry/clusterrolebindings/cluster-admin

.. ..
compact_rev_key
122912
</code></pre>
<p>或者通过endpoint status命令查看数据库size大小，对比双方的size是否一致。一旦差不多了，就可以停掉make-mirror的执行了！</p>
<h3>3、将shaolin上的apiserver连接的etcd改为连接etcd cluster，停止并删除shaolin上的etcd</h3>
<p>修改shaolin node上的/etc/kubernetes/manifests/kube-apiserver.yaml，让shaolin上的kube0-apiserver连接到emei node上的etcd：</p>
<pre><code>修改下面一行：
- --etcd-servers=http://10.27.52.72:2379
</code></pre>
<p>修改保存后，kubelet会自动重启kube-apiserver，重启后的kube-apiserver工作正常！</p>
<p>接下来，我们停掉并删除掉shaolin上的etcd(并删除相关数据存放目录)：</p>
<pre><code>root@shaolin:~# rm /etc/kubernetes/manifests/etcd.yaml
root@shaolin:~# rm -fr /var/lib/etcd
</code></pre>
<p>再查看k8s cluster当前pod，你会发现etcd-shaolin不见了。</p>
<p>至此，k8s集群的当前状态示意图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-2-3.png" alt="img{512x368}" /></p>
<h3>4、重新创建shaolin上的etcd ，并以member形式加入etcd cluster</h3>
<p>我们首先需要在已存在的etcd cluster中添加etcd-shaolin这个member:</p>
<pre><code>root@wudang:~/kubernetes-conf-shaolin/manifests# etcdctl member add etcd-shaolin --peer-urls=http://10.27.53.32:2380
Member 3184cfa57d8ef00c added to cluster 140cec6dd173ab61
</code></pre>
<p>然后，在shaolin node上基于原shaolin上的etcd.yaml文件进行如下修改：</p>
<pre><code>// /etc/kubernetes/manifests/etcd.yaml
... ...
spec:
  containers:
  - command:
    - etcd
    - --name=etcd-shaolin
    - --initial-advertise-peer-urls=http://10.27.53.32:2380
    - --listen-peer-urls=http://10.27.53.32:2380
    - --listen-client-urls=http://10.27.53.32:2379,http://127.0.0.1:2379
    - --advertise-client-urls=http://10.27.53.32:2379
    - --initial-cluster-token=etcd-cluster
    - --initial-cluster=etcd-shaolin=http://10.27.53.32:2380,etcd-wudang=http://10.24.138.208:2380,etcd-emei=http://10.27.52.72:2380
    - --initial-cluster-state=existing
    - --data-dir=/var/lib/etcd
    image: gcr.io/google_containers/etcd-amd64:3.0.17
</code></pre>
<p>修改保存后，kubelet将自动拉起etcd-shaolin：</p>
<pre><code>root@shaolin:~/k8s-install# pods
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE       IP              NODE
kube-system   etcd-emei                         1/1       Running   0          3h        10.27.52.72     emei
kube-system   etcd-shaolin                      1/1       Running   0          8s        10.27.53.32     shaolin
kube-system   etcd-wudang                       1/1       Running   0          3h        10.24.138.208   wudang
</code></pre>
<p>查看etcd cluster状态：</p>
<pre><code>root@shaolin:~# etcdctl endpoint status --endpoints=10.27.52.72:2379,10.24.138.208:2379,10.27.53.32:2379
10.27.52.72:2379, 6e80adf8cd57f826, 3.0.17, 11 MB, false, 17, 34941
10.24.138.208:2379, f3805d1ab19c110b, 3.0.17, 11 MB, true, 17, 34941
10.27.53.32:2379, 3184cfa57d8ef00c, 3.0.17, 11 MB, false, 17, 34941
</code></pre>
<p>可以看出三个etcd实例的数据size、raft status是一致的，wudang node上的etcd是leader！</p>
<h3>5、将shaolin上的apiserver的etcdserver指向改回etcd-shaolin</h3>
<pre><code>// /etc/kubernetes/manifests/kube-apiserver.yaml

... ...
- --etcd-servers=http://127.0.0.1:2379
... ...

</code></pre>
<p>生效重启后，当前kubernetes cluster的状态如下面示意图：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-ha-cluster-step-2-5.png" alt="img{512x368}" /></p>
<p>第二部分在<a href="http://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part2/">这里</a>。</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/05/15/setup-a-ha-kubernetes-cluster-based-on-kubeadm-part1/feed/</wfw:commentRss>
		<slash:comments>28</slash:comments>
		</item>
		<item>
		<title>理解Kubernetes网络之Flannel网络</title>
		<link>https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/</link>
		<comments>https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/#comments</comments>
		<pubDate>Tue, 17 Jan 2017 07:40:58 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[arp]]></category>
		<category><![CDATA[brctl]]></category>
		<category><![CDATA[calico]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[cni]]></category>
		<category><![CDATA[CNM]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[coreos]]></category>
		<category><![CDATA[DNAT]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[etcdctl]]></category>
		<category><![CDATA[fdb]]></category>
		<category><![CDATA[flanned]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[iproute2]]></category>
		<category><![CDATA[iptables]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[kube-up.sh]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MASQUERADE]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[NAT]]></category>
		<category><![CDATA[overlaynetwork]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[POSTROUTING]]></category>
		<category><![CDATA[PREROUTING]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[SNAT]]></category>
		<category><![CDATA[tunnel]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[VTEP]]></category>
		<category><![CDATA[VXLAN]]></category>
		<category><![CDATA[weave]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[阿里云]]></category>
		<category><![CDATA[隧道]]></category>
		<category><![CDATA[集群]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2126</guid>
		<description><![CDATA[第一次采用kube-up.sh脚本方式安装的Kubernetes cluster目前运行良好，master node上的组件状态也始终是“没毛病”： # kubectl get cs NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy {"health": "true"} 不过在第二次尝试用kubeadm安装和初始化Kubernetes cluster时遇到的各种网络问题还是让我“心有余悸”。于是趁上个周末，对Kubernetes的网络原理进行了一些针对性的学习。这里把对Kubernetes网络的理解记录一下和大家一起分享。 Kubernetes支持Flannel、Calico、Weave network等多种cni网络Drivers，但由于学习过程使用的是第一个cluster的Flannel网络，这里的网络原理只针对k8s+Flannel网络。 一、环境+提示 凡涉及到Docker、Kubernetes这类正在active dev的开源项目的文章，我都不得不提一嘴，那就是随着K8s以及flannel的演化，本文中的一些说法可能不再正确。提醒大家：阅读此类技术文章务必结合“环境”。 这里我们使用的环境就是我第一次建立k8s cluster的环境： # kube-apiserver --version Kubernetes v1.3.7 # /opt/bin/flanneld -version 0.5.5 # /opt/bin/etcd -version etcd Version: 3.0.12 Git SHA: 2d1e2e8 Go Version: go1.6.3 Go OS/Arch: linux/amd64 另外整个集群搭建在阿里云上，每个ECS上的OS及kernel版本：Ubuntu [...]]]></description>
			<content:encoded><![CDATA[<p>第一次<a href="http://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu">采用kube-up.sh脚本方式安装</a>的<a href="http://tonybai.com/tag/kubernetes">Kubernetes</a> cluster目前运行良好，master node上的组件状态也始终是“没毛病”：</p>
<pre><code># kubectl get cs
NAME                 STATUS    MESSAGE              ERROR
controller-manager   Healthy   ok
scheduler            Healthy   ok
etcd-0               Healthy   {"health": "true"}
</code></pre>
<p>不过在第二次尝试<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm">用kubeadm安装和初始化Kubernetes cluster</a>时遇到的各种网络问题还是让我“心有余悸”。于是趁上个周末，对Kubernetes的网络原理进行了一些针对性的学习。这里把对Kubernetes网络的理解记录一下和大家一起分享。</p>
<p>Kubernetes支持<a href="https://github.com/coreos/flannel">Flannel</a>、<a href="https://projectcalico.org/">Calico</a>、<a href="https://www.weave.works/">Weave network</a>等多种<a href="https://github.com/containernetworking/cni">cni网络</a>Drivers，但由于学习过程使用的是第一个cluster的Flannel网络，这里的网络原理只针对k8s+Flannel网络。</p>
<h3>一、环境+提示</h3>
<p>凡涉及到Docker、Kubernetes这类正在active dev的开源项目的文章，我都不得不提一嘴，那就是随着K8s以及flannel的演化，本文中的一些说法可能不再正确。提醒大家：阅读此类技术文章务必结合“环境”。</p>
<p>这里我们使用的环境就是我第一次建立k8s cluster的环境：</p>
<pre><code># kube-apiserver --version
Kubernetes v1.3.7

# /opt/bin/flanneld -version
0.5.5

# /opt/bin/etcd -version
etcd Version: 3.0.12
Git SHA: 2d1e2e8
Go Version: go1.6.3
Go OS/Arch: linux/amd64
</code></pre>
<p>另外整个集群搭建在<a href="https://www.aliyun.com/">阿里云</a>上，每个ECS上的OS及kernel版本：Ubuntu 14.04.4 LTS，3.19.0-70-generic。</p>
<p>在我的测试环境，有两个node：master node和一个minion node。master node参与workload的调度。所以你基本可以认为有两个minion node即可。</p>
<h3>二、Kubernetes Cluster中的几个“网络”</h3>
<p>之前的k8s cluster采用的是默认安装，即直接使用了配置脚本中(kubernetes/cluster/ubuntu/config-default.sh)自带的一些参数，比如：</p>
<pre><code>//摘自kubernetes/cluster/ubuntu/config-default.sh

export nodes=${nodes:-"root@master_node_ip root@minion_node_ip"}
export SERVICE_CLUSTER_IP_RANGE=${SERVICE_CLUSTER_IP_RANGE:-192.168.3.0/24}
export FLANNEL_NET=${FLANNEL_NET:-172.16.0.0/16}
</code></pre>
<p>从这里我们能够识别出三个“网络”：</p>
<ul>
<li>node network：承载kubernetes集群中各个“物理”Node(master和minion)通信的网络；</li>
<li>service network：由kubernetes集群中的Services所组成的“网络”；</li>
<li>flannel network： 即Pod网络，集群中承载各个Pod相互通信的网络。</li>
</ul>
<p>node network自不必多说，node间通过你的本地局域网（无论是物理的还是虚拟的）通信。</p>
<p>service network比较特殊，每个新创建的service会被分配一个service IP，在当前集群中，这个IP的分配范围是192.168.3.0/24。不过这个IP并不“真实”，更像一个“占位符”并且只有入口流量，所谓的“network”也是“名不符实”的，后续我们会详尽说明。</p>
<p>flannel network是我们要理解的重点，cluster中各个Pod要实现相互通信，必须走这个网络，无论是在同一node上的Pod还是跨node的Pod。我们的cluster中，flannel net的分配范围是：172.16.0.0/16。</p>
<p>在进一步挖掘“原理”之前，我们先来直观认知一下service network和flannel network：</p>
<p>Service network(看cluster-ip一列)：</p>
<pre><code># kubectl get services
NAME           CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
index-api      192.168.3.168   &lt;none&gt;        30080/TCP   18d
kubernetes     192.168.3.1     &lt;none&gt;        443/TCP     94d
my-nginx       192.168.3.179   &lt;nodes&gt;       80/TCP      90d
nginx-kit      192.168.3.196   &lt;nodes&gt;       80/TCP      12d
rbd-rest-api   192.168.3.22    &lt;none&gt;        8080/TCP    60d
</code></pre>
<p>Flannel network（看IP那列）:</p>
<pre><code># kubectl get pod -o wide
NAME                           READY     STATUS    RESTARTS   AGE       IP            NODE
my-nginx-2395715568-gpljv      1/1       Running   6          91d       172.16.99.3   {master node ip}
nginx-kit-3872865736-rc8hr     2/2       Running   0          12d       172.16.57.7   {minion node ip}
... ...
</code></pre>
<h3>三、平坦的Flannel网络</h3>
<h4>1、Kubenetes安装后的网络状态</h4>
<p>首先让我们来看看：<a href="http://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu">kube-up.sh在安装k8s集群</a>时对各个K8s Node都动了什么手脚！</p>
<h5>a) 修改docker default配置</h5>
<p>在ubuntu 14.04下，<a href="http://tonybai.com/2016/12/27/when-docker-meets-systemd">docker的配置</a>都在/etc/default/docker文件中。如果你曾经修改过该文件，那么kube-up.sh脚本方式安装完kubernetes后，你会发现/etc/default/docker已经变样了，只剩下了一行：</p>
<pre><code>master node:
DOCKER_OPTS=" -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.99.1/24 --mtu=1450"

minion node:
DOCKER_OPTS=" -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.57.1/24 --mtu=1450"
</code></pre>
<p>可以看出kube-up.sh修改了Docker daemon的&#8211;bip选项，使得该node上docker daemon在该node的fannel subnet范围以内为启动的Docker container分配IP地址。</p>
<h5>b) 在etcd中初始化flannel网络数据</h5>
<p>多个node上的Flanneld依赖一个<a href="https://github.com/coreos/etcd/">etcd cluster</a>来做集中配置服务，etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化，实时感知集群中node的变化。</p>
<p>我们可以通过etcdctl查询到这些配置数据：</p>
<pre><code>master node:

//flannel network配置
# etcdctl --endpoints http://127.0.0.1:{etcd listen port} get  /coreos.com/network/config
{"Network":"172.16.0.0/16", "Backend": {"Type": "vxlan"}}

# etcdctl --endpoints http://127.0.0.1:{etcd listen port} ls  /coreos.com/network/subnets
/coreos.com/network/subnets/172.16.99.0-24
/coreos.com/network/subnets/172.16.57.0-24

//某一node上的flanne subnet和vtep配置
# etcdctl --endpoints http://127.0.0.1:{etcd listen port} get  /coreos.com/network/subnets/172.16.99.0-24
{"PublicIP":"{master node ip}","BackendType":"vxlan","BackendData":{"VtepMAC":"b6:bf:4c:81:cf:3b"}}

minion node:
# etcdctl --endpoints http://127.0.0.1:{etcd listen port} get  /coreos.com/network/subnets/172.16.57.0-24
{"PublicIP":"{minion node ip}","BackendType":"vxlan","BackendData":{"VtepMAC":"d6:51:2e:80:5c:69"}}
</code></pre>
<p>或用etcd 提供的rest api：</p>
<pre><code># curl -L http://127.0.0.1:{etcd listen port}/v2/keys/coreos.com/network/config
{"action":"get","node":{"key":"/coreos.com/network/config","value":"{\"Network\":\"172.16.0.0/16\", \"Backend\": {\"Type\": \"vxlan\"}}","modifiedIndex":5,"createdIndex":5}}
</code></pre>
<h5>c) 启动flanneld</h5>
<p>kube-up.sh在每个Kubernetes node上启动了一个flanneld的程序：</p>
<pre><code># ps -ef|grep flanneld

master node:
root      1151     1  0  2016 ?        00:02:34 /opt/bin/flanneld --etcd-endpoints=http://127.0.0.1:{etcd listen port} --ip-masq --iface={master node ip}

minion node:
root     11940     1  0  2016 ?        00:07:05 /opt/bin/flanneld --etcd-endpoints=http://{master node ip}:{etcd listen port} --ip-masq --iface={minion node ip}
</code></pre>
<p>一旦flanneld启动，它将从etcd中读取配置，并请求获取一个subnet lease(租约)，有效期目前是24hrs，并且监视etcd的数据更新。flanneld一旦获取subnet租约、配置完backend，它会将一些信息写入/run/flannel/subnet.env文件。</p>
<pre><code>master node：
# cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.16.0.0/16
FLANNEL_SUBNET=172.16.99.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

minion node:
# cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.16.0.0/16
FLANNEL_SUBNET=172.16.57.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
</code></pre>
<p>当然flanneld的最大意义在于根据etcd中存储的全cluster的subnet信息，跨node传输flannel network中的数据包，这个后面会详细说明。</p>
<h5>d) 创建flannel.1 网络设备、更新路由信息</h5>
<p>各个node上的网络设备列表新增一个名为flannel.1的类型为vxlan的网络设备：</p>
<pre><code>master node:

# ip -d link show
4: flannel.1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether b6:bf:4c:81:cf:3b brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local {master node local ip} dev eth0 port 0 0 nolearning ageing 300

minion node:

349: flannel.1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether d6:51:2e:80:5c:69 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local  {minion node local ip} dev eth0 port 0 0 nolearning ageing 300

</code></pre>
<p>从flannel.1的设备信息来看，它似乎与eth0存在着某种bind关系。这是在其他bridge、veth设备描述信息中所没有的。</p>
<p>flannel.1设备的ip：</p>
<pre><code>master node:

flannel.1 Link encap:Ethernet  HWaddr b6:bf:4c:81:cf:3b
          inet addr:172.16.99.0  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
          RX packets:5993274 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5829044 errors:0 dropped:292 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1689890445 (1.6 GB)  TX bytes:1144725704 (1.1 GB)

minion node:

flannel.1 Link encap:Ethernet  HWaddr d6:51:2e:80:5c:69
          inet addr:172.16.57.0  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
          RX packets:6294640 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5755599 errors:0 dropped:25 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:989362527 (989.3 MB)  TX bytes:1861492847 (1.8 GB)

</code></pre>
<p>可以看到两个node上的flannel.1的ip与k8s cluster为两个node上分配subnet的ip范围是对应的。</p>
<p>下面是两个node上的当前路由表：</p>
<pre><code>master node:

# ip route
... ...
172.16.0.0/16 dev flannel.1  proto kernel  scope link  src 172.16.99.0
172.16.99.0/24 dev docker0  proto kernel  scope link  src 172.16.99.1
... ...

minion node:

# ip route
... ...
172.16.0.0/16 dev flannel.1
172.16.57.0/24 dev docker0  proto kernel  scope link  src 172.16.57.1
... ...

</code></pre>
<p>以上信息将为后续数据包传输分析打下基础。</p>
<h5>e) 平坦的flannel network</h5>
<p>从以上kubernetes和flannel network安装之后获得的网络信息，我们能看出flannel network是一个flat network。在flannel：172.16.0.0/16这个大网下，每个kubernetes node从中分配一个子网片段(/24)：</p>
<pre><code>master node：
  --bip=172.16.99.1/24

minion node：
  --bip=172.16.57.1/24

root@node1:~# etcdctl --endpoints http://127.0.0.1:{etcd listen port} ls  /coreos.com/network/subnets
/coreos.com/network/subnets/172.16.99.0-24
/coreos.com/network/subnets/172.16.57.0-24
</code></pre>
<p>用一张图来诠释可能更为直观：</p>
<p><img src="http://tonybai.com/wp-content/uploads/flat-flannel-network.png" alt="img{512x368}" /></p>
<p>这个是不是有些像x86-64的虚拟内存寻址空间啊（同样是平坦内存地址访问模型）！</p>
<p>在平坦的flannel network中，每个pod都会被分配唯一的ip地址，且每个k8s node的subnet各不重叠，没有交集。不过这样的subnet分配模型也有一定弊端，那就是可能存在ip浪费：一个node上有200多个flannel ip地址(xxx.xxx.xxx.xxx/24)，如果仅仅启动了几个Pod，那么其余ip就处于闲置状态。</p>
<h4>2、Flannel网络通信原理</h4>
<p>这里我们模仿flannel官方的那幅原理图，画了一幅与我们的实验环境匹配的图，作为后续讨论flannel网络通信流程的基础：</p>
<p><img src="http://tonybai.com/wp-content/uploads/kubernetes-flannel.png" alt="img{512x368}" /></p>
<p>如上图所示，我们来看看从pod1：172.16.99.8发出的数据包是如何到达pod3：172.16.57.15的（比如：在pod1的某个container中ping -c 3 172.16.57.15）。</p>
<h5>a) 从Pod出发</h5>
<p>由于k8s更改了docker的DOCKER_OPTS，显式指定了&#8211;bip，这个值与分配给该node上的subnet的范围是一致的。这样一来，docker引擎每次创建一个Docker container，该container被分配到的ip都在flannel subnet范围内。</p>
<p>当我们在Pod1下的某个容器内执行ping -c 3 172.16.57.15，数据包便开始了它在flannel network中的旅程。</p>
<p>Pod是Kubernetes调度的基本unit。Pod内的多个container共享一个<a href="http://tonybai.com/2017/01/11/understanding-linux-network-namespace-for-docker-network/">network namespace</a>。kubernetes在创建Pod时，首先先创建pause容器，然后再以pause的network namespace为基础，创建pod内的其他容器（&#8211;net=container:xxx），这样Pod内的所有容器便共享一个network namespace，这些容器间的访问直接通过localhost即可。比如Pod下A容器启动了一个服务，监听8080端口，那么同一个Pod下面的另外一个B容器通过访问localhost:8080即可访问到A容器下面的那个服务。</p>
<p>在之前的《<a href="http://tonybai.com/2017/01/11/understanding-linux-network-namespace-for-docker-network/">理解Docker容器网络之Linux Network Namespace</a>》一文中，我相信我已经讲清楚了单机下Docker容器数据传输的路径。在这个环节中，数据包的传输路径也并无不同。</p>
<p>我们看一下Pod1中某Container内的路由信息：</p>
<pre><code># docker exec ba75f81455c7 ip route
default via 172.16.99.1 dev eth0
172.16.99.0/24 dev eth0  proto kernel  scope link  src 172.16.99.8
</code></pre>
<p>目的地址172.16.57.15并不在直连网络中，因此数据包通过default路由出去。default路由的路由器地址是172.16.99.1，也就是上面的docker0 bridge的IP地址。相当于docker0 bridge以“三层的工作模式”直接接收到来自容器的数据包(而并非从bridge的二层端口接收)。</p>
<h5>b) docker0与flannel.1之间的包转发</h5>
<p>数据包到达docker0后，docker0的内核栈处理程序发现这个数据包的目的地址是172.16.57.15，并不是真的要送给自己，于是开始为该数据包找下一hop。根据master node上的路由表：</p>
<pre><code>master node：

# ip route
... ...
172.16.0.0/16 dev flannel.1  proto kernel  scope link  src 172.16.99.0
172.16.99.0/24 dev docker0  proto kernel  scope link  src 172.16.99.1
... ...
</code></pre>
<p>我们匹配到“172.16.0.0/16”这条路由！这是一条直连路由，数据包被直接送到flannel.1设备上。</p>
<h5>c) flannel.1设备以及flanneld的功用</h5>
<p>flannel.1是否会重复docker0的套路呢：包不是发给自己，转发数据包？会，也不会。</p>
<p>“会”是指flannel.1肯定要将包转发出去，因为毕竟包不是给自己的（包目的ip是172.16.57.15, vxlan设备ip是172.16.99.0）。<br />
“不会”是指flannel.1不会走寻常套路去转发包，因为它是一个vxlan类型的设备，也称为vtep，virtual tunnel end point。</p>
<p>那么它到底是怎么处理数据包的呢？这里涉及一些Linux内核对vxlan处理的内容，详细内容可参见本文末尾的参考资料。</p>
<p>flannel.1收到数据包后，由于自己不是目的地，也要尝试将数据包重新发送出去。数据包沿着网络协议栈向下流动，在二层时需要封二层以太包，填写目的mac地址，这时一般应该发出arp：”who is 172.16.57.15&#8243;。但vxlan设备的特殊性就在于它并没有真正在二层发出这个arp包，因为下面的这个内核参数设置：</p>
<pre><code>master node:

# cat /proc/sys/net/ipv4/neigh/flannel.1/app_solicit
3
</code></pre>
<p>而是由linux kernel引发一个”L3 MISS”事件并将arp请求发到用户空间的flanned程序。</p>
<p>flanned程序收到”L3 MISS”内核事件以及arp请求(who is 172.16.57.15)后，并不会向外网发送arp request，而是尝试从etcd查找该地址匹配的子网的vtep信息。在前面章节我们曾经展示过etcd中Flannel network的配置信息：</p>
<pre><code>master node:

# etcdctl --endpoints http://127.0.0.1:{etcd listen port} ls  /coreos.com/network/subnets
/coreos.com/network/subnets/172.16.99.0-24
/coreos.com/network/subnets/172.16.57.0-24

# curl -L http://127.0.0.1:{etcd listen port}/v2/keys/coreos.com/network/subnets/172.16.57.0-24
{"action":"get","node":{"key":"/coreos.com/network/subnets/172.16.57.0-24","value":"{\"PublicIP\":\"{minion node local ip}\",\"BackendType\":\"vxlan\",\"BackendData\":{\"VtepMAC\":\"d6:51:2e:80:5c:69\"}}","expiration":"2017-01-17T09:46:20.607339725Z","ttl":21496,"modifiedIndex":2275460,"createdIndex":2275460}}
</code></pre>
<p>flanneld从etcd中找到了答案：</p>
<pre><code>subnet: 172.16.57.0/24
public ip: {minion node local ip}
VtepMAC: d6:51:2e:80:5c:69
</code></pre>
<p>我们查看minion node上的信息，发现minion node上的flannel.1 设备mac就是d6:51:2e:80:5c:69：</p>
<pre><code>minion node:

#ip -d link show

349: flannel.1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether d6:51:2e:80:5c:69 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 10.46.181.146 dev eth0 port 0 0 nolearning ageing 300
</code></pre>
<p>接下来，flanned将查询到的信息放入master node host的arp cache表中：</p>
<pre><code>master node:

#ip n |grep 172.16.57.15
172.16.57.15 dev flannel.1 lladdr d6:51:2e:80:5c:69 REACHABLE
</code></pre>
<p>flanneld完成这项工作后，linux kernel就可以在arp table中找到 172.16.57.15对应的mac地址并封装二层以太包了。</p>
<p>到目前为止，已经呈现在大家眼前的封包如下图：</p>
<p><img src="http://tonybai.com/wp-content/uploads/flannel-network-inner-packet.png" alt="img{512x368}" /></p>
<p>不过这个封包还不能在物理网络上传输，因为它实际上只是vxlan tunnel上的packet。</p>
<h5>d) kernel的vxlan封包</h5>
<p>我们需要将上述的packet从master node传输到minion node，需要将上述packet再次封包。这个任务在backend为vxlan的flannel network中由linux kernel来完成。</p>
<p>flannel.1为vxlan设备，linux kernel可以自动识别，并将上面的packet进行vxlan封包处理。在这个封包过程中，kernel需要知道该数据包究竟发到哪个node上去。kernel需要查看node上的fdb(forwarding database)以获得上面对端vtep设备（已经从arp table中查到其mac地址：d6:51:2e:80:5c:69）所在的node地址。如果fdb中没有这个信息，那么kernel会向用户空间的flanned程序发起”L2 MISS”事件。flanneld收到该事件后，会查询etcd，获取该vtep设备对应的node的”Public IP“，并将信息注册到fdb中。</p>
<p>这样Kernel就可以顺利查询到该信息并封包了：</p>
<pre><code>master node:

# bridge fdb show dev flannel.1|grep d6:51:2e:80:5c:69
d6:51:2e:80:5c:69 dst {minion node local ip} self permanent
</code></pre>
<p>由于目标ip是minion node，查找路由表，包应该从master node的eth0发出，这样src ip和src mac地址也就确定了。封好的包示意图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/flannel-network-eth0-packet.png" alt="img{512x368}" /></p>
<h5>e) kernel的vxlan拆包</h5>
<p>minion node上的eth0接收到上述vxlan包，kernel将识别出这是一个vxlan包，于是拆包后将flannel.1 packet转给minion node上的vtep（flannel.1）。minion node上的flannel.1再将这个数据包转到minion node上的docker0，继而由docker0传输到Pod3的某个容器里。</p>
<h4>3、Pod内到外部网络</h4>
<p>我们在Pod中除了可以与pod network中的其他pod通信外，还可以访问外部网络，比如：</p>
<pre><code>master node:
# docker exec ba75f81455c7 ping -c 3 baidu.com
PING baidu.com (180.149.132.47): 56 data bytes
64 bytes from 180.149.132.47: icmp_seq=0 ttl=54 time=3.586 ms
64 bytes from 180.149.132.47: icmp_seq=1 ttl=54 time=3.752 ms
64 bytes from 180.149.132.47: icmp_seq=2 ttl=54 time=3.722 ms
--- baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.586/3.687/3.752/0.072 ms
</code></pre>
<p>这个通信与vxlan就没有什么关系了，主要是通过docker引擎在iptables的POSTROUTING chain中设置的MASQUERADE规则：</p>
<pre><code>mastre node:

#iptables -t nat -nL
... ...
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.16.99.0/24       0.0.0.0/0
... ...

</code></pre>
<p>docker将容器的pod network地址伪装为node ip出去，包回来时再snat回容器的pod network地址，这样网络就通了。</p>
<h3>四、”不真实”的Service网络</h3>
<p>每当我们在k8s cluster中创建一个service，k8s cluster就会在&#8211;service-cluster-ip-range的范围内为service分配一个cluster-ip，比如本文开始时提到的：</p>
<pre><code># kubectl get services
NAME           CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
index-api      192.168.3.168   &lt;none&gt;        30080/TCP   18d
kubernetes     192.168.3.1     &lt;none&gt;        443/TCP     94d
my-nginx       192.168.3.179   &lt;nodes&gt;       80/TCP      90d
nginx-kit      192.168.3.196   &lt;nodes&gt;       80/TCP      12d
rbd-rest-api   192.168.3.22    &lt;none&gt;        8080/TCP    60d
</code></pre>
<p>这个cluster-ip只是一个虚拟的ip，并不真实绑定某个物理网络设备或虚拟网络设备，仅仅存在于iptables的规则中：</p>
<pre><code>Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */

# iptables -t nat -nL|grep 192.168.3
Chain KUBE-SERVICES (2 references)
target     prot opt source               destination
KUBE-SVC-XGLOHA7QRQ3V22RZ  tcp  --  0.0.0.0/0            192.168.3.182        /* kube-system/kubernetes-dashboard: cluster IP */ tcp dpt:80
KUBE-SVC-NPX46M4PTMTKRN6Y  tcp  --  0.0.0.0/0            192.168.3.1          /* default/kubernetes:https cluster IP */ tcp dpt:443
KUBE-SVC-AU252PRZZQGOERSG  tcp  --  0.0.0.0/0            192.168.3.22         /* default/rbd-rest-api: cluster IP */ tcp dpt:8080
KUBE-SVC-TCOU7JCQXEZGVUNU  udp  --  0.0.0.0/0            192.168.3.10         /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
KUBE-SVC-BEPXDJBUHFCSYIC3  tcp  --  0.0.0.0/0            192.168.3.179        /* default/my-nginx: cluster IP */ tcp dpt:80
KUBE-SVC-UQG6736T32JE3S7H  tcp  --  0.0.0.0/0            192.168.3.196        /* default/nginx-kit: cluster IP */ tcp dpt:80
KUBE-SVC-ERIFXISQEP7F7OF4  tcp  --  0.0.0.0/0            192.168.3.10         /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
... ...
</code></pre>
<p>可以看到在PREROUTING环节，k8s设置了一个target: KUBE-SERVICES。而KUBE-SERVICES下面又设置了许多target，一旦destination和dstport匹配，就会沿着chain进行处理。</p>
<p>比如：当我们在pod网络curl 192.168.3.22  8080时，匹配到下面的KUBE-SVC-AU252PRZZQGOERSG target：</p>
<pre><code>KUBE-SVC-AU252PRZZQGOERSG  tcp  --  0.0.0.0/0            192.168.3.22         /* default/rbd-rest-api: cluster IP */ tcp dpt:8080
</code></pre>
<p>沿着target，我们看到”KUBE-SVC-AU252PRZZQGOERSG”对应的内容如下：</p>
<pre><code>Chain KUBE-SVC-AU252PRZZQGOERSG (1 references)
target     prot opt source               destination
KUBE-SEP-I6L4LR53UYF7FORX  all  --  0.0.0.0/0            0.0.0.0/0            /* default/rbd-rest-api: */ statistic mode random probability 0.50000000000
KUBE-SEP-LBWOKUH4CUTN7XKH  all  --  0.0.0.0/0            0.0.0.0/0            /* default/rbd-rest-api: */

Chain KUBE-SEP-I6L4LR53UYF7FORX (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  172.16.99.6          0.0.0.0/0            /* default/rbd-rest-api: */
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/rbd-rest-api: */ tcp to:172.16.99.6:8080

Chain KUBE-SEP-LBWOKUH4CUTN7XKH (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  172.16.99.7          0.0.0.0/0            /* default/rbd-rest-api: */
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/rbd-rest-api: */ tcp to:172.16.99.7:8080

Chain KUBE-MARK-MASQ (17 references)
target     prot opt source               destination
MARK       all  --  0.0.0.0/0            0.0.0.0/0            MARK or 0x4000
</code></pre>
<p>请求被按5：5开的比例分发（起到负载均衡的作用）到KUBE-SEP-I6L4LR53UYF7FORX 和KUBE-SEP-LBWOKUH4CUTN7XKH，而这两个chain的处理方式都是一样的，那就是先做mark，然后做dnat，将service ip改为pod network中的Pod IP，进而请求被实际传输到某个service下面的pod中处理了。</p>
<h3>五、参考资料</h3>
<ul>
<li><a href="http://www.slideshare.net/enakai/how-vxlan-works-on-linux">How VXLAN works on Linux&amp;VTEP implementation with Flannel</a></li>
<li><a href="http://events.linuxfoundation.org/sites/events/files/slides/LinuxConJapan2014_makita_0.pdf">Virtual switching technologies and Linux bridge</a></li>
<li><a href="http://enakai00.hatenablog.com/entry/2015/04/02/173739">How Flannel&#8217;s VXLAN backend works</a>  建议用google翻译将网页从日文翻译成英文再看^0^。</li>
<li><a href="http://events.linuxfoundation.org/sites/events/files/slides/2013-linuxcon.pdf">Software Defined Networking using VXLAN</a> </li>
</ul>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>使用Kubeadm安装Kubernetes-Part2</title>
		<link>https://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm-2/</link>
		<comments>https://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm-2/#comments</comments>
		<pubDate>Fri, 30 Dec 2016 08:48:26 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[Apt-get]]></category>
		<category><![CDATA[BGP]]></category>
		<category><![CDATA[brctl]]></category>
		<category><![CDATA[calico]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[cephrbd]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[cni]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[HA]]></category>
		<category><![CDATA[ipset]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kube-apiserver]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Master]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[nodeport]]></category>
		<category><![CDATA[overlaynetwork]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[registry]]></category>
		<category><![CDATA[route]]></category>
		<category><![CDATA[swarm]]></category>
		<category><![CDATA[swarmkit]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[troubleshooting]]></category>
		<category><![CDATA[tunnel]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[UDP]]></category>
		<category><![CDATA[vip]]></category>
		<category><![CDATA[VXLAN]]></category>
		<category><![CDATA[weave]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[服务编排]]></category>
		<category><![CDATA[集群]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2169</guid>
		<description><![CDATA[此文为《使用Kubeadm安装Kubernetes》的第二部分。文章第一部分在这里可以看到。 五、weave network for pod 经过上面那么多次尝试，结果是令人扫兴的。Weave network似乎是最后一颗救命稻草了。有了前面的铺垫，这里就不详细列出各种命令的输出细节了。Weave network也有专门的官方文档用于指导如何与kubernetes集群集成，我们主要也是参考它。 1、安装weave network add-on 在kubeadm reset后，我们重新初始化了集群。接下来我们安装weave network add-on： # kubectl apply -f https://git.io/weave-kube daemonset "weave-net" created 前面无论是Flannel还是calico，在安装pod network add-on时至少都还是顺利的。不过在Weave network这次，我们遭遇“当头棒喝”:(: # kubectl get pod --all-namespaces -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE kube-system dummy-2088944543-4kxtk 1/1 Running 0 42m 10.47.217.91 iz25beglnhtz kube-system etcd-iz25beglnhtz 1/1 Running 0 [...]]]></description>
			<content:encoded><![CDATA[<p>此文为《使用Kubeadm安装Kubernetes》的第二部分。文章第一部分在<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/">这里</a>可以看到。</p>
<h3>五、weave network for pod</h3>
<p>经过上面那么多次尝试，结果是令人扫兴的。Weave network似乎是最后一颗救命稻草了。有了前面的铺垫，这里就不详细列出各种命令的输出细节了。Weave network也有<a href="https://www.weave.works/docs/net/latest/kube-addon/">专门的官方文档</a>用于指导如何与kubernetes集群集成，我们主要也是参考它。</p>
<h4>1、安装weave network add-on</h4>
<p>在kubeadm reset后，我们重新初始化了集群。接下来我们安装weave network add-on：</p>
<pre><code># kubectl apply -f https://git.io/weave-kube
daemonset "weave-net" created
</code></pre>
<p>前面无论是Flannel还是calico，在安装pod network add-on时至少都还是顺利的。不过在Weave network这次，我们遭遇“当头棒喝”:(:</p>
<pre><code># kubectl get pod --all-namespaces -o wide
NAMESPACE     NAME                                   READY     STATUS              RESTARTS   AGE       IP             NODE
kube-system   dummy-2088944543-4kxtk                 1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   etcd-iz25beglnhtz                      1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   kube-apiserver-iz25beglnhtz            1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   kube-controller-manager-iz25beglnhtz   1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   kube-discovery-1769846148-pzv8p        1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   kube-dns-2924299975-09dcb              0/4       ContainerCreating   0          42m       &lt;none&gt;         iz25beglnhtz
kube-system   kube-proxy-z465f                       1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   kube-scheduler-iz25beglnhtz            1/1       Running             0          42m       10.47.217.91   iz25beglnhtz
kube-system   weave-net-3wk9h                        0/2       CrashLoopBackOff    16         17m       10.47.217.91   iz25beglnhtz
</code></pre>
<p>安装后，weave-net pod提示:CrashLoopBackOff。追踪其Container log，得到如下错误信息：</p>
<pre><code># docker logs cde899efa0af
time="2016-12-28T08:25:29Z" level=info msg="Starting Weaveworks NPC 1.8.2"
time="2016-12-28T08:25:29Z" level=info msg="Serving /metrics on :6781"
Wed Dec 28 08:25:29 2016 &lt;5&gt; ulogd.c:843 building new pluginstance stack: 'log1:NFLOG,base1:BASE,pcap1:PCAP'
time="2016-12-28T08:25:29Z" level=fatal msg="ipset [destroy] failed: ipset v6.29: Set cannot be destroyed: it is in use by a kernel component\n: exit status 1"
</code></pre>
<h4>2、解决ipset destroy错误</h4>
<p>从上述的错误日志来看，似乎某些内核组件占用了一些IP资源，没有释放。ipset(administration tool for IP sets)这个工具以前从来没有接触过。在node上利用apt-get install 一个ipset工具，手工执行以下命令：</p>
<pre><code># ipset destroy
ipset v6.29: Set cannot be destroyed: it is in use by a kernel component

</code></pre>
<p>这个错误输出与container中的error log一模一样。试着用ipset看看哪些ip资源没有释放，这一招让我们看到了蛛丝马迹：</p>
<p>在minion node上执行：</p>
<pre><code># ipset list
Name: felix-calico-hosts-4
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 1048576
Size in memory: 224
References: 1
Members:
123.56.200.187
59.110.67.15

Name: felix-all-ipam-pools
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 1048576
Size in memory: 448
References: 1
Members:
192.168.0.0/16

Name: felix-masq-ipam-pools
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 1048576
Size in memory: 448
References: 1
Members:
192.168.0.0/16
</code></pre>
<p>我们看到了calico字样。原来是calico的“残留势力”在作祟啊。进一步我们发现calico创建的一个network device依旧存在于两个Node上：</p>
<pre><code>47: tunl0@NONE: &lt;NOARP,UP,LOWER_UP&gt; mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 192.168.91.0/32 scope global tunl0
       valid_lft forever preferred_lft forever
</code></pre>
<p>我们试图删除它，但最终都以失败告终：</p>
<pre><code># ip tunnel show
tunl0: ip/ip  remote any  local any  ttl inherit  nopmtudisc

 #ip tunnel del tunl0
delete tunnel "tunl0" failed: Operation not permitted

</code></pre>
<p>无奈只能把它down掉：</p>
<pre><code>#ip -f inet addr delete 192.168.91.0/32  dev tunl0

47: tunl0@NONE: &lt;NOARP,UP,LOWER_UP&gt; mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0

# ifconfig tunl0 down

47: tunl0@NONE: &lt;NOARP&gt; mtu 1440 qdisc noqueue state DOWN group default qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
</code></pre>
<p>但依旧无法删除它。我们通过ipset del命令将上面ipset占用的ip entry逐个删除掉（比如ipset del felix-calico-hosts-4  123.56.200.187）。但即便全部清空，ipset destroy依然失败。</p>
<p>无奈之下，决定重启一下两个Node试试。重启后，calico创建的这个tunnel居然消失了。</p>
<h4>3、再遇路由冲突错误</h4>
<p>重启ECS实例后，我们重新从头来创建cluster。不过在执行“kubectl apply -f https://git.io/weave-kube” 后我们发现weave-net pod依旧没有起来，这次的错误是“路有冲突”：</p>
<pre><code>#docker logs 80383071f721
Network 10.32.0.0/12 overlaps with existing route 10.0.0.0/8 on host.
</code></pre>
<p>查看当前路由表：</p>
<pre><code>netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         123.56.203.247  0.0.0.0         UG        0 0          0 eth1
10.0.0.0        10.47.223.247   255.0.0.0       UG        0 0          0 eth0
10.47.216.0     0.0.0.0         255.255.248.0   U         0 0          0 eth0
100.64.0.0      10.47.223.247   255.192.0.0     UG        0 0          0 eth0
123.56.200.0    0.0.0.0         255.255.252.0   U         0 0          0 eth1
172.16.0.0      10.47.223.247   255.240.0.0     UG        0 0          0 eth0
192.168.0.0     0.0.0.0         255.255.240.0   U         0 0          0 docker0
</code></pre>
<p>的确weave-net默认要使用的 10.32.0.0/12与 10.0.0.0/8 存在交集。对此，weave net官方是给出<a href=":https://www.weave.works/documentation/net-latest-using-weave/net-latest-configuring-weave/">解决方案</a>了的。</p>
<p>我们先将https://git.io/weave-kube对应的yaml文件下载到本地：weave-daemonset.yaml。修改该文件，为container增加IPALLOC_RANGE环境变量：</p>
<pre><code>containers:
        - name: weave
          env:
            - name: IPALLOC_RANGE
              value: 172.30.0.0/16

</code></pre>
<p>更新weave net pod：</p>
<pre><code># kubectl delete -f weave-daemonset.yaml
daemonset "weave-net" deleted

# kubectl apply -f weave-daemonset.yaml
daemonset "weave-net" created
</code></pre>
<p>不过依然存在路有冲突。原来路由表里已经存在了一条这样的路由：</p>
<pre><code>172.16.0.0      10.28.63.247    255.240.0.0     UG    0      0        0 eth0
</code></pre>
<p>这条路由应该没有什么用，也许是之前折腾时被某个network addon加进去的。于是用route命令将其删除：</p>
<pre><code># route del -net 172.16.0.0 netmask 255.240.0.0 gw 10.28.63.247
</code></pre>
<p>再次更新weave net pod并查看cluster status：</p>
<pre><code># kubectl delete -f weave-daemonset.yaml
daemonset "weave-net" deleted

# kubectl apply -f weave-daemonset.yaml
daemonset "weave-net" created

# kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY     STATUS    RESTARTS   AGE       IP             NODE
kube-system   dummy-2088944543-93f4c                 1/1       Running   0          21m       10.47.217.91   iz25beglnhtz
kube-system   etcd-iz25beglnhtz                      1/1       Running   0          21m       10.47.217.91   iz25beglnhtz
kube-system   kube-apiserver-iz25beglnhtz            1/1       Running   0          20m       10.47.217.91   iz25beglnhtz
kube-system   kube-controller-manager-iz25beglnhtz   1/1       Running   0          21m       10.47.217.91   iz25beglnhtz
kube-system   kube-discovery-1769846148-wbc7h        1/1       Running   0          21m       10.47.217.91   iz25beglnhtz
kube-system   kube-dns-2924299975-206tg              4/4       Running   0          21m       172.30.0.2     iz25beglnhtz
kube-system   kube-proxy-n2xmf                       1/1       Running   0          21m       10.47.217.91   iz25beglnhtz
kube-system   kube-scheduler-iz25beglnhtz            1/1       Running   0          20m       10.47.217.91   iz25beglnhtz
kube-system   weave-net-h38k5                        2/2       Running   0          18s       10.47.217.91   iz25beglnhtz

</code></pre>
<p>这回weave-net pod running了。taint master node并且minion node join后cluster依旧是ok的：</p>
<pre><code># kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY     STATUS    RESTARTS   AGE       IP             NODE
kube-system   dummy-2088944543-93f4c                 1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   etcd-iz25beglnhtz                      1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-apiserver-iz25beglnhtz            1/1       Running   0          22m       10.47.217.91   iz25beglnhtz
kube-system   kube-controller-manager-iz25beglnhtz   1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-discovery-1769846148-wbc7h        1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-dns-2924299975-206tg              4/4       Running   0          23m       172.30.0.2     iz25beglnhtz
kube-system   kube-proxy-377zh                       1/1       Running   0          8s        10.28.61.30    iz2ze39jeyizepdxhwqci6z
kube-system   kube-proxy-n2xmf                       1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-scheduler-iz25beglnhtz            1/1       Running   0          22m       10.47.217.91   iz25beglnhtz
kube-system   weave-net-9tf1d                        2/2       Running   0          8s        10.28.61.30    iz2ze39jeyizepdxhwqci6z
kube-system   weave-net-h38k5                        2/2       Running   0          2m        10.47.217.91   iz25beglnhtz
</code></pre>
<h4>4、测试weave net跨节点pod连通性</h4>
<p>这回我们依旧启动my-nginx service，在任意一个节点curl localhost:30062，我们发现被调度到minion node上的my-nginx container也收到了request并成功回复response：</p>
<pre><code>172.30.0.1 - - [30/Dec/2016:03:14:47 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
</code></pre>
<p>Weave net初步测试ok！</p>
<h3>六、小结</h3>
<p>虽然过程坎坷，但最终在Weave net的帮助下，我们还是初步调通了一个使用kubeadm安装的kubernetes cluster。后来我发现，在K8s官方博客中有一篇名为《<a href="http://blog.kubernetes.io/2016/09/how-we-made-kubernetes-easy-to-install.html">Kubernetes: How we made Kubernetes insanely easy to install</a>》的文章，其使用的pod network add-on也是weave network。</p>
<p>这是一个试验环境。后续我们还是要进一步探究如何用上Flannel的。同时，Kubernetes 1.5带来的<a href="http://blog.kubernetes.io/2016/12/five-days-of-kubernetes-1.5.html">诸多新特性</a>，比如：Master HA等还需要进一步试验证明。</p>
<p>为了满足我们的production环境要求，之前实践的<a href="http://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd">Ceph RBD为K8s提供存储卷</a>、<a href="http://tonybai.com/2016/11/16/how-to-pull-images-from-private-registry-on-kubernetes-cluster">k8s从private registry拉取image</a>、<a href="http://tonybai.com/2016/11/25/the-security-settings-for-kubernetes-cluster">k8s集群的安全配置</a>等还要在新集群上进一步试验，直到满足我们的要求。</p>
<p style='text-align:left'>&copy; 2016 &#8211; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm-2/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>使用Kubeadm安装Kubernetes</title>
		<link>https://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/</link>
		<comments>https://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/#comments</comments>
		<pubDate>Fri, 30 Dec 2016 04:23:25 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[Apt-get]]></category>
		<category><![CDATA[BGP]]></category>
		<category><![CDATA[brctl]]></category>
		<category><![CDATA[calico]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[cephrbd]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[cni]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[HA]]></category>
		<category><![CDATA[ipset]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kube-apiserver]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Master]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[nodeport]]></category>
		<category><![CDATA[overlaynetwork]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[registry]]></category>
		<category><![CDATA[route]]></category>
		<category><![CDATA[swarm]]></category>
		<category><![CDATA[swarmkit]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[troubleshooting]]></category>
		<category><![CDATA[tunnel]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[UDP]]></category>
		<category><![CDATA[vip]]></category>
		<category><![CDATA[VXLAN]]></category>
		<category><![CDATA[weave]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[服务编排]]></category>
		<category><![CDATA[集群]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2105</guid>
		<description><![CDATA[在《当Docker遇到systemd》一文中，我提到过这两天儿一直在做的一个task：使用kubeadm在Ubuntu 16.04上安装部署Kubernetes的最新发布版本-k8s 1.5.1。 年中，Docker宣布在Docker engine中集成swarmkit工具包，这一announcement在轻量级容器界引发轩然大波。毕竟开发者是懒惰的^0^，有了docker swarmkit，驱动developer去安装其他容器编排工具的动力在哪里呢？即便docker engine还不是当年那个被人们高频使用的IE浏览器。作为针对Docker公司这一市场行为的回应，容器集群管理和服务编排领先者Kubernetes在三个月后发布了Kubernetes1.4.0版本。在这个版本中K8s新增了kubeadm工具。kubeadm的使用方式有点像集成在docker engine中的swarm kit工具，旨在改善开发者在安装、调试和使用k8s时的体验，降低安装和使用门槛。理论上通过两个命令：init和join即可搭建出一套完整的Kubernetes cluster。 不过，和初入docker引擎的swarmkit一样，kubeadm目前也在active development中，也不是那么stable，因此即便在当前最新的k8s 1.5.1版本中，它仍然处于Alpha状态，官方不建议在Production环境下使用。每次执行kubeadm init时，它都会打印如下提醒日志： [kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters. 不过由于之前部署的k8s 1.3.7集群运行良好，这给了我们在k8s这条路上继续走下去并走好的信心。但k8s在部署和管理方面的体验的确是太繁琐了，于是我们准备试验一下kubeadm是否能带给我们超出预期的体验。之前在aliyun ubuntu 14.04上安装kubernetes 1.3.7的经验和教训，让我略微有那么一丢丢底气，但实际安装过程依旧是一波三折。这既与kubeadm的unstable有关，同样也与cni、第三方网络add-ons的质量有关。无论哪一方出现问题都会让你的install过程异常坎坷曲折。 一、环境与约束 在kubeadm支持的Ubuntu 16.04+, CentOS 7 or HypriotOS v1.0.1+三种操作系统中，我们选择了Ubuntu 16.04。由于阿里云尚无官方16.04 Image可用，我们新开了两个Ubuntu 14.04ECS实例，并通过apt-get命令手工将其升级到Ubuntu 16.04.1，详细版本是：Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-58-generic x86_64)。 Ubuntu 16.04使用了systemd作为init system，在安装和配置Docker时，可以参考我的这篇《当Docker遇到system》。Docker版本我选择了目前可以得到的lastest stable release: [...]]]></description>
			<content:encoded><![CDATA[<p>在《<a href="http://tonybai.com/2016/12/27/when-docker-meets-systemd/">当Docker遇到systemd</a>》一文中，我提到过这两天儿一直在做的一个task：使用<a href="http://kubernetes.io/docs/admin/kubeadm/">kubeadm</a>在<a href="http://tonybai.com/tag/ubuntu">Ubuntu 16.04</a>上安装部署<a href="http://kubernetes.io/">Kubernetes</a>的最新发布版本-<a href="http://blog.kubernetes.io/2016/12/kubernetes-1.5-supporting-production-workloads.html">k8s 1.5.1</a>。</p>
<p>年中，Docker宣布在Docker engine中集成swarmkit工具包，这一announcement在轻量级容器界引发轩然大波。毕竟开发者是懒惰的^0^，有了docker swarmkit，驱动developer去安装其他容器编排工具的动力在哪里呢？即便docker engine还不是当年那个被人们高频使用的IE浏览器。作为针对Docker公司这一市场行为的回应，容器集群管理和服务编排领先者Kubernetes在三个月后发布了<a href="http://blog.kubernetes.io/2016/09/kubernetes-1.4-making-it-easy-to-run-on-kuberentes-anywhere.html">Kubernetes1.4.0版本</a>。在这个版本中K8s新增了kubeadm工具。kubeadm的使用方式有点像集成在<a href="http://tonybai.com/tag/docker">docker engine</a>中的<a href="http://tonybai.com/2016/10/11/some-problems-under-swarm-mode-in-docker-1-12">swarm kit工具</a>，旨在改善开发者在安装、调试和使用k8s时的体验，降低安装和使用门槛。理论上通过两个命令：init和join即可搭建出一套完整的Kubernetes cluster。</p>
<p>不过，和初入docker引擎的swarmkit一样，kubeadm目前也在active development中，也不是那么stable，因此即便在当前最新的k8s 1.5.1版本中，它仍然处于Alpha状态，官方不建议在Production环境下使用。每次执行kubeadm init时，它都会打印如下提醒日志：</p>
<pre><code>[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.

</code></pre>
<p>不过由于<a href="http://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu">之前部署的k8s 1.3.7集群</a>运行良好，这给了我们在k8s这条路上继续走下去并走好的信心。但k8s在部署和管理方面的体验的确是太繁琐了，于是我们准备试验一下kubeadm是否能带给我们超出预期的体验。之前在aliyun ubuntu 14.04上安装kubernetes 1.3.7的经验和教训，让我略微有那么一丢丢底气，但实际安装过程依旧是一波三折。这既与kubeadm的unstable有关，同样也与<a href="https://github.com/containernetworking/cni">cni</a>、第三方网络add-ons的质量有关。无论哪一方出现问题都会让你的install过程异常坎坷曲折。</p>
<h3>一、环境与约束</h3>
<p>在kubeadm支持的Ubuntu 16.04+, CentOS 7 or HypriotOS v1.0.1+三种操作系统中，我们选择了Ubuntu 16.04。由于阿里云尚无官方16.04 Image可用，我们新开了两个Ubuntu 14.04ECS实例，并通过apt-get命令手工将其升级到Ubuntu 16.04.1，详细版本是：Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-58-generic x86_64)。</p>
<p>Ubuntu 16.04使用了<a href="https://github.com/systemd/systemd">systemd</a>作为init system，在安装和配置Docker时，可以参考我的这篇《<a href="http://tonybai.com/2016/12/27/when-docker-meets-systemd/">当Docker遇到system</a>》。Docker版本我选择了目前可以得到的lastest stable release: 1.12.5。</p>
<pre><code># docker version
Client:
 Version:      1.12.5
 API version:  1.24
 Go version:   go1.6.4
 Git commit:   7392c3b
 Built:        Fri Dec 16 02:42:17 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.12.5
 API version:  1.24
 Go version:   go1.6.4
 Git commit:   7392c3b
 Built:        Fri Dec 16 02:42:17 2016
 OS/Arch:      linux/amd64

</code></pre>
<p>至于Kubernetes版本，前面已经提到过了，我们就使用最新发布的Kubernetes 1.5.1版本。1.5.1是<a href="http://blog.kubernetes.io/2016/12/kubernetes-1.5-supporting-production-workloads.html">1.5.0</a>的一个<a href="https://groups.google.com/forum/#!topic/kubernetes-announce/iclRj-6Nfsg">紧急fix版本</a>，主要”to address default flag values which in isolation were not problematic, but in concert could result in an insecure cluster”。官方建议skip 1.5.0，直接用1.5.1。</p>
<p>这里再重申一下：Kubernetes的安装、配置和调通是很难的，在阿里云上调通就更难了，有时还需要些运气。Kubernetes、Docker、cni以及各种网络Add-ons都在active development中，也许今天还好用的step、tip和trick，明天就out-dated，因此在借鉴本文的操作步骤时，请谨记这些^0^。</p>
<h3>二、安装包准备</h3>
<p>我们这次新开了两个ECS实例，一个作为master node，一个作为minion node。Kubeadm默认安装时，master node将不会参与Pod调度，不会承载work load，即不会有非核心组件的Pod在Master node上被创建出来。当然通过kubectl taint命令可以解除这一限制，不过这是后话了。</p>
<p>集群拓扑：</p>
<pre><code>master node：10.47.217.91，主机名：iZ25beglnhtZ
minion node：10.28.61.30，主机名：iZ2ze39jeyizepdxhwqci6Z
</code></pre>
<p>本次安装的主参考文档就是Kubernetes官方的那篇《<a href="http://kubernetes.io/docs/getting-started-guides/kubeadm">Installing Kubernetes on Linux with kubeadm</a>》。</p>
<p>本小节，我们将进行安装包准备，即将kubeadm以及此次安装所需要的k8s核心组件统统下载到上述两个Node上。注意：如果你有加速器，那么本节下面的安装过程将尤为顺利，反之，&#8230; <img src='https://tonybai.com/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> 。以下命令，在两个Node上均要执行。</p>
<h4>1、添加apt-key</h4>
<pre><code># curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
OK
</code></pre>
<h4>2、添加Kubernetes源并更新包信息</h4>
<p>添加Kubernetes源到sources.list.d目录下：</p>
<pre><code># cat &lt;&lt;EOF &gt; /etc/apt/sources.list.d/kubernetes.list
  deb http://apt.kubernetes.io/ kubernetes-xenial main
  EOF

# cat /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
</code></pre>
<p>更新包信息：</p>
<pre><code># apt-get update
... ...
Hit:2 http://mirrors.aliyun.com/ubuntu xenial InRelease
Hit:3 https://apt.dockerproject.org/repo ubuntu-xenial InRelease
Get:4 http://mirrors.aliyun.com/ubuntu xenial-security InRelease [102 kB]
Get:1 https://packages.cloud.google.com/apt kubernetes-xenial InRelease [6,299 B]
Get:5 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 Packages [1,739 B]
Get:6 http://mirrors.aliyun.com/ubuntu xenial-updates InRelease [102 kB]
Get:7 http://mirrors.aliyun.com/ubuntu xenial-proposed InRelease [253 kB]
Get:8 http://mirrors.aliyun.com/ubuntu xenial-backports InRelease [102 kB]
Fetched 568 kB in 19s (28.4 kB/s)
Reading package lists... Done
</code></pre>
<h4>3、下载Kubernetes核心组件</h4>
<p>在此次安装中，我们通过apt-get就可以下载Kubernetes的核心组件，包括kubelet、kubeadm、kubectl和kubernetes-cni等。</p>
<pre><code># apt-get install -y kubelet kubeadm kubectl kubernetes-cni
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following package was automatically installed and is no longer required:
  libtimedate-perl
Use 'apt autoremove' to remove it.
The following additional packages will be installed:
  ebtables ethtool socat
The following NEW packages will be installed:
  ebtables ethtool kubeadm kubectl kubelet kubernetes-cni socat
0 upgraded, 7 newly installed, 0 to remove and 0 not upgraded.
Need to get 37.6 MB of archives.
After this operation, 261 MB of additional disk space will be used.
Get:2 http://mirrors.aliyun.com/ubuntu xenial/main amd64 ebtables amd64 2.0.10.4-3.4ubuntu1 [79.6 kB]
Get:6 http://mirrors.aliyun.com/ubuntu xenial/main amd64 ethtool amd64 1:4.5-1 [97.5 kB]
Get:7 http://mirrors.aliyun.com/ubuntu xenial/universe amd64 socat amd64 1.7.3.1-1 [321 kB]
Get:1 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 kubernetes-cni amd64 0.3.0.1-07a8a2-00 [6,877 kB]
Get:3 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 kubelet amd64 1.5.1-00 [15.1 MB]
Get:4 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 kubectl amd64 1.5.1-00 [7,954 kB]
Get:5 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 kubeadm amd64 1.6.0-alpha.0-2074-a092d8e0f95f52-00 [7,120 kB]
Fetched 37.6 MB in 36s (1,026 kB/s)
... ...
Unpacking kubeadm (1.6.0-alpha.0-2074-a092d8e0f95f52-00) ...
Processing triggers for systemd (229-4ubuntu13) ...
Processing triggers for ureadahead (0.100.0-19) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up ebtables (2.0.10.4-3.4ubuntu1) ...
update-rc.d: warning: start and stop actions are no longer supported; falling back to defaults
Setting up ethtool (1:4.5-1) ...
Setting up kubernetes-cni (0.3.0.1-07a8a2-00) ...
Setting up socat (1.7.3.1-1) ...
Setting up kubelet (1.5.1-00) ...
Setting up kubectl (1.5.1-00) ...
Setting up kubeadm (1.6.0-alpha.0-2074-a092d8e0f95f52-00) ...
Processing triggers for systemd (229-4ubuntu13) ...
Processing triggers for ureadahead (0.100.0-19) ...
... ...
</code></pre>
<p>下载后的kube组件并未自动运行起来。在 /lib/systemd/system下面我们能看到kubelet.service：</p>
<pre><code># ls /lib/systemd/system|grep kube
kubelet.service

//kubelet.service
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target
</code></pre>
<p>kubelet的版本：</p>
<pre><code># kubelet --version
Kubernetes v1.5.1
</code></pre>
<p>k8s的核心组件都有了，接下来我们就要boostrap kubernetes cluster了。同时，问题也就随之而来了，而这些问题以及问题的解决才是本篇要说明的重点。</p>
<h3>三、初始化集群</h3>
<p>前面说过，理论上通过kubeadm使用init和join命令即可建立一个集群，这init就是在master节点对集群进行初始化。和k8s 1.4之前的部署方式不同的是，kubeadm安装的k8s核心组件都是以容器的形式运行于master node上的。因此在kubeadm init之前，最好给master node上的docker engine挂上加速器代理，因为kubeadm要从gcr.io/google_containers repository中pull许多核心组件的images，大约有如下一些：</p>
<pre><code>gcr.io/google_containers/kube-controller-manager-amd64   v1.5.1                     cd5684031720        2 weeks ago         102.4 MB
gcr.io/google_containers/kube-apiserver-amd64            v1.5.1                     8c12509df629        2 weeks ago         124.1 MB
gcr.io/google_containers/kube-proxy-amd64                v1.5.1                     71d2b27b03f6        2 weeks ago         175.6 MB
gcr.io/google_containers/kube-scheduler-amd64            v1.5.1                     6506e7b74dac        2 weeks ago         53.97 MB
gcr.io/google_containers/etcd-amd64                      3.0.14-kubeadm             856e39ac7be3        5 weeks ago         174.9 MB
gcr.io/google_containers/kubedns-amd64                   1.9                        26cf1ed9b144        5 weeks ago         47 MB
gcr.io/google_containers/dnsmasq-metrics-amd64           1.0                        5271aabced07        7 weeks ago         14 MB
gcr.io/google_containers/kube-dnsmasq-amd64              1.4                        3ec65756a89b        3 months ago        5.13 MB
gcr.io/google_containers/kube-discovery-amd64            1.0                        c5e0c9a457fc        3 months ago        134.2 MB
gcr.io/google_containers/exechealthz-amd64               1.2                        93a43bfb39bf        3 months ago        8.375 MB
gcr.io/google_containers/pause-amd64                     3.0                        99e59f495ffa        7 months ago        746.9 kB
</code></pre>
<p>在Kubeadm的文档中，Pod Network的安装是作为一个单独的步骤的。kubeadm init并没有为你选择一个默认的Pod network进行安装。我们将首选<a href="github.com/coreos/flannel">Flannel</a> 作为我们的Pod network，这不仅是因为我们的上一个集群用的就是flannel，而且表现稳定。更是由于Flannel就是coreos为k8s打造的专属overlay network add-ons。甚至于flannel repository的readme.md都这样写着：“flannel is a network fabric for containers, designed for Kubernetes”。如果我们要使用Flannel，那么在执行init时，按照kubeadm文档要求，我们必须给init命令带上option：&#8211;pod-network-cidr=10.244.0.0/16。</p>
<h4>1、执行kubeadm init</h4>
<p>执行kubeadm init命令：</p>
<pre><code># kubeadm init --pod-network-cidr=10.244.0.0/16
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Running pre-flight checks
[preflight] Starting the kubelet service
[init] Using Kubernetes version: v1.5.1
[tokens] Generated token: "2e7da9.7fc5668ff26430c7"
[certificates] Generated Certificate Authority key and certificate.
[certificates] Generated API Server key and certificate
[certificates] Generated Service Account signing keys
[certificates] Created keys and certificates in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[apiclient] Created API client, waiting for the control plane to become ready //如果没有挂加速器，可能会在这里hang住。
[apiclient] All control plane components are healthy after 54.789750 seconds
[apiclient] Waiting for at least one node to register and become ready
[apiclient] First node is ready after 1.003053 seconds
[apiclient] Creating a test deployment
[apiclient] Test deployment succeeded
[token-discovery] Created the kube-discovery deployment, waiting for it to become ready
[token-discovery] kube-discovery is ready after 62.503441 seconds
[addons] Created essential addon: kube-proxy
[addons] Created essential addon: kube-dns

Your Kubernetes master has initialized successfully!

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:

http://kubernetes.io/docs/admin/addons/

You can now join any number of machines by running the following on each node:

kubeadm join --token=2e7da9.7fc5668ff26430c7 123.56.200.187

</code></pre>
<p>init成功后的master node有啥变化？k8s的核心组件均正常启动：</p>
<pre><code># ps -ef|grep kube
root      2477  2461  1 16:36 ?        00:00:04 kube-proxy --kubeconfig=/run/kubeconfig
root     30860     1 12 16:33 ?        00:01:09 /usr/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cluster-dns=10.96.0.10 --cluster-domain=cluster.local
root     30952 30933  0 16:33 ?        00:00:01 kube-scheduler --address=127.0.0.1 --leader-elect --master=127.0.0.1:8080
root     31128 31103  2 16:33 ?        00:00:11 kube-controller-manager --address=127.0.0.1 --leader-elect --master=127.0.0.1:8080 --cluster-name=kubernetes --root-ca-file=/etc/kubernetes/pki/ca.pem --service-account-private-key-file=/etc/kubernetes/pki/apiserver-key.pem --cluster-signing-cert-file=/etc/kubernetes/pki/ca.pem --cluster-signing-key-file=/etc/kubernetes/pki/ca-key.pem --insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap --allocate-node-cidrs=true --cluster-cidr=10.244.0.0/16
root     31223 31207  2 16:34 ?        00:00:10 kube-apiserver --insecure-bind-address=127.0.0.1 --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota --service-cluster-ip-range=10.96.0.0/12 --service-account-key-file=/etc/kubernetes/pki/apiserver-key.pem --client-ca-file=/etc/kubernetes/pki/ca.pem --tls-cert-file=/etc/kubernetes/pki/apiserver.pem --tls-private-key-file=/etc/kubernetes/pki/apiserver-key.pem --token-auth-file=/etc/kubernetes/pki/tokens.csv --secure-port=6443 --allow-privileged --advertise-address=123.56.200.187 --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --anonymous-auth=false --etcd-servers=http://127.0.0.1:2379
root     31491 31475  0 16:35 ?        00:00:00 /usr/local/bin/kube-discovery
</code></pre>
<p>而且是多以container的形式启动：</p>
<pre><code># docker ps
CONTAINER ID        IMAGE                                                           COMMAND                  CREATED                  STATUS                  PORTS               NAMES
c16c442b7eca        gcr.io/google_containers/kube-proxy-amd64:v1.5.1                "kube-proxy --kubecon"   6 minutes ago            Up 6 minutes                                k8s_kube-proxy.36dab4e8_kube-proxy-sb4sm_kube-system_43fb1a2c-cb46-11e6-ad8f-00163e1001d7_2ba1648e
9f73998e01d7        gcr.io/google_containers/kube-discovery-amd64:1.0               "/usr/local/bin/kube-"   8 minutes ago            Up 8 minutes                                k8s_kube-discovery.7130cb0a_kube-discovery-1769846148-6z5pw_kube-system_1eb97044-cb46-11e6-ad8f-00163e1001d7_fd49c2e3
dd5412e5e15c        gcr.io/google_containers/kube-apiserver-amd64:v1.5.1            "kube-apiserver --ins"   9 minutes ago            Up 9 minutes                                k8s_kube-apiserver.1c5a91d9_kube-apiserver-iz25beglnhtz_kube-system_eea8df1717e9fea18d266103f9edfac3_8cae8485
60017f8819b2        gcr.io/google_containers/etcd-amd64:3.0.14-kubeadm              "etcd --listen-client"   9 minutes ago            Up 9 minutes                                k8s_etcd.c323986f_etcd-iz25beglnhtz_kube-system_3a26566bb004c61cd05382212e3f978f_06d517eb
03c2463aba9c        gcr.io/google_containers/kube-controller-manager-amd64:v1.5.1   "kube-controller-mana"   9 minutes ago            Up 9 minutes                                k8s_kube-controller-manager.d30350e1_kube-controller-manager-iz25beglnhtz_kube-system_9a40791dd1642ea35c8d95c9e610e6c1_3b05cb8a
fb9a724540a7        gcr.io/google_containers/kube-scheduler-amd64:v1.5.1            "kube-scheduler --add"   9 minutes ago            Up 9 minutes                                k8s_kube-scheduler.ef325714_kube-scheduler-iz25beglnhtz_kube-system_dc58861a0991f940b0834f8a110815cb_9b3ccda2
.... ...
</code></pre>
<p>不过这些核心组件并不是跑在pod network中的（没错，此时的pod network还没有创建），而是采用了host network。以kube-apiserver的pod信息为例：</p>
<pre><code>kube-system   kube-apiserver-iz25beglnhtz            1/1       Running   0          1h        10.47.217.91   iz25beglnhtz
</code></pre>
<p>kube-apiserver的IP是host ip，从而推断容器使用的是host网络，这从其对应的pause容器的network属性就可以看出：</p>
<pre><code># docker ps |grep apiserver
a5a76bc59e38        gcr.io/google_containers/kube-apiserver-amd64:v1.5.1            "kube-apiserver --ins"   About an hour ago   Up About an hour                        k8s_kube-apiserver.2529402_kube-apiserver-iz25beglnhtz_kube-system_25d646be9a0092138dc6088fae6f1656_ec0079fc
ef4d3bf057a6        gcr.io/google_containers/pause-amd64:3.0                        "/pause"                 About an hour ago   Up About an hour                        k8s_POD.d8dbe16c_kube-apiserver-iz25beglnhtz_kube-system_25d646be9a0092138dc6088fae6f1656_bbfd8a31

</code></pre>
<p>inspect pause容器，可以看到pause container的NetworkMode的值：</p>
<pre><code>"NetworkMode": "host",
</code></pre>
<p>如果kubeadm init执行过程中途出现了什么问题，比如前期忘记挂加速器导致init hang住，你可能会ctrl+c退出init执行。重新配置后，再执行kubeadm init，这时你可能会遇到下面kubeadm的输出：</p>
<pre><code># kubeadm init --pod-network-cidr=10.244.0.0/16
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Running pre-flight checks
[preflight] Some fatal errors occurred:
    Port 10250 is in use
    /etc/kubernetes/manifests is not empty
    /etc/kubernetes/pki is not empty
    /var/lib/kubelet is not empty
    /etc/kubernetes/admin.conf already exists
    /etc/kubernetes/kubelet.conf already exists
[preflight] If you know what you are doing, you can skip pre-flight checks with `--skip-preflight-checks`
</code></pre>
<p>kubeadm会自动检查当前环境是否有上次命令执行的“残留”。如果有，必须清理后再行执行init。我们可以通过”kubeadm reset”来清理环境，以备重来。</p>
<pre><code># kubeadm reset
[preflight] Running pre-flight checks
[reset] Draining node: "iz25beglnhtz"
[reset] Removing node: "iz25beglnhtz"
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
[reset] Deleting contents of stateful directories: [/var/lib/kubelet /etc/cni/net.d /var/lib/etcd]
[reset] Deleting contents of config directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf]

</code></pre>
<h4>2、安装flannel pod网络</h4>
<p>kubeadm init之后，如果你探索一下当前cluster的状态或者核心组件的日志，你会发现某些“异常”，比如：从kubelet的日志中我们可以看到一直刷屏的错误信息：</p>
<pre><code>Dec 26 16:36:48 iZ25beglnhtZ kubelet[30860]: E1226 16:36:48.365885   30860 docker_manager.go:2201] Failed to setup network for pod "kube-dns-2924299975-pddz5_kube-system(43fd7264-cb46-11e6-ad8f-00163e1001d7)" using network plugins "cni": cni config unintialized; Skipping pod
</code></pre>
<p>通过命令kubectl get pod &#8211;all-namespaces -o wide，你也会发现kube-dns pod处于ContainerCreating状态。</p>
<p>这些都不打紧，因为我们还没有为cluster安装Pod network呢。前面说过，我们要使用Flannel网络，因此我们需要执行如下安装命令：</p>
<pre><code>#kubectl apply -f  https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
configmap "kube-flannel-cfg" created
daemonset "kube-flannel-ds" created

</code></pre>
<p>稍等片刻，我们再来看master node上的cluster信息：</p>
<pre><code># ps -ef|grep kube|grep flannel
root      6517  6501  0 17:20 ?        00:00:00 /opt/bin/flanneld --ip-masq --kube-subnet-mgr
root      6573  6546  0 17:20 ?        00:00:00 /bin/sh -c set -e -x; cp -f /etc/kube-flannel/cni-conf.json /etc/cni/net.d/10-flannel.conf; while true; do sleep 3600; done

# kubectl get pods --all-namespaces
NAMESPACE     NAME                                   READY     STATUS    RESTARTS   AGE
kube-system   dummy-2088944543-s0c5g                 1/1       Running   0          50m
kube-system   etcd-iz25beglnhtz                      1/1       Running   0          50m
kube-system   kube-apiserver-iz25beglnhtz            1/1       Running   0          50m
kube-system   kube-controller-manager-iz25beglnhtz   1/1       Running   0          50m
kube-system   kube-discovery-1769846148-6z5pw        1/1       Running   0          50m
kube-system   kube-dns-2924299975-pddz5              4/4       Running   0          49m
kube-system   kube-flannel-ds-5ww9k                  2/2       Running   0          4m
kube-system   kube-proxy-sb4sm                       1/1       Running   0          49m
kube-system   kube-scheduler-iz25beglnhtz            1/1       Running   0          49m
</code></pre>
<p>至少集群的核心组件已经全部run起来了。看起来似乎是成功了。</p>
<h4>3、minion node：join the cluster</h4>
<p>接下来，就该minion node加入cluster了。这里我们用到了kubeadm的第二个命令：kubeadm join。</p>
<p>在minion node上执行（注意：这里要保证master node的9898端口在防火墙是打开的）：</p>
<pre><code># kubeadm join --token=2e7da9.7fc5668ff26430c7 123.56.200.187
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Running pre-flight checks
[tokens] Validating provided token
[discovery] Created cluster info discovery client, requesting info from "http://123.56.200.187:9898/cluster-info/v1/?token-id=2e7da9"
[discovery] Cluster info object received, verifying signature using given token
[discovery] Cluster info signature and contents are valid, will use API endpoints [https://123.56.200.187:6443]
[bootstrap] Trying to connect to endpoint https://123.56.200.187:6443
[bootstrap] Detected server version: v1.5.1
[bootstrap] Successfully established connection with endpoint "https://123.56.200.187:6443"
[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request
[csr] Received signed certificate from the API server:
Issuer: CN=kubernetes | Subject: CN=system:node:iZ2ze39jeyizepdxhwqci6Z | CA: false
Not before: 2016-12-26 09:31:00 +0000 UTC Not After: 2017-12-26 09:31:00 +0000 UTC
[csr] Generating kubelet configuration
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"

Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.
</code></pre>
<p>也很顺利。我们在minion node上看到的k8s组件情况如下：</p>
<pre><code>d85cf36c18ed        gcr.io/google_containers/kube-proxy-amd64:v1.5.1      "kube-proxy --kubecon"   About an hour ago   Up About an hour                        k8s_kube-proxy.36dab4e8_kube-proxy-lsn0t_kube-system_b8eddf1c-cb4e-11e6-ad8f-00163e1001d7_5826f32b
a60e373b48b8        gcr.io/google_containers/pause-amd64:3.0              "/pause"                 About an hour ago   Up About an hour                        k8s_POD.d8dbe16c_kube-proxy-lsn0t_kube-system_b8eddf1c-cb4e-11e6-ad8f-00163e1001d7_46bfcf67
a665145eb2b5        quay.io/coreos/flannel-git:v0.6.1-28-g5dde68d-amd64   "/bin/sh -c 'set -e -"   About an hour ago   Up About an hour                        k8s_install-cni.17d8cf2_kube-flannel-ds-tr8zr_kube-system_06eca729-cb72-11e6-ad8f-00163e1001d7_01e12f61
5b46f2cb0ccf        gcr.io/google_containers/pause-amd64:3.0              "/pause"                 About an hour ago   Up About an hour                        k8s_POD.d8dbe16c_kube-flannel-ds-tr8zr_kube-system_06eca729-cb72-11e6-ad8f-00163e1001d7_ac880d20
</code></pre>
<p>我们在master node上查看当前cluster状态：</p>
<pre><code># kubectl get nodes
NAME                      STATUS         AGE
iz25beglnhtz              Ready,master   1h
iz2ze39jeyizepdxhwqci6z   Ready          21s
</code></pre>
<p>k8s cluster创建”成功”！真的成功了吗？“折腾”才刚刚开始:(！</p>
<h3>三、Flannel Pod Network问题</h3>
<p>Join成功所带来的“余温”还未散去，我就发现了Flannel pod network的问题，troubleshooting正式开始:(。</p>
<h4>1、minion node上的flannel时不时地报错</h4>
<p>刚join时还好好的，可过了没一会儿，我们就发现在kubectl get pod &#8211;all-namespaces中有错误出现：</p>
<pre><code>kube-system   kube-flannel-ds-tr8zr                  1/2       CrashLoopBackOff   189        16h
</code></pre>
<p>我们发现这是minion node上的flannel pod中的一个container出错导致的，跟踪到的具体错误如下：</p>
<pre><code># docker logs bc0058a15969
E1227 06:17:50.605110       1 main.go:127] Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-ds-tr8zr': Get https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-tr8zr: dial tcp 10.96.0.1:443: i/o timeout
</code></pre>
<p>10.96.0.1是pod network中apiserver service的cluster ip，而minion node上的flannel组件居然无法访问到这个cluster ip！这个问题的奇怪之处还在于，有些时候这个Pod在被调度restart N多次后或者被删除重启后，又突然变为running状态了，行为十分怪异。</p>
<p>在flannel github.com issues中，至少有两个open issue与此问题有密切关系：</p>
<p>https://github.com/coreos/flannel/issues/545</p>
<p>https://github.com/coreos/flannel/issues/535</p>
<p>这个问题暂无明确解。当minion node上的flannel pod自恢复为running状态时，我们又可以继续了。</p>
<h4>2、minion node上flannel pod启动失败的一个应对方法</h4>
<p>在下面issue中，很多developer讨论了minion node上flannel pod启动失败的一种可能原因以及临时应对方法：</p>
<p>https://github.com/kubernetes/kubernetes/issues/34101</p>
<p>这种说法大致就是minion node上的kube-proxy使用了错误的interface，通过下面方法可以fix这个问题。在minion node上执行：</p>
<pre><code>#  kubectl -n kube-system get ds -l 'component=kube-proxy' -o json | jq '.items[0].spec.template.spec.containers[0].command |= .+ ["--cluster-cidr=10.244.0.0/16"]' | kubectl apply -f - &amp;&amp; kubectl -n kube-system delete pods -l 'component=kube-proxy'
daemonset "kube-proxy" configured
pod "kube-proxy-lsn0t" deleted
pod "kube-proxy-sb4sm" deleted
</code></pre>
<p>执行后，flannel pod的状态：</p>
<pre><code>kube-system   kube-flannel-ds-qw291                  2/2       Running   8          17h
kube-system   kube-flannel-ds-x818z                  2/2       Running   17         1h

</code></pre>
<p>经过17次restart，minion node上的flannel pod 启动ok了。其对应的flannel container启动日志如下：</p>
<pre><code># docker logs 1f64bd9c0386
I1227 07:43:26.670620       1 main.go:132] Installing signal handlers
I1227 07:43:26.671006       1 manager.go:133] Determining IP address of default interface
I1227 07:43:26.670825       1 kube.go:233] starting kube subnet manager
I1227 07:43:26.671514       1 manager.go:163] Using 59.110.67.15 as external interface
I1227 07:43:26.671575       1 manager.go:164] Using 59.110.67.15 as external endpoint
I1227 07:43:26.746811       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
I1227 07:43:26.749785       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
I1227 07:43:26.752343       1 ipmasq.go:47] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE
I1227 07:43:26.755126       1 manager.go:246] Lease acquired: 10.244.1.0/24
I1227 07:43:26.755444       1 network.go:58] Watching for L3 misses
I1227 07:43:26.755475       1 network.go:66] Watching for new subnet leases
I1227 07:43:27.755830       1 network.go:153] Handling initial subnet events
I1227 07:43:27.755905       1 device.go:163] calling GetL2List() dev.link.Index: 10
I1227 07:43:27.756099       1 device.go:168] calling NeighAdd: 123.56.200.187, ca:68:7c:9b:cc:67
</code></pre>
<p>issue中说到，在kubeadm init时，显式地指定&#8211;advertise-address将会避免这个问题。不过目前不要在&#8211;advertise-address后面写上多个IP，虽然文档上说是支持的，但实际情况是，当你显式指定&#8211;advertise-address的值为两个或两个以上IP时，比如下面这样：</p>
<pre><code>#kubeadm init --api-advertise-addresses=10.47.217.91,123.56.200.187 --pod-network-cidr=10.244.0.0/16
</code></pre>
<p>master初始化成功后，当minion node执行join cluster命令时，会panic掉：</p>
<pre><code># kubeadm join --token=92e977.f1d4d090906fc06a 10.47.217.91
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
... ...
[bootstrap] Successfully established connection with endpoint "https://10.47.217.91:6443"
[bootstrap] Successfully established connection with endpoint "https://123.56.200.187:6443"
E1228 10:14:05.405294   28378 runtime.go:64] Observed a panic: "close of closed channel" (close of closed channel)
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/runtime/runtime.go:70
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/runtime/runtime.go:63
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/runtime/runtime.go:49
/usr/local/go/src/runtime/asm_amd64.s:479
/usr/local/go/src/runtime/panic.go:458
/usr/local/go/src/runtime/chan.go:311
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kubeadm/app/node/bootstrap.go:85
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/wait/wait.go:96
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/wait/wait.go:97
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/wait/wait.go:52
/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kubeadm/app/node/bootstrap.go:93
/usr/local/go/src/runtime/asm_amd64.s:2086
[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request
panic: close of closed channel [recovered]
    panic: close of closed channel

goroutine 29 [running]:
panic(0x1342de0, 0xc4203eebf0)
    /usr/local/go/src/runtime/panic.go:500 +0x1a1
k8s.io/kubernetes/pkg/util/runtime.HandleCrash(0x0, 0x0, 0x0)
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/runtime/runtime.go:56 +0x126
panic(0x1342de0, 0xc4203eebf0)
    /usr/local/go/src/runtime/panic.go:458 +0x243
k8s.io/kubernetes/cmd/kubeadm/app/node.EstablishMasterConnection.func1.1()
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kubeadm/app/node/bootstrap.go:85 +0x29d
k8s.io/kubernetes/pkg/util/wait.JitterUntil.func1(0xc420563ee0)
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/wait/wait.go:96 +0x5e
k8s.io/kubernetes/pkg/util/wait.JitterUntil(0xc420563ee0, 0x12a05f200, 0x0, 0xc420022e01, 0xc4202c2060)
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/wait/wait.go:97 +0xad
k8s.io/kubernetes/pkg/util/wait.Until(0xc420563ee0, 0x12a05f200, 0xc4202c2060)
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/pkg/util/wait/wait.go:52 +0x4d
k8s.io/kubernetes/cmd/kubeadm/app/node.EstablishMasterConnection.func1(0xc4203a82f0, 0xc420269b90, 0xc4202c2060, 0xc4202c20c0, 0xc4203d8d80, 0x401, 0x480, 0xc4201e75e0, 0x17, 0xc4201e7560, ...)
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kubeadm/app/node/bootstrap.go:93 +0x100
created by k8s.io/kubernetes/cmd/kubeadm/app/node.EstablishMasterConnection
    /go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/cmd/kubeadm/app/node/bootstrap.go:94 +0x3ed

</code></pre>
<p>关于join panic这个问题，在这个issue中有详细讨论：https://github.com/kubernetes/kubernetes/issues/36988</p>
<h4>3、open /run/flannel/subnet.env: no such file or directory</h4>
<p>前面说过，默认情况下，考虑安全原因，master node是不承担work load的，不参与pod调度。我们这里机器少，只能让master node也辛苦一下。通过下面这个命令可以让master node也参与pod调度：</p>
<pre><code># kubectl taint nodes --all dedicated-
node "iz25beglnhtz" tainted
</code></pre>
<p>接下来，我们create一个deployment，manifest描述文件如下：</p>
<pre><code>//run-my-nginx.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.10.1
        ports:
        - containerPort: 80
</code></pre>
<p>create后，我们发现调度到master上的my-nginx pod启动是ok的，但minion node上的pod则一直失败，查看到的失败原因如下：</p>
<pre><code>Events:
  FirstSeen    LastSeen    Count    From                    SubObjectPath    Type        Reason        Message
  ---------    --------    -----    ----                    -------------    --------    ------        -------
  28s        28s        1    {default-scheduler }                    Normal        Scheduled    Successfully assigned my-nginx-2560993602-0440x to iz2ze39jeyizepdxhwqci6z
  27s        1s        26    {kubelet iz2ze39jeyizepdxhwqci6z}            Warning        FailedSync    Error syncing pod, skipping: failed to "SetupNetwork" for "my-nginx-2560993602-0440x_default" with SetupNetworkError: "Failed to setup network for pod \"my-nginx-2560993602-0440x_default(ba5ce554-cbf1-11e6-8c42-00163e1001d7)\" using network plugins \"cni\": open /run/flannel/subnet.env: no such file or directory; Skipping pod"
</code></pre>
<p>在minion node上的确没有找到/run/flannel/subnet.env该文件。但master node上有这个文件：</p>
<pre><code>// /run/flannel/subnet.env

FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
</code></pre>
<p>于是手动在minion node上创建一份/run/flannel/subnet.env，并复制master node同名文件的内容，保存。稍许片刻，minion node上的my-nginx pod从error变成running了。</p>
<h4>4、no IP addresses available in network: cbr0</h4>
<p>将之前的一个my-nginx deployment的replicas改为3，并创建基于该deployment中pods的my-nginx service：</p>
<pre><code>//my-nginx-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30062
    protocol: TCP
  selector:
    run: my-nginx
</code></pre>
<p>修改后，通过curl localhost:30062测试服务连通性。发现通过VIP负载均衡到master node上的my-nginx pod的request都成功得到了Response，但是负载均衡到minion node上pod的request，则阻塞在那里，直到timeout。查看pod信息才发现，原来新调度到minion node上的my-nginx pod并没有启动ok，错误原因如下：</p>
<pre><code>Events:
  FirstSeen    LastSeen    Count    From                    SubObjectPath    Type        Reason        Message
  ---------    --------    -----    ----                    -------------    --------    ------        -------
  2m        2m        1    {default-scheduler }                    Normal        Scheduled    Successfully assigned my-nginx-1948696469-ph11m to iz2ze39jeyizepdxhwqci6z
  2m        0s        177    {kubelet iz2ze39jeyizepdxhwqci6z}            Warning        FailedSync    Error syncing pod, skipping: failed to "SetupNetwork" for "my-nginx-1948696469-ph11m_default" with SetupNetworkError: "Failed to setup network for pod \"my-nginx-1948696469-ph11m_default(3700d74a-cc12-11e6-8c42-00163e1001d7)\" using network plugins \"cni\": no IP addresses available in network: cbr0; Skipping pod"

</code></pre>
<p>查看minion node上/var/lib/cni/networks/cbr0目录，发现该目录下有如下文件：</p>
<pre><code>10.244.1.10   10.244.1.12   10.244.1.14   10.244.1.16   10.244.1.18   10.244.1.2    10.244.1.219  10.244.1.239  10.244.1.3   10.244.1.5   10.244.1.7   10.244.1.9
10.244.1.100  10.244.1.120  10.244.1.140  10.244.1.160  10.244.1.180  10.244.1.20   10.244.1.22   10.244.1.24   10.244.1.30  10.244.1.50  10.244.1.70  10.244.1.90
10.244.1.101  10.244.1.121  10.244.1.141  10.244.1.161  10.244.1.187  10.244.1.200  10.244.1.220  10.244.1.240  10.244.1.31  10.244.1.51  10.244.1.71  10.244.1.91
10.244.1.102  10.244.1.122  10.244.1.142  10.244.1.162  10.244.1.182  10.244.1.201  10.244.1.221  10.244.1.241  10.244.1.32  10.244.1.52  10.244.1.72  10.244.1.92
10.244.1.103  10.244.1.123  10.244.1.143  10.244.1.163  10.244.1.183  10.244.1.202  10.244.1.222  10.244.1.242  10.244.1.33  10.244.1.53  10.244.1.73  10.244.1.93
10.244.1.104  10.244.1.124  10.244.1.144  10.244.1.164  10.244.1.184  10.244.1.203  10.244.1.223  10.244.1.243  10.244.1.34  10.244.1.54  10.244.1.74  10.244.1.94
10.244.1.105  10.244.1.125  10.244.1.145  10.244.1.165  10.244.1.185  10.244.1.204  10.244.1.224  10.244.1.244  10.244.1.35  10.244.1.55  10.244.1.75  10.244.1.95
10.244.1.106  10.244.1.126  10.244.1.146  10.244.1.166  10.244.1.186  10.244.1.205  10.244.1.225  10.244.1.245  10.244.1.36  10.244.1.56  10.244.1.76  10.244.1.96
10.244.1.107  10.244.1.127  10.244.1.147  10.244.1.167  10.244.1.187  10.244.1.206  10.244.1.226  10.244.1.246  10.244.1.37  10.244.1.57  10.244.1.77  10.244.1.97
10.244.1.108  10.244.1.128  10.244.1.148  10.244.1.168  10.244.1.188  10.244.1.207  10.244.1.227  10.244.1.247  10.244.1.38  10.244.1.58  10.244.1.78  10.244.1.98
10.244.1.109  10.244.1.129  10.244.1.149  10.244.1.169  10.244.1.189  10.244.1.208  10.244.1.228  10.244.1.248  10.244.1.39  10.244.1.59  10.244.1.79  10.244.1.99
10.244.1.11   10.244.1.13   10.244.1.15   10.244.1.17   10.244.1.19   10.244.1.209  10.244.1.229  10.244.1.249  10.244.1.4   10.244.1.6   10.244.1.8   last_reserved_ip
10.244.1.110  10.244.1.130  10.244.1.150  10.244.1.170  10.244.1.190  10.244.1.21   10.244.1.23   10.244.1.25   10.244.1.40  10.244.1.60  10.244.1.80
10.244.1.111  10.244.1.131  10.244.1.151  10.244.1.171  10.244.1.191  10.244.1.210  10.244.1.230  10.244.1.250  10.244.1.41  10.244.1.61  10.244.1.81
10.244.1.112  10.244.1.132  10.244.1.152  10.244.1.172  10.244.1.192  10.244.1.211  10.244.1.231  10.244.1.251  10.244.1.42  10.244.1.62  10.244.1.82
10.244.1.113  10.244.1.133  10.244.1.153  10.244.1.173  10.244.1.193  10.244.1.212  10.244.1.232  10.244.1.252  10.244.1.43  10.244.1.63  10.244.1.83
10.244.1.114  10.244.1.134  10.244.1.154  10.244.1.174  10.244.1.194  10.244.1.213  10.244.1.233  10.244.1.253  10.244.1.44  10.244.1.64  10.244.1.84
10.244.1.115  10.244.1.135  10.244.1.155  10.244.1.175  10.244.1.195  10.244.1.214  10.244.1.234  10.244.1.254  10.244.1.45  10.244.1.65  10.244.1.85
10.244.1.116  10.244.1.136  10.244.1.156  10.244.1.176  10.244.1.196  10.244.1.215  10.244.1.235  10.244.1.26   10.244.1.46  10.244.1.66  10.244.1.86
10.244.1.117  10.244.1.137  10.244.1.157  10.244.1.177  10.244.1.197  10.244.1.216  10.244.1.236  10.244.1.27   10.244.1.47  10.244.1.67  10.244.1.87
10.244.1.118  10.244.1.138  10.244.1.158  10.244.1.178  10.244.1.198  10.244.1.217  10.244.1.237  10.244.1.28   10.244.1.48  10.244.1.68  10.244.1.88
10.244.1.119  10.244.1.139  10.244.1.159  10.244.1.179  10.244.1.199  10.244.1.218  10.244.1.238  10.244.1.29   10.244.1.49  10.244.1.69  10.244.1.89

</code></pre>
<p>这已经将10.244.1.x段的所有ip占满，自然没有available的IP可供新pod使用了。至于为何占满，这个原因尚不明朗。下面两个open issue与这个问题相关：</p>
<p>https://github.com/containernetworking/cni/issues/306</p>
<p>https://github.com/kubernetes/kubernetes/issues/21656</p>
<p>进入到/var/lib/cni/networks/cbr0目录下，执行下面命令可以释放那些可能是kubelet leak的IP资源：</p>
<pre><code>for hash in $(tail -n +1 * | grep '^[A-Za-z0-9]*$' | cut -c 1-8); do if [ -z $(docker ps -a | grep $hash | awk '{print $1}') ]; then grep -irl $hash ./; fi; done | xargs rm
</code></pre>
<p>执行后，目录下的文件列表变成了：</p>
<pre><code>ls -l
total 32
drw-r--r-- 2 root root 12288 Dec 27 17:11 ./
drw-r--r-- 3 root root  4096 Dec 27 13:52 ../
-rw-r--r-- 1 root root    64 Dec 27 17:11 10.244.1.2
-rw-r--r-- 1 root root    64 Dec 27 17:11 10.244.1.3
-rw-r--r-- 1 root root    64 Dec 27 17:11 10.244.1.4
-rw-r--r-- 1 root root    10 Dec 27 17:11 last_reserved_ip

</code></pre>
<p>不过pod仍然处于失败状态，但这次失败的原因又发生了变化：</p>
<pre><code>Events:
  FirstSeen    LastSeen    Count    From                    SubObjectPath    Type        Reason        Message
  ---------    --------    -----    ----                    -------------    --------    ------        -------
  23s        23s        1    {default-scheduler }                    Normal        Scheduled    Successfully assigned my-nginx-1948696469-7p4nn to iz2ze39jeyizepdxhwqci6z
  22s        1s        22    {kubelet iz2ze39jeyizepdxhwqci6z}            Warning        FailedSync    Error syncing pod, skipping: failed to "SetupNetwork" for "my-nginx-1948696469-7p4nn_default" with SetupNetworkError: "Failed to setup network for pod \"my-nginx-1948696469-7p4nn_default(a40fe652-cc14-11e6-8c42-00163e1001d7)\" using network plugins \"cni\": \"cni0\" already has an IP address different from 10.244.1.1/24; Skipping pod"

</code></pre>
<p>而/var/lib/cni/networks/cbr0目录下的文件又开始迅速增加！问题陷入僵局。</p>
<h4>5、flannel vxlan不通，后端换udp，仍然不通</h4>
<p>折腾到这里，基本筋疲力尽了。于是在两个node上执行kubeadm reset，准备重新来过。</p>
<p>kubeadm reset后，之前flannel创建的bridge device cni0和网口设备flannel.1依然健在。为了保证环境彻底恢复到初始状态，我们可以通过下面命令删除这两个设备：</p>
<pre><code># ifconfig  cni0 down
# brctl delbr cni0
# ip link delete flannel.1
</code></pre>
<p>有了前面几个问题的“磨炼”后，重新init和join的k8s cluster显得格外顺利。这次minion node没有再出现什么异常。</p>
<pre><code>#  kubectl get nodes -o wide
NAME                      STATUS         AGE       EXTERNAL-IP
iz25beglnhtz              Ready,master   5m        &lt;none&gt;
iz2ze39jeyizepdxhwqci6z   Ready          51s       &lt;none&gt;

# kubectl get pod --all-namespaces
NAMESPACE     NAME                                   READY     STATUS    RESTARTS   AGE
default       my-nginx-1948696469-71h1l              1/1       Running   0          3m
default       my-nginx-1948696469-zwt5g              1/1       Running   0          3m
default       my-ubuntu-2560993602-ftdm6             1/1       Running   0          3m
kube-system   dummy-2088944543-lmlbh                 1/1       Running   0          5m
kube-system   etcd-iz25beglnhtz                      1/1       Running   0          6m
kube-system   kube-apiserver-iz25beglnhtz            1/1       Running   0          6m
kube-system   kube-controller-manager-iz25beglnhtz   1/1       Running   0          6m
kube-system   kube-discovery-1769846148-l5lfw        1/1       Running   0          5m
kube-system   kube-dns-2924299975-mdq5r              4/4       Running   0          5m
kube-system   kube-flannel-ds-9zwr1                  2/2       Running   0          5m
kube-system   kube-flannel-ds-p7xh2                  2/2       Running   0          1m
kube-system   kube-proxy-dwt5f                       1/1       Running   0          5m
kube-system   kube-proxy-vm6v2                       1/1       Running   0          1m
kube-system   kube-scheduler-iz25beglnhtz            1/1       Running   0          6m

</code></pre>
<p>接下来我们创建my-nginx deployment和service来测试flannel网络的连通性。通过curl my-nginx service的nodeport，发现可以reach master上的两个nginx pod，但是minion node上的pod依旧不通。</p>
<p>在master上看flannel docker的日志：</p>
<pre><code>I1228 02:52:22.097083       1 network.go:225] L3 miss: 10.244.1.2
I1228 02:52:22.097169       1 device.go:191] calling NeighSet: 10.244.1.2, 46:6c:7a:a6:06:60
I1228 02:52:22.097335       1 network.go:236] AddL3 succeeded
I1228 02:52:55.169952       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:00.801901       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:03.801923       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:04.801764       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:05.801848       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:06.888269       1 network.go:225] L3 miss: 10.244.1.2
I1228 02:53:06.888340       1 device.go:191] calling NeighSet: 10.244.1.2, 46:6c:7a:a6:06:60
I1228 02:53:06.888507       1 network.go:236] AddL3 succeeded
I1228 02:53:39.969791       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:45.153770       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:48.154822       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:49.153774       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:50.153734       1 network.go:220] Ignoring not a miss: 46:6c:7a:a6:06:60, 10.244.1.2
I1228 02:53:52.154056       1 network.go:225] L3 miss: 10.244.1.2
I1228 02:53:52.154110       1 device.go:191] calling NeighSet: 10.244.1.2, 46:6c:7a:a6:06:60
I1228 02:53:52.154256       1 network.go:236] AddL3 succeeded
</code></pre>
<p>日志中有大量：“Ignoring not a miss”字样的日志，似乎vxlan网络有问题。这个问题与下面issue中描述颇为接近：</p>
<p>https://github.com/coreos/flannel/issues/427</p>
<p>Flannel默认采用vxlan作为backend，使用kernel vxlan默认的udp 8742端口。Flannel还支持udp的backend，使用udp 8285端口。于是试着更换一下flannel后端。更换flannel后端的步骤如下：</p>
<ul>
<li>将https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml文件下载到本地;</li>
<li>修改kube-flannel.yml文件内容：主要是针对net-conf.json属性，增加”Backend”字段属性：</li>
</ul>
<pre><code>---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "type": "flannel",
      "delegate": {
        "isDefaultGateway": true
      }
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "udp",
        "Port": 8285
      }
    }
---
... ...
</code></pre>
<ul>
<li>卸载并重新安装pod网络</li>
</ul>
<pre><code># kubectl delete -f kube-flannel.yml
configmap "kube-flannel-cfg" deleted
daemonset "kube-flannel-ds" deleted

# kubectl apply -f kube-flannel.yml
configmap "kube-flannel-cfg" created
daemonset "kube-flannel-ds" created

# netstat -an|grep 8285
udp        0      0 123.56.200.187:8285     0.0.0.0:*
</code></pre>
<p>经过测试发现：udp端口是通的。在两个node上tcpdump -i flannel0 可以看到udp数据包的发送和接收。但是两个node间的pod network依旧不通。</p>
<h4>6、failed to register network: failed to acquire lease: node “iz25beglnhtz” not found</h4>
<p>正常情况下master node和minion node上的flannel pod的启动日志如下：</p>
<p>master node flannel的运行:</p>
<pre><code>I1227 04:56:16.577828       1 main.go:132] Installing signal handlers
I1227 04:56:16.578060       1 kube.go:233] starting kube subnet manager
I1227 04:56:16.578064       1 manager.go:133] Determining IP address of default interface
I1227 04:56:16.578576       1 manager.go:163] Using 123.56.200.187 as external interface
I1227 04:56:16.578616       1 manager.go:164] Using 123.56.200.187 as external endpoint
E1227 04:56:16.579079       1 network.go:106] failed to register network: failed to acquire lease: node "iz25beglnhtz" not found
I1227 04:56:17.583744       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
I1227 04:56:17.585367       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
I1227 04:56:17.587765       1 ipmasq.go:47] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE
I1227 04:56:17.589943       1 manager.go:246] Lease acquired: 10.244.0.0/24
I1227 04:56:17.590203       1 network.go:58] Watching for L3 misses
I1227 04:56:17.590255       1 network.go:66] Watching for new subnet leases
I1227 07:43:27.164103       1 network.go:153] Handling initial subnet events
I1227 07:43:27.164211       1 device.go:163] calling GetL2List() dev.link.Index: 5
I1227 07:43:27.164350       1 device.go:168] calling NeighAdd: 59.110.67.15, ca:50:97:1f:c2:ea

</code></pre>
<p>minion node上flannel的运行：</p>
<pre><code># docker logs 1f64bd9c0386
I1227 07:43:26.670620       1 main.go:132] Installing signal handlers
I1227 07:43:26.671006       1 manager.go:133] Determining IP address of default interface
I1227 07:43:26.670825       1 kube.go:233] starting kube subnet manager
I1227 07:43:26.671514       1 manager.go:163] Using 59.110.67.15 as external interface
I1227 07:43:26.671575       1 manager.go:164] Using 59.110.67.15 as external endpoint
I1227 07:43:26.746811       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
I1227 07:43:26.749785       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
I1227 07:43:26.752343       1 ipmasq.go:47] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE
I1227 07:43:26.755126       1 manager.go:246] Lease acquired: 10.244.1.0/24
I1227 07:43:26.755444       1 network.go:58] Watching for L3 misses
I1227 07:43:26.755475       1 network.go:66] Watching for new subnet leases
I1227 07:43:27.755830       1 network.go:153] Handling initial subnet events
I1227 07:43:27.755905       1 device.go:163] calling GetL2List() dev.link.Index: 10
I1227 07:43:27.756099       1 device.go:168] calling NeighAdd: 123.56.200.187, ca:68:7c:9b:cc:67
</code></pre>
<p>但在进行上面问题5的测试过程中，我们发现flannel container的启动日志中有如下错误：</p>
<p>master node:</p>
<pre><code># docker logs c2d1cee3df3d
I1228 06:53:52.502571       1 main.go:132] Installing signal handlers
I1228 06:53:52.502735       1 manager.go:133] Determining IP address of default interface
I1228 06:53:52.503031       1 manager.go:163] Using 123.56.200.187 as external interface
I1228 06:53:52.503054       1 manager.go:164] Using 123.56.200.187 as external endpoint
E1228 06:53:52.503869       1 network.go:106] failed to register network: failed to acquire lease: node "iz25beglnhtz" not found
I1228 06:53:52.503899       1 kube.go:233] starting kube subnet manager
I1228 06:53:53.522892       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
I1228 06:53:53.524325       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
I1228 06:53:53.526622       1 ipmasq.go:47] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE
I1228 06:53:53.528438       1 manager.go:246] Lease acquired: 10.244.0.0/24
I1228 06:53:53.528744       1 network.go:58] Watching for L3 misses
I1228 06:53:53.528777       1 network.go:66] Watching for new subnet leases
</code></pre>
<p>minion node:</p>
<pre><code># docker logs dcbfef45308b
I1228 05:28:05.012530       1 main.go:132] Installing signal handlers
I1228 05:28:05.012747       1 manager.go:133] Determining IP address of default interface
I1228 05:28:05.013011       1 manager.go:163] Using 59.110.67.15 as external interface
I1228 05:28:05.013031       1 manager.go:164] Using 59.110.67.15 as external endpoint
E1228 05:28:05.013204       1 network.go:106] failed to register network: failed to acquire lease: node "iz2ze39jeyizepdxhwqci6z" not found
I1228 05:28:05.013237       1 kube.go:233] starting kube subnet manager
I1228 05:28:06.041602       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
I1228 05:28:06.042863       1 ipmasq.go:47] Adding iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
I1228 05:28:06.044896       1 ipmasq.go:47] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE
I1228 05:28:06.046497       1 manager.go:246] Lease acquired: 10.244.1.0/24
I1228 05:28:06.046780       1 network.go:98] Watching for new subnet leases
I1228 05:28:07.047052       1 network.go:191] Subnet added: 10.244.0.0/24

</code></pre>
<p>两个Node都有“注册网络”失败的错误：failed to register network: failed to acquire lease: node “xxxx” not found。很难断定是否是因为这两个错误导致的两个node间的网络不通。从整个测试过程来看，这个问题时有时无。在下面flannel issue中也有类似的问题讨论：</p>
<p>https://github.com/coreos/flannel/issues/435</p>
<p>Flannel pod network的诸多问题让我决定暂时放弃在kubeadm创建的kubernetes cluster中继续使用Flannel。</p>
<h3>四、Calico pod network</h3>
<p>Kubernetes支持的pod network add-ons中，除了Flannel，还有<a href="https://projectcalico.org/">calico</a>、<a href="https://www.weave.works/">Weave net</a>等。这里我们试试基于边界网关BGP协议实现的Calico pod network。Calico Project针对在kubeadm建立的K8s集群的Pod网络安装也有专门的<a href="http://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/kubeadm/">文档</a>。文档中描述的需求和约束我们均满足，比如：</p>
<p>master node带有kubeadm.alpha.kubernetes.io/role: master标签：</p>
<pre><code># kubectl get nodes -o wide --show-labels
NAME           STATUS         AGE       EXTERNAL-IP   LABELS
iz25beglnhtz   Ready,master   3m        &lt;none&gt;        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubeadm.alpha.kubernetes.io/role=master,kubernetes.io/hostname=iz25beglnhtz

</code></pre>
<p>在安装calico之前，我们还是要执行kubeadm reset重置环境，并将flannel创建的各种网络设备删除，可参考上面几个小节中的命令。</p>
<h4>1、初始化集群</h4>
<p>使用calico的kubeadm init无需再指定&#8211;pod-network-cidr=10.244.0.0/16 option：</p>
<pre><code># kubeadm init --api-advertise-addresses=10.47.217.91
[kubeadm] WARNING: kubeadm is in alpha, please do not use it for production clusters.
[preflight] Running pre-flight checks
[preflight] Starting the kubelet service
[init] Using Kubernetes version: v1.5.1
[tokens] Generated token: "531b3f.3bd900d61b78d6c9"
[certificates] Generated Certificate Authority key and certificate.
[certificates] Generated API Server key and certificate
[certificates] Generated Service Account signing keys
[certificates] Created keys and certificates in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[apiclient] Created API client, waiting for the control plane to become ready
[apiclient] All control plane components are healthy after 13.527323 seconds
[apiclient] Waiting for at least one node to register and become ready
[apiclient] First node is ready after 0.503814 seconds
[apiclient] Creating a test deployment
[apiclient] Test deployment succeeded
[token-discovery] Created the kube-discovery deployment, waiting for it to become ready
[token-discovery] kube-discovery is ready after 1.503644 seconds
[addons] Created essential addon: kube-proxy
[addons] Created essential addon: kube-dns

Your Kubernetes master has initialized successfully!

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:

http://kubernetes.io/docs/admin/addons/

You can now join any number of machines by running the following on each node:

kubeadm join --token=531b3f.3bd900d61b78d6c9 10.47.217.91

</code></pre>
<h4>2、创建calico network</h4>
<pre><code># kubectl apply -f http://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/kubeadm/calico.yaml
configmap "calico-config" created
daemonset "calico-etcd" created
service "calico-etcd" created
daemonset "calico-node" created
deployment "calico-policy-controller" created
job "configure-calico" created
</code></pre>
<p>实际创建过程需要一段时间，因为calico需要pull 一些images：</p>
<pre><code># docker images
REPOSITORY                                               TAG                        IMAGE ID            CREATED             SIZE
quay.io/calico/node                                      v1.0.0                     74bff066bc6a        7 days ago          256.4 MB
calico/ctl                                               v1.0.0                     069830246cf3        8 days ago          43.35 MB
calico/cni                                               v1.5.5                     ada87b3276f3        12 days ago         67.13 MB
gcr.io/google_containers/etcd                            2.2.1                      a6cd91debed1        14 months ago       28.19 MB
</code></pre>
<p>calico在master node本地创建了两个network device：</p>
<pre><code># ip a
... ...
47: tunl0@NONE: &lt;NOARP,UP,LOWER_UP&gt; mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 192.168.91.0/32 scope global tunl0
       valid_lft forever preferred_lft forever
48: califa32a09679f@if4: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default
    link/ether 62:39:10:55:44:c8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</code></pre>
<h4>3、minion node join</h4>
<p>执行下面命令，将minion node加入cluster：</p>
<pre><code># kubeadm join --token=531b3f.3bd900d61b78d6c9 10.47.217.91
</code></pre>
<p>calico在minion node上也创建了一个network device:</p>
<pre><code>57988: tunl0@NONE: &lt;NOARP,UP,LOWER_UP&gt; mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 192.168.136.192/32 scope global tunl0
       valid_lft forever preferred_lft forever
</code></pre>
<p>join成功后，我们查看一下cluster status：</p>
<pre><code># kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                       READY     STATUS    RESTARTS   AGE       IP             NODE
kube-system   calico-etcd-488qd                          1/1       Running   0          18m       10.47.217.91   iz25beglnhtz
kube-system   calico-node-jcb3c                          2/2       Running   0          18m       10.47.217.91   iz25beglnhtz
kube-system   calico-node-zthzp                          2/2       Running   0          4m        10.28.61.30    iz2ze39jeyizepdxhwqci6z
kube-system   calico-policy-controller-807063459-f21q4   1/1       Running   0          18m       10.47.217.91   iz25beglnhtz
kube-system   dummy-2088944543-rtsfk                     1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   etcd-iz25beglnhtz                          1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-apiserver-iz25beglnhtz                1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-controller-manager-iz25beglnhtz       1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-discovery-1769846148-51wdk            1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-dns-2924299975-fhf5f                  4/4       Running   0          23m       192.168.91.1   iz25beglnhtz
kube-system   kube-proxy-2s7qc                           1/1       Running   0          4m        10.28.61.30    iz2ze39jeyizepdxhwqci6z
kube-system   kube-proxy-h2qds                           1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
kube-system   kube-scheduler-iz25beglnhtz                1/1       Running   0          23m       10.47.217.91   iz25beglnhtz
</code></pre>
<p>所有组件都是ok的。似乎是好兆头！但跨node的pod network是否联通，还需进一步探究。</p>
<h4>4、探究跨node的pod network联通性</h4>
<p>我们依旧利用上面测试flannel网络的my-nginx-svc.yaml和run-my-nginx.yaml，创建my-nginx service和my-nginx deployment。注意：这之前要先在master node上执行一下”kubectl taint nodes &#8211;all dedicated-”，以让master node承载work load。</p>
<p>遗憾的是，结果和flannel很相似，分配到master node上http request得到了nginx的响应；minion node上的pod依旧无法联通。</p>
<p>这次我不想在calico这块过多耽搁，我要快速看看下一个候选者：weave net是否满足要求。</p>
<h3>由于wordpress莫名其妙的问题，导致这篇文章无法发布完整，因此将其拆分为两个部分，本文为第一部分，第二部分请移步<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm-2/">这里</a>阅读。</h3>
<p style='text-align:left'>&copy; 2016 &#8211; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>一篇文章带你了解Kubernetes安装</title>
		<link>https://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu/</link>
		<comments>https://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu/#comments</comments>
		<pubDate>Tue, 18 Oct 2016 13:34:09 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[brctl]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[coreos]]></category>
		<category><![CDATA[dashboard]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[http_proxy]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Master]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[nodeport]]></category>
		<category><![CDATA[pause-amd64]]></category>
		<category><![CDATA[rancher]]></category>
		<category><![CDATA[routing-mesh]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[swarmkit]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vip]]></category>
		<category><![CDATA[VXLAN]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[服务编排]]></category>
		<category><![CDATA[阿里云]]></category>
		<category><![CDATA[集群]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2034</guid>
		<description><![CDATA[由于之前在阿里云上部署的Docker 1.12.2的Swarm集群没能正常展示出其所宣称的Routing mesh和VIP等功能，为了满足项目需要，我们只能转向另外一种容器集群管理和服务编排工具Kubernetes。 注：之前Docker1.12集群的Routing mesh和VIP功能失效的问题，经过在github上与Docker开发人员的沟通，目前已经将问题原因缩小在阿里云的网络上面，目前看是用于承载vxlan数据通信的节点4789 UDP端口不通的问题，针对这个问题，我正在通过阿里云售后工程师做进一步沟通，希望能找出真因。 Kubernetes(以下称k8s)是Google开源的一款容器集群管理工具，是Google内部工具Borg的“开源版”。背靠Google这个高大上的亲爹，k8s一出生就吸引了足够的眼球，并得到了诸多知名IT公司的支持。至于Google开源k8s的初衷，美好的说法是Google希望通过输出自己在容器领域长达10多年的丰富经验，帮助容器领域的开发人员和客户提升开发效率和容器管理的档次。但任何一种公司行为都会有其背后的短期或长期的商业目的，Google作为一个商业公司也不会例外。Google推出k8s到底为啥呢？众说纷纭。一种说法是Google通过k8s输出其容器工具的操作和使用方法、API标准等，为全世界的开发人员使用其公有容器预热并提供“零门槛”体验。 k8s目前是公认的最先进的容器集群管理工具，在1.0版本发布后，k8s的发展速度更加迅猛，并且得到了容器生态圈厂商的全力支持，这包括coreos、rancher等，诸多提供公有云服务的厂商在提供容器服务时也都基于k8s做二次开发来提供基础设施层的支撑，比如华为。可以说k8s也是Docker进军容器集群管理和服务编排领域最为强劲的竞争对手。 不过和已经原生集成了集群管理工具swarmkit的Docker相比，k8s在文档、安装和集群管理方面的体验还有很大的提升空间。k8s最新发布的1.4版本就是一个着重在这些方面进行改善的版本。比如1.4版本对于Linux主要发行版本Ubuntu Xenial和Red Hat centos7的用户，可以使用熟悉的apt-get和yum来直接安装Kubernetes。再比如，1.4版本引入了kubeadm命令，将集群启动简化为两条命令，不需要再使用复杂的kube-up脚本。 但对于1.4版本以前的1.3.x版本来说，安装起来的赶脚用最近流行的网络词汇来形容就是“蓝瘦，香菇”，但有些时候我们还不得不去挑战这个过程，本文要带大家了解的就是利用阿里云国内区的ECS主机，在Ubuntu 14.04.4操作系统上安装k8s 1.3.7版本的方法和安装过程。 零、心理建设 由于k8s是Google出品，很多组件与google是“打断了骨头还连着筋”，因此在国内网络中安装k8s是需要先进行心理建设的^_^，因为和文档中宣称的k8s 1.4版的安装或docker 1.12.x的安装相比，k8s 1.3.7版本的安装简直就是“灾难级”的。 要想让这一过程适当顺利一些，我们必须准备一个“加速器（你懂的）”。利用加速器应对三件事：慢、断和无法连接。 慢：国内从github或其他国外公有云上下东西简直太慢了，稍大一些的文件，通常都是几个小时或是10几个小时。 断：你说慢就算了，还总断。断了之后，遇到不支持断点续传的，一切还得重来。动不动就上G的文件，重来的时间成本是我们无法承受的。 无法连接：这个你知道的，很多托管在google名下的东西，你总是无法下载的。 总而言之，k8s的安装和容器集群的搭建过程是一个“漫长”且可能反复的过程，需要做好心理准备。 BTW，我在安装过程使用的 网友noah_昨夜星辰推荐的多态加速器，只需配置一个http_proxy即可，尤其适合服务器后台加速，非常方便，速度也很好。 一、安装模型 k8s的文档不可谓不丰富，尤其在k8s安装这个环节，k8s提供了针对各种云平台、裸机、各类OS甚至各类cluster network model实现的安装文档，你着实得费力挑选一个最适合自己情况的。 由于目前国内阿里云尚未提供Ubuntu 16.04LTS版本虚拟机镜像（通过apt-get install可直接安装最新1.4.x版本k8s），我们只能用ubuntu 14.04.x来安装k8s 1.3.x版本，k8s 1.4版本使用了systemd的相关组件，在ubuntu 14.04.x上手工安装k8s 1.4难度估计将是“地狱级”的。网络模型实现我选择coreos提供的flannel，因此我们需要参考的是由国内浙大团队维护的这份k8s安装文档。浙大的这份安装文档针对的是k8s 1.2+的，从文档评分来看，只是二星半，由此推断，完全按照文档中的步骤安装，成功与否要看运气^_^。注意该文档中提到：文档针对ubuntu 14.04是测试ok的，但由于ubuntu15.xx使用systemd替代upstart了，因此无法保证在ubuntu 15.xx上可以安装成功。 关于k8s的安装过程，网上也有很多资料，多数资料一上来就是下载xxx，配置yyy，install zzz，缺少一个k8s安装的总体视图。与内置编排引擎swarmkit的单一docker engine的安装不同，k8s是由一系列核心组件配合协作共同完成容器集群调度和服务编排功能的，安装k8s实际上就是将不同组件安装到承担不同角色的节点上去。 k8s的节点只有两种角色：master和minion，对比Docker swarm集群，master相当于docker swarm集群中的manager，而minion则相当于docker swarm集群中的worker。 在master节点上运行的k8s核心组件包括： # ls /opt/bin&#124;grep kube kube-apiserver [...]]]></description>
			<content:encoded><![CDATA[<p>由于之前在阿里云上部署的<a href="http://tonybai.com/2016/10/11/some-problems-under-swarm-mode-in-docker-1-12/">Docker 1.12.2的Swarm集群</a>没能正常展示出其所宣称的Routing mesh和VIP等功能，为了满足项目需要，我们只能转向另外一种容器集群管理和服务编排工具<a href="http://kubernetes.io/">Kubernetes</a>。</p>
<blockquote>
<p><em>注：之前Docker1.12集群的Routing mesh和VIP功能失效的问题，经过在github上<a href="https://github.com/docker/docker/issues/26946">与Docker开发人员的沟通</a>，目前已经将问题原因缩小在阿里云的网络上面，目前看是用于承载vxlan数据通信的节点4789 UDP端口不通的问题，针对这个问题，我正在通过阿里云售后工程师做进一步沟通，希望能找出真因。</em></p>
</blockquote>
<p>Kubernetes(以下称k8s)是Google开源的一款容器集群管理工具，是Google内部工具Borg的“开源版”。背靠Google这个高大上的亲爹，k8s一出生就吸引了足够的眼球，并得到了诸多知名IT公司的支持。至于Google开源k8s的初衷，美好的说法是Google希望通过输出自己在容器领域长达10多年的丰富经验，帮助容器领域的开发人员和客户提升开发效率和容器管理的档次。但任何一种公司行为都会有其背后的短期或长期的商业目的，Google作为一个商业公司也不会例外。Google推出k8s到底为啥呢？众说纷纭。一种说法是Google通过k8s输出其容器工具的操作和使用方法、API标准等，为全世界的开发人员使用其公有容器预热并提供“零门槛”体验。</p>
<p>k8s目前是公认的最先进的容器集群管理工具，在1.0版本发布后，k8s的发展速度更加迅猛，并且得到了容器生态圈厂商的全力支持，这包括<a href="https://coreos.com/">coreos</a>、<a href="http://tonybai.com/2016/04/14/an-introduction-about-rancher/">rancher</a>等，诸多提供公有云服务的厂商在提供容器服务时也都基于k8s做二次开发来提供基础设施层的支撑，比如华为。可以说k8s也是Docker进军容器集群管理和服务编排领域最为强劲的竞争对手。</p>
<p>不过和已经原生集成了集群管理工具swarmkit的Docker相比，k8s在文档、安装和集群管理方面的体验还有很大的提升空间。k8s最新发布的<a href="http://blog.kubernetes.io/2016/09/kubernetes-1.4-making-it-easy-to-run-on-kuberentes-anywhere.html">1.4版本</a>就是一个着重在这些方面进行改善的版本。比如1.4版本对于Linux主要发行版本Ubuntu Xenial和Red Hat centos7的用户，可以使用熟悉的apt-get和yum来直接安装Kubernetes。再比如，1.4版本引入了kubeadm命令，将集群启动简化为两条命令，不需要再使用复杂的kube-up脚本。</p>
<p>但对于1.4版本以前的1.3.x版本来说，安装起来的赶脚用最近流行的网络词汇来形容就是“蓝瘦，香菇”，但有些时候我们还不得不去挑战这个过程，本文要带大家了解的就是利用<a href="https://www.aliyun.com/">阿里云</a>国内区的ECS主机，在<a href="http://tonybai.com/tag/ubuntu">Ubuntu 14.04.4</a>操作系统上安装<a href="https://github.com/kubernetes/kubernetes/releases/tag/v1.3.7">k8s 1.3.7版本</a>的方法和安装过程。</p>
<h4>零、心理建设</h4>
<p>由于k8s是Google出品，很多组件与google是“打断了骨头还连着筋”，因此在国内网络中安装k8s是需要先进行心理建设的^_^，因为和文档中宣称的k8s 1.4版的安装或docker 1.12.x的安装相比，k8s 1.3.7版本的安装简直就是“灾难级”的。</p>
<p>要想让这一过程适当顺利一些，我们必须准备一个“加速器（你懂的）”。利用加速器应对三件事：慢、断和无法连接。</p>
<ul>
<li>慢：国内从github或其他国外公有云上下东西简直太慢了，稍大一些的文件，通常都是几个小时或是10几个小时。</li>
<li>断：你说慢就算了，还总断。断了之后，遇到不支持断点续传的，一切还得重来。动不动就上G的文件，重来的时间成本是我们无法承受的。</li>
<li>无法连接：这个你知道的，很多托管在google名下的东西，你总是无法下载的。</li>
</ul>
<p>总而言之，k8s的安装和容器集群的搭建过程是一个“漫长”且可能反复的过程，需要做好心理准备。</p>
<p>BTW，我在安装过程使用的 网友<a href="http://weibo.com/p/1005051016825817">noah_昨夜星辰</a>推荐的<a href="https://duotai.org">多态</a>加速器，只需配置一个http_proxy即可，尤其适合服务器后台加速，非常方便，速度也很好。</p>
<h4>一、安装模型</h4>
<p>k8s的文档不可谓不丰富，尤其在k8s安装这个环节，k8s提供了针对各种云平台、裸机、各类OS甚至各类cluster network model实现的<a href="http://kubernetes.io/docs/getting-started-guides/">安装文档</a>，你着实得费力挑选一个最适合自己情况的。</p>
<p>由于目前国内阿里云尚未提供<a href="https://wiki.ubuntu.com/XenialXerus/ReleaseNotes">Ubuntu 16.04LTS</a>版本虚拟机镜像（通过apt-get install可直接安装最新1.4.x版本k8s），我们只能用ubuntu 14.04.x来安装k8s 1.3.x版本，k8s 1.4版本使用了systemd的相关组件，在ubuntu 14.04.x上手工安装k8s 1.4难度估计将是“地狱级”的。网络模型实现我选择coreos提供的<a href="https://github.com/coreos/flannel">flannel</a>，因此我们需要参考的是由国内浙大团队维护的<a href="http://kubernetes.io/docs/getting-started-guides/ubuntu">这份k8s安装文档</a>。浙大的这份安装文档针对的是k8s 1.2+的，从文档评分来看，只是二星半，由此推断，完全按照文档中的步骤安装，成功与否要看运气^_^。注意该文档中提到：文档针对ubuntu 14.04是测试ok的，但由于ubuntu15.xx使用systemd替代upstart了，因此无法保证在ubuntu 15.xx上可以安装成功。</p>
<p>关于k8s的安装过程，网上也有很多资料，多数资料一上来就是下载xxx，配置yyy，install zzz，缺少一个k8s安装的总体视图。与内置编排引擎swarmkit的单一docker engine的安装不同，k8s是由一系列核心组件配合协作共同完成容器集群调度和服务编排功能的，安装k8s实际上就是将不同组件安装到承担不同角色的节点上去。</p>
<p>k8s的节点只有两种角色：master和minion，对比Docker swarm集群，master相当于docker swarm集群中的manager，而minion则相当于docker swarm集群中的worker。</p>
<p>在master节点上运行的k8s核心组件包括：</p>
<pre><code># ls /opt/bin|grep kube
kube-apiserver
kube-controller-manager
kubelet
kube-proxy
kube-scheduler
</code></pre>
<p>在minion节点上，k8s核心组件较少，包括：</p>
<pre><code># ls /opt/bin|grep kube
kubelet
kube-proxy
</code></pre>
<p>k8s的安装模型可以概述为：在安装机上将k8s的各个组件分别部署到不同角色的节点上去(通过ssh远程登录到各节点)，并启动起来。用下面这个简易图表达起来可能更加形象：</p>
<pre><code>安装机(放置k8s的安装程序和安装脚本） ----- install k8s core components to(via ssh) ----&gt;  master and minion nodes
</code></pre>
<p>在安装之前，这里再明确一下我所用的环境信息：</p>
<p>阿里云ECS: Ubuntu 14.04.4 LTS (GNU/Linux 3.19.0-70-generic x86_64)</p>
<p>root@iZ25cn4xxnvZ:~# docker version<br />
Client:<br />
 Version:      1.12.2<br />
 API version:  1.24<br />
 Go version:   go1.6.3<br />
 Git commit:   bb80604<br />
 Built:        Tue Oct 11 17:00:50 2016<br />
 OS/Arch:      linux/amd64</p>
<p>Server:<br />
 Version:      1.12.2<br />
 API version:  1.24<br />
 Go version:   go1.6.3<br />
 Git commit:   bb80604<br />
 Built:        Tue Oct 11 17:00:50 2016<br />
 OS/Arch:      linux/amd64</p>
<h4>二、先决条件</h4>
<p>根据浙大团队的那篇在Ubuntu上安装k8s的<a href="http://kubernetes.io/docs/getting-started-guides/ubuntu">文章</a>，在真正安装k8s组件之前，需要先满足一些先决条件：</p>
<h5>1、安装Docker</h5>
<p>关于<a href="http://tonybai.com/tag/docker">Docker</a>的文档，不得不说，写的还是不错的。Docker到目前为止已经发展了许多年了，其在Ubuntu上的安装已经逐渐成熟了。在其<a href="https://docs.docker.com/engine/installation/linux/ubuntulinux/">官方文档</a>中有针对ubuntu 12.04、14.04和16.04的详细安装说明。如果你的Ubuntu服务器上docker版本较低，还可以用国内Daocloud提供的<a href="http://get.daocloud.io/#install-docker">一键安装服务</a>来安装最新版的Docker。</p>
<h5>2、安装bridge-utils</h5>
<p>安装网桥管理工具：</p>
<pre><code>[sudo] apt-get install bridge-utils
</code></pre>
<p>安装后，可以测试一下安装是否ok：</p>
<pre><code>root@iZ25cn4xxnvZ:~# brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.0242988b938c    no        veth901efcb
docker_gwbridge        8000.0242bffb02d5    no        veth21546ed
                            veth984b294
</code></pre>
<h5>3、确保master node可以连接互联网并下载必要的文件</h5>
<p>这里要提到的是为master node配置上”加速器”。同时如果master node还承担逻辑上的minion node角色，还需要为节点上Docker配置上加速器（如果加速器是通过代理配置的），minion node上亦是如此，比如：</p>
<pre><code>/etc/default/docker

export http_proxy=http://duotai:xxxxx@sheraton.h.xduotai.com:24448
export https_proxy=$http_proxy

</code></pre>
<h5>4、在安装机上配置自动<a href="http://tonybai.com/2009/07/17/physical-examination-and-ssh-and-safari4/">免密ssh登录</a>各个master node 和minion node</h5>
<p>我在阿里云上开了两个ECS（暂成为node1 &#8211; 10.47.136.60和node2 &#8211; 10.46.181.146），我的k8s集群就由这两个物理node承载，但在逻辑上node1和node2承担着多种角色，逻辑上这是一个由一个master node和两个minion node组成的k8s集群：</p>
<pre><code>安装机：node1
master node：node1
minion node: node1和node2

</code></pre>
<p>因此为了满足安装机到各个k8s node免密ssh登录的先决条件，我需要实现从安装机(node1)到master node(node1)和minion node(node1和node2)的免费ssh登录设置。</p>
<p>在安装机node上执行：</p>
<pre><code># ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
... ...
</code></pre>
<p>安装机免密登录逻辑意义上的master node(实际上就是登录自己，即node1）：</p>
<pre><code>cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys
</code></pre>
<p>安装机免费登录minion node(node2)：</p>
<pre><code>将公钥复制到server：
#scp ~/.ssh/id_rsa.pub root@10.46.181.146:/root/id_rsa.pub
The authenticity of host '10.46.181.146 (10.46.181.146)' can't be established.
ECDSA key fingerprint is b7:31:8d:33:f5:6e:ef:a4:a1:cc:72:5f:cf:68:c6:3d.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.46.181.146' (ECDSA) to the list of known hosts.
root@10.46.181.146's password:
id_rsa.pub
</code></pre>
<p>在minion node，即node2上，导入安装机的公钥并修改访问权限：</p>
<pre><code>cat ~/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys
root@iZ25mjza4msZ:~# chmod 700 ~/.ssh
root@iZ25mjza4msZ:~#     chmod 600 ~/.ssh/authorized_keys
</code></pre>
<p>配置完成，你可以在安装机上测试一下到自身(node1)和到node2的免密登录，以免密登录node2为例：</p>
<pre><code>root@iZ25cn4xxnvZ:~/.ssh# ssh 10.46.181.146
Welcome to Ubuntu 14.04.4 LTS (GNU/Linux 3.19.0-70-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
New release '16.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to aliyun Elastic Compute Service!

Last login: Thu Oct 13 12:55:21 2016 from 218.25.32.210
</code></pre>
<h5>5、下载pause-amd64镜像</h5>
<p>k8s集群启动后，启动容器时会去下载google的gcr.io/google_containers下的一个pause-amd64镜像，为了避免那时出错时不便于查找，这些先下手为强，先通过“加速器”将该镜像下载到各个k8s node上：</p>
<p>修改/etc/default/docker，添加带有加速器的http_proxy/https_proxy，并增加&#8211;insecure-registry gcr.io</p>
<pre><code># If you need Docker to use an HTTP proxy, it can also be specified here.
export http_proxy=http://duotai:xxxx@sheraton.h.xduotai.com:24448
export https_proxy=http://duotai:xxxx@sheraton.h.xduotai.com:24448

# This is also a handy place to tweak where Docker's temporary files go.
#export TMPDIR="/mnt/bigdrive/docker-tmp"
DOCKER_OPTS="$DOCKER_OPTS -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 --insecure-registry gcr.io"
</code></pre>
<p>重启docker daemon服务。下载pause-amd64 image：</p>
<pre><code>root@iZ25cn4xxnvZ:~# docker search gcr.io/google_containers/pause-amd64
NAME                            DESCRIPTION   STARS     OFFICIAL   AUTOMATED
google_containers/pause-amd64                 0
root@iZ25cn4xxnvZ:~# docker pull gcr.io/google_containers/pause-amd64
Using default tag: latest
Pulling repository gcr.io/google_containers/pause-amd64
Tag latest not found in repository gcr.io/google_containers/pause-amd64
</code></pre>
<p>latest标签居然都没有，尝试下载3.0标签的pause-amd64：</p>
<pre><code>root@iZ25cn4xxnvZ:~# docker pull gcr.io/google_containers/pause-amd64:3.0
3.0: Pulling from google_containers/pause-amd64
a3ed95caeb02: Pull complete
f11233434377: Pull complete
Digest: sha256:163ac025575b775d1c0f9bf0bdd0f086883171eb475b5068e7defa4ca9e76516
Status: Downloaded newer image for gcr.io/google_containers/pause-amd64:3.0
</code></pre>
<h4>三、设置工作目录，进行安装前的各种配置</h4>
<p>到目前为止，所有node上，包括安装机node上还是“一无所有”的。接下来，我们开始在安装机node上做文章。</p>
<p>俗话说：“巧妇不为无米炊”。安装机想在各个node上安装k8s组件，安装机本身就要有”米”才行，这个米就是k8s源码包或release包中的安装脚本。</p>
<p>在官方文档中，这个获取“米”的步骤为clone k8s的源码库。由于之前就下载了<a href="https://github.com/kubernetes/kubernetes/releases/download/v1.3.7/kubernetes.tar.gz">k8s 1.3.7的release包</a>，这里我就直接使用release包中的”米”。</p>
<p>解压kubernetes.tar.gz后，在当前目录下将看到kubernetes目录：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/1.3.7/kubernetes# ls -F
cluster/  docs/  examples/  federation/  LICENSES  platforms/  README.md  server/  third_party/  Vagrantfile  version
</code></pre>
<p>这个kubernetes目录就是我们安装k8s的<strong>工作目录</strong>。由于我们在ubuntu上安装k8s，因此我们实际上要使用的脚本都在工作目录下的cluster/ubuntu下面，后续有详细说明。</p>
<p>在安装机上，我们最终是要执行这样一句脚本的：</p>
<pre><code>KUBERNETES_PROVIDER=ubuntu ./cluster/kube-up.sh
</code></pre>
<p>在provider=ubuntu的情况下，./cluster/kube-up.sh最终会调用到./cluster/ubuntu/util.sh中的kube-up shell函数，kube-up函数则会调用./cluster/ubuntu/download-release.sh下载k8s安装所使用到的所有包，包括k8s的安装包(kubernetes.tar.gz)、etcd和flannel等。由于之前我们已经下载完k8s的1.3.7版本release包了，这里我们就需要对down-release.sh做一些修改，防止重新下载，导致安装时间过长。</p>
<pre><code>./cluster/ubuntu/download-release.sh

    # KUBE_VERSION=$(get_latest_version_number | sed 's/^v//')
    #curl -L https://github.com/kubernetes/kubernetes/releases/download/v${KUBE_VERSION}/kubernetes.tar.gz -o kubernetes.tar.gz
</code></pre>
<p>这种情况下，你还需要把已经下载的kubernetes.tar.gz文件copy一份，放到./cluster/ubuntu下面。</p>
<p>如果你的网络访问国外主机足够快，你还有足够耐心，那么你大可忽略上面脚本修改的步骤。</p>
<p>在真正执行./cluster/kube-up.sh之前，安装机还需要知道：</p>
<p>1、k8s物理集群都有哪些node组成，node的角色都是什么？<br />
2、k8s的各个依赖程序，比如etcd的版本是什么？</p>
<p>我们需要通过配置./cluster/ubuntu/config-default.sh让./cluster/kube-up.sh获取这些信息。</p>
<pre><code>./cluster/ubuntu/config-default.sh

# node信息，本集群由两个物理node组成，其中第一个node既是master，也是minion
export nodes=${nodes:-"root@10.47.136.60  root@10.46.181.146"}
roles=${roles:-"ai i"}

# minion node个数
export NUM_NODES=${NUM_NODES:-2}

# 为安装脚本配置网络代理，这里主要是为了使用加速器，方便或加速下载一些包
PROXY_SETTING=${PROXY_SETTING:-"http_proxy=http://duotai:xxxx@sheraton.h.xduotai.com:24448 https_proxy=http://duotai:xxxx@sheraton.h.xduotai.com:24448"}
</code></pre>
<p>通过环境变量设置k8s要下载的依赖程序的版本：</p>
<pre><code>export KUBE_VERSION=1.3.7
export FLANNEL_VERSION=0.5.5
export ETCD_VERSION=3.0.12
</code></pre>
<p>如果不设置环境变量，./cluster/ubuntu/download-release.sh中默认的版本号将是：</p>
<pre><code>k8s： 最新版本
etcd：2.3.1
flannel : 0.5.5
</code></pre>
<h4>四、执行安装</h4>
<p>在安装机上，进入./cluster目录，执行如下安装命令：</p>
<pre><code>KUBERNETES_PROVIDER=ubuntu ./kube-up.sh
</code></pre>
<p>执行输出如下：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/1.3.7/kubernetes/cluster# KUBERNETES_PROVIDER=ubuntu ./kube-up.sh
... Starting cluster using provider: ubuntu
... calling verify-prereqs
Identity added: /root/.ssh/id_rsa (/root/.ssh/id_rsa)
... calling kube-up
~/k8stest/1.3.7/kubernetes/cluster/ubuntu ~/k8stest/1.3.7/kubernetes/cluster

Prepare flannel 0.5.5 release ...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   608    0   608    0     0    410      0 --:--:--  0:00:01 --:--:--   409
100 3408k  100 3408k    0     0   284k      0  0:00:11  0:00:11 --:--:--  389k

Prepare etcd 3.0.12 release ...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   607    0   607    0     0    388      0 --:--:--  0:00:01 --:--:--   388
  3  9.8M    3  322k    0     0  84238      0  0:02:02  0:00:03  0:01:59  173k
100  9.8M  100  9.8M    0     0   327k      0  0:00:30  0:00:30 --:--:--  344k

Prepare kubernetes 1.3.7 release ...

~/k8stest/1.3.7/kubernetes/cluster/ubuntu/kubernetes/server ~/k8stest/1.3.7/kubernetes/cluster/ubuntu ~/k8stest/1.3.7/kubernetes/cluster
~/k8stest/1.3.7/kubernetes/cluster/ubuntu ~/k8stest/1.3.7/kubernetes/cluster

Done! All your binaries locate in kubernetes/cluster/ubuntu/binaries directory
~/k8stest/1.3.7/kubernetes/cluster

Deploying master and node on machine 10.47.136.60
saltbase/salt/generate-cert/make-ca-cert.sh: No such file or directory
easy-rsa.tar.gz                                                                                                                               100%   42KB  42.4KB/s   00:00
config-default.sh                                                                                                                             100% 5610     5.5KB/s   00:00
util.sh                                                                                                                                       100%   29KB  28.6KB/s   00:00
kubelet.conf                                                                                                                                  100%  644     0.6KB/s   00:00
kube-proxy.conf                                                                                                                               100%  684     0.7KB/s   00:00
kubelet                                                                                                                                       100% 2158     2.1KB/s   00:00
kube-proxy                                                                                                                                    100% 2233     2.2KB/s   00:00
etcd.conf                                                                                                                                     100%  709     0.7KB/s   00:00
kube-scheduler.conf                                                                                                                           100%  674     0.7KB/s   00:00
kube-apiserver.conf                                                                                                                           100%  674     0.7KB/s   00:00
kube-controller-manager.conf                                                                                                                  100%  744     0.7KB/s   00:00
kube-scheduler                                                                                                                                100% 2360     2.3KB/s   00:00
kube-controller-manager                                                                                                                       100% 2672     2.6KB/s   00:00
kube-apiserver                                                                                                                                100% 2358     2.3KB/s   00:00
etcd                                                                                                                                          100% 2073     2.0KB/s   00:00
reconfDocker.sh                                                                                                                               100% 2074     2.0KB/s   00:00
kube-scheduler                                                                                                                                100%   56MB  56.2MB/s   00:01
kube-controller-manager                                                                                                                       100%   95MB  95.4MB/s   00:01
kube-apiserver                                                                                                                                100%  105MB 104.9MB/s   00:00
etcdctl                                                                                                                                       100%   18MB  17.6MB/s   00:00
flanneld                                                                                                                                      100%   16MB  15.8MB/s   00:01
etcd                                                              100% 2074     2.0KB/s   00:00
kube-scheduler                                                                                              100%   56MB  56.2MB/s   0         100%   56MB  56.2MB/s   00:01
kube-controller-manager                                                                                     100%   95MB  95.4MB/s             100%   95MB  95.4MB/s   00:01
kube-apiserver                                                                                             100%  105MB 104.9MB/s              100%  105MB 104.9MB/s   00:00
etcdctl                                                                                                    100%   18MB  17.6MB/s us           100%   18MB  17.6MB/s   00:00
flanneld                 10                                                                                100%   16MB  15.8MB/sge            100%   16MB  15.8MB/s   00:01

... ...

</code></pre>
<p>结果中并没有出现代表着安装成功的如下log字样：</p>
<pre><code>Cluster validation succeeded
</code></pre>
<p>查看上面安装日志输出，发现在向10.47.136.60 master节点部署组件时，出现如下错误日志：</p>
<pre><code>saltbase/salt/generate-cert/make-ca-cert.sh: No such file or directory

</code></pre>
<p>查看一下./cluster下的确没有saltbase目录，这个问题在网上找到了答案，解决方法如下：</p>
<pre><code>k8s安装包目录下，在./server/kubernetes下已经有salt包：kubernetes-salt.tar.gz，解压后，将saltbase整个目录cp到.cluster/下即可。
</code></pre>
<p>再次执行：KUBERNETES_PROVIDER=ubuntu ./kube-up.sh，可以看到如下执行输出：</p>
<pre><code>... ...

Deploying master and node on machine 10.47.136.60
make-ca-cert.sh                                                                                                                               100% 4028     3.9KB/s   00:00
easy-rsa.tar.gz                                                                                                                               100%   42KB  42.4KB/s   00:00
config-default.sh                                                                                                                             100% 5632     5.5KB/s   00:00
util.sh                                                                                                                                       100%   29KB  28.6KB/s   00:00
kubelet.conf                                                                                                                                  100%  644     0.6KB/s   00:00
kube-proxy.conf                                                                                                                               100%  684     0.7KB/s   00:00
kubelet                                                                                                                                       100% 2158     2.1KB/s   00:00
kube-proxy                                                                                                                                    100% 2233     2.2KB/s   00:00
etcd.conf                                                                                                                                     100%  709     0.7KB/s   00:00
kube-scheduler.conf                                                                                                                           100%  674     0.7KB/s   00:00
kube-apiserver.conf                                                                                                                           100%  674     0.7KB/s   00:00
kube-controller-manager.conf                                                                                                                  100%  744     0.7KB/s   00:00
kube-scheduler                                                                                                                                100% 2360     2.3KB/s   00:00
kube-controller-manager                                                                                                                       100% 2672     2.6KB/s   00:00
kube-apiserver                                                                                                                                100% 2358     2.3KB/s   00:00
etcd                                                                                                                                          100% 2073     2.0KB/s   00:00
reconfDocker.sh                                                                                                                               100% 2074     2.0KB/s   00:00
kube-scheduler                                                                                                                                100%   56MB  56.2MB/s   00:01
kube-controller-manager                                                                                                                       100%   95MB  95.4MB/s   00:00
kube-apiserver                                                                                                                                100%  105MB 104.9MB/s   00:01
etcdctl                                                                                                                                       100%   18MB  17.6MB/s   00:00
flanneld                                                                                                                                      100%   16MB  15.8MB/s   00:00
etcd                                                                                                                                          100%   19MB  19.3MB/s   00:00
flanneld                                                                                                                                      100%   16MB  15.8MB/s   00:00
kubelet                                                                                                                                       100%  103MB 103.1MB/s   00:01
kube-proxy                                                                                                                                    100%   48MB  48.4MB/s   00:00
flanneld.conf                                                                                                                                 100%  577     0.6KB/s   00:00
flanneld                                                                                                                                      100% 2121     2.1KB/s   00:00
flanneld.conf                                                                                                                                 100%  568     0.6KB/s   00:00
flanneld                                                                                                                                      100% 2131     2.1KB/s   00:00
etcd start/running, process 7997
Error:  dial tcp 127.0.0.1:2379: getsockopt: connection refused
{"Network":"172.16.0.0/16", "Backend": {"Type": "vxlan"}}
{"Network":"172.16.0.0/16", "Backend": {"Type": "vxlan"}}
docker stop/waiting
docker start/running, process 8220
Connection to 10.47.136.60 closed.

Deploying node on machine 10.46.181.146
config-default.sh                                                                                                                             100% 5632     5.5KB/s   00:00
util.sh                                                                                                                                       100%   29KB  28.6KB/s   00:00
reconfDocker.sh                                                                                                                               100% 2074     2.0KB/s   00:00
kubelet.conf                                                                                                                                  100%  644     0.6KB/s   00:00
kube-proxy.conf                                                                                                                               100%  684     0.7KB/s   00:00
kubelet                                                                                                                                       100% 2158     2.1KB/s   00:00
kube-proxy                                                                                                                                    100% 2233     2.2KB/s   00:00
flanneld                                                                                                                                      100%   16MB  15.8MB/s   00:00
kubelet                                                                                                                                       100%  103MB 103.1MB/s   00:01
kube-proxy                                                                                                                                    100%   48MB  48.4MB/s   00:00
flanneld.conf                                                                                                                                 100%  577     0.6KB/s   00:00
flanneld                                                                                                                                      100% 2121     2.1KB/s   00:00
flanneld start/running, process 2365
docker stop/waiting
docker start/running, process 2574
Connection to 10.46.181.146 closed.
Validating master
Validating root@10.47.136.60
Validating root@10.46.181.146
Using master 10.47.136.60
cluster "ubuntu" set.
user "ubuntu" set.
context "ubuntu" set.
switched to context "ubuntu".
Wrote config for ubuntu to /root/.kube/config
... calling validate-cluster

Error from server: an error on the server has prevented the request from succeeding
(kubectl failed, will retry 2 times)

Error from server: an error on the server has prevented the request from succeeding
(kubectl failed, will retry 1 times)

Error from server: an error on the server has prevented the request from succeeding
('kubectl get nodes' failed, giving up)

</code></pre>
<p>安装并未成功，至少calling validate-cluster后的validation过程并未成功。</p>
<p>但是和第一次的失败有所不同的是，在master node和minion node上，我们都可以看到已经安装并启动了的k8s核心组件：</p>
<p>master node：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/1.3.7/kubernetes/cluster# ps -ef|grep kube
root      8006     1  0 16:39 ?        00:00:00 /opt/bin/kube-scheduler --logtostderr=true --master=127.0.0.1:8080
root      8008     1  0 16:39 ?        00:00:01 /opt/bin/kube-apiserver --insecure-bind-address=0.0.0.0 --insecure-port=8080 --etcd-servers=http://127.0.0.1:4001 --logtostderr=true --service-cluster-ip-range=192.168.3.0/24 --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,SecurityContextDeny,ResourceQuota --service-node-port-range=30000-32767 --advertise-address=10.47.136.60 --client-ca-file=/srv/kubernetes/ca.crt --tls-cert-file=/srv/kubernetes/server.cert --tls-private-key-file=/srv/kubernetes/server.key
root      8009     1  0 16:39 ?        00:00:02 /opt/bin/kube-controller-manager --master=127.0.0.1:8080 --root-ca-file=/srv/kubernetes/ca.crt --service-account-private-key-file=/srv/kubernetes/server.key --logtostderr=true
root      8021     1  0 16:39 ?        00:00:04 /opt/bin/kubelet --hostname-override=10.47.136.60 --api-servers=http://10.47.136.60:8080 --logtostderr=true --cluster-dns=192.168.3.10 --cluster-domain=cluster.local --config=
root      8023     1  0 16:39 ?        00:00:00 /opt/bin/kube-proxy --hostname-override=10.47.136.60 --master=http://10.47.136.60:8080 --logtostderr=true
</code></pre>
<p>minion node：</p>
<pre><code>root@iZ25mjza4msZ:~# ps -ef|grep kube
root      2370     1  0 16:39 ?        00:00:04 /opt/bin/kubelet --hostname-override=10.46.181.146 --api-servers=http://10.47.136.60:8080 --logtostderr=true --cluster-dns=192.168.3.10 --cluster-domain=cluster.local --config=
root      2371     1  0 16:39 ?        00:00:00 /opt/bin/kube-proxy --hostname-override=10.46.181.146 --master=http://10.47.136.60:8080 --logtostderr=true

</code></pre>
<p>那为什么安装节点上的安装脚本在验证安装是否成功时一直阻塞、最终超时失败呢？我在安装节点，同时也是master node上执行了一下kubectl get node命令：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/1.3.7/kubernetes/cluster# kubectl get nodes

Error from server: an error on the server ("&lt;!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"&gt;\n&lt;html&gt;&lt;head&gt;\n&lt;meta type=\"copyright\" content=\"Copyright (C) 1996-2015 The Squid Software Foundation and contributors\"&gt;\n&lt;meta http-equiv=\"Content-Type\" CONTENT=\"text/html; charset=utf-8\"&gt;\n&lt;title&gt;ERROR: The requested URL could not be retrieved&lt;/title&gt;\n&lt;style type=\"text/css\"&gt;&lt;!-- \n /*\n * Copyright (C) 1996-2015 The Squid Software Foundation and contributors\n *\n * Squid software is distributed under GPLv2+ license and includes\n * contributions from numerous individuals and organizations.\n * Please see the COPYING and CONTRIBUTORS files for details.\n */\n\n/*\n Stylesheet for Squid Error pages\n Adapted from design by Free CSS Templates\n http://www.freecsstemplates.org\n Released for free under a Creative Commons Attribution 2.5 License\n*/\n\n/* Page basics */\n* {\n\tfont-family: verdana, sans-serif;\n}\n\nhtml body {\n\tmargin: 0;\n\tpadding: 0;\n\tbackground: #efefef;\n\tfont-size: 12px;\n\tcolor: #1e1e1e;\n}\n\n/* Page displayed title area */\n#titles {\n\tmargin-left: 15px;\n\tpadding: 10px;\n\tpadding-left: 100px;\n\tbackground: url('/squid-internal-static/icons/SN.png') no-repeat left;\n}\n\n/* initial title */\n#titles h1 {\n\tcolor: #000000;\n}\n#titles h2 {\n\tcolor: #000000;\n}\n\n/* special event: FTP success page titles */\n#titles ftpsuccess {\n\tbackground-color:#00ff00;\n\twidth:100%;\n}\n\n/* Page displayed body content area */\n#content {\n\tpadding: 10px;\n\tbackground: #ffffff;\n}\n\n/* General text */\np {\n}\n\n/* error brief description */\n#error p {\n}\n\n/* some data which may have caused the problem */\n#data {\n}\n\n/* the error message received from the system or other software */\n#sysmsg {\n}\n\npre {\n    font-family:sans-serif;\n}\n\n/* special event: FTP / Gopher directory listing */\n#dirmsg {\n    font-family: courier;\n    color: black;\n    font-size: 10pt;\n}\n#dirlisting {\n    margin-left: 2%;\n    margin-right: 2%;\n}\n#dirlisting tr.entry td.icon,td.filename,td.size,td.date {\n    border-bottom: groove;\n}\n#dirlisting td.size {\n    width: 50px;\n    text-align: right;\n    padding-right: 5px;\n}\n\n/* horizontal lines */\nhr {\n\tmargin: 0;\n}\n\n/* page displayed footer area */\n#footer {\n\tfont-size: 9px;\n\tpadding-left: 10px;\n}\n\n\nbody\n:lang(fa) { direction: rtl; font-size: 100%; font-family: Tahoma, Roya, sans-serif; float: right; }\n:lang(he) { direction: rtl; }\n --&gt;&lt;/style&gt;\n&lt;/head&gt;&lt;body id=ERR_CONNECT_FAIL&gt;\n&lt;div id=\"titles\"&gt;\n&lt;h1&gt;ERROR&lt;/h1&gt;\n&lt;h2&gt;The requested URL could not be retrieved&lt;/h2&gt;\n&lt;/div&gt;\n&lt;hr&gt;\n\n&lt;div id=\"content\"&gt;\n&lt;p&gt;The following error was encountered while trying to retrieve the URL: &lt;a href=\"http://10.47.136.60:8080/api\"&gt;http://10.47.136.60:8080/api&lt;/a&gt;&lt;/p&gt;\n\n&lt;blockquote id=\"error\"&gt;\n&lt;p&gt;&lt;b&gt;Connection to 10.47.136.60 failed.&lt;/b&gt;&lt;/p&gt;\n&lt;/blockquote&gt;\n\n&lt;p id=\"sysmsg\"&gt;The system returned: &lt;i&gt;(110) Connection timed out&lt;/i&gt;&lt;/p&gt;\n\n&lt;p&gt;The remote host or network may be down. Please try the request again.&lt;/p&gt;\n\n&lt;p&gt;Your cache administrator is &lt;a href=\"mailto:webmaster?subject=CacheErrorInfo%20-%20ERR_CONNECT_FAIL&amp;amp;body=CacheHost%3A%20192-241-236-182%0D%0AErrPage%3A%20ERR_CONNECT_FAIL%0D%0AErr%3A%20(110)%20Connection%20timed%20out%0D%0ATimeStamp%3A%20Thu,%2013%20Oct%202016%2008%3A49%3A35%20GMT%0D%0A%0D%0AClientIP%3A%20127.0.0.1%0D%0AServerIP%3A%2010.47.136.60%0D%0A%0D%0AHTTP%20Request%3A%0D%0AGET%20%2Fapi%20HTTP%2F1.1%0AUser-Agent%3A%20kubectl%2Fv1.4.0%20(linux%2Famd64)%20kubernetes%2F4b28af1%0D%0AAccept%3A%20application%2Fjson,%20*%2F*%0D%0AAccept-Encoding%3A%20gzip%0D%0AHost%3A%2010.47.136.60%3A8080%0D%0A%0D%0A%0D%0A\"&gt;webmaster&lt;/a&gt;.&lt;/p&gt;\n\n&lt;br&gt;\n&lt;/div&gt;\n\n&lt;hr&gt;\n&lt;div id=\"footer\"&gt;\n&lt;p&gt;Generated Thu, 13 Oct 2016 08:49:35 GMT by 192-241-236-182 (squid/3.5.12)&lt;/p&gt;\n&lt;!-- ERR_CONNECT_FAIL --&gt;\n&lt;/div&gt;\n&lt;/body&gt;&lt;/html&gt;") has prevented the request from succeeding

</code></pre>
<p>可以看到kubectl得到一坨信息，这是一个html页面内容的数据，仔细分析body内容，我们可以看到：</p>
<pre><code>&lt;body id=ERR_CONNECT_FAIL&gt;\n&lt;div id=\"titles\"&gt;\n&lt;h1&gt;ERROR&lt;/h1&gt;\n&lt;h2&gt;The requested URL could not be retrieved&lt;/h2&gt;\n&lt;/div&gt;\n&lt;hr&gt;\n\n&lt;div id=\"content\"&gt;\n&lt;p&gt;The following error was encountered while trying to retrieve the URL: &lt;a href=\"http://10.47.136.60:8080/api\"&gt;http://10.47.136.60:8080/api&lt;/a&gt;&lt;/p&gt;\n\n&lt;blockquote id=\"error\"&gt;\n&lt;p&gt;&lt;b&gt;Connection to 10.47.136.60 failed.&lt;/b&gt;&lt;/p&gt;\n&lt;/blockquote&gt;\n\n&lt;p id=\"sysmsg\"&gt;The system returned: &lt;i&gt;(110) Connection timed out&lt;/i&gt;&lt;/p&gt;\n\n&lt;p&gt;The remote host or network may be down. Please try the request again.&lt;/p&gt;
</code></pre>
<p>kubectl在访问http://10.47.136.60:8080/api这个url时出现了timed out错误。在master node上直接执行curl http://10.47.136.60:8080/api也是这个错误。猜想是否是我.bashrc中的http_proxy在作祟。于是在.bashrc中增加no_proxy:</p>
<pre><code>export no_proxy='10.47.136.60,10.46.181.146,localhost,127.0.0.1'
</code></pre>
<p>生效后，再在master node上执行curl：</p>
<pre><code># curl http://10.47.136.60:8080/api
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.47.136.60:6443"
    }
  ]
}

</code></pre>
<p>看来问题原因就是安装程序的PROXY_SETTING中没有加入no_proxy的设置的缘故，于是修改config-default.sh中的代理设置：</p>
<pre><code>PROXY_SETTING=${PROXY_SETTING:-"http_proxy=http://duotai:xxxx@sheraton.h.xduotai.com:24448 https_proxy=http://duotai:xxxx@sheraton.h.xduotai.com:24448 no_proxy=10.47.136.60,10.46.181.146,localhost,127.0.0.1"}
</code></pre>
<p>然后重新deploy：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/1.3.7/kubernetes/cluster# KUBERNETES_PROVIDER=ubuntu ./kube-up.sh
... Starting cluster using provider: ubuntu
... calling verify-prereqs
Identity added: /root/.ssh/id_rsa (/root/.ssh/id_rsa)
... calling kube-up
~/k8stest/1.3.7/kubernetes/cluster/ubuntu ~/k8stest/1.3.7/kubernetes/cluster
Prepare flannel 0.5.5 release ...
Prepare etcd 3.0.12 release ...
Prepare kubernetes 1.3.7 release ...
Done! All your binaries locate in kubernetes/cluster/ubuntu/binaries directory
~/k8stest/1.3.7/kubernetes/cluster

Deploying master and node on machine 10.47.136.60
make-ca-cert.sh                                                                                                                               100% 4028     3.9KB/s   00:00
easy-rsa.tar.gz                                                                                                                               100%   42KB  42.4KB/s   00:00
config-default.sh                                                                                                                             100% 5678     5.5KB/s   00:00
... ...
cp: cannot create regular file ‘/opt/bin/etcd’: Text file busy
cp: cannot create regular file ‘/opt/bin/flanneld’: Text file busy
cp: cannot create regular file ‘/opt/bin/kube-apiserver’: Text file busy
cp: cannot create regular file ‘/opt/bin/kube-controller-manager’: Text file busy
cp: cannot create regular file ‘/opt/bin/kube-scheduler’: Text file busy
Connection to 10.47.136.60 closed.
Deploying master and node on machine 10.47.136.60 failed
</code></pre>
<p>重新部署时，由于之前k8s cluster在各个node的组件已经启动，因此failed。我们需要通过</p>
<pre><code>KUBERNETES_PROVIDER=ubuntu kube-down.sh
</code></pre>
<p>将k8s集群停止后再尝试up，或者如果不用这个kube-down.sh脚本，也可以在各个节点上手动shutdown各个k8s组件(master上有五个核心组件，minion node上有两个核心组件，另外别忘了停止etcd和flanneld服务)，以kube-controller-manager为例：</p>
<pre><code>service kube-controller-manager stop
</code></pre>
<p>即可。</p>
<p>再次执行kube-up.sh：</p>
<pre><code>... ...
.. calling validate-cluster
Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying.
Found 2 node(s).
NAME            STATUS    AGE
10.46.181.146   Ready     4h
10.47.136.60    Ready     4h
Validate output:
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-0               Healthy   {"health": "true"}
Cluster validation succeeded
Done, listing cluster services:

Kubernetes master is running at http://10.47.136.60:8080

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

</code></pre>
<p>通过字样：”Cluster validation succeeded”可以证明我们成功安装了k8s集群。</p>
<p>执行kubectl get node可以看到当前集群的节点组成情况：</p>
<pre><code># kubectl get node
NAME            STATUS    AGE
10.46.181.146   Ready     4h
10.47.136.60    Ready     4h
</code></pre>
<p>通过执行kubectl cluster-info dump 可以看到k8s集群更为详尽的信息。</p>
<h4>五、测试k8s的service特性</h4>
<p>之所以采用k8s，初衷就是因为Docker 1.12在阿里云搭建的swarm集群的VIP和Routing mesh机制不好用。因此，在k8s集群部署成功后，我们需要测试一下这两种机制在k8s上是否能够获得支持。</p>
<p>k8s中一些关于集群的抽象概念，比如node、deployment、pod、service等，这里就不赘述了，需要的话可以参考<a href="http://kubernetes.io/docs/user-guide/">这里</a>的Concept guide。</p>
<h5>1、集群内负载均衡</h5>
<p>在k8s集群中，有一个等同于docker swarm vip的概念，成为cluster ip，k8s回为每个service分配一个cluster ip，这个cluster ip在service生命周期中不会改变，并且访问cluster ip的请求会被自动负载均衡到service里的后端container中。</p>
<p>我们来启动一个replicas= 2的nginx service，我们需要先从一个描述文件来部署一个deployment：</p>
<pre><code>//run-my-nginx.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.10.1
        ports:
        - containerPort: 80
</code></pre>
<p>启动deployment:</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl create -f ./run-my-nginx.yaml
deployment "my-nginx" created

root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl get deployment
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
my-nginx   2         2         2            2           9s

root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl get pods -l run=my-nginx -o wide
NAME                        READY     STATUS    RESTARTS   AGE       IP            NODE
my-nginx-2395715568-2t6xe   1/1       Running   0          50s       172.16.57.3   10.46.181.146
my-nginx-2395715568-gpljv   1/1       Running   0          50s       172.16.99.2   10.47.136.60
</code></pre>
<p>可以看到my-nginx deployment已经成功启动，并且被调度在两个minion node上。</p>
<p>接下来，我们将deployment转化为service:</p>
<pre><code># kubectl expose deployment/my-nginx
service "my-nginx" exposed

root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl get svc my-nginx
NAME       CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
my-nginx   192.168.3.239   &lt;none&gt;        80/TCP    15s

# kubectl describe svc my-nginx
Name:            my-nginx
Namespace:        default
Labels:            run=my-nginx
Selector:        run=my-nginx
Type:            ClusterIP
IP:            192.168.3.239
Port:            &lt;unset&gt;    80/TCP
Endpoints:        172.16.57.3:80,172.16.99.2:80
Session Affinity:    None

</code></pre>
<p>我们看到通过expose命令，可以将deployment转化为service，转化后，my-nginx service被分配了一个cluster-ip：192.168.3.239。</p>
<p>我们启动一个client container用于测试内部负载均衡：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl run myclient --image=registry.cn-hangzhou.aliyuncs.com/mioss/test --replicas=1 --command -- tail -f /var/log/bootstrap.log
deployment "myclient" created

root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl get pods
NAME                        READY     STATUS    RESTARTS   AGE
my-nginx-2395715568-2t6xe   1/1       Running   0          24m
my-nginx-2395715568-gpljv   1/1       Running   0          24m
myclient-1460251692-g7rnl   1/1       Running   0          21s

</code></pre>
<p>通过docker exec -it containerid /bin/bash进入myclient容器内，通过curl向上面的cluster-ip发起http请求：</p>
<pre><code>root@myclient-1460251692-g7rnl:/# curl -v 192.168.3.239:80
</code></pre>
<p>同时在两个minion节点上，通过docker logs -f查看my-nginx service下面的两个nginx container实例日志，可以看到两个container轮询收到http request:</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/demo# docker logs -f  ccc2f9bb814a
172.16.57.0 - - [17/Oct/2016:06:35:57 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.0 - - [17/Oct/2016:06:36:13 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.0 - - [17/Oct/2016:06:37:06 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.0 - - [17/Oct/2016:06:37:45 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.0 - - [17/Oct/2016:06:37:46 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.0 - - [17/Oct/2016:06:37:50 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"

root@iZ25mjza4msZ:~# docker logs -f 0e533ec2dc71
172.16.57.4 - - [17/Oct/2016:06:33:14 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.4 - - [17/Oct/2016:06:33:18 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.4 - - [17/Oct/2016:06:34:06 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.4 - - [17/Oct/2016:06:34:09 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.4 - - [17/Oct/2016:06:35:45 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.4 - - [17/Oct/2016:06:36:59 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
</code></pre>
<p>cluster-ip机制有效。</p>
<h5>2、nodeport机制</h5>
<p>k8s通过nodeport机制实现类似docker的routing mesh，但底层机制和原理是不同的。</p>
<p>k8s的nodePort的原理是在集群中的每个node上开了一个端口，将访问该端口的流量导入到该node上的kube-proxy，然后再由kube-proxy进一步讲流量转发给该对应该nodeport的service的alive的pod上。</p>
<p>我们先来删除掉前面启动的my-nginx service，再重新创建支持nodeport的新my-nginx service。在k8s delete service有点讲究，我们删除service的目的不仅要删除service“索引”，还要stop并删除该service对应的Pod中的所有docker container。但在k8s中，直接删除service或delete pods都无法让对应的container stop并deleted，而是要通过delete service and delete deployment两步才能彻底删除service。</p>
<pre><code>root@iZ25cn4xxnvZ:~# kubectl delete svc my-nginx
service "my-nginx" deleted

root@iZ25cn4xxnvZ:~# kubectl get service my-nginx
Error from server: services "my-nginx" not found

//容器依然在运行
root@iZ25cn4xxnvZ:~# kubectl get deployment my-nginx
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
my-nginx   2         2         2            2           20h

root@iZ25cn4xxnvZ:~# kubectl delete deployment my-nginx
deployment "my-nginx" deleted

再执行docker ps，看看对应docker container应该已经被删除。

</code></pre>
<p>重新创建暴露nodeport的my-nginx服务，我们先来创建一个新的service文件：</p>
<pre><code>//my-nginx-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30062
    protocol: TCP
  selector:
    run: my-nginx
</code></pre>
<p>创建服务：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl create -f ./my-nginx-svc.yaml
deployment "my-nginx" created
</code></pre>
<p>查看服务信息：</p>
<pre><code>root@iZ25cn4xxnvZ:~/k8stest/demo# kubectl describe service my-nginx
Name:            my-nginx
Namespace:        default
Labels:            run=my-nginx
Selector:        run=my-nginx
Type:            NodePort
IP:            192.168.3.179
Port:            &lt;unset&gt;    80/TCP
NodePort:        &lt;unset&gt;    30062/TCP
Endpoints:        172.16.57.3:80,172.16.99.2:80
Session Affinity:    None
</code></pre>
<p>可以看到与上一次的service信息相比，这里多出一个属性:NodePort 30062/TCP，这个就是整个服务暴露到集群外面的端口。</p>
<p>接下来我们通过这两个node的公网地址访问一下这个暴露的nodeport，看看service中的两个ngnix container是否能收到request：</p>
<p>通过公网ip curl  30062端口:</p>
<pre><code>curl -v x.x.x.x:30062
curl -v  y.y.y.y:30062
</code></pre>
<p>同样，我们用docker logs -f来监控两个nginx container的日志输出，可以看到：</p>
<pre><code>nginx1:

172.16.57.4 - - [17/Oct/2016:08:19:56 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
172.16.57.1 - - [17/Oct/2016:08:21:55 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.1 - - [17/Oct/2016:08:21:56 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.1 - - [17/Oct/2016:08:21:59 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.1 - - [17/Oct/2016:08:22:07 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.1 - - [17/Oct/2016:08:22:09 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"

nginx2：

172.16.57.0 - - [17/Oct/2016:08:22:05 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.0 - - [17/Oct/2016:08:22:06 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.0 - - [17/Oct/2016:08:22:08 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.0 - - [17/Oct/2016:08:22:09 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
</code></pre>
<p>两个container轮询地收到外部转来的http request。</p>
<p>现在我们将my-nginx服务的scale由2缩减为1：</p>
<pre><code>root@iZ25cn4xxnvZ:~# kubectl scale --replicas=1 deployment/my-nginx
deployment "my-nginx" scaled
</code></pre>
<p>再次测试nodeport机制：</p>
<pre><code>curl -v x.x.x.x:30062
curl -v  y.y.y.y:30062
</code></pre>
<p>scale后，只有master上的my-nginx存活。由于nodeport机制，没有my-nginx上的node收到请求后，将请求转给kube-proxy，通过内部clusterip机制，发给有my-nginx的container。</p>
<pre><code>master上的nginx container：

172.16.99.1 - - [18/Oct/2016:00:55:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
172.16.57.0 - - [18/Oct/2016:00:55:10 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.30.0" "-"
</code></pre>
<p>nodeport机制测试ok。通过netstat我们可以看到30062端口是node上的kube-proxy监听的端口，因此即便该node上没有nginx服务container运行，kube-proxy也会转发request。</p>
<pre><code>root@iZ25cn4xxnvZ:~# netstat -tnlp|grep 30062
tcp6       0      0 :::30062                :::*                    LISTEN      22076/kube-proxy
</code></pre>
<h4>六、尾声</h4>
<p>到这里，k8s集群已经是可用的了。但要用好背后拥有15年容器经验沉淀的k8s，还有很长的路要走，比如安装Addon（DNS plugin等）、比如安装Dashboard等。这些在这里暂不提了，文章已经很长了。后续可能会有单独文章说明。</p>
<p style='text-align:left'>&copy; 2016, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>理解Docker跨多主机容器网络</title>
		<link>https://tonybai.com/2016/02/15/understanding-docker-multi-host-networking/</link>
		<comments>https://tonybai.com/2016/02/15/understanding-docker-multi-host-networking/#comments</comments>
		<pubDate>Mon, 15 Feb 2016 09:04:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aufs]]></category>
		<category><![CDATA[bridge]]></category>
		<category><![CDATA[cgroups]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[coreos]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[ethtool]]></category>
		<category><![CDATA[firewall]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[gateway]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[ifconfig]]></category>
		<category><![CDATA[iproute2]]></category>
		<category><![CDATA[iptables]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[mesos]]></category>
		<category><![CDATA[NAT]]></category>
		<category><![CDATA[netns]]></category>
		<category><![CDATA[netperf]]></category>
		<category><![CDATA[netstat]]></category>
		<category><![CDATA[networking]]></category>
		<category><![CDATA[overlay]]></category>
		<category><![CDATA[overlaynetwork]]></category>
		<category><![CDATA[OVS]]></category>
		<category><![CDATA[rancher]]></category>
		<category><![CDATA[Router]]></category>
		<category><![CDATA[SDN]]></category>
		<category><![CDATA[switch]]></category>
		<category><![CDATA[tcpip]]></category>
		<category><![CDATA[telnet]]></category>
		<category><![CDATA[traceroute]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[VNI]]></category>
		<category><![CDATA[VTEP]]></category>
		<category><![CDATA[VXLAN]]></category>
		<category><![CDATA[交换机]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[网关]]></category>
		<category><![CDATA[网桥]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[覆盖网]]></category>
		<category><![CDATA[路由器]]></category>
		<category><![CDATA[软件定义网络]]></category>
		<category><![CDATA[防火墙]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1941</guid>
		<description><![CDATA[在Docker 1.9 出世前，跨多主机的容器通信方案大致有如下三种： 1、端口映射 将宿主机A的端口P映射到容器C的网络空间监听的端口P&#8217;上，仅提供四层及以上应用和服务使用。这样其他主机上的容器通过访问宿主机A的端口P实 现与容器C的通信。显然这个方案的应用场景很有局限。 2、将物理网卡桥接到虚拟网桥，使得容器与宿主机配置在同一网段下 在各个宿主机上都建立一个新虚拟网桥设备br0，将各自物理网卡eth0桥接br0上，eth0的IP地址赋给br0；同时修改Docker daemon的DOCKER_OPTS，设置-b=br0（替代docker0），并限制Container IP地址的分配范围为同物理段地址（&#8211;fixed-cidr）。重启各个主机的Docker Daemon后，处于与宿主机在同一网段的Docker容器就可以实现跨主机访问了。这个方案同样存在局限和扩展性差的问题：比如需将物理网段的地址划分 成小块，分布到各个主机上，防止IP冲突；子网划分依赖物理交换机设置；Docker容器的主机地址空间大小依赖物理网络划分等。 3、使用第三方的基于SDN的方案：比如 使用Open vSwitch &#8211; OVS 或CoreOS的Flannel 等。 关于这些第三方方案的细节大家可以参考O&#8217;Reilly的《Docker Cookbook》 一书。 Docker在1.9版本中给大家带来了一种原生的跨多主机容器网络的解决方案，该方案的实质是采用了基于VXLAN 的覆盖网技术。方案的使用有一些前提条件： 1、Linux Kernel版本 >= 3.16； 2、需要一个外部Key-value Store（官方例子中使用的是consul）； 3、各物理主机上的Docker Daemon需要一些特定的启动参数； 4、物理主机允许某些特定TCP/UDP端口可用。 本文将带着大家一起利用Docker 1.9.1创建一个跨多主机容器网络，并分析基于该网络的容器间通信原理。 一、实验环境建立 1、升级Linux Kernel 由于实验环境采用的是Ubuntu 14.04 server amd64，其kernel版本不能满足建立跨多主机容器网络要求，因此需要对内核版本进行升级。在Ubuntu的内核站点 下载3.16.7 utopic内核 的三个文件： linux-headers-3.16.7-031607_3.16.7-031607.201410301735_all.deb linux-image-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb linux-headers-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb 在本地执行下面命令安装： sudo dpkg -i linux-headers-3.16.7-*.deb linux-image-3.16.7-*.deb 需要注意的是：kernel [...]]]></description>
			<content:encoded><![CDATA[<p>在<a href="https://blog.docker.com/tag/docker-1-9/">Docker 1.9</a> 出世前，跨多主机的容器通信方案大致有如下三种：</p>
<p>1、<a href="http://tonybai.com/2016/01/18/understanding-binding-docker-container-ports-to-host/">端口映射</a></p>
<p>将宿主机A的端口P映射到容器C的网络空间监听的端口P&#8217;上，仅提供四层及以上应用和服务使用。这样其他主机上的容器通过访问宿主机A的端口P实 现与容器C的通信。显然这个方案的应用场景很有局限。</p>
<p>2、将物理网卡桥接到虚拟网桥，使得容器与宿主机配置在同一网段下</p>
<p>在各个宿主机上都建立一个新虚拟网桥设备br0，将各自物理网卡eth0桥接br0上，eth0的IP地址赋给br0；同时修改Docker daemon的DOCKER_OPTS，设置-b=br0（替代docker0），并限制Container IP地址的分配范围为同物理段地址（&#8211;fixed-cidr）。重启各个主机的Docker Daemon后，处于与宿主机在同一网段的Docker容器就可以实现跨主机访问了。这个方案同样存在局限和扩展性差的问题：比如需将物理网段的地址划分 成小块，分布到各个主机上，防止IP冲突；子网划分依赖物理交换机设置；Docker容器的主机地址空间大小依赖物理网络划分等。</p>
<p>3、使用第三方的基于<a href="https://en.wikipedia.org/wiki/Software-defined_networking">SDN</a>的方案：比如 使用<a href="http://openvswitch.org/">Open vSwitch &#8211; OVS</a> 或<a href="https://coreos.com/">CoreOS</a>的<a href="https://github.com/coreos/flannel">Flannel</a> 等。</p>
<p>关于这些第三方方案的细节大家可以参考O&#8217;Reilly的《<a href="http://book.douban.com/subject/26631435/">Docker Cookbook</a>》 一书。</p>
<p>Docker在1.9版本中给大家带来了一种原生的跨多主机容器网络的解决方案，该方案的实质是采用了基于<a href="https://datatracker.ietf.org/doc/rfc7348">VXLAN</a> 的覆盖网技术。方案的使用有一些前提条件：</p>
<p>1、Linux Kernel版本 >= 3.16；<br />
2、需要一个外部Key-value Store（官方例子中使用的是<a href="https://en.wikipedia.org/wiki/Software-defined_networking">consul</a>）；<br />
3、各物理主机上的Docker Daemon需要一些特定的启动参数；<br />
4、物理主机允许某些特定TCP/UDP端口可用。</p>
<p>本文将带着大家一起利用Docker 1.9.1创建一个跨多主机容器网络，并分析基于该网络的容器间通信原理。</p>
<h3>一、实验环境建立</h3>
<h4>1、升级Linux Kernel</h4>
<p>由于实验环境采用的是Ubuntu 14.04 server amd64，其kernel版本不能满足建立跨多主机容器网络要求，因此需要对内核版本进行升级。在<a href="http://kernel.ubuntu.com/~kernel-ppa/mainline/">Ubuntu的内核站点</a> 下载<a href="http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.16.7-utopic/">3.16.7 utopic内核</a> 的三个文件：</p>
<pre><code>linux-headers-3.16.7-031607_3.16.7-031607.201410301735_all.deb
linux-image-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb
linux-headers-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb
</code></pre>
<p>在本地执行下面命令安装：</p>
<pre><code>sudo dpkg -i linux-headers-3.16.7-*.deb linux-image-3.16.7-*.deb
</code></pre>
<p>需要注意的是：kernel mainline上的3.16.7内核没有带linux-image-extra，也就没有了<a href="https://en.wikipedia.org/wiki/Aufs">aufs</a> 的驱动，因此Docker Daemon将不支持默认的存储驱动：&#8211;storage-driver=aufs，我们需要将storage driver更换为<a href="http://en.wikipedia.org/wiki/Device_mapper">devicemapper</a>。</p>
<p>内核升级是一个有风险的操作，并且是否能升级成功还要看点“运气”：我的两台刀片服务器，就是一台升级成功一台升级失败（一直报网卡问题）。</p>
<h4>2、升级Docker到1.9.1版本</h4>
<p>从国内下载Docker官方的安装包比较慢，这里利用<a href="http://get.daocloud.io/#install-docker">daocloud.io提供的方法</a> 快速安装Docker最新版本：</p>
<pre><code>$ curl -sSL https://get.daocloud.io/docker | sh
</code></pre>
<h4>3、拓扑</h4>
<p>本次的跨多主机容器网络基于两台在不同子网网段内的物理机承载，基于物理机搭建，目的是简化后续网络通信原理分析。</p>
<p>拓扑图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/the-topology-of-docker-multi-host-networking.png" alt="img{512x368}" /></p>
<h3>二、跨多主机容器网络搭建</h3>
<h4>1、创建<a href="https://github.com/hashicorp/consul">consul</a> 服务</h4>
<p>考虑到kv store在本文并非关键，仅作跨多主机容器网络创建启动的前提条件之用，因此仅用包含一个server节点的”cluster”。</p>
<p>参照拓扑图，我们在10.10.126.101上启动一个consul，关于consul集群以及服务注册、服务发现等细节可以参考我之前的<a href="http://tonybai.com/2015/07/06/implement-distributed-services-registery-and-discovery-by-consul/">一 篇文章</a>：</p>
<pre><code>$./consul -d agent -server -bootstrap-expect 1 -data-dir ./data -node=master -bind=10.10.126.101 -client=0.0.0.0 &amp;
</code></pre>
<h4>2、修改Docker Daemon DOCKER_OPTS参数</h4>
<p>前面提到过，通过Docker 1.9创建跨多主机容器网络需要重新配置每个主机节点上的Docker Daemon的启动参数：</p>
<pre><code>ubuntu系统这个配置在/etc/default/docker下：

DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4  -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-advertise eth0:2375 --cluster-store consul://10.10.126.101:8500/network --storage-driver=devicemapper"
</code></pre>
<p>这里多说几句：</p>
<p>-H(或&#8211;host)配置的是Docker client(包括本地和远程的client)与Docker Daemon的通信媒介，也是Docker REST api的服务端口。默认是/var/run/docker.sock（仅用于本地），当然也可以通过tcp协议通信以方便远程Client访问，就像上面 配置的那样。非加密网通信采用2375端口，而TLS加密连接则用2376端口。这两个端口已经<a href="http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker">申请在IANA注册并获批</a>，变成了知名端口。-H可以配置多个，就像上面配置的那样。 unix socket便于本地docker client访问本地docker daemon；tcp端口则用于远程client访问。这样一来：docker pull ubuntu，走docker.sock；而docker -H 10.10.126.101:2375 pull ubuntu则走tcp socket。</p>
<p>&#8211;cluster-advertise 配置的是本Docker Daemon实例在cluster中的地址；<br />
&#8211;cluster-store配置的是Cluster的分布式KV store的访问地址；</p>
<p>如果你之前手工修改过iptables的规则，建议重启Docker Daemon之前清理一下iptables规则：sudo iptables -t nat -F, sudo iptables -t filter -F等。</p>
<h4>3、启动各节点上的Docker Daemon</h4>
<p>以10.10.126.101为例：</p>
<pre><code>$ sudo service docker start

$ ps -ef|grep docker
root      2069     1  0 Feb02 ?        00:01:41 /usr/bin/docker -d --dns 8.8.8.8 --dns 8.8.4.4 --storage-driver=devicemapper -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-advertise eth0:2375 --cluster-store consul://10.10.126.101:8500/network
</code></pre>
<p>启动后iptables的nat, filter规则与<a href="http://tonybai.com/2016/01/15/understanding-container-networking-on-single-host/">单机Docker网络</a>初始情况并无二致。</p>
<pre><code>101节点上初始网络driver类型：
$docker network ls
NETWORK ID          NAME                DRIVER
47e57d6fdfe8        bridge              bridge
7c5715710e34        none                null
19cc2d0d76f7        host                host
</code></pre>
<h4>4、创建overlay网络net1和net2</h4>
<p>在101节点上，创建net1：</p>
<pre><code>$ sudo docker network create -d overlay net1
</code></pre>
<p>在71节点上，创建net2:</p>
<pre><code>$ sudo docker network create -d overlay net2
</code></pre>
<p>之后无论在71节点还是101节点，我们查看当前网络以及驱动类型都是如下结果：</p>
<pre><code>$ docker network ls
NETWORK ID          NAME                DRIVER
283b96845cbe        net2                overlay
da3d1b5fcb8e        net1                overlay
00733ecf5065        bridge              bridge
71f3634bf562        none                null
7ff8b1007c09        host                host
</code></pre>
<p>此时，iptables规则也并无变化。</p>
<h4>5、启动两个overlay net下的containers</h4>
<p>我们分别在net1和net2下面启动两个container，每个节点上各种net1和net2的container各一个：</p>
<pre><code>101:
sudo docker run -itd --name net1c1 --net net1 ubuntu:14.04
sudo docker run -itd --name net2c1 --net net2 ubuntu:14.04

71:
sudo docker run -itd --name net1c2 --net net1 ubuntu:14.04
sudo docker run -itd --name net2c2 --net net2 ubuntu:14.04
</code></pre>
<p>启动后，我们就得到如下网络信息（容器的ip地址可能与前面拓扑图中的不一致，每次容器启动ip地址都可能变化）：</p>
<pre><code>net1:
    net1c1 - 10.0.0.7
    net1c2 - 10.0.0.5

net2:
    net2c1 - 10.0.0.4
    net2c2 -  10.0.0.6
</code></pre>
<h4>6、容器连通性</h4>
<p>在net1c1中，我们来看看其到net1和net2的连通性：</p>
<pre><code>root@021f14bf3924:/# ping net1c2
PING 10.0.0.5 (10.0.0.5) 56(84) bytes of data.
64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=0.670 ms
64 bytes from 10.0.0.5: icmp_seq=2 ttl=64 time=0.387 ms
^C
--- 10.0.0.5 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.387/0.528/0.670/0.143 ms

root@021f14bf3924:/# ping 10.0.0.4
PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data.
^C
--- 10.0.0.4 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1008ms
</code></pre>
<p>可见，net1中的容器是互通的，但net1和net2这两个overlay net之间是隔离的。</p>
<h3>三、跨多主机容器网络通信原理</h3>
<p>在“<a href="http://tonybai.com/2016/01/15/understanding-container-networking-on-single-host/">单机容器网络</a>”一文中，我们说过容器间的通信以及容器到外部网络的通信是通过docker0网桥并结合iptables实现的。那么在上面已经建立的跨多主机容器网络里，容器的通信又是如何实现的呢？下面我们一起来理解一下。注意：有了单机容器网络基础后，这里很多网络细节就不再赘述了。</p>
<p>我们先来看看，在net1下的容器的网络配置，以101上的net1c1容器为例：</p>
<pre><code>$ sudo docker attach net1c1

root@021f14bf3924:/# ip route
default via 172.19.0.1 dev eth1
10.0.0.0/24 dev eth0  proto kernel  scope link  src 10.0.0.4
172.19.0.0/16 dev eth1  proto kernel  scope link  src 172.19.0.2

root@021f14bf3924:/# ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
8: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UP group default
    link/ether 02:42:0a:00:00:04 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.4/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:aff:fe00:4/64 scope link
       valid_lft forever preferred_lft forever
10: eth1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe13:2/64 scope link
       valid_lft forever preferred_lft forever
</code></pre>
<p>可以看出net1c1有两个网口：eth0(10.0.0.4)和eth1(172.19.0.2)；从路由表来看，目的地址在172.19.0.0/16范围内的，走eth1；目的地址在10.0.0.0/8范围内的，走eth0。</p>
<p>我们跳出容器，回到主机网络范畴：</p>
<pre><code>在101上：
$ ip a
... ...
5: docker_gwbridge: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP
    link/ether 02:42:52:35:c9:fc brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 scope global docker_gwbridge
       valid_lft forever preferred_lft forever
    inet6 fe80::42:52ff:fe35:c9fc/64 scope link
       valid_lft forever preferred_lft forever
6: docker0: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu 1500 qdisc noqueue state DOWN
    link/ether 02:42:4b:70:68:9a brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
11: veth26f6db4: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master docker_gwbridge state UP
    link/ether b2:32:d7:65:dc:b2 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::b032:d7ff:fe65:dcb2/64 scope link
       valid_lft forever preferred_lft forever
16: veth54881a0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master docker_gwbridge state UP
    link/ether 9e:45:fa:5f:a0:15 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::9c45:faff:fe5f:a015/64 scope link
       valid_lft forever preferred_lft forever

</code></pre>
<p>我们看到除了我们熟悉的docker0网桥外，还多出了一个docker_gwbridge网桥：</p>
<pre><code>$ brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.02424b70689a    no
docker_gwbridge        8000.02425235c9fc    no        veth26f6db4
                            veth54881a0
</code></pre>
<p>并且从brctl的输出结果来看，两个veth都桥接在docker_gwbridge上，而不是docker0上；docker0在跨多主机容器网络中并没有被用到。docker_gwbridge替代了docker0，用来实现101上隶属于net1网络或net2网络中容器间的通信以及容器到外部的通信，其职能就和单机容器网络中docker0一样。</p>
<p>但位于不同host且隶属于net1的两个容器net1c1和net1c2间的通信显然并没有通过docker_gwbridge完成，从net1c1路由表来看，当net1c1 ping net1c2时，消息是通过eth0，即10.0.0.4这个ip出去的。从host的视角，net1c1的eth0似乎没有网络设备与之连接，那网络通信是如何完成的呢？</p>
<p>这一切是从创建network开始的。前面我们执行docker network create -d overlay net1来创建net1 overlay network，这个命令会创建一个新的network namespace。</p>
<p>我们知道每个容器都有自己的网络namespace，从容器的视角看其网络名字空间，我们能看到网络设备诸如：lo、eth0。这个eth0与主机网络名字空间中的vethx是一个虚拟网卡pair。overlay network也有自己的net ns，而overlay network的net ns与容器的net ns之间也有着一些网络设备对应关系。</p>
<p>我们先来查看一下network namespace的id。为了能利用<a href="https://en.wikipedia.org/wiki/Iproute2">iproute2</a>工具对network ns进行管理，我们需要做如下操作：</p>
<pre><code>$cd /var/run
$sudo ln -s /var/run/docker/netns netns

</code></pre>
<p>这是因为iproute2只能操作/var/run/netns下的net ns，而docker默认的net ns却放在/var/run/docker/netns下。上面的操作成功执行后，我们就可以通过ip命令查看和管理net ns了：</p>
<pre><code>$ sudo ip netns
29170076ddf6
1-283b96845c
5ae976d9dc6a
1-da3d1b5fcb
</code></pre>
<p>我们看到在101主机上，有4个已经建立的net ns。我们大胆猜测一下，这四个net ns分别是两个container的net ns和两个overlay network的net ns。从netns的ID格式以及结合下面命令输出结果中的network id来看：</p>
<pre><code>$ docker network ls
NETWORK ID          NAME                DRIVER
283b96845cbe        net2                overlay
da3d1b5fcb8e        net1                overlay
dd84da8e80bf        host                host
3295c22b22b8        docker_gwbridge     bridge
b96e2d8d4068        bridge              bridge
23749ee4292f        none                null

</code></pre>
<p>我们大致可以猜测出来：</p>
<pre><code>1-da3d1b5fcb 是 net1的net ns；
1-283b96845c是 net2的net ns；
29170076ddf6和5ae976d9dc6a则分属于两个container的net ns。
</code></pre>
<p>由于我们以net1为例，因此下面我们就来分析net1的net ns &#8211; 1-da3d1b5fcb。通过ip命令我们可以得到如下结果：</p>
<pre><code>$ sudo ip netns exec 1-da3d1b5fcb ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: br0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.1/24 scope global br0
       valid_lft forever preferred_lft forever
    inet6 fe80::b80a:bfff:fecc:a1e0/64 scope link
       valid_lft forever preferred_lft forever
7: vxlan1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master br0 state UNKNOWN
    link/ether ea:0c:e0:bc:19:c5 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::e80c:e0ff:febc:19c5/64 scope link
       valid_lft forever preferred_lft forever
9: veth2: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue master br0 state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::4b0:c6ff:fe93:25f3/64 scope link
       valid_lft forever preferred_lft forever

$ sudo ip netns exec 1-da3d1b5fcb ip route
10.0.0.0/24 dev br0  proto kernel  scope link  src 10.0.0.1

$ sudo ip netns exec 1-da3d1b5fcb brctl show
bridge name    bridge id        STP enabled    interfaces
br0        8000.06b0c69325f3    no        veth2
                            vxlan1

</code></pre>
<p>看到br0、veth2，我们心里终于有了底儿了。我们猜测net1c1容器中的eth0与veth2是一个veth pair，并桥接在br0上，通过ethtool查找veth序号的对应关系可以证实这点：</p>
<pre><code>$ sudo docker attach net1c1
root@021f14bf3924:/# ethtool -S eth0
NIC statistics:
     peer_ifindex: 9

101主机：
$ sudo ip netns exec 1-da3d1b5fcb ip -d link
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: br0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    bridge
7: vxlan1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue master br0 state UNKNOWN
    link/ether ea:0c:e0:bc:19:c5 brd ff:ff:ff:ff:ff:ff
    vxlan
9: veth2: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1450 qdisc noqueue master br0 state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    veth

</code></pre>
<p>可以看到net1c1的eth0的pair peer index为9，正好与net ns 1-da3d1b5fcb中的veth2的序号一致。</p>
<p>那么vxlan1呢？注意这个vxlan1并非是veth设备，在ip -d link输出的信息中，它的设备类型为vxlan。前面说过Docker的跨多主机容器网络是基于vxlan的，这里的vxlan1就是net1这个overlay network的一个 VTEP，即VXLAN Tunnel End Point &#8211; VXLAN隧道端点。它是VXLAN网络的边缘设备。VXLAN的相关处理都在VTEP上进行，例如识别以太网数据帧所属的VXLAN、基于 VXLAN对数据帧进行二层转发、封装/解封装报文等。</p>
<p>至此，我们可以大致画出一幅跨多主机网络的原理图：</p>
<p><img src="http://tonybai.com/wp-content/uploads/the-schematic-of-docker-multi-host-networking.png" alt="img{512x368}" /></p>
<p>如果在net1c1中ping net1c2，数据包的行走路径是怎样的呢？</p>
<p>1、net1c1(10.0.0.4)中ping net1c2(10.0.0.5)，根据net1c1的路由表，数据包可通过直连网络到达net1c2。于是arp请求获取net1c2的MAC地址（在vxlan上的arp这里不详述了），得到mac地址后，封包，从eth0发出；<br />
2、eth0桥接在net ns 1-da3d1b5fcb中的br0上，这个br0是个网桥(交换机)虚拟设备，需要将来自eth0的包转发出去，于是将包转给了vxlan设备；这个可以通过arp -a看到一些端倪：</p>
<pre><code>$ sudo ip netns exec 1-da3d1b5fcb arp -a
? (10.0.0.5) at 02:42:0a:00:00:05 [ether] PERM on vxlan1
</code></pre>
<p>3、vxlan是个特殊设备，收到包后，由vxlan设备创建时注册的设备处理程序对包进行处理，即进行VXLAN封包（这期间会查询consul中存储的net1信息），将ICMP包整体作为UDP包的payload封装起来，并将UDP包通过宿主机的eth0发送出去。</p>
<p>4、71宿主机收到UDP包后，发现是VXLAN包，根据VXLAN包中的相关信息（比如Vxlan Network Identifier，VNI=256)找到vxlan设备，并转给该vxlan设备处理。vxlan设备的处理程序进行解包，并将UDP中的payload取出，整体通过br0转给veth口，net1c2从eth0收到ICMP数据包，回复icmp reply。</p>
<p>我们可以通过<a href="https://www.wireshark.org">wireshark</a>抓取相关vxlan包，高版本wireshark内置VXLAN协议分析器，可以直接识别和展示VXLAN包，这里安装的是2.0.1版本（注意：一些低版本wireshark不支持VXLAN分析器，比如1.6.7版本）：</p>
<p><img src="http://tonybai.com/wp-content/uploads/docker-multi-host-networking-wireshark.png" alt="img{512x368}" /></p>
<p>关于VXLAN协议的细节，过于复杂，在后续的文章中maybe会有进一步理解。</p>
<p style='text-align:left'>&copy; 2016, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/02/15/understanding-docker-multi-host-networking/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>理解Docker容器端口映射</title>
		<link>https://tonybai.com/2016/01/18/understanding-binding-docker-container-ports-to-host/</link>
		<comments>https://tonybai.com/2016/01/18/understanding-binding-docker-container-ports-to-host/#comments</comments>
		<pubDate>Mon, 18 Jan 2016 09:43:28 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aufs]]></category>
		<category><![CDATA[bridge]]></category>
		<category><![CDATA[cgroups]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[coreos]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[DNAT]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[docker-proxy]]></category>
		<category><![CDATA[firewall]]></category>
		<category><![CDATA[flannel]]></category>
		<category><![CDATA[gateway]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[ifconfig]]></category>
		<category><![CDATA[iproute2]]></category>
		<category><![CDATA[iptables]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[mesos]]></category>
		<category><![CDATA[NAT]]></category>
		<category><![CDATA[netperf]]></category>
		<category><![CDATA[netstat]]></category>
		<category><![CDATA[networking]]></category>
		<category><![CDATA[rancher]]></category>
		<category><![CDATA[Router]]></category>
		<category><![CDATA[SNAT]]></category>
		<category><![CDATA[sparkyfish]]></category>
		<category><![CDATA[switch]]></category>
		<category><![CDATA[tcpip]]></category>
		<category><![CDATA[telnet]]></category>
		<category><![CDATA[traceroute]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[un-DNAT]]></category>
		<category><![CDATA[un-SNAT]]></category>
		<category><![CDATA[交换机]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[端口映射]]></category>
		<category><![CDATA[网关]]></category>
		<category><![CDATA[网桥]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[路由器]]></category>
		<category><![CDATA[防火墙]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1938</guid>
		<description><![CDATA[在”理解Docker单机容器网络“一文中，还有一个Docker容器网络的功能尚未提及，那就是Docker容器的端口映射。即将容器的服务端口P&#8217; 绑定到宿主机的端口P上，最终达到一种效果：外部程序通过宿主机的P端口访问，就像直接访问Docker容器网络内部容器提供的服务一样。 Docker针对端口映射前后有两种方案，一种是1.7版本之前docker-proxy+iptables DNAT的方式；另一种则是1.7版本(及之后)提供的完全由iptables DNAT实现的端口映射。不过在目前docker 1.9.1中，前一种方式依旧是默认方式。但是从Docker 1.7版本起，Docker提供了一个配置项：&#8211;userland-proxy，以让Docker用户决定是否启用docker-proxy，默认为true，即启用docker-proxy。本文续前文，继续探讨使用端口映射时Docker容器网络的通信流程。 本文中的实验环境依旧保持与上文相同：docker 1.9.1，ubuntu 12.04宿主机，docker image基于官方ubuntu 14.04 image做的一些软件安装。 一、&#8211;userland-proxy=true(defaut)的情况下端口映射 我们首先在实验环境下采用默认的方式进行端口映射，即&#8211;userland-proxy=true。 我们来建立一个 新container &#8211; container3(172.17.0.4)，实现了0.0.0.0:12580 -> container3:12580。 $docker run -it --name container3 -p 12580:12580 dockernetworking/ubuntu:14.04 /bin/bash 这个命令执行后，iptables增加了三条rules： filter forward链: Chain DOCKER (1 references) pkts bytes target prot opt in out source destination 0 0 ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.4 [...]]]></description>
			<content:encoded><![CDATA[<p>在”<a href="http://tonybai.com/2016/01/15/understanding-container-networking-on-single-host/">理解Docker单机容器网络</a>“一文中，还有一个<a href="http://tonybai.com/tag/docker">Docker</a>容器网络的功能尚未提及，那就是Docker容器的端口映射。即将容器的服务端口P&#8217; 绑定到宿主机的端口P上，最终达到一种效果：外部程序通过宿主机的P端口访问，就像直接访问Docker容器网络内部容器提供的服务一样。</p>
<p>Docker针对端口映射前后有两种方案，一种是1.7版本之前docker-proxy+iptables <a href="http://idallen.com/dnat.txt">DNAT</a>的方式；另一种则是1.7版本(及之后)提供的完全由iptables DNAT实现的端口映射。不过在目前docker 1.9.1中，前一种方式依旧是默认方式。但是从Docker 1.7版本起，Docker提供了一个配置项：&#8211;userland-proxy，以让Docker用户决定是否启用docker-proxy，默认为true，即启用docker-proxy。本文续前文，继续探讨使用端口映射时Docker容器网络的通信流程。</p>
<p>本文中的实验环境依旧保持与上文相同：docker 1.9.1，<a href="http://tonybai.com/tag/ubuntu">ubuntu</a> 12.04宿主机，docker image基于官方ubuntu 14.04 image做的一些软件安装。</p>
<h3>一、&#8211;userland-proxy=true(defaut)的情况下端口映射</h3>
<p>我们首先在实验环境下采用默认的方式进行端口映射，即&#8211;userland-proxy=true。</p>
<p>我们来建立一个 新container &#8211; container3(172.17.0.4)，实现了0.0.0.0:12580 -> container3:12580。</p>
<pre><code>$docker run -it --name container3 -p 12580:12580 dockernetworking/ubuntu:14.04 /bin/bash
</code></pre>
<p>这个命令执行后，iptables增加了三条rules：</p>
<pre><code>filter forward链:
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.4           tcp dpt:12580

nat output链:
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 to:172.17.0.4:12580

nat postrouting链：

Chain POSTROUTING (policy ACCEPT 24 packets, 1472 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.4           172.17.0.4           tcp dpt:12580

</code></pre>
<p>我们可以看到了一个DNAT target，是在nat output链中，这个是一个关键点。同样是考虑到调试的方便，在这新增的rules前面，增加LOG target，新的iptables导出内容为：</p>
<pre><code>iptables.portmap.stage1.rules

# Generated by iptables-save v1.4.12 on Fri Jan 15 15:31:06 2016
*raw
: PREROUTING ACCEPT [5737658:60554342802]
:OUTPUT ACCEPT [4294004:56674784720]
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
COMMIT
# Completed on Fri Jan 15 15:31:06 2016
# Generated by iptables-save v1.4.12 on Fri Jan 15 15:31:06 2016
*filter
:INPUT ACCEPT [4444190:53498587744]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4292173:56674165678]
: DOCKER - [0:0]
:FwdId0Od0 - [0:0]
:FwdId0Ond0 - [0:0]
:FwdOd0 - [0:0]
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j FwdOd0
-A FORWARD -i docker0 ! -o docker0 -j FwdId0Ond0
-A FORWARD -i docker0 -o docker0 -j FwdId0Od0
-A OUTPUT ! -s 127.0.0.1/32 -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapFowardDocker:" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j ACCEPT
-A FwdId0Od0 -i docker0 -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Od0:" --log-level 7
-A FwdId0Od0 -i docker0 -o docker0 -j ACCEPT
-A FwdId0Ond0 -i docker0 ! -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Ond0:" --log-level 7
-A FwdId0Ond0 -i docker0 ! -o docker0 -j ACCEPT
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j LOG --log-prefix "[TonyBai]-FwdOd0:" --log-level 7
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Fri Jan 15 15:31:06 2016
# Generated by iptables-save v1.4.12 on Fri Jan 15 15:31:06 2016
*nat
: PREROUTING ACCEPT [24690:5091417]
:INPUT ACCEPT [10942:2271167]
:OUTPUT ACCEPT [7756:523318]
: POSTROUTING ACCEPT [7759:523498]
: DOCKER - [0:0]
:LogNatPostRouting - [0:0]
-A PREROUTING -p icmp -j LOG --log-prefix "[TonyBai]-Enter iptables:" --log-level 7
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterNatInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j LogNatPostRouting
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapNatPostRouting:" --log-level 7
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapNatOutputDocker:" --log-level 7
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 12580 -j DNAT --to-destination 172.17.0.4:12580
-A LogNatPostRouting -s 172.17.0.0/16 ! -o docker0 -j LOG --log-prefix "[TonyBai]-NatPostRouting:" --log-level 7
-A LogNatPostRouting -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
COMMIT
# Completed on Fri Jan 15 15:31:06 2016
</code></pre>
<p>另外我们可以查看到宿主机中多了一个进程，这就是前面所说的docker-proxy，每增加一个端口映射，宿主机就会多出一个docker-proxy进程：</p>
<pre><code>root      5742  2113  0 08:48 ?        00:00:00 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12580 -container-ip 172.17.0.4 -container-port 12580
</code></pre>
<h4>1、从10.10.126.187访问宿主机(10.10.126.101)的12580端口</h4>
<p>10.10.126.187是与101在同一直连网路的主机，我们在其上执行telnet 10.10.126.101 12580。如果container3中有server在监听12580，则建立连接和数据通信(发送一个hello)的过程如下。</p>
<p>【187到101的tcp握手sync包】</p>
<p>101从eth0网卡收到目的地址是自己的sync数据包：</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.162828] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.162862] [TonyBai]-NatPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0
</code></pre>
<p>由于目的地址就是自己，因此在iptables中走input chain将数据包发给user层：</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.162885] [TonyBai]-FilterInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.162900] [TonyBai]-NatInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0
</code></pre>
<p>【101回复ack sync包】</p>
<p>101上的用户层是docker-proxy在监听12580端口，当收到sync后，会回复ack sync。由于是user空间自产包，路由后走output链。</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.162933] [TonyBai]-RawOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.162948] [TonyBai]-FilterOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=28960 RES=0x00 ACK SYN URGP=0
</code></pre>
<p>【187回复ack，101与187握手完成】</p>
<p>187回复握手过程最后的一个ack。这个过程与sync类似：</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.163397] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=32618 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.163437] [TonyBai]-FilterInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=32618 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK URGP=0
</code></pre>
<p>重点是接下来发生的事情：101上的docker-proxy向container3上的server程序建立tcp连接！</p>
<p>【host向container3发送sync】</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.163863] [TonyBai]-RawOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=5768 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.163901] [TonyBai]-FilterOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=5768 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
</code></pre>
<p>我们看到SYN数据包源地址用的是172.17.0.1，不知是否是docker-proxy内部有意选择了网桥的ip。由于是user层发出的包，于是走iptables output链。</p>
<p>【container3回复ack sync】</p>
<p>container3回复ack sync，目的地址是172.17.0.1，host从docker0网卡收到ack sync数据，路由后发现是发给自己的包，于是走input chain.</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.164000] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.164026] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=28960 RES=0x00 ACK SYN URGP=0
</code></pre>
<p>【host回复ack，host与container3握手完成】</p>
<p>host回复握手过程最后的一个ack。user空间自产数据包，于是走output chain：</p>
<pre><code>Jan 15 16:04:54 pc-baim kernel: [28410.164049] [TonyBai]-RawOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=5769 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.164058] [TonyBai]-FilterOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=5769 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
</code></pre>
<p>【187 在已经建立的连接上发送”hello”】</p>
<p>187发送hello to host，docker-proxy收到hello数据：</p>
<pre><code>Jan 15 16:04:58 pc-baim kernel: [28413.840854] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=32619 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK PSH URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.840874] [TonyBai]-FilterInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=32619 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK PSH URGP=0
</code></pre>
<p>【host返回 ack push】</p>
<pre><code>Jan 15 16:04:58 pc-baim kernel: [28413.840893] [TonyBai]-RawOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22415 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=227 RES=0x00 ACK URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.840902] [TonyBai]-FilterOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22415 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=227 RES=0x00 ACK URGP=0
</code></pre>
<p>接下来，docker-proxy将hello从已有连接上转发给container3。</p>
<p>【host转发hello到container3】</p>
<pre><code>Jan 15 16:04:58 pc-baim kernel: [28413.841000] [TonyBai]-RawOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=59 TOS=0x00 PREC=0x00 TTL=64 ID=5770 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK PSH URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.841026] [TonyBai]-FilterOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=59 TOS=0x00 PREC=0x00 TTL=64 ID=5770 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK PSH URGP=0
</code></pre>
<p>【container3回复ack 】</p>
<pre><code>Jan 15 16:04:58 pc-baim kernel: [28413.841101] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=61139 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=227 RES=0x00 ACK URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.841119] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=61139 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=227 RES=0x00 ACK URGP=0
</code></pre>
<p>通信过程到此结束。通过这个过程，我们至少了解到两点：</p>
<p>1、docker-proxy将外部建立在host:12580上的连接上的数据转发到container中，反之亦然，如果container 通过与host已经建立的连接向外发送数据，docker-proxy也会将数据转发给187。<br />
2、通过iptables log输出我们可以看到：为了port map而添加的DNAT和MASQUERADE 并没有被匹配到，也就是说在这个过程中并没有用到DNAT，而是完全依靠docker-proxy做的4层代理。</p>
<h4>2、从宿主机上访问10.10.126.101:12580</h4>
<p>我们在宿主机本机上访问10.10.126.101:12580，看看这个通信过程与上面的是否有差异。</p>
<p>【与本机12580端口建立连接，发送sync包】</p>
<p>由于是user层发送数据包，因此走iptables output链。</p>
<pre><code>Jan 15 16:40:15 pc-baim kernel: [30532.594545] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=53747 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
</code></pre>
<p>在output链上，匹配到nat output上的规则：</p>
<pre><code>Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    1    60 LOG        tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 LOG flags 0 level 7 prefix "[TonyBai]-PortmapNatOutputDoc"
    1    60 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 to:172.17.0.4:12580
</code></pre>
<p>于是这里将做一个DNAT，数据包的目的地址10.10.126.101被替换为172.17.0.4。</p>
<pre><code>Jan 15 16:40:15 pc-baim kernel: [30532.594561] [TonyBai]-PortmapNatOutputDoc IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=53747 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0

Jan 15 16:40:15 pc-baim kernel: [30532.594572] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=53747 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
</code></pre>
<p>DNAT后，将按照目的地址做一个重新路由：叫实际路由。消息实际重定向到docker0进行封包发送，sync包直接进入到container3 中。</p>
<p>【container3发送ack sync包】</p>
<p>docker0出来的ack sync 通过input chain送到user空间。这块应该由一个自动un-DNAT，将172.17.0.4自动转回10.10.126.101，但通过iptables日志无法确认这点。</p>
<pre><code>Jan 15 16:40:15 pc-baim kernel: [30532.594615] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 15 16:40:15 pc-baim kernel: [30532.594624] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=28960 RES=0x00 ACK SYN URGP=0
</code></pre>
<p>【host发送ack，完成握手】</p>
<p>host回复ack。user层自产包，走output链，看rawoutput，dst依旧是126.101(telnet自然不应该知道 172.17.0.4的存在)，但是filter output 前，iptables对该地址自动做了dnat，无需重新进入到nat output链，因为之前已经进过了。在filter output中，我们看到dst ip已经变成了container3的ip地址：</p>
<pre><code>Jan 15 16:40:15 pc-baim kernel: [30532.594637] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=53748 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
Jan 15 16:40:15 pc-baim kernel: [30532.594643] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=53748 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
</code></pre>
<p>【host发送hello】</p>
<p>这个过程同上，不赘述。</p>
<pre><code>Jan 15 16:40:18 pc-baim kernel: [30535.344921] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=53749 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK PSH URGP=0
Jan 15 16:40:18 pc-baim kernel: [30535.344956] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=53749 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK PSH URGP=0
</code></pre>
<p>【container回复ack】</p>
<p>不赘述。</p>
<pre><code>Jan 15 16:40:18 pc-baim kernel: [30535.345027] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=43021 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=227 RES=0x00 ACK URGP=0
Jan 15 16:40:18 pc-baim kernel: [30535.345056] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=43021 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=227 RES=0x00 ACK URGP=0
</code></pre>
<p>从这个过程可以看到，在宿主机上访问container的映射端口，通信流程不走docker-proxy，而是直接通过output 的dnat将数据包被直接转给container中的server程序。</p>
<h4>3、container to container</h4>
<p>在container1中telnet 10.10.126.101 12580会发生什么呢？这里就不长篇大论的列log了，直接给出结论：通过docker-proxy转发，因为不满足nat output中DNAT的匹配条件。</p>
<h3>二、在&#8211;userland-proxy=false的情况下</h3>
<p>我们修改了一下/etc/default/docker配置，为DOCKER_OPTS增加一个option: &#8211;userland-proxy=false。</p>
<pre><code>DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --userland-proxy=false"
</code></pre>
<p>重启docker daemon并清理iptables规则(-F)，并启动做端口映射的container3。启动后，你会发现之前的docker-proxy并没有出现在启动进程列表中，iptables的规则与&#8211;userland-proxy=true时也有所不同：</p>
<pre><code>$ sudo iptables -nL -v
Chain INPUT (policy ACCEPT 1645 packets, 368K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 263 packets, 134K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.4           tcp dpt:12580

$ sudo iptables -t nat -nL -v
Chain PREROUTING (policy ACCEPT 209 packets, 65375 bytes)
 pkts bytes target     prot opt in     out     source               destination
   71 49357 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 98 packets, 39060 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 34 packets, 2096 bytes)
 pkts bytes target     prot opt in     out     source               destination
   21  1302 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 34 packets, 2096 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match src-type LOCAL
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.4           172.17.0.4           tcp dpt:12580

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 to:172.17.0.4:12580
</code></pre>
<p>可以看到nat表中prerouting链增加了target为DOCKER链的规则，并且Docker链中对dnat的匹配条件也放开了，只要是dst-type是LOCAL的，dport=12580的，都将ip映射为172.17.0.4。</p>
<p>由于iptables的规则有所变化，因此因此我的log target的匹配条件也该调整一下了，调整后的iptables为：</p>
<pre><code>iptables.portmap.stage1.tmp.rules

# Generated by iptables-save v1.4.12 on Mon Jan 18 09:06:06 2016
*mangle
: POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o docker0 -m addrtype --src-type LOCAL -j LOG --log-prefix "[TonyBai]-manglepost1" --log-level 7
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j LOG --log-prefix "[TonyBai]-manglepost2" --log-level 7
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-manglepost3" --log-level 7
COMMIT

*raw
: PREROUTING ACCEPT [1008742:377375989]
:OUTPUT ACCEPT [426678:274235692]
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
COMMIT
# Completed on Mon Jan 18 09:06:06 2016
# Generated by iptables-save v1.4.12 on Mon Jan 18 09:06:06 2016
*filter
:INPUT ACCEPT [187016:64478647]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [81342:51955911]
: DOCKER - [0:0]
:FwdId0Od0 - [0:0]
:FwdId0Ond0 - [0:0]
:FwdOd0 - [0:0]
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j FwdOd0
-A FORWARD -i docker0 ! -o docker0 -j FwdId0Ond0
-A FORWARD -i docker0 -o docker0 -j FwdId0Od0
-A OUTPUT ! -s 127.0.0.1/32 -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapFowardDocker" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j ACCEPT
-A FwdId0Od0 -i docker0 -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Od0:" --log-level 7
-A FwdId0Od0 -i docker0 -o docker0 -j ACCEPT
-A FwdId0Ond0 -i docker0 ! -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Ond0:" --log-level 7
-A FwdId0Ond0 -i docker0 ! -o docker0 -j ACCEPT
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j LOG --log-prefix "[TonyBai]-FwdOd0:" --log-level 7
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Mon Jan 18 09:06:06 2016
# Generated by iptables-save v1.4.12 on Mon Jan 18 09:06:06 2016
*nat
: PREROUTING ACCEPT [34423:7014094]
:INPUT ACCEPT [9475:1880078]
:OUTPUT ACCEPT [3524:218202]
: POSTROUTING ACCEPT [3508:217098]
: DOCKER - [0:0]
:LogNatPostRouting1 - [0:0]
:LogNatPostRouting2 - [0:0]
:LogNatPostRouting3 - [0:0]
-A PREROUTING -p icmp -j LOG --log-prefix "[TonyBai]-Enter iptables:" --log-level 7
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterNatInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -p tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPostrouteEnter" --log-level 7
-A POSTROUTING -p tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatPostrouteEnter" --log-level 7
-A POSTROUTING -o docker0 -m addrtype --src-type LOCAL -j LogNatPostRouting1
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j LogNatPostRouting2
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LogNatPostRouting3
-A DOCKER -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapNatPrerouting" --log-level 7
-A DOCKER -p tcp -m tcp --dport 12580 -j DNAT --to-destination 172.17.0.4:12580
-A LogNatPostRouting1 -o docker0 -m addrtype --src-type LOCAL -j LOG --log-prefix "[TonyBai]-NatPost1" --log-level 7
-A LogNatPostRouting1 -o docker0 -m addrtype --src-type LOCAL -j MASQUERADE
-A LogNatPostRouting2 -s 172.17.0.0/16 ! -o docker0 -j LOG --log-prefix "[TonyBai]-NatPost2" --log-level 7
-A LogNatPostRouting2 -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A LogNatPostRouting3 -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPost3" --log-level 7
-A LogNatPostRouting3 -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j MASQUERADE
COMMIT
# Completed on Mon Jan 18 09:06:06 2016

</code></pre>
<p>接下来，我们按照上面的方法再做一遍实验例子，看看通信流程有何不同。这次我们将187主机换为10.10.105.71，其他无差别。</p>
<h4>1、 在71上telnet 10.10.126.101 12580</h4>
<p>宿主机从eth0接口收到syn，nat prerouting中做DNAT。路由后，通过forward链转发到docker0：</p>
<pre><code>Jan 18 13:35:55 pc-baim kernel: [278835.389225] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=63 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389275] [TonyBai]-NatPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=63 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389290] [TonyBai]-PortmapNatPreroutinIN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=63 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389326] [TonyBai]-PortmapFowardDockerIN=eth0 OUT=docker0 MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=62 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389339] [TonyBai]-NatPostrouteEnterIN= OUT=docker0 SRC=10.10.105.71 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=62 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
</code></pre>
<p>接下来从docker0网卡收到container3的ack syn应答，在从eth0转发出去前自动un-DNAT， src ip从172.17.0.4变为101.0126.101，但这个在日志中看不出来。</p>
<pre><code>Jan 18 13:35:55 pc-baim kernel: [278835.389496] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.105.71 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=41502 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389519] [TonyBai]-FwdId0Ond0:IN=docker0 OUT=eth0 PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.105.71 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=12580 DPT=41502 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389528] [TonyBai]-manglepost2IN= OUT=eth0 PHYSIN=veth0d66af2 SRC=172.17.0.4 DST=10.10.105.71 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=12580 DPT=41502 WINDOW=28960 RES=0x00 ACK SYN URGP=0
</code></pre>
<p>回送ack，这回无需再匹配natprerouting链，前面进过链一次，后续自动进行DNAT：</p>
<pre><code>Jan 18 13:35:55 pc-baim kernel: [278835.390079] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=63 ID=61481 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.390149] [TonyBai]-PortmapFowardDockerIN=eth0 OUT=docker0 MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=62 ID=61481 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
</code></pre>
<p>这次我们看到，在这种方式下，外部流量也是通过DNAT方式导入到container中的。</p>
<h4>2、在宿主机上 telnet 10.10.126.101 12580</h4>
<p>telnet发起tcp握手，syn包进入output链，匹配到nat output规则，做DNAT。目的ip转换为172.17.0.4。注意继续向下，我们看iptables匹配到了NatPost1，也就是规则：</p>
<pre><code>-A LogNatPostRouting1 -o docker0 -m addrtype --src-type LOCAL -j MASQUERADE
</code></pre>
<p>即将源地址伪装为出口网卡docker0的当前地址：172.0.0.1。于是实际上进入到container3的syn数据包的源地址为172.0.0.1，目的地址：172.0.0.4。</p>
<pre><code>Jan 18 13:49:43 pc-baim kernel: [279663.426497] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426526] [TonyBai]-PortmapNatPreroutinIN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426545] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426553] [TonyBai]-manglepost1IN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426561] [TonyBai]-NatPostrouteEnterIN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426567] [TonyBai]-NatPost1IN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
</code></pre>
<p>container3返回ack，从宿主机角度来看，相当于从docker0网卡收到ack。我们看到进来的原始数据：dst =  172.17.0.1，这是上面MASQUERADE的作用。在进入input链前，做自动un-SNAT，目的地址由172.17.0.1转换为10.10.126.101。在真正送到user层之前（output链等同的左边同纬度位置），做自动un-DNAT(但在下面日志中看不出来)，src由172.17.0.4变为10.10.126.101。数据包的变换总体次序依次为：即DNAT -> SNAT -> (应答包)un-SNAT -> un-DNAT。</p>
<pre><code>Jan 18 13:49:43 pc-baim kernel: [279663.426646] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=52736 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426665] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=52736 WINDOW=28960 RES=0x00 ACK SYN URGP=0
</code></pre>
<p>宿主机回复ack，握手完成。由于之前走过nat output和post链，因此这里不会再匹配，而是自动DNAT和SNAT：</p>
<pre><code>Jan 18 13:49:43 pc-baim kernel: [279663.426690] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=40855 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426707] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=40855 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426719] [TonyBai]-manglepost1IN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=40855 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
</code></pre>
<h4>3、从container1 telnet 10.10.126.101 12580</h4>
<p>container1向服务发起tcp连接，宿主机从docker0网卡收到sync包。</p>
<pre><code>Jan 18 13:51:10 pc-baim kernel: [279750.806496] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806519] [TonyBai]-NatPrerouting:IN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806531] [TonyBai]-PortmapNatPreroutinIN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
</code></pre>
<p>做DNAT后，再次路由到docker0，于是走forward链，但是没有匹配上nat postrouting，也就没有做SNAT：</p>
<pre><code>Jan 18 13:51:10 pc-baim kernel: [279750.806581] [TonyBai]-FwdId0Od0:IN=docker0 OUT=docker0 PHYSIN=veth44a97d7 PHYSOUT=veth0d66af2 MAC=02:42:ac:11:00:04:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806608] [TonyBai]-NatPostrouteEnterIN= OUT=docker0 PHYSIN=veth44a97d7 PHYSOUT=veth0d66af2 SRC=172.17.0.2 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
</code></pre>
<p>container3回复ack sync。宿主机从docker0收到ack sync包，目的地址172.17.0.2，再次路由到docker0。</p>
<pre><code>Jan 18 13:51:10 pc-baim kernel: [279750.806719] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:ac:11:00:02:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=54408 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806746] [TonyBai]-FwdOd0:IN=docker0 OUT=docker0 PHYSIN=veth0d66af2 PHYSOUT=veth44a97d7 MAC=02:42:ac:11:00:02:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=54408 WINDOW=28960 RES=0x00 ACK SYN URGP=0
</code></pre>
<p>由于之前docker0上做过DNAT，因此从docker0回到172.17.0.2时，src地址会自动un-DNAT，从172.17.0.4改为10.10.126.101，不过在上面日志中看不出这一点。</p>
<p>172.17.0.2回复ack，握手完成，DNAT自动进行：</p>
<pre><code>Jan 18 13:51:10 pc-baim kernel: [279750.806823] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=31889 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806852] [TonyBai]-FwdOd0:IN=docker0 OUT=docker0 PHYSIN=veth44a97d7 PHYSOUT=veth0d66af2 MAC=02:42:ac:11:00:04:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=31889 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
</code></pre>
<h3>三、网络性能考量</h3>
<p>docker-proxy常被docker使用者诟病，一是因为每个映射端口都要启动一个docker-proxy进程，映射端口多了，大量进程被创建、被调度势必消耗大量系统资源；二来，在高负载场合，docker-proxy的转发性能也力不从心。理论上，docker-proxy代理转发流量的方式在性能方面要比单纯iptables DNAT要弱上一些。不过我在单机上通过<a href="https://github.com/chrissnell/sparkyfish">sparkyfish</a>测试的结果倒是二者相差不大，估计是因为我仅仅启动了一个docker-proxy，系统负荷并不大的缘故。</p>
<p style='text-align:left'>&copy; 2016, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/01/18/understanding-binding-docker-container-ports-to-host/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
