# 容器虚拟机

# 概述

如果您的应用的依赖包比较复杂,无法直接上传或者通过新浪云的云端构建程序构建的时候,您可以通过线上手工部署的方式来部署您的程序,手工部署的过程和使用云主机、VPS等差不多,下文中我们将一步一步教您如何在新浪云手工部署一个应用。

请注意

手工部署主要用来在线构建容器镜像、运行调试,线上环境请一定要打成镜像后再部署。

# 工作流程示意图

容器虚拟机的工作流程如下图所示:

容器虚拟机运行环境示意图
  1. 容器虚拟机本身是一个运行特定操作系统的容器,包含5GB磁盘空间,如果有更多的磁盘需求可以挂载本地存储或者共享存储;
  2. 如果容器虚拟机对外提供web、websocket服务,需要将以上服务暴漏在0.0.0.0的5050端口,外层负载均衡器会将请求转发到这个端口;
  3. 容器虚拟机本身使用SSH管理。

# 最大实例数量

容器虚拟机最多启动1个实例,如果需要多个实例分部署运行,请将虚拟机打包为镜像,通过创建镜像类应用运行这里创建的镜像。

说明

如果在多个实例运行的系统本身应该遵循分布式系统的要求,临时产生的文件不能有强依赖性,且永久存储的文件应该写到网络磁盘中,而不是本地。

# 创建应用

  1. 登录云应用管理平台https://sae.sinacloud.com (opens new window),如果没有账号请先注册;

  2. 点击“+创建应用”进入应用的创建页面; 点击创建应用进入应用管理页面

  3. 开发语言选择“容器虚拟机”;

  4. 选择一个操作系统及操作系统的版本;

  5. 选择单实例的配置; 选择容器虚拟机器配置

  6. 填写应用的二级域名前缀和应用名称;

# 管理应用

在创建完成后,进入应用的管理中心,左侧选择“运行环境管理”,进入“容器管理中心”:

进入容器虚拟机管理中心

# 通过Web Console管理

点击操作中的“终端”进入Web Console:

通过Web Console管理容器虚拟机

在Web终端中可以执行命令:

在Web终端中可以执行命令

# 通过SSH客户端管理

容器虚拟机的应用也可以通过SSH客户端管理,从“容器管理”页面点击“SSH登录”可以查询应用的SSH主机地址、端口、用户名;当前仅支持通过SSH密钥方式登录,详情请参考SSH客户端章节。

查看SSH登录信息

# 限制

# 允许的Cap

容器虚拟机有以下的Cap分类权限:

SETUID
SETGID
CHOWN
DAC_OVERRIDE
SETFCAP
FOWNER
NET_BIND_SERVICE
KILL
SYS_PTRACE
AUDIT_WRITE

除以上权限外,其他所有的Cap都不可以使用,因此诸如/usr/sbin/apachectl start或者/bin/systemctl restart httpd.service等启动命令都会被禁止,可以找到软件的绝对路径启动。

说明

例如apache软件安装在/usr/sbin/httpd绝对路径,可以使用/usr/sbin/httpd -k start直接启动。

# Mint 进程管理工具

# 说明

Mint是新浪云为所有的容器虚拟机提供的进程管理工具,可以在任何容器虚拟机实例中直接使用,Mint 的配置文件为 /etc/Procfile ,Procfile 中每一行定义一个要启动的进程,格式为:

进程名:启动命令

例如:

web: go run web.go -a :$PORT
worker: bundle exec ruby worker.rb

请注意

冒号后需要有空格,此文件为yaml格式。

# 支持的命令

Mint 支持如下命令:

[root@douyu1 /]# mint
Usage:
  mint reload               # Reload Procfile
  mint start     PROC       # Start a Process
  mint stop      PROC       # Stop a Process
  mint restart   PROC       # Restart a Process
  mint status               # Print Processes' status
  mint check                # Show entries in Procfile

请注意

Mint 会自动重启异常退出的进程,但是如果进程连续 3 次启动不到 1 分钟就退出,Mint 会认为进程无法启动而不再重启。

# Crontab 定时任务

容器虚拟机默认没有安装crond (opens new window),如果你需要使用容器虚拟机的 crontab 功能,您需要安装并运行 crond 进程。(以 CentOS 为例):

yum install cronie

将 crond 启动命令加入到 /etc/Procfile

crond: crond -n

最后,执行 mint start crond 启动 crond 进程。

# 打包镜像

您可以将容器虚拟机的环境及文件打包为一个容器的镜像,打包后的镜像可以用于新的应用启动时的参数,也可以用于备份。

# 启动命令

