原文链接
最近在捯饬一个前端性能上报分析的项目,前端由 react
全家桶,打包部署公司有专门的发布系统,这块就没什么顾虑。
前端团队的后端没有什么规范或通用流程,就想自己先技术选型从0到1,决定使用 egg + mongodb
,后续也许会追加 nginx + redis + Kafka
相关配置。
docker
!总体目标不外乎几点:简单、快速、安全。
docker
的优势。CI
。PS:搜索搜的头秃,以下很多关键知识点收集自 google + github issue + Stack Overflow + docker 官方文档 + 英文博客。英文有障碍真是影响效率。
接下来说说实践中遇到的问题。
步骤:
换台电脑或协同其他小伙伴开发时,得把你的动作重复一遍。不同的操作系统和下的不同 node 或 db 版本,都有可能导致系统运行不起来。
你肯定听过这句话:我的电脑上是好的啊。
约束不了团队众多软件的安装和版本的控制,要求安装一定范围的 docker
还是简单的吧。
例如启动 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 效果是一样的。
编写 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
:
docker build -t node:egg . 复制代码
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 可以查看所有包括停止的容器 复制代码
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 复制代码
- 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
降到了 132MB
,node_modules
从 214MB
降到了 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 复制代码
在无 docker 本地开发时,你可以使用 egg-mongoose
这样连接数据库
`mongodb://127.0.0.1/your-database` 复制代码
使用 docker 后,容器的 127.0.0.1
或 localhost
与你本地的环境是不通的。
有两种方式连接 docker mongo:
mongodb://192.1.2.3/your-database
例如在 Dockerfile
中设置真实IP
# 设置数据库IP ENV docker_db=192.1.2.3 复制代码
连接 url
`mongodb://${process.env.docker_db}/your-database` 复制代码
对于每一个开发都得不停更换网络IP,对开发不友好。
思考:
node_modules
冲突?镜像包还面临一个存储问题,不小心发到开源 docker hub 仓库,可能导致源码泄漏。
自建仓库?
大多数教程一上来,必然或大篇章都是 Dockerfile
构建镜像。介于以上种种,能不能换种思路,放弃构建 image。
docker-compose
用来编排多容器的启动部署。
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_USERNAME
、MONGO_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' ], } ); 复制代码
secrets
文件,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
镜像不支持 bash
,egg-bin dev
会报错 Error: Cannot find module '/bin/bash'
。
environment: ... - docker_db=db # db 就是 services 中定义 mongo 的名称 复制代码
`mongodb://${process.env.docker_db}/your-database` 复制代码
大部分教程都是用 links
来解决,但官方不推荐并准备废弃。推荐使用 networks
。这里并没有配置 networks
。
这是因为 docker 会默认创建名称为 projectname_default
的 networks
,用来 docker-compose
容器间的通信。
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
优化记忆命令行。
开发部署问题暂时告一段落,项目还在开发当中,线上运行一段时间后再来分享。水平有限,有错误欢迎指出,有更好的建议也欢迎补充。