当前位置: 首页 > 后端技术 > Node.js

前端工程师Docker教程-实战

时间:2023-04-03 19:50:18 Node.js

上一篇我们学习了Docker的常用命令和基本操作,现在可以开始实战了。单页应用最常见的前端工作类型是单页应用。我们先使用create-react-app快速创建一个应用npmicreate-react-app-gcreate-react-appreact-appcdreact-appnpmrunstart可以看到正常的启动页面。尝试打包运行npmrunbuild,可以看到本地生成了一个build目录,也就是最后在线上运行的代码。我们在本地运行build目录可以看到npmihttp-server-ghttp-server-p4444./build访问http://localhost:4444可以看到打包后的页面列表页面应用Dockerization新建react-app目录下的Dockerfile.dockerignore和nginx.conf.dockerignorenode_modulesbuilddockerignore指定哪些文件不需要复制到镜像中,类似.gitignore。我们知道,单页应用的路由一般都是js托管的,所以需要专门为nginx配置nginx。confserver{听80;服务器名称本地主机;位置/{root/app/build;#包路径索引index.htmlindex.htm;try_files$uri$uri//index.html;#防止重新刷新返回404}error_page500502503504/50x.html;location=/50x.html{root/usr/share/nginx/html;}}Dockerfile#基于node11FROMnode:11#设置环境变量ENVPROJECT_ENVproductionENVNODE_ENVproduction#安装nginxRUNapt-getupdate&&apt-getinstall-ynginx#复制package.jsonpackage-lock.json到/app目录下#可以缓存npminstallCOPYpackage*.json/app/#切换到app目录WORKDIR/app#安装依赖RUNnpminstall--registry=https://registry.npm.taobao.org#复制所有源码到/appCOPY。/app#打包构建RUNnpmrunbuild#复制配置文件到nginxCOPYnginx.conf/etc/nginx/conf.d/default.confEXPOSE80#启动nginx并关闭守护进程运行,否则容器会在CMD后立即关闭starting["nginx","-g","daemonoff;"]特别注意:COPYpackage*.json/app/RUNnpminstallCOPY./app我们先把package.json文件拷贝到app中,安装依赖项,然后只需将所有文件复制到应用程序,为什么?这是为了充分利用dockercacheCOPY./appRUNnpminstall这样写的话,每次重建镜像都需要下载npm包,很浪费时间!将源文件中的package.json分离出来写入镜像,这样只有当package.json发生变化时,才会重新下载npm包。当然,缓存有时也会带来一些麻烦,比如在执行一些shell操作输出内容时,由于缓存的存在,新建的镜像中的内容还是旧版本。我们可以在构建镜像时指定不使用缓存dockerbuild--no-cache-tdeepred5/react-app。最佳做法是在文件顶部指定一个环境变量。如果不想使用缓存,只需更新环境变量即可,因为缓存失效从第一条发生变化的指令开始。打包镜像dockerbuild-tdeepred5/react-app。启动容器dockerrun-d--namemy-react-app-p8888:80deepred5/react-app访问http://localhost:8888看页面访问http://localhost:8888/deepred5,也可以看到页面,说明nginx的防刷新配置已经生效!我们之前写的Dockerfile的多层构建其实存在一些问题:镜像是基于node11的,但是整个镜像在node环境中只用于前端打包,实际启动的是Nginx。图中的项目源码和node_modules其实一点用都没有。这些冗余文件使图像尺寸非常大。而我们只需要将静态文件打包,启动一个静态服务器Nginx即可。这时可以采用多阶段多层施工。创建一个新的Dockerfile.multi#节点镜像只是为了将来自node:alpine的文件打包为builderENVPROJECT_ENVproductionENVNODE_ENVproductionCOPYpackage*.json/app/WORKDIR/appRUNnpminstall--registry=https://registry.npm.taobao。orgCOPY./appRUNnpmrunbuild#ChooseasmallerbaseimageFROMnginx:alpineCOPYnginx.conf/etc/nginx/conf.d/default.confCOPY--from=builder/app/build/app/build在此文件中,我们使用两个FROM基础镜像,第一个node:alpine只作为打包环境,真正的基础镜像是nginx:alpine打包镜像#-f指定使用Dockerfile.multi构建dockerbuild-tdeepred5/react-app-多。-fDockerfile.multi启动容器dockerrun-d--namemy-react-app-multi-p8889:80deepred5/react-app-multi访问http://localhost:8889看页面视图docker镜像大小imagesdeepred5/react-app-multidockerimagesdeepred5/react-app可以找到,两者大小相差巨大。deepred5/react-app镜像有1G多,而deepred5/react-app-multi只有20M多。主要原因是:deepred5/react-app的基础镜像node:11有900M,而deepred5/react-app-multi的基础镜像nginx:alpine只有20M。可以看出,多层构建对于减小图像尺寸非常有帮助。Node应用前端有时也会参与NodeBFF层的开发。让我们创建一个结合Node和Redis的简单项目koa-router');constRedis=require("ioredis");constapp=newKoa();constrouter=newRouter();constredis=newRedis({port:6379,host:'127.0.0.1'});router.get('/',(ctx,next)=>{ctx.body='helloworld.';});router.get('/api/json/get',async(ctx,next)=>{constresult=awaitredis.get('age');ctx.body=result;});router.get('/api/json/set',async(ctx,next)=>{constresult=awaitredis.set('age',ctx.query.age);ctx.body={status:result,age:ctx.query.age}});app.use(router.routes()).use(router.allowedMethods());app.listen(3000,()=>{console.log('serverstartatlocalhost:3000');})我们首先需要在本地安装Redis,然后启动redisredis-server启动Node项目nodeindex.js访问http://localhost:3000/看到页面访问http://localhost:3000/api/json/set?age=2,我们将发送到Redi将s中age的值设置为2,访问http://localhost:3000/api/json/get,我们将在RedisNode应用中获取age的值Docker化首先我们想一下,这个后端应用涉及到Node和Redis如果我们要部署到Docker,应该如何构建镜像呢?方案一:基于一个基本的ubuntu镜像,我们在里面安装Node和Redis,这样Node和Redis就可以互通了。这个方案只需要启动一个容器,因为Node和Redis已经在这个容器里面了。方案二:我们基于Redis镜像启动一个容器,专门用来运行Redis。基于Node镜像启动一个容器,专门用来运行Node。Docker的理念更倾向于第二种选择。我们想要一个镜像专注于做一件事,现在流行的微服务、微前端也有这种思想。在中间章节,我们说过各个容器之间是相互隔离的,容器内的网络应用只能通过端口映射来访问。但是容器之间如何通信呢?Docker中使用Networking来实现容器之间的通信Networking#创建一个app-test网络dockernetworkcreateapp-test我们只需要将所有需要通信的容器添加到app-test网络中,容器之间就可以互相访问了.dockerrun-d--nameredis-app--networkapp-test-p6389:6379redisdockerrun-it--namenode-app--networkapp-testnode:11/bin/bash我们创建了两个容器,两者都在应用程序测试网络中。我们进入node-app容器,然后pingredis-app,发现可以ping通,说明容器之间是可以通信的!我们修改之前的代码:constredis=newRedis({port:6379,host:'db',});将redis的主机更改为db并创建一个新的DockerfileFROMnode:11COPYpackage*.json/app/WORKDIR/appRUNnpminstallCOPY./appEXPOSE3000CMD["node","index.js"]buildimagedockerbuild-tdeepred5/node-redis-app.startcontainer#createnetworkdockernetworkcreateapp-test#启动redis容器dockerrun-d--namedb--networkapp-test-p6389:6379redis#启动节点容器dockerrun--namenode-redis-app-p4444:3000--networkapp-test-ddeepred5/node-redis-appaccesshttp://localhost:4444/可以看到页面还记得我们做的react-app单页应用吗前?我们也可以把这个应用添加到app-test网络中,这样前端单页应用也可以访问后端了!修改react-app目录下的nginx.confserver{listen80;服务器名称本地主机;位置/{root/app/build;#包路径索引index.htmlindex.htm;try_files$uri$uri//index.html;#防止重新刷新返回404}location/api{proxy_passhttp://node-redis-app:3000;#后台转发地址}}重建镜像dockerbuild-tdeepred5/react-app-多。-fDockerfile.multi启动容器dockerrun-d--namemy-react-app-multi--networkapp-test-p9999:80deepred5/react-app-multi访问http://localhost:9999/api/json/set?age=55返回数据成功Dockercompose我们当前项目有3个启动镜像:deepred5/react-app-multi前端单页应用redis数据缓存deepred5/node-redis-app后端服务,访问redis,同时为前端提供接口。如果要完整的启动这个项目,需要按如下顺序启动:#启动redis容器dockerrun-d--namedb--networkapp-test-p6389:6379redis#启动节点容器dockerrun--namenode-redis-app-p4444:3000--networkapp-test-ddeepred5/node-redis-app#启动前端容器dockerrun-d--namemy-react-app-multi--networkapp-test-p9999:80deepred5/react-app-multi这只是一个有3个容器的项目。如果容器比较多,启动会变得很复杂!这时候就需要dockercompose来玩了首先,您需要安装dockercompose。安装完成后,我们新建my-all-app目录,然后新建docker-compose.ymlmkdirmy-all-appcdmy-all-apptouchdocker-compose.ymlversion:'3.7'services:db:图片:redis重启:始终端口:-6389:6379网络:-app-testnode-redis-app:图片:deepred5/node-redis-app重启:始终依赖于:-db端口:-4444:3000网络:-app-测试react-app-multi:image:deepred5/react-app-multirestart:alwaysdepends_on:-node-redis-appports:-9999:80networks:-app-testnetworks:app-test:driver:bridge#start所有容器docker-composeup-d#停止所有容器docker-composestop访问http://localhost:9999查看前端页面访问http://localhost:4444查看后端界面可以看到,使用docker-compose.yml配置好启动步骤之后,启动多个容器就变得很简单了。参考Howtousedockertodeployfront-endapplications第一本Docker书改版