启动命令用于docker的CMD指令,在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号

如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:

echo $HOME

在实际执行中,会将其变更为:

[ "sh", "-c", "echo $HOME" ]

Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像真正的虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。

错误的启动命令

注意,以下是一个错误的启动命令示范。

service nginx start

然后发现容器执行后就立即退出了,此外还发现在容器内去使用 systemctl 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。

对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。

而使用 service nginx start 命令,则是希望 upstart 来以后台守护进程形式启动 nginx 服务。而刚才说了service nginx start会被理解为 CMD [ "sh", "-c", "service nginx start"],因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。

正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:

nginx -g "daemon off;"

也可以使用默认命令/bin/mint master,关于mint请参考mint-进程管理工具章节。

# 如何打包

  1. 点击“操作”区域的“打包镜像”按钮;
点击打包按钮
  1. 在弹出的框中输入“镜像名称”、“镜像版本”及“启动命令”,点击确认;
输入镜像信息
  1. 在打包过程中,容器的状态将变成“处理中”,等状态变成“运行中”时即表示打包已经完成;

  2. 从“镜像管理”标签,选择“所有镜像”分类,即可查询刚才创建的镜像

查询镜像列表

# 示例 - 安装openresty并对外提供HTTP服务

# 创建应用

从应用的创建页面创建一个“容器虚拟机”的应用,选择“centos”、“6.7”的操作系统:

创建一个容器虚拟机应用

# 进入容器管理页面

创建完成应用后,进入应用的管理中心:

进入容器管理中心

# 进入在线终端

从“操作”中点击“终端”进入web terminal:

进入web terminal 进入web terminal执行命令

# 编译openresty前的准备工作

先执行yum clean all清空本地的yum包列表缓存:

[root@v2centostest /]# yum clean all
Loaded plugins: fastestmirror
Cleaning repos: base extras updates
Cleaning up Everything

安装vim、perl、tar支持:

yum install -y vim tar perl

安装openresty的依赖包:

yum install -y pcre-devel openssl-devel gcc curl

下载openresty的源码:

cd /
mkdir openresty
cd openresty
curl 'https://openresty.org/download/openresty-1.13.6.2.tar.gz' -o openresty-1.13.6.2.tar.gz

解压压缩包:

tar -zxvf openresty-1.13.6.2.tar.gz

# 编译安装

开始编译:

cd openresty-1.13.6.2
./configure

看到以下输出为成功,否则请自行参考输出安装依赖包:

....
Configuration summary
  + using system PCRE library
  + using system OpenSSL library
  + using system zlib library
  nginx path prefix: "/usr/local/openresty/nginx"
  nginx binary file: "/usr/local/openresty/nginx/sbin/nginx"
  nginx modules path: "/usr/local/openresty/nginx/modules"
  nginx configuration prefix: "/usr/local/openresty/nginx/conf"
  nginx configuration file: "/usr/local/openresty/nginx/conf/nginx.conf"
  nginx pid file: "/usr/local/openresty/nginx/logs/nginx.pid"
  nginx error log file: "/usr/local/openresty/nginx/logs/error.log"
  nginx http access log file: "/usr/local/openresty/nginx/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp"
cd ../..
Type the following commands to build and install:
    gmake
    gmake install

编译和安装:

gmake && gmake install

有以下类似输出时说明安装成功:

...
cp conf/fastcgi.conf '/usr/local/openresty/nginx/conf/fastcgi.conf.default'
test -f '/usr/local/openresty/nginx/conf/uwsgi_params' \
                || cp conf/uwsgi_params '/usr/local/openresty/nginx/conf'
cp conf/uwsgi_params \
                '/usr/local/openresty/nginx/conf/uwsgi_params.default'
test -f '/usr/local/openresty/nginx/conf/scgi_params' \
                || cp conf/scgi_params '/usr/local/openresty/nginx/conf'
cp conf/scgi_params \
                '/usr/local/openresty/nginx/conf/scgi_params.default'
test -f '/usr/local/openresty/nginx/conf/nginx.conf' \
                || cp conf/nginx.conf '/usr/local/openresty/nginx/conf/nginx.conf'
cp conf/nginx.conf '/usr/local/openresty/nginx/conf/nginx.conf.default'
test -d '/usr/local/openresty/nginx/logs' \
                || mkdir -p '/usr/local/openresty/nginx/logs'
test -d '/usr/local/openresty/nginx/logs' \
                || mkdir -p '/usr/local/openresty/nginx/logs'
test -d '/usr/local/openresty/nginx/html' \
                || cp -R docs/html '/usr/local/openresty/nginx'
