【问题标题】:Docker builds for a monorepo environmentDocker 为 monorepo 环境构建
【发布时间】:2020-10-07 07:53:26
【问题描述】:

基本上,foobar 服务都依赖于 common 库。

假设 common 包已经发布到 npm 注册表。

|
├── packages
|    ├── common
|    |    ├── src
|    |    ├── package.json
|    |    ├── tsconfig.build.json
|    |    ├── tsconfig.json
|    ├── foo
|    |    ├── src
|    |    ├── Dockerfile
|    |    ├── package.json
|    |    ├── tsconfig.build.json
|    |    ├── tsconfig.json
|    ├── bar
|    |    ├── src
|    |    ├── Dockerfile
|    |    ├── package.json
|    |    ├── tsconfig.build.json
|    |    ├── tsconfig.json
├── tsconfig.json
├── package.json
├── yarn.lock
├── docker-compose.init.yml
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.init
├── .dockerignore

我在根 package.json 中添加了所有包共有的 devDependencies,如下:

"scripts": {
  "build": "lerna run build --stream",
  "setup": "yarn && yarn build",
  "docker:bootstrap": "docker-compose --file=docker-compose.init.yml build",
  "docker:up": "docker-compose up --build"
},
"devDependencies": {
  "@nestjs/cli": "^7.5.1",
  "@nestjs/common": "^7.4.4",
  "@nestjs/core": "^7.4.4",
  "@nestjs/platform-express": "^7.4.4",
  "@nestjs/schematics": "^7.1.2",
  "@nestjs/testing": "^7.4.4",
  "@types/express": "^4.17.8",
  "@types/jest": "^26.0.13",
  "@types/node": "^14.10.2",
  "@types/supertest": "^2.0.10",
  "@typescript-eslint/eslint-plugin": "^4.1.1",
  "@typescript-eslint/parser": "^4.1.1",
  "eslint": "^7.9.0",
  "eslint-config-prettier": "^6.11.0",
  "eslint-plugin-prettier": "^3.1.4",
  "express": "^4.17.1",
  "husky": "^4.3.0",
  "jest": "^26.4.2",
  "lerna": "^3.22.1",
  "lint-staged": "^10.4.0",
  "prettier": "^2.1.2",
  "reflect-metadata": "^0.1.13",
  "rimraf": "^3.0.2",
  "rxjs": "^6.6.3",
  "supertest": "^4.0.2",
  "ts-jest": "^26.3.0",
  "ts-loader": "^8.0.3",
  "ts-node": "^9.0.0",
  "typescript": "3.9.5"
}

foo包需要用到关系型数据库,所以我独立安装了以下包。

$ yarn workspace foo add @nestjs/typeorm mysql typeorm

为了解决错误消息"<package> has unmet peer dependency <package>",我点击了以下命令。

$ yarn workspace foo add @nestjs/common @nestjs/core @nestjs/platform-express rxjs

我有点迷路了。如果我不断重复自己将相同的包从一个包安装到另一个包,那么以 monorepo 方式组织多个应用程序有什么意义?毕竟写 Dockerfile 让我更难了。

我的第一个问题是,当开发人员在单片代码库上工作时,在必要时将库安装到特定包是正常行为吗?

这就是我的 Dockerfile 的样子:

// docker-compose.init.yml
# This file triggers the initial build
version: "3.8"

services:
  pkg_builder:
    image: pkg-builder
    build:
      context: .
      dockerfile: Dockerfile.init

首先,执行下面的命令。

$ yarn docker:bootstrap

Dockerfile.init 创建一个初始构建器镜像,“真正的”构建器镜像可以从中复制构建目录。

// Dockerfile.init
FROM scratch

# Copy files from the root to build directory
COPY package.json lerna.json yarn.lock tsconfig.json /build/

# This line is required to install dependencies from foo's package.json
COPY ./packages/foo/package.json /build/packages/foo/package.json

从那时起,使用以下命令构建映像:

$ yarn docker:up
// docker-compose.yml
version: "3.8"

