<-

Docker and Containerization

作为开发人员,你是否曾经感叹应用程序的部署和环境配置过程的纷繁复杂?有没有遇到过在开发环境好用,但是到了测试和商场环境就不好用的情况?有没有遇到过新同事加入项目组需要花费大量的时间来配置开发环境的问题?有没有遇到过花了一整天甚至更长的时间一步一步按照配置部署文档来配置环境,但是其中却卡在中间某一个步骤上再过不去的问题?Docker 了解一下。

Docker 的基本概念

Docker 是一个用于构建、运行和分享应用程序的平台。就像它的 logo 表达的那样可以将应用程序打包成一个个的集装箱把它运送到任何需要的地方。利用 Docker 我们可以将应用程序和它运行时所需要的各种依赖、第三方软件库、配置文件等打包在一起,以便在任何环境中都可以正确的运行。

比如我们写了一个网站用到了现在比较流行的前后端分离架构,前端使用 Vue 框架来构建网站的界面,后端使用 Java 的 Spring Boot 微服务框架来提供各种服务和接口,然后使用 MySQL 数据库来存储数据。如果没有 Docker,我们可能需要在本地先安装前端 Node.js 环境、各种 NPM 依赖包、Java 运行环境和 SpringBoot 微服务的各种第三方依赖包,以及 MySQL 数据库,配置各种环境变量,然后再启动这些服务网站才能正常运行起来。

如果项目规模再大一点,可能还需要配置 Redis 缓存,Nginx 负载均衡,甚至各种微服务框架等等。如果还需要把这个网站部署到测试环境或者生产环境上,那么刚刚所有的步骤都需要在新的环境再来一遍。利用 Docker 我们就可以将它们打包成一个个的集装箱,只要你在开发环境中运行成功了,那么在其他环境中也一定可以运行成功。

Docker 和传统虚拟机的区别

也许你听说过或者曾经使用过 VMware 等虚拟机软件以及 Windows 的 WSL 和 Hyper-V 功能。我们可以在 Windows 中通过 WSL 安装和使用 Linux 系统,也可以在 Mac 上通过虚拟机软件运行 Windows 和各种 Linux 系统。它们是完整的操作系统,和实际的 Windows 和 Linux 系统一样,可以在这个操作系统中运行应用程序。这是通过一种叫做虚拟化的技术来实现的。

虚拟化技术是一种将物理资源虚拟为多个逻辑资源的技术,它可以将一台物理服务器虚拟成多个逻辑服务器,每个逻辑服务器都有自己的操作系统、CPU、内存、硬盘和网络接口等。它们之间完全隔离,可以独立运行虚拟机。在一定程度上实现了资源的整合,可以将一台服务器的计算能力、存储能力、网络资源分配给多个逻辑服务器,实现多台服务器的功能,但是它的缺点也非常明显,每台虚拟机都需要占用大量的资源比如 CPU、内存、硬盘、网络等,而且启动速度非常慢,通常需要几分钟甚至几十分钟。

而大部分的情况下,我们的一台服务器上只需要运行一个主要对外提供服务的应用程序就可以了,并不需要一个完整的操作系统所提供的所有功能。拿前面的例子来说,其实我们所需要的可能只是一个 Web 服务器,但是虚拟机却需要启动一个完整的操作系统,包括操作系统的内核、各种系统服务、各种工具甚至图形界面,这些我们并不需要的服务占用了大量的资源,导致了资源的浪费和启动速度慢的问题。

了解了虚拟机之后,我们再来看一下容器,这里需要注意的是 Docker 和容器是两个不同的概念,Docker 非常的流行以至于很多人把 Docker 和容器混为一谈,其实 Docker 只是容器的一种实现,是一个容器化的解决方案和平台,而容器是一种虚拟化技术,和虚拟机类似,也是一个独立的环境,可以在这个环境中运行应用程序。和虚拟机不同的是它并不需要在容器中运行一个完整的操作系统,而是使用宿主机的操作系统,所以启动速度非常快,通常只需要几秒钟,同时因为需要的资源更少所以可以在一台物理服务器上运行更多的容器。这样就可以更加充分的利用服务器的资源,减少资源的闲置和浪费。比如我们一台物理服务器上可能只能运行几个虚拟机,但是却可以运行上百个容器,这就是容器和虚拟机的一些主要区别。

