1. 前言
如果公司我的项目应用容器化部署,那么或多或少理解过 nodejs 镜像,因为前端我的项目或者基于 nodejs 的 BFF 我的项目在构建或者部署的过程中都会依赖 nodejs 镜像
有同学会有疑难,nodejs 镜像有啥好理解的,间接去 docker 镜像官网搜寻下对应 node 版本,而后找到对应的版本号,看下有没有不就好了嘛,比方要找 16.20.0,如下图所示
编辑
而后在 Dockerfile 内写上对应的镜像版本,如下所示
javascript
复制代码
FROM node:16.20.0
眼尖的同学可能会看到,同一个版本比方 16.20.0、14.19.1 有不同的 tag,如下图所示
为什么一个 node 镜像版本会有那么多 tag?我该怎么选
为什么 14 与 16 的 tag,除了版本号之外竟然还有差别?为什么
- 解析 nodejs 镜像
在理解 nodejs 镜像之前,如果对 linux 不太熟,咱们能够先理解 debian 是什么及 Alpine 又是什么,如果对这部分很相熟,能够间接跳到 nodejs 官网镜像形成
2.1 Debian 是什么
Linux 有十分多的发行版本,从性质上划分,大体分为由商业公司保护的商业版本与由开源社区保护的收费发行版本。商业版本以 Redhat 为代表,开源社区版本则以 Debian 为代表。这些版本各有不同的特点,在不同的应用领域施展着不同的作用。
目前的风行发行版如下所示
一般来说 Debian 作为适宜于服务器的操作系统,它比 Ubuntu 要稳固得多。Debian 整个零碎,只有利用层面不呈现逻辑缺点,基本上不会出问题。Debian 整个零碎根底外围十分小,不仅稳固,而且占用硬盘空间小,占用内存小。
更多内容能够查看 CentOS、Ubuntu、Debian 三个 linux 比拟异同
Debian 发行版本 Debian 始终保护着至多三个发行版本:稳定版(stable),测试版(testing)和不稳定版(unstable)。
稳定版(stable): 稳定版蕴含了 Debian 官网最近一次发行的软件包。作为 Debian 的正式发行版本,它是咱们优先举荐给用户您选用的版本。以后 Debian 的稳定版版本号是 12,开发代号为 bookworm。最后版本为 12.0,于 2023 年 06 月 10 日 公布,其更新 12.1 已于 2023 年 07 月 22 日 公布。测试版(testing): 测试版蕴含了那些临时未被收录进入稳定版的软件包,但它们曾经进入了候选队列。应用这个版本的最大好处在于它领有更多版本较新的软件。以后的测试版版本代号是 trixie。不稳定版(unstable): 不稳定版寄存了 Debian 现行的开发工作。通常,只有开发者和那些喜爱过惊险刺激生存的人选用该版本,不稳定版的版本代号永远都被称为 sid。
Debian 发行生命周期及目录 Debian 通常会依照肯定的法则每隔一段时间公布一个新稳定版。对每个稳固发行版本,用户能够失去三年的残缺反对以及额定两年的长期反对。目前的发行工夫线如下图所示
能够看到最新的稳固版本是 12
2.2 Alpine 又是什么
Alpine 是一个面向平安的轻型 Linux 发行版。它不同于通常 Linux 发行版,Alpine 采纳了 musl libc 和 busybox 以减小零碎的体积和运行时资源耗费,但性能上比 busybox 又欠缺的多,因而失去开源社区越来越多的青眼。在放弃瘦身的同时,Alpine 还提供了本人的包管理工具 apk,能够间接通过 apk 命令间接查问和装置各种软件。
2.3 Debian 与 Alpine 的差别
空间大小差别,alpine 默认 5M 左右,debian 等都在 200M 左右。默认软件包差别,alpine 选用 busybox,debian 等则是 bash+coreutils 几件套。alpine 中,国际化组件被优化掉了。还有一点,alpine 中选用的都是“最简依赖”,这点和 archlinux 比拟像,举个例子,openssh 包不会自带 pam 插件,于是他也就不反对 ldap。这点我给 alpinelinux 官网提过 issue。和 php 不一样,php 能够做成 php-pdo,php-dom 的包,而后动静加载共享库。openssh 不行,“没带就是没写”。glibc 差别,alpine 选用 musl,centos 等选用 glibc,其余的倒还好,libc 的差别对开发很重要。
更多内容能够查看 Alpine Linux 与 CentOS 有什么区别?
2.4 nodejs 官网镜像形成
当初再来看 nodejs 镜像组成
node:<version>:基于 Debian 最新版本,依赖工具包 buildpack-deps,最根底的镜像,提供了最多最罕用的 Debian 软件包,比方 curl、bash、git 等
node:alpine:基于风行的 Alpine Linux 我的项目,提供最小的 node 镜像,没有内置罕用的软件包,须要通过 apk 进行装置,另外应用 musl libc 代替了 glibc, 只有对依赖了 glibc 的包有影响比方 grpc 之类的
node:buster:基于 Debian10 版本的镜像,依赖工具包 buildpack-deps
node:bullseye:基于 Debian11 版本的镜像,依赖工具包 buildpack-deps
node:bookworm:基于 Debian12 版本的镜像,依赖工具包 buildpack-deps
node:slim:基于 Debian 的以后版本,只蕴含可能让镜像跑起来的最根底的包,比方蕴含 bash、git,然而不蕴含 curl 等
当然当前还会有基于新的 Debian 版本的镜像
镜像内是否蕴含某个工具包,能够间接在镜像的详情页内搜寻,如下图所示
镜像整体构造组成如下图所示
不同 tag 的镜像区别如下,以 16.20.0(arm64) 为例
镜像 tag
node 版本
包数量
系统漏洞数
镜像大小
是否蕴含 yarn
node
16.20.0
751
230
856MB
✅
node:bullseye
16.20.0
751
121
890MB
✅
node:bullseye-slim
16.20.0
347
26
184MB
✅
node:bookworm
16.20.0
750
92
1.04GB
✅
node:bookworm-slim
16.20.0
334
15
206MB
✅
node:alpine
16.20.0
230
4
116MB
✅
从上表能够得出
alpine 与 slim 体积最小,bullseye、bookworm 及 node:version 镜像体积绝对 alpine 与 slim 都大上来好几倍
alpine 与 slim 的破绽数绝对起码,bullseye、bookworm 及 node:version 破绽数绝对 alpine 与 slim 多上好几倍
那么 node:version 到底对应哪个版本,能够通过 versions.json 文件查看,以 16.20.0 版本为例,那么 node:16.20.0 其实就是 node:16.20.0-buster
另外也能够从镜像的组成看,如下图所示
到这里咱们应该能够答复,后面的两个问题
为什么一个 node 镜像版本会有那么多 tag?我该怎么选
起因是: nodejs 基于不同的 linux 发型版本构建进去的镜像,能够依据镜像体积、性能来进行抉择
为什么 14 与 16 的 tag,除了版本号之外竟然还有差别?为什么
起因是:Debian 始终在公布新的版本,而 nodejs 只对最新的在保护的 nodejs 版本公布基于新的 Debian 版本的镜像
- 怎么抉择 nodejs 镜像
从下面咱们曾经晓得了各个 tag 的含意,及对应 tag 镜像的组成,那么咱们能够依据本人的应用场景进行抉择
3.1 web 我的项目
构建 web 我的项目,举荐 alpine 镜像,起因是咱们在构建 web 我的项目时候,个别只依赖构建工具 webpack 等,不依赖一些底层的工具库,个别也不须要进入容器内,具体示例如下所示
dockerfile
复制代码
FROM node:16.20.0-alpine AS builder_web
WORKDIR /app
ADD ./package.json /app/package.json
ADD ./pnpm-lock.yaml /app/pnpm-lock.yaml
装置依赖
RUN pnpm install –frozen-lockfile
ADD . /app
构建代码
RUN pnpm build
FROM nginx:1.21.0
CDN 同步脚本默认从 /app/dist 目录读文件
COPY –from=builder_web /app/dist /app/dist
COPY –from=builder_web /app/config/nginx.conf /etc/nginx/conf.d/default.conf
3.2 BFF 我的项目
构建基于 nodejs 的 BFF 我的项目,构建阶段举荐应用 alpine 镜像,运行部署阶段举荐 slim 镜像,起因镜像内蕴含一些根底的第三方工具包,不便咱们进容器的时候在进行二次装置
dockerfile
复制代码
构建阶段能够应用 alpine
FROM node:16.20.0-alpine as bff_build
工作目录
WORKDIR /workspace/app
拷贝依赖文件
COPY ./package.json /workspace/app/package.json
COPY ./yarn.lock /workspace/app/yarn.lock
装置依赖
RUN yarn –frozen-lockfile –check-files
拷贝源码
COPY . /workspace/app
构建
RUN yarn run build
部署阶段根底镜像,应用 slim 镜像
FROM node:16.20.0-slim
工作目录
WORKDIR /workspace/app
拷贝依赖
COPY –from=bff_build /workspace/app/package.json /workspace/app/package.json
COPY –from=bff_build /workspace/app/node_modules /workspace/app/node_modules
拷贝内容
COPY –from=bff_build /workspace/app /workspace/app
启动
CMD yarn run run
3.3 npm 包场景
公布 npm 包,以 gitlab-ci 为例,举荐 alpine or slim 镜像,起因是不依赖工具包,然而碰到 lerna 这样的多包管理工具时,因为依赖 git,能够在现有 alpine or slim 镜像的根底上线装置 git
yaml
复制代码
stages:
- publish
before_script:
- echo $CI_COMMIT_TAG
publish:
stage: publish
image: node:16.20.0-slim
script:
- echo -e $NPM_AUTH_CONTENT >> ~/.npmrc # 注入私仓发包 token
- yarn install –frozen-lockfile
- |
if [[$CI_COMMIT_TAG == “beta” ]]; then
echo ‘ 公布 beta tag’
yarn publish –tag beta –non-interactive –no-commit-hooks
else
echo ‘ 公布正式 tag’
yarn publish –non-interactive –no-commit-hooks
fi
only:
refs: - tags
当然还有更多的场景,然而只有依据每个 tag 的个性,就能选出适宜本人我的项目的 tag
- 自定义 nodejs 镜像
除了间接应用官网的 nodejs 镜像,其实咱们也可能会本人封装适宜本人公司我的项目的 nodejs 镜像,其目标都是为了在镜像内减少定制逻辑,不便对立解决公司所有我的项目的通用问题,比方设置公司的 npm 代理源,设置一些罕用的 npm 包第三方依赖变量,装置 pnpm,解决 install 及 build 过程中的因为 buildkit 缓存谬误导致的构建失败等,封装有两种思路
思路 1: 从零开始封装
思路 2: 基于官网镜像进行二次封装
4.1 从零封装本人的 nodejs 镜像
以 alpine 镜像为例
dockerfile
复制代码
抉择 alpine 版本镜像
FROM alpine:3.18
定义 node 版本
ENV NODE_VERSION 16.20.0
设置 apk 的源为阿里源
RUN sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g’ /etc/apk/repositories && apk update
设置源及设置一些国内不好下载的二进制依赖文件 host
COPY ./.npmrc /root/.npmrc
应用 apk 来进行装置依赖
RUN addgroup -g 1000 node \
&& adduser -u 1000 -G node -s /bin/sh -D node \
&& apk add –no-cache \
libstdc++ \
&& apk add –no-cache –virtual .build-deps \
curl \
&& ARCH= && alpineArch=”$(apk –print-arch)” \
&& case “${alpineArch##*-}” in \
x86_64) \
ARCH=’x64′ \
CHECKSUM=”d2df78a192bd78b958e19a77821916a38def5e9e46c0c9a0989fdf5eb6c14a7e” \
;; \
*) ;; \
esac \
&& if [-n “${CHECKSUM}” ]; then \
set -eu; \
curl -fsSLO –compressed “https://unofficial-builds.nodejs.org/download/release/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz”; \
echo “$CHECKSUM node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz” | sha256sum -c – \
&& tar -xJf “node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz” -C /usr/local –strip-components=1 –no-same-owner \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs; \
else \
echo “Building from source” \
backup build
&& apk add –no-cache –virtual .build-deps-full \
binutils-gold \
g++ \
gcc \
gnupg \
libgcc \
linux-headers \
make \
python3 \
use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150
&& export GNUPGHOME=”$(mktemp -d)” \
gpg keys listed at https://github.com/nodejs/node#release-keys
&& for key in \
4ED778F539E3634C779C87C6D7062848A1AB005C \
141F07595B7B3FFE74309A937405533BE57C7D57 \
74F12602B6F1C4E913FAA37AD3A89613643B6201 \
DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \
61FC681DFB92A079F1685E77973F295594EC4689 \
8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \
C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \
108F52B48DB57BB0CC439B2997B01419BD92F80A \
; do \
gpg –batch –keyserver hkps://keys.openpgp.org –recv-keys “$key” || \
gpg –batch –keyserver keyserver.ubuntu.com –recv-keys “$key” ; \
done \
&& curl -fsSLO –compressed “https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz” \
&& curl -fsSLO –compressed “https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc” \
&& gpg –batch –decrypt –output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpgconf –kill all \
&& rm -rf “$GNUPGHOME” \
&& grep ” node-v$NODE_VERSION.tar.xz\$” SHASUMS256.txt | sha256sum -c – \
&& tar -xf “node-v$NODE_VERSION.tar.xz” \
&& cd “node-v$NODE_VERSION” \
&& ./configure \
&& make -j$(getconf _NPROCESSORS_ONLN) V= \
&& make install \
&& apk del .build-deps-full \
&& cd .. \
&& rm -Rf “node-v$NODE_VERSION” \
&& rm “node-v$NODE_VERSION.tar.xz” SHASUMS256.txt.asc SHASUMS256.txt; \
fi \
&& rm -f “node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz” \
&& apk del .build-deps \
smoke tests
&& node –version \
&& npm –version
ENV YARN_VERSION 1.22.19
RUN apk add –no-cache –virtual .build-deps-yarn curl gnupg tar \
use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150
&& export GNUPGHOME=”$(mktemp -d)” \
&& for key in \
6A010C5166006599AA17F08146C2130DFD2497F5 \
; do \
gpg –batch –keyserver hkps://keys.openpgp.org –recv-keys “$key” || \
gpg –batch –keyserver keyserver.ubuntu.com –recv-keys “$key” ; \
done \
&& curl -fsSLO –compressed “https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz” \
&& curl -fsSLO –compressed “https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc” \
&& gpg –batch –verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
&& gpgconf –kill all \
&& rm -rf “$GNUPGHOME” \
&& mkdir -p /opt \
&& tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
&& ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
&& ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
&& rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
&& apk del .build-deps-yarn \
smoke test
&& yarn –version
装置 pnpm 依赖
RUN npm install -g pnpm
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT [“docker-entrypoint.sh”]
CMD [“node”]
其实就是参照官网镜像的构建形式,在外面进行适当的批改
当然还能够基于其它某个 linux 发行版来进行封装,如下所示
yaml
复制代码
FROM centos:7
RUN curl -L https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
RUN curl –silent –location https://rpm.nodesource.com/setup_14.x | bash –
RUN yum install -y nodejs yarn
WORKDIR /code
EXPOSE 80
CMD npm start
4.2 基于官网镜像二次封装
以 slim 镜像为例
dockerfile
复制代码
FROM node:16.20.0-slim
RUN sed -i s/deb.debian.org/archive.debian.org/g /etc/apt/sources.list && \
sed -i ‘s|security.debian.org|archive.debian.org|g’ /etc/apt/sources.list && \
sed -i ‘/stretch-updates/d’ /etc/apt/sources.list && \
apt-get clean && \
apt-get update && \
apt-get install -y tzdata tree git && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ARG CUSTOM_NODE_VERSION
COPY ./.npmrc /root/.npmrc
RUN npm install -g pnpm@6.32.3 \
&& pnpm config set store-dir /root/.pnpm-store \
&& npm config set custom_node_version $CUSTOM_NODE_VERSION
这样的话,封装起来清新一些
至于本人封装的 nodejs 镜像能够发到本人的私仓,比方 Nexus 搭建的私仓,或者阿里云、腾讯云上买的私仓等
当然在封装的时候如果咱们要装置一些额定的工具包,因为国内网络的问题,所以咱们个别会将 debian 的源设置成国内源
- 总结
nodejs 官网镜像次要由三局部组成 linux 版本 + 工具包合集 + nodejs 运行时,三局部能够组合成不同的 tag,每种组合都有其利用的场景,咱们能够依据大小、平安、性能来进行抉择
个别我的项目举荐应用:alpine or slim 镜像,不举荐应用残缺镜像非凡我的项目举荐依据 alpine or slim 镜像进行二次封装,解决公司的通用问题,进步研发效率
像咱们公司就抉择封装本人的 nodejs 镜像,会在镜像内做如下事件
设置公司本人的 npm 代理源
设置 npm 包的第三方依赖环境变量,比方 node-sass、puppeteer 等
装置 pnpm
解决 CI 场景因为 buildkit 缓存问题导致的 install 失败或者 build 失败
bff 启动之前的前置查看等
当然如果对镜像大小谋求极致的,能够在去删减镜像内的工具包,或者通过 copy 的形式只拷贝工具的执行文件等形式去缩减镜像大小
最初通过抉择适合的镜像,能够帮忙咱们构建高效的 CI/CD 流程
参考链接
如何查看 Debian 版本
Debian 发行版本
Debian 源应用帮忙
Alpine Linux 与 CentOS 有什么区别?
Choosing the best Node.js Docker image