我们有一个 web 应用,采用的微服务架构,各个部件都是用独立的 docker 环境运行,整体应用用 docker compose 管理。整个项目前后端都是 js 。
现在有一个问题,就是我们的项目源码都是打包到 docker image 的,这就导致源码只要有变动,就要重新 build docker image ,重复执行大量的 apt-get, npm install 等操作,十分耗时。前端还需要重复执行编译,更加耗时了。
我们目前有 production ,test 和 develop 三个环境,而且项目处于迭代阶段,更新比较多。我感觉每次更新版本的过程太慢了,特别是 production 环境是个 vps ,cpu 比较弱,编译起来很吃力。更蛋疼的是,我们有一个环境在国内云上,apt-get 和 npm 都会有各种网络问题。
想请教各位,有没有在一处配置,其它机器 只用传输数据 的发布方式? 我的意思是,我在本地机器上执行 apt-get, npm 和前端 build ,然后打包成一个文件,其它环境只需要复制这个文件就能跑起来的?
我尝试过的:
我以我现在前端 frontend 服务为例,把我的 Dockerfile 放在这里:
ARG DOCKER_BASE=debian:12.1-slim
ARG DOCKER_TAG=latest
FROM my-service/base:${DOCKER_TAG} as build
ARG NPM_ARGS
ENV BUILD_MODULE=frontend
ADD ./ /my-service
WORKDIR /my-service/${BUILD_MODULE}
RUN npm install ${NPM_ARGS}
RUN npm run build
FROM ${DOCKER_BASE}
ENV BUILD_MODULE=frontend
WORKDIR /my-service/${BUILD_MODULE}
# only copy dist/ to final image, will ignore node and node dependencies
# this compressed the image size by more than 800M
COPY --from=build /my-service/${BUILD_MODULE}/dist /my-service/${BUILD_MODULE}/dist
# install nginx
RUN apt-get update \
&& apt-get install -y nginx \
&& rm -rf /etc/nginx/sites-enabled/default \
&& rm -rf /var/lib/apt/lists/*
ADD ${BUILD_MODULE}/nginx_configs/frontend_deploy.conf /etc/nginx/sites-enabled/frontend_deploy.conf
COPY ${BUILD_MODULE}/run_deploy.sh /run_deploy.sh
RUN chmod 755 /run_deploy.sh
ENTRYPOINT ["/run_deploy.sh"]
我现在更新代码之后在 production 环境的操作是:
docker compose down
docker image rm frontend
docker compose up
这样还是会把 Dockerfile 里的 apt/npm install
走一遍。
我想正确的做法是一个地方执行 docker build
然后 push 到私有 registry,然后在其它环境去 docker pull,而不是 git pull 源代码重新 build?
1
danbai 157 天前 via Android
做一个编译环境的 dockerfile 编译镜像 ,编译在上面然后把编译物到拿到运行镜像只依赖所需要的运行环境。 这样可以减掉编译重复安装依赖的过程。
|
2
lingex 157 天前 via Android
apt-get npm 等不会变的部分做成一个基础镜像,后面的 dockfile 以它为基础。
|
3
fox0001 157 天前 via Android
除了楼上提到的方案解决无谓的重复工作,可以考虑部署私有仓库,所有机器从似有仓库获取镜像。
如果不想部署私有仓库,可以导出镜像包,复制到各个机器再导入。 |
4
OceanBreeze 157 天前 via iPhone
想请教各位,有没有在一处配置,其它机器 只用传输数据 的发布方式? 我的意思是,我在本地机器上执行 apt-get, npm 和前端 build ,然后打包成一个文件,其它环境只需要复制这个文件就能跑起来的?
------ 通常的做法就是通过一个独立的编译机/容器来做这个事,而不是本机。然后通过 docker push 推送到镜像仓库。其他的机器 docker pull 下来 再 run 。develop 环境可以采取外挂的方式共享,test 和 production 还是建议走 image 分发。 |
5
me221 157 天前
lock 文件没变化,也不会重新执行 npm install 吧
|
6
version 157 天前
可以 package.json 作为一个基础就像。其它子项目都用它文件就可以了
COPY --from=xxxxxx:1.0.0 /app/node_modules ./node_modules 这样减少 npm install 非必要的安装包括一些第三方的程序也可以直接引用复制二进制文件来容器执行 开发阶段也可以走一层代理. volumn 挂在源码。容器跑 yarn run dev 本地也可以搞个 sftp 更新代码同步到服务器.这样就解决了云环境的开发 |
7
imzhoukunqiang 157 天前 via Android
感觉你需要一个镜像仓库➕一个配置好的构建机
|
8
OceanBreeze 157 天前 via iPhone
@me221 lock 文件只是锁定版本,该安装还是要安装的。只是本地可能有缓存,不需要下载罢了
|
9
iyiluo 157 天前
为什么要在容器里面编译,你说的想法其实有对应的解决方案。弄一个 jenkins ,添加一个流水线自动编译,编译出来的文件传输到对应环境,调用脚本,生成镜像,重启容器
|
10
cheng6563 157 天前
你 Dockerfile 没写好,没利用好分层镜像做缓存。
|
11
batchfy OP @cheng6563 我分了层了。比如前端我会第一层安装 nodejs+npm 这些,然后第二层只安装 nginx ,把第一层编译的产物 copy 到第二层。但问题是,无论怎么分层,最后在部署的时候还是要把这些步骤重新做一遍不是么?
比如前端更新了,我需要 docker image rm frontend && docker compose up ,这样还是要都走一遍。 |
12
batchfy OP @me221 我更新前端之后会直接 docker image rm frontend 然后再 docker compose up ,无论如何都是重新 npm install 。
|
13
sujin190 157 天前
@batchfy #11 docker image 分层不就是为了不需要后面再干一遍么,所以你是不是对 image build 有啥误解还是用法有问题,正常都是本地 build 上传 image ,服务器直接启动哪里来的需要安装 npm 和编译之类的,这不就是你想要的本地直接编译好服务器 copy 镜像直接启动么,正常的 docker 就是这样用的啊
|
14
sunny352787 157 天前
@batchfy 我想你理解错了 docker 的用法了,你的这三个环境实际上不需要编译三遍 image ,而是一个 image 这三个环境使用,develop 开发完毕通知 test 拉取镜像测试,test 通过之后 prod 拉取同一个镜像上线,而且由于是分层的,所以未改变的层是不会重复拉取的
|
15
coolcoffee 157 天前
和楼上意见差不多,肯定分层没做好。
首先是 package.json package-lock.json 这两个文件单独拷贝过去,安装依赖。 这样只要 package.json 不变,就不会因为源码更新导致重新执行 npm install 。 我一个 nest 项目如果改几行代码,只会执行 pnpm build, 打包过程十几秒钟就完成了。 |
16
dzdh 157 天前
第一步 构建一个 image ,apt-get 都安装好
第二步 项目使用上一个 image ,使用 buildx 构建时使用外部构建缓存避免多次下载 npm 完整资源 第二步参考: https://github.com/docker/buildx/discussions/1283 |
17
dzdh 157 天前
|
18
xsi640 157 天前
如果是编译慢,可以把编译后的产物放到容器里。。。
|
19
cheng6563 157 天前
给你个示例吧,先拷贝 package*然后直接 install 。
我这个产出是静态页面,所以直接用 alpine 镜像当存储使用了。你如果要启服务就换成 node 镜像启服务就是了 FROM node:14-alpine as build WORKDIR /app COPY package* ./ RUN npm install --registry=https://registry.npmmirror.com # 以上步骤都能缓存好 ARG CMD=build COPY . . RUN npm run $CMD FROM alpine as archive COPY --from=build /app/dist /app/dist |
20
batchfy OP @cheng6563 实际上我 frontend 的 Dockerfile 跟你这个一样。但是每次部署还是要把整个流程走一遍。你所谓的 “# 以上步骤都能缓存好” 是指把这部分 tag 了 push 到 registry 么?
|
21
batchfy OP 我好像 get 到各位大哥说的所谓的“分层”的意思了。就是多创建几个 base images ,然后逐渐在不同 images 里把所需的软件装进去。把这些 base images push 到 registry ,在不同环境中直接 pull 就好了。
|
22
JayZXu 157 天前
这个前端的 dockerfile 没必要这么写吧
这个应该是前端构建容器的写法,真正的前端 dockerfile 应该是上面 npm run build 的产物复制到 nginx 基础镜像里面,再加上一个配置好的 nginx 配置,启动 nginx 进程即可。 |
23
Mithril 157 天前
编译用的镜像,和 runtime 镜像不要用同一个。
就你的场景而言,你可以做一个编译镜像,这个镜像的 docker file 里面拿你的依赖先 install 一下,缓存出需要的依赖,然后每次就用它编译。这个镜像大点无所谓,反正也就在你的编译机上跑。而且只在你项目依赖变化的时候才需要重新构建它,平时根本就不会动。正常情况下,你项目的依赖也不应该频繁变动。 项目本身做 multi stage ,使用编译镜像去 build ,然后产出物复制到 runtime 里做成最终的产品镜像。runtime 压根不需要什么 apt ,npm 。一个 alpine 加 node 就够了,镜像本身没有多大。 然后回到镜像管理本身,你应该有一个自己的 registry ,在你不同环境里,使用的镜像应该保持一致。就是你用来 test 的,和你扔到 production 环境里的应该是同一套东西。你 production 的不同网络,使用的也应该是相同的镜像。 通过 volume 挂载 js 是极其糟糕的做法,等同于你把二进制直接手动传到 EC2 里跑,完全失去了使用 docker 的必要性。到时候出了问题,你连对应的版本都找不到。回滚什么的更难办了。 |
24
leetomlee123 157 天前
开发环境直接拉源码 本机启动
微服务害人不浅 拖慢开发进度 |
25
julyclyde 157 天前
首先,apt 应该是可以缓存的层,按说不应该重复执行啊。要不你先检查一下这个?
其次,其他环境只复制这一个文件,则这个文件和 docker 容器的其他部分分离了,需要用额外的手段控制版本和更新动作,我觉得这个代价不划算。你如果解决了前一个问题,则这个问题就不成为问题了 |
26
ddd2500 157 天前
gitlab CICD 上传到仓库自动打包部署
|
27
petercui 157 天前
你们没有自己的 docker 私服么?先做好基础镜像,然后在这个基础上打包业务镜像,多环境尽量用一个包
|
29
sujin190 156 天前
@batchfy #28 docker 私服也是一个镜像啊,docker run 就搞定了,也怎么耗资源,然后可以带私服 ip 和端口地址请求镜像就可以,不带只是镜像名称的普通操作还是官方镜像,挺简单的也很方便
|
30
rebecca554owen 122 天前 via Android
一个思路,可以用 GitHub action 跑,然后 push 出来一个基础镜像。
|