镜像、容器和仓库

Docker 中有几个比较重要的概念:镜像、容器和仓库。

镜像是一个只读的模板,它可以用来创建容器。容器是 Docker 的运行实例,它提供了一个独立的可移植的环境,可以在这个环境中运行应用程序。Docker 仓库是用来存储 Docker 镜像的地方,最流行和最常用的仓库就是 Docker Hub,它是一个公共的 Docker 仓库,用来集中存储和管理 Docker 镜像,我们可以在那里下载各种镜像,也可以将自己的镜像上传到上面,这样就可以实现镜像的共享和复用,这也是 Docker 非常流行的一个重要原因。

理解了这些概念也就理解了 Docker 的生命周期和基本原理,尤其是镜像和容器,它们是 Docker 的核心概念,需要对这两个概念有一个深刻的理解和认识。这对于 Docker 的学习``和使用有非常大的帮助。

Docker 的安装

Docker 的安装非常简单,我们只需要在官网下载对应的安装包,然后双击即可完成安装。启动 Docker 之后你可以在终端或者命令行中使用 Docker 的各种命令,输入 docker --version 命令,能看到 Docker 的版本信息就说明 Docker 已经安装成功了。

Docker 的体系结构是使用 Client-Server 架构模式,Docker Client 和 Docker Daemon 之间通过 Socket 或者 Restful API 进行通信。Docker Daemon 就是服务端的守护进程,它负责管理 Docker 的各种资源,Docker Client 负责向 Docker Daemon 发送请求,Docker Daemon 接收到请求之后进行处理,然后将结果返回给 Docker Client。

容器化技术

容器化,顾名思义就是将应用程序打包成容器,然后在容器中运行应用程序的过程。

这个过程简单来说可以分成三个步骤,首先需要创建一个 Dockerfile 来告诉 Docker 构建应用程序镜像所需要的步骤和配置,然后使用 Dockerfile 来构建镜像,接下来我们就可以使用这个镜像来创建和运行容器。

这里出现了一个新的概念 Dockerfile。Dockerfile 是一个文本文件里面包含了一条条的指令,用来告诉 Docker 如何来构建镜像,这个镜像中包括了我们应用程序执行的所有命令,也就是我们刚刚提到的各种依赖、配置环境和运行应用程序所需要的所有内容。

Hello Docker

首先创建一个 Hello Docker 文件夹在任意位置,然后使用 VSCode 或其他任何代码编辑器打开这个文件夹,在文件夹中我们创建一个 index.js 的文件,

console.log("hello docker")

然后在这个文件中输入一段代码让它能够输出一段内容到控制台。这里只是利用 JavaScript 进行简单的演示,并不需要了解 JavaScript。在 VS Code 中打开一个终端,然后在终端命令行里面执行一下 node index.js 就可以看到成功输出了我们想要的内容。 Node.js 是一个运行环境,它可以让我们在浏览器之外的地方运行 JavaScript 的代码,Node.js 和 JavaScript 的关系就像 Java 和 JRE 的关系一样,如果想要运行 Java 程序那么就需要安装 JRE,同样的如果想要在浏览器之外的环境中运行 JavaScript 的代码那么就需要安装 Node.js。

当我们想要在另一个环境中运行这个应用程序需要执行哪些步骤呢?首先需要在这个环境中先安装好测试系统,然后需要安装对应版本的 JavaScript 的运行环境也就是 Node.js,再把应用程序和它所依赖的第三方包和库复制到这个环境上,最后再执行一下命令来运行用程序。而有了 Docker 之后我们就可以把这些步骤写入到 Dockerfile 中,剩下的工作就交给 Docker 来自动完成。

