一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    后端业务逻辑一般比较复杂,全堆在一个 http 服务里不太现实,所以基本都会用微服务架构来开发了。

    比如这样:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务

    把不同模块的业务逻辑拆分到不同微服务里,然后它们和主服务通过 tcp 通信,最终由主服务返回 http 响应。

    比如我用 nest 开发的一个微服务项目(具体开发过程见上篇文章):

    两个微服务分别监听了 8888 端口和 9999 端口:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Node.js_02

    用 yarn start 把它们跑起来:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_03

    主服务里注册了这两个微服务:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Node.js_04

    它监听了 3000 端口,同样用 yarn start 把它跑起来:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_05

    计算的微服务里有一个求和的逻辑:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_JavaScript_06

    日志的微服务里有一个打印日志的逻辑:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_07

    主服务里接受参数,然后把它传给两个微服务:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_08

    跑起来效果是这样的:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_09

    返回的是求和的结果,并且日志微服务做了打印:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Docker_10

    这说明微服务和 http 服务开发成功了。

    那问题来了,开发完以后怎么部署到线上呢?

    其实部署过程说起来也简单,就是执行 npm run build,然后把产物 dist 目录放到服务器上。

    比如这个:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_11

    然后用 node 跑起来:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_JavaScript_12

    但一般我们不会直接这么搞,而是会使用 docker 来做,因为这样手动搞的话每个服务都要这样来一次,太麻烦了,而且容易出错。

    docker 可以通过 Dockerfile 把构建和运行流程封装起来,还可以把运行环境也封装到镜像里,这样每次部署只需要重新构建镜像,然后服务器把镜像拉下来跑就行。

    比如 main 服务的 Dockerfile 我们会这么写:

    FROM node:alpine as development
    
    WORKDIR /usr/app
    
    COPY package.json ./
    
    RUN npm install
    
    COPY . .
    
    RUN npm run build
    
    FROM node:alpine as production
    
    WORKDIR /usr/app
    
    COPY package.json ./
    
    RUN npm install --only=production
    
    COPY . .
    
    COPY --from=development /usr/app/dist ./dist
    
    CMD ["node", "dist/main.js"]

    一行行来看:

    FROM node:alpine as development

    这一行是继承 node 基础镜像的意思,as 后面是给它起个名字。

    WORKDIR /usr/app

    把容器内的当前目录设置为 /user/app

    COPY package.json ./

    把宿主机的 package.json 复制到容器当前目录,也就是 /user/app 下。

    RUN npm install

    package.json 复制过去了,自然就可以在容器内安装依赖了。

    COPY . .

    然后再把其余的内容都复制过去。

    这里可以加个 .dockerignore 文件来排除 node_modules 的复制

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_JavaScript_13

    RUN npm run build

    复制完之后执行 npm run build,就在容器内生成了 dist 目录。

    FROM node:alpine as production

    为啥用重新创建了个镜像呢?

    这是因为 build 完之后我们就只需要 dist 目录了,其余的源码啥的都不需要,自然可以在一个新容器里,然后把上个容器的 dist 目录复制过去。

    WORKDIR /usr/app
    
    COPY package.json ./
    
    RUN npm install --only=production
    
    COPY . .

    然后同样是设置当前目录,复制 package.json,执行 npm install,然后复制其它文件。

    这里 npm install 加个 --only=production 可以只安装 dependecies 下的包。

    怎么复制呢?还记得我们 as 后面指定了一个名字么,就通过那个来指定从上个容器复制:

    COPY --from=development /usr/app/dist ./dist

    其实这种叫做分阶段构建,不然你要写两个 Dockerfile 才行。

    最后指定容器运行起来的时候执行的命令,也就是 node dist/main.js 把这个服务跑起来:

    CMD ["node", "dist/main.js"]

    有了这个 Dockerfile 就可以通过 docker build 命令生成 docker 镜像了:

    docker build -t main-app .

    这行命令的意思就是从 . 目录下的 Dockerfile 来构建一个 docker 镜像,名字是 main-app。

    它会一层层构建,我们刚好 14 行命令:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Docker_14

    构建完可以看到这个镜像的 hash。

    当然,在 docker desktop 里也可以看到:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Node.js_15

    这里用到的 docker desktop 从官网下载就行:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_16

    它除了会安装 docker 桌面端以外,也会同时安装 docker 和 docker-compose。

    然后把它跑起来:

    docker run -p 3000:3000 main-app

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_17

    -p 是端口映射的意思,也就是把宿主机的 3000 端口映射到容器的 3000 端口,这样宿主机就可以访问 3000 端口的 http 服务了。

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_18

    确实可以访问,只不过报了 500,因为两个微服务还没起嘛。

    然后我们写下 log 微服务的 DockerFile:

    FROM node:alpine As development
    
    WORKDIR /usr/app
    
    COPY package.json ./
    
    RUN npm install
    
    COPY . .
    
    RUN npm run build
    
    FROM node:alpine as production
    
    WORKDIR /usr/app
    
    COPY package.json ./
    
    RUN npm install --only=production
    
    COPY . .
    
    COPY --from=development /usr/app/dist ./dist
    
    CMD ["node", "dist/main.js"]

    一毛一样,就不解释了。

    然后执行

    docker build -t ms-log .

    同样,是用当前目录的 Dockerfile 构建一个名字为 ms-log 的镜像:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_19

    然后用

    docker run -p 9999:9999 ms-log

    把这个容器跑起来,映射容器内的 9999 端口到宿主机的 9999 端口。

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Node.js_20

    还有一个计算微服务,我们同样这么搞,就不展开了。

    这时候你就可以在 docker desktop 里面看到这三个 image(镜像):

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_21

    还有它们仨跑起来的 container(容器):

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_微服务_22

    这时候你浏览器访问一下 locahost:3000

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Node.js_23

    你就会发现返回了计算的结果,日志微服务容器内也打印了日志:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_JavaScript_24

    在 docker desktop 里看更方便一些:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_25

    至此,我们 docker 部署 node 微服务就成功了!

    其实有一点比较重要的我前面没说,这里提一下:

    两个微服务要起服务的时候要指定 0.0.0.0 这个 ip:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_26

    这涉及到 0.0.0.0 和 127.0.0.1 的区别:

    127.0.0.1 和 localhost 一样,都是只本机地址。

    0.0.0.0 不是一个 ip 地址,它指代的是本地所有网卡的 ip。

    这里如果用默认的 localhost,那服务只在容器内生效,要指定 0.0.0.0 才行。

    再就是主服务里访问这两个微服务的时候要用宿主机 ip 地址访问:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Docker_27

    同样是因为访问的是宿主机的 ip 的那个服务。

    有的同学可能会问,跑三个就执行三次 docker build 和 docker run,也太麻烦了吧,要是我有 10 个微服务,之间还有先后顺序的要求呢?

    没错,这样确实比较麻烦,所以有了 docker compose。

    这个也是 docker 自带的工具。

    我们在根目录写这样一个 docker-compose.yml 的文件:

    services:
      main-app:
        build:
          context: ./main-app
          dockerfile: ./Dockerfile
        depends_on:
          - ms-calc
          - ms-log
          - rabbitmq
        ports:
          - '3000:3000'
      ms-calc:
        build:
          context: ./micro-service-calc
          dockerfile: ./Dockerfile
        ports:
          - '8888:8888'
      ms-log:
        build:
          context: ./micro-service-log
          dockerfile: ./Dockerfile
        ports:
          - '9999:9999'
      rabbitmq:
        image: rabbitmq
        ports:
          - '5672:5672'

    这个还是比较容易看懂的。

    分别指定了 main-app、ms-calc、ms-log、rabbitmq 的 dockerfile 的地址以及端口映射。

    而且通过 depends 指定了先后顺序。

    这样只要跑一次 docker-compose up 就可以把它们全部跑起来。

    (rabbitmq 那个只是用来测试的,其实没用到)

    特别要注意 context 的配置,这个是指定路径的基础目录的,比如这个 package.json:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_docker_28

    加上 context: ./micro-service-log 那就是 ./micro-service-log/package.json。

    不加找不到路径。

    我们把那 3 个容器停掉:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_JavaScript_29

    执行

    docker-compose up

    跑起来是这样的:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Docker_30

    上面都是 rabbitmq 这个容器的日志。

    我们访问下 localhost:3000

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Docker_31

    可以看到 main-app 和 ms-log 的日志:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Node.js_32

    这是因为 docker-compose 把终端合并了,加了个前缀来区分。

    在 docker desktop 也可以看到新跑起来的 4 个容器:

    一文学会用 Docker 和 Docker Compose 部署 Node.js 微服务_Docker_33

    这样 3 个 node 服务就都跑起来了。

    感受到 docker compose 的好处了么?

    它可以批量创建一批容器,并且指定顺序、参数之类的,也就是容器编排。

    代码地址:github.com/QuarkGluonP…

    总结

    我们分别用 docker 和 docker compose 实现了 Node.js 的微服务部署。

    dockerfile 里指定宿主机文件到容器内的复制,npm install 以及把 node 服务跑起来的逻辑。

    可以使用分阶段构建功能来优化,也就是 from 的时候通过 as 指定一个名字,然后之后再一个 from 重新创建镜像,这时可以从上个镜像里复制文件。

    之后执行 docker build 根据 Dockerfile 构建镜像,通过 docker run -p 宿主机端口:容器内端口 把镜像跑起来。

    可以通过 docker desktop 来管理,更方便一些。

    这里涉及到的 ip 要指定 0.0.0.0 或者具体的宿主机 ip 才行,要注意一下。

    但这样三个服务就要跑 3 次 docker 镜像,比较麻烦。

    可以用 docker-compose 来做容器编排,指定容器的 dockerfile、启动顺序等等。

    这就是用 Docker 或者 Docker Compose 部署 node 微服务的方式,你学会了么?

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
    标签: Docker