使用Docker来编排Web应用

前言使用Docker可以轻松构建一个项目并运行 , 然而在真实的使用场景中,我们的项目并非是单一的 , 而是多个项目相互依赖组成一个web应用 。
考虑这样一个场景 , 一个正在运行的web应用,它用到了vue、JAVA8、java15、Tomcat、Nginx、php、MySQL和redis 。如果要将这个应用迁移到一台新的服务器上运行,那么就需要在这台机器上重新安装所需的软件以及环境变量,这是很痛苦的 。
那么,有没有什么办法来解决这个问题呢?当然有,那就是本文的主角docker-compose(docker编排) 。本文就跟大家分享下我是如何使用它解决我的痛苦的,欢迎各位感兴趣的开发者阅读本文 。
前置知识阅读本文前,你需要先提前安装好Docker[1]和 docker-compose[2] 。如果你已经安装 , 我们先来了解几个基础概念 。如果你仅仅只是想学习我是如何编排整个web应用的,请直接从编排容器[3]章节开始阅读 。
镜像操作系统分为内核和用户空间 , 对于linux而言,内核启动后,会挂载root文件系统 , 为其提供用户空间支持 。而Docker镜像就相当于一个root文件系统 。比如官方的镜像ubuntu:20.04就包含了完整的一套ubuntu最小系统的rooot文件系统 。
Docker的镜像是一个比较特殊的文件系统,它除了提供容器运行时所需的东西外,还包含了一些为运行时准备的一些配置参数(挂载卷、环境变量、用户等) 。镜像不会包含任何的动态数据 , 因此在构建之后,它的内容不会被改变 。
如果你还是一头雾水的话,可以将它比作“备份”,在需要的时候把它拿出来即可原封不动的获得备份时刻的内容 。
可能有些开发者会有疑问 , 即使是最小的root文件系统,它的体积也是庞大的 。Docker在设计时,就充分利用 Union FS[4] 的技术 , 将其设计为分层存储的架构 。这种架构使得镜像的复用、定制变的更为容易,可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像 。
??注意:镜像构建时,会一层层构建,前一层是后一层的基础 。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层 。因此 , 在编写镜像配置文件的时候,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉(比如你在某一层需要删除前一层的文件 , 它只是将文件标记为了已删除 , 最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像) 。
容器镜像与容器之间的关系,类似于Java中的类与实例 。镜像是类 , 容器是实例化出来的实例 。容器可以被创建、启动、停止、删除、暂停等 。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间 。因此容器可以拥有自己的root文件系统、网络配置、进程空间 。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样 。
前面我们讲过镜像使用的是分层存储,容器也是如此 。每一个容器运行时 , 以镜像为基础层,在其上创建一个当前容器的存储层,这个为容器运行时读写而准备的存储层称之为容器存储层 。
??注意:容器存储层的生命周期和容器一样,容器销毁时,存储层也随之销毁 。因此,任何保存于存储层的信息都会随着容器的销毁而丢失 。
因此 , 当我们的容器需要进行数据写入时,需要 使用volume数据卷 或者 挂载宿主机的目录 来实现数据的持久化 。
仓库当我们构建好镜像之后,很容易在当前的宿主机上运行,但是,如果需要在其他服务器上使用这个镜像 , 我们就需要一个仓库来存储和分发它们 。Docker Registry[5] 就是这样的服务 。
一个 Docker Registry 中可以包含多个 仓库(Repository);每个仓库可以包含多个 标签(Tag);每个标签对应一个镜像 。
一个仓库包含同一个软件不同版本的镜像,标签就对应着软件的各个版本 。我们通过<仓库名>:<标签>的格式来指定具体是那个版本的镜像 。
我们以ubuntu镜像为例,ubuntu是仓库的名字,其内包含有不同的版本标签,如:18.04,20.04 。我们在编写配置文件的时候 , 就可以通过ubuntu:18.04、ubuntu:20.04来指定具体所需版本的镜像 。如果忽略了标签,将视为ubuntu:latest 。


推荐阅读