本文使用的ZooKeeper官方镜像版本为3.5,可以去Docker Hub查看此版本镜像的Dockerfile文件
1. 单机启动一个简单的三点集群
ZooKeeper 可以很方便的用docker-compose方式在单机启动一个三点以上的集群,样例编排脚本如下:
version: '3.1'
services:
zoo1:
image: zookeeper
restart: always
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
zoo2:
image: zookeeper
restart: always
hostname: zoo2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zoo3:2888:3888;2181
zoo3:
image: zookeeper
restart: always
hostname: zoo3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181
核心配置部分即为配置ZooKeeper地址的ZOO_SERVERS和配置ZooKeeper的服务ID即ZOO_MY_ID。
ZooKeeper一共需要用到三个端口,端口的功能如下:
1、2181:对cline端提供服务
2、3888:选举leader使用
3、2888:集群内机器通讯使用(Leader监听此端口)
调试的时候使用docker-compose启动一个单机的集群是非常合适的,到了测试环境和生产环境,集群化的部署和高可用已经使必然。我们接下来就过渡到K8S集群环境中部署ZooKeeper
2. K8S部署思路
网上有大牛们的类似搭建文章例如k8s部署zookeeper/kakfa集群,看了之后发现虽然能满足功能但是启动了三个Deployment再用Service去对外暴露统一域名这种方式。而ZooKeeper是一个有状态的集群,StatefulSet+Headless Service方式来创建工作负载更加合适。另外推荐用一个StatefulSet的原因是相比与多个Deployment,StatefulSet更加容易通过脚本和环境变量扩展。
StatefulSet+HeadlessService方式下,每个ZooKeeper节点都有类似如下的域名:
zookeeper-service-0.zookeeper-service.namespace.svc.cluster.local:2888:3888;2181
zookeeper-service-1.zookeeper-service.namespace.svc.cluster.local:2888:3888;2181
zookeeper-service-2.zookeeper-service.namespace.svc.cluster.local:2888:3888;2181
...
zookeeper-service-N.zookeeper-service.namespace.svc.cluster.local:2888:3888;2181
联想到官方镜像提供的一个docker-compose编排脚本中的ZOO_SERVERS环境变量格式,我们很容易联想到,在StatefulSet的环境变量配置中,将ZOO_SERVERS设置成为
zookeeper-service-N.zookeeper-service.namespace.svc.cluster.local:2888:3888;2181
问题来了,没法对单个POD设置这个地址,如果在StatefulSet中设置这个地址,所有的POD又将共享同一地址,没法启动集群。因此我们需要做改造
3. 适用于K8S的镜像改造
最容易想到的还是设法修改启动脚本,让每个POD在启动时候,拥有不同的参数来创建这个ZOO_SERVERS和ZOO_MY_ID。我们可以编写如下的脚本:
#!/bin/bash
#MINE_MIDWARE_NAMESPACE=ns-mine-midware
#MINE_ZOOKEEPER_SERVICE=mine-zookeeper-service
#MINE_ZOOKEEPER_REPLICAS=3
#MINE_ZOOKEEPER_POD=mine-zookeeper-service
echo ${MINE_MIDWARE_NAMESPACE} #namespace
echo ${MINE_ZOOKEEPER_SERVICE} #service
echo ${MINE_ZOOKEEPER_REPLICAS} #replica count
echo ${MINE_ZOOKEEPER_POD} #podname
ZK_HOSTNAME=$(hostname)
# 解析格式如下:"mine-zookeeper-service-2"
ZK_ID=$( echo ${T_HOSTNAME} | cut -d "-" -f4 | cut -d "-" -f3 )
ZK_HOST_POSTFIX="$MINE_ZOOKEEPER_SERVICE.$MINE_MIDWARE_NAMESPACE.svc.cluster.local"
if [[ -n "${MINE_ZOOKEEPER_REPLICAS-}" ]]
then
i=0
while [[ ${i} -lt ${MINE_ZOOKEEPER_REPLICAS} ]]; do
tmp=""
idx=$(($i+1))
if [[ ${i} -eq ${T_ID} ]]; then
tmp="server.$idx=0.0.0.0:2888:3888;2181 "
else
tmp="server.$idx=$MINE_ZOOKEEPER_POD-$i.$ZK_HOST_POSTFIX:2888:3888;2181 "
fi
ZOOKEEPER_SERVERS="$ZOOKEEPER_SERVERS$tmp"
i=$(( i + 1 ))
done
fi
ZOOKEEPER_SERVERS=${ZOOKEEPER_SERVERS%?}
export ZOO_MY_ID=$(($T_ID+1))
export ZOO_SERVERS=${ZOOKEEPER_SERVERS}
echo ${ZOO_MY_ID}
echo ${ZOO_SERVERS}
以上的脚本通过四个输入的环境变量
MINE_MIDWARE_NAMESPACE
MINE_ZOOKEEPER_SERVICE
MINE_ZOOKEEPER_REPLICAS
MINE_ZOOKEEPER_POD
来创建ZOO_SERVERS和ZOO_MY_ID。这四个环境变量可以配置在StatefulSet中。创建了这个脚本之后,我们再扩展一下官方的Dockerfile:
FROM zookeeper:3.5
MAINTAINER simon.zhu.chn@hotmail.com
ADD zk-config.sh /
ENTRYPOINT ["/bin/bash", "-c", "source zk-config.sh && /docker-entrypoint.sh"]
这样子我们就能再启动docker-entrypoint之前优先将环境变量输出提供给官方镜像使用。
4. 微该官方镜像
构建完镜像之后在K8S中启动集群,发现无法正常启动。
具体原因是因为官方镜像脚本的docker-entrypoint.sh最后一行中引用了CMD中的启动参数。因为我们已经修改了ENTRYPIONT,所以,官方镜像的启动配置的docker-entrypoint.sh也需要做修改。
首先我们去git上拉取官方的镜像源码,然后修改docker-entrypoint.sh脚本
#!/bin/bash
set -e
# Allow the container to be started with `--user`
if [[ "$1" = 'zkServer.sh' && "$(id -u)" = '0' ]]; then
chown -R zookeeper "$ZOO_DATA_DIR" "$ZOO_DATA_LOG_DIR" "$ZOO_LOG_DIR" "$ZOO_CONF_DIR"
exec gosu zookeeper "$0" "$@"
fi
# Generate the config only if it doesn't exist
if [[ ! -f "$ZOO_CONF_DIR/zoo.cfg" ]]; then
CONFIG="$ZOO_CONF_DIR/zoo.cfg"
echo "clientPort=2181" >> "$CONFIG"
echo "dataDir=$ZOO_DATA_DIR" >> "$CONFIG"
echo "dataLogDir=$ZOO_DATA_LOG_DIR" >> "$CONFIG"
echo "tickTime=$ZOO_TICK_TIME" >> "$CONFIG"
echo "initLimit=$ZOO_INIT_LIMIT" >> "$CONFIG"
echo "syncLimit=$ZOO_SYNC_LIMIT" >> "$CONFIG"
echo "autopurge.snapRetainCount=$ZOO_AUTOPURGE_SNAPRETAINCOUNT" >> "$CONFIG"
echo "autopurge.purgeInterval=$ZOO_AUTOPURGE_PURGEINTERVAL" >> "$CONFIG"
echo "maxClientCnxns=$ZOO_MAX_CLIENT_CNXNS" >> "$CONFIG"
echo $ZOO_SERVERS
for server in $ZOO_SERVERS; do
echo ${server}
echo "$server" >> "$CONFIG"
done
fi
# Write myid only if it doesn't exist
if [[ ! -f "$ZOO_DATA_DIR/myid" ]]; then
echo "${ZOO_MY_ID:-1}" > "$ZOO_DATA_DIR/myid"
fi
#注释掉了下面的这行,因为CMD方式启动出错
#exec "$@"
#检查配置写入情况
cat $CONFIG
#添加这一行,直接使用脚本
zkServer.sh start-foreground
修改完之后,我们重新构建官方镜像:
docker build -t szzookeeper:3.5 .
构建完毕之后再次修改我们自定义构建镜像的文件,将引用镜像设置为我们刚构建好的小改官方镜像:
FROM szzookeeper:3.5
MAINTAINER simon.zhu.chn@hotmail.com
ADD zk-config.sh /
ENTRYPOINT ["/bin/bash", "-c", "source /zk-config.sh && /docker-entrypoint.sh"]
构建,提交至docker hub.
5. K8S启动ZooKeeper集群效果
这里我用Rancher搭建了K8S的两台机器集群,可以看到POD分布在了不同的NODE上
image.png进入任意一个POD,使用命令查看ZooKeeper集群状态:可以看到这是一个follower节点
网友评论