再遇 Docker,容器化 Node 应用
首先声明,这不是一个教程贴,更多的是遇到的问题和解决方式。内容仅供参考。
一直以来就想把 Node 应用容器化,奈何一直没有精力去捣鼓。今天下午捣鼓了一下午,终于捣鼓出来了。说说遇到坑还有怎么去解决吧。至于 docker 这玩意怎么去用网上内容一搜一大把。没有必要再去描述了。
编写 Dockerfile
首先,我们这次要做的容器首先肯定是要摆脱 node_modules 的,不能我 build 完 image 之后 push 到 docker hub,用户 pull 来之后还要再 npm install
一下的。这肯定是不行的。具体怎么实现摆脱 node_modules ,我在上一篇文章中讲述过了,可以参考一下 使用 GitHub CI 云构建和自动部署。
在项目根目录新建一个 dockerfile,编写如下。
FROM node:16 as builder
WORKDIR /app
COPY . .
RUN npm i -g pnpm
RUN pnpm install
RUN pnpm bundle # 可以参考 rimraf out && yarn run build && cd dist/src && npx ncc build main.js -o ../../out -m
FROM node:16
WORKDIR /app
COPY --from=builder /app/out .
EXPOSE 2333
CMD node index.js
这里用了两个 worker 去构建,第一个先 build 项目,生成构建产物,然后在第二个 worker copy 第一个中的构建产物,最后生成的 image 仅仅只有第二个的,第一个 builder 不会封装进去,可以大大减少 image 的体积。
这样 build 出来的 image 最终是 1G 左右,用户可以直接 pull 就直接跑的。这个体积算大吗,除了自带的 node、Debian 环境没有引入其他的包甚至 node_modules。再体积方面,可以用 node:16-alpine 这个 image 继续做优化,apline 是最小化的 Linux 镜像了(大概),整个 image 只有 200M 左右,应经测试,用 apline 构建出来的 image 体积只有 250M。可以对比一下。但是为什么我最终没有用 apline 呢,原因还是他太小了,ncc build 项目的时候缺了一堆库,就算用 apk 把缺的库全部补上之后,在生产中依旧跑不起来,可能还是摆脱不了 node_modules,多次尝试后,以失败告终。如有好的办法请联系我。
我的项目中用到了 MongoDB 和 Redis,那么就需要再去编写 docker-compose。这个就没啥好说了,网上一搜一大把。给个参考吧。而我想说的是,我遇到的坑。
version: '3.8'
services:
app:
container_name: mx-server
image: innei/mx-server:latest
restart: 'on-failure'
ports:
- '2333:2333'
depends_on:
- mongo
- redis
links:
- mongo
- redis
networks:
- app-network
mongo:
container_name: mongo
image: mongo
volumes:
- ./data:/data/db
ports:
- '3344:27017'
networks:
- app-network
redis:
image: redis
container_name: redis
ports:
- '3333:6379'
networks:
- app-network
networks:
app-network:
driver: bridge
首先是,在项目中,mongo 和 redis 连得都是 127.0.0.1 但是在 docker-compose up 之后,连不上,怎么回事,后来知道是要改成 service 的名字。就比如
services:
mongo: # 这里的 name 是啥 到时候连的时候 host 就要填这个
container_name: mongo
image: mongo
volumes:
- ./data:/data/db
ports:
- '3344:27017'
networks:
- app-network
上面的案例,在项目中的连接地址必须是 mongodb://mongo:27017
。然后原先项目中的 host 定义都是通过 argv 传递的,如
export const MONGO_DB = {
collectionName: (argv.collection_name as string) || 'mx-space',
get uri() {
return `mongodb://${argv.db_host || '127.0.0.1'}:${
argv.db_port || '27017'
}/${process.env.TEST ? 'mx-space_unitest' : this.collectionName}`
},
}
然后就一直再找,怎么在 run 的时候 pass argument,最后也是找了一圈没找到。说是可以在 dockerfile 中。加上这两行可以读到 argument。
FROM node:16
ARG redis_host # 这个
ARG mongo_host # 这个
但是,这好像实在 build 的时候就要传递的,那还不如写死算了。所有就变成了这样,也是最后的样子。
FROM node:16 as builder
WORKDIR /app
COPY . .
RUN npm i -g pnpm
RUN pnpm install
RUN pnpm bundle
FROM node:16
ARG redis_host
ARG mongo_host
RUN apt update
RUN apt install zip unzip mongo-tools -y # 因为业务需要额外的 command tools
WORKDIR /app
COPY --from=builder /app/out .
EXPOSE 2333
CMD node index.js --redis_host=redis --db_host=mongo # 直接 pass argument
GitHub CI 自动化构建发布
这一步倒是很简单的,官方有自己的 github action 给你用了,直接去 docker hub 先生成一个 token,填入 secrets。就没什么问题了。关于 GitHub workflow 的 yaml 可以贴一下。可以参考。
name: Docker build
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
# branches:
# - 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: innei/mx-server:latest
然后现在的话,mx-server 可以上 docker hub 了,可以直接跑在 docker 了。单用户可以直接用一下命令跑起服务。奈斯。
cd
mkdir -p mx/server
cd mx/server
wget https://cdn.jsdelivr.net/gh/mx-space/server-next@master/docker-compose.yml
docker-compose up -d