开源J2EE项目Zheng环境Docker部署

摘要:开源项目Zheng的部署环境十分复杂,虽然官方提到只需要10分钟就可以完成部署,但是涉及到多达6种依赖服务的安装和配置,十分繁琐。所以本文将使用docker对该项目的环境进行配置。目前第一部是将环境Docker化,第二步就是将应用本身也要Docker化。通过Docker基本实现一键部署,无需繁琐配置。下面就开始介绍是如何一步一步将zheng的环境进行Docker化。

项目介绍

基于Spring+SpringMVC+Mybatis分布式敏捷开发系统架构,提供整套公共微服务服务模块:集中权限管理(单点登录)、内容管理、支付中心、用户管理(支持第三方登录)、微信平台、存储系统、配置中心、日志分析、任务和通知等,支持服务治理、监控和追踪,努力为中小型企业打造全方位J2EE企业级开发解决方案。

参考资料

官方项目oschina
官方项目GitHub
开源项目【zheng】环境搭建指南

Docker 技术介绍

Docker 是一种容器技术,并且在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。

Docker 和传统虚拟化方式不同。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。

优势

所以对于复杂的环境的部署使用Docker有得天独厚的优势:

更高效的利用系统资源

由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。

更快速的启动时间

传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。

一致的运行环境

开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。

持续交付和部署

对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。

使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。

而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。

更轻松的迁移

由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

更轻松的维护和扩展

Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

Docker Compose

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。 。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。

通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose 中有两个重要的概念:

服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

主要服务介绍

根据zheng官方指导,我们知道目前系统主要依赖6个服务,分别是 mysqlrediszookeeperactivemqnginxdubbo。我们分别介绍其配置。

概述

我们参考了 JHipster 项目的风格,将不同的服务 compose 配置独立出来,然后在项目配置文件中通过 extends 关键字进行配置。比如我们有xxx-service.yml,然后在项目配置文件中app.yml使用如下语句配置:

1
2
3
4
xxx-service:
extends:
file: xxx-service.yml
service: xxx-service

MySQL

首先我们介绍数据库服务,官方给出了使用 MySQL,并且提供了初始化脚本 zheng.sqlzheng-ucenter.sql。实际上我们的数据服务可以选用MySQLMariaDB,这里使用MySQL的官方镜像,为了保证服务的一致性,我们没有使用 latest,而是使用具体标签5.7.22。如果使用最新的版本8,可能需要解决授权插件的问题,相关参考12

代码: mysql.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '2'
services:
zheng-mysql:
image: mysql:5.7.22
volumes:
- ./data/mysql/conf:/etc/mysql/conf.d
- ./data/mysql/data:/var/lib/mysql
- ./script/zheng.sql:/docker-entrypoint-initdb.d/zheng.sql
- ./script/zheng-ucenter.sql:/docker-entrypoint-initdb.d/zheng-ucenter.sql
environment:
- MYSQL_USER=root
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=zheng
- LANG=C.UTF-8
ports:
- 3306:3306
command: mysqld --skip-ssl --character_set_server=utf8mb4 --sql_mode=""

执行初始化脚本

我们希望在 MySQL 服务启动时自动根据项目提供的脚本创建数据,这里我们使用 MySQL Docker镜像的初始化机制,也就是在 MySQL 启动时,会执行/docker-entrypoint-initdb.d 目录下的sql脚本。所以我们只需要将我们的脚本映射到该目录就可以,所以这里使用了一个 Volume 的配置就是完成这个操作的。

1
2
- ./script/zheng.sql:/docker-entrypoint-initdb.d/zheng.sql
- ./script/zheng-ucenter.sql:/docker-entrypoint-initdb.d/zheng-ucenter.sql

这里需要注意,一开始在导入的时候,总是中文乱码,在反复检查编码确认都为 UTF8 后才通过该 issue 注意到,乱码的产生原来是 Docker 镜像在执行初始化命令的时候的 Terminal 编码配置的问题,因为脚本中有中文,而命令行窗口默认不是 UTF8,所以实际上所执行的脚本本身就是乱码,就不能怪MySQL了。解决办法很简单,指定 Docker 的默认编码方式:

1
- LANG=C.UTF-8

环境变量

