我曾经构建了符合公司内部云原生Kubernetes环境条件的ECK,但最终决定停止使用它来进行运维工作的故事

太长没看,简而言之。

    • ECK やるには PersistentVolume が必要だけどオンプレ K8s だとこれがツラい

他のサーバからどうアクセスさせるか(External IP )もちょっとツラい

よく考えたら外部公開の予定も無いのに ECK 頑張る意味はあまりなかった

「努力了很多,想方设法去做,但最终发现努力没有任何意义,这是我领悟到的。」

背景:只需要一个选项,用中文将以下内容改写一下

目前構建的本地 Elasticsearch 似乎是使用 ansible-elasticsearch 搭建的,但負責人已離職並且沒有留下相關文件,老實說不知道該如何進行更新。
此外,ansible-elasticsearch 本身也停止了更新,並在7.17版本被終止,所以繼續維護這個系統也沒有太多優點。
真是麻煩啊,該怎麼辦呢?我煩惱著,當我查找 Elasticsearch 官方資料時,居然發現了 Elastic Cloud on Kubernetes (ECK) 這樣的東西。
剛好我已經準備好了一個 Kubernetes 集群,從現在開始要遵循 Cloud Native 的時代了!我輕鬆地決定引入 ECK。
然而,我卻不知道這是一條多麼艱難的道路啊…

最开始的困难,持续存储卷。

参照了公式的快速开始指南和先辈们写的Qiita后,我认为只要按照这些参考就能暂时运行。
(实际上,如果不需要数据持久性,可以立即运行快速开始。这里不会详细说明,只是粗略介绍。)
但是,显然,如果不准备PersistentVolume,数据将失去持久性。

如果你正在使用AWS等服务,似乎可以简单实现通过使用gp2等选项,但是这次的目标是在本地构建的Kubernetes集群,所以不能那样做。
经过一番调查,似乎能够将glusterfs用作PersistentVolume。
由于已经有GlusterFS的运营经验,所以抓住这个机会,利用可用的虚拟机资源来构建GlusterFS卷,并决定尝试一下。
* 在撰写本文时,Kubernetes v1.25已明确将glusterfs设为弃用,但在调研过程中尚未提及…可能。

Volume和PersistentVolume的差别导致了沮丧感。

尽管遇到了一些困难,但我发现只需在所有Kubernetes节点上添加GlusterFS节点的主机名,并预先安装glusterfs-client,就可以将Pod挂载到Gluster卷上。

最终验证了能够像下面这样挂载并写入 Volume。

创建Pod

# cat glusterfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: glusterfs
spec:
  containers:
  - name: glusterfs
    image: nginx
    volumeMounts:
    - mountPath: "/mnt/glusterfs"
      name: glusterfsvol
  volumes:
  - name: glusterfsvol
    glusterfs:
      endpoints: glusterfs-cluster
      path: kubevol
      readOnly: false
# kc create -f glusterfs-pod.yaml
pod/glusterfs created

写作测试

# kc exec -it glusterfs -- /bin/sh
# ls /mnt/glusterfs
# echo "hoge" >> /mnt/glusterfs/hoge
# cat /mnt/glusterfs/hoge
hoge
#

确认在Gluster Server上写入的内容。

$ ls /var/spool/kubevol/
hoge
$ cat /var/spool/kubevol/hoge
hoge

看起来正在正确地装配并且写入数据。
删除 Pod 后,验证数据是否保留。

# kc delete pod glusterfs
pod "glusterfs" deleted
$ cat /var/spool/kubevol/hoge
hoge

看起来没有问题。
按照官方指南部署了 ECK 后,尝试创建 Elasticsearch 集群。
※ 这里错误地使用了 Pod 的卷而没有理解 PersistentVolume。

