Docker是一个用于开发、运行和发布的应用的开发平台。Docker能够将应用程序与基础架构分离,通过利用Docker快速交付、测试和部署代码的方法,显着减少编写代码和在生产中运行之间的延迟。
首先分离的作用,Docker是一种容器技术,一方面Docker可以将程序本身同程序的基础架构分离,如同类与实例之间的关系,只要创建好“类”,就能到处去使用“实例”。另一方面,容器就类似于一个封闭的环境,容器之间也是相互分离的,互不干扰的,保证了环境的统一和安全,就不会出现“测试环境没问题,到了生产环境就报错”这样尴尬的情况。
其次就是快速交付、测试以及部署,说明Docker可以用来做CD/CI,像是自动测试、自动部署以及自动交付代码产物等功能都集成了,所以说Docker减少了代码开发和上线之间手动操作导致的效率低下的问题。
一般部署一个前端应用,普遍的流程就是先运行单元测试,结束以后打包前端应用,将对应的dist文件夹给放到web服务器上,一般使用nginx来作为静态服务器。这个时候访问对应的IP就能看到项目了。
对于前端而言,可能大部分都不会用到这个技术,更多是后端会去使用。但是随着前端应用规模越来越大,经常会出现依赖版本不一样,或者线上环境不一样而导致的报错问题,排查起来也非常困难。因此这也是前端使用Docker一个最大的契机。
对于一个前端来说,不需要了解太多复杂的概念,只需要了解以下的基本概念即可。
首先就是开篇提到的类与实例的关系,镜像就是类,镜像类似于系统镜像的概念,对于前端而言,镜像就是包含了代码运行所需要的一切产物、依赖、配置等。这样的话,可以保证每次程序运行的环境一致。构建镜像,一般都是通过一个文本文件来生成。这个文件就是Dockerfile,文件内容就是一系列的指令集合。
举来说,对于一个简单的前端应用来说,首先需要安装NodeJS作为运行环境,其次则是需要安装依赖,最后需要通过npmrunbuild这样的命令来构建应用产物。这个过程在Dockerfile中就是一系列的指令集合,后面会具体分析各个指令用法。
有了镜像以后,可以通过镜像产出容器,这个“容器”就是实例的概念,所以拿到容器以后可以放到任意平台去使用、比如windows、linux、unix等,真正做到了一处开发,到处使用的功能。需要注意的是容器并不是虚拟机、它只是一个进程,同普通程序一样,理解这点在启动容器的时候尤为重要。
Docker中的仓库其实和github、gitee这样的代码仓库是类似的概念,只是后者是用来存储源代码、而前者是用来存储镜像的,比如前端肯定会使用到的NodeJS,则是在DockerHub中可以找到。使用的时候,就可以在仓库中找到对应的镜像即可。同样自己写的镜像也可以上传到仓库中,类似于git的push操作,而pull操作则是从仓库中拉取镜像。
Volumes翻译过来为卷,就是磁盘中的卷的意思,Docker中的卷主要是用来持久化数据的。当我们生成镜像的时候,需要保持镜像体积尽可能的小,并且镜像中操作数据,下次再去构建时并不会保存操作的数据,因此是不建议在镜像中去操作数据的,如果有操作数据的需要,则可以使用卷关联宿主机上的某个文件夹来持久化保存数据。对于前端而言,这个功能用到的很少。
了解以上这些基本概念以后,就可以尝试在项目中使用Docker了。
目录结构如下:
首先下载Docker,可以去官网中下载,找到对应的操作系统版本下载即可。这里需要注意windows系统下,安装Docker还需要开启BIOS中的配置,因为安装不是本篇的主要内容,这里就不在细说了。一般安装好以后,Docker就是一个普通的应用,双击打开,可以看到界面是这样的
这样就可以开始去定制项目的镜像了。
在项目的根目录下面新建一个Dockerfile文件,内容如下:
第二行的LABEL指令不对镜像的构建产生作用,只是一个记录信息的指令,按照key、value的格式,可以用来记录诸如作者、版本等元数据信息,类比到前端的话,就是和meta标签的作用类似。
COPY指令根据字面就可以知道,这是一个复制文件到指定目录的指令,可以有多个源路径,以空格分开,目的路径只能存在一个,并且源路径也可以是类似glob匹配路径的格式,注意这个匹配模式是遵循gofilespathmatch规则的。
在Dockerfile中,每一行的指令都可以理解为一个单独的layer,因此指令之间都是独立存在的,也就是下面这样的语句其实是无效的
RUNcd./srcCOPYdocs/var/web此时如果构建镜像的话,就会提示找不到docs目录,原因就是上面所说的Dockerfile中的每一级的指令都是独立的,虽然第一行是cdsrc了,但是到了COPY指令,实际还是在根目录下,根目录下面并没有docs的文件夹,因此才会报错了。
这个时候可以使用WORKDIR指令,指定接下来的所有指令的基础路径。
WORKDIR./src#下面的所有指令都是基于src的文件夹去执行的紧接着下面又是一个FROM指令,这里就要说到docker的一个多阶段构建的一个功能。所谓多阶段,就是把构建流程分为了多个阶段,上面的配置文件中,第一阶段就是生成目标文件夹,也就是打包以后的文件,第二阶段就是把生成好的文件放到nginx服务器中。所以可以看到第二阶段,也是以FROM指令开头,这次是依赖了nginx的镜像,表示这次需要在nginx的环境下运行。
dockerbuild--targetbuilder-tdocify/blog.接着EXPOSE指令暴露了80端口,注意这里只是表明了暴露的端口是多少,并不是设置了端口,因为实际的端口实际是容器中nginx端口。该指令只是起到了一个说明的作用。
COPY指令上面已经说过了,区别就在于这里多了一个form=0,这表示从第0阶段生成的结果中拿到install好的文件去拷贝到nginx的目录下。
最后的CMD指令,则是相当于打开了CMD的终端,然后输入对应的命令。
至此Dockerfile的配置文件常用的指令就讲完了,接下来就可以构建镜像了。
关于Docker的核心操作,其实算起来只有两个:构建镜像和启动容器。
首先进入项目根目录下,打开终端,输入命令:
dockerbuild命令后面的-t参数表示tag标签的意思,就是给镜像起了一个名字叫做docify/blog。
注意命令最后还有一个.非常重要,这个.表示把当前目录下的文件及文件夹作为Dockerfile配置文件中的上下文。什么意思呢?Dockerfile文件中所要做的事,其实就是把项目进行打包构建,但是项目是在本地环境下,并不在Docker的环境中,所以就需要这个.将当前的项目传输到Docker的环境中,也就是所谓的上下文中,这样才会在一开始的配置文件中出现了这么一句COPY./var/web/,这里的.实际就是构建镜像的时候,指定的当前目录下的内容。
关于这一点,也是整个Docker学习中的一个难点。一定要理解上下文的概念,这样才不会在构建的时候,找不到对应的构建目录。
接下来,可以选择点击上图中RUN的按钮来创建容器,也可以使用命令来创建容器。输入以下命令:
dockerrun-d-p80:80--nameblogdocify/blogdockerrun命令则是根据已有的镜像来创建一个容器。
-d参数表示后台运行容器。对于前端应用来说,是启用了nginx作为web服务器,nginx是一直需要保持后台运行,而不是只启动一次就结束了,所以需要加上-d的参数。
一般来说,创建好容器以后,可能需要去访问容器中运行的项目,所以需要把容器中的端口对外暴露出来,这样才可以在宿主机上面访问前端应用,此时可以使用-p参数。这里就表示容器中的80端口暴露到了宿主机的80端口,就可以访问到前端应用了。
再往后--name很好理解,就是设置容器的名字。如果没有该参数,则Docker会自动生成一个名字。
最后的docify/blog表示上一步中镜像的名字,也就是容器是基于镜像来创建的,这个参数也是一个必不可少的参数。
打开浏览器,输入地址localhost(nginx配置文件是默认本地的地址),可以看到部署好的应用:
总结一下,镜像和容器的关系就像JavaScript中的类与实例,镜像就是规定了容器该“长成什么样”。容器就是镜像最终的成果。一个镜像可以生成多个容器,容器也可以放到任意环境中运行,就像一个便捷的笔记本电脑,可以到处运行。
首先创建一个工作流的yaml文件:
#ThisisabasicworkflowtohelpyougetstartedwithActionsname:LearningRouteCI#Controlswhentheworkflowwillrunon:[push]#Aworkflowrunismadeupofoneormorejobsthatcanrunsequentiallyorinparalleljobs:#Thisworkflowcontainsasinglejobcalled"build"build:#Thetypeofrunnerthatthejobwillrunonruns-on:ubuntu-latest#Stepsrepresentasequenceoftasksthatwillbeexecutedaspartofthejobsteps:#Checks-outyourrepositoryunder$GITHUB_WORKSPACE,soyourjobcanaccessit-uses:actions/checkout@v3#Runsasinglecommandusingtherunnersshell-name:Builddockerimagerun:dockerbuild-tblog.#Runsasetofcommandsusingtherunnersshell-name:Createcontainerbyimagerun:dockerrun-d-p80:80blog--nameblog-name:Startcontainerlogrun:dockercontainerls整个工作流分为几个部分,触发事件、任务以及步骤。一个任务下面可以包含多个步骤,步骤下面则是具体的执行过程。
这里需要基于ubuntu的虚拟机作为运行CI的环境,也就是上面runs-on的配置。接着就是需要下载项目的源码,uses这里是使用了github上自带的action,可以帮助来下载源码。后面的步骤则是与上一节提到的步骤是一致的,就不再赘述。
以上流水线只是作为一个示例,实际部署到服务器上,或者是部署到githubpages上,步骤有所不同,但是流程都是类似的。
除此以外,还存在可以优化的地方,那就是上面提到的上下文。构建镜像的时候,是把整个项目中所有的文件都作为上下文传输到Docker中了,实际上有些构建的脚本文件是不需要的,可以把它排除出上下文。添加.dockerignore文件实现排除不需要传入上下文的文件,这和.gitignore是类似的行为。
最后可能会有人存在疑问:为什么要使用FROM指令来分割这么多步骤,不能放到一起去执行吗?结合上面提到的每一个都是镜像中的一层,所有的步骤都一次性执行的话,layer会比较大,最终导致整个镜像的体积也会比较大,比如例子中的项目,如果全部放到一起去构建镜像,最终的镜像大小可能有几百MB,甚至更大,而多阶段构建的镜像,却只有几十MB。
总结一下Docker之于前端的意义:
Docker还有很多细节没有提到,容器化也是未来技术发展的一个方向,学不学习,主要看自己是否需要,并一定要追寻所谓的最新的技术,技术最终还是为了去服务真实的业务而存在的。