除了前面介绍的编码方式,这里环境变量还配置了用户名,密码和初始创建的数据库名称。这里的密码使用的项目默认的密码,这个密码在配置文件中(比如 \zheng\zheng-upms\zheng-upms-rpc-service\src\main\resources\profiles\dev.properties)的datasource.master.jdbc.password=rWd3Hb+AzNg3IXF1b5vD+g==。大家需要注意这个配置是经过AES加密过了,具体加密方式参考该工具类(\zheng\zheng-common\src\main\java\com\zheng\common\util\AESUtil.java),直接运行 main

1
2
3
key | AESEncode | AESDecode
| FNFl9F2O2Skb8yoKM0jhHA== |
123456 | rWd3Hb+AzNg3IXF1b5vD+g== | 123456

也就是说如果配置文件中是 FNFl9F2O2Skb8yoKM0jhHA==,说明配置的就是空字符串。 如果配置文件中是rWd3Hb+AzNg3IXF1b5vD+g==,说明配置就是123456。为了让大家不需要修改项目配置,所以这里一律使用和默认密码相匹配的配置。

  1. MySQL: 123456
  2. redis:空字符串

命令行参数和端口

端口暴露默认端口 3306

命令行执行:

1
mysqld  --skip-ssl --character_set_server=utf8mb4 --sql_mode=""

这里指定了 --sql_mode 表示不对脚本做任何限制。因为在一开始参考其他的配置,将NO_ZERO_DATE配置到了 sql_mode 中,导致出错(ERROR 1067 (42000) at line 517: Invalid default value for 'last_login_time')。因为项目脚本有一个 last_login_time 配置成了0,所以这里不能开启这个选项。参考

单独测试

这样 MySQL 就配置好了,可以单独启动使用工具链接进行测试了,首先确认当前目录为项目的根目录,确保能够找到正确的 yaml 配置文件。

1
2
3
4
# 启动
docker-compose -f docker/mysql.yml up -d
# 结束
docker-compose -f docker/mysql.yml down

由于我们映射了MySQL数据到本地Volume,所以这时候会在 \docker\data\mysql\data 中创建数据。这个数据会一直保留,就是将服务停止,如果想更新数据,只需要删除所有数据在重新启动就可以了。

redis

Redis 的配置相对简单,这里由于官方镜像不支持各种配置,所以我们选用了bitnami/redis,版本为4.0,这是该镜像的优势,主要是可以支持各种配置同时是官方源码自动编译的,品质也是可靠的。

配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
version: '2'
services:
zheng-redis:
image: bitnami/redis:4.0
environment:
# - REDIS_PASSWORD=
- ALLOW_EMPTY_PASSWORD=yes
volumes:
- ./data/redis/data:/bitnami
ports:
- 6379:6379

使用默认端口:6379。

这里只需要一个配置就是,设置为空密码

1
2
# - REDIS_PASSWORD=
- ALLOW_EMPTY_PASSWORD=yes

如果你希望配置不同的密码,使用REDIS_PASSWORD指定密码。

zookeeper

配置简单,使用默认端口:2128

1
2
3
4
5
6
version: '2'
services:
zheng-zookeeper:
image: zookeeper:3.5
ports:
- 2181:2181

activemq

使用了一个第三方的镜像,配置简单

1
2
3
4
5
6
7
8
9
version: '2'
services:
zheng-activemq:
image: webcenter/activemq:5.14.3
volumes:
- ./data/activemq/log:/var/log/activemq
- ./data/activemq/data:/data
ports:
- 8161:8161

nginx

nginx 的配置由于项目中的配置文件应该是给windows使用的,这里进行一定的修改,基本包含了原有的配置,并修改了路径为相对路径。但是这里可能有些问题,主要是前端抽离的项目感觉并不完整,项目打包后也会在 webapp 中生成前端项目,不知道有何区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '2'
services:
zheng-nginx:
image: nginx:1.15
volumes:
# - ./data/nginx/conf/conf.d:/etc/nginx/conf.d:z
# use custom nginx.conf
- ./data/nginx/config/nginx.conf:/etc/nginx/nginx.conf:z
- ./data/nginx/config/servers:/etc/nginx/conf.d:z
- ./data/nginx/logs:/var/log/nginx:z
- ./../zheng-ui:/www/zheng-ui:z
- ./../zheng-cms/zheng-cms-web/src/main/webapp/resources:/www/zheng-cms-web:z
ports:
- 80:80
- 443:443
- 1000:1000
- 1001:1001
- 1002:1002

dubbo