# cat eck.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: ecksandbox
spec:
  version: 7.17.5
  nodeSets:
  - name: master-nodes
    count: 3
    config:
      node.store.allow_mmap: false
      node.roles: ["master"]
    podTemplate:
      spec:
        volumes:
        - name: elasticsearch-data
          glusterfs:
            endpoints: glusterfs-cluster
            path: kubevol
            readOnly: false
  - name: data-nodes
    count: 3
    config:
      node.store.allow_mmap: false
      node.roles: ["data", "ingest"]
    podTemplate:
      spec:
        volumes:
        - name: elasticsearch-data
          glusterfs:
            endpoints: glusterfs-cluster
            path: kubevol
            readOnly: false
# kc apply -f eck.yaml -n testeck
elasticsearch.elasticsearch.k8s.elastic.co/ecksandbox created

稍等片刻,它就变成了绿色。

 kubectl get elasticsearch -n testeck
NAME         HEALTH   NODES   VERSION   PHASE   AGE
ecksandbox   green    6       7.17.5    Ready   109s

根据先达给出的智慧,尝试进行数据输入等操作,看起来似乎是有效的。

# curl -u "elastic:$PASSWORD" -k -H "Content-Type: application/json" -XPOST "https://localhost:9200/bank/_bulk?pretty&refresh" --data-binary "@accounts.json"
{
  "took" : 2050,
  "errors" : false,
  "items" : [
    {
(略)
        "_seq_no" : 999,
        "_primary_term" : 1,
        "status" : 201
      }
    }
  ]
}

然而,我查看了Gluster,但里面没有任何写入内容。

# ls -la /var/spool/kubevol/
合計 16
drwxr-xr-x    4 root root  107  7月 15 18:12 .
drwxr-xr-x.   9 root root  102  7月 15 16:09 ..
drw-------  262 root root 8192  7月 15 16:11 .glusterfs
drwxr-xr-x    2 root root    6  7月 15 16:10 .glusterfs-anonymous-inode-90bb008b-f2b5-43f6-9f74-b3d088d48266
-rw-r--r--    2 root root    5  7月 15 18:12 hoge

在此之后,我删除了Pod并确认了数据的消失情况,还进行了一些无用的调查。但由于ECK使用StatefulSet,我意识到必须使用PersistentVolume(PersistentVolumeClaim)才能持久化数据(尽管先前的指南中已经写得很清楚,但我并没有理解到)。

因为没办法,我尝试了将GlusterFS作为PersistentVolume的方法,但无论我如何努力,似乎只有通过使用Heketi作为REST API的接口来实现的方法。
(虽然没有进行详细的调查,但我认为很可能glusterfs的provider是使用了Heketi。)
然而,关键的Heketi已经明确声明只会进行关键的bug修复,所以我放弃了将GlusterFS作为PersistentVolume的想法。

试图以某种方式将 Gluster 用作 PersistentVolume 进行试验和尝试。

Heketi虽然放弃了,但考虑到使用NFS Ganesha来导出GlusterFS,是否可以将其作为NFS的PersistentVolume来处理,并进行了各种尝试和错误。
但是,构建一个高可用的Ganesha HA配置的难度太高让我不得不放弃。
(由于贴日志会变得很长,我省略了一些内容,可能Ganesha / Gluster的配置是针对使用RHEL优秀存储的人设计的…)

虽然我的思绪在这周围已经迷失了方向,但经过曲折的思考,我灵光一现,提出了一个主意:通过将 Gluster 卷挂载到每个 Kubernetes 节点,再使用本地的 StorageClass 创建 PV,就可以实现持久化。(虽然并不真的是个灵光一现的主意……)

据说在这里,克服了许多PV和PVC之间必须保持1:1关系的失败。

    • Kubernetes node 1 つにつき Gluster Volume 1 つをマウント

node 毎のマウントパスは同じにする

PV を Kubernetes node 分それぞれ作る

通过这种荒谬的方法成功制作出了PV。

每个 Kubernetes 都挂载了 Gluster Volume 的情况(的图像)

k8s-server-1 | CHANGED | rc=0 >>
ファイルシス        1K-ブロック    使用    使用可 使用% マウント位置
gluster-server:/kubevol001   132485936 5455620 127030316    5% /var/spool/kubevol
k8s-server-2 | CHANGED | rc=0 >>
ファイルシス        1K-ブロック    使用    使用可 使用% マウント位置
gluster-server:/kubevol002   132485936 5455620 127030316    5% /var/spool/kubevol
k8s-server-3 | CHANGED | rc=0 >>
ファイルシス        1K-ブロック    使用    使用可 使用% マウント位置
gluster-server:/kubevol003   132485936 5455620 127030316    5% /var/spool/kubevol
k8s-server-4 | CHANGED | rc=0 >>
ファイルシス        1K-ブロック    使用    使用可 使用% マウント位置
gluster-server:/kubevol004   132485936 5455620 127030316    5% /var/spool/kubevol
k8s-server-5 | CHANGED | rc=0 >>
ファイルシス        1K-ブロック    使用    使用可 使用% マウント位置
gluster-server:/kubevol005   132485936 5455620 127030316    5% /var/spool/kubevol

強迫製作的音樂錄影帶

# cat sc-lv.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: kubevol
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
# cat pv-lv.yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: kubevol-pv1
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: kubevol
  local:
    path: /var/spool/kubevol
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - k8s-server-1
(以下略)

# kc get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                     STORAGECLASS   REASON   AGE
kubevol-pv1   10Gi       RWO            Retain           Bound    testeck/elasticsearch-data-ecksandbox-es-master-nodes-2   kubevol                 4h49m
kubevol-pv2   10Gi       RWO            Retain           Bound    testeck/elasticsearch-data-ecksandbox-es-master-nodes-1   kubevol                 4h49m
kubevol-pv3   10Gi       RWO            Retain           Bound    testeck/elasticsearch-data-ecksandbox-es-master-nodes-0   kubevol                 4h49m
kubevol-pv4   10Gi       RWO            Retain           Bound    testeck/elasticsearch-data-ecksandbox-es-data-nodes-0     kubevol                 4h49m
kubevol-pv5   10Gi       RWO            Retain           Bound    testeck/elasticsearch-data-ecksandbox-es-data-nodes-1     kubevol                 4h49m

对于 Elasticsearch 集群的配置文件已经被修改,以使用本地作为 PersistentVolume。

# cat old_eck.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: ecksandbox
spec:
  version: 7.17.4
  nodeSets:
  - name: master-nodes
    count: 3
    podTemplate:
      spec:
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: kubernetes.io/hostname
                  operator: In
                  values:
                  - k8s-server-1
                  - k8s-server-2
                  - k8s-server-3
    config:
      node.store.allow_mmap: false
      node.roles: ["master"]
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 5Gi
        storageClassName: kubevol
  - name: data-nodes
    count: 2
    podTemplate:
      spec:
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: kubernetes.io/hostname
                  operator: In
                  values:
                  - k8s-server-4
                  - k8s-server-5
    config:
      node.store.allow_mmap: false
      node.roles: ["data", "ingest"]
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 5Gi
        storageClassName: kubevol

通过这样做,可以运行Elasticsearch集群。

# kc get elasticsearch -n testeck
NAME         HEALTH   NODES   VERSION   PHASE   AGE
ecksandbox   green    5       7.17.4    Ready   3h32m

废话

关于本地系的PersistentVolume,我发现这篇Qiita非常有参考价值。
https://qiita.com/ysakashita/items/67a452e76260b1211920

以下是对”次のつまづき、External IP”的汉语表达,只提供一种选择:

接下来的问题是,外部IP。

虽然 Elasticsearch 已经在这里运行起来了,但是目前只能通过端口转发并在本地主机上进行访问,而无法从其他服务器上访问 Elasticsearch。
使用 ingress-nginx 作为 Service 的一种方法,但根据本次要求,并没有特别需要对外公开的意义,只要能够从局域网内进行访问即可满足要求。

