1/15

第四章:构建自定义镜像(NodeJS Demo)

这一章演示了如何将一个 NodeJS Web 应用打包成镜像,并使用该镜像运行容器,最后可以在浏览器访问这个 Web 应用。

 

新建文件夹,并在文件夹内创建两个文件:

package.json

{
 "name": "docker-simpleweb",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start":  "node index.js"
},
 "keywords": [],
 "author": "wuxianmimi",
 "license": "ISC",
 "dependencies": {
   "express": "^4.18.2"
}
}

index.js

const express = require('express');

const app = express();

app.get('/', (req, res) => {
  res.send('你好鸭');
});

app.listen(8080, () => {
   console.log("已监听8080端口");
})
  • 基础镜像问题

创建 Dockerfile 文件,并增加3条指令:

# 三步曲1:规定基础镜像
FROM alpine

# 三步曲2:运行命令来安装必要的依赖、程序
RUN npm install

# 三步曲3:规定容器启动参数
CMD ["npm", "start"]

然后运行 docker build . 命令,但是我们在第二步安装依赖时会报错:

wuxianmimi docker-simpleweb % docker build .
[+] Building 2.3s (5/5) FINISHED
=> [internal] load build definition from Dockerfile                                                                              0.0s
=> => transferring dockerfile: 229B                                                                                              0.0s
=> [internal] load .dockerignore                                                                                                 0.0s
=> => transferring context: 2B                                                                                                   0.0s
=> [internal] load metadata for docker.io/library/alpine:latest                                                                  2.1s
=> CACHED [1/2] FROM docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4            0.0s
=> ERROR [2/2] RUN npm install                                                                                                   0.2s
------
> [2/2] RUN npm install:
#5 0.172 /bin/sh: npm: not found
------
executor failed running [/bin/sh -c npm install]: exit code: 127

报错说没有找到 npm 这个命令,原因就是 alpine 这个镜像中,并没有 NodeJS 环境,当然也不能运行 npm 命令。

我们当然可以在使用 apt 自己安装 NodeJS 环境,但是 Docker 官方提供了 Node 基础镜像,镜像中已经安装了 Node 开发需要的工具,所以我们直接使用即可。

Docker Hub 上 Node 镜像地址:https://hub.docker.com/_/node

在 Supported tags and respective Dockerfile links 一栏下发现有很多支持的镜像标签,这里咪咪建议使用14-alpine 这个标签,原因是 NodeJs 16版本改动较大,可能与一些框架的老版本存在兼容性问题。

视频中讲师使用的 NodeJs 大版本是 8,喜欢原汁原味的同学可以使用这个版本。不过这个版本咪咪没有测试过,不一定能走通后续的流程。

# 三步曲1:规定基础镜像
# alpine ===> node:14-alpine
FROM node:14-alpine

# 三步曲2:运行命令来安装必要的依赖、程序
RUN npm install

# 三步曲3:规定容器启动参数
CMD ["npm", "start"]

运行 docker build .

wuxianmimi docker-simpleweb % docker build .
[+] Building 18.2s (6/6) FINISHED
=> [internal] load build definition from Dockerfile                                                                              0.0s
=> => transferring dockerfile: 237B                                                                                              0.0s
=> [internal] load .dockerignore                                                                                                 0.0s
=> => transferring context: 2B                                                                                                   0.0s
=> [internal] load metadata for docker.io/library/node:14-alpine                                                                 3.5s
=> [1/2] FROM docker.io/library/node:14-alpine@sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a          13.0s
=> => resolve docker.io/library/node:14-alpine@sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a           0.0s
=> => sha256:f19b50748cdfe11c52ec028226d21a0cbc5a6b860e311b41a6299c2c43d1bfae 37.78MB / 37.78MB                                 12.1s
=> => sha256:7bfe7e7dc195d0082ab6b8c2bb09a8a405c51369b5c25c015327227f1390a312 2.43MB / 2.43MB                                    1.5s
=> => sha256:cb8b674ae1bc8e69210b956081a7c092daabfd2a4834f660ef3e0cadaab5db44 449B / 449B                                        1.9s
=> => sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a 1.43kB / 1.43kB                                    0.0s
=> => sha256:98f1a5d744f2343703913c3981098ac92fed74a6edec9133291d9f0ad4a03fe8 1.16kB / 1.16kB                                    0.0s
=> => sha256:8673fd44cb467741701c5006a1edca4f315bc1ac8af2964fe0a9586c7bbe195f 6.45kB / 6.45kB                                    0.0s
=> => extracting sha256:f19b50748cdfe11c52ec028226d21a0cbc5a6b860e311b41a6299c2c43d1bfae                                         0.7s
=> => extracting sha256:7bfe7e7dc195d0082ab6b8c2bb09a8a405c51369b5c25c015327227f1390a312                                         0.1s
=> => extracting sha256:cb8b674ae1bc8e69210b956081a7c092daabfd2a4834f660ef3e0cadaab5db44                                         0.0s
=> [2/2] RUN npm install                                                                                                         1.5s
=> exporting to image                                                                                                            0.0s
=> => exporting layers                                                                                                           0.0s
=> => writing image sha256:5a8723a245fdf92ef58101dd66e664c3365af9015b84adb30fc93a58dd25b07a                                      0.0s

