一次头秃的 node + mongo docker 实践

玩转 docker

原文连接html

背景

最近在捯饬一个前端性能上报分析的项目,前端由 react 全家桶,打包部署公司有专门的发布系统,这块就没什么顾虑。前端

前端团队的后端没有什么规范或通用流程,就想本身先技术选型从0到1,决定使用 egg + mongodb,后续也许会追加 nginx + redis + Kafka 相关配置。node

  • 问题来了:怎么简化部署配置流程?
  • 答: docker

目标

整体目标不外乎几点:简单、快速、安全。react

  • 简单
  1. 一次配置,不一样环境都可执行,这也是 docker 的优点。
  2. 部署简单,可能就几行甚至一行命令行,方便接入 CI
  3. 本地开发方便。
  • 快速
  1. 开发编译热重载要快。
  2. 镜像包、部署包等要小,上传下载部署才会快。
  3. 快速回滚。
  • 安全
  1. 源码无泄漏风险。
  2. MongoDB 开启安全验证。

行动

PS:搜索搜的头秃,如下不少关键知识点收集自 google + github issue + Stack Overflow + docker 官方文档 + 英文博客。英文有障碍真是影响效率。nginx

接下来讲说实践中遇到的问题。git

无 docker

步骤:github

  1. 下载 node、mongodb等。
  2. 配置 node、mongodb等。
  3. 启动 egg 开发。

换台电脑或协同其余小伙伴开发时,得把你的动做重复一遍。不一样的操做系统和下的不一样 node 或 db 版本,都有可能致使系统运行不起来。web

你确定听过这句话:个人电脑上是好的啊。redis

初探 docker

约束不了团队众多软件的安装和版本的控制,要求安装必定范围的 docker 仍是简单的吧。mongodb

例如启动 mongodb 服务

docker run -p 27017:27017 -v <LocalDirectoryPath>:/data/db --name docker_mongodb -d mongo:4.2.6
复制代码

这里咱们启动了一个 mongo 最新稳定版本的 docker 容器。简单说明下:

  • run 运行镜像,本地没有会自动拉取。
  • -p 端口映射,本地 27017 映射容器 27017 端口,就能够经过访问本地端口而访问 docker mongo 的服务了。
  • -v 本地 <LocalDirectoryPath>映射容器目录 /data/db,用来持久化数据库,否则容器删除数据也丢失了。
  • --name 给容器取个名字,匿名容器能够经过 docker container prune 删除。
  • -d 后台运行
  • mongo:4.2.6 docker hub官方镜像:版本

接下来本地启动跟无 docker 效果是同样的。

egg 镜像化

编写 Dockerfile 文件

# 基于的基础镜像
FROM node:12.16.3
 # 踩坑1:注意目录使用前,保证存在 RUN mkdir -p /usr/src/egg  # 注意不要用 cd,须要了解 docker 分层构建的概念,须要改变上下文的 pwd,使用 WORKDIR WORKDIR /usr/src/egg  # 复制 Dockerfile 同级内容到容器的 /usr/src/egg 目录下 COPY . .  # 安装 npm 包 RUN npm install  # 暴露端口,这里只是声明便于理解维护,实际映射使用须要 -p xxx:7001 EXPOSE 7001  # 启动容器后默认执行的命令行 CMD [ "npm", "run", "start" ] 复制代码

编写 .dockerignore 文件

node_modules
npm-debug.log
.idea
复制代码

忽略 node_modules

  1. 构建时会把目录内容发送给 docker 进程,减小 I/O。
  2. 本地操做系统和版本安装的 npm 包未必适合 docker 环境运行,避免冲突。
  • 构建
docker build -t node:egg .
复制代码
  • 查看 image
docker images
复制代码
REPOSITORY  TAG  IMAGE ID      CREATED         SIZE
node        egg  ae65b8012120  28 seconds ago  1.12GB
复制代码
  • 运行
docker run -p 7001:7001 -d node:egg
复制代码
  • 查看运行容器
docker ps # 查看运行容器,获取CONTAINER ID,-a 能够查看全部包括中止的容器
复制代码
  • 查看容器 log
docker logs b0d0c3df5eed
复制代码
  • 进入容器
