一、文章核心内容
容器化时代,看山不是山,看天全是云~
- 今年的端午节粽子大家吃的是甜的还是咸的?是卷的~
文章核心内容,可快速完成SpringBoot服务+依赖服务一起编排,完成服务容器化
- docker-compose编排Redis,附有原生docker命令对比和docker编排指令的注释说明
- docker-compose编排MySQL,注明核心配置
- docker-compose编排SpringBoot微服务(这里以一个简单的SpringBoot集成Redis&MySQL服务代替)
- SpringBoot调用Redis、MySQL容器提供的服务,编排时的依赖关系处理
文章注意前提事项说明
- 需要掌握Docker的基本使用,文章不会对基础命令进行过多说明
- 此处的服务使用SpringBoot,其他可以连接Redis和MySQL的服务也可以
- 容器间相互连通有两大方式
- 通过IP地址:这种方式明确写死了ip地址,在生产中不推荐,当Docker容器重启时IP可能会发生变化
- 通过容器名称:这种方式屏蔽了ip地址,只要服务名称不变,服务不受到影响,核心就是docker network
- docker的基础和docker-compose前置知识可以参考本专栏的前面的文章
二、Redis编排
tip:通过容器名进行相互连通的容器间必须要加入到同一个网络
2.1 编写配置文件
编写redis的docker-compose.yml配置文件,参数含义参考注释
不同的编排文件建议放到单独的文件夹中,比如单独新建一个redis目录,mysql的就创建mysql目录,依次类推
# 原始命令:
# docker run -d --name redis -p 6379:6379 -v \
# - /root/docker/compose/redis/redis.conf:/etc/redis/redis.conf \
# - /root/docker/compose/redis/data:/data \
# --network my_network redis:latest redis-server /etc/redis/redis.conf
# 版本号
version: "3"
# 服务列表,命令:docker run
services:
# 服务名称,任意,不重复即可
redis:
# 指定服务名称,命令:--name redis
# 如果不指定,则将默认用docker-compose.yml所在文件夹名_服务名称_n命名
container_name: redis
# 指定镜像:命令 redis:latest
image: redis:latest
# 指定端口:命令 -p 主机端口:容器端口
ports:
- "6379:6379"
# 数据容器卷
volumes:
- /root/docker/compose/redis/redis.conf:/etc/redis/redis.conf
- /root/docker/compose/redis/data:/data
# 加入指定网络,容器间必须要处于同一个网络才能通过容器名称进行调用
networks:
- my_network
# 运行命令
command: redis-server /etc/redis/redis.conf
# 创建网络
networks:
# 默认将会创建为 docker-compose.yml所在 文件夹名_my_network 网络名称
my_network:
复制代码
2.2 运行配置文件
- 在docker-compose.yml所在文件夹通过docker-compose up运行,也可以添加-d参数在后台运行
- 此处docker-compose.yml所在文件夹:/root/docker/compose
# docker-compose up
Creating network "compose_my_network" with the default driver
Creating redis ... done
Attaching to redis
redis | 1:C 24 Mar 2022 00:13:38.331 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis | 1:C 24 Mar 2022 00:13:38.331 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
redis | 1:C 24 Mar 2022 00:13:38.332 # Configuration loaded
redis | 1:M 24 Mar 2022 00:13:38.332 * monotonic clock: POSIX clock_gettime
复制代码
- 查看docker实例
- 进入容器内部,可以执行redis的客户端工具
# docker-compose exec redis bash
root@f5865bc42306:/data# redis-cli
复制代码
- 查看网络,发现compose会自动创建在编排文件中的配置文件
# docker network ls
NETWORK ID NAME DRIVER SCOPE
012d4d17c012 bridge bridge local
# 默认将会创建为 docker-compose.yml所在文件夹名_my_network 网络名称
88c54133c87a compose_my_network bridge local
1524da0505ee host host local
f4cbc4779eaf none null local
复制代码
- 查看网络细节
- docker network inspect compose_my_network
[
{
"Name": "compose_my_network",
"Id": "88c54133c87a04cc8c86a55c240d2ccafb9bfb456b8e7d185a9a246b5d274025",
"Created": "2022-03-24T00:13:37.623603357Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"f5865bc42306380b1bd72690d6f67d8abdd46a240a96ea5f9bcfee412fe76c17": {
"Name": "redis",
"EndpointID": "8638b4c9850e533778d331bfd916b120f8a1ac59a9f547763c5e352df93ed897",
"MacAddress": "02:42:ac:14:00:02",
"IPv4Address": "172.20.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
# 内部实际名称还是my_network
"com.docker.compose.network": "my_network",
# 容器编排项目名称
"com.docker.compose.project": "compose",
"com.docker.compose.version": "1.29.2"
}
}
]
复制代码
- 停止容器
- docker-compose stop:停止但不删除
- docker-compose down:停止并且删除网络等一些列配置
# docker-compose down
Removing redis ... done
Removing network compose_my_network
复制代码
三、MySQL编排
3.1 编写配置文件
注意这里只贴出了compose的构建配置,等同的docker命令参考redis部分,只是内容变了,但是等级等价的命令并没有太大差异
version: "3"
services:
mysql:
container_name: mysql
image: mysql:5.7
ports:
- "33306:3306"
environment:
# 等同于 -e MYSQL_ROOT_PASSWORD指定root的登录密码
MYSQL_ROOT_PASSWORD: 'tianxin'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
# 这里这个指令compose启动成功后会自动创建名为docker的数据库
MYSQL_DATABASE: 'docker'
# 此处就是相当于 mysql create user,创建了数据库的登录用户
MYSQL_USER: 'docker'
MYSQL_PASSWORD: '123456'
volumes:
- /root/docker/compose/mysql/data:/var/lib/mysql
# 这里的my.cnf可以从原来的安装的MySQL里面找,如果没有不配置也不影响,只是为了方便外部更改
- /root/docker/compose/mysql/conf/my.cnf:/etc/my.cnf
- /root/docker/compose/mysql/init:/docker-entrypoint-initdb.d
networks:
# 注意加入同一个网络
- my_network
# 解决外部无法访问
command: --default-authentication-plugin=mysql_native_password
networks:
my_network:
复制代码
3.2 运行配置文件
- 在mysql docker-compose.yml所在文件夹通过docker-compose up运行,也可以添加-d参数在后台运行
- 运行之后养成随手就是 docker ps 查看容器是否启动的好习惯
- 进入容器内容,通过指定的密码登录
# docker-compose exec mysql bash
root@6cb40ff45da5:/# mysql -uroot -ptianxin
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.36 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
# 这里可以看到,自动创建了docker数据库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| docker |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> use docker;
Database changed
mysql> show tables;
Empty set (0.00 sec)
复制代码
四、微服务编排
tip:MySQL和Redis相对于独立,但是对于服务而言,依赖于Redis和MySQL,所以在服务启动前需要先启动Redis和MySQL,如果依赖服务较多,人工管理是一件比较麻烦的事情。通过docker-compose的编排可以指定依赖关系等等
4.1 编写服务
- 创建SpringBoot服务,采用SpringBoot+MyBatis+MySQL+Redis完成简单的SpringBoot应用
- maven的pom.xml依赖如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/>
</parent>
<groupId>com.codecoord</groupId>
<artifactId>docker</artifactId>
<version>1.0</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
复制代码
- 应用配置文件
特别说明:服务依赖的容器服务,使用容器服务名称,比如 jdbc:mysql://mysql:3306/docker,不要通过IP。端口号是容器内部端口,不是外部端口号!比如 -p 13306:3306,13306是对外端口,3306才是容器内部服务端口,服务依赖时不要指定为外部端口,生产中也不建议除了服务外的容器直接对外提供访问
server:
port: 80
spring:
redis:
port: 6379
# redis:依赖的服务
host: redis
datasource:
# mysql:依赖的服务,注意端口是容器端口,非宿主机端口
url: jdbc:mysql://mysql:3306/docker?userSSL=false
username: root
password: tianxin
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
复制代码
- 控制器
import com.codecoord.docker.domain.User;
import com.codecoord.docker.service.UserService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.*;
@RestController
@RequestMapping("/docker")
public class DockerController {
@Resource
private UserService userService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@RequestMapping("/list")
public List<User> list() {
return userService.list();
}
@RequestMapping("/add")
public boolean add() {
return userService.save(new User() {{
setName(UUID.randomUUID().toString());
}});
}
@RequestMapping("/get/{id}")
public Object get(@PathVariable("id") Long id) {
Map<String, Object> returnMap = new HashMap<>();
Object cache = redisTemplate.opsForValue().get(id);
if (Objects.nonNull(cache)) {
returnMap.put("data", cache);
returnMap.put("cache", "缓存命中");
return returnMap;
}
User user = userService.getById(id);
returnMap.put("data", user);
returnMap.put("cache", "缓存未命中");
redisTemplate.opsForValue().set(id.toString(), user);
return returnMap;
}
}
复制代码
- 其他DAO层的按照MP格式搭建接口,注意实体类需要序列化
- Redis的序列化按照自己的需要配置
4.2 服务构建
- 编写Dockerfile,将服务项目打成jar包(普通的springboot使用maven package打包后的jar包)之后和Dockerfile放于同一个文件文件夹下
# 基础镜像使用java8,需要先构建
FROM java:8
# 作者
MAINTAINER tianxincoord@163.com
# 容器卷,指定临时文件的目录为/tmp
VOLUME /tmp
# 指定工作目录
WORKDIR /tmp
# 重命名可选操作,方便docker ps查看
add docker-1.0.jar core_service.jar
# 运行jar包
ENTRYPOINT ["java", "-jar", "core_service.jar"]
# 暴露30001端口作为服务端口
EXPOSE 30001
复制代码
- 构建镜像
# 构建镜像
# docker build -t core_service:1.0 .
Sending build context to Docker daemon 245.5MB
Step 1/7 : FROM java:8
---> 95064f52c03b
Step 2/7 : MAINTAINER tianxincoord@163.com
---> Running in 4e2fe1672499
Removing intermediate container 4e2fe1672499
---> e17943871098
Step 3/7 : VOLUME /tmp
---> Running in b7f7d56bc0cf
Removing intermediate container b7f7d56bc0cf
---> 7b67adbb7378
Step 4/7 : WORKDIR /tmp
---> Running in 973e07d965cb
Removing intermediate container 973e07d965cb
---> 70f5211073eb
Step 5/7 : add docker-1.0.jar core_service.jar
---> 5015d0feafeb
Step 6/7 : ENTRYPOINT ["java", "-jar", "core_service.jar"]
---> Running in a8b45a171493
Removing intermediate container a8b45a171493
---> 0e9ea7fb43c7
Step 7/7 : EXPOSE 30001
---> Running in 01e5efc042a6
Removing intermediate container 01e5efc042a6
---> af170edd9100
Successfully built af170edd9100
Successfully tagged core_service:1.0
复制代码
- 查看构建完成后的镜像
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
core_service 1.0 af170edd9100 58 seconds ago 648MB
java 8 95064f52c03b 8 minutes ago 610MB
centos 7 eeb6ee3f44bd 6 months ago 204MB
复制代码
4.3 服务编排(redis&mysql&服务)
- 编写compose配置文件
version: "3"
services:
macro_server:
image: core_service:1.0
container_name: core_service
ports:
- "80:80"
volumes:
- /root/docker/compose/app:/data
# 指定网络,这一步必须要和以来的服务处于统一网络
networks:
- my_network
# 依赖于redis和mysql,在启动本服务之前会先启动依赖的服务
depends_on:
- redis
- mysql
# Redis服务
redis:
container_name: redis
image: redis:latest
ports:
- "6379:6379"
volumes:
- /root/docker/compose/redis/redis.conf:/etc/redis/redis.conf
- /root/docker/compose/redis/data:/data
networks:
- my_network
command: redis-server /etc/redis/redis.conf
# MySQL服务,上面的构建服务直接拷贝下来即可
mysql:
container_name: mysql
image: mysql:5.7
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 'tianxin'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
MYSQL_DATABASE: 'docker'
MYSQL_USER: 'docker'
MYSQL_PASSWORD: '123456'
volumes:
- /root/docker/compose/mysql/data:/var/lib/mysql
- /root/docker/compose/mysql/conf/my.cnf:/etc/my.cnf
- /root/docker/compose/mysql/init:/docker-entrypoint-initdb.d
networks:
- my_network
command: --default-authentication-plugin=mysql_native_password
networks:
my_network:
复制代码
- 启动compose
# docker-compose up
Creating network "compose_my_network" with the default driver
Creating redis ... done
Creating mysql ... done
Creating core_service ... done
...
复制代码
- 进入服务内部,ping一下服务名,看一下是否网络连接成功,只有这里ping得通,服务才会调用得通
4.5 服务测试
- 进入MySQL服务中,创建对应的库和表
docker exec -it mysql bash
复制代码
use docker;
create table user
(
id int primary key auto_increment,
name varchar(50),
create_time datetime not null default current_timestamp,
modify_time datetime not null default current_timestamp on update current_timestamp
);
复制代码
- 访问接口是否正常
- 需要先执行:http://192.168.33.10:80/docker/add 添加数据
- 第一遍放入缓存,第二遍从缓存中读取
五、结尾说明
5.1 自愈性
- 服务容器化之后可以比普通jar部署方式拥有更高自愈性,比如原来普通jar部署当服务挂了之后,必须要重新处理启动服务,此时也可以通过一个看门狗程序当发现进程挂了之后自动重启,但是当服务多了,管理起来是一个比较麻烦的时候
- 而对于容器来说,就是docker kill/stop和docker run就可以快速淘汰老的服务启动新服务
5.2 调度性
- 传统jar部署方式最大的问题在于环境的一致性,比如现在A服务器上的服务能力不够了,需要在B服务器上部署一个新服务,那么安装环境是一个很麻烦的事情,特别是对于配置项目较多的服务来说进行大规模的快速调度基本上是一个很麻烦且费力的操作
- 对于容器化服务来说,所有的依赖服务在一个镜像完成后就可以以此进行为模板快速docker run很多很多的相同配置环境的服务
- 那么在弹性扩缩容上,通过容器可以很方便达到这个目标,服务容器话是一个服务用户到了一定规模之后不得不考虑的问题