构建成功,我们运行一下

wuxianmimi docker-simpleweb % docker run 5a8723a245fd
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path /package.json
npm ERR! errno -2
npm ERR! enoent ENOENT: no such file or directory, open '/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2023-01-09T02_53_07_209Z-debug.log

报错了,说是没有 package.json 这个文件,什么情况?(可能由于 Docker Engine 版本差异,视频中讲师在构建阶段就会报着个错误)

  • COPY 指令

缺少 package.json 这个文件,是因为目前容器内的文件系统快照,是 node:14-alpine 这个镜像的拷贝,所以里面当然不会有我们的代码。

所以我们需要在构建镜像的时候,将我们的代码拷贝到容器内,这需要用到 COPY 指令:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

COPY 指令把 <src> 的文件和文件夹,拷贝到容器内的文件系统 <dest> 中。

在我们 Dockerfile 中添加 COPY 指令:

# 三步曲1:规定基础镜像
FROM node:14-alpine

# 三步曲2:运行命令来安装必要的依赖、程序
COPY ./ ./
RUN npm install

# 三步曲3:规定容器启动参数
CMD ["npm", "start"]

这里咪咪建议在项目根目录创建一个 .npmrc 文件,在文件内添加一行:

registry=https://registry.npmmirror.com

这是 npm 镜像地址,对大陆用户比较友好,否则在容器内安装 npm 依赖的时间很长很长。。。(后面所有 NodeJS 项目都一样)

重新构建运行容器:

wuxianmimi docker-simpleweb % docker run 6c8d589ccc7e

> docker-simpleweb@1.0.0 start /
> node index.js

已监听8080端口

服务成功运行了,我们打开本地浏览器,在地址栏输入 localhost:8080 并回车:

img无法访问应用

咦,网站无法访问,什么情况?

  • 端口映射

当我们在本地浏览器前往 http://localhost:8080 时,请求的是本地 8080 端口,而不是容器内的 8080 端口。而我们本地并没有程序监听这个端口,所以也不会有响应。

默认情况下,容器内的端口是不会**接收**到外部的请求的,我们需要在运行容器时,指定端口映射的规则。

docker run 命令添加 -p [本地端口]:[容器端口] 选项,增加端口映射:

wuxianmimi docker-simpleweb % docker run -p 8080:8080 b964820c57bd7

> docker-simpleweb@1.0.0 start /
> node index.js

已监听8080端口

重新在浏览器访问 localhost:8080,成功返回:

img获得返回数据

  • 工作目录

我们目前的项目文件,都是通过 COPY 指令拷贝到容器内的根目录的,这样的做法其实不太好。原因是根目录还有很多其他的文件,可能会与我们的项目有冲突。

/ # ls
Dockerfile         home               mnt               package.json       sbin               usr
bin               index.js           node_modules       proc               srv               var
dev               lib               opt               root               sys
etc               media             package-lock.json run               tmp

解决办法就是在 Dockerfile 中使用 WORKDIR 指令,这个指令设置一个文件夹,成为之后 RUNCMDCOPY 这些指令的起始目录。

更新我们的 Dockerfile:

# 三步曲1:规定基础镜像
FROM node:14-alpine

# 增加工作目录
WORKDIR /usr/app

# 三步曲2:运行命令来安装必要的依赖、程序
COPY ./ ./
RUN npm install

# 三步曲3:规定容器启动参数
CMD ["npm", "start"]

重新构建镜像并运行,现在在容器中的项目文件结构:

/usr/app # ls
Dockerfile         index.js           node_modules       package-lock.json package.json
/usr/app # cd /
/ # ls
bin   dev   etc   home   lib   media mnt   opt   proc   root   run   sbin   srv   sys   tmp   usr   var
/ #
  • 优化构建

目前构建镜像的流程已经走通了,但是仍有一个细节可以优化。

虽然 docker build 每一步指令都有缓存机制,只要上一步指令不更改,当前指令就会使用缓存,节省时间。但是如果我们现在更改了 index.js 的内容,我们肯定需要重新构建镜像,由于 RUN npm install 这步之前的指令肯定会变动(因为我们改了代码),所以每次更改代码后构建都需要重新安装依赖,比较浪费时间。

于是我们可以分两步 COPY 文件,先将 package.json 拷贝进容器,执行安装依赖的指令,再将其余文件拷贝进容器。这样的话,只要 package.json 不变,安装依赖这一步就可以用缓存的镜像。

# 三步曲1:规定基础镜像
FROM node:14-alpine

# 增加工作目录
WORKDIR /usr/app

# 三步曲2:运行命令来安装必要的依赖、程序
COPY ./package.json ./
COPY ./.npmrc ./
RUN npm install
COPY ./ ./

# 三步曲3:规定容器启动参数
CMD ["npm", "start"]
 
posted @ 2025-01-15 17:33  Hbro  阅读(18)  评论(0)    收藏  举报