docker exec -it b0d0c3df5eed bash
du -a -d 1 -h # 查看容器目录文件大小
复制代码

踩坑2:在 docker 中运行记得把 package.json 中的 egg-scripts start --daemon 中的 --daemon 删掉。 须要理解前台、后台运行进程的概念,docker 中的 shell 脚本必须之前台方式运行。

优化镜像大小

上文看到,可能源代码就几百K,镜像包却超过1G。看看能有哪些优化手段。

  • 基础镜像下手

也许你并不须要 docker node 提供完整的例如 bash、git 等工具。只须要基本的 node 运行环境便可,则可使用 alpine 镜像。

- FROM node:12.16.3
+ FROM node:12.16.3-alpine
复制代码
  • npm 包优化
- RUN npm install
+ # 无关运行的开发依赖包都该归属 devDependencies
+ RUN npm install --production
复制代码
  • 打包
docker build -t node:simplfy .
复制代码
  • 效果
REPOSITORY  TAG      IMAGE ID      CREATED         SIZE
node        simplfy  8ccafec91d90  28 seconds ago  132MB
复制代码

镜像包从 1.12G 降到了 132MBnode_modules214MB 降到了 44.5M

踩坑3:alpine 镜像容器不支持 bash

你若是须要 bash、git 能够这么作 issue

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh
复制代码

或不想臃肿你的镜像 issue,其实你可使用 sh

docker exec -it container_id sh
复制代码

你真的须要 egg 镜像吗?

在无 docker 本地开发时,你可使用 egg-mongoose 这样链接数据库

`mongodb://127.0.0.1/your-database`
复制代码

使用 docker 后,容器的 127.0.0.1localhost 与你本地的环境是不通的。

有两种方式链接 docker mongo:

  1. 使用可访问的真实IP地址,例如: mongodb://192.1.2.3/your-database
  2. docker networks 容器间的的通讯。

例如在 Dockerfile 中设置真实IP

# 设置数据库IP
ENV docker_db=192.1.2.3
复制代码

链接 url

`mongodb://${process.env.docker_db}/your-database`
复制代码

对于每个开发都得不停更换网络IP,对开发不友好。

思考:

  1. 如何自动区分本地与 docker 起的环境?
  2. 如何隔离本地与 docker 例如 node_modules 冲突?
  3. 如何既能享受本地环境和工具带来的开发效率,又能快速切入 docker 查看部署效果。

镜像包还面临一个存储问题,不当心发到开源 docker hub 仓库,可能致使源码泄漏。

自建仓库?

大多数教程一上来,必然或大篇章都是 Dockerfile 构建镜像。介于以上种种,能不能换种思路,放弃构建 image。

docker-compose

docker-compose 用来编排多容器的启动部署。

mongo 配置

  • 新建 docker-compose.yml 文件。
version: "3"
 services:  db:  image: mongo:4.2.6 # 镜像:版本  environment:  - MONGO_INITDB_ROOT_USERNAME=super # 默认开启受权,并建立超管用户 mongo -u godis -p godis@admin --authenticationDatabase admin  - MONGO_INITDB_ROOT_PASSWORD=xxx # 超管密码,敏感数据也可使用 `secrets`,不赘述。  - MONGO_INITDB_DATABASE=admin # *.js 中默认的数据库  volumes:  - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro  - ./mongo-volume:/data/db  ports:  - "27017:27017"  restart: always 复制代码

简单说明

  • version: "3",不是指你的应用配置版本,而是指 docker 支持的版本,详情说明

  • MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD 环境变量用来开启受权,docker 自动建立一个数据库超管角色。

  • docker mongo 启动容器时会执行 /docker-entrypoint-initdb.d/ 中的 *.js 脚本,例如这里 init-mongo.js 来初始化数据库角色。

  • MONGO_INITDB_DATABASE 数据库就是 *.js 中默认的 db 对象,这里指向 admin

  • ./mongo-volume:/data/db 映射目录或卷,持久化数据库文件。

  • init-mongo.js

