Docker构建针对单体代码库环境

4

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

为了解决错误消息 " has unmet peer dependency ",我执行了以下命令。
$ 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" ]

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

1个回答

0
每个package.json文件都需要列出其应用程序的完整直接依赖项。我应该能够检出您的源代码库,运行yarn install,并拥有一个可工作的应用程序树。当您的问题说“顺便提一下,这些其他依赖项已在环境中安装,我只是假设它们存在”时,这对于不在您确切系统上工作的任何人来说都是一个问题,特别是对于Docker和其他自动构建工具而言更是如此。
您的库依赖项可以有其自己的库依赖项。这些将在yarn.lock文件中列出,但不需要直接在package.json文件中列出。
以数据库访问库为例:如果您的主应用程序使用它们,则需要将它们包含在您的依赖项中。但是,如果所有数据库访问都封装在您的common共享库中,则您的应用程序只需要引用该库(在foo/package.json中),并且该库需要包含数据库依赖项(在common/package.json中)。
你应该将dependenciesdevDependencies分开。需要列出dependencies,以运行应用程序(express)所需的内容; 只需要构建应用程序(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中。 特别是如果你有一个不运行命令而只包含文件的镜像,它是多阶段构建中早期阶段的一个好选择。

1
谢谢你的回答David。你介意我们通过查看源代码库来进行吗?不幸的是,我没有完全理解你的帖子。所以每个包都不应该在它们的package.json文件中有自己的lib依赖关系?这是repo链接。https://github.com/jeffminsungkim/jmsk-tutorials/tree/master/monorepo-nestapps 我们也可以在VSCode实时共享上进行一些对话。 - JeffMinsungKim
3
此回答缺少关于单体仓库的上下文。 - Edward R. Mazurek
就像 Edward 所说的那样,我认为我还没有完全理解 David 的答案。 - JeffMinsungKim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接