我在Kubernetes上安装了Rook,并尝试使用块存储
首先
我正在尝试在Kubernetes环境中构建Ceph环境,并试用Rook。
基本引导步骤在下面的文件中有详细说明,但由于版本更新,稍微做了一些改动,所以请记下备忘录。
在Rook的v1.x及其后续版本中,假定每个Kubernetes节点上都配置了专用的HDD/SSD。您仍然可以在测试中继续指定directories: [path: /var/lib/rook],但在cluster.yaml中,默认设置为useAllDevices: true,并期望存在未使用的块设备(例如/dev/sdb)。使用Rook的新版本时需要注意。如果可能的话,建议避免使用Flex Volume。
论文中的参考文献来源
-
- Cloud NativeなストレージRookの検証@ysakashita
- Official Ceph Storage Quickstart for v0.8
环境
-
- Kubernetes v1.12.1 (Xeon e3-1220v2, 24GB Memory, Ubuntu 16.04.5) x4台
- Rook tag:v0.8.3 (git clone https://github.com/rook/rook)
节点在Baremetal(Ubuntu16.04.5)上使用kubespray进行构建。
在执行之前,命名空间的状态如下。
$ kubectl get ns
NAME STATUS AGE
default Active 181d
ingress-nginx Active 11d
istio-system Active 13h
kube-public Active 181d
kube-system Active 181d
metallb-system Active 179d
【提醒】有关传送速度的内容
我计划在不久的将来将整个系统更新到最新的v1.6.x版本。
请务必使用最新版本的Rook/Ceph(v1.7或v1.6.8及以上),因为旧版本(如v1.6)存在致命的缺陷。
在此之前,我已经在两个Ceph文件系统之间进行了数据传输,因此现在的环境如下。
-
- Kubernetes: v1.16.9 (TX1310m3 x4, Xeon E3-1225v6, 4TB HDD x2 RAID1)
-
- Kubernetes: v1.19.9 (TX1320m4 x5, Xeon E-2234, 4TB HDD + 500GB SSD)
-
- Local Network (K8sクラスター内部、両方とも): 10Gbps X520-DA1 + DAC + CRS309-1G-8S+
-
- Backend Network (K8sクラスター間接続): 1Gbps RJ45ケーブル + Switch
- Rook/Ceph v1.0.6 → v1.5.5
网络是在一个单一的192.168.1.0/24内部配置的。
以下是通过rsync从一个集群传输到另一个集群(pod到pod)的约3GB数据(实际传输数据约2.5GB),其中文件数量为2255364,这些文件主要由具有小于4KB的文件组成的Filesystem构成。
sent 2,491,171,331 bytes received 39,713,035 bytes 136,763.90 bytes/sec
total size is 2,945,556,696 speedup is 1.16
准备完成
在能够运行kubectl命令的节点上,使用git clone将rook的存储库获取下来。
$ git clone https://github.com/rook/rook
$ cd rook
$ git checkout refs/tags/v0.8.3 -b v0.8.3
部署
我基本上会遵循官方文件的指示。
当普通访问官方文档时,会显示主分支(master),然后我会在左下角的菜单中选择v0.8版本。
在之前的步骤后,从rook目录继续执行以下命令即可部署所需的服务。
如果您正在使用minikube,请修改cluster.yaml文件中的dataDirHostPath: /var/lib/rook。
$ cd cluster/examples/kubernetes/ceph
$ kubectl create -f operator.yaml
$ kubectl create -f cluster.yaml
【2018/11/20追記】如果在不更改FLEXVOLUME_DIR_PATH的情况下执行operator.yaml,可能无法正常运行。
虽然可以进行修正,但如果想避免重复劳动,请参考后半部分关于FLEXVOLUME_DIR_PATH的章节。
当我们进行到这一步时,命名空间发生了以下变化。
$ kubectl get ns
NAME STATUS AGE
...
rook-ceph Active 1m
rook-ceph-system Active 1m
在StorageClass中的使用
在公式文档中,尝试操作 “Block Storage” 部分中的 “StorageClass”。
假设当前目录为 cluster/examples/kubernetes/ceph,将继续执行前面的操作。
根据官方文档,应用 storageclass.yaml 文件,并确认 StorageClass 的状态。
$ kubectl apply -f storageclass.yaml
$ kubectl -n rook-ceph get storageclass
NAME PROVISIONER AGE
rook-ceph-block ceph.rook.io/block 1m
为了确认相关定义,我执行了`kubectl -n rook-ceph get storageclass -o yaml`命令并在末尾添加了`-o yaml`参数。
- apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
...
name: rook-ceph-block
namespace: ""
...
由于命名空间为空,实际上,无论指定哪个命名空间,都可以引用存储类的定义。
$ kubectl -n default get storageclass
NAME PROVISIONER AGE
rook-ceph-block ceph.rook.io/block 1h
$ kubectl -n kube-system get storageclass
NAME PROVISIONER AGE
rook-ceph-block ceph.rook.io/block 1h
因此,可以从整个系统中使用。
关于使用StatefulSet的案例信息。
我认为在ROOK的官方文档中包含的WordPress示例是从Kubernetes的官方文档中提取的(例如:使用持久卷部署WordPress和MySQL)。
因为这个本身并不是很有趣,所以我决定将之前写过的 Kubernetes 官方实例:使用 PersistentVolume 配置 Cassandra,恢复到原始形式。
在这个Cassandra的例子中,使用了volumeClaimTemplates,并且包含了StorageClass的定义。
在创建命名空间时,Service的定义与官方文件完全一致,没有问题。我选择将其命名为cassandra。
$ kubectl create ns cassandra
$ alias kubectl='kubectl -n cassandra'
$ kubectl create -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml
与官方文档中的cassandra-statefulset.yaml相比,StatefulSet的表现如下。
--- cassandra-statefulset.yaml.orig 2018-11-05 17:17:44.057436173 +0900
+++ cassandra-statefulset.yaml 2018-11-05 17:22:58.843780317 +0900
@@ -53,7 +53,7 @@
- name: HEAP_NEWSIZE
value: 100M
- name: CASSANDRA_SEEDS
- value: "cassandra-0.cassandra.default.svc.cluster.local"
+ value: "cassandra-0.cassandra.cassandra.svc.cluster.local"
- name: CASSANDRA_CLUSTER_NAME
value: "K8Demo"
- name: CASSANDRA_DC
@@ -86,15 +86,7 @@
name: cassandra-data
spec:
accessModes: [ "ReadWriteOnce" ]
- storageClassName: fast
+ storageClassName: rook-ceph-block
resources:
requests:
storage: 1Gi
----
-kind: StorageClass
-apiVersion: storage.k8s.io/v1
-metadata:
- name: fast
-provisioner: k8s.io/minikube-hostpath
-parameters:
- type: pd-ssd
由于指定了namespace,所以将其从”default”更改为”cassandra”。
在之前的位置,通过在kubectl中添加”-n cassandra”别名,因此在命令行中无需关注namespace。
另外,我们将StorageClass部分完全删除,并将storageClassName的指定更改为之前确认的StorageClass的metadata.name,即rook-ceph-block。
$ kubectl apply -f cassandra-statefulset.yaml
即使执行到这一步,Pod仍无法启动,让我感到困扰。
$ kubectl describe pod cassandra-0
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 24m default-scheduler Successfully assigned cassandra/cassandra-0 to node6
Warning FailedMount 2m (x10 over 22m) kubelet, node6 Unable to mount volumes for pod "cassandra-0_cassandra(0ad97640-e0d4-11e8-8510-000db93312a4)": timeout expired waiting for volumes to attach or mount for pod "cassandra"/"cassandra-0". list of unattached volumes=[cassandra-data default-token-hlb95]
我在这里尝试执行了样本的wordpress.yaml等内容,但看起来PV和PVC都被正常创建,但似乎无法从Pod中挂载。
让我们检查正在运行的节点6的/var/log/syslog记录。
Nov 5 18:57:28 node6 kubelet[1264]: E1105 18:57:28.898651 1264 desired_state_of_world_populator.go:311] Failed to add volume "cassandra-data" (specName: "pvc-15227c15-e0df-11e8-8510-000db93312a4") for pod "1527d22f-e0df-11e8-8510-000db93312a4" to desiredStateOfWorld. err=failed to get Plugin from volumeSpec for volume "pvc-15227c15-e0df-11e8-8510-000db93312a4" err=no volume plugin matched
与FLEXVOLUME_DIR_PATH相关的问题。
以下是可能適用的類似案例:
- https://github.com/rook/rook/issues/1888
根据链接的官方文档,可能存在以下两个理由。
-
- FlexVolume的配置
- 指定的FlexVolume已被指定给kubelet。
我确认了最初的FlexVolume配置,默认情况下会创建一个位于 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ 的目录。
$ ls -l /usr/libexec/kubernetes/kubelet-plugins/volume/exec/
total 16
drwxr-xr-x 2 root root 4096 Nov 6 00:10 ceph.rook.io~rook
drwxr-xr-x 2 root root 4096 Nov 6 00:10 ceph.rook.io~rook-ceph-system
drwxr-xr-x 2 root root 4096 Nov 6 00:10 rook.io~rook
drwxr-xr-x 2 root root 4096 Nov 6 00:10 rook.io~rook-ceph-system
我以为在这个状态下,使用默认设置应该可以正常运行。但是当我查看下一个 kubelet 的设置时,发现指向了 /var/lib/kubelet/volume-plugins 的不同位置。
$ ps aux|grep volume-plugin-dir
root 1179 11.9 1.7 1848808 142048 ? Ssl Nov05 41:51 /usr/local/bin/kubelet ... --volume-plugin-dir=/var/lib/kubelet/volume-plugins
为了实现这一点,我们编辑了 cluster/examples/kubernetes/ceph 下的 operator.yaml 文件,设置了 FLEXVOLUME_DIR_PATH。
$ cd cluster/examples/kubernetes/ceph
在这里,编辑了 operator.yaml 文件之后,运行了 $ git diff operator.yaml 命令的结果如下。
--- a/cluster/examples/kubernetes/ceph/operator.yaml
+++ b/cluster/examples/kubernetes/ceph/operator.yaml
@@ -308,8 +308,8 @@ spec:
# - name: AGENT_TOLERATION_KEY
# value: "<KeyOfTheTaintToTolerate>"
# Set the path where the Rook agent can find the flex volumes
- # - name: FLEXVOLUME_DIR_PATH
- # value: "<PathToFlexVolumes>"
+ - name: FLEXVOLUME_DIR_PATH
+ value: "/var/lib/kubelet/volume-plugins/"
# Rook Discover toleration. Will tolerate all taints with all keys.
# Choose between NoSchedule, PreferNoSchedule and NoExecute:
# - name: DISCOVER_TOLERATION
重新应用此 operator.yaml 文件后,过一段时间,Pod 将逐个重新启动。
$ kubectl apply -f operator.yaml
确认操作
通过至今的工作,rook-ceph 成功启动。
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/cassandra-0 1/1 Running 0 6m
pod/cassandra-1 1/1 Running 0 4m
pod/cassandra-2 1/1 Running 0 1m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cassandra ClusterIP None <none> 9042/TCP 7h
NAME DESIRED CURRENT AGE
statefulset.apps/cassandra 3 3 6m
将服务向外部公开
我在这一步重新添加了类型为LoadBalancer的Service,以便从外部访问cassandra。
$ cat 03.cassandra-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: cassandra
name: cassandracl
spec:
ports:
- port: 9042
selector:
app: cassandra
type: LoadBalancer
$ kubectl apply -f 03.cassandra-service.yaml
service/cassandracl created
到目前为止,可从外部访问的Cassandra集群已经成功运行。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cassandra ClusterIP None <none> 9042/TCP 24m
cassandracl LoadBalancer 10.233.32.79 192.168.100.156 9042:30653/TCP 23m
突然停止运动
在考虑到后续断电情况下,重新启动整个节点后,出现了无法正常运行的问题。查看状态后发现如下情况。
$ kubectl describe pod/cassandra-0
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m default-scheduler Successfully assigned cassandra/cassandra-0 to node3
Warning FailedMount 1m (x2 over 3m) kubelet, node3 Unable to mount volumes for pod "cassandra-0_cassandra(243ea4ec-e188-11e8-8a8f-000db93312a4)": timeout expired waiting for volumes to attach or mount for pod "cassandra"/"cassandra-0". list of unmounted volumes=[cassandra-data]. list of unattached volumes=[cassandra-data default-token-kl6r4]
从Pod的日志中可以看到它已经启动了,但是无法解析cassandra-0的IP地址。
起初我对kube-dns表示怀疑,但当使用StatefulSet时,我发现如果没有定义ClusterIP: None的Service(svc),将无法获取cassandra-0的IP。
所以 svc 定义了两个“cassandra”和“cassandracl”。
当发出对kube-dns(10.233.0.3)的集群名查询时,会以轮询方式返回其下的POD的A记录(IP)。
$ nslookup cassandra.cassandra.svc.cluster.local 10.233.0.3
Server: 10.233.0.3
Address: 10.233.0.3#53
Name: cassandra.cassandra.svc.cluster.local
Address: 10.233.100.160
Name: cassandra.cassandra.svc.cluster.local
Address: 10.233.71.56
Name: cassandra.cassandra.svc.cluster.local
Address: 10.233.74.92
我认为,在删除了ClusterIP: None的svc定义之后,重新定义了type: LoadBalancer的svc,所以在重新启动时才发现了问题。
无法创建下一个PVC的问题
然后,无法使用StorageClass从rook-ceph创建。
$ kubectl -n cassandra describe pvc myclaim
Name: myclaim
Namespace: cassandra
StorageClass: rook-ceph-block
Status: Pending
Volume:
Labels: <none>
Annotations: control-plane.alpha.kubernetes.io/leader={"holderIdentity":"dd36afbf-e457-11e8-8335-1a7fdbbc44fe","leaseDurationSeconds":15,"acquireTime":"2018-11-20T05:03:14Z","renewTime":"2018-11-20T05:08:29Z","lea...
kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"myclaim","namespace":"cassandra"},"spec":{"accessModes":["ReadWr...
volume.beta.kubernetes.io/storage-provisioner=ceph.rook.io/block
Finalizers: [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Provisioning 1m (x9 over 5m) ceph.rook.io/block rook-ceph-operator-6cc45dfb48-kpkj6 dd36afbf-e457-11e8-8335-1a7fdbbc44fe External provisioner is provisioning volume for claim "cassandra/myclaim"
Warning ProvisioningFailed 1m (x9 over 5m) ceph.rook.io/block rook-ceph-operator-6cc45dfb48-kpkj6 dd36afbf-e457-11e8-8335-1a7fdbbc44fe Failed to provision volume with StorageClass "rook-ceph-block": Failed to create rook block image replicapool/pvc-958a9ee5-ec81-11e8-849d-000db9331290: failed to create image pvc-958a9ee5-ec81-11e8-849d-000db9331290 in pool replicapool of size 5368709120: Failed to complete '': exit status 2. rbd: error opening pool 'replicapool': (2) No such file or directory
. output:
Normal ExternalProvisioning 16s (x78 over 5m) persistentvolume-controller waiting for a volume to be created, either by external provisioner "ceph.rook.io/block" or manually created by system administrator
应用toolbox.yaml后,通过ceph命令来检查存储池的状态,发现无法确认replicapool的存在。
$ kubectl -n rook-ceph exec -it rook-ceph-tools -- ceph osd lspools
$
查看池的定义时,发现同时指定了复制和纠删码选项,但在配置示例中却没有提及,因此我简单地选择了只使用复制。
$ kubectl -n rook-ceph get pool replicapool -o yaml
apiVersion: ceph.rook.io/v1beta1
kind: Pool
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"ceph.rook.io/v1beta1","kind":"Pool","metadata":{"annotations":{},"name":"replicapool","namespace":"rook-ceph"},"spec":{"erasureCoded":{"codingChunks":1,"dataChunks":2},"replicated":{"size":3}}}
creationTimestamp: 2018-11-20T05:02:46Z
generation: 1
name: replicapool
namespace: rook-ceph
resourceVersion: "32423166"
selfLink: /apis/ceph.rook.io/v1beta1/namespaces/rook-ceph/pools/replicapool
uid: 8539e6e1-ec81-11e8-849d-000db9331290
spec:
erasureCoded:
codingChunks: 1
dataChunks: 2
replicated:
size: 3
删除了pool后,删除了spec.erasureCoded,只保留了spec.replicated,并再次使用apply -f命令将YAML文件应用,成功运行。
由于仅执行 “apply -f” 命令无法立即反映更改,因此可以推断出这是在稍晚于之前修改 storageclass.yaml 文件的时候重新创建 StorageClass 时才发现的。
【离题不谈】故障排除
仪表板
为了故障排除,将Dashboard Service的类型从ClusterIP更改为LoadBalancer。
$ kubectl -n rook-ceph edit svc rook-ceph-mgr-dashboard
$ kubectl -n rook-ceph get svc rook-ceph-mgr-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rook-ceph-mgr-dashboard LoadBalancer 10.233.49.37 192.168.100.110 7000:32606/TCP 6h
我从浏览器中查看了仪表板,但并没有得到太有趣的结果。
这个仪表板在外观上还可以,但不太值得推荐。
工具箱
当执行ceph命令或rdb命令时,请按照以下步骤进行。
假定您在使用 kubectl apply -f 前先预先进行了 toolbox.yaml 的部署。
$ kubectl -n rook-ceph exec -it rook-ceph-tools bash
..# ceph osd status
# ceph osd status
+----+-------------------------------------+-------+-------+--------+---------+--------+---------+-----------+
| id | host | used | avail | wr ops | wr data | rd ops | rd data | state |
+----+-------------------------------------+-------+-------+--------+---------+--------+---------+-----------+
| 0 | rook-ceph-osd-id-0-6b577745cd-2dlcf | 35.4G | 421G | 0 | 0 | 0 | 0 | exists,up |
| 1 | rook-ceph-osd-id-1-56c8cb7449-wpmk9 | 21.0G | 206G | 0 | 0 | 0 | 0 | exists,up |
| 2 | rook-ceph-osd-id-2-b8c5d98d5-cmkwh | 20.8G | 206G | 0 | 0 | 1 | 16 | exists,up |
| 3 | rook-ceph-osd-id-3-894fc4d55-7ttr6 | 18.0G | 89.9G | 0 | 0 | 0 | 0 | exists,up |
| 4 | rook-ceph-osd-id-4-86d4557d64-85vvs | 17.5G | 90.4G | 0 | 0 | 1 | 90 | exists,up |
| 5 | rook-ceph-osd-id-5-cbfdb6c8b-ws966 | 16.7G | 37.9G | 0 | 0 | 0 | 0 | exists,up |
| 6 | rook-ceph-osd-id-6-ff948fbbd-dzvfd | 21.0G | 92.2G | 0 | 0 | 0 | 0 | exists,up |
+----+-------------------------------------+-------+-------+--------+---------+--------+---------+-----------+
[root@rook-ceph-tools /]# ceph df
GLOBAL:
SIZE AVAIL RAW USED %RAW USED
1295G 1145G 150G 11.63
POOLS:
NAME ID USED %USED MAX AVAIL OBJECTS
replicapool 1 134M 0.02 834G 48
myfs-metadata 2 15962 0 834G 21
myfs-data0 3 98 0 834G 2
使用toolbox.yaml来设置“ceph osd status”的别名可能会很方便。
别名 show_ceph_osd_status=”kubectl -n rook-ceph exec -it rook-ceph-tools — ceph osd status” -> show_ceph_osd_status 别名=”kubectl -n rook-ceph exec -it rook-ceph-tools — ceph osd status”
以上
(Chinese paraphrase: 以上)