qchenzi

在K8S上部署OceanBase的最佳实践

目录

1. 背景与选型

OB(下称OB)是一款分布式关系型数据库,具有高性能、高可用性和弹性扩展等特点,其企业版已经在公司内部的”去Oracle”项目中进行了落地,并取得了不错的效果。此外,考虑到我们仍有许多业务在关系型数据库上有着需求,同时考虑到我们已经具备MySQL/MariaDB/MongoDB/PostgresSQL在公司内部的K8S集群上进行容器化部署经验,因此我们决定将OceanBase也进行容器化部署。

1.1 为什么选择OB

在选择数据库时,我们从以下几个维度进行了分析:

1.2 为什么选择ob-operator实现OB on K8S

在将OB部署到K8S的过程中,我们选择了 ob-operator 作为核心组件。ob-operator 提供了自动化管理 OB集群的能力,能够简化部署、扩展和运维的复杂性。其主要优势包括:

2 部署实操

对于希望将 OB 接入 K8S 但不知如何下手的用户,ob-operator 提供了一个方便快捷的起点。

2.1 环境准备

在开始之前,请确保已满足以下条件:

安装 cert-manager

# 检查是否已安装 cert-manager
kubectl get pod -n cert-manager

# 若未安装,则执行以下命令
wget https://github.com/jetstack/cert-manager/releases/download/v1.5.3/cert-manager.yaml

# 拉取镜像需要科学上网
# 我们使用的K8S的网络插件为OVN,节点需要调度到 OVN 网络的节点上,否则可能无法通过 cert-manager 的 service 访问后端 POD
kubectl apply -f cert-manager.yaml

2.2 安装 ob-operator

安装ob-operator的操作可参考ob-operator部署,如果手动通过CRD部署可以自行从github仓库中下载CRD和Operator的yaml文件,然后通过kubectl apply -f 命令进行安装。

2.3 配置 OB 集群

可参考官方文档进行集群创建

2.4 配置 OBProxy 集群

OBProxy(即odp,OceanBase Database Proxy) 是 OB集群 的代理组件,生产环境上建议使用 OBProxy 对OB集群进行访问。使用 OBProxy 的好处包括:

配置步骤

安装 OBProxy:直接应用 YAML 文件进行安装。

obproxy YAML文件地址:obproxy.yaml,但在部署 OBProxy 前需要创建一个用于 OBProxy 与 OB集群 通信的 Secret。

# 创建用于 OBProxy 与 OB集群 通信的 Secret
kubectl create secret -n oceanbase generic proxyro-password --from-literal=password='<proxyro_password>'

# 部署 OBProxy
kubectl apply -f obproxy.yaml

基本内容如下

# 相比官方提供的 obproxy.yaml 文件,增加了 odp-headless 的无头服务配置,主要目的是用于 coreDNS 进行域名解析
apiVersion: v1
kind: Service
metadata:
  name: odp-headless
  namespace: oceanbase
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: odp
    name: odp
  ports:
    - name: "sql"
      port: 2883
      targetPort: 2883

---
apiVersion: v1
kind: Service
metadata:
  name: odp
  namespace: oceanbase
spec:
  type: ClusterIP
  selector:
    app: odp
    name: odp
  ports:
    - name: "sql"
      port: 2883
      targetPort: 2883
    - name: "prometheus"
      port: 2884
      targetPort: 2884

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: odp            # 生产环境下,不建议使用 odp 作为 Deployment 名称,建议使用 odp-${obcluster_name} 作为 Deployment 名称
  namespace: oceanbase
spec:
  selector:
    matchLabels:
      app: odp
      name: odp        # 生产环境下,不建议使用 odp 作为 Deployment 名称,建议使用 odp-${obcluster_name} 作为 Deployment 名称
  replicas: 3
  template:
    metadata:
      labels:
        app: odp
        name: odp
    spec:
      containers:
        - name: obproxy
          image: oceanbase/obproxy-ce:4.2.1.0-11
          ports:
            - containerPort: 2883
              name: "sql"
            - containerPort: 2884
              name: "prometheus"
          env:
            - name: APP_NAME
              value: helloworld # 用于 OBProxy 的名称
            - name: OB_CLUSTER
              value: obcluster # 此处填写OB集群的名称,其来源于 OB 部署YAML文件中的 clusterName 值
            - name: RS_LIST
              value: '******' # 格式为 ${OBServer1 POD_IP}:2881;${OBServer2 POD_IP}:2881;${OBServer3 POD_IP}:2881,需要根据实际OBSevrer PODIP来进行替换。
            - name: PROXYRO_PASSWORD
              valueFrom: 
                secretKeyRef:
                  name: proxyro-password # 用于 OBProxy 与 OB集群 通信的 Secret
                  key: password
          resources:
            limits:
              memory: 2Gi
              cpu: "1"
            requests: 
              memory: 200Mi
              cpu: 200m

部署完成后,如下图所示:

通过 OBProxy 访问OB集群

此时,可以通过OBProxy的Service提供OB数据库的访问入口,如下(obmysql是我提前创建好的租户,testdb是提前在obmysql下创建的用户):

当然,在实际的生产中,我们采用的是域名访问的方式,而不是通过IP地址访问,因此需要进行域名重写,可看下一小节。

2.5 Headless Service 和 CoreDNS 配置

