Meteor Docker部署详解 – 简书

来源: Meteor Docker部署详解 – 简书

很早以前,就有读者要求我详细讲讲Meteor的部署,虽然我在极客学院的视频中讲解过个大概,但是实际操作中大家还是遇到了不少问题。如果大家曾经试图在国内部署Meteor的程序或者应用的话,会感到非常沮丧,因为GFW的原因,很多东西都不明不白地「挂了」。尤其是国外已经有一系列自动化部署工具(如Docker image)的时候,却怎么用都用不了,或者没办法改(或懒得改),只得手动一步步部署,这对于我们使用Meteor的热情还是打击很大的。

所以,这里我也花了几天,研究了一下部署Meteor应用的简便方式,以供大家参考。

0. 遇到问题

之前在视频中有讲到过,部署meteor应用最方便的方法就是使用meteor-up这个项目的mupx分支。这是一种基于Docker的自动化部署方式,只需要在本地写一些配置文件,我们就可以自动把本地的代码库部署到远程服务器上了。

但在国内,我们却经常失败,主要原因有三:

  1. Docker Hub被墙,我们无法直接使用Docker官方镜像源
  2. NPM被墙,我们无法下载安装npm相应扩展包
  3. meteor本身不稳定,有时候国内packages下载较慢

不瞒大家说,当初选择meteor的一大主要原因就是meteor的官方扩展包源没有被墙,meteor add添加扩展包的时候速度还行,做prototype开发比较方便。但是,随着meteor越来越受到关注,又开始拥抱NPM,如何科学获得官方包就提上了议事日程。

这里插一句,人大咋从来没有程序员代表,问问这么多编程必备的网站被墙的问题。天朝封锁最厉害的互联网居然是天朝唯一能拿的出手和美帝比肩的领域,不得不说是一种讽刺。

1. Docker和NPM

1.1 Docker 和 DaoCloud

DaoCloud

Docker翻墙一般使用DaoCloud提供的加速器服务,我们来看一下它的描述:

DaoCloud 加速器是广受欢迎的 Docker 工具,解决了国内用户访问 Docker Hub 缓慢的问题。DaoCloud 加速器经历了两个版本,1.0 版本主要采用了 Docker Registry Mirror 的功能,结合国内的 CDN 服务,为用户提升镜像下载的速度;2.0 版本加入了 DaoCloud 大量自主研发的协议层优化,并提供了可以替代 Docker Pull 的客户端,完美解决国内获取 Docker 镜像 metadata 的问题,并再次成倍提升下载速度。

至于如何安装并使用dao,可以看一下我之前写的文章使用DaoCloud安装Docker和镜像

首先,肯定是注册DaoCloud账户,然后进入Dashboard,点击「加速器」,然后点击「立即开始」,这里进入选择界面,选择你的主机,这里以Ubuntu为例。

Ubuntu

然后我们可以看到,使用这个命令进行Docker的安装:

curl -sSL https://get.daocloud.io/docker | sh

安装过程结束后,可执行下面命令验证安装结果。如果看到输出 docker start/running 就表示安装成功。

sudo service docker status

点击「安装好了」,接着安装我们的主机监控程序:

curl -sSL https://get.daocloud.io/daomonit/install.sh | sh -s 72346df3cbe80你自己的token8bcc77d434518

这样,我们的Docker和dao就全部安装完成。

2. NPM

由于众所周知的网络原因,npm install命令行从npm官方源拖代码时会遇上麻烦。一般的方法是将npm仓库源替换为国内镜像:

npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

但是由于我们是在Docker中进行部署,所以我们需要在相应的Docker image中加入这两条修改镜像源的命令。至于在哪里修改,我们则需要看看mupx的内容了。

3. meteor-up with docker

首先,需要说明的是,这里的mupx指的是一种基于Docker部署meteor应用的工具,采用node.js和shell script编写,等mupx稳定之后,将合并到mup分支中。

可以看到,实际上有两个采用Docker的meteor-up项目:

arunoda mupx

kadirahq mup

其中,第二个是mupx的新项目地址,从更新上也可以看出,第一个repo已经停止更新,迁移到第二个kadirahq下面了。但是由于mupmupx重复的问题,第二个repo的命令需要自己编译一下,我们clone这个项目,然后来自己编译它:

git clone https://github.com/kadirahq/meteor-up
cd meteor-up
npm install
npm link

这里,如果你之前安装过(npm install -g mup)mup的话,需要删除mup和它的link,不然这里npm link会出错。

