本文分享如何在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了。
若是您以爲本文不錯,歡迎關注個人微信公衆號,您的關注是我堅持的動力!