docker 和 node.js 的最佳实践

889 阅读2分钟

前言

Node官方引用的 jmealo 写的一篇docker 和 node.js 的最佳实践。


环境变量


运行时将 NODE_ENV 设置为 production。这也是你向你的应用程序传递加密和其他运行时配置的方式。

-e "NODE_ENV=production"

全局npm依赖


如果你需要安装全局npm依赖项,建议将这些依赖项放在非root用户目录下。为了实现这一点,在你的 Dockerfile 文件中添加以下一行:

ENV NPM_CONFIG_PREFIX=/home/node/.npm-global

ENV PATH=$PATH:/home/node/.npm-global/bin 
# optionally if you want to run npm global bin without specifying path

升级/降级 Yarn


本地(局部)

如果你需要对本地安装的 yarn 进行升级/降级,你可以通过在 Dockerfile 中发出以下命令来实现:

注意,如果你创建了一些其他的目录,而这些目录不是你运行命令的地方的后代目录,你最终会使用全局(过时)的版本。如果你想在全局范围内升级yarn,请按照下一节的说明进行。

当按照本地安装说明进行安装时,由于重复的yarn,镜像最终会变大。

FROM node:6

ENV YARN_VERSION 1.16.0

RUN yarn policies set-version $YARN_VERSION

全局

FROM node:6

ENV YARN_VERSION 1.16.0

RUN curl -fSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
    && tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
    && ln -snf /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
    && ln -snf /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
    && rm yarn-v$YARN_VERSION.tar.gz

如果你使用的是基于Alpine的镜像,curl 命令将不存在,所以你需要确保在使用它的时候安装了它:

FROM node:6-alpine

ENV YARN_VERSION 1.5.1

RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
#国内可能需要切换下系统软件源
RUN apk add --no-cache --virtual .build-deps-yarn curl \
    && curl -fSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
    && tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
    && ln -snf /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
    && ln -snf /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
    && rm yarn-v$YARN_VERSION.tar.gz \
    && apk del .build-deps-yarn

处理内核信号


Node.js不是被设计成以PID 1的形式运行的,这导致了在Docker内部运行时的意外行为。例如,作为PID 1运行的Node.js进程不会响应 SIGINTCTRL-C)和类似的信号。从Docker 1.13开始,你可以使用 --init 标志将你的Node.js进程包裹在一个轻量级的init系统中,该系统可以正确处理作为PID 1运行。

$ docker run -it --init node

你也可以在你的Dockerfile文件中直接包含Tini,确保你的进程总是以init包装器启动。

非root用户


默认情况下,Docker以root身份在容器内运行命令,当超级用户权限不是严格需要时这违反了最低权限原则(PoLP)。你希望尽可能地以非特权用户的身份运行容器。node镜像为这种目的提供了 node 用户。然后可以用 node 用户运行Docker Image,方法如下:

-u "node"

或者,可以在 Dockerfile 文件中激活该用户:

FROM node:6.10.3
...
# At the end, set the user to use when running this image
USER node

注意,node 用户既不是构建时的依赖,也不是运行时的依赖,只要你想添加到容器中的应用程序的功能不依赖于它,它就可以被删除或改变。

如果你不想要也不需要在这个镜像中创建的用户,你可以用下面的方法删除它:

# For debian based images use:
RUN userdel -r node

# For alpine based images use:
RUN deluser --remove-home node

如果你需要改变用户的uid/gid,你可以使用:

RUN groupmod -g 999 node && usermod -u 999 -g 999 node

如果你需要用户的另一个名字(例如:myapp),执行:

RUN usermod -d /home/myapp -l myapp node

对于基于alpine的镜像,你没有 groupmod 也没有 usermod,所以要改变uid/gid你必须删除以前的用户:

RUN deluser --remove-home node \
  && addgroup -S node -g 999 \
  && adduser -S -G node -u 999 node

内存


默认情况下,任何Docker容器可能消耗大量的硬件资源,如CPU和RAM。如果你在同一台主机上运行多个容器,你应该限制它们能消耗多少内存。

-m "300M" --memory-swap "1G"

CMD


在创建镜像时,你可以绕过 package.jsonstart 命令,直接将其记录到镜像本身。首先,这可以减少在你的容器内运行的进程的数量。其次,它导致退出信号如 SIGTERMSIGINT 被Node.js进程接收,而不是npm吞下它们。

CMD ["node","index.js"]

Docker Run


下面是一个如何运行默认的Node.JS Docker容器化应用程序的例子:

$ docker run \
  -e "NODE_ENV=production" \
  -u "node" \
  -m "300M" --memory-swap "1G" \
  -w "/home/node/app" \
  --name "my-nodejs-app" \
  node [script]

安全


Docker团队提供了一个工具来分析你正在运行的容器的潜在安全问题。你可以从这里下载并运行这个工具:github.com/docker/dock…

node-gyp alpine


下面是一个例子,说明你如何在alpine变体上安装需要node-gyp支持的软件包的依赖:

FROM node:alpine

RUN apk add --no-cache --virtual .gyp python3 make g++ \
    && npm install [ your npm dependencies here ] \
    && apk del .gyp

国内需要切换系统软件源,需在FROM下一行加上:

RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

这里有一个多阶段构建的例子:

FROM node:alpine as builder

## Install build toolchain, install node deps and compile native add-ons
RUN apk add --no-cache python3 make g++
RUN npm install [ your npm dependencies here ]

FROM node:alpine as app

## Copy built node modules and binaries without including the toolchain
COPY --from=builder node_modules .

总结

十分实用的实践技巧。

参考链接: