Arukas樱花Docker容器及Endpoint体验

Arukas 容器介绍

之前我在了解目前主流的容器服务商时除了 IBM Bluemix 还知道了日本 Arukas 容器,不过一直都没有测试的机会,最近 Arukas 难得给我发了注册通过的邮件,最新的政策显示免费测试期会延长到 2017 年 6 月 30 日(未来收费策略待定)。

下面的基本操作都在 Arukas 控制面板:

https://app.arukas.io/

Arukas 容器限制

Arukas 容器还处于免费测试阶段,有一些限制需要先说明:

  • 不支持特权容器,无法使用 TUN/TAP 设备(目前的容器服务商基本都不支持);
  • 没有固定公网 IP 地址,容器可能每隔几天甚至几个小时就会重启;
  • 配置的容器内部端口会被映射到随机的外部端口;
  • 自定义 Endpoint 域名只能以 HTTPS 方式访问,对应的容器内部端口只支持 HTTP 协议(下面会详细介绍);
  • 不支持使用自定义的 Dockerfile 文件构建容器,只能使用 Docker hub 上的镜像;
  • 暂时不支持外部存储,因此容器里无法保存任何数据,每次重启容器数据会全部丢失;
  • 创建容器时只能配置 Dockerfile 中的 ENV(环境变量)和 CMD(启动执行命令)指令,不支持 RUNENTRYPOINT 等其它指令。

关于 Endpoint

通过参考 Arukas 日文版帮助文档,我们可以得知虽然 Arukas 容器没有固定的公网 IP 地址,容器开放的内部端口每次启动也都被映射到随机的外部端口,但容器的 Endpoint 地址是始终固定不变的,类似于这样:


https://xxx.arukascloud.io/

经过测试我发现容器的 Endpoint 地址必须以 HTTPS 方式访问(HTTP 访问会直接返回 Connection refused),Endpoint 地址只对容器开放的第一个内部端口有效,而且该内部端口只支持 HTTP 协议,不支持 TCP 或者 UDP。

用户访问 Endpoint 子域名地址时并不是直接访问容器的当前公网地址,而是访问 Arukas 提供的 HAProxy 负载均衡服务器,HAProxy 负责转发并均衡容器下多个实例的 HTTP 请求,其网络拓扑图大概如下所示:

提示

Arukas 官网上对 Endpoint 的说明遗漏了比较重要的一点:如果容器配置里开启了多个对外开放的内部端口,那么 Endpoint 请求转发只会在所有这些端口的服务都已经运行的情况下才有效。

这样 Arukas 就能够在有限的服务器(内部端口映射到随机外部端口)和公网 IP 地址(固定 Endpoint 地址)情况下提供尽量多的容器访问支持。

构建容器

从上面的介绍来看虽然 Arukas 容器有很多体验不太好的限制,但其所在的樱花机房线路还是相当有吸引力的,立马准备建立容器看看运行效果。

为了测试 Arukas 容器的运行效果,我没有使用一些做好的通用 SSH 系统(例如:centos-ssh)或者 Shadowsocks 系统镜像,本文还是用的之前捣鼓过的 baseimage-docker Docker 镜像。

baseimage-docker 的问题

baseimage-docker 镜像虽然用起来很方便,支持轻量级的 runit 服务管理方案,但该镜像默认并没有开启 SSH 登录,如果需要开启 SSH 服务器或者执行自定义命令都需要修改 Dockerfile 文件中的 RUN 指令,具体可以参考该项目 GitHub 主页。

然而 Arukas 容器并不支持使用 RUN 命令配置执行自定义命令,也不能保存任何数据,因此 SSH 登录和执行自定义命令的问题需要通过其它方法解决。

幸好 baseimage-docker 还支持通过 CMD 指令开启运行 one-shot command 单次命令支持,对应的 CMD 指令类似这样:

/sbin/my_init -- COMMAND ARGUMENTS ...

