再遇 Docker,容器化 Node 应用

1,140 阅读3分钟

首先声明,这不是一个教程贴,更多的是遇到的问题和解决方式。内容仅供参考。

一直以来就想把 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,多次尝试后,以失败告终。如有好的办法请联系我。

image-20210923175621771

我的项目中用到了 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