<?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; 容器</title>
	<atom:link href="https://zohead.com/archives/tag/container/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>使用 Lade 云开发平台部署容器应用</title>
		<link>https://zohead.com/archives/lade-cloud/</link>
		<comments>https://zohead.com/archives/lade-cloud/#comments</comments>
		<pubDate>Mon, 28 Apr 2025 15:35:39 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[Lade]]></category>
		<category><![CDATA[Miniflux]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[云平台]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[数据库]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1948</guid>
		<description><![CDATA[关于 Lade 云开发平台 最近了解到一个比较新的 Lade 云开发平台，Lade 是专为开发者设计的云端平台，可以帮助开发者在云端部署测试应用，并提供基础的免费应用额度，还有多处数据中心服务器可供选择，还默认直接支持 HTTPS 域名以方便连接访问容器应用。 Lade 开发平台默认支持以下编程语言： Go Node.js PHP Python Ruby 以及以下这些常用的数据库： MariaDB Memcached MongoDB MySQL PostgreSQL Redis 对于其它编程语言，Lade 还支持以 Dockerfile 形式运行测试应用容器。 首先可以在 Lade 官方网站注 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="about-lade-cloud">关于 Lade 云开发平台</h2>
<p>最近了解到一个比较新的 <a href="https://www.lade.io/">Lade</a> 云开发平台，Lade 是专为开发者设计的云端平台，可以帮助开发者在云端部署测试应用，并提供基础的免费应用额度，还有多处数据中心服务器可供选择，还默认直接支持 HTTPS 域名以方便连接访问容器应用。</p>
<p>Lade 开发平台默认支持以下编程语言：</p>
<ul>
<li>Go</li>
<li>Node.js</li>
<li>PHP</li>
<li>Python</li>
<li>Ruby</li>
</ul>
<p>以及以下这些常用的数据库：</p>
<ul>
<li>MariaDB</li>
<li>Memcached</li>
<li>MongoDB</li>
<li>MySQL</li>
<li>PostgreSQL</li>
<li>Redis</li>
</ul>
<p>对于其它编程语言，Lade 还支持以 Dockerfile 形式运行测试应用容器。</p>
<p>首先可以在 Lade 官方网站注册账户，然后需要绑定并验证 GitHub 账号后才能创建部署容器应用。</p>
<h2 id="lade-cli">Lade CLI 部署应用</h2>
<p>Lade 网站目前没有提供任何容器应用的部署和管理的功能，容器应用的部署和管理都需要使用 <a href="https://www.lade.io/docs/platform/cli">Lade CLI</a> 工具来完成。</p>
<p>这里以 Linux 系统为例进行简单的介绍，按照页面介绍来安装 Lade CLI：</p>
<pre class="brush: bash; title: ; notranslate">
~# curl -L https://github.com/lade-io/lade/releases/latest/download/lade-linux-amd64.tar.gz | tar xz
~# sudo mv lade /usr/local/bin
</pre>
<p>首先使用 CLI 登录 Lade 账户：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade login
Enter your Lade credentials:
? Username or email: zohead
? Password: 
Logged in as zohead*
</pre>
<p>输入用户名和密码登录成功之后就可以进行创建部署容器应用了，这次我准备先部署一个 <a href="https://miniflux.app/">Miniflux</a> 应用来试试效果。</p>
<p>Miniflux 是一个极简的 RSS 阅读器应用，基于 Go 语言开发，使用 Postgres 数据库保存数据，与其它 RSS 阅读器应用相比功能和界面都很简洁，不过关键还支持抓取 RSS 文章全文、全文检索等功能。</p>
<p>Lade 的收费方式可以参考官网 <a href="https://www.lade.io/pricing">Pricing</a> 页面，并有基础的免费额度，当然免费的额度也是随时有可能取消的：</p>
<ul>
<li>应用：
<ul>
<li>128 MB 内存；</li>
<li>1 CPU；</li>
</ul>
</li>
<li>数据库：
<ul>
<li>128 MB 内存；</li>
<li>1 GB 存储空间；</li>
</ul>
</li>
<li>磁盘：
<ul>
<li>1 GB 磁盘。</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>备注：</strong></p>
<p>2025-06-07 开始 Lade 平台正式取消了免费额度，目前最便宜的 128 MB 内存 / 1 CPU 的应用价格为每月 $1.25，大家自行酌情选择合适的容器收费配置计划。</p>
</blockquote>
<h3 id="app-container">应用容器</h3>
<p>我们首先使用 <code>lade apps create</code> 命令创建新的应用容器，按照提示输入应用容器名称，并选择容器配置和区域即可完成创建，这里就选择免费配置计划和日本东京区域：</p>
<blockquote>
<p><strong>提示：</strong></p>
<ul>
<li>容器名称非常关键，后面很多命令都需要用到，请谨慎输入；</li>
<li>请根据实际应用程序对内存和 CPU 的要求，选择合适的应用配置计划；</li>
<li>免费额度随时有可能取消，如果使用免费配置计划，请注意备份应用数据。</li>
</ul>
</blockquote>
<pre class="brush: bash; title: ; notranslate">
~# lade apps create
? App Name: miniflux
? Plan: 128mb
? Region: Tokyo
</pre>
<p>创建成功之后，可以运行 <code>lade apps list</code> 命令查看容器列表：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade apps list
NAME        PLAN     CREATED           STATUS
miniflux    128mb    43 minutes ago    running
</pre>
<p>另外使用 <code>lade apps show</code> 命令可以查看具体某个应用容器的详细信息和状态，下方输出数据最后的 <code>Web URL</code> 就是 Lade 云开发平台为容器应用自动分配的 HTTPS 访问域名：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade apps show miniflux
Owner:        XXX@XXX.com
Processes:    web: 0/1
Plan:         128mb
Region:       Tokyo
Status:       running
Web URL:      miniflux-zohead.ladeapp.com
</pre>
<p>接着我们就需要在容器上部署实际的应用了，对于 Miniflux 可以从 GitHub 检出最新 v2 版本的代码：</p>
<pre class="brush: bash; title: ; notranslate">
~# mkdir miniflux
~# cd miniflux
~/miniflux# git clone --depth 1 https://github.com/miniflux/v2.git
</pre>
<p>然后使用 <code>lade deploy</code> 命令进行容器应用部署：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade deploy --app miniflux
time=&quot;2025-04-24T20:28:59+08:00&quot; level=error msg=&quot;Can't add file internal/reader/readability/testdata to tar: archive/tar: unknown file mode ?rw-rw-rw-&quot;
Sending build context to Docker daemon  4.563MB
Step 1/11 : FROM golang:1.23.0
1.23.0: Pulling from library/golang
Successfully built e7ae4ac3677b
Successfully tagged registry.lade.io/zohead/miniflux:build-1njzwaheu4
Pushing miniflux build to registry
Build finished use &quot;lade logs -a miniflux -f&quot; to view app logs
</pre>
<p>容器应用部署成功之后，我们可以使用 <code>lade ps</code> 命令确认容器内的进程是否运行正常，可以看到 Lade 部署生成的 <code>miniflux.app</code> 应用程序进程正在运行：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade ps -a miniflux
NAME     PLAN     STARTED         COMMAND
web.1    128mb    14 hours ago    miniflux.app
</pre>
<p>我们还可以使用 <code>lade run</code> 命令实现在应用容器中运行需要的命令，这里就直接运行一个 Bash Shell，运行自定义命令也需要指定一个配置计划，这里还是使用免费配置计划：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade run &quot;bash&quot; -a miniflux -p 128mb
</pre>
<p>容器内的 Bash Shell 启动成功之后，我们可以查看并确认 Miniflux 应用信息，对于 Miniflux 这种 Go 语言应用，Lade 默认部署生成的应用程序在 <code>/go/bin</code> 目录下：</p>
<pre class="brush: bash; title: ; notranslate">
~$ ls -l /go/bin/miniflux.app
-rwxr-xr-x 1 web web 19411204 Apr 24 12:30 /go/bin/miniflux.app
~$ miniflux.app -i
Version: dev
Commit: HEAD
Build Date: undefined
Go Version: go1.23.0
Compiler: gc
Arch: amd64
OS: linux
</pre>
<p>另外根据上面部署命令输出中的提示信息，使用 <code>lade logs -a miniflux -f</code> 命令可以跟踪显示容器应用的运行日志，实际上就是显示容器内应用程序的标准输出和错误输出：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade logs -a miniflux -f
web.1 | level=INFO msg=&quot;The default value for DATABASE_URL is used&quot;
web.1 | dial tcp [::1]:5432: connect: connection refused
</pre>
<p>由于还没有为 Miniflux 应用配置数据库，会出现类似上面的报错信息。</p>
<h3 id="database-addon">数据库扩展</h3>
<p>接着我们需要使用 <code>lade addons create</code> 命令为容器应用配置数据库扩展，下面的 <code>lade addons create postgres</code> 命令即为创建 PostgreSQL 数据库，我仍然使用免费配置计划，并选择和应用相同位置的日本东京区域，以加快数据库访问速度：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade addons create postgres
? Addon Name: miniflux-postgres
? Plan: 128mb
? Region: Tokyo
? Version: 17
? Public: No
</pre>
<p>简单说明一下命令参数：</p>
<ul>
<li><code>Addon Name</code> 为数据库扩展名称，后续扩展管理相关命令也会用到；</li>
<li><code>Version</code> 为数据库版本，<code>lade</code> 命令会列出支持的数据库版本；</li>
<li><code>Public</code> 表示数据库是否允许外部公共访问，我只需要 PostgreSQL 数据库被 Miniflux 应用访问，并不需要公共访问，这里就选 <strong>No</strong> 了。</li>
</ul>
<p>创建成功之后，就可以使用 <code>lade addons show</code> 命令查看数据库扩展的详细信息和状态了：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade addons show miniflux-postgres
Owner:        XXX@XXX.com
Service:      PostgreSQL
Plan:         128mb
Region:       Tokyo
Version:      17
Public:       No
Status:       running
Addon URI:    postgresql://USERNAME:PASSWORD@tyo-sw1.lade.io:30066/XXXX?sslmode=require
</pre>
<p>最后的 <code>Addon URI</code> 就是最关键的数据库连接地址，我们需要为应用容器配置数据库连接地址，地址中包含数据库服务器地址、用户名、密码、数据库名称等数据。</p>
<p>确认数据库运行正常之后，就可以将 PostgreSQL 数据库扩展附加到 Miniflux 应用容器：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade addons attach miniflux-postgres --app miniflux
? Env Name: POSTGRES_URL
</pre>
<p>然后使用 <code>lade env list</code> 命令检查应用容器的环境变量：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade env list -a miniflux
DATABASE_URL=postgresql://USERNAME:PASSWORD@tyo-sw1.lade.io:30066/XXXX?sslmode=require
POSTGRES_URL=postgresql://USERNAME:PASSWORD@tyo-sw1.lade.io:30066/XXXX?sslmode=require
</pre>
<p>可以看到数据库扩展附加之后，会自动为应用容器配置数据库环境变量，包含通用的 <code>DATABASE_URL</code> 和 PostgreSQL 专用的 <code>POSTGRES_URL</code> 环境变量。</p>
<h2 id="miniflux-app-issue">Miniflux 应用容器问题</h2>
<p>我原以为按照上面的步骤为 Miniflux 应用容器配置好 PostgreSQL 数据库扩展之后，Miniflux 应用就可以正常运行了，而实际上容器仍然有报错：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade logs -a miniflux -f
web.1 | the database schema is not up to date: current=v0 expected=v108
</pre>
<p>参考 Miniflux 项目 <a href="https://github.com/miniflux/v2/issues/1680">You must run the SQL migrations, the database schema is not up to date: current=v0 expected=v62</a> Issue 的说明，对于类似 Docker 容器的环境 Miniflux 需要开启 <code>RUN_MIGRATIONS</code> 环境变量。</p>
<p>我们可以使用 <code>lade env set</code> 命令为容器配置或修改环境变量：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade env set RUN_MIGRATIONS=1 -a miniflux
</pre>
<p>稍等一会容器应用自动重启后，就可以看到 Miniflux 应用已经正常运行了：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade logs -a miniflux -f
web.1 | level=INFO msg=&quot;Running database migrations&quot; current_version=0 latest_version=108 driver=postgresql
web.1 | level=INFO msg=&quot;Starting HTTP server&quot; listen_address=:3000
</pre>
<p>现在我们其实就已经可以通过 Lade 云平台为容器应用分配的域名访问 Miniflux Web 管理界面了，当然还是需要用 HTTPS 访问的：</p>
<p><img src="https://images.weserv.nl/?url=https://res.cloudinary.com/digwht2y0/image/upload/v1752420850/lade-miniflux.png" alt="Miniflux Web 管理界面"></p>
<p>不过由于我们还没有为 Miniflux 配置管理员账户，还不能直接登录。</p>
<p>这个时候就需要借助 <code>lade run</code> 命令在应用容器中运行命令创建管理员账户了，下面输出中的 <code>miniflux.app</code> 程序是在容器内运行的：</p>
<pre class="brush: bash; title: ; notranslate">
~$ miniflux.app -create-admin
Enter Username: admin
Enter Password:
level=INFO msg=&quot;Created new admin user&quot; username=admin user_id=1
</pre>
<p>管理员账户创建成功之后，我们就可以通过容器域名访问 Miniflux Web 管理界面进行 RSS 源的配置和订阅管理了。</p>
<p>当然我们也可以开始就通过指定环境变量让 Miniflux 容器自动创建管理员账户，与上面的 <code>RUN_MIGRATIONS</code> 环境变量配合起来就类似：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade env set RUN_MIGRATIONS=1 CREATE_ADMIN=1 ADMIN_USERNAME=admin ADMIN_PASSWORD=password -a miniflux
</pre>
<p>这样通过环境变量指定管理员账户名称和密码也很方便。</p>
<h2 id="summary ">总结</h2>
<p>本文简单介绍了如何使用 Lade 云开发平台的 <code>lade</code> CLI 命令部署配置容器应用，我部署的 Miniflux RSS 阅读器容器应用也运行了一段时间，Lade 云平台还是比较稳定的，只是没有国内的数据中心，访问速度倒是一般。</p>
<p>最后 Lade 云平台的开发者已经确认，目前提供的免费额度仅供开发人员测试使用，免费额度后续随时有可能取消，希望大家不要滥用，如果觉得 Lade 好用也可以考虑付费使用哦，祝大家玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/lade-cloud/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ARM64 Linux下ARM容器使用yum的问题</title>
		<link>https://zohead.com/archives/arm64-arm-container-yum/</link>
		<comments>https://zohead.com/archives/arm64-arm-container-yum/#comments</comments>
		<pubDate>Thu, 29 Oct 2020 16:52:31 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[aarch64]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[ARM64]]></category>
		<category><![CDATA[armhf]]></category>
		<category><![CDATA[RPM]]></category>
		<category><![CDATA[YUM]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1689</guid>
		<description><![CDATA[ARM 容器问题 前段时间需要验证某客户的 32 位 ARM Linux 程序，由于我们只有现成的 ARM64 设备和系统，而 ARM64 兼容 32 位 armel 和 armhf，因此想到直接在 ARM64（aarch64）Linux 系统中运行 32 位 ARM（armhf）容器来进行测试。 客户程序使用的是 CentOS 7 32 位 ARM 开发环境，这里我使用 LXC 官方镜像来创建一个完整的 CentOS 7 容器系统： 32 位 ARM 容器可以正常启动，但使用 yum 安装任何软件包会报错： yum 修改 上面 yum 命令的报错提示找不到 repo 为 base/7/arm [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="arm-container-issue">ARM 容器问题</h2>
<p>前段时间需要验证某客户的 32 位 ARM Linux 程序，由于我们只有现成的 ARM64 设备和系统，而 ARM64 兼容 32 位 armel 和 armhf，因此想到直接在 ARM64（aarch64）Linux 系统中运行 32 位 ARM（armhf）容器来进行测试。</p>
<p>客户程序使用的是 CentOS 7 32 位 ARM 开发环境，这里我使用 LXC 官方镜像来创建一个完整的 CentOS 7 容器系统：</p>
<pre class="brush: bash; title: ; notranslate">
~ # lxc-create -n arm -t download --dir=/var/lib/lxc/arm/rootfs -- --server mirrors.tuna.tsinghua.edu.cn/lxc-images -d centos -r 7 -a armhf
</pre>
<p>32 位 ARM 容器可以正常启动，但使用 yum 安装任何软件包会报错：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum install openssh-server
Loaded plugins: fastestmirror
Determining fastest mirrors


 One of the configured repositories failed (Unknown),
 and yum doesn't have enough cached data to continue. At this point the only
 safe thing yum can do is fail. There are a few ways to work &quot;fix&quot; this:

     1. Contact the upstream for the repository and get them to fix the problem.

     2. Reconfigure the baseurl/etc. for the repository, to point to a working
        upstream. This is most often useful if you are using a newer
        distribution release than is supported by the repository (and the
        packages for the previous distribution release still work).

     3. Run the command with the repository temporarily disabled
            yum --disablerepo=&lt;repoid&gt; ...

     4. Disable the repository permanently, so yum won't use it by default. Yum
        will then just ignore the repository until you permanently enable it
        again or use --enablerepo for temporary usage:

            yum-config-manager --disable &lt;repoid&gt;
        or
            subscription-manager repos --disable=&lt;repoid&gt;

     5. Configure the failing repository to be skipped, if it is unavailable.
        Note that yum will try to contact the repo. when it runs most commands,
        so will have to try and fail each time (and thus. yum will be be much
        slower). If it is a very temporary problem though, this is often a nice
        compromise:

            yum-config-manager --save --setopt=&lt;repoid&gt;.skip_if_unavailable=true

Cannot find a valid baseurl for repo: base/7/armv8l
</pre>
<h2 id="yum-patch">yum 修改</h2>
<p>上面 yum 命令的报错提示找不到 repo 为 <code>base/7/armv8l</code> 的地址，我们可以在容器中使用 <code>uname</code> 命令确认当前的系统架构为 <code>armv8l</code>（ARM64 内核运行 32 位 ARM 程序）：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# uname -m
armv8l
</pre>
<p>而在主机端则可以看到真正的系统架构为 <code>aarch64</code>：</p>
<pre class="brush: bash; title: ; notranslate">
~ # uname -m
aarch64
</pre>
<p>正是由于 yum 不能正确识别 <code>armv8l</code>这个特殊的系统架构，导致命令执行失败。</p>
<p>我们可以修改 <code>/usr/lib/python2.7/site-packages/rpmUtils/arch.py</code> 文件，使 yum 可以正确处理 <code>armv8l</code> 架构，以下为 patch 修改内容：</p>
<pre class="brush: diff; title: ; notranslate">
diff -rNp a/arch.py b/arch.py
*** a/arch.py   Thu Oct 29 23:46:21 2020
--- b/arch.py   Thu Oct 29 23:48:55 2020
*************** arches = {
*** 73,78 ****
--- 73,79 ----
      &quot;armv5tel&quot;: &quot;noarch&quot;,
  
      #arm hardware floating point
+     &quot;armv8l&quot;: &quot;armv7hl&quot;,
      &quot;armv7hnl&quot;: &quot;armv7hl&quot;,
      &quot;armv7hl&quot;: &quot;noarch&quot;,
  
*************** def getBaseArch(myarch=None):
*** 442,447 ****
--- 443,450 ----
          return &quot;ppc&quot;
      elif myarch.startswith(&quot;arm64&quot;):
          return &quot;arm64&quot;
+     elif myarch == &quot;armv8l&quot;:
+         return &quot;armhfp&quot;
      elif myarch.startswith(&quot;armv7h&quot;):
          return &quot;armhfp&quot;
      elif myarch.startswith(&quot;arm&quot;):
</pre>
<p>此时再运行 yum 就可以正确找到软件包了：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum install openssh-server
Loaded plugins: fastestmirror
Determining fastest mirrors
 * base: mirrors.bfsu.edu.cn
 * extras: mirrors.bfsu.edu.cn
 * updates: mirrors.bfsu.edu.cn
base                                                                                                                                                                                              | 3.6 kB  00:00:00
extras                                                                                                                                                                                            | 2.9 kB  00:00:00
updates                                                                                                                                                                                           | 2.9 kB  00:00:00
(1/4): base/7/armhfp/group_gz                                                                                                                                                                     | 153 kB  00:00:00
(2/4): extras/7/armhfp/primary_db                                                                                                                                                                 | 170 kB  00:00:00
(3/4): updates/7/armhfp/primary_db                                                                                                                                                                | 562 kB  00:00:00
(4/4): base/7/armhfp/primary_db                                                                                                                                                                   | 4.1 MB  00:00:00
Resolving Dependencies
--&gt; Running transaction check
---&gt; Package openssh-server.armv7hl 0:7.4p1-21.el7 will be installed
--&gt; Processing Dependency: libwrap.so.0 for package: openssh-server-7.4p1-21.el7.armv7hl
--&gt; Running transaction check
---&gt; Package tcp_wrappers-libs.armv7hl 0:7.6-77.el7 will be installed
--&gt; Finished Dependency Resolution

Dependencies Resolved

=========================================================================================================================================================================================================================
 Package                                                    Arch                                             Version                                                 Repository                                     Size
=========================================================================================================================================================================================================================
Installing:
 openssh-server                                             armv7hl                                          7.4p1-21.el7                                            base                                          446 k
Installing for dependencies:
 tcp_wrappers-libs                                          armv7hl                                          7.6-77.el7                                              base                                           64 k

Transaction Summary
=========================================================================================================================================================================================================================
Install  1 Package (+1 Dependent package)

Total download size: 510 k
Installed size: 1.0 M
Is this ok [y/d/N]: y
Downloading packages:
(1/2): tcp_wrappers-libs-7.6-77.el7.armv7hl.rpm                                                                                                                                                   |  64 kB  00:00:00
(2/2): openssh-server-7.4p1-21.el7.armv7hl.rpm                                                                                                                                                    | 446 kB  00:00:00
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                                                                    1.7 MB/s | 510 kB  00:00:00
Running transaction check
Running transaction test


Transaction check error:
  package tcp_wrappers-libs-7.6-77.el7.armv7hl is intended for a different architecture
  package openssh-server-7.4p1-21.el7.armv7hl is intended for a different architecture
</pre>
<p>不过这次轮到 rpm 报错了，提示 RPM 包的架构不相符。</p>
<h2 id="rpm-patch">rpm 修改</h2>
<p>我们先试着手工安装 yum 下载下来但安装失败的 RPM 包，可以看到还是相同的报错：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# rpm -ivh /var/cache/yum/armhfp/7/base/packages/tcp_wrappers-libs-7.6-77.el7.armv7hl.rpm
Preparing...                          ################################# [100%]
        package tcp_wrappers-libs-7.6-77.el7.armv7hl is intended for a different architecture
</pre>
<p>这也是由于 rpm 命令不能正确处理 <code>armv8l</code> 架构，其实对于这种 RPM 包的架构不相符的情况，我们可以为 rpm 命令增加 <code>--ignorearch</code> 参数强制安装：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# rpm -ivh --ignorearch /var/cache/yum/armhfp/7/base/packages/tcp_wrappers-libs-7.6-77.el7.armv7hl.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:tcp_wrappers-libs-7.6-77.el7     ################################# [100%]
</pre>
<p>当然我们的最终目的是要让 yum 能够自动安装软件包，因此还需要让 rpm 命令也能正确识别 <code>armv8l</code> 架构。</p>
<p>为此我们需要修改 <code>/usr/lib/rpm/rpmrc</code> 文件，将 <code>armv8l</code> 视为 <code>armv7hnl</code>：</p>
<pre class="brush: diff; title: ; notranslate">
diff -rNp a/rpmrc b/rpmrc 
*** a/rpmrc     Fri Oct 30 00:15:17 2020
--- b/rpmrc     Fri Oct 30 00:16:32 2020
*************** arch_compat: armv5tel: armv4tl
*** 398,403 ****
--- 398,404 ----
  arch_compat: armv4tl: armv4l
  arch_compat: armv4l: armv3l
  arch_compat: armv3l: noarch
+ arch_compat: armv8l: armv7hnl
  arch_compat: armv7hnl: armv7hl
  arch_compat: armv7hl: noarch
</pre>
<p>此时我们再使用 yum 命令就可以正确下载和安装软件包了：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum install openssh-server
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.bfsu.edu.cn
 * extras: mirrors.bfsu.edu.cn
 * updates: mirrors.bfsu.edu.cn
Resolving Dependencies
--&gt; Running transaction check
---&gt; Package openssh-server.armv7hl 0:7.4p1-21.el7 will be installed
--&gt; Finished Dependency Resolution

Dependencies Resolved

=========================================================================================================================================================================================================================
 Package                                                  Arch                                              Version                                                 Repository                                      Size
=========================================================================================================================================================================================================================
Installing:
 openssh-server                                           armv7hl                                           7.4p1-21.el7                                            base                                           446 k

Transaction Summary
=========================================================================================================================================================================================================================
Install  1 Package

Total size: 446 k
Installed size: 921 k
Is this ok [y/d/N]: y
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
  Installing : openssh-server-7.4p1-21.el7.armv7hl                                                                                                                                                                   1/1
  Verifying  : openssh-server-7.4p1-21.el7.armv7hl                                                                                                                                                                   1/1

Installed:
  openssh-server.armv7hl 0:7.4p1-21.el7

Complete!
</pre>
<p>后续安装其它软件包也都没有问题了，例如安装基础开发包：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum -y install gcc make openssl-devel glibc-static openssl-static
</pre>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/arm64-arm-container-yum/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<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容器使用ge.tt替代Endpoint域名</title>
		<link>https://zohead.com/archives/arukas-ge-tt/</link>
		<comments>https://zohead.com/archives/arukas-ge-tt/#comments</comments>
		<pubDate>Sat, 06 May 2017 03:52:51 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Arukas]]></category>
		<category><![CDATA[Endpoint]]></category>
		<category><![CDATA[ge.tt]]></category>
		<category><![CDATA[gett-cli]]></category>
		<category><![CDATA[OpenWRT]]></category>
		<category><![CDATA[Shadowsocks]]></category>
		<category><![CDATA[ShadowsocksR]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1437</guid>
		<description><![CDATA[ge.tt 与 Endpoint 前面写了一篇介绍日本 Arukas 樱花容器及使用其 Endpoint 域名的文章，目前我在 Arukas 上面跑了一个 Shadowsocks 容器用于路由器自动爬墙。不过还是发现如果 Arukas 容器的开放端口里开启了 UDP 端口（给 OpenWRT 路由器上的 ss-tunnel 用于转发解析被封锁的域名），这种情况下 Arukas 的 Endpoint 地址就基本没办法正常访问了。文章中我写的 OpenWRT 下自动获取 Arukas 容器 Shadowsocks 配置的脚本也经常出错，用起来还是不太方便。 刚好之前为了给其他人分享文件，我还写了一 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="gett-and-endpoint">ge.tt 与 Endpoint</h2>
<p>前面写了一篇介绍日本 <a href="https://zohead.com/archives/arukas-container/">Arukas 樱花容器</a>及使用其 Endpoint 域名的文章，目前我在 Arukas 上面跑了一个 Shadowsocks 容器用于路由器自动爬墙。不过还是发现如果 Arukas 容器的开放端口里开启了 UDP 端口（给 OpenWRT 路由器上的 <code>ss-tunnel</code> 用于转发解析被封锁的域名），这种情况下 Arukas 的 Endpoint 地址就基本没办法正常访问了。文章中我写的 OpenWRT 下自动获取 Arukas 容器 Shadowsocks 配置的脚本也经常出错，用起来还是不太方便。</p>
<p>刚好之前为了给其他人分享文件，我还写了一篇介绍 <a href="https://zohead.com/archives/ge-tt-cli-api/">ge.tt 文件分享 API</a> 的文章，就想到既然 Endpoint 地址访问容易出错，那可以在 Arukas Shadowsocks 容器里使用 ge.tt 的 Python CLI 将容器的当前地址和外部端口配置文件上传到指定的 ge.tt 分享地址，这样 OpenWRT 系统里也能很方便地通过分享地址更新配置文件了。</p>
<h2 id="arukas-container-mod">Arukas 容器修改</h2>
<p>这里的 Arukas 容器系统我使用的还是最新 baseimage 系统，增加了 <code>shadowsocks-libev</code> 服务器程序，并将 <code>gett-cli</code> 工具集成到 baseimage 系统，然后将 <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

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>这里增加了两个 ge.tt 文件分享服务相关的环境变量，通过 Docker 容器的 ENV 环境变量指定：</p>
<ul>
<li><code>GETT_TOKEN</code> <br />
在某台设备上使用 <code>gett-cli</code> 工具登录成功之后就能看到 <code>~/.gett-token</code> 文件中的 Refresh Token 内容；</li>
<li><code>GETT_SHARE_NAME</code> <br />
预先创建好固定的 ge.tt 分享名（例如：<code>8D95lij2</code>），可以在 ge.tt 网页中上传文件创建，也可以用 <code>gett-cli</code> 工具创建。</li>
</ul>
<p>如果容器的 ENV 列表里有这两个环境变量，启动脚本会自动将 Arukas 容器的当前地址和外部端口配置文件 <code>marathon.conf</code> 上传到指定的 ge.tt 分享地址。</p>
<h2 id="use-shadowsocks-container">使用 Shadowsocks 容器</h2>
<p>容器的 <code>CMD</code> 指令值以及其它 SSH 公钥和 Shadowsocks 服务器 JSON 配置文件环境变量与 <a href="https://zohead.com/archives/arukas-container/">Arukas 容器</a>文章中介绍的相同，可以根据需要自行配置。</p>
<p>我修改后的 Arukas 上支持 ge.tt 并适用于 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>另外我还制作了一个 ShadowsocksR 服务器安装包，有需要的朋友可以下载测试看看，由于 baseimage 系统已经自带了 Python 3 环境，因此 ShadowsocksR 服务器安装包体积也要小一些：</p>
<p><a href="https://zohead.com/downloads/arukas-baseimage-xenial-ssr.tar.gz">https://zohead.com/downloads/arukas-baseimage-xenial-ssr.tar.gz</a></p>
<p>对于容器 ENV 列表中以 <code>SHADOWSOCKS_CFGS_</code> 开头的变量，启动脚本会自动在 <code>/etc/shadowsocks-libev</code> 目录（对于 ShadowsocksR 是 <code>/etc/shadowsocks</code> 目录）下生成 Shadowsocks 或 ShadowsocksR 服务器配置文件，配置文件内容就是该 JSON 格式变量的值。</p>
<p>例如我的 Arukas 容器配置中的 ShadowsocksR 环境变量：</p>
<pre class="brush: bash; title: ; notranslate">
SHADOWSOCKS_CFGS_xxx = { &quot;server&quot;:&quot;0.0.0.0&quot;, &quot;server_port&quot;:8113, &quot;local_port&quot;:1080, &quot;password&quot;:&quot;ssr-pass&quot;, &quot;timeout&quot;:30, &quot;method&quot;:&quot;aes-128-ctr&quot;, &quot;protocol&quot; : &quot;auth_aes128_md5&quot;, &quot;obfs&quot; : &quot;http_simple&quot; }
</pre>
<p>当然对于 ShadowsocksR 容器也需要相应地将 <code>CMD</code> 启动指令改为：</p>
<pre class="brush: bash; title: ; notranslate">
/sbin/my_init -- sh -c 'curl -o /arukas-baseimage-xenial-ssr.tar.gz https://zohead.com/downloads/arukas-baseimage-xenial-ssr.tar.gz &amp;&amp; tar -C / -xzf /arukas-baseimage-xenial-ssr.tar.gz &amp;&amp; /etc/my_init.d/01_start_ss_ssh.sh'
</pre>
<h2 id="openwrt-shadowsocks-script">OpenWRT 自动更新配置脚本</h2>
<p>为了能支持从 ge.tt 分享地址下载更新 Shadowsocks 配置，我对 OpenWRT 路由器上的 <code>arukas-ss.sh</code> 脚本也做了修改：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
ARUKAS_ENDPOINT=&quot;&quot;
GETT_SHRNAME=&quot;&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

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
	fi
	shift
	shift
done

if [ &quot;x$RESTART_SHADOWSOCKS&quot; != &quot;xno&quot; ]; then
	/etc/init.d/shadowsocks restart
fi
</pre>
<p>使用之前请先根据需要修改脚本最上面的 <code>ARUKAS_ENDPOINT</code> 和 <code>GETT_SHRNAME</code> 变量（一般指定其中一个即可），分别为 Arukas 容器的 Endpoint 名和 ge.tt 分享名，脚本会分别尝试通过 ge.tt 分享地址和 Arukas Endpoint 地址下载容器的 <code>marathon.conf</code> 配置文件。</p>
<p>为了使用简单性考虑，修改后的 <code>arukas-ss.sh</code> 脚本是直接通过 <code>/etc/init.d/shadowsocks</code> 服务脚本重启 OpenWRT 上的 Shadowsocks 客户端程序。该脚本的使用方法也请参考我之前写的介绍 Arukas 容器及 Endpoint 的文章。</p>
<p>最后祝大家心中无墙，玩的开心 ^_^。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/arukas-ge-tt/feed/</wfw:commentRss>
		<slash:comments>7</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>暂时迁移被爬虫扒得内存不足的VPS</title>
		<link>https://zohead.com/archives/vps-anti-spider/</link>
		<comments>https://zohead.com/archives/vps-anti-spider/#comments</comments>
		<pubDate>Sat, 11 Feb 2017 14:44:57 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[主机空间]]></category>
		<category><![CDATA[360Spider]]></category>
		<category><![CDATA[Bluemix]]></category>
		<category><![CDATA[HighSpeedWeb]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[robots]]></category>
		<category><![CDATA[VPS]]></category>
		<category><![CDATA[内存不足]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[爬虫]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1356</guid>
		<description><![CDATA[VPS 内存不足问题 最近一两个月我在查看 VPS 运行日志的时候，经常发现 kernel 日志中会有 Out of memory 内存不足报错，而且报错基本都是 php-fpm 引起的： 从日志里可以看到每个 php-fpm 进程的 rss 内存占用都接近 30 MB，我之前就已经将 LNMP 环境里的 php-fpm.conf 配置文件中的 pm.max_children 改为 8，这样如果碰到同时请求数较多的情况，php-fpm 就可能会占用 240 MB 内存。再加上 MySQL、BTSync 等其它程序也要占用内存，我这个在 HighSpeedWeb 上购买的 256 MB 内存的  [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="vps-out-of-memory">VPS 内存不足问题</h2>
<p>最近一两个月我在查看 VPS 运行日志的时候，经常发现 kernel 日志中会有 Out of memory 内存不足报错，而且报错基本都是 <code>php-fpm</code> 引起的：</p>
<pre class="brush: bash; title: ; notranslate">
root@zoserver:~# cat /var/log/kern.log
Dec 15 20:11:43 zoserver kernel: [55751339.090508] Out of memory in UB 1253: OOM killed process 32239 (php-fpm) score 0 vm:56336kB, rss:29832kB, swap:0kB
Dec 15 20:11:56 zoserver kernel: [55751352.643620] Out of memory in UB 1253: OOM killed process 32238 (php-fpm) score 0 vm:55580kB, rss:29444kB, swap:0kB
Dec 15 20:11:57 zoserver kernel: [55751353.609602] Out of memory in UB 1253: OOM killed process 32242 (php-fpm) score 0 vm:56088kB, rss:29800kB, swap:0kB
Dec 15 20:12:23 zoserver kernel: [55751379.072308] Out of memory in UB 1253: OOM killed process 32240 (php-fpm) score 0 vm:55496kB, rss:29520kB, swap:0kB
Dec 15 20:12:45 zoserver kernel: [55751401.084746] Out of memory in UB 1253: OOM killed process 32225 (php-fpm) score 0 vm:55848kB, rss:29564kB, swap:0kB
Dec 15 20:13:22 zoserver kernel: [55751438.326072] Out of memory in UB 1253: OOM killed process 32266 (php-fpm) score 0 vm:56008kB, rss:29880kB, swap:0kB
Dec 15 20:13:36 zoserver kernel: [55751452.087637] Out of memory in UB 1253: OOM killed process 32278 (php-fpm) score 0 vm:55328kB, rss:29356kB, swap:0kB
Dec 15 20:13:37 zoserver kernel: [55751453.035146] Out of memory in UB 1253: OOM killed process 32241 (php-fpm) score 0 vm:55752kB, rss:29784kB, swap:0kB
</pre>
<p>从日志里可以看到每个 <code>php-fpm</code> 进程的 rss 内存占用都接近 30 MB，我之前就已经将 LNMP 环境里的 <code>php-fpm.conf</code> 配置文件中的 <code>pm.max_children</code> 改为 8，这样如果碰到同时请求数较多的情况，<code>php-fpm</code> 就可能会占用 240 MB 内存。再加上 MySQL、BTSync 等其它程序也要占用内存，我这个在 <a href="https://zohead.com/archives/blog-hswvps/">HighSpeedWeb</a> 上购买的 256 MB 内存的 VPS 应该就撑不住了，出现 Out of memory 错误也就不足为怪了。</p>
<p>为了找到原因，我决定检查一下出现内存不足时候的 nginx 请求日志：</p>
<pre class="brush: bash; title: ; notranslate">
root@zoserver:~# more /home/wwwlogs/zohead.log
64.79.85.205 - - [15/Dec/2016:20:11:43 +0800] &quot;GET /archives/tcpkill-nfs/ HTTP/1.1&quot; 200
13304 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:43 +0800] &quot;GET /archives/newifi-mini-openwrt/ HTTP/1.1&quot; 200
18841 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:43 +0800] &quot;GET /archives/category/technology/linux/ubuntu/ HTTP/1.1&quot; 200
11921 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:43 +0800] &quot;GET /archives/category/technology/phone/ HTTP/1.1&quot; 200
12800 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:43 +0800] &quot;GET /archives/category/technology/ HTTP/1.1&quot; 200
14862 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:44 +0800] &quot;GET /archives/category/technology/android/ HTTP/1.1&quot; 200
15127 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:44 +0800] &quot;GET /archives/zerotier-container/ HTTP/1.1&quot; 200
16323 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:44 +0800] &quot;GET /archives/tag/bash/ HTTP/1.1&quot; 200
11221 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
-
64.79.85.205 - - [15/Dec/2016:20:11:44 +0800] &quot;GET /archives/tag/ssh/ HTTP/1.1&quot; 200
11266 - &quot;-&quot; &quot;Mozilla/5.0 (compatible; SMTBot/1.0; +http://www.similartech.com/smtbot)&quot;
</pre>
<p>这就明显是一个不太友善的爬虫干的好事了，由于请求日志太多这里就不列出来了，统计之后可以发现这个 SMTBot 在十几秒钟的时间里请求了几百次，明显超出了 VPS 能处理的范围了。</p>
<p>另外我在检查日志之后还发现经常有各种初步练习用的爬虫也在不断访问 WordPress 博客数据，这种爬虫的特征就是使用各种不同的 User agent：</p>
<pre class="brush: bash; title: ; notranslate">
root@zoserver:~# more /home/wwwlogs/zohead.log
138.197.19.145 - - [17/Dec/2016:08:28:49 +0800] &quot;GET /robots.txt HTTP/1.1&quot; 200
145 - &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:00 +0800] &quot;GET /wp-login.php HTTP/1.1&quot; 200
2464 - &quot;http://zohead.com&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:01 +0800] &quot;GET /archives/category/technology/network-tech/https-ssl/ HTTP/1.1&quot; 200
8604 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:03 +0800] &quot;GET /archives/category/travel/ HTTP/1.1&quot; 502
166 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:03 +0800] &quot;GET /archives/tag/video/ HTTP/1.1&quot; 200
11459 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:03 +0800] &quot;GET /guestbook/ HTTP/1.1&quot; 200
9962 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:03 +0800] &quot;GET /archives/tasker-shell/ HTTP/1.1&quot; 200
13094 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:04 +0800] &quot;GET /archives/category/technology/ HTTP/1.1&quot; 200
13717 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:04 +0800] &quot;GET /archives/tag/android/ HTTP/1.1&quot; 200
13879 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:04 +0800] &quot;GET /archives/category/technology/android/ HTTP/1.1&quot; 200
13842 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36&quot;
-
138.197.19.145 - - [17/Dec/2016:08:29:05 +0800] &quot;GET /archives/author/admin/ HTTP/1.1&quot; 200
13716 - &quot;https://zohead.com&quot; &quot;Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0&quot;
</pre>
<p>然而这些小爬虫发起请求来也是毫不手软，基本没有在多个请求之间加什么延时的。不过还好看起来爬虫还是读了 <code>robots.txt</code> 文件的，因此可以考虑在 <code>robots.txt</code> 和 nginx 配置里做一些限制。</p>
<h2 id="treatment">防治措施</h2>
<h3 id="mod-robots-txt">修改 robots.txt</h3>
<p>首先把原来不太重视的 <code>robots.txt</code> 文件完善一下，增加了一些限制，大概如下：</p>
<pre class="brush: plain; title: ; notranslate">
User-agent: *
Disallow: /wp-admin/
Disallow: /wp-content/
Disallow: /wp-includes/
Disallow: /*?replytocom=*
Crawl-delay: 30
Sitemap: https://zohead.com/sitemap.xml
</pre>
<p>禁止所有爬虫访问一些 WordPress 内部目录，增加了 <code>Crawl-delay</code> 参数，并设置为 30 秒，防止产生过多的请求。</p>
<h3 id="mod-nginx-config">修改 nginx 配置</h3>
<p>由于并不是所有爬虫都会读取并遵守 robots 协议，特别是 Google 和百度这样的搜索巨头也明确表态不支持上面增加的 <code>Crawl-delay</code> 参数，为此还是需要修改 nginx 配置直接限制并发连接数：</p>
<pre class="brush: bash; title: ; notranslate">
root@zoserver:~# more /usr/local/nginx/conf/nginx.conf
http {
	limit_req_zone $anti_spider zone=anti_spider:60m rate=200r/m;
}

server {
	limit_req zone=anti_spider burst=5 nodelay;
	set $anti_spider $http_user_agent;
}
</pre>
<p>上面只是简单节选列出了 nginx 服务器配置的修改，使用 <code>limit_req_zone</code> 限制每分钟 200 个请求，最大并发为 5。</p>
<p>经过上面两步修改之后，VPS 日志里的内存不足错误看起来是减少了，但是好景不长，直到过几天我再去检查内核日志和 nginx 请求日志时发现来了一个臭名昭著的家伙，其频繁的请求仍然导致 VPS 出现 Out of memory 问题：</p>
<pre class="brush: bash; title: ; notranslate">
root@zoserver:~# more /home/wwwlogs/zohead.log
42.236.99.242 - - [09/Jan/2017:05:41:44 +0800] &quot;GET /archives/tag/keepassdroid/?wpmp_switcher=mobile HTTP/1.1&quot; 503
608 - &quot;https://m.zohead.com/archives/tag/keepassdroid/?wpmp_switcher=mobile&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
42.236.99.178 - - [09/Jan/2017:05:41:44 +0800] &quot;GET /archives/easymoney-to-feidee/?lang=en&amp;replytocom=641replytocom=641replytocom=641replytocom=640replytocom=641replytocom=641replytocom=640replytocom=640&amp;wpmp_switcher=desktop HTTP/1.1&quot; 503
608 - &quot;https://zohead.com/archives/easymoney-to-feidee/?lang=en&amp;replytocom=641replytocom=641replytocom=641replytocom=640replytocom=641replytocom=641replytocom=640replytocom=640&amp;wpmp_switcher=desktop&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
42.236.99.230 - - [09/Jan/2017:05:41:45 +0800] &quot;GET / HTTP/1.1&quot; 301
178 - &quot;http://www.zohead.com/&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
42.236.99.194 - - [09/Jan/2017:05:41:47 +0800] &quot;GET /archives/qiniu-https-tamper/?lang=en&amp;replytocom=2190 HTTP/1.1&quot; 200
12300 - &quot;https://zohead.com/archives/qiniu-https-tamper/?lang=en&amp;replytocom=2190&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
42.236.99.206 - - [09/Jan/2017:05:41:48 +0800] &quot;GET /archives/category/technology/linux/page/3/?wpmp_switcher=true HTTP/1.1&quot; 503  
608 - &quot;https://m.zohead.com/archives/category/technology/linux/page/3/?wpmp_switcher=true&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
180.153.236.19 - - [09/Jan/2017:05:41:48 +0800] &quot;GET /archives/category/technology/cplusplus/ HTTP/1.1&quot; 503
608 - &quot;https://m.zohead.com/archives/category/technology/cplusplus/&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
42.236.99.154 - - [09/Jan/2017:05:41:48 +0800] &quot;GET /comments/feed/?lang=en HTTP/1.1&quot; 503
608 - &quot;http://zohead.com/comments/feed/?lang=en&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
-
180.153.236.165 - - [09/Jan/2017:05:41:49 +0800] &quot;GET /archives/tag/start-stop-daemon/?lang=en&amp;wpmp_switcher=mobile HTTP/1.1&quot; 200
8502 - &quot;http://zohead.com/archives/tag/start-stop-daemon/?lang=en&amp;wpmp_switcher=mobile&quot; &quot;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider&quot;
</pre>
<p>上面只是同一时段博客访问日志的很小一部分，360 搜索的 360Spider 爬虫在不断访问博客，而且看起来 360 服务器集群机器也是相当多，360 蜘蛛的 IP 地址列表可以在其官网查看：</p>
<p><a href="https://www.so.com/help/spider_ip.html">https://www.so.com/help/spider_ip.html</a></p>
<p>经过分析日志我发现最要命的是 360 爬虫根本就没有读取 <code>robots.txt</code> 文件，这样根本谈不上让 <code>Crawl-delay</code> 之类的参数发挥作用。</p>
<h2 id="migrate-server">迁移服务器</h2>
<p>经过我差不多一个月的观察，现在 VPS 遇到的内存不足问题基本都是由 360 爬虫引起的，另外偶尔也有一些小爬虫不按规矩狂发请求。只是考虑到现在这个 256 MB 内存的 VPS 始终不是长久之计，因此还是想把博客迁移到其它服务器上。</p>
<p>首先看了看 HighSpeedWeb 现有的<a href="https://billing.highspeedweb.net/cart.php?gid=19">套餐</a>，512 MB 内存以上的 OpenVZ 或 KVM 套餐现在价格也都不太便宜。一番参考之后我准备先将博客迁移到 IBM <a href="https://zohead.com/archives/ibm-bluemix-docker/">Bluemix</a> 容器平台顶着，因为看起来 Bluemix 容器系统里能使用的突发内存量还是比较多的，而且毕竟目前 Bluemix 平台在我这几个月的使用感受来看除了计费不太清晰之外其它方面还算比较稳定的。</p>
<p>现在博客域名的 A 记录已经修改，你现在看到的页面就是运行在 Bluemix 容器上的了。另外最近碰到好几次 HTTP 的博客网站老是被运营商插入广告代码，于是我也直接禁用了 HTTP 支持，现在必须以 HTTPS 方式访问本博客了。这么一来只是略微可惜了原来 HighSpeedWeb 相当稳定的服务器了：</p>
<pre class="brush: bash; title: ; notranslate">
root@zoserver:~# uptime
 21:29:27 up 325 days,  6:16,  1 user,  load average: 0.00, 0.00, 0.00
</pre>
<p>HighSpeedWeb VPS 服务器已经稳定运行了将近一年时间了，基本上自从上次续费时不小心重启了一下服务器之后 DNSPod 的监控就没有再报过警了。因此这段时间如果 Bluemix 容器万一出什么幺蛾子还能尽快切换回 HighSpeedWeb，最后还是希望现在的 Bluemix 容器能尽量稳定运行了，祝大家元宵节玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/vps-anti-spider/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>Bluemix容器系统更新udev的问题</title>
		<link>https://zohead.com/archives/bluemix-udev/</link>
		<comments>https://zohead.com/archives/bluemix-udev/#comments</comments>
		<pubDate>Tue, 29 Nov 2016 14:18:02 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Bluemix]]></category>
		<category><![CDATA[deb]]></category>
		<category><![CDATA[udev]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1306</guid>
		<description><![CDATA[Bluemix 更新 udev 的问题 最近在 IBM Bluemix 管理控制台中查看容器状态时发现一直会报 策略违例 问题，进入容器详细信息界面，可以看到漏洞顾问程序扫描到的容器系统中存在的违例情况，主要是系统中某些软件包版本比较老，Bluemix 建议进行安全更新升级： 由于我的 Bluemix 容器使用的是 Ubuntu 14.04 系统，起初想着直接运行 apt-get 命令将系统中的软件包都进行升级应该就差不多可以搞定了： 但是在升级 udev 软件包的时候发现能正确下载软件包但升级失败，提示 /etc/modprobe.d 内核模块配置目录无法正常写入： 我如果跳过升级 udev [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="bluemix-update-udev-issue">Bluemix 更新 udev 的问题</h2>
<p>最近在 IBM Bluemix 管理控制台中查看容器状态时发现一直会报 策略违例 问题，进入容器详细信息界面，可以看到漏洞顾问程序扫描到的容器系统中存在的违例情况，主要是系统中某些软件包版本比较老，Bluemix 建议进行安全更新升级：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370618/bluemix-violation.jpg" alt="Bluemix容器策略违例" title="Bluemix容器策略违例"></p>
<p>由于我的 Bluemix 容器使用的是 Ubuntu 14.04 系统，起初想着直接运行 <code>apt-get</code> 命令将系统中的软件包都进行升级应该就差不多可以搞定了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# apt-get update
root@instance-007a20ff:~# apt-get upgrade
</pre>
<p>但是在升级 udev 软件包的时候发现能正确下载软件包但升级失败，提示 <code>/etc/modprobe.d</code> 内核模块配置目录无法正常写入：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# apt-get install udev
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages will be upgraded:
  udev
1 upgraded, 0 newly installed, 0 to remove and 26 not upgraded.
28 not fully installed or removed.
Need to get 735 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ trusty-updates/main udev amd64 204-5ubuntu20.19 [735 kB]
Fetched 735 kB in 1s (437 kB/s)
(Reading database ... 30255 files and directories currently installed.)
Preparing to unpack .../udev_204-5ubuntu20.19_amd64.deb ...
Adding 'diversion of /bin/udevadm to /bin/udevadm.upgrade by fake-udev'
Unpacking udev (204-5ubuntu20.19) over (204-5ubuntu20.15) ...
dpkg: error processing archive /var/cache/apt/archives/udev_204-5ubuntu20.19_amd64.deb (--unpack):
 unable to create `/etc/modprobe.d/fbdev-blacklist.conf.dpkg-new' (while processing `./etc/modprobe.d/fbdev-blacklist.conf'): Permission denied
dpkg-deb: error: subprocess paste was killed by signal (Broken pipe)
Removing 'diversion of /bin/udevadm to /bin/udevadm.upgrade by fake-udev'
Processing triggers for ureadahead (0.100.0-16) ...
Errors were encountered while processing:
 /var/cache/apt/archives/udev_204-5ubuntu20.19_amd64.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)
</pre>
<p>我如果跳过升级 udev 包，又会由于 cpio 等软件包依赖新版本的 udev 包导致其它的软件包也无法正常升级，因此最好能解决 udev 包的升级问题。</p>
<p>接着我测试在报错的 <code>/etc/modprobe.d</code> 目录中创建文件或者拷贝文件，发现始终都是报 Permission denied 错误。看起来很有可能是由于 Bluemix 容器的限制导致无法在该目录中进行写操作。要解决 udev 包升级的问题看来需要重新打包 udev 进行安装。</p>
<h2 id="repack-udev">重新打包 udev</h2>
<p>首先使用 <code>apt-get</code> 命令下载最新版本的 udev 安装包：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# apt-get download udev
Get:1 http://archive.ubuntu.com/ubuntu/ trusty-updates/main udev amd64 204-5ubuntu20.19 [735 kB]
Fetched 735 kB in 0s (905 kB/s)
</pre>
<p>创建用于解压缩及重新构建 deb 包的目录：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# mkdir extract extract/DEBIAN build
</pre>
<p>开始解压缩 deb 安装包中的文件以及专用的 control 文件：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg -X udev_204-5ubuntu20.19_amd64.deb extract/
root@instance-007a20ff:~# dpkg -e udev_204-5ubuntu20.19_amd64.deb extract/DEBIAN/
</pre>
<p>接着就可以删除不需要的 <code>fbdev-blacklist.conf</code> 文件了，需要注意的是 control 目录中也需要移除对应项：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# rm -f extract/etc/modprobe.d/fbdev-blacklist.conf
root@instance-007a20ff:~# sed -i '/fbdev-blacklist.conf/d' extract/DEBIAN/conffiles
</pre>
<p>最后就可以打包新的 deb 安装文件了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg-deb -b extract build
dpkg-deb: warning: 'extract/DEBIAN/control' contains user-defined field 'Original-Maintainer'
dpkg-deb: warning: ignoring 1 warning about the control file(s)

dpkg-deb: building package `udev' in `build/udev_204-5ubuntu20.19_amd64.deb'.
</pre>
<h2 id="upgrade-udev">升级 udev 相关软件包</h2>
<p>到这一步就可以直接用 dpkg 命令安装我们重新打包的 udev 安装文件了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg -i build/udev_204-5ubuntu20.19_amd64.deb
(Reading database ... 30255 files and directories currently installed.)
Preparing to unpack .../udev_204-5ubuntu20.19_amd64.deb ...
Adding 'diversion of /bin/udevadm to /bin/udevadm.upgrade by fake-udev'
Unpacking udev (204-5ubuntu20.19) over (204-5ubuntu20.15) ...
dpkg: dependency problems prevent configuration of udev:
 udev depends on libdbus-1-3 (&gt;= 1.0.2); however:
  Package libdbus-1-3:amd64 is not configured yet.
 udev depends on libudev1 (= 204-5ubuntu20.19); however:
  Package libudev1:amd64 is not configured yet.

dpkg: error processing package udev (--install):
 dependency problems - leaving unconfigured
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Processing triggers for ureadahead (0.100.0-16) ...
Errors were encountered while processing:
 udev
</pre>
<p>这里的报错信息可以先不用管，后面我们再运行 <code>apt-get upgrade</code> 命令升级完其它的软件包就没有问题了。此时用 dpkg 命令就可以检查新的 udev 包状态：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg -l udev
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                   Version          Architecture     Description
+++-======================-================-================-=================================================
ii  udev                   204-5ubuntu20.19 amd64            /dev/ and hotplug management daemon
</pre>
<p>将 Bluemix 报告的有安全风险的软件包都进行升级，等到 Bluemix 重新进行漏洞扫描之后，再访问管理控制台就可以看到 Ubuntu 容器系统已经不会报策略违例了。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/bluemix-udev/feed/</wfw:commentRss>
		<slash:comments>2</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>
