Docker为Monorepo环境构建

问题描述

基本上,服务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

我有在这里迷路了。如果我不断重复将相同的程序包从一个程序包安装到另一个程序包,以单一存储方式组织多个应用程序有什么意义?毕竟,这使我更加难以编写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" ]

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

解决方法

每个package.json文件都需要列出其应用程序的完整直接依赖项。我应该能够检出您的源存储库,运行yarn install并可以正常工作应用程序树。您的问题说“以及通过其他方式将这些其他依赖项安装到环境中,而我只是假设它们”,这对于任何不在您所使用的确切系统上工作的人来说都是一个问题,尤其是对于Docker和其他自动构建工具。

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

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

您应该从common/package.json中分离dependencies您需要运行您的应用程序(devDependencies)在express中列出;您只需构建您的应用程序(dependencies)就应该是eslint。您讨论图像大小;这样,您便可以在实际运行容器时在容器中安装更小的一组软件包。

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

然后,进行多阶段构建可以生成较小的映像。此处的想法是,第一阶段将安装整个dev依赖项,并构建应用程序;第二阶段仅包括运行时依赖项和内置代码。或多或少看起来像这样:

devDependencies

您永远不需要“构建器容器”;除了分散在三个单独的Dockerfile之外,您显示的设置基本上与多阶段构建相同。特别是如果您的图像不运行命令而仅包含文件,那么它是多阶段构建的早期阶段的最佳选择。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...