运行ZooKeeper,一个分布式系统协调器
目标
在本教程之后,您将了解以下内容。
- 如何使用
StatefulSet
部署ZooKeeper集合
。 - 如何使用
ConfigMaps
一致地配置集合。 - 如何在集合中扩展
ZooKeeper
服务器的部署。 - 如何使用
PodDisruptionBudgets
确保计划维护期间的服务可用性。
创建ZooKeeper
下面的清单包含Headless Service
,Service
,PodDisruptionBudget
和StatefulSet
。
apiVersion: v1
kind: Service
metadata:
name: zk-hs
labels:
app: zk
spec:
ports:
- port: 2888
name: server
- port: 3888
name: leader-election
clusterIP: None
selector:
app: zk
---
apiVersion: v1
kind: Service
metadata:
name: zk-cs
labels:
app: zk
spec:
ports:
- port: 2181
name: client
selector:
app: zk
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
selector:
matchLabels:
app: zk
maxUnavailable: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zk
spec:
selector:
matchLabels:
app: zk
serviceName: zk-hs
replicas: 3
updateStrategy:
type: RollingUpdate
podManagementPolicy: Parallel
template:
metadata:
labels:
app: zk
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk
topologyKey: "kubernetes.io/hostname"
containers:
- name: kubernetes-zookeeper
imagePullPolicy: Always
image: "kubebiz/zookeeper:3.4.10"
resources:
requests:
memory: "1Gi"
cpu: "0.5"
ports:
- containerPort: 2181
name: client
- containerPort: 2888
name: server
- containerPort: 3888
name: leader-election
command:
- sh
- -c
- "start-zookeeper \
--servers=3 \
--data_dir=/var/lib/zookeeper/data \
--data_log_dir=/var/lib/zookeeper/data/log \
--conf_dir=/opt/zookeeper/conf \
--client_port=2181 \
--election_port=3888 \
--server_port=2888 \
--tick_time=2000 \
--init_limit=10 \
--sync_limit=5 \
--heap=512M \
--max_client_cnxns=60 \
--snap_retain_count=3 \
--purge_interval=12 \
--max_session_timeout=40000 \
--min_session_timeout=4000 \
--log_level=INFO"
readinessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 10
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: datadir
mountPath: /var/lib/zookeeper
securityContext:
runAsUser: 1000
fsGroup: 1000
volumeClaimTemplates:
- metadata:
name: datadir
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
打开终端,然后使用kubectl apply命令创建清单。
kubectl apply -f https://www.kubebiz.com/raw/KubeBiz/zookeeper/3.4.10/all?namespace=default
这将创建Headless Service为zk-hs
,Service为zk-cs
,PodDisruptionBudget是zk-pdb
,StatefulSet为zk
。
service/zk-hs created
service/zk-cs created
poddisruptionbudget.policy/zk-pdb created
statefulset.apps/zk created
使用kubectl get
来获取StatefulSet控制器创建StatefulSet
的Pod
。
kubectl get pods -w -l app=zk
一旦zk-2
运行并准备就绪,使用CTRL-C
终止kubectl
。
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 19s
zk-0 1/1 Running 0 40s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 ContainerCreating 0 0s
zk-1 0/1 Running 0 18s
zk-1 1/1 Running 0 40s
zk-2 0/1 Pending 0 0s
zk-2 0/1 Pending 0 0s
zk-2 0/1 ContainerCreating 0 0s
zk-2 0/1 Running 0 19s
zk-2 1/1 Running 0 40s
StatefulSet控制器创建三个Pod,每个Pod都有一个带ZooKeeper服务器的容器。
Leader 选举
由于在匿名网络中选择leader没有终止算法,因此Zab
需要显式成员资格配置来执行leader选举。集合中的每个服务器都需要具有唯一标识符,所有服务器都需要知道全局标识符集,并且每个标识符需要与网络地址相关联。
使用kubectl exec
获取zk StatefulSet
中Pod
的主机名。
for i in 0 1 2; do kubectl exec zk-$i -- hostname; done
StatefulSet
控制器根据其序数索引为每个Pod提供唯一的主机名。主机名采用<statefulset name> - <ordinal index>
的形式。 由于zk StatefulSet的副本字段设置为3,因此Set的控制器创建三个Pod,其主机名设置为zk-0
,zk-1
和zk-2
。
zk-0
zk-1
zk-2
ZooKeeper集合中的服务器使用自然数作为唯一标识符,并将每个服务器的标识符存储在服务器数据目录中名为myid
的文件中。
要检查每个服务器的myid
文件的内容,请使用以下命令。
for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done
因为标识符是自然数,而序数索引是非负整数,所以可以通过向序数加1来生成标识符。
myid zk-0
1
myid zk-1
2
myid zk-2
3
要获取zk StatefulSet中每个Pod的完全限定域名(FQDN),请使用以下命令。
for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done
zk-hs
服务为所有Pod创建一个域,
zk-hs.default.svc.cluster.local.
zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local
Kubernetes DNS
中的A记录将FQDN解析为Pod的IP地址。如果Kubernetes重新调度Pod,它将使用Pod的新IP地址更新A记录,但A记录名称不会更改。
ZooKeeper将其应用程序配置存储在名为zoo.cfg
的文件中。 使用kubectl exec
查看zk-0
Pod中zoo.cfg
文件的内容。
kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg
在文件底部的server.1
,server.2
和server.3
属性中,1
,2
和3
对应于ZooKeeper服务器的myid
文件中的标识符。 它们被设置为zk
StatefulSet中Pod的FQDN。
clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888
达成共识
一致性协议要求每个参与者的标识符唯一。 在 Zab 协议里任何两个参与者都不应该声明相同的唯一标识符。 对于让系统中的进程协商哪些进程已经提交了哪些数据而言,这是必须的。 如果有两个 Pod 使用相同的序号启动,这两个 ZooKeeper 服务器会将自己识别为相同的服务器。
kubectl get pods -w -l app=zk
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 19s
zk-0 1/1 Running 0 40s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 ContainerCreating 0 0s
zk-1 0/1 Running 0 18s
zk-1 1/1 Running 0 40s
zk-2 0/1 Pending 0 0s
zk-2 0/1 Pending 0 0s
zk-2 0/1 ContainerCreating 0 0s
zk-2 0/1 Running 0 19s
zk-2 1/1 Running 0 40s
每个 Pod 的 A 记录仅在 Pod 变成 Ready 状态时被录入。因此,ZooKeeper 服务器的 FQDN 只会解析到一个端点,而那个端点将是申领其 myid 文件中所配置标识的唯一 ZooKeeper 服务器。
zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local
这保证了 ZooKeeper 的 zoo.cfg 文件中的 servers 属性代表了一个正确配置的 ensemble。
server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888
当服务器使用 Zab 协议尝试提交一个值的时候,它们会达成一致并成功提交这个值 (如果领导者选举成功并且至少有两个 Pod 处于 Running 和 Ready 状态), 或者将会失败(如果没有满足上述条件中的任意一条)。 当一个服务器承认另一个服务器的代写时不会有状态产生。
测试
最基本的健康检查是向一个 ZooKeeper 服务器写入一些数据,然后从另一个服务器读取这些数据。
使用 zkCli.sh
脚本在 zk-0
Pod 上写入 world
到路径 /hello
。
kubectl exec zk-0 zkCli.sh create /hello world
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
Created /hello
从zk-1
获取数据。
kubectl exec zk-1 zkCli.sh get /hello
你在zk-0
上创建的数据在所有服务器上都可用。
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
持久存储
使用kubectl delete
删除zk StatefulSet
。
kubectl delete statefulset zk
statefulset.apps "zk" deleted
观察StatefulSet中Pod的终止。
kubectl get pods -w -l app=zk
当zk-0
完全终止时,使用CTRL-C
终止kubectl
。
zk-2 1/1 Terminating 0 9m
zk-0 1/1 Terminating 0 11m
zk-1 1/1 Terminating 0 10m
zk-2 0/1 Terminating 0 9m
zk-2 0/1 Terminating 0 9m
zk-2 0/1 Terminating 0 9m
zk-1 0/1 Terminating 0 10m
zk-1 0/1 Terminating 0 10m
zk-1 0/1 Terminating 0 10m
zk-0 0/1 Terminating 0 11m
zk-0 0/1 Terminating 0 11m
zk-0 0/1 Terminating 0 11m
重新apply zookeeper.yaml。
kubectl apply -f https://www.kubebiz.com/raw/KubeBiz/zookeeper/3.4.10/StatefulSet.yaml?namespace=default
观察StatefulSet
控制器重新创建StatefulSet
的Pod
。
kubectl get pods -w -l app=zk
Once the zk-2 Pod is Running and Ready, use CTRL-C to terminate kubectl.
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 19s
zk-0 1/1 Running 0 40s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 ContainerCreating 0 0s
zk-1 0/1 Running 0 18s
zk-1 1/1 Running 0 40s
zk-2 0/1 Pending 0 0s
zk-2 0/1 Pending 0 0s
zk-2 0/1 ContainerCreating 0 0s
zk-2 0/1 Running 0 19s
zk-2 1/1 Running 0 40s
使用以下命令从zk-2 Pod
获取之前创建的值。
kubectl exec zk-2 zkCli.sh get /hello
即使你终止并重新创建了zk StatefulSet
中的所有Pod
,但数据依然在。
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
zk StatefulSet规范的volumeClaimTemplates
字段指定为每个Pod配置的PersistentVolume
。
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 20Gi
StatefulSet控制器为每个Pod生成一个PersistentVolumeClaim
。
使用以下命令获取StatefulSet的PersistentVolumeClaims
。
kubectl get pvc -l app=zk
When the StatefulSet recreated its Pods, it remounts the Pods’ PersistentVolumes.
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
datadir-zk-0 Bound pvc-bed742cd-bcb1-11e6-994f-42010a800002 20Gi RWO 1h
datadir-zk-1 Bound pvc-bedd27d2-bcb1-11e6-994f-42010a800002 20Gi RWO 1h
datadir-zk-2 Bound pvc-bee0817e-bcb1-11e6-994f-42010a800002 20Gi RWO 1h
StatefulSet 的容器 template 中的 volumeMounts 使得 PersistentVolume 被挂载到 ZooKeeper 服务器的数据目录。
volumeMounts:
- name: datadir
mountPath: /var/lib/zookeeper
当(重新)调度zk StatefulSet中的Pod时,它将始终具有安装到ZooKeeper服务器的数据目录的相同PersistentVolume
。 即使重新调度Pod,对ZooKeeper服务器的WAL
及其所有快照的所有写入都保持持久。
确保一致的配置
如“促进leader选举和实现共识”部分所述,ZooKeeper集合中的服务器需要一致的配置来选举领导者并形成法定人数。 它们还需要一致配置Zab协议,以使协议在网络上正常工作。在我们的示例中,我们通过将配置直接嵌入清单来实现一致的配置。
获取zk StatefulSet。
kubectl get sts zk -o yaml
…
command:
- sh
- -c
- "start-zookeeper \
--servers=3 \
--data_dir=/var/lib/zookeeper/data \
--data_log_dir=/var/lib/zookeeper/data/log \
--conf_dir=/opt/zookeeper/conf \
--client_port=2181 \
--election_port=3888 \
--server_port=2888 \
--tick_time=2000 \
--init_limit=10 \
--sync_limit=5 \
--heap=512M \
--max_client_cnxns=60 \
--snap_retain_count=3 \
--purge_interval=12 \
--max_session_timeout=40000 \
--min_session_timeout=4000 \
--log_level=INFO"
…
用于启动 ZooKeeper 服务器的命令将这些配置作为命令行参数传给了 ensemble。 你也可以通过环境变量来传入这些配置。
配置日志
zkGenConfig.sh 脚本产生的一个文件控制了 ZooKeeper 的日志行为。 ZooKeeper 使用了 Log4j 并默认使用基于文件大小和时间的滚动文件追加器作为日志配置。
从 zk StatefulSet 的一个 Pod 中获取日志配置。
kubectl exec zk-0 cat /usr/etc/zookeeper/log4j.properties
下面的日志记录配置将导致ZooKeeper进程将其所有日志写入标准输出文件流。
zookeeper.root.logger=CONSOLE
zookeeper.console.threshold=INFO
log4j.rootLogger=${zookeeper.root.logger}
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
这是在容器里安全记录日志的最简单的方法。 由于应用的日志被写入标准输出,Kubernetes 将会为你处理日志轮转。 Kubernetes 还实现了一个智能保存策略, 保证写入标准输出和标准错误流的应用日志不会耗尽本地存储介质。
使用命令 kubectl logs 从一个 Pod 中取回最后 20 行日志。
kubectl logs zk-0 --tail 20
您可以使用kubectl logs
和Kubernetes Dashboard
查看写入标准输出或标准错误的应用程序日志。
2016-12-06 19:34:16,236 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740
2016-12-06 19:34:16,237 [myid:1] - INFO [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client)
2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749
2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749
2016-12-06 19:34:26,156 [myid:1] - INFO [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client)
2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750
2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750
2016-12-06 19:34:26,226 [myid:1] - INFO [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client)
2016-12-06 19:34:36,151 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client)
2016-12-06 19:34:36,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client)
2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client)
2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client)
Kubernetes支持与Stackdriver,Elasticsearch和Kibana进行更强大更复杂的日志记录集成。对于集群级日志传送和聚合,可以考虑部署sidecar容器以轮询和发送日志。
配置非特权用户
允许应用程序作为特权用户在容器内运行的最佳实践是一个有争议的问题。如果你们要求应用程序作为非特权用户运行,则可以使用SecurityContext
来控制入口点运行的用户。
zk
StatefulSet 的 Pod 的 template
包含了一个 SecurityContext。
securityContext:
runAsUser: 1000
fsGroup: 1000
在Pods的容器中,UID 1000对应于zookeeper用户,GID 1000对应于zookeeper组。
从zk-0 Pod
获取ZooKeeper
进程信息。
kubectl exec zk-0 -- ps -elf
由于securityContext对象的runAsUser
字段设置为1000,而不是以root身份运行,因此ZooKeeper进程作为zookeeper用户运行。
F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
4 S zookeep+ 1 0 0 80 0 - 1127 - 20:46 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
0 S zookeep+ 27 1 0 80 0 - 1155556 - 20:46 ? 00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg
默认情况下,当Pod的PersistentVolumes挂载到ZooKeeper服务器的数据目录时,只有root用户才能访问它。 此配置可防止ZooKeeper进程写入其WAL并存储其快照。
使用以下命令获取zk-0 Pod上ZooKeeper数据目录的文件权限。
kubectl exec -ti zk-0 -- ls -ld /var/lib/zookeeper/data
由于 securityContext 对象的 fsGroup 字段设置为 1000, Pod 的 PersistentVolume 的所有权属于 zookeeper 用户组, 因而 ZooKeeper 进程能够成功地读写数据。
drwxr-sr-x 3 zookeeper zookeeper 4096 Dec 5 20:45 /var/lib/zookeeper/data
管理 ZooKeeper 进程
ZooKeeper文档提到“你将需要一个管理每个ZooKeeper服务器进程(JVM)的监督进程。” 利用监视程序(监督进程)重新启动分布式系统中的失败进程是一种常见的模式。在Kubernetes中部署应用程序时,不应使用外部实用程序作为监督过程,而应使用Kubernetes作为应用程序的监视程序。
更新
zk StatefulSet
配置为使用RollingUpdate
更新策略。
您可以使用kubectl patch
来更新分配给服务器的cpu
数量。
kubectl patch sts zk --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]'
statefulset.apps/zk patched
使用kubectl rollout status
来监控更新的状态。
kubectl rollout status sts/zk
waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
statefulset rolling update complete 3 pods at revision zk-5db4499664...
这将按顺序以反向顺序终止Pod
,并使用新配置重新创建它们。这可确保在滚动更新期间维护quorum
。
使用kubectl rollout history
命令查看历史记录或以前的配置。
kubectl rollout history sts/zk
输出类似于:
statefulsets "zk"
REVISION
1
2
使用kubectl rollout undo
命令回滚修改。
kubectl rollout undo sts/zk
输出类似于:
statefulset.apps/zk rolled back
处理过程失败
重启策略 控制 Kubernetes 如何处理一个 Pod 中容器入口点的进程故障。对于 StatefulSet 中的 Pods 来说,Always
是唯一合适的 RestartPolicy
,也是默认值。你不应该覆盖有状态应用的默认策略。
检查 zk-0
Pod 中运行的 ZooKeeper 服务器的进程树
kubectl exec zk-0 -- ps -ef
作为容器入口点的命令的 PID 为 1,Zookeeper 进程是入口点的子进程,PID 为 27。
UID PID PPID C STIME TTY TIME CMD
zookeep+ 1 0 0 15:03 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
zookeep+ 27 1 0 15:03 ? 00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg
在一个终端观察 zk
StatefulSet
中的 Pods。
kubectl get pod -w -l app=zk
在另一个终端杀掉 Pod zk-0
中的 ZooKeeper 进程。
kubectl exec zk-0 -- pkill java
ZooKeeper 进程的终结导致了它父进程的终止。由于容器的 RestartPolicy
是 Always
,父进程被重启。
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 0 21m
zk-1 1/1 Running 0 20m
zk-2 1/1 Running 0 19m
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Error 0 29m
zk-0 0/1 Running 1 29m
zk-0 1/1 Running 1 29m
如果你的应用使用一个脚本(例如 zkServer.sh
)来启动一个实现了应用业务逻辑的进程, 这个脚本必须和子进程一起结束。这保证了当实现应用业务逻辑的进程故障时, Kubernetes 会重启这个应用的容器。
存活测试
你的应用配置为自动重启故障进程,但这对于保持一个分布式系统的健康来说是不够的。许多场景下,一个系统进程可以是活动状态但不响应请求,或者是不健康状态。 你应该使用存活性探针来通知 Kubernetes 你的应用进程处于不健康状态,需要被重启。
zk StatefulSet 的 Pod 的 template 一节指定了一个存活探针。
livenessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 15
timeoutSeconds: 5
这个探针调用一个简单的 Bash 脚本,使用 ZooKeeper 的四字缩写 ruok
来测试服务器的健康状态。
OK=$(echo ruok | nc 127.0.0.1 $1)
if [ "$OK" == "imok" ]; then
exit 0
else
exit 1
fi
在一个终端窗口中使用下面的命令观察 zk
StatefulSet 中的 Pods。
kubectl get pod -w -l app=zk
在另一个窗口中,从 Pod zk-0
的文件系统中删除 zookeeper-ready 脚本。
kubectl exec zk-0 -- rm /usr/bin/zookeeper-ready
当 ZooKeeper 进程的存活探针探测失败时,Kubernetes 将会为你自动重启这个进程,从而保证 ensemble 中不健康状态的进程都被重启。
kubectl get pod -w -l app=zk
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 0 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Running 0 1h
zk-0 0/1 Running 1 1h
zk-0 1/1 Running 1 1h
Testing for Readiness
就绪测试
就绪不同于存活。如果一个进程是存活的,它是可调度和健康的。如果一个进程是就绪的,它应该能够处理输入。存活是就绪的必要非充分条件。 在许多场景下,特别是初始化和终止过程中,一个进程可以是存活但没有就绪的。
如果你指定了一个就绪探针,Kubernetes 将保证在就绪检查通过之前, 你的应用不会接收到网络流量。
对于一个 ZooKeeper 服务器来说,存活即就绪。 因此 zookeeper.yaml
清单中的就绪探针和存活探针完全相同。
readinessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 15
timeoutSeconds: 5
虽然存活探针和就绪探针是相同的,但同时指定它们两者仍然重要。这保证了 ZooKeeper ensemble 中只有健康的服务器能接收网络流量。
容忍节点故障(Tolerating Node Failure)
ZooKeeper 需要一个 quorum 来提交数据变动。对于一个拥有 3 个服务器的 ensemble 来说, 必须有两个服务器是健康的,写入才能成功。 在基于 quorum 的系统里,成员被部署在多个故障域中以保证可用性。 为了防止由于某台机器断连引起服务中断,最佳实践是防止应用的多个实例在相同的机器上共存。
默认情况下,Kubernetes 可以把 StatefulSet 的 Pods 部署在相同节点上。 对于你创建的 3 个服务器的 ensemble 来说,如果有两个服务器并存于相同的节点上并且该节点发生故障时,ZooKeeper 服务将中断,直至至少一个 Pods 被重新调度。
你应该总是提供多余的容量以允许关键系统进程在节点故障时能够被重新调度。如果你这样做了,服务故障就只会持续到 Kubernetes 调度器重新调度某个 ZooKeeper 服务器为止。 但是,如果希望你的服务在容忍节点故障时无停服时间,你应该设置podAntiAffinity
。
获取 zk Stateful Set 中的 Pods 的节点。
for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done
zk
StatefulSet
中所有的 Pods 都被部署在不同的节点。
kubernetes-minion-group-cxpk
kubernetes-minion-group-a5aq
kubernetes-minion-group-2g2d
这是因为 zk StatefulSet
中的 Pods 指定了 PodAntiAffinity
。
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk
topologyKey: "kubernetes.io/hostname"
requiredDuringSchedulingIgnoredDuringExecution
告诉 Kubernetes 调度器, 在以 topologyKey
指定的域中,绝对不要把带有键为 app、值为 zk 的标签 的两个 Pods 调度到相同的节点。topologyKey
kubernetes.io/hostname
表示 这个域是一个单独的节点。 使用不同的规则、标签和选择算符,你能够通过这种技术把你的 ensemble 分布 在不同的物理、网络和电力故障域之间。
节点维护期间保持应用可用(Surviving Maintenance)
在本节中你将会隔离(Cordon)和腾空(Drain)节点。 如果你是在一个共享的集群里使用本教程,请保证不会影响到其他租户。
上一小节展示了如何在节点之间分散 Pods 以在计划外的节点故障时保证服务存活。 但是你也需要为计划内维护引起的临时节点故障做准备。
使用此命令获取你的集群中的节点。
kubectl get nodes
使用 kubectl cordon
隔离你的集群中除 4 个节点以外的所有节点。
kubectl cordon <node-name>
使用下面的命令获取 zk-pdb
PodDisruptionBudget
。
kubectl get pdb zk-pdb
max-unavailable
字段指示 Kubernetes 在任何时候,zk StatefulSet 至多有一个 Pod 是不可用的。
NAME MIN-AVAILABLE MAX-UNAVAILABLE ALLOWED-DISRUPTIONS AGE
zk-pdb N/A 1 1
在一个终端中,使用这个命令来观察zk StatefulSet
中的Pods
。
kubectl get pods -w -l app=zk
在另一个终端中,使用下面的命令获取 Pods 当前调度的节点。
for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done
kubernetes-minion-group-pb41
kubernetes-minion-group-ixsl
kubernetes-minion-group-i4c4
使用 kubectl drain
来隔离和腾空 zk-0 Pod
调度所在的节点。
kubectl drain $(kubectl get pod zk-0 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-local-data
node "kubernetes-minion-group-pb41" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-minion-group-pb41, kube-proxy-kubernetes-minion-group-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz
pod "zk-0" deleted
node "kubernetes-minion-group-pb41" drained
由于你的集群中有 4 个节点, kubectl drain
执行成功,zk-0
被调度到其它节点。
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 2 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 51s
zk-0 1/1 Running 0 1m
在第一个终端中持续观察 StatefulSet
的 Pods
并腾空 zk-1
调度所在的节点。
kubectl drain $(kubectl get pod zk-1 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-local-data "kubernetes-minion-group-ixsl" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-minion-group-ixsl, kube-proxy-kubernetes-minion-group-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74
pod "zk-1" deleted
node "kubernetes-minion-group-ixsl" drained
zk-1
Pod 不能被调度,这是因为 zk StatefulSet 包含了一个防止 Pods 共存的 PodAntiAffinity 规则,而且只有两个节点可用于调度,这个 Pod 将保持在 Pending 状态。
kubectl get pods -w -l app=zk
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 2 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 51s
zk-0 1/1 Running 0 1m
zk-1 1/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
继续观察 StatefulSet 中的 Pods 并腾空 zk-2 调度所在的节点。
kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-local-data
node "kubernetes-minion-group-i4c4" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-minion-group-i4c4, kube-proxy-kubernetes-minion-group-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-minion-group-i4c4, kube-proxy-kubernetes-minion-group-i4c4
There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget.
pod/zk-2
使用 CRTL-C
终止 kubectl。
你不能腾空第三个节点,因为驱逐 zk-2
将和 zk-budget
冲突。 然而这个节点仍然处于隔离状态(Cordoned)。
使用 zkCli.sh
从 zk-0
取回你的健康检查中输入的数值。
kubectl exec zk-0 zkCli.sh get /hello
由于遵守了 PodDisruptionBudget,服务仍然可用。
WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x200000002
ctime = Wed Dec 07 00:08:59 UTC 2016
mZxid = 0x200000002
mtime = Wed Dec 07 00:08:59 UTC 2016
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
使用 kubectl uncordon
来取消对第一个节点的隔离。
kubectl uncordon kubernetes-minion-group-pb41
node "kubernetes-minion-group-pb41" uncordoned
zk-1
被重新调度到了这个节点。等待zk-1
变为 Running 和 Ready 状态。
kubectl get pods -w -l app=zk
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 2 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 51s
zk-0 1/1 Running 0 1m
zk-1 1/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 12m
zk-1 0/1 ContainerCreating 0 12m
zk-1 0/1 Running 0 13m
zk-1 1/1 Running 0 13m
尝试 drain zk-2 调度的节点。
kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-local-data
输出:
node "kubernetes-minion-group-i4c4" already cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-minion-group-i4c4, kube-proxy-kubernetes-minion-group-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
pod "heapster-v1.2.0-2604621511-wht1r" deleted
pod "zk-2" deleted
node "kubernetes-minion-group-i4c4" drained
这次 kubectl drain
执行成功。
Uncordon 第二个节点以允许 zk-2
被重新调度。
kubectl uncordon kubernetes-minion-group-ixsl
node "kubernetes-minion-group-ixsl" uncordoned
你可以同时使用 kubectl drain
和 PodDisruptionBudgets
来保证你的服务在维护过程中仍然可用。如果使用 drain 来隔离节点并在此之前删除 pods 使节点进入离线维护状态,如果服务表达了 disruption budget,这个 budget 将被遵守。你应该总是为关键服务分配额外容量,这样它们的 Pods 就能够迅速的重新调度。
清理
使用
kubectl uncordon
解除集群中所有节点的隔离。你需要删除在本教程中使用的
PersistentVolumes
的持久存储。基于你的环境、存储配置和准备方法,保证回收所有的存储。
相关链接
https://kubernetes.io/docs/tutorials/stateful-application/zookeeper
https://www.kubebiz.com/KubeBiz/zookeeper?version=3.4.10
service/zk-hs unchanged service/zk-cs unchanged statefulset.apps/zk configured error: unable to recognize "zookeeper.yaml": no matches for kind "PodDisruptionBudget" in version "policy/v1"
kubectl api-versions | grep policy
查看你kubernetes支持的对应版本号
感谢分享,我在创建 zookeeper.yaml后,zk-0 一直处于pending状态
1 node(s) didn't find available persistent volumes to bind.
请教下这可能是什么原因呢,设置了默认
sc
,谢谢![root@hadoop03 hadoop]# kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE manual (default) kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 28h
不用纠结,就往
volumeClaimTemplates
,volumeMounts
认真的排查。可参考:https://www.kubebiz.com/KubeBiz/zookeeper
zk的镜像该怎么下载啊
https://www.kubebiz.com/KubeBiz/zookeeper