本文分享如何在docker环境中搭建redis cluster集群,并在搭建过程当中分享一些docker的经常使用知识。html
关于bin/ubuntu:16.04镜像的构建请参考docker基础环境搭建node
构建一个bin/redis:5.0.7镜像,该镜像基于bin/ubuntu:16.04,使用源码安装redis-5.0.7(请先下载redis-5.0.7.tar.gz)。
Dockerfile以下:linux
FROM bin/ubuntu:16.04 RUN apt-get update && apt install -yqq make gcc WORKDIR /var/lib COPY redis-5.0.7.tar.gz . RUN tar -xzvf redis-5.0.7.tar.gz && rm redis-5.0.7.tar.gz WORKDIR /var/lib/redis-5.0.7 RUN make install
构建一个bin/redis-server镜像,Dockerfile以下redis
FROM bin/redis:5.0.7 COPY docker-entrypoint.sh /usr/local/bin RUN groupadd -r redis && useradd -r -g redis redis \ && chmod 777 /usr/local/bin/docker-entrypoint.sh VOLUME /usr/local/redis/data/ ENTRYPOINT ["docker-entrypoint.sh"] CMD ["--port 6379"]
CMD 和 ENTRYPOINT 指令都是用来指定容器启动时运行的命令。而使用RUN命令启动容器时也能够指定容器启动时运行的命令。他们区别以下sql
- 不存在ENTRYPOINT指令时,CMD指令能够指定默认命令。若是使用RUN启动容器时指定了命令,会覆盖CMD指令,而若是没有指定,则执行CMD中的默认命令。
- 存在ENTRYPOINT指令,ENTRYPOINT指定的命令不能够被RUN覆盖(除非使用--entrypoint参数),但CMD/RUN能够指定参数(做为ENTRYPOINT指定命令的参数)。若是RUN中指定参数,覆盖CMD中的参数。若是没有指定,则使用CMD中的默认参数。
- CMD和ENTRYPOINT指令能够exec模式,如
ENTRYPOINT ["docker-entrypoint.sh","--port 6379"]
,也可使用shell模式,如ENTRYPOINT docker-entrypoint.sh --port 6379
,但这时docker会在指定命令前加/bin/sh -c,这样可能会致使一些错误,推荐使用exec模式。
上面Dockerfile配置了容器启动命令为docker-entrypoint.sh
,默认参数为--port 6379
,也能够在RUN命令中指定参数。
(这里说的RUN命令是启动容器的命令,不是Dockerfile中的RUN指令)docker
docker-entrypoint.sh负责修改配置,启动redisshell
#!/bin/bash mkdir -p /etc/redis/ /var/log/redis/ /usr/local/redis/data/ chown -R redis:redis /var/log/redis/ /usr/local/redis/data/ cat>>/etc/redis/redis.conf<<EOF protected-mode no appendonly yes logfile /var/log/redis/redis.log dir /usr/local/redis/data/ cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 EOF exec gosu redis redis-server /etc/redis/redis.conf $@
exec是 bash 的内置命令,exec用被执行的命令替换掉当前的shell进程,且exec命令后的其余命令将再也不执行。使用exec和gosu启动redis,可让redis成为PID等于1的进程,保证SIGTERM等信号正常工做。
CMD/ENTRYPOINT指令的exce模式也有这个做用。
启动全部的redis容器数据库
for i in `seq 1 6`; do sudo docker run -d --name redis-$i bin/redis-server done
注意,这里run命令没有linux命令参数,则容器启动后执行ENTRYPOINT指定命令和CMD默认参数 docker-entrypoint.sh '--port 6379'
ubuntu
查看进程bash
$ sudo docker top redis-1
能够经过logs目录查看日志
$ sudo docker logs -f redis-1
(正常使用下redis可能没有日志输出到前台)
docker容器运行时应该尽可能不进行写数据操做(不然删除容器后数据也被删除了),对于数据库这类须要保存动态数据的应用,其数据文件应该保存于卷(volume)中。
VOLUME指令就是用于挂载卷的。
简单来讲,就是将docker目录(/usr/local/redis/data/)挂载到宿主机的目录(docker会在宿主机/var/lib/docker/volumes下建立一个子目录),这样docker输出到该docker目录的文件实际保存到宿主机目录。
若是咱们修改宿主目录的文件,docker也会立刻知道到,这样咱们将代码文件放在宿主机上(方便咱们修改代码),而后让docker容器经过卷来读取文件。
卷还能够实现数据共享, 经过在run命令中使用--volumes-from选项。
Dockerfile中使用了VOLUME指令挂载了一个卷保存redis的AOF文件,这样容器被删除后,文件还保留在宿主机内。
经过docker inspect 能够查看Volume卷的挂载信息
"Mounts": [ { "Type": "volume", "Name": "025f5fff7a31e61f8a068b7b6de38731d16b5efac7e96ac0c01892a4139e8d83", "Source": "/var/lib/docker/volumes/025f5fff7a31e61f8a068b7b6de38731d16b5efac7e96ac0c01892a4139e8d83/_data", "Destination": "/usr/local/redis/data", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ],
run命令能够经过-v参数挂载卷,而且能够指定宿主机目录,VOLUME指令没法作到这点。
sudo docker run -d -v /home/binecy/redis/data/:/usr/local/redis/data/ bin/redis-server
获取全部的docker容器ip
$ for i in `seq 1 6`; do > echo `sudo docker inspect -f '{{ .NetworkSettings.IPAddress}}' redis-$i` > done 172.17.0.2 172.17.0.3 172.17.0.4 172.17.0.5 172.17.0.6 172.17.0.7
链接到redis-1容器,执行如下命令,建立redis cluster
$ sudo docker exec -it redis-1 /bin/bash root$ redis-cli --cluster create 172.17.0.2:6379 172.17.0.3:6379 172.17.0.4:6379 172.17.0.5:6379 172.17.0.6:6379 172.17.0.7:6379 --cluster-replicas 1
下面重点来看看docker网络
上面例子我使用的是docker默认的网络模式,即bridge模式
Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。
当Docker 进程启动时,会自动在主机上建立一个 docker0 虚拟网桥,默认分配网段172.17.0.0/16,实际上就是一个 Linux bridge 网桥,能够理解为一个软件交换机,附加在其上的任何网卡之间都能自动转发数据包。
在bridge模式下,docker会为每个容器建立一个Network Namespace。建立一个容器时,容器从docker0中的子网分配一个IP地址,并建立一对veth虚拟网络设备,其中一个设备在容器中做为容器的网卡,另外一个设备桥接在宿主机docker0上,可经过命令brctl show 查看(名称为vethXXX),经过这样的桥接方法宿主机上的全部容器都处于同一个二层网络中,这样使得容器与容器以及容器与宿主机之间可以互相通讯。(但不能跨宿主机通讯)。
veth 是 Virtual ETHernet 的缩写,是一种虚拟网络设备。当老是以两张虚拟网卡(Veth peer)形式被建立,而且在一个网卡上的数据包可直接转发给另外一个网卡上,即便这两个网卡不在同一个namespace中。
咱们也能够自定义了一个网桥
docker network create redis-net
自定义网桥和默认的docker0网桥最大区别是自定义网桥能够经过--network-alias指定容器的网络别名,容器间能够经过网络别名通讯。
for i in `seq 1 6`; do sudo docker run -d --name redis-$i --network redis-net --network-alias redis-$i bin/redis-server done
这样,就能够在redis-1容器中,能够ping redis-2 ping
ping通redis-2容器,也能够经过 redis-cli-h redis-2
链接到redis-2容器的redis。
Mysql MGR,Zookeeper等分布式系统,须要在应用启动前将集群内其余成员的ip信息写入配置文件,这时很适合使用网络别名。
(redis-cli --cluster create命令没法使用网络别名)
使用自定义网络后,能够经过如下命令获取docker容器ip
sudo docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis-1
docker inspect -f
能够支持go template语法,这里使用range循环遍历全部的.NetworkSettings.Networks,并取其下的IPAddress变量。
不过这样建立的redis cluster只有宿主机能够访问,外部机器(非宿主机)没法访问,由于docker中的redis端口没有映射到宿主机上。
咱们也能够在run命令上使用-p参数映射端口(-P能够映射Dockerfile中EXPOSE指定的端口):
for i in `seq 1 6`; do sudo docker run -d --name redis-$i -p 6379 -p 16379 bin/redis-server done
但即便这样外部机器仍是没法访问redis cluster,由于redis cluster内部通讯用的docker容器ip(就是redis-cli --cluster create命令中的ip),外部机器访问redis cluster时,redis cluster会将这些ip返回给外部机器,并让外部机器经过它们来访问redis cluster,但外部机器没法访问docker容器ip,因此这种方式只能在宿主机上访问redis cluster。
run命令中的-p选项映射端口是经过NAT协议实现的,可使用iptables命令查看。
下面说一下host模式
host模式下容器共享宿主机的Network Namespace,容器内启动的端口直接是宿主机的端口,而且容器不会建立网卡和IP,直接使用宿主机的网卡和IP
下面在host模式下搭建redis cluster集群。
启动redis 应用
for port in `seq 7000 7005`; do sudo docker run -d --name redis-${port} --net=host bin/redis-server "--port ${port}" done
这里RUN命令指定了参数,容器启动后执行ENTRYPOINT指定命令和RUN参数docker-entrypoint.sh '--port ${port}'
。
注意,这里容器启动的端口就是宿主机的端口,要保证宿主机端口不会冲突。
经过宿主机IP192.168.0.102启动redis cluster
$ sudo docker exec -it redis-7000 /bin/bash root$ redis-cli --cluster create 192.168.0.102:7000 192.168.0.102:7002 192.168.0.102:7002 192.168.0.102:7003 192.168.0.102:7004 192.168.0.102:7005 --cluster-replicas 1
这样外部机器就能够经过宿主机IP192.168.0.102访问redis cluster。
关于docker网络,这有一篇很好的文章 -- docker网络
Dockerfile优化 -- 如何编写最佳的Dockerfile
本文说了docker CMD/ENTRYPOINT,VOLUME,inspect,网络等知识点,基本知足咱们平常使用docker了。
若是您以为本文不错,欢迎关注个人微信公众号,您的关注是我坚持的动力!