1. 任务说明
本次任务从黑马程序员的SpringCloud课程出发,课程中的微服务集群cloud-demo通过由nginx进行管理的nacos集群实现注册管理和配置管理。我们的目标是将这一系列的微服务通过docker容器完成部署
2. 任务执行关键点分析
如果要求mysql不向外部暴露端口,即必须实现各个容器之间可以直接通过[容器名称]:[端口号]访问,这种情况下,根据情形不同可以划分为不同的关键点
情形1:如果考虑nacos集群,mysql数据库,cloud-demo集群尚未运行,可以自定义compose文件完成构建运行
情形2:如果考虑nacos集群和mysql已经通过docker命令直接构建并运行,cloud-demo集群尚未运行,只有cloud-demo可以自定义compose文件完成构建运行
其他情形都可以由上述情形延伸,所以不再枚举
情形1的关键点是如何在compose文件中进行配置,使得不同的compose文件创建的容器能够互联;情形2的关键点是compose文件创建的容器和docker命令直接创建的容器之间又如何互联
如果对mysql是否向外部暴露端口不做要求,那么问题就简单了,直接在配置中通过外网访问即可,所以这种情况不进行介绍
参考内容枚举(感谢前人的智慧):
-
docker创建与compose创建的的容器如何互联:参考文献1
-
由不同compose创建的容器间如何互联:参考文献2
ps:(后续我会说明,这两种情形的实现原理是相同的,但细节处有所不同,所以列举了两个参考文献)
-
在docker下构建由nginx管理的nacos集群:参考文献3
3. 任务实现细节
由nginx反向代理nacos集群(同一台服务器部署)
在实现任务时,我们已经预先构建好了运行在虚拟IP172.17.0.2的Mysql容器,不向外界开放端口
根据[1]:
- 第一步,我们建立一个自定义网络,执行如下代码:
[root@centos703 ~]# docker network create --driver bridge --subnet 172.18.0.0/16 --gateway 172.18.0.1 mynet
--driver bridge 网络模式为 桥接模式
--subnet 172.18.0.0/16 设置子网
--gateway 172.18.0.1 设置网关
--mynet 自定义的network名
- 第二步,把Mysql容器放入自定义网络mynet中,执行如下代码:
## 解除容器绑定的网络 bridge:容器以前的network b7c:容器标识符(容器id前缀,也可写完整)
[root@centos703 ~]# docker network disconnect bridge b7c
## 为容器重新指定自定义网络
[root@centos703 ~]# docker network connect mynet b7c
## 重新启动容器
[root@lcentos703 ~]# docker restart b7c
## 通过inspect命令可查看mynet中是否有容器mysql
[root@lcentos703 ~]# docker network inspect mynet
根据[3],我们来创建一个与Mysql关联的nacos集群:
- 第三步,克隆nacos-docker至本地,修改相关配置
- 克隆nacos-docker至本地(方便修改)
- 修改example/cluster-hostname.yaml文件(移除mysql生成,增加对mysql的外链依赖以及虚拟网卡配置)
- 修改env/nacos-hostname.env文件(修改mysql的host地址以及用户名和密码)
## 拉取nacos-docker
[root@centos703 ~]# git clone --depth 1 https://github.com/nacos-group/nacos-docker.git
[root@centos703 ~]# cd nacos-docker
## 在移除移除mysql生成与依赖,增加虚拟网卡配置,example/cluster-hostname.yaml文件变为如下格式
version: "3"
services:
nacos1:
hostname: nacos1
container_name: nacos1
image: nacos/nacos-server:${NACOS_VERSION}
volumes:
- ./cluster-logs/nacos1:/home/nacos/logs
- ./init.d/custom.properties:/home/nacos/init.d/custom.properties
# 取消端口映射,只允许内部容器nginx通过名称:默认端口访问,不开放至宿主机
# ports:
# - "8848:8848"
# - "9848:9848"
# - "9555:9555"
env_file:
- ../env/nacos-hostname.env
restart: always
external_links:
- [mysql_service] # 此处为你的mysql服务名称,如果这里不是默认mysql而是其他名称,那么你还需要去env文件中做修改
## 为节省篇幅,删除了nacos2和nacos3的展示,修改思路与nacos1完全一致
# 网络设置
networks:
#可以自定义network名称,这里使用default表示如果不在service中单独配置就使用该设置
default:
external:
#使用自定义network
name: mynet
## 在修改mysql_host为mysql服务的名称,绑定用户名和密码,env/nacos-hostname.env文件变为如下格式
## nacos dev env
PREFER_HOST_MODE=hostname
NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
MYSQL_SERVICE_HOST=[mysql_service] # 必须与上面一致,否则在build/conf/application.properties的构建中,数据库的地址将无法解析
MYSQL_SERVICE_DB_NAME=nacos_config
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=****
- 第四步,通过docker-compose启动nacos集群
[root@centos703 ~]# docker-compose -f example/cluster-hostname.yaml up
## 返回结果:
nacos1 | 2022-09-13 11:35:31,840 INFO Nacos started successfully in cluster mode. use external storage
nacos2 | 2022-09-13 11:35:31,840 INFO Nacos started successfully in cluster mode. use external storage
nacos3 | 2022-09-13 11:35:31,840 INFO Nacos started successfully in cluster mode. use external storage
到这里我们已经可以对实现细节中的原理做一个小小的总结了,在本方案中,基本依据是:
只有位于同一子网的docker容器间才可以通过服务名直接访问,前提是容器与容器完成了互联(如果是
docker
直接生成,那么在生成时使用links
参数指定连接的服务名;如果是docker-compose
生成容器,在生成容器的配置中要通过external_links
指定连接的外部容器)
- 第五步,将nacos集群挂载到nginx上,启动负载均衡
- 拷贝nginx默认配置到/volumes/nginx文件夹下
- 修改nginx配置,使其关联nacos集群
- 启动nginx,并将其虚拟网卡置于mynet网络下
## 拷贝默认配置
$ docker run --name tmp-nginx-container -d nginx
$ docker cp tmp-nginx-container:/etc/nginx/nginx.conf /var/lib/docker/volumes/nginx/nginx.conf
$ docker rm -f tmp-nginx-container
## 修改Nginx配置,使其关联nacos集群
http {
...
upstream nacos-servers {
server nacos1:8848;
server nacos2:8848;
server nacos3:8848;
}
server {
location /nacos {
proxy_pass http://nacos-servers;
} # uri匹配至/nacos,替换主机为nacos-servers管理的主机,由于proxy_pass结尾没加/,/nacos将加入uri
listen 8080; # 这里的listen指的是容器内端口,该端口得自对宿主机对应访问端口的映射设置
}
}
# -p中后一个端口必须和conf文件的监听端口匹配,否则无法找到
$ docker run \
--name nacos-nginx \
-p 8080:8080 \
--network mynet \
--link nacos1 \
--link nacos2 \
--link nacos3 \
-v /var/lib/docker/volumes/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
-d nginx
## 在浏览器访问下面的地址测试是否可以访问
$ [host]:[8080]/nacos
至此,nacos集群由nginx反向代理
通过docker-compose将微服务部署在宿主机
部署的过程按照:
- 根据项目结构新建一个由各模块命名文件夹组成的合集
- 在各模块下放入打包好的jar包以及生成镜像的Dockfile命令
- 在合集处编写docker-compose文件(通过Build命令来执行Dockfile并生成容器)
部署过程中出现了Mysql数据库的远程连接报错,是jdbc的access denied错误,解决方案参考自:
SpringBoot项目连接数据库报错:Access denied for user ‘root’@‘localhost’ (using password: YES)
这是以前从没遇到过的错误,由于yaml区分数据类型,也就是说,它和python一样,会自动做数据类型的转换,如果是纯数字,那么就会被转化为int类型。如果你的密码首尾为0,那么转为int时会出现字符丢失,从而导致密码不匹配(很有参考意义,所以我会把它记录在我的debug博客中)
额外强调一点
本实现中强调的是同一台宿主机中创造的容器之间如何实现通过名称直接访问,这个问题的扩展是在不同的宿主机创造的容器之间实现通过名称(唯一性)的直接访问
要实现上述需求,我们需要用到新的工具:docker-swarm,这个工具的任务是建立不同服务器间的集群管理,而核心的原理则与本实现所介绍的类似,即构建了一个名为Overlay的虚拟网络来保证各个容器处于同一子网下
有关于docker-swarm的相关知识,读者可移步参考资料[4][5]: