Eisen's Blog

© 2024. All rights reserved.

尝试 clickhouse operator

2022 January-26

Production 级别的 clickhouse 不是随便就能搞出来的,需要对其有更深入的理解。这篇主题只是针对如何在 k8s 里快速搭建一还行的 clickhouse。主要是为了实现整个系统不对外有额外的依赖,所有的依赖服务都包含在 k8s 集群中。和 将 MySQL 通过 presslabs/mysql-operator 部署到 k8s 内部 这篇对于 mysql 的处理非常类似。

诉求

之前用于测试的 clickhouse 是一非常随便的 deployment 实现,其主要缺点有如下几个:

  1. 没有考虑如何修改配置,如果需要额外的配置只好自己想办法 mount 一个文件到指定路径
  2. 没有初始化数据库的流程,只能自己手动去创建数据库
  3. 不支持高可用,也无法扩容

其中 3 的诉求并不强烈,毕竟在测试阶段对这部分的要求没有那么高,而且如果真的想要高可用可能甚至都不应该将 clickhouse 放进 k8s 里。不过 1 2 的诉求还是很强烈的。那么我这里的工作也都是针对 1 2 两项进行的。

方案

这里采用了 Altinity/clickhouse-operator 这个方案。该方案不仅仅是完美解决了 1 2 两项问题,甚至是 3 也有做了还不错的处理。不过我这里就没有测试扩容和高可用了,主要测试的是 1 2 两部分。

安装 operator

按照文档安装 operator:

curl -s https://raw.githubusercontent.com/Altinity/clickhouse-operator/master/deploy/operator-web-installer/clickhouse-operator-install.sh | OPERATOR_NAMESPACE=infra bash

这里我把 operator 安装的 namespace 放到了 infra。

提供部署 clickhouse 的 crd

apiVersion: v1
kind: "ConfigMap"
metadata:
  name: "serving-db-mounted-configmap"             # [5]
data:
  01_create_databases.sh: |
    #!/bin/bash
    set -e
    clickhouse client -n <<-EOSQL
      CREATE DATABASE IF NOT EXISTS serving;
    EOSQL
---
apiVersion: "clickhouse.altinity.com/v1"
kind: "ClickHouseInstallation"
metadata:
  name: "serving-db"
spec:
  configuration:
    settings:                                      # [5]
      max_concurrent_queries: 400
    clusters:
      - name: "serving-db"
        layout:
          shardsCount: 1
          replicasCount: 1
  defaults:                                       # [1]
    templates:
      podTemplate: pod-template
      dataVolumeClaimTemplate: data-volume-template
      logVolumeClaimTemplate: log-volume-template
      serviceTemplate: svc-template
  templates:
    serviceTemplates:                             # [2]
      - name: svc-template
        generateName: clickhouse-{chi}
        spec:
          ports:
            - name: http
              port: 8123
            - name: tcp
              port: 9000
          type: ClusterIP
    podTemplates:                                 # [3]
      - name: pod-template
        spec:
          containers:
            - name: clickhouse
              image: yandex/clickhouse-server:22.1.3
              volumeMounts:
                - name: serving-db-configmap-volume
                  mountPath: /docker-entrypoint-initdb.d
          volumes:
            - name: serving-db-configmap-volume
              configMap:
                name: serving-db-mounted-configmap

    volumeClaimTemplates:                         # [4]
      - name: data-volume-template
        reclaimPolicy: Retain
        spec:
          storageClassName: local
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 50Gi
      - name: log-volume-template
        reclaimPolicy: Retain
        spec:
          storageClassName: local
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 10Gi

这个 yaml 是我集成了自己的所有诉求的最终版本,可以看到它主要包含两个部分:

  1. serving-db-mounted-configmap 其中包含了初始化数据库的内容,这部分不是必须要,只有需要初始化数据库的时候才有
  2. ClickHouseInstallation 这个是 clickhouse operator 所提供的 crd 顾名思义,就用于创建 clickhouse 核心 crd。具体每一个东西什么意思还是看文档吧,我这里主要介绍下目前实现了上述诉求相关的内容

我在上面做了注释标记([1] 这样子),下面我一个个做介绍。

  1. 可以看到 defaults.templates 下定义了一系列的模板(Template)其包含了
    1. serviceTemplate: 暴露 clickhouse 的 service 的结构
    2. podTemplate: 创建 clickhouse 自身的 pod 的结构
    3. dataVolumeClaimTemplate: 提供给 clickhouse 的存储的结构
  2. serviceTemplate 其默认的类型为 LoadBalancer 由于我们的集群里不支持也不希望使用这个类型,因此这里做了自定义将其修改为了 ClusterIP
  3. podTemplate 中首先选择 image 版本为 22.1.3 其次增加了一个额外的 volumeMounts 到路径 /docker-entrypoint-initdb.d 这样做是为了利用该 operator 所提供的钩子,实现数据库的初始,具体文档参见 02-templates-07-bootstrap-schema.yaml
  4. volumeClaimTemplates 分别定义了 clikchouse 的日志和数据的 PV
  5. configuration.settings 可以自定义 clickhouse 的配置,这里我仅仅是修改了 max_concurrent_queries 这个配置,这里所写的配置最终会被合并到 clickhouse 的配置中

遇到的问题

clickhouse operator 很好的解决了我目前的两大诉求,不过在使用过程中也遇到了一个额外的问题:clickhouse 默认用户 default 无法通过 kubernete dns 去访问 clickhouse。仔细查看了下配置,发现其配置只支持 <service-name>.<namespace>.svc.cluster.local 的域名访问。这样做有两个明显的问题:

  1. 无法使用类似于 <service-name>.<namespace> 这样的 k8s 内支持的短域名访问
  2. 如果我在创建 k8s 集群时就修改了默认的域名配置(即不采用 cluster.local),那么这域名是一定不起作用的,甚至可以认为这个配置是一个 bug