test -d '/usr/local/openresty/nginx/logs' \
                || mkdir -p '/usr/local/openresty/nginx/logs'
gmake[2]: Leaving directory `/openresty/openresty-1.13.6.2/build/nginx-1.13.6'
gmake[1]: Leaving directory `/openresty/openresty-1.13.6.2/build/nginx-1.13.6'
mkdir -p /usr/local/openresty/site/lualib /usr/local/openresty/site/pod /usr/local/openresty/site/manifest
ln -sf /usr/local/openresty/nginx/sbin/nginx /usr/local/openresty/bin/openresty

# 修改openresty监听的端口

cd /usr/local/openresty/nginx/conf
vim nginx.conf

按下小写i进入编辑模式,找到listen 80;除,将80改为5050CTRL+C后输入:wq保存退出。

将nginx的端口改为5050

# 将启动进程加到mint的管理中

openresty的启动方式为:

/usr/local/openresty/nginx/sbin/nginx -g 'daemon off;'

将以上启动脚本加到Procfile中:

vim /etc/Procfile

输入以下内容:

openresty: /usr/local/openresty/nginx/sbin/nginx -g 'daemon off;'

完整的文件内容和路径如下:

[root@v2centostest /usr/local/openresty/nginx/conf]# cat /etc/Procfile 
openresty: /usr/local/openresty/nginx/sbin/nginx -g 'daemon off;'

reload mint:

mint reload

查看进程状态:

[root@v2centostest /usr/local/openresty/nginx/conf]# mint status
openresty    RUNNING    pid 8644, uptime 0:00:25

可以看到openresty的进程已经在执行中:

[root@v2centostest /usr/local/openresty/nginx/conf]# ps -ajxfww
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0    13    13    13 pts/0     8653 Ss       0   0:00 /bin/bash
   13  8653  8653    13 pts/0     8653 R+       0   0:00  \_ ps -ajxfww
    0     1     1     1 ?           -1 Ssl      0   0:00 /bin/mint master
    1  8644  8644     1 ?           -1 S        0   0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx -g daemon off;
 8644  8645  8644     1 ?           -1 S       99   0:00  \_ nginx: worker process 

# 访问应用

这是通过浏览器已经可以访问应用:

openresty默认页面

# 打包镜像

如果您的应用需要支持大流量访问,需要水平扩展,可以应用的环境打包为镜像,然后用一个“容器”分类的应用运行。

从容器的管理页面点击“操作”中的“打包镜像”:

点击打包

输入镜像的信息:

输入镜像信息

说明

这里使用默认的/bin/mint master为启动名称,进程托管服务将扫描/etc/Procfile下的服务列表启动。

# 使用镜像启动新的应用

打包完成后就可以使用打包后的镜像启动新的应用,回到应用的创建页面,应用分类选择“Docker”,“部署方式”选择“镜像”,“镜像类型”选择“我的镜像”,从“选择镜像”中选择刚刚打包的镜像:

选择刚刚打包的镜像

选择一个应用的二级域名,选择实例的数量:

输入应用的二级域名

说明

基于镜像的应用不同于容器虚拟机,可以启动多个实例,因此可以横向扩展实例数量支撑大流量业务。

创建完成后从容器管理页面可以看到新启动的实例:

新启动的实例

从浏览器访问应用可以看到openresty的默认页面:

访问新启动的实例

进入web terminal可以看到nginx的进程已经启动:

[root@fromimageexample /]# ps -ajxfw
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0    16    16    16 pts/0       23 Ss       0   0:00 /bin/bash
   16    23    23    16 pts/0       23 R+       0   0:00  \_ ps -ajxfw
    0     1     1     1 ?           -1 Ssl      0   0:00 /bin/mint master
    1    13    13     1 ?           -1 S        0   0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx -g daemon off;
   13    15    13     1 ?           -1 S       99   0:00  \_ nginx: worker process  

# 计费说明

# 计费规则

计费的规则为:单实例价格 * 实例总数 * 实例的运行时长,按分钟计费。

其中单实例的价格可以从价格中心 (opens new window)查询。

例如单实例的配置为标准型,单价为0.06元每实例每小时,则一天的价格为:

0.06 * 24 = 1.44

# 流量计费

当前容器虚拟机的HTTP、HTTPS流量不计费。

# 协议支持

应用支持http、https和websocket协议(ws、wss)。

# 端口映射

# 工作原理

应用的负载均衡(新浪云提供)监听80和443端口,然后将请求转发到容器中的5050端口,示意图如下:

协议及端口示意

# 支持的端口