快速搜索一下,官方文档上写着可以使用 LoadBalancer 类型,所以我尝试了一下。
但是,状态一直停留在 pending。

# kc get all -n testeck
NAME                               READY   STATUS    RESTARTS   AGE
pod/ecksandbox-es-data-nodes-0     1/1     Running   0          63m
pod/ecksandbox-es-data-nodes-1     1/1     Running   0          63m
pod/ecksandbox-es-master-nodes-0   1/1     Running   0          63m
pod/ecksandbox-es-master-nodes-1   1/1     Running   0          63m
pod/ecksandbox-es-master-nodes-2   1/1     Running   0          63m

NAME                                  TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/ecksandbox-es-data-nodes      ClusterIP      None           <none>        9200/TCP         63m
service/ecksandbox-es-http            LoadBalancer   10.233.7.95    <pending>     9200:31494/TCP   45m
service/ecksandbox-es-internal-http   ClusterIP      10.233.29.62   <none>        9200/TCP         63m
service/ecksandbox-es-master-nodes    ClusterIP      None           <none>        9200/TCP         63m
service/ecksandbox-es-transport       ClusterIP      None           <none>        9300/TCP         63m

NAME                                          READY   AGE
statefulset.apps/ecksandbox-es-data-nodes     2/2     63m
statefulset.apps/ecksandbox-es-master-nodes   3/3     63m

通过明确指定 externalIPs 解决了这个问题。

  1 apiVersion: elasticsearch.k8s.elastic.co/v1
  2 kind: Elasticsearch
  3 metadata:
  4   name: ecksandbox
  5 spec:
  6   version: 7.17.4
  7   http: #←このブロック追記
  8     service:
  9       spec:
 10         type: LoadBalancer
 11         externalIPs:
 12         - 192.168.1.3
 13         - 192.168.1.4
 14         - 192.168.1.5

在此次事故中,最初我错误地指定了“K8s主节点管理使用的IP地址”,导致整个K8s集群停止运行。
如果发生在生产环境中,这将是一个致命的错误,让人无法接受。在验证环境中做这样的错误可以算是万幸。
最终变成了这样的结果。

# kc get svc -n testeck
NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP                                       PORT(S)          AGE
ecksandbox-es-data-nodes      ClusterIP      None            <none>                                            9200/TCP         34m
ecksandbox-es-http            LoadBalancer   10.233.62.129   192.168.1.3,192.168.1.4,192.168.1.115   9200:30748/TCP   16m
ecksandbox-es-internal-http   ClusterIP      10.233.57.6     <none>                                            9200/TCP         34m
ecksandbox-es-master-nodes    ClusterIP      None            <none>                                            9200/TCP         34m
ecksandbox-es-transport       ClusterIP      None            <none>                                            9300/TCP         16m

到了这一步,我意识到了

如果是ECK,那么从这种状态开始进行操作肯定很容易升级,并且很容易进行外部公开。
然而,我们需要的是一个“本地保留数据并具有一定稳定性、可以从局域网访问的Elasticsearch集群”,并不需要在Kubernetes上运行,也不应为了数据保留而进行不切实际的配置,使运维难以进行并且难以扩展,这样反而是本末倒置的。
从一开始就应该“放弃别人制作的ansible,自己开发适用于我们的elasticsearch建设ansible任务”。

学习PersistentVolume和StatefulSet确实有所收获,而且发现相比较而言,K8s的永久化卷在AWS等平台上处理起来更容易(但似乎仍然最好保持分离),这是一次让我重新意识到一旦失去目标就容易做出无谓的事情的案例。

最终

虽然我并没有详细了解公司的方针,但从个人来说,我们非常需要能够熟悉 Kubernetes、网络和本地环境,同时保持目标明确的基础设施工程师。如果你不是基础设施工程师,也请务必来看一下。

 

bannerAds