V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
batchfy
V2EX  ›  Docker

Docker 微服务应用如何高效部署更新?

  •  
  •   batchfy ·
    batchfy · 150 天前 · 1804 次点击
    这是一个创建于 150 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们有一个 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 ,然后打包成一个文件,其它环境只需要复制这个文件就能跑起来的?

    我尝试过的:

    1. 源代码的路径通过 volumn 挂载到 docker container 内,这样改源代码就不用重新 build docker image 。这样后台代码没有问题,前端代码没法在 dockerfile 里面 build (因为 build 阶段 volume 还没有挂载上去)。
    2. 在本地机器上配置好,然后用 docker save 将所有 containers 存成文件,复制到其它环境。这样子存下来的文件有 10+G ,已经放弃。
    第 1 条附言  ·  150 天前

    我以我现在前端 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?

    30 条回复    2024-01-02 22:36:40 +08:00
    danbai
        1
    danbai  
       150 天前 via Android
    做一个编译环境的 dockerfile 编译镜像 ,编译在上面然后把编译物到拿到运行镜像只依赖所需要的运行环境。 这样可以减掉编译重复安装依赖的过程。
    lingex
        2
    lingex  
       150 天前 via Android
    apt-get npm 等不会变的部分做成一个基础镜像,后面的 dockfile 以它为基础。
    fox0001
        3
    fox0001  
       150 天前 via Android
    除了楼上提到的方案解决无谓的重复工作,可以考虑部署私有仓库,所有机器从似有仓库获取镜像。

    如果不想部署私有仓库,可以导出镜像包,复制到各个机器再导入。
    OceanBreeze
        4
    OceanBreeze  
       150 天前 via iPhone
    想请教各位,有没有在一处配置,其它机器 只用传输数据 的发布方式? 我的意思是,我在本地机器上执行 apt-get, npm 和前端 build ,然后打包成一个文件,其它环境只需要复制这个文件就能跑起来的?

    ------

    通常的做法就是通过一个独立的编译机/容器来做这个事,而不是本机。然后通过 docker push 推送到镜像仓库。其他的机器 docker pull 下来 再 run 。develop 环境可以采取外挂的方式共享,test 和 production 还是建议走 image 分发。
    me221
        5
    me221  
       150 天前
    lock 文件没变化,也不会重新执行 npm install 吧
    version
        6
    version  
       150 天前
    可以 package.json 作为一个基础就像。其它子项目都用它文件就可以了
    COPY --from=xxxxxx:1.0.0 /app/node_modules ./node_modules
    这样减少 npm install 非必要的安装包括一些第三方的程序也可以直接引用复制二进制文件来容器执行
    开发阶段也可以走一层代理. volumn 挂在源码。容器跑 yarn run dev 本地也可以搞个 sftp 更新代码同步到服务器.这样就解决了云环境的开发
    imzhoukunqiang
        7
    imzhoukunqiang  
       150 天前 via Android
    感觉你需要一个镜像仓库➕一个配置好的构建机
    OceanBreeze
        8
    OceanBreeze  
       150 天前 via iPhone
    @me221 lock 文件只是锁定版本,该安装还是要安装的。只是本地可能有缓存,不需要下载罢了
    iyiluo
        9
    iyiluo  
       150 天前
    为什么要在容器里面编译,你说的想法其实有对应的解决方案。弄一个 jenkins ,添加一个流水线自动编译,编译出来的文件传输到对应环境,调用脚本,生成镜像,重启容器
    cheng6563
        10
    cheng6563  
       150 天前
    你 Dockerfile 没写好,没利用好分层镜像做缓存。
    batchfy
        11
    batchfy  
    OP
       150 天前
    @cheng6563 我分了层了。比如前端我会第一层安装 nodejs+npm 这些,然后第二层只安装 nginx ,把第一层编译的产物 copy 到第二层。但问题是,无论怎么分层,最后在部署的时候还是要把这些步骤重新做一遍不是么?

    比如前端更新了,我需要 docker image rm frontend && docker compose up ,这样还是要都走一遍。
    batchfy
        12
    batchfy  
    OP
       150 天前
    @me221 我更新前端之后会直接 docker image rm frontend 然后再 docker compose up ,无论如何都是重新 npm install 。
    sujin190
        13
    sujin190  
       150 天前
    @batchfy #11 docker image 分层不就是为了不需要后面再干一遍么,所以你是不是对 image build 有啥误解还是用法有问题,正常都是本地 build 上传 image ,服务器直接启动哪里来的需要安装 npm 和编译之类的,这不就是你想要的本地直接编译好服务器 copy 镜像直接启动么,正常的 docker 就是这样用的啊
    sunny352787
        14
    sunny352787  
       150 天前
    @batchfy 我想你理解错了 docker 的用法了,你的这三个环境实际上不需要编译三遍 image ,而是一个 image 这三个环境使用,develop 开发完毕通知 test 拉取镜像测试,test 通过之后 prod 拉取同一个镜像上线,而且由于是分层的,所以未改变的层是不会重复拉取的
    coolcoffee
        15
    coolcoffee  
       150 天前
    和楼上意见差不多,肯定分层没做好。

    首先是 package.json package-lock.json 这两个文件单独拷贝过去,安装依赖。 这样只要 package.json 不变,就不会因为源码更新导致重新执行 npm install 。

    我一个 nest 项目如果改几行代码,只会执行 pnpm build, 打包过程十几秒钟就完成了。
    dzdh
        16
    dzdh  
       150 天前
    第一步 构建一个 image ,apt-get 都安装好

    第二步 项目使用上一个 image ,使用 buildx 构建时使用外部构建缓存避免多次下载 npm 完整资源

    第二步参考:
    https://github.com/docker/buildx/discussions/1283
    dzdh
        17
    dzdh  
       150 天前
    @dzdh

    podman 可以直接在 build 的过程中挂载目录

    podman build --security-opt label=disable -v $HOME:/home/user .
    xsi640
        18
    xsi640  
       150 天前
    如果是编译慢,可以把编译后的产物放到容器里。。。
    cheng6563
        19
    cheng6563  
       150 天前
    给你个示例吧,先拷贝 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
    batchfy
        20
    batchfy  
    OP
       150 天前
    @cheng6563 实际上我 frontend 的 Dockerfile 跟你这个一样。但是每次部署还是要把整个流程走一遍。你所谓的 “# 以上步骤都能缓存好” 是指把这部分 tag 了 push 到 registry 么?
    batchfy
        21
    batchfy  
    OP
       150 天前
    我好像 get 到各位大哥说的所谓的“分层”的意思了。就是多创建几个 base images ,然后逐渐在不同 images 里把所需的软件装进去。把这些 base images push 到 registry ,在不同环境中直接 pull 就好了。
    JayZXu
        22
    JayZXu  
       150 天前
    这个前端的 dockerfile 没必要这么写吧
    这个应该是前端构建容器的写法,真正的前端 dockerfile 应该是上面 npm run build 的产物复制到 nginx 基础镜像里面,再加上一个配置好的 nginx 配置,启动 nginx 进程即可。
    Mithril
        23
    Mithril  
       150 天前
    编译用的镜像,和 runtime 镜像不要用同一个。

    就你的场景而言,你可以做一个编译镜像,这个镜像的 docker file 里面拿你的依赖先 install 一下,缓存出需要的依赖,然后每次就用它编译。这个镜像大点无所谓,反正也就在你的编译机上跑。而且只在你项目依赖变化的时候才需要重新构建它,平时根本就不会动。正常情况下,你项目的依赖也不应该频繁变动。

    项目本身做 multi stage ,使用编译镜像去 build ,然后产出物复制到 runtime 里做成最终的产品镜像。runtime 压根不需要什么 apt ,npm 。一个 alpine 加 node 就够了,镜像本身没有多大。

    然后回到镜像管理本身,你应该有一个自己的 registry ,在你不同环境里,使用的镜像应该保持一致。就是你用来 test 的,和你扔到 production 环境里的应该是同一套东西。你 production 的不同网络,使用的也应该是相同的镜像。
    通过 volume 挂载 js 是极其糟糕的做法,等同于你把二进制直接手动传到 EC2 里跑,完全失去了使用 docker 的必要性。到时候出了问题,你连对应的版本都找不到。回滚什么的更难办了。
    leetomlee123
        24
    leetomlee123  
       150 天前
    开发环境直接拉源码 本机启动
    微服务害人不浅 拖慢开发进度
    julyclyde
        25
    julyclyde  
       150 天前
    首先,apt 应该是可以缓存的层,按说不应该重复执行啊。要不你先检查一下这个?
    其次,其他环境只复制这一个文件,则这个文件和 docker 容器的其他部分分离了,需要用额外的手段控制版本和更新动作,我觉得这个代价不划算。你如果解决了前一个问题,则这个问题就不成为问题了
    ddd2500
        26
    ddd2500  
       150 天前
    gitlab CICD 上传到仓库自动打包部署
    petercui
        27
    petercui  
       150 天前
    你们没有自己的 docker 私服么?先做好基础镜像,然后在这个基础上打包业务镜像,多环境尽量用一个包
    batchfy
        28
    batchfy  
    OP
       149 天前
    @petercui 没有,草台班子搞这个成本有点高,不过目前我已经用上了 gitlab 的 docker registry 。
    sujin190
        29
    sujin190  
       149 天前
    @batchfy #28 docker 私服也是一个镜像啊,docker run 就搞定了,也怎么耗资源,然后可以带私服 ip 和端口地址请求镜像就可以,不带只是镜像名称的普通操作还是官方镜像,挺简单的也很方便
    rebecca554owen
        30
    rebecca554owen  
       116 天前 via Android
    一个思路,可以用 GitHub action 跑,然后 push 出来一个基础镜像。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2830 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 15:09 · PVG 23:09 · LAX 08:09 · JFK 11:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.