Другой подход к созданию собственных проектов NestJS с помощью Docker

Что такое NestJS?
NestJS - это великолепный фреймворк для создания серверных приложений для разработчиков NodeJS, использующий в основном общий ExpressJS под капотом, и не только это. Фреймворк очень консолидирован и удобен для предприятий. Если вам интересно узнать больше о NestJS, прочтите мою статью о том, как написать REST API с NestJS.
В чем проблема со сборкой Docker?
При запуске вашего приложения с контейнерами, особенно с приложениями NodeJS, вы обычно хотите иметь более быстрые сборки, которые не потребляют много памяти. Базовая сборка с NestJS без особого внимания может легко увеличиться до 1 ГБ, и это потребует времени в конвейере CI / CD, а также большого расхода памяти. Если вам нужно быстрое решение этой проблемы, вы можете прочитать мою предыдущую статью о образах докеров для NestJS.
Когда я говорю о базовом файле Dockerfile, я имею в виду примерно следующее:
FROM node:14.15.0-alpine3.10 USER 2000 RUN mkdir -p /home/node/app/node_modules && chown -R 2000:2000 /home/node/app WORKDIR /home/node/app COPY --chown=2000:2000 . /home/node/appRUN yarn installRUN yarn build EXPOSE 3000 ENTRYPOINT ["node"] CMD ["/home/node/app/dist/main.js"]
Это создаст образ размером около 900 МБ.
Для простого решения, основанного на моей предыдущей статье, вы можете начать использовать мульти-сборку для сборки своего проекта, и последний подход был следующим:
FROM node:16-alpine3.11 AS BUILD_IMAGE RUN apk update && apk add yarn curl bash make && rm -rf /var/cache/apk/* RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | bash -s -- -b /usr/local/bin WORKDIR /usr/src/app # install dependencies RUN yarn --frozen-lockfile COPY . . RUN yarn install RUN yarn build RUN npm prune --production RUN /usr/local/bin/node-prune FROM node:16-alpine3.11 USER 1000 RUN mkdir -p /home/node/app/ RUN mkdir -p /home/node/app/node_modules RUN mkdir -p /home/node/app/dist RUN chown -R 1000:1000 /home/node/app RUN chown -R 1000:1000 /home/node/app/node_modules RUN chown -R 1000:1000 /home/node/app/dist WORKDIR /home/node/app COPY --from=BUILD_IMAGE /usr/src/app/dist /home/node/app/dist COPY --from=BUILD_IMAGE /usr/src/app/node_modules /home/node/app/node_modules EXPOSE 3000 ENTRYPOINT ["node"] CMD ["/home/node/app/dist/main.js"]
Вроде здорово, итоговое изображение получается ~ 156мб. Но можем ли мы улучшить скорость сборки?
После некоторого исследования я пришел к выводу, что хотел бы иметь три изображения в процессе сборки, но работающие по-другому:
- Чистый образ с моим предпочтительным изображением узла (лично мне нравится официальный узел-alpine). Затем из нашего предпочтительного базового образа мы устанавливаем все необходимые библиотеки для запуска нашего проекта, включая, в нашем случае, удаление узлов.
- Второй образ, который устанавливает наши необходимые модули node_modules, которые мы можем использовать в качестве «зависимостей изображений».
- Последний образ, который просто построит проект и подготовит его к запуску.
Чистый образ
Чтобы создать чистый образ, я добавлю публичный репозиторий докеров. Он будет содержать:
FROM node:16-alpine3.11 AS base_image RUN apk update && apk add yarn curl bash make && rm -rf /var/cache/apk/* RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | bash -s -- -b /usr/local/bin
затем для сборки я уже буду указывать на свой общедоступный репозиторий под названием makinhs / nestjs-base
docker build -t makinhs/nestjs-base .
Отлично, этот образ поможет избежать проблем с предупреждениями Python при создании проекта NestJS, а также с установкой node-prune для выполнения после запуска нашего проекта. Отправить в мой репозиторий было просто:
docker push makinhs/nestjs-base:latest
Помните, что вам не разрешат отправлять файлы в мой репозиторий, но вы можете использовать его в качестве базового образа, поскольку он общедоступен;) Исходный файл можно найти здесь.
Круто, а что дальше? Зависимости!
Настройка образа зависимостей
Этого шага можно избежать в зависимости от случая, но для меня в качестве доказательства концепции я хотел бы иметь изображение со всеми моими необходимыми модулями node_modules. Для зависимостей я буду использовать этот репозиторий git. Проект, который я хочу построить, тоже принадлежит этому репозиторию. Временно я просто скопирую package.json из моего проекта NestJS и буду использовать его как основу для моего репозитория изображений зависимостей. В итоге у нас будет что-то вроде этого для нашего package.json
{
"name": "docker-nestjs-dependencies",
"version": "1.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/makinhs/docker-nestjs-dependencies.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/makinhs/docker-nestjs-dependencies/issues"
},
"homepage": "https://github.com/makinhs/docker-nestjs-dependencies#readme",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^7.5.1",
"@nestjs/config": "^0.6.1",
"@nestjs/core": "^7.5.1",
"@nestjs/mapped-types": "^0.2.0",
"@nestjs/platform-express": "^7.5.1",
"@nestjs/typeorm": "^7.1.5",
"bcrypt": "^5.0.0",
"class-transformer": "^0.3.2",
"class-validator": "^0.13.1",
"pg": "^8.5.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.3",
"typeorm": "^0.2.30"
},
"devDependencies": {
"@nestjs/cli": "^7.5.1",
"@nestjs/schematics": "^7.1.3",
"@nestjs/testing": "^7.5.1",
"@types/express": "^4.17.8",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^4.6.1",
"@typescript-eslint/parser": "^4.6.1",
"eslint": "^7.12.1",
"eslint-config-prettier": "7.1.0",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.6.3",
"prettier": "^2.1.2",
"supertest": "^6.0.0",
"ts-jest": "^26.4.3",
"ts-loader": "^8.0.8",
"ts-node": "^9.0.0",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.0.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
Помните, что я взял это из своего проекта NestJS, который можно найти здесь.
Давайте теперь создадим наш Dockerfile с помощью:
FROM makinhs/nestjs-base:latest AS dependencies WORKDIR /usr/src/app # install dependencies RUN yarn --frozen-lockfile COPY . . RUN yarn install
Для публичной отправки я буду хранить в этом публичном репозитории с именем, установленным как makinhs / nestjs-dependencies.
Не забудьте добавить туда .dockerignore с node_modules, чтобы избежать проблем со сборкой.
docker build -t makinhs/nestjs-dependencies .
Это ловушка для приложений NodeJS ... Этот образ с node_modules содержит ~ 1 ГБ. Если ваш проект не часто добавляет новые зависимости, почему бы не установить этого монстра где-нибудь? Вот почему в этой статье я пытаюсь достичь среднего образа зависимостей, чтобы попытаться ускорить процесс сборки. Нажатие на докер будет:
docker push makinhs/nestjs-dependencies
В данный момент я не забочусь о тегах докеров, но в ваших проектах вам действительно нужно быть более организованными в этом смысле.
Объединение частей и построение нашего проекта
Теперь в нашем проекте NestJS мы обновим наш Dockerfile, чтобы попробовать и протестировать сборку:
FROM makinhs/nestjs-dependencies:latest AS BUILD_IMAGE WORKDIR /usr/src/app COPY . . RUN yarn build RUN npm prune --production RUN /usr/local/bin/node-prune FROM makinhs/nestjs-base:latest USER 1000 RUN mkdir -p /home/node/app/ RUN mkdir -p /home/node/app/node_modules RUN mkdir -p /home/node/app/dist RUN chown -R 1000:1000 /home/node/app RUN chown -R 1000:1000 /home/node/app/node_modules RUN chown -R 1000:1000 /home/node/app/dist WORKDIR /home/node/app COPY --from=BUILD_IMAGE /usr/src/app/dist /home/node/app/dist COPY --from=BUILD_IMAGE /usr/src/app/node_modules /home/node/app/node_modules EXPOSE 3000 ENTRYPOINT ["node"] CMD ["/home/node/app/dist/main.js"]
Обратите внимание, что мы используем образ зависимостей для создания образа, затем запускаем node-prune и после запуска нашего базового образа просто захватываем необходимые файлы для поддержки короткой сборки в конце (помните, что зависимости получили ~ 1 ГБ, мы хочу этого избежать).
docker build -t nestjs-api .
ваш окончательный размер изображения составляет ~ 200 МБ о /
В этой статье я попытался представить доказательство концепции другого способа создания ваших проектов NestJS. Это может быть слишком сложно, если ваш проект очень маленький, и вы можете избежать использования зависимостей в качестве внешнего образа. В следующей статье я предоставлю интеграцию с некоторыми CI и проведу тесты сборки производительности, используя подход этой версии по сравнению с моим предыдущим унифицированным подходом.
Эта статья является скорее доказательством концепции, чем инструкцией по выбору наилучшего способа создания вашего имиджа. Мне было любопытно применить этот подход, и до сих пор существуют фактические тесты производительности CI / CD, а также есть дополнительный уровень для заботы о ваших зависимостях package.json, которые требуют некоторой автоматизации в вашем конвейере, чтобы избежать проблем.
- Ветка NestJS с обновленным Dockerfile находится здесь.
- Dockerfile зависимости можно найти здесь
- Базовый файл Dockerfile можно найти здесь
Больше контента на plainenglish.io