容器中可以启动多个端口,但是仅5050端口会映射到负载均衡上,其他的端口只能在本地访问。可以从环境变量中获取支持的端口,环境变量的key为:

PORT 

# 日志系统

# 容器日志分类

容器日志分类stdoutstderr分类,分别是标准输出和标准错误输出的捕获。

因为容器的磁盘空间有限,大量的写入可能会占满磁盘导致服务不可用,请从程序中配置日志输出路径,将日志写到stdoutstderr分类,其中:

  • /proc/1/fd/1stdout的绝对路径
  • /proc/1/fd/2stderr的绝对路径

# 查询容器stdout日志

  1. 进入应用的列表页面https://sae.sinacloud.com (opens new window)
  2. 点击操作处的管理进入应用的管理中心
  3. 左侧导航选择“日志及监控”,选择“日志中心”进入日志管理
  4. 选择“容器日志”的“stdout”分类 查询stdout分类日志

# 查询容器stderr日志

  1. 进入应用的列表页面https://sae.sinacloud.com (opens new window)
  2. 点击操作处的管理进入应用的管理中心
  3. 左侧导航选择“日志及监控”,选择“日志中心”进入日志管理
  4. 选择“容器日志”的“stderr”分类 查询stderr分类日志

# 文件系统

# 本地文件系统

实例在创建时将会分配5GB的本地磁盘,用于存放程序的代码和系统文件等,你可以从程序往本地写文件,容器虚拟机本地的文件是持久化存储,在重启后文件不会删除

说明

# 挂载共享存储

共享存储服务是为容器云提供的分布式存储服务,利用多副本写入实现数据高可靠性,并提升读取性能,可以提供数十M/S的峰值读写性能。

您可以通过挂载,将对应的共享存储卷映射进容器中,是容器获得本地文件读写和数据持久化功能。

通过共享存储,还可以在不同的容器之间共享数据,实现跨容器的数据共享和交互。

有关共享存储的更多介绍,请参考共享存储章节,请参考以下步骤挂载共享存储:

  1. 共享存储 (opens new window)管理面板创建一个共享存储;
  2. 云应用管理平台 (opens new window)应用列表页面点击“管理”进入应用的管理中心;
  3. 左侧导航选择“运行环境管理”,“容器管理”进入容器管理页面; 如何进入容器管理页面
  4. 点击“挂载管理”进入挂载管理页面; 进入磁盘挂载的页面
  5. 点击“+新增挂载”,存储的分类选择“共享存储”,从下拉列表选择刚刚创建的存储,输入一个挂载路径。 填写挂载的信息

    说明

    挂载路径是系统的绝对路径,如果你挂载的路径在本地文件系统已经存在,则挂载后本地的文件系统将被覆盖。

  6. 选择“立即重启容器”后挂载会立即生效,挂载完成后可以看到挂载的详情: 挂载详情

挂载完成后,从容器的实例中可以看到挂载的路径: 在容器中查看挂载的路径

共享存储中的文件支持FTP管理,详情请参考共享存储的FTP管理章节。

挂载完共享存储到本地的路径后,就可以在程序中将需要存储的文件写到挂载的路径下了。

# 挂载本地存储

本地存储服务是为容器云提供的本地问年间存储服务,可以和本地磁盘读写一样速度的磁盘。

您可以通过挂载,将对应的本地存储卷映射进容器中,是容器获得本地文件读写和数据持久化功能。

有关共享存储的更多介绍,请参考本地存储章节,请参考以下步骤挂载本地存储:

  1. 本地存储 (opens new window)管理面板创建一个本地存储;
  2. 云应用管理平台 (opens new window)应用列表页面点击“管理”进入应用的管理中心;
  3. 左侧导航选择“运行环境管理”,“容器管理”进入容器管理页面; 如何进入容器管理页面
  4. 点击“挂载管理”进入挂载管理页面; 进入磁盘挂载的页面
  5. 点击“+新增挂载”,存储的分类选择“本地存储”,从下拉列表选择刚刚创建的存储,输入一个挂载路径。 填写挂载的信息

    说明

    挂载路径是系统的绝对路径,如果你挂载的路径在本地文件系统已经存在,则挂载后本地的文件系统将被覆盖。

  6. 选择“立即重启容器”后挂载会立即生效,挂载完成后可以看到挂载的详情: 挂载详情

挂载完成后,从容器的实例中可以看到挂载的路径: 在容器中查看挂载的路径

本地存储中的文件支持FTP管理,详情请参考本地存储的FTP管理章节。

挂载完地存储到本地的路径后,就可以在程序中将需要存储的文件写到挂载的路径了。