Skip to content

Commit 7052843

Browse files
committed
修改docker相关教程
1 parent 7bcc872 commit 7052843

File tree

1 file changed

+46
-82
lines changed

1 file changed

+46
-82
lines changed

text/09_node_production.md

+46-82
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pm2 命令还有好多命令行参数,如果单纯手敲的话就太麻烦了
134134
"NODE_ENV": "development",
135135
},
136136
env_production : {
137-
"NODE_ENV": "production"
137+
"NODE_ENV": "production"
138138
}
139139
}]
140140
}
@@ -249,102 +249,66 @@ $ pm2 unstartup systemv
249249

250250
有了docker,大家就可以本地开发代码,然后开发完成之后直接打一个包扔到服务器上运行,这个包就是我们所说的容器,它跟宿主机无关,不管运行在何种宿主机上,它的内部环境都是一致。所以说有了docker,我们再也不用担心在本地跑的好好的,结果一到服务器就出错的问题了。
251251

252-
> 当然如果你们服务器使用了Docker 技术的话,9.3小节的内容就没有必要使用了。因为在 Docker 上是没法设置开机服务的
252+
> 当然如果你们服务器使用了Docker 技术的话,9.3小节的内容就没有必要使用了。因为在 docker 上是不用设置开机服务的。同时我们也不用使用 PM2 技术来进行重启,docker 中会监听 1 号进程的运行情况,一旦这个进程退出整个容器也就处于退出状态,如果你在启动 docker 的时候加了 `--restart=always` 参数,那么容器随后也会被 docker 守护程序再启动起来
253253
254-
pm2 提供了生成 Dockerfile 的功能,不过生成的文件实用性不是很强,我需要稍加改造了一下。另外为了方便的演示docker使用,专门在 oschina 新建一个[代码仓库](http://git.oschina.net/nodebook/chapter8)用于第8章代码。下面演示一下dockerfile的编写,具体流程是在docker构建的时候,使用 git clone 从仓库中拿去代码,然后安装所需的依赖。构建完成之后,每次启动这个docker容器的使用使用 pm2 命令启动当前应用。dockerfile的示例代码如下
254+
为了能够运行 docker 容器,我们需要现有一个镜像,这个镜像通过 Dockerfile 文件来声明构建过程。一个简单的 Nodejs 的 Dockerfile 的格式可以是这样的
255255

256256
```dockerfile
257-
FROM mhart/alpine-node:latest
258-
259-
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
260-
RUN apk update && apk add git && apk add openssh-client && rm -rf /var/cache/apk/*
261-
262-
#创建应用目录
263-
RUN mkdir -p /var/app
264-
RUN mkdir -p /var/log/app
265-
#将git clone用的sshkey的私钥拷贝到.ssh目录下
266-
COPY deploy_key /root/.ssh/id_rsa
267-
RUN chmod 600 ~/.ssh/id_rsa
268-
#将当前git服务器域名添加到可信列表
269-
RUN ssh-keyscan -p 22 -t rsa git.oschina.net >> /root/.ssh/known_hosts
270-
271-
WORKDIR /var/app
272-
273-
#clone代码
274-
RUN git clone [email protected]:nodebook/chapter9.git .
275-
#拷贝配置文件
276-
COPY config.production.json config.json
277-
COPY process.production.json process.json
278-
279-
#安装cnpm
280-
RUN npm install -g cnpm --registry=https://registry.npmmirror.com
281-
#安装pm2
282-
RUN cnpm install pm2 -g
283-
RUN cnpm install
284-
285-
#向外暴露当前应用的端口
286-
EXPOSE 8100:8100
287-
288-
## 设置环境变量
289-
ENV NODE_ENV=production
290-
# 启动命令
291-
CMD ["pm2-docker", "process.json"]
257+
FROM node
258+
WORKDIR /app
259+
COPY . .
260+
RUN npm install
261+
CMD ["node", "app.js"]
292262
```
293263

294-
**代码 9.4.1 Dockerfile示例**
295-
296-
其中 `From` 代表使用的基础镜像,[alpine](https://alpinelinux.org/) 是一个非常轻量级的 linux 发行版本,所以基于其制作的 docker 镜像非常小,特别利于安装。这里的 [alpine-node](https://hub.docker.com/r/mhart/alpine-node/) 在 alpine 操作系统上集成了 node ,单纯 pull 安装的话也非常小。然后 RUN 和 COPY 两个命令是在构建的时候执行命令和拷贝文件,注意 COPY 命令仅仅只能拷贝当前执行docker 命令的目录下的文件,也就是说拷贝的时候不能使用相对路径,比如说你要执行 `COPY xxx/yyy /tmp/yyy` 或者 `COPY ../zzz /tmp/zzz` 都是不允许的。为了正确的 clone git 服务器上的代码,我们还需要配置一下 部署密钥。
297-
谈到部署密钥的概念,这里还要多说几句。我们一般从git服务器上clone下来代码后,会对代码进行编写,然后 push 你编写后的新代码。但是服务器上显然是不适合在其上面进行直接改动代码的场所,所以就有了部署密钥的概念,使用部署密钥你可以做 clone 和 pull 操作,但是你不能做 push 操作。
298-
299-
```shell
300-
$ ssh-keygen -f deploy_key -C "[email protected]"
301-
Generating public/private rsa key pair.
302-
Enter passphrase (empty for no passphrase):
303-
Enter same passphrase again:
304-
Your identification has been saved in deploy_key.
305-
Your public key has been saved in deploy_key.pub.
306-
The key fingerprint is:
307-
SHA256:S3JbyWc68K43kifBwYcJJxlIFlDlXz9MJDGI6gEhFKw [email protected]
308-
The key's randomart image is:
309-
+---[RSA 2048]----+
310-
|+o+==+o+ .+.. |
311-
| o....= o + |
312-
|. . ..= o. . |
313-
|E o .=o.= |
314-
| . ...So+ * |
315-
| . +o* + . |
316-
| oo+ |
317-
| +.+. |
318-
| .*.. |
319-
+----[SHA256]-----+
320-
```
264+
**代码 9.4.1 chapter9/simple/Dockerfile**
321265

322-
**命令9.4.1 生成密钥对**
266+
通过 `docker build . -t app:simple` 可以创建 app:simple 这个镜像;然后通过 `docker run app:simple` 即可运行这个镜像,运行后输出 `Example app listening on port 3000`
323267

324-
我们在第8章项目代码根目录下新建一个 deploy 文件夹,进入这个文件夹然后运行 **命令 9.4.1**,一路回车即可。然后我们就得到了 **代码 9.4.1** 中的 `deploy_key`了。生成完了之后去 git.oschina.com 上配置一下公钥(也就是我们生成的 `deploy_key.pub` 文件),在项目页(在这里是 http://git.oschina.net/nodebook/chapter8 )上点击 `管理` 导航链接(),在打开的页面中点击 `部署公钥管理`,然后选择 `添加公钥`,用记事本打开刚才生成的 deploy_key.pub 文件,全选复制,然后贴到输入框中:
268+
> 为了演示简单,这里的 app.js 只是一个 Express 的 helloworld 程序:
325269
326-
![添加部署公钥](images/add_deploy_public_key.png)
327-
**图 9.4.1 添加部署公钥**
270+
```javascript
271+
const express = require('express');
272+
const app = express();
273+
const port = 3000;
328274

329-
最后要注意一下 `EXPOSE` 命令,他代表 docker 及向宿主机暴露的端口号,如果不暴露端口的话,在宿主机上没法访问我们应用监听的端口。
330-
我们运行 `docker build -t someone/chapter8 .` 其中 `-t` 参数指定当前镜像的 tag 名称, `someone` 是指你在 [docker hub](https://hub.docker.com/) 网站上注册的用户,build 成功后你可以通过 `docker push someone/chapter8` 将构建后的结构 push 到 docker hub 网站上去,然后在服务器上运行 `docker pull someone/chapter8` 来拿取你当初 push 的仓库。当然你可以直接将 Dockerfile 拿到你的服务器上执行 build 命令,这时候 -t 参数可以随便指定,甚至不写。
275+
app.get('/', (req, res) => {
276+
res.send('Hello World!');
277+
});
331278

332-
> 鉴于国内的网络环境问题,在做 build 的时候,pull 基础镜像很有可能会失败,这时候你就只能求助于国内的 docker 镜像站了,比如说 [daocloud](https://www.daocloud.io/mirror#accelerator-doc)。
279+
app.listen(port, () => {
280+
console.log(`Example app listening on port ${port}`);
281+
});
282+
```
333283

334-
build 命令运行完成之后,运行 `docker images` 会输出:
284+
**代码 9.4.2 chapter9/simple/app.js**
335285

336-
```shell
337-
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
338-
someone/chapter8 latest 2a1a00cc1b41 4 minutes ago 147.7 MB
339-
```
286+
这么简单使用,看上去是没啥问题的,但是打包出来的镜像个头是比较大的,同时会影响打包和部署的时间。我们可以将 代码 9.4.1 中的 `FROM node` 换成 `FROM node:slim` 。之前我们使用的 FROM node 其实是 FROM node:latest 的缩写,它制作的时候使用的基础镜像包含了额外的一些软件包,但是我们平常用不到,但是 node:slim 这个版本基于一个精简版本的基础镜像制作而成,制作出来的体积会大大减少。我们复制代码 9.4.1,然后重命名为 slim.Dockerfile 文件,将第一行改为 FROM node:slim 。接着运行 `podman build . -f slim.Dockerfile -t app:slim` 即可打出来我们需要的更见轻量的镜像了。
340287

341-
最后我们通过 `docker run -d --name chapter8 someone/chapter8` 即可生成一个 docker 容器。其中 `-d` 参数代表在后台运行, `--name` 指定当前 docker 容器的名称, `someone/chapter8` 说明我们使用刚才 build 的镜像来生成容器。 通过 `docker ps` 命令的输出,我们可以查看生成的 docker 容器:
288+
不过平常我们在开发过程中会安装很多开发依赖,这些开发依赖根本不需要被打包出来的程序所使用,所以代码 9.4.1 中可以优化为 npm install --omit=dev ,这样就只会将生产依赖打包到镜像中,如果开发依赖的包有很多的话,可能又会减轻很多体积。
342289

343-
```shell
344-
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
345-
fb0d726a86dc someone/chapter8 "pm2-docker process. 4 seconds ago Up 4 seconds 8100/tcp chapter8
290+
还有一个需要注意的,npm 等包管理工具,在安装过程中同时还会写入磁盘缓存方便加快后续安装,但是我们的镜像打出来后就不会再重复运行安装了,这些缓存是很没有必要的。这时候我们可以利用 dockerfile 的构建阶段来做出来一个“两段式”的结构:
291+
292+
```dockerfile
293+
FROM node AS build
294+
WORKDIR /app
295+
COPY .npmrc .
296+
COPY ./package-lock.json .
297+
COPY ./package.json .
298+
RUN npm install --omit=dev
299+
FROM node:slim AS app
300+
WORKDIR /app
301+
COPY --from=build /app/node_modules ./node_modules
302+
COPY . .
303+
CMD ["node", "app.js"]
346304
```
347305

306+
**代码 9.4.3 chapter9/simple/two-stages.Dockerfile**
307+
308+
这里的构建分为 build 和 app 两个阶段,在 build 阶段我们把 .npmrc package-lock.json packge.json 三个文件拷贝到镜像中,用来安装生产依赖包。之所以要单独放一个 .npmrc 文件,是想通过修改 npm 的镜像源来加快 npm 的安装过程,否则使用官方源在国内安装是十分缓慢的。
309+
310+
311+
348312
### 9.5 代码
349313

350-
本章代码9.1、9.2小节代码和第8章存储在相同位置:https://github.com/yunnysunny/nodebook-sample/tree/master/chapter8 , 9.4章节代码为演示方便专门做了一个仓库,位于:http://git.oschina.net/nodebook/chapter8
314+
参见 https://github.com/yunnysunny/nodebook-sample/tree/master/chapter9

0 commit comments

Comments
 (0)