在新的meteor-up项目中,这里我们使用mup.js代替之前的mup.json。原因是我们可以在这里面写JavaScript代码,来处理例如读写ssh key文件等事情。一个示例的mup.js文件如下:

module.exports = {
  servers: {
    one: {
      host: '1.2.3.4',
      username: 'root'
      // pem:
      // password:
      // or leave blank for authenticate from ssh-agent
    }
  },

  meteor: {
    name: 'app',
    path: '../app',
    servers: {
      one: {}, two: {}, three: {} //list of servers to deploy, from the 'servers' list
    },
    env: {
      ROOT_URL: 'app.com',
      MONGO_URL: 'mongodb://localhost/meteor'
    },
    logs: { //optional
      driver: 'syslog',
      opts: {
        url:'udp://syslogserverurl.com:1234'
      }
    }
    dockerImage: 'madushan1000/meteord-test', //optional
    deployCheckWaitTime: 60 //default 10
  },

  mongo: { //optional
    oplog: true,
    port: 27017,
    servers: {
      one: {},
    },
  },
};

4. mup详解

下面,我们就来看一下这个repo的结构和内容,它到底干了些什么。

├── index.js
├── lib
│   ├── execute.js
│   ├── modules
│   │   ├── default
│   │   │   ├── __tests__
│   │   │   │   └── index.js
│   │   │   ├── index.js
│   │   │   └── template
│   │   │       ├── mup.js
│   │   │       └── settings.json
│   │   ├── docker
│   │   │   ├── __tests__
│   │   │   │   └── index.js
│   │   │   ├── assets
│   │   │   │   └── docker-setup.sh
│   │   │   └── index.js
│   │   ├── index.js
│   │   ├── meteor
│   │   │   ├── __tests__
│   │   │   │   └── index.js
│   │   │   ├── assets
│   │   │   │   ├── meteor-deploy-check.sh
│   │   │   │   ├── meteor-setup.sh
│   │   │   │   ├── meteor-start.sh
│   │   │   │   ├── meteor-stop.sh
│   │   │   │   ├── templates
│   │   │   │   │   ├── env.list
│   │   │   │   │   └── start.sh
│   │   │   │   └── verify-ssl-config.sh
│   │   │   ├── build.js
│   │   │   └── index.js
│   │   ├── mongo
│   │   │   ├── __tests__
│   │   │   │   └── index.js
│   │   │   ├── assets
│   │   │   │   ├── mongo-setup.sh
│   │   │   │   ├── mongo-start.sh
│   │   │   │   ├── mongo-stop.sh
│   │   │   │   └── mongodb.conf
│   │   │   └── index.js
│   │   ├── proxy
│   │   │   ├── __tests__
│   │   │   │   └── index.js
│   │   │   └── index.js
│   │   └── utils.js
│   ├── mup-api.js
│   └── updates.js
├── package.json
├── scripts
│   └── mocha-bootload.js
└── tests

可以看到,我们的主要模块有这五个default, docker, meteor, mongo, proxy。我们来分别讲解一下:

4.1 default

default里面定义了一些mup的基本命令,包括deploy,help,init,logs,reconfig,restart,setup,start,stop等。

4.2 docker

使用mup setup命令时,执行的是assets/docker-setup.sh这个脚本,这个脚本里的内容就是安装Docker。当然,由于墙的原因,在国内是安装不了Docker的,所以我们可以通过1.1讲述的那样,采用DaoCloud手动安装Docker。注意,这里setup命令会首先检测是否安装过Docker,如果安装过,那么不会重复安装了。

4.3 mongo

可以看到,在mongo/assets/mongo-start.sh中,我们看到这样一行,获取官方的mongodb最新镜像,然后运行:

sudo docker pull mongo:latest

sudo docker run \
  -d \
  --restart=always \
  --publish=127.0.0.1:27017:27017 \
  --volume=/var/lib/mongodb:/data/db \
  --volume=/opt/mongodb/mongodb.conf:/mongodb.conf \
  --name=mongodb \
  mongo mongod -f /mongodb.conf

使用docker pull肯定是不行的,会被墙,所以这里我们把docker pull改为dao pull

4.4 meteor

index.js中,我们定义了start.sh中image的名字,也就是我们使用的基础Docker镜像。

list.copy('Pushing the Startup Script', {
  src: path.resolve(__dirname, 'assets/templates/start.sh'),
  dest: '/opt/' + config.name + '/config/start.sh',
  vars: {
    appName: config.name,
    useLocalMongo: api.getConfig().mongo ? 1 : 0,
    port: config.env.PORT || 80,
    sslConfig: config.ssl,
    logConfig: config.log,
    image: config.dockerImage || 'meteorhacks/meteord:base'
  }
});