由于 dubbo这个项目不再获取,也没有官方镜像,这里我自己根据官网的源码制作了一个镜像。注意这个服务依赖于zookeeper。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: '2'
services:
zheng-zookeeper:
extends:
file: zookeeper.yml
service: zheng-zookeeper
networks:
zheng-net:
aliases:
- zookeeper
zheng-dubbo:
image: xinmeng/dubbo-admin:2.0.0
environment:
- ZOOKEEPER=zookeeper:2181
- ROOT_PASS=root
- GUEST_PASS=guest
ports:
- 8090:7001
networks:
zheng-net:
aliases:
- dubbo
networks:
zheng-net:

dubbo 的配置包含了指定 zookeeper 的地址,这里使用 docker-compse 的网络别名。

整体项目配置

有了单独的服务后,我们就可以配置整体的环境了,配置十分简单,就是把服务的配置链接到外部的文件即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
version: '2'
services:
zheng-mysql:
extends:
file: mysql.yml
service: zheng-mysql
networks:
zheng-net:
aliases:
- mysql
zheng-redis:
extends:
file: redis.yml
service: zheng-redis
networks:
zheng-net:
aliases:
- redis
zheng-activemq:
extends:
file: activemq.yml
service: zheng-activemq
networks:
zheng-net:
aliases:
- activemq
zheng-nginx:
extends:
file: nginx.yml
service: zheng-nginx
networks:
zheng-net:
aliases:
- nginx
zheng-zookeeper:
extends:
file: zookeeper.yml
service: zheng-zookeeper
networks:
zheng-net:
aliases:
- zookeeper
zheng-dubbo:
image: xinmeng/dubbo-admin:2.0.0
depends_on:
- zheng-zookeeper
environment:
- ZOOKEEPER=zookeeper:2181
- ROOT_PASS=root
- GUEST_PASS=guest
ports:
- 8090:7001
networks:
zheng-net:
aliases:
- dubbo
networks:
zheng-net:

依赖环境服务相关操作

至此整个环境部署完成

实现zheng环境的docker化,通过配置好的docker-compose,一键启动所有依赖服务,并且和目前项目的默认配置匹配,项目无需进行任何更改就可以立即运行。该配置参考JHipster,将不同的服务配置成不同的 yaml 文件,然后在同一的 app.yaml 中引用所有的服务。

整个docker-compose 由6个服务构成

  1. mysql
  2. redis
  3. nginx
  4. zookeeper
  5. activemq
  6. dubbo

单独测试独立服务

确保当前命令行当前路径为项目根路径

1
2
3
4
5
docker-compose -f docker/mysql.yml up -d
docker-compose -f docker/redis.yml up -d
docker-compose -f docker/zookeeper.yml up -d
docker-compose -f docker/activemq.yml up -d
docker-compose -f docker/nginx.yml up -d

测试dubbo

由于 dubbo 依赖于 zookeepr,所以单独有一个 dubbo 的 yaml 用来测试:

1
docker-compose -f docker/dubbo.yml up -d

一键启动所有的服务

1
docker-compose -f docker/app.yml up -d

所有服务启动完毕

一键关闭所有的服务

该操作会保留所有数据,因为数据会被存放在 Volume 中, 注意这些文件不会被 git 管理,请自行管理。

1
docker-compose -f docker/app.yml down

查看指定服务的log

其中 -f 表示 follow,会持续显示最新的log

1
docker-compose -f docker/app.yml logs -f <service_name>

For example:

1
2
docker-compose -f docker/app.yml logs -f zheng-mysql
docker-compose -f docker/app.yml logs -f <service_name>

重新 build 某个服务

如果对某个服务的 yaml 文件进行了更改,可以通过该命令进行重新build

1
docker-compose -f docker/app.yml up -d --no-deps --build <service_name>

For example

1
2
docker-compose -f docker/app.yml up -d --no-deps --build zheng-mysql
docker-compose -f docker/app.yml up -d --no-deps --build zheng-nginx

项目应用运行

分享了5个 Intellij 的执行配置文件,文件在 .idea\runConfigurations 下面,(.gitignore 排除了这些文件)

可以直接点击这些配置运行对应项目,按照要求应该依次启动:

  1. ZhengUpmsRpcServiceApplication.xml
  2. zheng_upms_server.xml
  3. ZhengCmsRpcServiceApplication.xml
  4. zheng_cms_admin.xml
  5. zheng_cms_web.xml

希望这些能够帮助面对复杂配置望而却步的开发者快速体验。