// https://stackoverflow.com/questions/42912755/how-to-create-a-db-for-mongodb-container-on-start-up
// 分别在 user、staff 数据库上建立访问角色。
// 这里 db 是 MONGO_INITDB_DATABASE 指定的数据库
db.getSiblingDB('user')
  .createUser(
    {
      user: 'user',
      pwd: 'xx',
      roles: [ 'readWrite', 'dbAdmin' ],
    }
  );
 db.getSiblingDB('staff') .createUser( { user: 'staff', pwd: 'yy', roles: [ 'readWrite', 'dbAdmin' ], } ); 复制代码
  • 密码配置有点散乱,如何跟应用一起存取(docker-compose 设置 secrets 文件,node 也读取改文件?),有点麻烦,读者有更好的方案还望不吝赐教。

node 配置

services:
  ...
 server:
 image: node:12.16.3-alpine
 depends_on:
 - db
 volumes:
 - ./:/usr/src/egg
 environment:
 - NODE_ENV=production
 - docker_db=db
 working_dir: /usr/src/egg
 command: /bin/sh -c "npm i && npm run start" # not works: npm i && npm run start and not support bash
 ports:
 - "7001:7001"
 volumes:  nodemodules: 复制代码

说明:

  • depends_on 表示依赖的容器,docker 会等待依赖项先启动。
  • volumes 映射本地目录到容器,这样本地修改了也能影响到容器。
  • environment 能够在 process.env 拿到。
  • working_dir 设置 pwd,不像 Dockerfile,不存在是会自动建立。
  • command 启动容器后执行的命令行。

踩坑4:command: npm i && npm run start 不支持 &&alpine 镜像不支持 bashegg-bin dev 会报错 Error: Cannot find module '/bin/bash'

  • 注意:docker node 是如何与 docker mongo 通讯的?
environment:
  ...
 - docker_db=db # db 就是 services 中定义 mongo 的名称
复制代码
`mongodb://${process.env.docker_db}/your-database`
复制代码

大部分教程都是用 links 来解决,但官方不推荐并准备废弃。推荐使用 networks。这里并无配置 networks。 这是由于 docker 会默认建立名称为 projectname_defaultnetworks,用来 docker-compose 容器间的通讯。

  • 如何隔离本地与 docker node 映射中 node_modules?
services:
  ...
  server:
    image: node:12.16.3-alpine
    depends_on:
      - db
    volumes:
+ - nodemodules:/usr/src/egg/node_modules
      - ./:/usr/src/egg
    environment:
      - NODE_ENV=production
      - docker_db=db
    working_dir: /usr/src/egg
    command: /bin/sh -c "npm i && npm run start" # not works: npm i && npm run start and not support bash
    ports:
      - "7001:7001"
 + volumes: + nodemodules: 复制代码
  • docker-compose.yml 文件目录下文件运行 docker-compose
docker-compose up -d
复制代码
  • 为何不直接使用匿名卷?
volumes:
 - :/usr/src/egg/node_modules
 - ./:/usr/src/egg
复制代码
  • 答:若是须要多 docker-compose 文件来区分环境,例如开发时,没有必要每次启动时执行一次 npm i

建立 docker-compose.notinstall.yml 文件

version: "3"
 services:  server:  environment:  - NODE_ENV=development # 覆盖  - NEW_ENV=add # 新增  command: npm run start # 覆盖 复制代码
  • 第二次,你能够执行如下命令,减小 npm i 带来的消耗。若是你使用匿名卷,则 node_modules 每一个容器相互独立没法共享,致使报错。
docker-compose -f docker-compose.yml -f docker-compose.notinstall.yml up -d
复制代码

更多多文件使用,查看文档 Share Compose configurations between files and projects

最后借助 package.json scripts 优化记忆命令行。

结果

开发部署问题暂时告一段落,项目还在开发当中,线上运行一段时间后再来分享。水平有限,有错误欢迎指出,有更好的建议也欢迎补充。

参考

  1. A Better Way to Develop Node.js with Docker
  2. Dockerizing a Node.js web app
  3. docker_practice
  4. YAML
  5. Managing MongoDB on docker with docker-compose
  6. mongoosejs
  7. docker mongo
  8. egg-docker-template
  9. 只需简单两步,轻松缩减 Node.js 应用的镜像大小
  10. Cannot pass any ENV variables from docker to Node process.env
相关文章
相关标签/搜索