解决的办法也很简单,就是不要用这个 default 用户,去创建一个新的用户并且设置可以访问的 ip 即可。


vim 里自动更新 markdown 格式

2022 January-22

问题

写 blog 的时候经常需要做中英文混排,然后这个混排为了美观需要在中和英文之间添加空格。现在形成习惯了,看到别人没有在中英文混排里添加空格都觉得有点难受。不过之前这个格式要求都靠自己敲空格敲出来的,感觉效率有点低下,恰巧发现了 autocorrect 这个工具,可以实现这个功能,这里尝试把它和我写 blog 用的 neovim 集成下实现自动格式化的功能。

思路

首先 autocorrect 本身已经是一个二进制文件了,通过如下命令可以实现对文件的处理:

$ autocorrect --fix <filepath>

因为 vim 有一些插件可以实现在保存的时候对文件进行格式化而 autocorrect 的处理也可以认为是一种格式化因此思路就很一致了。不过相较于很多格式化工具可以以文本内容的方式传递,autocorrect 只能对文件做修改,流程上会稍微有一点区别。这里我列一下集成思路:

  1. 确定内容更新的时机,由于 autocorrect 只能对文件做处理(而不是传递一段文本)因此必须要在文件更新后再次执行该命令对文件做二次刷新,vim 有一个 autocmd 的命令,可以某些重要的事件发生时(或者发生前后)执行一系列命令,这里就需要监听 BufWritePost 即当 Buf 写成功后执行一个命令
  2. 文件被 autocorrect 更新后需要重新读取,否则当前看到的内容就不是已经做过格式化的内容了,vim 中通过命令 edit 可以将文件重新读取,redraw 命令则可刷新视图
  3. 目前只考虑对 markdown 文件做上述处理,需要首先判别文件类别,只有符合的类别才能做上述处理

具体实现方式

我使用的是 neovim 这里就按照它的配置结果做了实现,vim 的实现也是类似的,只是具体的目录结构会有略微区别而已。

.config/nvim/after/ftplugin/markdown.vim:

function! MarkdownFormat()
  silent !autocorrect --fix '%:p' "3
  let view = winsaveview()        "4 
  silent edit                     "5
  call winrestview(view)
  redraw!                         "6
endfunction

augroup markdownFormat            "1
  autocmd!                        "2
  autocmd BufWritePost * if &filetype ==# 'markdown' | call MarkdownFormat() | endif "7
augroup END

具体做一些解释:

  1. augroup 相当于设置命名空间保证这个 autocmd 不影响其他的 autocmd
  2. autocmd! 是清理当前 augroup 下的所有 autocmd 没有这命令会发现不知不觉每次保存的时候 autocorrect 会执行多次,具体什么原因我尚不清楚,毕这也是我第一次折腾这些命令,后续如果有更多了解会做更多记录
  3. slient !autocorrent --fix '%:p'
    1. 执行外部的命令要加 !
    2. 添加 slient 是为了不要展示其执行的结果,我们只关心它执行了,不想看到它的返回内容
    3. '%:p' 就是当前文件的绝对路径
  4. winsaveview 是保当前视图的一些信息,并且在重新加载文件后恢复,为的是不要让正在编辑的文件的视图位置、光标位置发生跳动提升体验
  5. slient edit 是重新加载文件
  6. redraw! 是刷当前视图
  7. if &filetype ==# 'markdown' 是判断当前文件格式是否为 markdown 只有是 markdown 的时候才这行这个 autocmd

效果


处理 k8s 证书过期

2022 January-18

简单记录

k8s 为了鼓励大家更新,其 kubeadm 默认的证书有效期为 1 年,任何 k8s 版本的更新都会触发证书的更新。如果证书过期了可以按照如下方式处理:

  1. 如果发现自己本地 kubectl 无法访问集群并报错就很有可能是证书过期了,登陆任意一台 master 执行命令 kubeadm certs check-expiration 可以查看证书的有效期,如果报错没有命令 certs 那么可以尝试命令 kubeadm alpha certs ...
  2. 过期后可以用命令 kubeadm certs renew all 更新所有证书
  3. 更新后需要将 /etc/kubernetes/manifests/ 挪走,比如重命名为 manifests.1 20 秒以上,等待 static pod 全部都关闭了,然后重命名回来,这个步骤就是强迫所有的 static pod 重启,官网文档 就是这么建议操作的
  4. 如果是 HA 高可用模式,那么需要将每一台 master 都这么操作一下
  5. 将新的 /etc/kubernetes/admin.conf 拷贝到自己的电脑,并将其其中的 api-server 的访问地址修改成从自己电脑可以访问的地址即可,然后具体的管理可以参考 维护一大堆 kubeconfig 的一些实践

更好的办法

这样做现在并不是最好的方法,目前可以改进的方式有如下几个:

  1. 按照官方推荐,更频繁的升级 k8s 避免自己的集群版本掉队到无法维护,这应该是最好的方法,当然听起来成本也高一些
  2. 简单粗暴,修改对应版本的 kubeadm 源码,将 renew 时间改成什么 100 年之类的,然后用这个编译的版本去 renew 就是 100 年有效期了
  3. https://github.com/fanux/sealos 可能是不错的方案,具体还没看

资料

  1. Certificate Management with kubeadm
  2. kubeadm 集群修改证书时间到 99 年