services:
  pkg_builder:
    image: pkg-builder
    build: .
  mariadb:
    image: mariadb:10.3
    ports:
      - "3306:3306"
    environment:
      - MYSQL_USER=root
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=tutorial
    restart: always
  foo:
    container_name: foo
    build: ./packages/foo
    ports:
      - "8000:8000"
    depends_on:
      - mariadb
    restart: always
// Dockerfile
FROM node:12-alpine

COPY --from=pkg-builder /build /build

WORKDIR /build

RUN rm -rf node_modules
RUN yarn

CMD ["true"]

问题是,图像的尺寸太大了。这是因为,所有的开发依赖都从pkg-builder 复制而来。

// foo's Dockerfile

FROM node:12-alpine

WORKDIR /app/current

COPY --from=pkg-builder /build/node_modules /app/current/packages/foo/node_modules
COPY --from=pkg-builder /build/tsconfig.json ./tsconfig.json

WORKDIR /app/current/packages/foo

COPY . .

RUN yarn build

EXPOSE 8000

CMD [ "node", "./dist/main" ]

最后,我应该如何缩小图像?在这种情况下,我认为多阶段构建不是缩小尺寸的正确策略。我在这里错过了什么?

【问题讨论】:

    标签: docker lerna monorepo yarn-workspaces


    【解决方案1】:

    每个package.json 文件都需要列出其应用程序的完整直接依赖项。 我应该能够检查您的源存储库,运行yarn install,并拥有一个工作应用程序树。如果您的问题是“顺便说一下这些其他依赖项安装在环境中,我只是假设它们”,这对于没有在您所在的确切系统上工作的任何人来说都是一个问题,更具体地说是 Docker 和其他自动构建工具。

    您的库依赖项可以有自己的库依赖项。这些将在yarn.lock 文件中列出,但不需要直接在package.json 文件中列出。

    以数据库访问库为例:如果您的主应用程序使用它们,它们需要包含在您的dependencies 中。但是如果所有的数据库访问都封装在你的common共享库中,你的应用程序只需要引用那个库(foo/package.json),并且库需要包含数据库依赖(common/package.json)。

    您应该将dependenciesdevDependencies 分开。您需要运行您的应用程序(express) 需要在dependencies 中列出;你只需要构建你的应用程序(eslint)应该是devDependencies。您讨论图像大小;这为您提供了一种在实际运行容器时在容器中安装小得多的软件包的方法。

    (请注意,Yarn 实际上不支持不安装 devDependencies;npm 支持,但使用起来要慢得多。)

    那么多阶段构建可以生成更小的镜像。 这里的想法是,第一阶段安装整个开发依赖项,并构建应用程序;第二阶段仅包括运行时依赖项和构建代码。这或多或少看起来像:

    ARG node_version=12-current
    FROM node:${node_version} AS build
    WORKDIR /app
    COPY package.json yarn.lock .
    RUN yarn install --immutable
    COPY . .
    RUN yarn build
    
    FROM node:${node_version}
    WORKDIR /app
    ENV NODE_ENV=production
    COPY package.json yarn.lock .
    RUN yarn install --immutable
    # RUN npm ci  # in production mode, skips devDependencies
    COPY --from=build /app/dist dist
    CMD ["node", "/app/dist/main.js"]
    

    您永远不需要“构建器容器”;您展示的设置基本上与多阶段构建相同,除了分布在三个单独的 Dockerfile 中。特别是如果您有一个不运行命令而只包含文件的映像,那么它是多阶段构建的早期阶段的理想选择。

    【讨论】:

    • 感谢您的回答大卫。您介意我们通过查看源存储库来完成吗?可悲的是,我不完全理解你的帖子。所以每个包都不应该对他们的 package.json 文件有自己的 lib 依赖项?这是回购链接。 github.com/jeffminsungkim/jmsk-tutorials/tree/master/…我们也可以在 VSCode 直播分享中进行一些对话。
    • 这个答案缺少上下文 re: monorepos。
    • 就像爱德华说的那样,我认为我没有完全理解大卫的回答。
    猜你喜欢
    • 1970-01-01
    • 2019-02-25
    • 1970-01-01
    • 2021-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-01
    • 2018-04-20
    相关资源
    最近更新 更多