在之前的文章RocketMQ容器化最佳实践我们完成的RocketMQ
的容器化部署,为了方便管理docker容器
,本片文章笔者就来推荐一个笔者最常用的docker
图形化管理工具——Portainer
。
Portainer
部署的步骤比较简单,我们还是以docker-compose
文件的形式完成Portainer
的安装,首先我们需要需要编写的名为portainer.yml
的文件,内容如下:
version: "3"
services:
portainer:
image: portainer/portainer:latest
container_name: portainer
ports:
- "9000:9000"
volumes:
- /app/portainer/data:/data
- /var/run/docker.sock:/var/run/docker.sock
完成后,我们将这个文件上传到我们的服务器即。
将配置文件上传之后我们就可以尝试启动了,为了方便查看服务是否可以正常启动,我们还先以前台运行的方式启动,到达portainer.yml
文件位置,键入前台启动命令
docker-compose -f portainer.yml up
当控制台没有输出错误,并显示正常启动后,我们分开服务器的9000端口,浏览器输入服务器ip:9000
,j即可进入Portainer
初始化界面,它会要求我们配置用户名密码:
完成账户密码初始化之后,我们点击local
进行服务器本地容器管理
此时我们就会看到local
中docker
的基本信息了
在步入local
,我们就可以按需管理容器、镜像、网络等配置信息。我们不妨点击containers
查看我们服务器中当前运行的docker
容器。
可以看到我们docker
中容器的运行情况,由于portainer
涉及操作很多,笔者这里就不一一演示了,感兴趣的读者可以自行查阅portainer
官方文档https://www.portainer.io/take-5
到上述步骤我们已经可以确定portainer
安装基本成功了,我们可以回到服务器将portainer
后台启动,正式使用portainer
了。
docker-compose -f portainer.yml up -d
读完上述的介绍可能读者们对Portainer
还没有一个比较清晰的了解,所以笔者在这里就基于一个redis-sentinel
的部署介绍一下Portainer
的日常操作步骤。
首先我们来介绍一下本次部署架构,我们会基于一台服务器完成一主二从+三个哨兵的部署架构,最终效果是:
master
继续进行工作。首先我们先进性主从结构的部署,我们还是基于docker-compose
创建一个名为redis-cluster.yml
的文件配置一下主从信息,配置内容如下,笔者已将配置的含义都一一注释,读者可自行参考修改。需要注意以下两点:
6379
作为对外暴露的端口号,就算使用6379
也尽可能设置一个安全的密码,避免被人下挖矿程序。version: '3'
services:
# 主节点
master:
image: redis
# 主节点名称
container_name: redis-master
# 设置redis登录密码、从节点连接主节点的密码
command: redis-server --requirepass xxxx--masterauth xxxx
ports:
# 对外暴露端口号为16380
- 16379:6379
# 从节点
slave1:
image: redis
container_name: redis-slave-1
ports:
# 对外暴露端口号为16380
- 16380:6379
# 启动redis 从属于容器名为 redis-master的redis,端口号为容器端口号而不是对外映射端口号,设置连接密码,连接主节点的密码
command: redis-server --slaveof redis-master 6379 --requirepass xxxx--masterauth xxxxx
# 从节点2
slave2:
image: redis
container_name: redis-slave-2
ports:
- 16381:6379
command: redis-server --slaveof redis-master 6379 --requirepass xxxxx --masterauth xxxxx
完成后我们即可将配置上传到服务器并启动:
docker-compose -f redis-cluster.yml up
启动完成后就可以在portainer
中看到一主二从出现在containers
列表中
以master
为例,我们不妨点击log
查看master
节点的运行情况
从输出可以看到master
没有输出任何错误,说明master
正常运行了。
在确认每个容器都能正常启动之后,我们不妨尝试在master
节点写入一些数据测试一下主从节点同步功能是否正常。
在没有portainer
之前,我们进入docker
容器内容用的都是使用docker exec -it 容器id bash/sh
命令,有了portainer
之后,我们进入容器的操作就变得非常简单了。
回到容器列表,点击redis-master进入容器管理界面
点击console
,进入容器终端连接界面
点击connect
连接进入容器
然后我们就进入的容器内部,尝试使用redis-cli
认证并设置一个kv
值。
完成上述步骤后,我们到达从节点的容器内部查阅数据可以发现,主节点数据确实同步过来了,自此我们主从复制部署基本完成。
为了确保redis-sentinel
可以统一管理且和其他容器隔离,我们在部署sentinel
之前需要基于Portainer
创建一个自定义的brige
网络。
什么是brige
网络呢?其实docker
容器中有以下几种网络驱动类型,不同的网络驱动类型可以使得容器和宿主机有着不同的网络关联:
1. host: 使用docker宿主机网络
2. bridge: 该网络支持在同一个宿主机上的各个容器实例之间的通信。bridge网络是一个独立的网络空间,在网络空间内部的各个容器实例能够直接通信。
3. none: 是一个完全隔离的自治网络,甚至与Docker宿主机的网络都不通,必须手工配置网卡后才能够使用。加入到该网络的容器实例,往往要在后续设置中加入到其他的第三方网络。
4. overlay:该类型的网络适用于Docker宿主机集群中的各个独立的容器实例之间通信。
5. macvlan:该类型的网络适用于容器实例需要与宿主机的MAC地址直接通信,无需端口映射,也无需NAT,容器实例的eth0直接与宿主机的物理网卡通信。容器实例可以被赋予公共IP,并从宿主机外部直接访问。
所以我们这里就希望创建一个名为redis-sentinel
的bridge
网络将主从和哨兵节点关联起来,并且和docker
中的其他容器隔离开:
所以我们按照点击network
,选择add network
进行自定义网络创建
输入名称为redis-sentinel
,其余默认,然后直接点击
然后直接点击create the network
然后我们就可以在network
列表中看到我们配置的network
,自此网络redis
专用桥接网络配置完成。
重点来了,我们的redis
主从节点现在都处于默认的网络驱动中,我们必须手动将其配置到redis-sentinel
网络中,当然有了portainer
,这种操作也不会很难。
我们只需要点击容器列表,找到我们的主动节点容器,然后分别进入他们的管理列表最下方,找到network
选项,在network
列表中找到redis-sentinel
选择join network
即可。
接下来就可以配置哨兵节点了,首先自然是创建一个名为redis-sentinel.yml
的docker-compose
文件配置3个哨兵:
version: '3'
services:
sentinel1:
image: redis
# 容器名称
container_name: redis-sentinel-1
ports:
# 端口映射
- 26379:26379
# 启动redis哨兵
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
# 哨兵1的sentinel.conf和宿主文件位置映射
- /app/cloud/redis/sentinel/sentinel1.conf:/usr/local/etc/redis/sentinel.conf
sentinel2:
image: redis
container_name: redis-sentinel-2
ports:
- 26380:26379
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- /app/cloud/redis/sentinel/sentinel2.conf:/usr/local/etc/redis/sentinel.conf
sentinel3:
image: redis
container_name: redis-sentinel-3
ports:
- 26381:26379
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- /app/cloud/redis/sentinel/sentinel3.conf:/usr/local/etc/redis/sentinel.conf
# 重点,将3个哨兵加入到redis-sentinel和主从节点建立联系
networks:
default:
external:
name: redis-sentinel
从配置文件中可以看出笔者将哨兵文件和宿主文件进行关联,所以我们需要按照上面配置在app/cloud/redis/sentinel/
文件夹下创建3个哨兵的配置文件。
哨兵1的配置文件如下,其余哨兵配置同理,只需按需修改端口号即可:
port 26379
dir /tmp
# master节点ip
sentinel monitor redis-master 172.20.0.2 6379 2
# master节点密码
sentinel auth-pass redis-master xxxxx
sentinel down-after-milliseconds redis-master 30000
sentinel parallel-syncs redis-master 1
sentinel failover-timeout redis-master 180000
sentinel deny-scripts-reconfig yes
可以看到笔者将master
节点命名为redis-master
,然后网络配置为172.20.0.2
这个值是从哪里来的呢?很简单,我们点击redis-master
容器管理界面,到达最下方,就可以在ip address
一栏看到master节点的容器ip地址,因为哨兵节点和主从节点都处于redis-sentinel
这个网络中,所以170.20.0.x
这个网络是互通的,在bridge
模式下配置这个ip地址是完全没有问题的。
完成上述步骤后我们就可以将哨兵启动
docker-compose -f redis-sentinel.yml up
此时我们点击任意一个哨兵节点都可以看到,哨兵的状态信息,自此哨兵也部署完成了
此时我们就可以测试哨兵是否正常工作了,我们的测试用例很简单,我们将master关闭掉,查看哨兵是否会选举出新的master顶上。
在测试前我们首先进入master
容器输入info replication
查看master
容器中的redis
是否为主节点
再查看另外两个几点是否身份是否确属从节点。
从节点1:
从节点2:
我们通过portainer
图形界面将redis-master
关闭。
点入任意一个哨兵日志,可以看到它监控到主节点下线,并快速选举出一个新的节点作为master
上线
我们根据ip地址172.20.0.3
定位到是slave2
这个redis,我们进入容器查看其身份,确实变为master
。
自此我们的redis-sentinel
部署完成了。
为了保证docker
环境下部署的完整性,接下来我们就希望本地的web
应用可以通过Redis Sentinel
架构操作redis
。
在Sentinel
架构下我们操作redis集群
可直接通过sentinel
节点操作master
,通过访问sentinel
获取可用的master
地址,然后就可以把操作提交到master
中。
由于操作master
节点需要经历: 向sentinel
询问master
地址->
根据sentinel
返回信息操作master
,这就意味的哨兵返回的master
地址信息必须是可访问的。而我们上文中的sentinel.conf
配置的都是docker
容器的内网地址,我们的web服务是外网访问,是无法和内网容器连接的。
所以我们必须修改上文关于哨兵的配置文件sentinel1.conf
、sentinel2.conf
、sentinel3.conf
。
以sentinel1.conf
为例,将redis-master
的ip地址
改为外网ip
,确保返回的master
地址信息,对我们本地服务是可以访问的。
port 26379
dir /tmp
# master节点ip
sentinel monitor redis-master 外网ip 16379 2
# master节点密码
sentinel auth-pass redis-master 密码
sentinel down-after-milliseconds redis-master 30000
sentinel parallel-syncs redis-master 1
sentinel failover-timeout redis-master 180000
sentinel deny-scripts-reconfig yes
完成后到portainer
中重启哨兵
我们引入Spring Boot
关于Redis
的脚手架以及lettuce连接池
。
这里扩展一下我们为什么添加lettuce连接池
的依赖,Lettuce
和 Jedis
的都是连接Redis Server
的客户端程序。但是Jedis
在实现上是直连redis server
,多线程环境下非线程安全,除非使用连接池,为每个Jedis
实例增加物理连接。而Lettuce
基于Netty
的连接实例,可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce pool 缓存连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>
引入redis-starter
配置之后,我们就可以配置Redis
了。大部分读者配置这些参数可能都是直接cv网上配置进行修改,其实在spring boot
项目中,由于starter
的自动装配机制,我们完全可以通过源码得出需要配置的内容。
从上文依赖中已经表明笔者需要用到lettuce
操作Redis客户端
,所以我们可以到spring boot
自动配置包中找到关于lettuce pool
的配置类LettuceConnectionConfiguration
。
在LettuceConnectionConfiguration
找到关于客户端连接配置的Bean
方法,可以看到其配置获取顺序是:sentinel(哨兵)->cluster(集群)->Standalone(单机)
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources)
throws UnknownHostException {
....略
//创建连接工厂
return createLettuceConnectionFactory(clientConfig);
}
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
//有sentinel则创建sentinel配置工厂
if (getSentinelConfig() != null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
//有cluster配置则取cluster创建cluster连接工厂
if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
//默认配置
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
我们不如getSentinelConfig()
方法细节中即可定位到sentinel
配置的对象RedisSentinelConfiguration
,然后就可以根据每个成员变量的调用定位到该配置文件文件的配置格式:
以master
为例,通过对RedisSentinelConfiguration
全局搜索master
的引用处定位到了下面这段代码,由此可知redis
配置中的master
与配置文件中的spring.redis.sentinel.master
相关联。
private static Map<String, Object> asMap(String master, Set<String> sentinelHostAndPorts) {
........略
Map<String, Object> map = new HashMap<>();
//spring.redis.sentinel.master 决定master的值
map.put(REDIS_SENTINEL_MASTER_CONFIG_PROPERTY, master);
........略
return map;
}
经过分析总结得出下面这段配置,笔者这里为了测试方便就配置了一个哨兵,需要注意的是如果哨兵和主从节点有密码则配置中必须添加关于password
的配置。
spring:
redis:
# master有密码则需要配置master认证面膜
password: xxxxx
sentinel:
master: redis-master
# 多个节点用,分开,例如节点1ip:26379,节点2ip:26379
nodes: 节点ip:26379
password: xxxx
# lettuce连接池配置信息
lettuce:
pool:
max-idle: 10
max-active: 20
min-idle: 5
max-wait: 10000ms
我们在Controller
中注入RedisTemplate
。
@Autowired
private RedisTemplate redisTemplate;
然后编写写入和读取的类,接下来就可以将服务启动测试了,这里笔者为了测试为了能够快速测试可用性,编写了一个简单的RedisStrDto
对象用于存储string
类型。
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class RedisStrDto {
String key;
String value;
}
然后在controller
中编写存取两个接口。
@PostMapping("setKey")
public ResultData<String> setKey(@RequestBody RedisStrDto redisStrDto) {
redisTemplate.opsForValue().set(redisStrDto.getKey(),redisStrDto.getValue());
return ResultData.success("success");
}
@GetMapping("getKey/{key}")
public ResultData<Object> getKey(@PathVariable(value = "key") String key) {
return ResultData.success(redisTemplate.opsForValue().get(key));
}
然后我们就可以基于自己的测试工具进行请求测试了,首先先通过setKey
插入数据到redis
中。
为了查看数据是否成功插入,这里笔者直接通过接口查询,可以看到上一个接口插入的数据确实返回了,由此可知服务集成redis
成功。
使用Portainer部署Docker容器实践
Docker—Docker-compose部署安装Portainer
Day16(yapi搭建、docker图形化工具portainer、安装redis、redis客户端远程连接、与springboot整合、vue入门、vsCode入门介绍、nodejs介绍)
portainer下配置Redis sentinel
springboot+redis的sentinel实现哨兵模式(超详细)
Spring Boot 如何快速集成 Redis 哨兵?