微服务容器化
本章主要介绍Docker技术以及如何将Spring Boot项目容器化等内容。
1. Docker概述
1.1 Docker的优势
Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache 2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。
作为一种新兴的虚拟化方式,Docker与传统的虚拟化方式相比具有众多的优势。
- 高效的利用系统资源:由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker对系统资源的利用率更高。无论是应用的执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机往往可以运行更多数量的应用。
- 快速的启动时间:传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用由于直接运行于宿主内核,无须启动完整的操作系统,因此可以做到秒级甚至毫秒级的启动时间,大大地节约了开发、测试、部署的时间。
- 一致的运行环境:开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些bug并未在开发过程中被发现。而Docker的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现“这段代码在我机器上没问题啊”这类问题。
- 持续交付和部署:对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。使用Docker可以通过定制应用镜像来实现持续集成(Continuous Integration)、持续交付和部署(ContinuousDelivery/Deployment)。开发人员可以通过 Dockerfile来进行镜像构建,并结合持续集成系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署系统进行自动部署。而且使用Dockerfile使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需的条件,帮助更好地在生产环境中部署该镜像。
- 迁移简单:由于Docker确保了执行环境的一致性,使得应用的迁移更加容易。Docker可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本上,其运行结果是一致的。因此用户可以很轻易地将在一个平台上运行的应用迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
- 容易维护和扩展:当我们需要在宿主机器上运行一个虚拟操作系统时,往往需要安装虚拟软件,如Oracle VirtualBox或者VMware。当我们在虚拟软件上安装操作系统的时候,虚拟机软件需要模拟CPU、内存、I/O设备和网络资源等,为了能运行应用程序,除了需要部署应用程序本身及其依赖,还需要安装整个操作系统和驱动,会占用大量的系统开销。
Docker使用的分层存储及镜像技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大降低了应用服务镜像的制作成本。
下面我们来简单对比一下传统虚拟机和Docker。
从表中的数据来看,虚拟机和Docker容器虽然可以提供相同的功能,但是优缺点是显而易见的。
1.2 Docker的基本概念
下面我们来理解一下Docker的几个基本概念:镜像(Image)、容器(Container)、仓库(Respository)与镜像注册中心(DockerRegistry)。
镜像(Image)
Docker镜像可以理解为一个Linux文件系统,Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。
容器(Container)
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间,甚至自己的用户ID空间。容器内的进程运行在一个隔离的环境里,使用起来就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主机上运行更加安全。
仓库(Respository)与镜像注册中心(Docker Registry)
镜像构建完成后,如果需要在其他服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务,Docker Registry镜像注册中心就是这样的服务。
一个Docker Registry镜像注册中心可以包含多个仓库(Repository),每个仓库可以包含多个标签(Tag),每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签。
Docker官方提供了一个叫作Docker Hub的镜像注册中心,用于存放公有和私有的Docker镜像仓库(Repository)。可以通过Docker Hub下载Docker镜像,也可以将自己创建的Docker镜像上传到Docker Hub上。Docker Hub的网址为:https://hub.docker.com/。
1.3 Docker架构
我们先来了解一下Docker引擎。Docker引擎可以理解为一个运行在服务器上的后台进程,其主要包括3大组件。
- Docker后台服务(Docker Daemon):长时间运行在后台的守护进程,是Docker的核心服务,可以通过dockerd命令与它交互通信。
- REST接口(REST API):程序可以通REST的接口来访问后台服务,或向它发送操作指令。
- 交互式命令行界面(Docker CLI):可以使用命令行界面与Docker进行交互,例如以docker为开头的所有命令的操作。而命令行界面又是通过调用REST的接口来控制和操作Docker后台服务的,如图所示。
我们再来看看Docker官方文档上的一张架构图
- Docker客户端(Docker Client):与Docker后台服务交互的主要工具,在使用docker run命令时,客户端把命令发送到Docker后台服务,再由后台服务执行该命令。可以使用docker build命令创建Docker镜像,使用docker pull命令拉起Docker镜像,使用dockerrun命令运行Docker镜像,从而启动Docker容器。
- Docker Host:表示运行Docker引擎的宿主机,包括Dockerdaemon后台进程,可通过该进程创建Docker镜像,并在Docker镜像上启动Docker容器。
- Registry:表示Docker官方镜像注册中心,包含大量的Docker镜像仓库,可以通过Docker引擎拉取所需的Docker镜像到宿主机上。
1.4 Docker的安装
Docker分为两个版本:社区版(Community Edition,CE)和企业版(Enterprise Edition,EE)。Docker社区版主要提供给开发者学习和练习,而企业版主要提供给企业级开发和运维团队用于对线上产品的编译、打包和运行,有很高的安全性和扩展性。Docker的社区版和企业版都支持Linux、Cloud、Windows和Mac OS平台等,这里我们以Windows操作系统为例演示Docker的安装,具体步骤如下:
- Windows操作系统可以利用docker toolbox来安装,国内可以使用阿里云的镜像下载,下载地址:http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/,本书选择DockerToolbox-18.03.0-ce.exe版本。
- 用鼠标双击DockerToolbox-18.03.0-ce.exe,按照提示一步一步安装即可,安装完成后在桌面上会出现两个图标,一个是命令行形式的Docker终端,另一个是图形界面的Docker操作工具,如图所示。
- 通过CMD命令提示符查看Docker是否安装成功,输入docker-machine会出现Docker版本等信息,如图所示。
- 将C:\Program Files\Docker Toolbox文件夹下的boot2docker.iso复制到C:\Users\ay.docker\machine\cache下,然后断开网络。
- 双击桌面的Docker Quickstart Terminal终端,启动命令行终端,该过程比较缓慢,需要耐心等待。当出现如图所示的界面,代表命令行终端启动成功。
在命令行终端输入命令“docker run hello-world”,报请求超时异常。
原因是镜像都在国外,访问速度慢,所以我们需要换成国内的下载源,具体更改步骤如下:
- 访问DaoCloud网站:https://www.daocloud.io/mirror#accelerator-doc并注册账号,可以使用Git hub账号登录。注册并登录成功后,单击加速按钮,在页面的最后面找到加速地址。
- 在命令行终端输入下面4条命令(替换其中的加速地址)。
- 在命令行终端输入命令“docker run hello-world”,便可以下载hello-world镜像,并运行它。
2.Docker的常用命令
Docker安装成功后,下面我们学习一下Docker的常用命令。
2.1 查看版本信息
使用docker help命令查看相关的命令行帮助信息:
使用docker -v查看Docker的版本信息:
2.2 镜像相关命令
使用docker images查看本地镜像:
- REPOSITORY:表示镜像仓库名称。
- TAG:表示标签名称,latest表示最新版本。
- IMAGE_ID:镜像ID,唯一的,这里我们可以看到12位字符串,实际上它是64位完整镜像ID的缩略表达式。
- CREATE:表示镜像的创建时间。
- SIZE:表示镜像的字节大小。
如果想拉取Docker Hub中的镜像,可以使用docker pull命令:
如果想在Docker Hub中搜索镜像,可以使用docker search命令:
- NAME:表示镜像名称。
- DESCRIPTION:表示镜像仓库的描述。
- STARS:表示镜像仓库的收藏数,用户可以在Docker Hub上对镜像仓库进行收藏,一般可以通过收藏数判断该镜像的受欢迎程度。
- OFFICIAL:表示是否为官方仓库,官方仓库具有更高的安全性。
- AUTOMATED:表示是否自动构建镜像仓库,用户可以将自己的Docker Hub绑定到Github账号上,当代码提交后,可自动构建镜像仓库。
如果想导入/导出镜像,可以使用docker save或者docker load命令
导出的centos镜像包文件可随时在另一台Docker机器导入,命令如下:
如果想删除镜像,可使用docker rmi命令
2.3 容器相关命令
如果想查看运行的容器,可使用如下命令:
- CONTAINER ID:表示容器ID。
- IMAGE:表示镜像名称。
- COMMAND:表示启动容器运行的命令,Docker容器要求我们在启动容器时需要运行一个命令。
- CREATE:表示容器创建的时间。
- STATUS:表示容器运行的状态,例如UP表示运行中,Exited表示已退出。
- PORTS:表示容器需要对外暴露的端口。
- NAMES:表示容器的名称,由Docker引擎自动生成,也可以在docker run命令中通过–name选项来指定。
如果想创建并启动容器,可以使用如下命令:
- -i选项:表示启动容器后,打开标准输入设备(STDIN),可以使用键盘进行输入。
- -t选项:表示启动容器后,分配一个伪终端,将与容器建立会话。
- centos参数:表示要运行的镜像名称,标准格式为:centos:latest,若为latest版本,可省略latest。
- /bin/bash参数:表示运行容器中的bash应用程序。
Tips:上述命令首先从本地获得CentOs镜像,若本地没有此镜像,则从Docker Hub上拉取CentOs镜像并放入本地,随后根据CentOs镜像创建并启动CentOs容器。
除了使用该命令可以创建和进入容器外,还可以使用如下命令进入运行中的容器:
还可以使用以下命令向运行中的容器执行具体命令:
可以使用docker stop和docker kill命令停止或者终止容器:
可以使用docker start和docker restart命令启动或者重启容器:
可以使用docker rm命令来删除已经停止的容器:
如果想导入/导出容器,可以使用docker import或者dockerexport命令:
导出的centos容器包可随时在另一台Docker机器上导入为镜像,具体命令如下:
3.制作与自动化构建镜像
3.1 制作镜像
上一节我们已经学习了如何使用Docker命令来操作镜像和容器,这一节学习如何制作Java运行环境的镜像,并在此镜像上启动Java容器。具体步骤如下:
- 下载JDK安装包,下载地址为https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html。这里使用的JDK版本为:jdk-11.0.2_linux-x64_bin.tar.gz。
- 拉取centos镜像,并启动centos容器:
- -v选项:-v在Docker中称为数据卷(Data Volume),用于将宿主机上的磁盘挂载到容器中,格式为“宿主机路径:容器路径”,需要注意的是宿主机路径可以是相对路径,但是容器的路径必须是绝对路径。可以多次使用-v选项,同时挂载多个宿主机路径到容器中。
- /c/Users/Ay:/mnt/:/c/Users/Ay为宿主机JDK安装包的存放路径,/mnt/为centos容器的目录。
- 在centos容器的/mnt/目录下解压安装包并安装JDK:
- 再打开一个命令行终端,通过docker commit命令提交当前容器为新的镜像:
- 验证生成的镜像是否可用。
上述命令中,我们在hwy/centos镜像上启动一个容器,并在容器目录/opt/jdk-11.0.2/bin/下执行命令java -version,可以看到命令行终端输出了JDK版本号相关信息。需要注意的是,上述命令添加了一个–rm选项,该选项表示容器退出时可自动删除容器。
至此,手工制作Docker镜像已完成。
3.2 使用Dockerfile构建镜像
从上一节的Docker命令学习中可以了解到,镜像的定制实际上就是定制每一层所添加的配置和文件。如果可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,实现整个过程的自动化,既可以提高效率,又能够减少错误。这个脚本就是Dockerfile。
Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。下面我们创建一个空白文件(文件名为Dockerfile),并学习Dockerfile指令。
3.2.1 FROM指令
所谓定制镜像,一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个centos镜像的容器,再进行修改一样,基础镜像是必须指定的,而FROM命令就是用于指定基础镜像。因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。例如:
FROM centos:latest
FROM命令的值有固定的格式:即“仓库名称:标签名”,若使用基础镜像的最新版本,则latest标签名可以省略,否则需指定基础镜像的具体版本。
3.2.2 MAINTAINER指令
MAINTAINER用于设置该镜像的作者,具体格式:MAINTAINER。例如:
### 建议使用 姓名+邮箱 的形式
MAINTAINER "hwy"<huangwenyi10@163.com>
3.2.3 ADD指令
ADD是复制文件指令,它有两个参数 和。source参数为宿主机的来源路径,destination是容器内的路径,必须为绝对路径。语法为ADD 。例如:
### 添加jdk安装包到容器的/opt目录下
ADD /c/Users/Ay/jdk-11.0.2_linux-x64_bin.tar.gz /opt
ADD指令将自动解压来源中的压缩包,将解压后的文件复制到目标目录(/opt)中。
3.2.4 RUN指令
RUN指令用来执行一系列构建镜像所需要的命令。如果需要执行多条命令,可以使用多条RUN指令,例如:
### 执行shell命令
RUN echo 'hello ay...'
RUN ls -1
Dockerfile中每一个指令都会建立一层, RUN指令也不例外。每一个RUN的行为就和我们手工建立镜像的过程一样,即新建立一层,在其上执行这些命令,构成新的镜像。上面的这种写法,创建了多层镜像,但这是完全没有意义的,其结果就是产生非常臃肿、非常多层的镜像,不仅增加了构建部署的时间,也很容易出错。这是很多Docker初学者常犯的一个错误。
3.2.5 CMD指令
CMD指令提供了容器默认的执行命令。Dockerfile只允许使用一次CMD指令,使用多个CMD指令会抵消之前所有的指令,只有最后一个指令生效。CMD指令有以下3种形式:
CMD ["executable", "param1", "param2"]
CMD ["param1", "param2"]
CMD command paraml param2
例如,使用如下指令在容器启动时输出Java版本:
### 容器启动时执行的命令
CMD /opt/jdk-11.0.2/bin/java -version
熟悉了Dockerfile文件指令后,下面我们使用Dockerfile构建一个Java镜像。具体步骤如下:
- 在用户目录下(备注:C:\Users\Ay)创建dockerfile文件夹,在dockerfile文件夹中创建并编辑Dockerfile文件,同时把之前下载的jdk-11.0.2_linux-x64_bin.tar.gz文件放入dockerfile文件夹中。
- 在Dockerfile文件中添加如下命令,这些命令都是之前学习Dockerfile指令用到的。(注意:将Dockerfile文件与需要添加到容器的文件放在同一个目录下。)
- 使用docker bulid命令读取Dockerfile文件,并构建镜像:
- -t选项:用于指定镜像的名称,并读取当前目录(即.目录)中的Dockerfile文件。
- -f选项:用于指定Dockerfile文件名称。
从输出信息可知,执行docker build命令后,首先构建上下文发送到Docker引擎中,随后通过5个步骤来完成构建镜像的构建工作,在每个步骤中都会输出对应的Dockerfile命令,而且每个步骤都会生成一个“中间容器”与“中间镜像”。例如步骤5:
当执行完命令CMD /opt/jdk-11.0.2/bin/java -version后,将生成一个中间容器,容器ID为9d10dbefcf6c,接着从该容器中创建一个中间镜像,镜像ID为35d1544278ed,最后将中间容器删除。
注意:并不是每个步骤都会生成中间容器,但是每个步骤一定会产生中间镜像。这些中间镜像将加入到缓存中,当某一个构建步骤失败时,将停止整个构建过程,但是中间镜像仍然会存放在缓存中,下次再次构建时,直接从缓存中获取中间镜像,而不会重复执行之前已经构建成功的步骤。
- 查看生成的Docker镜像。
- 至此,我们完成了通过Dockerfile构建镜像。
4. Spring Boot集成Docker
本节我们开始学习如何在Spring Boot中集成Docker,并在构建SpringBoot应用程序时生成Docker镜像。具体步骤如下:
- 创建一个Spring Boot项目,项目名为spring-boot-docker,具体步骤参考1.3节。打开pom.xml文件,修改artifactId和version,具体代码如下:
Spring Boot使用spring-boot-maven-plugin插件构建项目,通过使用mvn package命令打包后将生成一个可直接运行的jar包,jar包默认文件名格式为 p r o j e c t . b u i l d . f i n a l N a m e , 这 是 一 个 M a v e n 属 性 , 相 当 于 {project.build.finalName},这是一个Maven属性,相当于 project.build.finalName,这是一个Maven属性,相当于{project.artifacId}-${project.version}.jar,生成的jar包在/target目录下。根据上面pom文件的配置,执行mvnpackage命令后,生成的jar包名为spring-boot-docker-0.01.jar。
- 在pom.xml文件中加入docker-maven-plugin插件依赖,具体代码如下:
- <imageName>:用于指定Docker镜像的完整名称。其中${docker.image.prefix}为仓库名称,${project.artifactId}为镜像名。
- <dockerDirectory>:用于指定dockerfile文件所在目录。
- <directory>:用于指定需要复制的根目录,其中${project.build.directory}表示target目录。
- <include>:用于指定需要复制的文件,即Maven打包后生成的jar文件。
- 在src/main/docker/目录下创建Dockerfile文件,Dockerfile文件内容如下:
命令执行后,可在控制台上看到相关的输出信息。执行dockerimages命令查看镜像是否成功生成。
- 执行docker run命令启动容器。
容器在启动的时候,会执行Dockerfile文件里的CMD命令:java-jar app.jar,该命令用来启动Spring Boot项目,之后就可以在控制台上看到Spring Boot的启动信息,具体内容如下:
- 至此,Spring Boot成功集成了Docker容器,并把SpringBoot项目打包成Docker镜像。