Node.JS Best Practices - How to Become a Better Developer in 2017 提到的几点, 我们 Fundebug https://www.fundebug.com/ 深有同感:
使用 ES6
使用 Promise
使用 LTS
使用 Docker https://www.docker.com/
...
想必大家都知道 ES6,Promise 以及 LTS, 那 Docker https://www.docker.com/ 是啥玩意啊? 翻遍 Node 文档 https://nodejs.org/api/ 也没见踪迹啊!
GitHub 仓库: https://github.com/Fundebug/nodejs-docker
什么是 Docker?
Docker 是最流行的的容器工具, 没有之一. 本文并不打算深入介绍 Docker, 不过可以从几个简单的角度来理解 Docker.
从进程的角度理解 Docker
在 Linux 中, 所有的进程构成了一棵树. 可以使用 http://man7.org/linux/man-pages/man1/pstree.1.html 命令进行查看:
pstree
init─┬─VBoxService───7*[{VBoxService}]
├─acpid
├─atd
├─cron
├─dbus-daemon
├─dhclient
├─dockerd─┬─docker-containe─┬─docker-containe─┬─Redis-server───2*[{Redis-server}]
│ │ │ └─8*[{docker-containe}]
│ │ ├─docker-containe─┬─mongod───16*[{mongod}]
│ │ │ └─8*[{docker-containe}]
│ │ └─11*[{docker-containe}]
│ └─13*[{dockerd}]
├─6*[getty]
├─influxd───9*[{influxd}]
├─irqbalance
├─Puppet───{Puppet}
├─rpc.idmapd
├─rpc.statd
├─rpcbind
├─rsyslogd───3*[{rsyslogd}]
├─Ruby───{Ruby}
├─sshd─┬─sshd───sshd───zsh───pstree
│ ├─sshd───sshd───zsh
│ └─sshd───sshd───zsh───mongo───2*[{mongo}]
├─systemd-logind
├─systemd-udevd
├─upstart-file-br
├─upstart-socket-
└─upstart-udev-br
可知, init 进程为所有进程的根(root), 其 PID 为 1.
Docker 将不同应用的进程隔离了起来, 这些被隔离的进程就是一个个容器. 隔离是基于两个 Linux 内核机制实现的, Namesapce 和 Cgroups.
Namespace 可以从 UTD,IPC,PID,Mount,User 和 Network 的角度隔离进程. 比如, 不同的进程将拥有不同 PID 空间, 这样容器中的进程将看不到主机上的进程, 也看不到其他容器中的进程. 这与 Node.JS 中模块化以隔离变量的命名空间的思想是异曲同工的.
通过 Cgroups, 可以限制进程对 CPU, 内存等资源的使用. 简单地说, 我们可以通过 Cgroups 指定容器只能使用 1G 内存.
从进程角度理解 Docker, 那每一个 Docker 容器就是被隔离的进程及其子进程. 上文 pstree 的输出中可以分辨出 2 个容器: MongoDB 和 Redis.
从文件的角度理解 Docker
基于 Namespace 与 Cgroups 的容器工具其实早已存在, 例如, OpenVZ https://openvz.org/Main_Page ,LXC https://linuxcontainers.org/ . 然而, 真正引爆容器技术的却是后来者 Docker. 为什么呢? 个人觉得是因为 Docker 镜像以及 Dockerfile.
在 Linux 中, 一切皆文件, 进程的运行离不开各种各样的文件. 跑一个简单的 Node.JS 程序, 传统的做法是手动安装各种依赖然后运行; 而 Docker 则是将所有依赖 (包括操作系统, Node,NPM 模块, 源代码) 打包到一个 Docker 镜像中, 然后基于这个镜像运行容器.
Docker 镜像可以通过 Docker 仓库共享给其他人, 这样他们只需要下载镜像即可运行程序. 想象一下, 当我们需要在另一台主机 (比如生产服务器, 新同事的机器) 上运行一个 Node.JS 应用, 仅仅需要下载对应的 Docker 镜像就可以了, 是不是很方便呢?
Docker 镜像可以通过文本文件, 即 Dockerfile 进行定义. 不妨看一个简单的例子(由于不可抗力, 这个 Dockerfile 构建大概会失败, 仅作为参考):
- # 基于 Ubuntu
- FROM Ubuntu
- # 安装 Node.JS 与 NPM
- RUN apt-get update && apt-get -y install Node.JS NPM
- # 安装 NPM 模块: Express
- RUN NPM install express
- # 添加源代码
- ADD App.JS /
其中, FROM,RUN 与 ADD 为 Dockerfile 命令. 结合注释, 该 Dockerfile 的含义非常直白. 基于这个 Dockerfile, 使用 docker build 命令就可以构建对应的 Docker 镜像. 基于这个 Docker 镜像, 就可以运行 Docker 容器来执行 App.JS:
- var express = require("express");
- var App = express();
- App.get("/", function(req, res)
- {
- res.send("Hello Fundebug!\n");
- });
- App.listen(3000);
Dockerfile 实际上是将 Docker 镜像代码化了, 另一方面也是将安装依赖的过程代码化了, 于是我们就可以像管理源码一样使用 Git 对 Dockerfile 进行版本管理.
为啥用 Docker?
当你的系统越来越复杂的时候, 你会发现 Docker 的价值.
从应用架构角度理解 Docker
刚开始, 你只需要写一个 Node.JS 程序, 挂载一个静态网站; 然后, 你做了一个用户账号系统, 这时需要数据库了, 比如说 MySQL; 后来, 为了提升性能, 你引入了 Memcached 缓存; 终于有一天, 你决定把前后端分离, 这样可以提高开发效率; 当用户越来越多, 你又不得不使用 Nginx 做反向代理; 对了, 随着功能越来越多, 你的应用依赖也会越来越多... 总之, 你的应用架构只会越来越复杂. 不同的组件的安装, 配置与运行步骤各不相同, 于是你不得不写一个很长的文档给新同事, 只为了让他搭建一个开发环境.
使用 Docker 的话, 你可以为不同的组件逐一编写 Dockerfile, 分别构建镜像, 然后运行在各个容器中. 这样做, 将复杂的架构统一了, 所有组件的安装和运行步骤统一为几个简单的命令:
构建 Docker 镜像: docker build
上传 Docker 镜像: docker push
下载 Docker 镜像: docker pull
运行 Docker 容器: docker run
从应用部署角度理解 Docker
通常, 你会有开发, 测试和生产服务器, 对于某些应用, 还会需要进行构建. 不同步骤的依赖会有一些不同, 并且在不同的服务器上执行. 如果手动地在不同的服务器上安装依赖, 是件很麻烦的事情. 比如说, 当你需要为 Node.JS 应用添加一个新的 NPM 模块, 或者升级一下 Node.JS, 是不是得重复操作很多次? 友情提示一下, 手动敲命令是极易出错的, 有些失误会导致致命的后果(参考最近 GitLab 误删数据库与 AWS 的 S3 故障).
如果使用 Docker 的话, 开发, 构建, 测试, 生产将全部在 Docker 容器中执行, 你需要为不同步骤编写不同的 Dockerfile. 当依赖变化时, 仅需要稍微修改 Dockerfile 即可. 结合构建工具 Jenkins https://jenkins.io/ , 就可以将整个部署流程自动化.
另一方面, Dockerfile 将 Docker 镜像描述得非常精准, 能够保证很强的一致性. 比如, 操作系统的版本, Node.JS 的版本, NPM 模块的版本等. 这就意味着, 在本地开发环境运行成功的镜像, 在构建, 测试, 生产环境中也没有问题. 还有, 不同的 Docker 容器是依赖于不同的 Docker 镜像, 这样他们互不干扰. 比如, 两个 Node.JS 应用可以分别使用不同版本的 Node.JS.
从集群管理角度理解 Docker
架构规模越来越大的时候, 你有必要引入集群了. 这就意味着, 服务器由 1 台变成了多台, 同一个应用需要运行多个备份来分担负载. 当然, 你可以手动对集群的功能进行划分: Nginx 服务器, Node.JS 服务器, MySQL 服务器, 测试服务器, 生产服务器... 这样做的好处是简单粗暴; 也可以说财大气粗, 因为资源闲置会非常严重. 还有一点, 每次新增节点的时候, 你就不得不花大量时间进行安装与配置, 这其实是一种低效的重复劳动.
下载 Docker 镜像之后, Docker 容器可以运行在集群的任何一个节点. 一方面, 各个组件可以共享主机, 且互不干扰; 另一方面, 也不需要在集群的节点上安装和配置任何组件. 至于整个 Docker 集群的管理, 业界有很多成熟的解决方案, 例如 Mesos http://mesos.apache.org/ ,Kubernetes https://kubernetes.io/ 与 Docker Swarm https://github.com/docker/swarm . 这些集群系统提供了调度, 服务发现, 负载均衡等功能, 让整个集群变成一个整体.
如何用 Docker?
编写 Dockerfile
正确的是这样的:
- # 使用 DaoCloud 的 Ubuntu 镜像
- FROM daocloud.io/library/Ubuntu:14.04
- # 设置镜像作者
- MAINTAINER Fundebug <help@fundebug.com>
- # 设置时区
- RUN sudo sh -c "echo'Asia/Shanghai'> /etc/timezone" && sudo dpkg-reconfigure -f noninteractive tzdata
- # 使用阿里云的 Ubuntu 镜像
- RUN echo '\ndeb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse\n'> /etc/apt/sources.list
- # 安装 node v6.10.1
- RUN sudo apt-get update && sudo apt-get install -y wget
- # 使用淘宝镜像安装 Node.JS v6.10.1
- RUN wget https://npm.taobao.org/mirrors/node/v6.10.1/node-v6.10.1-linux-x64.tar.gz && tar -C /usr/local --strip-components 1 -xzf node-v6.10.1-Linux-x64.tar.gz && rm node-v6.10.1-Linux-x64.tar.gz
- WORKDIR /App
- # 安装 NPM 模块
- ADD package.JSON /App/package.JSON
- # 使用淘宝的 NPM 镜像
- RUN NPM install --production -d --registry=https://registry.npm.taobao.org
- # 添加源代码
- ADD . /App
- # 运行 App.JS
- CMD ["node", "/app/app.js"]
有几点值得注意的地方:
使用国内 DaoCloud https://www.daocloud.io/ 的 Docker 仓库, 阿里云的 Ubuntu 镜像以及淘宝的 NPM 镜像, 否则会出事情的;
将时区设为 Asia/Shanghai, 否则日志的时间会不大对劲;
使用. dockerignore 忽略不需要添加到 Docker 镜像的文件和目录, 其语法与. gitigore 一致;
更重要的一点是, package.JSON 需要单独添加. Docker 在构建镜像的时候, 是一层一层构建的, 仅当这一层有变化时, 重新构建对应的层. 如果 package.JSON 和源代码一起添加到镜像, 则每次修改源码都需要重新安装 NPM 模块, 这样木有必要. 所以, 正确的顺序是: 添加 package.JSON; 安装 NPM 模块; 添加源代码.
构建 Docker 镜像
使用 docker build 命令构建 Docker 镜像
sudo docker build -t fundebug/Node.JS .
其中,-t 选项用于指定镜像的名称.
使用 docker images 命令查看 Docker 镜像
- sudo docker images
- REPOSITORY TAG IMAGE ID CREATED SIZE
- fundebug/Node.JS latest 64530ce811a1 32 minutes ago 266.4 MB
- daocloud.io/library/Ubuntu 14.04 b969ab9f929b 9 weeks ago 188 MB
可知, fundebug/Node.JS 镜像的大小为 266.4MB, 在 Ubuntu 镜像的基础上增加了 80MB 左右.
运行 Docker 容器
使用 docker run 命令运行 Docker 容器
sudo docker run -d --net=host --name=hello-fundebug fundebug/Node.JS
其中,-d 选项表示容器在后台运行;--net 选项指定容器的网络模式, host 表示与主机共享网络;--name 指定了容器的名称.
使用 docker ps 命令查看 Docker 容器
- sudo docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- e8eb5473970c fundebug/Node.JS "node /app/app.js" 37 minutes ago Up 37 minutes hello-
可知, COMMAND 为 "node /app/app.js", 表示容器中运行的命令. 这是我们再 Dockerfile 中使用 CMD 指定的. 不妨使用 docker exec 命令在容器内执行 ps 命令查看容器内的进程:
- sudo docker exec hello-fundebug ps -f
- UID PID PPID C STIME TTY TIME CMD
- root 1 0 0 15:14 ? 00:00:00 node /App/App.JS
可知, 容器内的 1 号进程即为 node 进程 node /App/App.JS. 在 Linux 中, PID 为 1 进程按说是唯一的, 即 init 进程. 但是, 容器使用了内核的 Namespace 机制, 为容器创建了独立的 PID 空间, 因此容器中也有 1 号进程.
测试
使用 curl 命令访问:
- curl localhost:3000
- Hello Fundebug!
是否用 Docker?
一方面, 使用 Docker 能够带来很大益处; 另一方面, 引入 Docker 必然会有很多挑战, 需要熟悉 Docker 才能应对自如. 想必这是一个艰难的决定. 如果从长远的角度来看, Docker 正在成为应用开发, 部署, 发布的标准技术, 也许我们不得不用开放的心态对待它.
作为 Node.JS 开发者, 真正理解 Docker 可能需要一些时间, 但是它可以给你带来很多便利.
参考链接
DOCKER 基础技术: Linux NAMESPACE(上) http://coolshell.cn/articles/17010.html
DOCKER 基础技术: Linux NAMESPACE(下) http://coolshell.cn/articles/17029.html
DOCKER 基础技术: Linux CGROUP http://coolshell.cn/articles/17049.html
从 GitLab 误删除数据库想到的 http://coolshell.cn/articles/17680.html
AWS 的 S3 故障回顾和思考 http://coolshell.cn/articles/17737.html
关于 Fundebug
Fundebug https://www.fundebug.com/ 专注于 JavaScript, 微信小程序, 微信小游戏, 支付宝小程序, React Native,Node.JS 和 Java 实时 BUG 监控. 自从 2016 年双十一正式上线, Fundebug 累计处理了 7 亿 + 错误事件, 得到了 Google,360, 金山软件, 百姓网等众多知名用户的认可. 欢迎免费试用!
来源: http://www.bubuko.com/infodetail-2849615.html