如何从具有纱线工作空间的monorepo中的nodejs项目构建泊坞窗图像

时间:2018-05-07 13:33:32

标签: node.js express docker monorepo yarn-workspaces

我们正在与我们的团队一起在我们的网站上查看CI / CD。我们最近也适应了monorepo结构,因为这使我们的依赖关系和概述更容易。目前测试等已准备好用于CI,但我现在正在进行部署。我想创建所需包的docker镜像。

我考虑的事情:

1)将完整的monorepo拉入docker项目,但在我们的项目中运行yarn install导致总项目大小约为700MB,这主要是由于我们的本机应用程序甚至不应该有docker镜像。此外,每次我们必须部署新版本时,这都会导致图像拉长时间长

2)以某种方式捆绑我的项目。我们的前端我们有工作设置,所以应该没问题。但我只是尝试将webpack添加到我们的 由于这个问题,表达api并最终在我的包中出现错误:https://github.com/mapbox/node-pre-gyp/issues/308

3)我尝试仅在所需的项目中运行纱线安装,但这仍将为我的所有项目安装我的node_modules。

4)运行npm包:pkg。这导致单个文件准备好在具有特定节点版本的特定系统上运行。这可行,但我不确定这将如何处理错误和崩溃。

5)另一个解决方案是将项目复制出工作区并在那里运行纱线安装。这个问题是使用纱线工作区(隐式链接的依赖关系)就好了。我必须明确地添加我的其他工作区依赖项。一种可能性是从某个提交哈希引用它们,我现在要测试它。 (编辑:您似乎无法将子目录引用为纱线包)

6)???

我想知道我是否缺少一个选项,只为某个项目提供所需的node_modules,这样我就可以保持我的docker图像很小。

3 个答案:

答案 0 :(得分:2)

我已经按照与您相似的结构进行了一个项目,看起来像:

project
├── package.json
├── packages
│   ├── package1
│   │   ├── package.json
│   │   └── src
│   ├── package2
│   │   ├── package.json
│   │   └── src
│   └── package3
│       ├── package.json
│       └── src
├── services
│   ├── service1
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   └── src
│   └── service2
│       ├── Dockerfile
│       ├── package.json
│       └── src
└── yarn.lock

services/文件夹在每个子文件夹中包含一项服务。每个服务都用node.js编写,并具有自己的package.json和Dockerfile。 它们通常是基于Express的Web服务器或REST API。

packages/文件夹包含所有不是服务的软件包,通常是内部库。

服务可以依赖一个或多个程序包,而不依赖于另一服务。 一个程序包可以依赖于另一个程序包,而不取决于服务。

主要package.json(位于项目根文件夹中的那个)仅包含一些devDependencies,例如eslint,测试运行器等。

假设Dockerfile同时依赖于service1package1,则单个package3看起来像这样:

FROM node:8.12.0-alpine AS base

WORKDIR /project

FROM base AS dependencies

# We only copy the dependencies we need
COPY packages/package1 packages/package1
COPY packages/package3 packages/package3

COPY services/services1 services/services1

# The global package.json only contains build dependencies
COPY package.json .

COPY yarn.lock .

RUN yarn install --production --pure-lockfile --non-interactive --cache-folder ./ycache; rm -rf ./ycache

我使用的实际Dockerfile更为复杂,因为它们必须构建子软件包,运行测试等。但是您应该从此样本中了解这个想法。

您可以看到,诀窍是仅复制特定服务所需的软件包。 yarn.lock文件包含package @ version的列表,其中列出了确切的版本和相关性。在没有所有子软件包的情况下复制它不是问题,yarn将在安装所包含软件包的依赖项时使用在那里解决的版本。

在您的情况下,react-native项目将永远不会成为任何Dockerfile的一部分,因为它不依赖任何服务,因此节省了大量空间。

为简洁起见,我在该答案中省略了许多细节,如果有不清楚之处,请随时在评论中要求精确性。

答案 1 :(得分:0)

我们最近将后端服务投入了monorepo,这是我们必须解决的几个问题之一。纱线没有任何可以在这方面帮助我们的东西,因此我们不得不寻找其他地方。

首先我们尝试了@zeit/ncc,但出现了一些问题,但最终我们设法获得了最终版本。它产生一个大文件,其中包含您的所有代码以及所有依赖项代码。看起来很棒。我只需要将几个文件(js,源映射,静态资产)复制到docker映像。图像要小得多,并且该应用程序可以正常工作。但是运行时内存消耗增加了很多。而不是〜70MB,正在运行的容器消耗了〜250MB。不知道我们是否做错了什么,但是我还没有找到任何解决方案,只有issue提到了这一点。我猜想Node.js会加载并解析捆绑中的所有代码,即使其中的大部分代码从未使用过。

我们所需要做的就是分离每个软件包生产依赖项以构建一个瘦的docker映像。看来这并不是一件容易的事,但我们毕竟找到了一个工具。

我们现在正在使用fleggal/monopack。它将我们的代码与Webpack捆绑在一起并打包为Babel。因此,它还产生一个文件包,但不包含所有依赖关系,仅包含我们的代码。这是我们真正不需要的步骤,但我们不介意它在那里。对我们来说,重要的部分是-Monopack仅将包的生产依赖关系树复制到dist / bundled node_modules。这正是我们所需要的。 Docker映像现在具有100MB-150MB而不是700MB。

有一种更简单的方法。如果node_modules中只有几个非常大的npm模块,则可以在根package.json中使用nohoist。这样,yarn会将这些模块保留在软件包的本地node_modules中,而不必将其复制到所有其他服务的Docker映像中。

例如:

"nohoist": [
  "**/puppeteer",
  "**/puppeteer/**",
  "**/aws-sdk",
  "**/aws-sdk/**"
]

答案 2 :(得分:0)

经过反复试验,我发现仔细使用文件.dockerignore是控制最终图像的好方法。当在monorepo下运行以排除“其他”软件包时,这非常有用。

对于每个软件包,我们都有一个类似的名为dockerignore的文件,它将在构建之前替换实时的.dockerignore文件。

例如 cp admin.dockerignore .dockerignore

下面是admin.dockerignore的示例。请注意,该文件顶部的*表示“忽略所有内容”。 !前缀表示“不要忽略”,即保留。这种组合意味着忽略指定文件之外的所有内容。

*
# Build specific keep
!packages/admin

# Common Keep
!*.json
!yarn.lock
!.yarnrc
!packages/common

**/.circleci
**/.editorconfig
**/.dockerignore
**/.git
**/.DS_Store
**/.vscode
**/node_modules