<?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>Soul Of Free Loop &#187; Docker</title>
	<atom:link href="https://zohead.com/archives/tag/docker/feed" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>使用kcptun曲线访问Arukas容器</title>
		<link>https://zohead.com/archives/arukas-kcptun/</link>
		<comments>https://zohead.com/archives/arukas-kcptun/#comments</comments>
		<pubDate>Wed, 06 Mar 2019 17:08:16 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[网络]]></category>
		<category><![CDATA[Arukas]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Endpoint]]></category>
		<category><![CDATA[kcptun]]></category>
		<category><![CDATA[OpenWRT]]></category>
		<category><![CDATA[Shadowsocks]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[樱花]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1573</guid>
		<description><![CDATA[新版 Arukas 容器现状 2017 年我写了两篇介绍日本 Arukas 樱花容器服务的文章，那个时候主要用来跑 Shadowsocks 梯子服务。后来不知道是否因为国内用户蜂拥而至，Arukas 容器基本所有的 IP 地址都被封了，接着 Arukas 也停止提供服务，直到去年才恢复。最近住处的移动宽带不知何故连接一直使用的美国 VPS 速度很慢，因此我想着可以试试新版本 Arukas 容器的效果。 恢复后的新版 Arukas 服务直接把原来的账户都给删除了，用户都需要重新注册，实际使用还必须绑定有效的信用卡，容器配置可以参考其官网（需要爬墙）： https://arukas.io/en/# [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="new-arukas">新版 Arukas 容器现状</h2>
<p>2017 年我写了两篇介绍日本 <a href="https://zohead.com/archives/tag/arukas/">Arukas</a> 樱花容器服务的文章，那个时候主要用来跑 Shadowsocks 梯子服务。后来不知道是否因为国内用户蜂拥而至，Arukas 容器基本所有的 IP 地址都被封了，接着 Arukas 也停止提供服务，直到去年才恢复。最近住处的移动宽带不知何故连接一直使用的美国 VPS 速度很慢，因此我想着可以试试新版本 Arukas 容器的效果。</p>
<p>恢复后的新版 Arukas 服务直接把原来的账户都给删除了，用户都需要重新注册，实际使用还必须绑定有效的信用卡，容器配置可以参考其官网（需要爬墙）：</p>
<p><a href="https://arukas.io/en/#pricing" target="_blank">https://arukas.io/en/#pricing</a></p>
<p>每个账户可以最多创建一个免费容器，免费容器配置如下：</p>
<ul>
<li>每个月 730 小时运行时间；</li>
<li>0.1 vCPU；</li>
<li>128 MB 内存；</li>
<li>100 GB 流量；</li>
<li>仍然不支持外部存储；</li>
<li>支持绑定域名（针对 Endpoint）；</li>
<li>仍然只支持 Docker Hub 库，而且只支持修改 <code>CMD</code> 启动命令。</li>
</ul>
<p>可以看到新版 Arukas 免费容器的配置是有点捉襟见肘，还好我也只是拿来访问 Google 服务之类的，对流量需求不大，如果你经常要看 YouTube 视频之类的，那就不用考虑 Arukas 容器咯。</p>
<p>按照原来写的 <a href="https://zohead.com/archives/arukas-ge-tt/">Arukas容器使用ge.tt替代Endpoint域名</a> 文章重新部署 Shadowsocks 容器，我发现还算欣慰的是 Arukas 提供的 Endpoint 域名还没有被封锁，容器内 Web 服务器提供的内容可以通过 Endpoint 域名访问。</p>
<p>如果你只需要跑一个简单的静态网站，对资源和流量要求也不高的话，那 Arukas 容器也是挺不错的，毕竟还支持绑定自定义的域名。不过 Arukas 的 Endpoint 域名转发服务仍然只支持 HTTPS 访问，而且 Endpoint 地址应该只对容器开放的第一个内部端口有效，只支持 HTTP 协议，不支持 TCP 或者 UDP。如果用 Arukas 容器来跑动态网站，那有可能效果不太理想。</p>
<p>不出意料的是我准备搭梯子的时候，发现墙对 Arukas 封锁的比较彻底，测试了多个 IP 地址都无法正常连接，为此考虑修改原来的容器试试通过 kcptun 进行转发访问。</p>
<h2 id="arukas-shadowsocks-mod">Shadowsocks 容器修改</h2>
<p>我原来制作的 Arukas 专用 Shadowsocks 容器是基于 <a href="https://github.com/phusion/baseimage-docker" target="_blank">Baseimage-docker</a> 镜像修改的，增加 kcptun 支持也很简单。访问 kcptun 的官方 GitHub release 页面：</p>
<p><a href="https://github.com/xtaci/kcptun/releases" target="_blank">https://github.com/xtaci/kcptun/releases</a></p>
<p>下载 Linux amd64 版本的 kcptun 程序，我们也只需要将服务端程序加到原来的 Shadowsocks 容器镜像中，并简单修改下 <code>/etc/my_init.d/01_start_ss_ssh.sh</code> 启动脚本（支持 kcptun 的修改已高亮显示）：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/bash

# Enable SSH
rm -f /etc/service/sshd/down
/etc/my_init.d/00_regen_ssh_host_keys.sh
touch /etc/service/sshd/down

# Setup SSH key
if [ &quot;x$SSH_AUTHORIZED_KEYS&quot; = &quot;x&quot; ]; then
	/usr/sbin/enable_insecure_key
else
	mkdir ~/.ssh
	echo &quot;$SSH_AUTHORIZED_KEYS&quot; | sed 's/\\n/\n/g' &gt; ~/.ssh/authorized_keys
	chmod 400 ~/.ssh/authorized_keys
fi

# Start web server
env | grep -E '^MARATHON_HOST=|MARATHON_PORT_' &gt; /home/wwwroot/default/marathon.conf
if [ &quot;x$MARATHON_HOST&quot; != &quot;x&quot; ]; then
	getent hosts $MARATHON_HOST | awk '{print &quot;MARATHON_HOST_IP=&quot;$1; exit;}' &gt;&gt; /home/wwwroot/default/marathon.conf
fi

start-stop-daemon -S -b -n tmp-httpd -d /home/wwwroot/default -x /usr/bin/python3 -- -m http.server 80

# Start ShadowSocks
env | grep '^SHADOWSOCKS_CFGS_' | awk -F '=' '{print $1;}' | while read T_NAME; do
	SS_NAME=&quot;${T_NAME:17}&quot;
	echo &quot;${!T_NAME}&quot; &gt; /etc/shadowsocks-libev/${SS_NAME}.json
	start-stop-daemon -n ss-$SS_NAME -x /usr/bin/ss-server -b -S -- -c /etc/shadowsocks-libev/${SS_NAME}.json -u --fast-open
done

# Start kcptun
env | grep '^KCPTUN_CFGS_' | awk -F '=' '{print $1;}' | while read T_NAME; do
	KCP_NAME=&quot;${T_NAME:12}&quot;
	echo &quot;${!T_NAME}&quot; &gt; /etc/kcptun/${KCP_NAME}.json
	start-stop-daemon -n kcp-$KCP_NAME -x /usr/bin/kcptun_server -b -S -- -c /etc/kcptun/${KCP_NAME}.json
done

if [ &quot;x$GETT_TOKEN&quot; != &quot;x&quot; ]; then
	if [ &quot;x$HOME&quot; = &quot;x&quot; ]; then
		export HOME=&quot;/root&quot;
	fi
	echo -n &quot;$GETT_TOKEN&quot; &gt; ~/.gett-token
	if [ &quot;x$GETT_SHARE_NAME&quot; != &quot;x&quot; ]; then
		python3 /usr/local/gett/uploader.py -l http://ge.tt/$GETT_SHARE_NAME | awk '{if (substr($5,1,13)==&quot;http://ge.tt/&quot;) {print $5;}}' | xargs python3 /usr/local/gett/uploader.py --delete
		python3 /usr/local/gett/uploader.py -s http://ge.tt/$GETT_SHARE_NAME /home/wwwroot/default/marathon.conf
	fi
fi

exec /usr/sbin/sshd -D -e -u 0
</pre>
<p>新版支持 kcptun 访问 Shadowsocks 的 Arukas 专用容器安装包下载地址，仍然支持 SSH 登录以及通过 Endpoint 地址或者 <a href="https://zohead.com/archives/ge-tt-cli-api/">ge.tt</a> 分享获取容器当前地址和端口：</p>
<p><a href="https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz">https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz</a></p>
<p>原有的 <code>CMD</code> 启动命令以及以 <code>SHADOWSOCKS_CFGS_</code> 开头的 Shadowsocks 配置变量和 <code>SSH_AUTHORIZED_KEYS</code> SSH 证书变量配置都没有变化。</p>
<p>此次增加了以 <code>KCPTUN_CFGS_</code> 开头的 kcptun 配置变量，当然别忘了容器端口配置中也得指定一个新的 UDP 端口给 kcptun 服务使用。</p>
<p>例如我现有的 Shadowsocks 服务配置变量：</p>
<pre class="brush: jscript; title: ; notranslate">
SHADOWSOCKS_CFGS_xxx={ &quot;server&quot;:&quot;0.0.0.0&quot;, &quot;server_port&quot;:17374, &quot;local_port&quot;:1080, &quot;password&quot;:&quot;arukas-ss-svr&quot;, &quot;timeout&quot;:30, &quot;method&quot;:&quot;aes-256-cfb&quot; }
</pre>
<p>现在需要在 Arukas 容器中运行 kcptun 服务器进行 UDP 转发，可以添加一个 kcptun 配置变量（仍然是 JSON 格式，需要写成一行）：</p>
<pre class="brush: jscript; title: ; notranslate">
KCPTUN_CFGS_xxx = { &quot;listen&quot;: &quot;:29900&quot;, &quot;target&quot;: &quot;127.0.0.1:17374&quot;, &quot;key&quot;: &quot;arukas-kcp-svr&quot;, &quot;crypt&quot;: &quot;xor&quot;, &quot;mode&quot;: &quot;fast2&quot;, &quot;mtu&quot;: 1350, &quot;sndwnd&quot;: 128, &quot;rcvwnd&quot;: 128, &quot;datashard&quot;: 0, &quot;parityshard&quot;: 0, &quot;dscp&quot;: 46, &quot;nocomp&quot;: true, &quot;acknodelay&quot;: false, &quot;nodelay&quot;: 0, &quot;interval&quot;: 40, &quot;resend&quot;: 2, &quot;nc&quot;: 1, &quot;sockbuf&quot;: 4194304, &quot;keepalive&quot;: 10, &quot;log&quot;: &quot;/var/log/kcptun.log&quot; }
</pre>
<p>这里我指定 kcptun 服务器绑定默认的 29900 UDP 端口（Arukas 容器端口配置中即需要开放 29900 UDP 端口），转发地址即指向本地的 Shadowsocks 服务器端口。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>上面贴出来的我使用的 kcptun 配置并不是最佳配置，只是由于我的 OpenWRT 路由器运算能力比较孱弱，避免出现路由器上的 kcptun 客户端导致 CPU 占用过高的问题。</p>
<p>kcptun 的配置稍微有点复杂，需要根据用户实际网络环境修改，这里我就不详细介绍了，请参考 <a href="https://github.com/xtaci/kcptun" target="_blank">GitHub</a> 项目主页的说明。</p>
</blockquote>
<h2 id="android-kcptun-test">Android kcptun 测试</h2>
<p>我们可以首先用 Android 手机测试通过 kcptun 连接 Arukas 容器的 Shadowsocks 服务，在手机上安装 <a href="https://play.google.com/store/apps/details?id=com.github.shadowsocks" target="_blank">Shadowsocks</a> App 和专用的 <a href="https://play.google.com/store/apps/details?id=com.github.shadowsocks.plugin.kcptun" target="_blank">kcptun</a> 插件。</p>
<p>安装完成之后 Shadowsocks 配置文件中就可以选择 kcptun 插件，需要注意你如果使用的是像我的 Nubia Z17 这种魔改版手机，可能需要在 <strong>手机管家</strong> 之类的系统设置中开启 kcptun 的自启动权限，防止出现 Shadowsocks App 无法启动 kcptun 的问题。</p>
<p>Shadowsocks App 连接的服务器地址也需要更换为 Arukas 容器实际地址和 kcptun 服务的外部访问端口，kcptun 插件的配置则保持与服务端一致即可，我的配置如下：</p>
<pre class="brush: plain; title: ; notranslate">
dscp=46;nocomp;mtu=1350;datashard=0;parityshard=0;mode=fast2;key=arukas-kcp-svr;crypt=xor
</pre>
<p>Android kcptun 插件的配置使用 <code>key=value</code> 的格式，中间以分号隔开，如果只有 <code>key</code> 没有 <code>value</code> 则表示启用该参数（true），具体配置参数可以点击 kcptun 插件的 <code>?</code> 按钮查看详细说明。</p>
<h2 id="openwrt-kcptun-script">OpenWRT 自动更新配置脚本</h2>
<p>使用 kcptun 之后，OpenWRT 路由器上的 Shadowsocks 配置也不太一样了，例如我的 <code>/etc/shadowsocks-arukas.json</code> 配置文件就可以保持不变了（固定连接本地 kcptun 地址和端口）：</p>
<pre class="brush: jscript; title: ; notranslate">
{
	&quot;server&quot; : &quot;127.0.0.1&quot;,
	&quot;server_port&quot; : &quot;17374&quot;,
	&quot;password&quot;: &quot;arukas-ss-svr&quot;,
	&quot;local_port&quot;: &quot;1080&quot;,
	&quot;timeout&quot; : &quot;30&quot;,
	&quot;method&quot;: &quot;aes-256-cfb&quot;
}
</pre>
<p>相应的增加 kcptun 客户端的配置文件 <code>/etc/kcptun-arukas.json</code>，<code>key</code> 密码与容器中 kcptun 服务器密码一致，<code>remoteaddr</code> 就是容器 kcptun 服务 UDP 监听的外部访问地址和端口：</p>
<pre class="brush: jscript; title: ; notranslate">
{
&quot;localaddr&quot;: &quot;:17374&quot;,
&quot;remoteaddr&quot;: &quot;&quot;,
&quot;key&quot;: &quot;arukas-kcp-svr&quot;,
&quot;crypt&quot;: &quot;xor&quot;,
&quot;mode&quot;: &quot;fast2&quot;,
&quot;mtu&quot;: 1350,
&quot;sndwnd&quot;: 128,
&quot;rcvwnd&quot;: 128,
&quot;datashard&quot;: 0,
&quot;parityshard&quot;: 0,
&quot;dscp&quot;: 46,
&quot;nocomp&quot;: true,
&quot;acknodelay&quot;: false,
&quot;nodelay&quot;: 0,
&quot;interval&quot;: 40,
&quot;resend&quot;: 2,
&quot;nc&quot;: 1,
&quot;sockbuf&quot;: 4194304,
&quot;keepalive&quot;: 10
}
</pre>
<p>为了支持 kcptun，我也稍微改了下 OpenWRT 路由器的 <code>arukas-ss.sh</code> 自动更新配置脚本，为了使用方便我把 kcptun 客户端的启动和停止操作也放到 <code>/etc/init.d/shadowsocks</code> 服务脚本中了，其它路由器请自行修改此脚本：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
ARUKAS_ENDPOINT=&quot;&quot;
GETT_SHRNAME=&quot;&quot;
RESTART_SHADOWSOCKS=&quot;no&quot;

[ $# -ge 2 ] || exit 1

[ -f /tmp/.marathon.conf ] &amp;&amp; mv /tmp/.marathon.conf /tmp/.marathon.conf.bak

IND=0
while [ $IND -lt 5 ]; do
	let IND++
	[ -f /tmp/.marathon.conf ] &amp;&amp; rm -f /tmp/.marathon.conf
	if [ &quot;x$GETT_SHRNAME&quot; != &quot;x&quot; ]; then
		T_FILEID=`curl -s -k -f http://api.ge.tt/1/shares/$GETT_SHRNAME | sed -e 's/^.*&quot;fileid&quot;[^:]*:[^&quot;]*&quot;//' -e 's/&quot;.*$//'`
		if [ &quot;x$T_FILEID&quot; != &quot;x&quot; ]; then
			curl -s -k -f -o /tmp/.marathon.conf -L -e &quot;http://ge.tt/$GETT_SHRNAME&quot; &quot;http://api.ge.tt/1/files/$GETT_SHRNAME/$T_FILEID/blob?download&quot;
			[ $? -eq 0 ] &amp;&amp; break
		fi
	fi
	curl -s -k -f -o /tmp/.marathon.conf https://$ARUKAS_ENDPOINT.arukascloud.io/marathon.conf
	[ $? -eq 0 ] &amp;&amp; break
	sleep 1
done

[ -s /tmp/.marathon.conf ] || exit 1
echo &quot;Fetch server config after $IND tries.&quot;

if [ -f /tmp/.marathon.conf.bak ]; then
	cmp -s /tmp/.marathon.conf /tmp/.marathon.conf.bak
	if [ $? -eq 0 ]; then
		rm -f /tmp/.marathon.conf
		exit 0
	fi
fi

. /tmp/.marathon.conf 2&gt;/dev/null
if [ &quot;x$MARATHON_HOST&quot; = &quot;x&quot; ]; then
	rm -f /tmp/.marathon.conf
	exit 1
fi

if [ &quot;x$MARATHON_HOST_IP&quot; != &quot;x&quot; ]; then
	MARATHON_HOST=&quot;$MARATHON_HOST_IP&quot;
fi

while [ $# -gt 2 ]; do
	CMD=&quot;echo \${MARATHON_PORT_$2}&quot;
	TMP_PORT=`eval $CMD`

	if [ -f $1 -a &quot;x$TMP_PORT&quot; != &quot;x&quot; ]; then
		if [ &quot;x$3&quot; = &quot;xshadowsocks&quot; ]; then
			sed -i -e 's/&quot;server&quot;[^:]*:[^,]*,/&quot;server&quot; : &quot;'$MARATHON_HOST'&quot;,/' -e 's/&quot;server_port&quot;[^:]*:[^,]*,/&quot;server_port&quot; : &quot;'$TMP_PORT'&quot;,/' $1
			grep -q '^CONFIG.*='$1 /etc/init.d/shadowsocks
			[ $? -eq 0 ] &amp;&amp; RESTART_SHADOWSOCKS=&quot;yes&quot;
		elif [ &quot;x$3&quot; = &quot;xkcptun&quot; ]; then
			sed -i 's/&quot;remoteaddr&quot;[^:]*:[^,]*,/&quot;remoteaddr&quot; : &quot;'$MARATHON_HOST':'$TMP_PORT'&quot;,/' $1
			grep -q '^KCPTUN_CONFIG.*='$1 /etc/init.d/shadowsocks
			[ $? -eq 0 ] &amp;&amp; RESTART_SHADOWSOCKS=&quot;yes&quot;
		fi
	fi
	shift
	shift
	shift
done

if [ &quot;x$RESTART_SHADOWSOCKS&quot; = &quot;xyes&quot; ]; then
	/etc/init.d/shadowsocks restart
fi
</pre>
<p>使用之前请修改脚本最上面的 <code>ARUKAS_ENDPOINT</code> 和 <code>GETT_SHRNAME</code> 变量，分别为 Arukas 容器的 Endpoint 名（Arukas Endpoint 地址可访问的情况下建议优先使用）和 ge.tt 分享名。</p>
<p>为了区分 Shadowsocks 和 kcptun 配置文件，我对脚本调用方式也做了修改，增加了指定配置类型的参数（<code>shadowsocks</code> 或 <code>kcptun</code>），例如我的 crontab 配置：</p>
<pre class="brush: plain; title: ; notranslate">
30 * * * * /etc/arukas-ss.sh /etc/kcptun-arukas.json 29900 kcptun
</pre>
<p>这样路由器就可以自动更新 kcptun 客户端配置文件中的 <code>remoteaddr</code> 项了。</p>
<p>我的渣渣移动宽带环境下电脑通过路由器 kcptun 爬墙看 YouTube 视频效果如下：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442943/shadowsocks-kcptun-speed.png" alt="Arukas 容器 Shadowsocks kcptun 速度"></p>
<p>受限于路由器硬件的性能，Arukas 网络的 kcptun 速度并没有完全发挥出来，不过还是比原先的美国 VPS 要好多了，最后祝大家在开会的特殊阶段也能把梯子搭的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/arukas-kcptun/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在Docker容器中使用FUSE文件系统</title>
		<link>https://zohead.com/archives/docker-fuse/</link>
		<comments>https://zohead.com/archives/docker-fuse/#comments</comments>
		<pubDate>Tue, 19 Dec 2017 16:32:18 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[User-mode Linux]]></category>
		<category><![CDATA[FUSE]]></category>
		<category><![CDATA[httpfs2]]></category>
		<category><![CDATA[NFS]]></category>
		<category><![CDATA[Slirp]]></category>
		<category><![CDATA[UML]]></category>
		<category><![CDATA[VDE]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[文件系统]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1518</guid>
		<description><![CDATA[容器使用 FUSE 的问题 我们一般使用的 Docker 容器都是非特权容器，也就是说容器内的 root 用户并不拥有真正的 root 权限，这就导致很多属于系统管理员的操作都被禁用了。 最近有个在 IBM Bluemix 容器内部挂载 FUSE 文件系统的需求，例如我使用 davfs2 挂载 WebDAV 服务器不出意外地会报错： mount.davfs 命令报错表示无法打开 fuse 设备，而 fuse 设备实际上是存在的（说明 fuse 模块也已经加载了）： 从容器内部可以查看到 cgroup 实际允许访问的设备，并没有包含 fuse 设备： 手工允许 fuse 设备自然也是不可行的：  [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="docker-fuse-problem">容器使用 FUSE 的问题</h2>
<p>我们一般使用的 Docker 容器都是非特权容器，也就是说容器内的 root 用户并不拥有真正的 root 权限，这就导致很多属于系统管理员的操作都被禁用了。</p>
<p>最近有个在 IBM Bluemix 容器内部挂载 FUSE 文件系统的需求，例如我使用 davfs2 挂载 WebDAV 服务器不出意外地会报错：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# mount.davfs https://dav.jianguoyun.com/dav/ /mnt/
Please enter the username to authenticate with server
https://dav.jianguoyun.com/dav/ or hit enter for none.
  Username: xxx@xx.com
Please enter the password to authenticate user fcoe@qq.com with server
https://dav.jianguoyun.com/dav/ or hit enter for none.
  Password: 
mount.davfs: can't open fuse device
mount.davfs: trying coda kernel file system
mount.davfs: no free coda device to mount
</pre>
<p>mount.davfs 命令报错表示无法打开 fuse 设备，而 fuse 设备实际上是存在的（说明 fuse 模块也已经加载了）：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# cat /sys/devices/virtual/misc/fuse/dev
10:229
</pre>
<p>从容器内部可以查看到 cgroup 实际允许访问的设备，并没有包含 fuse 设备：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# cat /sys/fs/cgroup/devices/devices.list
c 1:5 rwm
c 1:3 rwm
c 1:9 rwm
c 1:8 rwm
c 5:0 rwm
c 5:1 rwm
c *:* m
b *:* m
c 1:7 rwm
c 136:* rwm
c 5:2 rwm
c 10:200 rwm
</pre>
<p>手工允许 fuse 设备自然也是不可行的：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# echo &quot;c 10:229 rwm&quot; &gt; /sys/fs/cgroup/devices/devices.allow
-bash: /sys/fs/cgroup/devices/devices.allow: Permission denied
</pre>
<p>另外由于 Bluemix 提供的是非特权容器，即使允许访问 /dev/fuse 设备也会因为没有 mount 权限而挂载失败。</p>
<h2 id="uml-modify">UML 系统修改</h2>
<p>虽然 Docker 容器内部不能直接挂载使用 FUSE 文件系统，但我想到如果用 User-mode Linux（以下简称 UML） 来实现在应用层再运行一个 Linux kernel，就可以在 UML guest 系统中挂载 FUSE 文件系统了，而且 UML 系统中也可以通过 hostfs 直接访问容器本身的文件系统。</p>
<p>有关 UML 的介绍和编译使用可以参考我之前写的 <a href="https://zohead.com/archives/openvz-uml-bbr/">小内存OpenVZ VPS使用UML开启BBR</a> 文章。</p>
<p>首先我们需要修改 UML kernel 配置：</p>
<ul>
<li>增加对 FUSE 的支持，这个是必须的了，否则无法在 UML guest 系统中使用 FUSE 文件系统；</li>
<li>增加了对 xfs、btrfs、ext4、squashfs、iso9660 等常见文件系统的支持（方便在 UML 系统中挂载各种文件系统镜像）；</li>
<li>另外为了支持将 UML guest 的文件系统导出，启用了 sunrpc 和 NFS 服务器支持；</li>
<li>UML 网络配置中增加了对 Slirp 和 VDE 网卡的支持。</li>
</ul>
<p>UML kernel 目前支持常见的几种网络模式：</p>
<ul>
<li>TUN/TAP <br />
最简单和常用的模式，不过 host kernel 需要支持 tun 或者 tap 设备，这个在 Docker 容器中一般都不可用的；</li>
<li>SLIP <br />
SLIP 串行线路 IP 支持，现在一般很少用到了，同样 host kernel 需要支持 slip 设备；</li>
<li>Slirp <br />
通过用户层的 slirp 程序实现 SLIP 连接，好处是不依赖任何内核层的设备，而且 slirp 可以支持非 root 用户使用，不过 Slirp 只支持模拟 IP 协议的数据包，详细可以参考 <a href="http://slirp.sourceforge.net/" target="_blank">Slirp</a> 开源项目的官方网站。</li>
<li>VDE <br />
VDE（Virtual Distributed Ethernet）可以在不同的计算机间实现软件定义的以太网络，同样支持在用户层以非 root 用户身份来运行，目前 Linux 上的 QEMU 和 KVM 虚拟机都支持 VDE 虚拟网络。</li>
</ul>
<p>鉴于我们需要在 Docker 容器中运行 UML 系统，目前只能使用 Slirp 和 VDE 模式的网卡，另外单独的 slirp 程序使用起来相对 VDE 也更简单（支持 VDE 的 UML kernel 可以直接调用 slirp 程序，不像 VDE 还需要预先使用 <code>vde_switch</code> 等命令配置软件交换机），因此这里的 UML 系统就使用 Slirp 网卡了。</p>
<p>当然原来基于 busybox 的 UML 系统用户层也做了些修改：</p>
<ul>
<li>增加 libfuse 支持挂载 FUSE 文件系统；</li>
<li>增加 rpcbind、nfs-common、nfs-kernel-server 等软件包，支持在 UML 系统中运行 NFS 服务器，导出 UML guest 的文件系统；</li>
<li>增加 dropbear，支持 SSH 和 SFTP 服务器和客户端了；</li>
<li>增加 httpfs2 FUSE 文件系统支持，来自 GitHub 上的 <a href="https://github.com/Tomas-M/httpfs2-enhanced" target="_blank">httpfs2-enhanced</a> 项目，方便挂载访问 HTTP 和 HTTPS 远程文件；</li>
<li>增加 archivemount FUSE 文件系统支持，方便直接挂载 zip、rar、tar.gz 等各种格式的压缩包以支持直接访问压缩包中的文件；</li>
<li>增加 <a href="http://curlftpfs.sourceforge.net" target="_blank">CurlFtpFs</a> FUSE 文件系统支持，支持挂载 FTP 远程文件；</li>
<li>增加 <a href="http://savannah.nongnu.org/projects/davfs2" target="_blank">davfs2</a> FUSE 文件系统支持，支持挂载远程 WebDAV 目录；</li>
<li>增加 <a href="https://github.com/libfuse/sshfs" target="_blank">sshfs</a> FUSE 文件系统支持，支持通过 SFTP 方式挂载远程主机目录。</li>
</ul>
<blockquote>
<p><strong>提示</strong></p>
<ul>
<li>httpfs2-enhanced 最好使用支持 SSL 和多线程的版本，可以加快响应速度并能挂载 HTTPS 的远程文件；</li>
<li>我在测试中发现 httpfs2-enhanced 工具在国内的网络环境下挂载某些 HTTP 远程文件存在一些问题，因此做了简单的修改，并 fork 到我自己的 GitHub 仓库里了。有需要的朋友可以参考我修改过的 <a href="https://github.com/zohead/httpfs2-enhanced" target="_blank">httpfs2-enhanced</a>，检出其中的 <a href="https://github.com/zohead/httpfs2-enhanced/tree/http-fix" target="_blank">http-fix</a> 分支即可，我也已经针对原项目提交了 Pull request。</li>
</ul>
</blockquote>
<p>为了方便使用，我给原来的 <code>uml-linux.sh</code> 执行脚本增加了新的 <code>uml.conf</code> 配置文件：</p>
<pre class="brush: bash; title: ; notranslate">
UML_ID=&quot;umlvm&quot;
UML_MEM=&quot;64M&quot;

UML_NETMODE=&quot;slirp&quot;
#HOST_ADDR=&quot;192.168.0.3&quot;
#UML_ADDR=&quot;192.168.0.4&quot;
#UML_DNS=&quot;&quot;

TAP_DEV=&quot;tap0&quot;
ROUTER_DEV=&quot;eth0&quot;
VDE_SWITCH=&quot;&quot;

REV_TCP_PORTS=&quot;&quot;
REV_UDP_PORTS=&quot;&quot;
#FWD_TCP_PORTS=&quot;111 892 2049 32803 662&quot;
#FWD_UDP_PORTS=&quot;111 892 2049 947 32769 662 660&quot;
</pre>
<p>简单说明如下：</p>
<ul>
<li><code>UML_MEM</code> 指定为 UML 系统分配多少内存，默认 64 MB；</li>
<li><code>UML_NETMODE</code> 比较重要，指定 UML 系统的网卡模式，目前支持 <code>tuntap</code>、<code>slirp</code>、<code>vde</code> 这三个选项；</li>
<li>如果是 <code>tuntap</code> 网卡模式，<code>TAP_DEV</code> 指定 host 主机上的 TUN 网卡设备名称，可以使用 <code>HOST_ADDR</code> 配置 host 主机 TUN 网卡的 IP 地址，<code>UML_ADDR</code> 配置 UML guest 主机的 IP 地址（最好和 <code>HOST_ADDR</code> 在同一网段），如果需要端口转发，那么还需要修改 <code>ROUTER_DEV</code> 指定 host 主机用于转发的物理网卡设备名称；</li>
<li>如果是 <code>slirp</code> 网卡模式，那么会直接使用 Slirp 默认固定的专用地址：<code>10.0.2.2</code> 为 host 主机地址，<code>10.0.2.15</code> 为 UML guest 主机的 IP 地址，并自动将 UML guest 系统内的 DNS 服务器地址设置为 <code>10.0.2.3</code> 通过 host 主机进行域名解析；</li>
<li>如果是 <code>vde</code> 网卡模式，那么必须修改 <code>VDE_SWITCH</code> 指定 VDE 软件交换机的路径，VDE 软件交换机需要通过 <code>vde_switch</code> 等命令预先配置，详细使用说明可以参考 <a href="http://wiki.virtualsquare.org/wiki/index.php/VDE_Basic_Networking" target="_blank">Virtualsquare VDE Wiki</a> 页面；</li>
<li><code>FWD_TCP_PORTS</code> 和 <code>FWD_UDP_PORTS</code> 指定进行转发的 TCP 和 UDP 端口列表（多个转发端口以空格隔开），转发端口支持 <code>10080-80</code> 这种形式（表示将 host 主机的 10080 端口转到 UML guest 的 80 端口），上面 <code>uml.conf</code> 中的注释列出来的是 UML 系统中对外的 NFS 服务器所需要的端口（根据实际情况也可以只允许 111、892、2049 这几个端口）。</li>
</ul>
<p>为了能够根据 <code>uml.conf</code> 文件配置 Slirp 的端口转发功能，我还为 slirp 程序增加了一个 <code>slirp.sh</code> wrapper 脚本：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
DIR=&quot;$( cd &quot;$( dirname &quot;$0&quot; )&quot; &amp;&amp; pwd )&quot;

[ -f $DIR/uml.conf ] &amp;&amp; . $DIR/uml.conf

CMD=`which slirp-fullbolt 2&gt;/dev/null || which slirp`

for i in $FWD_TCP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		continue
	fi
	CMD=&quot;$CMD \&quot;redir ${i%-*} ${i##*-}\&quot;&quot;
done
for i in $FWD_UDP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		continue
	fi
	CMD=&quot;$CMD \&quot;redir udp ${i%-*} ${i##*-}\&quot;&quot;
done

eval &quot;exec $CMD&quot;
</pre>
<p>由于 UML kernel 调用 slirp 程序时不支持附加参数，这里才通过 <code>slirp.sh</code> 脚本来实现，功能也非常简单，通过 slirp 程序的 redir 选项配置端口转发。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>为了让 UML guest 系统的 Slirp 网络真正达到接近 host 主机的网络性能，host 主机上编译 slirp 程序时必须打开 <code>FULL_BOLT</code> 开关，并为 slirp 源代码打上 <a href="https://www.mail-archive.com/user-mode-linux-user@lists.sourceforge.net/msg07014.html" target="_blank">real full bolt</a> 的 patch，否则 UML guest 系统内通过 Slirp 访问外网的速度会很慢。</p>
<p>值得庆幸的是 Debian、Ubuntu 系统自带的 slirp 软件包一般都打上了这个 patch，而且提供了 <code>slirp-fullbolt</code> 和 <code>slirp</code> 这两个程序分别对应 <code>FULL_BOLT</code> 开启和关闭的 Slirp，我的 <code>slirp.sh</code> 脚本也对此做了判断，优先使用速度更快的 <code>slirp-fullbolt</code> 程序。</p>
</blockquote>
<p>至于新的启动 UML 系统的脚本 <code>uml-linux.sh</code> 稍微有点长，这里就不贴出来了，和原来相比的改动就是增加对 Slirp 和 VDE 网络的支持。</p>
<p>另外新的 <code>uml-linux.sh</code> 脚本改为默认前台方式启动 UML 系统；如果需要以后台方式启动 UML 系统，则可以用 <code>uml-linux.sh -D</code> 的方式来运行。</p>
<h2 id="uml-mount-fuse">使用 UML 挂载 FUSE 文件系统</h2>
<p>修改之后支持 FUSE 和 NFS 服务器的 UML 系统可以从这里下载（百度网盘备用）：</p>
<p><a href="https://zohead.com/downloads/uml-fuse-nfsd-x64.tar.xz">https://zohead.com/downloads/uml-fuse-nfsd-x64.tar.xz</a> <br />
<a href="https://pan.baidu.com/s/1bp6l7B5" target="_blank">https://pan.baidu.com/s/1bp6l7B5</a></p>
<p>解压缩下载下来的 <code>uml-fuse-nfsd-x64.tar.xz</code> 文件，运行其中的 <code>uml-linux</code> 脚本就可以启动 UML 系统了，此 UML 系统的 root 用户默认密码为 <code>uml</code>。</p>
<p>下面我以挂载 HTTP 远程 iso 文件中的安装包为例，介绍如何在 UML 系统中使用 FUSE。</p>
<p>首先使用 httpfs2 挂载阿里云上的 CentOS 6.9 iso 文件（这里为 httpfs2 指定 <code>-c /dev/null</code> 参数是为了去掉 httpfs2 的网络访问输出日志），挂载成功之后可以查看挂载点中的文件：</p>
<pre class="brush: bash; title: ; notranslate">
~ # httpfs2 -c /dev/null http://mirrors.aliyun.com/centos/6/isos/x86_64/CentOS-6.9-x86_64-minimal.iso /tmp
file name:      CentOS-6.9-x86_64-minimal.iso
host name:      mirrors.aliyun.com
port number:    80
protocol:       http
request path:   /centos/6/isos/x86_64/CentOS-6.9-x86_64-minimal.iso
auth data:      (null)
httpfs2: main: connecting to mirrors.aliyun.com port 80.
No SSL session data.
httpfs2: main: closing socket.
httpfs2: main: connecting to mirrors.aliyun.com port 80.
httpfs2: main: keeping socket open.
file size:      427819008
httpfs2: main: closing socket.
~ # ls -l /tmp
total 0
-r--r--r--    1 root     root     427819008 Mar 29  2017 CentOS-6.9-x86_64-minimal.iso
</pre>
<p>我们可以发现 httpfs2 的挂载点中实际上就只有一个远程文件名，我们可以直接挂载这个 iso 文件：</p>
<pre class="brush: bash; title: ; notranslate">
~ # mount -t iso9660 -o ro,loop /tmp/CentOS-6.9-x86_64-minimal.iso /media
~ # ls /media
CentOS_BuildTag                GPL                            RPM-GPG-KEY-CentOS-6           RPM-GPG-KEY-CentOS-Testing-6   isolinux
EFI                            Packages                       RPM-GPG-KEY-CentOS-Debug-6     TRANS.TBL                      repodata
EULA                           RELEASE-NOTES-en-US.html       RPM-GPG-KEY-CentOS-Security-6  images
</pre>
<p>到这里就可以直接查看远程 iso 文件中的软件包了，你可以很方便地将远程 iso 中的软件包拷贝到 host 主机的文件系统中。</p>
<p>如果你想直接拷贝软件包中的特定文件，也可以通过 archivemount 来实现哦：</p>
<pre class="brush: bash; title: ; notranslate">
~ # archivemount /media/Packages/sed-4.2.1-10.el6.x86_64.rpm /mnt
fuse: missing mountpoint parameter
~ # ls /mnt
bin  usr
</pre>
<p>这样直接复制软件包中的文件就实在太方便了（请忽略 archivemount 时的 fuse 警告，实际不影响使用）。</p>
<p>当然如果你想挂载 FTP 远程文件就可以通过 curlftpfs 命令来实现，也可以使用 mount.davfs 命令挂载 WebDAV 服务器上的文件（例如直接访问坚果云中的文件）。</p>
<h2 id="export-uml-fuse">导出 UML FUSE 文件系统</h2>
<p>有些情况下我们需要将 UML 系统中的 FUSE 挂载点导出给 host 主机或者网络中的其它主机使用，这时可以通过 hostfs、SFTP、NFS 等方式实现，分别简单说明一下。</p>
<h3 id="hostfs-export-fuse">hostfs 导出 FUSE</h3>
<p>这是最简单的方式，由于 UML 直接使用 hostfs 访问 host 主机的文件系统，因此 UML 系统内可以直接将 FUSE 中的文件拷贝到 hostfs 文件系统，只是这种方式 host 主机并不能直接访问 UML guest 主机的 FUSE 文件系统。</p>
<h3 id="sftp-export-fuse">SFTP 导出 FUSE</h3>
<p>这种方式也很简单，由于 UML 系统启动时自动运行了 dropbear 服务，我们可以先修改 <code>uml.conf</code> 配置文件设置 SSH 端口转发，将 host 主机的 2222 端口通过 Slirp 转发到 UML guest 系统内的 22 端口（如果 host 主机本身并没有运行 SSH 服务器，那甚至可以配置为 <code>FWD_TCP_PORTS="22"</code> 直接转发 22 端口）：</p>
<pre class="brush: bash; title: ; notranslate">
FWD_TCP_PORTS=&quot;2222-22&quot;
</pre>
<p>此时 host 主机就可以使用 scp 命令直接从 UML 的 FUSE 文件系统中拷贝文件了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# scp -P 2222 root@192.168.1.52:/mnt/bin/sed /home
</pre>
<blockquote>
<p><strong>注意</strong></p>
<p>上面命令中的 <code>192.168.1.52</code> 应根据实际情况替换为 host 主机上实际访问网络的网卡 IP 地址，不能使用 localhost 或者 127.0.0.1，因为 slirp 默认会自动选择访问网络的网卡，并不会进行本地转发。</p>
</blockquote>
<h3 id="nfs-export-fuse">NFS 导出 FUSE</h3>
<p>首先需要修改 <code>uml.conf</code> 配置文件中的 <code>FWD_TCP_PORTS</code> 和 <code>FWD_UDP_PORTS</code> 值，将默认的 NFS 服务需要的 TCP 和 UDP 端口注释去掉，表示将这些端口转发到 UML 系统内。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>如果你需要在 host 主机上直接 NFS 挂载 UML 系统里的 FUSE 文件系统，由于 sunrpc 的 111 端口无法修改而且需要转发到 UML 系统内，这种情况下 host 主机的 portmap 或者 rpcbind 服务需要保持关闭状态，防止 111 端口被占用。</p>
</blockquote>
<p>使用 <code>uml-linux.sh</code> 脚本启动 UML 系统，启动完成之后通过集成的 FUSE 相关工具挂载需要访问的 FUSE 文件系统。</p>
<p>假设需要导出 UML 系统中的 <code>/mnt</code> FUSE 文件系统，UML 中默认的 NFS 导出目录配置文件 <code>/etc/exports</code> 如下：</p>
<pre class="brush: plain; title: ; notranslate">
/mnt 0.0.0.0/0.0.0.0(async,insecure,no_subtree_check,rw,no_root_squash,fsid=0)
</pre>
<p>上面的配置文件表示将 /mnt 目录通过 NFS 导出，默认允许所有主机访问，你可以根据需要修改导出目录路径和允许访问的主机地址。</p>
<p>接着在 UML 系统中通过集成的服务脚本启动 rpcbind 和 nfs 服务：</p>
<pre class="brush: bash; title: ; notranslate">
~ # /etc/init.d/rpcbind start
Starting up rpcbind...
~ # /etc/init.d/nfs start
Starting nfs service:
fs.nfs.nlm_tcpport = 32803
fs.nfs.nlm_udpport = 32769
Exporting directories for NFS...
Starting NFS daemon: 
rpc.nfsd: address family inet6 not supported by protocol TCP
NFSD: the nfsdcld client tracking upcall will be removed in 3.10. Please transition to using nfsdcltrack.
NFSD: starting 90-second grace period (net 000000006035a880)
Starting NFS mountd:
</pre>
<p>如果一切正常的话，此时就可以在外部通过 NFS 挂载 UML 系统导出的 FUSE 文件系统进行访问了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# mount -t nfs -o nolock,proto=tcp,mountproto=tcp 192.168.1.52:/mnt /media
</pre>
<blockquote>
<p><strong>提示</strong></p>
<p><code>nolock</code> 参数是为了防止如果挂载的主机上没有运行 portmap 或者 rpcbind 服务导致挂载失败的问题（如果直接在 host 主机上挂载，host 的对应服务需要关闭）； <br />
  <code>proto=tcp</code> 和 <code>mountproto=tcp</code> 参数指定 NFS 数据和挂载请求都使用 TCP 协议，防止使用的随机 UDP 端口无法被 slirp 转发的问题。</p>
</blockquote>
<p>这里需要说明的是如果直接在 Docker 容器（UML 的 host 主机）上挂载 NFS，虽然绝大多数 Docker 容器平台都默认支持 NFS 文件系统，但在非特权容器内部由于没有权限 mount 还是会失败的。</p>
<h2 id="summary">总结</h2>
<p>本文介绍的在容器中使用 UML 挂载 FUSE 文件系统并通过 NFS 导出 UML 文件系统的方法是一个比较小众的需求，不过也算达到我的目的了。</p>
<p>对于非特权 Docker 容器来说，虽然还不能直接挂载 UML guest 的文件系统，但初步看起来还是可以通过 <a href="https://github.com/lkl/linux" target="_blank">LKL</a>（Linux Kernel Library）在应用层访问 UML 网络并实现 NFS 挂载的，只是 LKL 库的 NFS 挂载目录并不能直接给 Docker 容器中的普通应用程序使用。</p>
<p>最后祝大家在即将到来的 2018 年能继续玩地开心 ^_^。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/docker-fuse/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Arukas樱花Docker容器及Endpoint体验</title>
		<link>https://zohead.com/archives/arukas-container/</link>
		<comments>https://zohead.com/archives/arukas-container/#comments</comments>
		<pubDate>Fri, 31 Mar 2017 16:17:46 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Arukas]]></category>
		<category><![CDATA[Endpoint]]></category>
		<category><![CDATA[OpenWRT]]></category>
		<category><![CDATA[Shadowsocks]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[樱花]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1408</guid>
		<description><![CDATA[Arukas 容器介绍 之前我在了解目前主流的容器服务商时除了 IBM Bluemix 还知道了日本 Arukas 容器，不过一直都没有测试的机会，最近 Arukas 难得给我发了注册通过的邮件，最新的政策显示免费测试期会延长到 2017 年 6 月 30 日（未来收费策略待定）。 下面的基本操作都在 Arukas 控制面板： https://app.arukas.io/ Arukas 容器限制 Arukas 容器还处于免费测试阶段，有一些限制需要先说明： 不支持特权容器，无法使用 TUN/TAP 设备（目前的容器服务商基本都不支持）； 没有固定公网 IP 地址，容器可能每隔几天甚至几个小时就 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="arukas-intro">Arukas 容器介绍</h2>
<p>之前我在了解目前主流的容器服务商时除了 IBM Bluemix 还知道了日本 Arukas 容器，不过一直都没有测试的机会，最近 Arukas 难得给我发了注册通过的邮件，最新的政策显示免费测试期会延长到 2017 年 6 月 30 日（未来收费策略待定）。</p>
<p>下面的基本操作都在 Arukas 控制面板：</p>
<p><a href="https://app.arukas.io/" target="_blank">https://app.arukas.io/</a></p>
<h3 id="arukas-limits">Arukas 容器限制</h3>
<p>Arukas 容器还处于免费测试阶段，有一些限制需要先说明：</p>
<ul>
<li>不支持特权容器，无法使用 TUN/TAP 设备（目前的容器服务商基本都不支持）；</li>
<li>没有固定公网 IP 地址，容器可能每隔几天甚至几个小时就会重启；</li>
<li>配置的容器内部端口会被映射到随机的外部端口；</li>
<li>自定义 Endpoint 域名只能以 HTTPS 方式访问，对应的容器内部端口只支持 HTTP 协议（下面会详细介绍）；</li>
<li>不支持使用自定义的 <code>Dockerfile</code> 文件构建容器，只能使用 Docker hub 上的镜像；</li>
<li>暂时不支持外部存储，因此容器里无法保存任何数据，每次重启容器数据会全部丢失；</li>
<li>创建容器时只能配置 <code>Dockerfile</code> 中的 <code>ENV</code>（环境变量）和 <code>CMD</code>（启动执行命令）指令，不支持 <code>RUN</code> 及 <code>ENTRYPOINT</code> 等其它指令。</li>
</ul>
<h3 id="about-endpoint">关于 Endpoint</h3>
<p>通过参考 Arukas 日文版帮助文档，我们可以得知虽然 Arukas 容器没有固定的公网 IP 地址，容器开放的内部端口每次启动也都被映射到随机的外部端口，但容器的 Endpoint 地址是始终固定不变的，类似于这样：</p>
<pre class="brush: plain; title: ; notranslate">

https://xxx.arukascloud.io/

</pre>
<p>经过测试我发现容器的 Endpoint 地址必须以 HTTPS 方式访问（HTTP 访问会直接返回 Connection refused），Endpoint 地址只对容器开放的第一个内部端口有效，而且该内部端口只支持 HTTP 协议，不支持 TCP 或者 UDP。</p>
<p>用户访问 Endpoint 子域名地址时并不是直接访问容器的当前公网地址，而是访问 Arukas 提供的 HAProxy 负载均衡服务器，HAProxy 负责转发并均衡容器下多个实例的 HTTP 请求，其网络拓扑图大概如下所示：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370568/arukas-endpoint.png" alt="Arukas Endpoint 网络拓扑图" title="Arukas Endpoint 网络拓扑图"></p>
<blockquote>
<p><strong>提示</strong></p>
<p>Arukas 官网上对 Endpoint 的说明遗漏了比较重要的一点：如果容器配置里开启了多个对外开放的内部端口，那么 Endpoint 请求转发只会在所有这些端口的服务都已经运行的情况下才有效。</p>
</blockquote>
<p>这样 Arukas 就能够在有限的服务器（内部端口映射到随机外部端口）和公网 IP 地址（固定 Endpoint 地址）情况下提供尽量多的容器访问支持。</p>
<h2 id="build-container">构建容器</h2>
<p>从上面的介绍来看虽然 Arukas 容器有很多体验不太好的限制，但其所在的樱花机房线路还是相当有吸引力的，立马准备建立容器看看运行效果。</p>
<p>为了测试 Arukas 容器的运行效果，我没有使用一些做好的通用 SSH 系统（例如：centos-ssh）或者 Shadowsocks 系统镜像，本文还是用的之前捣鼓过的 <a href="https://github.com/phusion/baseimage-docker" target="_blank">baseimage-docker</a> Docker 镜像。</p>
<h3 id="baseimage-docker-problem">baseimage-docker 的问题</h3>
<p>baseimage-docker 镜像虽然用起来很方便，支持轻量级的 <a href="http://smarden.org/runit/" target="_blank"><code>runit</code></a> 服务管理方案，但该镜像默认并没有开启 SSH 登录，如果需要开启 SSH 服务器或者执行自定义命令都需要修改 <code>Dockerfile</code> 文件中的 <code>RUN</code> 指令，具体可以参考该项目 GitHub 主页。</p>
<p>然而 Arukas 容器并不支持使用 <code>RUN</code> 命令配置执行自定义命令，也不能保存任何数据，因此 SSH 登录和执行自定义命令的问题需要通过其它方法解决。</p>
<p>幸好 baseimage-docker 还支持通过 <code>CMD</code> 指令开启运行 <a href="https://github.com/phusion/baseimage-docker#oneshot" target="_blank">one-shot command</a> 单次命令支持，对应的 <code>CMD</code> 指令类似这样：</p>
<pre class="brush: bash; title: ; notranslate">
/sbin/my_init -- COMMAND ARGUMENTS ...
</pre>
<p>容器系统的运行流程就相当于：</p>
<ul>
<li>运行所有启动脚本，例如 <code>/etc/my_init.d/*</code> 及 <code>/etc/rc.local</code>；</li>
<li>启动所有 <code>runit</code> 服务；</li>
<li>运行 <code>my_init</code> 参数中指定的自定义命令；</li>
<li>自定义命令退出之后即停止所有 <code>runit</code> 服务。</li>
</ul>
<p>因此我们可以在单次命令参数上做文章，实现开启 SSH 服务器、安装需要的软件并启动附加的服务。</p>
<h3 id="container-config">容器基本配置</h3>
<p>下面是我在 Arukas 控制面板建立的一个 Shadowsocks 服务器容器的配置：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370567/arukas-docker-config.png" alt="Arukas 容器配置" title="Arukas Endpoint 容器配置"></p>
<ul>
<li><strong>Image</strong> 我使用的是最新的 baseimage-docker 镜像：<code>phusion/baseimage:0.9.19</code>；</li>
<li><strong>Instances</strong> 一般就用默认的一个实例；</li>
<li><strong>Memory</strong> 可以根据需要选择 256 MB 或者 512 MB；</li>
<li><strong>Endpoint</strong> 指定 Endpoint 子域名地址；</li>
<li><strong>Port</strong> 设置里可以将容器中最多 20 个 TCP 或者 UDP 端口对外开放，我填了 80、22 外加一个 Shadowsocks 服务器端口，需要注意的就是上面的固定 Endpoint 地址只支持第一个端口；</li>
<li><strong>ENV</strong> 环境变量根据需要设置，我在这里指定了 SSH key 和 Shadowsocks 服务器配置；</li>
<li>
<p><strong>CMD</strong> 设置比较关键，我建立的 Shadowsocks 测试容器是这样的：</p>
<pre class="brush: bash; title: ; notranslate">
/sbin/my_init -- sh -c 'curl -o /arukas-baseimage-xenial-ss.tar.gz https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz &amp;&amp; tar -C / -xzf /arukas-baseimage-xenial-ss.tar.gz &amp;&amp; /etc/my_init.d/01_start_ss_ssh.sh'
</pre>
</li>
</ul>
<p>我制作了一个适用于最新 baseimage 系统的 Shadowsocks 服务器安装包：</p>
<p><a href="https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz">https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz</a></p>
<p>容器系统启动时会自动将安装包下载到根目录下解压缩，然后执行其中的 <code>/etc/my_init.d/01_start_ss_ssh.sh</code> 启动脚本：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/bash

# Enable SSH
rm -f /etc/service/sshd/down
/etc/my_init.d/00_regen_ssh_host_keys.sh
touch /etc/service/sshd/down

# Setup SSH key
if [ &quot;x$SSH_AUTHORIZED_KEYS&quot; = &quot;x&quot; ]; then
	/usr/sbin/enable_insecure_key
else
	mkdir ~/.ssh
	echo &quot;$SSH_AUTHORIZED_KEYS&quot; | sed 's/\\n/\n/g' &gt; ~/.ssh/authorized_keys
	chmod 400 ~/.ssh/authorized_keys
fi

# Start web server
env | grep -E '^MARATHON_HOST=|MARATHON_PORT_' &gt; /home/wwwroot/default/marathon.conf
if [ &quot;x$MARATHON_HOST&quot; != &quot;x&quot; ]; then
	getent hosts $MARATHON_HOST | awk '{print &quot;MARATHON_HOST_IP=&quot;$1; exit;}' &gt;&gt; /home/wwwroot/default/marathon.conf
fi

start-stop-daemon -S -b -n tmp-httpd -d /home/wwwroot/default -x /usr/bin/python3 -- -m http.server 80

# Start ShadowSocks
env | grep '^SHADOWSOCKS_CFGS_' | awk -F '=' '{print $1;}' | while read T_NAME; do
	SS_NAME=&quot;${T_NAME:17}&quot;
	echo &quot;${!T_NAME}&quot; &gt; /etc/shadowsocks-libev/${SS_NAME}.json
	start-stop-daemon -n ss-$SS_NAME -x /usr/bin/ss-server -b -S -- -c /etc/shadowsocks-libev/${SS_NAME}.json -u --fast-open
done

exec /usr/sbin/sshd -D -e -u 0
</pre>
<p>该启动脚本的执行流程如下：</p>
<ul>
<li>首先启用 SSH 服务器，并产生 SSH host key；</li>
<li>如果容器配置的 <strong>ENV</strong> 环境变量里存在 <code>SSH_AUTHORIZED_KEYS</code> 变量，那么就使用这个变量的内容作为 SSH 公钥（容器配置截图里我就使用此方法指定自定义公钥），如果不存在则使用 baseimage 提供的默认公钥；</li>
<li>接着获取 Arukas 容器的当前临时域名、公网 IP 地址以及每个内部端口映射等内容输出到 <code>/home/wwwroot/default</code> Web 服务器目录下的 <code>marathon.conf</code> 文件；</li>
<li>在默认的 80 端口启动 Web 服务器以支持 Endpoint 地址访问；</li>
<li>
<p>遍历容器配置的所有 <strong>ENV</strong> 环境变量，如果存在以 <code>SHADOWSOCKS_CFGS_</code> 开头的变量，就在 <code>/etc/shadowsocks-libev</code> 目录下生成 Shadowsocks 服务器配置文件，配置文件内容就是该变量的值。</p>
<p>例如我使用的一个 Shadowsocks 配置变量（将 Shadowsocks 服务器的 JSON 配置写成一行）：</p>
<pre class="brush: jscript; title: ; notranslate">
SHADOWSOCKS_CFGS_xxx={ &quot;server&quot;:&quot;0.0.0.0&quot;, &quot;server_port&quot;:17374, &quot;local_port&quot;:1080, &quot;password&quot;:&quot;arukas-ss-svr&quot;, &quot;timeout&quot;:30, &quot;method&quot;:&quot;aes-256-cfb&quot; }
</pre>
<p>这样就会自动产生 <code>/etc/shadowsocks-libev/xxx.json</code> 配置文件；</p>
</li>
<li>依次启动 <strong>ENV</strong> 环境变量里指定的所有 Shadowsocks 服务器（可以指定多个以 <code>SHADOWSOCKS_CFGS_</code> 开头的变量）；</li>
<li>最后以前台方式启动 SSH 服务器，这样就能实现 SSH 远程登录，而且 baseimage 容器也不会退出。</li>
</ul>
<h3 id="container-status">容器运行状态</h3>
<p>Arukas 容器启动运行成功之后，你可以在控制面板里查看运行状态：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370567/arukas-docker-status.png" alt="Arukas 容器运行状态" title="Arukas Endpoint 容器运行状态"></p>
<p>这里可以看到容器的 Endpoint 地址、用于直接访问容器的临时域名和映射的外部端口等信息。</p>
<p>容器启动之后会将当前的临时域名、内部端口映射等信息输出到系统环境变量中，这样上面的 <code>/etc/my_init.d/01_start_ss_ssh.sh</code> 启动脚本才能直接获取到这些信息，有兴趣的朋友可以在容器中确认：</p>
<pre class="brush: bash; title: ; notranslate">
root@02a78258745b:~# env | grep -E '^MARATHON|MESOS'
MESOS_TASK_ID=db89b5a5-7df0-49f0-b5d4-47d6406abbd8.6cccf318-080c-11e7-aca2-0242a39fa0e7
MARATHON_PORT0=31198
MARATHON_PORT1=31689
MARATHON_PORT=31198
MARATHON_APP_RESOURCE_DISK=0.0
MARATHON_APP_RESOURCE_MEM=32.0
MARATHON_APP_LABEL_HAPROXY_0_VHOST=gloomy-golick-3736.arukascloud.io
MARATHON_APP_RESOURCE_GPUS=0
MESOS_CONTAINER_NAME=mesos-eb1747b8-0843-4061-bc0e-9cd1b5347ee2-S9.d07c30b6-51b1-44ca-a453-d4302ec08d2e
MARATHON_APP_LABEL_HAPROXY_GROUP=external
MARATHON_HOST=seaof-153-125-239-206.jp-tokyo-27.arukascloud.io
MARATHON_APP_LABELS=HAPROXY_GROUP HAPROXY_0_VHOST
MARATHON_APP_ID=/db89b5a5-7df0-49f0-b5d4-47d6406abbd8
MARATHON_APP_DOCKER_IMAGE=phusion/baseimage:0.9.19
MARATHON_APP_VERSION=2017-03-13T16:45:10.089Z
MARATHON_PORTS=31198,31689
MARATHON_APP_RESOURCE_CPUS=0.01
MARATHON_PORT_80=31198
MARATHON_PORT_22=31689
MESOS_SANDBOX=/mnt/mesos/sandbox
</pre>
<p>我们可以看看 Arukas 容器的服务器信息，其使用的是 CoreOS 系统（内核版本为 4.9.9）：</p>
<pre class="brush: bash; title: ; notranslate">
root@837bc28c1c36:~# uname -a
Linux 837bc28c1c36 4.9.9-coreos-r1 #1 SMP Tue Mar 28 22:27:48 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
</pre>
<p>内存是所有容器一起共享的（如果容器使用的内存超过配置中的限制太多会被重启）：</p>
<pre class="brush: bash; title: ; notranslate">
root@837bc28c1c36:~# free -m
              total        used        free      shared  buff/cache   available
Mem:          24115        4300        1520         715       18295       17563
Swap:          4095          24        4071
</pre>
<p>CPU 配置还算给力，使用的是 8 个 Xeon E5-2650：</p>
<pre class="brush: bash; title: ; notranslate">
root@837bc28c1c36:~# grep -E '^processor|physical id|model name' /proc/cpuinfo
processor	: 0
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 0
processor	: 1
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 1
processor	: 2
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 2
processor	: 3
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 3
processor	: 4
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 4
processor	: 5
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 5
processor	: 6
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 6
processor	: 7
model name	: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
physical id	: 7
</pre>
<p>只是容器内核对文件系统的支持比较坑爹：</p>
<pre class="brush: bash; title: ; notranslate">
root@837bc28c1c36:~# cat /proc/filesystems
nodev	sysfs
nodev	rootfs
nodev	tmpfs
nodev	bdev
nodev	proc
nodev	cpuset
nodev	cgroup
nodev	cgroup2
nodev	devtmpfs
nodev	debugfs
nodev	tracefs
nodev	securityfs
nodev	sockfs
nodev	bpf
nodev	pipefs
nodev	ramfs
nodev	hugetlbfs
nodev	devpts
nodev	pstore
nodev	mqueue
nodev	selinuxfs
nodev	autofs
	squashfs
	iso9660
	ext3
	ext2
	ext4
nodev	overlay
</pre>
<p>不支持 NFS 挂载远程目录，也不支持 FUSE 用户层文件系统来实现挂载 Amazon S3 存储之类的，这对于目前还不支持外部存储的 Arukas 容器来说实在不算友好。</p>
<h2 id="use-shadowsocks-container">使用 Shadowsocks 容器</h2>
<h3 id="shadowsocks-speed">Shadowsocks 效果</h3>
<p>客户端使用上面指定的 Shadowsocks 服务器配置就可以爬墙上网了，当然使用前需要将 Shadowsocks 服务器地址更换为 Arukas 容器的临时域名，并将服务器端口改为当前映射出来的外部端口（不能直接使用内部端口）。</p>
<p>这个是我在宿舍江苏移动宽带网络下观看 Youtube 1080p 视频的效果，8 Mbps 的速度看起来还是比较给力的：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370582/arukas-ss-speed.png" alt="Arukas Shadowsocks 速度" title="Arukas Shadowsocks 速度"></p>
<p>换到南京电信网络进行路由追踪，可以看到 ping 值还不到 50 ms：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370582/arukas-ip-route.png" alt="Arukas 容器路由追踪" title="Arukas 容器路由追踪" width="640" height="526"></p>
<h3 id="openwrt-shadowsocks-script">OpenWRT 自动更新 Shadowsocks 配置</h3>
<p>由于 Arukas 容器的外网地址和端口在每次启动时都不是固定的，容器重启后 Shadowsocks 客户端都需要获取当前临时域名和外部端口，这样用起来还是不太方便。特别对于我这种需要 OpenWRT 路由器直接翻墙的用户来说，每次修改 OpenWRT 设置就太麻烦了。</p>
<p>好消息就是 Arukas 容器的 Endpoint 地址还是固定的，我写的启动脚本也自动将容器当前的临时域名、公网 IP 地址、所有外部端口都输出到文件了，可以直接通过 Endpoint 地址访问。</p>
<p>因此我又专门写了一个适用于 OpenWRT 系统的更新 Arukas 容器配置的脚本：<code>arukas-ss.sh</code>，可添加到路由器的 Shadowsocks 服务脚本中，也可以设置成定时任务：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
[ $# -ge 2 ] || exit 1

[ -f /tmp/.marathon.conf ] &amp;&amp; mv /tmp/.marathon.conf /tmp/.marathon.conf.bak

IND=0
while [ $IND -lt 20 ]; do
	let IND++
	[ -f /tmp/.marathon.conf ] &amp;&amp; rm -f /tmp/.marathon.conf
	curl -s -k -f -o /tmp/.marathon.conf https://zohead-server.arukascloud.io/marathon.conf
	[ $? -eq 0 ] &amp;&amp; break
	sleep 1
done

[ -s /tmp/.marathon.conf ] || exit 1
echo &quot;Fetch server config after $IND tries.&quot;

if [ -f /tmp/.marathon.conf.bak ]; then
	cmp -s /tmp/.marathon.conf /tmp/.marathon.conf.bak
	if [ $? -eq 0 ]; then
		rm -f /tmp/.marathon.conf
		exit 0
	fi
fi

. /tmp/.marathon.conf 2&gt;/dev/null
if [ &quot;x$MARATHON_HOST&quot; = &quot;x&quot; ]; then
	rm -f /tmp/.marathon.conf
	exit 1
fi

while [ $# -gt 1 ]; do
	CMD=&quot;echo \${MARATHON_PORT_$2}&quot;
	SS_PORT=`eval $CMD`

	if [ -f $1 -a &quot;x$SS_PORT&quot; != &quot;x&quot; ]; then
		if [ &quot;x$MARATHON_HOST_IP&quot; != &quot;x&quot; ]; then
			MARATHON_HOST=&quot;$MARATHON_HOST_IP&quot;
		fi
		sed -i -e 's/&quot;server&quot;[^:]*:[^,]*,/&quot;server&quot; : &quot;'$MARATHON_HOST'&quot;,/' -e 's/&quot;server_port&quot;[^:]*:[^,]*,/&quot;server_port&quot; : &quot;'$SS_PORT'&quot;,/' $1
		SS_PID=`pgrep -f &quot;ss-redir -c $1&quot;`
		if [ $? -eq 0 ]; then
			kill $SS_PID &gt;/dev/null 2&gt;&amp;1
			kill -9 $SS_PID &gt;/dev/null 2&gt;&amp;1
			start-stop-daemon -x /usr/bin/ss-redir -b -S -- -c $1 -b 0.0.0.0
		fi
	fi
	shift
	shift
done
</pre>
<p><code>arukas-ss.sh</code> 脚本的使用方法为：</p>
<pre class="brush: bash; title: ; notranslate">
arukas-ss.sh shadowsocks-1.json 17374 shadowsocks-2.json 2233 ... ...
</pre>
<p>第一个参数为 Shadowsocks 客户端 JSON 配置文件的完整路径，第二个参数为该文件对应的 Arukas Shadowsocks 服务器配置的内部监听端口号，<code>arukas-ss.sh</code> 脚本支持多个本地 Shadowsocks 配置文件和服务器内部监听端口号，依次类推即可。</p>
<p>使用前需要替换 <code>arukas-ss.sh</code> 脚本中的 Endpoint 地址，考虑到 Arukas 容器目前的 Endpoint 访问还不太稳定的情况，脚本里也加了多次访问尝试，我的 Endpoint 地址是这样：</p>
<pre class="brush: plain; title: ; notranslate">

https://zohead-server.arukascloud.io/marathon.conf

</pre>
<p>另外可以修改 OpenWRT 的定时任务配置实现自动更新，例如在 <code>/etc/crontabs/root</code> 文件中增加一行：</p>
<pre class="brush: plain; title: ; notranslate">
30 * * * * /etc/arukas-ss.sh /etc/shadowsocks.json 17374
</pre>
<p>这就表示在每个小时的第 30 分钟执行 <code>arukas-ss.sh</code> 脚本更新 Arukas Shadowsocks 配置，一般这样就能对付 Arukas 容器每隔几个小时自动重启的问题了。</p>
<h2 id="postscript">后记</h2>
<p>经过我这段时间的试用，Arukas 容器做为爬墙服务器还是比较顺畅的，如果想捣鼓一个简单的 API 服务器也还凑合，不过由于 Arukas 目前还不支持外部存储，想拿来跑个博客程序之类的就要涉及到如何保存和恢复网站数据的问题了。</p>
<p>当然容器只能用 Endpoint 地址访问也是一个大问题，经过测试我也发现如果容器内开放的内部端口稍微一多（可能超过 3 个）时，Arukas 的 HAProxy 转发服务就不太稳定了，经常出现 502 或者 503 错误。如果容器内开放了 UDP 端口，Endpoint 地址甚至可能会处于一直都不能访问的状况。</p>
<p>如果 Arukas 容器在内测期间能解决这些问题，后续正式使用价格也不算离谱的话，感觉应该还是有些竞争力的，最后祝大家 4 月玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/arukas-container/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>在Linux容器中使用ZeroTier P2P VPN</title>
		<link>https://zohead.com/archives/zerotier-container/</link>
		<comments>https://zohead.com/archives/zerotier-container/#comments</comments>
		<pubDate>Wed, 31 Aug 2016 16:57:17 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[网络技术]]></category>
		<category><![CDATA[Codeanywhere]]></category>
		<category><![CDATA[P2P]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[TAP]]></category>
		<category><![CDATA[TUN]]></category>
		<category><![CDATA[VPN]]></category>
		<category><![CDATA[ZeroTier]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1266</guid>
		<description><![CDATA[关于 ZeroTier P2P VPN P2P VPN 和我们平常使用的 PPTP、OpenVPN 等 VPN 的不同之处在于其只负责将两个或多个主机通过点对点的方式进行连接，不需要中心服务器支持，多个主机之间通过 STUN 打洞或者 TURN 中继等方式进行连接，比较适合不同网络间的多个主机进行直接连接，不像 PPTP 这种在国内用的最多的就是拿来爬墙的。 目前使用的比较多的包括 n2n、tinc 和本文要介绍的 ZeroTier 等几种免费开源的 P2P VPN 软件。 n2n 由于已经没人继续开发支持了所以不予考虑，我在对比了 tinc 和 ZeroTier 之后发现 ZeroTier  [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="关于-zerotier-p2p-vpn">关于 ZeroTier P2P VPN</h2>
<p>P2P VPN 和我们平常使用的 PPTP、OpenVPN 等 VPN 的不同之处在于其只负责将两个或多个主机通过点对点的方式进行连接，不需要中心服务器支持，多个主机之间通过 <a href="https://en.wikipedia.org/wiki/STUN">STUN</a> 打洞或者 <a href="https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT">TURN</a> 中继等方式进行连接，比较适合不同网络间的多个主机进行直接连接，不像 PPTP 这种在国内用的最多的就是拿来爬墙的。</p>
<p>目前使用的比较多的包括 n2n、tinc 和本文要介绍的 ZeroTier 等几种免费开源的 P2P VPN 软件。</p>
<p>n2n 由于已经没人继续开发支持了所以不予考虑，我在对比了 tinc 和 ZeroTier 之后发现 ZeroTier 虽然看起来性能比 tinc 略差一点，但其客户端配置步骤非常简单，不需要像 tinc 那样要在多个主机分别手工生成配置文件，而且 ZeroTier 自己也自带了几个不同国家区域的中继服务器方便普通用户使用，另外一点就是本文要介绍的 ZeroTier 支持在 Linux 容器中使用，因此平时我主要使用 ZeroTier，tinc 用作辅助。</p>
<p>ZeroTier 的虚拟网络可以包含多个处于相同或不同物理网络里的主机，每个虚拟网络都有一个唯一的网络 ID，多个运行 ZeroTier 服务的主机加入同一个 ZeroTier 网络 ID 就可以构成一个虚拟网络。虚拟网络都是由 ZeroTier Controller 控制器来管理的，默认可以使用 ZeroTier 提供的控制器来管理，用户也可以自行搭建控制器来实现完全自己管理 ZeroTier 网络。</p>
<p>另外 ZeroTier 有一个特殊的 <code>8056c2e21c000001</code> 网络 ID 表示全球公共 ZeroTier 网络，如果只是想测试连接网络的话也可以加入这个公共网络；但实际使用中为了安全性考虑建议最好在 ZeroTier 网站上注册一个账户并新建你自己的 P2P VPN 网络用于管理。</p>
<p>有关 ZeroTier P2P VPN 的详细介绍可以参考其官网：</p>
<p><a href="https://www.zerotier.com/">https://www.zerotier.com/</a></p>
<h2 id="容器中使用-zerotier-的问题">容器中使用 ZeroTier 的问题</h2>
<p>简单了解 ZeroTier P2P VPN 主机端的工作机制之后我就发现一个问题，其依赖的 TUN/TAP 驱动（n2n 和 tinc VPN 也同样依赖）在很多 Linux 容器系统中是无法使用的。</p>
<p>我使用的 OpenVZ VPS 容器还好还可以在控制面板里开启 TUN/TAP 支持，而对于现在非常流行的 Docker 容器（例如之前我用到的 IBM Bluemix 容器）就比较麻烦了，Docker 容器在运行时必须包含 <code>SYS_NET_ADMIN</code> 特权才能正常执行 TUN/TAP 虚拟网卡设备的创建操作（Docker 容器中同样也不能使用 OpenVPN、OpenConnect 等依赖 TUN/TAP 驱动的程序），但目前的容器服务厂商基本上不会以特权方式运行容器。</p>
<p>ZeroTier 为此提出的解决方案就是 Network Containers 模式，通过实现一个轻量级的应用层 TCP/IP API 来 hook 现有应用程序的 Socket API，这样可以使现有的程序也能使用 ZeroTier 技术连接其它主机。</p>
<blockquote>
<p>最近 ZeroTier 已经将 Network Containers 模式更名为 ZeroTier SDK 并使用了单独的版本库，开发者也可以选择将 ZeroTier SDK 直接编译进自己的应用程序以方便直接使用。有关容器模式的详细介绍可以参考官方介绍文章：<a href="https://www.zerotier.com/product-netcon.shtml">ZeroTier Network Containers</a>。</p>
</blockquote>
<h2 id="关于-codeanywhere-容器">关于 Codeanywhere 容器</h2>
<p>这里我使用 <a href="https://codeanywhere.com/">Codeanywhere</a> 虚拟机来测试 ZeroTier Network Containers，Codeanywhere 作为一个云端 IDE 平台还是比较好用的，其提供的免费版本的容器支持：</p>
<ul>
<li>2GB 磁盘空间；</li>
<li>256MB RAM (+ 512 MB 交换空间)；</li>
<li>sudo 支持；</li>
<li>支持 SSH 登录（免费版本的容器每次启动时 SSH 端口不固定）；</li>
<li>支持访问所有 HTTP 和 Websocket 端口（默认 1024 - 9999）。</li>
</ul>
<p>问题是 Codeanywhere 免费版本的虚拟机不支持固定 IP 地址，也不支持域名绑定或者固定子域名，每次虚拟机启动之后分配到的都是一个超长的子域名，例如你看到的子域名可能就是下面的形式：</p>
<pre><code>preview.7reszf59tfv18aor9x7odheaio340a4ih8tcn8fvndzvkj4i.box.codeanywhere.com
</code></pre>
<p>这样实际开发测试起来还是有点不太方便。</p>
<p>当然 Codeanywhere 也是不提供 TUN/TAP 支持的，这样如果 ZeroTier 的容器模式可以在 Codeanywhere 虚拟机内正常使用，我就可以直接在自己的电脑上直接使用虚拟的 P2P VPN 节点 IP 地址来访问 Codeanywhere 虚拟机了，可以省掉那一长串恶心的子域名了。</p>
<h2 id="使用-zerotier-network-containers">使用 ZeroTier Network Containers</h2>
<h3 id="编译安装-zerotier-network-containers">编译安装 ZeroTier Network Containers</h3>
<p>首先创建一个新的 Codeanywhere 虚拟机，建议使用 <code>C/C++ Development Stack</code> 类型方便后续编译安装 ZeroTier 程序，另外操作系统最好选择 Centos 6.5 64 位，如果使用 Ubuntu 系统可能存在连接不上 P2P VPN 网络的问题。</p>
<p>在 Codeanywhere 虚拟机中使用 git 命令检出 ZeroTier 官方版本库，检出之后运行 <code>make netcon</code> 命令就可以编译出 ZeroTier Network Containers 模式需要的文件了：</p>
<pre class="brush: bash; title: ; notranslate">
[cabox@box-codeanywhere ZeroTierOne]$ ls                                           
AUTHORS.md   examples                 netcon        ui
LICENSE.txt  ext                      node          version.h
Makefile     include                  objects.mk    windows
README.md    java                     one.cpp       world
artwork      libzerotierintercept.so  osdep         zerotier-cli
attic        make-freebsd.mk          selftest.cpp  zerotier-idtool
cluster-geo  make-linux.mk            service       zerotier-netcon-service
controller   make-mac.mk              tcp-proxy
</pre>
<p>其中最重要的就是 <code>zerotier-netcon-service</code> 服务和 <code>libzerotierintercept.so</code> 以及 netcon 目录中的 <code>liblwip.so</code> 应用层 Socket API 库文件了，接着可以先将 ZeroTier Network Container 模式程序和有关库文件安装到 <code>/var/lib/zerotier-one</code> 目录中：</p>
<pre class="brush: bash; title: ; notranslate">
[cabox@box-codeanywhere ZeroTierOne]$ mkdir -m /var/lib/zerotier-one
[cabox@box-codeanywhere ZeroTierOne]$ cp -ra zerotier-* /var/lib/zerotier-one
[cabox@box-codeanywhere ZeroTierOne]$ cp libzerotierintercept.so netcon/liblwip.so /var/lib/zerotier-one
</pre>
<h3 id="将容器主机加入-zerotier-虚拟网络">将容器主机加入 ZeroTier 虚拟网络</h3>
<p>下面就可以在 Codeanywhere 虚拟机中进行测试了，首先启动 Network Container 模式下的 ZeroTier 主服务程序：</p>
<pre class="brush: bash; title: ; notranslate">
[cabox@box-codeanywhere workspace]$ /var/lib/zerotier-one/zerotier-netcon-service -d
</pre>
<p>服务启动成功之后稍等片刻，运行 <code>zerotier-cli</code> 命令加入现有的 ZeroTier 虚拟网络，下面命令行中的 <code>565799d8f61077e9</code> 就是我在 ZeroTier 网站上新创建的虚拟网络 ID：</p>
<pre class="brush: bash; title: ; notranslate">
[cabox@box-codeanywhere workspace]$ /var/lib/zerotier-one/zerotier-cli join 565799d8f61077e9
</pre>
<blockquote>
<p><strong>提示</strong></p>
<p>如果你加入的是 <code>8056c2e21c000001</code> 这个全球公共 ZeroTier 虚拟网络那就可以省掉下面这一段落的配置步骤了。</p>
</blockquote>
<p>join 操作完成之后需要在 ZeroTier 网站后台虚拟网络管理界面中先允许新的主机加入此网络，接着可以为此主机配置虚拟网络上的 IP 地址、子网掩码之类的，具体配置操作步骤还算比较简单基本都可以在 <a href="https://my.zerotier.com/network">ZeroTier Central</a> 网站后台中完成。</p>
<p>配置完成之后就可以在容器中继续运行 <code>zerotier-cli</code> 命令查询该主机上 ZeroTier 虚拟网络状态：</p>
<pre class="brush: bash; title: ; notranslate">
[cabox@box-codeanywhere workspace]$ /var/lib/zerotier-one/zerotier-cli listnetworks
200 listnetworks &lt;nwid&gt; &lt;name&gt; &lt;mac&gt; &lt;status&gt; &lt;type&gt; &lt;dev&gt; &lt;ZT assigned ips&gt;
200 listnetworks 565799d8f61077e9 zohead-network fa:d9:cf:a8:b2:40 OK PRIVATE /var/lib/zerotier-one/nc_565799d8f61077e9 10.10.8.4/24
</pre>
<p>可以看到该容器中已经正确连接到我建立的私有 ZeroTier 虚拟网络了，而且分配到了一个 <code>10.10.8.4</code> 的 IP 地址，子网掩码为 255.255.255.0（对应 24）。</p>
<h3 id="通过虚拟网络访问容器主机">通过虚拟网络访问容器主机</h3>
<p>此时需要特别注意的是容器中运行的程序还不能直接使用 ZeroTier 虚拟网络与其它主机进行连接，必须通过预加载 Network Container 库文件的方式运行程序才可以在容器系统中正确访问 ZeroTier 虚拟网络，这里我们以 SSH 服务器为例，首先可以新运行一个 <code>sshd</code> 服务程序：</p>
<pre class="brush: bash; title: ; notranslate">
[cabox@box-codeanywhere workspace]$ sudo ZT_NC_NETWORK=/var/lib/zerotier-one/nc_565799d8f61077e9 LD_PRELOAD=/var/lib/zerotier-one/libzerotierintercept.so /usr/sbin/sshd -p 27001
</pre>
<p>这样可以新运行一个 <code>sshd</code> 服务程序，并绑定新的端口（例如上面例子中的 27001 端口）防止和系统默认的 22 端口冲突，<code>ZT_NC_NETWORK</code> 变量指定需要使用的 ZeroTier 虚拟网络路径（默认为：<code>/var/lib/zerotier-one/nc_网络ID</code>），<code>LD_PRELOAD</code> 是必须指定的，否则新运行的 <code>sshd</code> 程序将无法访问 ZeroTier 虚拟网络。</p>
<p>新的 <code>sshd</code> 服务启动成功之后，我们就可以在测试的 Windows、Linux 等主机上运行 ZeroTier 客户端，加入同样的虚拟网络并配置虚拟网络 IP 地址（例如我的 Windows 机器 ZeroTier 网络 IP 地址是 <code>10.10.8.3</code>）及子网掩码。</p>
<p>最后我们可以在 Windows 主机上 ping 测试 Codeanywhere 容器的虚拟网络 IP 地址了，如果通讯正常的话就可以通过 ssh 客户端工具直接连接登录 Codeanywhere 容器系统了，这样就顺利绕过了 Codeanywhere 的随机子域名限制。</p>
<h2 id="后记">后记</h2>
<p>如果需要在 Codeanywhere 容器系统启动时自动连接 ZeroTier 虚拟网络并开启独立的 <code>sshd</code> 服务程序，那可以将类似下面的两句命令加到 <code>/etc/rc.local</code> 文件末尾：</p>
<pre class="brush: bash; title: ; notranslate">
/var/lib/zerotier-one/zerotier-netcon-service -d
echo &quot;ZT_NC_NETWORK=/var/lib/zerotier-one/nc_565799d8f61077e9 LD_PRELOAD=/var/lib/zerotier-one/libzerotierintercept.so /usr/sbin/sshd -p 27001&quot; | at now + 2 minutes
</pre>
<p>第二句命令表示在 ZeroTier Network Container 服务程序启动两分钟之后再启动单独的 <code>sshd</code> 服务程序，以使 ZeroTier 服务程序在等待的时间内能正确连接虚拟网络。</p>
<p>本文主要介绍 ZeroTier 程序里不太常见的 Network Container 容器模式，这对于越来越多的 Docker 容器系统而言能绕过 TUN/TAP 限制还是有点用处的，如果读者发现了有关 ZeroTier P2P VPN 的更多好玩法，欢迎提出交流哦，祝大家玩的开心～～～。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/zerotier-container/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>IBM Bluemix Docker容器初步体验</title>
		<link>https://zohead.com/archives/ibm-bluemix-docker/</link>
		<comments>https://zohead.com/archives/ibm-bluemix-docker/#comments</comments>
		<pubDate>Thu, 14 Apr 2016 19:20:13 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[Baseimage-docker]]></category>
		<category><![CDATA[Bluemix]]></category>
		<category><![CDATA[cf ic]]></category>
		<category><![CDATA[Cloud Foundry]]></category>
		<category><![CDATA[IBM]]></category>
		<category><![CDATA[云平台]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[端口]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1213</guid>
		<description><![CDATA[现在国内外流行的云平台越来越多，且不谈像国外的 Amazon ECS、AWS 以及 国内的阿里云、BAE、SAE 这些老牌的云服务，目前提供 Docker 容器服务的平台也多起来，国外有 StackEngine、Tutum、IBM Bluemix 等云平台，国内也有 DaoCloud、时速云、灵雀云之类提供 Docker 容器服务的平台。最近对 Docker 也稍微有了点兴趣，比较之下准备拿 IBM Bluemix 来捣鼓 Docker 容器练练手。 本文不打算对 Docker 容器技术本身对什么介绍了，选择 IBM Bluemix 开发平台的主要原因是： 同时支持运行时（基于 Cloud F [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>现在国内外流行的云平台越来越多，且不谈像国外的 Amazon ECS、AWS 以及 国内的阿里云、BAE、SAE 这些老牌的云服务，目前提供 Docker 容器服务的平台也多起来，国外有 StackEngine、Tutum、IBM Bluemix 等云平台，国内也有 DaoCloud、时速云、灵雀云之类提供 Docker 容器服务的平台。最近对 Docker 也稍微有了点兴趣，比较之下准备拿 IBM Bluemix 来捣鼓 Docker 容器练练手。</p>
<p>本文不打算对 Docker 容器技术本身对什么介绍了，选择 IBM Bluemix 开发平台的主要原因是：</p>
<ul>
<li>同时支持运行时（基于 Cloud Foundry）、容器和虚拟机（基于 openstack）；</li>
<li>服务器在国外，绑定域名不需要备案，这个优势我也挺无奈；</li>
<li>免费账户提供两个公网 IP、每个月有 365 GB-小时的运行内存额度（相当于一个 512MB 内存的容器运行一个月），能基本满足需求；</li>
<li>容器可以开启多个自己指定的 TCP 端口；</li>
<li>IBM 出的货应该还比较稳定。</li>
</ul>
<p>有关 IBM Bluemix 的详情请参考官网：</p>
<p><a href="http://www.ibm.com/cloud-computing/bluemix/cn-zh/">http://www.ibm.com/cloud-computing/bluemix/cn-zh/</a></p>
<p>注册账户什么的这里就不说了，Bluemix 的免费账户有 30 天的试用期，30 天之后添加有效的信用卡就可以继续使用免费额度内的运行时和容器。</p>
<p>Bluemix 提供了 Cloud Foundry 命令行界面 (cf) 来修改应用程序、服务实例和服务绑定，Cloud Foundry CLI 也可以安装 IBM Containers 插件以实现命令行方式管理 Docker 容器。考虑为了熟悉 Docker 命令行的目的，而且 Bluemix 容器中有一些高级功能使用 IBM Containers 插件（cf ic）可以很方便的完成，本文就主要介绍以 IBM Containers 插件（cf ic） CLI 命令的方式管理你的 Bluemix 容器。</p>
<p>IBM Containers 插件 (cf ic) 的官方文档请参考这里：</p>
<p><a href="https://console.ng.bluemix.net/docs/containers/container_cli_cfic.html">https://console.ng.bluemix.net/docs/containers/container_cli_cfic.html</a></p>
<h2 id="安装-cf-ic-插件">安装 cf ic 插件</h2>
<p>cf ic 插件的具体安装步骤可以参考 <a href="https://console.ng.bluemix.net/docs/containers/container_cli_ov.html#container_cli_cfic_install">IBM 官方文档</a>，这里我做个简单的说明。</p>
<p>IBM Containers 插件 (cf ic) 需要依赖 Docker 1.6 以上版本，而 Docker 官方推荐在 Linux 3.8 以上版本的 64 位系统中运行，我使用的 Chromebook 刚好能满足要求，在 Crouton 的 Ubuntu 14.04 环境中安装 Docker 非常简单：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ sudo apt-get install docker.io
</pre>
<p>接着需要从 <a href="https://github.com/cloudfoundry/cli/releases">https://github.com/cloudfoundry/cli/releases</a> 下载安装 Cloud Foundry 命令行界面 (cf)。</p>
<p>按照 IBM 官方文档介绍的使用 cf 命令安装容器插件时遇到报错：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ cf install-plugin https://static-ice.ng.bluemix.net/ibm-containers-linux_x64

**Attention: Plugins are binaries written by potentially untrusted authors. Install and use plugins at your own risk.**

Do you want to install the plugin https://static-ice.ng.bluemix.net/ibm-containers-linux_x64? (y or n)&gt; y

Attempting to download binary file from internet address...
10138400 bytes downloaded...
Installing plugin /tmp/ibm-containers-linux_x64...
FAILED
exit status 2
</pre>
<p>插件已经下载成功，但运行时直接报错，没办法我使用上面的地址手工下载 <code>ibm-containers-linux_x64</code> 之后运行，看具体是什么导致的运行出错：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ ./ibm-containers-linux_x64
panic: Asset i18n/resources/zh_Hans.all.json not found

goroutine 1 [running]:
github.com/ibm-containers/i18n.initWithLocale(0x8899d0, 0x7, 0x8899d0)
        /home/jenkins/workspace/Containers-CCSCLI/containers-dcscli/plugins/src/github.com/ibm-containers/i18n/i18n.go:61 +0x72
github.com/ibm-containers/i18n.Init(0x7fa7674a3558, 0xc20802eaf0, 0x7fa7674a3658, 0xac8a10, 0xe)
        /home/jenkins/workspace/Containers-CCSCLI/containers-dcscli/plugins/src/github.com/ibm-containers/i18n/i18n.go:55 +0x124
github.com/ibm-containers/i18n.init()
        /home/jenkins/workspace/Containers-CCSCLI/containers-dcscli/plugins/src/github.com/ibm-containers/i18n/i18n.go:41 +0x199
main.init()
        /home/jenkins/workspace/Containers-CCSCLI/containers-dcscli/plugins/src/github.com/ibm-containers/wrapper_docker.go:55 +0x78

goroutine 6 [runnable]:
os/signal.loop()
        /usr/local/go/src/os/signal/signal_unix.go:19
created by os/signal.init·1
        /usr/local/go/src/os/signal/signal_unix.go:27 +0x35
</pre>
<p>看起来是加载中文语言信息（我的 Ubuntu 默认语言就是中文）时出错然后直接退出了，试试直接把语言设为英文运行看看：</p>
<pre class="brush: bash; title: ; notranslate">
LANG=en_US.UTF-8 cf install-plugin https://static-ice.ng.bluemix.net/ibm-containers-linux_x64
......
(trusty)zzm@localhost:~$ cf plugins
Listing Installed Plugins...
OK

Plugin Name      Version   Command Name   Command Help   
IBM-Containers   0.8.826   ic             IBM Containers plug-in
</pre>
<p>这下果然就没有报错了，cf ic 插件安装成功，使用 <code>cf plugins</code> 命令可以列出所有安装的 Cloud Foundry 插件。</p>
<h2 id="初步使用-cf-ic-插件">初步使用 cf ic 插件</h2>
<p>首先使用 <code>cf login</code> 命令登录到区域服务器，目前 Bluemix 有美国南部、悉尼、英国这 3 个区域，这里我使用美国南部(其它区域更换 login 后面的地址即可)，输入邮箱和密码登录之后就可以看到区域信息:</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ cf login -a https://api.ng.bluemix.net
API endpoint: https://api.ng.bluemix.net

Email&gt; xxx@gmail.com
Password&gt; 
Authenticating...
OK

Targeted org xxx@gmail.com
Targeted space zohead

API endpoint:   https://api.ng.bluemix.net (API version: 2.40.0)
User:           xxx@gmail.com
Org:            xxx@gmail.com
Space:          zohead
</pre>
<p>然后需要初始化 CLI：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ cf ic init
</pre>
<p>初始化完成之后可以运行 <code>cf ic info</code> 获取容器云服务的状态：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~/bluemix$ cf ic info
Date/Time                : 2016-03-18 03:02:01.808641119 +0800 CST
Debug Mode               : false
Host/URL                 : https://containers-api.ng.bluemix.net
Registry Host            : registry.ng.bluemix.net
Bluemix API Host/URL     : https://api.ng.bluemix.net
Bluemix Org              : xxx@gmail.com(xxx-xxx-xxx-xxx-xxx)
Bluemix Space            : zohead(xxx-xxx-xxx-xxx-xxx)
CLI Version              : 0.8.826
API Version              : 3.0 2028 2016-03-16T19:34:24
Namespace                : zohead
Environment Name         : prod-dal09-vizio3
Containers Limit         : Unlimited
Containers Usage         : 0
Containers Running       : 0
CPU Limit (cores)        : Unlimited
CPU Usage (cores)        : 0
Memory Limit (MB)        : 2048
Memory Usage (MB)        : 0
Floating IPs Limit       : 2
Floating IPs Allocated   : 0
Public IPs Bound         : 0
</pre>
<p>可以看到试用账户的内存使用限制是 2GB，可以使用两个 IP 地址。</p>
<h2 id="使用-cf-ic-构建-docker-容器">使用 cf ic 构建 Docker 容器</h2>
<p>一般情况下你都可以使用 Bluemix 官方提供的 Docker 镜像进行快速构建，由于通常的 Docker 镜像只是为了运行特定的程序或服务设计的会有不少限制，无法满足我要调试运行多个程序的需求，而且为了后续 SSH 登录之类的考虑，我准备使用 <a href="https://github.com/phusion/baseimage-docker">Baseimage-docker</a> 这个比较特殊的 Docker 镜像来构建容器。</p>
<p>首先在当前目录下生成 Dockerfile 文件：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~/bluemix$ cat &gt; Dockerfile 
# Use phusion/baseimage as base image.
# See https://github.com/phusion/baseimage-docker/blob/master/Changelog.md for a list of version numbers.
FROM phusion/baseimage:0.9.18

EXPOSE 22 80 443 9080 9443

# Use baseimage-docker's init system.
CMD [&quot;/sbin/my_init&quot;]

# ...put your own build instructions here...

# Clean up APT when done.
RUN apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
</pre>
<p>Dockerfile 文件简单说明如下：</p>
<ul>
<li>FROM 后面的 <code>phusion/baseimage:0.9.18</code> 表示 Docker 镜像的地址和版本，具体使用哪个版本请参考上面注释里的 Changelog.md 文档；</li>
<li>EXPOSE 表示此 Docker 容器要导出哪些 TCP 端口以使外部能访问到容器。</li>
</ul>
<blockquote>
<p><strong>提示</strong></p>
<p>IBM Bluemix 目前对容器导出的端口有严格的限制，只有常用端口才能导出，官方论坛里列出的可用端口为： <br />
  <em>22,80,443,9080,9443</em></p>
<p>不属于上面列出的其它端口就算通过 Dockerfile 导出也无法访问。</p>
<p>同时需要注意的是使用 Dockerfile 构建容器完成之后就不能再修改导出的端口了，想再换别的端口只能删除旧容器重新构建，因此开始的端口选择需要谨慎。</p>
</blockquote>
<p>总之我的 Dockerfile 文件将可用的 5 个端口都导出了，下面使用 <code>cf ic build</code> 命令构建容器：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~/bluemix$ cf ic build -t zohead-server:v1 .
</pre>
<p>上面的 <code>zohead-server</code> 为新的容器名字，最后的 <code>.</code> 参数表示 Dockerfile 文件所在的目录（<code>.</code> 即当前目录）。</p>
<p>容器构建完成之后如果没有什么问题就可以用 <code>cf ic run</code> 命令启动新容器了：</p>
<pre class="brush: bash; title: ; notranslate">
cf ic run --name=zohead-server -m 512 -p 22 -p 80 -p 443 -p 9080 -p 9443 registry.ng.bluemix.net/`cf ic namespace get`/zohead-server:v1
</pre>
<p><code>cf ic run</code> 命令的 <code>--name</code> 参数指定容器名字；<code>-m</code> 参数则是为新容器分配的内存大小，为了不超过免费额度我分配了 512MB；<code>-p</code> 参数指定公开用于 TCP 流量的端口,可以指定多个，这里我指定的 5 个端口和 Dockerfile 文件中的一致。</p>
<p>启动容器成功之后等待一段时间就可以使用 <code>cf ic ip list</code> 命令查看 IBM Bluemix 为你的容器分配的公网 IP 地址，公网 IP 地址分配成功之后才可以直接访问容器中的服务：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~/bluemix$ cf ic ip list
Number of allocated public IP addresses: 1

Listing the IP addresses in this space...
IP Address       Container ID   
134.168.27.107   xxx-xxx-xxx-xxx-xxx
</pre>
<p>上面列出的 IP Address 就是为容器分配的公网 IP 地址，由于我使用的是美国南部区域，分配的公网 IP 地址 ping 值不是特别理想。</p>
<h2 id="访问-docker-容器">访问 Docker 容器</h2>
<p>即使容器没有分配正确的公网 IP 地址，我们也可以使用 <code>cf ic exec</code> 命令直接访问容器执行命令：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~/bluemix$ cf ic exec zohead-server echo &quot;Hello Bluemix&quot;
Hello Bluemix
</pre>
<p>当然也可以使用 <code>cf ic exec</code> 命令直接开启一个连接到容器的 Bash Shell 终端：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~/bluemix$ cf ic exec -t -i zohead-server bash -l
</pre>
<p>有了公网 IP 地址之后，你就可以参考 <a href="https://github.com/phusion/baseimage-docker#enabling_ssh">Baseimage-docker开启 SSH</a> 的说明启用 SSH 证书登录了，这里也比较简单我就不做介绍了。</p>
<p>Bluemix Docker 容器能够 SSH 登录之后就比较方便了，Baseimage-docker 镜像里自带了 Python 3 支持，我们可以直接使用 Python 3 运行一个最简单的 Web 服务器验证导出的 TCP 端口能否正常访问：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-001afe36:/home/wwwroot# python3 -m http.server 80
</pre>
<p>由于 Bluemix 容器用的是 IBM Containers，我们可以用 <code>capsh</code> 命令看看 Bluemix 是否和其它 Docker 容器服务一样存在权限限制：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-001afe36:~# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
</pre>
<p>看起来限制和常见的 Docker 平台也差不多，没有一些特别的系统管理权限，例如我想测试的基于 tun 驱动的开源 VPN 服务也无法使用了。</p>
<h2 id="监控-bluemix-容器运行状态">监控 Bluemix 容器运行状态</h2>
<p>一番使用下来你会发现参考 Bluemix 官方的文档就可以基本只用 cf ic 命令实现完成创建、构建、运行和管理 Bluemix 容器这一整套操作了。如果你要监控容器的历史运行状态或者要查看账单之类的那可能还是需要访问 Bluemix Web 控制台的，这个是我的 Docker 容器运行时的内存使用情况：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370583/bluemix-docker-status.png" alt="Bluemix 容器内存使用情况" title="Bluemix 容器内存使用情况"></p>
<p>基本的功能在 Bluemix Web 控制台里也是可以完成的，如果使用中遇到问题可以在 Web 控制台里创建技术支持工单（只是 IBM 的技术支持响应速度似乎很慢的）。</p>
<blockquote>
<p><strong>注意</strong></p>
<p>我在试用 Bluemix 容器的时候发现有一个不太容易被注意到的坑就是容器里使用 <code>free</code> 或者 <code>top</code> 等命令看到的内存非常大（远远超过我分配的 512MB，似乎显示的是实际服务器的总体内存，而且容器里的程序也能分配超过 512MB 的内存，因此容器里实际运行的程序内存占用需要注意不要超过限额。</p>
</blockquote>
<p><br/>
<p>你也可以在 Web 控制台中点击账户图标，然后进入「管理组织」设置，这里可以修改当前区域的运行时和容器使用配额：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370583/bluemix-quota.png" alt="Bluemix 管理区域配额" title="Bluemix 管理区域配额"></p>
<p>经过十几天对 IBM Bluemix Docker 容器的试用来看，虽然 IBM 的服务器不在国内，但 Bluemix 容器还算是比较稳定的，对于一般的开发者来说调试运行一些服务程序也是比较方便的。</p>
<p>一般用户使用这种容器服务最多的用途像跑博客之类的 Web 服务是没什么问题的，不过国内似乎不少人只是用 Bluemix 容器跑 Shadowsocks 代理了（真的算是国内用户的刚性需求了么 -_-#），对此我只能说 IBM Bluemix 目前还是比较良心的，大家且用且珍惜请不要滥用呢，最后祝玩的开心 ～～～。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/ibm-bluemix-docker/feed/</wfw:commentRss>
		<slash:comments>21</slash:comments>
		</item>
	</channel>
</rss>
