入门实战:使用Docker构建一个nodejs服务

关于Docker

对于我们大多数前端,接触服务器部署、运维的工作很少,潜意识可能会忽略 Docker 相关信息,但是说vmware、visualbox,我想前端们再熟悉不过了,还记得当年IE67891011怎么调试的吗。。。

Docker 是一个开源项目,基于 Go 语言实现,是一套轻量级的操作系统虚拟化解决方案。用户操作 Docker 的容器就像操作一个快速轻量级的虚拟机一样简单。

想想当年我们曾经部署过的apache、nginx、php、mysql、nodejs,参考网上各种lamp,lnmp搭建教程,在我们买的虚拟主机上折腾良久,搭建了一套博客,开始写文章。之中的苦与乐,我想有很多人都经历过。

场景

我是前端,某一天,我写了一个nodejs服务,我想后端帮我部署在服务器上。

在使用 Docker 以前,他脑海会迅速想到以下问题:

  • tmd我的服务器没有nodejs环境。。。
  • 你写的代码安不安全,这个可是要对外访问的。。。
  • 你的代码依赖太多,有详细部署文档吗。。。

在使用 Docker 之后,他会跟你说:

  • 给我Dockerfile,帮你build一下

好处

Docker 带来了什么,粗浅的理解:

  • 拥有沙盒机制,系统隔离,绿色无污染;
  • 应用服务化,端口即服务;
  • 一次创建或配置,可以在任意地方正常运行;
  • 分享。

准备折腾

假装你已经初步了解了 Docker,并且你拥有一台可以折腾的服务器,而且已经安装 Docker。如果没有请看这里

现在我们来看个例子。

项目驱动学习:状态条 statusbar

项目描述

通过url访问,传不同参数:title、label、color,来生成一个svg图片。效果如下:

http://statusbar.domain/?title=开发中&label=状态&color=orange

http://statubar.domain/?title=90&label=进度

项目结构

statusbar\  
  |
  |--index.js
  |--package.json
  |--README.md
  |--Dockerfile
  |--.gitignore
  \--.dockerignore

项目本身很简单,只有一个js文件,代码如下:

"use strict";

const express = require('express');  
const app = express();

app.get('/', (req, res) => {  
    res.type('image/svg+xml');
    res.send(getsvg(req.query.title || 'hello', req.query.color, req.query.label, req.query.flat));
    res.end();
});

function getsvg(title, color, label, flat) {  
    // 此处省略 ...
}

// note:这里用到一个环境变量
let port = process.env.HTTP_PORT || 8000;

app.listen(port, () => {  
    console.log('Listening on port %s', port);
});

这个文件有个依赖 express,后面我想用 nodemon 启动该项目,所以 package.json 文件简略如下:

{
  "name": "statusbar",
  "version": "1.0.0",
  "description": "status & process bar for markdown",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "author": "onbing",
  "dependencies": {
    "express": "~4.13.3",
    "nodemon": "~1.8.1"
  }
}

.gitignore 大家都懂的,主要是 .dockerignore,后面我希望部署时,把这个目录直接拷贝至容器,这个目录中肯定会存在一些奇奇怪怪的文件是不希望部署进去的,与 .gitignore 道理一样,一行添加一个进行忽略,根据项目实际情况,文件内容可能会如下:

node_modules/  
.git
.gitignore
.idea
.DS_Store
*.swp
*.log

Dockerfile

重点来了,项目构建以 Dockerfile 作为蓝本,先上内容:

# Statusbar
#
# VERSION    1.0.0

FROM daocloud.io/node:5  
MAINTAINER me@yanbingbing.com

ENV HTTP_PORT 8000

COPY . /app  
WORKDIR /app

RUN npm install --registry=https://registry.npm.taobao.org

EXPOSE 8000

CMD ["npm", "start"]  

基础镜像

通过 FROM 指令来指定基于的image,一般格式如下:

FROM <image>:<tag>  
# or
FROM <image>  

daocloud.io/node:5node:5 其实是同一个image,出于速度考虑选择 daocloud.io/node:5

设定环境变量

ENV 指令用来设定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。格式如下:

ENV <KEY> <value>  

可以看到之前js文件有用到 process.env.HTTP_PORT,就是在这里设定的。

拷贝项目文件

COPY 指令用来复制本地主机的文件到容器中,格式如下:

COPY <src> <dest>  

这里把我们项目目录本身,即当前目录 . 拷贝至容器的 /app 位置。然后通过指令 WORKDIR/app 目录设为工作目录。工作目录可以理解为运行时的 pwd

npm install

然后在 /app 目录中执行我们特别熟悉的 npm install,这里我们要用 RUN 指令来执行。

开放端口

作为一个服务,来让外部通过端口来访问,通过 EXPOSE 指令来指定端口号,很像我们的 module.exports,模块化思路无处不在。

npm start

最后指定,当这个容器运行的时候,如何启动这个服务。我们 npm start 就可以了,因为我们在 package.json 中设置了 scripts.start

提交代码

开发环节暂且告一段落,将代码提交至 githubgitlab,如果不想分享,可以忽略这一步。

本文项目代码地址:

https://github.com/yanbingbing/statusbar

部署

以我的服务器centos7为例,我已安装好 Docker,我只需要将项目拿过来,build一下,run一下就可以了。

首先检出代码:

git clone https://github.com/yanbingbing/statusbar.git statusbar  

进入目录构建:

cd statusbar  
docker build -t statusbarimg .  

build

构建目标名称 statusbarimg,是一个镜像,可以通过 docker images 来列出所有的镜像。

通过镜像 statusbarimg 创建一个容器并运行。

docker run --name statusbarcontainer -d -p 80:8000 statusbarimg  

创建的容器名称是 statusbarcontainer,你可以理解为 pid,这个名称唯一,创建之后如果不删除会一直存在。-p 用来指定端口映射,将容器的端口8000映射到主机80端口上,这样就可外部访问了。

最后可以通过以下命令来管理这个容器:

# 停止
docker stop statusbarcontainer  
# 重启容器
docker restart statusbarcontainer  
# 删除容器
docker rm statusbarcontainer  

结语

通过一个简单例子下来,发现 Docker 的实现思路和我们前端模块化思路很像,正所谓天下大同(就算总结少也要高大上)。