容器系统的运行流程就相当于:

  • 运行所有启动脚本,例如 /etc/my_init.d/*/etc/rc.local
  • 启动所有 runit 服务;
  • 运行 my_init 参数中指定的自定义命令;
  • 自定义命令退出之后即停止所有 runit 服务。

因此我们可以在单次命令参数上做文章,实现开启 SSH 服务器、安装需要的软件并启动附加的服务。

容器基本配置

下面是我在 Arukas 控制面板建立的一个 Shadowsocks 服务器容器的配置:

  • Image 我使用的是最新的 baseimage-docker 镜像:phusion/baseimage:0.9.19
  • Instances 一般就用默认的一个实例;
  • Memory 可以根据需要选择 256 MB 或者 512 MB;
  • Endpoint 指定 Endpoint 子域名地址;
  • Port 设置里可以将容器中最多 20 个 TCP 或者 UDP 端口对外开放,我填了 80、22 外加一个 Shadowsocks 服务器端口,需要注意的就是上面的固定 Endpoint 地址只支持第一个端口;
  • ENV 环境变量根据需要设置,我在这里指定了 SSH key 和 Shadowsocks 服务器配置;
  • CMD 设置比较关键,我建立的 Shadowsocks 测试容器是这样的:

    /sbin/my_init -- sh -c 'curl -o /arukas-baseimage-xenial-ss.tar.gz https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz && tar -C / -xzf /arukas-baseimage-xenial-ss.tar.gz && /etc/my_init.d/01_start_ss_ssh.sh'
    

我制作了一个适用于最新 baseimage 系统的 Shadowsocks 服务器安装包:

https://zohead.com/downloads/arukas-baseimage-xenial-ss.tar.gz

容器系统启动时会自动将安装包下载到根目录下解压缩,然后执行其中的 /etc/my_init.d/01_start_ss_ssh.sh 启动脚本:

#!/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 [ "x$SSH_AUTHORIZED_KEYS" = "x" ]; then
	/usr/sbin/enable_insecure_key
else
	mkdir ~/.ssh
	echo "$SSH_AUTHORIZED_KEYS" | sed 's/\\n/\n/g' > ~/.ssh/authorized_keys
	chmod 400 ~/.ssh/authorized_keys
fi

# Start web server
env | grep -E '^MARATHON_HOST=|MARATHON_PORT_' > /home/wwwroot/default/marathon.conf
if [ "x$MARATHON_HOST" != "x" ]; then
	getent hosts $MARATHON_HOST | awk '{print "MARATHON_HOST_IP="$1; exit;}' >> /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="${T_NAME:17}"
	echo "${!T_NAME}" > /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

该启动脚本的执行流程如下:

  • 首先启用 SSH 服务器,并产生 SSH host key;
  • 如果容器配置的 ENV 环境变量里存在 SSH_AUTHORIZED_KEYS 变量,那么就使用这个变量的内容作为 SSH 公钥(容器配置截图里我就使用此方法指定自定义公钥),如果不存在则使用 baseimage 提供的默认公钥;
  • 接着获取 Arukas 容器的当前临时域名、公网 IP 地址以及每个内部端口映射等内容输出到 /home/wwwroot/default Web 服务器目录下的 marathon.conf 文件;
  • 在默认的 80 端口启动 Web 服务器以支持 Endpoint 地址访问;
  • 遍历容器配置的所有 ENV 环境变量,如果存在以 SHADOWSOCKS_CFGS_ 开头的变量,就在 /etc/shadowsocks-libev 目录下生成 Shadowsocks 服务器配置文件,配置文件内容就是该变量的值。

    例如我使用的一个 Shadowsocks 配置变量(将 Shadowsocks 服务器的 JSON 配置写成一行):

    SHADOWSOCKS_CFGS_xxx={ "server":"0.0.0.0", "server_port":17374, "local_port":1080, "password":"arukas-ss-svr", "timeout":30, "method":"aes-256-cfb" }
    

    这样就会自动产生 /etc/shadowsocks-libev/xxx.json 配置文件;

  • 依次启动 ENV 环境变量里指定的所有 Shadowsocks 服务器(可以指定多个以 SHADOWSOCKS_CFGS_ 开头的变量);
  • 最后以前台方式启动 SSH 服务器,这样就能实现 SSH 远程登录,而且 baseimage 容器也不会退出。

容器运行状态

Arukas 容器启动运行成功之后,你可以在控制面板里查看运行状态:

这里可以看到容器的 Endpoint 地址、用于直接访问容器的临时域名和映射的外部端口等信息。

容器启动之后会将当前的临时域名、内部端口映射等信息输出到系统环境变量中,这样上面的 /etc/my_init.d/01_start_ss_ssh.sh 启动脚本才能直接获取到这些信息,有兴趣的朋友可以在容器中确认:

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

我们可以看看 Arukas 容器的服务器信息,其使用的是 CoreOS 系统(内核版本为 4.9.9):

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

内存是所有容器一起共享的(如果容器使用的内存超过配置中的限制太多会被重启):

root@837bc28c1c36:~# free -m
              total        used        free      shared  buff/cache   available
Mem:          24115        4300        1520         715       18295       17563
Swap:          4095          24        4071

CPU 配置还算给力,使用的是 8 个 Xeon E5-2650:

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

只是容器内核对文件系统的支持比较坑爹:

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

不支持 NFS 挂载远程目录,也不支持 FUSE 用户层文件系统来实现挂载 Amazon S3 存储之类的,这对于目前还不支持外部存储的 Arukas 容器来说实在不算友好。

使用 Shadowsocks 容器

Shadowsocks 效果

客户端使用上面指定的 Shadowsocks 服务器配置就可以爬墙上网了,当然使用前需要将 Shadowsocks 服务器地址更换为 Arukas 容器的临时域名,并将服务器端口改为当前映射出来的外部端口(不能直接使用内部端口)。

这个是我在宿舍江苏移动宽带网络下观看 Youtube 1080p 视频的效果,8 Mbps 的速度看起来还是比较给力的:

换到南京电信网络进行路由追踪,可以看到 ping 值还不到 50 ms:

OpenWRT 自动更新 Shadowsocks 配置

由于 Arukas 容器的外网地址和端口在每次启动时都不是固定的,容器重启后 Shadowsocks 客户端都需要获取当前临时域名和外部端口,这样用起来还是不太方便。特别对于我这种需要 OpenWRT 路由器直接翻墙的用户来说,每次修改 OpenWRT 设置就太麻烦了。

好消息就是 Arukas 容器的 Endpoint 地址还是固定的,我写的启动脚本也自动将容器当前的临时域名、公网 IP 地址、所有外部端口都输出到文件了,可以直接通过 Endpoint 地址访问。

因此我又专门写了一个适用于 OpenWRT 系统的更新 Arukas 容器配置的脚本:arukas-ss.sh,可添加到路由器的 Shadowsocks 服务脚本中,也可以设置成定时任务:

#!/bin/sh
[ $# -ge 2 ] || exit 1

[ -f /tmp/.marathon.conf ] && mv /tmp/.marathon.conf /tmp/.marathon.conf.bak

IND=0
while [ $IND -lt 20 ]; do
	let IND++
	[ -f /tmp/.marathon.conf ] && rm -f /tmp/.marathon.conf
	curl -s -k -f -o /tmp/.marathon.conf https://zohead-server.arukascloud.io/marathon.conf
	[ $? -eq 0 ] && break
	sleep 1
done

[ -s /tmp/.marathon.conf ] || exit 1
echo "Fetch server config after $IND tries."

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>/dev/null
if [ "x$MARATHON_HOST" = "x" ]; then
	rm -f /tmp/.marathon.conf
	exit 1
fi

while [ $# -gt 1 ]; do
	CMD="echo \${MARATHON_PORT_$2}"
	SS_PORT=`eval $CMD`

	if [ -f $1 -a "x$SS_PORT" != "x" ]; then
		if [ "x$MARATHON_HOST_IP" != "x" ]; then
			MARATHON_HOST="$MARATHON_HOST_IP"
		fi
		sed -i -e 's/"server"[^:]*:[^,]*,/"server" : "'$MARATHON_HOST'",/' -e 's/"server_port"[^:]*:[^,]*,/"server_port" : "'$SS_PORT'",/' $1
		SS_PID=`pgrep -f "ss-redir -c $1"`
		if [ $? -eq 0 ]; then
			kill $SS_PID >/dev/null 2>&1
			kill -9 $SS_PID >/dev/null 2>&1
			start-stop-daemon -x /usr/bin/ss-redir -b -S -- -c $1 -b 0.0.0.0
		fi
	fi
	shift
	shift
done

arukas-ss.sh 脚本的使用方法为:

arukas-ss.sh shadowsocks-1.json 17374 shadowsocks-2.json 2233 ... ...

第一个参数为 Shadowsocks 客户端 JSON 配置文件的完整路径,第二个参数为该文件对应的 Arukas Shadowsocks 服务器配置的内部监听端口号,arukas-ss.sh 脚本支持多个本地 Shadowsocks 配置文件和服务器内部监听端口号,依次类推即可。

使用前需要替换 arukas-ss.sh 脚本中的 Endpoint 地址,考虑到 Arukas 容器目前的 Endpoint 访问还不太稳定的情况,脚本里也加了多次访问尝试,我的 Endpoint 地址是这样:


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

另外可以修改 OpenWRT 的定时任务配置实现自动更新,例如在 /etc/crontabs/root 文件中增加一行:

30 * * * * /etc/arukas-ss.sh /etc/shadowsocks.json 17374

这就表示在每个小时的第 30 分钟执行 arukas-ss.sh 脚本更新 Arukas Shadowsocks 配置,一般这样就能对付 Arukas 容器每隔几个小时自动重启的问题了。

后记

经过我这段时间的试用,Arukas 容器做为爬墙服务器还是比较顺畅的,如果想捣鼓一个简单的 API 服务器也还凑合,不过由于 Arukas 目前还不支持外部存储,想拿来跑个博客程序之类的就要涉及到如何保存和恢复网站数据的问题了。

当然容器只能用 Endpoint 地址访问也是一个大问题,经过测试我也发现如果容器内开放的内部端口稍微一多(可能超过 3 个)时,Arukas 的 HAProxy 转发服务就不太稳定了,经常出现 502 或者 503 错误。如果容器内开放了 UDP 端口,Endpoint 地址甚至可能会处于一直都不能访问的状况。

如果 Arukas 容器在内测期间能解决这些问题,后续正式使用价格也不算离谱的话,感觉应该还是有些竞争力的,最后祝大家 4 月玩的开心。