谁不会休息,谁就不会工做。 —— 列宁node
本篇不会讲解 Docker 命令的使用、安装等,由于在以前一篇文章 一文零基础教你学会 Docker 入门到实践 中也已经讲解的很详细了,不清楚的能够点击连接回头在从新看下,本篇重点是介绍 Node.js 项目如何进行 Docker 容器化及一些实践优化,还有一些常见的问题,固然若是还有其它使用上的问题也欢迎你们在评论区进行留言补充。mysql
做者简介:五月君,Nodejs Developer,热爱技术、喜欢分享的 90 后青年,公众号「Nodejs技术栈」,Github 开源项目 www.nodejs.redgit
经过本篇文章能学到什么?github
在本篇开始咱们先建立一个简单的 Node.js 应用,而后为这个应用建立一个 Docker 镜像,并构建和运行它sql
首先咱们须要建立一个 app.js 开启一个 HTTP 服务,后面会借助 Docker 来运行这个程序docker
const http = require('http');
const PORT = 30010;
const server = http.createServer((req, res) => {
res.end('Hello Docker');
})
server.listen(PORT, () => {
console.log('Running on http://localhost:', PORT, 'NODE_ENV', process.env.NODE_ENV);
});
复制代码
而后咱们建立一个 package.json 文件,这里是描述你的应用程序以及须要的依赖,写过 Node.js 的同窗应该会很熟悉的,这里我在 scripts 里面增长了 npm run dev
、npm run pro
两个命令,由于我想在这里介绍如何在构建时传入参数来动态设置环境变量。npm
{
"name": "hello-docker",
"version": "1.0.2",
"description": "",
"author": "May",
"main": "app.js",
"scripts": {
"dev": "NODE_ENV=dev node app.js",
"pro": "NODE_ENV=pro node app.js"
}
}
复制代码
这是一个 Dockerfile 文件所包含的信息,这些命令在 Docker 入门与实践 中也有讲解过json
FROM node:10.0-alpine
RUN apk --update add tzdata \ && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ && apk del tzdata
RUN mkdir -p /usr/src/nodejs/
WORKDIR /usr/src/nodejs/
# add npm package
COPY package.json /usr/src/nodejs/package.json RUN cd /usr/src/nodejs/ RUN npm i
# copy code
COPY . /usr/src/nodejs/
EXPOSE 30010
CMD npm run dev 复制代码
在 Dockerfile 的同级文件下建立一个 .dockerignore 文件,避免将你本地的调试文件、node_modules 等一些文件放入 Docker 容器中缓存
.git
node_modules
npm-debug.log
复制代码
此时经过如下命令便可构建一个 Docker 镜像bash
$ docker image build -t mayjun/hello-docker
复制代码
再经过 docker run -d -p 30010:30010 mayjun/hello-docker 命令可运行一个 Docker 容器,可是有个疑问我是有生产和测试之分的,按照上面 CMD npm run dev
这样写死只能打包一种环境,固然你也能够在建一个文件来实现或者一些其它的方法。
为了解决上面的疑问,个人想法是在镜像构建时传入参数来动态设置环境变量,对 Dockerfile 文件作下修改,看如下实现:
EXPOSE 30010
ARG node_env # 新增长
ENV NODE_ENV=$node_env # 新增长
CMD npm run ${NODE_ENV} # 修改 复制代码
下面对上面的代码作个解释
ARG node_env
ENV NODE_ENV=$node_env
CMD npm run ${NODE_ENV}
剩下的就是在构建镜像时动态传入参数了
$ docker image build --build-arg node_env=dev -t mayjun/hello-docker:1.0.2 . # 构建测试环境
$ docker image build --build-arg node_env=pro -t mayjun/hello-docker:1.0.2 . # 构建生产环境
复制代码
运行容器
$ docker run -d -p 30010:30010 mayjun/hello-docker:1.0.2
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2bc6e62cd0e8 mayjun/hello-docker:1.0.2 "/bin/sh -c 'npm run…" 3 minutes ago Up 3 minutes 0.0.0.0:30010->30010/tcp elastic_bouman
复制代码
查看容器日志
docker logs -f 2bc6e62cd0e8
> hello-docker@1.0.0 dev /usr/src/nodejs
> NODE_ENV=dev node app.js
Running on http://localhost: 30010 NODE_ENV dev
复制代码
我将以上代码打包成了镜像 mayjun/hello-docker:1.0.2,能够拉取查看 docker pull mayjun/hello-docker:1.0.2
若是你的项目中使用了私有 NPM 包,在 Dcoker 构建镜像过程当中会出现 npm 私有包安装 404 的错误,若是是在容器外部咱们能够 npm login 登录拥有 NPM 私有包权限的帐户,来解决这个问题,可是在 Docker 的时候是不能这样作的。
建立身份验证令牌
为了安装私有包咱们须要 “建立身份验证令牌” 以便在持续集成环境、Docker 容器内部能访问咱们的私有 NPM 包,如何建立可参考 docs.npmjs.com/creating-an…
实现方法
咱们在建立 Dockerfile 文件过程当中就须要增长如下两条命令:
# 528das62-e03e-4dc2-ba67-********** 这个 Token 就为你建立的身份验证令牌 token
RUN echo "//registry.npmjs.org/:_authToken=528das62-e03e-4dc2-ba67-**********" > /root/.npmrc RUN cat /root/.npmrc 复制代码
在 Egg 里面,若是是 egg-scripts start --daemon
,去掉 --daemon 直接 egg-scripts start 便可,不然 Docker 容器会没法启动。
看如下代码示例,修改下 package.json 便可,Dockerfile 文件同上面第一个 Docker 化一个 Node.js 应用程序 是同样的
package.json
{
"scripts": {
"start": "egg-scripts start" // 去掉 --daemon
}
}
复制代码
也可参考 Egg Issues “docker容器不能run起来,请问有碰到的吗?” github.com/eggjs/egg/i…
若是一个镜像在不通过优化的状况下体积一般都是会很大的,如下也是在实践过程当中作的几点优化。
Dockerfile 中的每条指令都会建立一个镜像层,Dockerfile 指令或复制的项目文件在没有修改变更的状况下,每一个镜像层是能够被复用和缓存的。
如下代码可在 mayjun/hello-docker:latest 镜像仓库找到,如下示例中,源码改变以后,无论 package.json 有没有改变的状况下都会从新安装 NPM 模块,这样显然是很差的,所以下面咱们要改进
# ...
WORKDIR /usr/src/nodejs/hello-docker COPY . /usr/src/nodejs/hello-docker
RUN npm install
# ...
复制代码
改进以后的代码以下所示,咱们让 package.json 提早,在 package.json 没有修改的状况下是不会从新安装 NPM 包的,也会减小部署的时间。
# ...
WORKDIR /usr/src/nodejs/
# add npm package
COPY package.json /usr/src/app/package.json RUN cd /usr/src/app/ RUN npm i
# copy code
COPY . /usr/src/app/
# ...
复制代码
mayjun/hello-docker:1.0.0 这个镜像在 Docker 仓库也可搜索到,在未优化以前大约在 688MB
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE mayjun/hello-docker 1.0.0 7217fb3e9daa 5 seconds ago 688MB
使用 Alpine 优化
Alpine 是一个很小的 Linux 发行版,想要大幅度减少镜像体积选择 Node.js 的 Alpine 版本也是最简单的,另外 -alpine 的时区默认不是国内的,须要 Dockerfile 配置时区。
FROM node:10.0-alpine
RUN apk --update add tzdata \ && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ && apk del tzdata
RUN echo "Asia/Shanghai" > /etc/timezone
RUN mkdir -p /usr/src/nodejs/
WORKDIR /usr/src/nodejs/
# add npm package
COPY package.json /usr/src/app/package.json RUN cd /usr/src/app/ RUN npm i
# copy code
COPY . /usr/src/app/
EXPOSE 30010
CMD npm start 复制代码
从新打包了一个版本 mayjun/hello-docker:1.1.0 再次查看下效果,能够看到镜像文件从 688MB 减小至 85.3MB,这个体积优化仍是很大的
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mayjun/hello-docker 1.1.0 169e05b8197d 3 minutes ago 85.3MB
复制代码
有些测试环境用的包,在进行生产环境打镜像时不要包含进去,也就是 package.json 文件 devDependencies 对象,经过在 npm i 以后指定 --production 参数过滤
改进以下所示:
FROM node:10.0-alpine
# 省略 ...
# add npm package
COPY package.json /usr/src/app/package.json RUN cd /usr/src/app/ RUN npm i --production # 改变在这了
# 省略 ...
复制代码
从新打包了一个版本 mayjun/hello-docker:1.2.0 再次查看下效果,能够看到镜像文件从 85.3MB 又减小至 72.3MB
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mayjun/hello-docker 1.2.0 f018aa578711 3 seconds ago 72.3MB
复制代码
Question1
如下命令在删除镜像的时候报以下错误:
$ docker rmi 6b1c2775591e
Error response from daemon: conflict: unable to delete 6b1c2775591e (must be forced) - image is referenced in multiple repositories
复制代码
细心的你也许会发现镜像 ID 6b1c2775591e 同时指向了 hello-docker 和 mayjun/hello-docker 仓库,这也是形成删除失败的缘由
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 383867b75fd2 6 days ago 373MB
hello-docker latest 6b1c2775591e 7 days ago 675MB
mayjun/hello-docker latest 6b1c2775591e 7 days ago 675MB
复制代码
指定 repository 和 tag 来删除,执行删除命令以后再次查看 mayjun/hello-docker 仓库就已经没有了
$ docker rmi mayjun/hello-docker
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 383867b75fd2 6 days ago 373MB
hello-docker latest 6b1c2775591e 7 days ago 675MB
复制代码
Question2
执行删除镜像命令报以下错误:
$ docker rmi 9be467fd1285
Error response from daemon: conflict: unable to delete 9be467fd1285 (cannot be forced) - image is being used by running container 1febfb05b850
复制代码
根据提示是有正在运行的容器,需先中止容器、删除容器以后在删除镜像
$ docker container kill 1febfb05b850 # 中止容器
$ docker rm 1febfb05b850 # 删除容器
$ docker rmi 9be467fd1285 # 删除镜像
复制代码
Question3
设定的工做目录(WORKDIR)要与下面的要保持一致
...
WORKDIR /usr/src/nodejs/
# add npm package
COPY package.json /usr/src/node/package.json # 目录不一致
RUN cd /usr/src/node/ # 目录不一致
RUN npm i
...
复制代码
例如,如以上配置由于工做目录与实际 COPY 的目录不一致,会致使报如下错误:
再按照如下方式更改成一致便可
...
WORKDIR /usr/src/nodejs/
# add npm package
COPY package.json /usr/src/nodejs/package.json # 更改成一致
RUN cd /usr/src/nodejs/ # 更改成一致
RUN npm i
...
复制代码