可以看到,假如我们没有在mup.js中另外指定image的话,默认image则是meteorhacks/meteord:base。这里我们改成loongmxbt/meteord:base,你也可以修改成自己的meteord base image。

然后我们看一下meteor/assets/templates/start.sh这个文件,其中有这几行:

set +e
docker pull <%= image %>
set -e

这里和上面一样,将docker pull改为dao pull。这里的<%= image %>就是我们之前指定的image,默认为meteorhacks/meteord:base。在下一个部分里,我们详细看看这个image做了些啥。

接着,我们会运行这个meteorhacks/meteord:base

docker run \
  -d \
  --restart=always \
  --publish=$PORT:80 \
  --volume=$BUNDLE_PATH:/bundle \
  --hostname="$HOSTNAME-$APPNAME" \
  --env-file=$ENV_FILE \
  <% if(useLocalMongo)  { %>--link=mongodb:mongodb --env=MONGO_URL=mongodb://mongodb:27017/$APPNAME <% } %>\
  <% if(logConfig && logConfig.driver)  { %>--log-driver=<%= logConfig.driver %> <% } %>\
  <% for(var option in logConfig.opts) { %>--log-opt <%= option %>=<%= logConfig.opts[option] %> <% } %>\
  --name=$APPNAME \
  <%= image %>

如果还设置了SSL的话,我们会多运行一个meteorhacks/mup-frontend-server作为前端反代,之后,我们也会看看这个前端反代服务器是什么:

<% if(typeof sslConfig === "object")  { %>
  # We don't need to fail the deployment because of a docker hub downtime
  set +e
  dao pull meteorhacks/mup-frontend-server:latest
  set -e
  docker run \
    -d \
    --restart=always \
    --volume=/opt/$APPNAME/config/bundle.crt:/bundle.crt \
    --volume=/opt/$APPNAME/config/private.key:/private.key \
    --link=$APPNAME:backend \
    --publish=<%= sslConfig.port %>:443 \
    --name=$APPNAME-frontend \
    meteorhacks/mup-frontend-server /start.sh
<% } %>

小贴士:这里你可能会看到诸如set +eset -e的情况。set -e告诉bash一但有任何一个语句返回非真的值,则退出bash。使用-e的好处是避免错误滚雪球般的变成严重错误,能尽早的捕获错误。如果你必须使用返回非0值的命令,或者你对返回值并不感兴趣,或者你需要暂时关闭错误检查功能,就可以用set +eset -e包裹命令:
set +e
command1
command2
set -e
这样,如果docker hub挂了,我们的deploy也不会因此中断。

4.5 lib/updates.js

lib/updates.js中,我们看到有一行定义const uri的,我们依旧将它改成淘宝的uri:

const uri = 'https://registry.npm.taobao.org/npm';

可以看到,这里用到了silent-npm-registry-client这个包,具体的API可以查看npm-registry-client的文档

5. meteorhacks/meteord:base

5.1 Dockerfile

首先看这个Dockerfile,它主要运行了两个shell script:

RUN bash $METEORD_DIR/init.sh

EXPOSE 80
ENTRYPOINT bash $METEORD_DIR/run_app.sh

接着看看这个init.sh

5.2 init.sh

可以看到,主要有以下这四个脚本构成,它们都在scripts/lib目录下:

bash $METEORD_DIR/lib/install_base.sh
bash $METEORD_DIR/lib/install_node.sh
bash $METEORD_DIR/lib/install_phantomjs.sh
bash $METEORD_DIR/lib/cleanup.sh

其中,install_base安装了curl命令,install_node安装node和npm,install_phantomjs安装了phantomjs,cleanup删除了一些临时文件。

5.3 install_node

可以看到这里的scripts/lib/install_node.sh中,下载了现在meteor所使用的node版本,也就是0.10.41,当然,这是个比较古老的版本,据说1.3要换一个比较新的版本。

cd /tmp
curl -O -L http://nodejs.org/dist/v${NODE_VERSION}/${NODE_DIST}.tar.gz
tar xvzf ${NODE_DIST}.tar.gz
rm -rf /opt/nodejs
mv ${NODE_DIST} /opt/nodejs

ln -sf /opt/nodejs/bin/node /usr/bin/node
ln -sf /opt/nodejs/bin/npm /usr/bin/npm

这里我测试了一下,curl命令下载node在国内是可以成功的,只不过速度有点慢,为方便起见,这里还是更换下载地址为淘宝node源

curl -O -L http://npm.taobao.org/mirrors/node/v${NODE_VERSION}/${NODE_DIST}.tar.gz

当然,npm也是用不了的,所以这里还要修改npm的registry到淘宝镜像:

npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

这样,我们就大功告成了吗?显然还没有,我们继续看看install_phantomjs这个文件。

5.4 install_phantomjs

我们可以看到这一行

curl -L -O https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}.tar.bz2