回到 VSCode,我们在项目的根目录下创建一个叫做 Dockerfile 的文件,

FROM node:14-alpine
COPY index.js /index.js 
CMD [ "node", "/index.js" ]

注意这个文件的命名是 Dockerfile 没有任何扩展名第一个字母 D 大写其他都小写,这是一个约定俗成的规范,如果不遵守这个规范那么 Docker 就无法识别这个文件。Dockerfile 中我们需要先指定一个基础镜像,镜像是按层次结构来构建的,每一层都是基于上一层的,所以我们需要先指定一个基础镜像,然后在这个镜像的基础上添加我们的应用程序。我们可以从一个最基础的 Linux 镜像开始,然后在这个镜像基础上安装 Node.js 和我们的应用程序,或者我们也可以直接使用 Node.js 的镜像,因为这个镜像已经是基于 Linux 来构建的了,直接拿来使用就可以了。配置完运行环境之后还需要把我们的应用程序复制到镜像中,然后使用 cmd 命令在镜像中运行应用程序,到这里 Dockerfile 就编写完成了。回到终端,使用 docker build -t hello-docker .,这里 hello-docker 是创建镜像的名字,在后面加上一个点表示当前目录也就是 Dockerfile 所在的目录。执行之后,我们的镜像就构建完成了。 可以使用 docker images 或者 docker image ls 命令来查看我们所有的镜像,这里你就会看到刚刚构建的镜像它的名字是 hello-docker,冒号后面的 Latest 表示镜像的标签,也就是镜像的版本,如果不指定版本那默认就是 Latest,如果你想指定版本那么就可以在镜像名字后面加上冒号和版本号。

使用 docker run hello-docker 命令运行实例,可以看到控制台中出现了我们刚刚输入的内容,说明我们的应用程序已经运行起来了。如果你想要在另一个环境中运行这个应用程序那么就只需要把这个镜像文件复制过去,然后执行一下刚刚的命令就可以了。也可以把这个镜像文件上传到 Docker Hub 或者 Hubware 镜像仓库中,

docker tag hello-docker thekingofcool/hello-docker
docker image push thekingofcool/hello-docker

然后任何人都可以在任何地方使用 docker pull thekingofcool/hello-docker 命令来下载这个镜像文件,然后运行这个应用程序。

Docker Volume

Docker 容器有一个特点就是容器中的数据不会持久化,当我们创建一个容器的时候它通常以一个干净的文件系统开始,容器启动之后我们可以在容器中创建文件、修改文件。但是当容器被停止之后,容器中的所有数据都会丢失掉。如果我们想要持久化容器中的数据的话,需要用到 Volume 这个概念。它可以把容器中的目录或者指定路径映射到宿主机的某一个目录或者位置上,这样就可以将数据保存到宿主机的磁盘上,实现了数据的持久化。

Docker Compose

Docker Compose 是由 Docker 官方开源的项目,是一个用来定义和运行多个 Docker 容器应用程序的工具。比如前面提到过的搭建一个网站使用到前端、后端、数据库甚至缓存、负载均衡等多个服务器,这些服务都是独立的,但是它们之间又有关联需要相互配合来工作。前端需要连接后端,后端需要连接数据库,这些服务之间的关联关系就是 Docker Compose 要解决的问题。它通过一个单独的 docker-compose.yml 配置文件来将这一组互相关联的容器组合在一起形成一个项目,然后使用一条命令就可以启动、停止或者重建这些服务,这样就可以非常方便地管理这些服务。

如果你的项目组中来了一个新同事,之前他可能需要半天时间去安装各种依赖和配置运行环境,现在有了 Docker Compose 之后,他只需要执行 Docker Compose命令,就可以自动安装各种依赖和配置运行环境,然后在本地运行项目了。大大提高了开发效率,减少了沟通成本。

Happy hacking.