问题描述
基本上,服务foo
和bar
都依赖于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之外,您显示的设置基本上与多阶段构建相同。特别是如果您的图像不运行命令而仅包含文件,那么它是多阶段构建的早期阶段的最佳选择。