在前段时间,学习了有关 Docker 部署各类应用的知识,可以明确感觉到,虽然 Docker 解决了环境不一致的问题,并且大大提升了部署速度,但还是会有太多重复性的操作;比如最近做的项目环境都很类似,需要依赖 Redis,MySQL 等等服务,这时每个环境部署的时候,都得拉取镜像,启动镜像等系列操作,当然对于之前每个环境都要从头配置已经快太多,但是作为程序员,能省时间的地方绝不含糊,所以将目光投向了 docker-compose(服务编排工具)。
GitHub 地址:https://github.com/intomylife/Docker
作为最小单位,一个容器代表着一个应用。
每个服务中定义了容器描述、端口映射、配置映射以及依赖关系;一个服务可以实例出多个容器。
每个工程中包含了多个服务。
注:在本次部署中,有一个工程,包括四个服务:SpringBoot、Redis、MySQL 以及 Nginx,其中 SpringBoot 服务被实例两个
此次搭建只整合了 Redis 和 MySQL,如果对具体整合过程感兴趣,可以前往:
需要注意的地方如下
# 数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.host=127.0.0.1
spring.datasource.url=jdbc:mysql://${spring.datasource.host}:3306/base_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=PRC&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
# 打印 sql 日志
logging.level.com.zwc.base.mapper=debug
# Redis 配置
## Redis 数据库索引:默认为 0。Redis 一共有 16 个数据库,索引分别为 0-15。从 Redis 客户端也可以看出,从 db0 ~ db15。
spring.redis.database=2
## Redis 服务器地址
spring.redis.host=127.0.0.1
## Redis 服务器端口
spring.redis.port=6379
## Redis 服务器密码
spring.redis.password=123456789
注:这里专门把 host 都配置为本地 127.0.0.1 环境,因为在使用 docker-compose 服务编排时会用代码主动做处理,这时一套配置文件就足够了。
代码片段
/*
* @ClassName BaseTableService
* @Desc TODO 统计访问次数
* @Date 2019/9/16 14:42
* @Version 1.0
*/
@Transactional
public String comeCounts(String sessionId) {
// 返回数量
Integer resultCount = 1;
// 先从缓存中取访问次数
Object redisComeCounts = redisClient.get(BaseServiceConstant.COME_COUNTS);
// 取出所有 sessionId
List<BaseTable> sessionIdList = new ArrayList<>();
// 访问次数 - 非空判断
if(redisComeCounts != null) {
// 取出所有 sessionId
sessionIdList = JSON.parseObject(redisComeCounts.toString(), new TypeReference<List<BaseTable>>() {});
// 数据库中的数量
Integer mysqlCount = super.count(new QueryWrapper<BaseTable>());
// 计算出返回数量:mysql + redis
resultCount = mysqlCount + sessionIdList.size();
// 判断是否该同步到数据库
if(sessionIdList.size() >= BaseServiceConstant.MAX_COUNTS) {
// 同步到数据库中
if(save(sessionIdList)) {
// 同步成功,清空缓存
sessionIdList = new ArrayList<>();
redisClient.delete(BaseServiceConstant.COME_COUNTS);
}
}
}
// 存入对象
BaseTable baseTable = new BaseTable();
// sessionId
baseTable.setSessionId(sessionId);
// 添加时间
baseTable.setCreateDatetime(new Date());
// 放入集合中
sessionIdList.add(baseTable);
// 存入缓存中
redisClient.set(BaseServiceConstant.COME_COUNTS, JSON.toJSONString(sessionIdList));
try {
// 返回数量
return "ip:" + InetAddress.getLocalHost().getHostAddress() + " / count:" + String.valueOf(resultCount) + " / time:" + sdf.format(new Date());
} catch (UnknownHostException e) {
e.printStackTrace();
// 返回
return "获取地址失败";
}
}
/*
* @ClassName BaseTableService
* @Desc TODO 批量存入数据到数据库
* @Date 2019/9/16 14:49
* @Version 1.0
*/
public boolean save(List<BaseTable> baseTables) {
// 调用批量新增方法
return super.saveBatch(baseTables);
}
主要做的事情及细节有:
① 统计访问次数,同时把 Redis 和 MySQL 都强行用上;在 Redis 中写入了足够的访问次数对象的数量后,会同步到 MySQL,也就是降低了 MySQL 的写 IO 操作。
② 显示了当前主机的 ip 地址,因为后面会把 SpringBoot 服务实例出多个容器,并使用 Nginx 做负载均衡。
③ 显示当前时间,主要是处理了容器时区问题。
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD app.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
注:此次 SpringBoot 项目中没有配置 Docker 插件,也仅仅就是打成了 jar 包,这时就需要一个 Dockerfile 文件来构建镜像
此文件的默认名称为 docker-compose,后缀名可以为 .yml 也可以为 .yaml。
这个 docker-compose.yaml 文件对应的就是上面所说的工程。
version: '3'
构建文件的语法版本信息。version: '3' 表示使用第三代语法。
version: '3'
services:
service_redis:
...
service_mysql:
...
service_springboot:
...
service_nginx:
...
包含此工程中所有的服务列表。
服务可以是已存在的镜像(本地或远程),也可以是构建出来的镜像;如果其中有需要构建的镜像,则需要一个 Dockerfile 文件,正如此次部署我们需要构建 Springboot 镜像。
service_redis:
container_name: container_redis
image: redis:4.0.14
environment:
- TZ=Asia/Shanghai
ports:
- "6379:6379"
volumes:
- ./config/redis/redis.conf:/usr/local/etc/redis/redis.conf
- ./data/redis/:/data/
- ./log/redis/:/var/log/redis/
command: redis-server /usr/local/etc/redis/redis.conf
restart: always
Redis 服务描述:
service_redis:服务名称,可自定义。
container_name:容器名称,可自定义;也可不写,那会自动生成,生成规则为 【docker-compose.yaml 文件的父目录名称 + _ + 服务名称 + 从一开始的数字】。
image:指定镜像来启动容器。此处指定为 Redis 官方镜像,版本为 4.0.14。
environment:为启动的容器添加环境变量。此处配置了容器的时区。
ports:端口映射,映射规则为 宿主机端口:容器端口。此处映射 宿主机 6379 端口到 容器 6379 端口。
volumes:配置映射,映射规则为 宿主机:容器,可以映射文件或目录。此处映射了 配置文件,数据目录以及日志目录。
command:容器启动后执行的命令。此处命令为 使用配置文件来启动 Redis 容器。
restart:赋固定值 always,表示如果容器启动失败,会一直尝试重连。
注:redis.conf 配置文件中修改了如下几点
① daemonize no:前台启动,在 Docker 中后台启动 Redis 容器会报错
② requirepass 123456789:设置密码
③ # bind 127.0.0.1:注释掉了,使外网可访问
service_mysql:
container_name: container_mysql
image: mysql:5.7
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123456
MYSQL_ROOT_HOST: '%'
ports:
- "3306:3306"
volumes:
- ./config/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- ./data/mysql/:/var/lib/mysql/
- ./data/init/:/docker-entrypoint-initdb.d/
- ./log/mysql/:/var/log/mysql/
command: [
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci'
]
restart: always
MySQL 服务描述:
service_mysql:服务名称,可自定义。
container_name:容器名称,可自定义;也可不写,那会自动生成,生成规则为 【docker-compose.yaml 文件的父目录名称 + _ + 服务名称 + 从一开始的数字】。
image:指定镜像来启动容器。此处指定为 MySQL 官方镜像,版本为 5.7。
environment:为启动的容器添加环境变量。此处配置了容器的时区,以及数据库 ROOT 密码和权限。
ports:端口映射,映射规则为 宿主机端口:容器端口。此处映射 宿主机 3306 端口到 容器 3306 端口。
volumes:配置映射,映射规则为 宿主机:容器,可以映射文件或目录。此处映射了 配置文件,数据目录,初始化 SQL 目录以及日志目录。
command:容器启动后执行的命令。此处命令为 设置字符编码。
restart:赋固定值 always,表示如果容器启动失败,会一直尝试重连。
注:my.cnf 配置文件中有一个需要注意的地方如下
# 开启 bin-log,并指定文件目录和文件名前缀
log-bin=/var/log/mysql/binlog
此处配置了 bin-log 文件目录,则 volumes 中映射的 - ./log/mysql/:/var/log/mysql/ 目录才会有数据。
service_springboot:
build:
context: .
dockerfile: Dockerfile
environment:
TZ: Asia/Shanghai
spring.datasource.host: service_mysql
spring.redis.host: service_redis
expose:
- "8080"
depends_on:
- service_redis
- service_mysql
restart: always
SpringBoot 服务描述:
service_springboot:服务名称,可自定义。
此处没有指定容器名称,是因为在启动工程时,会加上 scale 参数,此服务会被实例出多个容器,如果指定了容器名称,则会报错。这时容器名称会自动生成,生成规则为 【docker-compose.yaml 文件的父目录名称 + _ + 服务名称 + 从一开始的数字】。
build:指定 Dockerfile 文件位置,构建镜像,并使用这个镜像来启动容器。
environment:为启动的容器添加环境变量。此处配置了容器的时区;并指定了 MySQL 的 host 为 service_mysql 服务,Redis 的 host 为 service_redis 服务,这两个 host 正是对应 SpringBoot 项目的配置文件(application.properties)中两个 host;这也是上面提到的 用代码主动做处理。(注:由于 MySQL 服务和 Redis 服务都只有一个,所有这里指定服务名和容器名都是可以的)
expose:暴露容器内端口,不映射到宿主机。因为 SpringBoot 服务会被 Nginx 做代理转发,所以不用暴露并映射到外部。
depends_on:依赖服务。在整个工程启动时,会先启动依赖服务,再启动当前服务。也就是说,这里 SpringBoot 服务会等待 MySQL 服务和 Redis 服务启动完成后,才会开始启动。
restart:赋固定值 always,表示如果容器启动失败,会一直尝试重连。
service_nginx:
container_name: container_nginx
image: nginx:1.8
environment:
- TZ=Asia/Shanghai
ports:
- "8000:8000"
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./data/nginx/:/usr/share/nginx/html/
- ./log/nginx/:/var/log/nginx/
depends_on:
- service_springboot
restart: always
Nginx 服务描述:
service_nginx:服务名称,可自定义。
container_name:容器名称,可自定义;也可不写,那会自动生成,生成规则为 【docker-compose.yaml 文件的父目录名称 + _ + 服务名称 + 从一开始的数字】。
image:指定镜像来启动容器。此处指定为 Nginx 官方镜像,版本为 1.8。
environment:为启动的容器添加环境变量。此处配置了容器的时区。
ports:端口映射,映射规则为 宿主机端口:容器端口。此处映射 宿主机 8000 端口到 容器 8000 端口。
volumes:配置映射,映射规则为 宿主机:容器,可以映射文件或目录。此处映射了 配置文件,数据目录以及日志目录。
depends_on:依赖服务。在整个工程启动时,会先启动依赖服务,再启动当前服务。也就是说,这里 Nginx 服务会等待 SpringBoot 服务启动完成后,才会开始启动。
restart:赋固定值 always,表示如果容器启动失败,会一直尝试重连。
注:nginx.conf 配置文件中需要注意如下地方
upstream dispense {
server docker-compose_service_springboot_1:8080 weight=1;
server docker-compose_service_springboot_2:8080 weight=2;
}
① 访问 Nginx 服务会转发到 SpringBoot 服务。
② SpringBoot 容器名称是自动生成的,生成规则为 【docker-compose.yaml 文件的父目录名称 + _ + 服务名称 + 从一开始的数字】。
③ SpringBoot 容器端口都是 8080。
④ 访问比例为 1:2。
① 文件具体在 build 目录,可直接下载
② 把 build 目录拷贝到服务器上
③ 进入目录
[root@zwc docker-compose]# ls -all
total 34932
drwxrwxr-x 5 root root 4096 Oct 17 13:42 .
drwxr-xr-x 9 root root 4096 Sep 17 16:05 ..
-rw-rw-r-- 1 root root 35740202 Oct 16 14:27 app.jar
drwxrwxr-x 5 root root 4096 Sep 17 14:09 config
drwxrwxr-x 6 root root 4096 Oct 16 11:56 data
-rw-rw-r-- 1 root root 1972 Oct 17 13:40 docker-compose.yaml
-rw-rw-r-- 1 root root 140 Sep 17 14:13 Dockerfile
drwxrwxrwx 5 root root 4096 Sep 18 17:37 log
[root@zwc docker-compose]#
④ 日志目录需要赋值权限:chmod -R 777 log/
---
启动 Docker
---
[root@zwc docker-compose]# systemctl start docker.service
---
查看当前目录
---
[root@zwc docker-compose]# ls -all
total 34932
drwxrwxr-x 5 root root 4096 Oct 17 13:42 .
drwxr-xr-x 9 root root 4096 Sep 17 16:05 ..
-rw-rw-r-- 1 root root 35740202 Oct 16 14:27 app.jar
drwxrwxr-x 5 root root 4096 Sep 17 14:09 config
drwxrwxr-x 6 root root 4096 Oct 16 11:56 data
-rw-rw-r-- 1 root root 1972 Oct 17 13:40 docker-compose.yaml
-rw-rw-r-- 1 root root 140 Sep 17 14:13 Dockerfile
drwxrwxrwx 5 root root 4096 Sep 18 17:37 log
---
使用 docker-compose 构建并启动工程
docker-compose up:最基本的启动命令
-d:后台启动
--scale:扩展服务节点,格式为 服务名=个数
---
[root@zwc docker-compose]# docker-compose up -d --scale service_springboot=2
Creating network "docker-compose_default" with the default driver
Building service_springboot
Step 1/4 : FROM openjdk:8-jdk-alpine
---> a3562aa0b991
Step 2/4 : VOLUME /tmp
---> Using cache
---> ceb1101d8851
Step 3/4 : ADD app.jar app.jar
---> e76f5dac4da4
Step 4/4 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
---> Running in ebfeefb03ef8
Removing intermediate container ebfeefb03ef8
---> 566f79654ab6
Successfully built 566f79654ab6
Successfully tagged docker-compose_service_springboot:latest
WARNING: Image for service service_springboot was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating container_mysql ... done
Creating container_redis ... done
Creating docker-compose_service_springboot_1 ... done
Creating docker-compose_service_springboot_2 ... done
Creating container_nginx ... done
---
查看容器启动情况
---
[root@zwc docker-compose]# docker-compose ps -a
Name Command State Ports
----------------------------------------------------------------------------------------------------------------------
docker-compose_service_springboot_1 java -Djava.security.egd=f ... Up 8080/tcp
container_redis docker-entrypoint.sh redis ... Up 0.0.0.0:6379->6379/tcp
container_mysql docker-entrypoint.sh --cha ... Up 0.0.0.0:3306->3306/tcp, 33060/tcp
container_nginx nginx -g daemon off; Up 443/tcp, 80/tcp, 0.0.0.0:8000->8000/tcp
docker-compose_service_springboot_2 java -Djava.security.egd=f ... Up 8080/tcp
---
查看镜像构建情况
---
[root@zwc docker-compose]# docker-compose images
Container Repository Tag Image Id Size
---------------------------------------------------------------------------------------------------------
container_mysql mysql 5.7 a1aa4f76fab9 356 MB
container_nginx nginx 1.8 0d493297b409 127 MB
container_redis redis 4.0.14 720768125f4f 79.6 MB
docker-compose_service_springboot_1 docker-compose_service_springboot latest 566f79654ab6 134 MB
docker-compose_service_springboot_2 docker-compose_service_springboot latest 566f79654ab6 134 MB
在浏览器中输入服务器 ip:8000,可看到 'ip:172.25.0.4 / count:16 / time:2019-10-17 15:50:31' 信息。
如果多次访问,可以发现:
① 一共会出现两个不同的 ip,并且比例为 1:2
② count 数会一直累计
③ time 为当前时间
在浏览器中输入服务器 ip:8000/index.html,可看到 'just do it'。
在 Navicat 中新建 MySQL 连接:
连接成功后,进入到 base_db 库中打开 base_table 表,如果访问超过五次了,这张表里就会有记录的数据了。
在 rdm 中新建连接:
连接成功后,进入到 db_2 库中查看 come_counts key。
---
停止并删除
---
[root@zwc docker-compose]# docker-compose down
Stopping container_nginx ... done
Stopping docker-compose_service_springboot_1 ... done
Stopping docker-compose_service_springboot_2 ... done
Stopping container_mysql ... done
Stopping container_redis ... done
Removing container_nginx ... done
Removing docker-compose_service_springboot_1 ... done
Removing docker-compose_service_springboot_2 ... done
Removing container_mysql ... done
Removing container_redis ... done
Removing network docker-compose_default
---
查看容器情况
---
[root@zwc docker-compose]# docker-compose ps -a
Name Command State Ports
------------------------------
---
查看镜像情况
---
[root@zwc docker-compose]# docker-compose images
Container Repository Tag Image Id Size
----------------------------------------------
单机中使用 docker-compose 部署项目可以很明显的感觉到方便快捷,在不同项目、相同环境下,仅需一个 jar 包即可完成上线工作。
希望能够帮助到你
over