显然,既有https,又是bitbucket,撞墙概率大大增加。幸运的是,淘宝也给我们提供了phantomjs镜像源。我们修改这个命令:

curl -L -O http://npm.taobao.org/mirrors/phantomjs/phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}.tar.bz2

安装phantomjs能够帮助你的站点被爬虫爬到。

小贴士:phantomjs是什么?PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API。它全面支持web而不需浏览器支持,其快速,原生支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG。 PhantomJS 可以用于页面自动化 ,网络监测,网页截屏,以及无界面测试等。它和meteor的spiderable这个扩展包有关,如果你使用meteor bundle部署应用,你必须安装phantomjs。如果你是部署在galaxy上,那么MDG已经帮你搞定了一切。

5.5 Docker Hub Automated Build

当然,如果不是在本地(国内)服务器上自行构建image的话,我们只需要添加这两行,发布到Docker Hub上构建即可。

npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

这里插入一点Docker Hub的自动构建使用方法,以便你今后自己的image发布。

我们fork了meteorhacks/meteord这个仓库之后,git clone到本地,然后创建一个新的分支,名为npm-taobao

git checkout -b npm-taobao

创建自动构建镜像

连接Github帐号

选择meteord项目

Build Settings中设置Docker file路径和tag

成功构建镜像

6. meteorhacks/mup-frontend-server

之前我们也提到过,如果你需要使用SSL证书,则需要添加一个Nginx前端的代理,也就是mup-frontend-server

lib/install_nginx.sh中,我们有这么一行下载nginx安装包。

wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz

当然,这个目前没有被墙,所以我们暂时不用担心哈。

7. 综合

综上所述,我们主要修改了两个repo,分别是mup命令的repo和meteord这个镜像的repo。

修改完成后的两个repo分别在这里:

所以我们的部署步骤变成了:

  1. 在服务器上,使用DaoCloud安装Docker和DaoCloud Toolbox加速器
  2. 使用mup init初始化配置
  3. 使用mup setup部署服务器环境
  4. 使用mup deploy部署本地应用至服务器

mup setup命令结果:

Started TaskList: Setup Docker
[www.meteorain.com] - setup docker
[www.meteorain.com] - setup docker: SUCCESS

Started TaskList: Setup Meteor
[www.meteorain.com] - Setup Environment

Started TaskList: Setup Mongo
[www.meteorain.com] - setup environment
[www.meteorain.com] - setup environment: SUCCESS
[www.meteorain.com] - copying mongodb.conf
[www.meteorain.com] - Setup Environment: SUCCESS
[www.meteorain.com] - copying mongodb.conf: SUCCESS

Started TaskList: Start Mongo
[www.meteorain.com] - start mongo
[www.meteorain.com] - start mongo: SUCCESS

服务器端docker ps结果:

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                        NAMES
9b87110206f8        loongmxbt/meteord:base   "/bin/sh -c 'bash $ME"   21 minutes ago      Up 21 minutes       0.0.0.0:80->80/tcp           meteorain
832f2cd61298        mongo                    "/entrypoint.sh mongo"   24 hours ago        Up 24 hours         127.0.0.1:27017->27017/tcp   mongodb

8. TODO List

  1. 你可能会发现,我们并没有改变安装Docker的步骤,那是因为daocloud加速器需要通过一个key来安装,这个key是网站产生的,后期考虑如果能轻松获取key的话做一下自动化的部署。
  2. 每次mup setup时,mongo都会重新安装,还是挺费时费流量的,考虑后期修改成Docker那样,检测是否已经有image。
  3. 使用meteord:base镜像重新deploy时,会重新pull镜像并构建,对于原型项目来说比较浪费时间。可以测试一下devbuild。
  4. SSL测试,使用Let’s Encrypt.

9. MeteoRain

部署成功后的Meteor1.3项目: http://www.meteorain.com/

Meteor全栈开发

参考资料

分享到: 更多 (0)