Сборки Docker для среды monorepo

#docker #lerna #monorepo #yarn-workspaces

#docker #lerna #monorepo #yarn-рабочие пространства

Вопрос:

По сути, обе службы 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
  

Я добавил все devDependencies, которые являются общими для всех пакетов в корневом package.json, следующим образом:

 "scripts": {
  "build": "lerna run build --stream",
  "setup": "yarn amp;amp; 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.

Мой первый вопрос: когда разработчик работает над монолитной кодовой базой, является ли это нормальным поведением для установки библиотек в определенный пакет, если это необходимо?

This is how my Dockerfile looks like:

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

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

First, execute the command below.

 $ yarn docker:bootstrap
  

Dockerfile.init creates an initial builder image from which the “real” builder image can copy the build directory.

 // 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:

В каждом package.json файле должен быть указан полный список непосредственных зависимостей его приложения.Я должен иметь возможность проверить ваш исходный репозиторий, запустить yarn install и иметь рабочее дерево приложений. Там, где в вашем вопросе говорится: «и, кстати, эти другие зависимости установлены в среде, и я просто предполагаю их», это проблема для всех, кто не работает именно с той системой, в которой вы находитесь, и, в частности, это проблема для Docker и других инструментов автоматической сборки.

Ваши библиотечные зависимости могут иметь свои собственные библиотечные зависимости. Они будут перечислены в yarn.lock файле, но их необязательно указывать непосредственно в package.json файле.

В качестве примера возьмем библиотеки доступа к базе данных: если ваше основное приложение использует их, они должны быть включены в ваше dependencies . Но если весь доступ к базе данных инкапсулирован в вашей common общей библиотеке, вашим приложениям нужно только ссылаться на эту библиотеку (in foo/package.json ), а библиотека должна включать зависимости базы данных (in common/package.json ).

Вы должны отделиться dependencies от devDependencies .В списке должны быть перечислены вещи, необходимые для запуска вашего приложения ( 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"]
  

Вам никогда не понадобится «контейнер builder»; показанная вами настройка в основном идентична многоступенчатой сборке, за исключением распределения по трем отдельным файлам Dockerfile. В частности, если у вас есть образ, который не запускает команду, а просто содержит файлы, это хороший кандидат на раннюю стадию многоступенчатой сборки.

Комментарии:

1. Спасибо за ваш ответ, Дэвид. Вы не возражаете, если мы посмотрим на исходный репозиторий? К сожалению, я не совсем понимаю ваш пост. Таким образом, каждый пакет не должен иметь собственных зависимостей lib от своего файла package.json? Вот ссылка на репозиторий. github.com/jeffminsungkim/jmsk-tutorials/tree/master/… Мы могли бы поговорить и о живом ресурсе VSCode.

2. В этом ответе отсутствует контекст re: monorepos.

3. Как сказал Эдвард, я не думаю, что полностью понял ответ Дэвида.