在我们的实践中,为了更好地管理 OBProxy 的访问,我们采用了 Headless Service 配合 CoreDNS 的方案:

  1. 为什么使用 Headless Service
    • Headless Service(无头服务)通过将 clusterIP: None 设置,使得 DNS 查询可以直接返回后端 Pod 的 IP 地址。
    • 这种方式避免了普通 Service 的 kube-proxy 转发,减少了网络跳转,提升了访问性能。
  2. CoreDNS 域名重写配置
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns
      namespace: kube-system
    data:
      Corefile: |
         .:3053 {
           errors
           log
           health {
             lameduck 10s
           }
           rewrite stop {
             name regex ob-(.*).rds.com odp-headless-ob-{1}.oceanbase.svc.cluster.local
             answer name odp-headless-ob-(.*).oceanbase.svc.cluster.local ob-{1}.rds.com
           }
    
           kubernetes cluster.local in-addr.arpa ip6.arpa {
             pods insecure
             fallthrough in-addr.arpa ip6.arpa
           }
           prometheus :9153
           ready :8153
           loop
           reload
           cache 10
           loadbalance
         }
    
  3. 域名重写的优势
    • 简化访问:用户可以通过简单的域名格式(如 ob-test.rds.com)访问数据库,无需关心内部复杂的 K8S 域名。
    • 统一管理:通过规范的域名格式(ob-*.rds.com),便于管理和维护多个 OB 集群。
    • 透明代理:CoreDNS 自动完成域名转换,对应用层完全透明。
    • 灵活扩展:可以根据需求轻松添加新的 OB 集群,只需遵循命名规范即可。
  4. 访问流程
    • 应用通过 ob-{clustername}.rds.com 访问数据库
    • CoreDNS 将请求域名重写为 odp-headless-ob-{clustername}.oceanbase.svc.cluster.local
    • Headless Service 返回对应 OBProxy Pod 的 IP
    • CoreDNS 在响应中将域名重写回 ob-{clustername}.rds.com
    • 应用获得 Pod IP 并建立连接
  5. CoreDNS 主机模式部署
    • 将 CoreDNS 部署在主机网络模式 (即 hostNetwork: true),使 CoreDNS POD与主机共享网络。
    • 这样用,在其余K8S集群中的机器上,将 /etc/resolv.conf 配置为 CoreDNS 服务器ip后,即可通过 CoreDNS 进行域名解析。
    • 这种配置方式使得外部机器能够方便地通过 CoreDNS 进行域名解析,适合需要跨集群访问的场景。
  6. 如图所示
    • 直接通过域名即可访问,而不用关心 obproxy 的service ip,进一步加强了集群的高可用能力

2.6 监控与运维

2.6.1 Promethues部署

执行以下命令部署

kubectl apply -f prometheus.yaml

执行以下命令检查是否部署完成

kubectl get pod -n oceanbase  | grep prometheus

执行以下命令获取SVC

kubectl get svc -n oceanbase  | grep prometheus

如下

root@(datamars)mhpl74334-10.20.248.59 ~$ kubectl get svc -n oceanbase  | grep pro
svc-prometheus    NodePort    12.80.144.38   <none>        9090:30090/TCP      7d15h

2.6.2 Grafana接入

因为我们本地已经有grafana,所以这里我们通过grafana的配置页面,添加prometheus数据源,然后通过prometheus的SVC地址进行接入。

2.6.2.1 配置Prometheus数据源
  1. 在Grafana左侧导航栏,单击 Configuration 按钮,然后单击 Add data source 按钮。
  2. Add data source 页面,选择 Prometheus 作为数据源类型。
  3. Prometheus 页面,填写 Nameob-prometheusURLhttp://12.80.144.38:9090(即上面的promethues对应的svc ip),然后单击 Save & Test 按钮。

2.6.2.2 配置Grafana Dashboard
  1. 新建一个名为OceanBase的文件夹

  1. 进入该文件夹,接着导入文件链接:grafana.yaml 中的grafana-dashboards-ob部分的json配置

  1. 监控展示如图

3. 部署中遇到的问题及解决方案

3.1 ob-operator 的调度器默认为K8S原生的 default-scheduler,而在我们环境中需要使用自定义的调度器

解决方案

apiVersion: oceanbase.oceanbase.com/v1alpha1
kind: OBCluster
metadata:
  name: test
  namespace: oceanbase
spec:
  observer:
    image: oceanbase/oceanbase-cloud-native:4.2.3.1-101000032024061316
    podFields:
      schedulerName: custom-scheduler # 指定调度器为 custom-scheduler
    resource:
      cpu: 8
      memory: 16Gi
    ...

3.2 网络配置问题

问题描述:在使用 OVN 网络插件时,发现 Pod IP 在重启后发生变化,导致OBProxy无法正常访问OB集群。

解决方案: (1)使用ob-operator的service模式,即为每个OBServer Pod创建一个Service,通过service来做静态IP的绑定,从而解决IP变化的问题,用法如下:

apiVersion: oceanbase.oceanbase.com/v1alpha1
kind: OBCluster
metadata:
  name: test
  namespace: oceanbase
  annotations:
    "oceanbase.oceanbase.com/mode": "service" # 指定为service模式
spec:
  observer:
    image: oceanbase/oceanbase-cloud-native:4.2.3.1-101000032024061316
    podFields:
      schedulerName: custom-scheduler
    resource:
      cpu: 8
      memory: 16Gi
    ...

但是链路上多一节service做静态IP的绑定,会增加网络的复杂度,而从生产角度和高可用shang因此我们采用了下面的方案。

(2)ob-operator更新到2.3.1,该版本支持OVN网络插件,并且能够做到Pod重建后IP不变。

(3)但仍存在潜在的IP冲突问题,即当一个 OB Pod 正在重建过程中时,如果此时有其他新的 Pod 被创建,这些新 Pod 可能会占用到正在重建的 OB Pod 原本使用的 IP 地址。这会导致该 OB Pod 重建完成后无法使用其原有的 IP 地址。

为了解决这个问题,我们采用了 OVN 的子网隔离方案:

这种配置的优势: