点击蓝字关注我们
大家好,我是杰哥
前几篇文章分别介绍了Docker的一些核心概念,并与大家一起分别通过将组件打包在同一个镜像,将组件独立到多个镜像等实战,探索到了Docker的使用方式以及一些更深层次的知识
上一篇,我们已经将Spring Boot项目和使用到的Mariadb分别放在两个独立容器中,部署并运行起来了。在真实环境中,后端程序中,往往不只会有Mariadb一个组件,还会有redis、mongodb、mq、注册中心、配置中心等等组件的存在。那么,对应的容器将会很多(即使这些组件都采用的是单机部署,都会有很多,更别提集群部署的情况了)
那么,该如何管理这些容器呢?有没有一种方式能够使我一次性就实现这些组件镜像的一键启动与停止呢?
有,当然有,技术人总是在不停探索的,那么大家能想到的,他们肯定都会尽可能地帮我们实现
比如说,Docker Compose,就是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后就可以一键创建并启动所有服务,还可以一键删除所有服务,镜像
Compose 使用包括三个步骤:
使用 Dockerfile 定义应用程序的环境
使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行
最后,执行 docker-compose up 命令来启动并运行整个应用程序
在这之前,我想先简化一下前两次实战的一些内容,对于Mariadb的配置
1 设置密码
采用以下命令
MYSQL_ROOT_PASSWORD: 123456
2 开启权限
MYSQL_ROOT_HOST: '%'
3 设置字符集
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
4 启动命令
因此我们的启动命令可以直接调整为如下所示:
docker run -d -p 3306:3306 -v /Users/wangjie/mysqldata2:/var/lib/mysql \> -e MYSQL_ROOT_PASSWORD=123456 \> -e MYSQL_ROOT_HOST='%' \> -e MYSQL_DATABASE=adp_test \> --net=app mymariadb:1.2 \> /sbin/init
说明:
1)-d:后台启动镜像mymariadb:1.2
2)-p 3306:3306:映射容器端口3306,到主机端口的3306
3)-v /Users/wangjie/mysqldata:/var/lib/mysql
创建挂载目录/var/lib/mysql,对应主机目录为:/Users/wangjie/mysqldata
4) -e MYSQL_ROOT_PASSWORD=123456
设置数据库密码为123456
5)-e MYSQL_ROOT_HOST='%'
开启数据库可被外连接的权限
6) -e MYSQL_DATABASE=adp_test
创建初始数据库adp_test
7) --net=app 加入到网桥app中
8)--privileged=true
开启容器最大权限
好了,接下来,有了一点点铺垫,我们就正式进入Docker Compose的实战环节
一 实战
Docker-Compose
本节,我们将演示,使用Docker Compose实现分别创建并启动Spring Boot集成Mysql的web项目。其中Spring Boot程序和Mysql是定义在各自的镜像中,即运行在不同的容器中的
说明:Spring Boot程序依旧是之前实战中提到的druid_demo,代码并没有做调整
前面提到,使用Docker Compose的三个步骤
使用 Dockerfile 定义应用程序的环境
使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行
最后,执行 docker-compose up 命令来启动并运行整个应用程序
那么,我们就按照步骤来进行我们的实战
01.编写Dockerfile
总共两个组件,我们直接使用仓库中的mysql:5.7镜像,就不需要专门编写Dockerfile文件了
druid_demo程序的Dockerfile定义如下
#用于构建独立druid_demo镜像:druid_demoFROM openjdk:8-jdk-alpineVOLUME /tmpARG JAR_FILE=druid_demo-0.0.1-SNAPSHOT.jarCOPY ${JAR_FILE} druid_demo-0.0.1-SNAPSHOT.jarEXPOSE 8888ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /druid_demo-0.0.1-SNAPSHOT.jar ${0} ${@}"]
02.编写docker-compose.yml
使用docker-compose.yml定义构成应用程序的服务。我们定义的docker-compose.yml文件内容如下:
version: '2'services: mysql: image: mysql:5.7 hostname: mysql container_name: mysql privileged: true environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: adp_test MYSQL_ROOT_HOST: '%' volumes: - /Users/wangjie/mysqldata4:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d/ ports: - "3306:3306" command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci networks: - app app: build: ./druid_demo image: druid_demo2:0.0.1-SNAPSHOT container_name: druid_demo ports: - "8888:8888" environment: SLEEP_SECOND: 8 depends_on: - mysql networks: - appnetworks: app: driver: bridge
乍一看,有点长。没关系,我们来解读一下,你就明白啦
1 这是一个yml文件,最左侧的属性有三个:version、services和networks
version:指定本地安装的Compose的版本
services:就是用来配置我们的不同服务的
networks:用来配置网络。这里新建了一个名为docker_app的bridge网络(使用Compose创建网络,会自动添加前缀docker_)
接下来,我们把目光主要放在services属性
2 services属性的最左侧子属性有两个:mysql和app,即我们要定义的两个docker服务
3 先让我们一起看看mysql服务
mysql: image: mysql:5.7 hostname: mysql container_name: mysql privileged: true environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: adp_test volumes: - /Users/wangjie/mysqldata4:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d/ ports: - "3306:3306" command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci networks: - app
从上到下,分别为:
image:镜像名称,指定为mysql:5.7
hostname:容器主机名称,相当于启动命令中的 -h 参数
container_name:容器名称,相当于启动命令中的 --name 参数
privileged:表示是否使容器中的root拥有真正的root权限
environment:环境参数。相当于启动命令中的 -e 参数
这里的
MYSQL_ROOT_PASSWORD: 123456MYSQL_DATABASE: adp_test
三个参数,分别表示设置mysql中root的密码为123456,初始化数据库为adp_test。并分别对应启动命令 -e 中的同名参数
volumes:挂载目录:相当于启动命令中的 -v 参数
这里的
volumes: - /Users/wangjie/mysqldata4:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d/
挂载了两个目录,分别用于数据持久化和数据初始化
我们知道,docker在启动时,会去加载并执行/docker-entrypoint-initdb.d目录下的sql文件
所以在这里,我们将项目的初始化sql文件放在了 mysql/init 目录下,通过该目录与 /docker-entrypoint-initdb.d 目录的映射,就可以实现初始化sql语句的执行
init.sql文件内容为:
create database if not exists adp_test;use adp_test;create table t_user2 (id int(11) primary key,name varchar(30),age int(20));insert into t_user2 values(1,"wangjie",21);
即创建adp_test数据库、创建t_user2表,并在表中插入一条数据
在这里会有两个坑,需要注意一下
踩坑指南
1)踩坑一
当数据目录/Users/wangjie/mysqldata4存在数据文件时,启动mysql,会出现mysql容器总是自动停止的情况
2)踩坑二
有时候,同样是数据文件目录/Users/wangjie/mysqldata4已被创建好再启动镜像时,会出现,/docker-entrypoint-initdb.d/目录下的sql文件不会被执行的情况
总结
所以,最好是,第一次启动时,不创建mysqldata4这个目录。让docker在启动的时候,自动去创建该文件夹,然后再去执行初始化sql语句就好啦
ports:指定端口,相当于启动命令中的 - p 参数,这里暴露了3306端口
command:覆盖容器启动的默认命令
这里的
--default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
分别指定开启外部连接的权限,设置mysql的服务端字符集和排序规则分别为utf8mb4和utf8mb4_unicode_ci
networks:指定容器的网络,相当于启动命令中的--net参数。这里指定为app网络,
综述:
使用Docker Compose,以root权限启动一个镜像名称为mysql:5.7,容器名称为mysql的mysql容器,主机名称为mysql
设置root的密码为123456,初始数据库为adp_test。设置数据库的持久化目录为/Users/wangjie/mysqldata4,定义数据库的初始化sql目录为/mysql/init目录
映射宿主机与容器间的3306端口,设置允许外部连接mysql,以及字符集和排序规则分别设置为utf8mb4和utf8mb4_unicode_ci。并将它启动在docker_app网络中
4 接下来,看看第二个服务:app服务
首先,我们先来看看Spring Boot项目
1) 数据库配置
server.port=8888spring.datasource.url=jdbc:mysql://mysql:3306/adp_test?characterEncoding=utf8spring.datasource.username=rootspring.datasource.password=123456
注意其用户名密码分别配置为root何123456,url则配置为主机名为mysql,端口为3306的mysql服务,数据库为adp_test,并指定字符集为utf8
2) 接口定义
该程序,定义了一个list接口,用来获取用户列表
@GetMapping("/list")public List<User> getAllUsers(){ return userService.listData();}
其底层是对t_user2 表的查询
/** * 采用RowMapper的方式,获取结果对象。 */public List<User> listData() { log.info("Count:{}", getCount()); //采用RowMapper的方式,获取结果对象。 List<User> userList2 = jdbcTemplate.query("select * from t_user2", new RowMapper<User>() { @Override public User mapRow(ResultSet resultSet, int i) throws SQLException { return User.builder() .id(resultSet.getLong(1) ).name(resultSet.getString("name") ).age(resultSet.getInt("age")) .build(); } }); userList2.forEach(user -> log.info("User2:{}", user)); return userList2;}
接下里,我们就来看看docker-compose.yml文件中对这个app服务的定义
app: build: ./druid_demo image: druid_demo2:0.0.1-SNAPSHOT container_name: druid_demo ports: - "8888:8888" environment: SLEEP_SECOND: 8 depends_on: - mysql networks: - app
这里,比mysql服务的定义多了两个不同的属性:build 和 depends_on
build:表示镜像构建目录
depends_on:表示依赖于某个或者某几个服务的运行而运行。效果就是会先启动被依赖的服务,再启动该服务
Tips1
此外,需要说明的是environment中的参数SLEEP_SECOND,表示等待连接依赖服务(此处为mysql服务)的时间,默认为2s。主要是为了解决当mysql服务还未完全启动时,应用程序就去连接导致报错的问题
加上该参数,应用程序app服务,若刚开始连接mysql不成功,会不断重试连接,直到8s后才会放弃,而报错
综述:
Spring Boot应用程序,使用druid目录下的Dockerfile文件进行镜像构建,构建后的镜像名称为druid_demo2:0.0.1-SNAPSHOT
构建之后,启动该镜像,启动之后的容器名称为druid_demo,映射端口为8888
在启动该服务之前,优先启动mysql服务。并设置连接mysql的最大超时时间为8s
好,解读完了docker-compose.yml文件,你一定已经轻松get到Docker Compose的简单性了吧,并也可以自己动手写一个简单的dokcer-compose.yml文件了~
那么,接下来,我们就来启动整个应用程序,见证一下效果
03.执行命令来启动并运行整个应用程序
执行命令docker-compose up,查看容器的启动情况
1 mysql容器启动情况
2 Spring Boot项目启动
我们发现,由于Spring Boot项目服务依赖于mysql服务,因此docker compose优先启动了mysql服务,再启动了Spring Boot项目druid_demo
3 查看网络
执行以下命令
docker network inspect docker_app
发现,我们启动的两个容器都在docker_app网桥里面
"Containers": { "558fd48114a0f0329efa56c5fffd12f4e29f7861c83870840cc7a1c8bc5d8292": { "Name": "mysql", "EndpointID": "00c319ae51eaca16fbbf3616dbacbd25226e1943aea9a93c0b7eb04f48d72e5b", "MacAddress": "02:42:ac:15:00:02", "IPv4Address": "172.21.0.2/16", "IPv6Address": "" }, "ba74a3b446879695fc46bd5c3b4c69c0901befa47260132a00e1ed80604f7654": { "Name": "druid_demo", "EndpointID": "dd19d9e13c3bd8a2c698e343622ba8c191295ded30ca61dca9e9d806f6c9cba0", "MacAddress": "02:42:ac:15:00:03", "IPv4Address": "172.21.0.3/16", "IPv6Address": "" }
4 验证程序
在postman中访问接口
正常返回一条数据
说明app服务启动成功,连接mysql服务成功。并且mysql中的用户名密码已配置正确,并且允许外部访问的规则生效,初始化sql语句执行成功
5 停止并删除容器
我们往往需要使用 -d 参数,来实现服务的后台启动,即执行命令
docker-compose up -d
如下所示:
查看容器,均已成功启动
此时,要如何实现容器的一键停止呢?
猜对了,直接执行
docker-compose down
就可以搞定啦,效果如下:
查看容器,发现已经没有运行中的容器了
二 总结
总而言之
好了,使用Docker-Compose来管理容器,实现容器的同时启动和同时停止,还是比较快捷方便的
到这里,我们的Docker Compose实战圆满成功!
步骤也比较简单,只有以下三步:
使用 Dockerfile 定义应用程序的环境
使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行
最后,执行 docker-compose up 命令来启动并运行整个应用程序
原创不易,如果本篇文章,对你有一点点帮助的话,记得点个在看哦~
嗯,就这样。每天学习一点,时间会见证你的强大~
下期预告:
Docker篇(七):K8s管理Docker容器
往期精彩回顾
Docker篇章
Docker篇(五):容器之间该如何通讯?
Docker篇(二):Docker实战,命令解析
Docker篇(一):为什么要用Docker?
..........
SpringCloud篇章
Spring Cloud(十三):Feign居然这么强大?
Spring Cloud(十):消息中心篇-Kafka经典面试题,你都会吗?
Spring Cloud(九):注册中心选型篇-四种注册中心特点超全总结
Spring Cloud(四):公司内部,关于Eureka和zookeeper的一场辩论赛
..........
Spring Boot篇章
Spring Boot(七):你不能不知道的Mybatis缓存机制!
Spring Boot(六):那些好用的数据库连接池们
Spring Boot(四):让人又爱又恨的JPA
SpringBoot(一):特性概览
..........
翻译
【译】基于 50 万个浏览器指纹的新发现
使用 CSS 提升页面渲染速度
WebTransport 会在不久的将来取代 WebRTC 吗?
.........
职业、生活感悟
你有没有想过,旅行的意义是什么?
程序员的职业规划
让程序员崩溃的十个瞬间!第6个简直不能忍!
聊聊这次换工作经历
欢迎大家关注们的公众号,一起持续性学习吧~