- Q& N7 C( D" `, Y( X6 D+ J DDD的挑战* H( f8 D" o* i' S' c0 H7 e
首先,领域驱动设计需要一定的学习成本,而且学习曲线陡峭,在团队中如果不能接受新的设计理念,那么实施起来一定困难重重。
5 e# e; k4 D: r! ?; h+ H# o 其次,领域驱动设计需要领域专家持续不断地参与项目,而领域专家多来自客户方,客户方能配合和参与项目团队的设计与讨论的领域专家并不多。最后,领域驱动设计与传统的系统设计思路大不相同,项目团队在适应新的思维方式的同时,还需要考虑许多设计活动,如需要投入时间和精力在沟通与建立统一语言等事情上。
" {' X; x7 V; d+ [6 T 不过,尽管DDD会面临诸多挑战,仍然建议学习和尝试这一设计方法,领域驱动设计可以使开发人员表达出更加丰富的业务需求,将软件转换为更加富有业务价值的功能,一旦我们采用了DDD的设计并成功应用后,就会习惯并再也离不开它。 - x, L* `! c0 L' Y
Docker和K8s
8 v/ p' ]) i! d3 D6 O8 O ◎ 虚拟化技术 / ?% a. W" `* e
◎ Docker容器化 9 y0 e% e) ~) d0 r+ O( O
◎ 学习使用Docker , l2 R. z3 c) ? h! E
◎ 容器编排
9 }8 p0 M% o0 _ k4 H; n3 U ◎ 云商的支持
- ?7 [; s% ~( A { i 提到微服务,首先想到的是服务小、职责小,如果是一个庞大复杂的系统,我们必然会建立很多的微服务,而且服务都可以水平扩展。在一些大型的互联网企业,服务的数量可能是成百上千的,如何去部署和管理这些服务成了一个难题,一旦发布新的版本,又该如何去更新?所以,Docker容器化、K8s容器编排等技术逐渐登上了舞台。
7 A4 x* l( j8 Y1 o3 z3 h8 C 下面介绍微服务架构下部署和维护服务的方式。 3 ]& e5 i; K; D: H+ P1 x, y
虚拟化技术
/ a2 l( X1 f7 m8 z 在以往的软件项目中,我们会使用虚拟化的技术来实现服务的部署和发布。虚拟化(Virtualization)是一种资源管理技术,将计算机的各种实体资源,如CPU、网络、内存及硬盘空间等,予以抽象、转换后呈现出来并可供分割、组合为一个或多个计算机配置环境,使用户可以通过比原本的组态更好的方式来应用这些资源。这些资源的新虚拟部分不受现有资源的架设方式、地域或物理组态所限制。一般所指的虚拟化资源包括计算能力和资料存储,虚拟技术按抽象层次可以分为5个层次:硬件抽象层次、指令集架构抽象层次、操作系统抽象层次、库抽象层次、应用抽象层次。
: ?$ A4 u- } S" `' M6 y
) R( y' I* h5 n( W" C 抽象程度由硬件到应用逐渐递增,通常我们将计算机硬件虚拟分割成一个或多个虚拟机(Virtual Machine,VM),然后将服务和页面都部署在各个虚拟机上,并提供多用户对大型计算机的同时交互、访问,可以算作硬件抽象层次,通过纵向地扩展虚拟机的配置,或者横向地扩展虚拟机的个数,更加容易增加系统的负载量。
$ j+ O& f/ @; |8 O- |/ T; O 但传统的虚拟化技术正在遭受到重大挑战,随着微服务的兴起,在笨重的虚拟机上部署应用无法满足我们的需求。笔者曾有一个微服务项目,这是一个庞大且运营了很多年的产品,要完整地运行一个项目需要上百个不同的服务。一般需要20~30个虚拟机,每次新部署一套产品上线,需要花费大量的人力进行虚拟机配置、网络调试、基础环境搭建。例如,数据库的安装部署、服务配置和部署、系统测试等工作,加上虚拟机的调试和启动速度不够理想,每次完整地部署这套产品,哪怕只是测试环境,都需要两周,如果换一个不熟悉系统的人,完成一次新产品的部署工作几乎不可能。 # j' P) w) M& {/ V6 g
6 f+ S3 e4 T3 p$ Y 如今网络信息化技术飞速发展,市场对企业响应速度的要求也越来越高,很多时候一旦慢人一步,很可能带来无法挽回的失败。那么,有没有一项技术可以替代虚拟机技术,加快响应速度,让应用更加便捷、快速部署呢? 3 |. A/ m. r( t" |6 H
容器化技术诞生了,Docker是当前最流行的一款容器技术,从原理上,Docker并没有采用与虚拟机一样的虚拟化技术,并不会对硬件进行虚拟化。它是直接基于Linux的内核,对文件系统、网络、进程等进行封装和隔离,由硬件虚拟化上升到了操作系统抽象层次的虚拟化技术,所以Docker更加轻量、快速、便捷。
& v" L% N! A0 l! J9 i+ a Docker容器化1 t3 P8 G( F$ y6 j. Q/ n. @
在软件技术与架构飞速发展时,业界逐渐认识到虚拟机技术既浪费资源,又无法满足业务上的需求,因此容器化技术诞生了,并迅速流行起来。什么是容器化?它与传统的虚拟化相比有什么优势呢?
o- i) Z+ ?; w3 d! l* p T2 W Docker的概念 , u2 [8 S h- T$ ]
Docker是目前最流行的容器化的软件和平台,可以在如macOS、Windows和Linux等环境中运行,借用官网上的一句话,Docker容器化解锁你的开发和运维的潜力。Docker可以将软件打包成标准化单元:容器,用于开发、迁移和部署。Docker通过创建简单的工具和通用打包方法,将容器内的所有应用程序依赖关系捆绑在一起,并使容器化应用程序能够在任何基础架构上一致地运行,为开发人员和运营团队解决依赖性问题,减少了“我的电脑是好的呀”声音出现。
- v2 }& o6 ` M2 B. b% d
; {( M3 W; R* f6 q9 O! F 那么,Docker是如何做到的?由前面章节我们知道,Docker是一款容器技术,是存在着系统抽象层次的虚拟化技术。我们可以把Docker的容器想象成一个集装箱,在项目中拥有多个不同的服务或应用程序,技术栈(如开发语言、框架、数据库等)都不相同,但可以将这些服务或应用程序打包到集装箱中,每个集装箱都用相同的方式运行、存储和运输。例如,在没有使用容器时,要运行或部署一个Java Web的应用程序,首先需要安装JDK,然后安装Web服务器,如Tomcat,接着安装数据库,最后需要将Java程序打包,部署到Tomcat中运行起来,这是相当烦琐且容易出错的过程。 9 u$ {" l% x, u/ [- k* S
2 L' y0 {; W4 m( {9 p1 c 在不同环境中,JDK的版本、Tomcat的版本及数据库的配置等都可能导致程序的运行结果出现差异,当问题发生时,我们很难快速定位是环境问题还是程序问题,这也是为什么程序员都喜欢说“我的电脑是好的呀”。毕竟程序运的环境往往比较复杂,需要很多依赖配置,而程序员的本地环境和程序正式的运的环境差异较大。而Docker可以将这些繁琐的步骤自动化,我们将JDK、Tomcat,甚至是数据库都打包在一起运行,无论是什么环境,我们只需将打包的集装箱进行迁移即可,每个环境运行的程序都完全相同,从而屏蔽复杂的操作和环境的差异。 * D, r$ {% x' y" Q/ Q4 w
每个集装箱都提供统一的接口给外部调用者使用,不用的程序都标准化管理起来,并且屏蔽了差异化,为开发和运维提供便利。例如,有的程序需要启动Tomcat,或者启动MySQL,或者启动Nginx,那么作为运维人员,要根据不同的技术或工具编写不同的操作脚本,运行startup,或者运行start,不同的程序指令层出不穷,这在大规模环境部署时非常痛苦,需要反复修改脚本文件,但我们需要的操作都有规律,如启动、停止、重启和日志等,Docker就提供一系列的操作接口,运维人员只需操作集装箱即可,不需要关注集装箱内部的运行细节。
) ]& Z, }% j+ V; a. f - P% C' ?5 [; u! g, y, k2 y
最后,集装箱还有隔离的特性,每个集装箱都相互独立,集装箱内部的运行互不影响。例如,我们可以在一个容器内使用JDK1.8,同时在另一个容器内使用JDK1.7,两个容器相互独立,这也更加契合微服务的思想。 5 l& G/ J' [# U- Q( z8 w0 J+ W
Docker提供社区版和企业版两个版本,社区版永久免费,并且内核与企业版完全相同,企业版将容器技术扩展到容器平台,提供企业级容器平台,提供安全和治理等企业级更高级的功能实现。社区版目前完全可以运用于生产环境,不过在大规模的场景下还需要一些(如Kubernetes等)容器编排系统来配合使用。
! E' g5 [: z+ G. S; h4 A' { 容器的概念
' ]) A$ n! d c2 s6 T# V3 k& R 在介绍Docker时可以看出,容器是Docker的核心技术,那么什么是容器?容器是一个标准的软件单元,它将代码及其所有依赖关系打包,以便应用程序从一个计算环境快速可靠地运行到另一个计算环境。Docker通过容器镜像(Docker Image)将包含运行应用程序所需的一切:代码、运行时、系统工具、系统库和设置,构建成一个轻量级、可独立执行的软件包,而容器镜像在运行时成为容器,无论基础架构如何,容器化软件都将始终运行在相同的配置环境中,容器将软件与其环境隔离开来,并确保它可以统一工作。容器和虚拟机具有类似的资源隔离和分配优势,但功能不同,因为容器是虚拟化操作系统而不是硬件,并且容器更便携、高效。容器的运行依赖于容器runtime,containerd是一个行业标准的容器runtime,利用了runc,创建时强调简单性、健壮性和便携性。而containerd也是Docker Engine的核心容器运行时。 0 P* Q1 ]% [" i# O+ d6 s& v
图8.1所示为Docker Engine的结构,Docker Engine主要包括Server、REST API和Client 3个部分。Server是一种长时间运行的程序,称为Docker守护进程;REST API则可以用来与守护进程通信并指示它做什么接口;Client是一个命令行接口(CLI)的客户端,CLI使用Docker REST API通过脚本或直接CLI命令控制Docker守护进程或与之交互,Docker对象包括图像(Image)、容器(Container)、网络(Network)和volumes。在Docker Engine上运行的Docker容器拥有以下3个特性。 ) }4 a+ k3 h8 j# q
. M# X# I# f* x
(1)标准:Docker为容器创建了行业标准,因此它们可以随处携带。
" \- e. Q7 \2 { (2)轻量级:容器共享机器的操作系统内核,因此不需要每个应用程序的操作系统,从而提高服务器效率并降低服务器和许可成本。 ' n) m( o: L: o5 E; u' n
(3)安全:应用程序在容器中更安全,Docker提供业界最强大的默认隔离功能。 + h- s0 r e4 o5 y. p @8 J) ?" o
Docker使用客户端-服务器架构。Docker客户端与Docker服务器(守护进程)进行通信,服务器负责构建、运行和分发Docker容器。Docker客户端和服务器可以在同一系统上运行,也可以将Docker客户端连接到远程Docker服务器。Docker客户端和服务器使用REST API,并通过UNIX Socket或网络接口进行通信。同时,Docker还提供了镜像仓库的机制,方便我们将构建好的镜像存储在镜像仓库中,快速地进行传输和部署,如图8.2所示。
- [7 A m% Z4 @$ ^
; S9 K' _- x" i. G( q8 l) R 学习使用Docker) A3 M3 q! [& c/ B& T
使用容器可以更快地构建和部署新应用程序,Docker容器将软件及其依赖关系整合到一个标准化的软件开发单元中,包括运行所需的一切:代码、运行时、系统工具和库。这可以保证应用程序始终运行在相同的环境下,并使协作变得像共享容器映像一样简单。 B a2 I5 Q) M" y: ^. ~
下面为大家介绍Docker具体的使用方法。
3 Q$ S6 G* V6 m/ t5 Y Docker的安装方法 , e* G1 P5 ]+ e2 O2 W1 F
Docker的安装十分简单,且对各平台都很友好。在macOS和Windows中都有相应的安装包,可以一键安装。Docker有社区版和企业版两个版本,下面以社区版为例来介绍Docker具体的使用方式。 - q3 u7 m" e# }8 C+ y* X
首先,需要下载Docker的安装文件,可以在Docker Hub上找到各自平台的安装包 。以 macOS 为 例,双击下载的安装文件 : 9 q& W. u7 O: `. n6 k( ~3 o
Docker.dmg ( Windows 版 本 的 文 件 名 为 Docker for Windows Installer.exe ) , 然 后 在 出 现 的 窗 口 中 将 Docker.app 拖入Applications文件夹即可,如图8.3所示。 5 ~/ s* J% i1 B4 h
& w& \9 o" l) f F# ~- `6 c* k8 E2 U
安装好后可以通过双击Docker.app来启动Docker。默认随系统一起启动,然后在快速访问工具栏中看见Docker的图标,单击Docker的图标可以打开快捷菜单,如图8.4所示。 ) X4 A( B% ^$ i5 V
7 o% G9 W6 j' a3 d 图 8.4 中 第一行会显示 Docker 的状态, “Docker Desktopisrunning”表示Docker目前正在运行,打开命令行工具输入如下指令。 ; j- Q/ y7 J9 ~2 W8 y
docker -v 0 m) \5 Z% k! Y# e4 Y" i" W, C0 a
如果安装成功,就会得到如下结果。 8 f! O4 _0 l- c
Docker version 18.09.0, build 4d60db4
, w, Z. h6 d. g+ |% B$ \4 {& @ 如果想要查看Docker完整的版本信息,可以输入以下指令。
' f; z; v( l8 D2 ]& X( e docker version 9 ]: P4 C I4 M7 i5 r
可以得到具体的Docker的Client和Server的详细信息,结果如下。 # P& B6 E/ ^$ ~6 R4 Z) X4 r
& G7 J) M, B1 `) W. W b$ h0 ^! P 详细的安装教程可以在Docker的官网上找到。
4 u9 O" h1 O( L; m* L. o! j 构建Docker镜像 h/ E) U( ~6 K- K0 S: j, t
如果说容器是Docker的核心,镜像就是核心的基础,正如之前所提到的,镜像在Docker Engine上运行时成为容器,无论基础架构如何,容器化软件都将始终运行在相同的环境中,容器将软件与其环境隔离开来,并确保它可以统一的工作,镜像是根文件系统更改的有序集合,它通常包含堆叠在彼此顶部的分层文件系统的并集,没有状态,而且永远不会改变。 ; Y* k* ]" g/ p, _( U/ I, ]
如何使用Docker镜像?一般在项目中,我们会搭建私有的Docker镜像仓库,或者使用云厂商提供的镜像仓库来存储项目中构建的镜像。当然,Docker也提供了公共的镜像仓库,可以把镜像仓库理解为我们在使用Gradle或Maven时的repository,这里使用Docker默认公网的镜像仓库来体验一下Docker镜像的用法。
( q$ T$ u# x, I9 p4 ]+ L4 n& ~ Docker官方提供了一个简单的镜像来让我们体验,镜像名为hello-world,打开命令行工具,输入如下指令,从镜像仓库拉取hello-world的镜像。
2 g. W5 G* V+ E8 A docker pull hello-world 6 s3 {& P/ O9 b7 X3 m
可以看到如下运行结果。
) n7 [, j* J; ^ b+ O3 D2 N
3 H; k/ z( `8 Q# c 通过运行的输出结果可以看出,这里下载了一个新的镜像helloworld:latest,Docker的镜像有tag的概念,tag就好比镜像的版本号,通过指定具体的tag来下载相应版本。例如,我们想要指定下载镜像a的v1.0.0版本,输入“docker pull a:v1.0.0”。这里拉取的hello-world并没有指定具体 的tag,Docker使用默认 的“tag:latest”的镜像进行下载。下载完成后,我们可以通过如下指令查询已经下载的镜像。 + s% n% s6 J' l6 H8 w# {9 Z0 w" V
docker images
1 [7 G) h; s8 P- I) X7 W3 s 显示结果如下。
/ ?: [4 K4 q2 u& Z
. [3 r5 x5 S7 F4 i3 L 下载完成后,我们可以通过docker run来运行镜像,指令如下。 % n' _2 Z) |# x5 L1 N7 a
docker run hello-world , X& I7 J2 R6 r7 w' E4 V
如果显示结果如下,说明已经成功运行了一个Docker镜像。
4 b( ]7 r! R2 i / b' W! {! [5 c: x! L+ X
当然,镜像也可以删除,指令如下。
7 @' F8 c; g, h' n docker rmi hello-world % Y1 x! i! W6 N1 P; k, \- i
得到结果如下。 ' J( @3 y3 c% @6 }2 Q
Error response from daemon: conflict: unable to remove repository reference "hello-world"(must force)- container f8064c37e60d is using its referenced image fce289c99cb9
& }2 w. C' U: p- a a 以上代码说明镜像有引用它的容器存在,还不能删除,并且告诉了我们容器的ID,所以可以先删除容器,指令如下。 % \3 c& }, t, {; |: ]# u
docker rm f8064e37e60d ) @$ A$ G3 r) }# o+ \' J
成功删除容器后,再执行删除镜像的指令,得到结果如下。
) n( }3 ~; E- s: I + H( ]# Z! U, e) e) `
说明镜像已经成功被删除,那么一个镜像又是如何产生的?
, w, P7 q3 {- |( x Docker通过从Dockerfile的方式来定义所有命令,在构建镜像时从文本文件(Dockerfile)中读取指令来自动构建镜像Dockerfile遵循特定的格式和指令集,每层都代表一个Dockerfile指令,这些层是堆叠的,每层都是前一层变化的增量,且会按顺序构建给定镜像。下面是一个Dockerfile的例子。 & Q9 m% { n( X# e
9 r$ e2 {1 J- B+ B, f. S4 `
这里解释得比较模糊,下面以一个真实的项目为例来构建并运行一个镜像。例如,一个Spring Boot的工程项目,我们快速创建一个Spring Boot的Web,然后添加一个hello world的接口,代码如下。 ) R% n7 Y5 P. T7 }* ` k
$ s! Z W$ |# v$ ?- \
由上述代码可知,这里提供了一个接口,URL是“/hello”,然后返回“World”的字符串,如果我们使用的是Gradle,编译之后在项目根目录的build/libs文件夹下产生一个jar包,即项目本身的最终产物,包名为 docker-test-webapp-0.0.1-SNAPSHOT.jar,然后通过Java指令运行这个jar包,指令如下。1 _% a2 m1 `# P# ~) }; K
) \3 U8 ~) F# o) V4 o; E+ a java -jar build/libs/docker-test-webapp-0.0.1-SNAPSHOT.jar ! r) }/ _9 A; B3 |. e
我们可以将指令写在一个Shell文件中,在项目根目录创建名为run.sh的文件,并添加启动指令,这里稍作修改,让指令可以估计指定名称的关键字自动寻找jar包,内容如下。
' B9 O V$ l, y2 m' y- y 3 w/ }; D8 K2 r7 Q) o' P
需要给run.sh配置权限,指令如下。 ( T9 [3 W# U3 y. z3 a
chmod 755 run.sh 3 d# M% g- }$ R! {9 \" j( X
接下来就可以编写Dockerfile文件,首先来分析一下,我们的目的很简单,就是要将项目构建成镜像并运行,传统的方式会使用shell脚本来运行编译好的jar包,运行jar包需要在Java环境下执行,也就是jre或jdk;其次需要shell脚本来运行jar包,所以镜像中还应该包含shell脚本和jar包两个文件;最后需要在容器运行时执行shell脚本,接下来在项目的根目录下新建一个名为Dockerfile的文件,并添加如下内容。 , J! p- k E6 E+ X. u
% \1 U8 y' ^( v+ _! Y8 o+ H 指令的意思很明显,首先FROM来构建基础镜像,也就是以openjdk的镜像作为基础镜像,这样就有了jdk的运行环境,然后在COPY,复制我们的jar包和shell脚本到指定的目录,这里是/app目录,最后通过CMD指令,即在容器运行时运行shell脚本run.sh。
. a, J. }7 i) h3 C2 v 构建之前还需要运行Gradle的编译指令生成jar包,指令如下。
0 X+ p6 E6 c4 _4 D0 o1 @3 G0 z -./gradlew clean build ; J. F1 R) F) a* I
下面来试一下,运行如下指令来构建镜像。 : A2 o h/ y" `6 x
docker build -t docker-test-webapp:001 . 2 s! h) R4 J5 n, U5 C# A
“.”表示从当前目录读取Dockerfile文件来构建镜像,设置镜像的名称为docker-test webapp,tag是001,如果得到结果如下就说明构建成功了。 2 f% v7 Z" W( e1 h: j! j
! A1 l. B' o% ~ \1 x \+ v
通过输出可以清楚地看出这里按照顺序执行了4步操作,与我们定义在Dockerfile中的一致,然后通过之前提过的docker image指令来查看本地镜像,结果如下。 4 K7 s1 i( `1 U
- j4 s0 V4 q; e0 D% L {2 L+ j% v
可以看到,在本地的镜像仓库中,已经存在了一个名为dockertest-webapp且tag为001的镜像,当然,在构建镜像之前不要忘记执行“./gradlew clean build”来构建好jar包,不然在构建镜像时会报错“找不到文件”。 : ?3 T7 U+ a8 o4 r( c
这里只是列举了一些常用的指令来帮助大家理解Docker镜像的用法,详细的Dockerfile的指令还有很多。例如,通过ENV来设置环境变量,通过EXPOSE来设置容器在运行时侦听指定的网络端口,通过ADD指令复制文件、目录或远程文件,并将它们添加到镜像的文件系统中。此外,还有ENTRYPOINT、VOLUME和USER等指令,具体用法由于篇幅关系不再列举,可以在Docker的官网上找到详细的介绍。 : a0 a8 W9 `' Y1 X/ p: |
本文给大家讲解的内容是DDD的挑战;下篇文章给大家讲解的是运行Docker容器觉得文章不错的朋友可以转发此文关注小编;感谢大家的支持!
; B8 @ `- y# z! O' P8 i ^$ j4 [: Z7 E, W4 n
; H8 X+ A, }" }* R p. N0 [ V
|