505 lines
20 KiB
Markdown
505 lines
20 KiB
Markdown
# StatefulSet
|
||
|
||
StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序。
|
||
|
||
使用案例参考:[kubernetes contrib - statefulsets](https://github.com/kubernetes/contrib/tree/master/statefulsets),其中包含zookeeper和kakfa的statefulset设置和使用说明。
|
||
|
||
StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:
|
||
|
||
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
|
||
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
|
||
- 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
|
||
- 有序收缩,有序删除(即从N-1到0)
|
||
|
||
从上面的应用场景可以发现,StatefulSet由以下几个部分组成:
|
||
|
||
- 用于定义网络标志(DNS domain)的Headless Service
|
||
- 用于创建PersistentVolumes的volumeClaimTemplates
|
||
- 定义具体应用的StatefulSet
|
||
|
||
StatefulSet中每个Pod的DNS格式为`statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local`,其中
|
||
|
||
- `serviceName`为Headless Service的名字
|
||
- `0..N-1`为Pod所在的序号,从0开始到N-1
|
||
- `statefulSetName`为StatefulSet的名字
|
||
- `namespace`为服务所在的namespace,Headless Servic和StatefulSet必须在相同的namespace
|
||
- `.cluster.local`为Cluster Domain
|
||
|
||
## 使用 StatefulSet
|
||
|
||
StatefulSet 适用于有以下某个或多个需求的应用:
|
||
|
||
- 稳定,唯一的网络标志。
|
||
- 稳定,持久化存储。
|
||
- 有序,优雅地部署和 scale。
|
||
- 有序,优雅地删除和终止。
|
||
- 有序,自动的滚动升级。
|
||
|
||
在上文中,稳定是 Pod (重新)调度中持久性的代名词。 如果应用程序不需要任何稳定的标识符、有序部署、删除和 scale,则应该使用提供一组无状态副本的 controller 来部署应用程序,例如 [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment) 或 [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset) 可能更适合您的无状态需求。
|
||
|
||
## 限制
|
||
|
||
- StatefulSet 是 beta 资源,Kubernetes 1.5 以前版本不支持。
|
||
- 对于所有的 alpha/beta 的资源,您都可以通过在 apiserver 中设置 `--runtime-config` 选项来禁用。
|
||
- 给定 Pod 的存储必须由 PersistentVolume Provisioner 根据请求的 `storage class` 进行配置,或由管理员预先配置。
|
||
- 删除或 scale StatefulSet 将_不会_删除与 StatefulSet 相关联的 volume。 这样做是为了确保数据安全性,这通常比自动清除所有相关 StatefulSet 资源更有价值。
|
||
- StatefulSets 目前要求 [Headless Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) 负责 Pod 的网络身份。 您有责任创建此服务。
|
||
|
||
## 组件
|
||
|
||
下面的示例中描述了 StatefulSet 中的组件。
|
||
|
||
- 一个名为 nginx 的 headless service,用于控制网络域。
|
||
- 一个名为 web 的 StatefulSet,它的 Spec 中指定在有 3 个运行 nginx 容器的 Pod。
|
||
- volumeClaimTemplates 使用 PersistentVolume Provisioner 提供的 [PersistentVolumes](https://kubernetes.io/docs/concepts/storage/volumes) 作为稳定存储。
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: nginx
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
ports:
|
||
- port: 80
|
||
name: web
|
||
clusterIP: None
|
||
selector:
|
||
app: nginx
|
||
---
|
||
apiVersion: apps/v1beta1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: web
|
||
spec:
|
||
serviceName: "nginx"
|
||
replicas: 3
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
terminationGracePeriodSeconds: 10
|
||
containers:
|
||
- name: nginx
|
||
image: gcr.io/google_containers/nginx-slim:0.8
|
||
ports:
|
||
- containerPort: 80
|
||
name: web
|
||
volumeMounts:
|
||
- name: www
|
||
mountPath: /usr/share/nginx/html
|
||
volumeClaimTemplates:
|
||
- metadata:
|
||
name: www
|
||
annotations:
|
||
volume.beta.kubernetes.io/storage-class: anything
|
||
spec:
|
||
accessModes: [ "ReadWriteOnce" ]
|
||
resources:
|
||
requests:
|
||
storage: 1Gi
|
||
```
|
||
|
||
## Pod 身份
|
||
|
||
StatefulSet Pod 具有唯一的身份,包括序数,稳定的网络身份和稳定的存储。 身份绑定到 Pod 上,不管它(重新)调度到哪个节点上。
|
||
|
||
### 序数
|
||
|
||
对于一个有 N 个副本的 StatefulSet,每个副本都会被指定一个整数序数,在 [0,N)之间,且唯一。
|
||
|
||
## 稳定的网络 ID
|
||
|
||
StatefulSet 中的每个 Pod 从 StatefulSet 的名称和 Pod 的序数派生其主机名。构造的主机名的模式是`$(statefulset名称)-$(序数)`。 上面的例子将创建三个名为`web-0,web-1,web-2`的 Pod。
|
||
|
||
StatefulSet 可以使用 [Headless Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) 来控制其 Pod 的域。此服务管理的域的格式为:`$(服务名称).$(namespace).svc.cluster.local`,其中 “cluster.local” 是集群域。
|
||
|
||
在创建每个Pod时,它将获取一个匹配的 DNS 子域,采用以下形式:`$(pod 名称).$(管理服务域)`,其中管理服务由 StatefulSet 上的 `serviceName` 字段定义。
|
||
|
||
以下是 Cluster Domain,服务名称,StatefulSet 名称以及如何影响 StatefulSet 的 Pod 的 DNS 名称的一些示例。
|
||
|
||
| Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
|
||
| -------------- | ----------------- | --------------------- | ------------------------------- | ---------------------------------------- | ------------ |
|
||
| cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
|
||
| cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0..N-1}.nginx.foo.svc.cluster.local | web-{0..N-1} |
|
||
| kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0..N-1}.nginx.foo.svc.kube.local | web-{0..N-1} |
|
||
|
||
注意 Cluster Domain 将被设置成 `cluster.local` 除非进行了其他配置。
|
||
|
||
### 稳定存储
|
||
|
||
Kubernetes 为每个 VolumeClaimTemplate 创建一个 [PersistentVolume](https://kubernetes.io/docs/concepts/storage/volumes)。上面的 nginx 的例子中,每个 Pod 将具有一个由 `anything` 存储类创建的 1 GB 存储的 PersistentVolume。当该 Pod (重新)调度到节点上,`volumeMounts` 将挂载与 PersistentVolume Claim 相关联的 PersistentVolume。请注意,与 PersistentVolume Claim 相关联的 PersistentVolume 在 产出 Pod 或 StatefulSet 的时候不会被删除。这必须手动完成。
|
||
|
||
## 部署和 Scale 保证
|
||
|
||
- 对于有 N 个副本的 StatefulSet,Pod 将按照 {0..N-1} 的顺序被创建和部署。
|
||
- 当 删除 Pod 的时候,将按照逆序来终结,从{N-1..0}
|
||
- 对 Pod 执行 scale 操作之前,它所有的前任必须处于 Running 和 Ready 状态。
|
||
- 在终止 Pod 前,它所有的继任者必须处于完全关闭状态。
|
||
|
||
不应该将 StatefulSet 的 `pod.Spec.TerminationGracePeriodSeconds` 设置为 0。这样是不安全的且强烈不建议您这样做。进一步解释,请参阅 [强制删除 StatefulSet Pod](https://kubernetes.io/docs/tasks/run-application/force-delete-stateful-set-pod)。
|
||
|
||
上面的 nginx 示例创建后,3 个 Pod 将按照如下顺序创建 web-0,web-1,web-2。在 web-0 处于 [运行并就绪](https://kubernetes.io/docs/user-guide/pod-states) 状态之前,web-1 将不会被部署,同样当 web-1 处于运行并就绪状态之前 web-2也不会被部署。如果在 web-1 运行并就绪后,web-2 启动之前, web-0 失败了,web-2 将不会启动,直到 web-0 成功重启并处于运行并就绪状态。
|
||
|
||
如果用户通过修补 StatefulSet 来 scale 部署的示例,以使 `replicas=1`,则 web-2 将首先被终止。 在 web-2 完全关闭和删除之前,web-1 不会被终止。 如果 web-0 在 web-2 终止并且完全关闭之后,但是在 web-1 终止之前失败,则 web-1 将不会终止,除非 web-0 正在运行并准备就绪。
|
||
|
||
### Pod 管理策略
|
||
|
||
在 Kubernetes 1.7 和之后版本,StatefulSet 允许您放开顺序保证,同时通过 `.spec.podManagementPolicy` 字段保证身份的唯一性。
|
||
|
||
#### OrderedReady Pod 管理
|
||
|
||
StatefulSet 中默认使用的是 `OrderedReady` pod 管理。它实现了 [如上](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset.md#deployment-and-scaling-guarantees) 所述的行为。
|
||
|
||
#### 并行 Pod 管理
|
||
|
||
`Parallel` pod 管理告诉 StatefulSet controller 并行的启动和终止 Pod,在启动和终止其他 Pod 之前不会等待 Pod 变成 运行并就绪或完全终止状态。
|
||
|
||
## 更新策略
|
||
|
||
在 kubernetes 1.7 和以上版本中,StatefulSet 的 `.spec.updateStrategy` 字段允许您配置和禁用 StatefulSet 中的容器、label、resource request/limit、annotation 的滚动更新。
|
||
|
||
### 删除
|
||
|
||
`OnDelete` 更新策略实现了遗留(1.6和以前)的行为。 当 `spec.updateStrategy` 未指定时,这是默认策略。 当StatefulSet 的 `.spec.updateStrategy.type` 设置为 `OnDelete` 时,StatefulSet 控制器将不会自动更新 `StatefulSet` 中的 Pod。 用户必须手动删除 Pod 以使控制器创建新的 Pod,以反映对StatefulSet的 `.spec.template` 进行的修改。
|
||
|
||
### 滚动更新
|
||
|
||
`RollingUpdate` 更新策略在 StatefulSet 中实现 Pod 的自动滚动更新。 当StatefulSet的 `.spec.updateStrategy.type` 设置为 `RollingUpdate` 时,StatefulSet 控制器将在 StatefulSet 中删除并重新创建每个 Pod。 它将以与 Pod 终止相同的顺序进行(从最大的序数到最小的序数),每次更新一个 Pod。 在更新其前身之前,它将等待正在更新的 Pod 状态变成正在运行并就绪。
|
||
|
||
#### 分区
|
||
|
||
可以通过指定 `.spec.updateStrategy.rollingUpdate.partition` 来对 `RollingUpdate` 更新策略进行分区。如果指定了分区,则当 StatefulSet 的 `.spec.template` 更新时,具有大于或等于分区序数的所有 Pod 将被更新。具有小于分区的序数的所有 Pod 将不会被更新,即使删除它们也将被重新创建。如果 StatefulSet 的 `.spec.updateStrategy.rollingUpdate.partition` 大于其 `.spec.replicas`,则其 `.spec.template` 的更新将不会传播到 Pod。
|
||
|
||
在大多数情况下,您不需要使用分区,但如果您想要进行分阶段更新,使用金丝雀发布或执行分阶段发布,它们将非常有用。
|
||
|
||
## 简单示例
|
||
|
||
以一个简单的nginx服务[web.yaml](https://github.com/rootsongjc/kubernetes-handbook/blob/master/manifests/test/web.yaml)为例:
|
||
|
||
```yaml
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: nginx
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
ports:
|
||
- port: 80
|
||
name: web
|
||
clusterIP: None
|
||
selector:
|
||
app: nginx
|
||
---
|
||
apiVersion: apps/v1beta1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: web
|
||
spec:
|
||
serviceName: "nginx"
|
||
replicas: 2
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: gcr.io/google_containers/nginx-slim:0.8
|
||
ports:
|
||
- containerPort: 80
|
||
name: web
|
||
volumeMounts:
|
||
- name: www
|
||
mountPath: /usr/share/nginx/html
|
||
volumeClaimTemplates:
|
||
- metadata:
|
||
name: www
|
||
annotations:
|
||
volume.alpha.kubernetes.io/storage-class: anything
|
||
spec:
|
||
accessModes: [ "ReadWriteOnce" ]
|
||
resources:
|
||
requests:
|
||
storage: 1Gi
|
||
```
|
||
|
||
```sh
|
||
$ kubectl create -f web.yaml
|
||
service "nginx" created
|
||
statefulset "web" created
|
||
|
||
# 查看创建的headless service和statefulset
|
||
$ kubectl get service nginx
|
||
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||
nginx None <none> 80/TCP 1m
|
||
$ kubectl get statefulset web
|
||
NAME DESIRED CURRENT AGE
|
||
web 2 2 2m
|
||
|
||
# 根据volumeClaimTemplates自动创建PVC(在GCE中会自动创建kubernetes.io/gce-pd类型的volume)
|
||
$ kubectl get pvc
|
||
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
|
||
www-web-0 Bound pvc-d064a004-d8d4-11e6-b521-42010a800002 1Gi RWO 16s
|
||
www-web-1 Bound pvc-d06a3946-d8d4-11e6-b521-42010a800002 1Gi RWO 16s
|
||
|
||
# 查看创建的Pod,他们都是有序的
|
||
$ kubectl get pods -l app=nginx
|
||
NAME READY STATUS RESTARTS AGE
|
||
web-0 1/1 Running 0 5m
|
||
web-1 1/1 Running 0 4m
|
||
|
||
# 使用nslookup查看这些Pod的DNS
|
||
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
|
||
/ # nslookup web-0.nginx
|
||
Server: 10.0.0.10
|
||
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
||
|
||
Name: web-0.nginx
|
||
Address 1: 10.244.2.10
|
||
/ # nslookup web-1.nginx
|
||
Server: 10.0.0.10
|
||
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
||
|
||
Name: web-1.nginx
|
||
Address 1: 10.244.3.12
|
||
/ # nslookup web-0.nginx.default.svc.cluster.local
|
||
Server: 10.0.0.10
|
||
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
||
|
||
Name: web-0.nginx.default.svc.cluster.local
|
||
Address 1: 10.244.2.10
|
||
```
|
||
|
||
还可以进行其他的操作
|
||
|
||
```sh
|
||
# 扩容
|
||
$ kubectl scale statefulset web --replicas=5
|
||
|
||
# 缩容
|
||
$ kubectl patch statefulset web -p '{"spec":{"replicas":3}}'
|
||
|
||
# 镜像更新(目前还不支持直接更新image,需要patch来间接实现)
|
||
$ kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.7"}]'
|
||
|
||
# 删除StatefulSet和Headless Service
|
||
$ kubectl delete statefulset web
|
||
$ kubectl delete service nginx
|
||
|
||
# StatefulSet删除后PVC还会保留着,数据不再使用的话也需要删除
|
||
$ kubectl delete pvc www-web-0 www-web-1
|
||
```
|
||
|
||
## zookeeper
|
||
|
||
另外一个更能说明StatefulSet强大功能的示例为[zookeeper.yaml](https://github.com/rootsongjc/kubernetes-handbook/blob/master/manifests/test/zookeeper.yaml),这个例子仅为讲解,实际可用的配置请使用 https://github.com/kubernetes/contrib/tree/master/statefulsets 中的配置。
|
||
|
||
```yaml
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: zk-headless
|
||
labels:
|
||
app: zk-headless
|
||
spec:
|
||
ports:
|
||
- port: 2888
|
||
name: server
|
||
- port: 3888
|
||
name: leader-election
|
||
clusterIP: None
|
||
selector:
|
||
app: zk
|
||
---
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: zk-config
|
||
data:
|
||
ensemble: "zk-0;zk-1;zk-2"
|
||
jvm.heap: "2G"
|
||
tick: "2000"
|
||
init: "10"
|
||
sync: "5"
|
||
client.cnxns: "60"
|
||
snap.retain: "3"
|
||
purge.interval: "1"
|
||
---
|
||
apiVersion: policy/v1beta1
|
||
kind: PodDisruptionBudget
|
||
metadata:
|
||
name: zk-budget
|
||
spec:
|
||
selector:
|
||
matchLabels:
|
||
app: zk
|
||
minAvailable: 2
|
||
---
|
||
apiVersion: apps/v1beta1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: zk
|
||
spec:
|
||
serviceName: zk-headless
|
||
replicas: 3
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: zk
|
||
annotations:
|
||
pod.alpha.kubernetes.io/initialized: "true"
|
||
scheduler.alpha.kubernetes.io/affinity: >
|
||
{
|
||
"podAntiAffinity": {
|
||
"requiredDuringSchedulingRequiredDuringExecution": [{
|
||
"labelSelector": {
|
||
"matchExpressions": [{
|
||
"key": "app",
|
||
"operator": "In",
|
||
"values": ["zk-headless"]
|
||
}]
|
||
},
|
||
"topologyKey": "kubernetes.io/hostname"
|
||
}]
|
||
}
|
||
}
|
||
spec:
|
||
containers:
|
||
- name: k8szk
|
||
imagePullPolicy: Always
|
||
image: gcr.io/google_samples/k8szk:v1
|
||
resources:
|
||
requests:
|
||
memory: "4Gi"
|
||
cpu: "1"
|
||
ports:
|
||
- containerPort: 2181
|
||
name: client
|
||
- containerPort: 2888
|
||
name: server
|
||
- containerPort: 3888
|
||
name: leader-election
|
||
env:
|
||
- name : ZK_ENSEMBLE
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: ensemble
|
||
- name : ZK_HEAP_SIZE
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: jvm.heap
|
||
- name : ZK_TICK_TIME
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: tick
|
||
- name : ZK_INIT_LIMIT
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: init
|
||
- name : ZK_SYNC_LIMIT
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: tick
|
||
- name : ZK_MAX_CLIENT_CNXNS
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: client.cnxns
|
||
- name: ZK_SNAP_RETAIN_COUNT
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: snap.retain
|
||
- name: ZK_PURGE_INTERVAL
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: zk-config
|
||
key: purge.interval
|
||
- name: ZK_CLIENT_PORT
|
||
value: "2181"
|
||
- name: ZK_SERVER_PORT
|
||
value: "2888"
|
||
- name: ZK_ELECTION_PORT
|
||
value: "3888"
|
||
command:
|
||
- sh
|
||
- -c
|
||
- zkGenConfig.sh && zkServer.sh start-foreground
|
||
readinessProbe:
|
||
exec:
|
||
command:
|
||
- "zkOk.sh"
|
||
initialDelaySeconds: 15
|
||
timeoutSeconds: 5
|
||
livenessProbe:
|
||
exec:
|
||
command:
|
||
- "zkOk.sh"
|
||
initialDelaySeconds: 15
|
||
timeoutSeconds: 5
|
||
volumeMounts:
|
||
- name: datadir
|
||
mountPath: /var/lib/zookeeper
|
||
securityContext:
|
||
runAsUser: 1000
|
||
fsGroup: 1000
|
||
volumeClaimTemplates:
|
||
- metadata:
|
||
name: datadir
|
||
annotations:
|
||
volume.alpha.kubernetes.io/storage-class: anything
|
||
spec:
|
||
accessModes: [ "ReadWriteOnce" ]
|
||
resources:
|
||
requests:
|
||
storage: 20Gi
|
||
```
|
||
|
||
```sh
|
||
kubectl create -f zookeeper.yaml
|
||
```
|
||
|
||
详细的使用说明见[zookeeper stateful application](https://kubernetes.io/docs/tutorials/stateful-application/zookeeper/)。
|
||
|
||
关于StatefulSet的更多示例请参阅 [github.com/kubernetes/contrib - statefulsets](https://github.com/kubernetes/contrib/tree/master/statefulsets),其中包括了zookeeper和kafka。
|
||
|
||
## 集群外部访问StatefulSet的Pod
|
||
|
||
我们设想一下这样的场景:在kubernetes集群外部调试StatefulSet中有序的Pod,那么如何访问这些的pod呢?
|
||
|
||
方法是为pod设置label,然后用`kubectl expose`将其以NodePort的方式暴露到集群外部,以上面的zookeeper的例子来说明,下面使用命令的方式来暴露其中的两个zookeeper节点,也可以写一个serivce配置yaml文件。
|
||
|
||
```bash
|
||
kubectl label pod zk-0 zkInst=0
|
||
kubectl label pod zk-1 zkInst=1
|
||
kubectl expose po zk-0 --port=2181 --target-port=2181 --name=zk-0 --selector=zkInst=0 --type=NodePort
|
||
kubectl expose po zk-1 --port=2181 --target-port=2181 --name=zk-1 --selector=zkInst=1 --type=NodePort
|
||
```
|
||
|
||
这样在kubernetes集群外部就可以根据pod所在的主机所映射的端口来访问了。
|
||
|
||
查看`zk-0`这个service可以看到如下结果:
|
||
|
||
```bash
|
||
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||
zk-0 10.254.98.14 <nodes> 2181:31693/TCP 5m
|
||
```
|
||
|
||
集群外部就可以使用所有的node中的任何一个IP:31693来访问这个zookeeper实例。
|
||
|
||
## 参考
|
||
|
||
- https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
|
||
|
||
- [kubernetes contrib - statefulsets](https://github.com/kubernetes/contrib/tree/master/statefulsets) |